Merge changes from upstream.

Replaced convert_bazel with convert_for_cobalt.

convert_for_cobalt has two main differences with convert_bazel:
1. convert_for_cobalt uses the fact that BUILD.blaze files are written
in StarLark which is a subset of Python. Instead of directly parsing the
files, we create a python environment which gathers the necessary
information and execute the BUILD.blaze files in that environment.

2. convert_for_cobalt does not attempt to convert every single target in
the tink repository. Instead, it takes a list of starting targets and
attempts to convert those targets and their transitive dependencies.
This makes updating Tink easier since we try to convert much fewer
targets.

In addition, convert_for_cobalt has the ability to exclude certain
targets that are unused and problematic, even if they are in the
transitive closure of the targets we do want. This is handled only when
it comes to build files. If a build target is actually needed by the
build, you're on your own.

Finally, convert_for_cobalt can symbolically move targets in order to
cope with CMake's idiosyncracies.

Note: convert_for_cobalt does not currentl support BUILD.gn.
I will add BUILD.gn support later.

Commands:
git clone https://fuchsia.googlesource.com/third_party/tink
cd tink
git checkout origin/master -b ${USER}-merge
git merge FETCH_HEAD
./tools/convert_for_cobalt

Manual Change:
It looks like Tink has adopted a dependency on a version of boringssl we
don't have yet. In order to cope with this, I had to create a dummy
function called EVP_aead_xchacha20_poly1305 which can be found in
cc/subtle/xchacha20_poly1305_boringssl.cc
It should crash the program if used. We can upgrade BoringSSL
separately.

Change-Id: I1204520b74e87b73ecaa6533245ffe82d34e3474
diff --git a/BUILD.bazel b/BUILD.bazel
index cc05a40..3e85084 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -9,13 +9,12 @@
 
 exports_files(["LICENSE"])
 
-# All go packages use github.com/google/tink prefix
-load("@io_bazel_rules_go//go:def.bzl", "gazelle")
+load("@bazel_gazelle//:def.bzl", "gazelle")
 
-# bazel rule definition
-gazelle(
-    name = "gazelle",
-    command = "update",
-    prefix = "github.com/google/tink",
+# gazelle:prefix github.com/google/tink
+gazelle(name = "gazelle")
+
+filegroup(
+    name = "tink_version",
+    srcs = ["tink_version.bzl"],
 )
-
diff --git a/README.md b/README.md
index 683b748..22db7ef 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,17 @@
 # Tink
+*A multi-language, cross-platform library that provides cryptographic APIs that are secure, easy to use correctly, and hard(er) to misuse.*
 
-**`Ubuntu`**                                                                              | **`macOS`**
------------------------------------------------------------------------------------------ | -----------
-![Kokoro Ubuntu](https://storage.googleapis.com/tink-kokoro-build-badges/tink-ubuntu.png) | ![Kokoro macOS](https://storage.googleapis.com/tink-kokoro-build-badges/tink-macos.png)
+**`Ubuntu`**                                                                                   | **`macOS`**
+---------------------------------------------------------------------------------------------- | -----------
+[![Kokoro Ubuntu](https://storage.googleapis.com/tink-kokoro-build-badges/tink-ubuntu.png)](#) | [![Kokoro macOS](https://storage.googleapis.com/tink-kokoro-build-badges/tink-macos.png)](#)
+
+## Index
+1. [Introduction](#introduction)
+2. [Getting Started](#getting-started)
+3. [Current Status](#current-status)
+4. [Learn More](#learn-more)
+5. [Contact and Mailing List](#contact-and-mailing-list)
+6. [Maintainers](#maintainers)
 
 ## Introduction
 
@@ -21,6 +30,12 @@
 already being used to secure data of many products such as AdMob, Google Pay,
 Google Assistant, Firebase, the Android Search App, etc.
 
+To get a quick overview of Tink design please take a look at
+[slides](docs/Tink-a_cryptographic_library--RealWorldCrypto2019.pdf) from [a
+talk about Tink](https://www.youtube.com/watch?v=pqev9r3rUJs&t=9665) presented
+at [Real World Crypto 2019](https://rwc.iacr.org/2019/).
+
+
 ## Getting started
 
 **TIP** The easiest way to get started with Tink is to install
@@ -65,7 +80,6 @@
 ```java
     import com.google.crypto.tink.Aead;
     import com.google.crypto.tink.KeysetHandle;
-    import com.google.crypto.tink.aead.AeadFactory;
     import com.google.crypto.tink.aead.AeadKeyTemplates;
 
     // 1. Generate the key material.
@@ -73,19 +87,19 @@
         AeadKeyTemplates.AES128_GCM);
 
     // 2. Get the primitive.
-    Aead aead = AeadFactory.getPrimitive(keysetHandle);
+    Aead aead = keysetHandle.getPrimitive(Aead.class);
 
     // 3. Use the primitive.
-    byte[] ciphertext = aead.encrypt(plaintext, aad);
+    byte[] ciphertext = aead.encrypt(plaintext, associatedData);
 ```
 
 ## Current Status
 
-*   [Java and Android](docs/JAVA-HOWTO.md),
-    [C++](docs/CPP-HOWTO.md) and [Obj-C](docs/OBJC-HOWTO.md)
-    are field tested and ready for production. The latest version is
-    [1.2.0](https://github.com/google/tink/releases/tag/v1.2.0),
-    released on 2018-08-09.
+*   [Java and Android](docs/JAVA-HOWTO.md), [C++](docs/CPP-HOWTO.md) and
+    [Obj-C](docs/OBJC-HOWTO.md) are field tested and ready for production. The
+    latest version is
+    [1.2.2](https://github.com/google/tink/releases/tag/v1.2.2), released on
+    2019-01-24.
 
 *   Tink for Go and JavaScript are in active development.
 
@@ -119,6 +133,8 @@
 -   Haris Andrianakis
 -   Daniel Bleichenbacher
 -   Thai Duong
+-   Thomas Holenstein
 -   Charles Lee
 -   Quan Nguyen
 -   Bartosz Przydatek
+-   Veronika Slívová
diff --git a/WORKSPACE b/WORKSPACE
index 966e4da..9a56a8a 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -1,3 +1,20 @@
+workspace(name="tink")
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file")
+
+#-----------------------------------------------------------------------------
+# Google PKI certs for connecting to GCP KMS
+#-----------------------------------------------------------------------------
+
+http_file(
+    name = "google_root_pem",
+    executable = 0,
+    urls = [
+        "https://pki.goog/roots.pem"
+    ],
+    sha256 = "24be41e33ba82bd7ac872f0ba0a9e6da219929a7c79b81ff23c229b40dca7234",
+)
+
 #-----------------------------------------------------------------------------
 # wycheproof, for JSON test vectors
 #-----------------------------------------------------------------------------
@@ -13,24 +30,64 @@
 #-----------------------------------------------------------------------------
 http_archive(
     name = "com_google_absl",
-    strip_prefix = "abseil-cpp-6cf9c731027f4d8aebe3c60df8e64317e6870870",
-    url = "https://github.com/abseil/abseil-cpp/archive/6cf9c731027f4d8aebe3c60df8e64317e6870870.zip",
-    sha256 = "9094c76bb75bb02bafbdc7339d3a6b331bd23d76c357813ac963916f2f12ec11",
+    strip_prefix = "abseil-cpp-c476da141ca9cffc2137baf85872f0cae9ffa9ad",
+    url = "https://github.com/abseil/abseil-cpp/archive/c476da141ca9cffc2137baf85872f0cae9ffa9ad.zip",
+    sha256 = "84b4277a9b56f9a192952beca535313497826c6ff2e38b2cac7351a3ed2ae780",
 )
 
 http_archive(
     name = "boringssl",
-    strip_prefix = "boringssl-bdd6c7c9d1b94f7010fb471b91bb490ccacafb98",
-    url = "https://github.com/google/boringssl/archive/bdd6c7c9d1b94f7010fb471b91bb490ccacafb98.zip",
-    sha256 = "bd9b5f5c93e81831402d2620e28e49d2e8c4ac7c5f278cec7a5476b6236d4615",
+    strip_prefix = "boringssl-18637c5f37b87e57ebde0c40fe19c1560ec88813",
+    url = "https://github.com/google/boringssl/archive/18637c5f37b87e57ebde0c40fe19c1560ec88813.zip",
+    sha256 = "bd923e59fca0d2b50db09af441d11c844c5e882a54c68943b7fc39a8cb5dd211",
 )
 
 # GoogleTest/GoogleMock framework. Used by most C++ unit-tests.
 http_archive(
     name = "com_google_googletest",
-    strip_prefix = "googletest-61330388862cf011fa956f7f59082b9923e6be0e",
-    url = "https://github.com/google/googletest/archive/61330388862cf011fa956f7f59082b9923e6be0e.zip",
-    sha256 = "9431adb18d26a304d864c2b05f6e5a165e73108fc89a110f5b27d65d7e51680b",
+    strip_prefix = "googletest-eb9225ce361affe561592e0912320b9db84985d0",
+    url = "https://github.com/google/googletest/archive/eb9225ce361affe561592e0912320b9db84985d0.zip",
+    sha256 = "a7db7d1295ce46b93f3d1a90dbbc55a48409c00d19684fcd87823037add88118",
+)
+
+http_archive(
+    name = "rapidjson",
+    urls = [
+        "https://github.com/Tencent/rapidjson/archive/v1.1.0.tar.gz",
+    ],
+    sha256 = "bf7ced29704a1e696fbccf2a2b4ea068e7774fa37f6d7dd4039d0787f8bed98e",
+    strip_prefix = "rapidjson-1.1.0",
+    build_file = "//:third_party/rapidjson.BUILD.bazel",
+)
+
+http_archive(
+    name = "aws_cpp_sdk",
+    # Must be in sync with defines in third_party/aws_sdk_cpp.BUILD.bazel.
+    urls = [
+        "https://github.com/aws/aws-sdk-cpp/archive/1.4.80.tar.gz",
+    ],
+    strip_prefix = "aws-sdk-cpp-1.4.80/",
+    build_file = "//:third_party/aws_sdk_cpp.BUILD.bazel",
+)
+
+http_archive(
+    name = "curl",
+    urls = [
+        "https://mirror.bazel.build/curl.haxx.se/download/curl-7.49.1.tar.gz",
+    ],
+    sha256 = "ff3e80c1ca6a068428726cd7dd19037a47cc538ce58ef61c59587191039b2ca6",
+    strip_prefix = "curl-7.49.1",
+    build_file = "//:third_party/curl.BUILD.bazel",
+)
+
+http_archive(
+    name = "zlib_archive",
+    urls = [
+        "https://mirror.bazel.build/zlib.net/zlib-1.2.11.tar.gz",
+    ],
+    sha256 = "c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1",
+    strip_prefix = "zlib-1.2.11",
+    build_file = "//:third_party/zlib.BUILD.bazel",
 )
 
 #-----------------------------------------------------------------------------
@@ -42,9 +99,9 @@
 # This statement defines the @com_google_protobuf repo.
 http_archive(
     name = "com_google_protobuf",
-    strip_prefix = "protobuf-3.6.0",
-    urls = ["https://github.com/google/protobuf/archive/v3.6.0.zip"],
-    sha256 = "e514c2e613dc47c062ea8df480efeec368ffbef98af0437ac00cdaadcb0d80d2",
+    strip_prefix = "protobuf-3.6.1.2",
+    urls = ["https://github.com/google/protobuf/archive/v3.6.1.2.zip"],
+    sha256 = "d6618d117698132dadf0f830b762315807dc424ba36ab9183f1f436008a2fdb6",
 )
 
 # java_lite_proto_library rules implicitly depend on
@@ -52,19 +109,9 @@
 # runtime (base classes and common utilities).
 http_archive(
     name = "com_google_protobuf_javalite",
-    strip_prefix = "protobuf-javalite",
-    urls = ["https://github.com/google/protobuf/archive/javalite.zip"],
-    sha256 = "38458deb90db61c054b708e141544c32863ab14a8747710ba3ee290d9b6dab92",
-)
-
-new_http_archive(
-    name = "rapidjson",
-    urls = [
-        "https://github.com/Tencent/rapidjson/archive/v1.1.0.tar.gz",
-    ],
-    sha256 = "bf7ced29704a1e696fbccf2a2b4ea068e7774fa37f6d7dd4039d0787f8bed98e",
-    strip_prefix = "rapidjson-1.1.0",
-    build_file = "third_party/rapidjson.BUILD.bazel",
+    strip_prefix = "protobuf-384989534b2246d413dbcd750744faab2607b516",
+    urls = ["https://github.com/google/protobuf/archive/384989534b2246d413dbcd750744faab2607b516.zip"],
+    sha256 = "79d102c61e2a479a0b7e5fc167bcfaa4832a0c6aad4a75fa7da0480564931bcc",
 )
 
 #-----------------------------------------------------------------------------
@@ -538,7 +585,9 @@
 http_file(
     name = "xctestrunner",
     executable = 1,
-    url = "https://github.com/google/xctestrunner/releases/download/0.2.1/ios_test_runner.par",
+    urls = [
+        "https://github.com/google/xctestrunner/releases/download/0.2.1/ios_test_runner.par"
+    ],
     sha256 = "5bfbd45c5ac89305e8bf3296999d490611b88d4d828b2a39ef6037027411aa94",
 )
 
@@ -547,10 +596,48 @@
 #-----------------------------------------------------------------------------
 http_archive(
     name = "io_bazel_rules_go",
-    url = "https://github.com/bazelbuild/rules_go/releases/download/0.10.1/rules_go-0.10.1.tar.gz",
-    sha256 = "4b14d8dd31c6dbaf3ff871adcd03f28c3274e42abc855cb8fb4d01233c0154dc",
+    url = "https://github.com/bazelbuild/rules_go/releases/download/0.16.2/rules_go-0.16.2.tar.gz",
+    sha256 = "f87fa87475ea107b3c69196f39c82b7bbf58fe27c62a338684c20ca17d1d8613",
+)
+http_archive(
+    name = "bazel_gazelle",
+    urls = ["https://github.com/bazelbuild/bazel-gazelle/releases/download/0.14.0/bazel-gazelle-0.14.0.tar.gz"],
+    sha256 = "c0a5739d12c6d05b6c1ad56f2200cb0b57c5a70e03ebd2f7b87ce88cabf09c7b",
 )
 
 load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")
 go_rules_dependencies()
-go_register_toolchains()
+go_register_toolchains(nogo="@//go:tink_nogo")
+
+load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies")
+gazelle_dependencies()
+
+load("@bazel_gazelle//:deps.bzl", "go_repository")
+go_repository(
+    name = "org_golang_x_crypto",
+    commit = "0e37d006457bf46f9e6692014ba72ef82c33022c",
+    importpath = "golang.org/x/crypto",
+)
+go_repository(
+    name = "org_golang_x_sys",
+    commit = "d0be0721c37eeb5299f245a996a483160fc36940",
+    importpath = "golang.org/x/sys",
+)
+
+go_repository(
+    name = "org_golang_google_api",
+    commit = "3097bf831ede4a24e08a3316254e29ca726383e3",
+    importpath = "google.golang.org/api",
+)
+
+go_repository(
+    name = "org_golang_x_oauth2",
+    commit = "ef147856a6ddbb60760db74283d2424e98c87bff",
+    importpath = "golang.org/x/oauth2",
+)
+
+go_repository(
+    name = "com_google_cloud_go",
+    commit = "777200caa7fb8936aed0f12b1fd79af64cc83ec9",
+    importpath = "cloud.google.com/go",
+)
diff --git a/apps/paymentmethodtoken/README.md b/apps/paymentmethodtoken/README.md
index a719f4e..3f17e46 100644
--- a/apps/paymentmethodtoken/README.md
+++ b/apps/paymentmethodtoken/README.md
@@ -3,9 +3,9 @@
 ## Latest release
 
 The most recent release is
-[1.2.0](https://github.com/google/tink/releases/tag/v1.2.0), released
-2018-08-09. API docs can be found
-[here](https://google.github.com/tink/javadoc/apps-paymentmethodtoken/1.2.0).
+[1.2.2](https://github.com/google/tink/releases/tag/v1.2.2), released
+2019-01-24. API docs can be found
+[here](https://google.github.com/tink/javadoc/apps-paymentmethodtoken/1.2.2).
 
 The Maven group ID is `com.google.crypto.tink`, and the artifact ID is
 `apps-paymentmethodtoken`.
@@ -16,7 +16,7 @@
 <dependency>
   <groupId>com.google.crypto.tink</groupId>
   <artifactId>apps-paymentmethodtoken</artifactId>
-  <version>1.2.0</version>
+  <version>1.2.2</version>
 </dependency>
 ```
 
diff --git a/apps/webpush/README.md b/apps/webpush/README.md
index 9eb28c7..b4ef951 100644
--- a/apps/webpush/README.md
+++ b/apps/webpush/README.md
@@ -11,7 +11,7 @@
 <dependency>
   <groupId>com.google.crypto.tink</groupId>
   <artifactId>apps-webpush</artifactId>
-  <version>1.2.0</version>
+  <version>1.2.2</version>
 </dependency>
 ```
 
@@ -19,7 +19,7 @@
 
 ```
 dependencies {
-  compile 'com.google.crypto.tink:apps-webpush:1.2.0'
+  compile 'com.google.crypto.tink:apps-webpush:1.2.2'
 }
 ```
 
diff --git a/cc/BUILD.bazel b/cc/BUILD.bazel
index d7ac09a..16c5f51 100644
--- a/cc/BUILD.bazel
+++ b/cc/BUILD.bazel
@@ -2,6 +2,9 @@
 
 licenses(["notice"])  # Apache 2.0
 
+load("//:tink_version.bzl", "TINK_VERSION_LABEL")
+load("//tools:common.bzl", "template_rule")
+
 # public libraries
 
 PUBLIC_APIS = [
@@ -13,6 +16,10 @@
     "binary_keyset_writer.h",
     "catalogue.h",
     "config.h",
+    "deterministic_aead.h",
+    "deterministic_aead_config.h",
+    "deterministic_aead_factory.h",
+    "deterministic_aead_key_templates.h",
     "hybrid_config.h",
     "hybrid_decrypt.h",
     "hybrid_decrypt_factory.h",
@@ -39,12 +46,14 @@
     "signature_config.h",
     "signature_key_templates.h",
     "tink_config.h",
+    "version.h",
 ]
 
 PUBLIC_API_DEPS = [
     ":aead",
     ":binary_keyset_reader",
     ":binary_keyset_writer",
+    ":deterministic_aead",
     ":hybrid_decrypt",
     ":hybrid_encrypt",
     ":json_keyset_reader",
@@ -60,10 +69,15 @@
     ":mac",
     ":primitive_set",
     ":registry",
+    ":registry_impl",
+    ":version",
     "//cc/aead:aead_config",
     "//cc/aead:aead_factory",
     "//cc/aead:aead_key_templates",
     "//cc/config:tink_config",
+    "//cc/daead:deterministic_aead_config",
+    "//cc/daead:deterministic_aead_factory",
+    "//cc/daead:deterministic_aead_key_templates",
     "//cc/hybrid:hybrid_config",
     "//cc/hybrid:hybrid_decrypt_factory",
     "//cc/hybrid:hybrid_encrypt_factory",
@@ -80,8 +94,10 @@
     "//cc/util:status",
     "//cc/util:statusor",
     "//cc/util:validation",
+    "@com_google_absl//absl/base:core_headers",
     "@com_google_absl//absl/memory",
     "@com_google_absl//absl/strings",
+    "@com_google_absl//absl/synchronization",
 ]
 
 cc_library(
@@ -94,6 +110,28 @@
 )
 
 cc_library(
+    name = "input_stream",
+    hdrs = ["input_stream.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        "//cc/util:status",
+        "//cc/util:statusor",
+    ],
+)
+
+cc_library(
+    name = "output_stream",
+    hdrs = ["output_stream.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        "//cc/util:status",
+        "//cc/util:statusor",
+    ],
+)
+
+cc_library(
     name = "aead",
     hdrs = ["aead.h"],
     include_prefix = "tink",
@@ -105,6 +143,30 @@
 )
 
 cc_library(
+    name = "deterministic_aead",
+    hdrs = ["deterministic_aead.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        "//cc/util:statusor",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_library(
+    name = "streaming_aead",
+    hdrs = ["streaming_aead.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        ":input_stream",
+        ":output_stream",
+        "//cc/util:statusor",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_library(
     name = "hybrid_decrypt",
     hdrs = ["hybrid_decrypt.h"],
     include_prefix = "tink",
@@ -274,6 +336,7 @@
     deps = [
         ":aead",
         ":catalogue",
+        ":deterministic_aead",
         ":hybrid_decrypt",
         ":hybrid_encrypt",
         ":key_manager",
@@ -281,6 +344,13 @@
         ":public_key_sign",
         ":public_key_verify",
         ":registry",
+        "//cc/aead:aead_wrapper",
+        "//cc/daead:deterministic_aead_wrapper",
+        "//cc/hybrid:hybrid_decrypt_wrapper",
+        "//cc/hybrid:hybrid_encrypt_wrapper",
+        "//cc/mac:mac_wrapper",
+        "//cc/signature:public_key_sign_wrapper",
+        "//cc/signature:public_key_verify_wrapper",
         "//cc/util:errors",
         "//cc/util:status",
         "//cc/util:statusor",
@@ -314,44 +384,72 @@
         "//cc/util:statusor",
         "//proto:tink_cc_proto",
         "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/synchronization",
+    ],
+)
+
+cc_library(
+    name = "primitive_wrapper",
+    hdrs = ["primitive_wrapper.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        ":primitive_set",
+        "//cc/util:statusor",
     ],
 )
 
 cc_library(
     name = "registry",
-    srcs = ["core/registry.cc"],
     hdrs = ["registry.h"],
     include_prefix = "tink",
     strip_include_prefix = "/cc",
     deps = [
+        ":registry_impl",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_library(
+    name = "registry_impl",
+    srcs = ["core/registry_impl.cc"],
+    hdrs = ["core/registry_impl.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
         ":catalogue",
         ":key_manager",
-        ":keyset_handle_hdr",
         ":primitive_set",
+        ":primitive_wrapper",
         "//cc/util:errors",
         "//cc/util:protobuf_helper",
         "//cc/util:status",
         "//cc/util:statusor",
         "//cc/util:validation",
         "//proto:tink_cc_proto",
+        "@com_google_absl//absl/base:core_headers",
         "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/synchronization",
     ],
 )
 
+template_rule(
+    name = "version_h",
+    src = "version.h.templ",
+    out = "version.h",
+    substitutions = {
+        "TINK_VERSION_LABEL": "%s" % TINK_VERSION_LABEL,
+    },
+)
+
 cc_library(
-    name = "keyset_handle_hdr",
-    srcs = ["keyset_handle.h"],
-    hdrs = ["keyset_handle.h"],
+    name = "version",
+    srcs = ["core/version.cc"],
+    hdrs = [":version_h"],
     include_prefix = "tink",
     strip_include_prefix = "/cc",
-    deps = [
-        ":aead",
-        ":keyset_reader",
-        ":keyset_writer",
-        "//cc/util:errors",
-        "//proto:tink_cc_proto",
-        "@com_google_absl//absl/memory",
-    ],
 )
 
 cc_library(
@@ -362,9 +460,10 @@
     strip_include_prefix = "/cc",
     deps = [
         ":aead",
-        ":keyset_manager",
+        ":key_manager",
         ":keyset_reader",
         ":keyset_writer",
+        ":primitive_set",
         ":registry",
         "//cc/util:errors",
         "//proto:tink_cc_proto",
@@ -379,7 +478,7 @@
     include_prefix = "tink",
     strip_include_prefix = "/cc",
     deps = [
-        ":keyset_handle_hdr",
+        ":keyset_handle",
         "//cc/util:errors",
         "//cc/util:status",
         "//cc/util:statusor",
@@ -389,7 +488,7 @@
 
 cc_library(
     name = "key_manager",
-    srcs = ["key_manager.h"],
+    srcs = ["core/key_manager.cc"],
     hdrs = ["key_manager.h"],
     include_prefix = "tink",
     strip_include_prefix = "/cc",
@@ -399,6 +498,24 @@
         "//cc/util:status",
         "//cc/util:statusor",
         "//proto:tink_cc_proto",
+        "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_library(
+    name = "key_manager_base",
+    hdrs = ["core/key_manager_base.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        ":key_manager",
+        "//cc/util:constants",
+        "//cc/util:errors",
+        "//cc/util:statusor",
+        "//proto:tink_cc_proto",
+        "@com_google_absl//absl/base",
+        "@com_google_absl//absl/memory",
         "@com_google_absl//absl/strings",
     ],
 )
@@ -411,7 +528,7 @@
     strip_include_prefix = "/cc",
     visibility = ["//visibility:public"],
     deps = [
-        ":keyset_handle_hdr",
+        ":keyset_handle",
         ":keyset_reader",
         ":registry",
         "//cc/util:enums",
@@ -420,7 +537,9 @@
         "//cc/util:status",
         "//cc/util:statusor",
         "//proto:tink_cc_proto",
+        "@com_google_absl//absl/base:core_headers",
         "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/synchronization",
     ],
 )
 
@@ -461,29 +580,29 @@
 # To get dead code elimination run Bazel with option --nolegacy_whole_archive.
 cc_binary(
     name = "libtink.so",
+    linkopts = select({
+        ":linux_x86_64": [
+            "-Wl,-version-script",  # The next line must be exported_symbols.lds file
+            "$(location :version_script.lds)",
+            "-Wl,--gc-sections",
+            "-Wl,--icf=all",
+            "-Wl,--strip-all",
+        ],
+        ":mac_x86_64": [
+            "-Wl,-install_name,@rpath/libtink.so",
+            "-Wl,-exported_symbols_list",  # The next line must be exported_symbols.lds file
+            "$(location :exported_symbols.lds)",
+            "-Wl,-x",
+            "-Wl,-dead_strip",
+        ],
+    }),
     linkshared = 1,
     linkstatic = 1,
-    linkopts = select({
-      ":linux_x86_64": [
-        "-Wl,-version-script",  # The next line must be exported_symbols.lds file
-        "$(location :version_script.lds)",
-        "-Wl,--gc-sections",
-        "-Wl,--icf=all",
-        "-Wl,--strip-all",
-      ],
-      ":mac_x86_64": [
-        "-Wl,-install_name,@rpath/libtink.so",
-        "-Wl,-exported_symbols_list", # The next line must be exported_symbols.lds file
-        "$(location :exported_symbols.lds)",
-        "-Wl,-x",
-        "-Wl,-dead_strip",
-      ],
-    }),
     visibility = ["//visibility:public"],
     deps = PUBLIC_API_DEPS + [
-      ":cleartext_keyset_handle",
-      ":exported_symbols.lds",
-      ":version_script.lds"
+        ":cleartext_keyset_handle",
+        ":exported_symbols.lds",
+        ":version_script.lds",
     ],
 )
 
@@ -524,9 +643,12 @@
     ],
     # The command below collects headers of Tink dependencies in two steps:
     #  * First a tar-archive with all Abseil .h-files is created.
+    #  * Then, absl .inc files are added to the tar-archive.
     #  * Then .h-files of Protobuf library are added to the tar-archive.
     cmd = "tar -cv -f $@ -C external/com_google_absl --dereference " +
           "    `cd external/com_google_absl; find absl/ -name \"*.h\"`; " +
+          "tar -rv -f $@ -C external/com_google_absl --dereference " +
+          "    `cd external/com_google_absl; find absl/ -name \"*.inc\"`; " +
           "tar -rv -f $@ -C external/com_google_protobuf/src --dereference " +
           "    `cd external/com_google_protobuf/src/; find google/ -name \"*.h\"`",
     local = 1,  # To avoid sandboxing; otherwise cannot access srcs.
@@ -577,8 +699,10 @@
         ":aead",
         ":catalogue",
         ":crypto_format",
+        ":keyset_manager",
         ":registry",
         "//cc/aead:aead_catalogue",
+        "//cc/aead:aead_wrapper",
         "//cc/aead:aes_gcm_key_manager",
         "//cc/hybrid:ecies_aead_hkdf_private_key_manager",
         "//cc/hybrid:ecies_aead_hkdf_public_key_manager",
@@ -597,6 +721,17 @@
 )
 
 cc_test(
+    name = "version_test",
+    size = "small",
+    srcs = ["core/version_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    deps = [
+        ":version",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
     name = "binary_keyset_reader_test",
     size = "small",
     srcs = ["core/binary_keyset_reader_test.cc"],
@@ -697,12 +832,14 @@
         ":keyset_handle",
         "//cc",
         "//cc/aead:aead_key_templates",
+        "//cc/aead:aead_wrapper",
         "//cc/aead:aes_gcm_key_manager",
         "//cc/config:tink_config",
         "//cc/signature:ecdsa_sign_key_manager",
         "//cc/signature:signature_key_templates",
         "//cc/util:keyset_util",
         "//cc/util:protobuf_helper",
+        "//cc/util:test_matchers",
         "//cc/util:test_util",
         "//proto:tink_cc_proto",
         "@com_google_googletest//:gtest_main",
@@ -710,6 +847,20 @@
 )
 
 cc_test(
+    name = "key_manager_test",
+    size = "small",
+    srcs = ["core/key_manager_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    deps = [
+        ":key_manager",
+        "//cc/util:status",
+        "//cc/util:test_matchers",
+        "//proto:empty_cc_proto",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
     name = "keyset_manager_test",
     size = "small",
     srcs = ["core/keyset_manager_test.cc"],
diff --git a/cc/CMakeLists.txt b/cc/CMakeLists.txt
index 96a3ab5..5e5082b 100644
--- a/cc/CMakeLists.txt
+++ b/cc/CMakeLists.txt
@@ -1,845 +1,295 @@
-# Copyright 2018 The Fuchsia Authors. All rights reserved.
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-add_subdirectory(util)
 add_subdirectory(aead)
-add_subdirectory(mac)
+add_subdirectory(core)
+add_subdirectory(daead)
 add_subdirectory(hybrid)
-add_subdirectory(config)
-add_subdirectory(subtle)
+add_subdirectory(mac)
 add_subdirectory(signature)
+add_subdirectory(subtle)
+add_subdirectory(util)
 
-# Library: 'tink_cc'
-add_library(
-  tink_cc
-  aead.h
-  aead_config.h
-  aead_factory.h
-  aead_key_templates.h
-  binary_keyset_reader.h
-  binary_keyset_writer.h
-  catalogue.h
-  config.h
-  hybrid_config.h
-  hybrid_decrypt.h
-  hybrid_decrypt_factory.h
-  hybrid_encrypt.h
-  hybrid_encrypt_factory.h
-  hybrid_key_templates.h
-  json_keyset_reader.h
-  json_keyset_writer.h
-  key_manager.h
-  keyset_handle.h
-  keyset_manager.h
-  keyset_reader.h
-  keyset_writer.h
-  kms_client.h
-  mac.h
-  mac_config.h
-  mac_factory.h
-  mac_key_templates.h
-  public_key_sign.h
-  public_key_sign_factory.h
-  public_key_verify.h
-  public_key_verify_factory.h
-  registry.h
-  signature_config.h
-  signature_key_templates.h
-  tink_config.h
-)
-set_target_properties(tink_cc PROPERTIES LINKER_LANGUAGE CXX)
-tink_export_hdrs(
-  aead.h
-  aead_config.h
-  aead_factory.h
-  aead_key_templates.h
-  binary_keyset_reader.h
-  binary_keyset_writer.h
-  catalogue.h
-  config.h
-  hybrid_config.h
-  hybrid_decrypt.h
-  hybrid_decrypt_factory.h
-  hybrid_encrypt.h
-  hybrid_encrypt_factory.h
-  hybrid_key_templates.h
-  json_keyset_reader.h
-  json_keyset_writer.h
-  key_manager.h
-  keyset_handle.h
-  keyset_manager.h
-  keyset_reader.h
-  keyset_writer.h
-  kms_client.h
-  mac.h
-  mac_config.h
-  mac_factory.h
-  mac_key_templates.h
-  public_key_sign.h
-  public_key_sign_factory.h
-  public_key_verify.h
-  public_key_verify_factory.h
-  registry.h
-  signature_config.h
-  signature_key_templates.h
-  tink_config.h
-)
-add_dependencies(
-  tink_cc
-  tink_aead
-  tink_binary_keyset_reader
-  tink_binary_keyset_writer
-  tink_hybrid_decrypt
-  tink_hybrid_encrypt
-  tink_json_keyset_reader
-  tink_json_keyset_writer
-  tink_key_manager
-  tink_keyset_handle
-  tink_keyset_manager
-  tink_public_key_sign
-  tink_public_key_verify
-  tink_keyset_reader
-  tink_keyset_writer
-  tink_kms_client
-  tink_mac
-  tink_primitive_set
-  tink_registry
-  tink_aead_aead_config
-  tink_aead_aead_factory
-  tink_aead_aead_key_templates
-  tink_config_tink_config
-  tink_hybrid_hybrid_config
-  tink_hybrid_hybrid_decrypt_factory
-  tink_hybrid_hybrid_encrypt_factory
-  tink_hybrid_hybrid_key_templates
-  tink_mac_mac_config
-  tink_mac_mac_factory
-  tink_mac_mac_key_templates
-  tink_signature_public_key_sign_factory
-  tink_signature_public_key_verify_factory
-  tink_signature_signature_config
-  tink_signature_signature_key_templates
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_util_validation
-)
-target_link_libraries(
-  tink_cc
-  tink_aead
-  tink_binary_keyset_reader
-  tink_binary_keyset_writer
-  tink_hybrid_decrypt
-  tink_hybrid_encrypt
-  tink_json_keyset_reader
-  tink_json_keyset_writer
-  tink_key_manager
-  tink_keyset_handle
-  tink_keyset_manager
-  tink_public_key_sign
-  tink_public_key_verify
-  tink_keyset_reader
-  tink_keyset_writer
-  tink_kms_client
-  tink_mac
-  tink_primitive_set
-  tink_registry
-  tink_aead_aead_config
-  tink_aead_aead_factory
-  tink_aead_aead_key_templates
-  tink_config_tink_config
-  tink_hybrid_hybrid_config
-  tink_hybrid_hybrid_decrypt_factory
-  tink_hybrid_hybrid_encrypt_factory
-  tink_hybrid_hybrid_key_templates
-  tink_mac_mac_config
-  tink_mac_mac_factory
-  tink_mac_mac_key_templates
-  tink_signature_public_key_sign_factory
-  tink_signature_public_key_verify_factory
-  tink_signature_signature_config
-  tink_signature_signature_key_templates
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_util_validation
-  absl::memory
-  absl::strings
-)
-
-# Library: 'tink_aead'
-add_library(tink_aead aead.h)
-set_target_properties(tink_aead PROPERTIES LINKER_LANGUAGE CXX)
+# CC Library : aead
+add_library(tink_cc_aead aead.h)
+set_target_properties(tink_cc_aead PROPERTIES LINKER_LANGUAGE CXX)
 tink_export_hdrs(aead.h)
-add_dependencies(tink_aead tink_util_statusor)
-target_link_libraries(tink_aead tink_util_statusor absl::strings)
+add_dependencies(tink_cc_aead tink_cc_util_statusor)
+target_link_libraries(tink_cc_aead tink_cc_util_statusor absl::strings)
 
-# Library: 'tink_hybrid_decrypt'
-add_library(tink_hybrid_decrypt hybrid_decrypt.h)
-set_target_properties(tink_hybrid_decrypt PROPERTIES LINKER_LANGUAGE CXX)
+# CC Library : deterministic_aead
+add_library(tink_cc_deterministic_aead deterministic_aead.h)
+set_target_properties(tink_cc_deterministic_aead PROPERTIES LINKER_LANGUAGE CXX)
+tink_export_hdrs(deterministic_aead.h)
+add_dependencies(tink_cc_deterministic_aead tink_cc_util_statusor)
+target_link_libraries(
+  tink_cc_deterministic_aead
+  tink_cc_util_statusor
+  absl::strings
+)
+
+# CC Library : hybrid_decrypt
+add_library(tink_cc_hybrid_decrypt hybrid_decrypt.h)
+set_target_properties(tink_cc_hybrid_decrypt PROPERTIES LINKER_LANGUAGE CXX)
 tink_export_hdrs(hybrid_decrypt.h)
-add_dependencies(tink_hybrid_decrypt tink_util_statusor)
-target_link_libraries(tink_hybrid_decrypt tink_util_statusor absl::strings)
+add_dependencies(tink_cc_hybrid_decrypt tink_cc_util_statusor)
+target_link_libraries(
+  tink_cc_hybrid_decrypt
+  tink_cc_util_statusor
+  absl::strings
+)
 
-# Library: 'tink_hybrid_encrypt'
-add_library(tink_hybrid_encrypt hybrid_encrypt.h)
-set_target_properties(tink_hybrid_encrypt PROPERTIES LINKER_LANGUAGE CXX)
+# CC Library : hybrid_encrypt
+add_library(tink_cc_hybrid_encrypt hybrid_encrypt.h)
+set_target_properties(tink_cc_hybrid_encrypt PROPERTIES LINKER_LANGUAGE CXX)
 tink_export_hdrs(hybrid_encrypt.h)
-add_dependencies(tink_hybrid_encrypt tink_util_statusor)
-target_link_libraries(tink_hybrid_encrypt tink_util_statusor absl::strings)
+add_dependencies(tink_cc_hybrid_encrypt tink_cc_util_statusor)
+target_link_libraries(
+  tink_cc_hybrid_encrypt
+  tink_cc_util_statusor
+  absl::strings
+)
 
-# Library: 'tink_mac'
-add_library(tink_mac mac.h)
-set_target_properties(tink_mac PROPERTIES LINKER_LANGUAGE CXX)
+# CC Library : mac
+add_library(tink_cc_mac mac.h)
+set_target_properties(tink_cc_mac PROPERTIES LINKER_LANGUAGE CXX)
 tink_export_hdrs(mac.h)
-add_dependencies(tink_mac tink_util_status tink_util_statusor)
+add_dependencies(tink_cc_mac tink_cc_util_status tink_cc_util_statusor)
 target_link_libraries(
-  tink_mac
-  tink_util_status
-  tink_util_statusor
+  tink_cc_mac
+  tink_cc_util_status
+  tink_cc_util_statusor
   absl::strings
 )
 
-# Library: 'tink_public_key_sign'
-add_library(tink_public_key_sign public_key_sign.h)
-set_target_properties(tink_public_key_sign PROPERTIES LINKER_LANGUAGE CXX)
+# CC Library : public_key_sign
+add_library(tink_cc_public_key_sign public_key_sign.h)
+set_target_properties(tink_cc_public_key_sign PROPERTIES LINKER_LANGUAGE CXX)
 tink_export_hdrs(public_key_sign.h)
-add_dependencies(tink_public_key_sign tink_util_statusor)
-target_link_libraries(tink_public_key_sign tink_util_statusor absl::strings)
+add_dependencies(tink_cc_public_key_sign tink_cc_util_statusor)
+target_link_libraries(
+  tink_cc_public_key_sign
+  tink_cc_util_statusor
+  absl::strings
+)
 
-# Library: 'tink_public_key_verify'
-add_library(tink_public_key_verify public_key_verify.h)
-set_target_properties(tink_public_key_verify PROPERTIES LINKER_LANGUAGE CXX)
+# CC Library : public_key_verify
+add_library(tink_cc_public_key_verify public_key_verify.h)
+set_target_properties(tink_cc_public_key_verify PROPERTIES LINKER_LANGUAGE CXX)
 tink_export_hdrs(public_key_verify.h)
-add_dependencies(tink_public_key_verify tink_util_status)
-target_link_libraries(tink_public_key_verify tink_util_status absl::strings)
+add_dependencies(tink_cc_public_key_verify tink_cc_util_status)
+target_link_libraries(
+  tink_cc_public_key_verify
+  tink_cc_util_status
+  absl::strings
+)
 
-# Library: 'tink_keyset_reader'
-add_library(tink_keyset_reader keyset_reader.h)
-set_target_properties(tink_keyset_reader PROPERTIES LINKER_LANGUAGE CXX)
+# CC Library : keyset_reader
+add_library(tink_cc_keyset_reader keyset_reader.h)
+set_target_properties(tink_cc_keyset_reader PROPERTIES LINKER_LANGUAGE CXX)
 tink_export_hdrs(keyset_reader.h)
-add_dependencies(tink_keyset_reader tink_util_statusor tink_proto_tink_lib)
-target_link_libraries(tink_keyset_reader tink_util_statusor tink_proto_tink_lib)
+add_dependencies(
+  tink_cc_keyset_reader
+  tink_cc_util_statusor
+  tink_proto_tink_lib
+)
+target_link_libraries(
+  tink_cc_keyset_reader
+  tink_cc_util_statusor
+  tink_proto_tink_lib
+)
 
-# Library: 'tink_keyset_writer'
-add_library(tink_keyset_writer keyset_writer.h)
-set_target_properties(tink_keyset_writer PROPERTIES LINKER_LANGUAGE CXX)
+# CC Library : keyset_writer
+add_library(tink_cc_keyset_writer keyset_writer.h)
+set_target_properties(tink_cc_keyset_writer PROPERTIES LINKER_LANGUAGE CXX)
 tink_export_hdrs(keyset_writer.h)
-add_dependencies(tink_keyset_writer tink_util_status tink_proto_tink_lib)
-target_link_libraries(tink_keyset_writer tink_util_status tink_proto_tink_lib)
-
-# Library: 'tink_binary_keyset_reader'
-add_library(tink_binary_keyset_reader core/binary_keyset_reader.cc binary_keyset_reader.h)
-tink_export_hdrs(binary_keyset_reader.h)
-add_dependencies(
-  tink_binary_keyset_reader
-  tink_keyset_reader
-  tink_util_errors
-  tink_util_statusor
-  tink_proto_tink_lib
-)
+add_dependencies(tink_cc_keyset_writer tink_cc_util_status tink_proto_tink_lib)
 target_link_libraries(
-  tink_binary_keyset_reader
-  tink_keyset_reader
-  tink_util_errors
-  tink_util_statusor
+  tink_cc_keyset_writer
+  tink_cc_util_status
   tink_proto_tink_lib
-  absl::memory
-  absl::strings
-  protobuf_lite
 )
 
-# Library: 'tink_binary_keyset_writer'
-add_library(tink_binary_keyset_writer core/binary_keyset_writer.cc binary_keyset_writer.h)
-tink_export_hdrs(binary_keyset_writer.h)
-add_dependencies(
-  tink_binary_keyset_writer
-  tink_keyset_writer
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_proto_tink_lib
-)
-target_link_libraries(
-  tink_binary_keyset_writer
-  tink_keyset_writer
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_proto_tink_lib
-  absl::strings
-)
-
-# Library: 'tink_json_keyset_reader'
-add_library(tink_json_keyset_reader core/json_keyset_reader.cc json_keyset_reader.h)
-tink_export_hdrs(json_keyset_reader.h)
-add_dependencies(
-  tink_json_keyset_reader
-  tink_keyset_reader
-  tink_util_enums
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_statusor
-  tink_proto_tink_lib
-)
-target_link_libraries(
-  tink_json_keyset_reader
-  tink_keyset_reader
-  tink_util_enums
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_statusor
-  tink_proto_tink_lib
-  absl::memory
-  absl::strings
-  rapidjson
-)
-
-# Library: 'tink_json_keyset_writer'
-add_library(tink_json_keyset_writer core/json_keyset_writer.cc json_keyset_writer.h)
-tink_export_hdrs(json_keyset_writer.h)
-add_dependencies(
-  tink_json_keyset_writer
-  tink_keyset_writer
-  tink_util_enums
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_proto_tink_lib
-)
-target_link_libraries(
-  tink_json_keyset_writer
-  tink_keyset_writer
-  tink_util_enums
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_proto_tink_lib
-  absl::strings
-  rapidjson
-)
-
-# Library: 'tink_catalogue'
-add_library(tink_catalogue catalogue.h)
-set_target_properties(tink_catalogue PROPERTIES LINKER_LANGUAGE CXX)
+# CC Library : catalogue
+add_library(tink_cc_catalogue catalogue.h)
+set_target_properties(tink_cc_catalogue PROPERTIES LINKER_LANGUAGE CXX)
 tink_export_hdrs(catalogue.h)
-add_dependencies(tink_catalogue tink_key_manager tink_util_statusor)
-target_link_libraries(tink_catalogue tink_key_manager tink_util_statusor)
+add_dependencies(tink_cc_catalogue tink_cc_key_manager tink_cc_util_statusor)
+target_link_libraries(
+  tink_cc_catalogue
+  tink_cc_key_manager
+  tink_cc_util_statusor
+)
 
-# Library: 'tink_config'
-add_library(tink_config core/config.cc config.h)
+# CC Library : config
+add_library(tink_cc_config config.h core/config.cc)
 tink_export_hdrs(config.h)
 add_dependencies(
-  tink_config
-  tink_aead
-  tink_catalogue
-  tink_hybrid_decrypt
-  tink_hybrid_encrypt
-  tink_key_manager
-  tink_mac
-  tink_public_key_sign
-  tink_public_key_verify
-  tink_registry
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
+  tink_cc_config
+  tink_cc_aead
+  tink_cc_catalogue
+  tink_cc_deterministic_aead
+  tink_cc_hybrid_decrypt
+  tink_cc_hybrid_encrypt
+  tink_cc_key_manager
+  tink_cc_mac
+  tink_cc_public_key_sign
+  tink_cc_public_key_verify
+  tink_cc_registry
+  tink_cc_aead_aead_wrapper
+  tink_cc_daead_deterministic_aead_wrapper
+  tink_cc_hybrid_hybrid_decrypt_wrapper
+  tink_cc_hybrid_hybrid_encrypt_wrapper
+  tink_cc_mac_mac_wrapper
+  tink_cc_signature_public_key_sign_wrapper
+  tink_cc_signature_public_key_verify_wrapper
+  tink_cc_util_errors
+  tink_cc_util_status
+  tink_cc_util_statusor
   tink_proto_config_lib
 )
 target_link_libraries(
-  tink_config
-  tink_aead
-  tink_catalogue
-  tink_hybrid_decrypt
-  tink_hybrid_encrypt
-  tink_key_manager
-  tink_mac
-  tink_public_key_sign
-  tink_public_key_verify
-  tink_registry
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
+  tink_cc_config
+  tink_cc_aead
+  tink_cc_catalogue
+  tink_cc_deterministic_aead
+  tink_cc_hybrid_decrypt
+  tink_cc_hybrid_encrypt
+  tink_cc_key_manager
+  tink_cc_mac
+  tink_cc_public_key_sign
+  tink_cc_public_key_verify
+  tink_cc_registry
+  tink_cc_aead_aead_wrapper
+  tink_cc_daead_deterministic_aead_wrapper
+  tink_cc_hybrid_hybrid_decrypt_wrapper
+  tink_cc_hybrid_hybrid_encrypt_wrapper
+  tink_cc_mac_mac_wrapper
+  tink_cc_signature_public_key_sign_wrapper
+  tink_cc_signature_public_key_verify_wrapper
+  tink_cc_util_errors
+  tink_cc_util_status
+  tink_cc_util_statusor
   tink_proto_config_lib
   absl::strings
 )
 
-# Library: 'tink_crypto_format'
-add_library(tink_crypto_format core/crypto_format.cc crypto_format.h)
+# CC Library : crypto_format
+add_library(tink_cc_crypto_format crypto_format.h core/crypto_format.cc)
 tink_export_hdrs(crypto_format.h)
 add_dependencies(
-  tink_crypto_format
-  tink_util_errors
-  tink_util_statusor
+  tink_cc_crypto_format
+  tink_cc_util_errors
+  tink_cc_util_statusor
   tink_proto_tink_lib
 )
 target_link_libraries(
-  tink_crypto_format
-  tink_util_errors
-  tink_util_statusor
+  tink_cc_crypto_format
+  tink_cc_util_errors
+  tink_cc_util_statusor
   tink_proto_tink_lib
 )
 
-# Library: 'tink_primitive_set'
-add_library(tink_primitive_set primitive_set.h primitive_set.h)
+# CC Library : primitive_set
+add_library(tink_cc_primitive_set primitive_set.h primitive_set.h)
 tink_export_hdrs(primitive_set.h)
 add_dependencies(
-  tink_primitive_set
-  tink_crypto_format
-  tink_util_errors
-  tink_util_statusor
+  tink_cc_primitive_set
+  tink_cc_crypto_format
+  tink_cc_util_errors
+  tink_cc_util_statusor
   tink_proto_tink_lib
 )
 target_link_libraries(
-  tink_primitive_set
-  tink_crypto_format
-  tink_util_errors
-  tink_util_statusor
+  tink_cc_primitive_set
+  tink_cc_crypto_format
+  tink_cc_util_errors
+  tink_cc_util_statusor
   tink_proto_tink_lib
   absl::memory
+  absl::synchronization
 )
 
-# Library: 'tink_registry'
-add_library(tink_registry core/registry.cc registry.h)
+# CC Library : primitive_wrapper
+add_library(tink_cc_primitive_wrapper primitive_wrapper.h)
+set_target_properties(tink_cc_primitive_wrapper PROPERTIES LINKER_LANGUAGE CXX)
+tink_export_hdrs(primitive_wrapper.h)
+add_dependencies(
+  tink_cc_primitive_wrapper
+  tink_cc_primitive_set
+  tink_cc_util_statusor
+)
+target_link_libraries(
+  tink_cc_primitive_wrapper
+  tink_cc_primitive_set
+  tink_cc_util_statusor
+)
+
+# CC Library : registry
+add_library(tink_cc_registry registry.h)
+set_target_properties(tink_cc_registry PROPERTIES LINKER_LANGUAGE CXX)
 tink_export_hdrs(registry.h)
 add_dependencies(
-  tink_registry
-  tink_catalogue
-  tink_key_manager
-  tink_keyset_handle_hdr
-  tink_primitive_set
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_util_validation
-  tink_proto_tink_lib
+  tink_cc_registry
+  tink_cc_core_registry_impl
+  tink_cc_util_status
+  tink_cc_util_statusor
 )
 target_link_libraries(
-  tink_registry
-  tink_catalogue
-  tink_key_manager
-  tink_keyset_handle_hdr
-  tink_primitive_set
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_util_validation
-  tink_proto_tink_lib
+  tink_cc_registry
+  tink_cc_core_registry_impl
+  tink_cc_util_status
+  tink_cc_util_statusor
   absl::strings
 )
 
-# Library: 'tink_keyset_handle_hdr'
-add_library(tink_keyset_handle_hdr keyset_handle.h keyset_handle.h)
+# CC Library : keyset_handle
+add_library(tink_cc_keyset_handle keyset_handle.h core/keyset_handle.cc)
 tink_export_hdrs(keyset_handle.h)
 add_dependencies(
-  tink_keyset_handle_hdr
-  tink_aead
-  tink_keyset_reader
-  tink_keyset_writer
-  tink_util_errors
+  tink_cc_keyset_handle
+  tink_cc_aead
+  tink_cc_key_manager
+  tink_cc_keyset_reader
+  tink_cc_keyset_writer
+  tink_cc_primitive_set
+  tink_cc_registry
+  tink_cc_util_errors
   tink_proto_tink_lib
 )
 target_link_libraries(
-  tink_keyset_handle_hdr
-  tink_aead
-  tink_keyset_reader
-  tink_keyset_writer
-  tink_util_errors
+  tink_cc_keyset_handle
+  tink_cc_aead
+  tink_cc_key_manager
+  tink_cc_keyset_reader
+  tink_cc_keyset_writer
+  tink_cc_primitive_set
+  tink_cc_registry
+  tink_cc_util_errors
   tink_proto_tink_lib
   absl::memory
 )
 
-# Library: 'tink_keyset_handle'
-add_library(tink_keyset_handle core/keyset_handle.cc keyset_handle.h)
-tink_export_hdrs(keyset_handle.h)
-add_dependencies(
-  tink_keyset_handle
-  tink_aead
-  tink_keyset_manager
-  tink_keyset_reader
-  tink_keyset_writer
-  tink_registry
-  tink_util_errors
-  tink_proto_tink_lib
-)
-target_link_libraries(
-  tink_keyset_handle
-  tink_aead
-  tink_keyset_manager
-  tink_keyset_reader
-  tink_keyset_writer
-  tink_registry
-  tink_util_errors
-  tink_proto_tink_lib
-  absl::memory
-)
-
-# Library: 'tink_cleartext_keyset_handle'
-add_library(tink_cleartext_keyset_handle core/cleartext_keyset_handle.cc cleartext_keyset_handle.h)
-tink_export_hdrs(cleartext_keyset_handle.h)
-add_dependencies(
-  tink_cleartext_keyset_handle
-  tink_keyset_handle_hdr
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
-  tink_proto_tink_lib
-)
-target_link_libraries(
-  tink_cleartext_keyset_handle
-  tink_keyset_handle_hdr
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
-  tink_proto_tink_lib
-)
-
-# Library: 'tink_key_manager'
-add_library(tink_key_manager key_manager.h key_manager.h)
+# CC Library : key_manager
+add_library(tink_cc_key_manager key_manager.h core/key_manager.cc)
 tink_export_hdrs(key_manager.h)
 add_dependencies(
-  tink_key_manager
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
+  tink_cc_key_manager
+  tink_cc_util_errors
+  tink_cc_util_protobuf_helper
+  tink_cc_util_status
+  tink_cc_util_statusor
   tink_proto_tink_lib
 )
 target_link_libraries(
-  tink_key_manager
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_proto_tink_lib
-  absl::strings
-)
-
-# Library: 'tink_keyset_manager'
-add_library(tink_keyset_manager core/keyset_manager.cc keyset_manager.h)
-tink_export_hdrs(keyset_manager.h)
-add_dependencies(
-  tink_keyset_manager
-  tink_keyset_handle_hdr
-  tink_keyset_reader
-  tink_registry
-  tink_util_enums
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_proto_tink_lib
-)
-target_link_libraries(
-  tink_keyset_manager
-  tink_keyset_handle_hdr
-  tink_keyset_reader
-  tink_registry
-  tink_util_enums
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
+  tink_cc_key_manager
+  tink_cc_util_errors
+  tink_cc_util_protobuf_helper
+  tink_cc_util_status
+  tink_cc_util_statusor
   tink_proto_tink_lib
   absl::memory
-)
-
-# Library: 'tink_kms_client'
-add_library(tink_kms_client kms_client.h)
-set_target_properties(tink_kms_client PROPERTIES LINKER_LANGUAGE CXX)
-tink_export_hdrs(kms_client.h)
-add_dependencies(tink_kms_client tink_aead tink_util_statusor)
-target_link_libraries(
-  tink_kms_client
-  tink_aead
-  tink_util_statusor
   absl::strings
 )
 
-# Test Binary: 'tink_registry_test'
-add_executable(tink_registry_test core/registry_test.cc)
-add_dependencies(
-  tink_registry_test
-  tink_aead
-  tink_catalogue
-  tink_crypto_format
-  tink_registry
-  tink_aead_aead_catalogue
-  tink_aead_aes_gcm_key_manager
-  tink_hybrid_ecies_aead_hkdf_private_key_manager
-  tink_hybrid_ecies_aead_hkdf_public_key_manager
-  tink_util_keyset_util
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-  tink_proto_aes_ctr_hmac_aead_lib
-  tink_proto_aes_gcm_lib
-  tink_proto_common_lib
-  tink_proto_tink_lib
-)
-add_dependencies(tink_registry_test build_external_projects)
-target_link_libraries(
-  tink_registry_test
-  tink_aead
-  tink_catalogue
-  tink_crypto_format
-  tink_registry
-  tink_aead_aead_catalogue
-  tink_aead_aes_gcm_key_manager
-  tink_hybrid_ecies_aead_hkdf_private_key_manager
-  tink_hybrid_ecies_aead_hkdf_public_key_manager
-  tink_util_keyset_util
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-  tink_proto_aes_ctr_hmac_aead_lib
-  tink_proto_aes_gcm_lib
-  tink_proto_common_lib
-  tink_proto_tink_lib
-  absl::memory
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_binary_keyset_reader_test'
-add_executable(tink_binary_keyset_reader_test core/binary_keyset_reader_test.cc)
-add_dependencies(
-  tink_binary_keyset_reader_test
-  tink_binary_keyset_reader
-  tink_util_test_util
-  tink_proto_tink_lib
-)
-add_dependencies(tink_binary_keyset_reader_test build_external_projects)
-target_link_libraries(
-  tink_binary_keyset_reader_test
-  tink_binary_keyset_reader
-  tink_util_test_util
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_binary_keyset_writer_test'
-add_executable(tink_binary_keyset_writer_test core/binary_keyset_writer_test.cc)
-add_dependencies(
-  tink_binary_keyset_writer_test
-  tink_binary_keyset_writer
-  tink_util_test_util
-  tink_proto_tink_lib
-)
-add_dependencies(tink_binary_keyset_writer_test build_external_projects)
-target_link_libraries(
-  tink_binary_keyset_writer_test
-  tink_binary_keyset_writer
-  tink_util_test_util
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_json_keyset_reader_test'
-add_executable(tink_json_keyset_reader_test core/json_keyset_reader_test.cc)
-add_dependencies(
-  tink_json_keyset_reader_test
-  tink_json_keyset_reader
-  tink_util_protobuf_helper
-  tink_util_test_util
-  tink_proto_aes_eax_lib
-  tink_proto_aes_gcm_lib
-  tink_proto_tink_lib
-)
-add_dependencies(tink_json_keyset_reader_test build_external_projects)
-target_link_libraries(
-  tink_json_keyset_reader_test
-  tink_json_keyset_reader
-  tink_util_protobuf_helper
-  tink_util_test_util
-  tink_proto_aes_eax_lib
-  tink_proto_aes_gcm_lib
-  tink_proto_tink_lib
-  absl::strings
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_json_keyset_writer_test'
-add_executable(tink_json_keyset_writer_test core/json_keyset_writer_test.cc)
-add_dependencies(
-  tink_json_keyset_writer_test
-  tink_json_keyset_reader
-  tink_json_keyset_writer
-  tink_util_protobuf_helper
-  tink_util_test_util
-  tink_proto_aes_eax_lib
-  tink_proto_aes_gcm_lib
-  tink_proto_tink_lib
-)
-add_dependencies(tink_json_keyset_writer_test build_external_projects)
-target_link_libraries(
-  tink_json_keyset_writer_test
-  tink_json_keyset_reader
-  tink_json_keyset_writer
-  tink_util_protobuf_helper
-  tink_util_test_util
-  tink_proto_aes_eax_lib
-  tink_proto_aes_gcm_lib
-  tink_proto_tink_lib
-  absl::strings
-  gtest gtest_main
-  rapidjson
-)
-
-# Test Binary: 'tink_config_test'
-add_executable(tink_config_test core/config_test.cc)
-add_dependencies(
-  tink_config_test
-  tink_config
-  tink_mac
-  tink_proto_config_lib
-)
-add_dependencies(tink_config_test build_external_projects)
-target_link_libraries(
-  tink_config_test
-  tink_config
-  tink_mac
-  tink_proto_config_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_crypto_format_test'
-add_executable(tink_crypto_format_test core/crypto_format_test.cc)
-add_dependencies(tink_crypto_format_test tink_crypto_format tink_proto_tink_lib)
-add_dependencies(tink_crypto_format_test build_external_projects)
-target_link_libraries(
-  tink_crypto_format_test
-  tink_crypto_format
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_keyset_handle_test'
-add_executable(tink_keyset_handle_test core/keyset_handle_test.cc)
-add_dependencies(
-  tink_keyset_handle_test
-  tink_binary_keyset_reader
-  tink_cleartext_keyset_handle
-  tink_config
-  tink_json_keyset_reader
-  tink_json_keyset_writer
-  tink_keyset_handle
-  tink_cc
-  tink_aead_aead_key_templates
-  tink_aead_aes_gcm_key_manager
-  tink_config_tink_config
-  tink_signature_ecdsa_sign_key_manager
-  tink_signature_signature_key_templates
-  tink_util_keyset_util
-  tink_util_protobuf_helper
-  tink_util_test_util
-  tink_proto_tink_lib
-)
-add_dependencies(tink_keyset_handle_test build_external_projects)
-target_link_libraries(
-  tink_keyset_handle_test
-  tink_binary_keyset_reader
-  tink_cleartext_keyset_handle
-  tink_config
-  tink_json_keyset_reader
-  tink_json_keyset_writer
-  tink_keyset_handle
-  tink_cc
-  tink_aead_aead_key_templates
-  tink_aead_aes_gcm_key_manager
-  tink_config_tink_config
-  tink_signature_ecdsa_sign_key_manager
-  tink_signature_signature_key_templates
-  tink_util_keyset_util
-  tink_util_protobuf_helper
-  tink_util_test_util
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_keyset_manager_test'
-add_executable(tink_keyset_manager_test core/keyset_manager_test.cc)
-add_dependencies(
-  tink_keyset_manager_test
-  tink_config
-  tink_keyset_handle
-  tink_keyset_manager
-  tink_aead_aead_config
-  tink_aead_aes_gcm_key_manager
-  tink_util_keyset_util
-  tink_util_test_util
-  tink_proto_aes_gcm_lib
-  tink_proto_tink_lib
-)
-add_dependencies(tink_keyset_manager_test build_external_projects)
-target_link_libraries(
-  tink_keyset_manager_test
-  tink_config
-  tink_keyset_handle
-  tink_keyset_manager
-  tink_aead_aead_config
-  tink_aead_aes_gcm_key_manager
-  tink_util_keyset_util
-  tink_util_test_util
-  tink_proto_aes_gcm_lib
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_cleartext_keyset_handle_test'
-add_executable(tink_cleartext_keyset_handle_test core/cleartext_keyset_handle_test.cc)
-add_dependencies(
-  tink_cleartext_keyset_handle_test
-  tink_binary_keyset_reader
-  tink_cleartext_keyset_handle
-  tink_keyset_handle
-  tink_keyset_reader
-  tink_util_keyset_util
-  tink_util_test_util
-  tink_proto_tink_lib
-)
-add_dependencies(tink_cleartext_keyset_handle_test build_external_projects)
-target_link_libraries(
-  tink_cleartext_keyset_handle_test
-  tink_binary_keyset_reader
-  tink_cleartext_keyset_handle
-  tink_keyset_handle
-  tink_keyset_reader
-  tink_util_keyset_util
-  tink_util_test_util
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_primitive_set_test'
-add_executable(tink_primitive_set_test core/primitive_set_test.cc)
-add_dependencies(
-  tink_primitive_set_test
-  tink_crypto_format
-  tink_mac
-  tink_primitive_set
-  tink_util_protobuf_helper
-  tink_util_test_util
-  tink_proto_tink_lib
-)
-add_dependencies(tink_primitive_set_test build_external_projects)
-target_link_libraries(
-  tink_primitive_set_test
-  tink_crypto_format
-  tink_mac
-  tink_primitive_set
-  tink_util_protobuf_helper
-  tink_util_test_util
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
diff --git a/cc/aead/BUILD.bazel b/cc/aead/BUILD.bazel
index f43ef00..6d183fc 100644
--- a/cc/aead/BUILD.bazel
+++ b/cc/aead/BUILD.bazel
@@ -3,19 +3,20 @@
 licenses(["notice"])  # Apache 2.0
 
 cc_library(
-    name = "aead_set_wrapper",
-    srcs = ["aead_set_wrapper.cc"],
-    hdrs = ["aead_set_wrapper.h"],
+    name = "aead_wrapper",
+    srcs = ["aead_wrapper.cc"],
+    hdrs = ["aead_wrapper.h"],
     include_prefix = "tink",
     strip_include_prefix = "/cc",
-    visibility = ["//visibility:private"],
     deps = [
         "//cc:aead",
         "//cc:crypto_format",
         "//cc:primitive_set",
+        "//cc:primitive_wrapper",
+        "//cc:registry",
+        "//cc/subtle:subtle_util_boringssl",
         "//cc/util:status",
         "//cc/util:statusor",
-        "//cc/subtle:subtle_util_boringssl",
         "//proto:tink_cc_proto",
         "@com_google_absl//absl/strings",
     ],
@@ -31,6 +32,7 @@
         ":aes_ctr_hmac_aead_key_manager",
         ":aes_eax_key_manager",
         ":aes_gcm_key_manager",
+        ":xchacha20_poly1305_key_manager",
         "//cc:aead",
         "//cc:catalogue",
         "//cc:key_manager",
@@ -46,10 +48,12 @@
     strip_include_prefix = "/cc",
     deps = [
         ":aead_catalogue",
+        ":aead_wrapper",
         "//cc:config",
         "//cc/mac:mac_config",
         "//cc/util:status",
         "//proto:config_cc_proto",
+        "@com_google_absl//absl/memory",
     ],
 )
 
@@ -60,7 +64,7 @@
     include_prefix = "tink",
     strip_include_prefix = "/cc",
     deps = [
-        ":aead_set_wrapper",
+        ":aead_wrapper",
         "//cc:aead",
         "//cc:key_manager",
         "//cc:keyset_handle",
@@ -68,6 +72,7 @@
         "//cc:registry",
         "//cc/util:status",
         "//cc/util:statusor",
+        "@com_google_absl//absl/base:core_headers",
     ],
 )
 
@@ -83,6 +88,7 @@
         "//proto:aes_gcm_cc_proto",
         "//proto:common_cc_proto",
         "//proto:tink_cc_proto",
+        "//proto:xchacha20_poly1305_cc_proto",
     ],
 )
 
@@ -95,6 +101,7 @@
     deps = [
         "//cc:aead",
         "//cc:key_manager",
+        "//cc:key_manager_base",
         "//cc/subtle:aes_eax_boringssl",
         "//cc/subtle:random",
         "//cc/util:errors",
@@ -105,6 +112,7 @@
         "//proto:aes_eax_cc_proto",
         "//proto:common_cc_proto",
         "//proto:tink_cc_proto",
+        "@com_google_absl//absl/base",
     ],
 )
 
@@ -117,6 +125,7 @@
     deps = [
         "//cc:aead",
         "//cc:key_manager",
+        "//cc:key_manager_base",
         "//cc/subtle:aes_gcm_boringssl",
         "//cc/subtle:random",
         "//cc/util:errors",
@@ -127,6 +136,7 @@
         "//proto:aes_gcm_cc_proto",
         "//proto:common_cc_proto",
         "//proto:tink_cc_proto",
+        "@com_google_absl//absl/base",
     ],
 )
 
@@ -139,6 +149,7 @@
     deps = [
         "//cc:aead",
         "//cc:key_manager",
+        "//cc:key_manager_base",
         "//cc:mac",
         "//cc:registry",
         "//cc/subtle:aes_ctr_boringssl",
@@ -154,18 +165,43 @@
         "//proto:aes_ctr_hmac_aead_cc_proto",
         "//proto:common_cc_proto",
         "//proto:tink_cc_proto",
+        "@com_google_absl//absl/base",
+    ],
+)
+
+cc_library(
+    name = "xchacha20_poly1305_key_manager",
+    srcs = ["xchacha20_poly1305_key_manager.cc"],
+    hdrs = ["xchacha20_poly1305_key_manager.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        "//cc:aead",
+        "//cc:key_manager",
+        "//cc:key_manager_base",
+        "//cc/subtle:random",
+        "//cc/subtle:xchacha20_poly1305_boringssl",
+        "//cc/util:errors",
+        "//cc/util:protobuf_helper",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "//cc/util:validation",
+        "//proto:empty_cc_proto",
+        "//proto:tink_cc_proto",
+        "//proto:xchacha20_poly1305_cc_proto",
+        "@com_google_absl//absl/strings",
     ],
 )
 
 # tests
 
 cc_test(
-    name = "aead_set_wrapper_test",
+    name = "aead_wrapper_test",
     size = "small",
-    srcs = ["aead_set_wrapper_test.cc"],
+    srcs = ["aead_wrapper_test.cc"],
     copts = ["-Iexternal/gtest/include"],
     deps = [
-        ":aead_set_wrapper",
+        ":aead_wrapper",
         "//cc:aead",
         "//cc:primitive_set",
         "//cc/util:status",
@@ -198,11 +234,15 @@
     copts = ["-Iexternal/gtest/include"],
     deps = [
         ":aead_config",
+        ":aead_key_templates",
         "//cc:aead",
         "//cc:catalogue",
         "//cc:config",
+        "//cc:keyset_handle",
         "//cc:registry",
         "//cc/util:status",
+        "//cc/util:test_matchers",
+        "//cc/util:test_util",
         "@com_google_googletest//:gtest_main",
     ],
 )
@@ -238,11 +278,13 @@
         ":aes_ctr_hmac_aead_key_manager",
         ":aes_eax_key_manager",
         ":aes_gcm_key_manager",
+        ":xchacha20_poly1305_key_manager",
         "//proto:aes_ctr_hmac_aead_cc_proto",
         "//proto:aes_eax_cc_proto",
         "//proto:aes_gcm_cc_proto",
         "//proto:common_cc_proto",
         "//proto:tink_cc_proto",
+        "//proto:xchacha20_poly1305_cc_proto",
         "@com_google_googletest//:gtest_main",
     ],
 )
@@ -301,3 +343,21 @@
         "@com_google_googletest//:gtest_main",
     ],
 )
+
+cc_test(
+    name = "xchacha20_poly1305_key_manager_test",
+    size = "small",
+    srcs = ["xchacha20_poly1305_key_manager_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    deps = [
+        ":xchacha20_poly1305_key_manager",
+        "//cc:aead",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "//proto:aes_eax_cc_proto",
+        "//proto:common_cc_proto",
+        "//proto:tink_cc_proto",
+        "//proto:xchacha20_poly1305_cc_proto",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
diff --git a/cc/aead/CMakeLists.txt b/cc/aead/CMakeLists.txt
index 25afa15..c4ac266 100644
--- a/cc/aead/CMakeLists.txt
+++ b/cc/aead/CMakeLists.txt
@@ -1,445 +1,257 @@
-# Copyright 2018 The Fuchsia Authors. All rights reserved.
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-# Library: 'tink_aead_aead_set_wrapper'
-add_library(tink_aead_aead_set_wrapper aead_set_wrapper.cc aead_set_wrapper.h)
-tink_export_hdrs(aead_set_wrapper.h)
+
+# CC Library : aead_wrapper
+add_library(tink_cc_aead_aead_wrapper aead_wrapper.h aead_wrapper.cc)
+tink_export_hdrs(aead_wrapper.h)
 add_dependencies(
-  tink_aead_aead_set_wrapper
-  tink_aead
-  tink_crypto_format
-  tink_primitive_set
-  tink_util_status
-  tink_util_statusor
-  tink_subtle_subtle_util_boringssl
+  tink_cc_aead_aead_wrapper
+  tink_cc_aead
+  tink_cc_crypto_format
+  tink_cc_primitive_set
+  tink_cc_primitive_wrapper
+  tink_cc_registry
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_status
+  tink_cc_util_statusor
   tink_proto_tink_lib
 )
 target_link_libraries(
-  tink_aead_aead_set_wrapper
-  tink_aead
-  tink_crypto_format
-  tink_primitive_set
-  tink_util_status
-  tink_util_statusor
-  tink_subtle_subtle_util_boringssl
+  tink_cc_aead_aead_wrapper
+  tink_cc_aead
+  tink_cc_crypto_format
+  tink_cc_primitive_set
+  tink_cc_primitive_wrapper
+  tink_cc_registry
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_status
+  tink_cc_util_statusor
   tink_proto_tink_lib
   absl::strings
 )
 
-# Library: 'tink_aead_aead_catalogue'
-add_library(tink_aead_aead_catalogue aead_catalogue.cc aead_catalogue.h)
+# CC Library : aead_catalogue
+add_library(tink_cc_aead_aead_catalogue aead_catalogue.h aead_catalogue.cc)
 tink_export_hdrs(aead_catalogue.h)
 add_dependencies(
-  tink_aead_aead_catalogue
-  tink_aead_aes_ctr_hmac_aead_key_manager
-  tink_aead_aes_eax_key_manager
-  tink_aead_aes_gcm_key_manager
-  tink_aead
-  tink_catalogue
-  tink_key_manager
-  tink_util_status
+  tink_cc_aead_aead_catalogue
+  tink_cc_aead_aes_ctr_hmac_aead_key_manager
+  tink_cc_aead_aes_eax_key_manager
+  tink_cc_aead_aes_gcm_key_manager
+  tink_cc_aead_xchacha20_poly1305_key_manager
+  tink_cc_aead
+  tink_cc_catalogue
+  tink_cc_key_manager
+  tink_cc_util_status
 )
 target_link_libraries(
-  tink_aead_aead_catalogue
-  tink_aead_aes_ctr_hmac_aead_key_manager
-  tink_aead_aes_eax_key_manager
-  tink_aead_aes_gcm_key_manager
-  tink_aead
-  tink_catalogue
-  tink_key_manager
-  tink_util_status
+  tink_cc_aead_aead_catalogue
+  tink_cc_aead_aes_ctr_hmac_aead_key_manager
+  tink_cc_aead_aes_eax_key_manager
+  tink_cc_aead_aes_gcm_key_manager
+  tink_cc_aead_xchacha20_poly1305_key_manager
+  tink_cc_aead
+  tink_cc_catalogue
+  tink_cc_key_manager
+  tink_cc_util_status
 )
 
-# Library: 'tink_aead_aead_config'
-add_library(tink_aead_aead_config aead_config.cc aead_config.h)
+# CC Library : aead_config
+add_library(tink_cc_aead_aead_config aead_config.h aead_config.cc)
 tink_export_hdrs(aead_config.h)
 add_dependencies(
-  tink_aead_aead_config
-  tink_aead_aead_catalogue
-  tink_config
-  tink_mac_mac_config
-  tink_util_status
+  tink_cc_aead_aead_config
+  tink_cc_aead_aead_catalogue
+  tink_cc_aead_aead_wrapper
+  tink_cc_config
+  tink_cc_mac_mac_config
+  tink_cc_util_status
   tink_proto_config_lib
 )
 target_link_libraries(
-  tink_aead_aead_config
-  tink_aead_aead_catalogue
-  tink_config
-  tink_mac_mac_config
-  tink_util_status
+  tink_cc_aead_aead_config
+  tink_cc_aead_aead_catalogue
+  tink_cc_aead_aead_wrapper
+  tink_cc_config
+  tink_cc_mac_mac_config
+  tink_cc_util_status
   tink_proto_config_lib
+  absl::memory
 )
 
-# Library: 'tink_aead_aead_factory'
-add_library(tink_aead_aead_factory aead_factory.cc aead_factory.h)
-tink_export_hdrs(aead_factory.h)
-add_dependencies(
-  tink_aead_aead_factory
-  tink_aead_aead_set_wrapper
-  tink_aead
-  tink_key_manager
-  tink_keyset_handle
-  tink_primitive_set
-  tink_registry
-  tink_util_status
-  tink_util_statusor
+# CC Library : aes_eax_key_manager
+add_library(
+  tink_cc_aead_aes_eax_key_manager
+  aes_eax_key_manager.h
+  aes_eax_key_manager.cc
 )
-target_link_libraries(
-  tink_aead_aead_factory
-  tink_aead_aead_set_wrapper
-  tink_aead
-  tink_key_manager
-  tink_keyset_handle
-  tink_primitive_set
-  tink_registry
-  tink_util_status
-  tink_util_statusor
-)
-
-# Library: 'tink_aead_aead_key_templates'
-add_library(tink_aead_aead_key_templates aead_key_templates.cc aead_key_templates.h)
-tink_export_hdrs(aead_key_templates.h)
-add_dependencies(
-  tink_aead_aead_key_templates
-  tink_proto_aes_ctr_hmac_aead_lib
-  tink_proto_aes_eax_lib
-  tink_proto_aes_gcm_lib
-  tink_proto_common_lib
-  tink_proto_tink_lib
-)
-target_link_libraries(
-  tink_aead_aead_key_templates
-  tink_proto_aes_ctr_hmac_aead_lib
-  tink_proto_aes_eax_lib
-  tink_proto_aes_gcm_lib
-  tink_proto_common_lib
-  tink_proto_tink_lib
-)
-
-# Library: 'tink_aead_aes_eax_key_manager'
-add_library(tink_aead_aes_eax_key_manager aes_eax_key_manager.cc aes_eax_key_manager.h)
 tink_export_hdrs(aes_eax_key_manager.h)
 add_dependencies(
-  tink_aead_aes_eax_key_manager
-  tink_aead
-  tink_key_manager
-  tink_subtle_aes_eax_boringssl
-  tink_subtle_random
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_util_validation
+  tink_cc_aead_aes_eax_key_manager
+  tink_cc_aead
+  tink_cc_key_manager
+  tink_cc_core_key_manager_base
+  tink_cc_subtle_aes_eax_boringssl
+  tink_cc_subtle_random
+  tink_cc_util_errors
+  tink_cc_util_protobuf_helper
+  tink_cc_util_status
+  tink_cc_util_statusor
+  tink_cc_util_validation
   tink_proto_aes_eax_lib
   tink_proto_common_lib
   tink_proto_tink_lib
 )
 target_link_libraries(
-  tink_aead_aes_eax_key_manager
-  tink_aead
-  tink_key_manager
-  tink_subtle_aes_eax_boringssl
-  tink_subtle_random
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_util_validation
+  tink_cc_aead_aes_eax_key_manager
+  tink_cc_aead
+  tink_cc_key_manager
+  tink_cc_core_key_manager_base
+  tink_cc_subtle_aes_eax_boringssl
+  tink_cc_subtle_random
+  tink_cc_util_errors
+  tink_cc_util_protobuf_helper
+  tink_cc_util_status
+  tink_cc_util_statusor
+  tink_cc_util_validation
   tink_proto_aes_eax_lib
   tink_proto_common_lib
   tink_proto_tink_lib
+  absl::base
 )
 
-# Library: 'tink_aead_aes_gcm_key_manager'
-add_library(tink_aead_aes_gcm_key_manager aes_gcm_key_manager.cc aes_gcm_key_manager.h)
+# CC Library : aes_gcm_key_manager
+add_library(
+  tink_cc_aead_aes_gcm_key_manager
+  aes_gcm_key_manager.h
+  aes_gcm_key_manager.cc
+)
 tink_export_hdrs(aes_gcm_key_manager.h)
 add_dependencies(
-  tink_aead_aes_gcm_key_manager
-  tink_aead
-  tink_key_manager
-  tink_subtle_aes_gcm_boringssl
-  tink_subtle_random
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_util_validation
+  tink_cc_aead_aes_gcm_key_manager
+  tink_cc_aead
+  tink_cc_key_manager
+  tink_cc_core_key_manager_base
+  tink_cc_subtle_aes_gcm_boringssl
+  tink_cc_subtle_random
+  tink_cc_util_errors
+  tink_cc_util_protobuf_helper
+  tink_cc_util_status
+  tink_cc_util_statusor
+  tink_cc_util_validation
   tink_proto_aes_gcm_lib
   tink_proto_common_lib
   tink_proto_tink_lib
 )
 target_link_libraries(
-  tink_aead_aes_gcm_key_manager
-  tink_aead
-  tink_key_manager
-  tink_subtle_aes_gcm_boringssl
-  tink_subtle_random
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_util_validation
+  tink_cc_aead_aes_gcm_key_manager
+  tink_cc_aead
+  tink_cc_key_manager
+  tink_cc_core_key_manager_base
+  tink_cc_subtle_aes_gcm_boringssl
+  tink_cc_subtle_random
+  tink_cc_util_errors
+  tink_cc_util_protobuf_helper
+  tink_cc_util_status
+  tink_cc_util_statusor
+  tink_cc_util_validation
   tink_proto_aes_gcm_lib
   tink_proto_common_lib
   tink_proto_tink_lib
+  absl::base
 )
 
-# Library: 'tink_aead_aes_ctr_hmac_aead_key_manager'
-add_library(tink_aead_aes_ctr_hmac_aead_key_manager aes_ctr_hmac_aead_key_manager.cc aes_ctr_hmac_aead_key_manager.h)
+# CC Library : aes_ctr_hmac_aead_key_manager
+add_library(
+  tink_cc_aead_aes_ctr_hmac_aead_key_manager
+  aes_ctr_hmac_aead_key_manager.h
+  aes_ctr_hmac_aead_key_manager.cc
+)
 tink_export_hdrs(aes_ctr_hmac_aead_key_manager.h)
 add_dependencies(
-  tink_aead_aes_ctr_hmac_aead_key_manager
-  tink_aead
-  tink_key_manager
-  tink_mac
-  tink_registry
-  tink_subtle_aes_ctr_boringssl
-  tink_subtle_encrypt_then_authenticate
-  tink_subtle_hmac_boringssl
-  tink_subtle_random
-  tink_util_enums
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_util_validation
+  tink_cc_aead_aes_ctr_hmac_aead_key_manager
+  tink_cc_aead
+  tink_cc_key_manager
+  tink_cc_core_key_manager_base
+  tink_cc_mac
+  tink_cc_registry
+  tink_cc_subtle_aes_ctr_boringssl
+  tink_cc_subtle_encrypt_then_authenticate
+  tink_cc_subtle_hmac_boringssl
+  tink_cc_subtle_random
+  tink_cc_util_enums
+  tink_cc_util_errors
+  tink_cc_util_protobuf_helper
+  tink_cc_util_status
+  tink_cc_util_statusor
+  tink_cc_util_validation
   tink_proto_aes_ctr_hmac_aead_lib
   tink_proto_common_lib
   tink_proto_tink_lib
 )
 target_link_libraries(
-  tink_aead_aes_ctr_hmac_aead_key_manager
-  tink_aead
-  tink_key_manager
-  tink_mac
-  tink_registry
-  tink_subtle_aes_ctr_boringssl
-  tink_subtle_encrypt_then_authenticate
-  tink_subtle_hmac_boringssl
-  tink_subtle_random
-  tink_util_enums
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_util_validation
+  tink_cc_aead_aes_ctr_hmac_aead_key_manager
+  tink_cc_aead
+  tink_cc_key_manager
+  tink_cc_core_key_manager_base
+  tink_cc_mac
+  tink_cc_registry
+  tink_cc_subtle_aes_ctr_boringssl
+  tink_cc_subtle_encrypt_then_authenticate
+  tink_cc_subtle_hmac_boringssl
+  tink_cc_subtle_random
+  tink_cc_util_enums
+  tink_cc_util_errors
+  tink_cc_util_protobuf_helper
+  tink_cc_util_status
+  tink_cc_util_statusor
+  tink_cc_util_validation
   tink_proto_aes_ctr_hmac_aead_lib
   tink_proto_common_lib
   tink_proto_tink_lib
+  absl::base
 )
 
-# Test Binary: 'tink_aead_aead_set_wrapper_test'
-add_executable(tink_aead_aead_set_wrapper_test aead_set_wrapper_test.cc)
+# CC Library : xchacha20_poly1305_key_manager
+add_library(
+  tink_cc_aead_xchacha20_poly1305_key_manager
+  xchacha20_poly1305_key_manager.h
+  xchacha20_poly1305_key_manager.cc
+)
+tink_export_hdrs(xchacha20_poly1305_key_manager.h)
 add_dependencies(
-  tink_aead_aead_set_wrapper_test
-  tink_aead_aead_set_wrapper
-  tink_aead
-  tink_primitive_set
-  tink_util_status
-  tink_util_test_util
+  tink_cc_aead_xchacha20_poly1305_key_manager
+  tink_cc_aead
+  tink_cc_key_manager
+  tink_cc_core_key_manager_base
+  tink_cc_subtle_random
+  tink_cc_subtle_xchacha20_poly1305_boringssl
+  tink_cc_util_errors
+  tink_cc_util_protobuf_helper
+  tink_cc_util_status
+  tink_cc_util_statusor
+  tink_cc_util_validation
+  tink_proto_empty_lib
   tink_proto_tink_lib
+  tink_proto_xchacha20_poly1305_lib
 )
-add_dependencies(tink_aead_aead_set_wrapper_test build_external_projects)
 target_link_libraries(
-  tink_aead_aead_set_wrapper_test
-  tink_aead_aead_set_wrapper
-  tink_aead
-  tink_primitive_set
-  tink_util_status
-  tink_util_test_util
+  tink_cc_aead_xchacha20_poly1305_key_manager
+  tink_cc_aead
+  tink_cc_key_manager
+  tink_cc_core_key_manager_base
+  tink_cc_subtle_random
+  tink_cc_subtle_xchacha20_poly1305_boringssl
+  tink_cc_util_errors
+  tink_cc_util_protobuf_helper
+  tink_cc_util_status
+  tink_cc_util_statusor
+  tink_cc_util_validation
+  tink_proto_empty_lib
   tink_proto_tink_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_aead_aead_catalogue_test'
-add_executable(tink_aead_aead_catalogue_test aead_catalogue_test.cc)
-add_dependencies(
-  tink_aead_aead_catalogue_test
-  tink_aead_aead_catalogue
-  tink_aead_aead_config
-  tink_aead
-  tink_catalogue
-  tink_util_status
-  tink_util_statusor
-)
-add_dependencies(tink_aead_aead_catalogue_test build_external_projects)
-target_link_libraries(
-  tink_aead_aead_catalogue_test
-  tink_aead_aead_catalogue
-  tink_aead_aead_config
-  tink_aead
-  tink_catalogue
-  tink_util_status
-  tink_util_statusor
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_aead_aead_config_test'
-add_executable(tink_aead_aead_config_test aead_config_test.cc)
-add_dependencies(
-  tink_aead_aead_config_test
-  tink_aead_aead_config
-  tink_aead
-  tink_catalogue
-  tink_config
-  tink_registry
-  tink_util_status
-)
-add_dependencies(tink_aead_aead_config_test build_external_projects)
-target_link_libraries(
-  tink_aead_aead_config_test
-  tink_aead_aead_config
-  tink_aead
-  tink_catalogue
-  tink_config
-  tink_registry
-  tink_util_status
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_aead_aead_factory_test'
-add_executable(tink_aead_aead_factory_test aead_factory_test.cc)
-add_dependencies(
-  tink_aead_aead_factory_test
-  tink_aead_aead_config
-  tink_aead_aead_factory
-  tink_aead_aes_gcm_key_manager
-  tink_aead
-  tink_crypto_format
-  tink_keyset_handle
-  tink_util_keyset_util
-  tink_util_status
-  tink_util_test_util
-  tink_proto_aes_gcm_lib
-  tink_proto_tink_lib
-)
-add_dependencies(tink_aead_aead_factory_test build_external_projects)
-target_link_libraries(
-  tink_aead_aead_factory_test
-  tink_aead_aead_config
-  tink_aead_aead_factory
-  tink_aead_aes_gcm_key_manager
-  tink_aead
-  tink_crypto_format
-  tink_keyset_handle
-  tink_util_keyset_util
-  tink_util_status
-  tink_util_test_util
-  tink_proto_aes_gcm_lib
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_aead_aead_key_templates_test'
-add_executable(tink_aead_aead_key_templates_test aead_key_templates_test.cc)
-add_dependencies(
-  tink_aead_aead_key_templates_test
-  tink_aead_aead_key_templates
-  tink_aead_aes_ctr_hmac_aead_key_manager
-  tink_aead_aes_eax_key_manager
-  tink_aead_aes_gcm_key_manager
-  tink_proto_aes_ctr_hmac_aead_lib
-  tink_proto_aes_eax_lib
-  tink_proto_aes_gcm_lib
-  tink_proto_common_lib
-  tink_proto_tink_lib
-)
-add_dependencies(tink_aead_aead_key_templates_test build_external_projects)
-target_link_libraries(
-  tink_aead_aead_key_templates_test
-  tink_aead_aead_key_templates
-  tink_aead_aes_ctr_hmac_aead_key_manager
-  tink_aead_aes_eax_key_manager
-  tink_aead_aes_gcm_key_manager
-  tink_proto_aes_ctr_hmac_aead_lib
-  tink_proto_aes_eax_lib
-  tink_proto_aes_gcm_lib
-  tink_proto_common_lib
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_aead_aes_eax_key_manager_test'
-add_executable(tink_aead_aes_eax_key_manager_test aes_eax_key_manager_test.cc)
-add_dependencies(
-  tink_aead_aes_eax_key_manager_test
-  tink_aead_aes_eax_key_manager
-  tink_aead
-  tink_util_status
-  tink_util_statusor
-  tink_proto_aes_eax_lib
-  tink_proto_aes_gcm_lib
-  tink_proto_common_lib
-  tink_proto_tink_lib
-)
-add_dependencies(tink_aead_aes_eax_key_manager_test build_external_projects)
-target_link_libraries(
-  tink_aead_aes_eax_key_manager_test
-  tink_aead_aes_eax_key_manager
-  tink_aead
-  tink_util_status
-  tink_util_statusor
-  tink_proto_aes_eax_lib
-  tink_proto_aes_gcm_lib
-  tink_proto_common_lib
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_aead_aes_gcm_key_manager_test'
-add_executable(tink_aead_aes_gcm_key_manager_test aes_gcm_key_manager_test.cc)
-add_dependencies(
-  tink_aead_aes_gcm_key_manager_test
-  tink_aead_aes_gcm_key_manager
-  tink_aead
-  tink_util_status
-  tink_util_statusor
-  tink_proto_aes_eax_lib
-  tink_proto_aes_gcm_lib
-  tink_proto_common_lib
-  tink_proto_tink_lib
-)
-add_dependencies(tink_aead_aes_gcm_key_manager_test build_external_projects)
-target_link_libraries(
-  tink_aead_aes_gcm_key_manager_test
-  tink_aead_aes_gcm_key_manager
-  tink_aead
-  tink_util_status
-  tink_util_statusor
-  tink_proto_aes_eax_lib
-  tink_proto_aes_gcm_lib
-  tink_proto_common_lib
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_aead_aes_ctr_hmac_aead_key_manager_test'
-add_executable(tink_aead_aes_ctr_hmac_aead_key_manager_test aes_ctr_hmac_aead_key_manager_test.cc)
-add_dependencies(
-  tink_aead_aes_ctr_hmac_aead_key_manager_test
-  tink_aead_aes_ctr_hmac_aead_key_manager
-  tink_config
-  tink_mac_mac_config
-  tink_util_status
-  tink_util_statusor
-  tink_proto_aes_ctr_hmac_aead_lib
-  tink_proto_aes_gcm_lib
-  tink_proto_common_lib
-  tink_proto_tink_lib
-)
-add_dependencies(tink_aead_aes_ctr_hmac_aead_key_manager_test build_external_projects)
-target_link_libraries(
-  tink_aead_aes_ctr_hmac_aead_key_manager_test
-  tink_aead_aes_ctr_hmac_aead_key_manager
-  tink_config
-  tink_mac_mac_config
-  tink_util_status
-  tink_util_statusor
-  tink_proto_aes_ctr_hmac_aead_lib
-  tink_proto_aes_gcm_lib
-  tink_proto_common_lib
-  tink_proto_tink_lib
-  gtest gtest_main
+  tink_proto_xchacha20_poly1305_lib
+  absl::strings
 )
 
diff --git a/cc/aead/aead_catalogue.cc b/cc/aead/aead_catalogue.cc
index 3fa7b11..535e189 100644
--- a/cc/aead/aead_catalogue.cc
+++ b/cc/aead/aead_catalogue.cc
@@ -20,6 +20,7 @@
 #include "tink/aead/aes_ctr_hmac_aead_key_manager.h"
 #include "tink/aead/aes_eax_key_manager.h"
 #include "tink/aead/aes_gcm_key_manager.h"
+#include "tink/aead/xchacha20_poly1305_key_manager.h"
 #include "tink/catalogue.h"
 #include "tink/key_manager.h"
 #include "tink/util/status.h"
@@ -32,15 +33,19 @@
 
 crypto::tink::util::StatusOr<std::unique_ptr<KeyManager<Aead>>>
 CreateKeyManager(const std::string& type_url) {
-  if (type_url == AesGcmKeyManager::kKeyType) {
+  if (type_url == AesGcmKeyManager::static_key_type()) {
     std::unique_ptr<KeyManager<Aead>> manager(new AesGcmKeyManager());
     return std::move(manager);
-  } else if (type_url == AesEaxKeyManager::kKeyType) {
+  } else if (type_url == AesEaxKeyManager::static_key_type()) {
     std::unique_ptr<KeyManager<Aead>> manager(new AesEaxKeyManager());
     return std::move(manager);
-  } else if (type_url == AesCtrHmacAeadKeyManager::kKeyType) {
+  } else if (type_url == AesCtrHmacAeadKeyManager::static_key_type()) {
     std::unique_ptr<KeyManager<Aead>> manager(new AesCtrHmacAeadKeyManager());
     return std::move(manager);
+  } else if (type_url == XChaCha20Poly1305KeyManager::static_key_type()) {
+    std::unique_ptr<KeyManager<Aead>> manager(
+        new XChaCha20Poly1305KeyManager());
+    return std::move(manager);
   }
   return ToStatusF(crypto::tink::util::error::NOT_FOUND,
                    "No key manager for type_url '%s'.", type_url.c_str());
diff --git a/cc/aead/aead_catalogue_test.cc b/cc/aead/aead_catalogue_test.cc
index 0887be8..9dc9803 100644
--- a/cc/aead/aead_catalogue_test.cc
+++ b/cc/aead/aead_catalogue_test.cc
@@ -31,10 +31,10 @@
 
 TEST_F(AeadCatalogueTest, testBasic) {
   std::string key_types[] = {
-    "type.googleapis.com/google.crypto.tink.AesEaxKey",
-    "type.googleapis.com/google.crypto.tink.AesGcmKey",
-    "type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey"
-  };
+      "type.googleapis.com/google.crypto.tink.AesEaxKey",
+      "type.googleapis.com/google.crypto.tink.AesGcmKey",
+      "type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey",
+      "type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key"};
 
   AeadCatalogue catalogue;
   {
@@ -72,9 +72,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/aead/aead_config.cc b/cc/aead/aead_config.cc
index 9e3fe43..40c4f17 100644
--- a/cc/aead/aead_config.cc
+++ b/cc/aead/aead_config.cc
@@ -16,6 +16,8 @@
 
 #include "tink/aead/aead_config.h"
 
+#include "absl/memory/memory.h"
+#include "tink/aead/aead_wrapper.h"
 #include "tink/config.h"
 #include "tink/registry.h"
 #include "tink/aead/aead_catalogue.h"
@@ -44,6 +46,9 @@
   config->add_entry()->MergeFrom(*Config::GetTinkKeyTypeEntry(
       AeadConfig::kCatalogueName, AeadConfig::kPrimitiveName,
       "AesEaxKey", 0, true));
+  config->add_entry()->MergeFrom(*Config::GetTinkKeyTypeEntry(
+      AeadConfig::kCatalogueName, AeadConfig::kPrimitiveName,
+      "XChaCha20Poly1305Key", 0, true));
   config->set_config_name("TINK_AEAD");
   return config;
 }
@@ -63,7 +68,8 @@
 util::Status AeadConfig::Register() {
   auto status = MacConfig::Register();
   if (!status.ok()) return status;
-  status = Registry::AddCatalogue(kCatalogueName, new AeadCatalogue());
+  status = Registry::AddCatalogue(kCatalogueName,
+                                  absl::make_unique<AeadCatalogue>());
   if (!status.ok()) return status;
   return Config::Register(Latest());
 }
diff --git a/cc/aead/aead_config_test.cc b/cc/aead/aead_config_test.cc
index 3822f80..662fdc1 100644
--- a/cc/aead/aead_config_test.cc
+++ b/cc/aead/aead_config_test.cc
@@ -16,18 +16,25 @@
 
 #include "tink/aead/aead_config.h"
 
+#include "gmock/gmock.h"
 #include "tink/aead.h"
+#include "tink/aead/aead_key_templates.h"
 #include "tink/catalogue.h"
 #include "tink/config.h"
+#include "tink/keyset_handle.h"
 #include "tink/registry.h"
 #include "tink/util/status.h"
 #include "gtest/gtest.h"
+#include "tink/util/test_util.h"
 
 
 namespace crypto {
 namespace tink {
 namespace {
 
+using ::crypto::tink::test::DummyAead;
+using ::testing::Eq;
+
 class DummyAeadCatalogue : public Catalogue<Aead> {
  public:
   DummyAeadCatalogue() {}
@@ -50,12 +57,13 @@
       "type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey";
   std::string aes_eax_key_type =
       "type.googleapis.com/google.crypto.tink.AesEaxKey";
-  std::string aes_gcm_key_type =
-      "type.googleapis.com/google.crypto.tink.AesGcmKey";
+  std::string aes_gcm_key_type = "type.googleapis.com/google.crypto.tink.AesGcmKey";
+  std::string xchacha20_poly1305_key_type =
+      "type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key";
   std::string hmac_key_type = "type.googleapis.com/google.crypto.tink.HmacKey";
   auto& config = AeadConfig::Latest();
 
-  EXPECT_EQ(4, AeadConfig::Latest().entry_size());
+  EXPECT_EQ(5, AeadConfig::Latest().entry_size());
 
   EXPECT_EQ("TinkMac", config.entry(0).catalogue_name());
   EXPECT_EQ("Mac", config.entry(0).primitive_name());
@@ -81,6 +89,12 @@
   EXPECT_EQ(true, config.entry(3).new_key_allowed());
   EXPECT_EQ(0, config.entry(3).key_manager_version());
 
+  EXPECT_EQ("TinkAead", config.entry(4).catalogue_name());
+  EXPECT_EQ("Aead", config.entry(4).primitive_name());
+  EXPECT_EQ(xchacha20_poly1305_key_type, config.entry(4).type_url());
+  EXPECT_EQ(true, config.entry(4).new_key_allowed());
+  EXPECT_EQ(0, config.entry(4).key_manager_version());
+
   // No key manager before registration.
   auto manager_result = Registry::get_key_manager<Aead>(aes_gcm_key_type);
   EXPECT_FALSE(manager_result.ok());
@@ -123,11 +137,36 @@
   EXPECT_EQ(util::error::ALREADY_EXISTS, status.error_code());
 }
 
+// Tests that the AeadWrapper has been properly registered and we can wrap
+// primitives.
+TEST_F(AeadConfigTest, WrappersRegistered) {
+  ASSERT_TRUE(AeadConfig::Register().ok());
+
+  google::crypto::tink::Keyset::Key key;
+  key.set_status(google::crypto::tink::KeyStatusType::ENABLED);
+  key.set_key_id(1234);
+  key.set_output_prefix_type(google::crypto::tink::OutputPrefixType::RAW);
+  auto primitive_set = absl::make_unique<PrimitiveSet<Aead>>();
+  primitive_set->set_primary(
+      primitive_set->AddPrimitive(absl::make_unique<DummyAead>("dummy"), key)
+          .ValueOrDie());
+
+  auto primitive_result = Registry::Wrap(std::move(primitive_set));
+
+  ASSERT_TRUE(primitive_result.ok()) << primitive_result.status();
+  auto encryption_result = primitive_result.ValueOrDie()->Encrypt("secret", "");
+  ASSERT_TRUE(encryption_result.ok());
+
+  auto decryption_result =
+      DummyAead("dummy").Decrypt(encryption_result.ValueOrDie(), "");
+  ASSERT_TRUE(decryption_result.status().ok());
+  EXPECT_THAT(decryption_result.ValueOrDie(), Eq("secret"));
+
+  decryption_result =
+      DummyAead("dummy").Decrypt(encryption_result.ValueOrDie(), "wrog");
+  EXPECT_FALSE(decryption_result.status().ok());
+}
+
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/aead/aead_factory.cc b/cc/aead/aead_factory.cc
index 2a7738b..f832330 100644
--- a/cc/aead/aead_factory.cc
+++ b/cc/aead/aead_factory.cc
@@ -20,7 +20,7 @@
 #include "tink/key_manager.h"
 #include "tink/keyset_handle.h"
 #include "tink/registry.h"
-#include "tink/aead/aead_set_wrapper.h"
+#include "tink/aead/aead_wrapper.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 
@@ -31,21 +31,25 @@
 // static
 util::StatusOr<std::unique_ptr<Aead>> AeadFactory::GetPrimitive(
     const KeysetHandle& keyset_handle) {
-  return GetPrimitive(keyset_handle, nullptr);
+  util::Status status =
+      Registry::RegisterPrimitiveWrapper(absl::make_unique<AeadWrapper>());
+  if (!status.ok()) {
+    return status;
+  }
+  return keyset_handle.GetPrimitive<Aead>();
 }
 
 // static
 util::StatusOr<std::unique_ptr<Aead>> AeadFactory::GetPrimitive(
     const KeysetHandle& keyset_handle,
     const KeyManager<Aead>* custom_key_manager) {
-  auto primitives_result = Registry::GetPrimitives<Aead>(
-      keyset_handle, custom_key_manager);
-  if (primitives_result.ok()) {
-    return AeadSetWrapper::NewAead(std::move(primitives_result.ValueOrDie()));
+  util::Status status =
+      Registry::RegisterPrimitiveWrapper(absl::make_unique<AeadWrapper>());
+  if (!status.ok()) {
+    return status;
   }
-  return primitives_result.status();
+  return keyset_handle.GetPrimitive<Aead>(custom_key_manager);
 }
 
-
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/aead/aead_factory.h b/cc/aead/aead_factory.h
index 7bbf431..59ab039 100644
--- a/cc/aead/aead_factory.h
+++ b/cc/aead/aead_factory.h
@@ -17,6 +17,7 @@
 #ifndef TINK_AEAD_AEAD_FACTORY_H_
 #define TINK_AEAD_AEAD_FACTORY_H_
 
+#include "absl/base/macros.h"
 #include "tink/aead.h"
 #include "tink/key_manager.h"
 #include "tink/keyset_handle.h"
@@ -26,22 +27,17 @@
 namespace tink {
 
 ///////////////////////////////////////////////////////////////////////////////
-// AeadFactory allows for obtaining an Aead primitive from a KeysetHandle.
+// This class is deprecated. Call keyset_handle->GetPrimitive<Aead>() instead.
 //
-// AeadFactory gets primitives from the Registry, which can be initialized
-// via a convenience method from AeadConfig-class. Here is an example
-// how one can obtain and use a Aead primitive:
-//
-//   auto status = AeadConfig::Register();
-//   if (!status.ok()) { /* fail with error */ }
-//   KeysetHandle keyset_handle = ...;
-//   std::unique_ptr<Aead> aead =
-//       std::move(AeadFactory::GetPrimitive(keyset_handle).ValueOrDie());
-//   std::string plaintext = ...;
-//   std::string aad = ...;
-//   std::string ciphertext = aead.Encrypt(plaintext, aad).ValueOrDie();
-//
-class AeadFactory {
+// Note that in order to for this change to be safe, the AeadSetWrapper has to
+// be registered in your binary before this call. This happens automatically if
+// you call one of
+// * AeadConfig::Register()
+// * HybridConfig::Register()
+// * TinkConfig::Register()
+class ABSL_DEPRECATED(
+    "Call GetPrimitive<Aead>() on the keyset_handle after registering the "
+    "AeadWrapper instead.") AeadFactory {
  public:
   // Returns an Aead-primitive that uses key material from the keyset
   // specified via 'keyset_handle'.
diff --git a/cc/aead/aead_factory_test.cc b/cc/aead/aead_factory_test.cc
index 550d075..e438795 100644
--- a/cc/aead/aead_factory_test.cc
+++ b/cc/aead/aead_factory_test.cc
@@ -124,9 +124,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/aead/aead_key_templates.cc b/cc/aead/aead_key_templates.cc
index 68aa4af..15957df 100644
--- a/cc/aead/aead_key_templates.cc
+++ b/cc/aead/aead_key_templates.cc
@@ -21,6 +21,7 @@
 #include "proto/aes_gcm.pb.h"
 #include "proto/common.pb.h"
 #include "proto/tink.pb.h"
+#include "proto/xchacha20_poly1305.pb.h"
 
 using google::crypto::tink::AesCtrHmacAeadKeyFormat;
 using google::crypto::tink::AesEaxKeyFormat;
@@ -77,6 +78,13 @@
   return key_template;
 }
 
+KeyTemplate* NewXChaCha20Poly1305KeyTemplate() {
+  KeyTemplate* key_template = new KeyTemplate;
+  key_template->set_type_url(
+      "type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key");
+  key_template->set_output_prefix_type(OutputPrefixType::TINK);
+  return key_template;
+}
 
 }  // anonymous namespace
 
@@ -132,6 +140,11 @@
   return *key_template;
 }
 
+// static
+const KeyTemplate& AeadKeyTemplates::XChaCha20Poly1305() {
+  static const KeyTemplate* key_template = NewXChaCha20Poly1305KeyTemplate();
+  return *key_template;
+}
 
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/aead/aead_key_templates.h b/cc/aead/aead_key_templates.h
index 3df610e..640e10e 100644
--- a/cc/aead/aead_key_templates.h
+++ b/cc/aead/aead_key_templates.h
@@ -86,6 +86,13 @@
   //   - HMAC hash function: SHA256
   //   - OutputPrefixType: TINK
   static const google::crypto::tink::KeyTemplate& Aes256CtrHmacSha256();
+
+  // Returns a KeyTemplate that generates new instances of XChaCha20Poly1305Key
+  // with the following parameters:
+  //   - XChacha20 key size: 32 bytes
+  //   - IV size: 24 bytes
+  //   - OutputPrefixType: TINK
+  static const google::crypto::tink::KeyTemplate& XChaCha20Poly1305();
 };
 
 }  // namespace tink
diff --git a/cc/aead/aead_key_templates_test.cc b/cc/aead/aead_key_templates_test.cc
index 6f9f0bb..9c2b24e 100644
--- a/cc/aead/aead_key_templates_test.cc
+++ b/cc/aead/aead_key_templates_test.cc
@@ -16,15 +16,17 @@
 
 #include "tink/aead/aead_key_templates.h"
 
+#include "gtest/gtest.h"
 #include "tink/aead/aes_ctr_hmac_aead_key_manager.h"
 #include "tink/aead/aes_eax_key_manager.h"
 #include "tink/aead/aes_gcm_key_manager.h"
+#include "tink/aead/xchacha20_poly1305_key_manager.h"
 #include "proto/aes_ctr_hmac_aead.pb.h"
 #include "proto/aes_eax.pb.h"
 #include "proto/aes_gcm.pb.h"
 #include "proto/common.pb.h"
 #include "proto/tink.pb.h"
-#include "gtest/gtest.h"
+#include "proto/xchacha20_poly1305.pb.h"
 
 using google::crypto::tink::AesCtrHmacAeadKeyFormat;
 using google::crypto::tink::AesEaxKeyFormat;
@@ -57,7 +59,8 @@
     // Check that the template works with the key manager.
     AesEaxKeyManager key_manager;
     EXPECT_EQ(key_manager.get_key_type(), key_template.type_url());
-    auto new_key_result = key_manager.get_key_factory().NewKey(key_format);
+    auto new_key_result =
+        key_manager.get_key_factory().NewKey(key_template.value());
     EXPECT_TRUE(new_key_result.ok()) << new_key_result.status();
   }
 
@@ -78,7 +81,8 @@
     // Check that the template works with the key manager.
     AesEaxKeyManager key_manager;
     EXPECT_EQ(key_manager.get_key_type(), key_template.type_url());
-    auto new_key_result = key_manager.get_key_factory().NewKey(key_format);
+    auto new_key_result =
+        key_manager.get_key_factory().NewKey(key_template.value());
     EXPECT_TRUE(new_key_result.ok()) << new_key_result.status();
   }
 }
@@ -102,7 +106,8 @@
     // Check that the template works with the key manager.
     AesGcmKeyManager key_manager;
     EXPECT_EQ(key_manager.get_key_type(), key_template.type_url());
-    auto new_key_result = key_manager.get_key_factory().NewKey(key_format);
+    auto new_key_result =
+        key_manager.get_key_factory().NewKey(key_template.value());
     EXPECT_TRUE(new_key_result.ok()) << new_key_result.status();
   }
 
@@ -122,7 +127,8 @@
     // Check that the template works with the key manager.
     AesGcmKeyManager key_manager;
     EXPECT_EQ(key_manager.get_key_type(), key_template.type_url());
-    auto new_key_result = key_manager.get_key_factory().NewKey(key_format);
+    auto new_key_result =
+        key_manager.get_key_factory().NewKey(key_template.value());
     EXPECT_TRUE(new_key_result.ok()) << new_key_result.status();
   }
 }
@@ -150,7 +156,8 @@
     // Check that the template works with the key manager.
     AesCtrHmacAeadKeyManager key_manager;
     EXPECT_EQ(key_manager.get_key_type(), key_template.type_url());
-    auto new_key_result = key_manager.get_key_factory().NewKey(key_format);
+    auto new_key_result =
+        key_manager.get_key_factory().NewKey(key_template.value());
     EXPECT_TRUE(new_key_result.ok()) << new_key_result.status();
   }
 
@@ -174,16 +181,33 @@
     // Check that the template works with the key manager.
     AesCtrHmacAeadKeyManager key_manager;
     EXPECT_EQ(key_manager.get_key_type(), key_template.type_url());
-    auto new_key_result = key_manager.get_key_factory().NewKey(key_format);
+    auto new_key_result =
+        key_manager.get_key_factory().NewKey(key_template.value());
     EXPECT_TRUE(new_key_result.ok()) << new_key_result.status();
   }
 }
 
+TEST(AeadKeyTemplatesTest, testXChaCha20Poly1305KeyTemplates) {
+  std::string type_url =
+      "type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key";
+
+  // Check that returned template is correct.
+  const KeyTemplate& key_template = AeadKeyTemplates::XChaCha20Poly1305();
+  EXPECT_EQ(type_url, key_template.type_url());
+  EXPECT_EQ(OutputPrefixType::TINK, key_template.output_prefix_type());
+
+  // Check that reference to the same object is returned.
+  const KeyTemplate& key_template_2 = AeadKeyTemplates::XChaCha20Poly1305();
+  EXPECT_EQ(&key_template, &key_template_2);
+
+  // Check that the template works with the key manager.
+  XChaCha20Poly1305KeyManager key_manager;
+  EXPECT_EQ(key_manager.get_key_type(), key_template.type_url());
+  auto new_key_result =
+      key_manager.get_key_factory().NewKey(key_template.value());
+  EXPECT_TRUE(new_key_result.ok()) << new_key_result.status();
+}
+
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/aead/aead_set_wrapper_test.cc b/cc/aead/aead_set_wrapper_test.cc
deleted file mode 100644
index 81e84b7..0000000
--- a/cc/aead/aead_set_wrapper_test.cc
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-#include "tink/aead/aead_set_wrapper.h"
-#include "tink/aead.h"
-#include "tink/primitive_set.h"
-#include "tink/util/status.h"
-#include "tink/util/test_util.h"
-#include "gtest/gtest.h"
-
-
-using crypto::tink::test::DummyAead;
-using google::crypto::tink::OutputPrefixType;
-using google::crypto::tink::Keyset;
-
-namespace crypto {
-namespace tink {
-namespace {
-
-class AeadSetWrapperTest : public ::testing::Test {
- protected:
-  void SetUp() override {
-  }
-  void TearDown() override {
-  }
-};
-
-TEST_F(AeadSetWrapperTest, testBasic) {
-  { // aead_set is nullptr.
-    auto aead_result = AeadSetWrapper::NewAead(nullptr);
-    EXPECT_FALSE(aead_result.ok());
-    EXPECT_EQ(util::error::INTERNAL, aead_result.status().error_code());
-    EXPECT_PRED_FORMAT2(testing::IsSubstring, "non-NULL",
-                        aead_result.status().error_message());
-  }
-
-  { // aead_set has no primary primitive.
-    std::unique_ptr<PrimitiveSet<Aead>> aead_set(new PrimitiveSet<Aead>());
-    auto aead_result = AeadSetWrapper::NewAead(std::move(aead_set));
-    EXPECT_FALSE(aead_result.ok());
-    EXPECT_EQ(util::error::INVALID_ARGUMENT, aead_result.status().error_code());
-    EXPECT_PRED_FORMAT2(testing::IsSubstring, "no primary",
-                        aead_result.status().error_message());
-  }
-
-  { // Correct aead_set;
-    Keyset::Key* key;
-    Keyset keyset;
-
-    uint32_t key_id_0 = 1234543;
-    key = keyset.add_key();
-    key->set_output_prefix_type(OutputPrefixType::TINK);
-    key->set_key_id(key_id_0);
-
-    uint32_t key_id_1 = 726329;
-    key = keyset.add_key();
-    key->set_output_prefix_type(OutputPrefixType::LEGACY);
-    key->set_key_id(key_id_1);
-
-    uint32_t key_id_2 = 7213743;
-    key = keyset.add_key();
-    key->set_output_prefix_type(OutputPrefixType::TINK);
-    key->set_key_id(key_id_2);
-
-    std::string aead_name_0 = "aead0";
-    std::string aead_name_1 = "aead1";
-    std::string aead_name_2 = "aead2";
-    std::unique_ptr<PrimitiveSet<Aead>> aead_set(new PrimitiveSet<Aead>());
-    std::unique_ptr<Aead> aead(new DummyAead(aead_name_0));
-    auto entry_result = aead_set->AddPrimitive(std::move(aead), keyset.key(0));
-    ASSERT_TRUE(entry_result.ok());
-    aead.reset(new DummyAead(aead_name_1));
-    entry_result = aead_set->AddPrimitive(std::move(aead), keyset.key(1));
-    ASSERT_TRUE(entry_result.ok());
-    aead.reset(new DummyAead(aead_name_2));
-    entry_result = aead_set->AddPrimitive(std::move(aead), keyset.key(2));
-    ASSERT_TRUE(entry_result.ok());
-    // The last key is the primary.
-    aead_set->set_primary(entry_result.ValueOrDie());
-
-    // Wrap aead_set and test the resulting Aead.
-    auto aead_result = AeadSetWrapper::NewAead(std::move(aead_set));
-    EXPECT_TRUE(aead_result.ok()) << aead_result.status();
-    aead = std::move(aead_result.ValueOrDie());
-    std::string plaintext = "some_plaintext";
-    std::string aad = "some_aad";
-
-    auto encrypt_result = aead->Encrypt(plaintext, aad);
-    EXPECT_TRUE(encrypt_result.ok()) << encrypt_result.status();
-    std::string ciphertext = encrypt_result.ValueOrDie();
-    EXPECT_PRED_FORMAT2(testing::IsSubstring, aead_name_2, ciphertext);
-
-    auto decrypt_result = aead->Decrypt(ciphertext, aad);
-    EXPECT_TRUE(decrypt_result.ok()) << decrypt_result.status();
-    EXPECT_EQ(plaintext, decrypt_result.ValueOrDie());
-
-    decrypt_result = aead->Decrypt("some bad ciphertext", aad);
-    EXPECT_FALSE(decrypt_result.ok());
-    EXPECT_EQ(util::error::INVALID_ARGUMENT,
-              decrypt_result.status().error_code());
-    EXPECT_PRED_FORMAT2(testing::IsSubstring, "decryption failed",
-                        decrypt_result.status().error_message());
-  }
-}
-
-}  // namespace
-}  // namespace tink
-}  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/aead/aead_set_wrapper.cc b/cc/aead/aead_wrapper.cc
similarity index 84%
rename from cc/aead/aead_set_wrapper.cc
rename to cc/aead/aead_wrapper.cc
index 420169c..141c18b 100644
--- a/cc/aead/aead_set_wrapper.cc
+++ b/cc/aead/aead_wrapper.cc
@@ -14,7 +14,7 @@
 //
 ///////////////////////////////////////////////////////////////////////////////
 
-#include "tink/aead/aead_set_wrapper.h"
+#include "tink/aead/aead_wrapper.h"
 
 #include "tink/aead.h"
 #include "tink/crypto_format.h"
@@ -39,16 +39,24 @@
   return util::Status::OK;
 }
 
-}  // anonymous namespace
+class AeadSetWrapper : public Aead {
+ public:
+  explicit AeadSetWrapper(std::unique_ptr<PrimitiveSet<Aead>> aead_set)
+      : aead_set_(std::move(aead_set)) {}
 
-// static
-util::StatusOr<std::unique_ptr<Aead>> AeadSetWrapper::NewAead(
-    std::unique_ptr<PrimitiveSet<Aead>> aead_set) {
-  util::Status status = Validate(aead_set.get());
-  if (!status.ok()) return status;
-  std::unique_ptr<Aead> aead(new AeadSetWrapper(std::move(aead_set)));
-  return std::move(aead);
-}
+  crypto::tink::util::StatusOr<std::string> Encrypt(
+      absl::string_view plaintext,
+      absl::string_view associated_data) const override;
+
+  crypto::tink::util::StatusOr<std::string> Decrypt(
+      absl::string_view ciphertext,
+      absl::string_view associated_data) const override;
+
+  ~AeadSetWrapper() override {}
+
+ private:
+  std::unique_ptr<PrimitiveSet<Aead>> aead_set_;
+};
 
 util::StatusOr<std::string> AeadSetWrapper::Encrypt(
     absl::string_view plaintext,
@@ -105,5 +113,15 @@
   return util::Status(util::error::INVALID_ARGUMENT, "decryption failed");
 }
 
+}  // anonymous namespace
+
+util::StatusOr<std::unique_ptr<Aead>> AeadWrapper::Wrap(
+    std::unique_ptr<PrimitiveSet<Aead>> aead_set) const {
+  util::Status status = Validate(aead_set.get());
+  if (!status.ok()) return status;
+  std::unique_ptr<Aead> aead(new AeadSetWrapper(std::move(aead_set)));
+  return std::move(aead);
+}
+
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/aead/aead_set_wrapper.h b/cc/aead/aead_wrapper.h
similarity index 69%
rename from cc/aead/aead_set_wrapper.h
rename to cc/aead/aead_wrapper.h
index 03f8859..be9e2e0 100644
--- a/cc/aead/aead_set_wrapper.h
+++ b/cc/aead/aead_wrapper.h
@@ -20,6 +20,7 @@
 #include "absl/strings/string_view.h"
 #include "tink/aead.h"
 #include "tink/primitive_set.h"
+#include "tink/primitive_wrapper.h"
 #include "tink/util/statusor.h"
 #include "proto/tink.pb.h"
 
@@ -31,28 +32,12 @@
 // instances, depending on the context:
 //   * Aead::Encrypt(...) uses the primary instance from the set
 //   * Aead::Decrypt(...) uses the instance that matches the ciphertext prefix.
-class AeadSetWrapper : public Aead {
+class AeadWrapper : public PrimitiveWrapper<Aead> {
  public:
   // Returns an Aead-primitive that uses Aead-instances provided in 'aead_set',
   // which must be non-NULL and must contain a primary instance.
-  static crypto::tink::util::StatusOr<std::unique_ptr<Aead>> NewAead(
-      std::unique_ptr<PrimitiveSet<Aead>> aead_set);
-
-  crypto::tink::util::StatusOr<std::string> Encrypt(
-      absl::string_view plaintext,
-      absl::string_view associated_data) const override;
-
-  crypto::tink::util::StatusOr<std::string> Decrypt(
-      absl::string_view ciphertext,
-      absl::string_view associated_data) const override;
-
-  virtual ~AeadSetWrapper() {}
-
- private:
-  std::unique_ptr<PrimitiveSet<Aead>> aead_set_;
-
-  AeadSetWrapper(std::unique_ptr<PrimitiveSet<Aead>> aead_set)
-      : aead_set_(std::move(aead_set)) {}
+  util::StatusOr<std::unique_ptr<Aead>> Wrap(
+      std::unique_ptr<PrimitiveSet<Aead>> aead_set) const override;
 };
 
 }  // namespace tink
diff --git a/cc/aead/aead_wrapper_test.cc b/cc/aead/aead_wrapper_test.cc
new file mode 100644
index 0000000..f272f1a
--- /dev/null
+++ b/cc/aead/aead_wrapper_test.cc
@@ -0,0 +1,112 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/aead/aead_wrapper.h"
+#include "gtest/gtest.h"
+#include "tink/aead.h"
+#include "tink/primitive_set.h"
+#include "tink/util/status.h"
+#include "tink/util/test_util.h"
+
+using crypto::tink::test::DummyAead;
+using google::crypto::tink::Keyset;
+using google::crypto::tink::OutputPrefixType;
+
+namespace crypto {
+namespace tink {
+namespace {
+
+TEST(AeadSetWrapperTest, WrapNullptr) {
+  AeadWrapper wrapper;
+  auto aead_result = wrapper.Wrap(nullptr);
+  EXPECT_FALSE(aead_result.ok());
+  EXPECT_EQ(util::error::INTERNAL, aead_result.status().error_code());
+  EXPECT_PRED_FORMAT2(testing::IsSubstring, "non-NULL",
+                      aead_result.status().error_message());
+}
+
+TEST(AeadSetWrapperTest, WrapEmpty) {
+  AeadWrapper wrapper;
+  auto aead_result = wrapper.Wrap(absl::make_unique<PrimitiveSet<Aead>>());
+  EXPECT_FALSE(aead_result.ok());
+  EXPECT_EQ(util::error::INVALID_ARGUMENT, aead_result.status().error_code());
+  EXPECT_PRED_FORMAT2(testing::IsSubstring, "no primary",
+                      aead_result.status().error_message());
+}
+
+TEST(AeadSetWrapperTest, Basic) {
+  Keyset::Key* key;
+  Keyset keyset;
+
+  uint32_t key_id_0 = 1234543;
+  key = keyset.add_key();
+  key->set_output_prefix_type(OutputPrefixType::TINK);
+  key->set_key_id(key_id_0);
+
+  uint32_t key_id_1 = 726329;
+  key = keyset.add_key();
+  key->set_output_prefix_type(OutputPrefixType::LEGACY);
+  key->set_key_id(key_id_1);
+
+  uint32_t key_id_2 = 7213743;
+  key = keyset.add_key();
+  key->set_output_prefix_type(OutputPrefixType::TINK);
+  key->set_key_id(key_id_2);
+
+  std::string aead_name_0 = "aead0";
+  std::string aead_name_1 = "aead1";
+  std::string aead_name_2 = "aead2";
+  std::unique_ptr<PrimitiveSet<Aead>> aead_set(new PrimitiveSet<Aead>());
+  std::unique_ptr<Aead> aead = absl::make_unique<DummyAead>(aead_name_0);
+  auto entry_result = aead_set->AddPrimitive(std::move(aead), keyset.key(0));
+  ASSERT_TRUE(entry_result.ok());
+  aead = absl::make_unique<DummyAead>(aead_name_1);
+  entry_result = aead_set->AddPrimitive(std::move(aead), keyset.key(1));
+  ASSERT_TRUE(entry_result.ok());
+  aead = absl::make_unique<DummyAead>(aead_name_2);
+  entry_result = aead_set->AddPrimitive(std::move(aead), keyset.key(2));
+  ASSERT_TRUE(entry_result.ok());
+  // The last key is the primary.
+  aead_set->set_primary(entry_result.ValueOrDie());
+
+  // Wrap aead_set and test the resulting Aead.
+  AeadWrapper wrapper;
+  auto aead_result = wrapper.Wrap(std::move(aead_set));
+  EXPECT_TRUE(aead_result.ok()) << aead_result.status();
+  aead = std::move(aead_result.ValueOrDie());
+  std::string plaintext = "some_plaintext";
+  std::string aad = "some_aad";
+
+  auto encrypt_result = aead->Encrypt(plaintext, aad);
+  EXPECT_TRUE(encrypt_result.ok()) << encrypt_result.status();
+  std::string ciphertext = encrypt_result.ValueOrDie();
+  EXPECT_PRED_FORMAT2(testing::IsSubstring, aead_name_2, ciphertext);
+
+  auto decrypt_result = aead->Decrypt(ciphertext, aad);
+  EXPECT_TRUE(decrypt_result.ok()) << decrypt_result.status();
+  EXPECT_EQ(plaintext, decrypt_result.ValueOrDie());
+
+  decrypt_result = aead->Decrypt("some bad ciphertext", aad);
+  EXPECT_FALSE(decrypt_result.ok());
+  EXPECT_EQ(util::error::INVALID_ARGUMENT,
+            decrypt_result.status().error_code());
+  EXPECT_PRED_FORMAT2(testing::IsSubstring, "decryption failed",
+                      decrypt_result.status().error_message());
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/aead/aes_ctr_hmac_aead_key_manager.cc b/cc/aead/aes_ctr_hmac_aead_key_manager.cc
index dabd70f..3fdef97 100644
--- a/cc/aead/aes_ctr_hmac_aead_key_manager.cc
+++ b/cc/aead/aes_ctr_hmac_aead_key_manager.cc
@@ -18,6 +18,7 @@
 
 #include <map>
 
+#include "absl/base/casts.h"
 #include "tink/aead.h"
 #include "tink/key_manager.h"
 #include "tink/mac.h"
@@ -45,95 +46,49 @@
 using google::crypto::tink::AesCtrHmacAeadKeyFormat;
 using google::crypto::tink::HashType;
 using google::crypto::tink::KeyData;
-using google::crypto::tink::KeyTemplate;
-using portable_proto::MessageLite;
 
-class AesCtrHmacAeadKeyFactory : public KeyFactory {
+class AesCtrHmacAeadKeyFactory
+    : public KeyFactoryBase<AesCtrHmacAeadKey, AesCtrHmacAeadKeyFormat> {
  public:
-  // Generates a new random AesCtrHmacAeadKey, based on the specified
-  // 'key_format', which must contain AesCtrHmacAeadKeyFormat-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<portable_proto::MessageLite>>
-  NewKey(const portable_proto::MessageLite& key_format) const override;
+  AesCtrHmacAeadKeyFactory() = default;
 
-  // Generates a new random AesCtrHmacAeadKey, based on the specified
-  // 'serialized_key_format', which must contain AesCtrHmacAeadKeyFormat-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<portable_proto::MessageLite>>
-  NewKey(absl::string_view serialized_key_format) const override;
+  KeyData::KeyMaterialType key_material_type() const override {
+    return KeyData::SYMMETRIC;
+  }
 
-  // Generates a new random AesCtrHmacAeadKey, based on the specified
-  // 'serialized_key_format' (which must contain AesCtrHmacAeadKeyFormat-proto),
-  // and wraps it in a KeyData-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<google::crypto::tink::KeyData>>
-  NewKeyData(absl::string_view serialized_key_format) const override;
+ protected:
+  StatusOr<std::unique_ptr<AesCtrHmacAeadKey>> NewKeyFromFormat(
+      const AesCtrHmacAeadKeyFormat& aes_ctr_hmac_aead_key_format)
+      const override {
+    Status status =
+        AesCtrHmacAeadKeyManager::Validate(aes_ctr_hmac_aead_key_format);
+    if (!status.ok()) return status;
+
+    auto aes_ctr_hmac_aead_key = absl::make_unique<AesCtrHmacAeadKey>();
+    aes_ctr_hmac_aead_key->set_version(AesCtrHmacAeadKeyManager::kVersion);
+
+    // Generate AesCtrKey.
+    auto aes_ctr_key = aes_ctr_hmac_aead_key->mutable_aes_ctr_key();
+    aes_ctr_key->set_version(AesCtrHmacAeadKeyManager::kVersion);
+    *(aes_ctr_key->mutable_params()) =
+        aes_ctr_hmac_aead_key_format.aes_ctr_key_format().params();
+    aes_ctr_key->set_key_value(subtle::Random::GetRandomBytes(
+        aes_ctr_hmac_aead_key_format.aes_ctr_key_format().key_size()));
+
+    // Generate HmacKey.
+    auto hmac_key = aes_ctr_hmac_aead_key->mutable_hmac_key();
+    hmac_key->set_version(AesCtrHmacAeadKeyManager::kVersion);
+    *(hmac_key->mutable_params()) =
+        aes_ctr_hmac_aead_key_format.hmac_key_format().params();
+    hmac_key->set_key_value(subtle::Random::GetRandomBytes(
+        aes_ctr_hmac_aead_key_format.hmac_key_format().key_size()));
+
+    return absl::implicit_cast<StatusOr<std::unique_ptr<AesCtrHmacAeadKey>>>(
+        std::move(aes_ctr_hmac_aead_key));
+  }
 };
 
-StatusOr<std::unique_ptr<MessageLite>> AesCtrHmacAeadKeyFactory::NewKey(
-    const portable_proto::MessageLite& key_format) const {
-  std::string key_format_url = std::string(AesCtrHmacAeadKeyManager::kKeyTypePrefix)
-      + key_format.GetTypeName();
-  if (key_format_url != AesCtrHmacAeadKeyManager::kKeyFormatUrl) {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Key format proto '%s' is not supported by this manager.",
-                     key_format_url.c_str());
-  }
-  const AesCtrHmacAeadKeyFormat& aes_ctr_hmac_aead_key_format =
-        reinterpret_cast<const AesCtrHmacAeadKeyFormat&>(key_format);
-  Status status =
-      AesCtrHmacAeadKeyManager::Validate(aes_ctr_hmac_aead_key_format);
-  if (!status.ok()) return status;
-
-  std::unique_ptr<AesCtrHmacAeadKey> aes_ctr_hmac_aead_key(
-      new AesCtrHmacAeadKey());
-  aes_ctr_hmac_aead_key->set_version(AesCtrHmacAeadKeyManager::kVersion);
-
-  // Generate AesCtrKey.
-  auto aes_ctr_key = aes_ctr_hmac_aead_key->mutable_aes_ctr_key();
-  aes_ctr_key->set_version(AesCtrHmacAeadKeyManager::kVersion);
-  *(aes_ctr_key->mutable_params()) =
-      aes_ctr_hmac_aead_key_format.aes_ctr_key_format().params();
-  aes_ctr_key->set_key_value(subtle::Random::GetRandomBytes(
-      aes_ctr_hmac_aead_key_format.aes_ctr_key_format().key_size()));
-
-  // Generate HmacKey.
-  auto hmac_key = aes_ctr_hmac_aead_key->mutable_hmac_key();
-  hmac_key->set_version(AesCtrHmacAeadKeyManager::kVersion);
-  *(hmac_key->mutable_params()) =
-      aes_ctr_hmac_aead_key_format.hmac_key_format().params();
-  hmac_key->set_key_value(subtle::Random::GetRandomBytes(
-      aes_ctr_hmac_aead_key_format.hmac_key_format().key_size()));
-
-  std::unique_ptr<MessageLite> key = std::move(aes_ctr_hmac_aead_key);
-  return std::move(key);
-}
-
-StatusOr<std::unique_ptr<MessageLite>> AesCtrHmacAeadKeyFactory::NewKey(
-    absl::string_view serialized_key_format) const {
-  AesCtrHmacAeadKeyFormat key_format;
-  if (!key_format.ParseFromString(std::string(serialized_key_format))) {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Could not parse the passed string as proto '%s'.",
-                     AesCtrHmacAeadKeyManager::kKeyFormatUrl);
-  }
-  return NewKey(key_format);
-}
-
-StatusOr<std::unique_ptr<KeyData>> AesCtrHmacAeadKeyFactory::NewKeyData(
-    absl::string_view serialized_key_format) const {
-  auto new_key_result = NewKey(serialized_key_format);
-  if (!new_key_result.ok()) return new_key_result.status();
-  auto new_key = reinterpret_cast<const AesCtrHmacAeadKey&>(
-      *(new_key_result.ValueOrDie()));
-  std::unique_ptr<KeyData> key_data(new KeyData());
-  key_data->set_type_url(AesCtrHmacAeadKeyManager::kKeyType);
-  key_data->set_value(new_key.SerializeAsString());
-  key_data->set_key_material_type(KeyData::SYMMETRIC);
-  return std::move(key_data);
-}
-
 constexpr char AesCtrHmacAeadKeyManager::kHmacKeyType[];
-constexpr char AesCtrHmacAeadKeyManager::kKeyFormatUrl[];
-constexpr char AesCtrHmacAeadKeyManager::kKeyTypePrefix[];
-constexpr char AesCtrHmacAeadKeyManager::kKeyType[];
 constexpr uint32_t AesCtrHmacAeadKeyManager::kVersion;
 
 const int kMinKeySizeInBytes = 16;
@@ -141,52 +96,15 @@
 const int kMinTagSizeInBytes = 10;
 
 AesCtrHmacAeadKeyManager::AesCtrHmacAeadKeyManager()
-    : key_type_(kKeyType), key_factory_(new AesCtrHmacAeadKeyFactory()) {}
-
-const std::string& AesCtrHmacAeadKeyManager::get_key_type() const {
-  return key_type_;
-}
+    : key_factory_(absl::make_unique<AesCtrHmacAeadKeyFactory>()) {}
 
 const KeyFactory& AesCtrHmacAeadKeyManager::get_key_factory() const {
   return *key_factory_;
 }
 
-uint32_t AesCtrHmacAeadKeyManager::get_version() const {
-  return kVersion;
-}
+uint32_t AesCtrHmacAeadKeyManager::get_version() const { return kVersion; }
 
-StatusOr<std::unique_ptr<Aead>> AesCtrHmacAeadKeyManager::GetPrimitive(
-    const KeyData& key_data) const {
-  if (DoesSupport(key_data.type_url())) {
-    AesCtrHmacAeadKey aes_ctr_hmac_aead_key;
-    if (!aes_ctr_hmac_aead_key.ParseFromString(key_data.value())) {
-      return ToStatusF(util::error::INVALID_ARGUMENT,
-                       "Could not parse key_data.value as key type '%s'.",
-                       key_data.type_url().c_str());
-    }
-    return GetPrimitiveImpl(aes_ctr_hmac_aead_key);
-  } else {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Key type '%s' is not supported by this manager.",
-                     key_data.type_url().c_str());
-  }
-}
-
-StatusOr<std::unique_ptr<Aead>> AesCtrHmacAeadKeyManager::GetPrimitive(
-    const MessageLite& key) const {
-  std::string key_type = std::string(kKeyTypePrefix) + key.GetTypeName();
-  if (DoesSupport(key_type)) {
-    const AesCtrHmacAeadKey& aes_ctr_hmac_aead_key =
-        reinterpret_cast<const AesCtrHmacAeadKey&>(key);
-    return GetPrimitiveImpl(aes_ctr_hmac_aead_key);
-  } else {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Key type '%s' is not supported by this manager.",
-                     key_type.c_str());
-  }
-}
-
-StatusOr<std::unique_ptr<Aead>> AesCtrHmacAeadKeyManager::GetPrimitiveImpl(
+StatusOr<std::unique_ptr<Aead>> AesCtrHmacAeadKeyManager::GetPrimitiveFromKey(
     const AesCtrHmacAeadKey& aes_ctr_hmac_aead_key) const {
   Status status = Validate(aes_ctr_hmac_aead_key);
   if (!status.ok()) return status;
@@ -217,15 +135,9 @@
   // Validate AesCtrKey.
   auto aes_ctr_key = key.aes_ctr_key();
   uint32_t aes_key_size = aes_ctr_key.key_value().size();
-  if (aes_key_size < kMinKeySizeInBytes) {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Invalid AesCtrHmacAeadKey: AES key_value is too short.");
-  }
-  if (aes_key_size != 16 && aes_key_size != 32) {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Invalid AesCtrHmacAeadKey: AES key_value has %d bytes; "
-                     "supported sizes: 16 or 32 bytes.",
-                     aes_key_size);
+  status = ValidateAesKeySize(aes_key_size);
+  if (!status.ok()) {
+    return status;
   }
   if (aes_ctr_key.params().iv_size() < kMinIvSizeInBytes ||
       aes_ctr_key.params().iv_size() > 16) {
@@ -240,10 +152,9 @@
     const AesCtrHmacAeadKeyFormat& key_format) {
   // Validate AesCtrKeyFormat.
   auto aes_ctr_key_format = key_format.aes_ctr_key_format();
-  if (aes_ctr_key_format.key_size() < kMinKeySizeInBytes) {
-    return ToStatusF(
-        util::error::INVALID_ARGUMENT,
-        "Invalid AesCtrHmacAeadKeyFormat: AES key_size is too small.");
+  auto status = ValidateAesKeySize(aes_ctr_key_format.key_size());
+  if (!status.ok()) {
+    return status;
   }
   if (aes_ctr_key_format.params().iv_size() < kMinIvSizeInBytes ||
       aes_ctr_key_format.params().iv_size() > 16) {
diff --git a/cc/aead/aes_ctr_hmac_aead_key_manager.h b/cc/aead/aes_ctr_hmac_aead_key_manager.h
index 44c57f8..b5bf726 100644
--- a/cc/aead/aes_ctr_hmac_aead_key_manager.h
+++ b/cc/aead/aes_ctr_hmac_aead_key_manager.h
@@ -13,15 +13,15 @@
 // limitations under the License.
 //
 ///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_AEAD_AES_CTR_HMAC_AEAD_KEY_MANAGER_H_
+#define TINK_AEAD_AES_CTR_HMAC_AEAD_KEY_MANAGER_H_
 
 #include <algorithm>
 #include <vector>
 
-#ifndef TINK_AEAD_AES_CTR_HMAC_AEAD_KEY_MANAGER_H_
-#define TINK_AEAD_AES_CTR_HMAC_AEAD_KEY_MANAGER_H_
-
 #include "absl/strings/string_view.h"
 #include "tink/aead.h"
+#include "tink/core/key_manager_base.h"
 #include "tink/key_manager.h"
 #include "tink/util/errors.h"
 #include "tink/util/protobuf_helper.h"
@@ -33,29 +33,15 @@
 namespace crypto {
 namespace tink {
 
-class AesCtrHmacAeadKeyManager : public KeyManager<Aead> {
+class AesCtrHmacAeadKeyManager
+    : public KeyManagerBase<Aead, google::crypto::tink::AesCtrHmacAeadKey> {
  public:
-  static constexpr char kKeyType[] =
-      "type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey";
   static constexpr char kHmacKeyType[] =
       "type.googleapis.com/google.crypto.tink.HmacKey";
   static constexpr uint32_t kVersion = 0;
 
   AesCtrHmacAeadKeyManager();
 
-  // Constructs an instance of AES-CTR-HMAC-AEAD Aead for the given 'key_data',
-  // which must contain AesCtrHmacAeadKey-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<Aead>> GetPrimitive(
-      const google::crypto::tink::KeyData& key_data) const override;
-
-  // Constructs an instance of AES-CTR-HMAC-AEAD Aead for the given 'key',
-  // which must be AesCtrHmacAeadKey-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<Aead>> GetPrimitive(
-      const portable_proto::MessageLite& key) const override;
-
-  // Returns the type_url identifying the key type handled by this manager.
-  const std::string& get_key_type() const override;
-
   // Returns the version of this key manager.
   uint32_t get_version() const override;
 
@@ -65,14 +51,14 @@
 
   virtual ~AesCtrHmacAeadKeyManager() {}
 
+ protected:
+  crypto::tink::util::StatusOr<std::unique_ptr<Aead>> GetPrimitiveFromKey(
+      const google::crypto::tink::AesCtrHmacAeadKey& aes_ctr_hmac_aead_key)
+      const override;
+
  private:
   friend class AesCtrHmacAeadKeyFactory;
 
-  static constexpr char kKeyTypePrefix[] = "type.googleapis.com/";
-  static constexpr char kKeyFormatUrl[] =
-      "type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKeyFormat";
-
-  std::string key_type_;
   std::unique_ptr<KeyFactory> key_factory_;
 
   // Constructs an instance of AES-CTR-HMAC-AEAD Aead for the given 'key'.
diff --git a/cc/aead/aes_ctr_hmac_aead_key_manager_test.cc b/cc/aead/aes_ctr_hmac_aead_key_manager_test.cc
index 43b2b2b..1fe6ef3 100644
--- a/cc/aead/aes_ctr_hmac_aead_key_manager_test.cc
+++ b/cc/aead/aes_ctr_hmac_aead_key_manager_test.cc
@@ -35,7 +35,6 @@
 using google::crypto::tink::AesGcmKeyFormat;
 using google::crypto::tink::HashType;
 using google::crypto::tink::KeyData;
-using google::crypto::tink::KeyTemplate;
 
 namespace {
 
@@ -123,13 +122,6 @@
       if (len == 16 || len == 32) {
         EXPECT_TRUE(result.ok()) << result.status();
       } else {
-        if (len < 16) {
-          EXPECT_FALSE(result.ok());
-          EXPECT_EQ(util::error::INVALID_ARGUMENT,
-                    result.status().error_code());
-          EXPECT_PRED_FORMAT2(testing::IsSubstring, "too short",
-                              result.status().error_message());
-        } else {
           EXPECT_FALSE(result.ok());
           EXPECT_EQ(util::error::INVALID_ARGUMENT,
                     result.status().error_code());
@@ -139,7 +131,6 @@
           EXPECT_PRED_FORMAT2(testing::IsSubstring, "supported sizes",
                               result.status().error_message());
         }
-      }
     }
   }
 }
@@ -173,22 +164,13 @@
       if (len == 16 || len == 32) {
         EXPECT_TRUE(result.ok()) << result.status();
       } else {
-        if (len < 16) {
-          EXPECT_FALSE(result.ok());
-          EXPECT_EQ(util::error::INVALID_ARGUMENT,
-                    result.status().error_code());
-          EXPECT_PRED_FORMAT2(testing::IsSubstring, "too short",
-                              result.status().error_message());
-        } else {
-          EXPECT_FALSE(result.ok());
-          EXPECT_EQ(util::error::INVALID_ARGUMENT,
-                    result.status().error_code());
-          EXPECT_PRED_FORMAT2(testing::IsSubstring,
-                              std::to_string(len) + " bytes",
-                              result.status().error_message());
-          EXPECT_PRED_FORMAT2(testing::IsSubstring, "supported sizes",
-                              result.status().error_message());
-        }
+        EXPECT_FALSE(result.ok());
+        EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+        EXPECT_PRED_FORMAT2(testing::IsSubstring,
+                            std::to_string(len) + " bytes",
+                            result.status().error_message());
+        EXPECT_PRED_FORMAT2(testing::IsSubstring, "supported sizes",
+                            result.status().error_message());
       }
     }
   }
@@ -264,14 +246,17 @@
     auto result = key_factory.NewKey(key_format);
     EXPECT_FALSE(result.ok());
     EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
-    EXPECT_PRED_FORMAT2(testing::IsSubstring, "key_size",
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "8 bytes",
                         result.status().error_message());
-    EXPECT_PRED_FORMAT2(testing::IsSubstring, "too small",
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "supported sizes",
                         result.status().error_message());
   }
 
   {  // Bad AesCtrHmacAeadKeyFormat: small HMAC key_size.
     AesCtrHmacAeadKeyFormat key_format;
+    auto aes_ctr_key_format = key_format.mutable_aes_ctr_key_format();
+    aes_ctr_key_format->set_key_size(16);
+    aes_ctr_key_format->mutable_params()->set_iv_size(12);
     key_format.mutable_hmac_key_format()->set_key_size(8);
     auto result = key_factory.NewKey(key_format);
     EXPECT_FALSE(result.ok());
@@ -301,7 +286,7 @@
     auto key = std::move(result.ValueOrDie());
     EXPECT_EQ(key_type_prefix + key->GetTypeName(), aes_ctr_hmac_aead_key_type);
     std::unique_ptr<AesCtrHmacAeadKey> aes_ctr_hmac_aead_key(
-        reinterpret_cast<AesCtrHmacAeadKey*>(key.release()));
+        static_cast<AesCtrHmacAeadKey*>(key.release()));
     EXPECT_EQ(0, aes_ctr_hmac_aead_key->version());
     EXPECT_EQ(key_format.aes_ctr_key_format().key_size(),
               aes_ctr_hmac_aead_key->aes_ctr_key().key_value().size());
@@ -319,7 +304,7 @@
     auto key = std::move(result.ValueOrDie());
     EXPECT_EQ(key_type_prefix + key->GetTypeName(), aes_ctr_hmac_aead_key_type);
     std::unique_ptr<AesCtrHmacAeadKey> aes_ctr_hmac_aead_key(
-        reinterpret_cast<AesCtrHmacAeadKey*>(key.release()));
+        static_cast<AesCtrHmacAeadKey*>(key.release()));
     EXPECT_EQ(0, aes_ctr_hmac_aead_key->version());
     EXPECT_EQ(key_format.aes_ctr_key_format().key_size(),
               aes_ctr_hmac_aead_key->aes_ctr_key().key_value().size());
@@ -354,8 +339,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/aead/aes_eax_key_manager.cc b/cc/aead/aes_eax_key_manager.cc
index e9870fa..210455f 100644
--- a/cc/aead/aes_eax_key_manager.cc
+++ b/cc/aead/aes_eax_key_manager.cc
@@ -14,6 +14,7 @@
 
 #include "tink/aead/aes_eax_key_manager.h"
 
+#include "absl/base/casts.h"
 #include "absl/strings/string_view.h"
 #include "tink/aead.h"
 #include "tink/key_manager.h"
@@ -30,95 +31,41 @@
 namespace crypto {
 namespace tink {
 
+using crypto::tink::util::Status;
+using crypto::tink::util::StatusOr;
 using google::crypto::tink::AesEaxKey;
 using google::crypto::tink::AesEaxKeyFormat;
 using google::crypto::tink::KeyData;
-using google::crypto::tink::KeyTemplate;
-using portable_proto::MessageLite;
-using crypto::tink::util::Status;
-using crypto::tink::util::StatusOr;
 
-class AesEaxKeyFactory : public KeyFactory {
+class AesEaxKeyFactory : public KeyFactoryBase<AesEaxKey, AesEaxKeyFormat> {
  public:
   AesEaxKeyFactory() {}
 
-  // Generates a new random AesEaxKey, based on the specified 'key_format',
-  // which must contain AesEaxKeyFormat-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<portable_proto::MessageLite>>
-  NewKey(const portable_proto::MessageLite& key_format) const override;
+  KeyData::KeyMaterialType key_material_type() const override {
+    return KeyData::SYMMETRIC;
+  }
 
-  // Generates a new random AesEaxKey, based on the specified
-  // 'serialized_key_format', which must contain AesEaxKeyFormat-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<portable_proto::MessageLite>>
-  NewKey(absl::string_view serialized_key_format) const override;
+ protected:
+  StatusOr<std::unique_ptr<AesEaxKey>> NewKeyFromFormat(
+      const AesEaxKeyFormat& aes_eax_key_format) const override {
+    Status status = AesEaxKeyManager::Validate(aes_eax_key_format);
+    if (!status.ok()) return status;
 
-  // Generates a new random AesEaxKey, based on the specified
-  // 'serialized_key_format' (which must contain AesEaxKeyFormat-proto),
-  // and wraps it in a KeyData-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<google::crypto::tink::KeyData>>
-  NewKeyData(absl::string_view serialized_key_format) const override;
+    auto aes_eax_key = absl::make_unique<AesEaxKey>();
+    aes_eax_key->set_version(AesEaxKeyManager::kVersion);
+    aes_eax_key->set_key_value(
+        subtle::Random::GetRandomBytes(aes_eax_key_format.key_size()));
+    aes_eax_key->mutable_params()->set_iv_size(
+        aes_eax_key_format.params().iv_size());
+    return absl::implicit_cast<StatusOr<std::unique_ptr<AesEaxKey>>>(
+        std::move(aes_eax_key));
+  }
 };
 
-StatusOr<std::unique_ptr<MessageLite>> AesEaxKeyFactory::NewKey(
-    const portable_proto::MessageLite& key_format) const {
-  std::string key_format_url =
-      std::string(AesEaxKeyManager::kKeyTypePrefix) + key_format.GetTypeName();
-  if (key_format_url != AesEaxKeyManager::kKeyFormatUrl) {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Key format proto '%s' is not supported by this manager.",
-                     key_format_url.c_str());
-  }
-  const AesEaxKeyFormat& aes_eax_key_format =
-        reinterpret_cast<const AesEaxKeyFormat&>(key_format);
-  Status status = AesEaxKeyManager::Validate(aes_eax_key_format);
-  if (!status.ok()) return status;
-
-  // Generate AesEaxKey.
-  std::unique_ptr<AesEaxKey> aes_eax_key(new AesEaxKey());
-  aes_eax_key->set_version(AesEaxKeyManager::kVersion);
-  aes_eax_key->set_key_value(
-      subtle::Random::GetRandomBytes(aes_eax_key_format.key_size()));
-  aes_eax_key->mutable_params()
-      ->set_iv_size(aes_eax_key_format.params().iv_size());
-  std::unique_ptr<MessageLite> key = std::move(aes_eax_key);
-  return std::move(key);
-}
-
-StatusOr<std::unique_ptr<MessageLite>> AesEaxKeyFactory::NewKey(
-    absl::string_view serialized_key_format) const {
-  AesEaxKeyFormat key_format;
-  if (!key_format.ParseFromString(std::string(serialized_key_format))) {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Could not parse the passed string as proto '%s'.",
-                     AesEaxKeyManager::kKeyFormatUrl);
-  }
-  return NewKey(key_format);
-}
-
-StatusOr<std::unique_ptr<KeyData>> AesEaxKeyFactory::NewKeyData(
-    absl::string_view serialized_key_format) const {
-  auto new_key_result = NewKey(serialized_key_format);
-  if (!new_key_result.ok()) return new_key_result.status();
-  auto new_key = reinterpret_cast<const AesEaxKey&>(
-      *(new_key_result.ValueOrDie()));
-  std::unique_ptr<KeyData> key_data(new KeyData());
-  key_data->set_type_url(AesEaxKeyManager::kKeyType);
-  key_data->set_value(new_key.SerializeAsString());
-  key_data->set_key_material_type(KeyData::SYMMETRIC);
-  return std::move(key_data);
-}
-
-constexpr char AesEaxKeyManager::kKeyFormatUrl[];
-constexpr char AesEaxKeyManager::kKeyTypePrefix[];
-constexpr char AesEaxKeyManager::kKeyType[];
 constexpr uint32_t AesEaxKeyManager::kVersion;
 
 AesEaxKeyManager::AesEaxKeyManager()
-    : key_type_(kKeyType), key_factory_(new AesEaxKeyFactory()) {}
-
-const std::string& AesEaxKeyManager::get_key_type() const {
-  return key_type_;
-}
+    : key_factory_(absl::make_unique<AesEaxKeyFactory>()) {}
 
 uint32_t AesEaxKeyManager::get_version() const {
   return kVersion;
@@ -128,38 +75,8 @@
   return *key_factory_;
 }
 
-StatusOr<std::unique_ptr<Aead>>
-AesEaxKeyManager::GetPrimitive(const KeyData& key_data) const {
-  if (DoesSupport(key_data.type_url())) {
-    AesEaxKey aes_eax_key;
-    if (!aes_eax_key.ParseFromString(key_data.value())) {
-      return ToStatusF(util::error::INVALID_ARGUMENT,
-                       "Could not parse key_data.value as key type '%s'.",
-                       key_data.type_url().c_str());
-    }
-    return GetPrimitiveImpl(aes_eax_key);
-  } else {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Key type '%s' is not supported by this manager.",
-                     key_data.type_url().c_str());
-  }
-}
-
-StatusOr<std::unique_ptr<Aead>>
-AesEaxKeyManager::GetPrimitive(const MessageLite& key) const {
-  std::string key_type = std::string(kKeyTypePrefix) + key.GetTypeName();
-  if (DoesSupport(key_type)) {
-    const AesEaxKey& aes_eax_key = reinterpret_cast<const AesEaxKey&>(key);
-    return GetPrimitiveImpl(aes_eax_key);
-  } else {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Key type '%s' is not supported by this manager.",
-                     key_type.c_str());
-  }
-}
-
-StatusOr<std::unique_ptr<Aead>>
-AesEaxKeyManager::GetPrimitiveImpl(const AesEaxKey& aes_eax_key) const {
+StatusOr<std::unique_ptr<Aead>> AesEaxKeyManager::GetPrimitiveFromKey(
+    const AesEaxKey& aes_eax_key) const {
   Status status = Validate(aes_eax_key);
   if (!status.ok()) return status;
   auto aes_eax_result = subtle::AesEaxBoringSsl::New(
diff --git a/cc/aead/aes_eax_key_manager.h b/cc/aead/aes_eax_key_manager.h
index c3c131e..0d442a7 100644
--- a/cc/aead/aes_eax_key_manager.h
+++ b/cc/aead/aes_eax_key_manager.h
@@ -11,7 +11,6 @@
 // limitations under the License.
 //
 ///////////////////////////////////////////////////////////////////////////////
-
 #ifndef TINK_AEAD_AES_EAX_KEY_MANAGER_H_
 #define TINK_AEAD_AES_EAX_KEY_MANAGER_H_
 
@@ -20,6 +19,7 @@
 
 #include "absl/strings/string_view.h"
 #include "tink/aead.h"
+#include "tink/core/key_manager_base.h"
 #include "tink/key_manager.h"
 #include "tink/util/errors.h"
 #include "tink/util/protobuf_helper.h"
@@ -31,27 +31,13 @@
 namespace crypto {
 namespace tink {
 
-class AesEaxKeyManager : public KeyManager<Aead> {
+class AesEaxKeyManager
+    : public KeyManagerBase<Aead, google::crypto::tink::AesEaxKey> {
  public:
-  static constexpr char kKeyType[] =
-      "type.googleapis.com/google.crypto.tink.AesEaxKey";
   static constexpr uint32_t kVersion = 0;
 
   AesEaxKeyManager();
 
-  // Constructs an instance of AES-EAX Aead for the given 'key_data',
-  // which must contain AesEaxKey-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<Aead>> GetPrimitive(
-      const google::crypto::tink::KeyData& key_data) const override;
-
-  // Constructs an instance of AES-EAX Aead for the given 'key',
-  // which must be AesEaxKey-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<Aead>>
-  GetPrimitive(const portable_proto::MessageLite& key) const override;
-
-  // Returns the type_url identifying the key type handled by this manager.
-  const std::string& get_key_type() const override;
-
   // Returns the version of this key manager.
   uint32_t get_version() const override;
 
@@ -61,20 +47,15 @@
 
   virtual ~AesEaxKeyManager() {}
 
+ protected:
+  crypto::tink::util::StatusOr<std::unique_ptr<Aead>> GetPrimitiveFromKey(
+      const google::crypto::tink::AesEaxKey& aes_eax_key) const override;
+
  private:
   friend class AesEaxKeyFactory;
 
-  static constexpr char kKeyTypePrefix[] = "type.googleapis.com/";
-  static constexpr char kKeyFormatUrl[] =
-      "type.googleapis.com/google.crypto.tink.AesEaxKeyFormat";
-
-  std::string key_type_;
   std::unique_ptr<KeyFactory> key_factory_;
 
-  // Constructs an instance of AES-EAX Aead for the given 'key'.
-  crypto::tink::util::StatusOr<std::unique_ptr<Aead>>
-  GetPrimitiveImpl(const google::crypto::tink::AesEaxKey& aes_eax_key) const;
-
   static crypto::tink::util::Status Validate(
       const google::crypto::tink::AesEaxKey& key);
   static crypto::tink::util::Status Validate(
diff --git a/cc/aead/aes_eax_key_manager_test.cc b/cc/aead/aes_eax_key_manager_test.cc
index cb269da..28684fb 100644
--- a/cc/aead/aes_eax_key_manager_test.cc
+++ b/cc/aead/aes_eax_key_manager_test.cc
@@ -31,7 +31,6 @@
 using google::crypto::tink::AesGcmKey;
 using google::crypto::tink::AesGcmKeyFormat;
 using google::crypto::tink::KeyData;
-using google::crypto::tink::KeyTemplate;
 
 namespace {
 
@@ -322,9 +321,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/aead/aes_gcm_key_manager.cc b/cc/aead/aes_gcm_key_manager.cc
index 6ecbb73..17dd0a9 100644
--- a/cc/aead/aes_gcm_key_manager.cc
+++ b/cc/aead/aes_gcm_key_manager.cc
@@ -16,6 +16,7 @@
 
 #include "tink/aead/aes_gcm_key_manager.h"
 
+#include "absl/base/casts.h"
 #include "absl/strings/string_view.h"
 #include "tink/aead.h"
 #include "tink/key_manager.h"
@@ -32,95 +33,40 @@
 namespace crypto {
 namespace tink {
 
-using google::crypto::tink::AesGcmKey;
-using google::crypto::tink::AesGcmKeyFormat;
-using google::crypto::tink::KeyData;
-using google::crypto::tink::KeyTemplate;
-using portable_proto::MessageLite;
-using crypto::tink::util::Status;
-using crypto::tink::util::StatusOr;
+using ::crypto::tink::util::Status;
+using ::crypto::tink::util::StatusOr;
+using ::google::crypto::tink::AesGcmKey;
+using ::google::crypto::tink::AesGcmKeyFormat;
+using ::google::crypto::tink::KeyData;
 
-class AesGcmKeyFactory : public KeyFactory {
+class AesGcmKeyFactory : public KeyFactoryBase<AesGcmKey, AesGcmKeyFormat> {
  public:
   AesGcmKeyFactory() {}
 
-  // Generates a new random AesGcmKey, based on the specified 'key_format',
-  // which must contain AesGcmKeyFormat-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<portable_proto::MessageLite>>
-  NewKey(const portable_proto::MessageLite& key_format) const override;
+  KeyData::KeyMaterialType key_material_type() const override {
+    return KeyData::SYMMETRIC;
+  }
 
-  // Generates a new random AesGcmKey, based on the specified
-  // 'serialized_key_format', which must contain AesGcmKeyFormat-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<portable_proto::MessageLite>>
-  NewKey(absl::string_view serialized_key_format) const override;
-
-  // Generates a new random AesGcmKey, based on the specified
-  // 'serialized_key_format' (which must contain AesGcmKeyFormat-proto),
-  // and wraps it in a KeyData-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<google::crypto::tink::KeyData>>
-  NewKeyData(absl::string_view serialized_key_format) const override;
+ protected:
+  StatusOr<std::unique_ptr<AesGcmKey>> NewKeyFromFormat(
+      const AesGcmKeyFormat& aes_gcm_key_format) const override {
+    Status status = AesGcmKeyManager::Validate(aes_gcm_key_format);
+    if (!status.ok()) return status;
+    std::unique_ptr<AesGcmKey> aes_gcm_key(new AesGcmKey());
+    aes_gcm_key->set_version(AesGcmKeyManager::kVersion);
+    aes_gcm_key->set_key_value(
+        subtle::Random::GetRandomBytes(aes_gcm_key_format.key_size()));
+    return absl::implicit_cast<StatusOr<std::unique_ptr<AesGcmKey>>>(
+        std::move(aes_gcm_key));
+  }
 };
 
-StatusOr<std::unique_ptr<MessageLite>> AesGcmKeyFactory::NewKey(
-    const portable_proto::MessageLite& key_format) const {
-  std::string key_format_url =
-      std::string(AesGcmKeyManager::kKeyTypePrefix) + key_format.GetTypeName();
-  if (key_format_url != AesGcmKeyManager::kKeyFormatUrl) {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Key format proto '%s' is not supported by this manager.",
-                     key_format_url.c_str());
-  }
-  const AesGcmKeyFormat& aes_gcm_key_format =
-        reinterpret_cast<const AesGcmKeyFormat&>(key_format);
-  Status status = AesGcmKeyManager::Validate(aes_gcm_key_format);
-  if (!status.ok()) return status;
-
-  // Generate AesGcmKey.
-  std::unique_ptr<AesGcmKey> aes_gcm_key(new AesGcmKey());
-  aes_gcm_key->set_version(AesGcmKeyManager::kVersion);
-  aes_gcm_key->set_key_value(
-      subtle::Random::GetRandomBytes(aes_gcm_key_format.key_size()));
-  std::unique_ptr<MessageLite> key = std::move(aes_gcm_key);
-  return std::move(key);
-}
-
-StatusOr<std::unique_ptr<MessageLite>> AesGcmKeyFactory::NewKey(
-    absl::string_view serialized_key_format) const {
-  AesGcmKeyFormat key_format;
-  if (!key_format.ParseFromString(std::string(serialized_key_format))) {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Could not parse the passed string as proto '%s'.",
-                     AesGcmKeyManager::kKeyFormatUrl);
-  }
-  return NewKey(key_format);
-}
-
-StatusOr<std::unique_ptr<KeyData>> AesGcmKeyFactory::NewKeyData(
-    absl::string_view serialized_key_format) const {
-  auto new_key_result = NewKey(serialized_key_format);
-  if (!new_key_result.ok()) return new_key_result.status();
-  auto new_key = reinterpret_cast<const AesGcmKey&>(
-      *(new_key_result.ValueOrDie()));
-  std::unique_ptr<KeyData> key_data(new KeyData());
-  key_data->set_type_url(AesGcmKeyManager::kKeyType);
-  key_data->set_value(new_key.SerializeAsString());
-  key_data->set_key_material_type(KeyData::SYMMETRIC);
-  return std::move(key_data);
-}
-
-constexpr char AesGcmKeyManager::kKeyFormatUrl[];
-constexpr char AesGcmKeyManager::kKeyTypePrefix[];
-constexpr char AesGcmKeyManager::kKeyType[];
 constexpr uint32_t AesGcmKeyManager::kVersion;
 
 const int kMinKeySizeInBytes = 16;
 
 AesGcmKeyManager::AesGcmKeyManager()
-    : key_type_(kKeyType), key_factory_(new AesGcmKeyFactory()) {}
-
-const std::string& AesGcmKeyManager::get_key_type() const {
-  return key_type_;
-}
+    : key_factory_(absl::make_unique<AesGcmKeyFactory>()) {}
 
 uint32_t AesGcmKeyManager::get_version() const {
   return kVersion;
@@ -130,38 +76,8 @@
   return *key_factory_;
 }
 
-StatusOr<std::unique_ptr<Aead>>
-AesGcmKeyManager::GetPrimitive(const KeyData& key_data) const {
-  if (DoesSupport(key_data.type_url())) {
-    AesGcmKey aes_gcm_key;
-    if (!aes_gcm_key.ParseFromString(key_data.value())) {
-      return ToStatusF(util::error::INVALID_ARGUMENT,
-                       "Could not parse key_data.value as key type '%s'.",
-                       key_data.type_url().c_str());
-    }
-    return GetPrimitiveImpl(aes_gcm_key);
-  } else {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Key type '%s' is not supported by this manager.",
-                     key_data.type_url().c_str());
-  }
-}
-
-StatusOr<std::unique_ptr<Aead>>
-AesGcmKeyManager::GetPrimitive(const MessageLite& key) const {
-  std::string key_type = std::string(kKeyTypePrefix) + key.GetTypeName();
-  if (DoesSupport(key_type)) {
-    const AesGcmKey& aes_gcm_key = reinterpret_cast<const AesGcmKey&>(key);
-    return GetPrimitiveImpl(aes_gcm_key);
-  } else {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Key type '%s' is not supported by this manager.",
-                     key_type.c_str());
-  }
-}
-
-StatusOr<std::unique_ptr<Aead>>
-AesGcmKeyManager::GetPrimitiveImpl(const AesGcmKey& aes_gcm_key) const {
+StatusOr<std::unique_ptr<Aead>> AesGcmKeyManager::GetPrimitiveFromKey(
+    const AesGcmKey& aes_gcm_key) const {
   Status status = Validate(aes_gcm_key);
   if (!status.ok()) return status;
   auto aes_gcm_result = subtle::AesGcmBoringSsl::New(aes_gcm_key.key_value());
@@ -173,26 +89,12 @@
 Status AesGcmKeyManager::Validate(const AesGcmKey& key) {
   Status status = ValidateVersion(key.version(), kVersion);
   if (!status.ok()) return status;
-  uint32_t key_size = key.key_value().size();
-  if (key_size < kMinKeySizeInBytes) {
-      return ToStatusF(util::error::INVALID_ARGUMENT,
-                       "Invalid AesGcmKey: key_value is too short.");
-  }
-  if (key_size != 16 && key_size != 32) {
-      return ToStatusF(util::error::INVALID_ARGUMENT,
-                       "Invalid AesGcmKey: key_value has %d bytes; "
-                       "supported sizes: 16 or 32 bytes.", key_size);
-  }
-  return Status::OK;
+  return ValidateAesKeySize(key.key_value().size());
 }
 
 // static
 Status AesGcmKeyManager::Validate(const AesGcmKeyFormat& key_format) {
-  if (key_format.key_size() < kMinKeySizeInBytes) {
-      return ToStatusF(util::error::INVALID_ARGUMENT,
-                       "Invalid AesGcmKeyFormat: key_size is too small.");
-  }
-  return Status::OK;
+  return ValidateAesKeySize(key_format.key_size());
 }
 
 }  // namespace tink
diff --git a/cc/aead/aes_gcm_key_manager.h b/cc/aead/aes_gcm_key_manager.h
index fbdcc4b..5bb41d4 100644
--- a/cc/aead/aes_gcm_key_manager.h
+++ b/cc/aead/aes_gcm_key_manager.h
@@ -13,15 +13,15 @@
 // limitations under the License.
 //
 ///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_AEAD_AES_GCM_KEY_MANAGER_H_
+#define TINK_AEAD_AES_GCM_KEY_MANAGER_H_
 
 #include <algorithm>
 #include <vector>
 
-#ifndef TINK_AEAD_AES_GCM_KEY_MANAGER_H_
-#define TINK_AEAD_AES_GCM_KEY_MANAGER_H_
-
 #include "absl/strings/string_view.h"
 #include "tink/aead.h"
+#include "tink/core/key_manager_base.h"
 #include "tink/key_manager.h"
 #include "tink/util/errors.h"
 #include "tink/util/protobuf_helper.h"
@@ -33,27 +33,13 @@
 namespace crypto {
 namespace tink {
 
-class AesGcmKeyManager : public KeyManager<Aead> {
+class AesGcmKeyManager
+    : public KeyManagerBase<Aead, google::crypto::tink::AesGcmKey> {
  public:
-  static constexpr char kKeyType[] =
-      "type.googleapis.com/google.crypto.tink.AesGcmKey";
   static constexpr uint32_t kVersion = 0;
 
   AesGcmKeyManager();
 
-  // Constructs an instance of AES-GCM Aead for the given 'key_data',
-  // which must contain AesGcmKey-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<Aead>> GetPrimitive(
-      const google::crypto::tink::KeyData& key_data) const override;
-
-  // Constructs an instance of AES-GCM Aead for the given 'key',
-  // which must be AesGcmKey-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<Aead>>
-  GetPrimitive(const portable_proto::MessageLite& key) const override;
-
-  // Returns the type_url identifying the key type handled by this manager.
-  const std::string& get_key_type() const override;
-
   // Returns the version of this key manager.
   uint32_t get_version() const override;
 
@@ -63,20 +49,15 @@
 
   virtual ~AesGcmKeyManager() {}
 
+ protected:
+  crypto::tink::util::StatusOr<std::unique_ptr<Aead>> GetPrimitiveFromKey(
+      const google::crypto::tink::AesGcmKey& key) const override;
+
  private:
   friend class AesGcmKeyFactory;
 
-  static constexpr char kKeyTypePrefix[] = "type.googleapis.com/";
-  static constexpr char kKeyFormatUrl[] =
-      "type.googleapis.com/google.crypto.tink.AesGcmKeyFormat";
-
-  std::string key_type_;
   std::unique_ptr<KeyFactory> key_factory_;
 
-  // Constructs an instance of AES-GCM Aead for the given 'key'.
-  crypto::tink::util::StatusOr<std::unique_ptr<Aead>>
-  GetPrimitiveImpl(const google::crypto::tink::AesGcmKey& key) const;
-
   static crypto::tink::util::Status Validate(
       const google::crypto::tink::AesGcmKey& key);
   static crypto::tink::util::Status Validate(
diff --git a/cc/aead/aes_gcm_key_manager_test.cc b/cc/aead/aes_gcm_key_manager_test.cc
index 76d7ae2..e069b44 100644
--- a/cc/aead/aes_gcm_key_manager_test.cc
+++ b/cc/aead/aes_gcm_key_manager_test.cc
@@ -33,7 +33,6 @@
 using google::crypto::tink::AesGcmKey;
 using google::crypto::tink::AesGcmKeyFormat;
 using google::crypto::tink::KeyData;
-using google::crypto::tink::KeyTemplate;
 
 namespace {
 
@@ -106,22 +105,13 @@
       if (len == 16 || len == 32) {
         EXPECT_TRUE(result.ok()) << result.status();
       } else {
-        if (len < 16) {
-          EXPECT_FALSE(result.ok());
-          EXPECT_EQ(util::error::INVALID_ARGUMENT,
-                    result.status().error_code());
-          EXPECT_PRED_FORMAT2(testing::IsSubstring, "too short",
-                              result.status().error_message());
-        } else {
-          EXPECT_FALSE(result.ok());
-          EXPECT_EQ(util::error::INVALID_ARGUMENT,
-                    result.status().error_code());
-          EXPECT_PRED_FORMAT2(testing::IsSubstring,
-                              std::to_string(len) + " bytes",
-                              result.status().error_message());
-          EXPECT_PRED_FORMAT2(testing::IsSubstring, "supported sizes",
-                              result.status().error_message());
-        }
+        EXPECT_FALSE(result.ok());
+        EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+        EXPECT_PRED_FORMAT2(testing::IsSubstring,
+                            std::to_string(len) + " bytes",
+                            result.status().error_message());
+        EXPECT_PRED_FORMAT2(testing::IsSubstring, "supported sizes",
+                            result.status().error_message());
       }
     }
   }
@@ -150,13 +140,6 @@
       if (len == 16 || len == 32) {
         EXPECT_TRUE(result.ok()) << result.status();
       } else {
-        if (len < 16) {
-          EXPECT_FALSE(result.ok());
-          EXPECT_EQ(util::error::INVALID_ARGUMENT,
-                    result.status().error_code());
-          EXPECT_PRED_FORMAT2(testing::IsSubstring, "too short",
-                              result.status().error_message());
-        } else {
           EXPECT_FALSE(result.ok());
           EXPECT_EQ(util::error::INVALID_ARGUMENT,
                     result.status().error_code());
@@ -165,7 +148,6 @@
                               result.status().error_message());
           EXPECT_PRED_FORMAT2(testing::IsSubstring, "supported sizes",
                               result.status().error_message());
-        }
       }
     }
   }
@@ -235,9 +217,9 @@
     auto result = key_factory.NewKey(key_format);
     EXPECT_FALSE(result.ok());
     EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
-    EXPECT_PRED_FORMAT2(testing::IsSubstring, "key_size",
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "8 bytes",
                         result.status().error_message());
-    EXPECT_PRED_FORMAT2(testing::IsSubstring, "too small",
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "supported sizes",
                         result.status().error_message());
   }
 }
@@ -286,9 +268,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/aead/xchacha20_poly1305_key_manager.cc b/cc/aead/xchacha20_poly1305_key_manager.cc
new file mode 100644
index 0000000..79a5fca
--- /dev/null
+++ b/cc/aead/xchacha20_poly1305_key_manager.cc
@@ -0,0 +1,101 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/aead/xchacha20_poly1305_key_manager.h"
+
+#include "absl/strings/string_view.h"
+#include "tink/aead.h"
+#include "tink/key_manager.h"
+#include "tink/subtle/random.h"
+#include "tink/subtle/xchacha20_poly1305_boringssl.h"
+#include "tink/util/errors.h"
+#include "tink/util/protobuf_helper.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "tink/util/validation.h"
+#include "proto/empty.pb.h"
+#include "proto/tink.pb.h"
+#include "proto/xchacha20_poly1305.pb.h"
+
+namespace crypto {
+namespace tink {
+
+using crypto::tink::util::Status;
+using crypto::tink::util::StatusOr;
+using google::crypto::tink::KeyData;
+using google::crypto::tink::XChaCha20Poly1305Key;
+
+const int kKeySizeInBytes = 32;
+
+class XChaCha20Poly1305KeyFactory
+    : public KeyFactoryBase<XChaCha20Poly1305Key, google::crypto::tink::Empty> {
+ public:
+  XChaCha20Poly1305KeyFactory() {}
+
+  KeyData::KeyMaterialType key_material_type() const override {
+    return KeyData::SYMMETRIC;
+  }
+
+ protected:
+  StatusOr<std::unique_ptr<XChaCha20Poly1305Key>> NewKeyFromFormat(
+      const google::crypto::tink::Empty&) const override {
+    auto xchacha20_poly1305_key = absl::make_unique<XChaCha20Poly1305Key>();
+    xchacha20_poly1305_key->set_version(XChaCha20Poly1305KeyManager::kVersion);
+    xchacha20_poly1305_key->set_key_value(
+        subtle::Random::GetRandomBytes(kKeySizeInBytes));
+    return std::move(xchacha20_poly1305_key);
+  }
+};
+
+constexpr uint32_t XChaCha20Poly1305KeyManager::kVersion;
+
+XChaCha20Poly1305KeyManager::XChaCha20Poly1305KeyManager()
+    : key_factory_(absl::make_unique<XChaCha20Poly1305KeyFactory>()) {}
+
+uint32_t XChaCha20Poly1305KeyManager::get_version() const { return kVersion; }
+
+const KeyFactory& XChaCha20Poly1305KeyManager::get_key_factory() const {
+  return *key_factory_;
+}
+
+StatusOr<std::unique_ptr<Aead>>
+XChaCha20Poly1305KeyManager::GetPrimitiveFromKey(
+    const XChaCha20Poly1305Key& xchacha20_poly1305_key) const {
+  Status status = Validate(xchacha20_poly1305_key);
+  if (!status.ok()) return status;
+  auto xchacha20_poly1305_result = subtle::XChacha20Poly1305BoringSsl::New(
+      xchacha20_poly1305_key.key_value());
+  if (!xchacha20_poly1305_result.ok())
+    return xchacha20_poly1305_result.status();
+  return std::move(xchacha20_poly1305_result.ValueOrDie());
+}
+
+// static
+Status XChaCha20Poly1305KeyManager::Validate(const XChaCha20Poly1305Key& key) {
+  Status status = ValidateVersion(key.version(), kVersion);
+  if (!status.ok()) return status;
+  uint32_t key_size = key.key_value().size();
+  if (key_size != 32) {
+    return ToStatusF(util::error::INVALID_ARGUMENT,
+                     "Invalid XChaCha20Poly1305Key: key_value has %d bytes; "
+                     "supported size: 32 bytes.",
+                     key_size);
+  }
+  return Status::OK;
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/aead/xchacha20_poly1305_key_manager.h b/cc/aead/xchacha20_poly1305_key_manager.h
new file mode 100644
index 0000000..aa10f56
--- /dev/null
+++ b/cc/aead/xchacha20_poly1305_key_manager.h
@@ -0,0 +1,68 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_AEAD_XCHACHA20_POLY1305_KEY_MANAGER_H_
+#define TINK_AEAD_XCHACHA20_POLY1305_KEY_MANAGER_H_
+
+#include <algorithm>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "tink/aead.h"
+#include "tink/core/key_manager_base.h"
+#include "tink/key_manager.h"
+#include "tink/util/errors.h"
+#include "tink/util/protobuf_helper.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "proto/tink.pb.h"
+#include "proto/xchacha20_poly1305.pb.h"
+
+namespace crypto {
+namespace tink {
+
+class XChaCha20Poly1305KeyManager
+    : public KeyManagerBase<Aead, google::crypto::tink::XChaCha20Poly1305Key> {
+ public:
+  static constexpr uint32_t kVersion = 0;
+
+  XChaCha20Poly1305KeyManager();
+
+  // Returns the version of this key manager.
+  uint32_t get_version() const override;
+
+  // Returns a factory that generates keys of the key type
+  // handled by this manager.
+  const KeyFactory& get_key_factory() const override;
+
+  virtual ~XChaCha20Poly1305KeyManager() {}
+
+ protected:
+  crypto::tink::util::StatusOr<std::unique_ptr<Aead>> GetPrimitiveFromKey(
+      const google::crypto::tink::XChaCha20Poly1305Key& key) const override;
+
+ private:
+  friend class XChaCha20Poly1305KeyFactory;
+
+  std::unique_ptr<KeyFactory> key_factory_;
+
+  static crypto::tink::util::Status Validate(
+      const google::crypto::tink::XChaCha20Poly1305Key& key);
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_AEAD_XCHACHA20_POLY1305_KEY_MANAGER_H_
diff --git a/cc/aead/xchacha20_poly1305_key_manager_test.cc b/cc/aead/xchacha20_poly1305_key_manager_test.cc
new file mode 100644
index 0000000..f87ab95
--- /dev/null
+++ b/cc/aead/xchacha20_poly1305_key_manager_test.cc
@@ -0,0 +1,230 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/aead/xchacha20_poly1305_key_manager.h"
+
+#include "gtest/gtest.h"
+#include "tink/aead.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "proto/aes_eax.pb.h"
+#include "proto/common.pb.h"
+#include "proto/tink.pb.h"
+#include "proto/xchacha20_poly1305.pb.h"
+
+namespace crypto {
+namespace tink {
+
+using google::crypto::tink::AesEaxKey;
+using google::crypto::tink::KeyData;
+using google::crypto::tink::XChaCha20Poly1305Key;
+
+namespace {
+
+class XChaCha20Poly1305KeyManagerTest : public ::testing::Test {
+ protected:
+  std::string key_type_prefix = "type.googleapis.com/";
+  std::string xchaha20_poly1305_key_type =
+      "type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key";
+};
+
+TEST_F(XChaCha20Poly1305KeyManagerTest, testBasic) {
+  XChaCha20Poly1305KeyManager key_manager;
+
+  EXPECT_EQ(0, key_manager.get_version());
+  EXPECT_EQ("type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key",
+            key_manager.get_key_type());
+  EXPECT_TRUE(key_manager.DoesSupport(key_manager.get_key_type()));
+}
+
+TEST_F(XChaCha20Poly1305KeyManagerTest, testKeyDataErrors) {
+  XChaCha20Poly1305KeyManager key_manager;
+
+  {  // Bad key type.
+    KeyData key_data;
+    std::string bad_key_type =
+        "type.googleapis.com/google.crypto.tink.SomeOtherKey";
+    key_data.set_type_url(bad_key_type);
+    auto result = key_manager.GetPrimitive(key_data);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "not supported",
+                        result.status().error_message());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, bad_key_type,
+                        result.status().error_message());
+  }
+
+  {  // Bad key value.
+    KeyData key_data;
+    key_data.set_type_url(xchaha20_poly1305_key_type);
+    key_data.set_value("some bad serialized proto");
+    auto result = key_manager.GetPrimitive(key_data);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "not parse",
+                        result.status().error_message());
+  }
+
+  {  // Bad version.
+    KeyData key_data;
+    XChaCha20Poly1305Key key;
+    key.set_version(1);
+    key_data.set_type_url(xchaha20_poly1305_key_type);
+    key_data.set_value(key.SerializeAsString());
+    auto result = key_manager.GetPrimitive(key_data);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "version",
+                        result.status().error_message());
+  }
+
+  {  // Bad key_value size (supported size: 32).
+    for (int len = 0; len < 42; len++) {
+      XChaCha20Poly1305Key key;
+      key.set_version(0);
+      key.set_key_value(std::string(len, 'a'));
+      KeyData key_data;
+      key_data.set_type_url(xchaha20_poly1305_key_type);
+      key_data.set_value(key.SerializeAsString());
+      auto result = key_manager.GetPrimitive(key_data);
+      if (len == 32) {
+        EXPECT_TRUE(result.ok()) << result.status();
+      } else {
+        EXPECT_FALSE(result.ok());
+        EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+        EXPECT_PRED_FORMAT2(testing::IsSubstring,
+                            std::to_string(len) + " bytes",
+                            result.status().error_message());
+        EXPECT_PRED_FORMAT2(testing::IsSubstring, "supported size",
+                            result.status().error_message());
+      }
+    }
+  }
+}
+
+TEST_F(XChaCha20Poly1305KeyManagerTest, testKeyMessageErrors) {
+  XChaCha20Poly1305KeyManager key_manager;
+
+  {  // Bad protobuffer.
+    AesEaxKey key;
+    auto result = key_manager.GetPrimitive(key);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "AesEaxKey",
+                        result.status().error_message());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "not supported",
+                        result.status().error_message());
+  }
+
+  {  // Bad key_value size (supported size: 32).
+    for (int len = 0; len < 42; len++) {
+      XChaCha20Poly1305Key key;
+      key.set_version(0);
+      key.set_key_value(std::string(len, 'a'));
+      auto result = key_manager.GetPrimitive(key);
+      if (len == 32) {
+        EXPECT_TRUE(result.ok()) << result.status();
+      } else {
+        EXPECT_FALSE(result.ok());
+        EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+        EXPECT_PRED_FORMAT2(testing::IsSubstring,
+                            std::to_string(len) + " bytes",
+                            result.status().error_message());
+        EXPECT_PRED_FORMAT2(testing::IsSubstring, "supported size",
+                            result.status().error_message());
+      }
+    }
+  }
+}
+
+TEST_F(XChaCha20Poly1305KeyManagerTest, testPrimitives) {
+  std::string plaintext = "some plaintext";
+  std::string aad = "some aad";
+  XChaCha20Poly1305KeyManager key_manager;
+  XChaCha20Poly1305Key key;
+
+  key.set_version(0);
+  key.set_key_value("32 bytes of key 0123456789abcdef");
+
+  {  // Using key message only.
+    auto result = key_manager.GetPrimitive(key);
+    EXPECT_TRUE(result.ok()) << result.status();
+    auto xchaha20_poly1305 = std::move(result.ValueOrDie());
+    auto encrypt_result = xchaha20_poly1305->Encrypt(plaintext, aad);
+    EXPECT_TRUE(encrypt_result.ok()) << encrypt_result.status();
+    auto decrypt_result =
+        xchaha20_poly1305->Decrypt(encrypt_result.ValueOrDie(), aad);
+    EXPECT_TRUE(decrypt_result.ok()) << decrypt_result.status();
+    EXPECT_EQ(plaintext, decrypt_result.ValueOrDie());
+  }
+
+  {  // Using KeyData proto.
+    KeyData key_data;
+    key_data.set_type_url(xchaha20_poly1305_key_type);
+    key_data.set_value(key.SerializeAsString());
+    auto result = key_manager.GetPrimitive(key_data);
+    EXPECT_TRUE(result.ok()) << result.status();
+    auto xchaha20_poly1305 = std::move(result.ValueOrDie());
+    auto encrypt_result = xchaha20_poly1305->Encrypt(plaintext, aad);
+    EXPECT_TRUE(encrypt_result.ok()) << encrypt_result.status();
+    auto decrypt_result =
+        xchaha20_poly1305->Decrypt(encrypt_result.ValueOrDie(), aad);
+    EXPECT_TRUE(decrypt_result.ok()) << decrypt_result.status();
+    EXPECT_EQ(plaintext, decrypt_result.ValueOrDie());
+  }
+}
+
+TEST_F(XChaCha20Poly1305KeyManagerTest, testNewKeyBasic) {
+  XChaCha20Poly1305KeyManager key_manager;
+  const KeyFactory& key_factory = key_manager.get_key_factory();
+  { // Via NewKey(format_proto).
+    auto result = key_factory.NewKey(nullptr /* ignored */);
+    EXPECT_TRUE(result.ok()) << result.status();
+    auto key = std::move(result.ValueOrDie());
+    EXPECT_EQ(key_type_prefix + key->GetTypeName(), xchaha20_poly1305_key_type);
+    std::unique_ptr<XChaCha20Poly1305Key> xchaha20_poly1305_key(
+        static_cast<XChaCha20Poly1305Key*>(key.release()));
+    EXPECT_EQ(0, xchaha20_poly1305_key->version());
+    EXPECT_EQ(32, xchaha20_poly1305_key->key_value().size());
+  }
+
+  { // Via NewKey(serialized_format_proto).
+    auto result = key_factory.NewKey("" /* ignored */);
+    EXPECT_TRUE(result.ok()) << result.status();
+    auto key = std::move(result.ValueOrDie());
+    EXPECT_EQ(key_type_prefix + key->GetTypeName(), xchaha20_poly1305_key_type);
+    std::unique_ptr<XChaCha20Poly1305Key> xchaha20_poly1305_key(
+        static_cast<XChaCha20Poly1305Key*>(key.release()));
+    EXPECT_EQ(0, xchaha20_poly1305_key->version());
+    EXPECT_EQ(32, xchaha20_poly1305_key->key_value().size());
+  }
+
+  { // Via NewKeyData(serialized_format_proto).
+    auto result = key_factory.NewKeyData("" /* ignored */);
+    EXPECT_TRUE(result.ok()) << result.status();
+    auto key_data = std::move(result.ValueOrDie());
+    EXPECT_EQ(xchaha20_poly1305_key_type, key_data->type_url());
+    EXPECT_EQ(KeyData::SYMMETRIC, key_data->key_material_type());
+    XChaCha20Poly1305Key xchaha20_poly1305_key;
+    EXPECT_TRUE(xchaha20_poly1305_key.ParseFromString(key_data->value()));
+    EXPECT_EQ(0, xchaha20_poly1305_key.version());
+    EXPECT_EQ(32, xchaha20_poly1305_key.key_value().size());
+  }
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/binary_keyset_reader.h b/cc/binary_keyset_reader.h
index fe49d53..d0404bb 100644
--- a/cc/binary_keyset_reader.h
+++ b/cc/binary_keyset_reader.h
@@ -45,7 +45,7 @@
   ReadEncrypted() override;
 
  private:
-  BinaryKeysetReader(absl::string_view serialized_keyset)
+  explicit BinaryKeysetReader(absl::string_view serialized_keyset)
       : serialized_keyset_(serialized_keyset) {}
 
   std::string serialized_keyset_;
diff --git a/cc/binary_keyset_writer.h b/cc/binary_keyset_writer.h
index f7dd715..6e3103f 100644
--- a/cc/binary_keyset_writer.h
+++ b/cc/binary_keyset_writer.h
@@ -43,7 +43,7 @@
   Write(const google::crypto::tink::EncryptedKeyset& encrypted_keyset) override;
 
  private:
-  BinaryKeysetWriter(std::unique_ptr<std::ostream> destination_stream)
+  explicit BinaryKeysetWriter(std::unique_ptr<std::ostream> destination_stream)
       : destination_stream_(std::move(destination_stream)) {}
 
   std::unique_ptr<std::ostream> destination_stream_;
diff --git a/cc/config.h b/cc/config.h
index 4145d6e..cc56848 100644
--- a/cc/config.h
+++ b/cc/config.h
@@ -65,6 +65,10 @@
   static crypto::tink::util::Status Register(
       const google::crypto::tink::RegistryConfig& config);
 
+  // Registers primitive wrappers for the entry given in KeyTypeEntry.
+  static crypto::tink::util::Status RegisterWrapper(
+      absl::string_view lowercase_primitive_name);
+
  private:
   static crypto::tink::util::Status Validate(
       const google::crypto::tink::KeyTypeEntry& entry);
@@ -87,9 +91,8 @@
   auto key_manager_result = catalogue->GetKeyManager(
       entry.type_url(), entry.primitive_name(), entry.key_manager_version());
   if (!key_manager_result.ok()) return key_manager_result.status();
-  return Registry::RegisterKeyManager<P>(
-      key_manager_result.ValueOrDie().release(),
-      entry.new_key_allowed());
+  return Registry::RegisterKeyManager(
+      std::move(key_manager_result.ValueOrDie()), entry.new_key_allowed());
 }
 
 }  // namespace tink
diff --git a/cc/config/BUILD.bazel b/cc/config/BUILD.bazel
index 7a36a46..26ff2b6 100644
--- a/cc/config/BUILD.bazel
+++ b/cc/config/BUILD.bazel
@@ -9,6 +9,7 @@
     deps = [
         "//cc:config",
         "//cc:key_manager",
+        "//cc/daead:deterministic_aead_config",
         "//cc/hybrid:hybrid_config",
         "//cc/signature:signature_config",
         "//cc/util:status",
@@ -30,6 +31,7 @@
         "//cc:aead",
         "//cc:catalogue",
         "//cc:config",
+        "//cc:deterministic_aead",
         "//cc:hybrid_decrypt",
         "//cc:hybrid_encrypt",
         "//cc:mac",
diff --git a/cc/config/CMakeLists.txt b/cc/config/CMakeLists.txt
deleted file mode 100644
index fc552a1..0000000
--- a/cc/config/CMakeLists.txt
+++ /dev/null
@@ -1,55 +0,0 @@
-# Copyright 2018 The Fuchsia Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# Library: 'tink_config_tink_config'
-add_library(tink_config_tink_config tink_config.cc tink_config.h)
-tink_export_hdrs(tink_config.h)
-add_dependencies(
-  tink_config_tink_config
-  tink_config
-  tink_key_manager
-  tink_hybrid_hybrid_config
-  tink_signature_signature_config
-  tink_util_status
-  tink_proto_config_lib
-)
-target_link_libraries(
-  tink_config_tink_config
-  tink_config
-  tink_key_manager
-  tink_hybrid_hybrid_config
-  tink_signature_signature_config
-  tink_util_status
-  tink_proto_config_lib
-)
-
-# Test Binary: 'tink_config_tink_config_test'
-add_executable(tink_config_tink_config_test tink_config_test.cc)
-add_dependencies(
-  tink_config_tink_config_test
-  tink_config_tink_config
-  tink_aead
-  tink_catalogue
-  tink_config
-  tink_hybrid_decrypt
-  tink_hybrid_encrypt
-  tink_mac
-  tink_registry
-  tink_util_status
-)
-add_dependencies(tink_config_tink_config_test build_external_projects)
-target_link_libraries(
-  tink_config_tink_config_test
-  tink_config_tink_config
-  tink_aead
-  tink_catalogue
-  tink_config
-  tink_hybrid_decrypt
-  tink_hybrid_encrypt
-  tink_mac
-  tink_registry
-  tink_util_status
-  gtest gtest_main
-)
-
diff --git a/cc/config/tink_config.cc b/cc/config/tink_config.cc
index 5baff98..6435d4d 100644
--- a/cc/config/tink_config.cc
+++ b/cc/config/tink_config.cc
@@ -19,6 +19,7 @@
 #include "tink/config.h"
 #include "tink/key_manager.h"
 #include "tink/registry.h"
+#include "tink/daead/deterministic_aead_config.h"
 #include "tink/hybrid/hybrid_config.h"
 #include "tink/signature/signature_config.h"
 #include "tink/util/status.h"
@@ -33,6 +34,7 @@
       new google::crypto::tink::RegistryConfig();
   config->MergeFrom(HybridConfig::Latest());  // includes Mac & Aead
   config->MergeFrom(SignatureConfig::Latest());
+  config->MergeFrom(DeterministicAeadConfig::Latest());
   config->set_config_name("TINK");
   return config;
 }
@@ -49,7 +51,9 @@
 util::Status TinkConfig::Register() {
   auto status = HybridConfig::Register();  // includes Mac & Aead
   if (!status.ok()) return status;
-  return SignatureConfig::Register();
+  status = SignatureConfig::Register();
+  if (!status.ok()) return status;
+  return DeterministicAeadConfig::Register();
 }
 
 }  // namespace tink
diff --git a/cc/config/tink_config_test.cc b/cc/config/tink_config_test.cc
index 5059fb0..db9d8a6 100644
--- a/cc/config/tink_config_test.cc
+++ b/cc/config/tink_config_test.cc
@@ -16,9 +16,11 @@
 
 #include "tink/config/tink_config.h"
 
+#include "gtest/gtest.h"
 #include "tink/aead.h"
 #include "tink/catalogue.h"
 #include "tink/config.h"
+#include "tink/deterministic_aead.h"
 #include "tink/hybrid_decrypt.h"
 #include "tink/hybrid_encrypt.h"
 #include "tink/mac.h"
@@ -26,7 +28,6 @@
 #include "tink/public_key_verify.h"
 #include "tink/registry.h"
 #include "tink/util/status.h"
-#include "gtest/gtest.h"
 
 namespace crypto {
 namespace tink {
@@ -37,122 +38,166 @@
   DummyHybridDecryptCatalogue() {}
 
   crypto::tink::util::StatusOr<std::unique_ptr<KeyManager<HybridDecrypt>>>
-  GetKeyManager(const std::string& type_url,
-                const std::string& primitive_name,
+  GetKeyManager(const std::string& type_url, const std::string& primitive_name,
                 uint32_t min_version) const override {
     return util::Status::UNKNOWN;
   }
 };
 
-
 class TinkConfigTest : public ::testing::Test {
  protected:
-  void SetUp() override {
-    Registry::Reset();
-  }
+  void SetUp() override { Registry::Reset(); }
 };
 
+typedef struct KeyTypeEntry {
+  std::string catalogue_name;
+  std::string primitive_name;
+  std::string type_url;
+  bool new_key_allowed;
+  int key_manager_version;
+} KeyTypeEntry;
+
 TEST_F(TinkConfigTest, testBasic) {
-  std::string public_key_sign_key_type =
-      "type.googleapis.com/google.crypto.tink.EcdsaPrivateKey";
-  std::string public_key_verify_key_type =
-      "type.googleapis.com/google.crypto.tink.EcdsaPublicKey";
-  std::string hybrid_decrypt_key_type =
-      "type.googleapis.com/google.crypto.tink.EciesAeadHkdfPrivateKey";
-  std::string hybrid_encrypt_key_type =
-      "type.googleapis.com/google.crypto.tink.EciesAeadHkdfPublicKey";
-  std::string aes_ctr_hmac_aead_key_type =
-      "type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey";
-  std::string aes_eax_key_type =
-      "type.googleapis.com/google.crypto.tink.AesEaxKey";
-  std::string aes_gcm_key_type =
-      "type.googleapis.com/google.crypto.tink.AesGcmKey";
-  std::string hmac_key_type =
-      "type.googleapis.com/google.crypto.tink.HmacKey";
+  std::vector<KeyTypeEntry> all_key_type_entries;
+
+  std::vector<KeyTypeEntry> mac_key_type_entries;
+  mac_key_type_entries.push_back(
+      {"TinkMac", "Mac", "type.googleapis.com/google.crypto.tink.HmacKey", true,
+       0});
+  all_key_type_entries.insert(std::end(all_key_type_entries),
+                              std::begin(mac_key_type_entries),
+                              std::end(mac_key_type_entries));
+
+  std::vector<KeyTypeEntry> aead_key_type_entries;
+  aead_key_type_entries.push_back(
+      {"TinkAead", "Aead",
+       "type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey", true, 0});
+  aead_key_type_entries.push_back(
+      {"TinkAead", "Aead", "type.googleapis.com/google.crypto.tink.AesGcmKey",
+       true, 0});
+  aead_key_type_entries.push_back(
+      {"TinkAead", "Aead", "type.googleapis.com/google.crypto.tink.AesEaxKey",
+       true, 0});
+  aead_key_type_entries.push_back(
+      {"TinkAead", "Aead",
+       "type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key", true, 0});
+  all_key_type_entries.insert(std::end(all_key_type_entries),
+                              std::begin(aead_key_type_entries),
+                              std::end(aead_key_type_entries));
+
+  std::vector<KeyTypeEntry> hybrid_key_type_entries;
+  hybrid_key_type_entries.push_back(
+      {"TinkHybridDecrypt", "HybridDecrypt",
+       "type.googleapis.com/google.crypto.tink.EciesAeadHkdfPrivateKey", true,
+       0});
+  hybrid_key_type_entries.push_back(
+      {"TinkHybridEncrypt", "HybridEncrypt",
+       "type.googleapis.com/google.crypto.tink.EciesAeadHkdfPublicKey", true,
+       0});
+  all_key_type_entries.insert(std::end(all_key_type_entries),
+                              std::begin(hybrid_key_type_entries),
+                              std::end(hybrid_key_type_entries));
+
+  std::vector<KeyTypeEntry> signature_key_type_entries;
+  signature_key_type_entries.push_back(
+      {"TinkPublicKeySign", "PublicKeySign",
+       "type.googleapis.com/google.crypto.tink.EcdsaPrivateKey", true, 0});
+  signature_key_type_entries.push_back(
+      {"TinkPublicKeyVerify", "PublicKeyVerify",
+       "type.googleapis.com/google.crypto.tink.EcdsaPublicKey", true, 0});
+  signature_key_type_entries.push_back(
+      {"TinkPublicKeySign", "PublicKeySign",
+       "type.googleapis.com/google.crypto.tink.Ed25519PrivateKey", true, 0});
+  signature_key_type_entries.push_back(
+      {"TinkPublicKeyVerify", "PublicKeyVerify",
+       "type.googleapis.com/google.crypto.tink.Ed25519PublicKey", true, 0});
+  signature_key_type_entries.push_back(
+      {"TinkPublicKeySign", "PublicKeySign",
+       "type.googleapis.com/google.crypto.tink.RsaSsaPssPrivateKey", true, 0});
+  signature_key_type_entries.push_back(
+      {"TinkPublicKeyVerify", "PublicKeyVerify",
+       "type.googleapis.com/google.crypto.tink.RsaSsaPssPublicKey", true, 0});
+  signature_key_type_entries.push_back(
+      {"TinkPublicKeySign", "PublicKeySign",
+       "type.googleapis.com/google.crypto.tink.RsaSsaPkcs1PrivateKey", true,
+       0});
+  signature_key_type_entries.push_back(
+      {"TinkPublicKeyVerify", "PublicKeyVerify",
+       "type.googleapis.com/google.crypto.tink.RsaSsaPkcs1PublicKey", true, 0});
+  all_key_type_entries.insert(std::end(all_key_type_entries),
+                              std::begin(signature_key_type_entries),
+                              std::end(signature_key_type_entries));
+
+  std::vector<KeyTypeEntry> daead_key_type_entries;
+  daead_key_type_entries.push_back(
+      {"TinkDeterministicAead", "DeterministicAead",
+       "type.googleapis.com/google.crypto.tink.AesSivKey", true, 0});
+  all_key_type_entries.insert(std::end(all_key_type_entries),
+                              std::begin(daead_key_type_entries),
+                              std::end(daead_key_type_entries));
+
   auto& config = TinkConfig::Latest();
 
-  EXPECT_EQ(8, TinkConfig::Latest().entry_size());
+  EXPECT_EQ(all_key_type_entries.size(), TinkConfig::Latest().entry_size());
 
-  EXPECT_EQ("TinkMac", config.entry(0).catalogue_name());
-  EXPECT_EQ("Mac", config.entry(0).primitive_name());
-  EXPECT_EQ(hmac_key_type, config.entry(0).type_url());
-  EXPECT_EQ(true, config.entry(0).new_key_allowed());
-  EXPECT_EQ(0, config.entry(0).key_manager_version());
-
-  EXPECT_EQ("TinkAead", config.entry(1).catalogue_name());
-  EXPECT_EQ("Aead", config.entry(1).primitive_name());
-  EXPECT_EQ(aes_ctr_hmac_aead_key_type, config.entry(1).type_url());
-  EXPECT_EQ(true, config.entry(1).new_key_allowed());
-  EXPECT_EQ(0, config.entry(1).key_manager_version());
-
-  EXPECT_EQ("TinkAead", config.entry(2).catalogue_name());
-  EXPECT_EQ("Aead", config.entry(2).primitive_name());
-  EXPECT_EQ(aes_gcm_key_type, config.entry(2).type_url());
-  EXPECT_EQ(true, config.entry(2).new_key_allowed());
-  EXPECT_EQ(0, config.entry(2).key_manager_version());
-
-  EXPECT_EQ("TinkAead", config.entry(3).catalogue_name());
-  EXPECT_EQ("Aead", config.entry(3).primitive_name());
-  EXPECT_EQ(aes_eax_key_type, config.entry(3).type_url());
-  EXPECT_EQ(true, config.entry(3).new_key_allowed());
-  EXPECT_EQ(0, config.entry(3).key_manager_version());
-
-  EXPECT_EQ("TinkHybridDecrypt", config.entry(4).catalogue_name());
-  EXPECT_EQ("HybridDecrypt", config.entry(4).primitive_name());
-  EXPECT_EQ(hybrid_decrypt_key_type, config.entry(4).type_url());
-  EXPECT_EQ(true, config.entry(4).new_key_allowed());
-  EXPECT_EQ(0, config.entry(3).key_manager_version());
-
-  EXPECT_EQ("TinkHybridEncrypt", config.entry(5).catalogue_name());
-  EXPECT_EQ("HybridEncrypt", config.entry(5).primitive_name());
-  EXPECT_EQ(hybrid_encrypt_key_type, config.entry(5).type_url());
-  EXPECT_EQ(true, config.entry(5).new_key_allowed());
-  EXPECT_EQ(0, config.entry(5).key_manager_version());
-
-  EXPECT_EQ("TinkPublicKeySign", config.entry(6).catalogue_name());
-  EXPECT_EQ("PublicKeySign", config.entry(6).primitive_name());
-  EXPECT_EQ(public_key_sign_key_type, config.entry(6).type_url());
-  EXPECT_EQ(true, config.entry(6).new_key_allowed());
-  EXPECT_EQ(0, config.entry(6).key_manager_version());
-
-  EXPECT_EQ("TinkPublicKeyVerify", config.entry(7).catalogue_name());
-  EXPECT_EQ("PublicKeyVerify", config.entry(7).primitive_name());
-  EXPECT_EQ(public_key_verify_key_type, config.entry(7).type_url());
-  EXPECT_EQ(true, config.entry(7).new_key_allowed());
-  EXPECT_EQ(0, config.entry(7).key_manager_version());
+  int i = 0;
+  for (const auto& key_type_entry : all_key_type_entries) {
+    EXPECT_EQ(key_type_entry.catalogue_name, config.entry(i).catalogue_name());
+    EXPECT_EQ(key_type_entry.primitive_name, config.entry(i).primitive_name());
+    EXPECT_EQ(key_type_entry.type_url, config.entry(i).type_url());
+    EXPECT_EQ(key_type_entry.new_key_allowed,
+              config.entry(i).new_key_allowed());
+    EXPECT_EQ(key_type_entry.key_manager_version,
+              config.entry(i).key_manager_version());
+    i++;
+  }
 
   // No key manager before registration.
-  {
-    auto manager_result = Registry::get_key_manager<Aead>(aes_gcm_key_type);
-    EXPECT_FALSE(manager_result.ok());
-    EXPECT_EQ(util::error::NOT_FOUND, manager_result.status().error_code());
-  }
-  {
-    auto manager_result = Registry::get_key_manager<Mac>(hmac_key_type);
-    EXPECT_FALSE(manager_result.ok());
-    EXPECT_EQ(util::error::NOT_FOUND, manager_result.status().error_code());
-  }
-  {
+  for (const auto& key_type_entry : aead_key_type_entries) {
     auto manager_result =
-        Registry::get_key_manager<HybridEncrypt>(hybrid_encrypt_key_type);
+        Registry::get_key_manager<Aead>(key_type_entry.type_url);
     EXPECT_FALSE(manager_result.ok());
     EXPECT_EQ(util::error::NOT_FOUND, manager_result.status().error_code());
   }
-  {
+  for (const auto& key_type_entry : mac_key_type_entries) {
     auto manager_result =
-        Registry::get_key_manager<HybridDecrypt>(hybrid_decrypt_key_type);
+        Registry::get_key_manager<Mac>(key_type_entry.type_url);
     EXPECT_FALSE(manager_result.ok());
     EXPECT_EQ(util::error::NOT_FOUND, manager_result.status().error_code());
   }
-  {
-    auto manager_result =
-        Registry::get_key_manager<PublicKeySign>(public_key_sign_key_type);
-    EXPECT_FALSE(manager_result.ok());
-    EXPECT_EQ(util::error::NOT_FOUND, manager_result.status().error_code());
+  for (const auto& key_type_entry : hybrid_key_type_entries) {
+    if (key_type_entry.catalogue_name == "TinkHybridEncrypt") {
+      // HybridEncrypt
+      auto manager_result =
+          Registry::get_key_manager<HybridEncrypt>(key_type_entry.type_url);
+      EXPECT_FALSE(manager_result.ok());
+      EXPECT_EQ(util::error::NOT_FOUND, manager_result.status().error_code());
+    } else {
+      // HybridDecrypt
+      auto manager_result =
+          Registry::get_key_manager<HybridDecrypt>(key_type_entry.type_url);
+      EXPECT_FALSE(manager_result.ok());
+      EXPECT_EQ(util::error::NOT_FOUND, manager_result.status().error_code());
+    }
   }
-  {
+  for (const auto& key_type_entry : signature_key_type_entries) {
+    if (key_type_entry.catalogue_name == "TinkPublicKeySign") {
+      // PublicKeySign
+      auto manager_result =
+          Registry::get_key_manager<PublicKeySign>(key_type_entry.type_url);
+      EXPECT_FALSE(manager_result.ok());
+      EXPECT_EQ(util::error::NOT_FOUND, manager_result.status().error_code());
+    } else {
+      // PublicKeyVerify
+      auto manager_result =
+          Registry::get_key_manager<PublicKeyVerify>(key_type_entry.type_url);
+      EXPECT_FALSE(manager_result.ok());
+      EXPECT_EQ(util::error::NOT_FOUND, manager_result.status().error_code());
+    }
+  }
+  for (const auto& key_type_entry : daead_key_type_entries) {
     auto manager_result =
-        Registry::get_key_manager<PublicKeyVerify>(public_key_verify_key_type);
+        Registry::get_key_manager<DeterministicAead>(key_type_entry.type_url);
     EXPECT_FALSE(manager_result.ok());
     EXPECT_EQ(util::error::NOT_FOUND, manager_result.status().error_code());
   }
@@ -160,45 +205,62 @@
   // Registration of standard key types works.
   auto status = TinkConfig::Register();
   EXPECT_TRUE(status.ok()) << status;
-  {
-    auto manager_result = Registry::get_key_manager<Aead>(aes_gcm_key_type);
-    EXPECT_TRUE(manager_result.ok()) << manager_result.status();
-    EXPECT_TRUE(manager_result.ValueOrDie()->DoesSupport(aes_gcm_key_type));
-  }
-  {
-    auto manager_result = Registry::get_key_manager<Mac>(hmac_key_type);
-    EXPECT_TRUE(manager_result.ok()) << manager_result.status();
-    EXPECT_TRUE(manager_result.ValueOrDie()->DoesSupport(hmac_key_type));
-  }
-  {
+  for (const auto& key_type_entry : aead_key_type_entries) {
     auto manager_result =
-        Registry::get_key_manager<HybridEncrypt>(hybrid_encrypt_key_type);
+        Registry::get_key_manager<Aead>(key_type_entry.type_url);
     EXPECT_TRUE(manager_result.ok()) << manager_result.status();
-    EXPECT_TRUE(manager_result.ValueOrDie()->DoesSupport(
-        hybrid_encrypt_key_type));
+    EXPECT_TRUE(
+        manager_result.ValueOrDie()->DoesSupport(key_type_entry.type_url));
   }
-  {
+
+  for (const auto& key_type_entry : mac_key_type_entries) {
     auto manager_result =
-        Registry::get_key_manager<HybridDecrypt>(hybrid_decrypt_key_type);
+        Registry::get_key_manager<Mac>(key_type_entry.type_url);
     EXPECT_TRUE(manager_result.ok()) << manager_result.status();
-    EXPECT_TRUE(manager_result.ValueOrDie()->DoesSupport(
-        hybrid_decrypt_key_type));
+    EXPECT_TRUE(
+        manager_result.ValueOrDie()->DoesSupport(key_type_entry.type_url));
   }
-  {
+
+  for (const auto& key_type_entry : hybrid_key_type_entries) {
+    if (key_type_entry.catalogue_name == "TinkHybridEncrypt") {
+      auto manager_result =
+          Registry::get_key_manager<HybridEncrypt>(key_type_entry.type_url);
+      EXPECT_TRUE(manager_result.ok()) << manager_result.status();
+      EXPECT_TRUE(
+          manager_result.ValueOrDie()->DoesSupport(key_type_entry.type_url));
+    } else {
+      auto manager_result =
+          Registry::get_key_manager<HybridDecrypt>(key_type_entry.type_url);
+      EXPECT_TRUE(manager_result.ok()) << manager_result.status();
+      EXPECT_TRUE(
+          manager_result.ValueOrDie()->DoesSupport(key_type_entry.type_url));
+    }
+  }
+
+  for (const auto& key_type_entry : signature_key_type_entries) {
+    if (key_type_entry.catalogue_name == "TinkPublicKeySign") {
+      auto manager_result =
+          Registry::get_key_manager<PublicKeySign>(key_type_entry.type_url);
+      EXPECT_TRUE(manager_result.ok()) << manager_result.status();
+      EXPECT_TRUE(
+          manager_result.ValueOrDie()->DoesSupport(key_type_entry.type_url));
+    } else {
+      auto manager_result =
+          Registry::get_key_manager<PublicKeyVerify>(key_type_entry.type_url);
+      EXPECT_TRUE(manager_result.ok()) << manager_result.status();
+      EXPECT_TRUE(
+          manager_result.ValueOrDie()->DoesSupport(key_type_entry.type_url));
+    }
+  }
+
+  for (const auto& key_type_entry : daead_key_type_entries) {
     auto manager_result =
-        Registry::get_key_manager<PublicKeySign>(public_key_sign_key_type);
+        Registry::get_key_manager<DeterministicAead>(key_type_entry.type_url);
     EXPECT_TRUE(manager_result.ok()) << manager_result.status();
-    EXPECT_TRUE(manager_result.ValueOrDie()->DoesSupport(
-        public_key_sign_key_type));
+    EXPECT_TRUE(
+        manager_result.ValueOrDie()->DoesSupport(key_type_entry.type_url));
   }
-  {
-    auto manager_result =
-        Registry::get_key_manager<PublicKeyVerify>(public_key_verify_key_type);
-    EXPECT_TRUE(manager_result.ok()) << manager_result.status();
-    EXPECT_TRUE(manager_result.ValueOrDie()->DoesSupport(
-        public_key_verify_key_type));
-  }
-}
+}  // namespace
 
 TEST_F(TinkConfigTest, testRegister) {
   std::string key_type = "type.googleapis.com/google.crypto.tink.AesGcmKey";
@@ -233,9 +295,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/core/CMakeLists.txt b/cc/core/CMakeLists.txt
index e69de29..2915aa0 100644
--- a/cc/core/CMakeLists.txt
+++ b/cc/core/CMakeLists.txt
@@ -0,0 +1,67 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+# CC Library : registry_impl
+add_library(tink_cc_core_registry_impl registry_impl.h registry_impl.cc)
+tink_export_hdrs(registry_impl.h)
+add_dependencies(
+  tink_cc_core_registry_impl
+  tink_cc_catalogue
+  tink_cc_key_manager
+  tink_cc_primitive_set
+  tink_cc_primitive_wrapper
+  tink_cc_util_errors
+  tink_cc_util_protobuf_helper
+  tink_cc_util_status
+  tink_cc_util_statusor
+  tink_cc_util_validation
+  tink_proto_tink_lib
+)
+target_link_libraries(
+  tink_cc_core_registry_impl
+  tink_cc_catalogue
+  tink_cc_key_manager
+  tink_cc_primitive_set
+  tink_cc_primitive_wrapper
+  tink_cc_util_errors
+  tink_cc_util_protobuf_helper
+  tink_cc_util_status
+  tink_cc_util_statusor
+  tink_cc_util_validation
+  tink_proto_tink_lib
+  absl::base
+  absl::strings
+  absl::synchronization
+)
+
+# CC Library : key_manager_base
+add_library(tink_cc_core_key_manager_base key_manager_base.h)
+set_target_properties(
+  tink_cc_core_key_manager_base
+  PROPERTIES
+  LINKER_LANGUAGE
+  CXX
+)
+tink_export_hdrs(key_manager_base.h)
+add_dependencies(
+  tink_cc_core_key_manager_base
+  tink_cc_key_manager
+  tink_cc_util_constants
+  tink_cc_util_errors
+  tink_cc_util_statusor
+  tink_proto_tink_lib
+)
+target_link_libraries(
+  tink_cc_core_key_manager_base
+  tink_cc_key_manager
+  tink_cc_util_constants
+  tink_cc_util_errors
+  tink_cc_util_statusor
+  tink_proto_tink_lib
+  absl::base
+  absl::memory
+  absl::strings
+)
+
diff --git a/cc/core/binary_keyset_reader_test.cc b/cc/core/binary_keyset_reader_test.cc
index 016b07a..984a4db 100644
--- a/cc/core/binary_keyset_reader_test.cc
+++ b/cc/core/binary_keyset_reader_test.cc
@@ -205,13 +205,6 @@
   }
 }
 
-
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/core/binary_keyset_writer_test.cc b/cc/core/binary_keyset_writer_test.cc
index 276c513..5e90204 100644
--- a/cc/core/binary_keyset_writer_test.cc
+++ b/cc/core/binary_keyset_writer_test.cc
@@ -122,9 +122,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/core/cleartext_keyset_handle.cc b/cc/core/cleartext_keyset_handle.cc
index e71a8af..34bdbb6 100644
--- a/cc/core/cleartext_keyset_handle.cc
+++ b/cc/core/cleartext_keyset_handle.cc
@@ -51,6 +51,5 @@
   return keyset_handle.get_keyset();
 }
 
-
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/core/cleartext_keyset_handle_test.cc b/cc/core/cleartext_keyset_handle_test.cc
index 0b34f7d..8adea29 100644
--- a/cc/core/cleartext_keyset_handle_test.cc
+++ b/cc/core/cleartext_keyset_handle_test.cc
@@ -69,13 +69,6 @@
   }
 }
 
-
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/core/config.cc b/cc/core/config.cc
index f7368e1..ed58693 100644
--- a/cc/core/config.cc
+++ b/cc/core/config.cc
@@ -16,13 +16,21 @@
 
 #include "tink/config.h"
 
-#include "tink/mac.h"
+#include "absl/strings/ascii.h"
 #include "tink/aead.h"
+#include "tink/aead/aead_wrapper.h"
+#include "tink/daead/deterministic_aead_wrapper.h"
+#include "tink/deterministic_aead.h"
+#include "tink/hybrid/hybrid_decrypt_wrapper.h"
+#include "tink/hybrid/hybrid_encrypt_wrapper.h"
 #include "tink/hybrid_decrypt.h"
 #include "tink/hybrid_encrypt.h"
+#include "tink/mac.h"
+#include "tink/mac/mac_wrapper.h"
 #include "tink/public_key_sign.h"
 #include "tink/public_key_verify.h"
-#include "absl/strings/ascii.h"
+#include "tink/signature/public_key_sign_wrapper.h"
+#include "tink/signature/public_key_verify_wrapper.h"
 #include "tink/util/errors.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
@@ -78,6 +86,8 @@
       status = Register<Mac>(entry);
     } else if (primitive_name == "aead") {
       status = Register<Aead>(entry);
+    } else if (primitive_name == "deterministicaead") {
+      status = Register<DeterministicAead>(entry);
     } else if (primitive_name == "hybriddecrypt") {
       status = Register<HybridDecrypt>(entry);
     } else if (primitive_name == "hybridencrypt") {
@@ -95,9 +105,43 @@
                          );
     }
     if (!status.ok()) return status;
+    status = RegisterWrapper(primitive_name);
+    if (!status.ok()) return status;
   }
   return util::Status::OK;
 }
 
+// static
+util::Status Config::RegisterWrapper(
+    absl::string_view lowercase_primitive_name) {
+  if (lowercase_primitive_name == "mac") {
+    return Registry::RegisterPrimitiveWrapper(absl::make_unique<MacWrapper>());
+  } else if (lowercase_primitive_name == "aead") {
+    return Registry::RegisterPrimitiveWrapper(absl::make_unique<AeadWrapper>());
+  } else if (lowercase_primitive_name == "deterministicaead") {
+    return Registry::RegisterPrimitiveWrapper(
+        absl::make_unique<DeterministicAeadWrapper>());
+  } else if (lowercase_primitive_name == "hybriddecrypt") {
+    return Registry::RegisterPrimitiveWrapper(
+        absl::make_unique<HybridDecryptWrapper>());
+  } else if (lowercase_primitive_name == "hybridencrypt") {
+    return Registry::RegisterPrimitiveWrapper(
+        absl::make_unique<HybridEncryptWrapper>());
+  } else if (lowercase_primitive_name == "publickeysign") {
+    return Registry::RegisterPrimitiveWrapper(
+        absl::make_unique<PublicKeySignWrapper>());
+  } else if (lowercase_primitive_name == "publickeyverify") {
+    return Registry::RegisterPrimitiveWrapper(
+        absl::make_unique<PublicKeyVerifyWrapper>());
+  } else {
+    return crypto::tink::util::Status(
+        crypto::tink::util::error::INVALID_ARGUMENT,
+        absl::StrCat("Cannot register primitive wrapper for non-standard "
+                     "primitive ",
+                     lowercase_primitive_name,
+                     " (call Registry::RegisterPrimitiveWrapper directly)"));
+  }
+}
+
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/core/config_test.cc b/cc/core/config_test.cc
index e5478a3..70f9d2d 100644
--- a/cc/core/config_test.cc
+++ b/cc/core/config_test.cc
@@ -53,9 +53,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/core/crypto_format_test.cc b/cc/core/crypto_format_test.cc
index b555c7e..d9f1d96 100644
--- a/cc/core/crypto_format_test.cc
+++ b/cc/core/crypto_format_test.cc
@@ -98,9 +98,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/core/json_keyset_reader_test.cc b/cc/core/json_keyset_reader_test.cc
index 3494072..f9e88b1 100644
--- a/cc/core/json_keyset_reader_test.cc
+++ b/cc/core/json_keyset_reader_test.cc
@@ -277,8 +277,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/core/json_keyset_writer_test.cc b/cc/core/json_keyset_writer_test.cc
index ffc0e00..03ed049 100644
--- a/cc/core/json_keyset_writer_test.cc
+++ b/cc/core/json_keyset_writer_test.cc
@@ -242,9 +242,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/core/key_manager.cc b/cc/core/key_manager.cc
new file mode 100644
index 0000000..e98787c
--- /dev/null
+++ b/cc/core/key_manager.cc
@@ -0,0 +1,52 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+#include "tink/key_manager.h"
+#include "absl/memory/memory.h"
+
+namespace crypto {
+namespace tink {
+
+// A key factory which always fails.
+class AlwaysFailingKeyFactory : public KeyFactory {
+ public:
+  AlwaysFailingKeyFactory() = delete;
+  explicit AlwaysFailingKeyFactory(const crypto::tink::util::Status& status)
+      : status_(status) {}
+
+  crypto::tink::util::StatusOr<std::unique_ptr<portable_proto::MessageLite>>
+  NewKey(const portable_proto::MessageLite& key_format) const override {
+    return status_;
+  }
+
+  crypto::tink::util::StatusOr<std::unique_ptr<portable_proto::MessageLite>>
+  NewKey(absl::string_view serialized_key_format) const override {
+    return status_;
+  }
+
+  crypto::tink::util::StatusOr<std::unique_ptr<google::crypto::tink::KeyData>>
+  NewKeyData(absl::string_view serialized_key_format) const override {
+    return status_;
+  }
+
+ private:
+  crypto::tink::util::Status status_;
+};
+std::unique_ptr<KeyFactory> KeyFactory::AlwaysFailingFactory(
+    const crypto::tink::util::Status& status) {
+  return absl::make_unique<AlwaysFailingKeyFactory>(status);
+}
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/core/key_manager_base.h b/cc/core/key_manager_base.h
new file mode 100644
index 0000000..d44a9ef
--- /dev/null
+++ b/cc/core/key_manager_base.h
@@ -0,0 +1,141 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_CORE_KEY_MANAGER_BASE_H_
+#define TINK_CORE_KEY_MANAGER_BASE_H_
+
+#include <memory>
+#include <string>
+
+#include "absl/base/casts.h"
+#include "absl/memory/memory.h"
+#include "absl/strings/str_cat.h"
+#include "tink/key_manager.h"
+#include "tink/util/constants.h"
+#include "tink/util/errors.h"
+#include "tink/util/statusor.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+
+template <typename KeyProto, typename KeyFormatProto>
+class KeyFactoryBase : public virtual KeyFactory {
+ public:
+  KeyFactoryBase() {}
+
+  crypto::tink::util::StatusOr<std::unique_ptr<portable_proto::MessageLite>>
+  NewKey(const portable_proto::MessageLite& key_format) const override {
+    if (key_format.GetTypeName() != KeyFormatProto().GetTypeName()) {
+      return crypto::tink::util::Status(
+          util::error::INVALID_ARGUMENT,
+          absl::StrCat("Key format proto '", key_format.GetTypeName(),
+                       "' is not supported by this manager."));
+    }
+    crypto::tink::util::StatusOr<std::unique_ptr<KeyProto>> new_key_result =
+        NewKeyFromFormat(static_cast<const KeyFormatProto&>(key_format));
+    if (!new_key_result.ok()) return new_key_result.status();
+    return absl::implicit_cast<std::unique_ptr<portable_proto::MessageLite>>(
+        std::move(new_key_result.ValueOrDie()));
+  }
+
+  crypto::tink::util::StatusOr<std::unique_ptr<portable_proto::MessageLite>>
+  NewKey(absl::string_view serialized_key_format) const override {
+    KeyFormatProto key_format;
+    if (!key_format.ParseFromString(std::string(serialized_key_format))) {
+      return crypto::tink::util::Status(
+          util::error::INVALID_ARGUMENT,
+          absl::StrCat("Could not parse the passed string as proto '",
+                       KeyFormatProto().GetTypeName(), "'."));
+    }
+    return NewKey(key_format);
+  }
+
+  crypto::tink::util::StatusOr<std::unique_ptr<google::crypto::tink::KeyData>>
+  NewKeyData(absl::string_view serialized_key_format) const override {
+    auto new_key_result = NewKey(serialized_key_format);
+    if (!new_key_result.ok()) return new_key_result.status();
+    auto new_key =
+        static_cast<const KeyProto&>(*(new_key_result.ValueOrDie()));
+    auto key_data = absl::make_unique<google::crypto::tink::KeyData>();
+    key_data->set_type_url(
+        absl::StrCat(kTypeGoogleapisCom, KeyProto().GetTypeName()));
+    key_data->set_value(new_key.SerializeAsString());
+    key_data->set_key_material_type(key_material_type());
+    return std::move(key_data);
+  }
+
+  virtual google::crypto::tink::KeyData::KeyMaterialType key_material_type()
+      const = 0;
+
+ protected:
+  virtual crypto::tink::util::StatusOr<std::unique_ptr<KeyProto>>
+  NewKeyFromFormat(const KeyFormatProto& format) const = 0;
+};
+
+template <typename Primitive, typename KeyProto>
+class KeyManagerBase : public KeyManager<Primitive> {
+ public:
+  // Constructs an instance of Primitive for the given 'key_data'.
+  crypto::tink::util::StatusOr<std::unique_ptr<Primitive>> GetPrimitive(
+      const google::crypto::tink::KeyData& key_data) const override {
+    if (this->DoesSupport(key_data.type_url())) {
+      KeyProto key_proto;
+      if (!key_proto.ParseFromString(key_data.value())) {
+        return ToStatusF(util::error::INVALID_ARGUMENT,
+                         "Could not parse key_data.value as key type '%s'.",
+                         key_data.type_url().c_str());
+      }
+      return GetPrimitiveFromKey(key_proto);
+    } else {
+      return ToStatusF(util::error::INVALID_ARGUMENT,
+                       "Key type '%s' is not supported by this manager.",
+                       key_data.type_url().c_str());
+    }
+  }
+
+  // Constructs an instance of Primitive for the given 'key'.
+  crypto::tink::util::StatusOr<std::unique_ptr<Primitive>> GetPrimitive(
+      const portable_proto::MessageLite& key) const override {
+    std::string key_type = absl::StrCat(kTypeGoogleapisCom, key.GetTypeName());
+    if (this->DoesSupport(key_type)) {
+      const KeyProto& key_proto = static_cast<const KeyProto&>(key);
+      return GetPrimitiveFromKey(key_proto);
+    } else {
+      return ToStatusF(util::error::INVALID_ARGUMENT,
+                       "Key type '%s' is not supported by this manager.",
+                       key_type.c_str());
+    }
+  }
+
+  const std::string& get_key_type() const override {
+    return KeyManagerBase::static_key_type();
+  }
+
+  static std::string& static_key_type() {
+    static std::string* key_type =
+        new std::string(absl::StrCat(kTypeGoogleapisCom, KeyProto().GetTypeName()));
+    return *key_type;
+  }
+
+ protected:
+  virtual crypto::tink::util::StatusOr<std::unique_ptr<Primitive>>
+  GetPrimitiveFromKey(const KeyProto& key_proto) const = 0;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_CORE_KEY_MANAGER_BASE_H_
diff --git a/cc/core/key_manager_test.cc b/cc/core/key_manager_test.cc
new file mode 100644
index 0000000..a969be5
--- /dev/null
+++ b/cc/core/key_manager_test.cc
@@ -0,0 +1,54 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+#include "tink/key_manager.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/util/status.h"
+#include "tink/util/test_matchers.h"
+#include "proto/empty.pb.h"
+
+namespace crypto {
+namespace tink {
+
+namespace {
+
+using ::crypto::tink::test::StatusIs;
+
+TEST(AlwaysFailingFactoryTest, NewKeyFromProtoLite) {
+  std::unique_ptr<KeyFactory> factory = KeyFactory::AlwaysFailingFactory(
+      util::Status(crypto::tink::util::error::ALREADY_EXISTS, ""));
+  google::crypto::tink::Empty empty_proto;
+  EXPECT_THAT(factory->NewKey(empty_proto).status(),
+              StatusIs(util::error::ALREADY_EXISTS));
+}
+
+TEST(AlwaysFailingFactoryTest, NewKeyFromStringView) {
+  std::unique_ptr<KeyFactory> factory = KeyFactory::AlwaysFailingFactory(
+      util::Status(crypto::tink::util::error::ALREADY_EXISTS, ""));
+  EXPECT_THAT(factory->NewKey("").status(),
+              StatusIs(util::error::ALREADY_EXISTS));
+}
+
+TEST(AlwaysFailingFactoryTest, NewKeyData) {
+  std::unique_ptr<KeyFactory> factory = KeyFactory::AlwaysFailingFactory(
+      util::Status(crypto::tink::util::error::ALREADY_EXISTS, ""));
+  EXPECT_THAT(factory->NewKeyData("").status(),
+              StatusIs(util::error::ALREADY_EXISTS));
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/core/keyset_handle.cc b/cc/core/keyset_handle.cc
index 6608616..9a5b092 100644
--- a/cc/core/keyset_handle.cc
+++ b/cc/core/keyset_handle.cc
@@ -13,11 +13,11 @@
 // limitations under the License.
 //
 ///////////////////////////////////////////////////////////////////////////////
+#include "tink/keyset_handle.h"
 
+#include <random>
 #include "absl/memory/memory.h"
 #include "tink/aead.h"
-#include "tink/keyset_handle.h"
-#include "tink/keyset_manager.h"
 #include "tink/keyset_reader.h"
 #include "tink/keyset_writer.h"
 #include "tink/registry.h"
@@ -58,6 +58,41 @@
   return std::move(keyset);
 }
 
+uint32_t NewKeyId() {
+  std::random_device rd;
+  std::minstd_rand0 gen(rd());
+  std::uniform_int_distribution<uint32_t> dist;
+  return dist(gen);
+}
+
+uint32_t GenerateUnusedKeyId(const Keyset& keyset) {
+  while (true) {
+    uint32_t key_id = NewKeyId();
+    bool already_exists = false;
+    for (auto& key : keyset.key()) {
+      if (key.key_id() == key_id) {
+        already_exists = true;
+        break;
+      }
+    }
+    if (!already_exists) return key_id;
+  }
+}
+
+util::Status ValidateNoSecret(const Keyset& keyset) {
+  for (const Keyset::Key& key : keyset.key()) {
+    if (key.key_data().key_material_type() == KeyData::UNKNOWN_KEYMATERIAL ||
+        key.key_data().key_material_type() == KeyData::SYMMETRIC ||
+        key.key_data().key_material_type() == KeyData::ASYMMETRIC_PRIVATE) {
+      return util::Status(
+          util::error::FAILED_PRECONDITION,
+          "Cannot create KeysetHandle with secret key material from "
+          "potentially unencrypted source.");
+    }
+  }
+  return util::Status::OK;
+}
+
 }  // anonymous namespace
 
 // static
@@ -83,6 +118,19 @@
   return std::move(handle);
 }
 
+// static
+util::StatusOr<std::unique_ptr<KeysetHandle>> KeysetHandle::ReadNoSecret(
+    const std::string& serialized_keyset) {
+  Keyset keyset;
+  if (!keyset.ParseFromString(serialized_keyset)) {
+    return util::Status(util::error::INVALID_ARGUMENT,
+                        "Could not parse the input string as a Keyset-proto.");
+  }
+  util::Status validation = ValidateNoSecret(keyset);
+  if (!validation.ok()) return validation;
+  return absl::WrapUnique(new KeysetHandle(std::move(keyset)));
+}
+
 util::Status KeysetHandle::Write(KeysetWriter* writer,
                                           const Aead& master_key_aead) {
   if (writer == nullptr) {
@@ -101,14 +149,14 @@
 // static
 util::StatusOr<std::unique_ptr<KeysetHandle>> KeysetHandle::GenerateNew(
     const KeyTemplate& key_template) {
-  auto manager_result = KeysetManager::New(key_template);
-  if (!manager_result.ok()) {
-    return manager_result.status();
+  Keyset keyset;
+  auto result = AddToKeyset(key_template, /*as_primary=*/true, &keyset);
+  if (!result.ok()) {
+    return result.status();
   }
-  return manager_result.ValueOrDie()->GetKeysetHandle();
+  return absl::WrapUnique<KeysetHandle>(new KeysetHandle(std::move(keyset)));
 }
 
-
 util::StatusOr<std::unique_ptr<Keyset::Key>> ExtractPublicKey(
     const Keyset::Key& key) {
   if (key.key_data().key_material_type() != KeyData::ASYMMETRIC_PRIVATE) {
@@ -137,11 +185,32 @@
   return std::move(handle);
 }
 
-KeysetHandle::KeysetHandle(std::unique_ptr<Keyset> keyset)
+crypto::tink::util::StatusOr<uint32_t> KeysetHandle::AddToKeyset(
+    const google::crypto::tink::KeyTemplate& key_template,
+    bool as_primary, Keyset* keyset) {
+  auto key_data_result = Registry::NewKeyData(key_template);
+  if (!key_data_result.ok()) return key_data_result.status();
+  auto key_data = std::move(key_data_result.ValueOrDie());
+  Keyset::Key* key = keyset->add_key();
+  uint32_t key_id = GenerateUnusedKeyId(*keyset);
+  *(key->mutable_key_data()) = *key_data;
+  key->set_status(google::crypto::tink::KeyStatusType::ENABLED);
+  key->set_key_id(key_id);
+  key->set_output_prefix_type(key_template.output_prefix_type());
+  if (as_primary) {
+    keyset->set_primary_key_id(key_id);
+  }
+  return key_id;
+}
+
+KeysetHandle::KeysetHandle(Keyset keyset)
     : keyset_(std::move(keyset)) {}
 
+KeysetHandle::KeysetHandle(std::unique_ptr<Keyset> keyset)
+    : keyset_(std::move(*keyset)) {}
+
 const Keyset& KeysetHandle::get_keyset() const {
-  return *(keyset_.get());
+  return keyset_;
 }
 
 }  // namespace tink
diff --git a/cc/core/keyset_handle_test.cc b/cc/core/keyset_handle_test.cc
index 8478cd2..bdc05c3 100644
--- a/cc/core/keyset_handle_test.cc
+++ b/cc/core/keyset_handle_test.cc
@@ -17,17 +17,20 @@
 #include "tink/keyset_handle.h"
 
 #include "gtest/gtest.h"
+#include "tink/aead/aead_key_templates.h"
+#include "tink/aead/aead_wrapper.h"
+#include "tink/aead/aes_gcm_key_manager.h"
 #include "tink/aead_key_templates.h"
 #include "tink/binary_keyset_reader.h"
 #include "tink/cleartext_keyset_handle.h"
+#include "tink/config/tink_config.h"
 #include "tink/json_keyset_reader.h"
 #include "tink/json_keyset_writer.h"
-#include "tink/aead/aes_gcm_key_manager.h"
-#include "tink/config/tink_config.h"
 #include "tink/signature/ecdsa_sign_key_manager.h"
 #include "tink/signature/signature_key_templates.h"
 #include "tink/util/keyset_util.h"
 #include "tink/util/protobuf_helper.h"
+#include "tink/util/test_matchers.h"
 #include "tink/util/test_util.h"
 #include "proto/tink.pb.h"
 
@@ -35,11 +38,13 @@
 namespace tink {
 
 using crypto::tink::KeysetUtil;
+using crypto::tink::test::AddKeyData;
 using crypto::tink::test::AddLegacyKey;
 using crypto::tink::test::AddRawKey;
 using crypto::tink::test::AddTinkKey;
 using crypto::tink::test::DummyAead;
-
+using crypto::tink::test::IsOk;
+using crypto::tink::test::StatusIs;
 using google::crypto::tink::EncryptedKeyset;
 using google::crypto::tink::KeyData;
 using google::crypto::tink::Keyset;
@@ -57,7 +62,7 @@
   }
 };
 
-TEST_F(KeysetHandleTest, testReadEncryptedKeyset_Binary) {
+TEST_F(KeysetHandleTest, ReadEncryptedKeysetBinary) {
   Keyset keyset;
   Keyset::Key key;
   AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
@@ -121,7 +126,7 @@
   }
 }
 
-TEST_F(KeysetHandleTest, testReadEncryptedKeyset_Json) {
+TEST_F(KeysetHandleTest, ReadEncryptedKeysetJson) {
   Keyset keyset;
   Keyset::Key key;
   AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
@@ -200,7 +205,7 @@
   }
 }
 
-TEST_F(KeysetHandleTest, testWriteEncryptedKeyset_Json) {
+TEST_F(KeysetHandleTest, WriteEncryptedKeyset_Json) {
   // Prepare a valid keyset handle
   Keyset keyset;
   Keyset::Key key;
@@ -241,7 +246,7 @@
   EXPECT_EQ(util::error::INVALID_ARGUMENT, status.error_code());
 }
 
-TEST_F(KeysetHandleTest, testGenerateNewKeysetHandle) {
+TEST_F(KeysetHandleTest, GenerateNewKeysetHandle) {
   const google::crypto::tink::KeyTemplate* key_templates[] = {
     &AeadKeyTemplates::Aes128Gcm(),
     &AeadKeyTemplates::Aes256Gcm(),
@@ -256,7 +261,7 @@
   }
 }
 
-TEST_F(KeysetHandleTest, testGenerateNewKeysetHandleErrors) {
+TEST_F(KeysetHandleTest, GenerateNewKeysetHandleErrors) {
   KeyTemplate templ;
   templ.set_type_url("type.googleapis.com/some.unknown.KeyType");
 
@@ -273,7 +278,7 @@
   EXPECT_EQ(expected.output_prefix_type(), actual.output_prefix_type());
 }
 
-TEST_F(KeysetHandleTest, testGetPublicKeysetHandle) {
+TEST_F(KeysetHandleTest, GetPublicKeysetHandle) {
   { // A keyset with a single key.
     auto handle_result = KeysetHandle::GenerateNew(
         SignatureKeyTemplates::EcdsaP256());
@@ -296,21 +301,21 @@
     Keyset keyset;
     int key_count = 3;
 
-    AddTinkKey(EcdsaSignKeyManager::kKeyType,
+    AddTinkKey(EcdsaSignKeyManager::static_key_type(),
                /* key_id= */ 623628,
                *(key_factory.NewKey(
                    SignatureKeyTemplates::EcdsaP256().value()).ValueOrDie()),
                KeyStatusType::ENABLED,
                KeyData::ASYMMETRIC_PRIVATE,
                &keyset);
-    AddLegacyKey(EcdsaSignKeyManager::kKeyType,
+    AddLegacyKey(EcdsaSignKeyManager::static_key_type(),
                  /* key_id= */ 36285,
                  *(key_factory.NewKey(
                      SignatureKeyTemplates::EcdsaP384().value()).ValueOrDie()),
                  KeyStatusType::DISABLED,
                  KeyData::ASYMMETRIC_PRIVATE,
                  &keyset);
-    AddRawKey(EcdsaSignKeyManager::kKeyType,
+    AddRawKey(EcdsaSignKeyManager::static_key_type(),
               /* key_id= */ 42,
               *(key_factory.NewKey(
                   SignatureKeyTemplates::EcdsaP384().value()).ValueOrDie()),
@@ -334,7 +339,7 @@
 }
 
 
-TEST_F(KeysetHandleTest, testGetPublicKeysetHandleErrors) {
+TEST_F(KeysetHandleTest, GetPublicKeysetHandleErrors) {
   { // A keyset with a single key.
     auto handle_result = KeysetHandle::GenerateNew(
         AeadKeyTemplates::Aes128Eax());
@@ -352,20 +357,21 @@
     const KeyFactory& aead_key_factory = aead_key_manager.get_key_factory();
     Keyset keyset;
 
-    AddTinkKey(EcdsaSignKeyManager::kKeyType,
+    AddTinkKey(EcdsaSignKeyManager::static_key_type(),
                /* key_id= */ 623628,
                *(key_factory.NewKey(
                    SignatureKeyTemplates::EcdsaP256().value()).ValueOrDie()),
                KeyStatusType::ENABLED,
                KeyData::ASYMMETRIC_PRIVATE,
                &keyset);
-    AddLegacyKey(AesGcmKeyManager::kKeyType,
-                 /* key_id= */ 42,
-                 *(aead_key_factory.NewKey(
-                     AeadKeyTemplates::Aes128Gcm().value()).ValueOrDie()),
-                 KeyStatusType::ENABLED,
-                 KeyData::ASYMMETRIC_PRIVATE,  // Intentionally wrong setting.
-                 &keyset);
+    AddLegacyKey(
+        AesGcmKeyManager::static_key_type(),
+        /* key_id= */ 42,
+        *(aead_key_factory.NewKey(AeadKeyTemplates::Aes128Gcm().value())
+              .ValueOrDie()),
+        KeyStatusType::ENABLED,
+        KeyData::ASYMMETRIC_PRIVATE,  // Intentionally wrong setting.
+        &keyset);
     keyset.set_primary_key_id(42);
     auto handle = KeysetUtil::GetKeysetHandle(keyset);
     auto public_handle_result = handle->GetPublicKeysetHandle();
@@ -375,12 +381,161 @@
   }
 }
 
+TEST_F(KeysetHandleTest, GetPrimitive) {
+  Keyset keyset;
+  KeyData key_data_0 =
+      *Registry::NewKeyData(AeadKeyTemplates::Aes128Gcm()).ValueOrDie();
+  AddKeyData(key_data_0, /*key_id=*/0,
+             google::crypto::tink::OutputPrefixType::TINK,
+             KeyStatusType::ENABLED, &keyset);
+  KeyData key_data_1 =
+      *Registry::NewKeyData(AeadKeyTemplates::Aes256Gcm()).ValueOrDie();
+  AddKeyData(key_data_1, /*key_id=*/1,
+             google::crypto::tink::OutputPrefixType::TINK,
+             KeyStatusType::ENABLED, &keyset);
+  KeyData key_data_2 =
+      *Registry::NewKeyData(AeadKeyTemplates::Aes256Gcm()).ValueOrDie();
+  AddKeyData(key_data_2, /*key_id=*/2,
+             google::crypto::tink::OutputPrefixType::RAW,
+             KeyStatusType::ENABLED, &keyset);
+  keyset.set_primary_key_id(1);
+  std::unique_ptr<KeysetHandle> keyset_handle =
+      KeysetUtil::GetKeysetHandle(keyset);
+
+  // Check that encryption with the primary can be decrypted with key_data_1.
+  auto aead_result = keyset_handle->GetPrimitive<Aead>();
+  ASSERT_TRUE(aead_result.ok()) << aead_result.status();
+  std::unique_ptr<Aead> aead = std::move(aead_result.ValueOrDie());
+
+  std::string plaintext = "plaintext";
+  std::string aad = "aad";
+  std::string encryption = aead->Encrypt(plaintext, aad).ValueOrDie();
+  EXPECT_EQ(aead->Decrypt(encryption, aad).ValueOrDie(), plaintext);
+
+  std::unique_ptr<Aead> raw_aead =
+      Registry::GetPrimitive<Aead>(key_data_2).ValueOrDie();
+  EXPECT_FALSE(raw_aead->Decrypt(encryption, aad).ok());
+
+  std::string raw_encryption = raw_aead->Encrypt(plaintext, aad).ValueOrDie();
+  EXPECT_EQ(aead->Decrypt(raw_encryption, aad).ValueOrDie(), plaintext);
+}
+
+// Tests that GetPrimitive(nullptr) fails with a non-ok status.
+TEST_F(KeysetHandleTest, GetPrimitiveNullptrKeyManager) {
+  Keyset keyset;
+  AddKeyData(*Registry::NewKeyData(AeadKeyTemplates::Aes128Gcm()).ValueOrDie(),
+             /*key_id=*/0, google::crypto::tink::OutputPrefixType::TINK,
+             KeyStatusType::ENABLED, &keyset);
+  keyset.set_primary_key_id(0);
+  std::unique_ptr<KeysetHandle> keyset_handle =
+      KeysetUtil::GetKeysetHandle(keyset);
+  ASSERT_THAT(keyset_handle->GetPrimitive<Aead>(nullptr).status(),
+              test::StatusIs(util::error::INVALID_ARGUMENT));
+}
+
+// Test creating with custom key manager. For this, we reset the registry before
+// asking for the primitive.
+TEST_F(KeysetHandleTest, GetPrimitiveCustomKeyManager) {
+  auto handle_result = KeysetHandle::GenerateNew(AeadKeyTemplates::Aes128Gcm());
+  ASSERT_TRUE(handle_result.ok()) << handle_result.status();
+  std::unique_ptr<KeysetHandle> handle = std::move(handle_result.ValueOrDie());
+  Registry::Reset();
+  ASSERT_TRUE(
+      Registry::RegisterPrimitiveWrapper(absl::make_unique<AeadWrapper>())
+          .ok());
+  // Without custom key manager it now fails.
+  ASSERT_FALSE(handle->GetPrimitive<Aead>().ok());
+  AesGcmKeyManager key_manager;
+  // With custom key manager it works ok.
+  ASSERT_TRUE(handle->GetPrimitive<Aead>(&key_manager).ok());
+}
+
+// Compile time check: ensures that the KeysetHandle can be copied.
+TEST_F(KeysetHandleTest, Copiable) {
+  auto handle_result = KeysetHandle::GenerateNew(AeadKeyTemplates::Aes128Eax());
+  ASSERT_TRUE(handle_result.ok()) << handle_result.status();
+  std::unique_ptr<KeysetHandle> handle = std::move(handle_result.ValueOrDie());
+  KeysetHandle handle_copy = *handle;
+}
+
+TEST_F(KeysetHandleTest, ReadNoSecret) {
+  Keyset keyset;
+  Keyset::Key key;
+  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+             KeyData::ASYMMETRIC_PUBLIC, &keyset);
+  AddRawKey("some other key type", 711, key, KeyStatusType::ENABLED,
+            KeyData::REMOTE, &keyset);
+  keyset.set_primary_key_id(42);
+  auto handle_result = KeysetHandle::ReadNoSecret(keyset.SerializeAsString());
+  ASSERT_THAT(handle_result.status(), IsOk());
+  std::unique_ptr<KeysetHandle>& keyset_handle = handle_result.ValueOrDie();
+
+  const Keyset& result = CleartextKeysetHandle::GetKeyset(*keyset_handle);
+  // We check that result equals keyset. For lack of a better method we do this
+  // by hand.
+  EXPECT_EQ(result.primary_key_id(), keyset.primary_key_id());
+  ASSERT_EQ(result.key_size(), keyset.key_size());
+  ASSERT_EQ(result.key(0).key_id(), keyset.key(0).key_id());
+  ASSERT_EQ(result.key(1).key_id(), keyset.key(1).key_id());
+}
+
+TEST_F(KeysetHandleTest, ReadNoSecretFailForTypeUnknown) {
+  Keyset keyset;
+  Keyset::Key key;
+  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+             KeyData::UNKNOWN_KEYMATERIAL, &keyset);
+  keyset.set_primary_key_id(42);
+  auto result = KeysetHandle::ReadNoSecret(keyset.SerializeAsString());
+  EXPECT_THAT(result.status(), StatusIs(util::error::FAILED_PRECONDITION));
+}
+
+TEST_F(KeysetHandleTest, ReadNoSecretFailForTypeSymmetric) {
+  Keyset keyset;
+  Keyset::Key key;
+  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+             KeyData::SYMMETRIC, &keyset);
+  keyset.set_primary_key_id(42);
+  auto result = KeysetHandle::ReadNoSecret(keyset.SerializeAsString());
+  EXPECT_THAT(result.status(), StatusIs(util::error::FAILED_PRECONDITION));
+}
+
+TEST_F(KeysetHandleTest, ReadNoSecretFailForTypeAssymmetricPrivate) {
+  Keyset keyset;
+  Keyset::Key key;
+  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+             KeyData::ASYMMETRIC_PRIVATE, &keyset);
+  keyset.set_primary_key_id(42);
+  auto result = KeysetHandle::ReadNoSecret(keyset.SerializeAsString());
+  EXPECT_THAT(result.status(), StatusIs(util::error::FAILED_PRECONDITION));
+}
+
+TEST_F(KeysetHandleTest, ReadNoSecretFailForHidden) {
+  Keyset keyset;
+  Keyset::Key key;
+  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED,
+             KeyData::ASYMMETRIC_PUBLIC, &keyset);
+  for (int i = 0; i < 10; ++i) {
+    AddTinkKey(absl::StrCat("more key type", i), i, key, KeyStatusType::ENABLED,
+               KeyData::ASYMMETRIC_PUBLIC, &keyset);
+  }
+  AddRawKey("some other key type", 10, key, KeyStatusType::ENABLED,
+            KeyData::ASYMMETRIC_PRIVATE, &keyset);
+  for (int i = 0; i < 10; ++i) {
+    AddRawKey(absl::StrCat("more key type", i + 100), i + 100, key,
+              KeyStatusType::ENABLED, KeyData::ASYMMETRIC_PUBLIC, &keyset);
+  }
+
+  keyset.set_primary_key_id(42);
+  auto result = KeysetHandle::ReadNoSecret(keyset.SerializeAsString());
+  EXPECT_THAT(result.status(), StatusIs(util::error::FAILED_PRECONDITION));
+}
+
+TEST_F(KeysetHandleTest, ReadNoSecretFailForInvalidString) {
+  auto result = KeysetHandle::ReadNoSecret("bad serialized keyset");
+  EXPECT_FALSE(result.ok());
+  EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+}
+
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/core/keyset_manager.cc b/cc/core/keyset_manager.cc
index e960781..9cce931 100644
--- a/cc/core/keyset_manager.cc
+++ b/cc/core/keyset_manager.cc
@@ -37,17 +37,6 @@
 using crypto::tink::util::Status;
 using crypto::tink::util::StatusOr;
 
-namespace {
-
-uint32_t NewKeyId() {
-  std::random_device rd;
-  std::minstd_rand0 gen(rd());
-  std::uniform_int_distribution<uint32_t> dist;
-  return dist(gen);
-}
-
-}  // namespace
-
 // static
 StatusOr<std::unique_ptr<KeysetManager>> KeysetManager::New(
     const KeyTemplate& key_template) {
@@ -61,27 +50,13 @@
 StatusOr<std::unique_ptr<KeysetManager>> KeysetManager::New(
     const KeysetHandle& keyset_handle) {
   auto manager = absl::make_unique<KeysetManager>();
+  absl::MutexLock lock(&manager->keyset_mutex_);
   manager->keyset_ = keyset_handle.get_keyset();
   return std::move(manager);
 }
 
-uint32_t KeysetManager::GenerateNewKeyId() {
-  std::lock_guard<std::recursive_mutex> lock(keyset_mutex_);
-  while (true) {
-    uint32_t key_id = NewKeyId();
-    bool already_exists = false;
-    for (auto& key : keyset_.key()) {
-      if (key.key_id() == key_id) {
-        already_exists = true;
-        break;
-      }
-    }
-    if (!already_exists) return key_id;
-  }
-}
-
 std::unique_ptr<KeysetHandle> KeysetManager::GetKeysetHandle() {
-  std::lock_guard<std::recursive_mutex> lock(keyset_mutex_);
+  absl::MutexLock lock(&keyset_mutex_);
   std::unique_ptr<Keyset> keyset_copy(new Keyset(keyset_));
   std::unique_ptr<KeysetHandle> handle(
       new KeysetHandle(std::move(keyset_copy)));
@@ -89,32 +64,22 @@
 }
 
 StatusOr<uint32_t> KeysetManager::Add(const KeyTemplate& key_template) {
-  std::lock_guard<std::recursive_mutex> lock(keyset_mutex_);
-  auto key_data_result = Registry::NewKeyData(key_template);
-  if (!key_data_result.ok()) return key_data_result.status();
-  auto key_data = std::move(key_data_result.ValueOrDie());
-  Keyset::Key* key = keyset_.add_key();
-  uint32_t key_id = GenerateNewKeyId();
-  *(key->mutable_key_data()) = *key_data;
-  key->set_status(KeyStatusType::ENABLED);
-  key->set_key_id(key_id);
-  key->set_output_prefix_type(key_template.output_prefix_type());
-  return key_id;
+  return Add(key_template, false);
+}
+
+crypto::tink::util::StatusOr<uint32_t> KeysetManager::Add(
+    const google::crypto::tink::KeyTemplate& key_template, bool as_primary) {
+  absl::MutexLock lock(&keyset_mutex_);
+  return KeysetHandle::AddToKeyset(key_template, as_primary, &keyset_);
 }
 
 StatusOr<uint32_t> KeysetManager::Rotate(const KeyTemplate& key_template) {
-  std::lock_guard<std::recursive_mutex> lock(keyset_mutex_);
-  auto add_result = Add(key_template);
-  if (!add_result.ok()) return add_result.status();
-  auto key_id = add_result.ValueOrDie();
-  auto status = SetPrimary(key_id);
-  if (!status.ok()) return status;
-  return key_id;
+  return Add(key_template, true);
 }
 
 
 Status KeysetManager::Enable(uint32_t key_id) {
-  std::lock_guard<std::recursive_mutex> lock(keyset_mutex_);
+  absl::MutexLock lock(&keyset_mutex_);
   for (auto& key : *(keyset_.mutable_key())) {
     if (key.key_id() == key_id) {
       if (key.status() != KeyStatusType::DISABLED &&
@@ -134,7 +99,7 @@
 }
 
 Status KeysetManager::Disable(uint32_t key_id) {
-  std::lock_guard<std::recursive_mutex> lock(keyset_mutex_);
+  absl::MutexLock lock(&keyset_mutex_);
   if (keyset_.primary_key_id() == key_id) {
     return ToStatusF(util::error::INVALID_ARGUMENT,
                      "Cannot disable primary key (key_id %" PRIu32 ").",
@@ -159,7 +124,7 @@
 }
 
 Status KeysetManager::Delete(uint32_t key_id) {
-  std::lock_guard<std::recursive_mutex> lock(keyset_mutex_);
+  absl::MutexLock lock(&keyset_mutex_);
   if (keyset_.primary_key_id() == key_id) {
     return ToStatusF(util::error::INVALID_ARGUMENT,
                      "Cannot delete primary key (key_id %" PRIu32 ").",
@@ -181,7 +146,7 @@
 }
 
 Status KeysetManager::Destroy(uint32_t key_id) {
-  std::lock_guard<std::recursive_mutex> lock(keyset_mutex_);
+  absl::MutexLock lock(&keyset_mutex_);
   if (keyset_.primary_key_id() == key_id) {
     return ToStatusF(util::error::INVALID_ARGUMENT,
                      "Cannot destroy primary key (key_id %" PRIu32 ").",
@@ -208,7 +173,7 @@
 }
 
 Status KeysetManager::SetPrimary(uint32_t key_id) {
-  std::lock_guard<std::recursive_mutex> lock(keyset_mutex_);
+  absl::MutexLock lock(&keyset_mutex_);
   for (auto& key : keyset_.key()) {
     if (key.key_id() == key_id) {
       if (key.status() != KeyStatusType::ENABLED) {
@@ -225,8 +190,9 @@
                    key_id);
 }
 
+
 int KeysetManager::KeyCount() const {
-  std::lock_guard<std::recursive_mutex> lock(keyset_mutex_);
+  absl::MutexLock lock(&keyset_mutex_);
   return keyset_.key_size();
 }
 
diff --git a/cc/core/keyset_manager_test.cc b/cc/core/keyset_manager_test.cc
index 25e0839..e94515f 100644
--- a/cc/core/keyset_manager_test.cc
+++ b/cc/core/keyset_manager_test.cc
@@ -41,15 +41,14 @@
     auto status = AeadConfig::Register();
     ASSERT_TRUE(status.ok()) << status;
   }
-  void TearDown() override {
-  }
+  void TearDown() override {}
 };
 
 TEST_F(KeysetManagerTest, testBasicOperations) {
   AesGcmKeyFormat key_format;
   key_format.set_key_size(16);
   KeyTemplate key_template;
-  key_template.set_type_url(AesGcmKeyManager::kKeyType);
+  key_template.set_type_url(AesGcmKeyManager::static_key_type());
   key_template.set_output_prefix_type(OutputPrefixType::TINK);
   key_template.set_value(key_format.SerializeAsString());
 
@@ -66,7 +65,8 @@
   EXPECT_EQ(key_id_0, keyset.primary_key_id());
   EXPECT_EQ(KeyStatusType::ENABLED, keyset.key(0).status());
   EXPECT_EQ(OutputPrefixType::TINK, keyset.key(0).output_prefix_type());
-  EXPECT_EQ(AesGcmKeyManager::kKeyType, keyset.key(0).key_data().type_url());
+  EXPECT_EQ(AesGcmKeyManager::static_key_type(),
+            keyset.key(0).key_data().type_url());
   EXPECT_EQ(KeyData::SYMMETRIC, keyset.key(0).key_data().key_material_type());
 
   // Add another key.
@@ -78,11 +78,12 @@
   keyset = KeysetUtil::GetKeyset(*(keyset_manager->GetKeysetHandle()));
   EXPECT_EQ(2, keyset.key().size());
   EXPECT_EQ(key_id_0, keyset.primary_key_id());
-  EXPECT_FALSE(
-      keyset.key(0).key_data().value() == keyset.key(1).key_data().value());
+  EXPECT_FALSE(keyset.key(0).key_data().value() ==
+               keyset.key(1).key_data().value());
   EXPECT_EQ(KeyStatusType::ENABLED, keyset.key(1).status());
   EXPECT_EQ(OutputPrefixType::RAW, keyset.key(1).output_prefix_type());
-  EXPECT_EQ(AesGcmKeyManager::kKeyType, keyset.key(1).key_data().type_url());
+  EXPECT_EQ(AesGcmKeyManager::static_key_type(),
+            keyset.key(1).key_data().type_url());
   EXPECT_EQ(KeyData::SYMMETRIC, keyset.key(1).key_data().key_material_type());
 
   // And another one, via rotation.
@@ -94,13 +95,14 @@
   keyset = KeysetUtil::GetKeyset(*(keyset_manager->GetKeysetHandle()));
   EXPECT_EQ(3, keyset.key().size());
   EXPECT_EQ(key_id_2, keyset.primary_key_id());
-  EXPECT_FALSE(
-      keyset.key(0).key_data().value() == keyset.key(2).key_data().value());
-  EXPECT_FALSE(
-      keyset.key(1).key_data().value() == keyset.key(2).key_data().value());
+  EXPECT_FALSE(keyset.key(0).key_data().value() ==
+               keyset.key(2).key_data().value());
+  EXPECT_FALSE(keyset.key(1).key_data().value() ==
+               keyset.key(2).key_data().value());
   EXPECT_EQ(KeyStatusType::ENABLED, keyset.key(2).status());
   EXPECT_EQ(OutputPrefixType::LEGACY, keyset.key(2).output_prefix_type());
-  EXPECT_EQ(AesGcmKeyManager::kKeyType, keyset.key(2).key_data().type_url());
+  EXPECT_EQ(AesGcmKeyManager::static_key_type(),
+            keyset.key(2).key_data().type_url());
   EXPECT_EQ(KeyData::SYMMETRIC, keyset.key(2).key_data().key_material_type());
 
   // Change the primary.
@@ -228,9 +230,3 @@
 
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/core/primitive_set_test.cc b/cc/core/primitive_set_test.cc
index e0ba6a7..391800b 100644
--- a/cc/core/primitive_set_test.cc
+++ b/cc/core/primitive_set_test.cc
@@ -67,7 +67,7 @@
   }
 }
 
-TEST_F(PrimitiveSetTest, testConcurrentOperations) {
+TEST_F(PrimitiveSetTest, ConcurrentOperations) {
   PrimitiveSet<Mac> mac_set;
   int offset_a = 100;
   int offset_b = 150;
@@ -103,7 +103,7 @@
   }
 }
 
-TEST_F(PrimitiveSetTest, testBasic) {
+TEST_F(PrimitiveSetTest, Basic) {
   std::string mac_name_1 = "MAC#1";
   std::unique_ptr<Mac> mac_1(new DummyMac(mac_name_1));
   std::string mac_name_2 = "MAC#2";
@@ -194,53 +194,60 @@
     auto primary = primitive_set.get_primary();
     EXPECT_FALSE(primary == nullptr);
     EXPECT_EQ(KeyStatusType::ENABLED, primary->get_status());
-    EXPECT_EQ(data + mac_name_3,
+    EXPECT_EQ(DummyMac(mac_name_3).ComputeMac(data).ValueOrDie(),
               primary->get_primitive().ComputeMac(data).ValueOrDie());
   }
 
   {  // Check raw primitives.
     auto& primitives = *(primitive_set.get_raw_primitives().ValueOrDie());
     EXPECT_EQ(2, primitives.size());
-    EXPECT_EQ(data + mac_name_4,
+    EXPECT_EQ(DummyMac(mac_name_4).ComputeMac(data).ValueOrDie(),
               primitives[0]->get_primitive().ComputeMac(data).ValueOrDie());
     EXPECT_EQ(KeyStatusType::ENABLED, primitives[0]->get_status());
-    EXPECT_EQ(data + mac_name_5,
+    EXPECT_EQ(OutputPrefixType::RAW, primitives[0]->get_output_prefix_type());
+    EXPECT_EQ(DummyMac(mac_name_5).ComputeMac(data).ValueOrDie(),
               primitives[1]->get_primitive().ComputeMac(data).ValueOrDie());
     EXPECT_EQ(KeyStatusType::DISABLED, primitives[1]->get_status());
+    EXPECT_EQ(OutputPrefixType::RAW, primitives[1]->get_output_prefix_type());
   }
 
   {  // Check Tink primitives.
     std::string prefix = CryptoFormat::get_output_prefix(key_1).ValueOrDie();
     auto& primitives = *(primitive_set.get_primitives(prefix).ValueOrDie());
     EXPECT_EQ(2, primitives.size());
-    EXPECT_EQ(data + mac_name_1,
+    EXPECT_EQ(DummyMac(mac_name_1).ComputeMac(data).ValueOrDie(),
               primitives[0]->get_primitive().ComputeMac(data).ValueOrDie());
     EXPECT_EQ(KeyStatusType::ENABLED, primitives[0]->get_status());
-    EXPECT_EQ(data + mac_name_6,
+    EXPECT_EQ(OutputPrefixType::TINK, primitives[0]->get_output_prefix_type());
+    EXPECT_EQ(DummyMac(mac_name_6).ComputeMac(data).ValueOrDie(),
               primitives[1]->get_primitive().ComputeMac(data).ValueOrDie());
     EXPECT_EQ(KeyStatusType::DISABLED, primitives[1]->get_status());
+    EXPECT_EQ(OutputPrefixType::TINK, primitives[1]->get_output_prefix_type());
   }
 
   {  // Check another Tink primitive.
     std::string prefix = CryptoFormat::get_output_prefix(key_3).ValueOrDie();
     auto& primitives = *(primitive_set.get_primitives(prefix).ValueOrDie());
     EXPECT_EQ(1, primitives.size());
-    EXPECT_EQ(data + mac_name_3,
+    EXPECT_EQ(DummyMac(mac_name_3).ComputeMac(data).ValueOrDie(),
               primitives[0]->get_primitive().ComputeMac(data).ValueOrDie());
     EXPECT_EQ(KeyStatusType::ENABLED, primitives[0]->get_status());
+    EXPECT_EQ(OutputPrefixType::TINK, primitives[0]->get_output_prefix_type());
   }
 
   {  // Check legacy primitive.
     std::string prefix = CryptoFormat::get_output_prefix(key_2).ValueOrDie();
     auto& primitives = *(primitive_set.get_primitives(prefix).ValueOrDie());
     EXPECT_EQ(1, primitives.size());
-    EXPECT_EQ(data + mac_name_2,
+    EXPECT_EQ(DummyMac(mac_name_2).ComputeMac(data).ValueOrDie(),
               primitives[0]->get_primitive().ComputeMac(data).ValueOrDie());
     EXPECT_EQ(KeyStatusType::ENABLED, primitives[0]->get_status());
+    EXPECT_EQ(OutputPrefixType::LEGACY,
+              primitives[0]->get_output_prefix_type());
   }
 }
 
-TEST_F(PrimitiveSetTest, testPrimaryKeyWithIdCollisions) {
+TEST_F(PrimitiveSetTest, PrimaryKeyWithIdCollisions) {
   std::string mac_name_1 = "MAC#1";
   std::string mac_name_2 = "MAC#2";
 
@@ -340,9 +347,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/core/registry.cc b/cc/core/registry.cc
deleted file mode 100644
index 04d3271..0000000
--- a/cc/core/registry.cc
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-///////////////////////////////////////////////////////////////////////////////
-
-#include "tink/registry.h"
-
-#include <mutex>  // NOLINT(build/c++11)
-
-#include "tink/util/errors.h"
-#include "tink/util/statusor.h"
-#include "proto/tink.pb.h"
-
-using crypto::tink::util::StatusOr;
-using google::crypto::tink::KeyData;
-using google::crypto::tink::KeyTemplate;
-
-namespace crypto {
-namespace tink {
-
-std::recursive_mutex Registry::maps_mutex_;
-Registry::LabelToObjectMap Registry::type_to_manager_map_;
-Registry::LabelToTypeNameMap Registry::type_to_primitive_map_;
-Registry::LabelToBoolMap Registry::type_to_new_key_allowed_map_;
-Registry::LabelToKeyFactoryMap Registry::type_to_key_factory_map_;
-Registry::LabelToObjectMap Registry::name_to_catalogue_map_;
-Registry::LabelToTypeNameMap Registry::name_to_primitive_map_;
-
-// static
-StatusOr<bool> Registry::get_new_key_allowed(const std::string& type_url) {
-  std::lock_guard<std::recursive_mutex> lock(maps_mutex_);
-  auto new_key_entry = type_to_new_key_allowed_map_.find(type_url);
-  if (new_key_entry == type_to_new_key_allowed_map_.end()) {
-    return ToStatusF(util::error::NOT_FOUND,
-                     "No manager for type '%s' has been registered.",
-                     type_url.c_str());
-  }
-  return new_key_entry->second;
-}
-
-// static
-StatusOr<const KeyFactory*> Registry::get_key_factory(
-    const std::string& type_url) {
-  std::lock_guard<std::recursive_mutex> lock(maps_mutex_);
-  auto key_factory_entry = type_to_key_factory_map_.find(type_url);
-  if (key_factory_entry == type_to_key_factory_map_.end()) {
-    return ToStatusF(util::error::INTERNAL,
-                     "No KeyFactory for key manager for type '%s' found.",
-                     type_url.c_str());
-  }
-  return key_factory_entry->second;
-}
-
-// static
-StatusOr<std::unique_ptr<KeyData>> Registry::NewKeyData(
-    const KeyTemplate& key_template) {
-  std::lock_guard<std::recursive_mutex> lock(maps_mutex_);
-
-  auto new_key_allowed_result = get_new_key_allowed(key_template.type_url());
-  if (!new_key_allowed_result.ok()) {
-    return new_key_allowed_result.status();
-  }
-  if (!new_key_allowed_result.ValueOrDie()) {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "KeyManager for type '%s' does not allow "
-                     "for creation of new keys.",
-                     key_template.type_url().c_str());
-  }
-  auto key_factory_result = get_key_factory(key_template.type_url());
-  if (!key_factory_result.ok()) {
-    return key_factory_result.status();
-  }
-  auto factory = key_factory_result.ValueOrDie();
-  auto result = factory->NewKeyData(key_template.value());
-  return result;
-}
-
-// static
-StatusOr<std::unique_ptr<KeyData>> Registry::GetPublicKeyData(
-    const std::string& type_url, const std::string& serialized_private_key) {
-  std::lock_guard<std::recursive_mutex> lock(maps_mutex_);
-  auto key_factory_result = get_key_factory(type_url);
-  if (!key_factory_result.ok()) {
-    return key_factory_result.status();
-  }
-  auto factory =
-      dynamic_cast<const PrivateKeyFactory*>(key_factory_result.ValueOrDie());
-  if (factory == nullptr) {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "KeyManager for type '%s' does not have "
-                     "a PrivateKeyFactory.", type_url.c_str());
-  }
-  auto result = factory->GetPublicKeyData(serialized_private_key);
-  return result;
-}
-
-void Registry::Reset() {
-  std::lock_guard<std::recursive_mutex> lock(maps_mutex_);
-  type_to_manager_map_.clear();
-  type_to_primitive_map_.clear();
-  type_to_new_key_allowed_map_.clear();
-  type_to_key_factory_map_.clear();
-  name_to_catalogue_map_.clear();
-  name_to_primitive_map_.clear();
-}
-
-
-
-}  // namespace tink
-}  // namespace crypto
diff --git a/cc/core/registry_impl.cc b/cc/core/registry_impl.cc
new file mode 100644
index 0000000..bbdebdb
--- /dev/null
+++ b/cc/core/registry_impl.cc
@@ -0,0 +1,75 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+#include "tink/core/registry_impl.h"
+
+#include "tink/util/errors.h"
+#include "tink/util/statusor.h"
+#include "proto/tink.pb.h"
+
+using crypto::tink::util::StatusOr;
+using google::crypto::tink::KeyData;
+using google::crypto::tink::KeyTemplate;
+
+namespace crypto {
+namespace tink {
+
+StatusOr<std::unique_ptr<KeyData>> RegistryImpl::NewKeyData(
+    const KeyTemplate& key_template) const {
+  absl::MutexLock lock(&maps_mutex_);
+  const std::string& type_url = key_template.type_url();
+  auto it = type_url_to_info_.find(type_url);
+  if (it == type_url_to_info_.end()) {
+    return ToStatusF(util::error::NOT_FOUND,
+                     "No manager for type '%s' has been registered.",
+                     type_url.c_str());
+  }
+  if (!it->second.new_key_allowed) {
+    return ToStatusF(util::error::INVALID_ARGUMENT,
+                     "KeyManager for type '%s' does not allow "
+                     "for creation of new keys.",
+                     type_url.c_str());
+  }
+  return it->second.key_factory.NewKeyData(key_template.value());
+}
+
+StatusOr<std::unique_ptr<KeyData>> RegistryImpl::GetPublicKeyData(
+    const std::string& type_url, const std::string& serialized_private_key) const {
+  absl::MutexLock lock(&maps_mutex_);
+  auto it = type_url_to_info_.find(type_url);
+  if (it == type_url_to_info_.end()) {
+    return ToStatusF(util::error::INTERNAL, "No Key type '%s' registered.",
+                     type_url.c_str());
+  }
+  auto factory =
+      dynamic_cast<const PrivateKeyFactory*>(&it->second.key_factory);
+  if (factory == nullptr) {
+    return ToStatusF(util::error::INVALID_ARGUMENT,
+                     "KeyManager for type '%s' does not have "
+                     "a PrivateKeyFactory.", type_url.c_str());
+  }
+  auto result = factory->GetPublicKeyData(serialized_private_key);
+  return result;
+}
+
+void RegistryImpl::Reset() {
+  absl::MutexLock lock(&maps_mutex_);
+  type_url_to_info_.clear();
+  name_to_catalogue_map_.clear();
+  primitive_to_wrapper_.clear();
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/core/registry_impl.h b/cc/core/registry_impl.h
new file mode 100644
index 0000000..2cb4c8a
--- /dev/null
+++ b/cc/core/registry_impl.h
@@ -0,0 +1,358 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_CORE_REGISTRY_IMPL_H_
+#define TINK_CORE_REGISTRY_IMPL_H_
+
+#include <typeinfo>
+#include <unordered_map>
+
+#include "absl/base/thread_annotations.h"
+#include "absl/strings/str_cat.h"
+#include "absl/synchronization/mutex.h"
+#include "tink/catalogue.h"
+#include "tink/core/registry_impl.h"
+#include "tink/key_manager.h"
+#include "tink/primitive_set.h"
+#include "tink/primitive_wrapper.h"
+#include "tink/util/errors.h"
+#include "tink/util/protobuf_helper.h"
+#include "tink/util/status.h"
+#include "tink/util/validation.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+
+class RegistryImpl {
+ public:
+  static RegistryImpl& GlobalInstance() {
+    static RegistryImpl* instance = new RegistryImpl();
+    return *instance;
+  }
+
+  template <class P>
+  crypto::tink::util::StatusOr<const Catalogue<P>*> get_catalogue(
+      const std::string& catalogue_name) const LOCKS_EXCLUDED(maps_mutex_);
+
+  template <class P>
+  crypto::tink::util::Status AddCatalogue(const std::string& catalogue_name,
+                                          Catalogue<P>* catalogue)
+      LOCKS_EXCLUDED(maps_mutex_);
+
+  // Registers the given 'manager' for the key type 'manager->get_key_type()'.
+  // Takes ownership of 'manager', which must be non-nullptr.
+  template <class P>
+  crypto::tink::util::Status RegisterKeyManager(KeyManager<P>* manager,
+                                                bool new_key_allowed)
+      LOCKS_EXCLUDED(maps_mutex_);
+
+  template <class P>
+  crypto::tink::util::Status RegisterKeyManager(KeyManager<P>* manager)
+      LOCKS_EXCLUDED(maps_mutex_) {
+    return RegisterKeyManager(manager, /* new_key_allowed= */ true);
+  }
+
+  template <class P>
+  crypto::tink::util::StatusOr<const KeyManager<P>*> get_key_manager(
+      const std::string& type_url) const LOCKS_EXCLUDED(maps_mutex_);
+
+
+  template <class P>
+  crypto::tink::util::Status RegisterPrimitiveWrapper(
+      PrimitiveWrapper<P>* wrapper) LOCKS_EXCLUDED(maps_mutex_);
+
+  template <class P>
+  crypto::tink::util::StatusOr<std::unique_ptr<P>> GetPrimitive(
+      const google::crypto::tink::KeyData& key_data)
+      const LOCKS_EXCLUDED(maps_mutex_);
+
+  template <class P>
+  crypto::tink::util::StatusOr<std::unique_ptr<P>> GetPrimitive(
+      const std::string& type_url, const portable_proto::MessageLite& key)
+      const LOCKS_EXCLUDED(maps_mutex_);
+
+  crypto::tink::util::StatusOr<std::unique_ptr<google::crypto::tink::KeyData>>
+  NewKeyData(const google::crypto::tink::KeyTemplate& key_template)
+      const LOCKS_EXCLUDED(maps_mutex_);
+
+  crypto::tink::util::StatusOr<std::unique_ptr<google::crypto::tink::KeyData>>
+  GetPublicKeyData(const std::string& type_url, const std::string& serialized_private_key)
+      const LOCKS_EXCLUDED(maps_mutex_);
+
+  template <class P>
+  crypto::tink::util::StatusOr<std::unique_ptr<P>> Wrap(
+      std::unique_ptr<PrimitiveSet<P>> primitive_set) const
+      LOCKS_EXCLUDED(maps_mutex_);
+
+  void Reset() LOCKS_EXCLUDED(maps_mutex_);
+
+ private:
+  // All information for a given type url.
+  struct KeyTypeInfo {
+    KeyTypeInfo(std::unique_ptr<void, void (*)(void*)> key_manager,
+                const char* type_id_name, bool new_key_allowed,
+                const KeyFactory& key_factory)
+        : key_manager(std::move(key_manager)),
+          type_id_name(type_id_name),
+          new_key_allowed(new_key_allowed),
+          key_factory(key_factory) {}
+
+    // A pointer to a KeyManager<P>. We cannot use a normal unique_ptr because
+    // we do not know P. Hence, we pass a custom deleter which knows how to
+    // delete the object.
+    const std::unique_ptr<void, void (*)(void*)> key_manager;
+    // TypeId of the primitive for which this key was inserted.
+    const char* type_id_name;
+    // Whether the key manager allows creating new keys.
+    bool new_key_allowed;
+    // The factory which can produce keys of this type.
+    const KeyFactory& key_factory;
+  };
+
+  // All information for a given primitive label.
+  struct LabelInfo {
+    LabelInfo(std::unique_ptr<void, void (*)(void*)> catalogue,
+              const char* type_id_name)
+        : catalogue(std::move(catalogue)), type_id_name(type_id_name) {}
+    // A pointer to the underlying Catalogue<P>.
+    const std::unique_ptr<void, void (*)(void*)> catalogue;
+    // TypeId of the primitive for which this key was inserted.
+    const char* type_id_name;
+  };
+
+  RegistryImpl() = default;
+  RegistryImpl(const RegistryImpl&) = delete;
+  RegistryImpl& operator=(const RegistryImpl&) = delete;
+
+  template <class P>
+  crypto::tink::util::StatusOr<const PrimitiveWrapper<P>*> get_wrapper()
+      const LOCKS_EXCLUDED(maps_mutex_);
+
+
+  mutable absl::Mutex maps_mutex_;
+  std::unordered_map<std::string, KeyTypeInfo> type_url_to_info_
+      GUARDED_BY(maps_mutex_);
+  std::unordered_map<std::string, std::unique_ptr<void, void (*)(void*)>>
+      primitive_to_wrapper_ GUARDED_BY(maps_mutex_);
+
+  std::unordered_map<std::string, LabelInfo> name_to_catalogue_map_
+      GUARDED_BY(maps_mutex_);
+};
+
+template <class P>
+void delete_manager(void* t) {
+  delete static_cast<KeyManager<P>*>(t);
+}
+
+template <class P>
+void delete_catalogue(void* t) {
+  delete static_cast<Catalogue<P>*>(t);
+}
+
+template <class Type>
+std::unique_ptr<void, void (*)(void*)> WrapAsVoidUnique(Type* ptr) {
+  return std::unique_ptr<void, void (*)(void*)>(
+      static_cast<void*>(ptr), [](void* t) { delete static_cast<Type*>(t); });
+}
+
+template <class P>
+crypto::tink::util::Status RegistryImpl::AddCatalogue(
+    const std::string& catalogue_name, Catalogue<P>* catalogue) {
+  if (catalogue == nullptr) {
+    return crypto::tink::util::Status(
+        crypto::tink::util::error::INVALID_ARGUMENT,
+        "Parameter 'catalogue' must be non-null.");
+  }
+  std::unique_ptr<void, void (*)(void*)> entry(catalogue, delete_catalogue<P>);
+  absl::MutexLock lock(&maps_mutex_);
+  auto curr_catalogue = name_to_catalogue_map_.find(catalogue_name);
+  if (curr_catalogue != name_to_catalogue_map_.end()) {
+    auto existing =
+        static_cast<Catalogue<P>*>(curr_catalogue->second.catalogue.get());
+    if (typeid(*existing).name() != typeid(*catalogue).name()) {
+      return ToStatusF(crypto::tink::util::error::ALREADY_EXISTS,
+                       "A catalogue named '%s' has been already added.",
+                       catalogue_name.c_str());
+    }
+  } else {
+    name_to_catalogue_map_.emplace(
+        std::piecewise_construct, std::forward_as_tuple(catalogue_name),
+        std::forward_as_tuple(std::move(entry), typeid(P).name()));
+  }
+  return crypto::tink::util::Status::OK;
+}
+
+template <class P>
+crypto::tink::util::StatusOr<const Catalogue<P>*> RegistryImpl::get_catalogue(
+    const std::string& catalogue_name) const {
+  absl::MutexLock lock(&maps_mutex_);
+  auto catalogue_entry = name_to_catalogue_map_.find(catalogue_name);
+  if (catalogue_entry == name_to_catalogue_map_.end()) {
+    return ToStatusF(crypto::tink::util::error::NOT_FOUND,
+                     "No catalogue named '%s' has been added.",
+                     catalogue_name.c_str());
+  }
+  if (catalogue_entry->second.type_id_name != typeid(P).name()) {
+    return ToStatusF(crypto::tink::util::error::INVALID_ARGUMENT,
+                     "Wrong Primitive type for catalogue named '%s': "
+                     "got '%s', expected '%s'",
+                     catalogue_name.c_str(), typeid(P).name(),
+                     catalogue_entry->second.type_id_name);
+  }
+  return static_cast<Catalogue<P>*>(catalogue_entry->second.catalogue.get());
+}
+
+template <class P>
+crypto::tink::util::Status RegistryImpl::RegisterKeyManager(
+    KeyManager<P>* manager, bool new_key_allowed) {
+  if (manager == nullptr) {
+    return crypto::tink::util::Status(
+        crypto::tink::util::error::INVALID_ARGUMENT,
+        "Parameter 'manager' must be non-null.");
+  }
+  std::unique_ptr<void, void (*)(void*)> entry(manager, delete_manager<P>);
+  std::string type_url = manager->get_key_type();
+  if (!manager->DoesSupport(type_url)) {
+    return ToStatusF(crypto::tink::util::error::INVALID_ARGUMENT,
+                     "The manager does not support type '%s'.",
+                     type_url.c_str());
+  }
+  absl::MutexLock lock(&maps_mutex_);
+  auto it = type_url_to_info_.find(type_url);
+  if (it != type_url_to_info_.end()) {
+    auto existing = static_cast<KeyManager<P>*>(it->second.key_manager.get());
+    if (typeid(*existing).name() != typeid(*manager).name()) {
+      return ToStatusF(crypto::tink::util::error::ALREADY_EXISTS,
+                       "A manager for type '%s' has been already registered.",
+                       type_url.c_str());
+    } else {
+      if (!it->second.new_key_allowed && new_key_allowed) {
+        return ToStatusF(crypto::tink::util::error::ALREADY_EXISTS,
+                         "A manager for type '%s' has been already registered "
+                         "with forbidden new key operation.",
+                         type_url.c_str());
+      }
+      it->second.new_key_allowed = new_key_allowed;
+    }
+  } else {
+    type_url_to_info_.emplace(
+        std::piecewise_construct, std::forward_as_tuple(type_url),
+        std::forward_as_tuple(std::move(entry), typeid(P).name(),
+                              new_key_allowed, manager->get_key_factory()));
+  }
+  return crypto::tink::util::Status::OK;
+}
+
+template <class P>
+crypto::tink::util::Status RegistryImpl::RegisterPrimitiveWrapper(
+    PrimitiveWrapper<P>* wrapper) {
+  if (wrapper == nullptr) {
+    return crypto::tink::util::Status(
+        crypto::tink::util::error::INVALID_ARGUMENT,
+        "Parameter 'wrapper' must be non-null.");
+  }
+  std::unique_ptr<void, void (*)(void*)> entry = WrapAsVoidUnique(wrapper);
+
+  absl::MutexLock lock(&maps_mutex_);
+  auto it = primitive_to_wrapper_.find(typeid(P).name());
+  if (it != primitive_to_wrapper_.end()) {
+    if (typeid(*static_cast<PrimitiveWrapper<P>*>(it->second.get())).name() !=
+        typeid(*static_cast<PrimitiveWrapper<P>*>(entry.get())).name()) {
+      return ToStatusF(
+          crypto::tink::util::error::ALREADY_EXISTS,
+          "A wrapper named for this primitive has already been added.");
+    }
+    return crypto::tink::util::Status::OK;
+  }
+  primitive_to_wrapper_.insert(
+      std::make_pair(typeid(P).name(), std::move(entry)));
+  return crypto::tink::util::Status::OK;
+}
+
+template <class P>
+crypto::tink::util::StatusOr<const KeyManager<P>*>
+RegistryImpl::get_key_manager(const std::string& type_url) const {
+  absl::MutexLock lock(&maps_mutex_);
+  auto it = type_url_to_info_.find(type_url);
+  if (it == type_url_to_info_.end()) {
+    return ToStatusF(crypto::tink::util::error::NOT_FOUND,
+                     "No manager for type '%s' has been registered.",
+                     type_url.c_str());
+  }
+  if (it->second.type_id_name != typeid(P).name()) {
+    return ToStatusF(crypto::tink::util::error::INVALID_ARGUMENT,
+                     "Wrong Primitive type for key type '%s': "
+                     "got '%s', expected '%s'",
+                     type_url.c_str(), typeid(P).name(),
+                     it->second.type_id_name);
+  }
+  return static_cast<KeyManager<P>*>(it->second.key_manager.get());
+}
+
+template <class P>
+crypto::tink::util::StatusOr<std::unique_ptr<P>> RegistryImpl::GetPrimitive(
+    const google::crypto::tink::KeyData& key_data) const {
+  auto key_manager_result = get_key_manager<P>(key_data.type_url());
+  if (key_manager_result.ok()) {
+    return key_manager_result.ValueOrDie()->GetPrimitive(key_data);
+  }
+  return key_manager_result.status();
+}
+
+template <class P>
+crypto::tink::util::StatusOr<std::unique_ptr<P>> RegistryImpl::GetPrimitive(
+    const std::string& type_url, const portable_proto::MessageLite& key) const {
+  auto key_manager_result = get_key_manager<P>(type_url);
+  if (key_manager_result.ok()) {
+    return key_manager_result.ValueOrDie()->GetPrimitive(key);
+  }
+  return key_manager_result.status();
+}
+
+template <class P>
+crypto::tink::util::StatusOr<const PrimitiveWrapper<P>*>
+RegistryImpl::get_wrapper() const {
+  absl::MutexLock lock(&maps_mutex_);
+  auto it = primitive_to_wrapper_.find(typeid(P).name());
+  if (it == primitive_to_wrapper_.end()) {
+    return util::Status(
+        util::error::INVALID_ARGUMENT,
+        absl::StrCat("No wrapper registered for type ", typeid(P).name()));
+  }
+  return static_cast<PrimitiveWrapper<P>*>(it->second.get());
+}
+
+template <class P>
+crypto::tink::util::StatusOr<std::unique_ptr<P>> RegistryImpl::Wrap(
+    std::unique_ptr<PrimitiveSet<P>> primitive_set) const {
+  if (primitive_set == nullptr) {
+    return crypto::tink::util::Status(
+        crypto::tink::util::error::INVALID_ARGUMENT,
+        "Parameter 'primitive_set' must be non-null.");
+  }
+  util::StatusOr<const PrimitiveWrapper<P>*> wrapper_result = get_wrapper<P>();
+  if (!wrapper_result.ok()) {
+    return wrapper_result.status();
+  }
+  crypto::tink::util::StatusOr<std::unique_ptr<P>> primitive_result =
+      wrapper_result.ValueOrDie()->Wrap(std::move(primitive_set));
+  return std::move(primitive_result);
+}
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_CORE_REGISTRY_IMPL_H_
diff --git a/cc/core/registry_test.cc b/cc/core/registry_test.cc
index c609a31..9eb7d2a 100644
--- a/cc/core/registry_test.cc
+++ b/cc/core/registry_test.cc
@@ -23,11 +23,13 @@
 #include "absl/strings/string_view.h"
 #include "tink/aead.h"
 #include "tink/aead/aead_catalogue.h"
+#include "tink/aead/aead_wrapper.h"
 #include "tink/aead/aes_gcm_key_manager.h"
-#include "tink/hybrid/ecies_aead_hkdf_private_key_manager.h"
-#include "tink/hybrid/ecies_aead_hkdf_public_key_manager.h"
 #include "tink/catalogue.h"
 #include "tink/crypto_format.h"
+#include "tink/hybrid/ecies_aead_hkdf_private_key_manager.h"
+#include "tink/hybrid/ecies_aead_hkdf_public_key_manager.h"
+#include "tink/keyset_manager.h"
 #include "tink/registry.h"
 #include "tink/util/keyset_util.h"
 #include "tink/util/protobuf_helper.h"
@@ -59,6 +61,7 @@
 using google::crypto::tink::Keyset;
 using google::crypto::tink::KeyStatusType;
 using google::crypto::tink::KeyTemplate;
+using google::crypto::tink::OutputPrefixType;
 using portable_proto::MessageLite;
 
 class RegistryTest : public ::testing::Test {
@@ -132,7 +135,6 @@
   TestKeyFactory key_factory_;
 };
 
-
 class TestAeadCatalogue : public Catalogue<Aead> {
  public:
   TestAeadCatalogue() {}
@@ -146,6 +148,16 @@
   }
 };
 
+template <typename P>
+class TestWrapper : public PrimitiveWrapper<P> {
+ public:
+  TestWrapper() {}
+  crypto::tink::util::StatusOr<std::unique_ptr<P>> Wrap(
+      std::unique_ptr<PrimitiveSet<P>> primitive_set) const override {
+    return util::Status(util::error::UNIMPLEMENTED, "This is a test wrapper.");
+  }
+};
+
 void register_test_managers(const std::string& key_type_prefix,
                             int manager_count) {
   for (int i = 0; i < manager_count; i++) {
@@ -175,16 +187,18 @@
   // Register the key manager with new_key_allowed == true and verify that
   // new key data can be created.
   util::Status status = Registry::RegisterKeyManager(
-      new TestAeadKeyManager(key_type), /* new_key_allowed= */ true);
+      absl::make_unique<TestAeadKeyManager>(key_type),
+      /* new_key_allowed= */ true);
   EXPECT_TRUE(status.ok()) << status;
 
   auto result_before = Registry::NewKeyData(key_template);
-  EXPECT_TRUE(result_before.ok());
+  EXPECT_TRUE(result_before.ok()) << result_before.status();
 
   // Re-register the key manager with new_key_allowed == false and check the
   // restriction (i.e. new key data cannot be created).
   status = Registry::RegisterKeyManager(
-      new TestAeadKeyManager(key_type), /* new_key_allowed= */ false);
+      absl::make_unique<TestAeadKeyManager>(key_type),
+      /* new_key_allowed= */ false);
   EXPECT_TRUE(status.ok()) << status;
 
   auto result_after = Registry::NewKeyData(key_template);
@@ -203,14 +217,16 @@
 
   // Register the key manager with new_key_allowed == false.
   util::Status status = Registry::RegisterKeyManager(
-      new TestAeadKeyManager(key_type), /* new_key_allowed= */ false);
+      absl::make_unique<TestAeadKeyManager>(key_type),
+      /* new_key_allowed= */ false);
   EXPECT_TRUE(status.ok()) << status;
 
   // Verify that re-registering the key manager with new_key_allowed == true is
   // not possible and that the restriction still holds after that operation
   // (i.e. new key data cannot be created).
   status = Registry::RegisterKeyManager(
-      new TestAeadKeyManager(key_type), /* new_key_allowed= */ true);
+      absl::make_unique<TestAeadKeyManager>(key_type),
+      /* new_key_allowed= */ true);
   EXPECT_FALSE(status.ok());
   EXPECT_EQ(util::error::ALREADY_EXISTS, status.error_code()) << status;
   EXPECT_PRED_FORMAT2(testing::IsSubstring, key_type,
@@ -270,11 +286,13 @@
             manager_result.status().error_code());
 
   auto status = Registry::RegisterKeyManager(
-      new TestAeadKeyManager(key_type_1));
+      absl::make_unique<TestAeadKeyManager>(key_type_1), true);
+
+
   EXPECT_TRUE(status.ok()) << status;
 
   status = Registry::RegisterKeyManager(
-      new TestAeadKeyManager(key_type_2));
+      absl::make_unique<TestAeadKeyManager>(key_type_2), true);
   EXPECT_TRUE(status.ok()) << status;
 
   manager_result = Registry::get_key_manager<Aead>(key_type_1);
@@ -291,23 +309,26 @@
 }
 
 TEST_F(RegistryTest, testRegisterKeyManager) {
-  std::string key_type_1 = AesGcmKeyManager::kKeyType;
+  std::string key_type_1 = AesGcmKeyManager::static_key_type();
 
-  TestAeadKeyManager* null_key_manager = nullptr;
-  auto status = Registry::RegisterKeyManager(null_key_manager);
+  std::unique_ptr<TestAeadKeyManager> null_key_manager = nullptr;
+  auto status = Registry::RegisterKeyManager(std::move(null_key_manager), true);
   EXPECT_FALSE(status.ok());
   EXPECT_EQ(util::error::INVALID_ARGUMENT, status.error_code()) << status;
 
   // Register a key manager.
-  status = Registry::RegisterKeyManager(new TestAeadKeyManager(key_type_1));
+  status = Registry::RegisterKeyManager(
+      absl::make_unique<TestAeadKeyManager>(key_type_1), true);
   EXPECT_TRUE(status.ok()) << status;
 
   // Register the same key manager again, it should work (idempotence).
-  status = Registry::RegisterKeyManager(new TestAeadKeyManager(key_type_1));
+  status = Registry::RegisterKeyManager(
+      absl::make_unique<TestAeadKeyManager>(key_type_1), true);
   EXPECT_TRUE(status.ok()) << status;
 
   // Try overriding a key manager.
-  status = Registry::RegisterKeyManager(new AesGcmKeyManager());
+  status =
+      Registry::RegisterKeyManager(absl::make_unique<AesGcmKeyManager>(), true);
   EXPECT_FALSE(status.ok());
   EXPECT_EQ(util::error::ALREADY_EXISTS, status.error_code()) << status;
 
@@ -321,21 +342,25 @@
 TEST_F(RegistryTest, testAddCatalogue) {
   std::string catalogue_name = "SomeCatalogue";
 
-  TestAeadCatalogue* null_catalogue = nullptr;
-  auto status = Registry::AddCatalogue(catalogue_name, null_catalogue);
+  std::unique_ptr<TestAeadCatalogue> null_catalogue = nullptr;
+  auto status =
+      Registry::AddCatalogue(catalogue_name, std::move(null_catalogue));
   EXPECT_FALSE(status.ok());
   EXPECT_EQ(util::error::INVALID_ARGUMENT, status.error_code()) << status;
 
   // Add a catalogue.
-  status = Registry::AddCatalogue(catalogue_name, new TestAeadCatalogue());
+  status = Registry::AddCatalogue(catalogue_name,
+                                  absl::make_unique<TestAeadCatalogue>());
   EXPECT_TRUE(status.ok()) << status;
 
   // Add the same catalogue again, it should work (idempotence).
-  status = Registry::AddCatalogue(catalogue_name, new TestAeadCatalogue());
+  status = Registry::AddCatalogue(catalogue_name,
+                                  absl::make_unique<TestAeadCatalogue>());
   EXPECT_TRUE(status.ok()) << status;
 
   // Try overriding a catalogue.
-  status = Registry::AddCatalogue(catalogue_name, new AeadCatalogue());
+  status = Registry::AddCatalogue(catalogue_name,
+                                  absl::make_unique<AeadCatalogue>());
   EXPECT_FALSE(status.ok());
   EXPECT_EQ(util::error::ALREADY_EXISTS, status.error_code()) << status;
 
@@ -382,9 +407,11 @@
 
   // Register key managers.
   util::Status status;
-  status = Registry::RegisterKeyManager(new TestAeadKeyManager(key_type_1));
+  status = Registry::RegisterKeyManager(
+      absl::make_unique<TestAeadKeyManager>(key_type_1), true);
   EXPECT_TRUE(status.ok()) << status;
-  status = Registry::RegisterKeyManager(new TestAeadKeyManager(key_type_2));
+  status = Registry::RegisterKeyManager(
+      absl::make_unique<TestAeadKeyManager>(key_type_2), true);
   EXPECT_TRUE(status.ok()) << status;
 
   // Get and use primitives.
@@ -396,7 +423,7 @@
     auto result = Registry::GetPrimitive<Aead>(keyset.key(0).key_data());
     EXPECT_TRUE(result.ok()) << result.status();
     auto aead = std::move(result.ValueOrDie());
-    EXPECT_EQ(plaintext + key_type_1,
+    EXPECT_EQ(DummyAead(key_type_1).Encrypt(plaintext, aad).ValueOrDie(),
               aead->Encrypt(plaintext, aad).ValueOrDie());
   }
 
@@ -405,45 +432,9 @@
     auto result = Registry::GetPrimitive<Aead>(keyset.key(2).key_data());
     EXPECT_TRUE(result.ok()) << result.status();
     auto aead = std::move(result.ValueOrDie());
-    EXPECT_EQ(plaintext + key_type_2,
+    EXPECT_EQ(DummyAead(key_type_2).Encrypt(plaintext, aad).ValueOrDie(),
               aead->Encrypt(plaintext, aad).ValueOrDie());
   }
-
-  // Keyset without custom key manager.
-  {
-    auto result = Registry::GetPrimitives<Aead>(
-        *KeysetUtil::GetKeysetHandle(keyset), nullptr);
-    EXPECT_TRUE(result.ok()) << result.status();
-    auto aead_set = std::move(result.ValueOrDie());
-
-    // Check primary.
-    EXPECT_FALSE(aead_set->get_primary() == nullptr);
-    EXPECT_EQ(CryptoFormat::get_output_prefix(keyset.key(2)).ValueOrDie(),
-              aead_set->get_primary()->get_identifier());
-
-    // Check raw.
-    auto& raw = *(aead_set->get_raw_primitives().ValueOrDie());
-    EXPECT_EQ(2, raw.size());
-    EXPECT_EQ(plaintext + key_type_1,
-              raw[0]->get_primitive().Encrypt(plaintext, aad).ValueOrDie());
-    EXPECT_EQ(plaintext + key_type_2,
-              raw[1]->get_primitive().Encrypt(plaintext, aad).ValueOrDie());
-
-    // Check Tink.
-    auto& tink = *(aead_set->get_primitives(CryptoFormat::get_output_prefix(
-        keyset.key(0)).ValueOrDie()).ValueOrDie());
-    EXPECT_EQ(1, tink.size());
-    EXPECT_EQ(plaintext + key_type_1,
-              tink[0]->get_primitive().Encrypt(plaintext, aad).ValueOrDie());
-
-    // Check DISABLED.
-    auto disabled = aead_set->get_primitives(
-        CryptoFormat::get_output_prefix(keyset.key(1)).ValueOrDie());
-    EXPECT_FALSE(disabled.ok());
-    EXPECT_EQ(util::error::NOT_FOUND, disabled.status().error_code());
-  }
-
-  // TODO(przydatek): add test: Keyset with custom key manager.
 }
 
 TEST_F(RegistryTest, testNewKeyData) {
@@ -453,12 +444,17 @@
 
   // Register key managers.
   util::Status status;
-  status = Registry::RegisterKeyManager(new TestAeadKeyManager(key_type_1));
+  status = Registry::RegisterKeyManager(
+      absl::make_unique<TestAeadKeyManager>(key_type_1),
+      /*new_key_allowed=*/true);
   EXPECT_TRUE(status.ok()) << status;
-  status = Registry::RegisterKeyManager(new TestAeadKeyManager(key_type_2));
+  status = Registry::RegisterKeyManager(
+      absl::make_unique<TestAeadKeyManager>(key_type_2),
+      /*new_key_allowed=*/true);
   EXPECT_TRUE(status.ok()) << status;
-  status = Registry::RegisterKeyManager(new TestAeadKeyManager(key_type_3),
-                                        /* new_key_allowed= */ false);
+  status = Registry::RegisterKeyManager(
+      absl::make_unique<TestAeadKeyManager>(key_type_3),
+      /*new_key_allowed=*/false);
   EXPECT_TRUE(status.ok()) << status;
 
   {  // A supported key type.
@@ -512,10 +508,11 @@
 TEST_F(RegistryTest, testGetPublicKeyData) {
   // Setup the registry.
   Registry::Reset();
-  auto status =
-      Registry::RegisterKeyManager(new EciesAeadHkdfPrivateKeyManager());
+  auto status = Registry::RegisterKeyManager(
+      absl::make_unique<EciesAeadHkdfPrivateKeyManager>(), true);
   ASSERT_TRUE(status.ok()) << status;
-  status = Registry::RegisterKeyManager(new AesGcmKeyManager());
+  status =
+      Registry::RegisterKeyManager(absl::make_unique<AesGcmKeyManager>(), true);
   ASSERT_TRUE(status.ok()) << status;
 
   // Get a test private key.
@@ -525,10 +522,11 @@
 
   // Extract public key data and check.
   auto public_key_data_result = Registry::GetPublicKeyData(
-      EciesAeadHkdfPrivateKeyManager::kKeyType, ecies_key.SerializeAsString());
+      EciesAeadHkdfPrivateKeyManager::static_key_type(),
+      ecies_key.SerializeAsString());
   EXPECT_TRUE(public_key_data_result.ok()) << public_key_data_result.status();
   auto public_key_data = std::move(public_key_data_result.ValueOrDie());
-  EXPECT_EQ(EciesAeadHkdfPublicKeyManager::kKeyType,
+  EXPECT_EQ(EciesAeadHkdfPublicKeyManager::static_key_type(),
             public_key_data->type_url());
   EXPECT_EQ(KeyData::ASYMMETRIC_PUBLIC, public_key_data->key_material_type());
   EXPECT_EQ(ecies_key.public_key().SerializeAsString(),
@@ -536,7 +534,7 @@
 
   // Try with a wrong key type.
   auto wrong_key_type_result = Registry::GetPublicKeyData(
-      AesGcmKeyManager::kKeyType, ecies_key.SerializeAsString());
+      AesGcmKeyManager::static_key_type(), ecies_key.SerializeAsString());
   EXPECT_FALSE(wrong_key_type_result.ok());
   EXPECT_EQ(util::error::INVALID_ARGUMENT,
             wrong_key_type_result.status().error_code());
@@ -545,7 +543,8 @@
 
   // Try with a bad serialized key.
   auto bad_key_result = Registry::GetPublicKeyData(
-      EciesAeadHkdfPrivateKeyManager::kKeyType, "some bad serialized key");
+      EciesAeadHkdfPrivateKeyManager::static_key_type(),
+      "some bad serialized key");
   EXPECT_FALSE(bad_key_result.ok());
   EXPECT_EQ(util::error::INVALID_ARGUMENT,
             bad_key_result.status().error_code());
@@ -553,12 +552,124 @@
                       bad_key_result.status().error_message());
 }
 
+// Tests that if we register the same type of wrapper twice, the second call
+// succeeds.
+TEST_F(RegistryTest, RegisterWrapperTwice) {
+  EXPECT_TRUE(
+      Registry::RegisterPrimitiveWrapper(absl::make_unique<AeadWrapper>())
+          .ok());
+  EXPECT_TRUE(
+      Registry::RegisterPrimitiveWrapper(absl::make_unique<AeadWrapper>())
+          .ok());
+}
+
+// Tests that if we register different wrappers for the same primitive twice,
+// the second call fails.
+TEST_F(RegistryTest, RegisterDifferentWrappers) {
+  EXPECT_TRUE(
+      Registry::RegisterPrimitiveWrapper(absl::make_unique<AeadWrapper>())
+          .ok());
+  util::Status result = Registry::RegisterPrimitiveWrapper(
+      absl::make_unique<TestWrapper<Aead>>());
+  EXPECT_FALSE(result.ok());
+  EXPECT_EQ(util::error::ALREADY_EXISTS, result.error_code());
+}
+
+// Tests that if we register different wrappers for different primitives, this
+// returns ok.
+TEST_F(RegistryTest, RegisterDifferentWrappersDifferentPrimitives) {
+  EXPECT_TRUE(
+      Registry::RegisterPrimitiveWrapper(absl::make_unique<TestWrapper<Aead>>())
+          .ok());
+  EXPECT_TRUE(
+      Registry::RegisterPrimitiveWrapper(absl::make_unique<TestWrapper<Mac>>())
+          .ok());
+}
+
+// Tests that if we do not register a wrapper, then calls to Wrap
+// fail with "No wrapper registered" -- even if there is a wrapper for a
+// different primitive registered.
+TEST_F(RegistryTest, NoWrapperRegistered) {
+  EXPECT_TRUE(
+      Registry::RegisterPrimitiveWrapper(absl::make_unique<TestWrapper<Mac>>())
+          .ok());
+
+  crypto::tink::util::StatusOr<std::unique_ptr<Aead>> result =
+      Registry::Wrap<Aead>(absl::make_unique<PrimitiveSet<Aead>>());
+  EXPECT_FALSE(result.ok());
+  EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+  EXPECT_PRED_FORMAT2(testing::IsSubstring, "No wrapper registered",
+                      result.status().error_message());
+}
+
+// Tests that if the wrapper fails, the error of the wrapped is forwarded
+// in GetWrappedPrimitive.
+TEST_F(RegistryTest, WrapperFails) {
+  EXPECT_TRUE(
+      Registry::RegisterPrimitiveWrapper(absl::make_unique<TestWrapper<Aead>>())
+          .ok());
+
+  crypto::tink::util::StatusOr<std::unique_ptr<Aead>> result =
+      Registry::Wrap<Aead>(absl::make_unique<PrimitiveSet<Aead>>());
+  EXPECT_FALSE(result.ok());
+  EXPECT_PRED_FORMAT2(testing::IsSubstring, "This is a test wrapper",
+                      result.status().error_message());
+}
+
+// Tests that wrapping works as expected in the usual case.
+TEST_F(RegistryTest, UsualWrappingTest) {
+  Keyset keyset;
+
+  keyset.add_key();
+  keyset.mutable_key(0)->set_output_prefix_type(OutputPrefixType::TINK);
+  keyset.mutable_key(0)->set_key_id(1234543);
+  keyset.add_key();
+  keyset.mutable_key(1)->set_output_prefix_type(OutputPrefixType::LEGACY);
+  keyset.mutable_key(1)->set_key_id(726329);
+  keyset.add_key();
+  keyset.mutable_key(2)->set_output_prefix_type(OutputPrefixType::TINK);
+  keyset.mutable_key(2)->set_key_id(7213743);
+
+  auto primitive_set = absl::make_unique<PrimitiveSet<Aead>>();
+  ASSERT_TRUE(
+      primitive_set
+          ->AddPrimitive(absl::make_unique<DummyAead>("aead0"), keyset.key(0))
+          .ok());
+  ASSERT_TRUE(
+      primitive_set
+          ->AddPrimitive(absl::make_unique<DummyAead>("aead1"), keyset.key(1))
+          .ok());
+  auto entry_result = primitive_set->AddPrimitive(
+      absl::make_unique<DummyAead>("primary_aead"), keyset.key(2));
+  primitive_set->set_primary(entry_result.ValueOrDie());
+
+  EXPECT_TRUE(
+      Registry::RegisterPrimitiveWrapper(absl::make_unique<AeadWrapper>())
+          .ok());
+
+  auto aead_result = Registry::Wrap<Aead>(std::move(primitive_set));
+  EXPECT_TRUE(aead_result.ok()) << aead_result.status();
+  std::unique_ptr<Aead> aead = std::move(aead_result.ValueOrDie());
+  std::string plaintext = "some_plaintext";
+  std::string aad = "some_aad";
+
+  auto encrypt_result = aead->Encrypt(plaintext, aad);
+  EXPECT_TRUE(encrypt_result.ok()) << encrypt_result.status();
+  std::string ciphertext = encrypt_result.ValueOrDie();
+  EXPECT_PRED_FORMAT2(testing::IsSubstring, "primary_aead", ciphertext);
+
+  auto decrypt_result = aead->Decrypt(ciphertext, aad);
+  EXPECT_TRUE(decrypt_result.ok()) << decrypt_result.status();
+  EXPECT_EQ(plaintext, decrypt_result.ValueOrDie());
+
+  decrypt_result = aead->Decrypt("some bad ciphertext", aad);
+  EXPECT_FALSE(decrypt_result.ok());
+  EXPECT_EQ(util::error::INVALID_ARGUMENT,
+            decrypt_result.status().error_code());
+  EXPECT_PRED_FORMAT2(testing::IsSubstring, "decryption failed",
+                      decrypt_result.status().error_message());
+}
+
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/core/version.cc b/cc/core/version.cc
new file mode 100644
index 0000000..0e2e3f1
--- /dev/null
+++ b/cc/core/version.cc
@@ -0,0 +1,25 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/version.h"
+
+namespace crypto {
+namespace tink {
+
+constexpr char Version::kTinkVersion[];
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/core/version_test.cc b/cc/core/version_test.cc
new file mode 100644
index 0000000..e47a9b9
--- /dev/null
+++ b/cc/core/version_test.cc
@@ -0,0 +1,39 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/version.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+TEST(VersionTest, testVersionFormat) {
+  // The regex represents Semantic Versioning syntax (www.semver.org),
+  // i.e. three dot-separated numbers, with an optional suffix
+  // that starts with a hyphen, to cover alpha/beta releases and
+  // release candiates, for example:
+  //   1.2.3
+  //   1.2.3-beta
+  //   1.2.3-RC1
+  std::string version_regex = "[0-9]+[.][0-9]+[.][0-9]+(-[A-Za-z0-9]+)?";
+  EXPECT_THAT(Version::kTinkVersion, testing::MatchesRegex(version_regex));
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/daead/BUILD.bazel b/cc/daead/BUILD.bazel
new file mode 100644
index 0000000..a525251
--- /dev/null
+++ b/cc/daead/BUILD.bazel
@@ -0,0 +1,220 @@
+package(default_visibility = ["//tools/build_defs:internal_pkg"])
+
+licenses(["notice"])  # Apache 2.0
+
+cc_library(
+    name = "aes_siv_key_manager",
+    srcs = ["aes_siv_key_manager.cc"],
+    hdrs = ["aes_siv_key_manager.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        "//cc:aead",
+        "//cc:key_manager",
+        "//cc:key_manager_base",
+        "//cc/subtle:aes_siv_boringssl",
+        "//cc/subtle:random",
+        "//cc/util:errors",
+        "//cc/util:protobuf_helper",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "//cc/util:validation",
+        "//proto:aes_siv_cc_proto",
+        "//proto:common_cc_proto",
+        "//proto:tink_cc_proto",
+        "@com_google_absl//absl/base",
+    ],
+)
+
+cc_library(
+    name = "deterministic_aead_wrapper",
+    srcs = ["deterministic_aead_wrapper.cc"],
+    hdrs = ["deterministic_aead_wrapper.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        "//cc:crypto_format",
+        "//cc:deterministic_aead",
+        "//cc:primitive_set",
+        "//cc:primitive_wrapper",
+        "//cc/subtle:subtle_util_boringssl",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "//proto:tink_cc_proto",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_library(
+    name = "deterministic_aead_catalogue",
+    srcs = ["deterministic_aead_catalogue.cc"],
+    hdrs = ["deterministic_aead_catalogue.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        ":aes_siv_key_manager",
+        "//cc:catalogue",
+        "//cc:deterministic_aead",
+        "//cc:key_manager",
+        "//cc/util:status",
+        "@com_google_absl//absl/memory",
+    ],
+)
+
+cc_library(
+    name = "deterministic_aead_config",
+    srcs = ["deterministic_aead_config.cc"],
+    hdrs = ["deterministic_aead_config.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        ":deterministic_aead_catalogue",
+        "//cc:config",
+        "//cc/mac:mac_config",
+        "//cc/util:status",
+        "//proto:config_cc_proto",
+        "@com_google_absl//absl/memory",
+    ],
+)
+
+cc_library(
+    name = "deterministic_aead_factory",
+    srcs = ["deterministic_aead_factory.cc"],
+    hdrs = ["deterministic_aead_factory.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        ":deterministic_aead_wrapper",
+        "//cc:deterministic_aead",
+        "//cc:key_manager",
+        "//cc:keyset_handle",
+        "//cc:primitive_set",
+        "//cc:registry",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "@com_google_absl//absl/base:core_headers",
+    ],
+)
+
+cc_library(
+    name = "deterministic_aead_key_templates",
+    srcs = ["deterministic_aead_key_templates.cc"],
+    hdrs = ["deterministic_aead_key_templates.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        "//proto:aes_siv_cc_proto",
+        "//proto:common_cc_proto",
+        "//proto:tink_cc_proto",
+    ],
+)
+
+# tests
+
+cc_test(
+    name = "aes_siv_key_manager_test",
+    size = "small",
+    srcs = ["aes_siv_key_manager_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    deps = [
+        ":aes_siv_key_manager",
+        "//cc:aead",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "//proto:aes_eax_cc_proto",
+        "//proto:aes_siv_cc_proto",
+        "//proto:common_cc_proto",
+        "//proto:tink_cc_proto",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "deterministic_aead_wrapper_test",
+    size = "small",
+    srcs = ["deterministic_aead_wrapper_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    deps = [
+        ":deterministic_aead_wrapper",
+        "//cc:deterministic_aead",
+        "//cc:primitive_set",
+        "//cc/util:status",
+        "//cc/util:test_util",
+        "//proto:tink_cc_proto",
+        "@com_google_absl//absl/memory",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "deterministic_aead_catalogue_test",
+    size = "small",
+    srcs = ["deterministic_aead_catalogue_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    deps = [
+        ":deterministic_aead_catalogue",
+        ":deterministic_aead_config",
+        "//cc:catalogue",
+        "//cc:deterministic_aead",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "deterministic_aead_config_test",
+    size = "small",
+    srcs = ["deterministic_aead_config_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    deps = [
+        ":deterministic_aead_config",
+        ":deterministic_aead_key_templates",
+        "//cc:catalogue",
+        "//cc:config",
+        "//cc:deterministic_aead",
+        "//cc:keyset_handle",
+        "//cc:registry",
+        "//cc/util:status",
+        "//cc/util:test_matchers",
+        "//cc/util:test_util",
+        "@com_google_absl//absl/memory",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "deterministic_aead_factory_test",
+    size = "small",
+    srcs = ["deterministic_aead_factory_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    deps = [
+        ":aes_siv_key_manager",
+        ":deterministic_aead_config",
+        ":deterministic_aead_factory",
+        "//cc:crypto_format",
+        "//cc:deterministic_aead",
+        "//cc:keyset_handle",
+        "//cc/util:keyset_util",
+        "//cc/util:status",
+        "//cc/util:test_util",
+        "//proto:aes_siv_cc_proto",
+        "//proto:tink_cc_proto",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "deterministic_aead_key_templates_test",
+    size = "small",
+    srcs = ["deterministic_aead_key_templates_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    deps = [
+        ":aes_siv_key_manager",
+        ":deterministic_aead_key_templates",
+        "//proto:aes_siv_cc_proto",
+        "//proto:common_cc_proto",
+        "//proto:tink_cc_proto",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
diff --git a/cc/daead/CMakeLists.txt b/cc/daead/CMakeLists.txt
new file mode 100644
index 0000000..2bb5738
--- /dev/null
+++ b/cc/daead/CMakeLists.txt
@@ -0,0 +1,36 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+# CC Library : deterministic_aead_wrapper
+add_library(
+  tink_cc_daead_deterministic_aead_wrapper
+  deterministic_aead_wrapper.h
+  deterministic_aead_wrapper.cc
+)
+tink_export_hdrs(deterministic_aead_wrapper.h)
+add_dependencies(
+  tink_cc_daead_deterministic_aead_wrapper
+  tink_cc_crypto_format
+  tink_cc_deterministic_aead
+  tink_cc_primitive_set
+  tink_cc_primitive_wrapper
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_status
+  tink_cc_util_statusor
+  tink_proto_tink_lib
+)
+target_link_libraries(
+  tink_cc_daead_deterministic_aead_wrapper
+  tink_cc_crypto_format
+  tink_cc_deterministic_aead
+  tink_cc_primitive_set
+  tink_cc_primitive_wrapper
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_status
+  tink_cc_util_statusor
+  tink_proto_tink_lib
+  absl::strings
+)
+
diff --git a/cc/daead/aes_siv_key_manager.cc b/cc/daead/aes_siv_key_manager.cc
new file mode 100644
index 0000000..dd8c614
--- /dev/null
+++ b/cc/daead/aes_siv_key_manager.cc
@@ -0,0 +1,112 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/daead/aes_siv_key_manager.h"
+
+#include "absl/base/casts.h"
+#include "absl/strings/string_view.h"
+#include "tink/deterministic_aead.h"
+#include "tink/key_manager.h"
+#include "tink/subtle/aes_siv_boringssl.h"
+#include "tink/subtle/random.h"
+#include "tink/util/errors.h"
+#include "tink/util/protobuf_helper.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "tink/util/validation.h"
+#include "proto/aes_siv.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+
+using ::crypto::tink::util::Status;
+using ::crypto::tink::util::StatusOr;
+using ::google::crypto::tink::AesSivKey;
+using ::google::crypto::tink::AesSivKeyFormat;
+using ::google::crypto::tink::KeyData;
+
+class AesSivKeyFactory : public KeyFactoryBase<AesSivKey, AesSivKeyFormat> {
+ public:
+  AesSivKeyFactory() {}
+
+  KeyData::KeyMaterialType key_material_type() const override {
+    return KeyData::SYMMETRIC;
+  }
+
+ protected:
+  StatusOr<std::unique_ptr<AesSivKey>> NewKeyFromFormat(
+      const AesSivKeyFormat& aes_siv_key_format) const override {
+    Status status = AesSivKeyManager::Validate(aes_siv_key_format);
+    if (!status.ok()) return status;
+
+    auto aes_siv_key = absl::make_unique<AesSivKey>();
+    aes_siv_key->set_version(AesSivKeyManager::kVersion);
+    aes_siv_key->set_key_value(
+        subtle::Random::GetRandomBytes(aes_siv_key_format.key_size()));
+    return absl::implicit_cast<StatusOr<std::unique_ptr<AesSivKey>>>(
+        std::move(aes_siv_key));
+  }
+};
+
+constexpr uint32_t AesSivKeyManager::kVersion;
+
+AesSivKeyManager::AesSivKeyManager()
+    : key_factory_(absl::make_unique<AesSivKeyFactory>()) {}
+
+uint32_t AesSivKeyManager::get_version() const { return kVersion; }
+
+const KeyFactory& AesSivKeyManager::get_key_factory() const {
+  return *key_factory_;
+}
+
+StatusOr<std::unique_ptr<DeterministicAead>>
+AesSivKeyManager::GetPrimitiveFromKey(const AesSivKey& aes_siv_key) const {
+  Status status = Validate(aes_siv_key);
+  if (!status.ok()) return status;
+  auto aes_siv_result = subtle::AesSivBoringSsl::New(aes_siv_key.key_value());
+  if (!aes_siv_result.ok()) return aes_siv_result.status();
+  return std::move(aes_siv_result.ValueOrDie());
+}
+
+// static
+Status AesSivKeyManager::Validate(const AesSivKey& key) {
+  Status status = ValidateVersion(key.version(), kVersion);
+  if (!status.ok()) return status;
+  uint32_t key_size = key.key_value().size();
+  if (key_size != 64) {
+    return ToStatusF(util::error::INVALID_ARGUMENT,
+                     "Invalid AesSivKey: key_value has %d bytes; "
+                     "supported size: 64 bytes.",
+                     key_size);
+  }
+  return Status::OK;
+}
+
+// static
+Status AesSivKeyManager::Validate(const AesSivKeyFormat& key_format) {
+  uint32_t key_size = key_format.key_size();
+  if (key_size != 64) {
+    return ToStatusF(util::error::INVALID_ARGUMENT,
+                     "Invalid AesSivKeyFormat: key_size is %d bytes; "
+                     "supported size: 64 bytes.",
+                     key_size);
+  }
+  return Status::OK;
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/daead/aes_siv_key_manager.h b/cc/daead/aes_siv_key_manager.h
new file mode 100644
index 0000000..4888169
--- /dev/null
+++ b/cc/daead/aes_siv_key_manager.h
@@ -0,0 +1,72 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_AEAD_AES_SIV_KEY_MANAGER_H_
+#define TINK_AEAD_AES_SIV_KEY_MANAGER_H_
+
+#include <algorithm>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "tink/deterministic_aead.h"
+#include "tink/core/key_manager_base.h"
+#include "tink/key_manager.h"
+#include "tink/util/errors.h"
+#include "tink/util/protobuf_helper.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "proto/aes_siv.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+
+class AesSivKeyManager
+    : public KeyManagerBase<DeterministicAead,
+                            google::crypto::tink::AesSivKey> {
+ public:
+  static constexpr uint32_t kVersion = 0;
+
+  AesSivKeyManager();
+
+  // Returns the version of this key manager.
+  uint32_t get_version() const override;
+
+  // Returns a factory that generates keys of the key type
+  // handled by this manager.
+  const KeyFactory& get_key_factory() const override;
+
+  virtual ~AesSivKeyManager() {}
+
+ protected:
+  crypto::tink::util::StatusOr<std::unique_ptr<DeterministicAead>>
+  GetPrimitiveFromKey(
+      const google::crypto::tink::AesSivKey& aes_siv_key) const override;
+
+ private:
+  friend class AesSivKeyFactory;
+
+  std::unique_ptr<KeyFactory> key_factory_;
+
+  static crypto::tink::util::Status Validate(
+      const google::crypto::tink::AesSivKey& key);
+  static crypto::tink::util::Status Validate(
+      const google::crypto::tink::AesSivKeyFormat& key_format);
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_AEAD_AES_SIV_KEY_MANAGER_H_
diff --git a/cc/daead/aes_siv_key_manager_test.cc b/cc/daead/aes_siv_key_manager_test.cc
new file mode 100644
index 0000000..6cb3649
--- /dev/null
+++ b/cc/daead/aes_siv_key_manager_test.cc
@@ -0,0 +1,273 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/daead/aes_siv_key_manager.h"
+
+#include "tink/deterministic_aead.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "gtest/gtest.h"
+#include "proto/aes_eax.pb.h"
+#include "proto/aes_siv.pb.h"
+#include "proto/common.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+
+using google::crypto::tink::AesEaxKey;
+using google::crypto::tink::AesEaxKeyFormat;
+using google::crypto::tink::AesSivKey;
+using google::crypto::tink::AesSivKeyFormat;
+using google::crypto::tink::KeyData;
+using google::crypto::tink::KeyTemplate;
+
+namespace {
+
+class AesSivKeyManagerTest : public ::testing::Test {
+ protected:
+  std::string key_type_prefix_ = "type.googleapis.com/";
+  std::string aes_siv_key_type_ =
+      "type.googleapis.com/google.crypto.tink.AesSivKey";
+};
+
+TEST_F(AesSivKeyManagerTest, testBasic) {
+  AesSivKeyManager key_manager;
+
+  EXPECT_EQ(0, key_manager.get_version());
+  EXPECT_EQ("type.googleapis.com/google.crypto.tink.AesSivKey",
+            key_manager.get_key_type());
+  EXPECT_TRUE(key_manager.DoesSupport(key_manager.get_key_type()));
+}
+
+TEST_F(AesSivKeyManagerTest, testKeyDataErrors) {
+  AesSivKeyManager key_manager;
+
+  {  // Bad key type.
+    KeyData key_data;
+    std::string bad_key_type =
+        "type.googleapis.com/google.crypto.tink.SomeOtherKey";
+    key_data.set_type_url(bad_key_type);
+    auto result = key_manager.GetPrimitive(key_data);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "not supported",
+                        result.status().error_message());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, bad_key_type,
+                        result.status().error_message());
+  }
+
+  {  // Bad key value.
+    KeyData key_data;
+    key_data.set_type_url(aes_siv_key_type_);
+    key_data.set_value("some bad serialized proto");
+    auto result = key_manager.GetPrimitive(key_data);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "not parse",
+                        result.status().error_message());
+  }
+
+  {  // Bad version.
+    KeyData key_data;
+    AesSivKey key;
+    key.set_version(1);
+    key_data.set_type_url(aes_siv_key_type_);
+    key_data.set_value(key.SerializeAsString());
+    auto result = key_manager.GetPrimitive(key_data);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "version",
+                        result.status().error_message());
+  }
+
+  {  // Bad key_value size (supported size: 64).
+    for (int len = 0; len < 72; len++) {
+      AesSivKey key;
+      key.set_version(0);
+      key.set_key_value(std::string(len, 'a'));
+      KeyData key_data;
+      key_data.set_type_url(aes_siv_key_type_);
+      key_data.set_value(key.SerializeAsString());
+      auto result = key_manager.GetPrimitive(key_data);
+      if (len == 64) {
+        EXPECT_TRUE(result.ok()) << result.status();
+      } else {
+        EXPECT_FALSE(result.ok());
+        EXPECT_EQ(util::error::INVALID_ARGUMENT,
+                  result.status().error_code());
+        EXPECT_PRED_FORMAT2(testing::IsSubstring,
+                            std::to_string(len) + " bytes",
+                            result.status().error_message());
+        EXPECT_PRED_FORMAT2(testing::IsSubstring, "supported size",
+                            result.status().error_message());
+      }
+    }
+  }
+}
+
+TEST_F(AesSivKeyManagerTest, testKeyMessageErrors) {
+  AesSivKeyManager key_manager;
+
+  {  // Bad protobuffer.
+    AesEaxKey key;
+    auto result = key_manager.GetPrimitive(key);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "AesEaxKey",
+                        result.status().error_message());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "not supported",
+                        result.status().error_message());
+  }
+
+  {  // Bad key_value size (supported size: 64).
+    for (int len = 0; len < 72; len++) {
+      AesSivKey key;
+      key.set_version(0);
+      key.set_key_value(std::string(len, 'a'));
+      auto result = key_manager.GetPrimitive(key);
+      if (len == 64) {
+        EXPECT_TRUE(result.ok()) << result.status();
+      } else {
+        EXPECT_FALSE(result.ok());
+        EXPECT_EQ(util::error::INVALID_ARGUMENT,
+                  result.status().error_code());
+        EXPECT_PRED_FORMAT2(testing::IsSubstring,
+                            std::to_string(len) + " bytes",
+                            result.status().error_message());
+        EXPECT_PRED_FORMAT2(testing::IsSubstring, "supported size",
+                            result.status().error_message());
+      }
+    }
+  }
+}
+
+TEST_F(AesSivKeyManagerTest, testPrimitives) {
+  std::string plaintext = "some plaintext";
+  std::string aad = "some aad";
+  AesSivKeyManager key_manager;
+  AesSivKey key;
+
+  key.set_version(0);
+  key.set_key_value(
+      "64 bytes of key 0123456789abcdef0123456789abcdef0123456789abcdef");
+
+  {  // Using key message only.
+    auto result = key_manager.GetPrimitive(key);
+    EXPECT_TRUE(result.ok()) << result.status();
+    auto aes_siv = std::move(result.ValueOrDie());
+    auto encrypt_result = aes_siv->EncryptDeterministically(plaintext, aad);
+    EXPECT_TRUE(encrypt_result.ok()) << encrypt_result.status();
+    auto decrypt_result =
+        aes_siv->DecryptDeterministically(encrypt_result.ValueOrDie(), aad);
+    EXPECT_TRUE(decrypt_result.ok()) << decrypt_result.status();
+    EXPECT_EQ(plaintext, decrypt_result.ValueOrDie());
+  }
+
+  {  // Using KeyData proto.
+    KeyData key_data;
+    key_data.set_type_url(aes_siv_key_type_);
+    key_data.set_value(key.SerializeAsString());
+    auto result = key_manager.GetPrimitive(key_data);
+    EXPECT_TRUE(result.ok()) << result.status();
+    auto aes_siv = std::move(result.ValueOrDie());
+    auto encrypt_result = aes_siv->EncryptDeterministically(plaintext, aad);
+    EXPECT_TRUE(encrypt_result.ok()) << encrypt_result.status();
+    auto decrypt_result =
+        aes_siv->DecryptDeterministically(encrypt_result.ValueOrDie(), aad);
+    EXPECT_TRUE(decrypt_result.ok()) << decrypt_result.status();
+    EXPECT_EQ(plaintext, decrypt_result.ValueOrDie());
+  }
+}
+
+TEST_F(AesSivKeyManagerTest, testNewKeyErrors) {
+  AesSivKeyManager key_manager;
+  const KeyFactory& key_factory = key_manager.get_key_factory();
+
+  {  // Bad key format.
+    AesEaxKeyFormat key_format;
+    auto result = key_factory.NewKey(key_format);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "not supported",
+                        result.status().error_message());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "AesEaxKeyFormat",
+                        result.status().error_message());
+  }
+
+  {  // Bad serialized key format.
+    auto result = key_factory.NewKey("some bad serialized proto");
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "not parse",
+                        result.status().error_message());
+  }
+
+  {  // Bad AesSivKeyFormat: small key_size.
+    AesSivKeyFormat key_format;
+    key_format.set_key_size(32);
+    auto result = key_factory.NewKey(key_format);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "key_size",
+                        result.status().error_message());
+  }
+}
+
+TEST_F(AesSivKeyManagerTest, testNewKeyBasic) {
+  AesSivKeyManager key_manager;
+  const KeyFactory& key_factory = key_manager.get_key_factory();
+  AesSivKeyFormat key_format;
+  key_format.set_key_size(64);
+
+  { // Via NewKey(format_proto).
+    auto result = key_factory.NewKey(key_format);
+    EXPECT_TRUE(result.ok()) << result.status();
+    auto key = std::move(result.ValueOrDie());
+    EXPECT_EQ(key_type_prefix_ + key->GetTypeName(), aes_siv_key_type_);
+    std::unique_ptr<AesSivKey> aes_siv_key(
+        reinterpret_cast<AesSivKey*>(key.release()));
+    EXPECT_EQ(0, aes_siv_key->version());
+    EXPECT_EQ(key_format.key_size(), aes_siv_key->key_value().size());
+  }
+
+  { // Via NewKey(serialized_format_proto).
+    auto result = key_factory.NewKey(key_format.SerializeAsString());
+    EXPECT_TRUE(result.ok()) << result.status();
+    auto key = std::move(result.ValueOrDie());
+    EXPECT_EQ(key_type_prefix_ + key->GetTypeName(), aes_siv_key_type_);
+    std::unique_ptr<AesSivKey> aes_siv_key(
+        reinterpret_cast<AesSivKey*>(key.release()));
+    EXPECT_EQ(0, aes_siv_key->version());
+    EXPECT_EQ(key_format.key_size(), aes_siv_key->key_value().size());
+  }
+
+  { // Via NewKeyData(serialized_format_proto).
+    auto result = key_factory.NewKeyData(key_format.SerializeAsString());
+    EXPECT_TRUE(result.ok()) << result.status();
+    auto key_data = std::move(result.ValueOrDie());
+    EXPECT_EQ(aes_siv_key_type_, key_data->type_url());
+    EXPECT_EQ(KeyData::SYMMETRIC, key_data->key_material_type());
+    AesSivKey aes_siv_key;
+    EXPECT_TRUE(aes_siv_key.ParseFromString(key_data->value()));
+    EXPECT_EQ(0, aes_siv_key.version());
+    EXPECT_EQ(key_format.key_size(), aes_siv_key.key_value().size());
+  }
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/daead/deterministic_aead_catalogue.cc b/cc/daead/deterministic_aead_catalogue.cc
new file mode 100644
index 0000000..7913bc8
--- /dev/null
+++ b/cc/daead/deterministic_aead_catalogue.cc
@@ -0,0 +1,66 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/daead/deterministic_aead_catalogue.h"
+
+#include "absl/memory/memory.h"
+#include "absl/strings/ascii.h"
+#include "tink/catalogue.h"
+#include "tink/daead/aes_siv_key_manager.h"
+#include "tink/key_manager.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+namespace {
+
+crypto::tink::util::StatusOr<std::unique_ptr<KeyManager<DeterministicAead>>>
+CreateKeyManager(const std::string& type_url) {
+  if (type_url == AesSivKeyManager::static_key_type()) {
+    std::unique_ptr<KeyManager<DeterministicAead>> manager(
+        new AesSivKeyManager());
+    return std::move(manager);
+  }
+  return ToStatusF(crypto::tink::util::error::NOT_FOUND,
+                   "No key manager for type_url '%s'.", type_url.c_str());
+}
+
+}  // anonymous namespace
+
+crypto::tink::util::StatusOr<std::unique_ptr<KeyManager<DeterministicAead>>>
+DeterministicAeadCatalogue::GetKeyManager(const std::string& type_url,
+                                          const std::string& primitive_name,
+                                          uint32_t min_version) const {
+  if (!(absl::AsciiStrToLower(primitive_name) == "deterministicaead")) {
+    return ToStatusF(crypto::tink::util::error::NOT_FOUND,
+                     "This catalogue does not support primitive %s.",
+                     primitive_name.c_str());
+  }
+  auto manager_result = CreateKeyManager(type_url);
+  if (!manager_result.ok()) return manager_result;
+  if (manager_result.ValueOrDie()->get_version() < min_version) {
+    return ToStatusF(
+        crypto::tink::util::error::NOT_FOUND,
+        "No key manager for type_url '%s' with version at least %d.",
+        type_url.c_str(), min_version);
+  }
+  return std::move(manager_result.ValueOrDie());
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/daead/deterministic_aead_catalogue.h b/cc/daead/deterministic_aead_catalogue.h
new file mode 100644
index 0000000..96917e9
--- /dev/null
+++ b/cc/daead/deterministic_aead_catalogue.h
@@ -0,0 +1,42 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_DAEAD_DETERMINISTIC_AEAD_CATALOGUE_H_
+#define TINK_DAEAD_DETERMINISTIC_AEAD_CATALOGUE_H_
+
+#include "tink/catalogue.h"
+#include "tink/deterministic_aead.h"
+#include "tink/key_manager.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+///////////////////////////////////////////////////////////////////////////////
+// A catalogue of Tink DeterministicAead key mangers.
+class DeterministicAeadCatalogue : public Catalogue<DeterministicAead> {
+ public:
+  DeterministicAeadCatalogue() {}
+
+  crypto::tink::util::StatusOr<std::unique_ptr<KeyManager<DeterministicAead>>>
+  GetKeyManager(const std::string& type_url, const std::string& primitive_name,
+                uint32_t min_version) const;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_DAEAD_DETERMINISTIC_AEAD_CATALOGUE_H_
diff --git a/cc/daead/deterministic_aead_catalogue_test.cc b/cc/daead/deterministic_aead_catalogue_test.cc
new file mode 100644
index 0000000..136a4ef
--- /dev/null
+++ b/cc/daead/deterministic_aead_catalogue_test.cc
@@ -0,0 +1,73 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/daead/deterministic_aead_catalogue.h"
+
+#include "gtest/gtest.h"
+#include "tink/catalogue.h"
+#include "tink/daead/deterministic_aead_config.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+class DeterministicAeadCatalogueTest : public ::testing::Test {};
+
+TEST_F(DeterministicAeadCatalogueTest, testBasic) {
+  std::string key_types[] = {"type.googleapis.com/google.crypto.tink.AesSivKey"};
+
+  DeterministicAeadCatalogue catalogue;
+  {
+    auto manager_result =
+        catalogue.GetKeyManager("bad.key_type", "DeterministicAead", 0);
+    EXPECT_FALSE(manager_result.ok());
+    EXPECT_EQ(util::error::NOT_FOUND, manager_result.status().error_code());
+  }
+  for (const std::string& key_type : key_types) {
+    {
+      auto manager_result =
+          catalogue.GetKeyManager(key_type, "DeterministicAead", 0);
+      EXPECT_TRUE(manager_result.ok()) << manager_result.status();
+      EXPECT_TRUE(manager_result.ValueOrDie()->DoesSupport(key_type));
+    }
+
+    {
+      auto manager_result =
+          catalogue.GetKeyManager(key_type, "deterministicaead", 0);
+      EXPECT_TRUE(manager_result.ok()) << manager_result.status();
+      EXPECT_TRUE(manager_result.ValueOrDie()->DoesSupport(key_type));
+    }
+
+    {
+      auto manager_result = catalogue.GetKeyManager(key_type, "Mac", 0);
+      EXPECT_FALSE(manager_result.ok());
+      EXPECT_EQ(util::error::NOT_FOUND, manager_result.status().error_code());
+    }
+
+    {
+      auto manager_result =
+          catalogue.GetKeyManager(key_type, "DeterministicAead", 1);
+      EXPECT_FALSE(manager_result.ok());
+      EXPECT_EQ(util::error::NOT_FOUND, manager_result.status().error_code());
+    }
+  }
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/daead/deterministic_aead_config.cc b/cc/daead/deterministic_aead_config.cc
new file mode 100644
index 0000000..e0ccd0d
--- /dev/null
+++ b/cc/daead/deterministic_aead_config.cc
@@ -0,0 +1,63 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/daead/deterministic_aead_config.h"
+
+#include "absl/memory/memory.h"
+#include "tink/config.h"
+#include "tink/daead/deterministic_aead_catalogue.h"
+#include "tink/registry.h"
+#include "tink/util/status.h"
+#include "proto/config.pb.h"
+
+using google::crypto::tink::RegistryConfig;
+
+namespace crypto {
+namespace tink {
+
+namespace {
+
+google::crypto::tink::RegistryConfig* GenerateRegistryConfig() {
+  google::crypto::tink::RegistryConfig* config =
+      new google::crypto::tink::RegistryConfig();
+  config->add_entry()->MergeFrom(*Config::GetTinkKeyTypeEntry(
+      DeterministicAeadConfig::kCatalogueName,
+      DeterministicAeadConfig::kPrimitiveName, "AesSivKey", 0, true));
+  config->set_config_name("TINK_DAEAD");
+  return config;
+}
+
+}  // anonymous namespace
+
+constexpr char DeterministicAeadConfig::kCatalogueName[];
+constexpr char DeterministicAeadConfig::kPrimitiveName[];
+
+// static
+const google::crypto::tink::RegistryConfig& DeterministicAeadConfig::Latest() {
+  static const auto config = GenerateRegistryConfig();
+  return *config;
+}
+
+// static
+util::Status DeterministicAeadConfig::Register() {
+  auto status = Registry::AddCatalogue(
+      kCatalogueName, absl::make_unique<DeterministicAeadCatalogue>());
+  if (!status.ok()) return status;
+  return Config::Register(Latest());
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/daead/deterministic_aead_config.h b/cc/daead/deterministic_aead_config.h
new file mode 100644
index 0000000..38f93fa
--- /dev/null
+++ b/cc/daead/deterministic_aead_config.h
@@ -0,0 +1,58 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_DAEAD_DAEAD_CONFIG_H_
+#define TINK_DAEAD_DAEAD_CONFIG_H_
+
+#include "tink/config.h"
+#include "tink/util/status.h"
+#include "proto/config.pb.h"
+
+namespace crypto {
+namespace tink {
+
+///////////////////////////////////////////////////////////////////////////////
+// Static methods and constants for registering with the Registry
+// all instances of DeterministicAead key types supported in a release of Tink.
+//
+// To register all DeterministicAead key types from the current Tink release
+// one can do:
+//
+//   auto status = DeterministicAeadConfig::Register();
+//
+// For more information on creation and usage of DeterministicAead instances
+// see DeterministicAeadFactory.
+class DeterministicAeadConfig {
+ public:
+  static constexpr char kCatalogueName[] = "TinkDeterministicAead";
+  static constexpr char kPrimitiveName[] = "DeterministicAead";
+
+  // Returns config of DeterministicAead implementations supported
+  // in the current Tink release.
+  static const google::crypto::tink::RegistryConfig& Latest();
+
+  // Registers key managers for all DeterministicAead key types
+  // from the current Tink release.
+  static crypto::tink::util::Status Register();
+
+ private:
+  DeterministicAeadConfig() {}
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_DAEAD_DAEAD_CONFIG_H_
diff --git a/cc/daead/deterministic_aead_config_test.cc b/cc/daead/deterministic_aead_config_test.cc
new file mode 100644
index 0000000..137715b
--- /dev/null
+++ b/cc/daead/deterministic_aead_config_test.cc
@@ -0,0 +1,146 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/daead/deterministic_aead_config.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/catalogue.h"
+#include "tink/config.h"
+#include "tink/daead/deterministic_aead_key_templates.h"
+#include "tink/deterministic_aead.h"
+#include "tink/keyset_handle.h"
+#include "tink/registry.h"
+#include "tink/util/status.h"
+#include "tink/util/test_util.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::test::DummyDeterministicAead;
+using ::testing::Eq;
+
+class DummyDaeadCatalogue : public Catalogue<DeterministicAead> {
+ public:
+  DummyDaeadCatalogue() {}
+
+  crypto::tink::util::StatusOr<std::unique_ptr<KeyManager<DeterministicAead>>>
+  GetKeyManager(const std::string& type_url, const std::string& primitive_name,
+                uint32_t min_version) const override {
+    return util::Status::UNKNOWN;
+  }
+};
+
+class DeterministicAeadConfigTest : public ::testing::Test {
+ protected:
+  void SetUp() override { Registry::Reset(); }
+};
+
+TEST_F(DeterministicAeadConfigTest, testBasic) {
+  std::string aes_siv_key_type = "type.googleapis.com/google.crypto.tink.AesSivKey";
+  auto& config = DeterministicAeadConfig::Latest();
+
+  EXPECT_EQ(1, DeterministicAeadConfig::Latest().entry_size());
+
+  EXPECT_EQ("TinkDeterministicAead", config.entry(0).catalogue_name());
+  EXPECT_EQ("DeterministicAead", config.entry(0).primitive_name());
+  EXPECT_EQ(aes_siv_key_type, config.entry(0).type_url());
+  EXPECT_EQ(true, config.entry(0).new_key_allowed());
+  EXPECT_EQ(0, config.entry(0).key_manager_version());
+
+  // No key manager before registration.
+  auto manager_result =
+      Registry::get_key_manager<DeterministicAead>(aes_siv_key_type);
+  EXPECT_FALSE(manager_result.ok());
+  EXPECT_EQ(util::error::NOT_FOUND, manager_result.status().error_code());
+
+  // Registration of standard key types works.
+  auto status = DeterministicAeadConfig::Register();
+  EXPECT_TRUE(status.ok()) << status;
+  manager_result =
+      Registry::get_key_manager<DeterministicAead>(aes_siv_key_type);
+  EXPECT_TRUE(manager_result.ok()) << manager_result.status();
+  EXPECT_TRUE(manager_result.ValueOrDie()->DoesSupport(aes_siv_key_type));
+}
+
+TEST_F(DeterministicAeadConfigTest, testRegister) {
+  std::string key_type = "type.googleapis.com/google.crypto.tink.AesSivKey";
+
+  // Try on empty registry.
+  auto status = Config::Register(DeterministicAeadConfig::Latest());
+  EXPECT_FALSE(status.ok());
+  EXPECT_EQ(util::error::NOT_FOUND, status.error_code());
+  auto manager_result = Registry::get_key_manager<DeterministicAead>(key_type);
+  EXPECT_FALSE(manager_result.ok());
+
+  // Register and try again.
+  status = DeterministicAeadConfig::Register();
+  EXPECT_TRUE(status.ok()) << status;
+  manager_result = Registry::get_key_manager<DeterministicAead>(key_type);
+  EXPECT_TRUE(manager_result.ok()) << manager_result.status();
+
+  // Try Register() again, should succeed (idempotence).
+  status = DeterministicAeadConfig::Register();
+  EXPECT_TRUE(status.ok()) << status;
+
+  // Reset the registry, and try overriding a catalogue with a different one.
+  Registry::Reset();
+  status = Registry::AddCatalogue("TinkDeterministicAead",
+                                  absl::make_unique<DummyDaeadCatalogue>());
+  EXPECT_TRUE(status.ok()) << status;
+  status = DeterministicAeadConfig::Register();
+  EXPECT_FALSE(status.ok());
+  EXPECT_EQ(util::error::ALREADY_EXISTS, status.error_code());
+}
+
+// Tests that the DeterministicAeadWrapper has been properly registered and we
+// can wrap primitives.
+TEST_F(DeterministicAeadConfigTest, WrappersRegistered) {
+  ASSERT_TRUE(DeterministicAeadConfig::Register().ok());
+
+  google::crypto::tink::Keyset::Key key;
+  key.set_status(google::crypto::tink::KeyStatusType::ENABLED);
+  key.set_key_id(1234);
+  key.set_output_prefix_type(google::crypto::tink::OutputPrefixType::RAW);
+  auto primitive_set = absl::make_unique<PrimitiveSet<DeterministicAead>>();
+  primitive_set->set_primary(
+      primitive_set
+          ->AddPrimitive(absl::make_unique<DummyDeterministicAead>("dummy"),
+                         key)
+          .ValueOrDie());
+
+  auto registry_wrapped = Registry::Wrap(std::move(primitive_set));
+
+  ASSERT_TRUE(registry_wrapped.ok()) << registry_wrapped.status();
+  auto encryption_result =
+      registry_wrapped.ValueOrDie()->EncryptDeterministically("secret", "");
+  ASSERT_TRUE(encryption_result.ok());
+
+  auto decryption_result =
+      DummyDeterministicAead("dummy").DecryptDeterministically(
+          encryption_result.ValueOrDie(), "");
+  ASSERT_TRUE(decryption_result.status().ok());
+  EXPECT_THAT(decryption_result.ValueOrDie(), Eq("secret"));
+
+  decryption_result = DummyDeterministicAead("dummy").DecryptDeterministically(
+      encryption_result.ValueOrDie(), "wrog");
+  EXPECT_FALSE(decryption_result.status().ok());
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/daead/deterministic_aead_factory.cc b/cc/daead/deterministic_aead_factory.cc
new file mode 100644
index 0000000..bdd23ed
--- /dev/null
+++ b/cc/daead/deterministic_aead_factory.cc
@@ -0,0 +1,55 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/daead/deterministic_aead_factory.h"
+
+#include "tink/daead/deterministic_aead_wrapper.h"
+#include "tink/deterministic_aead.h"
+#include "tink/key_manager.h"
+#include "tink/keyset_handle.h"
+#include "tink/registry.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+// static
+util::StatusOr<std::unique_ptr<DeterministicAead>>
+DeterministicAeadFactory::GetPrimitive(const KeysetHandle& keyset_handle) {
+  util::Status status = Registry::RegisterPrimitiveWrapper(
+      absl::make_unique<DeterministicAeadWrapper>());
+  if (!status.ok()) {
+    return status;
+  }
+  return keyset_handle.GetPrimitive<DeterministicAead>();
+}
+
+// static
+util::StatusOr<std::unique_ptr<DeterministicAead>>
+DeterministicAeadFactory::GetPrimitive(
+    const KeysetHandle& keyset_handle,
+    const KeyManager<DeterministicAead>* custom_key_manager) {
+  util::Status status = Registry::RegisterPrimitiveWrapper(
+      absl::make_unique<DeterministicAeadWrapper>());
+  if (!status.ok()) {
+    return status;
+  }
+  return keyset_handle.GetPrimitive<DeterministicAead>(custom_key_manager);
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/daead/deterministic_aead_factory.h b/cc/daead/deterministic_aead_factory.h
new file mode 100644
index 0000000..5359a29
--- /dev/null
+++ b/cc/daead/deterministic_aead_factory.h
@@ -0,0 +1,62 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_DAEAD_DETERMINISTIC_AEAD_FACTORY_H_
+#define TINK_DAEAD_DETERMINISTIC_AEAD_FACTORY_H_
+
+#include "absl/base/macros.h"
+#include "tink/deterministic_aead.h"
+#include "tink/key_manager.h"
+#include "tink/keyset_handle.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+///////////////////////////////////////////////////////////////////////////////
+// This class is deprecated. Call
+// keyset_handle->GetPrimitive<DeterministicAead>() instead.
+//
+// Note that in order to for this change to be safe, the
+// DeterministicAeadWrapper has to be registered in your binary before this
+// call. This happens automatically if you call one of
+// * DeterministicAeadConfig::Register()
+// * TinkConfig::Register()
+class ABSL_DEPRECATED(
+    "Call getPrimitive<DeterministicAeadFactory>() on the keyset_handle after "
+    "registering the DeterministicAeadWrapper instead.")
+    DeterministicAeadFactory {
+ public:
+  // Returns an DeterministicAead-primitive that uses key material from
+  // the keyset specified via 'keyset_handle'.
+  static crypto::tink::util::StatusOr<std::unique_ptr<DeterministicAead>>
+  GetPrimitive(const KeysetHandle& keyset_handle);
+
+  // Returns an DeterministicAead-primitive that uses key material from
+  // the keyset specified via 'keyset_handle' and is instantiated by the given
+  // 'custom_key_manager' (instead of the key manager from the Registry).
+  static crypto::tink::util::StatusOr<std::unique_ptr<DeterministicAead>>
+  GetPrimitive(const KeysetHandle& keyset_handle,
+               const KeyManager<DeterministicAead>* custom_key_manager);
+
+ private:
+  DeterministicAeadFactory() {}
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_DAEAD_DETERMINISTIC_AEAD_FACTORY_H_
diff --git a/cc/daead/deterministic_aead_factory_test.cc b/cc/daead/deterministic_aead_factory_test.cc
new file mode 100644
index 0000000..ad4ad1d
--- /dev/null
+++ b/cc/daead/deterministic_aead_factory_test.cc
@@ -0,0 +1,127 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+#include "tink/daead/deterministic_aead_factory.h"
+
+#include "gtest/gtest.h"
+#include "tink/crypto_format.h"
+#include "tink/daead/aes_siv_key_manager.h"
+#include "tink/daead/deterministic_aead_config.h"
+#include "tink/deterministic_aead.h"
+#include "tink/keyset_handle.h"
+#include "tink/util/keyset_util.h"
+#include "tink/util/status.h"
+#include "tink/util/test_util.h"
+#include "proto/aes_siv.pb.h"
+#include "proto/tink.pb.h"
+
+using crypto::tink::KeysetUtil;
+using crypto::tink::test::AddRawKey;
+using crypto::tink::test::AddTinkKey;
+using google::crypto::tink::AesSivKeyFormat;
+using google::crypto::tink::KeyData;
+using google::crypto::tink::Keyset;
+using google::crypto::tink::KeyStatusType;
+
+
+namespace crypto {
+namespace tink {
+namespace {
+
+class DeterministicAeadFactoryTest : public ::testing::Test {};
+
+TEST_F(DeterministicAeadFactoryTest, testBasic) {
+  Keyset keyset;
+  auto daead_result = DeterministicAeadFactory::GetPrimitive(
+      *KeysetUtil::GetKeysetHandle(keyset));
+  EXPECT_FALSE(daead_result.ok());
+  EXPECT_EQ(util::error::INVALID_ARGUMENT, daead_result.status().error_code());
+  EXPECT_PRED_FORMAT2(testing::IsSubstring, "at least one key",
+                      daead_result.status().error_message());
+}
+
+TEST_F(DeterministicAeadFactoryTest, testPrimitive) {
+  // Prepare a template for generating keys for a Keyset.
+  AesSivKeyManager key_manager;
+  const KeyFactory& key_factory = key_manager.get_key_factory();
+  std::string key_type = key_manager.get_key_type();
+
+  AesSivKeyFormat key_format;
+  key_format.set_key_size(64);
+
+  // Prepare a Keyset.
+  Keyset keyset;
+  uint32_t key_id_1 = 1234543;
+  auto new_key = std::move(key_factory.NewKey(key_format).ValueOrDie());
+  AddTinkKey(key_type, key_id_1, *new_key, KeyStatusType::ENABLED,
+             KeyData::SYMMETRIC, &keyset);
+
+  uint32_t key_id_2 = 726329;
+  new_key = std::move(key_factory.NewKey(key_format).ValueOrDie());
+  AddRawKey(key_type, key_id_2, *new_key, KeyStatusType::ENABLED,
+            KeyData::SYMMETRIC, &keyset);
+
+  uint32_t key_id_3 = 7213743;
+  new_key = std::move(key_factory.NewKey(key_format).ValueOrDie());
+  AddTinkKey(key_type, key_id_3, *new_key, KeyStatusType::ENABLED,
+             KeyData::SYMMETRIC, &keyset);
+
+  keyset.set_primary_key_id(key_id_3);
+
+  // Initialize the registry.
+  ASSERT_TRUE(DeterministicAeadConfig::Register().ok());
+
+  // Create a KeysetHandle and use it with the factory.
+  auto daead_result = DeterministicAeadFactory::GetPrimitive(
+      *KeysetUtil::GetKeysetHandle(keyset));
+  EXPECT_TRUE(daead_result.ok()) << daead_result.status();
+  auto daead = std::move(daead_result.ValueOrDie());
+
+  // Test the resulting DeterministicAead-instance.
+  std::string plaintext = "some_plaintext";
+  std::string aad = "some_aad";
+
+  auto encrypt_result = daead->EncryptDeterministically(plaintext, aad);
+  EXPECT_TRUE(encrypt_result.ok()) << encrypt_result.status();
+  std::string ciphertext = encrypt_result.ValueOrDie();
+  std::string prefix =
+      CryptoFormat::get_output_prefix(keyset.key(2)).ValueOrDie();
+  EXPECT_PRED_FORMAT2(testing::IsSubstring, prefix, ciphertext);
+
+  auto decrypt_result = daead->DecryptDeterministically(ciphertext, aad);
+  EXPECT_TRUE(decrypt_result.ok()) << decrypt_result.status();
+  EXPECT_EQ(plaintext, decrypt_result.ValueOrDie());
+
+  decrypt_result = daead->DecryptDeterministically("some bad ciphertext", aad);
+  EXPECT_FALSE(decrypt_result.ok());
+  EXPECT_EQ(util::error::INVALID_ARGUMENT,
+            decrypt_result.status().error_code());
+  EXPECT_PRED_FORMAT2(testing::IsSubstring, "decryption failed",
+                      decrypt_result.status().error_message());
+
+  // Create raw ciphertext with 2nd key, and decrypt
+  // with DeterministicAead-instance.
+  auto raw_daead = std::move(
+      key_manager.GetPrimitive(keyset.key(1).key_data()).ValueOrDie());
+  std::string raw_ciphertext =
+      raw_daead->EncryptDeterministically(plaintext, aad).ValueOrDie();
+  decrypt_result = daead->DecryptDeterministically(ciphertext, aad);
+  EXPECT_TRUE(decrypt_result.ok()) << decrypt_result.status();
+  EXPECT_EQ(plaintext, decrypt_result.ValueOrDie());
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/daead/deterministic_aead_key_templates.cc b/cc/daead/deterministic_aead_key_templates.cc
new file mode 100644
index 0000000..dc9137c
--- /dev/null
+++ b/cc/daead/deterministic_aead_key_templates.cc
@@ -0,0 +1,53 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/daead/deterministic_aead_key_templates.h"
+
+#include "proto/aes_siv.pb.h"
+#include "proto/common.pb.h"
+#include "proto/tink.pb.h"
+
+using google::crypto::tink::AesSivKeyFormat;
+using google::crypto::tink::KeyTemplate;
+using google::crypto::tink::OutputPrefixType;
+
+namespace crypto {
+namespace tink {
+
+namespace {
+
+KeyTemplate* NewAesSivKeyTemplate(int key_size_in_bytes) {
+  KeyTemplate* key_template = new KeyTemplate;
+  key_template->set_type_url(
+      "type.googleapis.com/google.crypto.tink.AesSivKey");
+  key_template->set_output_prefix_type(OutputPrefixType::TINK);
+  AesSivKeyFormat key_format;
+  key_format.set_key_size(key_size_in_bytes);
+  key_format.SerializeToString(key_template->mutable_value());
+  return key_template;
+}
+
+}  // anonymous namespace
+
+// static
+const KeyTemplate& DeterministicAeadKeyTemplates::Aes256Siv() {
+  static const KeyTemplate* key_template =
+      NewAesSivKeyTemplate(/* key_size_in_bytes= */ 64);
+  return *key_template;
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/daead/deterministic_aead_key_templates.h b/cc/daead/deterministic_aead_key_templates.h
new file mode 100644
index 0000000..37f79a7
--- /dev/null
+++ b/cc/daead/deterministic_aead_key_templates.h
@@ -0,0 +1,48 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_DAEAD_DETERMINISTIC_AEAD_KEY_TEMPLATES_H_
+#define TINK_DAEAD_DETERMINISTIC_AEAD_KEY_TEMPLATES_H_
+
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+
+///////////////////////////////////////////////////////////////////////////////
+// Pre-generated KeyTemplate for DeterministicAead key types.  One can use
+// these templates to generate new KeysetHandle object with fresh keys.
+// To generate a new keyset that contains a single AesGcmKey, one can do:
+//
+//   auto status = DeterministicAeadConfig::Register();
+//   if (!status.ok()) { /* fail with error */ }
+//   auto handle_result =
+//       KeysetHandle::GenerateNew(DeterministicAeadKeyTemplates::Aes256Siv());
+//   if (!handle_result.ok()) { /* fail with error */ }
+//   auto keyset_handle = std::move(handle_result.ValueOrDie());
+class DeterministicAeadKeyTemplates {
+ public:
+  // Returns a KeyTemplate that generates new instances of AesSivKey
+  // with the following parameters:
+  //   - key size: 64 bytes
+  //   - OutputPrefixType: TINK
+  static const google::crypto::tink::KeyTemplate& Aes256Siv();
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_DAEAD_DETERMINISTIC_AEAD_KEY_TEMPLATES_H_
diff --git a/cc/daead/deterministic_aead_key_templates_test.cc b/cc/daead/deterministic_aead_key_templates_test.cc
new file mode 100644
index 0000000..daa19d2
--- /dev/null
+++ b/cc/daead/deterministic_aead_key_templates_test.cc
@@ -0,0 +1,63 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/daead/deterministic_aead_key_templates.h"
+
+#include "gtest/gtest.h"
+#include "tink/daead/aes_siv_key_manager.h"
+#include "proto/aes_siv.pb.h"
+#include "proto/common.pb.h"
+#include "proto/tink.pb.h"
+
+using google::crypto::tink::AesSivKeyFormat;
+using google::crypto::tink::HashType;
+using google::crypto::tink::KeyTemplate;
+using google::crypto::tink::OutputPrefixType;
+
+namespace crypto {
+namespace tink {
+namespace {
+
+TEST(DeterministicAeadKeyTemplatesTest, testAesSivKeyTemplates) {
+  std::string type_url = "type.googleapis.com/google.crypto.tink.AesSivKey";
+
+  {  // Test Aes256Siv().
+    // Check that returned template is correct.
+    const KeyTemplate& key_template =
+        DeterministicAeadKeyTemplates::Aes256Siv();
+    EXPECT_EQ(type_url, key_template.type_url());
+    EXPECT_EQ(OutputPrefixType::TINK, key_template.output_prefix_type());
+    AesSivKeyFormat key_format;
+    EXPECT_TRUE(key_format.ParseFromString(key_template.value()));
+    EXPECT_EQ(64, key_format.key_size());
+
+    // Check that reference to the same object is returned.
+    const KeyTemplate& key_template_2 =
+        DeterministicAeadKeyTemplates::Aes256Siv();
+    EXPECT_EQ(&key_template, &key_template_2);
+
+    // Check that the template works with the key manager.
+    AesSivKeyManager key_manager;
+    EXPECT_EQ(key_manager.get_key_type(), key_template.type_url());
+    auto new_key_result =
+        key_manager.get_key_factory().NewKey(key_template.value());
+    EXPECT_TRUE(new_key_result.ok()) << new_key_result.status();
+  }
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/daead/deterministic_aead_wrapper.cc b/cc/daead/deterministic_aead_wrapper.cc
new file mode 100644
index 0000000..09ec0c5
--- /dev/null
+++ b/cc/daead/deterministic_aead_wrapper.cc
@@ -0,0 +1,131 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/daead/deterministic_aead_wrapper.h"
+
+#include "tink/crypto_format.h"
+#include "tink/deterministic_aead.h"
+#include "tink/primitive_set.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+namespace {
+
+util::Status Validate(PrimitiveSet<DeterministicAead>* daead_set) {
+  if (daead_set == nullptr) {
+    return util::Status(util::error::INTERNAL, "daead_set must be non-NULL");
+  }
+  if (daead_set->get_primary() == nullptr) {
+    return util::Status(util::error::INVALID_ARGUMENT,
+                        "daead_set has no primary");
+  }
+  return util::Status::OK;
+}
+
+class  DeterministicAeadSetWrapper : public DeterministicAead {
+ public:
+  explicit DeterministicAeadSetWrapper(
+      std::unique_ptr<PrimitiveSet<DeterministicAead>> daead_set)
+      : daead_set_(std::move(daead_set)) {}
+
+  crypto::tink::util::StatusOr<std::string> EncryptDeterministically(
+      absl::string_view plaintext,
+      absl::string_view associated_data) const override;
+
+  crypto::tink::util::StatusOr<std::string> DecryptDeterministically(
+      absl::string_view ciphertext,
+      absl::string_view associated_data) const override;
+
+  ~DeterministicAeadSetWrapper() override {}
+
+ private:
+  std::unique_ptr<PrimitiveSet<DeterministicAead>> daead_set_;
+};
+
+util::StatusOr<std::string> DeterministicAeadSetWrapper::EncryptDeterministically(
+    absl::string_view plaintext, absl::string_view associated_data) const {
+  // BoringSSL expects a non-null pointer for plaintext and additional_data,
+  // regardless of whether the size is 0.
+  plaintext = subtle::SubtleUtilBoringSSL::EnsureNonNull(plaintext);
+  associated_data = subtle::SubtleUtilBoringSSL::EnsureNonNull(associated_data);
+
+  auto encrypt_result =
+      daead_set_->get_primary()->get_primitive().EncryptDeterministically(
+          plaintext, associated_data);
+  if (!encrypt_result.ok()) return encrypt_result.status();
+  const std::string& key_id = daead_set_->get_primary()->get_identifier();
+  return key_id + encrypt_result.ValueOrDie();
+}
+
+util::StatusOr<std::string> DeterministicAeadSetWrapper::DecryptDeterministically(
+    absl::string_view ciphertext, absl::string_view associated_data) const {
+  // BoringSSL expects a non-null pointer for plaintext and additional_data,
+  // regardless of whether the size is 0.
+  associated_data = subtle::SubtleUtilBoringSSL::EnsureNonNull(associated_data);
+
+  if (ciphertext.length() > CryptoFormat::kNonRawPrefixSize) {
+    const std::string& key_id = std::string(
+        ciphertext.substr(0, CryptoFormat::kNonRawPrefixSize));
+    auto primitives_result = daead_set_->get_primitives(key_id);
+    if (primitives_result.ok()) {
+      absl::string_view raw_ciphertext =
+          ciphertext.substr(CryptoFormat::kNonRawPrefixSize);
+      for (auto& daead_entry : *(primitives_result.ValueOrDie())) {
+        DeterministicAead& daead = daead_entry->get_primitive();
+        auto decrypt_result =
+            daead.DecryptDeterministically(raw_ciphertext, associated_data);
+        if (decrypt_result.ok()) {
+          return std::move(decrypt_result.ValueOrDie());
+        } else {
+          // LOG that a matching key didn't decrypt the ciphertext.
+        }
+      }
+    }
+  }
+
+  // No matching key succeeded with decryption, try all RAW keys.
+  auto raw_primitives_result = daead_set_->get_raw_primitives();
+  if (raw_primitives_result.ok()) {
+    for (auto& daead_entry : *(raw_primitives_result.ValueOrDie())) {
+      DeterministicAead& daead = daead_entry->get_primitive();
+      auto decrypt_result =
+          daead.DecryptDeterministically(ciphertext, associated_data);
+      if (decrypt_result.ok()) {
+        return std::move(decrypt_result.ValueOrDie());
+      }
+    }
+  }
+  return util::Status(util::error::INVALID_ARGUMENT, "decryption failed");
+}
+
+}  // anonymous namespace
+
+util::StatusOr<std::unique_ptr<DeterministicAead>>
+DeterministicAeadWrapper::Wrap(
+    std::unique_ptr<PrimitiveSet<DeterministicAead>> primitive_set) const {
+  util::Status status = Validate(primitive_set.get());
+  if (!status.ok()) return status;
+  std::unique_ptr<DeterministicAead> daead(
+      new DeterministicAeadSetWrapper(std::move(primitive_set)));
+  return std::move(daead);
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/daead/deterministic_aead_wrapper.h b/cc/daead/deterministic_aead_wrapper.h
new file mode 100644
index 0000000..0d650c2
--- /dev/null
+++ b/cc/daead/deterministic_aead_wrapper.h
@@ -0,0 +1,49 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_DAEAD_DETERMINISTIC_AEAD_WRAPPER_H_
+#define TINK_DAEAD_DETERMINISTIC_AEAD_WRAPPER_H_
+
+#include "absl/strings/string_view.h"
+#include "tink/deterministic_aead.h"
+#include "tink/primitive_set.h"
+#include "tink/primitive_wrapper.h"
+#include "tink/util/statusor.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+
+// Wraps a set of DeterministicAead-instances that correspond to a keyset,
+// and combines them into a single DeterministicAead-primitive, that uses
+// the provided instances, depending on the context:
+//   * DeterministicAead::EncryptDeterministically(...) uses the primary
+//     instance from the set
+//   * DeterministicAead::DecryptDeterministically(...) uses the instance
+//     that matches the ciphertext prefix.
+class DeterministicAeadWrapper : public PrimitiveWrapper<DeterministicAead> {
+ public:
+  // Returns a DeterministicAead-primitive that uses Daead-instances provided
+  // in 'daead_set', which must be non-NULL and must contain a primary instance.
+  crypto::tink::util::StatusOr<std::unique_ptr<DeterministicAead>> Wrap(
+      std::unique_ptr<PrimitiveSet<DeterministicAead>> primitive_set)
+      const override;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_DAEAD_DETERMINISTIC_AEAD_WRAPPER_H_
diff --git a/cc/daead/deterministic_aead_wrapper_test.cc b/cc/daead/deterministic_aead_wrapper_test.cc
new file mode 100644
index 0000000..388628d
--- /dev/null
+++ b/cc/daead/deterministic_aead_wrapper_test.cc
@@ -0,0 +1,130 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/daead/deterministic_aead_wrapper.h"
+#include "gtest/gtest.h"
+#include "absl/memory/memory.h"
+#include "tink/deterministic_aead.h"
+#include "tink/primitive_set.h"
+#include "tink/util/status.h"
+#include "tink/util/test_util.h"
+
+using crypto::tink::test::DummyDeterministicAead;
+using google::crypto::tink::Keyset;
+using google::crypto::tink::OutputPrefixType;
+
+namespace crypto {
+namespace tink {
+namespace {
+
+class DeterministicAeadSetWrapperTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+  }
+  void TearDown() override {
+  }
+};
+
+TEST_F(DeterministicAeadSetWrapperTest, testBasic) {
+  {  // daead_set is nullptr.
+    auto daead_result =
+        DeterministicAeadWrapper().Wrap(nullptr);
+    EXPECT_FALSE(daead_result.ok());
+    EXPECT_EQ(util::error::INTERNAL, daead_result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "non-NULL",
+                        daead_result.status().error_message());
+  }
+
+  {  // daead_set has no primary primitive.
+    std::unique_ptr<PrimitiveSet<DeterministicAead>> daead_set(
+        new PrimitiveSet<DeterministicAead>());
+    auto daead_result =
+        DeterministicAeadWrapper().Wrap(std::move(daead_set));
+    EXPECT_FALSE(daead_result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT,
+              daead_result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "no primary",
+                        daead_result.status().error_message());
+  }
+
+  {  // Correct daead_set;
+    Keyset::Key* key;
+    Keyset keyset;
+
+    uint32_t key_id_0 = 1234543;
+    key = keyset.add_key();
+    key->set_output_prefix_type(OutputPrefixType::TINK);
+    key->set_key_id(key_id_0);
+
+    uint32_t key_id_1 = 726329;
+    key = keyset.add_key();
+    key->set_output_prefix_type(OutputPrefixType::LEGACY);
+    key->set_key_id(key_id_1);
+
+    uint32_t key_id_2 = 7213743;
+    key = keyset.add_key();
+    key->set_output_prefix_type(OutputPrefixType::TINK);
+    key->set_key_id(key_id_2);
+
+    std::string daead_name_0 = "daead0";
+    std::string daead_name_1 = "daead1";
+    std::string daead_name_2 = "daead2";
+    std::unique_ptr<PrimitiveSet<DeterministicAead>> daead_set(
+        new PrimitiveSet<DeterministicAead>());
+    std::unique_ptr<DeterministicAead> daead(
+        new DummyDeterministicAead(daead_name_0));
+    auto entry_result =
+        daead_set->AddPrimitive(std::move(daead), keyset.key(0));
+    ASSERT_TRUE(entry_result.ok());
+    daead = absl::make_unique<DummyDeterministicAead>(daead_name_1);
+    entry_result = daead_set->AddPrimitive(std::move(daead), keyset.key(1));
+    ASSERT_TRUE(entry_result.ok());
+    daead = absl::make_unique<DummyDeterministicAead>(daead_name_2);
+    entry_result = daead_set->AddPrimitive(std::move(daead), keyset.key(2));
+    ASSERT_TRUE(entry_result.ok());
+    // The last key is the primary.
+    daead_set->set_primary(entry_result.ValueOrDie());
+
+    // Wrap daead_set and test the resulting DeterministicAead.
+    auto daead_result =
+        DeterministicAeadWrapper().Wrap(std::move(daead_set));
+    EXPECT_TRUE(daead_result.ok()) << daead_result.status();
+    daead = std::move(daead_result.ValueOrDie());
+    std::string plaintext = "some_plaintext";
+    std::string aad = "some_aad";
+
+    auto encrypt_result = daead->EncryptDeterministically(plaintext, aad);
+    EXPECT_TRUE(encrypt_result.ok()) << encrypt_result.status();
+    std::string ciphertext = encrypt_result.ValueOrDie();
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, daead_name_2, ciphertext);
+
+    auto decrypt_result = daead->DecryptDeterministically(ciphertext, aad);
+    EXPECT_TRUE(decrypt_result.ok()) << decrypt_result.status();
+    EXPECT_EQ(plaintext, decrypt_result.ValueOrDie());
+
+    decrypt_result =
+        daead->DecryptDeterministically("some bad ciphertext", aad);
+    EXPECT_FALSE(decrypt_result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT,
+              decrypt_result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "decryption failed",
+                        decrypt_result.status().error_message());
+  }
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/deterministic_aead.h b/cc/deterministic_aead.h
new file mode 100644
index 0000000..73027b6
--- /dev/null
+++ b/cc/deterministic_aead.h
@@ -0,0 +1,61 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_DETERMINISTIC_AEAD_H_
+#define TINK_DETERMINISTIC_AEAD_H_
+
+#include "absl/strings/string_view.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+///////////////////////////////////////////////////////////////////////////////
+// The interface for deterministic authenticated encryption with associated
+// data.
+// TODO(bleichen): Copy the interface from Java.
+//   Check the properties:
+//   - authenticated
+//   - secure in multi-user setting
+//   - thread safe/copy safe
+// References:
+// https://eprint.iacr.org/2016/1124.pdf
+class DeterministicAead {
+ public:
+  // Encrypts 'plaintext' with 'associated_data' as associated data
+  // deterministically, and returns the resulting ciphertext.
+  // The ciphertext allows for checking authenticity and integrity
+  // of the associated data, but does not guarantee its secrecy.
+  virtual crypto::tink::util::StatusOr<std::string> EncryptDeterministically(
+      absl::string_view plaintext,
+      absl::string_view associated_data) const = 0;
+
+  // Decrypts 'ciphertext' with 'associated_data' as associated data,
+  // and returns the resulting plaintext.
+  // The decryption verifies the authenticity and integrity
+  // of the associated data, but there are no guarantees wrt. secrecy
+  // of that data.
+  virtual crypto::tink::util::StatusOr<std::string> DecryptDeterministically(
+      absl::string_view ciphertext,
+      absl::string_view associated_data) const = 0;
+
+  virtual ~DeterministicAead() {}
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_DETERMINISTIC_AEAD_H_
diff --git a/cc/deterministic_aead_config.h b/cc/deterministic_aead_config.h
new file mode 100644
index 0000000..4c98d78
--- /dev/null
+++ b/cc/deterministic_aead_config.h
@@ -0,0 +1,24 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_DETERMINISTIC_AEAD_CONFIG_H_
+#define TINK_DETERMINISTIC_AEAD_CONFIG_H_
+
+// IWYU pragma: begin_exports
+#include "tink/daead/deterministic_aead_config.h"
+// IWYU pragma: end_exports
+
+#endif  // TINK_DETERMINISTIC_AEAD_CONFIG_H_
diff --git a/cc/deterministic_aead_factory.h b/cc/deterministic_aead_factory.h
new file mode 100644
index 0000000..74575e6
--- /dev/null
+++ b/cc/deterministic_aead_factory.h
@@ -0,0 +1,24 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_DETERMINISTIC_AEAD_FACTORY_H_
+#define TINK_DETERMINISTIC_AEAD_FACTORY_H_
+
+// IWYU pragma: begin_exports
+#include "tink/daead/deterministic_aead_factory.h"
+// IWYU pragma: end_exports
+
+#endif  // TINK_DETERMINISTIC_AEAD_FACTORY_H_
diff --git a/cc/deterministic_aead_key_templates.h b/cc/deterministic_aead_key_templates.h
new file mode 100644
index 0000000..1ff3bdc
--- /dev/null
+++ b/cc/deterministic_aead_key_templates.h
@@ -0,0 +1,24 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_DETERMINISTIC_AEAD_KEY_TEMPLATES_H_
+#define TINK_DETERMINISTIC_AEAD_KEY_TEMPLATES_H_
+
+// IWYU pragma: begin_exports
+#include "tink/daead/deterministic_aead_key_templates.h"
+// IWYU pragma: end_exports
+
+#endif  // TINK_DETERMINISTIC_AEAD_KEY_TEMPLATES_H_
diff --git a/cc/exported_symbols.lds b/cc/exported_symbols.lds
index d74d230..e6ccbbd 100644
--- a/cc/exported_symbols.lds
+++ b/cc/exported_symbols.lds
@@ -1 +1,2 @@
 *tink*
+*absl*
diff --git a/cc/hybrid/BUILD.bazel b/cc/hybrid/BUILD.bazel
index 49f4cb6..0a229c4 100644
--- a/cc/hybrid/BUILD.bazel
+++ b/cc/hybrid/BUILD.bazel
@@ -15,42 +15,43 @@
         "//cc/aead:aead_config",
         "//cc/util:status",
         "//proto:config_cc_proto",
+        "@com_google_absl//absl/memory",
     ],
 )
 
 cc_library(
-    name = "hybrid_decrypt_set_wrapper",
-    srcs = ["hybrid_decrypt_set_wrapper.cc"],
-    hdrs = ["hybrid_decrypt_set_wrapper.h"],
+    name = "hybrid_decrypt_wrapper",
+    srcs = ["hybrid_decrypt_wrapper.cc"],
+    hdrs = ["hybrid_decrypt_wrapper.h"],
     include_prefix = "tink",
     strip_include_prefix = "/cc",
-    visibility = ["//visibility:private"],
     deps = [
         "//cc:crypto_format",
         "//cc:hybrid_decrypt",
         "//cc:primitive_set",
+        "//cc:primitive_wrapper",
+        "//cc/subtle:subtle_util_boringssl",
         "//cc/util:status",
         "//cc/util:statusor",
-        "//cc/subtle:subtle_util_boringssl",
         "//proto:tink_cc_proto",
         "@com_google_absl//absl/strings",
     ],
 )
 
 cc_library(
-    name = "hybrid_encrypt_set_wrapper",
-    srcs = ["hybrid_encrypt_set_wrapper.cc"],
-    hdrs = ["hybrid_encrypt_set_wrapper.h"],
+    name = "hybrid_encrypt_wrapper",
+    srcs = ["hybrid_encrypt_wrapper.cc"],
+    hdrs = ["hybrid_encrypt_wrapper.h"],
     include_prefix = "tink",
     strip_include_prefix = "/cc",
-    visibility = ["//visibility:private"],
     deps = [
         "//cc:crypto_format",
         "//cc:hybrid_encrypt",
         "//cc:primitive_set",
+        "//cc:primitive_wrapper",
+        "//cc/subtle:subtle_util_boringssl",
         "//cc/util:status",
         "//cc/util:statusor",
-        "//cc/subtle:subtle_util_boringssl",
         "//proto:tink_cc_proto",
         "@com_google_absl//absl/strings",
     ],
@@ -79,7 +80,7 @@
     include_prefix = "tink",
     strip_include_prefix = "/cc",
     deps = [
-        ":hybrid_decrypt_set_wrapper",
+        ":hybrid_decrypt_wrapper",
         "//cc:hybrid_decrypt",
         "//cc:key_manager",
         "//cc:keyset_handle",
@@ -87,6 +88,7 @@
         "//cc:registry",
         "//cc/util:status",
         "//cc/util:statusor",
+        "@com_google_absl//absl/base:core_headers",
     ],
 )
 
@@ -113,7 +115,7 @@
     include_prefix = "tink",
     strip_include_prefix = "/cc",
     deps = [
-        ":hybrid_encrypt_set_wrapper",
+        ":hybrid_encrypt_wrapper",
         "//cc:hybrid_encrypt",
         "//cc:key_manager",
         "//cc:keyset_handle",
@@ -121,6 +123,7 @@
         "//cc:registry",
         "//cc/util:status",
         "//cc/util:statusor",
+        "@com_google_absl//absl/base:core_headers",
     ],
 )
 
@@ -220,12 +223,14 @@
         ":ecies_aead_hkdf_public_key_manager",
         "//cc:hybrid_decrypt",
         "//cc:key_manager",
+        "//cc:key_manager_base",
         "//cc/subtle:subtle_util_boringssl",
         "//cc/util:enums",
         "//cc/util:errors",
         "//cc/util:protobuf_helper",
         "//cc/util:status",
         "//cc/util:statusor",
+        "//cc/util:validation",
         "//proto:ecies_aead_hkdf_cc_proto",
         "//proto:tink_cc_proto",
         "@com_google_absl//absl/memory",
@@ -247,6 +252,7 @@
         ":ecies_aead_hkdf_hybrid_encrypt",
         "//cc:hybrid_encrypt",
         "//cc:key_manager",
+        "//cc:key_manager_base",
         "//cc:registry",
         "//cc/util:protobuf_helper",
         "//cc/util:status",
@@ -266,21 +272,27 @@
     copts = ["-Iexternal/gtest/include"],
     deps = [
         ":hybrid_config",
+        ":hybrid_key_templates",
         "//cc:catalogue",
         "//cc:config",
+        "//cc:hybrid_decrypt",
+        "//cc:hybrid_encrypt",
+        "//cc:keyset_handle",
         "//cc:registry",
         "//cc/util:status",
+        "//cc/util:test_matchers",
+        "//cc/util:test_util",
         "@com_google_googletest//:gtest_main",
     ],
 )
 
 cc_test(
-    name = "hybrid_decrypt_set_wrapper_test",
+    name = "hybrid_decrypt_wrapper_test",
     size = "small",
-    srcs = ["hybrid_decrypt_set_wrapper_test.cc"],
+    srcs = ["hybrid_decrypt_wrapper_test.cc"],
     copts = ["-Iexternal/gtest/include"],
     deps = [
-        ":hybrid_decrypt_set_wrapper",
+        ":hybrid_decrypt_wrapper",
         "//cc:hybrid_decrypt",
         "//cc:primitive_set",
         "//cc/util:status",
@@ -291,12 +303,12 @@
 )
 
 cc_test(
-    name = "hybrid_encrypt_set_wrapper_test",
+    name = "hybrid_encrypt_wrapper_test",
     size = "small",
-    srcs = ["hybrid_encrypt_set_wrapper_test.cc"],
+    srcs = ["hybrid_encrypt_wrapper_test.cc"],
     copts = ["-Iexternal/gtest/include"],
     deps = [
-        ":hybrid_encrypt_set_wrapper",
+        ":hybrid_encrypt_wrapper",
         "//cc:hybrid_encrypt",
         "//cc:primitive_set",
         "//cc/util:status",
diff --git a/cc/hybrid/CMakeLists.txt b/cc/hybrid/CMakeLists.txt
index da0780c..d4875cb 100644
--- a/cc/hybrid/CMakeLists.txt
+++ b/cc/hybrid/CMakeLists.txt
@@ -1,218 +1,235 @@
-# Copyright 2018 The Fuchsia Authors. All rights reserved.
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-# Library: 'tink_hybrid_hybrid_config'
-add_library(tink_hybrid_hybrid_config hybrid_config.cc hybrid_config.h)
+
+# CC Library : hybrid_config
+add_library(tink_cc_hybrid_hybrid_config hybrid_config.h hybrid_config.cc)
 tink_export_hdrs(hybrid_config.h)
 add_dependencies(
-  tink_hybrid_hybrid_config
-  tink_hybrid_hybrid_decrypt_catalogue
-  tink_hybrid_hybrid_encrypt_catalogue
-  tink_config
-  tink_aead_aead_config
-  tink_util_status
+  tink_cc_hybrid_hybrid_config
+  tink_cc_hybrid_hybrid_decrypt_catalogue
+  tink_cc_hybrid_hybrid_encrypt_catalogue
+  tink_cc_config
+  tink_cc_aead_aead_config
+  tink_cc_util_status
   tink_proto_config_lib
 )
 target_link_libraries(
-  tink_hybrid_hybrid_config
-  tink_hybrid_hybrid_decrypt_catalogue
-  tink_hybrid_hybrid_encrypt_catalogue
-  tink_config
-  tink_aead_aead_config
-  tink_util_status
+  tink_cc_hybrid_hybrid_config
+  tink_cc_hybrid_hybrid_decrypt_catalogue
+  tink_cc_hybrid_hybrid_encrypt_catalogue
+  tink_cc_config
+  tink_cc_aead_aead_config
+  tink_cc_util_status
   tink_proto_config_lib
+  absl::memory
 )
 
-# Library: 'tink_hybrid_hybrid_decrypt_set_wrapper'
-add_library(tink_hybrid_hybrid_decrypt_set_wrapper hybrid_decrypt_set_wrapper.cc hybrid_decrypt_set_wrapper.h)
-tink_export_hdrs(hybrid_decrypt_set_wrapper.h)
+# CC Library : hybrid_decrypt_wrapper
+add_library(
+  tink_cc_hybrid_hybrid_decrypt_wrapper
+  hybrid_decrypt_wrapper.h
+  hybrid_decrypt_wrapper.cc
+)
+tink_export_hdrs(hybrid_decrypt_wrapper.h)
 add_dependencies(
-  tink_hybrid_hybrid_decrypt_set_wrapper
-  tink_crypto_format
-  tink_hybrid_decrypt
-  tink_primitive_set
-  tink_util_status
-  tink_util_statusor
-  tink_subtle_subtle_util_boringssl
+  tink_cc_hybrid_hybrid_decrypt_wrapper
+  tink_cc_crypto_format
+  tink_cc_hybrid_decrypt
+  tink_cc_primitive_set
+  tink_cc_primitive_wrapper
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_status
+  tink_cc_util_statusor
   tink_proto_tink_lib
 )
 target_link_libraries(
-  tink_hybrid_hybrid_decrypt_set_wrapper
-  tink_crypto_format
-  tink_hybrid_decrypt
-  tink_primitive_set
-  tink_util_status
-  tink_util_statusor
-  tink_subtle_subtle_util_boringssl
+  tink_cc_hybrid_hybrid_decrypt_wrapper
+  tink_cc_crypto_format
+  tink_cc_hybrid_decrypt
+  tink_cc_primitive_set
+  tink_cc_primitive_wrapper
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_status
+  tink_cc_util_statusor
   tink_proto_tink_lib
   absl::strings
 )
 
-# Library: 'tink_hybrid_hybrid_encrypt_set_wrapper'
-add_library(tink_hybrid_hybrid_encrypt_set_wrapper hybrid_encrypt_set_wrapper.cc hybrid_encrypt_set_wrapper.h)
-tink_export_hdrs(hybrid_encrypt_set_wrapper.h)
+# CC Library : hybrid_encrypt_wrapper
+add_library(
+  tink_cc_hybrid_hybrid_encrypt_wrapper
+  hybrid_encrypt_wrapper.h
+  hybrid_encrypt_wrapper.cc
+)
+tink_export_hdrs(hybrid_encrypt_wrapper.h)
 add_dependencies(
-  tink_hybrid_hybrid_encrypt_set_wrapper
-  tink_crypto_format
-  tink_hybrid_encrypt
-  tink_primitive_set
-  tink_util_status
-  tink_util_statusor
-  tink_subtle_subtle_util_boringssl
+  tink_cc_hybrid_hybrid_encrypt_wrapper
+  tink_cc_crypto_format
+  tink_cc_hybrid_encrypt
+  tink_cc_primitive_set
+  tink_cc_primitive_wrapper
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_status
+  tink_cc_util_statusor
   tink_proto_tink_lib
 )
 target_link_libraries(
-  tink_hybrid_hybrid_encrypt_set_wrapper
-  tink_crypto_format
-  tink_hybrid_encrypt
-  tink_primitive_set
-  tink_util_status
-  tink_util_statusor
-  tink_subtle_subtle_util_boringssl
+  tink_cc_hybrid_hybrid_encrypt_wrapper
+  tink_cc_crypto_format
+  tink_cc_hybrid_encrypt
+  tink_cc_primitive_set
+  tink_cc_primitive_wrapper
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_status
+  tink_cc_util_statusor
   tink_proto_tink_lib
   absl::strings
 )
 
-# Library: 'tink_hybrid_hybrid_decrypt_catalogue'
-add_library(tink_hybrid_hybrid_decrypt_catalogue hybrid_decrypt_catalogue.cc hybrid_decrypt_catalogue.h)
+# CC Library : hybrid_decrypt_catalogue
+add_library(
+  tink_cc_hybrid_hybrid_decrypt_catalogue
+  hybrid_decrypt_catalogue.h
+  hybrid_decrypt_catalogue.cc
+)
 tink_export_hdrs(hybrid_decrypt_catalogue.h)
 add_dependencies(
-  tink_hybrid_hybrid_decrypt_catalogue
-  tink_hybrid_ecies_aead_hkdf_private_key_manager
-  tink_catalogue
-  tink_hybrid_decrypt
-  tink_key_manager
-  tink_util_status
-  tink_util_statusor
+  tink_cc_hybrid_hybrid_decrypt_catalogue
+  tink_cc_hybrid_ecies_aead_hkdf_private_key_manager
+  tink_cc_catalogue
+  tink_cc_hybrid_decrypt
+  tink_cc_key_manager
+  tink_cc_util_status
+  tink_cc_util_statusor
 )
 target_link_libraries(
-  tink_hybrid_hybrid_decrypt_catalogue
-  tink_hybrid_ecies_aead_hkdf_private_key_manager
-  tink_catalogue
-  tink_hybrid_decrypt
-  tink_key_manager
-  tink_util_status
-  tink_util_statusor
+  tink_cc_hybrid_hybrid_decrypt_catalogue
+  tink_cc_hybrid_ecies_aead_hkdf_private_key_manager
+  tink_cc_catalogue
+  tink_cc_hybrid_decrypt
+  tink_cc_key_manager
+  tink_cc_util_status
+  tink_cc_util_statusor
 )
 
-# Library: 'tink_hybrid_hybrid_decrypt_factory'
-add_library(tink_hybrid_hybrid_decrypt_factory hybrid_decrypt_factory.cc hybrid_decrypt_factory.h)
+# CC Library : hybrid_decrypt_factory
+add_library(
+  tink_cc_hybrid_hybrid_decrypt_factory
+  hybrid_decrypt_factory.h
+  hybrid_decrypt_factory.cc
+)
 tink_export_hdrs(hybrid_decrypt_factory.h)
 add_dependencies(
-  tink_hybrid_hybrid_decrypt_factory
-  tink_hybrid_hybrid_decrypt_set_wrapper
-  tink_hybrid_decrypt
-  tink_key_manager
-  tink_keyset_handle
-  tink_primitive_set
-  tink_registry
-  tink_util_status
-  tink_util_statusor
+  tink_cc_hybrid_hybrid_decrypt_factory
+  tink_cc_hybrid_hybrid_decrypt_wrapper
+  tink_cc_hybrid_decrypt
+  tink_cc_key_manager
+  tink_cc_keyset_handle
+  tink_cc_primitive_set
+  tink_cc_registry
+  tink_cc_util_status
+  tink_cc_util_statusor
 )
 target_link_libraries(
-  tink_hybrid_hybrid_decrypt_factory
-  tink_hybrid_hybrid_decrypt_set_wrapper
-  tink_hybrid_decrypt
-  tink_key_manager
-  tink_keyset_handle
-  tink_primitive_set
-  tink_registry
-  tink_util_status
-  tink_util_statusor
+  tink_cc_hybrid_hybrid_decrypt_factory
+  tink_cc_hybrid_hybrid_decrypt_wrapper
+  tink_cc_hybrid_decrypt
+  tink_cc_key_manager
+  tink_cc_keyset_handle
+  tink_cc_primitive_set
+  tink_cc_registry
+  tink_cc_util_status
+  tink_cc_util_statusor
+  absl::base
 )
 
-# Library: 'tink_hybrid_hybrid_encrypt_catalogue'
-add_library(tink_hybrid_hybrid_encrypt_catalogue hybrid_encrypt_catalogue.cc hybrid_encrypt_catalogue.h)
+# CC Library : hybrid_encrypt_catalogue
+add_library(
+  tink_cc_hybrid_hybrid_encrypt_catalogue
+  hybrid_encrypt_catalogue.h
+  hybrid_encrypt_catalogue.cc
+)
 tink_export_hdrs(hybrid_encrypt_catalogue.h)
 add_dependencies(
-  tink_hybrid_hybrid_encrypt_catalogue
-  tink_hybrid_ecies_aead_hkdf_public_key_manager
-  tink_catalogue
-  tink_hybrid_encrypt
-  tink_key_manager
-  tink_util_status
-  tink_util_statusor
+  tink_cc_hybrid_hybrid_encrypt_catalogue
+  tink_cc_hybrid_ecies_aead_hkdf_public_key_manager
+  tink_cc_catalogue
+  tink_cc_hybrid_encrypt
+  tink_cc_key_manager
+  tink_cc_util_status
+  tink_cc_util_statusor
 )
 target_link_libraries(
-  tink_hybrid_hybrid_encrypt_catalogue
-  tink_hybrid_ecies_aead_hkdf_public_key_manager
-  tink_catalogue
-  tink_hybrid_encrypt
-  tink_key_manager
-  tink_util_status
-  tink_util_statusor
+  tink_cc_hybrid_hybrid_encrypt_catalogue
+  tink_cc_hybrid_ecies_aead_hkdf_public_key_manager
+  tink_cc_catalogue
+  tink_cc_hybrid_encrypt
+  tink_cc_key_manager
+  tink_cc_util_status
+  tink_cc_util_statusor
 )
 
-# Library: 'tink_hybrid_hybrid_encrypt_factory'
-add_library(tink_hybrid_hybrid_encrypt_factory hybrid_encrypt_factory.cc hybrid_encrypt_factory.h)
+# CC Library : hybrid_encrypt_factory
+add_library(
+  tink_cc_hybrid_hybrid_encrypt_factory
+  hybrid_encrypt_factory.h
+  hybrid_encrypt_factory.cc
+)
 tink_export_hdrs(hybrid_encrypt_factory.h)
 add_dependencies(
-  tink_hybrid_hybrid_encrypt_factory
-  tink_hybrid_hybrid_encrypt_set_wrapper
-  tink_hybrid_encrypt
-  tink_key_manager
-  tink_keyset_handle
-  tink_primitive_set
-  tink_registry
-  tink_util_status
-  tink_util_statusor
+  tink_cc_hybrid_hybrid_encrypt_factory
+  tink_cc_hybrid_hybrid_encrypt_wrapper
+  tink_cc_hybrid_encrypt
+  tink_cc_key_manager
+  tink_cc_keyset_handle
+  tink_cc_primitive_set
+  tink_cc_registry
+  tink_cc_util_status
+  tink_cc_util_statusor
 )
 target_link_libraries(
-  tink_hybrid_hybrid_encrypt_factory
-  tink_hybrid_hybrid_encrypt_set_wrapper
-  tink_hybrid_encrypt
-  tink_key_manager
-  tink_keyset_handle
-  tink_primitive_set
-  tink_registry
-  tink_util_status
-  tink_util_statusor
+  tink_cc_hybrid_hybrid_encrypt_factory
+  tink_cc_hybrid_hybrid_encrypt_wrapper
+  tink_cc_hybrid_encrypt
+  tink_cc_key_manager
+  tink_cc_keyset_handle
+  tink_cc_primitive_set
+  tink_cc_registry
+  tink_cc_util_status
+  tink_cc_util_statusor
+  absl::base
 )
 
-# Library: 'tink_hybrid_hybrid_key_templates'
-add_library(tink_hybrid_hybrid_key_templates hybrid_key_templates.cc hybrid_key_templates.h)
-tink_export_hdrs(hybrid_key_templates.h)
-add_dependencies(
-  tink_hybrid_hybrid_key_templates
-  tink_aead_aead_key_templates
-  tink_proto_common_lib
-  tink_proto_ecies_aead_hkdf_lib
-  tink_proto_tink_lib
+# CC Library : ecies_aead_hkdf_dem_helper
+add_library(
+  tink_cc_hybrid_ecies_aead_hkdf_dem_helper
+  ecies_aead_hkdf_dem_helper.h
+  ecies_aead_hkdf_dem_helper.cc
 )
-target_link_libraries(
-  tink_hybrid_hybrid_key_templates
-  tink_aead_aead_key_templates
-  tink_proto_common_lib
-  tink_proto_ecies_aead_hkdf_lib
-  tink_proto_tink_lib
-  absl::strings
-)
-
-# Library: 'tink_hybrid_ecies_aead_hkdf_dem_helper'
-add_library(tink_hybrid_ecies_aead_hkdf_dem_helper ecies_aead_hkdf_dem_helper.cc ecies_aead_hkdf_dem_helper.h)
 tink_export_hdrs(ecies_aead_hkdf_dem_helper.h)
 add_dependencies(
-  tink_hybrid_ecies_aead_hkdf_dem_helper
-  tink_aead
-  tink_key_manager
-  tink_registry
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
+  tink_cc_hybrid_ecies_aead_hkdf_dem_helper
+  tink_cc_aead
+  tink_cc_key_manager
+  tink_cc_registry
+  tink_cc_util_protobuf_helper
+  tink_cc_util_status
+  tink_cc_util_statusor
   tink_proto_aes_ctr_hmac_aead_lib
   tink_proto_aes_gcm_lib
   tink_proto_common_lib
   tink_proto_tink_lib
 )
 target_link_libraries(
-  tink_hybrid_ecies_aead_hkdf_dem_helper
-  tink_aead
-  tink_key_manager
-  tink_registry
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
+  tink_cc_hybrid_ecies_aead_hkdf_dem_helper
+  tink_cc_aead
+  tink_cc_key_manager
+  tink_cc_registry
+  tink_cc_util_protobuf_helper
+  tink_cc_util_status
+  tink_cc_util_statusor
   tink_proto_aes_ctr_hmac_aead_lib
   tink_proto_aes_gcm_lib
   tink_proto_common_lib
@@ -220,478 +237,155 @@
   absl::memory
 )
 
-# Library: 'tink_hybrid_ecies_aead_hkdf_hybrid_decrypt'
-add_library(tink_hybrid_ecies_aead_hkdf_hybrid_decrypt ecies_aead_hkdf_hybrid_decrypt.cc ecies_aead_hkdf_hybrid_decrypt.h)
+# CC Library : ecies_aead_hkdf_hybrid_decrypt
+add_library(
+  tink_cc_hybrid_ecies_aead_hkdf_hybrid_decrypt
+  ecies_aead_hkdf_hybrid_decrypt.h
+  ecies_aead_hkdf_hybrid_decrypt.cc
+)
 tink_export_hdrs(ecies_aead_hkdf_hybrid_decrypt.h)
 add_dependencies(
-  tink_hybrid_ecies_aead_hkdf_hybrid_decrypt
-  tink_hybrid_ecies_aead_hkdf_dem_helper
-  tink_hybrid_decrypt
-  tink_subtle_ec_util
-  tink_subtle_ecies_hkdf_recipient_kem_boringssl
-  tink_util_enums
-  tink_util_status
-  tink_util_statusor
+  tink_cc_hybrid_ecies_aead_hkdf_hybrid_decrypt
+  tink_cc_hybrid_ecies_aead_hkdf_dem_helper
+  tink_cc_hybrid_decrypt
+  tink_cc_subtle_ec_util
+  tink_cc_subtle_ecies_hkdf_recipient_kem_boringssl
+  tink_cc_util_enums
+  tink_cc_util_status
+  tink_cc_util_statusor
   tink_proto_common_lib
   tink_proto_ecies_aead_hkdf_lib
   tink_proto_tink_lib
 )
 target_link_libraries(
-  tink_hybrid_ecies_aead_hkdf_hybrid_decrypt
-  tink_hybrid_ecies_aead_hkdf_dem_helper
-  tink_hybrid_decrypt
-  tink_subtle_ec_util
-  tink_subtle_ecies_hkdf_recipient_kem_boringssl
-  tink_util_enums
-  tink_util_status
-  tink_util_statusor
+  tink_cc_hybrid_ecies_aead_hkdf_hybrid_decrypt
+  tink_cc_hybrid_ecies_aead_hkdf_dem_helper
+  tink_cc_hybrid_decrypt
+  tink_cc_subtle_ec_util
+  tink_cc_subtle_ecies_hkdf_recipient_kem_boringssl
+  tink_cc_util_enums
+  tink_cc_util_status
+  tink_cc_util_statusor
   tink_proto_common_lib
   tink_proto_ecies_aead_hkdf_lib
   tink_proto_tink_lib
 )
 
-# Library: 'tink_hybrid_ecies_aead_hkdf_hybrid_encrypt'
-add_library(tink_hybrid_ecies_aead_hkdf_hybrid_encrypt ecies_aead_hkdf_hybrid_encrypt.cc ecies_aead_hkdf_hybrid_encrypt.h)
+# CC Library : ecies_aead_hkdf_hybrid_encrypt
+add_library(
+  tink_cc_hybrid_ecies_aead_hkdf_hybrid_encrypt
+  ecies_aead_hkdf_hybrid_encrypt.h
+  ecies_aead_hkdf_hybrid_encrypt.cc
+)
 tink_export_hdrs(ecies_aead_hkdf_hybrid_encrypt.h)
 add_dependencies(
-  tink_hybrid_ecies_aead_hkdf_hybrid_encrypt
-  tink_hybrid_ecies_aead_hkdf_dem_helper
-  tink_aead
-  tink_hybrid_encrypt
-  tink_key_manager
-  tink_registry
-  tink_subtle_ecies_hkdf_sender_kem_boringssl
-  tink_util_enums
-  tink_util_status
-  tink_util_statusor
+  tink_cc_hybrid_ecies_aead_hkdf_hybrid_encrypt
+  tink_cc_hybrid_ecies_aead_hkdf_dem_helper
+  tink_cc_aead
+  tink_cc_hybrid_encrypt
+  tink_cc_key_manager
+  tink_cc_registry
+  tink_cc_subtle_ecies_hkdf_sender_kem_boringssl
+  tink_cc_util_enums
+  tink_cc_util_status
+  tink_cc_util_statusor
   tink_proto_common_lib
   tink_proto_ecies_aead_hkdf_lib
   tink_proto_tink_lib
 )
 target_link_libraries(
-  tink_hybrid_ecies_aead_hkdf_hybrid_encrypt
-  tink_hybrid_ecies_aead_hkdf_dem_helper
-  tink_aead
-  tink_hybrid_encrypt
-  tink_key_manager
-  tink_registry
-  tink_subtle_ecies_hkdf_sender_kem_boringssl
-  tink_util_enums
-  tink_util_status
-  tink_util_statusor
+  tink_cc_hybrid_ecies_aead_hkdf_hybrid_encrypt
+  tink_cc_hybrid_ecies_aead_hkdf_dem_helper
+  tink_cc_aead
+  tink_cc_hybrid_encrypt
+  tink_cc_key_manager
+  tink_cc_registry
+  tink_cc_subtle_ecies_hkdf_sender_kem_boringssl
+  tink_cc_util_enums
+  tink_cc_util_status
+  tink_cc_util_statusor
   tink_proto_common_lib
   tink_proto_ecies_aead_hkdf_lib
   tink_proto_tink_lib
 )
 
-# Library: 'tink_hybrid_ecies_aead_hkdf_private_key_manager'
-add_library(tink_hybrid_ecies_aead_hkdf_private_key_manager ecies_aead_hkdf_private_key_manager.cc ecies_aead_hkdf_private_key_manager.h)
+# CC Library : ecies_aead_hkdf_private_key_manager
+add_library(
+  tink_cc_hybrid_ecies_aead_hkdf_private_key_manager
+  ecies_aead_hkdf_private_key_manager.h
+  ecies_aead_hkdf_private_key_manager.cc
+)
 tink_export_hdrs(ecies_aead_hkdf_private_key_manager.h)
 add_dependencies(
-  tink_hybrid_ecies_aead_hkdf_private_key_manager
-  tink_hybrid_ecies_aead_hkdf_hybrid_decrypt
-  tink_hybrid_ecies_aead_hkdf_public_key_manager
-  tink_hybrid_decrypt
-  tink_key_manager
-  tink_subtle_subtle_util_boringssl
-  tink_util_enums
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
+  tink_cc_hybrid_ecies_aead_hkdf_private_key_manager
+  tink_cc_hybrid_ecies_aead_hkdf_hybrid_decrypt
+  tink_cc_hybrid_ecies_aead_hkdf_public_key_manager
+  tink_cc_hybrid_decrypt
+  tink_cc_key_manager
+  tink_cc_core_key_manager_base
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_enums
+  tink_cc_util_errors
+  tink_cc_util_protobuf_helper
+  tink_cc_util_status
+  tink_cc_util_statusor
+  tink_cc_util_validation
   tink_proto_ecies_aead_hkdf_lib
   tink_proto_tink_lib
 )
 target_link_libraries(
-  tink_hybrid_ecies_aead_hkdf_private_key_manager
-  tink_hybrid_ecies_aead_hkdf_hybrid_decrypt
-  tink_hybrid_ecies_aead_hkdf_public_key_manager
-  tink_hybrid_decrypt
-  tink_key_manager
-  tink_subtle_subtle_util_boringssl
-  tink_util_enums
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
+  tink_cc_hybrid_ecies_aead_hkdf_private_key_manager
+  tink_cc_hybrid_ecies_aead_hkdf_hybrid_decrypt
+  tink_cc_hybrid_ecies_aead_hkdf_public_key_manager
+  tink_cc_hybrid_decrypt
+  tink_cc_key_manager
+  tink_cc_core_key_manager_base
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_enums
+  tink_cc_util_errors
+  tink_cc_util_protobuf_helper
+  tink_cc_util_status
+  tink_cc_util_statusor
+  tink_cc_util_validation
   tink_proto_ecies_aead_hkdf_lib
   tink_proto_tink_lib
   absl::memory
   absl::strings
 )
 
-# Library: 'tink_hybrid_ecies_aead_hkdf_public_key_manager'
-add_library(tink_hybrid_ecies_aead_hkdf_public_key_manager ecies_aead_hkdf_public_key_manager.cc ecies_aead_hkdf_public_key_manager.h)
+# CC Library : ecies_aead_hkdf_public_key_manager
+add_library(
+  tink_cc_hybrid_ecies_aead_hkdf_public_key_manager
+  ecies_aead_hkdf_public_key_manager.h
+  ecies_aead_hkdf_public_key_manager.cc
+)
 tink_export_hdrs(ecies_aead_hkdf_public_key_manager.h)
 add_dependencies(
-  tink_hybrid_ecies_aead_hkdf_public_key_manager
-  tink_hybrid_ecies_aead_hkdf_hybrid_encrypt
-  tink_hybrid_encrypt
-  tink_key_manager
-  tink_registry
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
+  tink_cc_hybrid_ecies_aead_hkdf_public_key_manager
+  tink_cc_hybrid_ecies_aead_hkdf_hybrid_encrypt
+  tink_cc_hybrid_encrypt
+  tink_cc_key_manager
+  tink_cc_core_key_manager_base
+  tink_cc_registry
+  tink_cc_util_protobuf_helper
+  tink_cc_util_status
+  tink_cc_util_statusor
   tink_proto_common_lib
   tink_proto_ecies_aead_hkdf_lib
   tink_proto_tink_lib
 )
 target_link_libraries(
-  tink_hybrid_ecies_aead_hkdf_public_key_manager
-  tink_hybrid_ecies_aead_hkdf_hybrid_encrypt
-  tink_hybrid_encrypt
-  tink_key_manager
-  tink_registry
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
+  tink_cc_hybrid_ecies_aead_hkdf_public_key_manager
+  tink_cc_hybrid_ecies_aead_hkdf_hybrid_encrypt
+  tink_cc_hybrid_encrypt
+  tink_cc_key_manager
+  tink_cc_core_key_manager_base
+  tink_cc_registry
+  tink_cc_util_protobuf_helper
+  tink_cc_util_status
+  tink_cc_util_statusor
   tink_proto_common_lib
   tink_proto_ecies_aead_hkdf_lib
   tink_proto_tink_lib
 )
 
-# Test Binary: 'tink_hybrid_hybrid_config_test'
-add_executable(tink_hybrid_hybrid_config_test hybrid_config_test.cc)
-add_dependencies(
-  tink_hybrid_hybrid_config_test
-  tink_hybrid_hybrid_config
-  tink_catalogue
-  tink_config
-  tink_registry
-  tink_util_status
-)
-add_dependencies(tink_hybrid_hybrid_config_test build_external_projects)
-target_link_libraries(
-  tink_hybrid_hybrid_config_test
-  tink_hybrid_hybrid_config
-  tink_catalogue
-  tink_config
-  tink_registry
-  tink_util_status
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_hybrid_hybrid_decrypt_set_wrapper_test'
-add_executable(tink_hybrid_hybrid_decrypt_set_wrapper_test hybrid_decrypt_set_wrapper_test.cc)
-add_dependencies(
-  tink_hybrid_hybrid_decrypt_set_wrapper_test
-  tink_hybrid_hybrid_decrypt_set_wrapper
-  tink_hybrid_decrypt
-  tink_primitive_set
-  tink_util_status
-  tink_util_test_util
-  tink_proto_tink_lib
-)
-add_dependencies(tink_hybrid_hybrid_decrypt_set_wrapper_test build_external_projects)
-target_link_libraries(
-  tink_hybrid_hybrid_decrypt_set_wrapper_test
-  tink_hybrid_hybrid_decrypt_set_wrapper
-  tink_hybrid_decrypt
-  tink_primitive_set
-  tink_util_status
-  tink_util_test_util
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_hybrid_hybrid_encrypt_set_wrapper_test'
-add_executable(tink_hybrid_hybrid_encrypt_set_wrapper_test hybrid_encrypt_set_wrapper_test.cc)
-add_dependencies(
-  tink_hybrid_hybrid_encrypt_set_wrapper_test
-  tink_hybrid_hybrid_encrypt_set_wrapper
-  tink_hybrid_encrypt
-  tink_primitive_set
-  tink_util_status
-  tink_util_test_util
-  tink_proto_tink_lib
-)
-add_dependencies(tink_hybrid_hybrid_encrypt_set_wrapper_test build_external_projects)
-target_link_libraries(
-  tink_hybrid_hybrid_encrypt_set_wrapper_test
-  tink_hybrid_hybrid_encrypt_set_wrapper
-  tink_hybrid_encrypt
-  tink_primitive_set
-  tink_util_status
-  tink_util_test_util
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_hybrid_hybrid_decrypt_catalogue_test'
-add_executable(tink_hybrid_hybrid_decrypt_catalogue_test hybrid_decrypt_catalogue_test.cc)
-add_dependencies(
-  tink_hybrid_hybrid_decrypt_catalogue_test
-  tink_hybrid_hybrid_decrypt_catalogue
-  tink_catalogue
-  tink_hybrid_decrypt
-  tink_util_status
-  tink_util_statusor
-)
-add_dependencies(tink_hybrid_hybrid_decrypt_catalogue_test build_external_projects)
-target_link_libraries(
-  tink_hybrid_hybrid_decrypt_catalogue_test
-  tink_hybrid_hybrid_decrypt_catalogue
-  tink_catalogue
-  tink_hybrid_decrypt
-  tink_util_status
-  tink_util_statusor
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_hybrid_hybrid_decrypt_factory_test'
-add_executable(tink_hybrid_hybrid_decrypt_factory_test hybrid_decrypt_factory_test.cc)
-add_dependencies(
-  tink_hybrid_hybrid_decrypt_factory_test
-  tink_hybrid_ecies_aead_hkdf_public_key_manager
-  tink_hybrid_hybrid_config
-  tink_hybrid_hybrid_decrypt_factory
-  tink_config
-  tink_hybrid_decrypt
-  tink_hybrid_encrypt
-  tink_keyset_handle
-  tink_util_keyset_util
-  tink_util_status
-  tink_util_test_util
-  tink_proto_ecies_aead_hkdf_lib
-  tink_proto_tink_lib
-)
-add_dependencies(tink_hybrid_hybrid_decrypt_factory_test build_external_projects)
-target_link_libraries(
-  tink_hybrid_hybrid_decrypt_factory_test
-  tink_hybrid_ecies_aead_hkdf_public_key_manager
-  tink_hybrid_hybrid_config
-  tink_hybrid_hybrid_decrypt_factory
-  tink_config
-  tink_hybrid_decrypt
-  tink_hybrid_encrypt
-  tink_keyset_handle
-  tink_util_keyset_util
-  tink_util_status
-  tink_util_test_util
-  tink_proto_ecies_aead_hkdf_lib
-  tink_proto_tink_lib
-  absl::memory
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_hybrid_hybrid_encrypt_catalogue_test'
-add_executable(tink_hybrid_hybrid_encrypt_catalogue_test hybrid_encrypt_catalogue_test.cc)
-add_dependencies(
-  tink_hybrid_hybrid_encrypt_catalogue_test
-  tink_hybrid_hybrid_encrypt_catalogue
-  tink_catalogue
-  tink_hybrid_encrypt
-  tink_util_status
-  tink_util_statusor
-)
-add_dependencies(tink_hybrid_hybrid_encrypt_catalogue_test build_external_projects)
-target_link_libraries(
-  tink_hybrid_hybrid_encrypt_catalogue_test
-  tink_hybrid_hybrid_encrypt_catalogue
-  tink_catalogue
-  tink_hybrid_encrypt
-  tink_util_status
-  tink_util_statusor
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_hybrid_hybrid_encrypt_factory_test'
-add_executable(tink_hybrid_hybrid_encrypt_factory_test hybrid_encrypt_factory_test.cc)
-add_dependencies(
-  tink_hybrid_hybrid_encrypt_factory_test
-  tink_hybrid_hybrid_config
-  tink_hybrid_hybrid_encrypt_factory
-  tink_config
-  tink_hybrid_encrypt
-  tink_keyset_handle
-  tink_util_keyset_util
-  tink_util_status
-  tink_util_test_util
-  tink_proto_ecies_aead_hkdf_lib
-  tink_proto_tink_lib
-)
-add_dependencies(tink_hybrid_hybrid_encrypt_factory_test build_external_projects)
-target_link_libraries(
-  tink_hybrid_hybrid_encrypt_factory_test
-  tink_hybrid_hybrid_config
-  tink_hybrid_hybrid_encrypt_factory
-  tink_config
-  tink_hybrid_encrypt
-  tink_keyset_handle
-  tink_util_keyset_util
-  tink_util_status
-  tink_util_test_util
-  tink_proto_ecies_aead_hkdf_lib
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_hybrid_hybrid_key_templates_test'
-add_executable(tink_hybrid_hybrid_key_templates_test hybrid_key_templates_test.cc)
-add_dependencies(
-  tink_hybrid_hybrid_key_templates_test
-  tink_hybrid_ecies_aead_hkdf_private_key_manager
-  tink_hybrid_hybrid_config
-  tink_hybrid_hybrid_key_templates
-  tink_aead_aead_key_templates
-  tink_proto_common_lib
-  tink_proto_ecies_aead_hkdf_lib
-  tink_proto_tink_lib
-)
-add_dependencies(tink_hybrid_hybrid_key_templates_test build_external_projects)
-target_link_libraries(
-  tink_hybrid_hybrid_key_templates_test
-  tink_hybrid_ecies_aead_hkdf_private_key_manager
-  tink_hybrid_hybrid_config
-  tink_hybrid_hybrid_key_templates
-  tink_aead_aead_key_templates
-  tink_proto_common_lib
-  tink_proto_ecies_aead_hkdf_lib
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_hybrid_ecies_aead_hkdf_hybrid_decrypt_test'
-add_executable(tink_hybrid_ecies_aead_hkdf_hybrid_decrypt_test ecies_aead_hkdf_hybrid_decrypt_test.cc)
-add_dependencies(
-  tink_hybrid_ecies_aead_hkdf_hybrid_decrypt_test
-  tink_hybrid_ecies_aead_hkdf_hybrid_decrypt
-  tink_hybrid_ecies_aead_hkdf_hybrid_encrypt
-  tink_hybrid_decrypt
-  tink_aead_aes_gcm_key_manager
-  tink_subtle_random
-  tink_subtle_subtle_util_boringssl
-  tink_util_enums
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-  tink_proto_common_lib
-  tink_proto_ecies_aead_hkdf_lib
-)
-add_dependencies(tink_hybrid_ecies_aead_hkdf_hybrid_decrypt_test build_external_projects)
-target_link_libraries(
-  tink_hybrid_ecies_aead_hkdf_hybrid_decrypt_test
-  tink_hybrid_ecies_aead_hkdf_hybrid_decrypt
-  tink_hybrid_ecies_aead_hkdf_hybrid_encrypt
-  tink_hybrid_decrypt
-  tink_aead_aes_gcm_key_manager
-  tink_subtle_random
-  tink_subtle_subtle_util_boringssl
-  tink_util_enums
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-  tink_proto_common_lib
-  tink_proto_ecies_aead_hkdf_lib
-  absl::memory
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_hybrid_ecies_aead_hkdf_hybrid_encrypt_test'
-add_executable(tink_hybrid_ecies_aead_hkdf_hybrid_encrypt_test ecies_aead_hkdf_hybrid_encrypt_test.cc)
-add_dependencies(
-  tink_hybrid_ecies_aead_hkdf_hybrid_encrypt_test
-  tink_hybrid_ecies_aead_hkdf_hybrid_encrypt
-  tink_hybrid_encrypt
-  tink_registry
-  tink_aead_aes_gcm_key_manager
-  tink_subtle_random
-  tink_subtle_subtle_util_boringssl
-  tink_util_enums
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-  tink_proto_common_lib
-  tink_proto_ecies_aead_hkdf_lib
-)
-add_dependencies(tink_hybrid_ecies_aead_hkdf_hybrid_encrypt_test build_external_projects)
-target_link_libraries(
-  tink_hybrid_ecies_aead_hkdf_hybrid_encrypt_test
-  tink_hybrid_ecies_aead_hkdf_hybrid_encrypt
-  tink_hybrid_encrypt
-  tink_registry
-  tink_aead_aes_gcm_key_manager
-  tink_subtle_random
-  tink_subtle_subtle_util_boringssl
-  tink_util_enums
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-  tink_proto_common_lib
-  tink_proto_ecies_aead_hkdf_lib
-  absl::memory
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_hybrid_ecies_aead_hkdf_private_key_manager_test'
-add_executable(tink_hybrid_ecies_aead_hkdf_private_key_manager_test ecies_aead_hkdf_private_key_manager_test.cc)
-add_dependencies(
-  tink_hybrid_ecies_aead_hkdf_private_key_manager_test
-  tink_hybrid_ecies_aead_hkdf_private_key_manager
-  tink_hybrid_ecies_aead_hkdf_public_key_manager
-  tink_hybrid_hybrid_key_templates
-  tink_hybrid_decrypt
-  tink_aead_aead_key_templates
-  tink_aead_aes_ctr_hmac_aead_key_manager
-  tink_aead_aes_gcm_key_manager
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-  tink_proto_aes_eax_lib
-  tink_proto_common_lib
-  tink_proto_ecies_aead_hkdf_lib
-  tink_proto_tink_lib
-)
-add_dependencies(tink_hybrid_ecies_aead_hkdf_private_key_manager_test build_external_projects)
-target_link_libraries(
-  tink_hybrid_ecies_aead_hkdf_private_key_manager_test
-  tink_hybrid_ecies_aead_hkdf_private_key_manager
-  tink_hybrid_ecies_aead_hkdf_public_key_manager
-  tink_hybrid_hybrid_key_templates
-  tink_hybrid_decrypt
-  tink_aead_aead_key_templates
-  tink_aead_aes_ctr_hmac_aead_key_manager
-  tink_aead_aes_gcm_key_manager
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-  tink_proto_aes_eax_lib
-  tink_proto_common_lib
-  tink_proto_ecies_aead_hkdf_lib
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_hybrid_ecies_aead_hkdf_public_key_manager_test'
-add_executable(tink_hybrid_ecies_aead_hkdf_public_key_manager_test ecies_aead_hkdf_public_key_manager_test.cc)
-add_dependencies(
-  tink_hybrid_ecies_aead_hkdf_public_key_manager_test
-  tink_hybrid_ecies_aead_hkdf_public_key_manager
-  tink_hybrid_encrypt
-  tink_registry
-  tink_aead_aes_gcm_key_manager
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-  tink_proto_aes_eax_lib
-  tink_proto_common_lib
-  tink_proto_ecies_aead_hkdf_lib
-  tink_proto_tink_lib
-)
-add_dependencies(tink_hybrid_ecies_aead_hkdf_public_key_manager_test build_external_projects)
-target_link_libraries(
-  tink_hybrid_ecies_aead_hkdf_public_key_manager_test
-  tink_hybrid_ecies_aead_hkdf_public_key_manager
-  tink_hybrid_encrypt
-  tink_registry
-  tink_aead_aes_gcm_key_manager
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-  tink_proto_aes_eax_lib
-  tink_proto_common_lib
-  tink_proto_ecies_aead_hkdf_lib
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
diff --git a/cc/hybrid/ecies_aead_hkdf_dem_helper.cc b/cc/hybrid/ecies_aead_hkdf_dem_helper.cc
index cf038ca..a6ddc4b 100644
--- a/cc/hybrid/ecies_aead_hkdf_dem_helper.cc
+++ b/cc/hybrid/ecies_aead_hkdf_dem_helper.cc
@@ -95,11 +95,11 @@
 bool EciesAeadHkdfDemHelper::ReplaceKeyBytes(
     const std::string& key_bytes, portable_proto::MessageLite* proto) const {
   if (dem_key_type_ == AES_GCM_KEY) {
-    AesGcmKey* key = reinterpret_cast<AesGcmKey*>(proto);
+    AesGcmKey* key = static_cast<AesGcmKey*>(proto);
     key->set_key_value(key_bytes);
     return true;
   } else if (dem_key_type_ == AES_CTR_HMAC_AEAD_KEY) {
-    AesCtrHmacAeadKey* key = reinterpret_cast<AesCtrHmacAeadKey*>(proto);
+    AesCtrHmacAeadKey* key = static_cast<AesCtrHmacAeadKey*>(proto);
     auto aes_ctr_key = key->mutable_aes_ctr_key();
     aes_ctr_key->set_key_value(key_bytes.substr(0, aes_ctr_key_size_in_bytes_));
     auto hmac_key = key->mutable_hmac_key();
diff --git a/cc/hybrid/ecies_aead_hkdf_hybrid_decrypt_test.cc b/cc/hybrid/ecies_aead_hkdf_hybrid_decrypt_test.cc
index 2481178..cb57199 100644
--- a/cc/hybrid/ecies_aead_hkdf_hybrid_decrypt_test.cc
+++ b/cc/hybrid/ecies_aead_hkdf_hybrid_decrypt_test.cc
@@ -126,7 +126,7 @@
   // Register DEM key manager.
   auto key_manager = absl::make_unique<AesGcmKeyManager>();
   std::string dem_key_type = key_manager->get_key_type();
-  ASSERT_TRUE(Registry::RegisterKeyManager(key_manager.release()).ok());
+  ASSERT_TRUE(Registry::RegisterKeyManager(std::move(key_manager), true).ok());
 
   // Generate and test many keys with various parameters.
   std::string context_info = "some context info";
@@ -211,8 +211,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/hybrid/ecies_aead_hkdf_hybrid_encrypt_test.cc b/cc/hybrid/ecies_aead_hkdf_hybrid_encrypt_test.cc
index 3c83a8a..12f5bad 100644
--- a/cc/hybrid/ecies_aead_hkdf_hybrid_encrypt_test.cc
+++ b/cc/hybrid/ecies_aead_hkdf_hybrid_encrypt_test.cc
@@ -120,7 +120,7 @@
   // Register DEM key manager.
   auto key_manager = absl::make_unique<AesGcmKeyManager>();
   std::string dem_key_type = key_manager->get_key_type();
-  ASSERT_TRUE(Registry::RegisterKeyManager(key_manager.release()).ok());
+  ASSERT_TRUE(Registry::RegisterKeyManager(std::move(key_manager), true).ok());
 
   // Generate and test many keys with various parameters.
   std::string plaintext = "some plaintext";
@@ -152,8 +152,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/hybrid/ecies_aead_hkdf_private_key_manager.cc b/cc/hybrid/ecies_aead_hkdf_private_key_manager.cc
index ec77602..a1856a0 100644
--- a/cc/hybrid/ecies_aead_hkdf_private_key_manager.cc
+++ b/cc/hybrid/ecies_aead_hkdf_private_key_manager.cc
@@ -46,46 +46,31 @@
 using crypto::tink::util::Status;
 using crypto::tink::util::StatusOr;
 
-class EciesAeadHkdfPrivateKeyFactory : public PrivateKeyFactory {
+class EciesAeadHkdfPrivateKeyFactory
+    : public PrivateKeyFactory,
+      public KeyFactoryBase<EciesAeadHkdfPrivateKey,
+                            EciesAeadHkdfKeyFormat> {
  public:
   EciesAeadHkdfPrivateKeyFactory() {}
 
-  // Generates a new random EciesAeadHkdfPrivateKey, based on
-  // the given 'key_format', which must contain EciesAeadHkdfKeyFormat-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<portable_proto::MessageLite>>
-  NewKey(const portable_proto::MessageLite& key_format) const override;
-
-  // Generates a new random EciesAeadHkdfPrivateKey, based on
-  // the given 'serialized_key_format', which must contain
-  // EciesAeadHkdfKeyFormat-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<portable_proto::MessageLite>>
-  NewKey(absl::string_view serialized_key_format) const override;
-
-  // Generates a new random EciesAeadHkdfPrivateKey based on
-  // the given 'serialized_key_format' (which must contain
-  // EciesAeadHkdfKeyFormat-proto), and wraps it in a KeyData-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<google::crypto::tink::KeyData>>
-  NewKeyData(absl::string_view serialized_key_format) const override;
+  KeyData::KeyMaterialType key_material_type() const override {
+    return KeyData::ASYMMETRIC_PRIVATE;
+  }
 
   // Returns KeyData proto that contains EciesAeadHkdfPublicKey
   // extracted from the given serialized_private_key, which must contain
   // EciesAeadHkdfPrivateKey-proto.
   crypto::tink::util::StatusOr<std::unique_ptr<google::crypto::tink::KeyData>>
   GetPublicKeyData(absl::string_view serialized_private_key) const override;
+
+ protected:
+  StatusOr<std::unique_ptr<EciesAeadHkdfPrivateKey>> NewKeyFromFormat(
+      const EciesAeadHkdfKeyFormat& ecies_key_format) const override;
 };
 
-StatusOr<std::unique_ptr<MessageLite>> EciesAeadHkdfPrivateKeyFactory::NewKey(
-    const portable_proto::MessageLite& key_format) const {
-  std::string key_format_url =
-      std::string(EciesAeadHkdfPrivateKeyManager::kKeyTypePrefix) +
-      key_format.GetTypeName();
-  if (key_format_url != EciesAeadHkdfPrivateKeyManager::kKeyFormatUrl) {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Key format proto '%s' is not supported by this manager.",
-                     key_format_url.c_str());
-  }
-  const EciesAeadHkdfKeyFormat& ecies_key_format =
-        reinterpret_cast<const EciesAeadHkdfKeyFormat&>(key_format);
+StatusOr<std::unique_ptr<EciesAeadHkdfPrivateKey>>
+EciesAeadHkdfPrivateKeyFactory::NewKeyFromFormat(
+    const EciesAeadHkdfKeyFormat& ecies_key_format) const {
   Status status = EciesAeadHkdfPublicKeyManager::Validate(ecies_key_format);
   if (!status.ok()) return status;
 
@@ -107,32 +92,9 @@
   ecies_public_key->set_y(ec_key.pub_y);
   *(ecies_public_key->mutable_params()) = ecies_key_format.params();
 
-  std::unique_ptr<MessageLite> key = std::move(ecies_private_key);
-  return std::move(key);
-}
-
-StatusOr<std::unique_ptr<MessageLite>> EciesAeadHkdfPrivateKeyFactory::NewKey(
-    absl::string_view serialized_key_format) const {
-  EciesAeadHkdfKeyFormat key_format;
-  if (!key_format.ParseFromString(std::string(serialized_key_format))) {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Could not parse the passed string as proto '%s'.",
-                     EciesAeadHkdfPrivateKeyManager::kKeyFormatUrl);
-  }
-  return NewKey(key_format);
-}
-
-StatusOr<std::unique_ptr<KeyData>> EciesAeadHkdfPrivateKeyFactory::NewKeyData(
-    absl::string_view serialized_key_format) const {
-  auto new_key_result = NewKey(serialized_key_format);
-  if (!new_key_result.ok()) return new_key_result.status();
-  auto new_key = reinterpret_cast<const EciesAeadHkdfPrivateKey&>(
-      *(new_key_result.ValueOrDie()));
-  std::unique_ptr<KeyData> key_data(new KeyData());
-  key_data->set_type_url(EciesAeadHkdfPrivateKeyManager::kKeyType);
-  key_data->set_value(new_key.SerializeAsString());
-  key_data->set_key_material_type(KeyData::ASYMMETRIC_PRIVATE);
-  return std::move(key_data);
+  return absl::implicit_cast<
+      StatusOr<std::unique_ptr<EciesAeadHkdfPrivateKey>>>(
+      std::move(ecies_private_key));
 }
 
 StatusOr<std::unique_ptr<KeyData>>
@@ -142,28 +104,21 @@
   if (!private_key.ParseFromString(std::string(serialized_private_key))) {
     return ToStatusF(util::error::INVALID_ARGUMENT,
                      "Could not parse the passed string as proto '%s'.",
-                     EciesAeadHkdfPrivateKeyManager::kKeyType);
+                     EciesAeadHkdfPrivateKeyManager::static_key_type().c_str());
   }
   auto status = EciesAeadHkdfPrivateKeyManager::Validate(private_key);
   if (!status.ok()) return status;
   auto key_data = absl::make_unique<KeyData>();
-  key_data->set_type_url(EciesAeadHkdfPublicKeyManager::kKeyType);
+  key_data->set_type_url(EciesAeadHkdfPublicKeyManager::static_key_type());
   key_data->set_value(private_key.public_key().SerializeAsString());
   key_data->set_key_material_type(KeyData:: ASYMMETRIC_PUBLIC);
   return std::move(key_data);
 }
 
-constexpr char EciesAeadHkdfPrivateKeyManager::kKeyFormatUrl[];
-constexpr char EciesAeadHkdfPrivateKeyManager::kKeyTypePrefix[];
-constexpr char EciesAeadHkdfPrivateKeyManager::kKeyType[];
 constexpr uint32_t EciesAeadHkdfPrivateKeyManager::kVersion;
 
 EciesAeadHkdfPrivateKeyManager::EciesAeadHkdfPrivateKeyManager()
-    : key_type_(kKeyType), key_factory_(new EciesAeadHkdfPrivateKeyFactory()) {
-}
-
-const std::string& EciesAeadHkdfPrivateKeyManager::get_key_type() const {
-  return key_type_;
+    : key_factory_(new EciesAeadHkdfPrivateKeyFactory()) {
 }
 
 const KeyFactory& EciesAeadHkdfPrivateKeyManager::get_key_factory() const {
@@ -175,38 +130,7 @@
 }
 
 StatusOr<std::unique_ptr<HybridDecrypt>>
-EciesAeadHkdfPrivateKeyManager::GetPrimitive(const KeyData& key_data) const {
-  if (DoesSupport(key_data.type_url())) {
-    EciesAeadHkdfPrivateKey ecies_private_key;
-    if (!ecies_private_key.ParseFromString(key_data.value())) {
-      return ToStatusF(util::error::INVALID_ARGUMENT,
-                       "Could not parse key_data.value as key type '%s'.",
-                       key_data.type_url().c_str());
-    }
-    return GetPrimitiveImpl(ecies_private_key);
-  } else {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Key type '%s' is not supported by this manager.",
-                     key_data.type_url().c_str());
-  }
-}
-
-StatusOr<std::unique_ptr<HybridDecrypt>>
-EciesAeadHkdfPrivateKeyManager::GetPrimitive(const MessageLite& key) const {
-  std::string key_type = std::string(kKeyTypePrefix) + key.GetTypeName();
-  if (DoesSupport(key_type)) {
-    const EciesAeadHkdfPrivateKey& ecies_private_key =
-        reinterpret_cast<const EciesAeadHkdfPrivateKey&>(key);
-    return GetPrimitiveImpl(ecies_private_key);
-  } else {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Key type '%s' is not supported by this manager.",
-                     key_type.c_str());
-  }
-}
-
-StatusOr<std::unique_ptr<HybridDecrypt>>
-EciesAeadHkdfPrivateKeyManager::GetPrimitiveImpl(
+EciesAeadHkdfPrivateKeyManager::GetPrimitiveFromKey(
     const EciesAeadHkdfPrivateKey& ecies_private_key) const {
   Status status = Validate(ecies_private_key);
   if (!status.ok()) return status;
diff --git a/cc/hybrid/ecies_aead_hkdf_private_key_manager.h b/cc/hybrid/ecies_aead_hkdf_private_key_manager.h
index 6eb3cf0..9b07582 100644
--- a/cc/hybrid/ecies_aead_hkdf_private_key_manager.h
+++ b/cc/hybrid/ecies_aead_hkdf_private_key_manager.h
@@ -13,14 +13,14 @@
 // limitations under the License.
 //
 ///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_HYBRID_ECIES_AEAD_HKDF_PRIVATE_KEY_MANAGER_H_
+#define TINK_HYBRID_ECIES_AEAD_HKDF_PRIVATE_KEY_MANAGER_H_
 
 #include <algorithm>
 #include <vector>
 
-#ifndef TINK_HYBRID_ECIES_AEAD_HKDF_PRIVATE_KEY_MANAGER_H_
-#define TINK_HYBRID_ECIES_AEAD_HKDF_PRIVATE_KEY_MANAGER_H_
-
 #include "absl/strings/string_view.h"
+#include "tink/core/key_manager_base.h"
 #include "tink/hybrid_decrypt.h"
 #include "tink/key_manager.h"
 #include "tink/util/errors.h"
@@ -33,27 +33,14 @@
 namespace crypto {
 namespace tink {
 
-class EciesAeadHkdfPrivateKeyManager : public KeyManager<HybridDecrypt> {
+class EciesAeadHkdfPrivateKeyManager
+    : public KeyManagerBase<HybridDecrypt,
+                            google::crypto::tink::EciesAeadHkdfPrivateKey> {
  public:
-  static constexpr char kKeyType[] =
-      "type.googleapis.com/google.crypto.tink.EciesAeadHkdfPrivateKey";
   static constexpr uint32_t kVersion = 0;
 
   EciesAeadHkdfPrivateKeyManager();
 
-  // Constructs an instance of ECIES-AEAD-HKDF HybridDecrypt
-  // for the given 'key_data', which must contain EciesAeadHkdfPrivateKey-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<HybridDecrypt>> GetPrimitive(
-      const google::crypto::tink::KeyData& key_data) const override;
-
-  // Constructs an instance of ECIES-AEAD-HKDF HybridDecrypt
-  // for the given 'key', which must be EciesAeadHkdfPrivateKey-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<HybridDecrypt>>
-  GetPrimitive(const portable_proto::MessageLite& key) const override;
-
-  // Returns the type_url identifying the key type handled by this manager.
-  const std::string& get_key_type() const override;
-
   // Returns the version of this key manager.
   uint32_t get_version() const override;
 
@@ -63,21 +50,16 @@
 
   virtual ~EciesAeadHkdfPrivateKeyManager() {}
 
+ protected:
+  crypto::tink::util::StatusOr<std::unique_ptr<HybridDecrypt>>
+  GetPrimitiveFromKey(const google::crypto::tink::EciesAeadHkdfPrivateKey&
+                          ecies_private_key) const override;
+
  private:
   friend class EciesAeadHkdfPrivateKeyFactory;
 
-  static constexpr char kKeyTypePrefix[] = "type.googleapis.com/";
-  static constexpr char kKeyFormatUrl[] =
-      "type.googleapis.com/google.crypto.tink.EciesAeadHkdfKeyFormat";
-
-  std::string key_type_;
   std::unique_ptr<KeyFactory> key_factory_;
 
-  // Constructs an instance of ECIES-AEAD-HKDF HybridDecrypt
-  // for the given 'key'.
-  crypto::tink::util::StatusOr<std::unique_ptr<HybridDecrypt>> GetPrimitiveImpl(
-  const google::crypto::tink::EciesAeadHkdfPrivateKey& ecies_private_key) const;
-
   static crypto::tink::util::Status Validate(
       const google::crypto::tink::EciesAeadHkdfPrivateKey& key);
 };
diff --git a/cc/hybrid/ecies_aead_hkdf_private_key_manager_test.cc b/cc/hybrid/ecies_aead_hkdf_private_key_manager_test.cc
index 2c9547f..d8154ee 100644
--- a/cc/hybrid/ecies_aead_hkdf_private_key_manager_test.cc
+++ b/cc/hybrid/ecies_aead_hkdf_private_key_manager_test.cc
@@ -49,10 +49,12 @@
 class EciesAeadHkdfPrivateKeyManagerTest : public ::testing::Test {
  protected:
   static void SetUpTestCase() {
-    auto aes_gcm_key_manager = new AesGcmKeyManager();
-    ASSERT_TRUE(Registry::RegisterKeyManager(aes_gcm_key_manager).ok());
-    auto aes_ctr_hmac_key_manager = new AesCtrHmacAeadKeyManager();
-    ASSERT_TRUE(Registry::RegisterKeyManager(aes_ctr_hmac_key_manager).ok());
+    ASSERT_TRUE(Registry::RegisterKeyManager(
+                    absl::make_unique<AesGcmKeyManager>(), true)
+                    .ok());
+    ASSERT_TRUE(Registry::RegisterKeyManager(
+                    absl::make_unique<AesCtrHmacAeadKeyManager>(), true)
+                    .ok());
   }
 
   std::string key_type_prefix = "type.googleapis.com/";
@@ -237,7 +239,7 @@
       new_key->SerializeAsString());
   EXPECT_TRUE(public_key_data_result.ok()) << public_key_data_result.status();
   auto public_key_data = std::move(public_key_data_result.ValueOrDie());
-  EXPECT_EQ(EciesAeadHkdfPublicKeyManager::kKeyType,
+  EXPECT_EQ(EciesAeadHkdfPublicKeyManager::static_key_type(),
             public_key_data->type_url());
   EXPECT_EQ(KeyData::ASYMMETRIC_PUBLIC, public_key_data->key_material_type());
   EXPECT_EQ(new_key->public_key().SerializeAsString(),
@@ -335,9 +337,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/hybrid/ecies_aead_hkdf_public_key_manager.cc b/cc/hybrid/ecies_aead_hkdf_public_key_manager.cc
index 85cfa9b..938ae86 100644
--- a/cc/hybrid/ecies_aead_hkdf_public_key_manager.cc
+++ b/cc/hybrid/ecies_aead_hkdf_public_key_manager.cc
@@ -44,97 +44,24 @@
 using crypto::tink::util::Status;
 using crypto::tink::util::StatusOr;
 
-class EciesAeadHkdfPublicKeyFactory : public KeyFactory {
- public:
-  EciesAeadHkdfPublicKeyFactory() {}
-
-  // Not implemented for public keys.
-  crypto::tink::util::StatusOr<std::unique_ptr<portable_proto::MessageLite>>
-  NewKey(const portable_proto::MessageLite& key_format) const override;
-
-  // Not implemented for public keys.
-  crypto::tink::util::StatusOr<std::unique_ptr<portable_proto::MessageLite>>
-  NewKey(absl::string_view serialized_key_format) const override;
-
-  // Not implemented for public keys.
-  crypto::tink::util::StatusOr<std::unique_ptr<google::crypto::tink::KeyData>>
-  NewKeyData(absl::string_view serialized_key_format) const override;
-};
-
-StatusOr<std::unique_ptr<MessageLite>> EciesAeadHkdfPublicKeyFactory::NewKey(
-    const portable_proto::MessageLite& key_format) const {
-  return util::Status(util::error::UNIMPLEMENTED,
-                      "Operation not supported for public keys, "
-                      "please use EciesAeadHkdfPrivateKeyManager.");
-}
-
-StatusOr<std::unique_ptr<MessageLite>> EciesAeadHkdfPublicKeyFactory::NewKey(
-    absl::string_view serialized_key_format) const {
-  return util::Status(util::error::UNIMPLEMENTED,
-                      "Operation not supported for public keys, "
-                      "please use EciesAeadHkdfPrivateKeyManager.");
-}
-
-StatusOr<std::unique_ptr<KeyData>> EciesAeadHkdfPublicKeyFactory::NewKeyData(
-    absl::string_view serialized_key_format) const {
-  return util::Status(util::error::UNIMPLEMENTED,
-                      "Operation not supported for public keys, "
-                      "please use EciesAeadHkdfPrivateKeyManager.");
-}
-
-constexpr char EciesAeadHkdfPublicKeyManager::kKeyTypePrefix[];
-constexpr char EciesAeadHkdfPublicKeyManager::kKeyType[];
 constexpr uint32_t EciesAeadHkdfPublicKeyManager::kVersion;
 
 EciesAeadHkdfPublicKeyManager::EciesAeadHkdfPublicKeyManager()
-    : key_type_(kKeyType), key_factory_(new EciesAeadHkdfPublicKeyFactory()) {
-}
+    : key_factory_(KeyFactory::AlwaysFailingFactory(
+          util::Status(util::error::UNIMPLEMENTED,
+                       "Operation not supported for public keys, "
+                       "please use EciesAeadHkdfPrivateKeyManager."))) {}
 
 const KeyFactory& EciesAeadHkdfPublicKeyManager::get_key_factory() const {
   return *key_factory_;
 }
 
-const std::string& EciesAeadHkdfPublicKeyManager::get_key_type() const {
-  return key_type_;
-}
-
 uint32_t EciesAeadHkdfPublicKeyManager::get_version() const {
   return kVersion;
 }
 
 StatusOr<std::unique_ptr<HybridEncrypt>>
-EciesAeadHkdfPublicKeyManager::GetPrimitive(const KeyData& key_data) const {
-  if (DoesSupport(key_data.type_url())) {
-    EciesAeadHkdfPublicKey ecies_public_key;
-    if (!ecies_public_key.ParseFromString(key_data.value())) {
-      return ToStatusF(util::error::INVALID_ARGUMENT,
-                       "Could not parse key_data.value as key type '%s'.",
-                       key_data.type_url().c_str());
-    }
-    return GetPrimitiveImpl(ecies_public_key);
-  } else {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Key type '%s' is not supported by this manager.",
-                     key_data.type_url().c_str());
-  }
-}
-
-StatusOr<std::unique_ptr<HybridEncrypt>>
-EciesAeadHkdfPublicKeyManager::GetPrimitive(const MessageLite& key) const {
-  std::string key_type = std::string(kKeyTypePrefix) + key.GetTypeName();
-  if (DoesSupport(key_type)) {
-    const EciesAeadHkdfPublicKey& ecies_public_key =
-        reinterpret_cast<const EciesAeadHkdfPublicKey&>(key);
-    return GetPrimitiveImpl(ecies_public_key);
-  } else {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Key type '%s' is not supported by this manager.",
-                     key_type.c_str());
-  }
-}
-
-StatusOr<std::unique_ptr<HybridEncrypt>>
-EciesAeadHkdfPublicKeyManager::GetPrimitiveImpl(
+EciesAeadHkdfPublicKeyManager::GetPrimitiveFromKey(
     const EciesAeadHkdfPublicKey& recipient_key) const {
   Status status = Validate(recipient_key);
   if (!status.ok()) return status;
diff --git a/cc/hybrid/ecies_aead_hkdf_public_key_manager.h b/cc/hybrid/ecies_aead_hkdf_public_key_manager.h
index 4c8206e..e76389e 100644
--- a/cc/hybrid/ecies_aead_hkdf_public_key_manager.h
+++ b/cc/hybrid/ecies_aead_hkdf_public_key_manager.h
@@ -13,14 +13,14 @@
 // limitations under the License.
 //
 ///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_HYBRID_ECIES_AEAD_HKDF_PUBLIC_KEY_MANAGER_H_
+#define TINK_HYBRID_ECIES_AEAD_HKDF_PUBLIC_KEY_MANAGER_H_
 
 #include <algorithm>
 #include <vector>
 
-#ifndef TINK_HYBRID_ECIES_AEAD_HKDF_PUBLIC_KEY_MANAGER_H_
-#define TINK_HYBRID_ECIES_AEAD_HKDF_PUBLIC_KEY_MANAGER_H_
-
 #include "absl/strings/string_view.h"
+#include "tink/core/key_manager_base.h"
 #include "tink/hybrid_encrypt.h"
 #include "tink/key_manager.h"
 #include "tink/util/errors.h"
@@ -33,27 +33,14 @@
 namespace crypto {
 namespace tink {
 
-class EciesAeadHkdfPublicKeyManager : public KeyManager<HybridEncrypt> {
+class EciesAeadHkdfPublicKeyManager
+    : public KeyManagerBase<HybridEncrypt,
+                            google::crypto::tink::EciesAeadHkdfPublicKey> {
  public:
-  static constexpr char kKeyType[] =
-      "type.googleapis.com/google.crypto.tink.EciesAeadHkdfPublicKey";
   static constexpr uint32_t kVersion = 0;
 
   EciesAeadHkdfPublicKeyManager();
 
-  // Constructs an instance of ECIES-AEAD-HKDF HybridEncrypt
-  // for the given 'key_data', which must contain EciesAeadHkdfPublicKey-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<HybridEncrypt>> GetPrimitive(
-      const google::crypto::tink::KeyData& key_data) const override;
-
-  // Constructs an instance of ECIES-AEAD-HKDF HybridEncrypt
-  // for the given 'key', which must be EciesAeadHkdfPublicKey-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<HybridEncrypt>>
-  GetPrimitive(const portable_proto::MessageLite& key) const override;
-
-  // Returns the type_url identifying the key type handled by this manager.
-  const std::string& get_key_type() const override;
-
   // Returns the version of this key manager.
   uint32_t get_version() const override;
 
@@ -63,20 +50,18 @@
 
   virtual ~EciesAeadHkdfPublicKeyManager() {}
 
+ protected:
+  crypto::tink::util::StatusOr<std::unique_ptr<HybridEncrypt>>
+  GetPrimitiveFromKey(const google::crypto::tink::EciesAeadHkdfPublicKey&
+                          recipient_key) const override;
+
  private:
   // Friends that re-use proto validation helpers.
   friend class EciesAeadHkdfPrivateKeyFactory;
   friend class EciesAeadHkdfPrivateKeyManager;
 
-  static constexpr char kKeyTypePrefix[] = "type.googleapis.com/";
-
-  std::string key_type_;
   std::unique_ptr<KeyFactory> key_factory_;
 
-  // Constructs an instance of HybridEncrypt for the given 'key'.
-  crypto::tink::util::StatusOr<std::unique_ptr<HybridEncrypt>> GetPrimitiveImpl(
-      const google::crypto::tink::EciesAeadHkdfPublicKey& recipient_key) const;
-
   static crypto::tink::util::Status Validate(
       const google::crypto::tink::EciesAeadHkdfParams& params);
   static crypto::tink::util::Status Validate(
diff --git a/cc/hybrid/ecies_aead_hkdf_public_key_manager_test.cc b/cc/hybrid/ecies_aead_hkdf_public_key_manager_test.cc
index a264c9d..a085f60 100644
--- a/cc/hybrid/ecies_aead_hkdf_public_key_manager_test.cc
+++ b/cc/hybrid/ecies_aead_hkdf_public_key_manager_test.cc
@@ -45,8 +45,9 @@
 class EciesAeadHkdfPublicKeyManagerTest : public ::testing::Test {
  protected:
   static void SetUpTestCase() {
-    auto aes_gcm_key_manager = new AesGcmKeyManager();
-    ASSERT_TRUE(Registry::RegisterKeyManager(aes_gcm_key_manager).ok());
+    ASSERT_TRUE(Registry::RegisterKeyManager(
+                    absl::make_unique<AesGcmKeyManager>(), true)
+                    .ok());
   }
 
   std::string key_type_prefix = "type.googleapis.com/";
@@ -183,9 +184,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/hybrid/hybrid_config.cc b/cc/hybrid/hybrid_config.cc
index 4dd7ecc..90aab13 100644
--- a/cc/hybrid/hybrid_config.cc
+++ b/cc/hybrid/hybrid_config.cc
@@ -16,14 +16,14 @@
 
 #include "tink/hybrid/hybrid_config.h"
 
-#include "tink/config.h"
+#include "absl/memory/memory.h"
 #include "tink/aead/aead_config.h"
+#include "tink/config.h"
 #include "tink/hybrid/hybrid_decrypt_catalogue.h"
 #include "tink/hybrid/hybrid_encrypt_catalogue.h"
 #include "tink/util/status.h"
 #include "proto/config.pb.h"
 
-
 namespace crypto {
 namespace tink {
 
@@ -62,11 +62,11 @@
 util::Status HybridConfig::Register() {
   auto status = AeadConfig::Register();
   if (!status.ok()) return status;
-  status = Registry::AddCatalogue(
-      kHybridDecryptCatalogueName, new HybridDecryptCatalogue());
+  status = Registry::AddCatalogue(kHybridDecryptCatalogueName,
+                                  absl::make_unique<HybridDecryptCatalogue>());
   if (!status.ok()) return status;
-  status = Registry::AddCatalogue(
-      kHybridEncryptCatalogueName, new HybridEncryptCatalogue());
+  status = Registry::AddCatalogue(kHybridEncryptCatalogueName,
+                                  absl::make_unique<HybridEncryptCatalogue>());
   if (!status.ok()) return status;
   return Config::Register(Latest());
 }
diff --git a/cc/hybrid/hybrid_config_test.cc b/cc/hybrid/hybrid_config_test.cc
index 0d79ed0..b55f27c 100644
--- a/cc/hybrid/hybrid_config_test.cc
+++ b/cc/hybrid/hybrid_config_test.cc
@@ -16,17 +16,25 @@
 
 #include "tink/hybrid/hybrid_config.h"
 
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
 #include "tink/catalogue.h"
 #include "tink/config.h"
+#include "tink/hybrid/hybrid_key_templates.h"
+#include "tink/hybrid_decrypt.h"
+#include "tink/hybrid_encrypt.h"
+#include "tink/keyset_handle.h"
 #include "tink/registry.h"
 #include "tink/util/status.h"
-#include "gtest/gtest.h"
-
+#include "tink/util/test_util.h"
 
 namespace crypto {
 namespace tink {
 namespace {
 
+using ::crypto::tink::test::DummyHybridEncrypt;
+using ::crypto::tink::test::DummyHybridDecrypt;
+
 class DummyHybridDecryptCatalogue : public Catalogue<HybridDecrypt> {
  public:
   DummyHybridDecryptCatalogue() {}
@@ -58,11 +66,13 @@
       "type.googleapis.com/google.crypto.tink.AesEaxKey";
   std::string aes_gcm_key_type =
       "type.googleapis.com/google.crypto.tink.AesGcmKey";
+  std::string xchacha20_poly1305_key_type =
+      "type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key";
   std::string hmac_key_type =
       "type.googleapis.com/google.crypto.tink.HmacKey";
   auto& config = HybridConfig::Latest();
 
-  EXPECT_EQ(6, HybridConfig::Latest().entry_size());
+  EXPECT_EQ(7, HybridConfig::Latest().entry_size());
 
   EXPECT_EQ("TinkMac", config.entry(0).catalogue_name());
   EXPECT_EQ("Mac", config.entry(0).primitive_name());
@@ -88,18 +98,24 @@
   EXPECT_EQ(true, config.entry(3).new_key_allowed());
   EXPECT_EQ(0, config.entry(3).key_manager_version());
 
-  EXPECT_EQ("TinkHybridDecrypt", config.entry(4).catalogue_name());
-  EXPECT_EQ("HybridDecrypt", config.entry(4).primitive_name());
-  EXPECT_EQ(decrypt_key_type, config.entry(4).type_url());
+  EXPECT_EQ("TinkAead", config.entry(4).catalogue_name());
+  EXPECT_EQ("Aead", config.entry(4).primitive_name());
+  EXPECT_EQ(xchacha20_poly1305_key_type, config.entry(4).type_url());
   EXPECT_EQ(true, config.entry(4).new_key_allowed());
   EXPECT_EQ(0, config.entry(4).key_manager_version());
 
-  EXPECT_EQ("TinkHybridEncrypt", config.entry(5).catalogue_name());
-  EXPECT_EQ("HybridEncrypt", config.entry(5).primitive_name());
-  EXPECT_EQ(encrypt_key_type, config.entry(5).type_url());
+  EXPECT_EQ("TinkHybridDecrypt", config.entry(5).catalogue_name());
+  EXPECT_EQ("HybridDecrypt", config.entry(5).primitive_name());
+  EXPECT_EQ(decrypt_key_type, config.entry(5).type_url());
   EXPECT_EQ(true, config.entry(5).new_key_allowed());
   EXPECT_EQ(0, config.entry(5).key_manager_version());
 
+  EXPECT_EQ("TinkHybridEncrypt", config.entry(6).catalogue_name());
+  EXPECT_EQ("HybridEncrypt", config.entry(6).primitive_name());
+  EXPECT_EQ(encrypt_key_type, config.entry(6).type_url());
+  EXPECT_EQ(true, config.entry(6).new_key_allowed());
+  EXPECT_EQ(0, config.entry(6).key_manager_version());
+
   // No key manager before registration.
   auto decrypt_manager_result =
       Registry::get_key_manager<HybridDecrypt>(decrypt_key_type);
@@ -159,12 +175,64 @@
   EXPECT_EQ(util::error::ALREADY_EXISTS, status.error_code());
 }
 
+// Tests that the HybridEncryptWrapper has been properly registered and we
+// can wrap primitives.
+TEST_F(HybridConfigTest, EncryptWrapperRegistered) {
+  ASSERT_TRUE(HybridConfig::Register().ok());
+
+  google::crypto::tink::Keyset::Key key;
+  key.set_status(google::crypto::tink::KeyStatusType::ENABLED);
+  key.set_key_id(1234);
+  key.set_output_prefix_type(google::crypto::tink::OutputPrefixType::TINK);
+  auto primitive_set = absl::make_unique<PrimitiveSet<HybridEncrypt>>();
+  primitive_set->set_primary(
+      primitive_set
+          ->AddPrimitive(absl::make_unique<DummyHybridEncrypt>("dummy"), key)
+          .ValueOrDie());
+
+  auto wrapped = Registry::Wrap(std::move(primitive_set));
+
+  ASSERT_TRUE(wrapped.ok()) << wrapped.status();
+  auto encryption_result = wrapped.ValueOrDie()->Encrypt("secret", "");
+  ASSERT_TRUE(encryption_result.ok());
+
+  std::string prefix = CryptoFormat::get_output_prefix(key).ValueOrDie();
+  EXPECT_EQ(
+      encryption_result.ValueOrDie(),
+      absl::StrCat(
+          prefix,
+          DummyHybridEncrypt("dummy").Encrypt("secret", "").ValueOrDie()));
+}
+
+// Tests that the HybridDecryptWrapper has been properly registered and we
+// can wrap primitives.
+TEST_F(HybridConfigTest, DecryptWrapperRegistered) {
+  ASSERT_TRUE(HybridConfig::Register().ok());
+
+  google::crypto::tink::Keyset::Key key;
+  key.set_status(google::crypto::tink::KeyStatusType::ENABLED);
+  key.set_key_id(1234);
+  key.set_output_prefix_type(google::crypto::tink::OutputPrefixType::TINK);
+  auto primitive_set = absl::make_unique<PrimitiveSet<HybridDecrypt>>();
+  primitive_set->set_primary(
+      primitive_set
+          ->AddPrimitive(absl::make_unique<DummyHybridDecrypt>("dummy"), key)
+          .ValueOrDie());
+
+  auto wrapped = Registry::Wrap(std::move(primitive_set));
+
+  ASSERT_TRUE(wrapped.ok()) << wrapped.status();
+
+  std::string prefix = CryptoFormat::get_output_prefix(key).ValueOrDie();
+  std::string encryption =
+      DummyHybridEncrypt("dummy").Encrypt("secret", "").ValueOrDie();
+
+  ASSERT_EQ(wrapped.ValueOrDie()
+                ->Decrypt(absl::StrCat(prefix, encryption), "")
+                .ValueOrDie(),
+            "secret");
+}
+
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/hybrid/hybrid_decrypt_catalogue.cc b/cc/hybrid/hybrid_decrypt_catalogue.cc
index b03991b..ecf6fea 100644
--- a/cc/hybrid/hybrid_decrypt_catalogue.cc
+++ b/cc/hybrid/hybrid_decrypt_catalogue.cc
@@ -30,7 +30,7 @@
 
 crypto::tink::util::StatusOr<std::unique_ptr<KeyManager<HybridDecrypt>>>
 CreateKeyManager(const std::string& type_url) {
-  if (type_url == EciesAeadHkdfPrivateKeyManager::kKeyType) {
+  if (type_url == EciesAeadHkdfPrivateKeyManager::static_key_type()) {
     std::unique_ptr<KeyManager<HybridDecrypt>> manager(
         new EciesAeadHkdfPrivateKeyManager());
     return std::move(manager);
diff --git a/cc/hybrid/hybrid_decrypt_catalogue_test.cc b/cc/hybrid/hybrid_decrypt_catalogue_test.cc
index 8e128f1..7e26d5e 100644
--- a/cc/hybrid/hybrid_decrypt_catalogue_test.cc
+++ b/cc/hybrid/hybrid_decrypt_catalogue_test.cc
@@ -61,9 +61,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/hybrid/hybrid_decrypt_factory.cc b/cc/hybrid/hybrid_decrypt_factory.cc
index 2c47cfd..401d257 100644
--- a/cc/hybrid/hybrid_decrypt_factory.cc
+++ b/cc/hybrid/hybrid_decrypt_factory.cc
@@ -20,7 +20,7 @@
 #include "tink/key_manager.h"
 #include "tink/keyset_handle.h"
 #include "tink/registry.h"
-#include "tink/hybrid/hybrid_decrypt_set_wrapper.h"
+#include "tink/hybrid/hybrid_decrypt_wrapper.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 
@@ -31,20 +31,25 @@
 // static
 util::StatusOr<std::unique_ptr<HybridDecrypt>>
 HybridDecryptFactory::GetPrimitive(const KeysetHandle& keyset_handle) {
-  return GetPrimitive(keyset_handle, nullptr);
+  util::Status status = Registry::RegisterPrimitiveWrapper(
+      absl::make_unique<HybridDecryptWrapper>());
+  if (!status.ok()) {
+    return status;
+  }
+  return keyset_handle.GetPrimitive<HybridDecrypt>();
 }
 
 // static
 util::StatusOr<std::unique_ptr<HybridDecrypt>>
-HybridDecryptFactory::GetPrimitive(const KeysetHandle& keyset_handle,
+HybridDecryptFactory::GetPrimitive(
+    const KeysetHandle& keyset_handle,
     const KeyManager<HybridDecrypt>* custom_key_manager) {
-  auto primitives_result = Registry::GetPrimitives<HybridDecrypt>(
-      keyset_handle, custom_key_manager);
-  if (primitives_result.ok()) {
-    return HybridDecryptSetWrapper::NewHybridDecrypt(
-        std::move(primitives_result.ValueOrDie()));
+  util::Status status = Registry::RegisterPrimitiveWrapper(
+      absl::make_unique<HybridDecryptWrapper>());
+  if (!status.ok()) {
+    return status;
   }
-  return primitives_result.status();
+  return keyset_handle.GetPrimitive<HybridDecrypt>(custom_key_manager);
 }
 
 }  // namespace tink
diff --git a/cc/hybrid/hybrid_decrypt_factory.h b/cc/hybrid/hybrid_decrypt_factory.h
index 46ce8d1..5218697 100644
--- a/cc/hybrid/hybrid_decrypt_factory.h
+++ b/cc/hybrid/hybrid_decrypt_factory.h
@@ -17,6 +17,7 @@
 #ifndef TINK_HYBRID_HYBRID_DECRYPT_FACTORY_H_
 #define TINK_HYBRID_HYBRID_DECRYPT_FACTORY_H_
 
+#include "absl/base/macros.h"
 #include "tink/hybrid_decrypt.h"
 #include "tink/key_manager.h"
 #include "tink/keyset_handle.h"
@@ -25,38 +26,34 @@
 namespace crypto {
 namespace tink {
 
-
 ///////////////////////////////////////////////////////////////////////////////
-// HybridDecryptFactory allows for obtaining an HybridDecrypt primitive
-// from a KeysetHandle.
+// This class is deprecated. Call keyset_handle->GetPrimitive<HybridDecrypt>()
+// instead.
 //
-// HybridDecryptFactory gets primitives from the Registry, which can
-// be initialized via a convenience method from HybridConfig-class.
-//  Here is an example how one can obtain and use a HybridDecrypt primitive:
-//
-//   auto status = HybridConfig::Register();
-//   if (!status.ok()) { /* fail with error */ }
-//   KeysetHandle keyset_handle = ...;
-//   std::unique_ptr<HybridDecrypt> hybrid_decrypt = std::move(
-//           HybridDecryptFactory::GetPrimitive(keyset_handle).ValueOrDie());
-//   std::string ciphertext = ...;
-//   std::string context_info = ...;
-//   std::string plaintext =
-//       hybrid_decrypt.Decrypt(ciphertext, context_info).ValueOrDie();
-//
-class HybridDecryptFactory {
+// Note that in order to for this change to be safe, the AeadSetWrapper has to
+// be registered in your binary before this call. This happens automatically if
+// you call one of
+// * HybridConfig::Register()
+// * TinkConfig::Register()
+class ABSL_DEPRECATED(
+    "Call getPrimitive<HybridDecrypt>() on the keyset_handle after registering "
+    "the HybridDecryptWrapper instead.") HybridDecryptFactory {
  public:
   // Returns a HybridDecrypt-primitive that uses key material from the keyset
   // specified via 'keyset_handle'.
+  ABSL_DEPRECATED(
+      "Call getPrimitive<HybridDecrypt>() on the keyset_handle after "
+      "registering "
+      "the HybridEncryptWrapper instead.")
   static crypto::tink::util::StatusOr<std::unique_ptr<HybridDecrypt>>
-      GetPrimitive(const KeysetHandle& keyset_handle);
+  GetPrimitive(const KeysetHandle& keyset_handle);
 
   // Returns a HybridDecrypt-primitive that uses key material from the keyset
   // specified via 'keyset_handle' and is instantiated by the given
   // 'custom_key_manager' (instead of the key manager from the Registry).
   static crypto::tink::util::StatusOr<std::unique_ptr<HybridDecrypt>>
-      GetPrimitive(const KeysetHandle& keyset_handle,
-                   const KeyManager<HybridDecrypt>* custom_key_manager);
+  GetPrimitive(const KeysetHandle& keyset_handle,
+               const KeyManager<HybridDecrypt>* custom_key_manager);
 
  private:
   HybridDecryptFactory() {}
diff --git a/cc/hybrid/hybrid_decrypt_factory_test.cc b/cc/hybrid/hybrid_decrypt_factory_test.cc
index 53407f5..3e172c5 100644
--- a/cc/hybrid/hybrid_decrypt_factory_test.cc
+++ b/cc/hybrid/hybrid_decrypt_factory_test.cc
@@ -143,9 +143,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/hybrid/hybrid_decrypt_set_wrapper.h b/cc/hybrid/hybrid_decrypt_set_wrapper.h
deleted file mode 100644
index e8402eb..0000000
--- a/cc/hybrid/hybrid_decrypt_set_wrapper.h
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-///////////////////////////////////////////////////////////////////////////////
-
-#ifndef TINK_HYBRID_HYBRID_DECRYPT_SET_WRAPPER_H_
-#define TINK_HYBRID_HYBRID_DECRYPT_SET_WRAPPER_H_
-
-#include "absl/strings/string_view.h"
-#include "tink/hybrid_decrypt.h"
-#include "tink/primitive_set.h"
-#include "tink/util/statusor.h"
-#include "proto/tink.pb.h"
-
-namespace crypto {
-namespace tink {
-
-// Wraps a set of HybridDecrypt-instances that correspond to a keyset,
-// and combines them into a single HybridDecrypt-primitive, that for
-// actual decryption uses the instance that matches the ciphertext prefix.
-class HybridDecryptSetWrapper : public HybridDecrypt {
- public:
-  // Returns an HybridDecrypt-primitive that uses HybridDecrypt-instances
-  // provided in 'hybrid_decrypt_set', which must be non-NULL.
-  static crypto::tink::util::StatusOr<std::unique_ptr<HybridDecrypt>>
-      NewHybridDecrypt(
-          std::unique_ptr<PrimitiveSet<HybridDecrypt>> hybrid_decrypt_set);
-
-  crypto::tink::util::StatusOr<std::string> Decrypt(
-      absl::string_view ciphertext,
-      absl::string_view context_info) const override;
-
-  virtual ~HybridDecryptSetWrapper() {}
-
- private:
-  std::unique_ptr<PrimitiveSet<HybridDecrypt>> hybrid_decrypt_set_;
-
-  HybridDecryptSetWrapper(
-      std::unique_ptr<PrimitiveSet<HybridDecrypt>> hybrid_decrypt_set)
-      : hybrid_decrypt_set_(std::move(hybrid_decrypt_set)) {}
-};
-
-}  // namespace tink
-}  // namespace crypto
-
-#endif  // TINK_HYBRID_HYBRID_DECRYPT_SET_WRAPPER_H_
diff --git a/cc/hybrid/hybrid_decrypt_set_wrapper.cc b/cc/hybrid/hybrid_decrypt_wrapper.cc
similarity index 81%
rename from cc/hybrid/hybrid_decrypt_set_wrapper.cc
rename to cc/hybrid/hybrid_decrypt_wrapper.cc
index a4814cb..c6edf59 100644
--- a/cc/hybrid/hybrid_decrypt_set_wrapper.cc
+++ b/cc/hybrid/hybrid_decrypt_wrapper.cc
@@ -14,7 +14,7 @@
 //
 ///////////////////////////////////////////////////////////////////////////////
 
-#include "tink/hybrid/hybrid_decrypt_set_wrapper.h"
+#include "tink/hybrid/hybrid_decrypt_wrapper.h"
 
 #include "tink/crypto_format.h"
 #include "tink/hybrid_decrypt.h"
@@ -28,30 +28,21 @@
 
 namespace {
 
-util::Status Validate(PrimitiveSet<HybridDecrypt>* hybrid_decrypt_set) {
-  if (hybrid_decrypt_set == nullptr) {
-    return util::Status(util::error::INTERNAL,
-                        "hybrid_decrypt_set must be non-NULL");
-  }
-  if (hybrid_decrypt_set->get_primary() == nullptr) {
-    return util::Status(util::error::INVALID_ARGUMENT,
-                        "hybrid_decrypt_set has no primary");
-  }
-  return util::Status::OK;
-}
+class HybridDecryptSetWrapper : public HybridDecrypt {
+ public:
+  explicit HybridDecryptSetWrapper(
+      std::unique_ptr<PrimitiveSet<HybridDecrypt>> hybrid_decrypt_set)
+      : hybrid_decrypt_set_(std::move(hybrid_decrypt_set)) {}
 
-}  // anonymous namespace
+  crypto::tink::util::StatusOr<std::string> Decrypt(
+      absl::string_view ciphertext,
+      absl::string_view context_info) const override;
 
-// static
-util::StatusOr<std::unique_ptr<HybridDecrypt>>
-HybridDecryptSetWrapper::NewHybridDecrypt(
-    std::unique_ptr<PrimitiveSet<HybridDecrypt>> hybrid_decrypt_set) {
-  util::Status status = Validate(hybrid_decrypt_set.get());
-  if (!status.ok()) return status;
-  std::unique_ptr<HybridDecrypt> hybrid_decrypt(
-      new HybridDecryptSetWrapper(std::move(hybrid_decrypt_set)));
-  return std::move(hybrid_decrypt);
-}
+  ~HybridDecryptSetWrapper() override {}
+
+ private:
+  std::unique_ptr<PrimitiveSet<HybridDecrypt>> hybrid_decrypt_set_;
+};
 
 util::StatusOr<std::string> HybridDecryptSetWrapper::Decrypt(
     absl::string_view ciphertext,
@@ -94,5 +85,30 @@
   return util::Status(util::error::INVALID_ARGUMENT, "decryption failed");
 }
 
+util::Status Validate(PrimitiveSet<HybridDecrypt>* hybrid_decrypt_set) {
+  if (hybrid_decrypt_set == nullptr) {
+    return util::Status(util::error::INTERNAL,
+                        "hybrid_decrypt_set must be non-NULL");
+  }
+  if (hybrid_decrypt_set->get_primary() == nullptr) {
+    return util::Status(util::error::INVALID_ARGUMENT,
+                        "hybrid_decrypt_set has no primary");
+  }
+  return util::Status::OK;
+}
+
+}  // anonymous namespace
+
+// static
+util::StatusOr<std::unique_ptr<HybridDecrypt>>
+HybridDecryptWrapper::Wrap(
+    std::unique_ptr<PrimitiveSet<HybridDecrypt>> primitive_set) const {
+  util::Status status = Validate(primitive_set.get());
+  if (!status.ok()) return status;
+  std::unique_ptr<HybridDecrypt> hybrid_decrypt(
+      new HybridDecryptSetWrapper(std::move(primitive_set)));
+  return std::move(hybrid_decrypt);
+}
+
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/hybrid/hybrid_decrypt_wrapper.h b/cc/hybrid/hybrid_decrypt_wrapper.h
new file mode 100644
index 0000000..83f314d
--- /dev/null
+++ b/cc/hybrid/hybrid_decrypt_wrapper.h
@@ -0,0 +1,43 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_HYBRID_HYBRID_DECRYPT_WRAPPER_H_
+#define TINK_HYBRID_HYBRID_DECRYPT_WRAPPER_H_
+
+#include "absl/strings/string_view.h"
+#include "tink/hybrid_decrypt.h"
+#include "tink/primitive_set.h"
+#include "tink/primitive_wrapper.h"
+#include "tink/util/statusor.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+
+// Wraps a set of HybridDecrypt-instances that correspond to a keyset,
+// and combines them into a single HybridDecrypt-primitive, that for
+// actual decryption uses the instance that matches the ciphertext prefix.
+class HybridDecryptWrapper : public PrimitiveWrapper<HybridDecrypt> {
+ public:
+  util::StatusOr<std::unique_ptr<HybridDecrypt>> Wrap(
+      std::unique_ptr<PrimitiveSet<HybridDecrypt>> primitive_set)
+      const override;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_HYBRID_HYBRID_DECRYPT_WRAPPER_H_
diff --git a/cc/hybrid/hybrid_decrypt_set_wrapper_test.cc b/cc/hybrid/hybrid_decrypt_wrapper_test.cc
similarity index 88%
rename from cc/hybrid/hybrid_decrypt_set_wrapper_test.cc
rename to cc/hybrid/hybrid_decrypt_wrapper_test.cc
index b99396c..03c6283 100644
--- a/cc/hybrid/hybrid_decrypt_set_wrapper_test.cc
+++ b/cc/hybrid/hybrid_decrypt_wrapper_test.cc
@@ -14,7 +14,7 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-#include "tink/hybrid/hybrid_decrypt_set_wrapper.h"
+#include "tink/hybrid/hybrid_decrypt_wrapper.h"
 #include "tink/hybrid_decrypt.h"
 #include "tink/primitive_set.h"
 #include "tink/util/status.h"
@@ -22,6 +22,7 @@
 #include "gtest/gtest.h"
 
 using crypto::tink::test::DummyHybridDecrypt;
+using crypto::tink::test::DummyHybridEncrypt;
 using google::crypto::tink::OutputPrefixType;
 using google::crypto::tink::Keyset;
 
@@ -38,10 +39,10 @@
   }
 };
 
-TEST_F(HybridDecryptSetWrapperTest, testBasic) {
+TEST_F(HybridDecryptSetWrapperTest, Basic) {
   { // hybrid_decrypt_set is nullptr.
     auto hybrid_decrypt_result =
-        HybridDecryptSetWrapper::NewHybridDecrypt(nullptr);
+        HybridDecryptWrapper().Wrap(nullptr);
     EXPECT_FALSE(hybrid_decrypt_result.ok());
     EXPECT_EQ(util::error::INTERNAL,
         hybrid_decrypt_result.status().error_code());
@@ -52,7 +53,7 @@
   { // hybrid_decrypt_set has no primary primitive.
     std::unique_ptr<PrimitiveSet<HybridDecrypt>>
         hybrid_decrypt_set(new PrimitiveSet<HybridDecrypt>());
-    auto hybrid_decrypt_result = HybridDecryptSetWrapper::NewHybridDecrypt(
+    auto hybrid_decrypt_result = HybridDecryptWrapper().Wrap(
         std::move(hybrid_decrypt_set));
     EXPECT_FALSE(hybrid_decrypt_result.ok());
     EXPECT_EQ(util::error::INVALID_ARGUMENT,
@@ -103,7 +104,7 @@
     hybrid_decrypt_set->set_primary(entry_result.ValueOrDie());
 
     // Wrap hybrid_decrypt_set and test the resulting HybridDecrypt.
-    auto hybrid_decrypt_result = HybridDecryptSetWrapper::NewHybridDecrypt(
+    auto hybrid_decrypt_result = HybridDecryptWrapper().Wrap(
         std::move(hybrid_decrypt_set));
     EXPECT_TRUE(hybrid_decrypt_result.ok()) << hybrid_decrypt_result.status();
     hybrid_decrypt = std::move(hybrid_decrypt_result.ValueOrDie());
@@ -111,7 +112,9 @@
     std::string context_info = "some_context";
 
     {  // RAW key
-      std::string ciphertext = plaintext + hybrid_name_0;
+      std::string ciphertext = DummyHybridEncrypt(hybrid_name_0)
+                              .Encrypt(plaintext, context_info)
+                              .ValueOrDie();
       auto decrypt_result = hybrid_decrypt->Decrypt(ciphertext, context_info);
       EXPECT_TRUE(decrypt_result.ok()) << decrypt_result.status();
       EXPECT_EQ(plaintext, decrypt_result.ValueOrDie());
@@ -128,7 +131,9 @@
     }
 
     {  // Correct ciphertext prefix.
-      std::string ciphertext = prefix_id_1 + plaintext + hybrid_name_1;
+      std::string ciphertext =  prefix_id_1 + DummyHybridEncrypt(hybrid_name_1)
+                                       .Encrypt(plaintext, context_info)
+                                       .ValueOrDie();
       auto decrypt_result = hybrid_decrypt->Decrypt(ciphertext, context_info);
       EXPECT_TRUE(decrypt_result.ok()) << decrypt_result.status();
       EXPECT_EQ(plaintext, decrypt_result.ValueOrDie());
@@ -149,9 +154,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/hybrid/hybrid_encrypt_catalogue.cc b/cc/hybrid/hybrid_encrypt_catalogue.cc
index 05b5cf6..4375625 100644
--- a/cc/hybrid/hybrid_encrypt_catalogue.cc
+++ b/cc/hybrid/hybrid_encrypt_catalogue.cc
@@ -30,9 +30,9 @@
 
 crypto::tink::util::StatusOr<std::unique_ptr<KeyManager<HybridEncrypt>>>
 CreateKeyManager(const std::string& type_url) {
-  if (type_url == EciesAeadHkdfPublicKeyManager::kKeyType) {
+  if (type_url == EciesAeadHkdfPublicKeyManager::static_key_type()) {
     std::unique_ptr<KeyManager<HybridEncrypt>> manager(
-        new EciesAeadHkdfPublicKeyManager());
+        absl::make_unique<EciesAeadHkdfPublicKeyManager>());
     return std::move(manager);
   }
   return ToStatusF(crypto::tink::util::error::NOT_FOUND,
diff --git a/cc/hybrid/hybrid_encrypt_catalogue_test.cc b/cc/hybrid/hybrid_encrypt_catalogue_test.cc
index b21a57a..0fde8c4 100644
--- a/cc/hybrid/hybrid_encrypt_catalogue_test.cc
+++ b/cc/hybrid/hybrid_encrypt_catalogue_test.cc
@@ -61,9 +61,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/hybrid/hybrid_encrypt_factory.cc b/cc/hybrid/hybrid_encrypt_factory.cc
index 005392d..9e2d92f 100644
--- a/cc/hybrid/hybrid_encrypt_factory.cc
+++ b/cc/hybrid/hybrid_encrypt_factory.cc
@@ -20,7 +20,7 @@
 #include "tink/key_manager.h"
 #include "tink/keyset_handle.h"
 #include "tink/registry.h"
-#include "tink/hybrid/hybrid_encrypt_set_wrapper.h"
+#include "tink/hybrid/hybrid_encrypt_wrapper.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 
@@ -31,20 +31,25 @@
 // static
 util::StatusOr<std::unique_ptr<HybridEncrypt>>
 HybridEncryptFactory::GetPrimitive(const KeysetHandle& keyset_handle) {
-  return GetPrimitive(keyset_handle, nullptr);
+  util::Status status = Registry::RegisterPrimitiveWrapper(
+      absl::make_unique<HybridEncryptWrapper>());
+  if (!status.ok()) {
+    return status;
+  }
+  return keyset_handle.GetPrimitive<HybridEncrypt>();
 }
 
 // static
 util::StatusOr<std::unique_ptr<HybridEncrypt>>
-HybridEncryptFactory::GetPrimitive(const KeysetHandle& keyset_handle,
+HybridEncryptFactory::GetPrimitive(
+    const KeysetHandle& keyset_handle,
     const KeyManager<HybridEncrypt>* custom_key_manager) {
-  auto primitives_result = Registry::GetPrimitives<HybridEncrypt>(
-      keyset_handle, custom_key_manager);
-  if (primitives_result.ok()) {
-    return HybridEncryptSetWrapper::NewHybridEncrypt(
-        std::move(primitives_result.ValueOrDie()));
+  util::Status status = Registry::RegisterPrimitiveWrapper(
+      absl::make_unique<HybridEncryptWrapper>());
+  if (!status.ok()) {
+    return status;
   }
-  return primitives_result.status();
+  return keyset_handle.GetPrimitive<HybridEncrypt>(custom_key_manager);
 }
 
 }  // namespace tink
diff --git a/cc/hybrid/hybrid_encrypt_factory.h b/cc/hybrid/hybrid_encrypt_factory.h
index 5783985..05f776c 100644
--- a/cc/hybrid/hybrid_encrypt_factory.h
+++ b/cc/hybrid/hybrid_encrypt_factory.h
@@ -17,6 +17,7 @@
 #ifndef TINK_HYBRID_HYBRID_ENCRYPT_FACTORY_H_
 #define TINK_HYBRID_HYBRID_ENCRYPT_FACTORY_H_
 
+#include "absl/base/macros.h"
 #include "tink/hybrid_encrypt.h"
 #include "tink/key_manager.h"
 #include "tink/keyset_handle.h"
@@ -26,36 +27,29 @@
 namespace tink {
 
 ///////////////////////////////////////////////////////////////////////////////
-// HybridEncryptFactory allows for obtaining an HybridEncrypt primitive
-// from a KeysetHandle.
+// This class is deprecated. Call keyset_handle->GetPrimitive<HybridEncrypt>()
+// instead.
 //
-// HybridEncryptFactory gets primitives from the Registry, which can
-// be initialized via a convenience method from HybridConfig-class.
-// Here is an example how one can obtain and use a HybridEncrypt primitive:
-//
-//   auto status = HybridConfig::Register();
-//   if (!status.ok()) { /* fail with error */ }
-//   KeysetHandle keyset_handle = ...;
-//   std::unique_ptr<HybridEncrypt> hybrid_encrypt = std::move(
-//           HybridEncryptFactory::GetPrimitive(keyset_handle).ValueOrDie());
-//   std::string plaintext = ...;
-//   std::string context_info = ...;
-//   std::string ciphertext =
-//       hybrid_encrypt.Encrypt(plaintext, context_info).ValueOrDie();
-//
-class HybridEncryptFactory {
+// Note that in order to for this change to be safe, the AeadSetWrapper has to
+// be registered in your binary before this call. This happens automatically if
+// you call one of
+// * HybridConfig::Register()
+// * TinkConfig::Register()
+class ABSL_DEPRECATED(
+    "Call getPrimitive<HybridEncrypt>() on the keyset_handle after registering "
+    "the HybridEncryptWrapper instead.") HybridEncryptFactory {
  public:
   // Returns a HybridEncrypt-primitive that uses key material from the keyset
   // specified via 'keyset_handle'.
   static crypto::tink::util::StatusOr<std::unique_ptr<HybridEncrypt>>
-      GetPrimitive(const KeysetHandle& keyset_handle);
+  GetPrimitive(const KeysetHandle& keyset_handle);
 
   // Returns a HybridEncrypt-primitive that uses key material from the keyset
   // specified via 'keyset_handle' and is instantiated by the given
   // 'custom_key_manager' (instead of the key manager from the Registry).
   static crypto::tink::util::StatusOr<std::unique_ptr<HybridEncrypt>>
-      GetPrimitive(const KeysetHandle& keyset_handle,
-                   const KeyManager<HybridEncrypt>* custom_key_manager);
+  GetPrimitive(const KeysetHandle& keyset_handle,
+               const KeyManager<HybridEncrypt>* custom_key_manager);
 
  private:
   HybridEncryptFactory() {}
diff --git a/cc/hybrid/hybrid_encrypt_factory_test.cc b/cc/hybrid/hybrid_encrypt_factory_test.cc
index 869c6dd..b12293b 100644
--- a/cc/hybrid/hybrid_encrypt_factory_test.cc
+++ b/cc/hybrid/hybrid_encrypt_factory_test.cc
@@ -108,9 +108,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/hybrid/hybrid_encrypt_set_wrapper.h b/cc/hybrid/hybrid_encrypt_set_wrapper.h
deleted file mode 100644
index 590cfca..0000000
--- a/cc/hybrid/hybrid_encrypt_set_wrapper.h
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-///////////////////////////////////////////////////////////////////////////////
-
-#ifndef TINK_HYBRID_HYBRID_ENCRYPT_SET_WRAPPER_H_
-#define TINK_HYBRID_HYBRID_ENCRYPT_SET_WRAPPER_H_
-
-#include "absl/strings/string_view.h"
-#include "tink/hybrid_encrypt.h"
-#include "tink/primitive_set.h"
-#include "tink/util/statusor.h"
-#include "proto/tink.pb.h"
-
-namespace crypto {
-namespace tink {
-
-// Wraps a set of HybridEncrypt-instances that correspond to a keyset,
-// and combines them into a single HybridEncrypt-primitive, that uses
-// the primary instance to do the actual encryption.
-class HybridEncryptSetWrapper : public HybridEncrypt {
- public:
-  // Returns an HybridEncrypt-primitive that uses the primary
-  // HybridEncrypt-instance provided in 'hybrid_encrypt_set',
-  // which must be non-NULL (and must contain a primary instance).
-  static crypto::tink::util::StatusOr<std::unique_ptr<HybridEncrypt>>
-      NewHybridEncrypt(
-          std::unique_ptr<PrimitiveSet<HybridEncrypt>> hybrid_encrypt_set);
-
-  crypto::tink::util::StatusOr<std::string> Encrypt(
-      absl::string_view plaintext,
-      absl::string_view context_info) const override;
-
-  virtual ~HybridEncryptSetWrapper() {}
-
- private:
-  std::unique_ptr<PrimitiveSet<HybridEncrypt>> hybrid_encrypt_set_;
-
-  HybridEncryptSetWrapper(
-      std::unique_ptr<PrimitiveSet<HybridEncrypt>> hybrid_encrypt_set)
-      : hybrid_encrypt_set_(std::move(hybrid_encrypt_set)) {}
-};
-
-}  // namespace tink
-}  // namespace crypto
-
-#endif  // TINK_HYBRID_HYBRID_ENCRYPT_SET_WRAPPER_H_
diff --git a/cc/hybrid/hybrid_encrypt_set_wrapper.cc b/cc/hybrid/hybrid_encrypt_wrapper.cc
similarity index 69%
rename from cc/hybrid/hybrid_encrypt_set_wrapper.cc
rename to cc/hybrid/hybrid_encrypt_wrapper.cc
index a8dcae0..69e874c 100644
--- a/cc/hybrid/hybrid_encrypt_set_wrapper.cc
+++ b/cc/hybrid/hybrid_encrypt_wrapper.cc
@@ -14,7 +14,7 @@
 //
 ///////////////////////////////////////////////////////////////////////////////
 
-#include "tink/hybrid/hybrid_encrypt_set_wrapper.h"
+#include "tink/hybrid/hybrid_encrypt_wrapper.h"
 
 #include "tink/crypto_format.h"
 #include "tink/hybrid_encrypt.h"
@@ -40,18 +40,25 @@
   return util::Status::OK;
 }
 
-}  // anonymous namespace
 
-// static
-util::StatusOr<std::unique_ptr<HybridEncrypt>>
-HybridEncryptSetWrapper::NewHybridEncrypt(
-    std::unique_ptr<PrimitiveSet<HybridEncrypt>> hybrid_encrypt_set) {
-  util::Status status = Validate(hybrid_encrypt_set.get());
-  if (!status.ok()) return status;
-  std::unique_ptr<HybridEncrypt> hybrid_encrypt(
-      new HybridEncryptSetWrapper(std::move(hybrid_encrypt_set)));
-  return std::move(hybrid_encrypt);
-}
+// Returns an HybridEncrypt-primitive that uses the primary
+// HybridEncrypt-instance provided in 'hybrid_encrypt_set',
+// which must be non-NULL (and must contain a primary instance).
+class HybridEncryptSetWrapper : public HybridEncrypt {
+ public:
+  explicit HybridEncryptSetWrapper(
+      std::unique_ptr<PrimitiveSet<HybridEncrypt>> hybrid_encrypt_set)
+      : hybrid_encrypt_set_(std::move(hybrid_encrypt_set)) {}
+
+  crypto::tink::util::StatusOr<std::string> Encrypt(
+      absl::string_view plaintext,
+      absl::string_view context_info) const override;
+
+  ~HybridEncryptSetWrapper() override {}
+
+ private:
+  std::unique_ptr<PrimitiveSet<HybridEncrypt>> hybrid_encrypt_set_;
+};
 
 util::StatusOr<std::string> HybridEncryptSetWrapper::Encrypt(
     absl::string_view plaintext,
@@ -69,5 +76,16 @@
   return key_id + encrypt_result.ValueOrDie();
 }
 
+}  // anonymous namespace
+
+util::StatusOr<std::unique_ptr<HybridEncrypt>> HybridEncryptWrapper::Wrap(
+    std::unique_ptr<PrimitiveSet<HybridEncrypt>> primitive_set) const {
+  util::Status status = Validate(primitive_set.get());
+  if (!status.ok()) return status;
+  std::unique_ptr<HybridEncrypt> hybrid_encrypt(
+      new HybridEncryptSetWrapper(std::move(primitive_set)));
+  return std::move(hybrid_encrypt);
+}
+
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/hybrid/hybrid_encrypt_wrapper.h b/cc/hybrid/hybrid_encrypt_wrapper.h
new file mode 100644
index 0000000..3a1210c
--- /dev/null
+++ b/cc/hybrid/hybrid_encrypt_wrapper.h
@@ -0,0 +1,43 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_HYBRID_HYBRID_ENCRYPT_WRAPPER_H_
+#define TINK_HYBRID_HYBRID_ENCRYPT_WRAPPER_H_
+
+#include "absl/strings/string_view.h"
+#include "tink/hybrid_encrypt.h"
+#include "tink/primitive_set.h"
+#include "tink/primitive_wrapper.h"
+#include "tink/util/statusor.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+
+// Wraps a set of HybridEncrypt-instances that correspond to a keyset,
+// and combines them into a single HybridEncrypt-primitive, that uses
+// the primary instance to do the actual encryption.
+class HybridEncryptWrapper : public PrimitiveWrapper<HybridEncrypt> {
+ public:
+  util::StatusOr<std::unique_ptr<HybridEncrypt>> Wrap(
+      std::unique_ptr<PrimitiveSet<HybridEncrypt>> primitive_set)
+      const override;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_HYBRID_HYBRID_ENCRYPT_WRAPPER_H_
diff --git a/cc/hybrid/hybrid_encrypt_set_wrapper_test.cc b/cc/hybrid/hybrid_encrypt_wrapper_test.cc
similarity index 91%
rename from cc/hybrid/hybrid_encrypt_set_wrapper_test.cc
rename to cc/hybrid/hybrid_encrypt_wrapper_test.cc
index 96ea6cc..444484f 100644
--- a/cc/hybrid/hybrid_encrypt_set_wrapper_test.cc
+++ b/cc/hybrid/hybrid_encrypt_wrapper_test.cc
@@ -14,7 +14,7 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-#include "tink/hybrid/hybrid_encrypt_set_wrapper.h"
+#include "tink/hybrid/hybrid_encrypt_wrapper.h"
 #include "tink/hybrid_encrypt.h"
 #include "tink/primitive_set.h"
 #include "tink/util/status.h"
@@ -25,7 +25,6 @@
 using google::crypto::tink::OutputPrefixType;
 using google::crypto::tink::Keyset;
 
-
 namespace crypto {
 namespace tink {
 namespace {
@@ -41,7 +40,7 @@
 TEST_F(HybridEncryptSetWrapperTest, testBasic) {
   { // hybrid_encrypt_set is nullptr.
     auto hybrid_encrypt_result =
-        HybridEncryptSetWrapper::NewHybridEncrypt(nullptr);
+        HybridEncryptWrapper().Wrap(nullptr);
     EXPECT_FALSE(hybrid_encrypt_result.ok());
     EXPECT_EQ(util::error::INTERNAL,
         hybrid_encrypt_result.status().error_code());
@@ -52,7 +51,7 @@
   { // hybrid_encrypt_set has no primary primitive.
     std::unique_ptr<PrimitiveSet<HybridEncrypt>>
         hybrid_encrypt_set(new PrimitiveSet<HybridEncrypt>());
-    auto hybrid_encrypt_result = HybridEncryptSetWrapper::NewHybridEncrypt(
+    auto hybrid_encrypt_result = HybridEncryptWrapper().Wrap(
         std::move(hybrid_encrypt_set));
     EXPECT_FALSE(hybrid_encrypt_result.ok());
     EXPECT_EQ(util::error::INVALID_ARGUMENT,
@@ -102,7 +101,7 @@
     hybrid_encrypt_set->set_primary(entry_result.ValueOrDie());
 
     // Wrap hybrid_encrypt_set and test the resulting HybridEncrypt.
-    auto hybrid_encrypt_result = HybridEncryptSetWrapper::NewHybridEncrypt(
+    auto hybrid_encrypt_result = HybridEncryptWrapper().Wrap(
         std::move(hybrid_encrypt_set));
     EXPECT_TRUE(hybrid_encrypt_result.ok()) << hybrid_encrypt_result.status();
     hybrid_encrypt = std::move(hybrid_encrypt_result.ValueOrDie());
@@ -120,9 +119,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/hybrid/hybrid_key_templates_test.cc b/cc/hybrid/hybrid_key_templates_test.cc
index e35d5fb..e23aa4a 100644
--- a/cc/hybrid/hybrid_key_templates_test.cc
+++ b/cc/hybrid/hybrid_key_templates_test.cc
@@ -121,8 +121,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/input_stream.h b/cc/input_stream.h
new file mode 100644
index 0000000..f1ac7f1
--- /dev/null
+++ b/cc/input_stream.h
@@ -0,0 +1,98 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_INPUT_STREAM_H_
+#define TINK_INPUT_STREAM_H_
+
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+// Abstract interface similar to an input stream, and to
+// Protocol Buffers' google::protobuf::io::ZeroCopyInputStream.
+class InputStream {
+ public:
+  InputStream() {}
+  virtual ~InputStream() {}
+
+  // Obtains a chunk of data from the stream.
+  //
+  // Preconditions:
+  // * "data" is not NULL.
+  //
+  // Postconditions:
+  // * If the returned status is not OK, there is no more data to return
+  //   (status equal to OUT_OF_RANGE) or an error occurred
+  //   (status not in {OK, OUT_OF_RANGE}.  All errors are permanent.
+  // * Otherwise, the returned value is the number of bytes read and "data"
+  //   points to a buffer containing these bytes.
+  // * Ownership of this buffer remains with the stream, and the buffer
+  //   remains valid only until some other non-const method of the stream
+  //   is called or the stream is destroyed.
+  // * It is legal for the returned buffer to have zero size, as long as
+  //   repeatedly calling Next() eventually yields a buffer with non-zero
+  //   size or a non-OK status.
+  virtual crypto::tink::util::StatusOr<int> Next(const void** data) = 0;
+
+  // Backs up a number of bytes, so that the next call to Next() returns
+  // data again that was already returned by the last call to Next().
+  // This is useful when writing procedures that are only supposed to read
+  // up to a certain point in the input, then return.  If Next() returns a
+  // buffer that goes beyond what you wanted to read, you can use BackUp()
+  // to return to the point where you intended to finish.
+  //
+  // Preconditions:
+  // * The last call to Next() must have returned status OK.
+  //   If there was no Next()-call yet, or the last one failed,
+  //   BackUp()-call is ignored.
+  // * count must be less than or equal to the size of the last buffer
+  //   returned by Next().  Non-positive count is ignored (no action on this
+  //   stream), and count larger than the size of the last buffer is treated
+  //   as equal to the size of the last buffer.
+  //
+  // Postconditions:
+  // * The last "count" bytes of the last buffer returned by Next() will be
+  //   pushed back into the stream.  Subsequent calls to Next() will return
+  //   the same data again before producing new data.
+  // * Repeated calls to BackUp() accumulate:
+  //      BackUp(a);
+  //      Backup(b);
+  //   is equivalent to
+  //      Backup(c);
+  //   with c = max(0, a) + max(0, b).
+  // * The actual result of BackUp()-call can be verified via Position().
+  virtual void BackUp(int count) = 0;
+
+  // Returns the current byte position in the stream.
+  //
+  // Preconditions:
+  // * The last call to Next() ended with status in {OK, OUT_OF_RANGE}.
+  //
+  // Postconditions:
+  // * Position equal to i means that the next call to Next() will
+  //   return a data buffer starting at (i+1)st byte of the stream (if any).
+  // * If the last call to Next() reached end of stream (status OUT_OF_RANGE),
+  //   then returned value is the total number of bytes in the stream.
+  // * If the last call to Next() ended with a failure, -1 is returned;
+  virtual int64_t Position() const = 0;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_INPUT_STREAM_H_
diff --git a/cc/integration/awskms/BUILD.bazel b/cc/integration/awskms/BUILD.bazel
new file mode 100644
index 0000000..093b0ec
--- /dev/null
+++ b/cc/integration/awskms/BUILD.bazel
@@ -0,0 +1,53 @@
+package(default_visibility = ["//tools/build_defs:internal_pkg"])
+
+licenses(["notice"])  # Apache 2.0
+
+
+cc_library(
+    name = "aws_crypto",
+    srcs = [
+        "aws_crypto.cc",
+    ],
+    hdrs = [
+        "aws_crypto.h",
+    ],
+    deps = [
+        "@aws_cpp_sdk//:aws_sdk_core",
+        "@boringssl//:crypto",
+        "@com_google_absl//absl/base",
+    ],
+    alwayslink = 1,
+    strip_include_prefix = "/cc",
+    include_prefix = "tink",
+)
+
+cc_library(
+    name = "aws_kms_aead",
+    srcs = ["aws_kms_aead.cc"],
+    hdrs = ["aws_kms_aead.h"],
+    deps = [
+        "//cc:aead",
+        "//cc/util:errors",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "@aws_cpp_sdk//:aws_sdk_core",
+    ],
+    alwayslink = 1,
+    strip_include_prefix = "/cc",
+    include_prefix = "tink",
+)
+
+cc_test(
+    name = "aws_kms_aead_test",
+    size = "medium",
+    srcs = ["aws_kms_aead_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    deps = [
+        ":aws_kms_aead",
+        "//cc/util:errors",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "@aws_cpp_sdk//:aws_sdk_core",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
diff --git a/cc/integration/awskms/aws_crypto.cc b/cc/integration/awskms/aws_crypto.cc
new file mode 100644
index 0000000..e0714e8
--- /dev/null
+++ b/cc/integration/awskms/aws_crypto.cc
@@ -0,0 +1,126 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/integration/awskms/aws_crypto.h"
+
+#include "aws/core/Aws.h"
+#include "aws/core/utils/Outcome.h"
+#include "aws/core/utils/crypto/Factories.h"
+#include "aws/core/utils/crypto/HashResult.h"
+#include "aws/core/utils/crypto/HMAC.h"
+#include "aws/core/utils/crypto/Hash.h"
+
+#include "absl/base/attributes.h"
+#include "openssl/hmac.h"
+#include "openssl/sha.h"
+
+namespace crypto {
+namespace tink {
+namespace integration {
+namespace awskms {
+
+ABSL_CONST_INIT const char* kAwsCryptoAllocationTag = "AwsCryptoAllocation";
+
+class AwsSha256HmacOpenSslImpl : public Aws::Utils::Crypto::HMAC {
+ public:
+  AwsSha256HmacOpenSslImpl() {}
+
+  virtual ~AwsSha256HmacOpenSslImpl() = default;
+
+  Aws::Utils::Crypto::HashResult Calculate(
+      const Aws::Utils::ByteBuffer& toSign,
+      const Aws::Utils::ByteBuffer& secret) override {
+    unsigned int length = SHA256_DIGEST_LENGTH;
+    Aws::Utils::ByteBuffer digest(length);
+    memset(digest.GetUnderlyingData(), 0, length);
+
+    HMAC_CTX ctx;
+    HMAC_CTX_init(&ctx);
+
+    HMAC_Init_ex(&ctx, secret.GetUnderlyingData(),
+                 static_cast<int>(secret.GetLength()), EVP_sha256(), NULL);
+    HMAC_Update(&ctx, toSign.GetUnderlyingData(), toSign.GetLength());
+    HMAC_Final(&ctx, digest.GetUnderlyingData(), &length);
+    HMAC_CTX_cleanup(&ctx);
+
+    return Aws::Utils::Crypto::HashResult(std::move(digest));
+  }
+};
+
+class AwsSha256OpenSslImpl : public Aws::Utils::Crypto::Hash {
+ public:
+  AwsSha256OpenSslImpl() {}
+
+  virtual ~AwsSha256OpenSslImpl() = default;
+
+  Aws::Utils::Crypto::HashResult Calculate(const Aws::String& str) override {
+    SHA256_CTX sha256;
+    SHA256_Init(&sha256);
+    SHA256_Update(&sha256, str.data(), str.size());
+
+    Aws::Utils::ByteBuffer hash(SHA256_DIGEST_LENGTH);
+    SHA256_Final(hash.GetUnderlyingData(), &sha256);
+
+    return Aws::Utils::Crypto::HashResult(std::move(hash));
+  }
+
+  Aws::Utils::Crypto::HashResult Calculate(Aws::IStream& stream) override {
+    SHA256_CTX sha256;
+    SHA256_Init(&sha256);
+
+    auto currentPos = stream.tellg();
+    if (currentPos == std::streampos(std::streamoff(-1))) {
+      currentPos = 0;
+      stream.clear();
+    }
+
+    stream.seekg(0, stream.beg);
+
+    char streamBuffer
+        [Aws::Utils::Crypto::Hash::INTERNAL_HASH_STREAM_BUFFER_SIZE];
+    while (stream.good()) {
+      stream.read(streamBuffer,
+                  Aws::Utils::Crypto::Hash::INTERNAL_HASH_STREAM_BUFFER_SIZE);
+      auto bytesRead = stream.gcount();
+
+      if (bytesRead > 0) {
+        SHA256_Update(&sha256, streamBuffer, static_cast<size_t>(bytesRead));
+      }
+    }
+
+    stream.clear();
+    stream.seekg(currentPos, stream.beg);
+
+    Aws::Utils::ByteBuffer hash(SHA256_DIGEST_LENGTH);
+    SHA256_Final(hash.GetUnderlyingData(), &sha256);
+
+    return Aws::Utils::Crypto::HashResult(std::move(hash));
+  }
+};
+
+std::shared_ptr<Aws::Utils::Crypto::Hash>
+AwsSha256Factory::CreateImplementation() const {
+  return Aws::MakeShared<AwsSha256OpenSslImpl>(kAwsCryptoAllocationTag);
+}
+
+std::shared_ptr<Aws::Utils::Crypto::HMAC>
+AwsSha256HmacFactory::CreateImplementation() const {
+  return Aws::MakeShared<AwsSha256HmacOpenSslImpl>(kAwsCryptoAllocationTag);
+}
+
+
+}  // namespace awskms
+}  // namespace integration
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/integration/awskms/aws_crypto.h b/cc/integration/awskms/aws_crypto.h
new file mode 100644
index 0000000..be0de82
--- /dev/null
+++ b/cc/integration/awskms/aws_crypto.h
@@ -0,0 +1,64 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_INTEGRATION_AWSKMS_AWS_CRYPTO_H_
+#define TINK_INTEGRATION_AWSKMS_AWS_CRYPTO_H_
+
+#include "aws/core/Aws.h"
+#include "aws/core/utils/crypto/Factories.h"
+#include "aws/core/utils/crypto/HMAC.h"
+#include "aws/core/utils/crypto/Hash.h"
+
+namespace crypto {
+namespace tink {
+namespace integration {
+namespace awskms {
+
+// Helpers for building AWS C++ Client without OpenSSL, to avoid
+// collisions with BoringSSL used by Tink.
+// These were "borrowed" from TensorFlow (https://github.com/tensorflow/).
+//
+// With these helpers the initialization of AWS API looks as follows:
+//
+//   Aws::SDKOptions options;
+//   options.cryptoOptions.sha256Factory_create_fn = []() {
+//       return Aws::MakeShared<AwsSha256Factory>(kAwsCryptoAllocationTag);
+//   };
+//   options.cryptoOptions.sha256HMACFactory_create_fn = []() {
+//       return Aws::MakeShared<AwsSha256HmacFactory>(kAwsCryptoAllocationTag);
+//   };
+//   Aws::InitAPI(options);
+//
+///////////////////////////////////////////////////////////////////////////////
+
+extern const char* kAwsCryptoAllocationTag;
+
+class AwsSha256Factory : public Aws::Utils::Crypto::HashFactory {
+ public:
+  std::shared_ptr<Aws::Utils::Crypto::Hash> CreateImplementation()
+      const override;
+};
+
+class AwsSha256HmacFactory : public Aws::Utils::Crypto::HMACFactory {
+ public:
+  std::shared_ptr<Aws::Utils::Crypto::HMAC> CreateImplementation()
+      const override;
+};
+
+}  // namespace awskms
+}  // namespace integration
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_INTEGRATION_AWSKMS_AWS_CRYPTO_H_
diff --git a/cc/integration/awskms/aws_kms_aead.cc b/cc/integration/awskms/aws_kms_aead.cc
new file mode 100644
index 0000000..8ec0ccf
--- /dev/null
+++ b/cc/integration/awskms/aws_kms_aead.cc
@@ -0,0 +1,146 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/integration/awskms/aws_kms_aead.h"
+
+#include "absl/strings/match.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "aws/core/auth/AWSCredentialsProvider.h"
+#include "aws/core/client/AWSClient.h"
+#include "aws/core/utils/Outcome.h"
+#include "aws/core/utils/memory/AWSMemory.h"
+#include "aws/kms/KMSClient.h"
+#include "aws/kms/KMSErrors.h"
+#include "aws/kms/model/DecryptRequest.h"
+#include "aws/kms/model/DecryptResult.h"
+#include "aws/kms/model/EncryptRequest.h"
+#include "aws/kms/model/EncryptResult.h"
+#include "tink/aead.h"
+#include "tink/util/errors.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace integration {
+namespace awskms {
+
+using crypto::tink::util::Status;
+using crypto::tink::util::StatusOr;
+
+namespace {
+
+// TODO: pull out hex-helpers from test_util and remove the copy below.
+std::string HexEncode(absl::string_view bytes) {
+  std::string hexchars = "0123456789abcdef";
+  std::string res(bytes.size() * 2, static_cast<char>(255));
+  for (size_t i = 0; i < bytes.size(); ++i) {
+    uint8_t c = static_cast<uint8_t>(bytes[i]);
+    res[2 * i] = hexchars[c / 16];
+    res[2 * i + 1] = hexchars[c % 16];
+  }
+  return res;
+}
+
+std::string AwsErrorToString(Aws::Client::AWSError<Aws::KMS::KMSErrors> err) {
+  return absl::StrCat("AWS error code: ", err.GetErrorType(), ", ",
+                      err.GetExceptionName(), ": ", err.GetMessage());
+}
+
+}  // namespace
+
+AwsKmsAead::AwsKmsAead(absl::string_view key_arn,
+                       std::shared_ptr<Aws::KMS::KMSClient> aws_client) :
+    key_arn_(key_arn), aws_client_(aws_client) {
+}
+
+// static
+StatusOr<std::unique_ptr<AwsKmsAead>>
+AwsKmsAead::New(absl::string_view key_arn,
+                std::shared_ptr<Aws::KMS::KMSClient> aws_client) {
+  if (key_arn.empty()) {
+    return Status(util::error::INVALID_ARGUMENT,
+                  "Key ARN cannot be empty.");
+  }
+  if (aws_client == nullptr) {
+    return Status(util::error::INVALID_ARGUMENT,
+                  "AWS KMS client cannot be null.");
+  }
+  std::unique_ptr<AwsKmsAead> kms_aead(
+      new AwsKmsAead(key_arn, std::move(aws_client)));
+  return std::move(kms_aead);
+}
+
+StatusOr<std::string> AwsKmsAead::Encrypt(
+    absl::string_view plaintext,
+    absl::string_view associated_data) const {
+  Aws::KMS::Model::EncryptRequest req;
+  req.SetKeyId(key_arn_.c_str());
+  Aws::Utils::ByteBuffer plaintext_buffer(
+      reinterpret_cast<const unsigned char*>(plaintext.data()),
+      plaintext.length());
+  req.SetPlaintext(plaintext_buffer);
+  if (!associated_data.empty()) {
+    req.AddEncryptionContext("associatedData",
+                             HexEncode(associated_data).c_str());
+  }
+  auto outcome = aws_client_->Encrypt(req);
+  if (outcome.IsSuccess()) {
+    auto& blob = outcome.GetResult().GetCiphertextBlob();
+    std::string ciphertext(
+        reinterpret_cast<const char*>(blob.GetUnderlyingData()),
+        blob.GetLength());
+    return ciphertext;
+  }
+  auto& err = outcome.GetError();
+  return ToStatusF(util::error::INVALID_ARGUMENT,
+                   "AWS KMS encryption failed with error: %s",
+                   AwsErrorToString(err).c_str());
+}
+
+StatusOr<std::string> AwsKmsAead::Decrypt(
+    absl::string_view ciphertext,
+    absl::string_view associated_data) const {
+  Aws::KMS::Model::DecryptRequest req;
+  Aws::Utils::ByteBuffer ciphertext_buffer(
+      reinterpret_cast<const unsigned char*>(ciphertext.data()),
+      ciphertext.length());
+  req.SetCiphertextBlob(ciphertext_buffer);
+  if (!associated_data.empty()) {
+    req.AddEncryptionContext("associatedData",
+                             HexEncode(associated_data).c_str());
+  }
+  auto outcome = aws_client_->Decrypt(req);
+  if (outcome.IsSuccess()) {
+    if (outcome.GetResult().GetKeyId() != Aws::String(key_arn_.c_str())) {
+      return ToStatusF(util::error::INVALID_ARGUMENT,
+                       "AWS KMS decryption failed: wrong key ARN.");
+    }
+    auto& buffer = outcome.GetResult().GetPlaintext();
+    std::string plaintext(
+        reinterpret_cast<const char*>(buffer.GetUnderlyingData()),
+        buffer.GetLength());
+    return plaintext;
+  }
+  auto& err = outcome.GetError();
+  return ToStatusF(util::error::INVALID_ARGUMENT,
+                   "AWS KMS decryption failed with error: %s",
+                   AwsErrorToString(err).c_str());
+}
+
+}  // namespace awskms
+}  // namespace integration
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/integration/awskms/aws_kms_aead.h b/cc/integration/awskms/aws_kms_aead.h
new file mode 100644
index 0000000..ad008e9
--- /dev/null
+++ b/cc/integration/awskms/aws_kms_aead.h
@@ -0,0 +1,62 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_INTEGRATION_AWSKMS_AWS_KMS_AEAD_H_
+#define TINK_INTEGRATION_AWSKMS_AWS_KMS_AEAD_H_
+
+#include "absl/strings/string_view.h"
+#include "aws/kms/KMSClient.h"
+#include "tink/aead.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace integration {
+namespace awskms {
+
+// AwsKmsAead is an implementation of AEAD that forwards
+// encryption/decryption requests to a key managed by
+// <a href="https://aws.amazon.com/kms/">AWS KMS</a>.
+class AwsKmsAead : public Aead {
+ public:
+  // Creates a new AwsKmsAead that is bound to the key specified in 'key_arn',
+  // and that uses the given client when communicating with the KMS.
+  static crypto::tink::util::StatusOr<std::unique_ptr<AwsKmsAead>>
+  New(absl::string_view key_arn,
+      std::shared_ptr<Aws::KMS::KMSClient> aws_client);
+
+  crypto::tink::util::StatusOr<std::string> Encrypt(
+      absl::string_view plaintext,
+      absl::string_view associated_data) const override;
+
+  crypto::tink::util::StatusOr<std::string> Decrypt(
+      absl::string_view ciphertext,
+      absl::string_view associated_data) const override;
+
+  virtual ~AwsKmsAead() {}
+
+ private:
+  AwsKmsAead(absl::string_view key_arn,
+             std::shared_ptr<Aws::KMS::KMSClient> aws_client);
+  std::string key_arn_;  // The location of a crypto key in AWS KMS.
+  std::shared_ptr<Aws::KMS::KMSClient> aws_client_;
+};
+
+
+}  // namespace awskms
+}  // namespace integration
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_INTEGRATION_AWSKMS_AWS_KMS_AEAD_H_
diff --git a/cc/integration/awskms/aws_kms_aead_test.cc b/cc/integration/awskms/aws_kms_aead_test.cc
new file mode 100644
index 0000000..fef1930
--- /dev/null
+++ b/cc/integration/awskms/aws_kms_aead_test.cc
@@ -0,0 +1,42 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/integration/awskms/aws_kms_aead.h"
+
+#include <string>
+#include <vector>
+
+#include "absl/strings/str_cat.h"
+#include "aws/core/Aws.h"
+#include "aws/kms/KMSClient.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "gtest/gtest.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+namespace {
+
+using crypto::tink::integration::awskms::AwsKmsAead;
+
+class AwsKmsAeadTest : public ::testing::Test {
+  // TODO(przydatek): add a test with a mock KMSClient.
+};
+
+
+}  // namespace
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/json_keyset_reader.h b/cc/json_keyset_reader.h
index c68ef42..265392b 100644
--- a/cc/json_keyset_reader.h
+++ b/cc/json_keyset_reader.h
@@ -45,9 +45,9 @@
   ReadEncrypted() override;
 
  private:
-  JsonKeysetReader(std::unique_ptr<std::istream> keyset_stream)
+  explicit JsonKeysetReader(std::unique_ptr<std::istream> keyset_stream)
       : serialized_keyset_(""), keyset_stream_(std::move(keyset_stream)) {}
-  JsonKeysetReader(absl::string_view serialized_keyset)
+  explicit JsonKeysetReader(absl::string_view serialized_keyset)
       : serialized_keyset_(serialized_keyset), keyset_stream_(nullptr) {}
 
   std::string serialized_keyset_;
diff --git a/cc/json_keyset_writer.h b/cc/json_keyset_writer.h
index efd2217..00ac2a2 100644
--- a/cc/json_keyset_writer.h
+++ b/cc/json_keyset_writer.h
@@ -43,7 +43,7 @@
   Write(const google::crypto::tink::EncryptedKeyset& encrypted_keyset) override;
 
  private:
-  JsonKeysetWriter(std::unique_ptr<std::ostream> destination_stream)
+  explicit JsonKeysetWriter(std::unique_ptr<std::ostream> destination_stream)
       : destination_stream_(std::move(destination_stream)) {}
 
   std::unique_ptr<std::ostream> destination_stream_;
diff --git a/cc/key_manager.h b/cc/key_manager.h
index 0b49a45..99e62a2 100644
--- a/cc/key_manager.h
+++ b/cc/key_manager.h
@@ -35,6 +35,11 @@
 // is independent of the primitive of the corresponding KeyManager.
 class KeyFactory {
  public:
+  // Helper function which creates a factory which always fails with a specified
+  // status.
+  static std::unique_ptr<KeyFactory> AlwaysFailingFactory(
+      const crypto::tink::util::Status& status);
+
   // Generates a new random key, based on the specified 'key_format'.
   virtual
   crypto::tink::util::StatusOr<std::unique_ptr<portable_proto::MessageLite>>
@@ -54,7 +59,7 @@
   virtual ~KeyFactory() {}
 };
 
-class PrivateKeyFactory : public KeyFactory {
+class PrivateKeyFactory : public virtual KeyFactory {
  public:
   // Returns public key data extracted from the given serialized_private_key.
   virtual
diff --git a/cc/keyset_handle.h b/cc/keyset_handle.h
index 9b706f8..d9270f1 100644
--- a/cc/keyset_handle.h
+++ b/cc/keyset_handle.h
@@ -18,8 +18,11 @@
 #define TINK_KEYSET_HANDLE_H_
 
 #include "tink/aead.h"
+#include "tink/key_manager.h"
 #include "tink/keyset_reader.h"
 #include "tink/keyset_writer.h"
+#include "tink/primitive_set.h"
+#include "tink/registry.h"
 #include "proto/tink.pb.h"
 
 namespace crypto {
@@ -35,15 +38,20 @@
   static crypto::tink::util::StatusOr<std::unique_ptr<KeysetHandle>> Read(
       std::unique_ptr<KeysetReader> reader, const Aead& master_key_aead);
 
+  // Creates a KeysetHandle from a keyset which contains no secret key material.
+  // This can be used to load public keysets or envelope encryption keysets.
+  static crypto::tink::util::StatusOr<std::unique_ptr<KeysetHandle>>
+  ReadNoSecret(const std::string& serialized_keyset);
+
   // Returns a new KeysetHandle that contains a single fresh key generated
   // according to |key_template|.
   static crypto::tink::util::StatusOr<std::unique_ptr<KeysetHandle>>
   GenerateNew(const google::crypto::tink::KeyTemplate& key_template);
 
   // Encrypts the underlying keyset with the provided |master_key_aead|
-  // and writes the resulting EncrytpedKeyset to the given |writer|,
+  // and writes the resulting EncryptedKeyset to the given |writer|,
   // which must be non-null.
-  crypto::tink::util::Status  Write(KeysetWriter* writer,
+  crypto::tink::util::Status Write(KeysetWriter* writer,
       const Aead& master_key_aead);
 
   // Returns a new KeysetHandle that contains public keys corresponding
@@ -52,24 +60,117 @@
   crypto::tink::util::StatusOr<std::unique_ptr<KeysetHandle>>
   GetPublicKeysetHandle();
 
+  // Creates a wrapped primitive corresponding to this keyset or fails with
+  // a non-ok status. Uses the KeyManager and PrimitiveWrapper objects in the
+  // global registry to create the primitive. This function is the most common
+  // way of creating a primitive.
+  template <class P>
+  crypto::tink::util::StatusOr<std::unique_ptr<P>> GetPrimitive() const;
+
+  // Creates a wrapped primitive corresponding to this keyset. Uses the given
+  // KeyManager, as well as the KeyManager and PrimitiveWrapper objects in the
+  // global registry to create the primitive. The given KeyManager is used for
+  // keys supported by it. For those, the registry is ignored.
+  template <class P>
+  crypto::tink::util::StatusOr<std::unique_ptr<P>> GetPrimitive(
+      const KeyManager<P>* custom_manager) const;
 
  private:
   // The classes below need access to get_keyset();
   friend class CleartextKeysetHandle;
+  friend class NoSecretKeysetHandle;
   friend class KeysetManager;
-  friend class Registry;
+  friend class RegistryImpl;
 
   // KeysetUtil::GetKeyset() provides access to get_keyset().
   friend class KeysetUtil;
 
+  // Creates a handle that contains the given keyset.
+  explicit KeysetHandle(google::crypto::tink::Keyset keyset);
+  // Creates a handle that contains the given keyset.
+  explicit KeysetHandle(std::unique_ptr<google::crypto::tink::Keyset> keyset);
+
+  // Helper function which generates a key from a template, then adds it
+  // to the keyset. TODO(tholenst): Change this to a proper member operating
+  // on the internal keyset.
+  static crypto::tink::util::StatusOr<uint32_t> AddToKeyset(
+      const google::crypto::tink::KeyTemplate& key_template, bool as_primary,
+      google::crypto::tink::Keyset* keyset);
+
   // Returns keyset held by this handle.
   const google::crypto::tink::Keyset& get_keyset() const;
 
-  // Creates a handle that contains and owns the given keyset.
-  KeysetHandle(std::unique_ptr<google::crypto::tink::Keyset> keyset);
-  std::unique_ptr<google::crypto::tink::Keyset> keyset_;
+  // Creates a set of primitives corresponding to the keys with
+  // (status == ENABLED) in the keyset given in 'keyset_handle',
+  // assuming all the corresponding key managers are present (keys
+  // with (status != ENABLED) are skipped).
+  //
+  // The returned set is usually later "wrapped" into a class that
+  // implements the corresponding Primitive-interface.
+  template <class P>
+  crypto::tink::util::StatusOr<std::unique_ptr<PrimitiveSet<P>>>
+      GetPrimitives(const KeyManager<P>* custom_manager) const;
+
+  google::crypto::tink::Keyset keyset_;
 };
 
+///////////////////////////////////////////////////////////////////////////////
+// Implementation details of templated methods.
+
+template <class P>
+crypto::tink::util::StatusOr<std::unique_ptr<PrimitiveSet<P>>>
+KeysetHandle::GetPrimitives(const KeyManager<P>* custom_manager) const {
+  crypto::tink::util::Status status = ValidateKeyset(get_keyset());
+  if (!status.ok()) return status;
+  std::unique_ptr<PrimitiveSet<P>> primitives(new PrimitiveSet<P>());
+  for (const google::crypto::tink::Keyset::Key& key : get_keyset().key()) {
+    if (key.status() == google::crypto::tink::KeyStatusType::ENABLED) {
+      std::unique_ptr<P> primitive;
+      if (custom_manager != nullptr &&
+          custom_manager->DoesSupport(key.key_data().type_url())) {
+        auto primitive_result = custom_manager->GetPrimitive(key.key_data());
+        if (!primitive_result.ok()) return primitive_result.status();
+        primitive = std::move(primitive_result.ValueOrDie());
+      } else {
+        auto primitive_result = Registry::GetPrimitive<P>(key.key_data());
+        if (!primitive_result.ok()) return primitive_result.status();
+        primitive = std::move(primitive_result.ValueOrDie());
+      }
+      auto entry_result = primitives->AddPrimitive(std::move(primitive), key);
+      if (!entry_result.ok()) return entry_result.status();
+      if (key.key_id() == get_keyset().primary_key_id()) {
+        primitives->set_primary(entry_result.ValueOrDie());
+      }
+    }
+  }
+  return std::move(primitives);
+}
+
+template <class P>
+crypto::tink::util::StatusOr<std::unique_ptr<P>> KeysetHandle::GetPrimitive()
+    const {
+  auto primitives_result = this->GetPrimitives<P>(nullptr);
+  if (!primitives_result.ok()) {
+    return primitives_result.status();
+  }
+  return Registry::Wrap<P>(std::move(primitives_result.ValueOrDie()));
+}
+
+template <class P>
+crypto::tink::util::StatusOr<std::unique_ptr<P>> KeysetHandle::GetPrimitive(
+    const KeyManager<P>* custom_manager) const {
+  if (custom_manager == nullptr) {
+    return crypto::tink::util::Status(util::error::INVALID_ARGUMENT,
+                                      "custom_manager must not be null");
+  }
+  auto primitives_result = this->GetPrimitives<P>(custom_manager);
+  if (!primitives_result.ok()) {
+    return primitives_result.status();
+  }
+  return Registry::Wrap<P>(std::move(primitives_result.ValueOrDie()));
+}
+
+
 }  // namespace tink
 }  // namespace crypto
 
diff --git a/cc/keyset_manager.h b/cc/keyset_manager.h
index 32b11d5..97b4c3d 100644
--- a/cc/keyset_manager.h
+++ b/cc/keyset_manager.h
@@ -13,12 +13,13 @@
 // limitations under the License.
 //
 ///////////////////////////////////////////////////////////////////////////////
-
 #ifndef TINK_KEYSET_MANAGER_H_
 #define TINK_KEYSET_MANAGER_H_
 
 #include <mutex>  // NOLINT(build/c++11)
 
+#include "absl/base/thread_annotations.h"
+#include "absl/synchronization/mutex.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 #include "proto/tink.pb.h"
@@ -29,7 +30,7 @@
 class KeysetHandle;
 
 // KeysetManager provides convenience methods for creation of Keysets, and for
-// rotating, disabling, enabling, or destroing keys.
+// rotating, disabling, enabling, or destroying keys.
 // An instance of this class takes care of a single Keyset, that can be
 // accessed via GetKeysetHandle()-method.
 class KeysetManager {
@@ -51,53 +52,61 @@
   // 'keyset_template' and returns the key_id of the added key.
   // The added key has status 'ENABLED'.
   crypto::tink::util::StatusOr<uint32_t> Add(
-      const google::crypto::tink::KeyTemplate& key_template);
+      const google::crypto::tink::KeyTemplate& key_template)
+      LOCKS_EXCLUDED(keyset_mutex_);
 
   // Adds to the managed keyset a fresh key generated according to
   // 'keyset_template', sets the new key as the primary,
   // and returns the key_id of the added key.
   // The key that was primary prior to rotation remains 'ENABLED'.
   crypto::tink::util::StatusOr<uint32_t> Rotate(
-      const google::crypto::tink::KeyTemplate& key_template);
+      const google::crypto::tink::KeyTemplate& key_template)
+      LOCKS_EXCLUDED(keyset_mutex_);
 
   // Sets the status of the specified key to 'ENABLED'.
   // Succeeds only if before the call the specified key
   // has status 'DISABLED' or 'ENABLED'.
-  crypto::tink::util::Status Enable(uint32_t key_id);
+  crypto::tink::util::Status Enable(uint32_t key_id)
+      LOCKS_EXCLUDED(keyset_mutex_);
 
   // Sets the status of the specified key to 'DISABLED'.
   // Succeeds only if before the call the specified key
   // is not primary and has status 'DISABLED' or 'ENABLED'.
-  crypto::tink::util::Status Disable(uint32_t key_id);
+  crypto::tink::util::Status Disable(uint32_t key_id)
+      LOCKS_EXCLUDED(keyset_mutex_);
 
   // Sets the status of the specified key to 'DESTROYED',
   // and removes the corresponding key material, if any.
   // Succeeds only if before the call the specified key
   // is not primary and has status 'DISABLED', or 'ENABLED',
-  // or 'DESTROYED'
-  crypto::tink::util::Status Destroy(uint32_t key_id);
+  // or 'DESTROYED'.
+  crypto::tink::util::Status Destroy(uint32_t key_id)
+      LOCKS_EXCLUDED(keyset_mutex_);
 
   // Removes the specifed key from the managed keyset.
   // Succeeds only if the specified key is not primary.
   // After deletion the keyset contains one key fewer.
-  crypto::tink::util::Status Delete(uint32_t key_id);
+  crypto::tink::util::Status Delete(uint32_t key_id)
+      LOCKS_EXCLUDED(keyset_mutex_);
 
   // Sets the specified key as the primary.
   // Succeeds only if the specified key is 'ENABLED'.
-  crypto::tink::util::Status SetPrimary(uint32_t key_id);
+  crypto::tink::util::Status SetPrimary(uint32_t key_id)
+      LOCKS_EXCLUDED(keyset_mutex_);
 
   // Returns the count of all keys in the keyset.
   int KeyCount() const;
 
   // Returns a handle with a copy of the managed keyset.
-  std::unique_ptr<KeysetHandle> GetKeysetHandle();
+  std::unique_ptr<KeysetHandle> GetKeysetHandle() LOCKS_EXCLUDED(keyset_mutex_);
 
  private:
-  mutable std::recursive_mutex keyset_mutex_;
-  google::crypto::tink::Keyset keyset_;  // guarded by keyset_mutex_
+  crypto::tink::util::StatusOr<uint32_t> Add(
+      const google::crypto::tink::KeyTemplate& key_template, bool as_primary)
+      LOCKS_EXCLUDED(keyset_mutex_);
 
-  // Generates a new key_id avoiding collisions in the managed keyset.
-  uint32_t GenerateNewKeyId();
+  mutable absl::Mutex keyset_mutex_;
+  google::crypto::tink::Keyset keyset_ GUARDED_BY(keyset_mutex_);
 };
 
 }  // namespace tink
diff --git a/cc/mac/BUILD.bazel b/cc/mac/BUILD.bazel
index 0586865..6ad3347 100644
--- a/cc/mac/BUILD.bazel
+++ b/cc/mac/BUILD.bazel
@@ -3,19 +3,19 @@
 licenses(["notice"])  # Apache 2.0
 
 cc_library(
-    name = "mac_set_wrapper",
-    srcs = ["mac_set_wrapper.cc"],
-    hdrs = ["mac_set_wrapper.h"],
+    name = "mac_wrapper",
+    srcs = ["mac_wrapper.cc"],
+    hdrs = ["mac_wrapper.h"],
     include_prefix = "tink",
     strip_include_prefix = "/cc",
-    visibility = ["//visibility:private"],
     deps = [
         "//cc:crypto_format",
         "//cc:mac",
         "//cc:primitive_set",
+        "//cc:primitive_wrapper",
+        "//cc/subtle:subtle_util_boringssl",
         "//cc/util:status",
         "//cc/util:statusor",
-        "//cc/subtle:subtle_util_boringssl",
         "//proto:tink_cc_proto",
     ],
 )
@@ -31,6 +31,7 @@
         "//cc:config",
         "//cc/util:status",
         "//proto:config_cc_proto",
+        "@com_google_absl//absl/memory",
     ],
 )
 
@@ -54,7 +55,7 @@
     include_prefix = "tink",
     strip_include_prefix = "/cc",
     deps = [
-        ":mac_set_wrapper",
+        ":mac_wrapper",
         "//cc:key_manager",
         "//cc:keyset_handle",
         "//cc:mac",
@@ -62,6 +63,7 @@
         "//cc:registry",
         "//cc/util:status",
         "//cc/util:statusor",
+        "@com_google_absl//absl/base:core_headers",
     ],
 )
 
@@ -86,6 +88,7 @@
     strip_include_prefix = "/cc",
     deps = [
         "//cc:key_manager",
+        "//cc:key_manager_base",
         "//cc:mac",
         "//cc/subtle:hmac_boringssl",
         "//cc/subtle:random",
@@ -104,12 +107,13 @@
 # tests
 
 cc_test(
-    name = "mac_set_wrapper_test",
+    name = "mac_wrapper_test",
     size = "small",
-    srcs = ["mac_set_wrapper_test.cc"],
+    srcs = ["mac_wrapper_test.cc"],
     copts = ["-Iexternal/gtest/include"],
     deps = [
-        ":mac_set_wrapper",
+        ":mac_wrapper",
+        "//cc:crypto_format",
         "//cc:mac",
         "//cc:primitive_set",
         "//cc/util:status",
@@ -142,11 +146,15 @@
     copts = ["-Iexternal/gtest/include"],
     deps = [
         ":mac_config",
+        ":mac_key_templates",
         "//cc:catalogue",
         "//cc:config",
+        "//cc:keyset_handle",
         "//cc:mac",
         "//cc:registry",
         "//cc/util:status",
+        "//cc/util:test_matchers",
+        "//cc/util:test_util",
         "@com_google_googletest//:gtest_main",
     ],
 )
@@ -159,7 +167,7 @@
     deps = [
         ":mac_config",
         ":mac_factory",
-        ":mac_set_wrapper",
+        ":mac_wrapper",
         "//cc:config",
         "//cc:crypto_format",
         "//cc:keyset_handle",
diff --git a/cc/mac/CMakeLists.txt b/cc/mac/CMakeLists.txt
index 6bfd334..6e28f9c 100644
--- a/cc/mac/CMakeLists.txt
+++ b/cc/mac/CMakeLists.txt
@@ -1,298 +1,104 @@
-# Copyright 2018 The Fuchsia Authors. All rights reserved.
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-# Library: 'tink_mac_mac_set_wrapper'
-add_library(tink_mac_mac_set_wrapper mac_set_wrapper.cc mac_set_wrapper.h)
-tink_export_hdrs(mac_set_wrapper.h)
+
+# CC Library : mac_wrapper
+add_library(tink_cc_mac_mac_wrapper mac_wrapper.h mac_wrapper.cc)
+tink_export_hdrs(mac_wrapper.h)
 add_dependencies(
-  tink_mac_mac_set_wrapper
-  tink_crypto_format
-  tink_mac
-  tink_primitive_set
-  tink_util_status
-  tink_util_statusor
-  tink_subtle_subtle_util_boringssl
+  tink_cc_mac_mac_wrapper
+  tink_cc_crypto_format
+  tink_cc_mac
+  tink_cc_primitive_set
+  tink_cc_primitive_wrapper
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_status
+  tink_cc_util_statusor
   tink_proto_tink_lib
 )
 target_link_libraries(
-  tink_mac_mac_set_wrapper
-  tink_crypto_format
-  tink_mac
-  tink_primitive_set
-  tink_util_status
-  tink_util_statusor
-  tink_subtle_subtle_util_boringssl
+  tink_cc_mac_mac_wrapper
+  tink_cc_crypto_format
+  tink_cc_mac
+  tink_cc_primitive_set
+  tink_cc_primitive_wrapper
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_status
+  tink_cc_util_statusor
   tink_proto_tink_lib
 )
 
-# Library: 'tink_mac_mac_config'
-add_library(tink_mac_mac_config mac_config.cc mac_config.h)
+# CC Library : mac_config
+add_library(tink_cc_mac_mac_config mac_config.h mac_config.cc)
 tink_export_hdrs(mac_config.h)
 add_dependencies(
-  tink_mac_mac_config
-  tink_mac_mac_catalogue
-  tink_config
-  tink_util_status
+  tink_cc_mac_mac_config
+  tink_cc_mac_mac_catalogue
+  tink_cc_config
+  tink_cc_util_status
   tink_proto_config_lib
 )
 target_link_libraries(
-  tink_mac_mac_config
-  tink_mac_mac_catalogue
-  tink_config
-  tink_util_status
+  tink_cc_mac_mac_config
+  tink_cc_mac_mac_catalogue
+  tink_cc_config
+  tink_cc_util_status
   tink_proto_config_lib
+  absl::memory
 )
 
-# Library: 'tink_mac_mac_catalogue'
-add_library(tink_mac_mac_catalogue mac_catalogue.cc mac_catalogue.h)
+# CC Library : mac_catalogue
+add_library(tink_cc_mac_mac_catalogue mac_catalogue.h mac_catalogue.cc)
 tink_export_hdrs(mac_catalogue.h)
 add_dependencies(
-  tink_mac_mac_catalogue
-  tink_mac_hmac_key_manager
-  tink_catalogue
-  tink_util_status
+  tink_cc_mac_mac_catalogue
+  tink_cc_mac_hmac_key_manager
+  tink_cc_catalogue
+  tink_cc_util_status
 )
 target_link_libraries(
-  tink_mac_mac_catalogue
-  tink_mac_hmac_key_manager
-  tink_catalogue
-  tink_util_status
+  tink_cc_mac_mac_catalogue
+  tink_cc_mac_hmac_key_manager
+  tink_cc_catalogue
+  tink_cc_util_status
 )
 
-# Library: 'tink_mac_mac_factory'
-add_library(tink_mac_mac_factory mac_factory.cc mac_factory.h)
-tink_export_hdrs(mac_factory.h)
-add_dependencies(
-  tink_mac_mac_factory
-  tink_mac_mac_set_wrapper
-  tink_key_manager
-  tink_keyset_handle
-  tink_mac
-  tink_primitive_set
-  tink_registry
-  tink_util_status
-  tink_util_statusor
-)
-target_link_libraries(
-  tink_mac_mac_factory
-  tink_mac_mac_set_wrapper
-  tink_key_manager
-  tink_keyset_handle
-  tink_mac
-  tink_primitive_set
-  tink_registry
-  tink_util_status
-  tink_util_statusor
-)
-
-# Library: 'tink_mac_mac_key_templates'
-add_library(tink_mac_mac_key_templates mac_key_templates.cc mac_key_templates.h)
-tink_export_hdrs(mac_key_templates.h)
-add_dependencies(
-  tink_mac_mac_key_templates
-  tink_proto_common_lib
-  tink_proto_hmac_lib
-  tink_proto_tink_lib
-)
-target_link_libraries(
-  tink_mac_mac_key_templates
-  tink_proto_common_lib
-  tink_proto_hmac_lib
-  tink_proto_tink_lib
-)
-
-# Library: 'tink_mac_hmac_key_manager'
-add_library(tink_mac_hmac_key_manager hmac_key_manager.cc hmac_key_manager.h)
+# CC Library : hmac_key_manager
+add_library(tink_cc_mac_hmac_key_manager hmac_key_manager.h hmac_key_manager.cc)
 tink_export_hdrs(hmac_key_manager.h)
 add_dependencies(
-  tink_mac_hmac_key_manager
-  tink_key_manager
-  tink_mac
-  tink_subtle_hmac_boringssl
-  tink_subtle_random
-  tink_util_enums
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_util_validation
+  tink_cc_mac_hmac_key_manager
+  tink_cc_key_manager
+  tink_cc_core_key_manager_base
+  tink_cc_mac
+  tink_cc_subtle_hmac_boringssl
+  tink_cc_subtle_random
+  tink_cc_util_enums
+  tink_cc_util_errors
+  tink_cc_util_protobuf_helper
+  tink_cc_util_status
+  tink_cc_util_statusor
+  tink_cc_util_validation
   tink_proto_common_lib
   tink_proto_hmac_lib
   tink_proto_tink_lib
 )
 target_link_libraries(
-  tink_mac_hmac_key_manager
-  tink_key_manager
-  tink_mac
-  tink_subtle_hmac_boringssl
-  tink_subtle_random
-  tink_util_enums
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_util_validation
+  tink_cc_mac_hmac_key_manager
+  tink_cc_key_manager
+  tink_cc_core_key_manager_base
+  tink_cc_mac
+  tink_cc_subtle_hmac_boringssl
+  tink_cc_subtle_random
+  tink_cc_util_enums
+  tink_cc_util_errors
+  tink_cc_util_protobuf_helper
+  tink_cc_util_status
+  tink_cc_util_statusor
+  tink_cc_util_validation
   tink_proto_common_lib
   tink_proto_hmac_lib
   tink_proto_tink_lib
 )
 
-# Test Binary: 'tink_mac_mac_set_wrapper_test'
-add_executable(tink_mac_mac_set_wrapper_test mac_set_wrapper_test.cc)
-add_dependencies(
-  tink_mac_mac_set_wrapper_test
-  tink_mac_mac_set_wrapper
-  tink_mac
-  tink_primitive_set
-  tink_util_status
-  tink_util_test_util
-  tink_proto_tink_lib
-)
-add_dependencies(tink_mac_mac_set_wrapper_test build_external_projects)
-target_link_libraries(
-  tink_mac_mac_set_wrapper_test
-  tink_mac_mac_set_wrapper
-  tink_mac
-  tink_primitive_set
-  tink_util_status
-  tink_util_test_util
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_mac_mac_catalogue_test'
-add_executable(tink_mac_mac_catalogue_test mac_catalogue_test.cc)
-add_dependencies(
-  tink_mac_mac_catalogue_test
-  tink_mac_mac_catalogue
-  tink_mac_mac_config
-  tink_catalogue
-  tink_config
-  tink_util_status
-  tink_util_statusor
-)
-add_dependencies(tink_mac_mac_catalogue_test build_external_projects)
-target_link_libraries(
-  tink_mac_mac_catalogue_test
-  tink_mac_mac_catalogue
-  tink_mac_mac_config
-  tink_catalogue
-  tink_config
-  tink_util_status
-  tink_util_statusor
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_mac_mac_config_test'
-add_executable(tink_mac_mac_config_test mac_config_test.cc)
-add_dependencies(
-  tink_mac_mac_config_test
-  tink_mac_mac_config
-  tink_catalogue
-  tink_config
-  tink_mac
-  tink_registry
-  tink_util_status
-)
-add_dependencies(tink_mac_mac_config_test build_external_projects)
-target_link_libraries(
-  tink_mac_mac_config_test
-  tink_mac_mac_config
-  tink_catalogue
-  tink_config
-  tink_mac
-  tink_registry
-  tink_util_status
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_mac_mac_factory_test'
-add_executable(tink_mac_mac_factory_test mac_factory_test.cc)
-add_dependencies(
-  tink_mac_mac_factory_test
-  tink_mac_mac_config
-  tink_mac_mac_factory
-  tink_mac_mac_set_wrapper
-  tink_config
-  tink_crypto_format
-  tink_keyset_handle
-  tink_mac
-  tink_mac_hmac_key_manager
-  tink_util_keyset_util
-  tink_util_status
-  tink_util_test_util
-  tink_proto_common_lib
-  tink_proto_hmac_lib
-  tink_proto_tink_lib
-)
-add_dependencies(tink_mac_mac_factory_test build_external_projects)
-target_link_libraries(
-  tink_mac_mac_factory_test
-  tink_mac_mac_config
-  tink_mac_mac_factory
-  tink_mac_mac_set_wrapper
-  tink_config
-  tink_crypto_format
-  tink_keyset_handle
-  tink_mac
-  tink_mac_hmac_key_manager
-  tink_util_keyset_util
-  tink_util_status
-  tink_util_test_util
-  tink_proto_common_lib
-  tink_proto_hmac_lib
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_mac_mac_key_templates_test'
-add_executable(tink_mac_mac_key_templates_test mac_key_templates_test.cc)
-add_dependencies(
-  tink_mac_mac_key_templates_test
-  tink_mac_hmac_key_manager
-  tink_mac_mac_key_templates
-  tink_proto_common_lib
-  tink_proto_hmac_lib
-  tink_proto_tink_lib
-)
-add_dependencies(tink_mac_mac_key_templates_test build_external_projects)
-target_link_libraries(
-  tink_mac_mac_key_templates_test
-  tink_mac_hmac_key_manager
-  tink_mac_mac_key_templates
-  tink_proto_common_lib
-  tink_proto_hmac_lib
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_mac_hmac_key_manager_test'
-add_executable(tink_mac_hmac_key_manager_test hmac_key_manager_test.cc)
-add_dependencies(
-  tink_mac_hmac_key_manager_test
-  tink_mac_hmac_key_manager
-  tink_mac
-  tink_util_status
-  tink_util_statusor
-  tink_proto_aes_ctr_lib
-  tink_proto_aes_ctr_hmac_aead_lib
-  tink_proto_common_lib
-  tink_proto_hmac_lib
-  tink_proto_tink_lib
-)
-add_dependencies(tink_mac_hmac_key_manager_test build_external_projects)
-target_link_libraries(
-  tink_mac_hmac_key_manager_test
-  tink_mac_hmac_key_manager
-  tink_mac
-  tink_util_status
-  tink_util_statusor
-  tink_proto_aes_ctr_lib
-  tink_proto_aes_ctr_hmac_aead_lib
-  tink_proto_common_lib
-  tink_proto_hmac_lib
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
diff --git a/cc/mac/hmac_key_manager.cc b/cc/mac/hmac_key_manager.cc
index 5154fec..060fdd3 100644
--- a/cc/mac/hmac_key_manager.cc
+++ b/cc/mac/hmac_key_manager.cc
@@ -47,90 +47,38 @@
 using crypto::tink::util::Status;
 using crypto::tink::util::StatusOr;
 
-class HmacKeyFactory : public KeyFactory {
+class HmacKeyFactory : public KeyFactoryBase<HmacKey, HmacKeyFormat> {
  public:
   HmacKeyFactory() {}
 
-  // Generates a new random HmacKey, based on the specified 'key_format',
-  // which must contain HmacKeyFormat-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<portable_proto::MessageLite>>
-  NewKey(const portable_proto::MessageLite& key_format) const override;
+  KeyData::KeyMaterialType key_material_type() const override {
+    return KeyData::SYMMETRIC;
+  }
 
-
-  // Generates a new random HmacKey, based on the specified
-  // 'serialized_key_format', which must contain HmacKeyFormat-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<portable_proto::MessageLite>>
-  NewKey(absl::string_view serialized_key_format) const override;
-
-  // Generates a new random HmacKey, based on the specified
-  // 'serialized_key_format' (which must contain HmacKeyFormat-proto),
-  // and wraps it in a KeyData-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<google::crypto::tink::KeyData>>
-  NewKeyData(absl::string_view serialized_key_format) const override;
+ protected:
+  StatusOr<std::unique_ptr<HmacKey>> NewKeyFromFormat(
+      const HmacKeyFormat& hmac_key_format) const override;
 };
 
-StatusOr<std::unique_ptr<MessageLite>> HmacKeyFactory::NewKey(
-    const portable_proto::MessageLite& key_format) const {
-  std::string key_format_url =
-      std::string(HmacKeyManager::kKeyTypePrefix) + key_format.GetTypeName();
-  if (key_format_url != HmacKeyManager::kKeyFormatUrl) {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Key format proto '%s' is not supported by this manager.",
-                     key_format_url.c_str());
-  }
-  const HmacKeyFormat& hmac_key_format =
-      reinterpret_cast<const HmacKeyFormat&>(key_format);
+StatusOr<std::unique_ptr<HmacKey>> HmacKeyFactory::NewKeyFromFormat(
+    const HmacKeyFormat& hmac_key_format) const {
   Status status =  HmacKeyManager::Validate(hmac_key_format);
   if (!status.ok()) return status;
-
-  // Generate HmacKey.
-  std::unique_ptr<HmacKey> hmac_key(new HmacKey());
+  auto hmac_key = absl::make_unique<HmacKey>();
   hmac_key->set_version(HmacKeyManager::kVersion);
   *(hmac_key->mutable_params()) = hmac_key_format.params();
   hmac_key->set_key_value(
       subtle::Random::GetRandomBytes(hmac_key_format.key_size()));
-  std::unique_ptr<MessageLite> key = std::move(hmac_key);
-  return std::move(key);
+  return absl::implicit_cast<StatusOr<std::unique_ptr<HmacKey>>>(
+        std::move(hmac_key));
 }
 
-StatusOr<std::unique_ptr<MessageLite>> HmacKeyFactory::NewKey(
-    absl::string_view serialized_key_format) const {
-  HmacKeyFormat key_format;
-  if (!key_format.ParseFromString(std::string(serialized_key_format))) {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Could not parse the passed string as proto '%s'.",
-                     HmacKeyManager::kKeyFormatUrl);
-  }
-  return NewKey(key_format);
-}
-
-StatusOr<std::unique_ptr<KeyData>> HmacKeyFactory::NewKeyData(
-    absl::string_view serialized_key_format) const {
-  auto new_key_result = NewKey(serialized_key_format);
-  if (!new_key_result.ok()) return new_key_result.status();
-  auto new_key = reinterpret_cast<const HmacKey&>(
-      *(new_key_result.ValueOrDie()));
-  std::unique_ptr<KeyData> key_data(new KeyData());
-  key_data->set_type_url(HmacKeyManager::kKeyType);
-  key_data->set_value(new_key.SerializeAsString());
-  key_data->set_key_material_type(KeyData::SYMMETRIC);
-  return std::move(key_data);
-}
-
-constexpr char HmacKeyManager::kKeyFormatUrl[];
-constexpr char HmacKeyManager::kKeyTypePrefix[];
-constexpr char HmacKeyManager::kKeyType[];
 constexpr uint32_t HmacKeyManager::kVersion;
 
 const int kMinKeySizeInBytes = 16;
 const int kMinTagSizeInBytes = 10;
 
-HmacKeyManager::HmacKeyManager()
-    : key_type_(kKeyType), key_factory_(new HmacKeyFactory()) {}
-
-const std::string& HmacKeyManager::get_key_type() const {
-  return key_type_;
-}
+HmacKeyManager::HmacKeyManager() : key_factory_(new HmacKeyFactory()) {}
 
 uint32_t HmacKeyManager::get_version() const {
   return kVersion;
@@ -140,38 +88,8 @@
   return *key_factory_;
 }
 
-StatusOr<std::unique_ptr<Mac>>
-HmacKeyManager::GetPrimitive(const KeyData& key_data) const {
-  if (DoesSupport(key_data.type_url())) {
-    HmacKey hmac_key;
-    if (!hmac_key.ParseFromString(key_data.value())) {
-      return ToStatusF(util::error::INVALID_ARGUMENT,
-                       "Could not parse key_data.value as key type '%s'.",
-                       key_data.type_url().c_str());
-    }
-    return GetPrimitiveImpl(hmac_key);
-  } else {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Key type '%s' is not supported by this manager.",
-                     key_data.type_url().c_str());
-  }
-}
-
-StatusOr<std::unique_ptr<Mac>>
-HmacKeyManager::GetPrimitive(const MessageLite& key) const {
-  std::string key_type = std::string(kKeyTypePrefix) + key.GetTypeName();
-  if (DoesSupport(key_type)) {
-    const HmacKey& hmac_key = reinterpret_cast<const HmacKey&>(key);
-    return GetPrimitiveImpl(hmac_key);
-  } else {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Key type '%s' is not supported by this manager.",
-                     key_type.c_str());
-  }
-}
-
-StatusOr<std::unique_ptr<Mac>>
-HmacKeyManager::GetPrimitiveImpl(const HmacKey& hmac_key) const {
+StatusOr<std::unique_ptr<Mac>> HmacKeyManager::GetPrimitiveFromKey(
+    const HmacKey& hmac_key) const {
   Status status = Validate(hmac_key);
   if (!status.ok()) return status;
   auto hmac_result = subtle::HmacBoringSsl::New(
diff --git a/cc/mac/hmac_key_manager.h b/cc/mac/hmac_key_manager.h
index 092cc09..1597fb2 100644
--- a/cc/mac/hmac_key_manager.h
+++ b/cc/mac/hmac_key_manager.h
@@ -13,16 +13,16 @@
 // limitations under the License.
 //
 ///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_MAC_HMAC_KEY_MANAGER_H_
+#define TINK_MAC_HMAC_KEY_MANAGER_H_
 
 #include <algorithm>
 #include <vector>
 
-#ifndef TINK_MAC_HMAC_KEY_MANAGER_H_
-#define TINK_MAC_HMAC_KEY_MANAGER_H_
-
 #include "absl/strings/string_view.h"
-#include "tink/mac.h"
+#include "tink/core/key_manager_base.h"
 #include "tink/key_manager.h"
+#include "tink/mac.h"
 #include "tink/util/errors.h"
 #include "tink/util/protobuf_helper.h"
 #include "tink/util/status.h"
@@ -33,27 +33,13 @@
 namespace crypto {
 namespace tink {
 
-class HmacKeyManager : public KeyManager<Mac> {
+class HmacKeyManager
+    : public KeyManagerBase<Mac, google::crypto::tink::HmacKey> {
  public:
-  static constexpr char kKeyType[] =
-      "type.googleapis.com/google.crypto.tink.HmacKey";
   static constexpr uint32_t kVersion = 0;
 
   HmacKeyManager();
 
-  // Constructs an instance of HMAC-Mac for the given 'key_data',
-  // which must contain HmacKey-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<Mac>> GetPrimitive(
-      const google::crypto::tink::KeyData& key_data) const override;
-
-  // Constructs an instance of HMAC-Mac for the given 'key',
-  // which must be HmacKey-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<Mac>>
-  GetPrimitive(const portable_proto::MessageLite& key) const override;
-
-  // Returns the type_url identifying the key type handled by this manager.
-  const std::string& get_key_type() const override;
-
   // Returns the version of this key manager.
   uint32_t get_version() const override;
 
@@ -63,20 +49,15 @@
 
   virtual ~HmacKeyManager() {}
 
+ protected:
+  crypto::tink::util::StatusOr<std::unique_ptr<Mac>> GetPrimitiveFromKey(
+      const google::crypto::tink::HmacKey& hmac_key) const override;
+
  private:
   friend class HmacKeyFactory;
 
-  static constexpr char kKeyTypePrefix[] = "type.googleapis.com/";
-  static constexpr char kKeyFormatUrl[] =
-      "type.googleapis.com/google.crypto.tink.HmacKeyFormat";
-
-  std::string key_type_;
   std::unique_ptr<KeyFactory> key_factory_;
 
-  // Constructs an instance of HMAC-Mac for the given 'key'.
-  crypto::tink::util::StatusOr<std::unique_ptr<Mac>>
-  GetPrimitiveImpl(const google::crypto::tink::HmacKey& key) const;
-
   static crypto::tink::util::Status Validate(
       const google::crypto::tink::HmacParams& params);
   static crypto::tink::util::Status Validate(
diff --git a/cc/mac/hmac_key_manager_test.cc b/cc/mac/hmac_key_manager_test.cc
index fae3021..0d8f8c9 100644
--- a/cc/mac/hmac_key_manager_test.cc
+++ b/cc/mac/hmac_key_manager_test.cc
@@ -232,7 +232,7 @@
     auto key = std::move(result.ValueOrDie());
     EXPECT_EQ(key_type_prefix + key->GetTypeName(), hmac_key_type);
     std::unique_ptr<HmacKey> hmac_key(
-        reinterpret_cast<HmacKey*>(key.release()));
+        static_cast<HmacKey*>(key.release()));
     EXPECT_EQ(0, hmac_key->version());
     EXPECT_EQ(key_format.params().hash(), hmac_key->params().hash());
     EXPECT_EQ(key_format.params().tag_size(), hmac_key->params().tag_size());
@@ -245,7 +245,7 @@
     auto key = std::move(result.ValueOrDie());
     EXPECT_EQ(key_type_prefix + key->GetTypeName(), hmac_key_type);
     std::unique_ptr<HmacKey> hmac_key(
-        reinterpret_cast<HmacKey*>(key.release()));
+        static_cast<HmacKey*>(key.release()));
     EXPECT_EQ(0, hmac_key->version());
     EXPECT_EQ(key_format.params().hash(), hmac_key->params().hash());
     EXPECT_EQ(key_format.params().tag_size(), hmac_key->params().tag_size());
@@ -270,9 +270,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/mac/mac_catalogue.cc b/cc/mac/mac_catalogue.cc
index 3f5e1df..8b210c7 100644
--- a/cc/mac/mac_catalogue.cc
+++ b/cc/mac/mac_catalogue.cc
@@ -30,7 +30,7 @@
 
 crypto::tink::util::StatusOr<std::unique_ptr<KeyManager<Mac>>> CreateKeyManager(
     const std::string& type_url) {
-  if (type_url == HmacKeyManager::kKeyType) {
+  if (type_url == HmacKeyManager::static_key_type()) {
     std::unique_ptr<KeyManager<Mac>> manager(new HmacKeyManager());
     return std::move(manager);
   }
diff --git a/cc/mac/mac_catalogue.h b/cc/mac/mac_catalogue.h
index a523eae..76403e6 100644
--- a/cc/mac/mac_catalogue.h
+++ b/cc/mac/mac_catalogue.h
@@ -39,4 +39,4 @@
 }  // namespace tink
 }  // namespace crypto
 
-#endif  // TINK_MAC_MAC_CONFIG_H_
+#endif  // TINK_MAC_MAC_CATALOGUE_H_
diff --git a/cc/mac/mac_catalogue_test.cc b/cc/mac/mac_catalogue_test.cc
index b58bf18..81d0e68 100644
--- a/cc/mac/mac_catalogue_test.cc
+++ b/cc/mac/mac_catalogue_test.cc
@@ -61,9 +61,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/mac/mac_config.cc b/cc/mac/mac_config.cc
index 7d3a952..254a3d9 100644
--- a/cc/mac/mac_config.cc
+++ b/cc/mac/mac_config.cc
@@ -16,11 +16,11 @@
 
 #include "tink/mac/mac_config.h"
 
+#include "absl/memory/memory.h"
 #include "tink/config.h"
 #include "tink/mac/mac_catalogue.h"
 #include "tink/util/status.h"
 
-
 namespace crypto {
 namespace tink {
 
@@ -49,7 +49,8 @@
 
 // static
 util::Status MacConfig::Register() {
-  auto status = Registry::AddCatalogue(kCatalogueName, new MacCatalogue());
+  auto status =
+      Registry::AddCatalogue(kCatalogueName, absl::make_unique<MacCatalogue>());
   if (!status.ok()) return status;
   return Config::Register(Latest());
 }
diff --git a/cc/mac/mac_config_test.cc b/cc/mac/mac_config_test.cc
index 417dcc4..8695a01 100644
--- a/cc/mac/mac_config_test.cc
+++ b/cc/mac/mac_config_test.cc
@@ -18,16 +18,20 @@
 
 #include "tink/catalogue.h"
 #include "tink/config.h"
+#include "tink/keyset_handle.h"
 #include "tink/mac.h"
+#include "tink/mac/mac_key_templates.h"
 #include "tink/registry.h"
 #include "tink/util/status.h"
 #include "gtest/gtest.h"
-
+#include "tink/util/test_util.h"
 
 namespace crypto {
 namespace tink {
 namespace {
 
+using ::crypto::tink::test::DummyMac;
+
 class DummyMacCatalogue : public Catalogue<Mac> {
  public:
   DummyMacCatalogue() {}
@@ -101,12 +105,34 @@
   EXPECT_EQ(util::error::ALREADY_EXISTS, status.error_code());
 }
 
+// Tests that the MacWrapper has been properly registered and we can wrap
+// primitives.
+TEST_F(MacConfigTest, WrappersRegistered) {
+  ASSERT_TRUE(MacConfig::Register().ok());
+
+  google::crypto::tink::Keyset::Key key;
+  key.set_status(google::crypto::tink::KeyStatusType::ENABLED);
+  key.set_key_id(1234);
+  key.set_output_prefix_type(google::crypto::tink::OutputPrefixType::RAW);
+  auto primitive_set = absl::make_unique<PrimitiveSet<Mac>>();
+  primitive_set->set_primary(
+      primitive_set->AddPrimitive(absl::make_unique<DummyMac>("dummy"), key)
+          .ValueOrDie());
+
+  auto primitive_result = Registry::Wrap(std::move(primitive_set));
+
+  ASSERT_TRUE(primitive_result.ok()) << primitive_result.status();
+  auto mac_result =
+      primitive_result.ValueOrDie()->ComputeMac("verified text");
+  ASSERT_TRUE(mac_result.ok());
+
+  EXPECT_TRUE(DummyMac("dummy")
+                  .VerifyMac(mac_result.ValueOrDie(), "verified text")
+                  .ok());
+  EXPECT_FALSE(
+      DummyMac("dummy").VerifyMac(mac_result.ValueOrDie(), "faked text").ok());
+}
+
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/mac/mac_factory.cc b/cc/mac/mac_factory.cc
index 7cef6ee..954ac82 100644
--- a/cc/mac/mac_factory.cc
+++ b/cc/mac/mac_factory.cc
@@ -18,7 +18,7 @@
 
 #include "tink/mac.h"
 #include "tink/registry.h"
-#include "tink/mac/mac_set_wrapper.h"
+#include "tink/mac/mac_wrapper.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 
@@ -29,19 +29,24 @@
 // static
 util::StatusOr<std::unique_ptr<Mac>> MacFactory::GetPrimitive(
     const KeysetHandle& keyset_handle) {
-  return GetPrimitive(keyset_handle, nullptr);
+  util::Status status =
+      Registry::RegisterPrimitiveWrapper(absl::make_unique<MacWrapper>());
+  if (!status.ok()) {
+    return status;
+  }
+  return keyset_handle.GetPrimitive<Mac>();
 }
 
 // static
 util::StatusOr<std::unique_ptr<Mac>> MacFactory::GetPrimitive(
     const KeysetHandle& keyset_handle,
     const KeyManager<Mac>* custom_key_manager) {
-  auto primitives_result = Registry::GetPrimitives<Mac>(
-      keyset_handle, custom_key_manager);
-  if (primitives_result.ok()) {
-    return MacSetWrapper::NewMac(std::move(primitives_result.ValueOrDie()));
+  util::Status status =
+      Registry::RegisterPrimitiveWrapper(absl::make_unique<MacWrapper>());
+  if (!status.ok()) {
+    return status;
   }
-  return primitives_result.status();
+  return keyset_handle.GetPrimitive<Mac>(custom_key_manager);
 }
 
 }  // namespace tink
diff --git a/cc/mac/mac_factory.h b/cc/mac/mac_factory.h
index 86fd4eb..1fc427b 100644
--- a/cc/mac/mac_factory.h
+++ b/cc/mac/mac_factory.h
@@ -17,6 +17,7 @@
 #ifndef TINK_MAC_MAC_FACTORY_H_
 #define TINK_MAC_MAC_FACTORY_H_
 
+#include "absl/base/macros.h"
 #include "tink/key_manager.h"
 #include "tink/keyset_handle.h"
 #include "tink/mac.h"
@@ -27,21 +28,18 @@
 namespace tink {
 
 ///////////////////////////////////////////////////////////////////////////////
-// MacFactory allows for obtaining a Mac primitive from a KeysetHandle.
+// This class is deprecated. Call keyset_handle->GetPrimitive<Mac>() instead.
 //
-// MacFactory gets primitives from the Registry, which can be initialized
-// via a convenience method from MacConfig-class. Here is an example
-// how one can obtain and use a Mac primitive:
-//
-//   auto status = MacConfig::Register();
-//   if (!status.ok()) { /* fail with error */ }
-//   KeysetHandle keyset_handle = ...;
-//   std::unique_ptr<Mac> mac =
-//       std::move(MacFactory::GetPrimitive(keyset_handle).ValueOrDie());
-//   std::string data = ...;
-//   std::string mac_value = mac.ComputeMac(data).ValueOrDie();
-//
-class MacFactory {
+// Note that in order to for this change to be safe, the AeadSetWrapper has to
+// be registered in your binary before this call. This happens automatically if
+// you call one of
+// * MacConfig::Register()
+// * AeadConfig::Register()
+// * HybridConfig::Register()
+// * TinkConfig::Register()
+class ABSL_DEPRECATED(
+    "Call getPrimitive<Mac>() on the keyset_handle after registering the "
+    "MacWrapper instead.") MacFactory {
  public:
   // Returns a Mac-primitive that uses key material from the keyset
   // specified via 'keyset_handle'.
diff --git a/cc/mac/mac_factory_test.cc b/cc/mac/mac_factory_test.cc
index c9b1236..c43b991 100644
--- a/cc/mac/mac_factory_test.cc
+++ b/cc/mac/mac_factory_test.cc
@@ -131,9 +131,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/mac/mac_key_templates_test.cc b/cc/mac/mac_key_templates_test.cc
index 2fc0878..863ab51 100644
--- a/cc/mac/mac_key_templates_test.cc
+++ b/cc/mac/mac_key_templates_test.cc
@@ -83,8 +83,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/mac/mac_set_wrapper_test.cc b/cc/mac/mac_set_wrapper_test.cc
deleted file mode 100644
index 521e47b..0000000
--- a/cc/mac/mac_set_wrapper_test.cc
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-#include "tink/mac/mac_set_wrapper.h"
-#include "tink/mac.h"
-#include "tink/primitive_set.h"
-#include "tink/util/status.h"
-#include "tink/util/test_util.h"
-#include "gtest/gtest.h"
-
-using crypto::tink::test::DummyMac;
-using google::crypto::tink::OutputPrefixType;
-using google::crypto::tink::Keyset;
-
-
-namespace crypto {
-namespace tink {
-namespace {
-
-class MacSetWrapperTest : public ::testing::Test {
- protected:
-  void SetUp() override {
-  }
-  void TearDown() override {
-  }
-};
-
-TEST_F(MacSetWrapperTest, testBasic) {
-  { // mac_set is nullptr.
-    auto mac_result = MacSetWrapper::NewMac(nullptr);
-    EXPECT_FALSE(mac_result.ok());
-    EXPECT_EQ(util::error::INTERNAL, mac_result.status().error_code());
-    EXPECT_PRED_FORMAT2(testing::IsSubstring, "non-NULL",
-                        mac_result.status().error_message());
-  }
-
-  { // mac_set has no primary primitive.
-    std::unique_ptr<PrimitiveSet<Mac>> mac_set(new PrimitiveSet<Mac>());
-    auto mac_result = MacSetWrapper::NewMac(std::move(mac_set));
-    EXPECT_FALSE(mac_result.ok());
-    EXPECT_EQ(util::error::INVALID_ARGUMENT, mac_result.status().error_code());
-    EXPECT_PRED_FORMAT2(testing::IsSubstring, "no primary",
-                        mac_result.status().error_message());
-  }
-
-  { // Correct mac_set;
-    Keyset::Key* key;
-    Keyset keyset;
-
-    uint32_t key_id_0 = 1234543;
-    key = keyset.add_key();
-    key->set_output_prefix_type(OutputPrefixType::TINK);
-    key->set_key_id(key_id_0);
-
-    uint32_t key_id_1 = 726329;
-    key = keyset.add_key();
-    key->set_output_prefix_type(OutputPrefixType::LEGACY);
-    key->set_key_id(key_id_1);
-
-    uint32_t key_id_2 = 7213743;
-    key = keyset.add_key();
-    key->set_output_prefix_type(OutputPrefixType::TINK);
-    key->set_key_id(key_id_2);
-
-    std::string mac_name_0 = "mac0";
-    std::string mac_name_1 = "mac1";
-    std::string mac_name_2 = "mac2";
-    std::unique_ptr<PrimitiveSet<Mac>> mac_set(new PrimitiveSet<Mac>());
-    std::unique_ptr<Mac> mac(new DummyMac(mac_name_0));
-    auto entry_result = mac_set->AddPrimitive(std::move(mac), keyset.key(0));
-    ASSERT_TRUE(entry_result.ok());
-    mac.reset(new DummyMac(mac_name_1));
-    entry_result = mac_set->AddPrimitive(std::move(mac), keyset.key(1));
-    ASSERT_TRUE(entry_result.ok());
-    mac.reset(new DummyMac(mac_name_2));
-    entry_result = mac_set->AddPrimitive(std::move(mac), keyset.key(2));
-    ASSERT_TRUE(entry_result.ok());
-    // The last key is the primary.
-    mac_set->set_primary(entry_result.ValueOrDie());
-
-    // Wrap mac_set and test the resulting Mac.
-    auto mac_result = MacSetWrapper::NewMac(std::move(mac_set));
-    EXPECT_TRUE(mac_result.ok()) << mac_result.status();
-    mac = std::move(mac_result.ValueOrDie());
-    std::string data = "some_data_for_mac";
-
-    auto compute_mac_result = mac->ComputeMac(data);
-    EXPECT_TRUE(compute_mac_result.ok()) << compute_mac_result.status();
-    std::string mac_value = compute_mac_result.ValueOrDie();
-    EXPECT_PRED_FORMAT2(testing::IsSubstring, mac_name_2, mac_value);
-
-    util::Status status = mac->VerifyMac(mac_value, data);
-    EXPECT_TRUE(status.ok()) << status;
-
-    status = mac->VerifyMac("some bad mac", data);
-    EXPECT_FALSE(status.ok());
-    EXPECT_EQ(util::error::INVALID_ARGUMENT, status.error_code());
-    EXPECT_PRED_FORMAT2(testing::IsSubstring, "verification failed",
-                        status.error_message());
-  }
-}
-
-}  // namespace
-}  // namespace tink
-}  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/mac/mac_set_wrapper.cc b/cc/mac/mac_wrapper.cc
similarity index 67%
rename from cc/mac/mac_set_wrapper.cc
rename to cc/mac/mac_wrapper.cc
index 6bbe621..f594817 100644
--- a/cc/mac/mac_set_wrapper.cc
+++ b/cc/mac/mac_wrapper.cc
@@ -14,7 +14,7 @@
 //
 ///////////////////////////////////////////////////////////////////////////////
 
-#include "tink/mac/mac_set_wrapper.h"
+#include "tink/mac/mac_wrapper.h"
 
 #include "tink/crypto_format.h"
 #include "tink/mac.h"
@@ -22,12 +22,32 @@
 #include "tink/subtle/subtle_util_boringssl.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
+#include "proto/tink.pb.h"
 
 namespace crypto {
 namespace tink {
 
+using google::crypto::tink::OutputPrefixType;
+
 namespace {
 
+class MacSetWrapper : public Mac {
+ public:
+  explicit MacSetWrapper(std::unique_ptr<PrimitiveSet<Mac>> mac_set)
+      : mac_set_(std::move(mac_set)) {}
+
+  crypto::tink::util::StatusOr<std::string> ComputeMac(
+      absl::string_view data) const override;
+
+  crypto::tink::util::Status VerifyMac(absl::string_view mac_value,
+                                       absl::string_view data) const override;
+
+  ~MacSetWrapper() override {}
+
+ private:
+  std::unique_ptr<PrimitiveSet<Mac>> mac_set_;
+};
+
 util::Status Validate(PrimitiveSet<Mac>* mac_set) {
   if (mac_set == nullptr) {
     return util::Status(util::error::INTERNAL, "mac_set must be non-NULL");
@@ -39,33 +59,32 @@
   return util::Status::OK;
 }
 
-}  // anonymous namespace
-
-// static
-util::StatusOr<std::unique_ptr<Mac>> MacSetWrapper::NewMac(
-    std::unique_ptr<PrimitiveSet<Mac>> mac_set) {
-  util::Status status = Validate(mac_set.get());
-  if (!status.ok()) return status;
-  std::unique_ptr<Mac> mac(new MacSetWrapper(std::move(mac_set)));
-  return std::move(mac);
-}
-
 util::StatusOr<std::string> MacSetWrapper::ComputeMac(
     absl::string_view data) const {
   // BoringSSL expects a non-null pointer for data,
   // regardless of whether the size is 0.
   data = subtle::SubtleUtilBoringSSL::EnsureNonNull(data);
 
-  auto compute_mac_result =
-      mac_set_->get_primary()->get_primitive().ComputeMac(data);
+  auto primary = mac_set_->get_primary();
+  std::string local_data;
+  if (primary->get_output_prefix_type() == OutputPrefixType::LEGACY) {
+    local_data = std::string(data);
+    local_data.append(
+        reinterpret_cast<const char*>(&CryptoFormat::kLegacyStartByte), 1);
+    data = local_data;
+  }
+  auto compute_mac_result = primary->get_primitive().ComputeMac(data);
   if (!compute_mac_result.ok()) return compute_mac_result.status();
-  const std::string& key_id = mac_set_->get_primary()->get_identifier();
+  const std::string& key_id = primary->get_identifier();
   return key_id + compute_mac_result.ValueOrDie();
 }
 
 util::Status MacSetWrapper::VerifyMac(
     absl::string_view mac_value,
     absl::string_view data) const {
+  data = subtle::SubtleUtilBoringSSL::EnsureNonNull(data);
+  mac_value = subtle::SubtleUtilBoringSSL::EnsureNonNull(mac_value);
+
   if (mac_value.length() > CryptoFormat::kNonRawPrefixSize) {
     const std::string& key_id = std::string(mac_value.substr(0,
         CryptoFormat::kNonRawPrefixSize));
@@ -73,7 +92,13 @@
     if (primitives_result.ok()) {
       absl::string_view raw_mac_value =
           mac_value.substr(CryptoFormat::kNonRawPrefixSize);
+      std::string local_data;
       for (auto& mac_entry : *(primitives_result.ValueOrDie())) {
+        if (mac_entry->get_output_prefix_type() == OutputPrefixType::LEGACY) {
+          local_data = std::string(data);
+          local_data.append(1, CryptoFormat::kLegacyStartByte);
+          data = local_data;
+        }
         Mac& mac = mac_entry->get_primitive();
         util::Status status = mac.VerifyMac(raw_mac_value, data);
         if (status.ok()) {
@@ -99,5 +124,15 @@
   return util::Status(util::error::INVALID_ARGUMENT, "verification failed");
 }
 
+}  // namespace
+
+util::StatusOr<std::unique_ptr<Mac>> MacWrapper::Wrap(
+      std::unique_ptr<PrimitiveSet<Mac>> mac_set) const {
+  util::Status status = Validate(mac_set.get());
+  if (!status.ok()) return status;
+  std::unique_ptr<Mac> mac(new MacSetWrapper(std::move(mac_set)));
+  return std::move(mac);
+}
+
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/mac/mac_set_wrapper.h b/cc/mac/mac_wrapper.h
similarity index 60%
rename from cc/mac/mac_set_wrapper.h
rename to cc/mac/mac_wrapper.h
index a4d03e3..7001aec 100644
--- a/cc/mac/mac_set_wrapper.h
+++ b/cc/mac/mac_wrapper.h
@@ -14,12 +14,13 @@
 //
 ///////////////////////////////////////////////////////////////////////////////
 
-#ifndef TINK_MAC_MAC_SET_WRAPPER_H_
-#define TINK_MAC_MAC_SET_WRAPPER_H_
+#ifndef TINK_MAC_MAC_WRAPPER_H_
+#define TINK_MAC_MAC_WRAPPER_H_
 
 #include "absl/strings/string_view.h"
 #include "tink/mac.h"
 #include "tink/primitive_set.h"
+#include "tink/primitive_wrapper.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 #include "proto/tink.pb.h"
@@ -32,30 +33,13 @@
 // instances, depending on the context:
 //   * Mac::ComputeMac(...) uses the primary instance from the set
 //   * Mac::VerifyMac(...) uses the instance that matches the MAC prefix.
-class MacSetWrapper : public Mac {
+class MacWrapper : public PrimitiveWrapper<Mac> {
  public:
-  // Returns an Mac-primitive that uses Mac-instances provided in 'mac_set',
-  // which must be non-NULL and must contain a primary instance.
-  static crypto::tink::util::StatusOr<std::unique_ptr<Mac>> NewMac(
-      std::unique_ptr<PrimitiveSet<Mac>> mac_set);
-
-  crypto::tink::util::StatusOr<std::string> ComputeMac(
-      absl::string_view data) const override;
-
-  crypto::tink::util::Status VerifyMac(
-      absl::string_view mac_value,
-      absl::string_view data) const override;
-
-  virtual ~MacSetWrapper() {}
-
- private:
-  std::unique_ptr<PrimitiveSet<Mac>> mac_set_;
-
-  MacSetWrapper(std::unique_ptr<PrimitiveSet<Mac>> mac_set)
-      : mac_set_(std::move(mac_set)) {}
+  util::StatusOr<std::unique_ptr<Mac>> Wrap(
+      std::unique_ptr<PrimitiveSet<Mac>> mac_set) const override;
 };
 
 }  // namespace tink
 }  // namespace crypto
 
-#endif  // TINK_MAC_MAC_SET_WRAPPER_H_
+#endif  // TINK_MAC_MAC_WRAPPER_H_
diff --git a/cc/mac/mac_wrapper_test.cc b/cc/mac/mac_wrapper_test.cc
new file mode 100644
index 0000000..c3b04e3
--- /dev/null
+++ b/cc/mac/mac_wrapper_test.cc
@@ -0,0 +1,150 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/mac/mac_wrapper.h"
+#include "tink/crypto_format.h"
+#include "tink/mac.h"
+#include "tink/primitive_set.h"
+#include "tink/util/status.h"
+#include "tink/util/test_util.h"
+#include "gtest/gtest.h"
+
+using crypto::tink::test::DummyMac;
+using google::crypto::tink::OutputPrefixType;
+using google::crypto::tink::Keyset;
+
+namespace crypto {
+namespace tink {
+namespace {
+
+TEST(MacWrapperTest, WrapNullptr) {
+  auto mac_result = MacWrapper().Wrap(nullptr);
+  EXPECT_FALSE(mac_result.ok());
+  EXPECT_EQ(util::error::INTERNAL, mac_result.status().error_code());
+  EXPECT_PRED_FORMAT2(testing::IsSubstring, "non-NULL",
+                      mac_result.status().error_message());
+}
+
+TEST(MacWrapperTest, WrapEmpty) {
+  std::unique_ptr<PrimitiveSet<Mac>> mac_set(new PrimitiveSet<Mac>());
+  auto mac_result = MacWrapper().Wrap(std::move(mac_set));
+  EXPECT_FALSE(mac_result.ok());
+  EXPECT_EQ(util::error::INVALID_ARGUMENT, mac_result.status().error_code());
+  EXPECT_PRED_FORMAT2(testing::IsSubstring, "no primary",
+                      mac_result.status().error_message());
+}
+
+TEST(MacWrapperTest, Basic) {
+  Keyset::Key* key;
+  Keyset keyset;
+
+  uint32_t key_id_0 = 1234543;
+  key = keyset.add_key();
+  key->set_output_prefix_type(OutputPrefixType::TINK);
+  key->set_key_id(key_id_0);
+
+  uint32_t key_id_1 = 726329;
+  key = keyset.add_key();
+  key->set_output_prefix_type(OutputPrefixType::LEGACY);
+  key->set_key_id(key_id_1);
+
+  uint32_t key_id_2 = 7213743;
+  key = keyset.add_key();
+  key->set_output_prefix_type(OutputPrefixType::TINK);
+  key->set_key_id(key_id_2);
+
+  std::string mac_name_0 = "mac0";
+  std::string mac_name_1 = "mac1";
+  std::string mac_name_2 = "mac2";
+  std::unique_ptr<PrimitiveSet<Mac>> mac_set(new PrimitiveSet<Mac>());
+  auto entry_result = mac_set->AddPrimitive(
+      absl::make_unique<DummyMac>(mac_name_0), keyset.key(0));
+  ASSERT_TRUE(entry_result.ok());
+  entry_result = mac_set->AddPrimitive(absl::make_unique<DummyMac>(mac_name_1),
+                                       keyset.key(1));
+  ASSERT_TRUE(entry_result.ok());
+  entry_result = mac_set->AddPrimitive(absl::make_unique<DummyMac>(mac_name_2),
+                                       keyset.key(2));
+  ASSERT_TRUE(entry_result.ok());
+  // The last key is the primary.
+  mac_set->set_primary(entry_result.ValueOrDie());
+
+  // Wrap mac_set and test the resulting Mac.
+  auto mac_result = MacWrapper().Wrap(std::move(mac_set));
+  EXPECT_TRUE(mac_result.ok()) << mac_result.status();
+  std::unique_ptr<Mac> mac = std::move(mac_result.ValueOrDie());
+  std::string data = "some_data_for_mac";
+
+  auto compute_mac_result = mac->ComputeMac(data);
+  EXPECT_TRUE(compute_mac_result.ok()) << compute_mac_result.status();
+  std::string mac_value = compute_mac_result.ValueOrDie();
+  EXPECT_PRED_FORMAT2(testing::IsSubstring, mac_name_2, mac_value);
+
+  util::Status status = mac->VerifyMac(mac_value, data);
+  EXPECT_TRUE(status.ok()) << status;
+
+  status = mac->VerifyMac("some bad mac", data);
+  EXPECT_FALSE(status.ok());
+  EXPECT_EQ(util::error::INVALID_ARGUMENT, status.error_code());
+  EXPECT_PRED_FORMAT2(testing::IsSubstring, "verification failed",
+                      status.error_message());
+}
+
+TEST(MacWrapperTest, testLegacyAuthentication) {
+  // Prepare a set for the wrapper.
+  Keyset::Key key;
+  uint32_t key_id = 1234543;
+  key.set_output_prefix_type(OutputPrefixType::LEGACY);
+  key.set_key_id(key_id);
+  std::string mac_name = "SomeLegacyMac";
+
+  std::unique_ptr<PrimitiveSet<Mac>> mac_set(new PrimitiveSet<Mac>());
+  std::unique_ptr<Mac> mac(new DummyMac(mac_name));
+  auto entry_result = mac_set->AddPrimitive(std::move(mac), key);
+  ASSERT_TRUE(entry_result.ok());
+  mac_set->set_primary(entry_result.ValueOrDie());
+
+  // Wrap mac_set and test the resulting Mac.
+  auto mac_result = MacWrapper().Wrap(std::move(mac_set));
+  EXPECT_TRUE(mac_result.ok()) << mac_result.status();
+  mac = std::move(mac_result.ValueOrDie());
+  std::string data = "Some data to authenticate";
+
+  // Compute and verify MAC via wrapper.
+  auto compute_mac_result = mac->ComputeMac(data);
+  EXPECT_TRUE(compute_mac_result.ok()) << compute_mac_result.status();
+  std::string mac_value = compute_mac_result.ValueOrDie();
+  EXPECT_PRED_FORMAT2(testing::IsSubstring, mac_name, mac_value);
+  auto status = mac->VerifyMac(mac_value, data);
+  EXPECT_TRUE(status.ok()) << status;
+
+  // Try verifying on raw Mac-primitive using original data.
+  std::unique_ptr<Mac> raw_mac(new DummyMac(mac_name));  // same as in wrapper
+  std::string raw_mac_value = mac_value.substr(CryptoFormat::kNonRawPrefixSize);
+  status = raw_mac->VerifyMac(raw_mac_value, data);
+  EXPECT_FALSE(status.ok());
+  EXPECT_EQ(util::error::INVALID_ARGUMENT, status.error_code());
+
+  // Verify on raw Mac-primitive using legacy-formatted data.
+  std::string legacy_data = data;
+  legacy_data.append(1, CryptoFormat::kLegacyStartByte);
+  status = raw_mac->VerifyMac(raw_mac_value, legacy_data);
+  EXPECT_TRUE(status.ok()) << status;
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/output_stream.h b/cc/output_stream.h
new file mode 100644
index 0000000..c7de9b3
--- /dev/null
+++ b/cc/output_stream.h
@@ -0,0 +1,113 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_OUTPUT_STREAM_H_
+#define TINK_OUTPUT_STREAM_H_
+
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+// Abstract interface similar to an output stream, and to
+// Protocol Buffers' google::protobuf::io::ZeroCopyOutputStream.
+class OutputStream {
+ public:
+  OutputStream() {}
+  virtual ~OutputStream() {}
+
+  // Obtains a buffer into which data can be written.  Any data written
+  // into this buffer will eventually (maybe instantly, maybe later on)
+  // be written to the output.
+  //
+  // Preconditions:
+  // * "data" is not NULL.
+  //
+  // Postconditions:
+  // * If the returned status is not OK, then an error occurred.
+  //   All errors are permanent.
+  // * Otherwise, the returned value is the actual number of bytes
+  //   in the buffer and "data" points to the buffer.
+  // * Ownership of this buffer remains with the stream, and the buffer
+  //   remains valid only until some other non-const method of the stream
+  //   is called or the stream is destroyed.
+  // * Any data which the caller stores in this buffer will eventually be
+  //   written to the output (unless BackUp() is called).
+  // * It is legal for the returned buffer to have zero size, as long
+  //   as repeatedly calling Next() eventually yields a buffer with non-zero
+  //   size.
+  virtual crypto::tink::util::StatusOr<int> Next(void** data) = 0;
+
+  // Backs up a number of bytes, so that the end of the last buffer returned
+  // by Next() is not actually written.  This is needed when you finish
+  // writing all the data you want to write, but the last buffer was bigger
+  // than you needed.  You don't want to write a bunch of garbage after the
+  // end of your data, so you use BackUp() to back up.
+  //
+  // Preconditions:
+  // * The last call to Next() must have returned status OK.
+  //   If there was no Next()-call yet, or the last one failed,
+  //   BackUp()-call is ignored.
+  // * count must be less than or equal to the size of the last buffer
+  //   returned by Next().  Non-positive count is ignored (no action on this
+  //   stream), and count larger than the size of the last buffer is treated
+  //   as equal to the size of the last buffer.
+  // * The caller must not have written anything to the last "count" bytes
+  //   of that buffer.
+  //
+  // Postconditions:
+  // * The last "count" bytes of the last buffer returned by Next() will be
+  //   ignored.
+  // * Repeated calls to BackUp() accumulate:
+  //      BackUp(a);
+  //      Backup(b);
+  //   is equivalent to
+  //      Backup(c);
+  //   with c = max(0, a) + max(0, b).
+  // * The actual result of BackUp()-call can be verified via Position().
+  virtual void BackUp(int count) = 0;
+
+  // Closes this output stream. It flushes all the data from the last buffer
+  // returned by Next(), (except the ones backed up via BackUp(), if any)
+  // Returns a non-OK status if some error occurred.
+  //
+  // Preconditions:
+  // * The stream is not closed yet,
+  // * The last call to Next() did not return an error.
+  //
+  // Postconditions:
+  // * Writing to the stream is not possible any more, and all calls
+  //   to non-const methods will fail.
+  virtual crypto::tink::util::Status Close() = 0;
+
+  // Returns the total number of bytes written since this object was created.
+  // Preconditions:
+  // * The most recent call to Next() (if any) was successful, or the stream
+  //   was successfully closed.
+  //
+  // Postconditions:
+  // * The returned position includes the bytes from the most recent
+  //   successful call to Next(), excluding the ones that were backed up
+  //   via BackUp() (if any).
+  // * If the last call to Next() ended with a failure, -1 is returned;
+  virtual int64_t Position() const = 0;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_OUTPUT_STREAM_H_
diff --git a/cc/primitive_set.h b/cc/primitive_set.h
index 8e81a29..c782794 100644
--- a/cc/primitive_set.h
+++ b/cc/primitive_set.h
@@ -17,11 +17,11 @@
 #ifndef TINK_PRIMITIVE_SET_H_
 #define TINK_PRIMITIVE_SET_H_
 
-#include <mutex>  // NOLINT(build/c++11)
 #include <unordered_map>
 #include <vector>
 
 #include "absl/memory/memory.h"
+#include "absl/synchronization/mutex.h"
 #include "tink/crypto_format.h"
 #include "tink/util/errors.h"
 #include "tink/util/statusor.h"
@@ -56,10 +56,12 @@
   class Entry {
    public:
     Entry(std::unique_ptr<P2> primitive, const std::string& identifier,
-          google::crypto::tink::KeyStatusType status)
+          google::crypto::tink::KeyStatusType status,
+          google::crypto::tink::OutputPrefixType output_prefix_type)
         : primitive_(std::move(primitive)),
           identifier_(identifier),
-          status_(status) {}
+          status_(status),
+          output_prefix_type_(output_prefix_type) {}
 
     P2& get_primitive() const { return *primitive_; }
 
@@ -69,10 +71,16 @@
       return status_;
     }
 
+    const google::crypto::tink::OutputPrefixType get_output_prefix_type()
+        const {
+      return output_prefix_type_;
+    }
+
    private:
     std::unique_ptr<P> primitive_;
     std::string identifier_;
     google::crypto::tink::KeyStatusType status_;
+    google::crypto::tink::OutputPrefixType output_prefix_type_;
   };
 
   typedef std::vector<std::unique_ptr<Entry<P>>> Primitives;
@@ -90,17 +98,18 @@
                        "The primitive must be non-null.");
     }
     std::string identifier = identifier_result.ValueOrDie();
-    std::lock_guard<std::mutex> lock(primitives_mutex_);
+    absl::MutexLock lock(&primitives_mutex_);
     primitives_[identifier].push_back(
         absl::make_unique<Entry<P>>(std::move(primitive),
-                                    identifier, key.status()));
+                                    identifier, key.status(),
+                                    key.output_prefix_type()));
     return primitives_[identifier].back().get();
   }
 
   // Returns the entries with primitives identifed by 'identifier'.
   crypto::tink::util::StatusOr<const Primitives*> get_primitives(
       const std::string& identifier) {
-    std::lock_guard<std::mutex> lock(primitives_mutex_);
+    absl::MutexLock lock(&primitives_mutex_);
     typename CiphertextPrefixToPrimitivesMap::iterator found =
         primitives_.find(identifier);
     if (found == primitives_.end()) {
@@ -126,8 +135,8 @@
   typedef std::unordered_map<std::string, Primitives>
       CiphertextPrefixToPrimitivesMap;
   Entry<P>* primary_;  // the Entry<P> object is owned by primitives_
-  std::mutex primitives_mutex_;
-  CiphertextPrefixToPrimitivesMap primitives_;  // guarded by primitives_mutex_
+  absl::Mutex primitives_mutex_;
+  CiphertextPrefixToPrimitivesMap primitives_ GUARDED_BY(primitives_mutex_);
 };
 
 }  // namespace tink
diff --git a/cc/primitive_wrapper.h b/cc/primitive_wrapper.h
new file mode 100644
index 0000000..c03e756
--- /dev/null
+++ b/cc/primitive_wrapper.h
@@ -0,0 +1,45 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_SET_WRAPPER_FACTORY_H_
+#define TINK_SET_WRAPPER_FACTORY_H_
+
+#include <memory>
+
+#include "tink/primitive_set.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+// A PrimitiveWrapper knows how to wrap multiple instances of a primitive to
+// a single instance, enabling key-rotation. It requires a
+// PrimitiveSet<Primitive> and wraps it into a single primitive.
+//
+// PrimitiveWrappers need to be written for every new primitive. They can be
+// registered in the registry to be fully integrated in tink.
+template <class Primitive>
+class PrimitiveWrapper {
+ public:
+  virtual ~PrimitiveWrapper() {}
+  virtual crypto::tink::util::StatusOr<std::unique_ptr<Primitive>> Wrap(
+      std::unique_ptr<PrimitiveSet<Primitive>> primitive_set) const = 0;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_SET_WRAPPER_FACTORY_H_
diff --git a/cc/public_key_sign.h b/cc/public_key_sign.h
index 8361aa5..38554c1 100644
--- a/cc/public_key_sign.h
+++ b/cc/public_key_sign.h
@@ -14,8 +14,8 @@
 //
 ///////////////////////////////////////////////////////////////////////////////
 
-#ifndef PUBLIC_KEY_SIGN_H_
-#define PUBLIC_KEY_SIGN_H_
+#ifndef TINK_PUBLIC_KEY_SIGN_H_
+#define TINK_PUBLIC_KEY_SIGN_H_
 
 #include "absl/strings/string_view.h"
 #include "tink/util/statusor.h"
@@ -43,4 +43,4 @@
 }  // namespace tink
 }  // namespace crypto
 
-#endif  // PUBLIC_KEY_SIGN_H_
+#endif  // TINK_PUBLIC_KEY_SIGN_H_
diff --git a/cc/public_key_verify.h b/cc/public_key_verify.h
index b225434..7883caf 100644
--- a/cc/public_key_verify.h
+++ b/cc/public_key_verify.h
@@ -14,8 +14,8 @@
 //
 ///////////////////////////////////////////////////////////////////////////////
 
-#ifndef PUBLIC_KEY_VERIFY_H_
-#define PUBLIC_KEY_VERIFY_H_
+#ifndef TINK_PUBLIC_KEY_VERIFY_H_
+#define TINK_PUBLIC_KEY_VERIFY_H_
 
 #include "absl/strings/string_view.h"
 #include "tink/util/status.h"
@@ -44,4 +44,4 @@
 }  // namespace tink
 }  // namespace crypto
 
-#endif  // PUBLIC_KEY_VERIFY_H_
+#endif  // TINK_PUBLIC_KEY_VERIFY_H_
diff --git a/cc/registry.h b/cc/registry.h
index 7969223..5bc6df7 100644
--- a/cc/registry.h
+++ b/cc/registry.h
@@ -17,19 +17,12 @@
 #ifndef TINK_REGISTRY_H_
 #define TINK_REGISTRY_H_
 
-#include <mutex>  // NOLINT(build/c++11)
-#include <typeinfo>
-#include <unordered_map>
+#include <memory>
+#include <string>
 
-#include "tink/catalogue.h"
-#include "tink/key_manager.h"
-#include "tink/keyset_handle.h"
-#include "tink/primitive_set.h"
-#include "tink/util/errors.h"
-#include "tink/util/protobuf_helper.h"
+#include "tink/core/registry_impl.h"
 #include "tink/util/status.h"
-#include "tink/util/validation.h"
-#include "proto/tink.pb.h"
+#include "tink/util/statusor.h"
 
 namespace crypto {
 namespace tink {
@@ -62,7 +55,9 @@
   // see https://goo.gl/x0ymDz)
   template <class P>
   static crypto::tink::util::StatusOr<const Catalogue<P>*> get_catalogue(
-      const std::string& catalogue_name);
+      const std::string& catalogue_name) {
+    return RegistryImpl::GlobalInstance().get_catalogue<P>(catalogue_name);
+  }
 
   // Adds the given 'catalogue' under the specified 'catalogue_name',
   // to enable custom configuration of key types and key managers.
@@ -70,22 +65,56 @@
   // Adding a custom catalogue should be a one-time operation,
   // and fails if the given 'catalogue' tries to override
   // an existing, different catalogue for the specified name.
-  //
-  // Takes ownership of 'catalogue', which must be non-nullptr
-  // (in case of failure, 'catalogue' is deleted).
-  template <class P>
+  template <class ConcreteCatalogue>
   static crypto::tink::util::Status AddCatalogue(
-      const std::string& catalogue_name, Catalogue<P>* catalogue);
+      const std::string& catalogue_name,
+      std::unique_ptr<ConcreteCatalogue> catalogue) {
+    return RegistryImpl::GlobalInstance().AddCatalogue(catalogue_name,
+                                                       catalogue.release());
+  }
+
+  // AddCatalogue has the same functionality as the overload which uses a
+  // unique_ptr and which should be preferred.
+  //
+  // Takes ownership of 'catalogue', which must be non-nullptr (in case of
+  // failure, 'catalogue' is deleted).
+  template <class P>
+  ABSL_DEPRECATED("Use AddCatalogue with a unique_ptr input instead.")
+  static crypto::tink::util::Status AddCatalogue(const std::string& catalogue_name,
+                                                 Catalogue<P>* catalogue) {
+    return AddCatalogue(catalogue_name, absl::WrapUnique(catalogue));
+  }
 
   // Registers the given 'manager' for the key type 'manager->get_key_type()'.
-  // Takes ownership of 'manager', which must be non-nullptr.
-  template <class P>
+  template <class ConcreteKeyManager>
   static crypto::tink::util::Status RegisterKeyManager(
-      KeyManager<P>* manager, bool new_key_allowed);
+      std::unique_ptr<ConcreteKeyManager> manager, bool new_key_allowed) {
+    return RegistryImpl::GlobalInstance().RegisterKeyManager(manager.release(),
+                                                             new_key_allowed);
+  }
+
+  // Same functionality as the overload which takes a unique pointer, for
+  // new_key_allowed = true.
+  template <class P>
+  ABSL_DEPRECATED(
+      "Use RegisterKeyManager with a unique_ptr manager and new_key_allowed = "
+      "true instead.")
+  static crypto::tink::util::Status RegisterKeyManager(KeyManager<P>* manager) {
+    return RegisterKeyManager(absl::WrapUnique(manager), true);
+  }
 
   template <class P>
-  static crypto::tink::util::Status RegisterKeyManager(KeyManager<P>* manager) {
-    return RegisterKeyManager(manager, /* new_key_allowed= */ true);
+  ABSL_DEPRECATED("Use RegisterKeyManager with a unique_ptr manager instead.")
+  static crypto::tink::util::Status RegisterKeyManager(KeyManager<P>* manager,
+                                                       bool new_key_allowed) {
+    return RegisterKeyManager(absl::WrapUnique(manager), new_key_allowed);
+  }
+
+  template <class ConcretePrimitiveWrapper>
+  static crypto::tink::util::Status RegisterPrimitiveWrapper(
+      std::unique_ptr<ConcretePrimitiveWrapper> wrapper) {
+    return RegistryImpl::GlobalInstance().RegisterPrimitiveWrapper(
+        wrapper.release());
   }
 
   // Returns a key manager for the given type_url (if any found).
@@ -96,267 +125,63 @@
   // see https://goo.gl/x0ymDz)
   template <class P>
   static crypto::tink::util::StatusOr<const KeyManager<P>*> get_key_manager(
-      const std::string& type_url);
+      const std::string& type_url) {
+    return RegistryImpl::GlobalInstance().get_key_manager<P>(type_url);
+  }
 
   // Convenience method for creating a new primitive for the key given
   // in 'key_data'.  It looks up a KeyManager identified by key_data.type_url,
   // and calls manager's GetPrimitive(key_data)-method.
   template <class P>
   static crypto::tink::util::StatusOr<std::unique_ptr<P>> GetPrimitive(
-      const google::crypto::tink::KeyData& key_data);
-
+      const google::crypto::tink::KeyData& key_data) {
+    return RegistryImpl::GlobalInstance().GetPrimitive<P>(key_data);
+  }
   // Convenience method for creating a new primitive for the key given
   // in 'key'.  It looks up a KeyManager identified by type_url,
   // and calls manager's GetPrimitive(key)-method.
   template <class P>
   static crypto::tink::util::StatusOr<std::unique_ptr<P>> GetPrimitive(
-      const std::string& type_url, const portable_proto::MessageLite& key);
-
-  // Creates a set of primitives corresponding to the keys with
-  // (status == ENABLED) in the keyset given in 'keyset_handle',
-  // assuming all the corresponding key managers are present (keys
-  // with (status != ENABLED) are skipped).
-  //
-  // The returned set is usually later "wrapped" into a class that
-  // implements the corresponding Primitive-interface.
-  template <class P>
-  static crypto::tink::util::StatusOr<std::unique_ptr<PrimitiveSet<P>>>
-  GetPrimitives(const KeysetHandle& keyset_handle,
-                const KeyManager<P>* custom_manager);
+      const std::string& type_url, const portable_proto::MessageLite& key) {
+    return RegistryImpl::GlobalInstance().GetPrimitive<P>(type_url, key);
+  }
 
   // Generates a new KeyData for the specified 'key_template'.
   // It looks up a KeyManager identified by key_template.type_url,
   // and calls KeyManager::NewKeyData.
   // This method should be used solely for key management.
   static crypto::tink::util::StatusOr<
-    std::unique_ptr<google::crypto::tink::KeyData>>
-  NewKeyData(const google::crypto::tink::KeyTemplate& key_template);
+      std::unique_ptr<google::crypto::tink::KeyData>>
+  NewKeyData(const google::crypto::tink::KeyTemplate& key_template) {
+    return RegistryImpl::GlobalInstance().NewKeyData(key_template);
+  }
 
   // Convenience method for extracting the public key data from the
   // private key given in serialized_private_key.
-  // It looks up a KeyManager identified by type_url, which must
-  // be a PrivateKeyManager, and calls PrivateKeyManager::GetPublicKeyData.
+  // It looks up a KeyManager identified by type_url, whose KeyFactory must be
+  // a PrivateKeyFactory, and calls PrivateKeyFactory::GetPublicKeyData.
   static crypto::tink::util::StatusOr<
-    std::unique_ptr<google::crypto::tink::KeyData>>
+      std::unique_ptr<google::crypto::tink::KeyData>>
   GetPublicKeyData(const std::string& type_url,
-                   const std::string& serialized_private_key);
+                   const std::string& serialized_private_key) {
+    return RegistryImpl::GlobalInstance().GetPublicKeyData(
+        type_url, serialized_private_key);
+  }
+
+  // Looks up the globally registered PrimitiveWrapper for this primitive
+  // and wraps the given PrimitiveSet with it.
+  template <class P>
+  static crypto::tink::util::StatusOr<std::unique_ptr<P>> Wrap(
+      std::unique_ptr<PrimitiveSet<P>> primitive_set) {
+    return RegistryImpl::GlobalInstance().Wrap<P>(std::move(primitive_set));
+  }
 
   // Resets the registry.
   // After reset the registry is empty, i.e. it contains neither catalogues
   // nor key managers. This method is intended for testing only.
-  static void Reset();
-
- private:
-  typedef std::unordered_map<std::string,
-                             std::unique_ptr<void, void (*)(void*)>>
-      LabelToObjectMap;
-  typedef std::unordered_map<std::string, const char*> LabelToTypeNameMap;
-  typedef std::unordered_map<std::string, bool> LabelToBoolMap;
-  typedef std::unordered_map<std::string, const KeyFactory*>
-      LabelToKeyFactoryMap;
-
-  static std::recursive_mutex maps_mutex_;
-  // Maps for key manager data.
-  static LabelToObjectMap type_to_manager_map_;      // guarded by maps_mutex_
-  static LabelToTypeNameMap type_to_primitive_map_;  // guarded by maps_mutex_
-  static LabelToBoolMap type_to_new_key_allowed_map_;        // by maps_mutex_
-  static LabelToKeyFactoryMap type_to_key_factory_map_;      // by maps_mutex_
-  // Maps for catalogue-data.
-  static LabelToObjectMap name_to_catalogue_map_;    // guarded by maps_mutex_
-  static LabelToTypeNameMap name_to_primitive_map_;  // guarded by maps_mutex_
-
-  static crypto::tink::util::StatusOr<bool> get_new_key_allowed(
-      const std::string& type_url);
-  static crypto::tink::util::StatusOr<const KeyFactory*> get_key_factory(
-      const std::string& type_url);
+  static void Reset() { return RegistryImpl::GlobalInstance().Reset(); }
 };
 
-///////////////////////////////////////////////////////////////////////////////
-// Implementation details.
-
-template <class P>
-void delete_manager(void* t) {
-  delete static_cast<KeyManager<P>*>(t);
-}
-
-template <class P>
-void delete_catalogue(void* t) {
-  delete static_cast<Catalogue<P>*>(t);
-}
-
-// static
-template <class P>
-crypto::tink::util::Status Registry::AddCatalogue(
-    const std::string& catalogue_name, Catalogue<P>* catalogue) {
-  if (catalogue == nullptr) {
-    return crypto::tink::util::Status(
-        crypto::tink::util::error::INVALID_ARGUMENT,
-        "Parameter 'catalogue' must be non-null.");
-  }
-  std::unique_ptr<void, void (*)(void*)> entry(catalogue, delete_catalogue<P>);
-  std::lock_guard<std::recursive_mutex> lock(maps_mutex_);
-  auto curr_catalogue = name_to_catalogue_map_.find(catalogue_name);
-  if (curr_catalogue != name_to_catalogue_map_.end()) {
-    auto existing = static_cast<Catalogue<P>*>(curr_catalogue->second.get());
-    if (typeid(*existing).name() != typeid(*catalogue).name()) {
-      return ToStatusF(crypto::tink::util::error::ALREADY_EXISTS,
-                       "A catalogue named '%s' has been already added.",
-                       catalogue_name.c_str());
-    }
-  } else {
-    name_to_catalogue_map_.insert(
-        std::make_pair(catalogue_name, std::move(entry)));
-    name_to_primitive_map_.insert(
-        std::make_pair(catalogue_name, typeid(P).name()));
-  }
-  return crypto::tink::util::Status::OK;
-}
-
-// static
-template <class P>
-crypto::tink::util::StatusOr<const Catalogue<P>*> Registry::get_catalogue(
-    const std::string& catalogue_name) {
-  std::lock_guard<std::recursive_mutex> lock(maps_mutex_);
-  auto catalogue_entry = name_to_catalogue_map_.find(catalogue_name);
-  if (catalogue_entry == name_to_catalogue_map_.end()) {
-    return ToStatusF(crypto::tink::util::error::NOT_FOUND,
-                     "No catalogue named '%s' has been added.",
-                     catalogue_name.c_str());
-  }
-  if (name_to_primitive_map_[catalogue_name] != typeid(P).name()) {
-    return ToStatusF(crypto::tink::util::error::INVALID_ARGUMENT,
-                     "Wrong Primitive type for catalogue named '%s': "
-                     "got '%s', expected '%s'",
-                     catalogue_name.c_str(), typeid(P).name(),
-                     name_to_primitive_map_[catalogue_name]);
-  }
-  return static_cast<Catalogue<P>*>(catalogue_entry->second.get());
-}
-
-// static
-template <class P>
-crypto::tink::util::Status Registry::RegisterKeyManager(
-    KeyManager<P>* manager, bool new_key_allowed) {
-  if (manager == nullptr) {
-    return crypto::tink::util::Status(
-        crypto::tink::util::error::INVALID_ARGUMENT,
-        "Parameter 'manager' must be non-null.");
-  }
-  std::string type_url = manager->get_key_type();
-  std::unique_ptr<void, void (*)(void*)> entry(manager, delete_manager<P>);
-  if (!manager->DoesSupport(type_url)) {
-    return ToStatusF(crypto::tink::util::error::INVALID_ARGUMENT,
-                     "The manager does not support type '%s'.",
-                     type_url.c_str());
-  }
-  std::lock_guard<std::recursive_mutex> lock(maps_mutex_);
-  auto curr_manager = type_to_manager_map_.find(type_url);
-  if (curr_manager != type_to_manager_map_.end()) {
-    auto existing = static_cast<KeyManager<P>*>(curr_manager->second.get());
-    if (typeid(*existing).name() != typeid(*manager).name()) {
-      return ToStatusF(crypto::tink::util::error::ALREADY_EXISTS,
-                       "A manager for type '%s' has been already registered.",
-                       type_url.c_str());
-    } else {
-      auto curr_new_key_allowed = type_to_new_key_allowed_map_.find(type_url);
-      if (!curr_new_key_allowed->second && new_key_allowed) {
-        return ToStatusF(crypto::tink::util::error::ALREADY_EXISTS,
-                         "A manager for type '%s' has been already registered "
-                         "with forbidden new key operation.",
-                         type_url.c_str());
-      } else {
-        curr_new_key_allowed->second = new_key_allowed;
-      }
-    }
-  } else {
-    type_to_manager_map_.insert(
-        std::make_pair(type_url, std::move(entry)));
-    type_to_primitive_map_.insert(
-        std::make_pair(type_url, typeid(P).name()));
-    type_to_new_key_allowed_map_.insert(
-        std::make_pair(type_url, new_key_allowed));
-    type_to_key_factory_map_.insert(
-        std::make_pair(type_url, &(manager->get_key_factory())));
-  }
-  return crypto::tink::util::Status::OK;
-}
-
-// static
-template <class P>
-crypto::tink::util::StatusOr<const KeyManager<P>*> Registry::get_key_manager(
-    const std::string& type_url) {
-  std::lock_guard<std::recursive_mutex> lock(maps_mutex_);
-  auto manager_entry = type_to_manager_map_.find(type_url);
-  if (manager_entry == type_to_manager_map_.end()) {
-    return ToStatusF(crypto::tink::util::error::NOT_FOUND,
-                     "No manager for type '%s' has been registered.",
-                     type_url.c_str());
-  }
-  if (type_to_primitive_map_[type_url] != typeid(P).name()) {
-    return ToStatusF(crypto::tink::util::error::INVALID_ARGUMENT,
-                     "Wrong Primitive type for key type '%s': "
-                     "got '%s', expected '%s'",
-                     type_url.c_str(), typeid(P).name(),
-                     type_to_primitive_map_[type_url]);
-  }
-  return static_cast<KeyManager<P>*>(manager_entry->second.get());
-}
-
-// static
-template <class P>
-crypto::tink::util::StatusOr<std::unique_ptr<P>> Registry::GetPrimitive(
-    const google::crypto::tink::KeyData& key_data) {
-  auto key_manager_result = get_key_manager<P>(key_data.type_url());
-  if (key_manager_result.ok()) {
-    return key_manager_result.ValueOrDie()->GetPrimitive(key_data);
-  }
-  return key_manager_result.status();
-}
-
-// static
-template <class P>
-crypto::tink::util::StatusOr<std::unique_ptr<P>> Registry::GetPrimitive(
-    const std::string& type_url, const portable_proto::MessageLite& key) {
-  auto key_manager_result = get_key_manager<P>(type_url);
-  if (key_manager_result.ok()) {
-    return key_manager_result.ValueOrDie()->GetPrimitive(key);
-  }
-  return key_manager_result.status();
-}
-
-// static
-template <class P>
-crypto::tink::util::StatusOr<std::unique_ptr<PrimitiveSet<P>>>
-Registry::GetPrimitives(const KeysetHandle& keyset_handle,
-                        const KeyManager<P>* custom_manager) {
-  crypto::tink::util::Status status =
-      ValidateKeyset(keyset_handle.get_keyset());
-  if (!status.ok()) return status;
-  std::unique_ptr<PrimitiveSet<P>> primitives(new PrimitiveSet<P>());
-  for (const google::crypto::tink::Keyset::Key& key :
-       keyset_handle.get_keyset().key()) {
-    if (key.status() == google::crypto::tink::KeyStatusType::ENABLED) {
-      std::unique_ptr<P> primitive;
-      if (custom_manager != nullptr &&
-          custom_manager->DoesSupport(key.key_data().type_url())) {
-        auto primitive_result = custom_manager->GetPrimitive(key.key_data());
-        if (!primitive_result.ok()) return primitive_result.status();
-        primitive = std::move(primitive_result.ValueOrDie());
-      } else {
-        auto primitive_result = GetPrimitive<P>(key.key_data());
-        if (!primitive_result.ok()) return primitive_result.status();
-        primitive = std::move(primitive_result.ValueOrDie());
-      }
-      auto entry_result = primitives->AddPrimitive(std::move(primitive), key);
-      if (!entry_result.ok()) return entry_result.status();
-      if (key.key_id() == keyset_handle.get_keyset().primary_key_id()) {
-        primitives->set_primary(entry_result.ValueOrDie());
-      }
-    }
-  }
-  return std::move(primitives);
-}
-
 }  // namespace tink
 }  // namespace crypto
 
diff --git a/cc/signature/BUILD.bazel b/cc/signature/BUILD.bazel
index 402950a..683593a 100644
--- a/cc/signature/BUILD.bazel
+++ b/cc/signature/BUILD.bazel
@@ -3,15 +3,15 @@
 licenses(["notice"])  # Apache 2.0
 
 cc_library(
-    name = "public_key_verify_set_wrapper",
-    srcs = ["public_key_verify_set_wrapper.cc"],
-    hdrs = ["public_key_verify_set_wrapper.h"],
+    name = "public_key_verify_wrapper",
+    srcs = ["public_key_verify_wrapper.cc"],
+    hdrs = ["public_key_verify_wrapper.h"],
     include_prefix = "tink",
     strip_include_prefix = "/cc",
-    visibility = ["//visibility:private"],
     deps = [
         "//cc:crypto_format",
         "//cc:primitive_set",
+        "//cc:primitive_wrapper",
         "//cc:public_key_verify",
         "//cc/subtle:subtle_util_boringssl",
         "//cc/util:status",
@@ -28,7 +28,7 @@
     include_prefix = "tink",
     strip_include_prefix = "/cc",
     deps = [
-        ":public_key_verify_set_wrapper",
+        ":public_key_verify_wrapper",
         "//cc:key_manager",
         "//cc:keyset_handle",
         "//cc:primitive_set",
@@ -36,19 +36,20 @@
         "//cc:registry",
         "//cc/util:status",
         "//cc/util:statusor",
+        "@com_google_absl//absl/base:core_headers",
     ],
 )
 
 cc_library(
-    name = "public_key_sign_set_wrapper",
-    srcs = ["public_key_sign_set_wrapper.cc"],
-    hdrs = ["public_key_sign_set_wrapper.h"],
+    name = "public_key_sign_wrapper",
+    srcs = ["public_key_sign_wrapper.cc"],
+    hdrs = ["public_key_sign_wrapper.h"],
     include_prefix = "tink",
     strip_include_prefix = "/cc",
-    visibility = ["//visibility:private"],
     deps = [
         "//cc:crypto_format",
         "//cc:primitive_set",
+        "//cc:primitive_wrapper",
         "//cc:public_key_sign",
         "//cc/subtle:subtle_util_boringssl",
         "//cc/util:status",
@@ -65,7 +66,7 @@
     include_prefix = "tink",
     strip_include_prefix = "/cc",
     deps = [
-        ":public_key_sign_set_wrapper",
+        ":public_key_sign_wrapper",
         "//cc:key_manager",
         "//cc:keyset_handle",
         "//cc:primitive_set",
@@ -73,6 +74,7 @@
         "//cc:registry",
         "//cc/util:status",
         "//cc/util:statusor",
+        "@com_google_absl//absl/base:core_headers",
     ],
 )
 
@@ -83,9 +85,17 @@
     include_prefix = "tink",
     strip_include_prefix = "/cc",
     deps = [
+        "//cc/subtle:subtle_util_boringssl",
+        "//cc/util:constants",
         "//proto:common_cc_proto",
         "//proto:ecdsa_cc_proto",
+        "//proto:ed25519_cc_proto",
+        "//proto:rsa_ssa_pkcs1_cc_proto",
+        "//proto:rsa_ssa_pss_cc_proto",
         "//proto:tink_cc_proto",
+        "@boringssl//:crypto",
+        "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/strings",
     ],
 )
 
@@ -98,6 +108,7 @@
     deps = [
         ":ecdsa_verify_key_manager",
         "//cc:key_manager",
+        "//cc:key_manager_base",
         "//cc:public_key_sign",
         "//cc:registry",
         "//cc/subtle:ecdsa_sign_boringssl",
@@ -123,6 +134,7 @@
     strip_include_prefix = "/cc",
     deps = [
         "//cc:key_manager",
+        "//cc:key_manager_base",
         "//cc:public_key_verify",
         "//cc:registry",
         "//cc/subtle:ecdsa_verify_boringssl",
@@ -139,6 +151,148 @@
 )
 
 cc_library(
+    name = "ed25519_sign_key_manager",
+    srcs = ["ed25519_sign_key_manager.cc"],
+    hdrs = ["ed25519_sign_key_manager.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        ":ed25519_verify_key_manager",
+        "//cc:key_manager",
+        "//cc:key_manager_base",
+        "//cc:public_key_sign",
+        "//cc/subtle:ed25519_sign_boringssl",
+        "//cc/subtle:subtle_util_boringssl",
+        "//cc/util:enums",
+        "//cc/util:errors",
+        "//cc/util:protobuf_helper",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "//cc/util:validation",
+        "//proto:ed25519_cc_proto",
+        "//proto:empty_cc_proto",
+        "//proto:tink_cc_proto",
+        "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_library(
+    name = "ed25519_verify_key_manager",
+    srcs = ["ed25519_verify_key_manager.cc"],
+    hdrs = ["ed25519_verify_key_manager.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        "//cc:key_manager",
+        "//cc:key_manager_base",
+        "//cc:public_key_verify",
+        "//cc/subtle:ed25519_verify_boringssl",
+        "//cc/subtle:subtle_util_boringssl",
+        "//cc/util:errors",
+        "//cc/util:protobuf_helper",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "//cc/util:validation",
+        "//proto:ed25519_cc_proto",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_library(
+    name = "sig_util",
+    srcs = ["sig_util.cc"],
+    hdrs = ["sig_util.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        "//cc:public_key_sign",
+        "//cc:public_key_verify",
+        "//cc/util:status",
+    ],
+)
+
+cc_library(
+    name = "rsa_ssa_pkcs1_sign_key_manager",
+    srcs = ["rsa_ssa_pkcs1_sign_key_manager.cc"],
+    hdrs = ["rsa_ssa_pkcs1_sign_key_manager.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        ":rsa_ssa_pkcs1_verify_key_manager",
+        ":sig_util",
+        "//cc:key_manager",
+        "//cc:key_manager_base",
+        "//cc:public_key_sign",
+        "//cc:registry",
+        "//cc/subtle:rsa_ssa_pkcs1_sign_boringssl",
+        "//cc/subtle:subtle_util_boringssl",
+        "//cc/util:enums",
+        "//cc/util:errors",
+        "//cc/util:protobuf_helper",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "//proto:common_cc_proto",
+        "//proto:rsa_ssa_pkcs1_cc_proto",
+        "//proto:tink_cc_proto",
+        "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_library(
+    name = "rsa_ssa_pkcs1_verify_key_manager",
+    srcs = ["rsa_ssa_pkcs1_verify_key_manager.cc"],
+    hdrs = ["rsa_ssa_pkcs1_verify_key_manager.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        "//cc:key_manager",
+        "//cc:key_manager_base",
+        "//cc:public_key_verify",
+        "//cc:registry",
+        "//cc/subtle:rsa_ssa_pkcs1_verify_boringssl",
+        "//cc/subtle:subtle_util_boringssl",
+        "//cc/util:enums",
+        "//cc/util:errors",
+        "//cc/util:protobuf_helper",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "//proto:common_cc_proto",
+        "//proto:rsa_ssa_pkcs1_cc_proto",
+        "//proto:tink_cc_proto",
+    ],
+)
+
+cc_library(
+    name = "rsa_ssa_pss_sign_key_manager",
+    srcs = ["rsa_ssa_pss_sign_key_manager.cc"],
+    hdrs = ["rsa_ssa_pss_sign_key_manager.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        ":rsa_ssa_pss_verify_key_manager",
+        ":sig_util",
+        "//cc:key_manager",
+        "//cc:key_manager_base",
+        "//cc:public_key_sign",
+        "//cc:registry",
+        "//cc/subtle:rsa_ssa_pss_sign_boringssl",
+        "//cc/subtle:subtle_util_boringssl",
+        "//cc/util:enums",
+        "//cc/util:errors",
+        "//cc/util:protobuf_helper",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "//proto:common_cc_proto",
+        "//proto:rsa_ssa_pss_cc_proto",
+        "//proto:tink_cc_proto",
+        "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_library(
     name = "rsa_ssa_pss_verify_key_manager",
     srcs = ["rsa_ssa_pss_verify_key_manager.cc"],
     hdrs = ["rsa_ssa_pss_verify_key_manager.h"],
@@ -146,6 +300,7 @@
     strip_include_prefix = "/cc",
     deps = [
         "//cc:key_manager",
+        "//cc:key_manager_base",
         "//cc:public_key_verify",
         "//cc:registry",
         "//cc/subtle:rsa_ssa_pss_verify_boringssl",
@@ -169,6 +324,9 @@
     strip_include_prefix = "/cc",
     deps = [
         ":ecdsa_sign_key_manager",
+        ":ed25519_sign_key_manager",
+        ":rsa_ssa_pkcs1_sign_key_manager",
+        ":rsa_ssa_pss_sign_key_manager",
         "//cc:catalogue",
         "//cc:key_manager",
         "//cc:public_key_sign",
@@ -185,6 +343,9 @@
     strip_include_prefix = "/cc",
     deps = [
         ":ecdsa_verify_key_manager",
+        ":ed25519_verify_key_manager",
+        ":rsa_ssa_pkcs1_verify_key_manager",
+        ":rsa_ssa_pss_verify_key_manager",
         "//cc:catalogue",
         "//cc:key_manager",
         "//cc:public_key_verify",
@@ -206,18 +367,19 @@
         "//cc:registry",
         "//cc/util:status",
         "//proto:config_cc_proto",
+        "@com_google_absl//absl/memory",
     ],
 )
 
 # tests
 
 cc_test(
-    name = "public_key_verify_set_wrapper_test",
+    name = "public_key_verify_wrapper_test",
     size = "small",
-    srcs = ["public_key_verify_set_wrapper_test.cc"],
+    srcs = ["public_key_verify_wrapper_test.cc"],
     copts = ["-Iexternal/gtest/include"],
     deps = [
-        ":public_key_verify_set_wrapper",
+        ":public_key_verify_wrapper",
         "//cc:primitive_set",
         "//cc:public_key_sign",
         "//cc:public_key_verify",
@@ -251,12 +413,13 @@
 )
 
 cc_test(
-    name = "public_key_sign_set_wrapper_test",
+    name = "public_key_sign_wrapper_test",
     size = "small",
-    srcs = ["public_key_sign_set_wrapper_test.cc"],
+    srcs = ["public_key_sign_wrapper_test.cc"],
     copts = ["-Iexternal/gtest/include"],
     deps = [
-        ":public_key_sign_set_wrapper",
+        ":public_key_sign_wrapper",
+        "//cc:crypto_format",
         "//cc:primitive_set",
         "//cc:public_key_sign",
         "//cc:public_key_verify",
@@ -312,6 +475,51 @@
 )
 
 cc_test(
+    name = "ed25519_verify_key_manager_test",
+    size = "small",
+    srcs = ["ed25519_verify_key_manager_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    deps = [
+        ":ed25519_sign_key_manager",
+        ":ed25519_verify_key_manager",
+        "//cc:public_key_sign",
+        "//cc:public_key_verify",
+        "//cc:registry",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "//cc/util:test_util",
+        "//proto:aes_eax_cc_proto",
+        "//proto:common_cc_proto",
+        "//proto:ed25519_cc_proto",
+        "//proto:empty_cc_proto",
+        "//proto:tink_cc_proto",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "rsa_ssa_pkcs1_verify_key_manager_test",
+    size = "small",
+    srcs = ["rsa_ssa_pkcs1_verify_key_manager_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    deps = [
+        ":rsa_ssa_pkcs1_verify_key_manager",
+        "//cc:config",
+        "//cc:public_key_sign",
+        "//cc:public_key_verify",
+        "//cc:registry",
+        "//cc/util:status",
+        "//cc/util:test_util",
+        "//proto:aes_eax_cc_proto",
+        "//proto:common_cc_proto",
+        "//proto:rsa_ssa_pkcs1_cc_proto",
+        "//proto:tink_cc_proto",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
     name = "rsa_ssa_pss_verify_key_manager_test",
     size = "small",
     srcs = ["rsa_ssa_pss_verify_key_manager_test.cc"],
@@ -358,6 +566,84 @@
 )
 
 cc_test(
+    name = "ed25519_sign_key_manager_test",
+    size = "small",
+    srcs = ["ed25519_sign_key_manager_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    deps = [
+        ":ed25519_sign_key_manager",
+        ":ed25519_verify_key_manager",
+        ":signature_key_templates",
+        "//cc:public_key_sign",
+        "//cc:registry",
+        "//cc/aead:aead_key_templates",
+        "//cc/aead:aes_gcm_key_manager",
+        "//cc/util:protobuf_helper",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "//cc/util:test_util",
+        "//proto:aes_eax_cc_proto",
+        "//proto:common_cc_proto",
+        "//proto:ed25519_cc_proto",
+        "//proto:empty_cc_proto",
+        "//proto:tink_cc_proto",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "rsa_ssa_pkcs1_sign_key_manager_test",
+    size = "small",
+    srcs = ["rsa_ssa_pkcs1_sign_key_manager_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    deps = [
+        ":rsa_ssa_pkcs1_sign_key_manager",
+        ":rsa_ssa_pkcs1_verify_key_manager",
+        ":signature_key_templates",
+        "//cc:config",
+        "//cc:public_key_sign",
+        "//cc:registry",
+        "//cc/aead:aead_key_templates",
+        "//cc/aead:aes_gcm_key_manager",
+        "//cc/subtle:subtle_util_boringssl",
+        "//cc/util:status",
+        "//cc/util:test_util",
+        "//proto:aes_eax_cc_proto",
+        "//proto:common_cc_proto",
+        "//proto:rsa_ssa_pkcs1_cc_proto",
+        "//proto:tink_cc_proto",
+        "@boringssl//:crypto",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "rsa_ssa_pss_sign_key_manager_test",
+    size = "small",
+    srcs = ["rsa_ssa_pss_sign_key_manager_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    deps = [
+        ":rsa_ssa_pss_sign_key_manager",
+        ":rsa_ssa_pss_verify_key_manager",
+        ":signature_key_templates",
+        "//cc:config",
+        "//cc:public_key_sign",
+        "//cc:registry",
+        "//cc/aead:aead_key_templates",
+        "//cc/aead:aes_gcm_key_manager",
+        "//cc/subtle:subtle_util_boringssl",
+        "//cc/util:status",
+        "//cc/util:test_util",
+        "//proto:aes_eax_cc_proto",
+        "//proto:common_cc_proto",
+        "//proto:rsa_ssa_pss_cc_proto",
+        "//proto:tink_cc_proto",
+        "@boringssl//:crypto",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
     name = "public_key_sign_catalogue_test",
     size = "small",
     srcs = ["public_key_sign_catalogue_test.cc"],
@@ -392,12 +678,17 @@
     copts = ["-Iexternal/gtest/include"],
     deps = [
         ":signature_config",
+        ":signature_key_templates",
         "//cc:catalogue",
         "//cc:config",
+        "//cc:keyset_handle",
         "//cc:public_key_sign",
         "//cc:public_key_verify",
         "//cc:registry",
         "//cc/util:status",
+        "//cc/util:test_matchers",
+        "//cc/util:test_util",
+        "@com_google_absl//absl/memory",
         "@com_google_googletest//:gtest_main",
     ],
 )
@@ -409,10 +700,19 @@
     copts = ["-Iexternal/gtest/include"],
     deps = [
         ":ecdsa_sign_key_manager",
+        ":ed25519_sign_key_manager",
+        ":rsa_ssa_pkcs1_sign_key_manager",
+        ":rsa_ssa_pss_sign_key_manager",
         ":signature_key_templates",
+        "//cc/subtle:subtle_util_boringssl",
         "//proto:common_cc_proto",
         "//proto:ecdsa_cc_proto",
+        "//proto:ed25519_cc_proto",
+        "//proto:empty_cc_proto",
+        "//proto:rsa_ssa_pkcs1_cc_proto",
+        "//proto:rsa_ssa_pss_cc_proto",
         "//proto:tink_cc_proto",
+        "@boringssl//:crypto",
         "@com_google_googletest//:gtest_main",
     ],
 )
diff --git a/cc/signature/CMakeLists.txt b/cc/signature/CMakeLists.txt
index 67eef22..426fb57 100644
--- a/cc/signature/CMakeLists.txt
+++ b/cc/signature/CMakeLists.txt
@@ -1,612 +1,67 @@
-# Copyright 2018 The Fuchsia Authors. All rights reserved.
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-# Library: 'tink_signature_public_key_verify_set_wrapper'
-add_library(tink_signature_public_key_verify_set_wrapper public_key_verify_set_wrapper.cc public_key_verify_set_wrapper.h)
-tink_export_hdrs(public_key_verify_set_wrapper.h)
+
+# CC Library : public_key_verify_wrapper
+add_library(
+  tink_cc_signature_public_key_verify_wrapper
+  public_key_verify_wrapper.h
+  public_key_verify_wrapper.cc
+)
+tink_export_hdrs(public_key_verify_wrapper.h)
 add_dependencies(
-  tink_signature_public_key_verify_set_wrapper
-  tink_crypto_format
-  tink_primitive_set
-  tink_public_key_verify
-  tink_subtle_subtle_util_boringssl
-  tink_util_status
-  tink_util_statusor
+  tink_cc_signature_public_key_verify_wrapper
+  tink_cc_crypto_format
+  tink_cc_primitive_set
+  tink_cc_primitive_wrapper
+  tink_cc_public_key_verify
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_status
+  tink_cc_util_statusor
   tink_proto_tink_lib
 )
 target_link_libraries(
-  tink_signature_public_key_verify_set_wrapper
-  tink_crypto_format
-  tink_primitive_set
-  tink_public_key_verify
-  tink_subtle_subtle_util_boringssl
-  tink_util_status
-  tink_util_statusor
+  tink_cc_signature_public_key_verify_wrapper
+  tink_cc_crypto_format
+  tink_cc_primitive_set
+  tink_cc_primitive_wrapper
+  tink_cc_public_key_verify
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_status
+  tink_cc_util_statusor
   tink_proto_tink_lib
   absl::strings
 )
 
-# Library: 'tink_signature_public_key_verify_factory'
-add_library(tink_signature_public_key_verify_factory public_key_verify_factory.cc public_key_verify_factory.h)
-tink_export_hdrs(public_key_verify_factory.h)
-add_dependencies(
-  tink_signature_public_key_verify_factory
-  tink_signature_public_key_verify_set_wrapper
-  tink_key_manager
-  tink_keyset_handle
-  tink_primitive_set
-  tink_public_key_verify
-  tink_registry
-  tink_util_status
-  tink_util_statusor
+# CC Library : public_key_sign_wrapper
+add_library(
+  tink_cc_signature_public_key_sign_wrapper
+  public_key_sign_wrapper.h
+  public_key_sign_wrapper.cc
 )
-target_link_libraries(
-  tink_signature_public_key_verify_factory
-  tink_signature_public_key_verify_set_wrapper
-  tink_key_manager
-  tink_keyset_handle
-  tink_primitive_set
-  tink_public_key_verify
-  tink_registry
-  tink_util_status
-  tink_util_statusor
-)
-
-# Library: 'tink_signature_public_key_sign_set_wrapper'
-add_library(tink_signature_public_key_sign_set_wrapper public_key_sign_set_wrapper.cc public_key_sign_set_wrapper.h)
-tink_export_hdrs(public_key_sign_set_wrapper.h)
+tink_export_hdrs(public_key_sign_wrapper.h)
 add_dependencies(
-  tink_signature_public_key_sign_set_wrapper
-  tink_crypto_format
-  tink_primitive_set
-  tink_public_key_sign
-  tink_subtle_subtle_util_boringssl
-  tink_util_status
-  tink_util_statusor
+  tink_cc_signature_public_key_sign_wrapper
+  tink_cc_crypto_format
+  tink_cc_primitive_set
+  tink_cc_primitive_wrapper
+  tink_cc_public_key_sign
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_status
+  tink_cc_util_statusor
   tink_proto_tink_lib
 )
 target_link_libraries(
-  tink_signature_public_key_sign_set_wrapper
-  tink_crypto_format
-  tink_primitive_set
-  tink_public_key_sign
-  tink_subtle_subtle_util_boringssl
-  tink_util_status
-  tink_util_statusor
+  tink_cc_signature_public_key_sign_wrapper
+  tink_cc_crypto_format
+  tink_cc_primitive_set
+  tink_cc_primitive_wrapper
+  tink_cc_public_key_sign
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_status
+  tink_cc_util_statusor
   tink_proto_tink_lib
   absl::strings
 )
 
-# Library: 'tink_signature_public_key_sign_factory'
-add_library(tink_signature_public_key_sign_factory public_key_sign_factory.cc public_key_sign_factory.h)
-tink_export_hdrs(public_key_sign_factory.h)
-add_dependencies(
-  tink_signature_public_key_sign_factory
-  tink_signature_public_key_sign_set_wrapper
-  tink_key_manager
-  tink_keyset_handle
-  tink_primitive_set
-  tink_public_key_sign
-  tink_registry
-  tink_util_status
-  tink_util_statusor
-)
-target_link_libraries(
-  tink_signature_public_key_sign_factory
-  tink_signature_public_key_sign_set_wrapper
-  tink_key_manager
-  tink_keyset_handle
-  tink_primitive_set
-  tink_public_key_sign
-  tink_registry
-  tink_util_status
-  tink_util_statusor
-)
-
-# Library: 'tink_signature_signature_key_templates'
-add_library(tink_signature_signature_key_templates signature_key_templates.cc signature_key_templates.h)
-tink_export_hdrs(signature_key_templates.h)
-add_dependencies(
-  tink_signature_signature_key_templates
-  tink_proto_common_lib
-  tink_proto_ecdsa_lib
-  tink_proto_tink_lib
-)
-target_link_libraries(
-  tink_signature_signature_key_templates
-  tink_proto_common_lib
-  tink_proto_ecdsa_lib
-  tink_proto_tink_lib
-)
-
-# Library: 'tink_signature_ecdsa_sign_key_manager'
-add_library(tink_signature_ecdsa_sign_key_manager ecdsa_sign_key_manager.cc ecdsa_sign_key_manager.h)
-tink_export_hdrs(ecdsa_sign_key_manager.h)
-add_dependencies(
-  tink_signature_ecdsa_sign_key_manager
-  tink_signature_ecdsa_verify_key_manager
-  tink_key_manager
-  tink_public_key_sign
-  tink_registry
-  tink_subtle_ecdsa_sign_boringssl
-  tink_subtle_subtle_util_boringssl
-  tink_util_enums
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_proto_common_lib
-  tink_proto_ecdsa_lib
-  tink_proto_tink_lib
-)
-target_link_libraries(
-  tink_signature_ecdsa_sign_key_manager
-  tink_signature_ecdsa_verify_key_manager
-  tink_key_manager
-  tink_public_key_sign
-  tink_registry
-  tink_subtle_ecdsa_sign_boringssl
-  tink_subtle_subtle_util_boringssl
-  tink_util_enums
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_proto_common_lib
-  tink_proto_ecdsa_lib
-  tink_proto_tink_lib
-  absl::memory
-  absl::strings
-)
-
-# Library: 'tink_signature_ecdsa_verify_key_manager'
-add_library(tink_signature_ecdsa_verify_key_manager ecdsa_verify_key_manager.cc ecdsa_verify_key_manager.h)
-tink_export_hdrs(ecdsa_verify_key_manager.h)
-add_dependencies(
-  tink_signature_ecdsa_verify_key_manager
-  tink_key_manager
-  tink_public_key_verify
-  tink_registry
-  tink_subtle_ecdsa_verify_boringssl
-  tink_subtle_subtle_util_boringssl
-  tink_util_enums
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_proto_common_lib
-  tink_proto_ecdsa_lib
-  tink_proto_tink_lib
-)
-target_link_libraries(
-  tink_signature_ecdsa_verify_key_manager
-  tink_key_manager
-  tink_public_key_verify
-  tink_registry
-  tink_subtle_ecdsa_verify_boringssl
-  tink_subtle_subtle_util_boringssl
-  tink_util_enums
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_proto_common_lib
-  tink_proto_ecdsa_lib
-  tink_proto_tink_lib
-)
-
-# Library: 'tink_signature_rsa_ssa_pss_verify_key_manager'
-add_library(tink_signature_rsa_ssa_pss_verify_key_manager rsa_ssa_pss_verify_key_manager.cc rsa_ssa_pss_verify_key_manager.h)
-tink_export_hdrs(rsa_ssa_pss_verify_key_manager.h)
-add_dependencies(
-  tink_signature_rsa_ssa_pss_verify_key_manager
-  tink_key_manager
-  tink_public_key_verify
-  tink_registry
-  tink_subtle_rsa_ssa_pss_verify_boringssl
-  tink_subtle_subtle_util_boringssl
-  tink_util_enums
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_proto_common_lib
-  tink_proto_rsa_ssa_pss_lib
-  tink_proto_tink_lib
-)
-target_link_libraries(
-  tink_signature_rsa_ssa_pss_verify_key_manager
-  tink_key_manager
-  tink_public_key_verify
-  tink_registry
-  tink_subtle_rsa_ssa_pss_verify_boringssl
-  tink_subtle_subtle_util_boringssl
-  tink_util_enums
-  tink_util_errors
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_proto_common_lib
-  tink_proto_rsa_ssa_pss_lib
-  tink_proto_tink_lib
-)
-
-# Library: 'tink_signature_public_key_sign_catalogue'
-add_library(tink_signature_public_key_sign_catalogue public_key_sign_catalogue.cc public_key_sign_catalogue.h)
-tink_export_hdrs(public_key_sign_catalogue.h)
-add_dependencies(
-  tink_signature_public_key_sign_catalogue
-  tink_signature_ecdsa_sign_key_manager
-  tink_catalogue
-  tink_key_manager
-  tink_public_key_sign
-  tink_util_status
-  tink_util_statusor
-)
-target_link_libraries(
-  tink_signature_public_key_sign_catalogue
-  tink_signature_ecdsa_sign_key_manager
-  tink_catalogue
-  tink_key_manager
-  tink_public_key_sign
-  tink_util_status
-  tink_util_statusor
-)
-
-# Library: 'tink_signature_public_key_verify_catalogue'
-add_library(tink_signature_public_key_verify_catalogue public_key_verify_catalogue.cc public_key_verify_catalogue.h)
-tink_export_hdrs(public_key_verify_catalogue.h)
-add_dependencies(
-  tink_signature_public_key_verify_catalogue
-  tink_signature_ecdsa_verify_key_manager
-  tink_catalogue
-  tink_key_manager
-  tink_public_key_verify
-  tink_util_status
-  tink_util_statusor
-)
-target_link_libraries(
-  tink_signature_public_key_verify_catalogue
-  tink_signature_ecdsa_verify_key_manager
-  tink_catalogue
-  tink_key_manager
-  tink_public_key_verify
-  tink_util_status
-  tink_util_statusor
-)
-
-# Library: 'tink_signature_signature_config'
-add_library(tink_signature_signature_config signature_config.cc signature_config.h)
-tink_export_hdrs(signature_config.h)
-add_dependencies(
-  tink_signature_signature_config
-  tink_signature_public_key_sign_catalogue
-  tink_signature_public_key_verify_catalogue
-  tink_config
-  tink_registry
-  tink_util_status
-  tink_proto_config_lib
-)
-target_link_libraries(
-  tink_signature_signature_config
-  tink_signature_public_key_sign_catalogue
-  tink_signature_public_key_verify_catalogue
-  tink_config
-  tink_registry
-  tink_util_status
-  tink_proto_config_lib
-)
-
-# Test Binary: 'tink_signature_public_key_verify_set_wrapper_test'
-add_executable(tink_signature_public_key_verify_set_wrapper_test public_key_verify_set_wrapper_test.cc)
-add_dependencies(
-  tink_signature_public_key_verify_set_wrapper_test
-  tink_signature_public_key_verify_set_wrapper
-  tink_primitive_set
-  tink_public_key_sign
-  tink_public_key_verify
-  tink_util_status
-  tink_util_test_util
-  tink_proto_tink_lib
-)
-add_dependencies(tink_signature_public_key_verify_set_wrapper_test build_external_projects)
-target_link_libraries(
-  tink_signature_public_key_verify_set_wrapper_test
-  tink_signature_public_key_verify_set_wrapper
-  tink_primitive_set
-  tink_public_key_sign
-  tink_public_key_verify
-  tink_util_status
-  tink_util_test_util
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_signature_public_key_verify_factory_test'
-add_executable(tink_signature_public_key_verify_factory_test public_key_verify_factory_test.cc)
-add_dependencies(
-  tink_signature_public_key_verify_factory_test
-  tink_signature_ecdsa_verify_key_manager
-  tink_signature_public_key_verify_factory
-  tink_signature_signature_config
-  tink_config
-  tink_keyset_handle
-  tink_public_key_verify
-  tink_registry
-  tink_util_keyset_util
-  tink_util_status
-  tink_util_test_util
-  tink_proto_ecdsa_lib
-  tink_proto_tink_lib
-)
-add_dependencies(tink_signature_public_key_verify_factory_test build_external_projects)
-target_link_libraries(
-  tink_signature_public_key_verify_factory_test
-  tink_signature_ecdsa_verify_key_manager
-  tink_signature_public_key_verify_factory
-  tink_signature_signature_config
-  tink_config
-  tink_keyset_handle
-  tink_public_key_verify
-  tink_registry
-  tink_util_keyset_util
-  tink_util_status
-  tink_util_test_util
-  tink_proto_ecdsa_lib
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_signature_public_key_sign_set_wrapper_test'
-add_executable(tink_signature_public_key_sign_set_wrapper_test public_key_sign_set_wrapper_test.cc)
-add_dependencies(
-  tink_signature_public_key_sign_set_wrapper_test
-  tink_signature_public_key_sign_set_wrapper
-  tink_primitive_set
-  tink_public_key_sign
-  tink_public_key_verify
-  tink_util_status
-  tink_util_test_util
-  tink_proto_tink_lib
-)
-add_dependencies(tink_signature_public_key_sign_set_wrapper_test build_external_projects)
-target_link_libraries(
-  tink_signature_public_key_sign_set_wrapper_test
-  tink_signature_public_key_sign_set_wrapper
-  tink_primitive_set
-  tink_public_key_sign
-  tink_public_key_verify
-  tink_util_status
-  tink_util_test_util
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_signature_public_key_sign_factory_test'
-add_executable(tink_signature_public_key_sign_factory_test public_key_sign_factory_test.cc)
-add_dependencies(
-  tink_signature_public_key_sign_factory_test
-  tink_signature_ecdsa_sign_key_manager
-  tink_signature_public_key_sign_factory
-  tink_signature_signature_config
-  tink_config
-  tink_keyset_handle
-  tink_public_key_sign
-  tink_registry
-  tink_util_keyset_util
-  tink_util_status
-  tink_util_test_util
-  tink_proto_ecdsa_lib
-  tink_proto_tink_lib
-)
-add_dependencies(tink_signature_public_key_sign_factory_test build_external_projects)
-target_link_libraries(
-  tink_signature_public_key_sign_factory_test
-  tink_signature_ecdsa_sign_key_manager
-  tink_signature_public_key_sign_factory
-  tink_signature_signature_config
-  tink_config
-  tink_keyset_handle
-  tink_public_key_sign
-  tink_registry
-  tink_util_keyset_util
-  tink_util_status
-  tink_util_test_util
-  tink_proto_ecdsa_lib
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_signature_ecdsa_verify_key_manager_test'
-add_executable(tink_signature_ecdsa_verify_key_manager_test ecdsa_verify_key_manager_test.cc)
-add_dependencies(
-  tink_signature_ecdsa_verify_key_manager_test
-  tink_signature_ecdsa_sign_key_manager
-  tink_signature_ecdsa_verify_key_manager
-  tink_config
-  tink_public_key_sign
-  tink_public_key_verify
-  tink_registry
-  tink_util_status
-  tink_util_test_util
-  tink_proto_aes_eax_lib
-  tink_proto_common_lib
-  tink_proto_ecdsa_lib
-  tink_proto_tink_lib
-)
-add_dependencies(tink_signature_ecdsa_verify_key_manager_test build_external_projects)
-target_link_libraries(
-  tink_signature_ecdsa_verify_key_manager_test
-  tink_signature_ecdsa_sign_key_manager
-  tink_signature_ecdsa_verify_key_manager
-  tink_config
-  tink_public_key_sign
-  tink_public_key_verify
-  tink_registry
-  tink_util_status
-  tink_util_test_util
-  tink_proto_aes_eax_lib
-  tink_proto_common_lib
-  tink_proto_ecdsa_lib
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_signature_rsa_ssa_pss_verify_key_manager_test'
-add_executable(tink_signature_rsa_ssa_pss_verify_key_manager_test rsa_ssa_pss_verify_key_manager_test.cc)
-add_dependencies(
-  tink_signature_rsa_ssa_pss_verify_key_manager_test
-  tink_signature_rsa_ssa_pss_verify_key_manager
-  tink_config
-  tink_public_key_sign
-  tink_public_key_verify
-  tink_registry
-  tink_util_status
-  tink_util_test_util
-  tink_proto_aes_eax_lib
-  tink_proto_common_lib
-  tink_proto_rsa_ssa_pss_lib
-  tink_proto_tink_lib
-)
-add_dependencies(tink_signature_rsa_ssa_pss_verify_key_manager_test build_external_projects)
-target_link_libraries(
-  tink_signature_rsa_ssa_pss_verify_key_manager_test
-  tink_signature_rsa_ssa_pss_verify_key_manager
-  tink_config
-  tink_public_key_sign
-  tink_public_key_verify
-  tink_registry
-  tink_util_status
-  tink_util_test_util
-  tink_proto_aes_eax_lib
-  tink_proto_common_lib
-  tink_proto_rsa_ssa_pss_lib
-  tink_proto_tink_lib
-  absl::strings
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_signature_ecdsa_sign_key_manager_test'
-add_executable(tink_signature_ecdsa_sign_key_manager_test ecdsa_sign_key_manager_test.cc)
-add_dependencies(
-  tink_signature_ecdsa_sign_key_manager_test
-  tink_signature_ecdsa_sign_key_manager
-  tink_signature_ecdsa_verify_key_manager
-  tink_signature_signature_key_templates
-  tink_config
-  tink_public_key_sign
-  tink_registry
-  tink_aead_aead_key_templates
-  tink_aead_aes_gcm_key_manager
-  tink_util_status
-  tink_util_test_util
-  tink_proto_aes_eax_lib
-  tink_proto_common_lib
-  tink_proto_ecdsa_lib
-  tink_proto_tink_lib
-)
-add_dependencies(tink_signature_ecdsa_sign_key_manager_test build_external_projects)
-target_link_libraries(
-  tink_signature_ecdsa_sign_key_manager_test
-  tink_signature_ecdsa_sign_key_manager
-  tink_signature_ecdsa_verify_key_manager
-  tink_signature_signature_key_templates
-  tink_config
-  tink_public_key_sign
-  tink_registry
-  tink_aead_aead_key_templates
-  tink_aead_aes_gcm_key_manager
-  tink_util_status
-  tink_util_test_util
-  tink_proto_aes_eax_lib
-  tink_proto_common_lib
-  tink_proto_ecdsa_lib
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_signature_public_key_sign_catalogue_test'
-add_executable(tink_signature_public_key_sign_catalogue_test public_key_sign_catalogue_test.cc)
-add_dependencies(
-  tink_signature_public_key_sign_catalogue_test
-  tink_signature_public_key_sign_catalogue
-  tink_catalogue
-  tink_util_status
-  tink_util_statusor
-)
-add_dependencies(tink_signature_public_key_sign_catalogue_test build_external_projects)
-target_link_libraries(
-  tink_signature_public_key_sign_catalogue_test
-  tink_signature_public_key_sign_catalogue
-  tink_catalogue
-  tink_util_status
-  tink_util_statusor
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_signature_public_key_verify_catalogue_test'
-add_executable(tink_signature_public_key_verify_catalogue_test public_key_verify_catalogue_test.cc)
-add_dependencies(
-  tink_signature_public_key_verify_catalogue_test
-  tink_signature_public_key_verify_catalogue
-  tink_catalogue
-  tink_util_status
-  tink_util_statusor
-)
-add_dependencies(tink_signature_public_key_verify_catalogue_test build_external_projects)
-target_link_libraries(
-  tink_signature_public_key_verify_catalogue_test
-  tink_signature_public_key_verify_catalogue
-  tink_catalogue
-  tink_util_status
-  tink_util_statusor
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_signature_signature_config_test'
-add_executable(tink_signature_signature_config_test signature_config_test.cc)
-add_dependencies(
-  tink_signature_signature_config_test
-  tink_signature_signature_config
-  tink_catalogue
-  tink_config
-  tink_public_key_sign
-  tink_public_key_verify
-  tink_registry
-  tink_util_status
-)
-add_dependencies(tink_signature_signature_config_test build_external_projects)
-target_link_libraries(
-  tink_signature_signature_config_test
-  tink_signature_signature_config
-  tink_catalogue
-  tink_config
-  tink_public_key_sign
-  tink_public_key_verify
-  tink_registry
-  tink_util_status
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_signature_signature_key_templates_test'
-add_executable(tink_signature_signature_key_templates_test signature_key_templates_test.cc)
-add_dependencies(
-  tink_signature_signature_key_templates_test
-  tink_signature_ecdsa_sign_key_manager
-  tink_signature_signature_key_templates
-  tink_proto_common_lib
-  tink_proto_ecdsa_lib
-  tink_proto_tink_lib
-)
-add_dependencies(tink_signature_signature_key_templates_test build_external_projects)
-target_link_libraries(
-  tink_signature_signature_key_templates_test
-  tink_signature_ecdsa_sign_key_manager
-  tink_signature_signature_key_templates
-  tink_proto_common_lib
-  tink_proto_ecdsa_lib
-  tink_proto_tink_lib
-  gtest gtest_main
-)
-
diff --git a/cc/signature/ecdsa_sign_key_manager.cc b/cc/signature/ecdsa_sign_key_manager.cc
index 5d066ef..ee522bb 100644
--- a/cc/signature/ecdsa_sign_key_manager.cc
+++ b/cc/signature/ecdsa_sign_key_manager.cc
@@ -18,6 +18,7 @@
 
 #include "absl/strings/string_view.h"
 #include "absl/memory/memory.h"
+#include "tink/core/key_manager_base.h"
 #include "tink/public_key_sign.h"
 #include "tink/key_manager.h"
 #include "tink/signature/ecdsa_verify_key_manager.h"
@@ -44,46 +45,30 @@
 using crypto::tink::util::Status;
 using crypto::tink::util::StatusOr;
 
-class EcdsaPrivateKeyFactory : public PrivateKeyFactory {
+class EcdsaPrivateKeyFactory
+    : public PrivateKeyFactory,
+      public KeyFactoryBase<EcdsaPrivateKey, EcdsaKeyFormat> {
  public:
   EcdsaPrivateKeyFactory() {}
 
-  // Generates a new random EcdsaPrivateKey, based on
-  // the given 'key_format', which must contain EcdsaKeyFormat-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<portable_proto::MessageLite>>
-  NewKey(const portable_proto::MessageLite& key_format) const override;
-
-  // Generates a new random EcdsaPrivateKey, based on
-  // the given 'serialized_key_format', which must contain
-  // EcdsaKeyFormat-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<portable_proto::MessageLite>>
-  NewKey(absl::string_view serialized_key_format) const override;
-
-  // Generates a new random EcdsaPrivateKey based on
-  // the given 'serialized_key_format' (which must contain
-  // EcdsaKeyFormat-proto), and wraps it in a KeyData-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<google::crypto::tink::KeyData>>
-  NewKeyData(absl::string_view serialized_key_format) const override;
+  KeyData::KeyMaterialType key_material_type() const override {
+    return KeyData::ASYMMETRIC_PRIVATE;
+  }
 
   // Returns KeyData proto that contains EcdsaPublicKey
   // extracted from the given serialized_private_key, which must contain
   // EcdsaPrivateKey-proto.
   crypto::tink::util::StatusOr<std::unique_ptr<google::crypto::tink::KeyData>>
   GetPublicKeyData(absl::string_view serialized_private_key) const override;
+
+ protected:
+  StatusOr<std::unique_ptr<EcdsaPrivateKey>> NewKeyFromFormat(
+      const EcdsaKeyFormat& ecdsa_key_format) const override;
 };
 
-StatusOr<std::unique_ptr<MessageLite>> EcdsaPrivateKeyFactory::NewKey(
-    const portable_proto::MessageLite& key_format) const {
-  std::string key_format_url =
-      std::string(EcdsaSignKeyManager::kKeyTypePrefix) +
-      key_format.GetTypeName();
-  if (key_format_url != EcdsaSignKeyManager::kKeyFormatUrl) {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Key format proto '%s' is not supported by this manager.",
-                     key_format_url.c_str());
-  }
-  const EcdsaKeyFormat& ecdsa_key_format =
-        reinterpret_cast<const EcdsaKeyFormat&>(key_format);
+StatusOr<std::unique_ptr<EcdsaPrivateKey>>
+EcdsaPrivateKeyFactory::NewKeyFromFormat(
+    const EcdsaKeyFormat& ecdsa_key_format) const {
   Status status = EcdsaVerifyKeyManager::Validate(ecdsa_key_format);
   if (!status.ok()) return status;
 
@@ -104,32 +89,8 @@
   ecdsa_public_key->set_y(ec_key.pub_y);
   *(ecdsa_public_key->mutable_params()) = ecdsa_key_format.params();
 
-  std::unique_ptr<MessageLite> key = std::move(ecdsa_private_key);
-  return std::move(key);
-}
-
-StatusOr<std::unique_ptr<MessageLite>> EcdsaPrivateKeyFactory::NewKey(
-    absl::string_view serialized_key_format) const {
-  EcdsaKeyFormat key_format;
-  if (!key_format.ParseFromString(std::string(serialized_key_format))) {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Could not parse the passed string as proto '%s'.",
-                     EcdsaSignKeyManager::kKeyFormatUrl);
-  }
-  return NewKey(key_format);
-}
-
-StatusOr<std::unique_ptr<KeyData>> EcdsaPrivateKeyFactory::NewKeyData(
-    absl::string_view serialized_key_format) const {
-  auto new_key_result = NewKey(serialized_key_format);
-  if (!new_key_result.ok()) return new_key_result.status();
-  auto new_key = reinterpret_cast<const EcdsaPrivateKey&>(
-      *(new_key_result.ValueOrDie()));
-  std::unique_ptr<KeyData> key_data(new KeyData());
-  key_data->set_type_url(EcdsaSignKeyManager::kKeyType);
-  key_data->set_value(new_key.SerializeAsString());
-  key_data->set_key_material_type(KeyData::ASYMMETRIC_PRIVATE);
-  return std::move(key_data);
+  return absl::implicit_cast<StatusOr<std::unique_ptr<EcdsaPrivateKey>>>(
+        std::move(ecdsa_private_key));
 }
 
 StatusOr<std::unique_ptr<KeyData>>
@@ -137,31 +98,23 @@
     absl::string_view serialized_private_key) const {
   EcdsaPrivateKey private_key;
   if (!private_key.ParseFromString(std::string(serialized_private_key))) {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Could not parse the passed string as proto '%s'.",
-                     EcdsaVerifyKeyManager::kKeyType);
+    return Status(util::error::INVALID_ARGUMENT,
+                  absl::StrCat("Could not parse the passed string as proto '",
+                               EcdsaVerifyKeyManager::static_key_type(), "'."));
   }
   auto status = EcdsaSignKeyManager::Validate(private_key);
   if (!status.ok()) return status;
   auto key_data = absl::make_unique<KeyData>();
-  key_data->set_type_url(EcdsaVerifyKeyManager::kKeyType);
+  key_data->set_type_url(EcdsaVerifyKeyManager::static_key_type());
   key_data->set_value(private_key.public_key().SerializeAsString());
-  key_data->set_key_material_type(KeyData:: ASYMMETRIC_PUBLIC);
+  key_data->set_key_material_type(KeyData::ASYMMETRIC_PUBLIC);
   return std::move(key_data);
 }
 
-constexpr char EcdsaSignKeyManager::kKeyFormatUrl[];
-constexpr char EcdsaSignKeyManager::kKeyTypePrefix[];
-constexpr char EcdsaSignKeyManager::kKeyType[];
 constexpr uint32_t EcdsaSignKeyManager::kVersion;
 
 EcdsaSignKeyManager::EcdsaSignKeyManager()
-    : key_type_(kKeyType), key_factory_(new EcdsaPrivateKeyFactory()) {
-}
-
-const std::string& EcdsaSignKeyManager::get_key_type() const {
-  return key_type_;
-}
+    : key_factory_(absl::make_unique<EcdsaPrivateKeyFactory>()) {}
 
 const KeyFactory& EcdsaSignKeyManager::get_key_factory() const {
   return *key_factory_;
@@ -172,38 +125,7 @@
 }
 
 StatusOr<std::unique_ptr<PublicKeySign>>
-EcdsaSignKeyManager::GetPrimitive(const KeyData& key_data) const {
-  if (DoesSupport(key_data.type_url())) {
-    EcdsaPrivateKey ecdsa_private_key;
-    if (!ecdsa_private_key.ParseFromString(key_data.value())) {
-      return ToStatusF(util::error::INVALID_ARGUMENT,
-                       "Could not parse key_data.value as key type '%s'.",
-                       key_data.type_url().c_str());
-    }
-    return GetPrimitiveImpl(ecdsa_private_key);
-  } else {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Key type '%s' is not supported by this manager.",
-                     key_data.type_url().c_str());
-  }
-}
-
-StatusOr<std::unique_ptr<PublicKeySign>>
-EcdsaSignKeyManager::GetPrimitive(const MessageLite& key) const {
-  std::string key_type = std::string(kKeyTypePrefix) + key.GetTypeName();
-  if (DoesSupport(key_type)) {
-    const EcdsaPrivateKey& ecdsa_private_key =
-        reinterpret_cast<const EcdsaPrivateKey&>(key);
-    return GetPrimitiveImpl(ecdsa_private_key);
-  } else {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Key type '%s' is not supported by this manager.",
-                     key_type.c_str());
-  }
-}
-
-StatusOr<std::unique_ptr<PublicKeySign>>
-EcdsaSignKeyManager::GetPrimitiveImpl(
+EcdsaSignKeyManager::GetPrimitiveFromKey(
     const EcdsaPrivateKey& ecdsa_private_key) const {
   Status status = Validate(ecdsa_private_key);
   if (!status.ok()) return status;
diff --git a/cc/signature/ecdsa_sign_key_manager.h b/cc/signature/ecdsa_sign_key_manager.h
index ac6257a..4287e78 100644
--- a/cc/signature/ecdsa_sign_key_manager.h
+++ b/cc/signature/ecdsa_sign_key_manager.h
@@ -13,16 +13,16 @@
 // limitations under the License.
 //
 ///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_SIGNATURE_ECDSA_SIGN_KEY_MANAGER_H_
+#define TINK_SIGNATURE_ECDSA_SIGN_KEY_MANAGER_H_
 
 #include <algorithm>
 #include <vector>
 
-#ifndef TINK_SIGNATURE_ECDSA_SIGN_KEY_MANAGER_H_
-#define TINK_SIGNATURE_ECDSA_SIGN_KEY_MANAGER_H_
-
 #include "absl/strings/string_view.h"
-#include "tink/public_key_sign.h"
+#include "tink/core/key_manager_base.h"
 #include "tink/key_manager.h"
+#include "tink/public_key_sign.h"
 #include "tink/util/errors.h"
 #include "tink/util/protobuf_helper.h"
 #include "tink/util/status.h"
@@ -33,27 +33,14 @@
 namespace crypto {
 namespace tink {
 
-class EcdsaSignKeyManager : public KeyManager<PublicKeySign> {
+class EcdsaSignKeyManager
+    : public KeyManagerBase<PublicKeySign,
+                            google::crypto::tink::EcdsaPrivateKey> {
  public:
-  static constexpr char kKeyType[] =
-      "type.googleapis.com/google.crypto.tink.EcdsaPrivateKey";
   static constexpr uint32_t kVersion = 0;
 
   EcdsaSignKeyManager();
 
-  // Constructs an instance of ECDSA PublicKeySign
-  // for the given 'key_data', which must contain EcdsaPrivateKey-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<PublicKeySign>> GetPrimitive(
-      const google::crypto::tink::KeyData& key_data) const override;
-
-  // Constructs an instance of ECDSA PublicKeySign
-  // for the given 'key', which must be EcdsaPrivateKey-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<PublicKeySign>>
-  GetPrimitive(const portable_proto::MessageLite& key) const override;
-
-  // Returns the type_url identifying the key type handled by this manager.
-  const std::string& get_key_type() const override;
-
   // Returns the version of this key manager.
   uint32_t get_version() const override;
 
@@ -63,21 +50,16 @@
 
   virtual ~EcdsaSignKeyManager() {}
 
+ protected:
+  crypto::tink::util::StatusOr<std::unique_ptr<PublicKeySign>>
+  GetPrimitiveFromKey(const google::crypto::tink::EcdsaPrivateKey&
+                          ecdsa_private_key) const override;
+
  private:
   friend class EcdsaPrivateKeyFactory;
 
-  static constexpr char kKeyTypePrefix[] = "type.googleapis.com/";
-  static constexpr char kKeyFormatUrl[] =
-      "type.googleapis.com/google.crypto.tink.EcdsaKeyFormat";
-
-  std::string key_type_;
   std::unique_ptr<KeyFactory> key_factory_;
 
-  // Constructs an instance of ECDSA PublicKeySign
-  // for the given 'key'.
-  crypto::tink::util::StatusOr<std::unique_ptr<PublicKeySign>> GetPrimitiveImpl(
-  const google::crypto::tink::EcdsaPrivateKey& ecdsa_private_key) const;
-
   static crypto::tink::util::Status Validate(
       const google::crypto::tink::EcdsaPrivateKey& key);
 };
diff --git a/cc/signature/ecdsa_sign_key_manager_test.cc b/cc/signature/ecdsa_sign_key_manager_test.cc
index bfaf798..ffe8236 100644
--- a/cc/signature/ecdsa_sign_key_manager_test.cc
+++ b/cc/signature/ecdsa_sign_key_manager_test.cc
@@ -228,7 +228,7 @@
     auto key = std::move(result.ValueOrDie());
     ASSERT_EQ(ecdsa_sign_key_type_, key_type_prefix_ + key->GetTypeName());
     std::unique_ptr<EcdsaPrivateKey> ecdsa_key(
-        reinterpret_cast<EcdsaPrivateKey*>(key.release()));
+        static_cast<EcdsaPrivateKey*>(key.release()));
     CheckNewKey(*ecdsa_key, key_format);
   }
 
@@ -241,7 +241,7 @@
     auto key = std::move(result.ValueOrDie());
     ASSERT_EQ(ecdsa_sign_key_type_, key_type_prefix_ + key->GetTypeName());
     std::unique_ptr<EcdsaPrivateKey> ecdsa_key(
-        reinterpret_cast<EcdsaPrivateKey*>(key.release()));
+        static_cast<EcdsaPrivateKey*>(key.release()));
     CheckNewKey(*ecdsa_key, key_format);
   }
 
@@ -275,7 +275,7 @@
       new_key->SerializeAsString());
   EXPECT_TRUE(public_key_data_result.ok()) << public_key_data_result.status();
   auto public_key_data = std::move(public_key_data_result.ValueOrDie());
-  EXPECT_EQ(EcdsaVerifyKeyManager::kKeyType,
+  EXPECT_EQ(EcdsaVerifyKeyManager::static_key_type(),
             public_key_data->type_url());
   EXPECT_EQ(KeyData::ASYMMETRIC_PUBLIC, public_key_data->key_material_type());
   EXPECT_EQ(new_key->public_key().SerializeAsString(),
@@ -370,9 +370,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/signature/ecdsa_verify_key_manager.cc b/cc/signature/ecdsa_verify_key_manager.cc
index 9b008b3..c74f9ae 100644
--- a/cc/signature/ecdsa_verify_key_manager.cc
+++ b/cc/signature/ecdsa_verify_key_manager.cc
@@ -17,8 +17,8 @@
 #include "tink/signature/ecdsa_verify_key_manager.h"
 
 #include "absl/strings/string_view.h"
-#include "tink/public_key_verify.h"
 #include "tink/key_manager.h"
+#include "tink/public_key_verify.h"
 #include "tink/subtle/ecdsa_verify_boringssl.h"
 #include "tink/subtle/subtle_util_boringssl.h"
 #include "tink/util/enums.h"
@@ -33,6 +33,9 @@
 namespace crypto {
 namespace tink {
 
+using crypto::tink::util::Enums;
+using crypto::tink::util::Status;
+using crypto::tink::util::StatusOr;
 using google::crypto::tink::EcdsaKeyFormat;
 using google::crypto::tink::EcdsaParams;
 using google::crypto::tink::EcdsaPublicKey;
@@ -41,101 +44,23 @@
 using google::crypto::tink::HashType;
 using google::crypto::tink::KeyData;
 using portable_proto::MessageLite;
-using crypto::tink::util::Enums;
-using crypto::tink::util::Status;
-using crypto::tink::util::StatusOr;
 
-class EcdsaPublicKeyFactory : public KeyFactory {
- public:
-  EcdsaPublicKeyFactory() {}
-
-  // Not implemented for public keys.
-  crypto::tink::util::StatusOr<std::unique_ptr<portable_proto::MessageLite>>
-  NewKey(const portable_proto::MessageLite& key_format) const override;
-
-  // Not implemented for public keys.
-  crypto::tink::util::StatusOr<std::unique_ptr<portable_proto::MessageLite>>
-  NewKey(absl::string_view serialized_key_format) const override;
-
-  // Not implemented for public keys.
-  crypto::tink::util::StatusOr<std::unique_ptr<google::crypto::tink::KeyData>>
-  NewKeyData(absl::string_view serialized_key_format) const override;
-};
-
-StatusOr<std::unique_ptr<MessageLite>> EcdsaPublicKeyFactory::NewKey(
-    const portable_proto::MessageLite& key_format) const {
-  return util::Status(util::error::UNIMPLEMENTED,
-                      "Operation not supported for public keys, "
-                      "please use the EcdsaSignKeyManager.");
-}
-
-StatusOr<std::unique_ptr<MessageLite>> EcdsaPublicKeyFactory::NewKey(
-    absl::string_view serialized_key_format) const {
-  return util::Status(util::error::UNIMPLEMENTED,
-                      "Operation not supported for public keys, "
-                      "please use the EcdsaSignKeyManager.");
-}
-
-StatusOr<std::unique_ptr<KeyData>> EcdsaPublicKeyFactory::NewKeyData(
-    absl::string_view serialized_key_format) const {
-  return util::Status(util::error::UNIMPLEMENTED,
-                      "Operation not supported for public keys, "
-                      "please use the EcdsaSignKeyManager.");
-}
-
-constexpr char EcdsaVerifyKeyManager::kKeyTypePrefix[];
-constexpr char EcdsaVerifyKeyManager::kKeyType[];
 constexpr uint32_t EcdsaVerifyKeyManager::kVersion;
 
 EcdsaVerifyKeyManager::EcdsaVerifyKeyManager()
-    : key_type_(kKeyType), key_factory_(new EcdsaPublicKeyFactory()) {
-}
-
-const std::string& EcdsaVerifyKeyManager::get_key_type() const {
-  return key_type_;
-}
+    : key_factory_(KeyFactory::AlwaysFailingFactory(
+          util::Status(util::error::UNIMPLEMENTED,
+                       "Operation not supported for public keys, "
+                       "please use the EcdsaSignKeyManager."))) {}
 
 const KeyFactory& EcdsaVerifyKeyManager::get_key_factory() const {
   return *key_factory_;
 }
 
-uint32_t EcdsaVerifyKeyManager::get_version() const {
-  return kVersion;
-}
+uint32_t EcdsaVerifyKeyManager::get_version() const { return kVersion; }
 
 StatusOr<std::unique_ptr<PublicKeyVerify>>
-EcdsaVerifyKeyManager::GetPrimitive(const KeyData& key_data) const {
-  if (DoesSupport(key_data.type_url())) {
-    EcdsaPublicKey ecdsa_public_key;
-    if (!ecdsa_public_key.ParseFromString(key_data.value())) {
-      return ToStatusF(util::error::INVALID_ARGUMENT,
-                       "Could not parse key_data.value as key type '%s'.",
-                       key_data.type_url().c_str());
-    }
-    return GetPrimitiveImpl(ecdsa_public_key);
-  } else {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Key type '%s' is not supported by this manager.",
-                     key_data.type_url().c_str());
-  }
-}
-
-StatusOr<std::unique_ptr<PublicKeyVerify>>
-EcdsaVerifyKeyManager::GetPrimitive(const MessageLite& key) const {
-  std::string key_type = std::string(kKeyTypePrefix) + key.GetTypeName();
-  if (DoesSupport(key_type)) {
-    const EcdsaPublicKey& ecdsa_public_key =
-        reinterpret_cast<const EcdsaPublicKey&>(key);
-    return GetPrimitiveImpl(ecdsa_public_key);
-  } else {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Key type '%s' is not supported by this manager.",
-                     key_type.c_str());
-  }
-}
-
-StatusOr<std::unique_ptr<PublicKeyVerify>>
-EcdsaVerifyKeyManager::GetPrimitiveImpl(
+EcdsaVerifyKeyManager::GetPrimitiveFromKey(
     const EcdsaPublicKey& ecdsa_public_key) const {
   Status status = Validate(ecdsa_public_key);
   if (!status.ok()) return status;
@@ -186,16 +111,14 @@
 }
 
 // static
-Status EcdsaVerifyKeyManager::Validate(
-    const EcdsaPublicKey& key) {
+Status EcdsaVerifyKeyManager::Validate(const EcdsaPublicKey& key) {
   Status status = ValidateVersion(key.version(), kVersion);
   if (!status.ok()) return status;
   return Validate(key.params());
 }
 
 // static
-Status EcdsaVerifyKeyManager::Validate(
-    const EcdsaKeyFormat& key_format) {
+Status EcdsaVerifyKeyManager::Validate(const EcdsaKeyFormat& key_format) {
   if (!key_format.has_params()) {
     return Status(util::error::INVALID_ARGUMENT, "Missing params.");
   }
diff --git a/cc/signature/ecdsa_verify_key_manager.h b/cc/signature/ecdsa_verify_key_manager.h
index 7fc3cb2..e35fc7c 100644
--- a/cc/signature/ecdsa_verify_key_manager.h
+++ b/cc/signature/ecdsa_verify_key_manager.h
@@ -13,16 +13,16 @@
 // limitations under the License.
 //
 ///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_SIGNATURE_ECDSA_VERIFY_KEY_MANAGER_H_
+#define TINK_SIGNATURE_ECDSA_VERIFY_KEY_MANAGER_H_
 
 #include <algorithm>
 #include <vector>
 
-#ifndef TINK_SIGNATURE_ECDSA_VERIFY_KEY_MANAGER_H_
-#define TINK_SIGNATURE_ECDSA_VERIFY_KEY_MANAGER_H_
-
 #include "absl/strings/string_view.h"
-#include "tink/public_key_verify.h"
+#include "tink/core/key_manager_base.h"
 #include "tink/key_manager.h"
+#include "tink/public_key_verify.h"
 #include "tink/util/errors.h"
 #include "tink/util/protobuf_helper.h"
 #include "tink/util/status.h"
@@ -33,27 +33,14 @@
 namespace crypto {
 namespace tink {
 
-class EcdsaVerifyKeyManager : public KeyManager<PublicKeyVerify> {
+class EcdsaVerifyKeyManager
+    : public KeyManagerBase<PublicKeyVerify,
+                            google::crypto::tink::EcdsaPublicKey> {
  public:
-  static constexpr char kKeyType[] =
-      "type.googleapis.com/google.crypto.tink.EcdsaPublicKey";
   static constexpr uint32_t kVersion = 0;
 
   EcdsaVerifyKeyManager();
 
-  // Constructs an instance of ECDSA PublicKeyVerify
-  // for the given 'key_data', which must contain EcdsaPrivateKey-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<PublicKeyVerify>> GetPrimitive(
-      const google::crypto::tink::KeyData& key_data) const override;
-
-  // Constructs an instance of ECDSA PublicKeyVerify
-  // for the given 'key', which must be EcdsaPrivateKey-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<PublicKeyVerify>>
-  GetPrimitive(const portable_proto::MessageLite& key) const override;
-
-  // Returns the type_url identifying the key type handled by this manager.
-  const std::string& get_key_type() const override;
-
   // Returns the version of this key manager.
   uint32_t get_version() const override;
 
@@ -63,24 +50,18 @@
 
   virtual ~EcdsaVerifyKeyManager() {}
 
+ protected:
+  crypto::tink::util::StatusOr<std::unique_ptr<PublicKeyVerify>>
+  GetPrimitiveFromKey(const google::crypto::tink::EcdsaPublicKey&
+                          ecdsa_public_key) const override;
+
  private:
   // Friends that re-use proto validation helpers.
   friend class EcdsaPrivateKeyFactory;
   friend class EcdsaSignKeyManager;
 
-  static constexpr char kKeyTypePrefix[] = "type.googleapis.com/";
-  static constexpr char kKeyFormatUrl[] =
-      "type.googleapis.com/google.crypto.tink.EcdsaKeyFormat";
-
-  std::string key_type_;
   std::unique_ptr<KeyFactory> key_factory_;
 
-  // Constructs an instance of ECDSA PublicKeyVerify
-  // for the given 'key'.
-  crypto::tink::util::StatusOr<std::unique_ptr<PublicKeyVerify>>
-      GetPrimitiveImpl(
-          const google::crypto::tink::EcdsaPublicKey& ecdsa_public_key) const;
-
   static crypto::tink::util::Status Validate(
       const google::crypto::tink::EcdsaParams& params);
   static crypto::tink::util::Status Validate(
diff --git a/cc/signature/ecdsa_verify_key_manager_test.cc b/cc/signature/ecdsa_verify_key_manager_test.cc
index 7a0520f..38e6598 100644
--- a/cc/signature/ecdsa_verify_key_manager_test.cc
+++ b/cc/signature/ecdsa_verify_key_manager_test.cc
@@ -251,9 +251,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/signature/ed25519_sign_key_manager.cc b/cc/signature/ed25519_sign_key_manager.cc
new file mode 100644
index 0000000..8950bef
--- /dev/null
+++ b/cc/signature/ed25519_sign_key_manager.cc
@@ -0,0 +1,148 @@
+// Copyright 2019 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/signature/ed25519_sign_key_manager.h"
+
+#include "absl/memory/memory.h"
+#include "absl/strings/string_view.h"
+#include "tink/core/key_manager_base.h"
+#include "tink/key_manager.h"
+#include "tink/public_key_sign.h"
+#include "tink/signature/ed25519_verify_key_manager.h"
+#include "tink/subtle/ed25519_sign_boringssl.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/util/enums.h"
+#include "tink/util/errors.h"
+#include "tink/util/protobuf_helper.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "tink/util/validation.h"
+#include "proto/ed25519.pb.h"
+#include "proto/empty.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+
+using crypto::tink::util::Status;
+using crypto::tink::util::StatusOr;
+using google::crypto::tink::Ed25519PrivateKey;
+using google::crypto::tink::Empty;
+using google::crypto::tink::KeyData;
+
+class Ed25519PrivateKeyFactory
+    : public PrivateKeyFactory,
+      public KeyFactoryBase<Ed25519PrivateKey, Empty> {
+ public:
+  Ed25519PrivateKeyFactory() {}
+
+  KeyData::KeyMaterialType key_material_type() const override {
+    return KeyData::ASYMMETRIC_PRIVATE;
+  }
+
+  // Returns KeyData proto that contains Ed25519PublicKey
+  // extracted from the given serialized_private_key, which must contain
+  // Ed25519PrivateKey-proto.
+  crypto::tink::util::StatusOr<std::unique_ptr<google::crypto::tink::KeyData>>
+  GetPublicKeyData(absl::string_view serialized_private_key) const override;
+
+ protected:
+  StatusOr<std::unique_ptr<Ed25519PrivateKey>> NewKeyFromFormat(
+      const Empty& unused) const override;
+};
+
+StatusOr<std::unique_ptr<Ed25519PrivateKey>>
+Ed25519PrivateKeyFactory::NewKeyFromFormat(const Empty& unused) const {
+  auto key = subtle::SubtleUtilBoringSSL::GetNewEd25519Key();
+
+  // Build Ed25519PrivateKey.
+  std::unique_ptr<Ed25519PrivateKey> ed25519_private_key(
+      new Ed25519PrivateKey());
+  ed25519_private_key->set_version(Ed25519SignKeyManager::kVersion);
+  ed25519_private_key->set_key_value(key->private_key);
+
+  // Build Ed25519PublicKey.
+  auto ed25519_public_key = ed25519_private_key->mutable_public_key();
+  ed25519_public_key->set_version(Ed25519SignKeyManager::kVersion);
+  ed25519_public_key->set_key_value(key->public_key);
+
+  return absl::implicit_cast<StatusOr<std::unique_ptr<Ed25519PrivateKey>>>(
+      std::move(ed25519_private_key));
+}
+
+StatusOr<std::unique_ptr<KeyData>> Ed25519PrivateKeyFactory::GetPublicKeyData(
+    absl::string_view serialized_private_key) const {
+  Ed25519PrivateKey private_key;
+  if (!private_key.ParseFromString(std::string(serialized_private_key))) {
+    return Status(
+        util::error::INVALID_ARGUMENT,
+        absl::StrCat("Could not parse the passed string as proto '",
+                     Ed25519VerifyKeyManager::static_key_type(), "'."));
+    return util::Status::OK;
+  }
+  auto status = Ed25519SignKeyManager::Validate(private_key);
+  if (!status.ok()) return status;
+  auto key_data = absl::make_unique<KeyData>();
+  key_data->set_type_url(Ed25519VerifyKeyManager::static_key_type());
+  key_data->set_value(private_key.public_key().SerializeAsString());
+  key_data->set_key_material_type(KeyData::ASYMMETRIC_PUBLIC);
+  return std::move(key_data);
+}
+
+constexpr uint32_t Ed25519SignKeyManager::kVersion;
+
+Ed25519SignKeyManager::Ed25519SignKeyManager()
+    : key_factory_(absl::make_unique<Ed25519PrivateKeyFactory>()) {}
+
+const KeyFactory& Ed25519SignKeyManager::get_key_factory() const {
+  return *key_factory_;
+}
+
+uint32_t Ed25519SignKeyManager::get_version() const { return kVersion; }
+
+StatusOr<std::unique_ptr<PublicKeySign>>
+Ed25519SignKeyManager::GetPrimitiveFromKey(
+    const Ed25519PrivateKey& ed25519_private_key) const {
+  Status status = Validate(ed25519_private_key);
+  if (!status.ok()) return status;
+
+  // BoringSSL expects a 64-byte private key which contains the public key as a
+  // suffix.
+  std::string sk = ed25519_private_key.key_value() +
+              ed25519_private_key.public_key().key_value();
+
+  auto ed25519_result = subtle::Ed25519SignBoringSsl::New(sk);
+  if (!ed25519_result.ok()) return ed25519_result.status();
+
+  std::unique_ptr<PublicKeySign> ed25519(ed25519_result.ValueOrDie().release());
+  return std::move(ed25519);
+}
+
+// static
+Status Ed25519SignKeyManager::Validate(const Ed25519PrivateKey& key) {
+  Status status = ValidateVersion(key.version(), kVersion);
+  if (!status.ok()) return status;
+
+  if (key.key_value().length() != 32) {
+    return Status(util::error::INVALID_ARGUMENT,
+                  "The ED25519 private key must be 32-bytes long.");
+  }
+
+  return Ed25519VerifyKeyManager::Validate(key.public_key());
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/signature/ed25519_sign_key_manager.h b/cc/signature/ed25519_sign_key_manager.h
new file mode 100644
index 0000000..7e62a1f
--- /dev/null
+++ b/cc/signature/ed25519_sign_key_manager.h
@@ -0,0 +1,70 @@
+// Copyright 2019 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_SIGNATURE_ED25519_SIGN_KEY_MANAGER_H_
+#define TINK_SIGNATURE_ED25519_SIGN_KEY_MANAGER_H_
+
+#include <algorithm>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "tink/core/key_manager_base.h"
+#include "tink/key_manager.h"
+#include "tink/public_key_sign.h"
+#include "tink/util/errors.h"
+#include "tink/util/protobuf_helper.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "proto/ed25519.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+
+class Ed25519SignKeyManager
+    : public KeyManagerBase<PublicKeySign,
+                            google::crypto::tink::Ed25519PrivateKey> {
+ public:
+  static constexpr uint32_t kVersion = 0;
+
+  Ed25519SignKeyManager();
+
+  // Returns the version of this key manager.
+  uint32_t get_version() const override;
+
+  // Returns a factory that generates keys of the key type
+  // handled by this manager.
+  const KeyFactory& get_key_factory() const override;
+
+  ~Ed25519SignKeyManager() override {}
+
+ protected:
+  crypto::tink::util::StatusOr<std::unique_ptr<PublicKeySign>>
+  GetPrimitiveFromKey(const google::crypto::tink::Ed25519PrivateKey&
+                          ed25519_private_key) const override;
+
+ private:
+  friend class Ed25519PrivateKeyFactory;
+
+  std::unique_ptr<KeyFactory> key_factory_;
+
+  static crypto::tink::util::Status Validate(
+      const google::crypto::tink::Ed25519PrivateKey& key);
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_SIGNATURE_ED25519_SIGN_KEY_MANAGER_H_
diff --git a/cc/signature/ed25519_sign_key_manager_test.cc b/cc/signature/ed25519_sign_key_manager_test.cc
new file mode 100644
index 0000000..602a700
--- /dev/null
+++ b/cc/signature/ed25519_sign_key_manager_test.cc
@@ -0,0 +1,197 @@
+// Copyright 2019 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/signature/ed25519_sign_key_manager.h"
+
+#include "gtest/gtest.h"
+#include "tink/aead/aead_key_templates.h"
+#include "tink/aead/aes_gcm_key_manager.h"
+#include "tink/public_key_sign.h"
+#include "tink/registry.h"
+#include "tink/signature/ed25519_verify_key_manager.h"
+#include "tink/signature/signature_key_templates.h"
+#include "tink/util/protobuf_helper.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_util.h"
+#include "proto/aes_eax.pb.h"
+#include "proto/common.pb.h"
+#include "proto/ed25519.pb.h"
+#include "proto/empty.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+
+using google::crypto::tink::AesEaxKey;
+using google::crypto::tink::Ed25519PrivateKey;
+using google::crypto::tink::Empty;
+using google::crypto::tink::KeyData;
+
+namespace {
+
+class Ed25519SignKeyManagerTest : public ::testing::Test {
+ protected:
+  std::string key_type_prefix_ = "type.googleapis.com/";
+  std::string ed25519_sign_key_type_ =
+      "type.googleapis.com/google.crypto.tink.Ed25519PrivateKey";
+};
+
+TEST_F(Ed25519SignKeyManagerTest, testBasic) {
+  Ed25519SignKeyManager key_manager;
+
+  EXPECT_EQ(0, key_manager.get_version());
+  EXPECT_EQ("type.googleapis.com/google.crypto.tink.Ed25519PrivateKey",
+            key_manager.get_key_type());
+  EXPECT_TRUE(key_manager.DoesSupport(key_manager.get_key_type()));
+}
+
+TEST_F(Ed25519SignKeyManagerTest, testKeyDataErrors) {
+  Ed25519SignKeyManager key_manager;
+
+  {  // Bad key type.
+    KeyData key_data;
+    std::string bad_key_type = "type.googleapis.com/google.crypto.tink.SomeOtherKey";
+    key_data.set_type_url(bad_key_type);
+    auto result = key_manager.GetPrimitive(key_data);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "not supported",
+                        result.status().error_message());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, bad_key_type,
+                        result.status().error_message());
+  }
+
+  {  // Bad key value.
+    KeyData key_data;
+    key_data.set_type_url(ed25519_sign_key_type_);
+    key_data.set_value("some bad serialized proto");
+    auto result = key_manager.GetPrimitive(key_data);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "not parse",
+                        result.status().error_message());
+  }
+
+  {  // Bad version.
+    KeyData key_data;
+    Ed25519PrivateKey key;
+    key.set_version(1);
+    key_data.set_type_url(ed25519_sign_key_type_);
+    key_data.set_value(key.SerializeAsString());
+    auto result = key_manager.GetPrimitive(key_data);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "version",
+                        result.status().error_message());
+  }
+}
+
+TEST_F(Ed25519SignKeyManagerTest, testKeyMessageErrors) {
+  Ed25519SignKeyManager key_manager;
+
+  {  // Bad protobuffer.
+    AesEaxKey key;
+    auto result = key_manager.GetPrimitive(key);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "AesEaxKey",
+                        result.status().error_message());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "not supported",
+                        result.status().error_message());
+  }
+}
+
+TEST_F(Ed25519SignKeyManagerTest, testPrimitives) {
+  std::string message = "some message to sign";
+  Ed25519SignKeyManager sign_key_manager;
+  Ed25519PrivateKey key = test::GetEd25519TestPrivateKey();
+
+  {  // Using Key proto.
+    auto result = sign_key_manager.GetPrimitive(key);
+    EXPECT_TRUE(result.ok()) << result.status();
+    auto sign = std::move(result.ValueOrDie());
+    auto signing_result = sign->Sign(message);
+    EXPECT_TRUE(signing_result.ok()) << signing_result.status();
+  }
+
+  {  // Using KeyData proto.
+    KeyData key_data;
+    key_data.set_type_url(ed25519_sign_key_type_);
+    key_data.set_value(key.SerializeAsString());
+    auto result = sign_key_manager.GetPrimitive(key_data);
+    EXPECT_TRUE(result.ok()) << result.status();
+    auto sign = std::move(result.ValueOrDie());
+    auto signing_result = sign->Sign(message);
+    EXPECT_TRUE(signing_result.ok()) << signing_result.status();
+  }
+}
+
+TEST_F(Ed25519SignKeyManagerTest, testPublicKeyExtraction) {
+  Ed25519SignKeyManager key_manager;
+  auto private_key_factory =
+      dynamic_cast<const PrivateKeyFactory*>(&(key_manager.get_key_factory()));
+  ASSERT_NE(private_key_factory, nullptr);
+
+  auto new_key_result =
+      private_key_factory->NewKey(SignatureKeyTemplates::Ed25519().value());
+  std::unique_ptr<Ed25519PrivateKey> new_key(
+      reinterpret_cast<Ed25519PrivateKey*>(
+          new_key_result.ValueOrDie().release()));
+  auto public_key_data_result =
+      private_key_factory->GetPublicKeyData(new_key->SerializeAsString());
+  EXPECT_TRUE(public_key_data_result.ok()) << public_key_data_result.status();
+  auto public_key_data = std::move(public_key_data_result.ValueOrDie());
+  EXPECT_EQ(Ed25519VerifyKeyManager::static_key_type(),
+            public_key_data->type_url());
+  EXPECT_EQ(KeyData::ASYMMETRIC_PUBLIC, public_key_data->key_material_type());
+  EXPECT_EQ(new_key->public_key().SerializeAsString(),
+            public_key_data->value());
+}
+
+TEST_F(Ed25519SignKeyManagerTest, testPublicKeyExtractionErrors) {
+  Ed25519SignKeyManager key_manager;
+  auto private_key_factory =
+      dynamic_cast<const PrivateKeyFactory*>(&(key_manager.get_key_factory()));
+  ASSERT_NE(private_key_factory, nullptr);
+
+  AesGcmKeyManager aead_key_manager;
+  auto aead_private_key_factory = dynamic_cast<const PrivateKeyFactory*>(
+      &(aead_key_manager.get_key_factory()));
+  ASSERT_EQ(nullptr, aead_private_key_factory);
+
+  auto aead_key_result = aead_key_manager.get_key_factory().NewKey(
+      AeadKeyTemplates::Aes128Gcm().value());
+  ASSERT_TRUE(aead_key_result.ok()) << aead_key_result.status();
+  auto aead_key = std::move(aead_key_result.ValueOrDie());
+  auto public_key_data_result =
+      private_key_factory->GetPublicKeyData(aead_key->SerializeAsString());
+  EXPECT_FALSE(public_key_data_result.ok());
+  EXPECT_EQ(util::error::INVALID_ARGUMENT,
+            public_key_data_result.status().error_code());
+}
+
+TEST_F(Ed25519SignKeyManagerTest, testNewKey) {
+  Ed25519SignKeyManager key_manager;
+  const KeyFactory& key_factory = key_manager.get_key_factory();
+  Empty key_format;
+  auto result = key_factory.NewKey(key_format);
+  EXPECT_TRUE(result.ok());
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/signature/ed25519_verify_key_manager.cc b/cc/signature/ed25519_verify_key_manager.cc
new file mode 100644
index 0000000..52abae0
--- /dev/null
+++ b/cc/signature/ed25519_verify_key_manager.cc
@@ -0,0 +1,81 @@
+// Copyright 2019 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/signature/ed25519_verify_key_manager.h"
+
+#include "absl/strings/string_view.h"
+#include "tink/key_manager.h"
+#include "tink/public_key_verify.h"
+#include "tink/subtle/ed25519_verify_boringssl.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/util/errors.h"
+#include "tink/util/protobuf_helper.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "tink/util/validation.h"
+#include "proto/ed25519.pb.h"
+
+namespace crypto {
+namespace tink {
+
+using crypto::tink::util::Status;
+using crypto::tink::util::StatusOr;
+using google::crypto::tink::Ed25519PublicKey;
+
+constexpr uint32_t Ed25519VerifyKeyManager::kVersion;
+
+Ed25519VerifyKeyManager::Ed25519VerifyKeyManager()
+    : key_factory_(KeyFactory::AlwaysFailingFactory(
+          util::Status(util::error::UNIMPLEMENTED,
+                       "Operation not supported for public keys, "
+                       "please use the Ed25519SignKeyManager."))) {}
+
+const KeyFactory& Ed25519VerifyKeyManager::get_key_factory() const {
+  return *key_factory_;
+}
+
+uint32_t Ed25519VerifyKeyManager::get_version() const { return kVersion; }
+
+StatusOr<std::unique_ptr<PublicKeyVerify>>
+Ed25519VerifyKeyManager::GetPrimitiveFromKey(
+    const Ed25519PublicKey& ed25519_public_key) const {
+  Status status = Validate(ed25519_public_key);
+  if (!status.ok()) return status;
+
+  auto ed25519_result =
+      subtle::Ed25519VerifyBoringSsl::New(ed25519_public_key.key_value());
+  if (!ed25519_result.ok()) return ed25519_result.status();
+
+  std::unique_ptr<PublicKeyVerify> ed25519(
+      ed25519_result.ValueOrDie().release());
+  return std::move(ed25519);
+}
+
+// static
+Status Ed25519VerifyKeyManager::Validate(const Ed25519PublicKey& key) {
+  Status status = ValidateVersion(key.version(), kVersion);
+  if (!status.ok()) return status;
+
+  if (key.key_value().length() != 32) {
+    return Status(util::error::INVALID_ARGUMENT,
+                  "The ED25519 public key must be 32-bytes long.");
+  }
+
+  return Status::OK;
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/signature/ed25519_verify_key_manager.h b/cc/signature/ed25519_verify_key_manager.h
new file mode 100644
index 0000000..3cacbec
--- /dev/null
+++ b/cc/signature/ed25519_verify_key_manager.h
@@ -0,0 +1,71 @@
+// Copyright 2019 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_SIGNATURE_ED25519_VERIFY_KEY_MANAGER_H_
+#define TINK_SIGNATURE_ED25519_VERIFY_KEY_MANAGER_H_
+
+#include <algorithm>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "tink/core/key_manager_base.h"
+#include "tink/key_manager.h"
+#include "tink/public_key_verify.h"
+#include "tink/util/errors.h"
+#include "tink/util/protobuf_helper.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "proto/ed25519.pb.h"
+
+namespace crypto {
+namespace tink {
+
+class Ed25519VerifyKeyManager
+    : public KeyManagerBase<PublicKeyVerify,
+                            google::crypto::tink::Ed25519PublicKey> {
+ public:
+  static constexpr uint32_t kVersion = 0;
+
+  Ed25519VerifyKeyManager();
+
+  // Returns the version of this key manager.
+  uint32_t get_version() const override;
+
+  // Returns a factory that generates keys of the key type
+  // handled by this manager.
+  const KeyFactory& get_key_factory() const override;
+
+  ~Ed25519VerifyKeyManager() override {}
+
+ protected:
+  crypto::tink::util::StatusOr<std::unique_ptr<PublicKeyVerify>>
+  GetPrimitiveFromKey(const google::crypto::tink::Ed25519PublicKey&
+                          ed25519_public_key) const override;
+
+ private:
+  // Friends that re-use proto validation helpers.
+  friend class Ed25519PrivateKeyFactory;
+  friend class Ed25519SignKeyManager;
+
+  std::unique_ptr<KeyFactory> key_factory_;
+
+  static crypto::tink::util::Status Validate(
+      const google::crypto::tink::Ed25519PublicKey& key);
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_SIGNATURE_ED25519_VERIFY_KEY_MANAGER_H_
diff --git a/cc/signature/ed25519_verify_key_manager_test.cc b/cc/signature/ed25519_verify_key_manager_test.cc
new file mode 100644
index 0000000..77ffdb2
--- /dev/null
+++ b/cc/signature/ed25519_verify_key_manager_test.cc
@@ -0,0 +1,160 @@
+// Copyright 2019 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/signature/ed25519_verify_key_manager.h"
+
+#include "gtest/gtest.h"
+#include "tink/public_key_sign.h"
+#include "tink/public_key_verify.h"
+#include "tink/registry.h"
+#include "tink/signature/ed25519_sign_key_manager.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_util.h"
+#include "proto/aes_eax.pb.h"
+#include "proto/common.pb.h"
+#include "proto/ed25519.pb.h"
+#include "proto/empty.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+
+using google::crypto::tink::AesEaxKey;
+using google::crypto::tink::Ed25519PrivateKey;
+using google::crypto::tink::Ed25519PublicKey;
+using google::crypto::tink::Empty;
+using google::crypto::tink::KeyData;
+
+namespace {
+
+class Ed25519VerifyKeyManagerTest : public ::testing::Test {
+ protected:
+  std::string key_type_prefix_ = "type.googleapis.com/";
+  std::string ed25519_verify_key_type_ =
+      "type.googleapis.com/google.crypto.tink.Ed25519PublicKey";
+};
+
+TEST_F(Ed25519VerifyKeyManagerTest, testBasic) {
+  Ed25519VerifyKeyManager key_manager;
+
+  EXPECT_EQ(0, key_manager.get_version());
+  EXPECT_EQ("type.googleapis.com/google.crypto.tink.Ed25519PublicKey",
+            key_manager.get_key_type());
+  EXPECT_TRUE(key_manager.DoesSupport(key_manager.get_key_type()));
+}
+
+TEST_F(Ed25519VerifyKeyManagerTest, testKeyDataErrors) {
+  Ed25519VerifyKeyManager key_manager;
+
+  {  // Bad key type.
+    KeyData key_data;
+    std::string bad_key_type = "type.googleapis.com/google.crypto.tink.SomeOtherKey";
+    key_data.set_type_url(bad_key_type);
+    auto result = key_manager.GetPrimitive(key_data);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "not supported",
+                        result.status().error_message());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, bad_key_type,
+                        result.status().error_message());
+  }
+
+  {  // Bad key value.
+    KeyData key_data;
+    key_data.set_type_url(ed25519_verify_key_type_);
+    key_data.set_value("some bad serialized proto");
+    auto result = key_manager.GetPrimitive(key_data);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "not parse",
+                        result.status().error_message());
+  }
+
+  {  // Bad version.
+    KeyData key_data;
+    Ed25519PublicKey key;
+    key.set_version(1);
+    key_data.set_type_url(ed25519_verify_key_type_);
+    key_data.set_value(key.SerializeAsString());
+    auto result = key_manager.GetPrimitive(key_data);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "version",
+                        result.status().error_message());
+  }
+}
+
+TEST_F(Ed25519VerifyKeyManagerTest, testKeyMessageErrors) {
+  Ed25519VerifyKeyManager key_manager;
+
+  {  // Bad protobuffer.
+    AesEaxKey key;
+    auto result = key_manager.GetPrimitive(key);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "AesEaxKey",
+                        result.status().error_message());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "not supported",
+                        result.status().error_message());
+  }
+}
+
+TEST_F(Ed25519VerifyKeyManagerTest, testPrimitives) {
+  std::string message = "some message to sign";
+  Ed25519SignKeyManager sign_key_manager;
+  Ed25519VerifyKeyManager verify_key_manager;
+  Ed25519PrivateKey private_key = test::GetEd25519TestPrivateKey();
+  Ed25519PublicKey key = private_key.public_key();
+  auto sign =
+      std::move(sign_key_manager.GetPrimitive(private_key).ValueOrDie());
+  std::string signature = sign->Sign(message).ValueOrDie();
+
+  {  // Using Key proto.
+    auto result = verify_key_manager.GetPrimitive(key);
+    EXPECT_TRUE(result.ok()) << result.status();
+    auto verify = std::move(result.ValueOrDie());
+    auto verify_status = verify->Verify(signature, message);
+    EXPECT_TRUE(verify_status.ok()) << verify_status;
+  }
+
+  {  // Using KeyData proto.
+    KeyData key_data;
+    key_data.set_type_url(ed25519_verify_key_type_);
+    key_data.set_value(key.SerializeAsString());
+    auto result = verify_key_manager.GetPrimitive(key_data);
+    EXPECT_TRUE(result.ok()) << result.status();
+    auto verify = std::move(result.ValueOrDie());
+    auto verify_status = verify->Verify(signature, message);
+    EXPECT_TRUE(verify_status.ok()) << verify_status;
+  }
+}
+
+TEST_F(Ed25519VerifyKeyManagerTest, testNewKey) {
+  Ed25519VerifyKeyManager key_manager;
+  const KeyFactory& key_factory = key_manager.get_key_factory();
+  Empty key_format;
+  auto result = key_factory.NewKey(key_format);
+  EXPECT_FALSE(result.ok());
+  EXPECT_PRED_FORMAT2(testing::IsSubstring,
+                      "Operation not supported for public keys, please use the "
+                      "Ed25519SignKeyManager.",
+                      result.status().error_message());
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/signature/public_key_sign_catalogue.cc b/cc/signature/public_key_sign_catalogue.cc
index ee72574..537d25c 100644
--- a/cc/signature/public_key_sign_catalogue.cc
+++ b/cc/signature/public_key_sign_catalogue.cc
@@ -18,8 +18,11 @@
 
 #include "absl/strings/ascii.h"
 #include "tink/catalogue.h"
-#include "tink/signature/ecdsa_sign_key_manager.h"
 #include "tink/key_manager.h"
+#include "tink/signature/ecdsa_sign_key_manager.h"
+#include "tink/signature/ed25519_sign_key_manager.h"
+#include "tink/signature/rsa_ssa_pkcs1_sign_key_manager.h"
+#include "tink/signature/rsa_ssa_pss_sign_key_manager.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 
@@ -30,13 +33,26 @@
 
 crypto::tink::util::StatusOr<std::unique_ptr<KeyManager<PublicKeySign>>>
 CreateKeyManager(const std::string& type_url) {
-  if (type_url == EcdsaSignKeyManager::kKeyType) {
+  if (type_url == EcdsaSignKeyManager::static_key_type()) {
     std::unique_ptr<KeyManager<PublicKeySign>> manager(
         new EcdsaSignKeyManager());
     return std::move(manager);
+  } else if (type_url == Ed25519SignKeyManager::static_key_type()) {
+    std::unique_ptr<KeyManager<PublicKeySign>> manager(
+        new Ed25519SignKeyManager());
+    return std::move(manager);
+  } else if (type_url == RsaSsaPkcs1SignKeyManager::static_key_type()) {
+    std::unique_ptr<KeyManager<PublicKeySign>> manager(
+        new RsaSsaPkcs1SignKeyManager());
+    return std::move(manager);
+  } else if (type_url == RsaSsaPssSignKeyManager::static_key_type()) {
+    std::unique_ptr<KeyManager<PublicKeySign>> manager(
+        new RsaSsaPssSignKeyManager());
+    return std::move(manager);
+  } else {
+    return ToStatusF(crypto::tink::util::error::NOT_FOUND,
+                     "No key manager for type_url '%s'.", type_url.c_str());
   }
-  return ToStatusF(crypto::tink::util::error::NOT_FOUND,
-                   "No key manager for type_url '%s'.", type_url.c_str());
 }
 
 }  // anonymous namespace
diff --git a/cc/signature/public_key_sign_catalogue_test.cc b/cc/signature/public_key_sign_catalogue_test.cc
index 75e5c91..9368536 100644
--- a/cc/signature/public_key_sign_catalogue_test.cc
+++ b/cc/signature/public_key_sign_catalogue_test.cc
@@ -61,9 +61,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/signature/public_key_sign_factory.cc b/cc/signature/public_key_sign_factory.cc
index d614960..e0fe718 100644
--- a/cc/signature/public_key_sign_factory.cc
+++ b/cc/signature/public_key_sign_factory.cc
@@ -16,11 +16,11 @@
 
 #include "tink/signature/public_key_sign_factory.h"
 
-#include "tink/public_key_sign.h"
 #include "tink/key_manager.h"
 #include "tink/keyset_handle.h"
+#include "tink/public_key_sign.h"
 #include "tink/registry.h"
-#include "tink/signature/public_key_sign_set_wrapper.h"
+#include "tink/signature/public_key_sign_wrapper.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 
@@ -30,20 +30,25 @@
 // static
 util::StatusOr<std::unique_ptr<PublicKeySign>>
 PublicKeySignFactory::GetPrimitive(const KeysetHandle& keyset_handle) {
-  return GetPrimitive(keyset_handle, nullptr);
+  util::Status status = Registry::RegisterPrimitiveWrapper(
+      absl::make_unique<PublicKeySignWrapper>());
+  if (!status.ok()) {
+    return status;
+  }
+  return keyset_handle.GetPrimitive<PublicKeySign>();
 }
 
 // static
 util::StatusOr<std::unique_ptr<PublicKeySign>>
-PublicKeySignFactory::GetPrimitive(const KeysetHandle& keyset_handle,
+PublicKeySignFactory::GetPrimitive(
+    const KeysetHandle& keyset_handle,
     const KeyManager<PublicKeySign>* custom_key_manager) {
-  auto primitives_result = Registry::GetPrimitives<PublicKeySign>(
-      keyset_handle, custom_key_manager);
-  if (primitives_result.ok()) {
-    return PublicKeySignSetWrapper::NewPublicKeySign(
-        std::move(primitives_result.ValueOrDie()));
+  util::Status status = Registry::RegisterPrimitiveWrapper(
+      absl::make_unique<PublicKeySignWrapper>());
+  if (!status.ok()) {
+    return status;
   }
-  return primitives_result.status();
+  return keyset_handle.GetPrimitive<PublicKeySign>(custom_key_manager);
 }
 
 }  // namespace tink
diff --git a/cc/signature/public_key_sign_factory.h b/cc/signature/public_key_sign_factory.h
index 69a75f1..35e6e78 100644
--- a/cc/signature/public_key_sign_factory.h
+++ b/cc/signature/public_key_sign_factory.h
@@ -17,48 +17,39 @@
 #ifndef TINK_SIGNATURE_PUBLIC_KEY_SIGN_FACTORY_H_
 #define TINK_SIGNATURE_PUBLIC_KEY_SIGN_FACTORY_H_
 
-#include "tink/public_key_sign.h"
+#include "absl/base/macros.h"
 #include "tink/key_manager.h"
 #include "tink/keyset_handle.h"
+#include "tink/public_key_sign.h"
 #include "tink/util/statusor.h"
 
 namespace crypto {
 namespace tink {
 
 ///////////////////////////////////////////////////////////////////////////////
-// PublicKeySignFactory allows for obtaining an PublicKeySign primitive
-// from a KeysetHandle.
+// This class is deprecated. Call keyset_handle->GetPrimitive<PublicKeySign>()
+// instead.
 //
-// PublicKeySignFactory gets primitives from the Registry, which can
-// be initialized via a convenience method from SignatureConfig-class.
-// Here is an example how one can obtain and use a PublicKeySign primitive:
-//
-//   auto status = SignatureConfig::Register();
-//   if (!status.ok()) { /* fail with error */ }
-//   KeysetHandle keyset_handle = ...;
-//   std::unique_ptr<PublicKeySign> public_key_sign = std::move(
-//           PublicKeySignFactory::GetPrimitive(keyset_handle).ValueOrDie());
-//   std::string data = ...;
-//   auto sign_result = public_key_sign.Sign(data);
-//   if (!sign_result.ok()) {
-//     // Signing failed.
-//     // ...
-//   }
-//   std::string signature = sign_result.ValueOrDie();
-//
-class PublicKeySignFactory {
+// Note that in order to for this change to be safe, the AeadSetWrapper has to
+// be registered in your binary before this call. This happens automatically if
+// you call one of
+// * SignatureConfig::Register()
+// * TinkConfig::Register()
+class ABSL_DEPRECATED(
+    "Call getPrimitive<PublicKeySign>() on the keyset_handle after registering "
+    "the PublicKeySignWrapper instead.") PublicKeySignFactory {
  public:
   // Returns a PublicKeySign-primitive that uses key material from the keyset
   // specified via 'keyset_handle'.
   static crypto::tink::util::StatusOr<std::unique_ptr<PublicKeySign>>
-      GetPrimitive(const KeysetHandle& keyset_handle);
+  GetPrimitive(const KeysetHandle& keyset_handle);
 
   // Returns a PublicKeySign-primitive that uses key material from the keyset
   // specified via 'keyset_handle' and is instantiated by the given
   // 'custom_key_manager' (instead of the key manager from the Registry).
   static crypto::tink::util::StatusOr<std::unique_ptr<PublicKeySign>>
-      GetPrimitive(const KeysetHandle& keyset_handle,
-                   const KeyManager<PublicKeySign>* custom_key_manager);
+  GetPrimitive(const KeysetHandle& keyset_handle,
+               const KeyManager<PublicKeySign>* custom_key_manager);
 
  private:
   PublicKeySignFactory() {}
diff --git a/cc/signature/public_key_sign_factory_test.cc b/cc/signature/public_key_sign_factory_test.cc
index b63a9bb..fc70fa0 100644
--- a/cc/signature/public_key_sign_factory_test.cc
+++ b/cc/signature/public_key_sign_factory_test.cc
@@ -105,9 +105,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/signature/public_key_sign_set_wrapper.cc b/cc/signature/public_key_sign_wrapper.cc
similarity index 65%
rename from cc/signature/public_key_sign_set_wrapper.cc
rename to cc/signature/public_key_sign_wrapper.cc
index 26239e6..06c8b5c 100644
--- a/cc/signature/public_key_sign_set_wrapper.cc
+++ b/cc/signature/public_key_sign_wrapper.cc
@@ -14,16 +14,20 @@
 //
 ///////////////////////////////////////////////////////////////////////////////
 
-#include "tink/signature/public_key_sign_set_wrapper.h"
+#include "tink/signature/public_key_sign_wrapper.h"
 
 #include "tink/crypto_format.h"
 #include "tink/primitive_set.h"
 #include "tink/public_key_sign.h"
 #include "tink/subtle/subtle_util_boringssl.h"
 #include "tink/util/statusor.h"
+#include "proto/tink.pb.h"
 
 namespace crypto {
 namespace tink {
+
+using google::crypto::tink::OutputPrefixType;
+
 namespace {
 
 util::Status Validate(PrimitiveSet<PublicKeySign>* public_key_sign_set) {
@@ -38,18 +42,20 @@
   return util::Status::OK;
 }
 
-}  // anonymous namespace
+class PublicKeySignSetWrapper : public PublicKeySign {
+ public:
+  explicit PublicKeySignSetWrapper(
+      std::unique_ptr<PrimitiveSet<PublicKeySign>> public_key_sign_set)
+      : public_key_sign_set_(std::move(public_key_sign_set)) {}
 
-// static
-util::StatusOr<std::unique_ptr<PublicKeySign>>
-PublicKeySignSetWrapper::NewPublicKeySign(
-    std::unique_ptr<PrimitiveSet<PublicKeySign>> public_key_sign_set) {
-  util::Status status = Validate(public_key_sign_set.get());
-  if (!status.ok()) return status;
-  std::unique_ptr<PublicKeySign> public_key_sign(
-      new PublicKeySignSetWrapper(std::move(public_key_sign_set)));
-  return std::move(public_key_sign);
-}
+  crypto::tink::util::StatusOr<std::string> Sign(
+      absl::string_view data) const override;
+
+  ~PublicKeySignSetWrapper() override {}
+
+ private:
+  std::unique_ptr<PrimitiveSet<PublicKeySign>> public_key_sign_set_;
+};
 
 util::StatusOr<std::string> PublicKeySignSetWrapper::Sign(
     absl::string_view data) const {
@@ -58,11 +64,28 @@
   data = subtle::SubtleUtilBoringSSL::EnsureNonNull(data);
 
   auto primary = public_key_sign_set_->get_primary();
+  std::string local_data;
+  if (primary->get_output_prefix_type() == OutputPrefixType::LEGACY) {
+    local_data = std::string(data);
+    local_data.append(1, CryptoFormat::kLegacyStartByte);
+    data = local_data;
+  }
   auto sign_result = primary->get_primitive().Sign(data);
   if (!sign_result.ok()) return sign_result.status();
   const std::string& key_id = primary->get_identifier();
   return key_id + sign_result.ValueOrDie();
 }
 
+}  // anonymous namespace
+
+util::StatusOr<std::unique_ptr<PublicKeySign>> PublicKeySignWrapper::Wrap(
+    std::unique_ptr<PrimitiveSet<PublicKeySign>> primitive_set) const {
+  util::Status status = Validate(primitive_set.get());
+  if (!status.ok()) return status;
+  std::unique_ptr<PublicKeySign> public_key_sign(
+      new PublicKeySignSetWrapper(std::move(primitive_set)));
+  return std::move(public_key_sign);
+}
+
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/signature/public_key_sign_set_wrapper.h b/cc/signature/public_key_sign_wrapper.h
similarity index 64%
rename from cc/signature/public_key_sign_set_wrapper.h
rename to cc/signature/public_key_sign_wrapper.h
index 8c0faa0..bc8f5a6 100644
--- a/cc/signature/public_key_sign_set_wrapper.h
+++ b/cc/signature/public_key_sign_wrapper.h
@@ -14,12 +14,13 @@
 //
 ///////////////////////////////////////////////////////////////////////////////
 
-#ifndef TINK_SIGNATURE_PUBLIC_KEY_SIGN_SET_WRAPPER_H_
-#define TINK_SIGNATURE_PUBLIC_KEY_SIGN_SET_WRAPPER_H_
+#ifndef TINK_SIGNATURE_PUBLIC_KEY_SIGN_WRAPPER_H_
+#define TINK_SIGNATURE_PUBLIC_KEY_SIGN_WRAPPER_H_
 
 #include "absl/strings/string_view.h"
-#include "tink/public_key_sign.h"
 #include "tink/primitive_set.h"
+#include "tink/primitive_wrapper.h"
+#include "tink/public_key_sign.h"
 #include "tink/util/statusor.h"
 #include "proto/tink.pb.h"
 
@@ -30,29 +31,17 @@
 // and combines them into a single PublicKeySign-primitive,
 // that for the actual verification uses the instance that maches the
 // signature prefix.
-class PublicKeySignSetWrapper : public PublicKeySign {
+class PublicKeySignWrapper : public PrimitiveWrapper<PublicKeySign> {
  public:
   // Returns an PublicKeySign-primitive that uses the primary
   // PublicKeySign-instance provided in 'public_key_sign_set',
   // which must be non-NULL (and must contain a primary instance).
-  static crypto::tink::util::StatusOr<std::unique_ptr<PublicKeySign>>
-      NewPublicKeySign(
-          std::unique_ptr<PrimitiveSet<PublicKeySign>> public_key_sign_set);
-
-  crypto::tink::util::StatusOr<std::string> Sign(absl::string_view data)
+  crypto::tink::util::StatusOr<std::unique_ptr<PublicKeySign>> Wrap(
+      std::unique_ptr<PrimitiveSet<PublicKeySign>> primitive_set)
       const override;
-
-  virtual ~PublicKeySignSetWrapper() {}
-
- private:
-  std::unique_ptr<PrimitiveSet<PublicKeySign>> public_key_sign_set_;
-
-  PublicKeySignSetWrapper(
-      std::unique_ptr<PrimitiveSet<PublicKeySign>> public_key_sign_set)
-      : public_key_sign_set_(std::move(public_key_sign_set)) {}
 };
 
 }  // namespace tink
 }  // namespace crypto
 
-#endif  // TINK_SIGNATURE_PUBLIC_KEY_SIGN_SET_WRAPPER_H_
+#endif  // TINK_SIGNATURE_PUBLIC_KEY_SIGN_WRAPPER_H_
diff --git a/cc/signature/public_key_sign_set_wrapper_test.cc b/cc/signature/public_key_sign_wrapper_test.cc
similarity index 64%
rename from cc/signature/public_key_sign_set_wrapper_test.cc
rename to cc/signature/public_key_sign_wrapper_test.cc
index 98f3584..2fb1bf9 100644
--- a/cc/signature/public_key_sign_set_wrapper_test.cc
+++ b/cc/signature/public_key_sign_wrapper_test.cc
@@ -14,12 +14,13 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-#include "tink/signature/public_key_sign_set_wrapper.h"
-#include "tink/public_key_sign.h"
+#include "tink/signature/public_key_sign_wrapper.h"
+#include "gtest/gtest.h"
+#include "tink/crypto_format.h"
 #include "tink/primitive_set.h"
+#include "tink/public_key_sign.h"
 #include "tink/util/status.h"
 #include "tink/util/test_util.h"
-#include "gtest/gtest.h"
 
 using crypto::tink::test::DummyPublicKeySign;
 using crypto::tink::test::DummyPublicKeyVerify;
@@ -40,8 +41,7 @@
 
 TEST_F(PublicKeySignSetWrapperTest, testBasic) {
   { // pk_sign_set is nullptr.
-    auto pk_sign_result =
-        PublicKeySignSetWrapper::NewPublicKeySign(nullptr);
+    auto pk_sign_result = PublicKeySignWrapper().Wrap(nullptr);
     EXPECT_FALSE(pk_sign_result.ok());
     EXPECT_EQ(util::error::INTERNAL, pk_sign_result.status().error_code());
     EXPECT_PRED_FORMAT2(testing::IsSubstring, "non-NULL",
@@ -51,8 +51,7 @@
   { // pk_sign_set has no primary primitive.
     std::unique_ptr<PrimitiveSet<PublicKeySign>>
         pk_sign_set(new PrimitiveSet<PublicKeySign>());
-    auto pk_sign_result =
-        PublicKeySignSetWrapper::NewPublicKeySign(std::move(pk_sign_set));
+    auto pk_sign_result = PublicKeySignWrapper().Wrap(std::move(pk_sign_set));
     EXPECT_FALSE(pk_sign_result.ok());
     EXPECT_EQ(util::error::INVALID_ARGUMENT,
         pk_sign_result.status().error_code());
@@ -66,7 +65,7 @@
 
     uint32_t key_id_0 = 1234543;
     key = keyset.add_key();
-    key->set_output_prefix_type(OutputPrefixType::RAW);
+    key->set_output_prefix_type(OutputPrefixType::TINK);
     key->set_key_id(key_id_0);
 
     uint32_t key_id_1 = 726329;
@@ -76,7 +75,7 @@
 
     uint32_t key_id_2 = 7213743;
     key = keyset.add_key();
-    key->set_output_prefix_type(OutputPrefixType::TINK);
+    key->set_output_prefix_type(OutputPrefixType::RAW);
     key->set_key_id(key_id_2);
 
     std::string signature_name_0 = "signature_0";
@@ -105,8 +104,7 @@
     pk_sign_set->set_primary(entry_result.ValueOrDie());
 
     // Wrap pk_sign_set and test the resulting PublicKeySign.
-    auto pk_sign_result = PublicKeySignSetWrapper::NewPublicKeySign(
-        std::move(pk_sign_set));
+    auto pk_sign_result = PublicKeySignWrapper().Wrap(std::move(pk_sign_set));
     EXPECT_TRUE(pk_sign_result.ok()) << pk_sign_result.status();
     pk_sign = std::move(pk_sign_result.ValueOrDie());
     std::string data = "some data to sign";
@@ -120,12 +118,49 @@
   }
 }
 
+TEST_F(PublicKeySignSetWrapperTest, testLegacySignatures) {
+    // Prepare a set for the wrapper.
+    Keyset::Key key;
+    uint32_t key_id = 1234543;
+    key.set_output_prefix_type(OutputPrefixType::LEGACY);
+    key.set_key_id(key_id);
+    std::string signature_name = "SomeLegacySignatures";
+
+    std::unique_ptr<PrimitiveSet<PublicKeySign>> pk_sign_set(
+        new PrimitiveSet<PublicKeySign>());
+    std::string data = "Some data to sign";
+    std::unique_ptr<PublicKeySign> pk_sign(
+        new DummyPublicKeySign(signature_name));
+    auto entry_result = pk_sign_set->AddPrimitive(std::move(pk_sign), key);
+    ASSERT_TRUE(entry_result.ok());
+    pk_sign_set->set_primary(entry_result.ValueOrDie());
+
+    // Wrap pk_sign_set and test the resulting PublicKeySign.
+    auto pk_sign_result = PublicKeySignWrapper().Wrap(std::move(pk_sign_set));
+    EXPECT_TRUE(pk_sign_result.ok()) << pk_sign_result.status();
+    pk_sign = std::move(pk_sign_result.ValueOrDie());
+
+    // Compute the signature via wrapper.
+    auto sign_result = pk_sign->Sign(data);
+    EXPECT_TRUE(sign_result.ok()) << sign_result.status();
+    std::string signature = sign_result.ValueOrDie();
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, signature_name, signature);
+
+    // Try verifying on raw PublicKeyVerify-primitive using original data.
+    std::unique_ptr<PublicKeyVerify> raw_pk_verify(
+        new DummyPublicKeyVerify(signature_name));
+    std::string raw_signature = signature.substr(CryptoFormat::kNonRawPrefixSize);
+    auto status = raw_pk_verify->Verify(raw_signature, data);
+    EXPECT_FALSE(status.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, status.error_code());
+
+    // Verify on raw PublicKeyVerify-primitive using legacy-formatted data.
+    std::string legacy_data = data;
+    legacy_data.append(1, CryptoFormat::kLegacyStartByte);
+    status = raw_pk_verify->Verify(raw_signature, legacy_data);
+    EXPECT_TRUE(status.ok()) << status;
+}
+
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/signature/public_key_verify_catalogue.cc b/cc/signature/public_key_verify_catalogue.cc
index 7d415f1..f8f2851 100644
--- a/cc/signature/public_key_verify_catalogue.cc
+++ b/cc/signature/public_key_verify_catalogue.cc
@@ -18,8 +18,11 @@
 
 #include "absl/strings/ascii.h"
 #include "tink/catalogue.h"
-#include "tink/signature/ecdsa_verify_key_manager.h"
 #include "tink/key_manager.h"
+#include "tink/signature/ecdsa_verify_key_manager.h"
+#include "tink/signature/ed25519_verify_key_manager.h"
+#include "tink/signature/rsa_ssa_pkcs1_verify_key_manager.h"
+#include "tink/signature/rsa_ssa_pss_verify_key_manager.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 
@@ -30,21 +33,34 @@
 
 crypto::tink::util::StatusOr<std::unique_ptr<KeyManager<PublicKeyVerify>>>
 CreateKeyManager(const std::string& type_url) {
-  if (type_url == EcdsaVerifyKeyManager::kKeyType) {
+  if (type_url == EcdsaVerifyKeyManager::static_key_type()) {
     std::unique_ptr<KeyManager<PublicKeyVerify>> manager(
         new EcdsaVerifyKeyManager());
     return std::move(manager);
+  } else if (type_url == Ed25519VerifyKeyManager::static_key_type()) {
+    std::unique_ptr<KeyManager<PublicKeyVerify>> manager(
+        new Ed25519VerifyKeyManager());
+    return std::move(manager);
+  } else if (type_url == RsaSsaPkcs1VerifyKeyManager::static_key_type()) {
+    std::unique_ptr<KeyManager<PublicKeyVerify>> manager(
+        new RsaSsaPkcs1VerifyKeyManager());
+    return std::move(manager);
+  } else if (type_url == RsaSsaPssVerifyKeyManager::static_key_type()) {
+    std::unique_ptr<KeyManager<PublicKeyVerify>> manager(
+        new RsaSsaPssVerifyKeyManager());
+    return std::move(manager);
+  } else {
+    return ToStatusF(crypto::tink::util::error::NOT_FOUND,
+                     "No key manager for type_url '%s'.", type_url.c_str());
   }
-  return ToStatusF(crypto::tink::util::error::NOT_FOUND,
-                   "No key manager for type_url '%s'.", type_url.c_str());
 }
 
 }  // anonymous namespace
 
 crypto::tink::util::StatusOr<std::unique_ptr<KeyManager<PublicKeyVerify>>>
 PublicKeyVerifyCatalogue::GetKeyManager(const std::string& type_url,
-                                      const std::string& primitive_name,
-                                      uint32_t min_version) const {
+                                        const std::string& primitive_name,
+                                        uint32_t min_version) const {
   if (!(absl::AsciiStrToLower(primitive_name) == "publickeyverify")) {
     return ToStatusF(crypto::tink::util::error::NOT_FOUND,
                      "This catalogue does not support primitive %s.",
diff --git a/cc/signature/public_key_verify_catalogue_test.cc b/cc/signature/public_key_verify_catalogue_test.cc
index 66bdd25..58c6226 100644
--- a/cc/signature/public_key_verify_catalogue_test.cc
+++ b/cc/signature/public_key_verify_catalogue_test.cc
@@ -64,9 +64,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/signature/public_key_verify_factory.cc b/cc/signature/public_key_verify_factory.cc
index ffefb97..dffa6d5 100644
--- a/cc/signature/public_key_verify_factory.cc
+++ b/cc/signature/public_key_verify_factory.cc
@@ -16,11 +16,11 @@
 
 #include "tink/signature/public_key_verify_factory.h"
 
-#include "tink/public_key_verify.h"
 #include "tink/key_manager.h"
 #include "tink/keyset_handle.h"
+#include "tink/public_key_verify.h"
 #include "tink/registry.h"
-#include "tink/signature/public_key_verify_set_wrapper.h"
+#include "tink/signature/public_key_verify_wrapper.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 
@@ -30,20 +30,25 @@
 // static
 util::StatusOr<std::unique_ptr<PublicKeyVerify>>
 PublicKeyVerifyFactory::GetPrimitive(const KeysetHandle& keyset_handle) {
-  return GetPrimitive(keyset_handle, nullptr);
+  util::Status status = Registry::RegisterPrimitiveWrapper(
+      absl::make_unique<PublicKeyVerifyWrapper>());
+  if (!status.ok()) {
+    return status;
+  }
+  return keyset_handle.GetPrimitive<PublicKeyVerify>();
 }
 
 // static
 util::StatusOr<std::unique_ptr<PublicKeyVerify>>
-PublicKeyVerifyFactory::GetPrimitive(const KeysetHandle& keyset_handle,
+PublicKeyVerifyFactory::GetPrimitive(
+    const KeysetHandle& keyset_handle,
     const KeyManager<PublicKeyVerify>* custom_key_manager) {
-  auto primitives_result = Registry::GetPrimitives<PublicKeyVerify>(
-      keyset_handle, custom_key_manager);
-  if (primitives_result.ok()) {
-    return PublicKeyVerifySetWrapper::NewPublicKeyVerify(
-        std::move(primitives_result.ValueOrDie()));
+  util::Status status = Registry::RegisterPrimitiveWrapper(
+      absl::make_unique<PublicKeyVerifyWrapper>());
+  if (!status.ok()) {
+    return status;
   }
-  return primitives_result.status();
+  return keyset_handle.GetPrimitive<PublicKeyVerify>(custom_key_manager);
 }
 
 }  // namespace tink
diff --git a/cc/signature/public_key_verify_factory.h b/cc/signature/public_key_verify_factory.h
index ffcfacf..e039a04 100644
--- a/cc/signature/public_key_verify_factory.h
+++ b/cc/signature/public_key_verify_factory.h
@@ -17,48 +17,39 @@
 #ifndef TINK_SIGNATURE_PUBLIC_KEY_VERIFY_FACTORY_H_
 #define TINK_SIGNATURE_PUBLIC_KEY_VERIFY_FACTORY_H_
 
-#include "tink/public_key_verify.h"
+#include "absl/base/macros.h"
 #include "tink/key_manager.h"
 #include "tink/keyset_handle.h"
+#include "tink/public_key_verify.h"
 #include "tink/util/statusor.h"
 
 namespace crypto {
 namespace tink {
 
 ///////////////////////////////////////////////////////////////////////////////
-// PublicKeyVerifyFactory allows for obtaining an PublicKeyVerify primitive
-// from a KeysetHandle.
+// This class is deprecated. Call keyset_handle->GetPrimitive<PublicKeyVerify>()
+// instead.
 //
-// PublicKeyVerifyFactory gets primitives from the Registry, which can
-// be initialized via a convenience method from SignatureConfig-class.
-// Here is an example how one can obtain and use a PublicKeyVerify primitive:
-//
-//   auto status = SignatureConfig::Register();
-//   if (!status.ok()) { /* fail with error */ }
-//   KeysetHandle keyset_handle = ...;
-//   std::unique_ptr<PublicKeyVerify> public_key_verify = std::move(
-//           PublicKeyVerifyFactory::GetPrimitive(keyset_handle).ValueOrDie());
-//   std::string data = ...;
-//   std::string signature = ...;
-//   status = public_key_verify.Verify(signature, data);
-//   if (!status.ok()) {
-//     // Signature was not correct.
-//     // ...
-//   }
-//
-class PublicKeyVerifyFactory {
+// Note that in order to for this change to be safe, the AeadSetWrapper has to
+// be registered in your binary before this call. This happens automatically if
+// you call one of
+// * SignatureConfig::Register()
+// * TinkConfig::Register()
+class ABSL_DEPRECATED(
+    "Call getPrimitive<PublicKeyVerify>() on the keyset_handle after "
+    "registering the PublicKeyVerifyWrapper instead.") PublicKeyVerifyFactory {
  public:
   // Returns a PublicKeyVerify-primitive that uses key material from the keyset
   // specified via 'keyset_handle'.
   static crypto::tink::util::StatusOr<std::unique_ptr<PublicKeyVerify>>
-      GetPrimitive(const KeysetHandle& keyset_handle);
+  GetPrimitive(const KeysetHandle& keyset_handle);
 
   // Returns a PublicKeyVerify-primitive that uses key material from the keyset
   // specified via 'keyset_handle' and is instantiated by the given
   // 'custom_key_manager' (instead of the key manager from the Registry).
   static crypto::tink::util::StatusOr<std::unique_ptr<PublicKeyVerify>>
-      GetPrimitive(const KeysetHandle& keyset_handle,
-                   const KeyManager<PublicKeyVerify>* custom_key_manager);
+  GetPrimitive(const KeysetHandle& keyset_handle,
+               const KeyManager<PublicKeyVerify>* custom_key_manager);
 
  private:
   PublicKeyVerifyFactory() {}
diff --git a/cc/signature/public_key_verify_factory_test.cc b/cc/signature/public_key_verify_factory_test.cc
index b2501ef..9baa193 100644
--- a/cc/signature/public_key_verify_factory_test.cc
+++ b/cc/signature/public_key_verify_factory_test.cc
@@ -101,9 +101,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/signature/public_key_verify_set_wrapper.cc b/cc/signature/public_key_verify_wrapper.cc
similarity index 74%
rename from cc/signature/public_key_verify_set_wrapper.cc
rename to cc/signature/public_key_verify_wrapper.cc
index 30b5471..81af698 100644
--- a/cc/signature/public_key_verify_set_wrapper.cc
+++ b/cc/signature/public_key_verify_wrapper.cc
@@ -14,7 +14,7 @@
 //
 ///////////////////////////////////////////////////////////////////////////////
 
-#include "tink/signature/public_key_verify_set_wrapper.h"
+#include "tink/signature/public_key_verify_wrapper.h"
 
 #include "tink/crypto_format.h"
 #include "tink/primitive_set.h"
@@ -22,9 +22,13 @@
 #include "tink/subtle/subtle_util_boringssl.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
+#include "proto/tink.pb.h"
 
 namespace crypto {
 namespace tink {
+
+using google::crypto::tink::OutputPrefixType;
+
 namespace {
 
 util::Status Validate(PrimitiveSet<PublicKeyVerify>* public_key_verify_set) {
@@ -39,18 +43,20 @@
   return util::Status::OK;
 }
 
-}  // anonymous namespace
+class PublicKeyVerifySetWrapper : public PublicKeyVerify {
+ public:
+  explicit PublicKeyVerifySetWrapper(
+      std::unique_ptr<PrimitiveSet<PublicKeyVerify>> public_key_verify_set)
+      : public_key_verify_set_(std::move(public_key_verify_set)) {}
 
-// static
-util::StatusOr<std::unique_ptr<PublicKeyVerify>>
-PublicKeyVerifySetWrapper::NewPublicKeyVerify(
-    std::unique_ptr<PrimitiveSet<PublicKeyVerify>> public_key_verify_set) {
-  util::Status status = Validate(public_key_verify_set.get());
-  if (!status.ok()) return status;
-  std::unique_ptr<PublicKeyVerify> public_key_verify(
-      new PublicKeyVerifySetWrapper(std::move(public_key_verify_set)));
-  return std::move(public_key_verify);
-}
+  crypto::tink::util::Status Verify(absl::string_view signature,
+                                    absl::string_view data) const override;
+
+  ~PublicKeyVerifySetWrapper() override {}
+
+ private:
+  std::unique_ptr<PrimitiveSet<PublicKeyVerify>> public_key_verify_set_;
+};
 
 util::Status PublicKeyVerifySetWrapper::Verify(
     absl::string_view signature,
@@ -58,6 +64,7 @@
   // BoringSSL expects a non-null pointer for data,
   // regardless of whether the size is 0.
   data = subtle::SubtleUtilBoringSSL::EnsureNonNull(data);
+  signature = subtle::SubtleUtilBoringSSL::EnsureNonNull(signature);
 
   if (signature.length() <= CryptoFormat::kNonRawPrefixSize) {
     // This also rejects raw signatures with size of 4 bytes or fewer.
@@ -70,8 +77,14 @@
   if (primitives_result.ok()) {
     absl::string_view raw_signature =
         signature.substr(CryptoFormat::kNonRawPrefixSize);
-    for (auto& public_key_verify_entry : *(primitives_result.ValueOrDie())) {
-      auto& public_key_verify = public_key_verify_entry->get_primitive();
+    std::string local_data;
+    for (auto& entry : *(primitives_result.ValueOrDie())) {
+      if (entry->get_output_prefix_type() == OutputPrefixType::LEGACY) {
+        local_data = std::string(data);
+        local_data.append(1, CryptoFormat::kLegacyStartByte);
+        data = local_data;
+      }
+      auto& public_key_verify = entry->get_primitive();
       auto verify_result =
           public_key_verify.Verify(raw_signature, data);
       if (verify_result.ok()) {
@@ -97,5 +110,17 @@
   return util::Status(util::error::INVALID_ARGUMENT, "Invalid signature.");
 }
 
+}  // anonymous namespace
+
+util::StatusOr<std::unique_ptr<PublicKeyVerify>> PublicKeyVerifyWrapper::Wrap(
+    std::unique_ptr<PrimitiveSet<PublicKeyVerify>> public_key_verify_set)
+    const {
+  util::Status status = Validate(public_key_verify_set.get());
+  if (!status.ok()) return status;
+  std::unique_ptr<PublicKeyVerify> public_key_verify(
+      new PublicKeyVerifySetWrapper(std::move(public_key_verify_set)));
+  return std::move(public_key_verify);
+}
+
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/signature/public_key_verify_set_wrapper.h b/cc/signature/public_key_verify_wrapper.h
similarity index 66%
rename from cc/signature/public_key_verify_set_wrapper.h
rename to cc/signature/public_key_verify_wrapper.h
index d4279eb..074643d 100644
--- a/cc/signature/public_key_verify_set_wrapper.h
+++ b/cc/signature/public_key_verify_wrapper.h
@@ -14,12 +14,13 @@
 //
 ///////////////////////////////////////////////////////////////////////////////
 
-#ifndef TINK_SIGNATURE_PUBLIC_KEY_VERIFY_SET_WRAPPER_H_
-#define TINK_SIGNATURE_PUBLIC_KEY_VERIFY_SET_WRAPPER_H_
+#ifndef TINK_SIGNATURE_PUBLIC_KEY_VERIFY_WRAPPER_H_
+#define TINK_SIGNATURE_PUBLIC_KEY_VERIFY_WRAPPER_H_
 
 #include "absl/strings/string_view.h"
-#include "tink/public_key_verify.h"
 #include "tink/primitive_set.h"
+#include "tink/primitive_wrapper.h"
+#include "tink/public_key_verify.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 #include "proto/tink.pb.h"
@@ -31,30 +32,17 @@
 // and combines them into a single PublicKeyVerify-primitive,
 // that for the actual verification uses the instance that maches the
 // signature prefix.
-class PublicKeyVerifySetWrapper : public PublicKeyVerify {
+class PublicKeyVerifyWrapper : public PrimitiveWrapper<PublicKeyVerify> {
  public:
   // Returns an PublicKeyVerify-primitive that uses the primary
   // PublicKeyVerify-instance provided in 'public_key_verify_set',
   // which must be non-NULL (and must contain a primary instance).
-  static crypto::tink::util::StatusOr<std::unique_ptr<PublicKeyVerify>>
-      NewPublicKeyVerify(
-          std::unique_ptr<PrimitiveSet<PublicKeyVerify>> public_key_verify_set);
-
-  crypto::tink::util::Status Verify(
-      absl::string_view signature,
-      absl::string_view data) const override;
-
-  virtual ~PublicKeyVerifySetWrapper() {}
-
- private:
-  std::unique_ptr<PrimitiveSet<PublicKeyVerify>> public_key_verify_set_;
-
-  PublicKeyVerifySetWrapper(
+  crypto::tink::util::StatusOr<std::unique_ptr<PublicKeyVerify>> Wrap(
       std::unique_ptr<PrimitiveSet<PublicKeyVerify>> public_key_verify_set)
-      : public_key_verify_set_(std::move(public_key_verify_set)) {}
+      const override;
 };
 
 }  // namespace tink
 }  // namespace crypto
 
-#endif  // TINK_SIGNATURE_PUBLIC_KEY_VERIFY_SET_WRAPPER_H_
+#endif  // TINK_SIGNATURE_PUBLIC_KEY_VERIFY_WRAPPER_H_
diff --git a/cc/signature/public_key_verify_set_wrapper_test.cc b/cc/signature/public_key_verify_wrapper_test.cc
similarity index 90%
rename from cc/signature/public_key_verify_set_wrapper_test.cc
rename to cc/signature/public_key_verify_wrapper_test.cc
index b3f89d6..546643b 100644
--- a/cc/signature/public_key_verify_set_wrapper_test.cc
+++ b/cc/signature/public_key_verify_wrapper_test.cc
@@ -14,12 +14,12 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-#include "tink/signature/public_key_verify_set_wrapper.h"
-#include "tink/public_key_verify.h"
+#include "tink/signature/public_key_verify_wrapper.h"
+#include "gtest/gtest.h"
 #include "tink/primitive_set.h"
+#include "tink/public_key_verify.h"
 #include "tink/util/status.h"
 #include "tink/util/test_util.h"
-#include "gtest/gtest.h"
 
 using crypto::tink::test::DummyPublicKeySign;
 using crypto::tink::test::DummyPublicKeyVerify;
@@ -40,8 +40,7 @@
 
 TEST_F(PublicKeyVerifySetWrapperTest, testBasic) {
   { // pk_verify_set is nullptr.
-    auto pk_verify_result =
-        PublicKeyVerifySetWrapper::NewPublicKeyVerify(nullptr);
+    auto pk_verify_result = PublicKeyVerifyWrapper().Wrap(nullptr);
     EXPECT_FALSE(pk_verify_result.ok());
     EXPECT_EQ(util::error::INTERNAL, pk_verify_result.status().error_code());
     EXPECT_PRED_FORMAT2(testing::IsSubstring, "non-NULL",
@@ -52,7 +51,7 @@
     std::unique_ptr<PrimitiveSet<PublicKeyVerify>>
         pk_verify_set(new PrimitiveSet<PublicKeyVerify>());
     auto pk_verify_result =
-        PublicKeyVerifySetWrapper::NewPublicKeyVerify(std::move(pk_verify_set));
+        PublicKeyVerifyWrapper().Wrap(std::move(pk_verify_set));
     EXPECT_FALSE(pk_verify_result.ok());
     EXPECT_EQ(util::error::INVALID_ARGUMENT,
         pk_verify_result.status().error_code());
@@ -105,8 +104,8 @@
     pk_verify_set->set_primary(entry_result.ValueOrDie());
 
     // Wrap pk_verify_set and test the resulting PublicKeyVerify.
-    auto pk_verify_result = PublicKeyVerifySetWrapper::NewPublicKeyVerify(
-        std::move(pk_verify_set));
+    auto pk_verify_result =
+        PublicKeyVerifyWrapper().Wrap(std::move(pk_verify_set));
     EXPECT_TRUE(pk_verify_result.ok()) << pk_verify_result.status();
     pk_verify = std::move(pk_verify_result.ValueOrDie());
     std::string data = "some data to sign";
@@ -121,9 +120,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/signature/rsa_ssa_pkcs1_sign_key_manager.cc b/cc/signature/rsa_ssa_pkcs1_sign_key_manager.cc
new file mode 100644
index 0000000..c572b9e
--- /dev/null
+++ b/cc/signature/rsa_ssa_pkcs1_sign_key_manager.cc
@@ -0,0 +1,204 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/signature/rsa_ssa_pkcs1_sign_key_manager.h"
+
+#include "absl/memory/memory.h"
+#include "absl/strings/string_view.h"
+#include "tink/core/key_manager_base.h"
+#include "tink/key_manager.h"
+#include "tink/public_key_sign.h"
+#include "tink/signature/rsa_ssa_pkcs1_verify_key_manager.h"
+#include "tink/signature/sig_util.h"
+#include "tink/subtle/rsa_ssa_pkcs1_sign_boringssl.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/util/enums.h"
+#include "tink/util/errors.h"
+#include "tink/util/protobuf_helper.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "tink/util/validation.h"
+#include "proto/rsa_ssa_pkcs1.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+
+using crypto::tink::util::Enums;
+using crypto::tink::util::Status;
+using crypto::tink::util::StatusOr;
+using google::crypto::tink::KeyData;
+using google::crypto::tink::RsaSsaPkcs1KeyFormat;
+using google::crypto::tink::RsaSsaPkcs1Params;
+using google::crypto::tink::RsaSsaPkcs1PrivateKey;
+
+namespace {
+std::unique_ptr<RsaSsaPkcs1PrivateKey> RsaPrivateKeySubtleToProto(
+    const subtle::SubtleUtilBoringSSL::RsaPrivateKey& private_key) {
+  auto key_proto = absl::make_unique<RsaSsaPkcs1PrivateKey>();
+  key_proto->set_version(RsaSsaPkcs1SignKeyManager::kVersion);
+  key_proto->set_d(private_key.d);
+  key_proto->set_p(private_key.p);
+  key_proto->set_q(private_key.q);
+  key_proto->set_dp(private_key.dp);
+  key_proto->set_dq(private_key.dq);
+  key_proto->set_crt(private_key.crt);
+  auto* public_key_proto = key_proto->mutable_public_key();
+  public_key_proto->set_version(RsaSsaPkcs1SignKeyManager::kVersion);
+  public_key_proto->set_n(private_key.n);
+  public_key_proto->set_e(private_key.e);
+  return key_proto;
+}
+
+subtle::SubtleUtilBoringSSL::RsaPrivateKey RsaPrivateKeyProtoToSubtle(
+    const RsaSsaPkcs1PrivateKey& key_proto) {
+  subtle::SubtleUtilBoringSSL::RsaPrivateKey key;
+  key.n = key_proto.public_key().n();
+  key.e = key_proto.public_key().e();
+  key.d = key_proto.d();
+  key.p = key_proto.p();
+  key.q = key_proto.q();
+  key.dp = key_proto.dp();
+  key.dq = key_proto.dq();
+  key.crt = key_proto.crt();
+  return key;
+}
+
+}  // namespace
+
+class RsaSsaPkcs1PrivateKeyFactory
+    : public PrivateKeyFactory,
+      public KeyFactoryBase<RsaSsaPkcs1PrivateKey, RsaSsaPkcs1KeyFormat> {
+ public:
+  RsaSsaPkcs1PrivateKeyFactory() {}
+
+  KeyData::KeyMaterialType key_material_type() const override {
+    return KeyData::ASYMMETRIC_PRIVATE;
+  }
+
+  // Returns KeyData proto that contains RsaSsaPkcs1PublicKey
+  // extracted from the given serialized_private_key, which must contain
+  // RsaSsaPkcs1PrivateKey-proto.
+  crypto::tink::util::StatusOr<std::unique_ptr<google::crypto::tink::KeyData>>
+  GetPublicKeyData(absl::string_view serialized_private_key) const override;
+
+ protected:
+  StatusOr<std::unique_ptr<RsaSsaPkcs1PrivateKey>> NewKeyFromFormat(
+      const RsaSsaPkcs1KeyFormat& rsa_ssa_pkcs1_key_format) const override;
+};
+
+StatusOr<std::unique_ptr<RsaSsaPkcs1PrivateKey>>
+RsaSsaPkcs1PrivateKeyFactory::NewKeyFromFormat(
+    const RsaSsaPkcs1KeyFormat& rsa_ssa_pkcs1_key_format) const {
+  util::Status is_valid =
+      RsaSsaPkcs1SignKeyManager::Validate(rsa_ssa_pkcs1_key_format);
+  if (!is_valid.ok()) return is_valid;
+
+  auto e = subtle::SubtleUtilBoringSSL::str2bn(
+      rsa_ssa_pkcs1_key_format.public_exponent());
+  if (!e.ok()) return e.status();
+
+  subtle::SubtleUtilBoringSSL::RsaPrivateKey private_key;
+  subtle::SubtleUtilBoringSSL::RsaPublicKey public_key;
+  util::Status status = subtle::SubtleUtilBoringSSL::GetNewRsaKeyPair(
+      rsa_ssa_pkcs1_key_format.modulus_size_in_bits(), e.ValueOrDie().get(),
+      &private_key, &public_key);
+  if (!status.ok()) return status;
+
+  auto key_proto = RsaPrivateKeySubtleToProto(private_key);
+  auto* public_key_proto = key_proto->mutable_public_key();
+  *public_key_proto->mutable_params() = rsa_ssa_pkcs1_key_format.params();
+
+  return absl::implicit_cast<StatusOr<std::unique_ptr<RsaSsaPkcs1PrivateKey>>>(
+      std::move(key_proto));
+}
+
+StatusOr<std::unique_ptr<KeyData>>
+RsaSsaPkcs1PrivateKeyFactory::GetPublicKeyData(
+    absl::string_view serialized_private_key) const {
+  RsaSsaPkcs1PrivateKey private_key;
+  if (!private_key.ParseFromString(std::string(serialized_private_key))) {
+    return ToStatusF(util::error::INVALID_ARGUMENT,
+                     "Could not parse the passed string as proto '%s'.",
+                     RsaSsaPkcs1VerifyKeyManager::static_key_type().c_str());
+  }
+  auto status = RsaSsaPkcs1SignKeyManager::Validate(private_key);
+  if (!status.ok()) return status;
+  auto key_data = absl::make_unique<KeyData>();
+  key_data->set_type_url(RsaSsaPkcs1VerifyKeyManager::static_key_type());
+  key_data->set_value(private_key.public_key().SerializeAsString());
+  key_data->set_key_material_type(KeyData::ASYMMETRIC_PUBLIC);
+  return std::move(key_data);
+}
+
+constexpr uint32_t RsaSsaPkcs1SignKeyManager::kVersion;
+
+RsaSsaPkcs1SignKeyManager::RsaSsaPkcs1SignKeyManager()
+    : key_factory_(absl::make_unique<RsaSsaPkcs1PrivateKeyFactory>()) {}
+
+const KeyFactory& RsaSsaPkcs1SignKeyManager::get_key_factory() const {
+  return *key_factory_;
+}
+
+uint32_t RsaSsaPkcs1SignKeyManager::get_version() const { return kVersion; }
+
+StatusOr<std::unique_ptr<PublicKeySign>>
+RsaSsaPkcs1SignKeyManager::GetPrimitiveFromKey(
+    const RsaSsaPkcs1PrivateKey& key_proto) const {
+  Status status = Validate(key_proto);
+  if (!status.ok()) return status;
+  auto key = RsaPrivateKeyProtoToSubtle(key_proto);
+  subtle::SubtleUtilBoringSSL::RsaSsaPkcs1Params params;
+  const RsaSsaPkcs1Params& params_proto = key_proto.public_key().params();
+  params.hash_type = Enums::ProtoToSubtle(params_proto.hash_type());
+  auto signer = subtle::RsaSsaPkcs1SignBoringSsl::New(key, params);
+  if (!signer.ok()) return signer.status();
+  // To check that the key is correct, we sign a test message with private key
+  // and verify with public key.
+  auto public_key_data_result =
+      key_factory_->GetPublicKeyData(key_proto.SerializeAsString());
+  if (!public_key_data_result.ok()) return public_key_data_result.status();
+  auto public_key_data = std::move(public_key_data_result.ValueOrDie());
+  RsaSsaPkcs1VerifyKeyManager verify_key_manager;
+  auto verifier = verify_key_manager.GetPrimitive(*public_key_data);
+  if (!verifier.ok()) return verifier.status();
+  auto sign_verify_result =
+      SignAndVerify(signer.ValueOrDie().get(), verifier.ValueOrDie().get());
+  if (!sign_verify_result.ok()) {
+    return util::Status(util::error::INTERNAL,
+                        "security bug: signing with private key followed by "
+                        "verifying with public key failed");
+  }
+  return signer;
+}
+
+// static
+Status RsaSsaPkcs1SignKeyManager::Validate(const RsaSsaPkcs1PrivateKey& key) {
+  Status status = ValidateVersion(key.version(), kVersion);
+  if (!status.ok()) return status;
+  return RsaSsaPkcs1VerifyKeyManager::Validate(key.public_key());
+}
+
+// static
+Status RsaSsaPkcs1SignKeyManager::Validate(
+    const RsaSsaPkcs1KeyFormat& key_format) {
+  auto modulus_status = subtle::SubtleUtilBoringSSL::ValidateRsaModulusSize(
+      key_format.modulus_size_in_bits());
+  if (!modulus_status.ok()) return modulus_status;
+  return RsaSsaPkcs1VerifyKeyManager::Validate(key_format.params());
+}
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/signature/rsa_ssa_pkcs1_sign_key_manager.h b/cc/signature/rsa_ssa_pkcs1_sign_key_manager.h
new file mode 100644
index 0000000..105244d
--- /dev/null
+++ b/cc/signature/rsa_ssa_pkcs1_sign_key_manager.h
@@ -0,0 +1,73 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_SIGNATURE_RSA_SSA_PKCS1_SIGN_KEY_MANAGER_H_
+#define TINK_SIGNATURE_RSA_SSA_PKCS1_SIGN_KEY_MANAGER_H_
+
+#include <algorithm>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "tink/core/key_manager_base.h"
+#include "tink/key_manager.h"
+#include "tink/public_key_sign.h"
+#include "tink/util/errors.h"
+#include "tink/util/protobuf_helper.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "proto/rsa_ssa_pkcs1.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+
+class RsaSsaPkcs1SignKeyManager
+    : public KeyManagerBase<PublicKeySign,
+                            google::crypto::tink::RsaSsaPkcs1PrivateKey> {
+ public:
+  static constexpr uint32_t kVersion = 0;
+
+  RsaSsaPkcs1SignKeyManager();
+
+  // Returns the version of this key manager.
+  uint32_t get_version() const override;
+
+  // Returns a factory that generates keys of the key type
+  // handled by this manager.
+  const KeyFactory& get_key_factory() const override;
+
+  virtual ~RsaSsaPkcs1SignKeyManager() {}
+
+ protected:
+  crypto::tink::util::StatusOr<std::unique_ptr<PublicKeySign>>
+  GetPrimitiveFromKey(const google::crypto::tink::RsaSsaPkcs1PrivateKey&
+                          key_proto) const override;
+
+ private:
+  friend class RsaSsaPkcs1PrivateKeyFactory;
+
+  std::unique_ptr<PrivateKeyFactory> key_factory_;
+
+  static crypto::tink::util::Status Validate(
+      const google::crypto::tink::RsaSsaPkcs1KeyFormat& key_format);
+
+  static crypto::tink::util::Status Validate(
+      const google::crypto::tink::RsaSsaPkcs1PrivateKey& key);
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_SIGNATURE_RSA_SSA_PKCS1_SIGN_KEY_MANAGER_H_
diff --git a/cc/signature/rsa_ssa_pkcs1_sign_key_manager_test.cc b/cc/signature/rsa_ssa_pkcs1_sign_key_manager_test.cc
new file mode 100644
index 0000000..0277e80
--- /dev/null
+++ b/cc/signature/rsa_ssa_pkcs1_sign_key_manager_test.cc
@@ -0,0 +1,353 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/signature/rsa_ssa_pkcs1_sign_key_manager.h"
+
+#include "gtest/gtest.h"
+#include "tink/aead/aead_key_templates.h"
+#include "tink/aead/aes_gcm_key_manager.h"
+#include "tink/public_key_sign.h"
+#include "tink/registry.h"
+#include "tink/signature/rsa_ssa_pkcs1_verify_key_manager.h"
+#include "tink/signature/signature_key_templates.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_util.h"
+#include "proto/aes_eax.pb.h"
+#include "proto/common.pb.h"
+#include "proto/rsa_ssa_pkcs1.pb.h"
+#include "proto/tink.pb.h"
+
+namespace pb = google::crypto::tink;
+
+namespace crypto {
+namespace tink {
+
+using google::crypto::tink::AesEaxKeyFormat;
+using google::crypto::tink::KeyData;
+using google::crypto::tink::RsaSsaPkcs1KeyFormat;
+using google::crypto::tink::RsaSsaPkcs1PrivateKey;
+using google::crypto::tink::RsaSsaPkcs1PublicKey;
+using subtle::SubtleUtilBoringSSL;
+
+namespace {
+
+class RsaSsaPkcs1SignKeyManagerTest : public ::testing::Test {
+ protected:
+  std::string key_type_prefix_ = "type.googleapis.com/";
+  std::string rsa_ssa_pkcs1_sign_key_type_ =
+      "type.googleapis.com/google.crypto.tink.RsaSsaPkcs1PrivateKey";
+};
+
+// Checks whether given key is compatible with the given format.
+void CheckNewKey(const RsaSsaPkcs1PrivateKey& private_key,
+                 const RsaSsaPkcs1KeyFormat& key_format) {
+  RsaSsaPkcs1SignKeyManager key_manager;
+  RsaSsaPkcs1PublicKey public_key = private_key.public_key();
+  EXPECT_EQ(0, private_key.version());
+  EXPECT_TRUE(private_key.has_public_key());
+  EXPECT_EQ(0, public_key.version());
+  EXPECT_GT(public_key.n().length(), 0);
+  EXPECT_GT(public_key.e().length(), 0);
+  EXPECT_EQ(public_key.params().SerializeAsString(),
+            key_format.params().SerializeAsString());
+  EXPECT_EQ(key_format.public_exponent(), public_key.e());
+  auto primitive_result = key_manager.GetPrimitive(private_key);
+  EXPECT_TRUE(primitive_result.ok()) << primitive_result.status();
+  auto n = std::move(SubtleUtilBoringSSL::str2bn(public_key.n()).ValueOrDie());
+  auto d = std::move(SubtleUtilBoringSSL::str2bn(private_key.d()).ValueOrDie());
+  auto p = std::move(SubtleUtilBoringSSL::str2bn(private_key.p()).ValueOrDie());
+  auto q = std::move(SubtleUtilBoringSSL::str2bn(private_key.q()).ValueOrDie());
+  auto dp =
+      std::move(SubtleUtilBoringSSL::str2bn(private_key.dp()).ValueOrDie());
+  auto dq =
+      std::move(SubtleUtilBoringSSL::str2bn(private_key.dq()).ValueOrDie());
+  bssl::UniquePtr<BN_CTX> ctx(BN_CTX_new());
+
+  // Check n = p * q.
+  auto n_calc = bssl::UniquePtr<BIGNUM>(BN_new());
+  ASSERT_TRUE(BN_mul(n_calc.get(), p.get(), q.get(), ctx.get()));
+  ASSERT_TRUE(BN_equal_consttime(n_calc.get(), n.get()));
+
+  // Check n size >= modulus_size_in_bits bit.
+  EXPECT_GE(BN_num_bits(n.get()), key_format.modulus_size_in_bits());
+
+  // dp = d mod (p - 1)
+  auto pm1 = bssl::UniquePtr<BIGNUM>(BN_dup(p.get()));
+  ASSERT_TRUE(BN_sub_word(pm1.get(), 1));
+  auto dp_calc = bssl::UniquePtr<BIGNUM>(BN_new());
+  ASSERT_TRUE(BN_mod(dp_calc.get(), d.get(), pm1.get(), ctx.get()));
+  ASSERT_TRUE(BN_equal_consttime(dp_calc.get(), dp.get()));
+
+  // dq = d mod (q - 1)
+  auto qm1 = bssl::UniquePtr<BIGNUM>(BN_dup(q.get()));
+  ASSERT_TRUE(BN_sub_word(qm1.get(), 1));
+  auto dq_calc = bssl::UniquePtr<BIGNUM>(BN_new());
+  ASSERT_TRUE(BN_mod(dq_calc.get(), d.get(), qm1.get(), ctx.get()));
+
+  ASSERT_TRUE(BN_equal_consttime(dq_calc.get(), dq.get()));
+}
+
+TEST_F(RsaSsaPkcs1SignKeyManagerTest, Basic) {
+  RsaSsaPkcs1SignKeyManager key_manager;
+  EXPECT_EQ(0, key_manager.get_version());
+  EXPECT_EQ("type.googleapis.com/google.crypto.tink.RsaSsaPkcs1PrivateKey",
+            key_manager.get_key_type());
+  EXPECT_TRUE(key_manager.DoesSupport(key_manager.get_key_type()));
+}
+
+TEST_F(RsaSsaPkcs1SignKeyManagerTest, NewKeyFromKeyFormat) {
+  RsaSsaPkcs1SignKeyManager key_manager;
+  const KeyFactory& key_factory = key_manager.get_key_factory();
+  RsaSsaPkcs1KeyFormat key_format;
+  ASSERT_TRUE(key_format.ParseFromString(
+      SignatureKeyTemplates::RsaSsaPkcs13072Sha256F4().value()));
+  auto result = key_factory.NewKey(key_format);
+  EXPECT_TRUE(result.ok()) << result.status();
+  auto key = std::move(result.ValueOrDie());
+  ASSERT_EQ(rsa_ssa_pkcs1_sign_key_type_,
+            key_type_prefix_ + key->GetTypeName());
+  std::unique_ptr<RsaSsaPkcs1PrivateKey> rsa_key(
+      static_cast<RsaSsaPkcs1PrivateKey*>(key.release()));
+  CheckNewKey(*rsa_key, key_format);
+}
+
+TEST_F(RsaSsaPkcs1SignKeyManagerTest, NewKeyFromSerializedKeyFormat) {
+  RsaSsaPkcs1SignKeyManager key_manager;
+  const KeyFactory& key_factory = key_manager.get_key_factory();
+  RsaSsaPkcs1KeyFormat key_format;
+  ASSERT_TRUE(key_format.ParseFromString(
+      SignatureKeyTemplates::RsaSsaPkcs14096Sha512F4().value()));
+  auto result = key_factory.NewKey(key_format.SerializeAsString());
+  EXPECT_TRUE(result.ok()) << result.status();
+  auto key = std::move(result.ValueOrDie());
+  ASSERT_EQ(rsa_ssa_pkcs1_sign_key_type_,
+            key_type_prefix_ + key->GetTypeName());
+  std::unique_ptr<RsaSsaPkcs1PrivateKey> rsa_key(
+      static_cast<RsaSsaPkcs1PrivateKey*>(key.release()));
+  CheckNewKey(*rsa_key, key_format);
+}
+
+TEST_F(RsaSsaPkcs1SignKeyManagerTest, NewKeyDataFromSerializedKeyFormat) {
+  RsaSsaPkcs1SignKeyManager key_manager;
+  const KeyFactory& key_factory = key_manager.get_key_factory();
+  RsaSsaPkcs1KeyFormat key_format;
+  ASSERT_TRUE(key_format.ParseFromString(
+      SignatureKeyTemplates::RsaSsaPkcs14096Sha512F4().value()));
+  auto result = key_factory.NewKeyData(key_format.SerializeAsString());
+  EXPECT_TRUE(result.ok()) << result.status();
+  auto key_data = std::move(result.ValueOrDie());
+  ASSERT_EQ(rsa_ssa_pkcs1_sign_key_type_, key_data->type_url());
+  RsaSsaPkcs1PrivateKey rsa_key;
+  ASSERT_TRUE(rsa_key.ParseFromString(key_data->value()));
+  CheckNewKey(rsa_key, key_format);
+}
+
+TEST_F(RsaSsaPkcs1SignKeyManagerTest, PublicKeyExtraction) {
+  RsaSsaPkcs1SignKeyManager sign_key_manager;
+  auto private_key_factory = dynamic_cast<const PrivateKeyFactory*>(
+      &(sign_key_manager.get_key_factory()));
+  ASSERT_NE(private_key_factory, nullptr);
+  auto new_key_result = private_key_factory->NewKey(
+      SignatureKeyTemplates::RsaSsaPkcs13072Sha256F4().value());
+  std::unique_ptr<RsaSsaPkcs1PrivateKey> private_key(
+      static_cast<RsaSsaPkcs1PrivateKey*>(
+          new_key_result.ValueOrDie().release()));
+  auto public_key_data_result =
+      private_key_factory->GetPublicKeyData(private_key->SerializeAsString());
+  EXPECT_TRUE(public_key_data_result.ok()) << public_key_data_result.status();
+  auto public_key_data = std::move(public_key_data_result.ValueOrDie());
+  EXPECT_EQ(RsaSsaPkcs1VerifyKeyManager::static_key_type(),
+            public_key_data->type_url());
+  EXPECT_EQ(KeyData::ASYMMETRIC_PUBLIC, public_key_data->key_material_type());
+  EXPECT_EQ(private_key->public_key().SerializeAsString(),
+            public_key_data->value());
+  // Sign with private key and verify with public key.
+  RsaSsaPkcs1VerifyKeyManager verify_key_manager;
+  auto signer = sign_key_manager.GetPrimitive(*private_key);
+  auto verifier = verify_key_manager.GetPrimitive(*public_key_data);
+  std::string message = "Wycheproof";
+  EXPECT_TRUE(
+      verifier.ValueOrDie()
+          ->Verify(signer.ValueOrDie()->Sign(message).ValueOrDie(), message)
+          .ok());
+}
+
+TEST_F(RsaSsaPkcs1SignKeyManagerTest, NewKeyWithWeakSignatureHash) {
+  RsaSsaPkcs1SignKeyManager key_manager;
+  const KeyFactory& key_factory = key_manager.get_key_factory();
+  RsaSsaPkcs1KeyFormat key_format;
+  ASSERT_TRUE(key_format.ParseFromString(
+      SignatureKeyTemplates::RsaSsaPkcs14096Sha512F4().value()));
+  key_format.mutable_params()->set_hash_type(pb::HashType::SHA1);
+  auto result = key_factory.NewKey(key_format.SerializeAsString());
+  EXPECT_FALSE(result.ok());
+  EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+  EXPECT_PRED_FORMAT2(testing::IsSubstring,
+                      "SHA1 is not safe for digital signature",
+                      result.status().error_message());
+}
+
+TEST_F(RsaSsaPkcs1SignKeyManagerTest, NewKeyWithSmallModulus) {
+  RsaSsaPkcs1SignKeyManager key_manager;
+  const KeyFactory& key_factory = key_manager.get_key_factory();
+  RsaSsaPkcs1KeyFormat key_format;
+  ASSERT_TRUE(key_format.ParseFromString(
+      SignatureKeyTemplates::RsaSsaPkcs14096Sha512F4().value()));
+  key_format.set_modulus_size_in_bits(512);
+  auto result = key_factory.NewKey(key_format.SerializeAsString());
+  EXPECT_FALSE(result.ok());
+  EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+  EXPECT_PRED_FORMAT2(testing::IsSubstring,
+                      "only modulus size >= 2048-bit is supported",
+                      result.status().error_message());
+}
+
+TEST_F(RsaSsaPkcs1SignKeyManagerTest, GetPrimitiveWithWeakSignatureHash) {
+  RsaSsaPkcs1SignKeyManager sign_key_manager;
+  auto private_key_factory = dynamic_cast<const PrivateKeyFactory*>(
+      &(sign_key_manager.get_key_factory()));
+  ASSERT_NE(private_key_factory, nullptr);
+  auto new_key_result = private_key_factory->NewKey(
+      SignatureKeyTemplates::RsaSsaPkcs13072Sha256F4().value());
+  std::unique_ptr<RsaSsaPkcs1PrivateKey> private_key(
+      static_cast<RsaSsaPkcs1PrivateKey*>(
+          new_key_result.ValueOrDie().release()));
+  private_key->mutable_public_key()->mutable_params()->set_hash_type(
+      pb::HashType::SHA1);
+  auto result = sign_key_manager.GetPrimitive(*private_key);
+  EXPECT_FALSE(result.ok());
+  EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+  EXPECT_PRED_FORMAT2(testing::IsSubstring,
+                      "SHA1 is not safe for digital signature",
+                      result.status().error_message());
+}
+
+TEST_F(RsaSsaPkcs1SignKeyManagerTest, GetPrimitiveWithSmallModulus) {
+  RsaSsaPkcs1SignKeyManager sign_key_manager;
+  auto private_key_factory = dynamic_cast<const PrivateKeyFactory*>(
+      &(sign_key_manager.get_key_factory()));
+  ASSERT_NE(private_key_factory, nullptr);
+  auto new_key_result = private_key_factory->NewKey(
+      SignatureKeyTemplates::RsaSsaPkcs13072Sha256F4().value());
+  std::unique_ptr<RsaSsaPkcs1PrivateKey> private_key(
+      static_cast<RsaSsaPkcs1PrivateKey*>(
+          new_key_result.ValueOrDie().release()));
+  private_key->mutable_public_key()->set_n("\x23");
+  private_key->mutable_public_key()->set_e("\x3");
+  auto result = sign_key_manager.GetPrimitive(*private_key);
+  EXPECT_FALSE(result.ok());
+  EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+  EXPECT_PRED_FORMAT2(testing::IsSubstring,
+                      "only modulus size >= 2048-bit is supported",
+                      result.status().error_message());
+}
+
+TEST_F(RsaSsaPkcs1SignKeyManagerTest, KeyDataErrors) {
+  RsaSsaPkcs1SignKeyManager key_manager;
+
+  {  // Bad key type.
+    KeyData key_data;
+    std::string bad_key_type = "type.googleapis.com/google.crypto.tink.SomeOtherKey";
+    key_data.set_type_url(bad_key_type);
+    auto result = key_manager.GetPrimitive(key_data);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "not supported",
+                        result.status().error_message());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, bad_key_type,
+                        result.status().error_message());
+  }
+
+  {  // Bad key value.
+    KeyData key_data;
+    key_data.set_type_url(rsa_ssa_pkcs1_sign_key_type_);
+    key_data.set_value("some bad serialized proto");
+    auto result = key_manager.GetPrimitive(key_data);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "not parse",
+                        result.status().error_message());
+  }
+
+  {  // Bad version.
+    KeyData key_data;
+    RsaSsaPkcs1PrivateKey key;
+    key.set_version(1);
+    key_data.set_type_url(rsa_ssa_pkcs1_sign_key_type_);
+    key_data.set_value(key.SerializeAsString());
+    auto result = key_manager.GetPrimitive(key_data);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "version",
+                        result.status().error_message());
+  }
+}
+
+TEST_F(RsaSsaPkcs1SignKeyManagerTest, NewKeyErrors) {
+  RsaSsaPkcs1SignKeyManager key_manager;
+  const KeyFactory& key_factory = key_manager.get_key_factory();
+
+  // Empty key format.
+  RsaSsaPkcs1KeyFormat key_format;
+  {
+    auto result = key_factory.NewKey(key_format);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+  }
+
+  // Bad serialized format.
+  {
+    auto result = key_factory.NewKey("some bad serialization");
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+  }
+
+  // Wrong format proto.
+  {
+    AesEaxKeyFormat wrong_key_format;
+    auto result = key_factory.NewKey(wrong_key_format);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+  }
+}
+
+TEST_F(RsaSsaPkcs1SignKeyManagerTest, PublicKeyExtractionErrors) {
+  RsaSsaPkcs1SignKeyManager key_manager;
+  auto private_key_factory =
+      dynamic_cast<const PrivateKeyFactory*>(&(key_manager.get_key_factory()));
+  ASSERT_NE(private_key_factory, nullptr);
+
+  AesGcmKeyManager aead_key_manager;
+  auto aead_private_key_factory = dynamic_cast<const PrivateKeyFactory*>(
+      &(aead_key_manager.get_key_factory()));
+  ASSERT_EQ(nullptr, aead_private_key_factory);
+
+  auto aead_key_result = aead_key_manager.get_key_factory().NewKey(
+      AeadKeyTemplates::Aes128Gcm().value());
+  ASSERT_TRUE(aead_key_result.ok()) << aead_key_result.status();
+  auto aead_key = std::move(aead_key_result.ValueOrDie());
+  auto public_key_data_result =
+      private_key_factory->GetPublicKeyData(aead_key->SerializeAsString());
+  EXPECT_FALSE(public_key_data_result.ok());
+  EXPECT_EQ(util::error::INVALID_ARGUMENT,
+            public_key_data_result.status().error_code());
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/signature/rsa_ssa_pkcs1_verify_key_manager.cc b/cc/signature/rsa_ssa_pkcs1_verify_key_manager.cc
new file mode 100644
index 0000000..b08c304
--- /dev/null
+++ b/cc/signature/rsa_ssa_pkcs1_verify_key_manager.cc
@@ -0,0 +1,98 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/signature/rsa_ssa_pkcs1_verify_key_manager.h"
+
+#include "absl/strings/string_view.h"
+#include "tink/key_manager.h"
+#include "tink/public_key_verify.h"
+#include "tink/subtle/rsa_ssa_pkcs1_verify_boringssl.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/util/enums.h"
+#include "tink/util/errors.h"
+#include "tink/util/protobuf_helper.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "tink/util/validation.h"
+#include "proto/rsa_ssa_pkcs1.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+
+using crypto::tink::util::Enums;
+using crypto::tink::util::Status;
+using crypto::tink::util::StatusOr;
+using google::crypto::tink::KeyData;
+using google::crypto::tink::RsaSsaPkcs1Params;
+using google::crypto::tink::RsaSsaPkcs1PublicKey;
+using portable_proto::MessageLite;
+
+constexpr uint32_t RsaSsaPkcs1VerifyKeyManager::kVersion;
+
+RsaSsaPkcs1VerifyKeyManager::RsaSsaPkcs1VerifyKeyManager()
+    : key_factory_(KeyFactory::AlwaysFailingFactory(
+          util::Status(util::error::UNIMPLEMENTED,
+                       "Operation not supported for public keys, "
+                       "please use the RsaSsaPkcs1SignKeyManager."))) {}
+
+const KeyFactory& RsaSsaPkcs1VerifyKeyManager::get_key_factory() const {
+  return *key_factory_;
+}
+
+uint32_t RsaSsaPkcs1VerifyKeyManager::get_version() const { return kVersion; }
+
+StatusOr<std::unique_ptr<PublicKeyVerify>>
+RsaSsaPkcs1VerifyKeyManager::GetPrimitiveFromKey(
+    const RsaSsaPkcs1PublicKey& rsa_ssa_pkcs1_public_key) const {
+  Status status = Validate(rsa_ssa_pkcs1_public_key);
+  if (!status.ok()) return status;
+  subtle::SubtleUtilBoringSSL::RsaPublicKey rsa_pub_key;
+  rsa_pub_key.n = rsa_ssa_pkcs1_public_key.n();
+  rsa_pub_key.e = rsa_ssa_pkcs1_public_key.e();
+
+  subtle::SubtleUtilBoringSSL::RsaSsaPkcs1Params params;
+  RsaSsaPkcs1Params rsa_ssa_pkcs1_params = rsa_ssa_pkcs1_public_key.params();
+  params.hash_type = Enums::ProtoToSubtle(rsa_ssa_pkcs1_params.hash_type());
+
+  auto rsa_ssa_pkcs1_result =
+      subtle::RsaSsaPkcs1VerifyBoringSsl::New(rsa_pub_key, params);
+  if (!rsa_ssa_pkcs1_result.ok()) return rsa_ssa_pkcs1_result.status();
+  std::unique_ptr<PublicKeyVerify> rsa_ssa_pkcs1(
+      rsa_ssa_pkcs1_result.ValueOrDie().release());
+  return std::move(rsa_ssa_pkcs1);
+}
+
+// static
+Status RsaSsaPkcs1VerifyKeyManager::Validate(const RsaSsaPkcs1Params& params) {
+  return subtle::SubtleUtilBoringSSL::ValidateSignatureHash(
+      Enums::ProtoToSubtle(params.hash_type()));
+}
+
+// static
+Status RsaSsaPkcs1VerifyKeyManager::Validate(const RsaSsaPkcs1PublicKey& key) {
+  Status status = ValidateVersion(key.version(), kVersion);
+  if (!status.ok()) return status;
+  auto status_or_n = subtle::SubtleUtilBoringSSL::str2bn(key.n());
+  if (!status_or_n.ok()) return status_or_n.status();
+  auto modulus_status = subtle::SubtleUtilBoringSSL::ValidateRsaModulusSize(
+      BN_num_bits(status_or_n.ValueOrDie().get()));
+  if (!modulus_status.ok()) return modulus_status;
+  return Validate(key.params());
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/signature/rsa_ssa_pkcs1_verify_key_manager.h b/cc/signature/rsa_ssa_pkcs1_verify_key_manager.h
new file mode 100644
index 0000000..2e78d0e
--- /dev/null
+++ b/cc/signature/rsa_ssa_pkcs1_verify_key_manager.h
@@ -0,0 +1,75 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_SIGNATURE_RSA_SSA_PKCS1_VERIFY_KEY_MANAGER_H_
+#define TINK_SIGNATURE_RSA_SSA_PKCS1_VERIFY_KEY_MANAGER_H_
+
+#include <algorithm>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "tink/core/key_manager_base.h"
+#include "tink/key_manager.h"
+#include "tink/public_key_verify.h"
+#include "tink/util/errors.h"
+#include "tink/util/protobuf_helper.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "proto/rsa_ssa_pkcs1.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+
+class RsaSsaPkcs1VerifyKeyManager
+    : public KeyManagerBase<PublicKeyVerify,
+                            google::crypto::tink::RsaSsaPkcs1PublicKey> {
+ public:
+  static constexpr uint32_t kVersion = 0;
+
+  RsaSsaPkcs1VerifyKeyManager();
+
+  // Returns the version of this key manager.
+  uint32_t get_version() const override;
+
+  // Returns a factory that generates keys of the key type
+  // handled by this manager.
+  const KeyFactory& get_key_factory() const override;
+
+  virtual ~RsaSsaPkcs1VerifyKeyManager() {}
+
+ protected:
+  crypto::tink::util::StatusOr<std::unique_ptr<PublicKeyVerify>>
+  GetPrimitiveFromKey(const google::crypto::tink::RsaSsaPkcs1PublicKey&
+                          rsa_ssa_pkcs1_public_key) const override;
+
+ private:
+  // Friends that re-use proto validation helpers.
+  friend class RsaSsaPkcs1PrivateKeyFactory;
+  friend class RsaSsaPkcs1SignKeyManager;
+
+  std::unique_ptr<KeyFactory> key_factory_;
+
+  static crypto::tink::util::Status Validate(
+      const google::crypto::tink::RsaSsaPkcs1Params& params);
+  static crypto::tink::util::Status Validate(
+      const google::crypto::tink::RsaSsaPkcs1PublicKey& key);
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_SIGNATURE_RSA_SSA_PKCS1_VERIFY_KEY_MANAGER_H_
diff --git a/cc/signature/rsa_ssa_pkcs1_verify_key_manager_test.cc b/cc/signature/rsa_ssa_pkcs1_verify_key_manager_test.cc
new file mode 100644
index 0000000..e82d381
--- /dev/null
+++ b/cc/signature/rsa_ssa_pkcs1_verify_key_manager_test.cc
@@ -0,0 +1,225 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/signature/rsa_ssa_pkcs1_verify_key_manager.h"
+
+#include "gtest/gtest.h"
+#include "absl/strings/escaping.h"
+#include "tink/public_key_sign.h"
+#include "tink/public_key_verify.h"
+
+#include "tink/registry.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_util.h"
+#include "proto/common.pb.h"
+#include "proto/rsa_ssa_pkcs1.pb.h"
+#include "proto/tink.pb.h"
+
+namespace pb = google::crypto::tink;
+
+// TODO(quannguyen): add more tests once RsaSsaPkcs1SignKeyManager is available.
+namespace crypto {
+namespace tink {
+
+using google::crypto::tink::KeyData;
+using google::crypto::tink::RsaSsaPkcs1KeyFormat;
+using google::crypto::tink::RsaSsaPkcs1PublicKey;
+
+namespace {
+
+// Test vector from
+// https://csrc.nist.gov/Projects/Cryptographic-Algorithm-Validation-Program/Digital-Signatures
+struct NistTestVector {
+  std::string n;
+  std::string e;
+  std::string message;
+  std::string signature;
+  pb::HashType hash_type;
+};
+
+class RsaSsaPkcs1VerifyKeyManagerTest : public ::testing::Test {
+ protected:
+  const NistTestVector nist_test_vector_{
+      absl::HexStringToBytes(
+          "c9548608087bed6be0a4623b9d849aa0b4b4b6114ad0a7d82578076ceefe26ce48d1"
+          "448e16d69963510e1e5fc658f3cf8f32a489b62d93fec1cdea6e1dde3feba04bb6a0"
+          "34518d83fd6138ea999982ab95d6a03517688ab6f8411c4a96b3e79d4141b8f68338"
+          "a9baa99f4e2c7845b573981061c5fd29d5fc21833ff1b030b2deb651e51a291168e2"
+          "b45ab4202dcd97b891925c75338e0e648d9d9ad325c10884e1fcdccc1c547b4a9c36"
+          "aef939e8802b62405d6e3d358ffa88f206b976b87f8b12b827b0ee7823f9d1955f47"
+          "f8678f7843b4cd03777e46717060e82bf149b36d4cf3d0bc7e4d0effde51a72f4ced"
+          "8e8e5b11bdb135825ff08873e2f776929abb"),
+      absl::HexStringToBytes("3c7bf9"),
+      absl::HexStringToBytes(
+          "bf082fa4b79f32849e8fae692696fc978ccb648c6e278d9bde4338d7b4632e3228b4"
+          "77e6a0d2cd14c68d51abdeed7c8c577457ec9fa2eff93cbf03c019d4014e1dfb3115"
+          "02d82f9265689e2d19f91b61c17a701c9ef50a69a55aae4cd57e67edc763c3f987ba"
+          "3e46a2a6ffb680c3c25df46716e61228c832419e9f43916a4959"),
+      absl::HexStringToBytes(
+          "621120a71ff2a182dd2997beb2480f54be516b79a4c202d1d6f59270f8e4d4dbd625"
+          "ac52fe0e49c5fd69dc0d15fb19ec58c9312a8161a61cb878abcb11399937f28ff080"
+          "3877c239ce0b7c4cbc1e23eca22746b071b2716475424c12944660b929b6240aebe8"
+          "47fcb94f63d212f3aa538515dc061e9810fdb0adeb374d0f69d24fd52c94e42668a4"
+          "8fc0a57819952a40efb732cfa08b3d2b371780aea97be34efb5239994d7ee7c6ab91"
+          "34b76711e76813ad5f5c3a5c95399e907650534dbfafec900c21be1308ddff6eda52"
+          "5f35e4fb3d275de46250ea1e4b96b60bd125b85f6c52b5419a725cd69b10cefd0901"
+          "abe7f9e15940594cf811e34c60f38768244c"),
+      pb::HashType::SHA256};
+
+  std::string rsa_ssa_pkcs1_verify_key_type_ =
+      "type.googleapis.com/google.crypto.tink.RsaSsaPkcs1PublicKey";
+};
+
+TEST_F(RsaSsaPkcs1VerifyKeyManagerTest, NistTestVector) {
+  RsaSsaPkcs1VerifyKeyManager key_manager;
+
+  EXPECT_EQ(0, key_manager.get_version());
+  EXPECT_EQ("type.googleapis.com/google.crypto.tink.RsaSsaPkcs1PublicKey",
+            key_manager.get_key_type());
+  EXPECT_TRUE(key_manager.DoesSupport(key_manager.get_key_type()));
+
+  // NIST test vector should work.
+  RsaSsaPkcs1PublicKey key;
+  key.mutable_params()->set_hash_type(nist_test_vector_.hash_type);
+  key.set_version(0);
+  key.set_n(nist_test_vector_.n);
+  key.set_e(nist_test_vector_.e);
+  auto result = key_manager.GetPrimitive(key);
+  EXPECT_TRUE(result.ok());
+  EXPECT_TRUE(
+      result.ValueOrDie()
+          ->Verify(nist_test_vector_.signature, nist_test_vector_.message)
+          .ok());
+}
+
+TEST_F(RsaSsaPkcs1VerifyKeyManagerTest, KeyDataErrors) {
+  RsaSsaPkcs1VerifyKeyManager key_manager;
+
+  {  // Bad key type.
+    KeyData key_data;
+    std::string bad_key_type = "type.googleapis.com/google.crypto.tink.SomeOtherKey";
+    key_data.set_type_url(bad_key_type);
+    auto result = key_manager.GetPrimitive(key_data);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "not supported",
+                        result.status().error_message());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, bad_key_type,
+                        result.status().error_message());
+  }
+
+  {  // Bad key value.
+    KeyData key_data;
+    key_data.set_type_url(rsa_ssa_pkcs1_verify_key_type_);
+    key_data.set_value("some bad serialized proto");
+    auto result = key_manager.GetPrimitive(key_data);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "not parse",
+                        result.status().error_message());
+  }
+
+  {  // Bad version.
+    KeyData key_data;
+    RsaSsaPkcs1PublicKey key;
+    key.set_version(1);
+    key_data.set_type_url(rsa_ssa_pkcs1_verify_key_type_);
+    key_data.set_value(key.SerializeAsString());
+    auto result = key_manager.GetPrimitive(key_data);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "version",
+                        result.status().error_message());
+  }
+}
+
+TEST_F(RsaSsaPkcs1VerifyKeyManagerTest, KeyMessageErrors) {
+  RsaSsaPkcs1VerifyKeyManager key_manager;
+
+  {  // Use SHA1 as signature hash.
+    RsaSsaPkcs1PublicKey key;
+    key.mutable_params()->set_hash_type(pb::HashType::SHA1);
+    key.set_version(0);
+    key.set_n(nist_test_vector_.n);
+    key.set_e(nist_test_vector_.e);
+    auto result = key_manager.GetPrimitive(key);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring,
+                        "SHA1 is not safe for digital signature",
+                        result.status().error_message());
+  }
+
+  {  // Small modulus.
+    RsaSsaPkcs1PublicKey key;
+    key.mutable_params()->set_hash_type(pb::HashType::SHA256);
+    key.set_version(0);
+    key.set_n("\x23");
+    key.set_e("\x3");
+    auto result = key_manager.GetPrimitive(key);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring,
+                        "only modulus size >= 2048-bit is supported",
+                        result.status().error_message());
+  }
+}
+
+TEST_F(RsaSsaPkcs1VerifyKeyManagerTest, NewKeyError) {
+  RsaSsaPkcs1VerifyKeyManager key_manager;
+  const KeyFactory& key_factory = key_manager.get_key_factory();
+
+  {  // Via NewKey(format_proto).
+    RsaSsaPkcs1KeyFormat key_format;
+    auto result = key_factory.NewKey(key_format);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::UNIMPLEMENTED, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "not supported",
+                        result.status().error_message());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring,
+                        "use the RsaSsaPkcs1SignKeyManager",
+                        result.status().error_message());
+  }
+
+  {  // Via NewKey(serialized_format_proto).
+    RsaSsaPkcs1KeyFormat key_format;
+    auto result = key_factory.NewKey(key_format.SerializeAsString());
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::UNIMPLEMENTED, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "not supported",
+                        result.status().error_message());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring,
+                        "use the RsaSsaPkcs1SignKeyManager",
+                        result.status().error_message());
+  }
+
+  {  // Via NewKeyData(serialized_format_proto).
+    RsaSsaPkcs1KeyFormat key_format;
+    auto result = key_factory.NewKeyData(key_format.SerializeAsString());
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::UNIMPLEMENTED, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "not supported",
+                        result.status().error_message());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring,
+                        "use the RsaSsaPkcs1SignKeyManager",
+                        result.status().error_message());
+  }
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/signature/rsa_ssa_pss_sign_key_manager.cc b/cc/signature/rsa_ssa_pss_sign_key_manager.cc
new file mode 100644
index 0000000..9bdac19
--- /dev/null
+++ b/cc/signature/rsa_ssa_pss_sign_key_manager.cc
@@ -0,0 +1,201 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/signature/rsa_ssa_pss_sign_key_manager.h"
+
+#include "absl/memory/memory.h"
+#include "absl/strings/string_view.h"
+#include "tink/key_manager.h"
+#include "tink/public_key_sign.h"
+#include "tink/signature/rsa_ssa_pss_verify_key_manager.h"
+#include "tink/signature/sig_util.h"
+#include "tink/subtle/rsa_ssa_pss_sign_boringssl.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/util/enums.h"
+#include "tink/util/errors.h"
+#include "tink/util/protobuf_helper.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "tink/util/validation.h"
+#include "proto/rsa_ssa_pss.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+
+using crypto::tink::util::Enums;
+using crypto::tink::util::Status;
+using crypto::tink::util::StatusOr;
+using google::crypto::tink::KeyData;
+using google::crypto::tink::RsaSsaPssKeyFormat;
+using google::crypto::tink::RsaSsaPssParams;
+using google::crypto::tink::RsaSsaPssPrivateKey;
+
+namespace {
+std::unique_ptr<RsaSsaPssPrivateKey> RsaPrivateKeySubtleToProto(
+    const subtle::SubtleUtilBoringSSL::RsaPrivateKey& private_key) {
+  auto key_proto = absl::make_unique<RsaSsaPssPrivateKey>();
+  key_proto->set_version(RsaSsaPssSignKeyManager::kVersion);
+  key_proto->set_d(private_key.d);
+  key_proto->set_p(private_key.p);
+  key_proto->set_q(private_key.q);
+  key_proto->set_dp(private_key.dp);
+  key_proto->set_dq(private_key.dq);
+  key_proto->set_crt(private_key.crt);
+  auto* public_key_proto = key_proto->mutable_public_key();
+  public_key_proto->set_version(RsaSsaPssSignKeyManager::kVersion);
+  public_key_proto->set_n(private_key.n);
+  public_key_proto->set_e(private_key.e);
+  return key_proto;
+}
+
+subtle::SubtleUtilBoringSSL::RsaPrivateKey RsaPrivateKeyProtoToSubtle(
+    const RsaSsaPssPrivateKey& key_proto) {
+  subtle::SubtleUtilBoringSSL::RsaPrivateKey key;
+  key.n = key_proto.public_key().n();
+  key.e = key_proto.public_key().e();
+  key.d = key_proto.d();
+  key.p = key_proto.p();
+  key.q = key_proto.q();
+  key.dp = key_proto.dp();
+  key.dq = key_proto.dq();
+  key.crt = key_proto.crt();
+  return key;
+}
+
+}  // namespace
+
+class RsaSsaPssPrivateKeyFactory
+    : public PrivateKeyFactory,
+      public KeyFactoryBase<RsaSsaPssPrivateKey, RsaSsaPssKeyFormat> {
+ public:
+  RsaSsaPssPrivateKeyFactory() {}
+
+  KeyData::KeyMaterialType key_material_type() const override {
+    return KeyData::ASYMMETRIC_PRIVATE;
+  }
+
+  // Returns KeyData proto that contains RsaSsaPssPublicKey
+  // extracted from the given serialized_private_key, which must contain
+  // RsaSsaPssPrivateKey-proto.
+  crypto::tink::util::StatusOr<std::unique_ptr<google::crypto::tink::KeyData>>
+  GetPublicKeyData(absl::string_view serialized_private_key) const override;
+
+ protected:
+  StatusOr<std::unique_ptr<RsaSsaPssPrivateKey>> NewKeyFromFormat(
+      const RsaSsaPssKeyFormat& rsa_ssa_pss_key_format) const override;
+};
+
+StatusOr<std::unique_ptr<RsaSsaPssPrivateKey>>
+RsaSsaPssPrivateKeyFactory::NewKeyFromFormat(
+    const RsaSsaPssKeyFormat& rsa_ssa_pss_key_format) const {
+  util::Status is_valid =
+      RsaSsaPssSignKeyManager::Validate(rsa_ssa_pss_key_format);
+  if (!is_valid.ok()) return is_valid;
+
+  auto e = subtle::SubtleUtilBoringSSL::str2bn(
+      rsa_ssa_pss_key_format.public_exponent());
+  if (!e.ok()) return e.status();
+
+  subtle::SubtleUtilBoringSSL::RsaPrivateKey private_key;
+  subtle::SubtleUtilBoringSSL::RsaPublicKey public_key;
+  util::Status status = subtle::SubtleUtilBoringSSL::GetNewRsaKeyPair(
+      rsa_ssa_pss_key_format.modulus_size_in_bits(), e.ValueOrDie().get(),
+      &private_key, &public_key);
+  if (!status.ok()) return status;
+
+  auto key_proto = RsaPrivateKeySubtleToProto(private_key);
+  auto* public_key_proto = key_proto->mutable_public_key();
+  *public_key_proto->mutable_params() = rsa_ssa_pss_key_format.params();
+  return absl::implicit_cast<StatusOr<std::unique_ptr<RsaSsaPssPrivateKey>>>(
+      std::move(key_proto));
+}
+
+StatusOr<std::unique_ptr<KeyData>> RsaSsaPssPrivateKeyFactory::GetPublicKeyData(
+    absl::string_view serialized_private_key) const {
+  RsaSsaPssPrivateKey private_key;
+  if (!private_key.ParseFromString(std::string(serialized_private_key))) {
+    return ToStatusF(util::error::INVALID_ARGUMENT,
+                     "Could not parse the passed string as proto '%s'.",
+                     RsaSsaPssVerifyKeyManager::static_key_type().c_str());
+  }
+  auto status = RsaSsaPssSignKeyManager::Validate(private_key);
+  if (!status.ok()) return status;
+  auto key_data = absl::make_unique<KeyData>();
+  key_data->set_type_url(RsaSsaPssVerifyKeyManager::static_key_type());
+  key_data->set_value(private_key.public_key().SerializeAsString());
+  key_data->set_key_material_type(KeyData::ASYMMETRIC_PUBLIC);
+  return std::move(key_data);
+}
+
+constexpr uint32_t RsaSsaPssSignKeyManager::kVersion;
+
+RsaSsaPssSignKeyManager::RsaSsaPssSignKeyManager()
+    : key_factory_(absl::make_unique<RsaSsaPssPrivateKeyFactory>()) {}
+
+const KeyFactory& RsaSsaPssSignKeyManager::get_key_factory() const {
+  return *key_factory_;
+}
+
+uint32_t RsaSsaPssSignKeyManager::get_version() const { return kVersion; }
+
+StatusOr<std::unique_ptr<PublicKeySign>>
+RsaSsaPssSignKeyManager::GetPrimitiveFromKey(
+    const RsaSsaPssPrivateKey& key_proto) const {
+  Status status = Validate(key_proto);
+  if (!status.ok()) return status;
+  auto key = RsaPrivateKeyProtoToSubtle(key_proto);
+  subtle::SubtleUtilBoringSSL::RsaSsaPssParams params;
+  const RsaSsaPssParams& params_proto = key_proto.public_key().params();
+  params.sig_hash = Enums::ProtoToSubtle(params_proto.sig_hash());
+  params.mgf1_hash = Enums::ProtoToSubtle(params_proto.mgf1_hash());
+  params.salt_length = params_proto.salt_length();
+  auto signer = subtle::RsaSsaPssSignBoringSsl::New(key, params);
+  if (!signer.ok()) return signer.status();
+  // To check that the key is correct, we sign a test message with private key
+  // and verify with public key.
+  auto public_key_data_result =
+      key_factory_->GetPublicKeyData(key_proto.SerializeAsString());
+  auto public_key_data = std::move(public_key_data_result.ValueOrDie());
+  RsaSsaPssVerifyKeyManager verify_key_manager;
+  auto verifier = verify_key_manager.GetPrimitive(*public_key_data);
+  if (!verifier.ok()) return verifier.status();
+  auto sign_verify_result =
+      SignAndVerify(signer.ValueOrDie().get(), verifier.ValueOrDie().get());
+  if (!sign_verify_result.ok()) {
+    return util::Status(util::error::INTERNAL,
+                        "security bug: signing with private key followed by "
+                        "verifying with public key failed");
+  }
+  return signer;
+}
+
+// static
+Status RsaSsaPssSignKeyManager::Validate(const RsaSsaPssPrivateKey& key) {
+  Status status = ValidateVersion(key.version(), kVersion);
+  if (!status.ok()) return status;
+  return RsaSsaPssVerifyKeyManager::Validate(key.public_key());
+}
+
+// static
+Status RsaSsaPssSignKeyManager::Validate(const RsaSsaPssKeyFormat& key_format) {
+  auto modulus_status = subtle::SubtleUtilBoringSSL::ValidateRsaModulusSize(
+      key_format.modulus_size_in_bits());
+  if (!modulus_status.ok()) return modulus_status;
+  return RsaSsaPssVerifyKeyManager::Validate(key_format.params());
+}
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/signature/rsa_ssa_pss_sign_key_manager.h b/cc/signature/rsa_ssa_pss_sign_key_manager.h
new file mode 100644
index 0000000..7ceb2db
--- /dev/null
+++ b/cc/signature/rsa_ssa_pss_sign_key_manager.h
@@ -0,0 +1,73 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_SIGNATURE_RSA_SSA_PSS_SIGN_KEY_MANAGER_H_
+#define TINK_SIGNATURE_RSA_SSA_PSS_SIGN_KEY_MANAGER_H_
+
+#include <algorithm>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "tink/core/key_manager_base.h"
+#include "tink/key_manager.h"
+#include "tink/public_key_sign.h"
+#include "tink/util/errors.h"
+#include "tink/util/protobuf_helper.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "proto/rsa_ssa_pss.pb.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+
+class RsaSsaPssSignKeyManager
+    : public KeyManagerBase<PublicKeySign,
+                            google::crypto::tink::RsaSsaPssPrivateKey> {
+ public:
+  static constexpr uint32_t kVersion = 0;
+
+  RsaSsaPssSignKeyManager();
+
+  // Returns the version of this key manager.
+  uint32_t get_version() const override;
+
+  // Returns a factory that generates keys of the key type
+  // handled by this manager.
+  const KeyFactory& get_key_factory() const override;
+
+  virtual ~RsaSsaPssSignKeyManager() {}
+
+ protected:
+  crypto::tink::util::StatusOr<std::unique_ptr<PublicKeySign>>
+  GetPrimitiveFromKey(const google::crypto::tink::RsaSsaPssPrivateKey&
+                          key_proto) const override;
+
+ private:
+  friend class RsaSsaPssPrivateKeyFactory;
+
+  std::unique_ptr<PrivateKeyFactory> key_factory_;
+
+  static crypto::tink::util::Status Validate(
+      const google::crypto::tink::RsaSsaPssKeyFormat& key_format);
+
+  static crypto::tink::util::Status Validate(
+      const google::crypto::tink::RsaSsaPssPrivateKey& key);
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_SIGNATURE_RSA_SSA_PSS_SIGN_KEY_MANAGER_H_
diff --git a/cc/signature/rsa_ssa_pss_sign_key_manager_test.cc b/cc/signature/rsa_ssa_pss_sign_key_manager_test.cc
new file mode 100644
index 0000000..210aff0
--- /dev/null
+++ b/cc/signature/rsa_ssa_pss_sign_key_manager_test.cc
@@ -0,0 +1,381 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/signature/rsa_ssa_pss_sign_key_manager.h"
+
+#include "gtest/gtest.h"
+#include "tink/aead/aead_key_templates.h"
+#include "tink/aead/aes_gcm_key_manager.h"
+#include "tink/public_key_sign.h"
+#include "tink/registry.h"
+#include "tink/signature/rsa_ssa_pss_verify_key_manager.h"
+#include "tink/signature/signature_key_templates.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_util.h"
+#include "proto/aes_eax.pb.h"
+#include "proto/common.pb.h"
+#include "proto/rsa_ssa_pss.pb.h"
+#include "proto/tink.pb.h"
+
+namespace pb = google::crypto::tink;
+
+namespace crypto {
+namespace tink {
+
+using google::crypto::tink::AesEaxKeyFormat;
+using google::crypto::tink::KeyData;
+using google::crypto::tink::RsaSsaPssKeyFormat;
+using google::crypto::tink::RsaSsaPssPrivateKey;
+using google::crypto::tink::RsaSsaPssPublicKey;
+using subtle::SubtleUtilBoringSSL;
+
+namespace {
+
+class RsaSsaPssSignKeyManagerTest : public ::testing::Test {
+ protected:
+  std::string key_type_prefix_ = "type.googleapis.com/";
+  std::string rsa_ssa_pss_sign_key_type_ =
+      "type.googleapis.com/google.crypto.tink.RsaSsaPssPrivateKey";
+};
+
+// Checks whether given key is compatible with the given format.
+void CheckNewKey(const RsaSsaPssPrivateKey& private_key,
+                 const RsaSsaPssKeyFormat& key_format) {
+  RsaSsaPssSignKeyManager key_manager;
+  RsaSsaPssPublicKey public_key = private_key.public_key();
+  EXPECT_EQ(0, private_key.version());
+  EXPECT_TRUE(private_key.has_public_key());
+  EXPECT_EQ(0, public_key.version());
+  EXPECT_GT(public_key.n().length(), 0);
+  EXPECT_GT(public_key.e().length(), 0);
+  EXPECT_EQ(public_key.params().SerializeAsString(),
+            key_format.params().SerializeAsString());
+  EXPECT_EQ(key_format.public_exponent(), public_key.e());
+  auto primitive_result = key_manager.GetPrimitive(private_key);
+  EXPECT_TRUE(primitive_result.ok()) << primitive_result.status();
+  auto n = std::move(SubtleUtilBoringSSL::str2bn(public_key.n()).ValueOrDie());
+  auto d = std::move(SubtleUtilBoringSSL::str2bn(private_key.d()).ValueOrDie());
+  auto p = std::move(SubtleUtilBoringSSL::str2bn(private_key.p()).ValueOrDie());
+  auto q = std::move(SubtleUtilBoringSSL::str2bn(private_key.q()).ValueOrDie());
+  auto dp =
+      std::move(SubtleUtilBoringSSL::str2bn(private_key.dp()).ValueOrDie());
+  auto dq =
+      std::move(SubtleUtilBoringSSL::str2bn(private_key.dq()).ValueOrDie());
+  bssl::UniquePtr<BN_CTX> ctx(BN_CTX_new());
+
+  // Check n = p * q.
+  auto n_calc = bssl::UniquePtr<BIGNUM>(BN_new());
+  ASSERT_TRUE(BN_mul(n_calc.get(), p.get(), q.get(), ctx.get()));
+  ASSERT_TRUE(BN_equal_consttime(n_calc.get(), n.get()));
+
+  // Check n size >= modulus_size_in_bits bit.
+  EXPECT_GE(BN_num_bits(n.get()), key_format.modulus_size_in_bits());
+
+  // dp = d mod (p - 1)
+  auto pm1 = bssl::UniquePtr<BIGNUM>(BN_dup(p.get()));
+  ASSERT_TRUE(BN_sub_word(pm1.get(), 1));
+  auto dp_calc = bssl::UniquePtr<BIGNUM>(BN_new());
+  ASSERT_TRUE(BN_mod(dp_calc.get(), d.get(), pm1.get(), ctx.get()));
+  ASSERT_TRUE(BN_equal_consttime(dp_calc.get(), dp.get()));
+
+  // dq = d mod (q - 1)
+  auto qm1 = bssl::UniquePtr<BIGNUM>(BN_dup(q.get()));
+  ASSERT_TRUE(BN_sub_word(qm1.get(), 1));
+  auto dq_calc = bssl::UniquePtr<BIGNUM>(BN_new());
+  ASSERT_TRUE(BN_mod(dq_calc.get(), d.get(), qm1.get(), ctx.get()));
+
+  ASSERT_TRUE(BN_equal_consttime(dq_calc.get(), dq.get()));
+}
+
+TEST_F(RsaSsaPssSignKeyManagerTest, Basic) {
+  RsaSsaPssSignKeyManager key_manager;
+  EXPECT_EQ(0, key_manager.get_version());
+  EXPECT_EQ("type.googleapis.com/google.crypto.tink.RsaSsaPssPrivateKey",
+            key_manager.get_key_type());
+  EXPECT_TRUE(key_manager.DoesSupport(key_manager.get_key_type()));
+}
+
+TEST_F(RsaSsaPssSignKeyManagerTest, NewKeyFromKeyFormat) {
+  RsaSsaPssSignKeyManager key_manager;
+  const KeyFactory& key_factory = key_manager.get_key_factory();
+  RsaSsaPssKeyFormat key_format;
+  ASSERT_TRUE(key_format.ParseFromString(
+      SignatureKeyTemplates::RsaSsaPss3072Sha256Sha256F4().value()));
+  auto result = key_factory.NewKey(key_format);
+  EXPECT_TRUE(result.ok()) << result.status();
+  auto key = std::move(result.ValueOrDie());
+  ASSERT_EQ(rsa_ssa_pss_sign_key_type_, key_type_prefix_ + key->GetTypeName());
+  std::unique_ptr<RsaSsaPssPrivateKey> rsa_key(
+      static_cast<RsaSsaPssPrivateKey*>(key.release()));
+  CheckNewKey(*rsa_key, key_format);
+}
+
+TEST_F(RsaSsaPssSignKeyManagerTest, NewKeyFromSerializedKeyFormat) {
+  RsaSsaPssSignKeyManager key_manager;
+  const KeyFactory& key_factory = key_manager.get_key_factory();
+  RsaSsaPssKeyFormat key_format;
+  ASSERT_TRUE(key_format.ParseFromString(
+      SignatureKeyTemplates::RsaSsaPss4096Sha512Sha512F4().value()));
+  auto result = key_factory.NewKey(key_format.SerializeAsString());
+  EXPECT_TRUE(result.ok()) << result.status();
+  auto key = std::move(result.ValueOrDie());
+  ASSERT_EQ(rsa_ssa_pss_sign_key_type_, key_type_prefix_ + key->GetTypeName());
+  std::unique_ptr<RsaSsaPssPrivateKey> rsa_key(
+      static_cast<RsaSsaPssPrivateKey*>(key.release()));
+  CheckNewKey(*rsa_key, key_format);
+}
+
+TEST_F(RsaSsaPssSignKeyManagerTest, NewKeyDataFromSerializedKeyFormat) {
+  RsaSsaPssSignKeyManager key_manager;
+  const KeyFactory& key_factory = key_manager.get_key_factory();
+  RsaSsaPssKeyFormat key_format;
+  ASSERT_TRUE(key_format.ParseFromString(
+      SignatureKeyTemplates::RsaSsaPss4096Sha512Sha512F4().value()));
+  auto result = key_factory.NewKeyData(key_format.SerializeAsString());
+  EXPECT_TRUE(result.ok()) << result.status();
+  auto key_data = std::move(result.ValueOrDie());
+  ASSERT_EQ(rsa_ssa_pss_sign_key_type_, key_data->type_url());
+  RsaSsaPssPrivateKey rsa_key;
+  ASSERT_TRUE(rsa_key.ParseFromString(key_data->value()));
+  CheckNewKey(rsa_key, key_format);
+}
+
+TEST_F(RsaSsaPssSignKeyManagerTest, PublicKeyExtraction) {
+  RsaSsaPssSignKeyManager sign_key_manager;
+  auto private_key_factory = dynamic_cast<const PrivateKeyFactory*>(
+      &(sign_key_manager.get_key_factory()));
+  ASSERT_NE(private_key_factory, nullptr);
+  auto new_key_result = private_key_factory->NewKey(
+      SignatureKeyTemplates::RsaSsaPss3072Sha256Sha256F4().value());
+  std::unique_ptr<RsaSsaPssPrivateKey> private_key(
+      static_cast<RsaSsaPssPrivateKey*>(new_key_result.ValueOrDie().release()));
+  auto public_key_data_result =
+      private_key_factory->GetPublicKeyData(private_key->SerializeAsString());
+  EXPECT_TRUE(public_key_data_result.ok()) << public_key_data_result.status();
+  auto public_key_data = std::move(public_key_data_result.ValueOrDie());
+  EXPECT_EQ(RsaSsaPssVerifyKeyManager::static_key_type(),
+            public_key_data->type_url());
+  EXPECT_EQ(KeyData::ASYMMETRIC_PUBLIC, public_key_data->key_material_type());
+  EXPECT_EQ(private_key->public_key().SerializeAsString(),
+            public_key_data->value());
+  // Sign with private key and verify with public key.
+  RsaSsaPssVerifyKeyManager verify_key_manager;
+  auto signer = sign_key_manager.GetPrimitive(*private_key);
+  auto verifier = verify_key_manager.GetPrimitive(*public_key_data);
+  std::string message = "Wycheproof";
+  EXPECT_TRUE(
+      verifier.ValueOrDie()
+          ->Verify(signer.ValueOrDie()->Sign(message).ValueOrDie(), message)
+          .ok());
+}
+
+TEST_F(RsaSsaPssSignKeyManagerTest, NewKeyWithWeakSignatureHash) {
+  RsaSsaPssSignKeyManager key_manager;
+  const KeyFactory& key_factory = key_manager.get_key_factory();
+  RsaSsaPssKeyFormat key_format;
+  ASSERT_TRUE(key_format.ParseFromString(
+      SignatureKeyTemplates::RsaSsaPss3072Sha256Sha256F4().value()));
+  key_format.mutable_params()->set_sig_hash(pb::HashType::SHA1);
+  auto result = key_factory.NewKey(key_format.SerializeAsString());
+  EXPECT_FALSE(result.ok());
+  EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+  EXPECT_PRED_FORMAT2(testing::IsSubstring,
+                      "SHA1 is not safe for digital signature",
+                      result.status().error_message());
+}
+
+TEST_F(RsaSsaPssSignKeyManagerTest, NewKeyWithSmallModulus) {
+  RsaSsaPssSignKeyManager key_manager;
+  const KeyFactory& key_factory = key_manager.get_key_factory();
+  RsaSsaPssKeyFormat key_format;
+  ASSERT_TRUE(key_format.ParseFromString(
+      SignatureKeyTemplates::RsaSsaPss3072Sha256Sha256F4().value()));
+  key_format.set_modulus_size_in_bits(512);
+  auto result = key_factory.NewKey(key_format.SerializeAsString());
+  EXPECT_FALSE(result.ok());
+  EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+  EXPECT_PRED_FORMAT2(testing::IsSubstring,
+                      "only modulus size >= 2048-bit is supported",
+                      result.status().error_message());
+}
+
+TEST_F(RsaSsaPssSignKeyManagerTest, NewKeyWithMismatchMg1HashAndSigHash) {
+  RsaSsaPssSignKeyManager key_manager;
+  const KeyFactory& key_factory = key_manager.get_key_factory();
+  RsaSsaPssKeyFormat key_format;
+  ASSERT_TRUE(key_format.ParseFromString(
+      SignatureKeyTemplates::RsaSsaPss3072Sha256Sha256F4().value()));
+  key_format.mutable_params()->set_sig_hash(pb::HashType::SHA512);
+  key_format.mutable_params()->set_mgf1_hash(pb::HashType::SHA256);
+  auto result = key_factory.NewKey(key_format.SerializeAsString());
+  EXPECT_FALSE(result.ok());
+  EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+}
+
+TEST_F(RsaSsaPssSignKeyManagerTest,
+       GetPrimitiveWithMismatchMgf1HashAndSigHash) {
+  RsaSsaPssSignKeyManager sign_key_manager;
+  auto private_key_factory = dynamic_cast<const PrivateKeyFactory*>(
+      &(sign_key_manager.get_key_factory()));
+  ASSERT_NE(private_key_factory, nullptr);
+  auto new_key_result = private_key_factory->NewKey(
+      SignatureKeyTemplates::RsaSsaPss3072Sha256Sha256F4().value());
+  std::unique_ptr<RsaSsaPssPrivateKey> private_key(
+      static_cast<RsaSsaPssPrivateKey*>(new_key_result.ValueOrDie().release()));
+  private_key->mutable_public_key()->mutable_params()->set_sig_hash(
+      pb::HashType::SHA256);
+  private_key->mutable_public_key()->mutable_params()->set_mgf1_hash(
+      pb::HashType::SHA512);
+
+  auto result = sign_key_manager.GetPrimitive(*private_key);
+  EXPECT_FALSE(result.ok());
+  EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+}
+
+TEST_F(RsaSsaPssSignKeyManagerTest, GetPrimitiveWithWeakSignatureHash) {
+  RsaSsaPssSignKeyManager sign_key_manager;
+  auto private_key_factory = dynamic_cast<const PrivateKeyFactory*>(
+      &(sign_key_manager.get_key_factory()));
+  ASSERT_NE(private_key_factory, nullptr);
+  auto new_key_result = private_key_factory->NewKey(
+      SignatureKeyTemplates::RsaSsaPss3072Sha256Sha256F4().value());
+  std::unique_ptr<RsaSsaPssPrivateKey> private_key(
+      static_cast<RsaSsaPssPrivateKey*>(new_key_result.ValueOrDie().release()));
+  private_key->mutable_public_key()->mutable_params()->set_sig_hash(
+      pb::HashType::SHA1);
+  auto result = sign_key_manager.GetPrimitive(*private_key);
+  EXPECT_FALSE(result.ok());
+  EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+  EXPECT_PRED_FORMAT2(testing::IsSubstring,
+                      "SHA1 is not safe for digital signature",
+                      result.status().error_message());
+}
+
+TEST_F(RsaSsaPssSignKeyManagerTest, GetPrimitiveWithSmallModulus) {
+  RsaSsaPssSignKeyManager sign_key_manager;
+  auto private_key_factory = dynamic_cast<const PrivateKeyFactory*>(
+      &(sign_key_manager.get_key_factory()));
+  ASSERT_NE(private_key_factory, nullptr);
+  auto new_key_result = private_key_factory->NewKey(
+      SignatureKeyTemplates::RsaSsaPss3072Sha256Sha256F4().value());
+  std::unique_ptr<RsaSsaPssPrivateKey> private_key(
+      static_cast<RsaSsaPssPrivateKey*>(new_key_result.ValueOrDie().release()));
+  private_key->mutable_public_key()->set_n("\x23");
+  private_key->mutable_public_key()->set_e("\x3");
+  auto result = sign_key_manager.GetPrimitive(*private_key);
+  EXPECT_FALSE(result.ok());
+  EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+  EXPECT_PRED_FORMAT2(testing::IsSubstring,
+                      "only modulus size >= 2048-bit is supported",
+                      result.status().error_message());
+}
+
+TEST_F(RsaSsaPssSignKeyManagerTest, KeyDataErrors) {
+  RsaSsaPssSignKeyManager key_manager;
+
+  {  // Bad key type.
+    KeyData key_data;
+    std::string bad_key_type = "type.googleapis.com/google.crypto.tink.SomeOtherKey";
+    key_data.set_type_url(bad_key_type);
+    auto result = key_manager.GetPrimitive(key_data);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "not supported",
+                        result.status().error_message());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, bad_key_type,
+                        result.status().error_message());
+  }
+
+  {  // Bad key value.
+    KeyData key_data;
+    key_data.set_type_url(rsa_ssa_pss_sign_key_type_);
+    key_data.set_value("some bad serialized proto");
+    auto result = key_manager.GetPrimitive(key_data);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "not parse",
+                        result.status().error_message());
+  }
+
+  {  // Bad version.
+    KeyData key_data;
+    RsaSsaPssPrivateKey key;
+    key.set_version(1);
+    key_data.set_type_url(rsa_ssa_pss_sign_key_type_);
+    key_data.set_value(key.SerializeAsString());
+    auto result = key_manager.GetPrimitive(key_data);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring, "version",
+                        result.status().error_message());
+  }
+}
+
+TEST_F(RsaSsaPssSignKeyManagerTest, NewKeyErrors) {
+  RsaSsaPssSignKeyManager key_manager;
+  const KeyFactory& key_factory = key_manager.get_key_factory();
+
+  // Empty key format.
+  RsaSsaPssKeyFormat key_format;
+  {
+    auto result = key_factory.NewKey(key_format);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+  }
+
+  // Bad serialized format.
+  {
+    auto result = key_factory.NewKey("some bad serialization");
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+  }
+
+  // Wrong format proto.
+  {
+    AesEaxKeyFormat wrong_key_format;
+    auto result = key_factory.NewKey(wrong_key_format);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+  }
+}
+
+TEST_F(RsaSsaPssSignKeyManagerTest, PublicKeyExtractionErrors) {
+  RsaSsaPssSignKeyManager key_manager;
+  auto private_key_factory =
+      dynamic_cast<const PrivateKeyFactory*>(&(key_manager.get_key_factory()));
+  ASSERT_NE(private_key_factory, nullptr);
+
+  AesGcmKeyManager aead_key_manager;
+  auto aead_private_key_factory = dynamic_cast<const PrivateKeyFactory*>(
+      &(aead_key_manager.get_key_factory()));
+  ASSERT_EQ(nullptr, aead_private_key_factory);
+
+  auto aead_key_result = aead_key_manager.get_key_factory().NewKey(
+      AeadKeyTemplates::Aes128Gcm().value());
+  ASSERT_TRUE(aead_key_result.ok()) << aead_key_result.status();
+  auto aead_key = std::move(aead_key_result.ValueOrDie());
+  auto public_key_data_result =
+      private_key_factory->GetPublicKeyData(aead_key->SerializeAsString());
+  EXPECT_FALSE(public_key_data_result.ok());
+  EXPECT_EQ(util::error::INVALID_ARGUMENT,
+            public_key_data_result.status().error_code());
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/signature/rsa_ssa_pss_verify_key_manager.cc b/cc/signature/rsa_ssa_pss_verify_key_manager.cc
index 9106a71..4ffe374 100644
--- a/cc/signature/rsa_ssa_pss_verify_key_manager.cc
+++ b/cc/signature/rsa_ssa_pss_verify_key_manager.cc
@@ -31,8 +31,7 @@
 #include "proto/tink.pb.h"
 
 // TODO(quannguyen):
-//  + Validate mgf1 hash, salt length and possible e.
-//  + Add friend class RsaSsaPssSignBoringSSL.
+//  + Validate salt length and possible e.
 namespace crypto {
 namespace tink {
 
@@ -46,54 +45,13 @@
 using google::crypto::tink::RsaSsaPssPublicKey;
 using portable_proto::MessageLite;
 
-class RsaSsaPssPublicKeyFactory : public KeyFactory {
- public:
-  RsaSsaPssPublicKeyFactory() {}
-
-  // Not implemented for public keys.
-  crypto::tink::util::StatusOr<std::unique_ptr<portable_proto::MessageLite>>
-  NewKey(const portable_proto::MessageLite& key_format) const override;
-
-  // Not implemented for public keys.
-  crypto::tink::util::StatusOr<std::unique_ptr<portable_proto::MessageLite>>
-  NewKey(absl::string_view serialized_key_format) const override;
-
-  // Not implemented for public keys.
-  crypto::tink::util::StatusOr<std::unique_ptr<google::crypto::tink::KeyData>>
-  NewKeyData(absl::string_view serialized_key_format) const override;
-};
-
-StatusOr<std::unique_ptr<MessageLite>> RsaSsaPssPublicKeyFactory::NewKey(
-    const portable_proto::MessageLite& key_format) const {
-  return util::Status(util::error::UNIMPLEMENTED,
-                      "Operation not supported for public keys, "
-                      "please use the RsaSsaPssSignKeyManager.");
-}
-
-StatusOr<std::unique_ptr<MessageLite>> RsaSsaPssPublicKeyFactory::NewKey(
-    absl::string_view serialized_key_format) const {
-  return util::Status(util::error::UNIMPLEMENTED,
-                      "Operation not supported for public keys, "
-                      "please use the RsaSsaPssSignKeyManager.");
-}
-
-StatusOr<std::unique_ptr<KeyData>> RsaSsaPssPublicKeyFactory::NewKeyData(
-    absl::string_view serialized_key_format) const {
-  return util::Status(util::error::UNIMPLEMENTED,
-                      "Operation not supported for public keys, "
-                      "please use the RsaSsaPssSignKeyManager.");
-}
-
-constexpr char RsaSsaPssVerifyKeyManager::kKeyTypePrefix[];
-constexpr char RsaSsaPssVerifyKeyManager::kKeyType[];
 constexpr uint32_t RsaSsaPssVerifyKeyManager::kVersion;
 
 RsaSsaPssVerifyKeyManager::RsaSsaPssVerifyKeyManager()
-    : key_type_(kKeyType), key_factory_(new RsaSsaPssPublicKeyFactory()) {}
-
-const std::string& RsaSsaPssVerifyKeyManager::get_key_type() const {
-  return key_type_;
-}
+    : key_factory_(KeyFactory::AlwaysFailingFactory(
+          util::Status(util::error::UNIMPLEMENTED,
+                       "Operation not supported for public keys, "
+                       "please use the RsaSsaPssSignKeyManager."))) {}
 
 const KeyFactory& RsaSsaPssVerifyKeyManager::get_key_factory() const {
   return *key_factory_;
@@ -102,38 +60,7 @@
 uint32_t RsaSsaPssVerifyKeyManager::get_version() const { return kVersion; }
 
 StatusOr<std::unique_ptr<PublicKeyVerify>>
-RsaSsaPssVerifyKeyManager::GetPrimitive(const KeyData& key_data) const {
-  if (DoesSupport(key_data.type_url())) {
-    RsaSsaPssPublicKey rsa_ssa_pss_public_key;
-    if (!rsa_ssa_pss_public_key.ParseFromString(key_data.value())) {
-      return ToStatusF(util::error::INVALID_ARGUMENT,
-                       "Could not parse key_data.value as key type '%s'.",
-                       key_data.type_url().c_str());
-    }
-    return GetPrimitiveImpl(rsa_ssa_pss_public_key);
-  } else {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Key type '%s' is not supported by this manager.",
-                     key_data.type_url().c_str());
-  }
-}
-
-StatusOr<std::unique_ptr<PublicKeyVerify>>
-RsaSsaPssVerifyKeyManager::GetPrimitive(const MessageLite& key) const {
-  std::string key_type = std::string(kKeyTypePrefix) + key.GetTypeName();
-  if (DoesSupport(key_type)) {
-    const RsaSsaPssPublicKey& rsa_ssa_pss_public_key =
-        reinterpret_cast<const RsaSsaPssPublicKey&>(key);
-    return GetPrimitiveImpl(rsa_ssa_pss_public_key);
-  } else {
-    return ToStatusF(util::error::INVALID_ARGUMENT,
-                     "Key type '%s' is not supported by this manager.",
-                     key_type.c_str());
-  }
-}
-
-StatusOr<std::unique_ptr<PublicKeyVerify>>
-RsaSsaPssVerifyKeyManager::GetPrimitiveImpl(
+RsaSsaPssVerifyKeyManager::GetPrimitiveFromKey(
     const RsaSsaPssPublicKey& rsa_ssa_pss_public_key) const {
   Status status = Validate(rsa_ssa_pss_public_key);
   if (!status.ok()) return status;
@@ -157,18 +84,9 @@
 
 // static
 Status RsaSsaPssVerifyKeyManager::Validate(const RsaSsaPssParams& params) {
-  // Validates signature hash.
-  switch (params.sig_hash()) {
-    case HashType::SHA256: /* fall through */
-    case HashType::SHA512:
-      break;
-    case HashType::SHA1:
-      return util::Status(util::error::INVALID_ARGUMENT,
-                          "SHA1 is not safe for digital signature");
-    default:
-      return ToStatusF(util::error::INVALID_ARGUMENT,
-                       "Unsupported hash function '%d'", params.sig_hash());
-  }
+  auto hash_result = subtle::SubtleUtilBoringSSL::ValidateSignatureHash(
+      Enums::ProtoToSubtle(params.sig_hash()));
+  if (!hash_result.ok()) return hash_result;
   // The most common use case is that MGF1 hash is the same as signature hash.
   // This is recommended by RFC https://tools.ietf.org/html/rfc8017#section-8.1.
   // While using different hashes doesn't cause security vulnerabilities, there
@@ -194,28 +112,11 @@
   if (!status.ok()) return status;
   auto status_or_n = subtle::SubtleUtilBoringSSL::str2bn(key.n());
   if (!status_or_n.ok()) return status_or_n.status();
-  size_t modulus_size = BN_num_bits(status_or_n.ValueOrDie().get());
-  if (modulus_size < kMinModulusSizeInBits) {
-    return ToStatusF(
-        util::error::INVALID_ARGUMENT,
-        "Modulus size is %zu; only modulus size >= 2048-bit is supported",
-        modulus_size);
-  }
+  auto modulus_status = subtle::SubtleUtilBoringSSL::ValidateRsaModulusSize(
+      BN_num_bits(status_or_n.ValueOrDie().get()));
+  if (!modulus_status.ok()) return modulus_status;
   return Validate(key.params());
 }
 
-// static
-Status RsaSsaPssVerifyKeyManager::Validate(
-    const RsaSsaPssKeyFormat& key_format) {
-  size_t modulus_size = key_format.modulus_size_in_bits();
-  if (modulus_size < kMinModulusSizeInBits) {
-    return ToStatusF(
-        util::error::INTERNAL,
-        "Modulus size is %zu; only modulus size >= 2048-bit is supported",
-        modulus_size);
-  }
-  return Validate(key_format.params());
-}
-
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/signature/rsa_ssa_pss_verify_key_manager.h b/cc/signature/rsa_ssa_pss_verify_key_manager.h
index 1b60753..022b723 100644
--- a/cc/signature/rsa_ssa_pss_verify_key_manager.h
+++ b/cc/signature/rsa_ssa_pss_verify_key_manager.h
@@ -13,14 +13,14 @@
 // limitations under the License.
 //
 ///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_SIGNATURE_RSA_SSA_PSS_VERIFY_KEY_MANAGER_H_
+#define TINK_SIGNATURE_RSA_SSA_PSS_VERIFY_KEY_MANAGER_H_
 
 #include <algorithm>
 #include <vector>
 
-#ifndef THIRD_PARTY_TINK_CC_SIGNATURE_RSA_SSA_PSS_VERIFY_KEY_MANAGER_H_
-#define THIRD_PARTY_TINK_CC_SIGNATURE_RSA_SSA_PSS_VERIFY_KEY_MANAGER_H_
-
 #include "absl/strings/string_view.h"
+#include "tink/core/key_manager_base.h"
 #include "tink/key_manager.h"
 #include "tink/public_key_verify.h"
 #include "tink/util/errors.h"
@@ -33,27 +33,14 @@
 namespace crypto {
 namespace tink {
 
-class RsaSsaPssVerifyKeyManager : public KeyManager<PublicKeyVerify> {
+class RsaSsaPssVerifyKeyManager
+    : public KeyManagerBase<PublicKeyVerify,
+                            google::crypto::tink::RsaSsaPssPublicKey> {
  public:
-  static constexpr char kKeyType[] =
-      "type.googleapis.com/google.crypto.tink.RsaSsaPssPublicKey";
   static constexpr uint32_t kVersion = 0;
 
   RsaSsaPssVerifyKeyManager();
 
-  // Constructs an instance of RsaSsaPss PublicKeyVerify
-  // for the given 'key_data', which must contain RsaSsaPssPublicKey-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<PublicKeyVerify>> GetPrimitive(
-      const google::crypto::tink::KeyData& key_data) const override;
-
-  // Constructs an instance of RsaSsaPss PublicKeyVerify
-  // for the given 'key', which must be RsaSsaPssPublicKey-proto.
-  crypto::tink::util::StatusOr<std::unique_ptr<PublicKeyVerify>> GetPrimitive(
-      const portable_proto::MessageLite& key) const override;
-
-  // Returns the type_url identifying the key type handled by this manager.
-  const std::string& get_key_type() const override;
-
   // Returns the version of this key manager.
   uint32_t get_version() const override;
 
@@ -63,34 +50,25 @@
 
   virtual ~RsaSsaPssVerifyKeyManager() {}
 
- private:
-  // To reach 128-bit security strength, RSA's modulus must be at least 3072-bit
-  // while 2048-bit RSA key only has 112-bit security. Nevertheless, a 2048-bit
-  // RSA key is considered safe by NIST until 2030 (see
-  // https://www.keylength.com/en/4/).
-  static const size_t kMinModulusSizeInBits = 2048;
-  static constexpr char kKeyTypePrefix[] = "type.googleapis.com/";
-  static constexpr char kKeyFormatUrl[] =
-      "type.googleapis.com/google.crypto.tink.RsaSsaPssKeyFormat";
-
-  std::string key_type_;
-  std::unique_ptr<KeyFactory> key_factory_;
-
-  // Constructs an instance of RsaSsaPss PublicKeyVerify
-  // for the given 'key'.
+ protected:
   crypto::tink::util::StatusOr<std::unique_ptr<PublicKeyVerify>>
-  GetPrimitiveImpl(const google::crypto::tink::RsaSsaPssPublicKey&
-                       rsa_ssa_pss_public_key) const;
+  GetPrimitiveFromKey(const google::crypto::tink::RsaSsaPssPublicKey&
+                          rsa_ssa_pss_public_key) const override;
+
+ private:
+  // Friends that re-use proto validation helpers.
+  friend class RsaSsaPssPrivateKeyFactory;
+  friend class RsaSsaPssSignKeyManager;
+
+  std::unique_ptr<KeyFactory> key_factory_;
 
   static crypto::tink::util::Status Validate(
       const google::crypto::tink::RsaSsaPssParams& params);
   static crypto::tink::util::Status Validate(
       const google::crypto::tink::RsaSsaPssPublicKey& key);
-  static crypto::tink::util::Status Validate(
-      const google::crypto::tink::RsaSsaPssKeyFormat& key_format);
 };
 
 }  // namespace tink
 }  // namespace crypto
 
-#endif  // THIRD_PARTY_TINK_CC_SIGNATURE_RSA_SSA_PSS_VERIFY_KEY_MANAGER_H_
+#endif  // TINK_SIGNATURE_RSA_SSA_PSS_VERIFY_KEY_MANAGER_H_
diff --git a/cc/signature/rsa_ssa_pss_verify_key_manager_test.cc b/cc/signature/rsa_ssa_pss_verify_key_manager_test.cc
index d7b2116..674ebe2 100644
--- a/cc/signature/rsa_ssa_pss_verify_key_manager_test.cc
+++ b/cc/signature/rsa_ssa_pss_verify_key_manager_test.cc
@@ -246,8 +246,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/signature/sig_util.cc b/cc/signature/sig_util.cc
new file mode 100644
index 0000000..fb695d7
--- /dev/null
+++ b/cc/signature/sig_util.cc
@@ -0,0 +1,31 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/signature/sig_util.h"
+
+namespace crypto {
+namespace tink {
+
+crypto::tink::util::Status SignAndVerify(const PublicKeySign* signer,
+                                         const PublicKeyVerify* verifier) {
+  static constexpr char kTestMessage[] = "Wycheproof and Tink.";
+  auto sign_result = signer->Sign(kTestMessage);
+  if (!sign_result.ok()) return sign_result.status();
+  return verifier->Verify(sign_result.ValueOrDie(), kTestMessage);
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/signature/sig_util.h b/cc/signature/sig_util.h
new file mode 100644
index 0000000..3eb11ee
--- /dev/null
+++ b/cc/signature/sig_util.h
@@ -0,0 +1,32 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_SIGNATURE_SIG_UTIL_H_
+#define TINK_SIGNATURE_SIG_UTIL_H_
+
+#include "tink/public_key_sign.h"
+#include "tink/public_key_verify.h"
+#include "tink/util/status.h"
+
+namespace crypto {
+namespace tink {
+
+crypto::tink::util::Status SignAndVerify(const PublicKeySign* signer,
+                                         const PublicKeyVerify* verifier);
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_SIGNATURE_SIG_UTIL_H_
diff --git a/cc/signature/signature_config.cc b/cc/signature/signature_config.cc
index 9290581..445f4dc 100644
--- a/cc/signature/signature_config.cc
+++ b/cc/signature/signature_config.cc
@@ -16,6 +16,7 @@
 
 #include "tink/signature/signature_config.h"
 
+#include "absl/memory/memory.h"
 #include "tink/config.h"
 #include "tink/registry.h"
 #include "tink/signature/public_key_sign_catalogue.h"
@@ -31,14 +32,45 @@
 google::crypto::tink::RegistryConfig* GenerateRegistryConfig() {
   google::crypto::tink::RegistryConfig* config =
       new google::crypto::tink::RegistryConfig();
-  config->add_entry()->MergeFrom(*Config::GetTinkKeyTypeEntry(
-      SignatureConfig::kPublicKeySignCatalogueName,
-      SignatureConfig::kPublicKeySignPrimitiveName,
-      "EcdsaPrivateKey", 0, true));
+  // ECDSA
+  config->add_entry()->MergeFrom(
+      *Config::GetTinkKeyTypeEntry(SignatureConfig::kPublicKeySignCatalogueName,
+                                   SignatureConfig::kPublicKeySignPrimitiveName,
+                                   "EcdsaPrivateKey", 0, true));
   config->add_entry()->MergeFrom(*Config::GetTinkKeyTypeEntry(
       SignatureConfig::kPublicKeyVerifyCatalogueName,
-      SignatureConfig::kPublicKeyVerifyPrimitiveName,
-      "EcdsaPublicKey", 0, true));
+      SignatureConfig::kPublicKeyVerifyPrimitiveName, "EcdsaPublicKey", 0,
+      true));
+
+  // ED25519
+  config->add_entry()->MergeFrom(
+      *Config::GetTinkKeyTypeEntry(SignatureConfig::kPublicKeySignCatalogueName,
+                                   SignatureConfig::kPublicKeySignPrimitiveName,
+                                   "Ed25519PrivateKey", 0, true));
+  config->add_entry()->MergeFrom(*Config::GetTinkKeyTypeEntry(
+      SignatureConfig::kPublicKeyVerifyCatalogueName,
+      SignatureConfig::kPublicKeyVerifyPrimitiveName, "Ed25519PublicKey", 0,
+      true));
+
+  // RSA SSA PSS
+  config->add_entry()->MergeFrom(
+      *Config::GetTinkKeyTypeEntry(SignatureConfig::kPublicKeySignCatalogueName,
+                                   SignatureConfig::kPublicKeySignPrimitiveName,
+                                   "RsaSsaPssPrivateKey", 0, true));
+  config->add_entry()->MergeFrom(*Config::GetTinkKeyTypeEntry(
+      SignatureConfig::kPublicKeyVerifyCatalogueName,
+      SignatureConfig::kPublicKeyVerifyPrimitiveName, "RsaSsaPssPublicKey", 0,
+      true));
+
+  // RSA SSA PKCS1
+  config->add_entry()->MergeFrom(
+      *Config::GetTinkKeyTypeEntry(SignatureConfig::kPublicKeySignCatalogueName,
+                                   SignatureConfig::kPublicKeySignPrimitiveName,
+                                   "RsaSsaPkcs1PrivateKey", 0, true));
+  config->add_entry()->MergeFrom(*Config::GetTinkKeyTypeEntry(
+      SignatureConfig::kPublicKeyVerifyCatalogueName,
+      SignatureConfig::kPublicKeyVerifyPrimitiveName, "RsaSsaPkcs1PublicKey", 0,
+      true));
   config->set_config_name("TINK_SIGNATURE");
   return config;
 }
@@ -59,10 +91,11 @@
 // static
 util::Status SignatureConfig::Register() {
   auto status = Registry::AddCatalogue(
-      kPublicKeySignCatalogueName, new PublicKeySignCatalogue());
+      kPublicKeySignCatalogueName, absl::make_unique<PublicKeySignCatalogue>());
   if (!status.ok()) return status;
-  status = Registry::AddCatalogue(
-      kPublicKeyVerifyCatalogueName, new PublicKeyVerifyCatalogue());
+  status =
+      Registry::AddCatalogue(kPublicKeyVerifyCatalogueName,
+                             absl::make_unique<PublicKeyVerifyCatalogue>());
   if (!status.ok()) return status;
   return Config::Register(Latest());
 }
diff --git a/cc/signature/signature_config_test.cc b/cc/signature/signature_config_test.cc
index 63d8fc3..435ca7e 100644
--- a/cc/signature/signature_config_test.cc
+++ b/cc/signature/signature_config_test.cc
@@ -16,25 +16,32 @@
 
 #include "tink/signature/signature_config.h"
 
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
 #include "tink/catalogue.h"
 #include "tink/config.h"
+#include "tink/keyset_handle.h"
 #include "tink/public_key_sign.h"
 #include "tink/public_key_verify.h"
 #include "tink/registry.h"
+#include "tink/signature/signature_key_templates.h"
 #include "tink/util/status.h"
-#include "gtest/gtest.h"
+#include "tink/util/test_util.h"
+#include "absl/memory/memory.h"
 
 namespace crypto {
 namespace tink {
 namespace {
 
+using ::crypto::tink::test::DummyPublicKeySign;
+using ::crypto::tink::test::DummyPublicKeyVerify;
+
 class DummySignCatalogue : public Catalogue<PublicKeySign> {
  public:
   DummySignCatalogue() {}
 
   crypto::tink::util::StatusOr<std::unique_ptr<KeyManager<PublicKeySign>>>
-  GetKeyManager(const std::string& type_url,
-                const std::string& primitive_name,
+  GetKeyManager(const std::string& type_url, const std::string& primitive_name,
                 uint32_t min_version) const override {
     return util::Status::UNKNOWN;
   }
@@ -42,50 +49,88 @@
 
 class SignatureConfigTest : public ::testing::Test {
  protected:
-  void SetUp() override {
-    Registry::Reset();
-  }
+  void SetUp() override { Registry::Reset(); }
 };
 
 TEST_F(SignatureConfigTest, testBasic) {
-  std::string sign_key_type =
-      "type.googleapis.com/google.crypto.tink.EcdsaPrivateKey";
-  std::string verify_key_type =
-      "type.googleapis.com/google.crypto.tink.EcdsaPublicKey";
+  std::vector<std::string> sign_key_types;
+  sign_key_types.push_back(
+      "type.googleapis.com/google.crypto.tink.EcdsaPrivateKey");
+  sign_key_types.push_back(
+      "type.googleapis.com/google.crypto.tink.Ed25519PrivateKey");
+  sign_key_types.push_back(
+      "type.googleapis.com/google.crypto.tink.RsaSsaPssPrivateKey");
+  sign_key_types.push_back(
+      "type.googleapis.com/google.crypto.tink.RsaSsaPkcs1PrivateKey");
+
+  std::vector<std::string> verify_key_types;
+  verify_key_types.push_back(
+      "type.googleapis.com/google.crypto.tink.EcdsaPublicKey");
+  verify_key_types.push_back(
+      "type.googleapis.com/google.crypto.tink.Ed25519PublicKey");
+  verify_key_types.push_back(
+      "type.googleapis.com/google.crypto.tink.RsaSsaPssPublicKey");
+  verify_key_types.push_back(
+      "type.googleapis.com/google.crypto.tink.RsaSsaPkcs1PublicKey");
+
+  const size_t total_key_types =
+      sign_key_types.size() + verify_key_types.size();
+
   auto& config = SignatureConfig::Latest();
 
-  EXPECT_EQ(2, SignatureConfig::Latest().entry_size());
+  EXPECT_EQ(total_key_types, SignatureConfig::Latest().entry_size());
+  EXPECT_EQ(sign_key_types.size(), verify_key_types.size());
 
-  EXPECT_EQ("TinkPublicKeySign", config.entry(0).catalogue_name());
-  EXPECT_EQ("PublicKeySign", config.entry(0).primitive_name());
-  EXPECT_EQ(sign_key_type, config.entry(0).type_url());
-  EXPECT_EQ(true, config.entry(0).new_key_allowed());
-  EXPECT_EQ(0, config.entry(0).key_manager_version());
+  for (int i = 0; i < SignatureConfig::Latest().entry_size(); i += 2) {
+    std::string sign_key_type = sign_key_types[i / 2];
+    EXPECT_EQ("TinkPublicKeySign", config.entry(i).catalogue_name());
+    EXPECT_EQ("PublicKeySign", config.entry(i).primitive_name());
+    EXPECT_EQ(sign_key_type, config.entry(i).type_url());
+    EXPECT_EQ(true, config.entry(i).new_key_allowed());
+    EXPECT_EQ(0, config.entry(i).key_manager_version());
 
-  EXPECT_EQ("TinkPublicKeyVerify", config.entry(1).catalogue_name());
-  EXPECT_EQ("PublicKeyVerify", config.entry(1).primitive_name());
-  EXPECT_EQ(verify_key_type, config.entry(1).type_url());
-  EXPECT_EQ(true, config.entry(1).new_key_allowed());
-  EXPECT_EQ(0, config.entry(1).key_manager_version());
+    std::string verify_key_type = verify_key_types[i / 2];
+    EXPECT_EQ("TinkPublicKeyVerify", config.entry(i + 1).catalogue_name());
+    EXPECT_EQ("PublicKeyVerify", config.entry(i + 1).primitive_name());
+    EXPECT_EQ(verify_key_type, config.entry(i + 1).type_url());
+    EXPECT_EQ(true, config.entry(i + 1).new_key_allowed());
+    EXPECT_EQ(0, config.entry(i + 1).key_manager_version());
+  }
 
   // No key manager before registration.
-  auto sign_manager_result =
-      Registry::get_key_manager<PublicKeySign>(sign_key_type);
-  EXPECT_FALSE(sign_manager_result.ok());
-  EXPECT_EQ(util::error::NOT_FOUND, sign_manager_result.status().error_code());
+  for (const auto& sign_key_type : sign_key_types) {
+    auto sign_manager_result =
+        Registry::get_key_manager<PublicKeySign>(sign_key_type);
+    EXPECT_FALSE(sign_manager_result.ok());
+    EXPECT_EQ(util::error::NOT_FOUND,
+              sign_manager_result.status().error_code());
+  }
+  for (const auto& verify_key_type : verify_key_types) {
+    auto verify_manager_result =
+        Registry::get_key_manager<PublicKeyVerify>(verify_key_type);
+    EXPECT_FALSE(verify_manager_result.ok());
+    EXPECT_EQ(util::error::NOT_FOUND,
+              verify_manager_result.status().error_code());
+  }
 
   // Registration of standard key types works.
   auto status = SignatureConfig::Register();
   EXPECT_TRUE(status.ok()) << status;
 
-  sign_manager_result = Registry::get_key_manager<PublicKeySign>(sign_key_type);
-  EXPECT_TRUE(sign_manager_result.ok()) << sign_manager_result.status();
-  EXPECT_TRUE(sign_manager_result.ValueOrDie()->DoesSupport(sign_key_type));
+  for (const auto& sign_key_type : sign_key_types) {
+    auto sign_manager_result =
+        Registry::get_key_manager<PublicKeySign>(sign_key_type);
+    EXPECT_TRUE(sign_manager_result.ok()) << sign_manager_result.status();
+    EXPECT_TRUE(sign_manager_result.ValueOrDie()->DoesSupport(sign_key_type));
+  }
 
-  auto verify_manager_result =
-      Registry::get_key_manager<PublicKeyVerify>(verify_key_type);
-  EXPECT_TRUE(verify_manager_result.ok()) << verify_manager_result.status();
-  EXPECT_TRUE(verify_manager_result.ValueOrDie()->DoesSupport(verify_key_type));
+  for (const auto& verify_key_type : verify_key_types) {
+    auto verify_manager_result =
+        Registry::get_key_manager<PublicKeyVerify>(verify_key_type);
+    EXPECT_TRUE(verify_manager_result.ok()) << verify_manager_result.status();
+    EXPECT_TRUE(
+        verify_manager_result.ValueOrDie()->DoesSupport(verify_key_type));
+  }
 }
 
 TEST_F(SignatureConfigTest, testRegister) {
@@ -111,19 +156,67 @@
   // Reset the registry, and try overriding a catalogue with a different one.
   Registry::Reset();
   status = Registry::AddCatalogue("TinkPublicKeySign",
-                                  new DummySignCatalogue());
+                                  absl::make_unique<DummySignCatalogue>());
   EXPECT_TRUE(status.ok()) << status;
   status = SignatureConfig::Register();
   EXPECT_FALSE(status.ok());
   EXPECT_EQ(util::error::ALREADY_EXISTS, status.error_code());
 }
 
+// Tests that the PublicKeySignWrapper has been properly registered and we
+// can wrap primitives.
+TEST_F(SignatureConfigTest, PublicKeySignWrapperRegistered) {
+  ASSERT_TRUE(SignatureConfig::Register().ok());
+
+  google::crypto::tink::Keyset::Key key;
+  key.set_status(google::crypto::tink::KeyStatusType::ENABLED);
+  key.set_key_id(1234);
+  key.set_output_prefix_type(google::crypto::tink::OutputPrefixType::TINK);
+  auto primitive_set = absl::make_unique<PrimitiveSet<PublicKeySign>>();
+  primitive_set->set_primary(
+      primitive_set
+          ->AddPrimitive(absl::make_unique<DummyPublicKeySign>("dummy"), key)
+          .ValueOrDie());
+
+  auto wrapped = Registry::Wrap(std::move(primitive_set));
+
+  ASSERT_TRUE(wrapped.ok()) << wrapped.status();
+  auto signature_result = wrapped.ValueOrDie()->Sign("message");
+  ASSERT_TRUE(signature_result.ok());
+
+  std::string prefix = CryptoFormat::get_output_prefix(key).ValueOrDie();
+  EXPECT_EQ(
+      signature_result.ValueOrDie(),
+      absl::StrCat(prefix,
+                   DummyPublicKeySign("dummy").Sign("message").ValueOrDie()));
+}
+
+
+// Tests that the PublicKeyVerifyWrapper has been properly registered and we
+// can wrap primitives.
+TEST_F(SignatureConfigTest, PublicKeyVerifyWrapperRegistered) {
+  ASSERT_TRUE(SignatureConfig::Register().ok());
+
+  google::crypto::tink::Keyset::Key key;
+  key.set_status(google::crypto::tink::KeyStatusType::ENABLED);
+  key.set_key_id(1234);
+  key.set_output_prefix_type(google::crypto::tink::OutputPrefixType::TINK);
+  auto primitive_set = absl::make_unique<PrimitiveSet<PublicKeyVerify>>();
+  primitive_set->set_primary(
+      primitive_set
+          ->AddPrimitive(absl::make_unique<DummyPublicKeyVerify>("dummy"), key)
+          .ValueOrDie());
+  std::string prefix = CryptoFormat::get_output_prefix(key).ValueOrDie();
+  std::string signature = DummyPublicKeySign("dummy").Sign("message").ValueOrDie();
+
+  auto wrapped = Registry::Wrap(std::move(primitive_set));
+
+  ASSERT_TRUE(wrapped.ok()) << wrapped.status();
+  ASSERT_TRUE(wrapped.ValueOrDie()
+                  ->Verify(absl::StrCat(prefix, signature), "message")
+                  .ok());
+}
+
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/signature/signature_key_templates.cc b/cc/signature/signature_key_templates.cc
index f8994e4..98f34f3 100644
--- a/cc/signature/signature_key_templates.cc
+++ b/cc/signature/signature_key_templates.cc
@@ -16,8 +16,17 @@
 
 #include "tink/signature/signature_key_templates.h"
 
-#include "proto/ecdsa.pb.h"
+#include "absl/memory/memory.h"
+#include "absl/strings/str_cat.h"
+#include "openssl/bn.h"
+#include "openssl/rsa.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/util/constants.h"
 #include "proto/common.pb.h"
+#include "proto/ecdsa.pb.h"
+#include "proto/ed25519.pb.h"
+#include "proto/rsa_ssa_pkcs1.pb.h"
+#include "proto/rsa_ssa_pss.pb.h"
 #include "proto/tink.pb.h"
 
 namespace crypto {
@@ -25,18 +34,24 @@
 namespace {
 
 using google::crypto::tink::EcdsaKeyFormat;
+using google::crypto::tink::EcdsaPrivateKey;
 using google::crypto::tink::EcdsaSignatureEncoding;
+using google::crypto::tink::Ed25519PrivateKey;
 using google::crypto::tink::EllipticCurveType;
 using google::crypto::tink::HashType;
 using google::crypto::tink::KeyTemplate;
 using google::crypto::tink::OutputPrefixType;
+using google::crypto::tink::RsaSsaPkcs1KeyFormat;
+using google::crypto::tink::RsaSsaPkcs1PrivateKey;
+using google::crypto::tink::RsaSsaPssKeyFormat;
+using google::crypto::tink::RsaSsaPssPrivateKey;
 
-KeyTemplate* NewEcdsaKeyTemplate(HashType hash_type,
-                                 EllipticCurveType curve_type,
-                                 EcdsaSignatureEncoding encoding) {
-  KeyTemplate* key_template = new KeyTemplate;
+std::unique_ptr<KeyTemplate> NewEcdsaKeyTemplate(
+    HashType hash_type, EllipticCurveType curve_type,
+    EcdsaSignatureEncoding encoding) {
+  auto key_template = absl::make_unique<KeyTemplate>();
   key_template->set_type_url(
-      "type.googleapis.com/google.crypto.tink.EcdsaPrivateKey");
+      absl::StrCat(kTypeGoogleapisCom, EcdsaPrivateKey().GetTypeName()));
   key_template->set_output_prefix_type(OutputPrefixType::TINK);
   EcdsaKeyFormat key_format;
   auto params = key_format.mutable_params();
@@ -47,32 +62,76 @@
   return key_template;
 }
 
+std::unique_ptr<KeyTemplate> NewRsaSsaPkcs1KeyTemplate(HashType hash_type,
+                                                       int modulus_size_in_bits,
+                                                       int public_exponent) {
+  auto key_template = absl::make_unique<KeyTemplate>();
+  key_template->set_type_url(
+      absl::StrCat(kTypeGoogleapisCom, RsaSsaPkcs1PrivateKey().GetTypeName()));
+  key_template->set_output_prefix_type(OutputPrefixType::TINK);
+  RsaSsaPkcs1KeyFormat key_format;
+  auto params = key_format.mutable_params();
+  params->set_hash_type(hash_type);
+  key_format.set_modulus_size_in_bits(modulus_size_in_bits);
+  bssl::UniquePtr<BIGNUM> e(BN_new());
+  BN_set_word(e.get(), public_exponent);
+  key_format.set_public_exponent(
+      subtle::SubtleUtilBoringSSL::bn2str(e.get(), BN_num_bytes(e.get()))
+          .ValueOrDie());
+  key_format.SerializeToString(key_template->mutable_value());
+  return key_template;
+}
+
+std::unique_ptr<KeyTemplate> NewRsaSsaPssKeyTemplate(HashType sig_hash,
+                                                     HashType mgf1_hash,
+                                                     int salt_length,
+                                                     int modulus_size_in_bits,
+                                                     int public_exponent) {
+  auto key_template = absl::make_unique<KeyTemplate>();
+  key_template->set_type_url(
+      absl::StrCat(kTypeGoogleapisCom, RsaSsaPssPrivateKey().GetTypeName()));
+  key_template->set_output_prefix_type(OutputPrefixType::TINK);
+  RsaSsaPssKeyFormat key_format;
+  auto params = key_format.mutable_params();
+  params->set_sig_hash(sig_hash);
+  params->set_mgf1_hash(mgf1_hash);
+  params->set_salt_length(salt_length);
+  key_format.set_modulus_size_in_bits(modulus_size_in_bits);
+  bssl::UniquePtr<BIGNUM> e(BN_new());
+  BN_set_word(e.get(), public_exponent);
+  key_format.set_public_exponent(
+      subtle::SubtleUtilBoringSSL::bn2str(e.get(), BN_num_bytes(e.get()))
+          .ValueOrDie());
+  key_format.SerializeToString(key_template->mutable_value());
+  return key_template;
+}
+
 }  // anonymous namespace
 
 // static
 const KeyTemplate& SignatureKeyTemplates::EcdsaP256() {
-  static const KeyTemplate* key_template = NewEcdsaKeyTemplate(
-      HashType::SHA256,
-      EllipticCurveType::NIST_P256,
-      EcdsaSignatureEncoding::DER);
+  static const KeyTemplate* key_template =
+      NewEcdsaKeyTemplate(HashType::SHA256, EllipticCurveType::NIST_P256,
+                          EcdsaSignatureEncoding::DER)
+          .release();
   return *key_template;
 }
 
 // static
 const KeyTemplate& SignatureKeyTemplates::EcdsaP384() {
-  static const KeyTemplate* key_template = NewEcdsaKeyTemplate(
-      HashType::SHA512,
-      EllipticCurveType::NIST_P384,
-      EcdsaSignatureEncoding::DER);
+  static const KeyTemplate* key_template =
+      NewEcdsaKeyTemplate(HashType::SHA512, EllipticCurveType::NIST_P384,
+                          EcdsaSignatureEncoding::DER)
+          .release();
   return *key_template;
 }
 
 // static
 const KeyTemplate& SignatureKeyTemplates::EcdsaP521() {
-  static const KeyTemplate* key_template = NewEcdsaKeyTemplate(
-      HashType::SHA512,
-      EllipticCurveType::NIST_P521,
-      EcdsaSignatureEncoding::DER);
+  static const KeyTemplate* key_template =
+      NewEcdsaKeyTemplate(HashType::SHA512, EllipticCurveType::NIST_P521,
+                          EcdsaSignatureEncoding::DER)
+          .release();
   return *key_template;
 }
 
@@ -80,7 +139,8 @@
 const KeyTemplate& SignatureKeyTemplates::EcdsaP256Ieee() {
   static const KeyTemplate* key_template =
       NewEcdsaKeyTemplate(HashType::SHA256, EllipticCurveType::NIST_P256,
-                          EcdsaSignatureEncoding::IEEE_P1363);
+                          EcdsaSignatureEncoding::IEEE_P1363)
+          .release();
   return *key_template;
 }
 
@@ -88,7 +148,8 @@
 const KeyTemplate& SignatureKeyTemplates::EcdsaP384Ieee() {
   static const KeyTemplate* key_template =
       NewEcdsaKeyTemplate(HashType::SHA512, EllipticCurveType::NIST_P384,
-                          EcdsaSignatureEncoding::IEEE_P1363);
+                          EcdsaSignatureEncoding::IEEE_P1363)
+          .release();
   return *key_template;
 }
 
@@ -96,7 +157,49 @@
 const KeyTemplate& SignatureKeyTemplates::EcdsaP521Ieee() {
   static const KeyTemplate* key_template =
       NewEcdsaKeyTemplate(HashType::SHA512, EllipticCurveType::NIST_P521,
-                          EcdsaSignatureEncoding::IEEE_P1363);
+                          EcdsaSignatureEncoding::IEEE_P1363)
+          .release();
+  return *key_template;
+}
+
+// static
+const KeyTemplate& SignatureKeyTemplates::RsaSsaPkcs13072Sha256F4() {
+  static const KeyTemplate* key_template =
+      NewRsaSsaPkcs1KeyTemplate(HashType::SHA256, 3072, RSA_F4).release();
+  return *key_template;
+}
+
+// static
+const KeyTemplate& SignatureKeyTemplates::RsaSsaPkcs14096Sha512F4() {
+  static const KeyTemplate* key_template =
+      NewRsaSsaPkcs1KeyTemplate(HashType::SHA512, 4096, RSA_F4).release();
+  return *key_template;
+}
+
+// static
+const KeyTemplate& SignatureKeyTemplates::RsaSsaPss3072Sha256Sha256F4() {
+  static const KeyTemplate* key_template =
+      NewRsaSsaPssKeyTemplate(HashType::SHA256, HashType::SHA256, 32, 3072,
+                              RSA_F4)
+          .release();
+  return *key_template;
+}
+
+// static
+const KeyTemplate& SignatureKeyTemplates::RsaSsaPss4096Sha512Sha512F4() {
+  static const KeyTemplate* key_template =
+      NewRsaSsaPssKeyTemplate(HashType::SHA512, HashType::SHA512, 64, 4096,
+                              RSA_F4)
+          .release();
+  return *key_template;
+}
+
+// static
+const google::crypto::tink::KeyTemplate& SignatureKeyTemplates::Ed25519() {
+  static KeyTemplate* key_template = new KeyTemplate();
+  key_template->set_type_url(
+      absl::StrCat(kTypeGoogleapisCom, Ed25519PrivateKey().GetTypeName()));
+  key_template->set_output_prefix_type(OutputPrefixType::TINK);
   return *key_template;
 }
 
diff --git a/cc/signature/signature_key_templates.h b/cc/signature/signature_key_templates.h
index adf9977..78e63cc 100644
--- a/cc/signature/signature_key_templates.h
+++ b/cc/signature/signature_key_templates.h
@@ -82,6 +82,45 @@
   //   - signature encoding: IEEE_P1363
   //   - OutputPrefixType: TINK
   static const google::crypto::tink::KeyTemplate& EcdsaP521Ieee();
+
+  // Returns a KeyTemplate that generates new instances of RsaSsaPkcs1PrivateKey
+  // with the following parameters:
+  //   - Modulus size in bits: 3072.
+  //   - Hash function: SHA256.
+  //   - Public Exponent: 65537 (aka F4).
+  //   - OutputPrefixType: TINK
+  static const google::crypto::tink::KeyTemplate& RsaSsaPkcs13072Sha256F4();
+
+  // Returns a KeyTemplate that generates new instances of RsaSsaPkcs1PrivateKey
+  // with the following parameters:
+  //   - Modulus size in bits: 4096.
+  //   - Hash function: SHA512.
+  //   - Public Exponent: 65537 (aka F4).
+  //   - OutputPrefixType: TINK
+  static const google::crypto::tink::KeyTemplate& RsaSsaPkcs14096Sha512F4();
+
+  // Returns a KeyTemplate that generates new instances of RsaSsaPssPrivateKey
+  // with the following parameters:
+  //   - Modulus size in bits: 3072.
+  //   - Signature hash: SHA256.
+  //   - MGF1 hash: SHA256.
+  //   - Salt length: 32 (i.e., SHA256's output length).
+  //   - Public Exponent: 65537 (aka F4).
+  //   - OutputPrefixType: TINK
+  static const google::crypto::tink::KeyTemplate& RsaSsaPss3072Sha256Sha256F4();
+
+  // Returns a KeyTemplate that generates new instances of RsaSsaPssPrivateKey
+  // with the following parameters:
+  //   - Modulus size in bits: 4096.
+  //   - Signature hash: SHA512.
+  //   - MGF1 hash: SHA512.
+  //   - Salt length: 64 (i.e., SHA512's output length).
+  //   - Public Exponent: 65537 (aka F4).
+  //   - OutputPrefixType: TINK
+  static const google::crypto::tink::KeyTemplate& RsaSsaPss4096Sha512Sha512F4();
+
+  // Returns a KeyTemplate that generates new instances of Ed25519PrivateKey.
+  static const google::crypto::tink::KeyTemplate& Ed25519();
 };
 
 }  // namespace tink
diff --git a/cc/signature/signature_key_templates_test.cc b/cc/signature/signature_key_templates_test.cc
index caf3cde..8e4070f 100644
--- a/cc/signature/signature_key_templates_test.cc
+++ b/cc/signature/signature_key_templates_test.cc
@@ -16,11 +16,21 @@
 
 #include "tink/signature/signature_key_templates.h"
 
+#include "gtest/gtest.h"
+#include "openssl/bn.h"
+#include "openssl/rsa.h"
 #include "tink/signature/ecdsa_sign_key_manager.h"
+#include "tink/signature/ed25519_sign_key_manager.h"
+#include "tink/signature/rsa_ssa_pkcs1_sign_key_manager.h"
+#include "tink/signature/rsa_ssa_pss_sign_key_manager.h"
+#include "tink/subtle/subtle_util_boringssl.h"
 #include "proto/common.pb.h"
 #include "proto/ecdsa.pb.h"
+#include "proto/empty.pb.h"
+#include "proto/rsa_ssa_pkcs1.pb.h"
+#include "proto/rsa_ssa_pss.pb.h"
+
 #include "proto/tink.pb.h"
-#include "gtest/gtest.h"
 
 namespace crypto {
 namespace tink {
@@ -29,11 +39,14 @@
 using google::crypto::tink::EcdsaKeyFormat;
 using google::crypto::tink::EcdsaSignatureEncoding;
 using google::crypto::tink::EllipticCurveType;
+using google::crypto::tink::Empty;
 using google::crypto::tink::HashType;
 using google::crypto::tink::KeyTemplate;
 using google::crypto::tink::OutputPrefixType;
+using google::crypto::tink::RsaSsaPkcs1KeyFormat;
+using google::crypto::tink::RsaSsaPssKeyFormat;
 
-TEST(SignatureKeyTemplatesTest, testKeyTemplatesWithDerEncoding) {
+TEST(SignatureKeyTemplatesTest, KeyTemplatesWithDerEncoding) {
   std::string type_url = "type.googleapis.com/google.crypto.tink.EcdsaPrivateKey";
 
   {  // Test EcdsaP256().
@@ -103,7 +116,7 @@
   }
 }
 
-TEST(SignatureKeyTemplatesTest, testKeyTemplatesWithIeeeEncoding) {
+TEST(SignatureKeyTemplatesTest, KeyTemplatesWithIeeeEncoding) {
   std::string type_url = "type.googleapis.com/google.crypto.tink.EcdsaPrivateKey";
 
   {  // Test EcdsaP256Ieee().
@@ -176,11 +189,155 @@
   }
 }
 
+TEST(SignatureKeyTemplatesTest, KeyTemplatesWithRsaSsaPkcs13072Sha256F4) {
+  std::string type_url =
+      "type.googleapis.com/google.crypto.tink.RsaSsaPkcs1PrivateKey";
+
+  const KeyTemplate& key_template =
+      SignatureKeyTemplates::RsaSsaPkcs13072Sha256F4();
+  EXPECT_EQ(type_url, key_template.type_url());
+  EXPECT_EQ(OutputPrefixType::TINK, key_template.output_prefix_type());
+  RsaSsaPkcs1KeyFormat key_format;
+  EXPECT_TRUE(key_format.ParseFromString(key_template.value()));
+  EXPECT_EQ(HashType::SHA256, key_format.params().hash_type());
+  EXPECT_GE(key_format.modulus_size_in_bits(), 3072);
+  bssl::UniquePtr<BIGNUM> e(BN_new());
+  BN_set_word(e.get(), RSA_F4);
+  EXPECT_EQ(
+      BN_cmp(subtle::SubtleUtilBoringSSL::str2bn(key_format.public_exponent())
+                 .ValueOrDie()
+                 .get(),
+             e.get()),
+      0);
+  // Check that reference to the same object is returned.
+  const KeyTemplate& key_template_2 =
+      SignatureKeyTemplates::RsaSsaPkcs13072Sha256F4();
+  EXPECT_EQ(&key_template, &key_template_2);
+
+  // Check that the key manager works with the template.
+  RsaSsaPkcs1SignKeyManager key_manager;
+  EXPECT_EQ(key_manager.get_key_type(), key_template.type_url());
+  auto new_key_result = key_manager.get_key_factory().NewKey(key_format);
+  EXPECT_TRUE(new_key_result.ok()) << new_key_result.status();
+}
+
+TEST(SignatureKeyTemplatesTest, KeyTemplatesWithRsaSsaPkcs14096Sha512F4) {
+  std::string type_url =
+      "type.googleapis.com/google.crypto.tink.RsaSsaPkcs1PrivateKey";
+  const KeyTemplate& key_template =
+      SignatureKeyTemplates::RsaSsaPkcs14096Sha512F4();
+  EXPECT_EQ(type_url, key_template.type_url());
+  EXPECT_EQ(OutputPrefixType::TINK, key_template.output_prefix_type());
+  RsaSsaPkcs1KeyFormat key_format;
+  EXPECT_TRUE(key_format.ParseFromString(key_template.value()));
+  EXPECT_EQ(HashType::SHA512, key_format.params().hash_type());
+  EXPECT_GE(key_format.modulus_size_in_bits(), 4096);
+  bssl::UniquePtr<BIGNUM> e(BN_new());
+  BN_set_word(e.get(), RSA_F4);
+  EXPECT_EQ(
+      BN_cmp(subtle::SubtleUtilBoringSSL::str2bn(key_format.public_exponent())
+                 .ValueOrDie()
+                 .get(),
+             e.get()),
+      0);
+  // Check that reference to the same object is returned.
+  const KeyTemplate& key_template_2 =
+      SignatureKeyTemplates::RsaSsaPkcs14096Sha512F4();
+  EXPECT_EQ(&key_template, &key_template_2);
+
+  // Check that the key manager works with the template.
+  RsaSsaPkcs1SignKeyManager key_manager;
+  EXPECT_EQ(key_manager.get_key_type(), key_template.type_url());
+  auto new_key_result = key_manager.get_key_factory().NewKey(key_format);
+  EXPECT_TRUE(new_key_result.ok()) << new_key_result.status();
+}
+
+TEST(SignatureKeyTemplatesTest, KeyTemplatesWithRsaSsaPss3072Sha256Sha256F4) {
+  std::string type_url =
+      "type.googleapis.com/google.crypto.tink.RsaSsaPssPrivateKey";
+  const KeyTemplate& key_template =
+      SignatureKeyTemplates::RsaSsaPss3072Sha256Sha256F4();
+  EXPECT_EQ(type_url, key_template.type_url());
+  EXPECT_EQ(OutputPrefixType::TINK, key_template.output_prefix_type());
+  RsaSsaPssKeyFormat key_format;
+  EXPECT_TRUE(key_format.ParseFromString(key_template.value()));
+  EXPECT_EQ(HashType::SHA256, key_format.params().sig_hash());
+  EXPECT_EQ(HashType::SHA256, key_format.params().mgf1_hash());
+  EXPECT_EQ(32, key_format.params().salt_length());
+  EXPECT_GE(key_format.modulus_size_in_bits(), 3072);
+  bssl::UniquePtr<BIGNUM> e(BN_new());
+  BN_set_word(e.get(), RSA_F4);
+  EXPECT_EQ(
+      BN_cmp(subtle::SubtleUtilBoringSSL::str2bn(key_format.public_exponent())
+                 .ValueOrDie()
+                 .get(),
+             e.get()),
+      0);
+
+  // Check that reference to the same object is returned.
+  const KeyTemplate& key_template_2 =
+      SignatureKeyTemplates::RsaSsaPss3072Sha256Sha256F4();
+  EXPECT_EQ(&key_template, &key_template_2);
+
+  // Check that the key manager works with the template.
+  RsaSsaPssSignKeyManager key_manager;
+  EXPECT_EQ(key_manager.get_key_type(), key_template.type_url());
+  auto new_key_result = key_manager.get_key_factory().NewKey(key_format);
+  EXPECT_TRUE(new_key_result.ok()) << new_key_result.status();
+}
+
+TEST(SignatureKeyTemplatesTest, KeyTemplatesWithRsaSsaPss4096Sha512Sha512F4) {
+  std::string type_url =
+      "type.googleapis.com/google.crypto.tink.RsaSsaPssPrivateKey";
+  const KeyTemplate& key_template =
+      SignatureKeyTemplates::RsaSsaPss4096Sha512Sha512F4();
+  EXPECT_EQ(type_url, key_template.type_url());
+  EXPECT_EQ(OutputPrefixType::TINK, key_template.output_prefix_type());
+  RsaSsaPssKeyFormat key_format;
+  EXPECT_TRUE(key_format.ParseFromString(key_template.value()));
+  EXPECT_EQ(HashType::SHA512, key_format.params().sig_hash());
+  EXPECT_EQ(HashType::SHA512, key_format.params().mgf1_hash());
+  EXPECT_EQ(64, key_format.params().salt_length());
+  EXPECT_GE(key_format.modulus_size_in_bits(), 4096);
+  bssl::UniquePtr<BIGNUM> e(BN_new());
+  BN_set_word(e.get(), RSA_F4);
+  EXPECT_EQ(
+      BN_cmp(subtle::SubtleUtilBoringSSL::str2bn(key_format.public_exponent())
+                 .ValueOrDie()
+                 .get(),
+             e.get()),
+      0);
+
+  // Check that reference to the same object is returned.
+  const KeyTemplate& key_template_2 =
+      SignatureKeyTemplates::RsaSsaPss4096Sha512Sha512F4();
+  EXPECT_EQ(&key_template, &key_template_2);
+
+  // Check that the key manager works with the template.
+  RsaSsaPssSignKeyManager key_manager;
+  EXPECT_EQ(key_manager.get_key_type(), key_template.type_url());
+  auto new_key_result = key_manager.get_key_factory().NewKey(key_format);
+  EXPECT_TRUE(new_key_result.ok()) << new_key_result.status();
+}
+
+TEST(SignatureKeyTemplatesTest, KeyTemplatesWithEd25519) {
+  std::string type_url = "type.googleapis.com/google.crypto.tink.Ed25519PrivateKey";
+  const KeyTemplate& key_template = SignatureKeyTemplates::Ed25519();
+  EXPECT_EQ(type_url, key_template.type_url());
+  EXPECT_EQ(OutputPrefixType::TINK, key_template.output_prefix_type());
+
+  // Check that reference to the same object is returned.
+  const KeyTemplate& key_template_2 = SignatureKeyTemplates::Ed25519();
+  EXPECT_EQ(&key_template, &key_template_2);
+
+  // Check that the key manager works with the template.
+  Ed25519SignKeyManager key_manager;
+  EXPECT_EQ(key_manager.get_key_type(), key_template.type_url());
+  Empty empty;
+  auto new_key_result = key_manager.get_key_factory().NewKey(empty);
+  EXPECT_TRUE(new_key_result.ok()) << new_key_result.status();
+}
+
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/streaming_aead.h b/cc/streaming_aead.h
new file mode 100644
index 0000000..b51a61f
--- /dev/null
+++ b/cc/streaming_aead.h
@@ -0,0 +1,70 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_STREAMING_AEAD_H_
+#define TINK_STREAMING_AEAD_H_
+
+#include <memory>
+
+#include "absl/strings/string_view.h"
+#include "tink/input_stream.h"
+#include "tink/output_stream.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+
+// An interface for streaming authenticated encryption with associated data.
+// Streaming encryption is typically used for encrypting large plaintexts such
+// as large files.  Tink may eventually contain multiple interfaces for
+// streaming encryption depending on the supported properties. This interface
+// supports a streaming interface for symmetric encryption with
+// authentication. The underlying encryption modes are selected so that partial
+// plaintext can be obtained fast by decrypting and authenticating just a part
+// of the ciphertext.
+class StreamingAead {
+ public:
+  // Returns a wrapper around 'ciphertext_destination', such that any bytes
+  // written via the wrapper are AEAD-encrypted using 'associated_data' as
+  // associated authenticated data. The associated data is not included in the
+  // ciphertext and has to be passed in as parameter for decryption.
+  // ByteCount() of the wrapper returns the number of written plaintext bytes.
+  // Closing the wrapper results in closing of the wrapped stream.
+  virtual crypto::tink::util::StatusOr<
+      std::unique_ptr<crypto::tink::OutputStream>>
+  NewEncryptingStream(
+      std::unique_ptr<crypto::tink::OutputStream> ciphertext_destination,
+      absl::string_view associated_data) = 0;
+
+  // Returns a wrapper around 'ciphertext_source', such that reading via the
+  // via the wrapper leads to AEAD-decryption of the underlying ciphertext,
+  // using 'associated_data' as associated authenticated data, and the
+  // read bytes are bytes of the resulting plaintext.
+  // ByteCount() of the wrapper returns the number of read plaintext bytes.
+  virtual crypto::tink::util::StatusOr<
+      std::unique_ptr<crypto::tink::InputStream>>
+  NewDecryptingStream(
+      std::unique_ptr<crypto::tink::InputStream> ciphertext_source,
+      absl::string_view associated_data) = 0;
+
+  virtual ~StreamingAead() {}
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_STREAMING_AEAD_H_
diff --git a/cc/streamingaead/BUILD.bazel b/cc/streamingaead/BUILD.bazel
new file mode 100644
index 0000000..67ed7a3
--- /dev/null
+++ b/cc/streamingaead/BUILD.bazel
@@ -0,0 +1,47 @@
+package(default_visibility = ["//tools/build_defs:internal_pkg"])
+
+licenses(["notice"])  # Apache 2.0
+
+cc_library(
+    name = "streaming_aead_wrapper",
+    srcs = ["streaming_aead_wrapper.cc"],
+    hdrs = ["streaming_aead_wrapper.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        "//cc:crypto_format",
+        "//cc:input_stream",
+        "//cc:output_stream",
+        "//cc:primitive_set",
+        "//cc:primitive_wrapper",
+        "//cc:registry",
+        "//cc:streaming_aead",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "//proto:tink_cc_proto",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+# tests
+
+cc_test(
+    name = "streaming_aead_wrapper_test",
+    size = "small",
+    srcs = ["streaming_aead_wrapper_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    deps = [
+        ":streaming_aead_wrapper",
+        "//cc:input_stream",
+        "//cc:output_stream",
+        "//cc:streaming_aead",
+        "//cc:primitive_set",
+        "//cc/util:istream_input_stream",
+        "//cc/util:ostream_output_stream",
+        "//cc/util:status",
+        "//cc/util:test_util",
+        "//proto:tink_cc_proto",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
diff --git a/cc/streamingaead/streaming_aead_wrapper.cc b/cc/streamingaead/streaming_aead_wrapper.cc
new file mode 100644
index 0000000..11a524c
--- /dev/null
+++ b/cc/streamingaead/streaming_aead_wrapper.cc
@@ -0,0 +1,96 @@
+// Copyright 2019 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/streamingaead/streaming_aead_wrapper.h"
+
+#include "tink/streaming_aead.h"
+#include "tink/crypto_format.h"
+#include "tink/input_stream.h"
+#include "tink/output_stream.h"
+#include "tink/primitive_set.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+using ::crypto::tink::util::Status;
+using ::crypto::tink::util::StatusOr;
+
+namespace {
+
+Status Validate(PrimitiveSet<StreamingAead>* primitives) {
+  if (primitives == nullptr) {
+    return Status(util::error::INTERNAL, "primitive set must be non-NULL");
+  }
+  if (primitives->get_primary() == nullptr) {
+    return Status(util::error::INVALID_ARGUMENT,
+                        "primitive set has no primary");
+  }
+  return Status::OK;
+}
+
+class StreamingAeadSetWrapper: public StreamingAead {
+ public:
+  explicit StreamingAeadSetWrapper(
+      std::unique_ptr<PrimitiveSet<StreamingAead>> primitives)
+      : primitives_(std::move(primitives)) {}
+
+  crypto::tink::util::StatusOr<std::unique_ptr<crypto::tink::OutputStream>>
+  NewEncryptingStream(
+      std::unique_ptr<crypto::tink::OutputStream> ciphertext_destination,
+      absl::string_view associated_data) override;
+
+  crypto::tink::util::StatusOr<std::unique_ptr<crypto::tink::InputStream>>
+  NewDecryptingStream(
+      std::unique_ptr<crypto::tink::InputStream> ciphertext_source,
+      absl::string_view associated_data) override;
+
+  ~StreamingAeadSetWrapper() override {}
+
+ private:
+  std::unique_ptr<PrimitiveSet<StreamingAead>> primitives_;
+};  // class StreamingAeadSetWrapper
+
+StatusOr<std::unique_ptr<OutputStream>>
+StreamingAeadSetWrapper::NewEncryptingStream(
+    std::unique_ptr<OutputStream> ciphertext_destination,
+    absl::string_view associated_data) {
+  return primitives_->get_primary()->get_primitive().NewEncryptingStream(
+          std::move(ciphertext_destination), associated_data);
+}
+
+StatusOr<std::unique_ptr<InputStream>>
+StreamingAeadSetWrapper::NewDecryptingStream(
+    std::unique_ptr<InputStream> ciphertext_source,
+    absl::string_view associated_data) {
+  return Status(util::error::UNIMPLEMENTED, "Not implemented yet");
+}
+
+}  // anonymous namespace
+
+StatusOr<std::unique_ptr<StreamingAead>> StreamingAeadWrapper::Wrap(
+    std::unique_ptr<PrimitiveSet<StreamingAead>> streaming_aead_set) const {
+  auto status = Validate(streaming_aead_set.get());
+  if (!status.ok()) return status;
+  std::unique_ptr<StreamingAead> streaming_aead =
+      absl::make_unique<StreamingAeadSetWrapper>(
+          std::move(streaming_aead_set));
+  return std::move(streaming_aead);
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/streamingaead/streaming_aead_wrapper.h b/cc/streamingaead/streaming_aead_wrapper.h
new file mode 100644
index 0000000..ac03648
--- /dev/null
+++ b/cc/streamingaead/streaming_aead_wrapper.h
@@ -0,0 +1,50 @@
+// Copyright 2019 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_STREAMINGAEAD_STREAMING_AEAD_WRAPPER_H_
+#define TINK_STREAMINGAEAD_STREAMING_AEAD_WRAPPER_H_
+
+#include "absl/strings/string_view.h"
+#include "tink/streaming_aead.h"
+#include "tink/primitive_set.h"
+#include "tink/primitive_wrapper.h"
+#include "tink/util/statusor.h"
+#include "proto/tink.pb.h"
+
+namespace crypto {
+namespace tink {
+
+// Wraps a set of StreamingAead-instances that correspond to a keyset,
+// and combines them into a single StreamingAead-primitive, that uses
+// the provided instances, depending on the context:
+//   * StreamingAead::NewEncryptingStream(...) uses the primary instance
+//     from the set
+//   * StreamingAead::NewDecryptingStream(...) uses the instance that matches
+//     the ciphertext prefix.
+class StreamingAeadWrapper : public PrimitiveWrapper<StreamingAead> {
+ public:
+  // Returns an StreamingAead-primitive that uses StreamingAead-instances
+  // provided in 'streaming_aead_set', which must be non-NULL and must contain
+  // a primary instance.
+  util::StatusOr<std::unique_ptr<StreamingAead>> Wrap(
+      std::unique_ptr<PrimitiveSet<StreamingAead>> streaming_aead_set)
+      const override;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_STREAMINGAEAD_STREAMING_AEAD_WRAPPER_H_
diff --git a/cc/streamingaead/streaming_aead_wrapper_test.cc b/cc/streamingaead/streaming_aead_wrapper_test.cc
new file mode 100644
index 0000000..ec4591b
--- /dev/null
+++ b/cc/streamingaead/streaming_aead_wrapper_test.cc
@@ -0,0 +1,133 @@
+// Copyright 2019 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/streamingaead/streaming_aead_wrapper.h"
+
+#include <sstream>
+
+#include "gtest/gtest.h"
+#include "tink/primitive_set.h"
+#include "tink/streaming_aead.h"
+#include "tink/util/istream_input_stream.h"
+#include "tink/util/ostream_output_stream.h"
+#include "tink/util/status.h"
+#include "tink/util/test_util.h"
+
+using crypto::tink::test::DummyStreamingAead;
+using google::crypto::tink::Keyset;
+using google::crypto::tink::OutputPrefixType;
+
+namespace crypto {
+namespace tink {
+namespace {
+
+TEST(StreamingAeadSetWrapperTest, WrapNullptr) {
+  StreamingAeadWrapper wrapper;
+  auto result = wrapper.Wrap(nullptr);
+  EXPECT_FALSE(result.ok());
+  EXPECT_EQ(util::error::INTERNAL, result.status().error_code());
+  EXPECT_PRED_FORMAT2(testing::IsSubstring, "non-NULL",
+                      result.status().error_message());
+}
+
+TEST(StreamingAeadSetWrapperTest, WrapEmpty) {
+  StreamingAeadWrapper wrapper;
+  auto result = wrapper.Wrap(absl::make_unique<PrimitiveSet<StreamingAead>>());
+  EXPECT_FALSE(result.ok());
+  EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+  EXPECT_PRED_FORMAT2(testing::IsSubstring, "no primary",
+                      result.status().error_message());
+}
+
+TEST(StreamingAeadSetWrapperTest, Basic) {
+  Keyset::Key* key;
+  Keyset keyset;
+
+  uint32_t key_id_0 = 1234543;
+  key = keyset.add_key();
+  key->set_output_prefix_type(OutputPrefixType::TINK);
+  key->set_key_id(key_id_0);
+
+  uint32_t key_id_1 = 726329;
+  key = keyset.add_key();
+  key->set_output_prefix_type(OutputPrefixType::LEGACY);
+  key->set_key_id(key_id_1);
+
+  uint32_t key_id_2 = 7213743;
+  key = keyset.add_key();
+  key->set_output_prefix_type(OutputPrefixType::TINK);
+  key->set_key_id(key_id_2);
+
+  std::string saead_name_0 = "streaming_aead0";
+  std::string saead_name_1 = "streaming_aead1";
+  std::string saead_name_2 = "streaming_aead2";
+  std::unique_ptr<PrimitiveSet<StreamingAead>> saead_set(
+      new PrimitiveSet<StreamingAead>());
+
+  std::unique_ptr<StreamingAead> saead =
+      absl::make_unique<DummyStreamingAead>(saead_name_0);
+  auto entry_result = saead_set->AddPrimitive(std::move(saead), keyset.key(0));
+  ASSERT_TRUE(entry_result.ok());
+
+  saead = absl::make_unique<DummyStreamingAead>(saead_name_1);
+  entry_result = saead_set->AddPrimitive(std::move(saead), keyset.key(1));
+  ASSERT_TRUE(entry_result.ok());
+
+  saead = absl::make_unique<DummyStreamingAead>(saead_name_2);
+  entry_result = saead_set->AddPrimitive(std::move(saead), keyset.key(2));
+  ASSERT_TRUE(entry_result.ok());
+  // The last key is the primary.
+  saead_set->set_primary(entry_result.ValueOrDie());
+
+  // Wrap aead_set and test the resulting StreamingAead.
+  StreamingAeadWrapper wrapper;
+  auto wrap_result = wrapper.Wrap(std::move(saead_set));
+  EXPECT_TRUE(wrap_result.ok()) << wrap_result.status();
+  saead = std::move(wrap_result.ValueOrDie());
+  std::string plaintext = "some_plaintext";
+  std::string aad = "some_aad";
+
+  // Prepare ciphertext destination stream.
+  auto ct_stream = absl::make_unique<std::stringstream>();
+  // A reference to the ciphertext buffer, for later validation.
+  auto ct_buf = ct_stream->rdbuf();
+  std::unique_ptr<OutputStream> ct_destination(
+      absl::make_unique<util::OstreamOutputStream>(std::move(ct_stream)));
+
+  auto encrypt_result =
+      saead->NewEncryptingStream(std::move(ct_destination), aad);
+  EXPECT_TRUE(encrypt_result.ok()) << encrypt_result.status();
+  auto encrypting_stream = std::move(encrypt_result.ValueOrDie());
+  auto status = encrypting_stream->Close();
+  EXPECT_TRUE(status.ok()) << status;
+  EXPECT_EQ(absl::StrCat(saead_name_2, aad), ct_buf->str());
+
+  // Prepare ciphertext source stream.
+  ct_stream = absl::make_unique<std::stringstream>();
+  // A reference to the ciphertext buffer, for later validation.
+  ct_buf = ct_stream->rdbuf();
+  std::unique_ptr<InputStream> ct_source(
+      absl::make_unique<util::IstreamInputStream>(std::move(ct_stream)));
+  auto decrypt_result = saead->NewDecryptingStream(std::move(ct_source), aad);
+  EXPECT_FALSE(decrypt_result.ok());
+  EXPECT_EQ(util::error::UNIMPLEMENTED, decrypt_result.status().error_code());
+  EXPECT_PRED_FORMAT2(testing::IsSubstring, "Not implemented yet",
+                      decrypt_result.status().error_message());
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/subtle/BUILD.bazel b/cc/subtle/BUILD.bazel
index b1684aa..ab909ab 100644
--- a/cc/subtle/BUILD.bazel
+++ b/cc/subtle/BUILD.bazel
@@ -104,6 +104,40 @@
 )
 
 cc_library(
+    name = "ed25519_sign_boringssl",
+    srcs = ["ed25519_sign_boringssl.cc"],
+    hdrs = ["ed25519_sign_boringssl.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        ":subtle_util_boringssl",
+        "//cc:public_key_sign",
+        "//cc/util:errors",
+        "//cc/util:statusor",
+        "@boringssl//:crypto",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/strings:str_format",
+    ],
+)
+
+cc_library(
+    name = "ed25519_verify_boringssl",
+    srcs = ["ed25519_verify_boringssl.cc"],
+    hdrs = ["ed25519_verify_boringssl.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        ":subtle_util_boringssl",
+        "//cc:public_key_verify",
+        "//cc/util:errors",
+        "//cc/util:statusor",
+        "@boringssl//:crypto",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/strings:str_format",
+    ],
+)
+
+cc_library(
     name = "hkdf",
     srcs = ["hkdf.cc"],
     hdrs = ["hkdf.h"],
@@ -193,6 +227,60 @@
 )
 
 cc_library(
+    name = "rsa_ssa_pss_sign_boringssl",
+    srcs = ["rsa_ssa_pss_sign_boringssl.cc"],
+    hdrs = ["rsa_ssa_pss_sign_boringssl.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        ":common_enums",
+        ":subtle_util_boringssl",
+        "//cc:public_key_sign",
+        "//cc/util:errors",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "@boringssl//:crypto",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_library(
+    name = "rsa_ssa_pkcs1_verify_boringssl",
+    srcs = ["rsa_ssa_pkcs1_verify_boringssl.cc"],
+    hdrs = ["rsa_ssa_pkcs1_verify_boringssl.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        ":common_enums",
+        ":subtle_util_boringssl",
+        "//cc:public_key_verify",
+        "//cc/util:errors",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "@boringssl//:crypto",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_library(
+    name = "rsa_ssa_pkcs1_sign_boringssl",
+    srcs = ["rsa_ssa_pkcs1_sign_boringssl.cc"],
+    hdrs = ["rsa_ssa_pkcs1_sign_boringssl.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        ":common_enums",
+        ":subtle_util_boringssl",
+        "//cc:public_key_sign",
+        "//cc/util:errors",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "@boringssl//:crypto",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_library(
     name = "aes_gcm_boringssl",
     srcs = ["aes_gcm_boringssl.cc"],
     hdrs = ["aes_gcm_boringssl.h"],
@@ -277,6 +365,65 @@
 )
 
 cc_library(
+    name = "xchacha20_poly1305_boringssl",
+    srcs = ["xchacha20_poly1305_boringssl.cc"],
+    hdrs = ["xchacha20_poly1305_boringssl.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        ":common_enums",
+        ":random",
+        ":subtle_util_boringssl",
+        "//cc:aead",
+        "//cc/util:errors",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "@boringssl//:crypto",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_library(
+    name = "aes_siv_boringssl",
+    srcs = [
+        "aes_siv_boringssl.cc",
+    ],
+    hdrs = ["aes_siv_boringssl.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        ":random",
+        ":subtle_util_boringssl",
+        "//cc:deterministic_aead",
+        "//cc/util:errors",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "@boringssl//:crypto",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_library(
+    name = "aes_gcm_siv_boringssl",
+    srcs = [
+        "aes_gcm_siv_boringssl.cc",
+    ],
+    hdrs = ["aes_gcm_siv_boringssl.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        ":random",
+        ":subtle_util_boringssl",
+        "//cc:aead",
+        "//cc/util:errors",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "@boringssl//:crypto",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_library(
     name = "common_enums",
     srcs = ["common_enums.cc"],
     hdrs = ["common_enums.h"],
@@ -315,6 +462,47 @@
     ],
 )
 
+cc_library(
+    name = "stream_segment_encrypter",
+    hdrs = ["stream_segment_encrypter.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        "//cc/util:status",
+    ],
+)
+
+cc_library(
+    name = "streaming_aead_encrypting_stream",
+    srcs = ["streaming_aead_encrypting_stream.cc"],
+    hdrs = ["streaming_aead_encrypting_stream.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        ":stream_segment_encrypter",
+        "//cc:output_stream",
+        "//cc/util:statusor",
+        "@com_google_absl//absl/memory",
+    ],
+)
+
+cc_library(
+    name = "nonce_based_streaming_aead",
+    srcs = ["nonce_based_streaming_aead.cc"],
+    hdrs = ["nonce_based_streaming_aead.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        ":stream_segment_encrypter",
+        ":streaming_aead_encrypting_stream",
+        "//cc:input_stream",
+        "//cc:output_stream",
+        "//cc:streaming_aead",
+        "//cc/util:statusor",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
 # tests
 
 cc_test(
@@ -473,6 +661,28 @@
 )
 
 cc_test(
+    name = "aes_siv_boringssl_test",
+    size = "small",
+    srcs = ["aes_siv_boringssl_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    data = [
+        "@wycheproof//testvectors:aes_siv_cmac",
+    ],
+    deps = [
+        ":aes_siv_boringssl",
+        ":common_enums",
+        ":wycheproof_util",
+        "//cc:deterministic_aead",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "//cc/util:test_util",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+        "@rapidjson",
+    ],
+)
+
+cc_test(
     name = "ecdsa_sign_boringssl_test",
     size = "small",
     srcs = ["ecdsa_sign_boringssl_test.cc"],
@@ -516,6 +726,51 @@
 )
 
 cc_test(
+    name = "ed25519_sign_boringssl_test",
+    size = "small",
+    srcs = ["ed25519_sign_boringssl_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    deps = [
+        ":ed25519_sign_boringssl",
+        ":ed25519_verify_boringssl",
+        ":random",
+        ":subtle_util_boringssl",
+        "//cc:public_key_sign",
+        "//cc:public_key_verify",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "//cc/util:test_util",
+        "@boringssl//:crypto",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "ed25519_verify_boringssl_test",
+    size = "small",
+    srcs = ["ed25519_verify_boringssl_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    data = [
+        "@wycheproof//testvectors:all",
+    ],
+    deps = [
+        ":ed25519_sign_boringssl",
+        ":ed25519_verify_boringssl",
+        ":subtle_util_boringssl",
+        ":wycheproof_util",
+        "//cc:public_key_sign",
+        "//cc:public_key_verify",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "//cc/util:test_util",
+        "@boringssl//:crypto",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
     name = "rsa_ssa_pss_verify_boringssl_test",
     size = "small",
     srcs = ["rsa_ssa_pss_verify_boringssl_test.cc"],
@@ -539,6 +794,82 @@
 )
 
 cc_test(
+    name = "rsa_ssa_pss_sign_boringssl_test",
+    size = "small",
+    srcs = ["rsa_ssa_pss_sign_boringssl_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    deps = [
+        ":rsa_ssa_pss_sign_boringssl",
+        ":rsa_ssa_pss_verify_boringssl",
+        ":subtle_util_boringssl",
+        "//cc/util:test_matchers",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "rsa_ssa_pkcs1_verify_boringssl_test",
+    size = "small",
+    srcs = ["rsa_ssa_pkcs1_verify_boringssl_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    data = [
+        "@wycheproof//testvectors:all",
+    ],
+    deps = [
+        ":common_enums",
+        ":rsa_ssa_pkcs1_verify_boringssl",
+        ":wycheproof_util",
+        "//cc:public_key_sign",
+        "//cc:public_key_verify",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "//cc/util:test_util",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+        "@rapidjson",
+    ],
+)
+
+cc_test(
+    name = "rsa_ssa_pkcs1_sign_boringssl_test",
+    size = "small",
+    srcs = ["rsa_ssa_pkcs1_sign_boringssl_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    deps = [
+        ":rsa_ssa_pkcs1_sign_boringssl",
+        ":rsa_ssa_pkcs1_verify_boringssl",
+        ":subtle_util_boringssl",
+        "//cc/util:test_matchers",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "aes_gcm_siv_boringssl_test",
+    size = "small",
+    srcs = ["aes_gcm_siv_boringssl_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    data = [
+        "@wycheproof//testvectors:aes_gcm_siv",
+    ],
+    deps = [
+        ":aes_gcm_siv_boringssl",
+        ":common_enums",
+        ":wycheproof_util",
+        "//cc:aead",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "//cc/util:test_util",
+        "@boringssl//:crypto",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+        "@rapidjson",
+    ],
+)
+
+cc_test(
     name = "random_test",
     size = "small",
     srcs = ["random_test.cc"],
@@ -585,3 +916,40 @@
         "@rapidjson",
     ],
 )
+
+cc_test(
+    name = "xchacha20_poly1305_boringssl_test",
+    size = "small",
+    srcs = ["xchacha20_poly1305_boringssl_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    deps = [
+        ":xchacha20_poly1305_boringssl",
+        "//cc:aead",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "//cc/util:test_util",
+        "@boringssl//:crypto",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "streaming_aead_encrypting_stream_test",
+    size = "medium",
+    srcs = ["streaming_aead_encrypting_stream_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    linkopts = ["-lpthread"],
+    deps = [
+        ":random",
+        ":stream_segment_encrypter",
+        ":streaming_aead_encrypting_stream",
+        "//cc:output_stream",
+        "//cc/util:ostream_output_stream",
+        "//cc/util:status",
+        "//cc/util:statusor",
+        "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
diff --git a/cc/subtle/CMakeLists.txt b/cc/subtle/CMakeLists.txt
index ca4c3d1..367fa54 100644
--- a/cc/subtle/CMakeLists.txt
+++ b/cc/subtle/CMakeLists.txt
@@ -1,716 +1,323 @@
-# Copyright 2018 The Fuchsia Authors. All rights reserved.
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-# Library: 'tink_subtle_subtle'
-add_library(
-  tink_subtle_subtle
-  common_enums.h
-  aes_gcm_boringssl.h
-  encrypt_then_authenticate.h
-  hmac_boringssl.h
-  hkdf.h
-  ind_cpa_cipher.h
-  random.h
+
+# CC Library : ind_cpa_cipher
+add_library(tink_cc_subtle_ind_cpa_cipher ind_cpa_cipher.h)
+set_target_properties(
+  tink_cc_subtle_ind_cpa_cipher
+  PROPERTIES
+  LINKER_LANGUAGE
+  CXX
 )
-set_target_properties(tink_subtle_subtle PROPERTIES LINKER_LANGUAGE CXX)
-tink_export_hdrs(
-  common_enums.h
-  aes_gcm_boringssl.h
-  encrypt_then_authenticate.h
-  hmac_boringssl.h
-  hkdf.h
-  ind_cpa_cipher.h
-  random.h
-)
-add_dependencies(
-  tink_subtle_subtle
-  tink_subtle_aes_gcm_boringssl
-  tink_subtle_common_enums
-  tink_subtle_encrypt_then_authenticate
-  tink_subtle_hkdf
-  tink_subtle_hmac_boringssl
-  tink_subtle_random
-  tink_aead
-  tink_hybrid_decrypt
-  tink_hybrid_encrypt
-  tink_mac
-  tink_public_key_sign
-  tink_public_key_verify
-  tink_util_status
-  tink_util_statusor
-)
+tink_export_hdrs(ind_cpa_cipher.h)
+add_dependencies(tink_cc_subtle_ind_cpa_cipher tink_cc_util_statusor)
 target_link_libraries(
-  tink_subtle_subtle
-  tink_subtle_aes_gcm_boringssl
-  tink_subtle_common_enums
-  tink_subtle_encrypt_then_authenticate
-  tink_subtle_hkdf
-  tink_subtle_hmac_boringssl
-  tink_subtle_random
-  tink_aead
-  tink_hybrid_decrypt
-  tink_hybrid_encrypt
-  tink_mac
-  tink_public_key_sign
-  tink_public_key_verify
-  tink_util_status
-  tink_util_statusor
-  crypto
+  tink_cc_subtle_ind_cpa_cipher
+  tink_cc_util_statusor
   absl::strings
 )
 
-# Library: 'tink_subtle_ind_cpa_cipher'
-add_library(tink_subtle_ind_cpa_cipher ind_cpa_cipher.h)
-set_target_properties(tink_subtle_ind_cpa_cipher PROPERTIES LINKER_LANGUAGE CXX)
-tink_export_hdrs(ind_cpa_cipher.h)
-add_dependencies(tink_subtle_ind_cpa_cipher tink_util_statusor)
-target_link_libraries(tink_subtle_ind_cpa_cipher tink_util_statusor absl::strings)
-
-# Library: 'tink_subtle_ecies_hkdf_recipient_kem_boringssl'
-add_library(tink_subtle_ecies_hkdf_recipient_kem_boringssl ecies_hkdf_recipient_kem_boringssl.cc ecies_hkdf_recipient_kem_boringssl.h)
+# CC Library : ecies_hkdf_recipient_kem_boringssl
+add_library(
+  tink_cc_subtle_ecies_hkdf_recipient_kem_boringssl
+  ecies_hkdf_recipient_kem_boringssl.h
+  ecies_hkdf_recipient_kem_boringssl.cc
+)
 tink_export_hdrs(ecies_hkdf_recipient_kem_boringssl.h)
 add_dependencies(
-  tink_subtle_ecies_hkdf_recipient_kem_boringssl
-  tink_subtle_common_enums
-  tink_subtle_hkdf
-  tink_subtle_subtle_util_boringssl
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
+  tink_cc_subtle_ecies_hkdf_recipient_kem_boringssl
+  tink_cc_subtle_common_enums
+  tink_cc_subtle_hkdf
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_errors
+  tink_cc_util_status
+  tink_cc_util_statusor
 )
 target_link_libraries(
-  tink_subtle_ecies_hkdf_recipient_kem_boringssl
-  tink_subtle_common_enums
-  tink_subtle_hkdf
-  tink_subtle_subtle_util_boringssl
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
+  tink_cc_subtle_ecies_hkdf_recipient_kem_boringssl
+  tink_cc_subtle_common_enums
+  tink_cc_subtle_hkdf
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_errors
+  tink_cc_util_status
+  tink_cc_util_statusor
   crypto
   absl::memory
   absl::strings
 )
 
-# Library: 'tink_subtle_ecies_hkdf_sender_kem_boringssl'
-add_library(tink_subtle_ecies_hkdf_sender_kem_boringssl ecies_hkdf_sender_kem_boringssl.cc ecies_hkdf_sender_kem_boringssl.h)
+# CC Library : ecies_hkdf_sender_kem_boringssl
+add_library(
+  tink_cc_subtle_ecies_hkdf_sender_kem_boringssl
+  ecies_hkdf_sender_kem_boringssl.h
+  ecies_hkdf_sender_kem_boringssl.cc
+)
 tink_export_hdrs(ecies_hkdf_sender_kem_boringssl.h)
 add_dependencies(
-  tink_subtle_ecies_hkdf_sender_kem_boringssl
-  tink_subtle_common_enums
-  tink_subtle_hkdf
-  tink_subtle_subtle_util_boringssl
-  tink_util_status
-  tink_util_statusor
+  tink_cc_subtle_ecies_hkdf_sender_kem_boringssl
+  tink_cc_subtle_common_enums
+  tink_cc_subtle_hkdf
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_status
+  tink_cc_util_statusor
 )
 target_link_libraries(
-  tink_subtle_ecies_hkdf_sender_kem_boringssl
-  tink_subtle_common_enums
-  tink_subtle_hkdf
-  tink_subtle_subtle_util_boringssl
-  tink_util_status
-  tink_util_statusor
+  tink_cc_subtle_ecies_hkdf_sender_kem_boringssl
+  tink_cc_subtle_common_enums
+  tink_cc_subtle_hkdf
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_status
+  tink_cc_util_statusor
   crypto
   absl::memory
   absl::strings
 )
 
-# Library: 'tink_subtle_ec_util'
-add_library(tink_subtle_ec_util ec_util.cc ec_util.h)
+# CC Library : ec_util
+add_library(tink_cc_subtle_ec_util ec_util.h ec_util.cc)
 tink_export_hdrs(ec_util.h)
 add_dependencies(
-  tink_subtle_ec_util
-  tink_subtle_common_enums
-  tink_subtle_subtle_util_boringssl
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
+  tink_cc_subtle_ec_util
+  tink_cc_subtle_common_enums
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_errors
+  tink_cc_util_status
+  tink_cc_util_statusor
 )
 target_link_libraries(
-  tink_subtle_ec_util
-  tink_subtle_common_enums
-  tink_subtle_subtle_util_boringssl
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
+  tink_cc_subtle_ec_util
+  tink_cc_subtle_common_enums
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_errors
+  tink_cc_util_status
+  tink_cc_util_statusor
   crypto
   absl::strings
 )
 
-# Library: 'tink_subtle_hkdf'
-add_library(tink_subtle_hkdf hkdf.cc hkdf.h)
+# CC Library : hkdf
+add_library(tink_cc_subtle_hkdf hkdf.h hkdf.cc)
 tink_export_hdrs(hkdf.h)
 add_dependencies(
-  tink_subtle_hkdf
-  tink_subtle_common_enums
-  tink_subtle_subtle_util_boringssl
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
+  tink_cc_subtle_hkdf
+  tink_cc_subtle_common_enums
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_errors
+  tink_cc_util_status
+  tink_cc_util_statusor
 )
 target_link_libraries(
-  tink_subtle_hkdf
-  tink_subtle_common_enums
-  tink_subtle_subtle_util_boringssl
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
+  tink_cc_subtle_hkdf
+  tink_cc_subtle_common_enums
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_errors
+  tink_cc_util_status
+  tink_cc_util_statusor
   crypto
   absl::strings
 )
 
-# Library: 'tink_subtle_hmac_boringssl'
-add_library(tink_subtle_hmac_boringssl hmac_boringssl.cc hmac_boringssl.h)
+# CC Library : hmac_boringssl
+add_library(tink_cc_subtle_hmac_boringssl hmac_boringssl.h hmac_boringssl.cc)
 tink_export_hdrs(hmac_boringssl.h)
 add_dependencies(
-  tink_subtle_hmac_boringssl
-  tink_subtle_common_enums
-  tink_subtle_subtle_util_boringssl
-  tink_mac
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
+  tink_cc_subtle_hmac_boringssl
+  tink_cc_subtle_common_enums
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_mac
+  tink_cc_util_errors
+  tink_cc_util_status
+  tink_cc_util_statusor
 )
 target_link_libraries(
-  tink_subtle_hmac_boringssl
-  tink_subtle_common_enums
-  tink_subtle_subtle_util_boringssl
-  tink_mac
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
+  tink_cc_subtle_hmac_boringssl
+  tink_cc_subtle_common_enums
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_mac
+  tink_cc_util_errors
+  tink_cc_util_status
+  tink_cc_util_statusor
   crypto
   absl::strings
 )
 
-# Library: 'tink_subtle_ecdsa_sign_boringssl'
-add_library(tink_subtle_ecdsa_sign_boringssl ecdsa_sign_boringssl.cc ecdsa_sign_boringssl.h)
-tink_export_hdrs(ecdsa_sign_boringssl.h)
-add_dependencies(
-  tink_subtle_ecdsa_sign_boringssl
-  tink_subtle_common_enums
-  tink_subtle_subtle_util_boringssl
-  tink_public_key_sign
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
+# CC Library : aes_gcm_boringssl
+add_library(
+  tink_cc_subtle_aes_gcm_boringssl
+  aes_gcm_boringssl.h
+  aes_gcm_boringssl.cc
 )
-target_link_libraries(
-  tink_subtle_ecdsa_sign_boringssl
-  tink_subtle_common_enums
-  tink_subtle_subtle_util_boringssl
-  tink_public_key_sign
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
-  crypto
-  absl::strings
-)
-
-# Library: 'tink_subtle_ecdsa_verify_boringssl'
-add_library(tink_subtle_ecdsa_verify_boringssl ecdsa_verify_boringssl.cc ecdsa_verify_boringssl.h)
-tink_export_hdrs(ecdsa_verify_boringssl.h)
-add_dependencies(
-  tink_subtle_ecdsa_verify_boringssl
-  tink_subtle_common_enums
-  tink_subtle_subtle_util_boringssl
-  tink_public_key_verify
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
-)
-target_link_libraries(
-  tink_subtle_ecdsa_verify_boringssl
-  tink_subtle_common_enums
-  tink_subtle_subtle_util_boringssl
-  tink_public_key_verify
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
-  crypto
-  absl::strings
-)
-
-# Library: 'tink_subtle_rsa_ssa_pss_verify_boringssl'
-add_library(tink_subtle_rsa_ssa_pss_verify_boringssl rsa_ssa_pss_verify_boringssl.cc rsa_ssa_pss_verify_boringssl.h)
-tink_export_hdrs(rsa_ssa_pss_verify_boringssl.h)
-add_dependencies(
-  tink_subtle_rsa_ssa_pss_verify_boringssl
-  tink_subtle_common_enums
-  tink_subtle_subtle_util_boringssl
-  tink_public_key_verify
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
-)
-target_link_libraries(
-  tink_subtle_rsa_ssa_pss_verify_boringssl
-  tink_subtle_common_enums
-  tink_subtle_subtle_util_boringssl
-  tink_public_key_verify
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
-  crypto
-  absl::strings
-)
-
-# Library: 'tink_subtle_aes_gcm_boringssl'
-add_library(tink_subtle_aes_gcm_boringssl aes_gcm_boringssl.cc aes_gcm_boringssl.h)
 tink_export_hdrs(aes_gcm_boringssl.h)
 add_dependencies(
-  tink_subtle_aes_gcm_boringssl
-  tink_subtle_random
-  tink_subtle_subtle_util_boringssl
-  tink_aead
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
+  tink_cc_subtle_aes_gcm_boringssl
+  tink_cc_subtle_random
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_aead
+  tink_cc_util_errors
+  tink_cc_util_status
+  tink_cc_util_statusor
 )
 target_link_libraries(
-  tink_subtle_aes_gcm_boringssl
-  tink_subtle_random
-  tink_subtle_subtle_util_boringssl
-  tink_aead
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
+  tink_cc_subtle_aes_gcm_boringssl
+  tink_cc_subtle_random
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_aead
+  tink_cc_util_errors
+  tink_cc_util_status
+  tink_cc_util_statusor
   crypto
   absl::strings
 )
 
-# Library: 'tink_subtle_aes_eax_boringssl'
-add_library(tink_subtle_aes_eax_boringssl aes_eax_boringssl.cc aes_eax_boringssl.h)
+# CC Library : aes_eax_boringssl
+add_library(
+  tink_cc_subtle_aes_eax_boringssl
+  aes_eax_boringssl.h
+  aes_eax_boringssl.cc
+)
 tink_export_hdrs(aes_eax_boringssl.h)
 add_dependencies(
-  tink_subtle_aes_eax_boringssl
-  tink_subtle_random
-  tink_subtle_subtle_util_boringssl
-  tink_aead
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
+  tink_cc_subtle_aes_eax_boringssl
+  tink_cc_subtle_random
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_aead
+  tink_cc_util_errors
+  tink_cc_util_status
+  tink_cc_util_statusor
 )
 target_link_libraries(
-  tink_subtle_aes_eax_boringssl
-  tink_subtle_random
-  tink_subtle_subtle_util_boringssl
-  tink_aead
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
+  tink_cc_subtle_aes_eax_boringssl
+  tink_cc_subtle_random
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_aead
+  tink_cc_util_errors
+  tink_cc_util_status
+  tink_cc_util_statusor
   crypto
   absl::strings
 )
 
-# Library: 'tink_subtle_encrypt_then_authenticate'
-add_library(tink_subtle_encrypt_then_authenticate encrypt_then_authenticate.cc encrypt_then_authenticate.h)
+# CC Library : encrypt_then_authenticate
+add_library(
+  tink_cc_subtle_encrypt_then_authenticate
+  encrypt_then_authenticate.h
+  encrypt_then_authenticate.cc
+)
 tink_export_hdrs(encrypt_then_authenticate.h)
 add_dependencies(
-  tink_subtle_encrypt_then_authenticate
-  tink_subtle_aes_ctr_boringssl
-  tink_subtle_ind_cpa_cipher
-  tink_subtle_subtle_util_boringssl
-  tink_aead
-  tink_mac
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
+  tink_cc_subtle_encrypt_then_authenticate
+  tink_cc_subtle_aes_ctr_boringssl
+  tink_cc_subtle_ind_cpa_cipher
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_aead
+  tink_cc_mac
+  tink_cc_util_errors
+  tink_cc_util_status
+  tink_cc_util_statusor
 )
 target_link_libraries(
-  tink_subtle_encrypt_then_authenticate
-  tink_subtle_aes_ctr_boringssl
-  tink_subtle_ind_cpa_cipher
-  tink_subtle_subtle_util_boringssl
-  tink_aead
-  tink_mac
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
+  tink_cc_subtle_encrypt_then_authenticate
+  tink_cc_subtle_aes_ctr_boringssl
+  tink_cc_subtle_ind_cpa_cipher
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_aead
+  tink_cc_mac
+  tink_cc_util_errors
+  tink_cc_util_status
+  tink_cc_util_statusor
   absl::strings
 )
 
-# Library: 'tink_subtle_aes_ctr_boringssl'
-add_library(tink_subtle_aes_ctr_boringssl aes_ctr_boringssl.cc aes_ctr_boringssl.h)
+# CC Library : aes_ctr_boringssl
+add_library(
+  tink_cc_subtle_aes_ctr_boringssl
+  aes_ctr_boringssl.h
+  aes_ctr_boringssl.cc
+)
 tink_export_hdrs(aes_ctr_boringssl.h)
 add_dependencies(
-  tink_subtle_aes_ctr_boringssl
-  tink_subtle_ind_cpa_cipher
-  tink_subtle_random
-  tink_subtle_subtle_util_boringssl
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
+  tink_cc_subtle_aes_ctr_boringssl
+  tink_cc_subtle_ind_cpa_cipher
+  tink_cc_subtle_random
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_errors
+  tink_cc_util_status
+  tink_cc_util_statusor
 )
 target_link_libraries(
-  tink_subtle_aes_ctr_boringssl
-  tink_subtle_ind_cpa_cipher
-  tink_subtle_random
-  tink_subtle_subtle_util_boringssl
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
+  tink_cc_subtle_aes_ctr_boringssl
+  tink_cc_subtle_ind_cpa_cipher
+  tink_cc_subtle_random
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_util_errors
+  tink_cc_util_status
+  tink_cc_util_statusor
   crypto
   absl::strings
 )
 
-# Library: 'tink_subtle_random'
-add_library(tink_subtle_random random.cc random.h)
+# CC Library : random
+add_library(tink_cc_subtle_random random.h random.cc)
 tink_export_hdrs(random.h)
-target_link_libraries(tink_subtle_random crypto)
+target_link_libraries(tink_cc_subtle_random crypto)
 
-# Library: 'tink_subtle_common_enums'
-add_library(tink_subtle_common_enums common_enums.cc common_enums.h)
+# CC Library : xchacha20_poly1305_boringssl
+add_library(
+  tink_cc_subtle_xchacha20_poly1305_boringssl
+  xchacha20_poly1305_boringssl.h
+  xchacha20_poly1305_boringssl.cc
+)
+tink_export_hdrs(xchacha20_poly1305_boringssl.h)
+add_dependencies(
+  tink_cc_subtle_xchacha20_poly1305_boringssl
+  tink_cc_subtle_common_enums
+  tink_cc_subtle_random
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_aead
+  tink_cc_util_errors
+  tink_cc_util_status
+  tink_cc_util_statusor
+)
+target_link_libraries(
+  tink_cc_subtle_xchacha20_poly1305_boringssl
+  tink_cc_subtle_common_enums
+  tink_cc_subtle_random
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_aead
+  tink_cc_util_errors
+  tink_cc_util_status
+  tink_cc_util_statusor
+  crypto
+  absl::strings
+)
+
+# CC Library : common_enums
+add_library(tink_cc_subtle_common_enums common_enums.h common_enums.cc)
 tink_export_hdrs(common_enums.h)
 
-# Library: 'tink_subtle_subtle_util_boringssl'
-add_library(tink_subtle_subtle_util_boringssl subtle_util_boringssl.cc subtle_util_boringssl.h)
+# CC Library : subtle_util_boringssl
+add_library(
+  tink_cc_subtle_subtle_util_boringssl
+  subtle_util_boringssl.h
+  subtle_util_boringssl.cc
+)
 tink_export_hdrs(subtle_util_boringssl.h)
 add_dependencies(
-  tink_subtle_subtle_util_boringssl
-  tink_subtle_common_enums
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_subtle_common_enums
+  tink_cc_util_errors
+  tink_cc_util_status
+  tink_cc_util_statusor
 )
 target_link_libraries(
-  tink_subtle_subtle_util_boringssl
-  tink_subtle_common_enums
-  tink_util_errors
-  tink_util_status
-  tink_util_statusor
+  tink_cc_subtle_subtle_util_boringssl
+  tink_cc_subtle_common_enums
+  tink_cc_util_errors
+  tink_cc_util_status
+  tink_cc_util_statusor
   crypto
   absl::strings
 )
 
-# Library: 'tink_subtle_wycheproof_util'
-add_library(tink_subtle_wycheproof_util wycheproof_util.cc wycheproof_util.h)
-tink_export_hdrs(wycheproof_util.h)
-add_dependencies(
-  tink_subtle_wycheproof_util
-  tink_subtle_common_enums
-  tink_util_status
-  tink_util_statusor
-)
-target_link_libraries(
-  tink_subtle_wycheproof_util
-  tink_subtle_common_enums
-  tink_util_status
-  tink_util_statusor
-  absl::strings
-  rapidjson
-)
-
-# Test Binary: 'tink_subtle_ecies_hkdf_recipient_kem_boringssl_test'
-add_executable(tink_subtle_ecies_hkdf_recipient_kem_boringssl_test ecies_hkdf_recipient_kem_boringssl_test.cc)
-add_dependencies(
-  tink_subtle_ecies_hkdf_recipient_kem_boringssl_test
-  tink_subtle_common_enums
-  tink_subtle_ecies_hkdf_recipient_kem_boringssl
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-)
-add_dependencies(tink_subtle_ecies_hkdf_recipient_kem_boringssl_test build_external_projects)
-target_link_libraries(
-  tink_subtle_ecies_hkdf_recipient_kem_boringssl_test
-  tink_subtle_common_enums
-  tink_subtle_ecies_hkdf_recipient_kem_boringssl
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_subtle_ecies_hkdf_sender_kem_boringssl_test'
-add_executable(tink_subtle_ecies_hkdf_sender_kem_boringssl_test ecies_hkdf_sender_kem_boringssl_test.cc)
-add_dependencies(
-  tink_subtle_ecies_hkdf_sender_kem_boringssl_test
-  tink_subtle_common_enums
-  tink_subtle_ecies_hkdf_recipient_kem_boringssl
-  tink_subtle_ecies_hkdf_sender_kem_boringssl
-  tink_subtle_subtle_util_boringssl
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-)
-add_dependencies(tink_subtle_ecies_hkdf_sender_kem_boringssl_test build_external_projects)
-target_link_libraries(
-  tink_subtle_ecies_hkdf_sender_kem_boringssl_test
-  tink_subtle_common_enums
-  tink_subtle_ecies_hkdf_recipient_kem_boringssl
-  tink_subtle_ecies_hkdf_sender_kem_boringssl
-  tink_subtle_subtle_util_boringssl
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_subtle_ec_util_test'
-add_executable(tink_subtle_ec_util_test ec_util_test.cc)
-add_dependencies(
-  tink_subtle_ec_util_test
-  tink_subtle_ec_util
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-)
-add_dependencies(tink_subtle_ec_util_test build_external_projects)
-target_link_libraries(
-  tink_subtle_ec_util_test
-  tink_subtle_ec_util
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_subtle_hkdf_test'
-add_executable(tink_subtle_hkdf_test hkdf_test.cc)
-add_dependencies(
-  tink_subtle_hkdf_test
-  tink_subtle_common_enums
-  tink_subtle_hkdf
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-)
-add_dependencies(tink_subtle_hkdf_test build_external_projects)
-target_link_libraries(
-  tink_subtle_hkdf_test
-  tink_subtle_common_enums
-  tink_subtle_hkdf
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_subtle_hmac_boringssl_test'
-add_executable(tink_subtle_hmac_boringssl_test hmac_boringssl_test.cc)
-add_dependencies(
-  tink_subtle_hmac_boringssl_test
-  tink_subtle_common_enums
-  tink_subtle_hmac_boringssl
-  tink_mac
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-)
-add_dependencies(tink_subtle_hmac_boringssl_test build_external_projects)
-target_link_libraries(
-  tink_subtle_hmac_boringssl_test
-  tink_subtle_common_enums
-  tink_subtle_hmac_boringssl
-  tink_mac
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_subtle_aes_gcm_boringssl_test'
-add_executable(tink_subtle_aes_gcm_boringssl_test aes_gcm_boringssl_test.cc)
-add_dependencies(
-  tink_subtle_aes_gcm_boringssl_test
-  tink_subtle_aes_gcm_boringssl
-  tink_subtle_wycheproof_util
-  tink_aead
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-)
-add_dependencies(tink_subtle_aes_gcm_boringssl_test build_external_projects)
-target_link_libraries(
-  tink_subtle_aes_gcm_boringssl_test
-  tink_subtle_aes_gcm_boringssl
-  tink_subtle_wycheproof_util
-  tink_aead
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-  absl::strings
-  gtest gtest_main
-  rapidjson
-)
-
-# Test Binary: 'tink_subtle_aes_eax_boringssl_test'
-add_executable(tink_subtle_aes_eax_boringssl_test aes_eax_boringssl_test.cc)
-add_dependencies(
-  tink_subtle_aes_eax_boringssl_test
-  tink_subtle_aes_eax_boringssl
-  tink_subtle_common_enums
-  tink_subtle_wycheproof_util
-  tink_aead
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-)
-add_dependencies(tink_subtle_aes_eax_boringssl_test build_external_projects)
-target_link_libraries(
-  tink_subtle_aes_eax_boringssl_test
-  tink_subtle_aes_eax_boringssl
-  tink_subtle_common_enums
-  tink_subtle_wycheproof_util
-  tink_aead
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-  absl::strings
-  gtest gtest_main
-  rapidjson
-)
-
-# Test Binary: 'tink_subtle_encrypt_then_authenticate_test'
-add_executable(tink_subtle_encrypt_then_authenticate_test encrypt_then_authenticate_test.cc)
-add_dependencies(
-  tink_subtle_encrypt_then_authenticate_test
-  tink_subtle_aes_ctr_boringssl
-  tink_subtle_common_enums
-  tink_subtle_encrypt_then_authenticate
-  tink_subtle_hmac_boringssl
-  tink_subtle_random
-  tink_aead
-  tink_mac
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-)
-add_dependencies(tink_subtle_encrypt_then_authenticate_test build_external_projects)
-target_link_libraries(
-  tink_subtle_encrypt_then_authenticate_test
-  tink_subtle_aes_ctr_boringssl
-  tink_subtle_common_enums
-  tink_subtle_encrypt_then_authenticate
-  tink_subtle_hmac_boringssl
-  tink_subtle_random
-  tink_aead
-  tink_mac
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_subtle_aes_ctr_boringssl_test'
-add_executable(tink_subtle_aes_ctr_boringssl_test aes_ctr_boringssl_test.cc)
-add_dependencies(
-  tink_subtle_aes_ctr_boringssl_test
-  tink_subtle_aes_ctr_boringssl
-  tink_subtle_random
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-)
-add_dependencies(tink_subtle_aes_ctr_boringssl_test build_external_projects)
-target_link_libraries(
-  tink_subtle_aes_ctr_boringssl_test
-  tink_subtle_aes_ctr_boringssl
-  tink_subtle_random
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_subtle_ecdsa_sign_boringssl_test'
-add_executable(tink_subtle_ecdsa_sign_boringssl_test ecdsa_sign_boringssl_test.cc)
-add_dependencies(
-  tink_subtle_ecdsa_sign_boringssl_test
-  tink_subtle_common_enums
-  tink_subtle_ec_util
-  tink_subtle_ecdsa_sign_boringssl
-  tink_subtle_ecdsa_verify_boringssl
-  tink_public_key_sign
-  tink_public_key_verify
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-)
-add_dependencies(tink_subtle_ecdsa_sign_boringssl_test build_external_projects)
-target_link_libraries(
-  tink_subtle_ecdsa_sign_boringssl_test
-  tink_subtle_common_enums
-  tink_subtle_ec_util
-  tink_subtle_ecdsa_sign_boringssl
-  tink_subtle_ecdsa_verify_boringssl
-  tink_public_key_sign
-  tink_public_key_verify
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_subtle_ecdsa_verify_boringssl_test'
-add_executable(tink_subtle_ecdsa_verify_boringssl_test ecdsa_verify_boringssl_test.cc)
-add_dependencies(
-  tink_subtle_ecdsa_verify_boringssl_test
-  tink_subtle_common_enums
-  tink_subtle_ecdsa_sign_boringssl
-  tink_subtle_ecdsa_verify_boringssl
-  tink_subtle_wycheproof_util
-  tink_public_key_sign
-  tink_public_key_verify
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-)
-add_dependencies(tink_subtle_ecdsa_verify_boringssl_test build_external_projects)
-target_link_libraries(
-  tink_subtle_ecdsa_verify_boringssl_test
-  tink_subtle_common_enums
-  tink_subtle_ecdsa_sign_boringssl
-  tink_subtle_ecdsa_verify_boringssl
-  tink_subtle_wycheproof_util
-  tink_public_key_sign
-  tink_public_key_verify
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-  absl::strings
-  gtest gtest_main
-  rapidjson
-)
-
-# Test Binary: 'tink_subtle_rsa_ssa_pss_verify_boringssl_test'
-add_executable(tink_subtle_rsa_ssa_pss_verify_boringssl_test rsa_ssa_pss_verify_boringssl_test.cc)
-add_dependencies(
-  tink_subtle_rsa_ssa_pss_verify_boringssl_test
-  tink_subtle_common_enums
-  tink_subtle_rsa_ssa_pss_verify_boringssl
-  tink_subtle_wycheproof_util
-  tink_public_key_sign
-  tink_public_key_verify
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-)
-add_dependencies(tink_subtle_rsa_ssa_pss_verify_boringssl_test build_external_projects)
-target_link_libraries(
-  tink_subtle_rsa_ssa_pss_verify_boringssl_test
-  tink_subtle_common_enums
-  tink_subtle_rsa_ssa_pss_verify_boringssl
-  tink_subtle_wycheproof_util
-  tink_public_key_sign
-  tink_public_key_verify
-  tink_util_status
-  tink_util_statusor
-  tink_util_test_util
-  absl::strings
-  gtest gtest_main
-  rapidjson
-)
-
-# Test Binary: 'tink_subtle_random_test'
-add_executable(tink_subtle_random_test random_test.cc)
-add_dependencies(tink_subtle_random_test tink_subtle_random)
-add_dependencies(tink_subtle_random_test build_external_projects)
-target_link_libraries(tink_subtle_random_test tink_subtle_random gtest gtest_main)
-
-# Test Binary: 'tink_subtle_common_enums_test'
-add_executable(tink_subtle_common_enums_test common_enums_test.cc)
-add_dependencies(tink_subtle_common_enums_test tink_subtle_common_enums)
-add_dependencies(tink_subtle_common_enums_test build_external_projects)
-target_link_libraries(tink_subtle_common_enums_test tink_subtle_common_enums gtest gtest_main)
diff --git a/cc/subtle/aes_ctr_boringssl_test.cc b/cc/subtle/aes_ctr_boringssl_test.cc
index f5d9423..cbe0ce7 100644
--- a/cc/subtle/aes_ctr_boringssl_test.cc
+++ b/cc/subtle/aes_ctr_boringssl_test.cc
@@ -121,8 +121,3 @@
 }  // namespace subtle
 }  // namespace tink
 }  // namespace crypto
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/subtle/aes_eax_aesni.cc b/cc/subtle/aes_eax_aesni.cc
index 196c659..6a8bb10 100644
--- a/cc/subtle/aes_eax_aesni.cc
+++ b/cc/subtle/aes_eax_aesni.cc
@@ -81,14 +81,14 @@
 inline __m128i Add(__m128i x, uint64 y) {
   // Convert to a vector of two uint64.
   uint64 vec[2];
-  _mm_storeu_si128((__m128i*) vec, x);
+  _mm_storeu_si128(reinterpret_cast<__m128i*>(vec), x);
   // Perform the addition on the vector.
   vec[0] += y;
   if (y > vec[0]) {
     vec[1]++;
   }
   // Convert back to xmm.
-  return _mm_loadu_si128((__m128i*) vec);
+  return _mm_loadu_si128(reinterpret_cast<__m128i*>(vec));
 }
 
 // Decrement x by 1.
@@ -137,7 +137,7 @@
   uint8_t tmp[16];
   memset(tmp, 0, 16);
   memmove(tmp, block, block_size);
-  return _mm_loadu_si128((__m128i*) tmp);
+  return _mm_loadu_si128(reinterpret_cast<__m128i*>(tmp));
 }
 
 // Store the block_size least significant bytes from value in
@@ -145,7 +145,7 @@
 // critical.
 void StorePartialBlock(uint8_t* block, size_t block_size, __m128i value) {
   uint8_t tmp[16];
-  _mm_storeu_si128((__m128i*) tmp, value);
+  _mm_storeu_si128(reinterpret_cast<__m128i*>(tmp), value);
   memmove(block, tmp, block_size);
 }
 
@@ -320,11 +320,11 @@
   memset(tmp, 0, BLOCK_SIZE);
   memmove(tmp, data, len);
   if (len == BLOCK_SIZE) {
-    __m128i block = _mm_loadu_si128((__m128i*) tmp);
+    __m128i block = _mm_loadu_si128(reinterpret_cast<__m128i*>(tmp));
     return _mm_xor_si128(block, B_);
   } else {
     tmp[len] = 0x80;
-    __m128i block = _mm_loadu_si128((__m128i*) tmp);
+    __m128i block = _mm_loadu_si128(reinterpret_cast<__m128i*>(tmp));
     return _mm_xor_si128(block, P_);
   }
 }
@@ -383,11 +383,11 @@
     // Get the key stream for one message block and compute
     // the MAC for the previous ciphertext block or header.
     Encrypt2Blocks(mac, ctr_big_endian, &mac, &key_stream);
-    __m128i pt = _mm_loadu_si128((const __m128i*) plaintext);
+    __m128i pt = _mm_loadu_si128(reinterpret_cast<const __m128i*>(plaintext));
     __m128i ct = _mm_xor_si128(pt, key_stream);
     mac = _mm_xor_si128(mac, ct);
     ctr = Increment(ctr);
-    _mm_storeu_si128((__m128i*) out, ct);
+    _mm_storeu_si128(reinterpret_cast<__m128i*>(out), ct);
     plaintext += BLOCK_SIZE;
     out += BLOCK_SIZE;
     idx += BLOCK_SIZE;
@@ -436,8 +436,8 @@
   }
 
   // Get the tag from the ciphertext.
-  const __m128i tag =
-      _mm_loadu_si128((const __m128i*) &ciphertext[plaintext_size]);
+  const __m128i tag = _mm_loadu_si128(
+      reinterpret_cast<const __m128i*>(&ciphertext[plaintext_size]));
 
   // A CBC-MAC is reversible. This allows to pipeline the MAC verification
   // by recomputing the MAC for the first half of the ciphertext and
diff --git a/cc/subtle/aes_eax_aesni_test.cc b/cc/subtle/aes_eax_aesni_test.cc
index a54d94a..8365792 100644
--- a/cc/subtle/aes_eax_aesni_test.cc
+++ b/cc/subtle/aes_eax_aesni_test.cc
@@ -14,6 +14,9 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
+#ifdef __SSE4_1__
+#ifdef __AES__
+
 #include "tink/subtle/aes_eax_aesni.h"
 
 #include <string>
@@ -269,7 +272,5 @@
 }  // namespace tink
 }  // namespace crypto
 
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
+#endif  // __AES__
+#endif  // __SSE4_1__
diff --git a/cc/subtle/aes_eax_boringssl_test.cc b/cc/subtle/aes_eax_boringssl_test.cc
index 678cfce..1e7d69c 100644
--- a/cc/subtle/aes_eax_boringssl_test.cc
+++ b/cc/subtle/aes_eax_boringssl_test.cc
@@ -284,7 +284,3 @@
 }  // namespace tink
 }  // namespace crypto
 
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/subtle/aes_gcm_boringssl.cc b/cc/subtle/aes_gcm_boringssl.cc
index d3b1959..ab839f6 100644
--- a/cc/subtle/aes_gcm_boringssl.cc
+++ b/cc/subtle/aes_gcm_boringssl.cc
@@ -24,198 +24,90 @@
 #include "tink/util/errors.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
+#include "openssl/aead.h"
 #include "openssl/err.h"
-#include "openssl/evp.h"
 
 
 namespace crypto {
 namespace tink {
 namespace subtle {
 
-static const EVP_CIPHER* GetCipherForKeySize(uint32_t size_in_bytes) {
+static const EVP_AEAD* GetAeadForKeySize(uint32_t size_in_bytes) {
   switch (size_in_bytes) {
     case 16:
-      return EVP_aes_128_gcm();
+      return EVP_aead_aes_128_gcm();
     case 32:
-      return EVP_aes_256_gcm();
+      return EVP_aead_aes_256_gcm();
     default:
       return nullptr;
   }
 }
 
-AesGcmBoringSsl::AesGcmBoringSsl(absl::string_view key_value,
-                                 const EVP_CIPHER* cipher)
-    : key_(key_value), cipher_(cipher) {}
+util::Status AesGcmBoringSsl::Init(absl::string_view key_value) {
+  const EVP_AEAD* aead = GetAeadForKeySize(key_value.size());
+  if (aead == nullptr) {
+    return util::Status(util::error::INTERNAL, "invalid key size");
+  }
+  if (EVP_AEAD_CTX_init(
+          ctx_.get(), aead, reinterpret_cast<const uint8_t*>(key_value.data()),
+          key_value.size(), EVP_AEAD_DEFAULT_TAG_LENGTH, nullptr) != 1) {
+    return util::Status(util::error::INTERNAL,
+                        "could not initialize EVP_AEAD_CTX");
+  }
+  return util::OkStatus();
+}
 
 util::StatusOr<std::unique_ptr<Aead>> AesGcmBoringSsl::New(
     absl::string_view key_value) {
-  const EVP_CIPHER* cipher = GetCipherForKeySize(key_value.size());
-  if (cipher == nullptr) {
-    return util::Status(util::error::INTERNAL, "invalid key size");
+  std::unique_ptr<AesGcmBoringSsl> aead(new AesGcmBoringSsl);
+  auto status = aead->Init(key_value);
+  if (!status.ok()) {
+    return status;
   }
-  std::unique_ptr<Aead> aead(new AesGcmBoringSsl(key_value, cipher));
-  return std::move(aead);
+  return util::StatusOr<std::unique_ptr<Aead>>(std::move(aead));
 }
 
 util::StatusOr<std::string> AesGcmBoringSsl::Encrypt(
-    absl::string_view plaintext,
-    absl::string_view additional_data) const {
-  bssl::UniquePtr<EVP_CIPHER_CTX> ctx(EVP_CIPHER_CTX_new());
-  // BoringSSL expects a non-null pointer for plaintext and additional_data,
-  // regardless of whether the size is 0.
-  if (plaintext.size() == 0 && plaintext.data() == nullptr) {
-    plaintext = absl::string_view("");
-  }
-  if (additional_data.size() == 0 && additional_data.data() == nullptr) {
-    additional_data = absl::string_view("");
-  }
-  if (ctx.get() == nullptr) {
-    return util::Status(util::error::INTERNAL,
-                        "could not initialize EVP_CIPHER_CTX");
-  }
-  int ret = EVP_EncryptInit_ex(ctx.get(), cipher_, nullptr /* engine */,
-                               nullptr /* key */, nullptr /* IV */);
-  if (ret != 1) {
-    return util::Status(util::error::INTERNAL, "EVP_EncryptInit_ex failed");
-  }
-  ret = EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, IV_SIZE_IN_BYTES,
-                            nullptr);
-  if (ret != 1) {
-    return util::Status(util::error::INTERNAL, "IV length not supported");
-  }
+    absl::string_view plaintext, absl::string_view additional_data) const {
   const std::string iv = Random::GetRandomBytes(IV_SIZE_IN_BYTES);
-  ret = EVP_EncryptInit_ex(ctx.get(), nullptr, nullptr,
-                           reinterpret_cast<const uint8_t*>(key_.data()),
-                           reinterpret_cast<const uint8_t*>(iv.data()));
-  if (ret != 1) {
-    return util::Status(util::error::INTERNAL, "Could not initialize ctx");
-  }
-  int len;
-  ret = EVP_EncryptUpdate(
-      ctx.get(), nullptr, &len,
-      reinterpret_cast<const uint8_t*>(additional_data.data()),
-      additional_data.size());
-  if (ret != 1) {
-    return util::Status(util::error::INTERNAL, "AAD is not supported");
-  }
-  size_t ciphertext_size = iv.size() + plaintext.size() + TAG_SIZE_IN_BYTES;
   // TODO(bleichen): Check if it is OK to work on a std::string.
   //   This is unclear since some compiler may use copy-on-write.
-  // Allocates 1 byte more than necessary because we may potentially access
-  // &ct[ciphertext_size] causing vector range check error.
-  std::vector<uint8_t> ct(ciphertext_size + 1);
-  memcpy(&ct[0], reinterpret_cast<const uint8_t*>(iv.data()), iv.size());
-  size_t written = iv.size();
-  ret = EVP_EncryptUpdate(ctx.get(), &ct[written], &len,
-                          reinterpret_cast<const uint8_t*>(plaintext.data()),
-                          plaintext.size());
-  if (ret != 1) {
+  std::vector<uint8_t> ct(iv.size() + plaintext.size() + TAG_SIZE_IN_BYTES);
+  memcpy(ct.data(), iv.data(), iv.size());
+  size_t len;
+  if (EVP_AEAD_CTX_seal(
+          ctx_.get(), ct.data() + iv.size(), &len, ct.size() - iv.size(),
+          reinterpret_cast<const uint8_t*>(iv.data()), iv.size(),
+          reinterpret_cast<const uint8_t*>(plaintext.data()), plaintext.size(),
+          reinterpret_cast<const uint8_t*>(additional_data.data()),
+          additional_data.size()) != 1) {
     return util::Status(util::error::INTERNAL, "Encryption failed");
   }
-  written += len;
-  ret = EVP_EncryptFinal_ex(ctx.get(), &ct[written], &len);
-  written += len;
-  if (ret != 1) {
-    return util::Status(util::error::INTERNAL, "EVP_EncryptFinal_ex failed");
-  }
-  ret = EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, TAG_SIZE_IN_BYTES,
-                            &ct[written]);
-  if (ret != 1) {
-    return util::Status(util::error::INTERNAL, "Could not compute tag");
-  }
-  written += TAG_SIZE_IN_BYTES;
-  if (written != ciphertext_size) {
-    return util::Status(util::error::INTERNAL, "Incorrect ciphertext size");
-  }
-  return std::string(reinterpret_cast<const char*>(&ct[0]), written);
+  return std::string(reinterpret_cast<const char*>(ct.data()), iv.size() + len);
 }
 
 util::StatusOr<std::string> AesGcmBoringSsl::Decrypt(
-    absl::string_view ciphertext,
-    absl::string_view additional_data) const {
-  // BoringSSL expects a non-null pointer for additional_data,
-  // regardless of whether the size is 0.
-  if (additional_data.size() == 0 && additional_data.data() == nullptr) {
-    additional_data = absl::string_view("");
-  }
-
+    absl::string_view ciphertext, absl::string_view additional_data) const {
   if (ciphertext.size() < IV_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES) {
     return util::Status(util::error::INTERNAL, "Ciphertext too short");
   }
 
-  bssl::UniquePtr<EVP_CIPHER_CTX> ctx(EVP_CIPHER_CTX_new());
-  if (ctx.get() == nullptr) {
-    return util::Status(util::error::INTERNAL,
-                        "could not initialize EVP_CIPHER_CTX");
-  }
-  // Set the cipher.
-  int ret = EVP_DecryptInit_ex(ctx.get(), cipher_, nullptr, nullptr, nullptr);
-  if (ret != 1) {
-    return util::Status(util::error::INTERNAL, "EVP_DecryptInit_ex failed");
-  }
-  // Set IV and tag length.
-  ret = EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, IV_SIZE_IN_BYTES,
-                            nullptr);
-  if (ret != 1) {
-    return util::Status(util::error::INTERNAL, "IV length not supported");
-  }
-
-  // Initialise key and IV
-  ret = EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr,
-                           reinterpret_cast<const uint8_t*>(key_.data()),
-                           reinterpret_cast<const uint8_t*>(ciphertext.data()));
-  if (ret != 1) {
-    return util::Status(util::error::INTERNAL, "Could not initialize key");
-  }
-
-  // EVP_DecryptUpdate expects a non-null pointer for additional_data,
-  // regardless of whether the size is 0.
-  if (additional_data.data() == nullptr) {
-    additional_data = absl::string_view("");
-  }
-  int len;
-  ret = EVP_DecryptUpdate(
-      ctx.get(), nullptr, &len,
-      reinterpret_cast<const uint8_t*>(additional_data.data()),
-      additional_data.size());
-  if (ret != 1) {
-    return util::Status(util::error::INTERNAL, "AAD is not supported");
-  }
-  size_t plaintext_size =
-      ciphertext.size() - IV_SIZE_IN_BYTES - TAG_SIZE_IN_BYTES;
-  // Allocates 1 byte more than necessary because we may potentially access
-  // &pt[plaintext_size] causing vector range check error.
-  std::vector<uint8_t> pt(plaintext_size + 1);
-  size_t read = IV_SIZE_IN_BYTES;
-  size_t written = 0;
-  ret = EVP_DecryptUpdate(
-      ctx.get(), &pt[written], &len,
-      reinterpret_cast<const uint8_t*>(&ciphertext.data()[read]),
-      plaintext_size);
-  if (ret != 1) {
-    return util::Status(util::error::INTERNAL, "Decryption failed");
-  }
-  written += len;
-
-  // Copy the tag since EVP_CIPHER_CTX_ctrl does not accept const pointers.
-  uint8_t tag[TAG_SIZE_IN_BYTES];
-  memcpy(tag, &ciphertext.data()[ciphertext.size() - TAG_SIZE_IN_BYTES],
-         TAG_SIZE_IN_BYTES);
-  ret = EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, TAG_SIZE_IN_BYTES,
-                            tag);
-  if (ret != 1) {
-    return util::Status(util::error::INTERNAL, "Could not set tag");
-  }
-
-  ret = EVP_DecryptFinal_ex(ctx.get(), &pt[written], &len);
-  written += len;
-  if (ret != 1) {
+  std::vector<uint8_t> pt(ciphertext.size() - IV_SIZE_IN_BYTES -
+                          TAG_SIZE_IN_BYTES);
+  size_t len;
+  if (EVP_AEAD_CTX_open(
+          ctx_.get(), pt.data(), &len, pt.size(),
+          // The nonce is the first |IV_SIZE_IN_BYTES| bytes of |ciphertext|.
+          reinterpret_cast<const uint8_t*>(ciphertext.data()), IV_SIZE_IN_BYTES,
+          // The input is the remainder.
+          reinterpret_cast<const uint8_t*>(ciphertext.data()) +
+              IV_SIZE_IN_BYTES,
+          ciphertext.size() - IV_SIZE_IN_BYTES,
+          reinterpret_cast<const uint8_t*>(additional_data.data()),
+          additional_data.size()) != 1) {
     return util::Status(util::error::INTERNAL, "Authentication failed");
   }
-  if (written != plaintext_size) {
-    return util::Status(util::error::INTERNAL, "Incorrect plaintext size");
-  }
-  return std::string(reinterpret_cast<const char*>(&pt[0]), written);
+  return std::string(reinterpret_cast<const char*>(pt.data()), len);
 }
 
 }  // namespace subtle
diff --git a/cc/subtle/aes_gcm_boringssl.h b/cc/subtle/aes_gcm_boringssl.h
index 2ae1fd4..6677623 100644
--- a/cc/subtle/aes_gcm_boringssl.h
+++ b/cc/subtle/aes_gcm_boringssl.h
@@ -23,7 +23,7 @@
 #include "tink/aead.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
-#include "openssl/evp.h"
+#include "openssl/aead.h"
 
 namespace crypto {
 namespace tink {
@@ -49,14 +49,9 @@
   static const int TAG_SIZE_IN_BYTES = 16;
 
   AesGcmBoringSsl() {}
-  AesGcmBoringSsl(absl::string_view key_value,
-                  const EVP_CIPHER *cipher);
+  crypto::tink::util::Status Init(absl::string_view key_value);
 
-  const std::string key_;
-  // cipher_ is a singleton owned by BoringSsl.
-  // Preferable would be to use the AEAD interface, but unfortunately this
-  // interface does not support 192-bit keys.
-  const EVP_CIPHER *cipher_;
+  bssl::ScopedEVP_AEAD_CTX ctx_;
 };
 
 }  // namespace subtle
diff --git a/cc/subtle/aes_gcm_boringssl_test.cc b/cc/subtle/aes_gcm_boringssl_test.cc
index 1a1ae6c..e6d63af 100644
--- a/cc/subtle/aes_gcm_boringssl_test.cc
+++ b/cc/subtle/aes_gcm_boringssl_test.cc
@@ -286,8 +286,3 @@
 }  // namespace subtle
 }  // namespace tink
 }  // namespace crypto
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/subtle/aes_gcm_siv_boringssl.cc b/cc/subtle/aes_gcm_siv_boringssl.cc
new file mode 100644
index 0000000..7cefcfb
--- /dev/null
+++ b/cc/subtle/aes_gcm_siv_boringssl.cc
@@ -0,0 +1,112 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/subtle/aes_gcm_siv_boringssl.h"
+
+#include <string>
+#include <vector>
+
+#include "openssl/aead.h"
+#include "openssl/err.h"
+#include "tink/aead.h"
+#include "tink/subtle/random.h"
+#include "tink/util/errors.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+
+static const EVP_AEAD* GetCipherForKeySize(int size_in_bytes) {
+  switch (size_in_bytes) {
+    case 16:
+      return EVP_aead_aes_128_gcm_siv();
+    case 32:
+      return EVP_aead_aes_256_gcm_siv();
+    default:
+      return nullptr;
+  }
+}
+
+util::Status AesGcmSivBoringSsl::Init(absl::string_view key_value) {
+  const EVP_AEAD* aead = GetCipherForKeySize(key_value.size());
+  if (aead == nullptr) {
+    return util::Status(util::error::INTERNAL, "invalid key size");
+  }
+  if (EVP_AEAD_CTX_init(
+          ctx_.get(), aead, reinterpret_cast<const uint8_t*>(key_value.data()),
+          key_value.size(), EVP_AEAD_DEFAULT_TAG_LENGTH, nullptr) != 1) {
+    return util::Status(util::error::INTERNAL,
+                        "could not initialize EVP_AEAD_CTX");
+  }
+  return util::OkStatus();
+}
+
+util::StatusOr<std::unique_ptr<Aead>> AesGcmSivBoringSsl::New(
+    absl::string_view key_value) {
+  std::unique_ptr<AesGcmSivBoringSsl> aead(new AesGcmSivBoringSsl);
+  auto status = aead->Init(key_value);
+  if (!status.ok()) {
+    return status;
+  }
+  return util::StatusOr<std::unique_ptr<Aead>>(std::move(aead));
+}
+
+util::StatusOr<std::string> AesGcmSivBoringSsl::Encrypt(
+    absl::string_view plaintext, absl::string_view additional_data) const {
+  const std::string iv = Random::GetRandomBytes(IV_SIZE_IN_BYTES);
+  std::vector<uint8_t> ct(iv.size() + plaintext.size() + TAG_SIZE_IN_BYTES);
+  memcpy(ct.data(), iv.data(), iv.size());
+  size_t len;
+  if (EVP_AEAD_CTX_seal(
+          ctx_.get(), ct.data() + iv.size(), &len, ct.size() - iv.size(),
+          reinterpret_cast<const uint8_t*>(iv.data()), iv.size(),
+          reinterpret_cast<const uint8_t*>(plaintext.data()), plaintext.size(),
+          reinterpret_cast<const uint8_t*>(additional_data.data()),
+          additional_data.size()) != 1) {
+    return util::Status(util::error::INTERNAL, "Encryption failed");
+  }
+  return std::string(reinterpret_cast<const char*>(ct.data()), iv.size() + len);
+}
+
+util::StatusOr<std::string> AesGcmSivBoringSsl::Decrypt(
+    absl::string_view ciphertext, absl::string_view additional_data) const {
+  if (ciphertext.size() < IV_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES) {
+    return util::Status(util::error::INTERNAL, "Ciphertext too short");
+  }
+
+  std::vector<uint8_t> pt(ciphertext.size() - IV_SIZE_IN_BYTES -
+                          TAG_SIZE_IN_BYTES);
+  size_t len;
+  if (EVP_AEAD_CTX_open(
+          ctx_.get(), pt.data(), &len, pt.size(),
+          // The nonce is the first |IV_SIZE_IN_BYTES| bytes of |ciphertext|.
+          reinterpret_cast<const uint8_t*>(ciphertext.data()), IV_SIZE_IN_BYTES,
+          // The input is the remainder.
+          reinterpret_cast<const uint8_t*>(ciphertext.data()) +
+              IV_SIZE_IN_BYTES,
+          ciphertext.size() - IV_SIZE_IN_BYTES,
+          reinterpret_cast<const uint8_t*>(additional_data.data()),
+          additional_data.size()) != 1) {
+    return util::Status(util::error::INTERNAL, "Authentication failed");
+  }
+  return std::string(reinterpret_cast<const char*>(pt.data()), len);
+}
+
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/subtle/aes_gcm_siv_boringssl.h b/cc/subtle/aes_gcm_siv_boringssl.h
new file mode 100644
index 0000000..94ac87f
--- /dev/null
+++ b/cc/subtle/aes_gcm_siv_boringssl.h
@@ -0,0 +1,78 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_SUBTLE_AES_GCM_SIV_BORINGSSL_H_
+#define TINK_SUBTLE_AES_GCM_SIV_BORINGSSL_H_
+
+#include <memory>
+
+#include "absl/strings/string_view.h"
+#include "openssl/aead.h"
+#include "tink/aead.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+
+// AES-GCM-SIV is based on the paper
+// “GCM-SIV: full nonce misuse-resistant authenticated encryption at under one
+// cycle per byte.” by S.Gueron, and Y.Lindell,
+// Proceedings of the 22nd ACM SIGSAC Conference on Computer and Communications
+// Security. ACM, 2015.
+// The implementation uses AES-GCM-SIV as defined in draft-irtf-cfrg-gcmsiv-08
+// https://datatracker.ietf.org/doc/draft-irtf-cfrg-gcmsiv/
+//
+// This encryption mode is intended for authenticated encryption with
+// additional authenticated data. A major security problem with AES-GCM is
+// that reusing the same nonce twice leaks the authentication key.
+// AES-GCM-SIV on the other hand has been designed to avoid this vulnerability.
+//
+// Usage bounds for the encryption mode can be found on
+// https://cyber.biu.ac.il/aes-gcm-siv/
+// or Section 6.3 of this paper:
+// https://eprint.iacr.org/2017/702.pdf
+class AesGcmSivBoringSsl : public Aead {
+ public:
+  static crypto::tink::util::StatusOr<std::unique_ptr<Aead>> New(
+      absl::string_view key_value);
+
+  crypto::tink::util::StatusOr<std::string> Encrypt(
+      absl::string_view plaintext,
+      absl::string_view additional_data) const override;
+
+  crypto::tink::util::StatusOr<std::string> Decrypt(
+      absl::string_view ciphertext,
+      absl::string_view additional_data) const override;
+
+  ~AesGcmSivBoringSsl() override {}
+
+ private:
+  static const int IV_SIZE_IN_BYTES = 12;
+  static const int TAG_SIZE_IN_BYTES = 16;
+
+  AesGcmSivBoringSsl() {}
+  crypto::tink::util::Status Init(absl::string_view key_value);
+
+  bssl::ScopedEVP_AEAD_CTX ctx_;
+};
+
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_SUBTLE_AES_GCM_SIV_BORINGSSL_H_
diff --git a/cc/subtle/aes_gcm_siv_boringssl_test.cc b/cc/subtle/aes_gcm_siv_boringssl_test.cc
new file mode 100644
index 0000000..15e86a1
--- /dev/null
+++ b/cc/subtle/aes_gcm_siv_boringssl_test.cc
@@ -0,0 +1,275 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/subtle/aes_gcm_siv_boringssl.h"
+
+#include <string>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "absl/strings/str_cat.h"
+#include "openssl/err.h"
+#include "include/rapidjson/document.h"
+#include "tink/subtle/wycheproof_util.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_util.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+namespace {
+
+TEST(AesGcmSivBoringSslTest, Basic) {
+  std::string key(test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
+  auto res = AesGcmSivBoringSsl::New(key);
+  EXPECT_TRUE(res.ok()) << res.status();
+  auto cipher = std::move(res.ValueOrDie());
+  std::string message = "Some data to encrypt.";
+  std::string aad = "Some data to authenticate.";
+  auto ct = cipher->Encrypt(message, aad);
+  EXPECT_TRUE(ct.ok()) << ct.status();
+  // The ciphertext is the concatenation of the nonce, the encrypted message
+  // and the tag.
+  const int TAG_SIZE = 16;
+  const int NONCE_SIZE = 12;
+  EXPECT_EQ(ct.ValueOrDie().size(), NONCE_SIZE + message.size() + TAG_SIZE);
+  auto pt = cipher->Decrypt(ct.ValueOrDie(), aad);
+  EXPECT_TRUE(pt.ok()) << pt.status();
+  EXPECT_EQ(pt.ValueOrDie(), message);
+}
+
+TEST(AesGcmSivBoringSslTest, Sizes) {
+  std::string key(test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
+  auto res = AesGcmSivBoringSsl::New(key);
+  EXPECT_TRUE(res.ok()) << res.status();
+  auto cipher = std::move(res.ValueOrDie());
+  // message size
+  std::string message;
+  std::string aad;
+  for (size_t size = 0; size < 1100; size++) {
+    message += static_cast<const char>(size % 101);
+    auto ct = cipher->Encrypt(message, aad);
+    EXPECT_TRUE(ct.ok()) << ct.status();
+    auto pt = cipher->Decrypt(ct.ValueOrDie(), aad);
+    EXPECT_TRUE(pt.ok()) << pt.status();
+    EXPECT_EQ(pt.ValueOrDie(), message);
+  }
+  // aad sizes
+  message = "";
+  for (size_t size = 0; size < 1100; size++) {
+    aad += static_cast<const char>(size % 101);
+    auto ct = cipher->Encrypt(message, aad);
+    EXPECT_TRUE(ct.ok()) << ct.status();
+    auto pt = cipher->Decrypt(ct.ValueOrDie(), aad);
+    EXPECT_TRUE(pt.ok()) << pt.status();
+    EXPECT_EQ(pt.ValueOrDie(), message);
+  }
+}
+
+TEST(AesGcmSivBoringSslTest, Modification) {
+  std::string key(test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
+  auto cipher = std::move(AesGcmSivBoringSsl::New(key).ValueOrDie());
+  std::string message = "Some data to encrypt.";
+  std::string aad = "Some data to authenticate.";
+  std::string ct = cipher->Encrypt(message, aad).ValueOrDie();
+  EXPECT_TRUE(cipher->Decrypt(ct, aad).ok());
+  // Modify the ciphertext
+  for (size_t i = 0; i < ct.size() * 8; i++) {
+    std::string modified_ct = ct;
+    modified_ct[i / 8] ^= 1 << (i % 8);
+    EXPECT_FALSE(cipher->Decrypt(modified_ct, aad).ok()) << i;
+  }
+  // Modify the additional data
+  for (size_t i = 0; i < aad.size() * 8; i++) {
+    std::string modified_aad = aad;
+    modified_aad[i / 8] ^= 1 << (i % 8);
+    auto decrypted = cipher->Decrypt(ct, modified_aad);
+    EXPECT_FALSE(decrypted.ok()) << i << " pt:" << decrypted.ValueOrDie();
+  }
+  // Truncate the ciphertext
+  for (size_t i = 0; i < ct.size(); i++) {
+    std::string truncated_ct(ct, 0, i);
+    EXPECT_FALSE(cipher->Decrypt(truncated_ct, aad).ok()) << i;
+  }
+  // ... and a final check that no modification corrupted the internal state.
+  EXPECT_TRUE(cipher->Decrypt(ct, aad).ok());
+}
+
+TEST(AesGcmSivBoringSslTest, AadEmptyVersusNullStringView) {
+  const std::string key(test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
+  auto cipher = std::move(AesGcmSivBoringSsl::New(key).ValueOrDie());
+  const std::string message = "Some data to encrypt.";
+  // Encryption
+  // AAD is a null string_view.
+  const absl::string_view aad;
+  auto ct0_or_status = cipher->Encrypt(message, aad);
+  EXPECT_TRUE(ct0_or_status.ok()) << ct0_or_status.status();
+  auto ct0 = ct0_or_status.ValueOrDie();
+  // AAD is a an empty std::string.
+  auto ct1_or_status = cipher->Encrypt(message, "");
+  EXPECT_TRUE(ct1_or_status.ok()) << ct1_or_status.status();
+  auto ct1 = ct1_or_status.ValueOrDie();
+  // AAD is a nullptr.
+  auto ct2_or_status = cipher->Encrypt(message, nullptr);
+  EXPECT_TRUE(ct2_or_status.ok()) << ct2_or_status.status();
+  auto ct2 = ct2_or_status.ValueOrDie();
+
+  // Decrypts all ciphertexts the different versions of AAD.
+  // AAD is a null string_view.
+  auto pt = cipher->Decrypt(ct0, aad);
+  EXPECT_TRUE(pt.ok()) << pt.status();
+  EXPECT_EQ(message, pt.ValueOrDie());
+  pt = cipher->Decrypt(ct1, aad);
+  EXPECT_TRUE(pt.ok()) << pt.status();
+  EXPECT_EQ(message, pt.ValueOrDie());
+  pt = cipher->Decrypt(ct2, aad);
+  EXPECT_TRUE(pt.ok()) << pt.status();
+  EXPECT_EQ(message, pt.ValueOrDie());
+
+  // AAD is a an empty std::string.
+  pt = cipher->Decrypt(ct0, "");
+  EXPECT_TRUE(pt.ok()) << pt.status();
+  EXPECT_EQ(message, pt.ValueOrDie());
+  pt = cipher->Decrypt(ct1, "");
+  EXPECT_TRUE(pt.ok()) << pt.status();
+  EXPECT_EQ(message, pt.ValueOrDie());
+  pt = cipher->Decrypt(ct2, "");
+  EXPECT_TRUE(pt.ok()) << pt.status();
+  EXPECT_EQ(message, pt.ValueOrDie());
+
+  // AAD is a nullptr.
+  pt = cipher->Decrypt(ct0, nullptr);
+  EXPECT_TRUE(pt.ok()) << pt.status();
+  EXPECT_EQ(message, pt.ValueOrDie());
+  pt = cipher->Decrypt(ct1, nullptr);
+  EXPECT_TRUE(pt.ok()) << pt.status();
+  EXPECT_EQ(message, pt.ValueOrDie());
+  pt = cipher->Decrypt(ct2, nullptr);
+  EXPECT_TRUE(pt.ok()) << pt.status();
+  EXPECT_EQ(message, pt.ValueOrDie());
+}
+
+TEST(AesGcmSivBoringSslTest, MessageEmptyVersusNullStringView) {
+  const std::string key(test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
+  auto cipher = std::move(AesGcmSivBoringSsl::New(key).ValueOrDie());
+  const std::string aad = "Some data to authenticate.";
+  const std::string nonce = test::HexDecodeOrDie("00112233445566778899aabb");
+  // Message is a null string_view.
+  const absl::string_view message0;
+  auto ct0_or_status = cipher->Encrypt(message0, aad);
+  EXPECT_TRUE(ct0_or_status.ok());
+  auto ct0 = ct0_or_status.ValueOrDie();
+  auto pt0_or_status = cipher->Decrypt(ct0, aad);
+  EXPECT_TRUE(pt0_or_status.ok()) << pt0_or_status.status();
+  auto pt0 = pt0_or_status.ValueOrDie();
+  EXPECT_EQ("", pt0);
+
+  // Message is an empty std::string.
+  const std::string message1 = "";
+  auto ct1_or_status = cipher->Encrypt(message1, aad);
+  EXPECT_TRUE(ct1_or_status.ok());
+  auto ct1 = ct1_or_status.ValueOrDie();
+  auto pt1_or_status = cipher->Decrypt(ct1, aad);
+  EXPECT_TRUE(pt1_or_status.ok()) << pt1_or_status.status();
+  auto pt1 = pt1_or_status.ValueOrDie();
+  EXPECT_EQ("", pt1);
+
+  // Message is a nullptr.
+  auto ct2_or_status = cipher->Encrypt(nullptr, aad);
+  EXPECT_TRUE(ct2_or_status.ok());
+  auto ct2 = ct2_or_status.ValueOrDie();
+  auto pt2_or_status = cipher->Decrypt(ct2, aad);
+  EXPECT_TRUE(pt2_or_status.ok()) << pt2_or_status.status();
+  auto pt2 = pt2_or_status.ValueOrDie();
+  EXPECT_EQ("", pt2);
+}
+
+TEST(AesGcmSivBoringSslTest, InvalidKeySizes) {
+  for (int keysize = 0; keysize < 65; keysize++) {
+    if (keysize == 16 || keysize == 32) {
+      continue;
+    }
+    std::string key(keysize, 'x');
+    auto cipher = AesGcmSivBoringSsl::New(key);
+    EXPECT_FALSE(cipher.ok());
+  }
+  absl::string_view null_string_view;
+  auto nokeycipher = AesGcmSivBoringSsl::New(null_string_view);
+  EXPECT_FALSE(nokeycipher.ok());
+}
+
+// Test with test vectors from Wycheproof project.
+bool WycheproofTest(const rapidjson::Document& root) {
+  int errors = 0;
+  for (const rapidjson::Value& test_group : root["testGroups"].GetArray()) {
+    const size_t iv_size = test_group["ivSize"].GetInt();
+    const size_t key_size = test_group["keySize"].GetInt();
+    const size_t tag_size = test_group["tagSize"].GetInt();
+    // AesGcmSivBoringSsl only supports 12-byte IVs and 16-byte authentication
+    // tag. Key sizes are either 128 bits or 256 bits. tink supports both.
+    // Invalid test vectors may contain other sizes.
+    if (key_size != 128 && key_size != 256) {
+      continue;
+    }
+    for (const rapidjson::Value& test : test_group["tests"].GetArray()) {
+      std::string comment = test["comment"].GetString();
+      std::string key = WycheproofUtil::GetBytes(test["key"]);
+      std::string nonce = WycheproofUtil::GetBytes(test["iv"]);
+      std::string msg = WycheproofUtil::GetBytes(test["msg"]);
+      std::string ct = WycheproofUtil::GetBytes(test["ct"]);
+      std::string aad = WycheproofUtil::GetBytes(test["aad"]);
+      std::string tag = WycheproofUtil::GetBytes(test["tag"]);
+      std::string id = absl::StrCat(test["tcId"].GetInt());
+      std::string expected = test["result"].GetString();
+      auto cipher = std::move(AesGcmSivBoringSsl::New(key).ValueOrDie());
+
+      // Tests decryption only, since the AEAD interface does
+      // not allow to set the nonce.
+      auto dec = cipher->Decrypt(nonce + ct + tag, aad);
+      if (dec.ok()) {
+        std::string decrypted = dec.ValueOrDie();
+        if (expected == "invalid") {
+          ADD_FAILURE() << "decrypted invalid ciphertext:" << id;
+          errors++;
+        } else if (msg != decrypted) {
+          ADD_FAILURE() << "Incorrect decryption:" << id;
+          errors++;
+        }
+      } else {
+        if (expected == "valid" || expected == "acceptable") {
+          ADD_FAILURE() << "Could not decrypt test with tcId:" << id
+                        << " iv_size:" << iv_size << " tag_size:" << tag_size
+                        << " key_size:" << key_size
+                        << " error:" << dec.status();
+          errors++;
+        }
+      }
+    }
+  }
+  return errors == 0;
+}
+
+TEST(AesGcmSivBoringSslTest, TestVectors) {
+  std::unique_ptr<rapidjson::Document> root =
+      WycheproofUtil::ReadTestVectors("aes_gcm_siv_test.json");
+  ASSERT_TRUE(WycheproofTest(*root));
+}
+
+}  // namespace
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/subtle/aes_siv_boringssl.cc b/cc/subtle/aes_siv_boringssl.cc
new file mode 100644
index 0000000..d8fc094
--- /dev/null
+++ b/cc/subtle/aes_siv_boringssl.cc
@@ -0,0 +1,232 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/subtle/aes_siv_boringssl.h"
+
+#include <string>
+#include <vector>
+
+#include "tink/deterministic_aead.h"
+#include "tink/util/errors.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "openssl/err.h"
+#include "openssl/aes.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+
+static void XorBlock(
+    const uint8_t x[16],
+    const uint8_t y[16],
+    uint8_t res[16]) {
+  for (int i = 0; i < 16; i++) {
+    res[i] = x[i] ^ y[i];
+  }
+}
+
+// static
+crypto::tink::util::StatusOr<std::unique_ptr<DeterministicAead>>
+AesSivBoringSsl::New(absl::string_view key_value) {
+  std::unique_ptr<AesSivBoringSsl> aes_siv(new AesSivBoringSsl());
+  if (aes_siv->SetKey(key_value)) {
+    return std::unique_ptr<DeterministicAead>(aes_siv.release());
+  } else {
+    return util::Status(util::error::INTERNAL, "invalid key size");
+  }
+}
+
+bool AesSivBoringSsl::SetKey(absl::string_view key) {
+  size_t key_size = key.size();
+  if (!IsValidKeySizeInBytes(key_size)) {
+    return false;
+  }
+  if (0 != AES_set_encrypt_key(
+              reinterpret_cast<const uint8_t*>(key.data()),
+              4 * key_size, &k1_)) {
+    return false;
+  }
+  if (0 != AES_set_encrypt_key(
+              reinterpret_cast<const uint8_t*>(key.data() + key_size / 2),
+              4 * key_size, &k2_)) {
+    return false;
+  }
+  uint8_t block[BLOCK_SIZE];
+  memset(block, 0, BLOCK_SIZE);
+  EncryptBlock(block, block);
+  MultiplyByX(block);
+  memcpy(cmac_k1_, block, BLOCK_SIZE);
+  MultiplyByX(block);
+  memcpy(cmac_k2_, block, BLOCK_SIZE);
+  return true;
+}
+
+void AesSivBoringSsl::CtrCrypt(const uint8_t siv[BLOCK_SIZE],
+                               const uint8_t *in, uint8_t *out,
+                               size_t size) const {
+  uint8_t iv[BLOCK_SIZE];
+  memcpy(iv, siv, BLOCK_SIZE);
+  iv[8] &= 0x7f;
+  iv[12] &= 0x7f;
+  unsigned int num = 0;
+  uint8_t ecount_buf[BLOCK_SIZE];
+  memset(ecount_buf, 0, BLOCK_SIZE);
+  AES_ctr128_encrypt(in, out, size, &k2_, iv, ecount_buf, &num);
+}
+
+void AesSivBoringSsl::EncryptBlock(const uint8_t in[BLOCK_SIZE],
+                                   uint8_t out[BLOCK_SIZE]) const {
+  AES_encrypt(in, out, &k1_);
+}
+
+// static
+void AesSivBoringSsl::MultiplyByX(uint8_t block[BLOCK_SIZE]) {
+  uint8_t carry = block[0] >> 7;
+  for (size_t i = 0; i < BLOCK_SIZE - 1; i++) {
+    block[i] = (block[i] << 1) | (block[i+1] >> 7);
+  }
+  block[BLOCK_SIZE - 1 ] = (block[BLOCK_SIZE - 1] << 1) ^ (carry ? 0x87 : 0);
+}
+
+void AesSivBoringSsl::Cmac(const uint8_t* data, size_t size,
+                           uint8_t mac[BLOCK_SIZE]) const {
+  size_t blocks = (size + BLOCK_SIZE - 1) / BLOCK_SIZE;
+  if (blocks == 0) {
+    blocks = 1;
+  }
+  size_t last_block_size = size - BLOCK_SIZE * (blocks - 1);
+  uint8_t block[BLOCK_SIZE];
+  memset(block, 0, BLOCK_SIZE);
+  size_t idx = 0;
+  for (size_t i = 0; i < blocks - 1; i++) {
+    XorBlock(block, &data[idx], block);
+    EncryptBlock(block, block);
+    idx += BLOCK_SIZE;
+  }
+  for (size_t j = 0; j < last_block_size; j++) {
+    block[j] ^= data[idx + j];
+  }
+  if (last_block_size == BLOCK_SIZE) {
+    XorBlock(block, cmac_k1_, block);
+  } else {
+    block[last_block_size] ^= 0x80;
+    XorBlock(block, cmac_k2_, block);
+  }
+  EncryptBlock(block, mac);
+}
+
+// Computes Cmac(XorEnd(data, last))
+void AesSivBoringSsl::CmacLong(
+    const uint8_t* data, size_t size, const uint8_t last[BLOCK_SIZE],
+    uint8_t mac[BLOCK_SIZE]) const {
+  uint8_t block[BLOCK_SIZE];
+  memcpy(block, data, BLOCK_SIZE);
+  size_t idx = BLOCK_SIZE;
+  while (BLOCK_SIZE <= size - idx) {
+    EncryptBlock(block, block);
+    XorBlock(block, &data[idx], block);
+    idx += BLOCK_SIZE;
+  }
+  size_t remaining = size - idx;
+  for (int j = 0; j < BLOCK_SIZE - remaining; ++j) {
+    block[remaining + j] ^= last[j];
+  }
+  if (remaining == 0) {
+    XorBlock(block, cmac_k1_, block);
+  } else {
+    EncryptBlock(block, block);
+    for (int j = 0; j < remaining; ++j) {
+      block[j] ^= last[BLOCK_SIZE - remaining + j];
+      block[j] ^= data[idx + j];
+    }
+    block[remaining] ^= 0x80;
+    XorBlock(block, cmac_k2_, block);
+  }
+  EncryptBlock(block, mac);
+}
+
+void AesSivBoringSsl::S2v(const uint8_t* aad, size_t aad_size,
+                          const uint8_t* msg, size_t msg_size,
+                          uint8_t siv[BLOCK_SIZE]) const {
+  // This stuff could be precomputed.
+  uint8_t block[BLOCK_SIZE];
+  memset(block, 0, BLOCK_SIZE);
+  Cmac(block, BLOCK_SIZE, block);
+  MultiplyByX(block);
+
+  uint8_t aad_mac[BLOCK_SIZE];
+  Cmac(aad, aad_size, aad_mac);
+  XorBlock(block, aad_mac, block);
+
+  if (msg_size >= BLOCK_SIZE) {
+    CmacLong(msg, msg_size, block, siv);
+  } else {
+    MultiplyByX(block);
+    for (size_t i = 0; i < msg_size; i++) {
+      block[i] ^= msg[i];
+    }
+    block[msg_size] ^= 0x80;
+    Cmac(block, BLOCK_SIZE, siv);
+  }
+}
+
+util::StatusOr<std::string> AesSivBoringSsl::EncryptDeterministically(
+    absl::string_view plaintext,
+    absl::string_view additional_data) const {
+  uint8_t siv[BLOCK_SIZE];
+  S2v(reinterpret_cast<const uint8_t*>(additional_data.data()),
+      additional_data.size(),
+      reinterpret_cast<const uint8_t*>(plaintext.data()),
+      plaintext.size(),
+      siv);
+  size_t ciphertext_size = plaintext.size() + BLOCK_SIZE;
+  std::vector<uint8_t> ct(ciphertext_size);
+  memcpy(ct.data(), siv, BLOCK_SIZE);
+  CtrCrypt(siv, reinterpret_cast<const uint8_t*>(plaintext.data()),
+           ct.data() + BLOCK_SIZE, plaintext.size());
+  return std::string(reinterpret_cast<const char*>(ct.data()), ciphertext_size);
+}
+
+util::StatusOr<std::string> AesSivBoringSsl::DecryptDeterministically(
+    absl::string_view ciphertext,
+    absl::string_view additional_data) const {
+  if (ciphertext.size() < BLOCK_SIZE) {
+    return util::Status(util::error::INVALID_ARGUMENT, "ciphertext too short");
+  }
+  size_t plaintext_size = ciphertext.size() - BLOCK_SIZE;
+  std::vector<uint8_t> pt(plaintext_size);
+  const uint8_t *siv = reinterpret_cast<const uint8_t*>(&ciphertext[0]);
+  const uint8_t *ct = reinterpret_cast<const uint8_t*>(&ciphertext[BLOCK_SIZE]);
+  CtrCrypt(siv, ct, pt.data(), plaintext_size);
+
+  uint8_t s2v[BLOCK_SIZE];
+  S2v(reinterpret_cast<const uint8_t*>(additional_data.data()),
+      additional_data.size(), pt.data(), plaintext_size, s2v);
+  // Compare the siv from the ciphertext with the recomputed siv
+  uint8_t diff = 0;
+  for (int i = 0; i < BLOCK_SIZE; ++i) {
+    diff |= siv[i] ^ s2v[i];
+  }
+  if (diff != 0) {
+    return util::Status(util::error::INVALID_ARGUMENT, "invalid ciphertext");
+  }
+  return std::string(reinterpret_cast<const char*>(pt.data()), plaintext_size);
+}
+
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/subtle/aes_siv_boringssl.h b/cc/subtle/aes_siv_boringssl.h
new file mode 100644
index 0000000..674d795
--- /dev/null
+++ b/cc/subtle/aes_siv_boringssl.h
@@ -0,0 +1,121 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_SUBTLE_AES_SIV_BORINGSSL_H_
+#define TINK_SUBTLE_AES_SIV_BORINGSSL_H_
+
+#include <memory>
+
+#include "absl/strings/string_view.h"
+#include "tink/deterministic_aead.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "openssl/aes.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+
+// AesSivBoringSsl is an implemenatation of AES-SIV-CMAC as defined in
+// https://tools.ietf.org/html/rfc5297 .
+// AesSivBoringSsl implements a deterministic encryption with additional
+// data (i.e. the DeterministicAead interface). Hence the implementation
+// below is restricted to one AD component.
+//
+// Thread safety: This class is thread safe and thus can be used
+// concurrently.
+//
+// Security:
+// =========
+// Chatterjee, Menezes and Sarkar analyze AES-SIV in Section 5.1 of
+// https://www.math.uwaterloo.ca/~ajmeneze/publications/tightness.pdf
+// Their analysis shows that AES-SIV is susceptible to an attack in
+// a multi-user setting. Concretely, if an attacker knows the encryption
+// of a message m encrypted and authenticated with k different keys,
+// then it is possible  to find one of the MAC keys in time 2^b / k
+// where b is the size of the MAC key. A consequence of this attack
+// is that 128-bit MAC keys give unsufficient security.
+// Since 192-bit AES keys are not supported by tink for voodoo reasons
+// and RFC 5297 only supports same size encryption and MAC keys this
+// implies that keys must be 64 bytes (2*256 bits) long.
+class AesSivBoringSsl : public DeterministicAead {
+ public:
+  static crypto::tink::util::StatusOr<std::unique_ptr<DeterministicAead>>
+  New(absl::string_view key_value);
+
+  crypto::tink::util::StatusOr<std::string> EncryptDeterministically(
+      absl::string_view plaintext,
+      absl::string_view additional_data) const override;
+
+  crypto::tink::util::StatusOr<std::string> DecryptDeterministically(
+      absl::string_view ciphertext,
+      absl::string_view additional_data) const override;
+
+  virtual ~AesSivBoringSsl() {}
+
+  static bool IsValidKeySizeInBytes(size_t size) {
+    return size == 64;
+  }
+
+ private:
+  static const size_t BLOCK_SIZE = 16;
+
+  AesSivBoringSsl() {}
+
+  // Sets the key and precomputes the sub keys of an instance.
+  // This method must be used only in New().
+  bool SetKey(absl::string_view key_value);
+
+  // Encrypts (or decrypts) the bytes in in using an SIV and
+  // writes the result to out.
+  void CtrCrypt(const uint8_t siv[BLOCK_SIZE],
+                const uint8_t *in, uint8_t *out,
+                size_t size) const;
+  // Encrypts a single block using k2_.
+  // This is used for CMACs.
+
+  void EncryptBlock(const uint8_t in[BLOCK_SIZE],
+                    uint8_t out[BLOCK_SIZE]) const;
+
+  // Computes a CMAC of some data.
+  void Cmac(const uint8_t* data, size_t size,
+            uint8_t mac[BLOCK_SIZE]) const;
+
+  // Computes CMAC(XorEnd(data, last)), where XorEnd
+  // xors the bytes in last to the last bytes in data.
+  // The size of the data must be at least 16 bytes.
+  void CmacLong(const uint8_t* data, size_t size,
+                const uint8_t last[BLOCK_SIZE],
+                uint8_t mac[BLOCK_SIZE]) const;
+
+  // Multiplying an element in GF(2^128) by its generator.
+  // This functions is incorrectly named "doubling" in section 2.3 of RFC 5297.
+  static void MultiplyByX(uint8_t block[BLOCK_SIZE]);
+
+  void S2v(const uint8_t* aad, size_t aad_size,
+           const uint8_t* msg, size_t msg_size,
+           uint8_t siv[BLOCK_SIZE]) const;
+  AES_KEY k1_;
+  AES_KEY k2_;
+  uint8_t cmac_k1_[BLOCK_SIZE];
+  uint8_t cmac_k2_[BLOCK_SIZE];
+};
+
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_SUBTLE_AES_SIV_BORINGSSL_H_
diff --git a/cc/subtle/aes_siv_boringssl_test.cc b/cc/subtle/aes_siv_boringssl_test.cc
new file mode 100644
index 0000000..7318661
--- /dev/null
+++ b/cc/subtle/aes_siv_boringssl_test.cc
@@ -0,0 +1,229 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/subtle/aes_siv_boringssl.h"
+
+#include <string>
+#include <vector>
+
+#include "tink/subtle/wycheproof_util.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_util.h"
+#include "gtest/gtest.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+namespace {
+
+TEST(AesSivBoringSslTest, testEncryptDecrypt) {
+  std::string key(test::HexDecodeOrDie(
+      "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
+      "00112233445566778899aabbccddeefff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"));
+  auto res = AesSivBoringSsl::New(key);
+  EXPECT_TRUE(res.ok()) << res.status();
+  auto cipher = std::move(res.ValueOrDie());
+  std::string aad = "Additional data";
+  std::string message = "Some data to encrypt.";
+  auto ct = cipher->EncryptDeterministically(message, aad);
+  EXPECT_TRUE(ct.ok()) << ct.status();
+  auto pt = cipher->DecryptDeterministically(ct.ValueOrDie(), aad);
+  EXPECT_TRUE(pt.ok()) << pt.status();
+  EXPECT_EQ(pt.ValueOrDie(), message);
+}
+
+TEST(AesSivBoringSslTest, testNullPtrStringView) {
+  std::string key(test::HexDecodeOrDie(
+      "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
+      "00112233445566778899aabbccddeefff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"));
+  auto res = AesSivBoringSsl::New(key);
+  EXPECT_TRUE(res.ok()) << res.status();
+  // Checks that a string_view initialized with a null ptr works.
+  auto cipher = std::move(res.ValueOrDie());
+  absl::string_view null(nullptr);
+  auto ct = cipher->EncryptDeterministically(null, null);
+  EXPECT_TRUE(ct.ok()) << ct.status();
+  auto pt = cipher->DecryptDeterministically(ct.ValueOrDie(), null);
+  EXPECT_TRUE(pt.ok()) << pt.status();
+  EXPECT_EQ("", pt.ValueOrDie());
+  // Decryption with ct == null should return an appropriate status.
+  pt = cipher->DecryptDeterministically(null, "");
+  EXPECT_FALSE(pt.ok());
+  // Additional data with an empty std::string view is the same an empty std::string.
+  std::string message("123456789abcdefghijklmnop");
+  ct = cipher->EncryptDeterministically(message, null);
+  pt = cipher->DecryptDeterministically(ct.ValueOrDie(), "");
+  EXPECT_TRUE(pt.ok()) << pt.status();
+  EXPECT_EQ(message, pt.ValueOrDie());
+  ct = cipher->EncryptDeterministically(message, "");
+  pt = cipher->DecryptDeterministically(ct.ValueOrDie(), null);
+  EXPECT_TRUE(pt.ok()) << pt.status();
+  EXPECT_EQ(message, pt.ValueOrDie());
+}
+
+// Only 64 byte key sizes are supported.
+TEST(AesSivBoringSslTest, testEncryptDecryptKeySizes) {
+  std::string keymaterial(test::HexDecodeOrDie(
+      "198371900187498172316311acf81d238ff7619873a61983d619c87b63a1987f"
+      "987131819803719b847126381cd763871638aa71638176328761287361231321"
+      "812731321de508761437195ff231765aa4913219873ac6918639816312130011"
+      "abc900bba11400187984719827431246bbab1231eb4145215ff7141436616beb"
+      "9817298148712fed3aab61000ff123313e"));
+  for (int keysize = 0; keysize <= keymaterial.size(); ++keysize){
+    std::string key = std::string(keymaterial, 0, keysize);
+    auto cipher = AesSivBoringSsl::New(key);
+    if (keysize == 64) {
+      EXPECT_TRUE(cipher.ok());
+    } else {
+      EXPECT_FALSE(cipher.ok()) << "Accepted invalid key size:" << keysize;
+    }
+  }
+}
+
+// Checks a range of message sizes.
+TEST(AesSivBoringSslTest, testEncryptDecryptMessageSize) {
+  std::string key(test::HexDecodeOrDie(
+      "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
+      "00112233445566778899aabbccddeefff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"));
+  auto res = AesSivBoringSsl::New(key);
+  EXPECT_TRUE(res.ok()) << res.status();
+  auto cipher = std::move(res.ValueOrDie());
+  std::string aad = "Additional data";
+  for (int i = 0; i < 1024; ++i) {
+    std::string message = std::string(i, 'a');
+    auto ct = cipher->EncryptDeterministically(message, aad);
+    EXPECT_TRUE(ct.ok()) << ct.status();
+    auto pt = cipher->DecryptDeterministically(ct.ValueOrDie(), aad);
+    EXPECT_TRUE(pt.ok()) << pt.status();
+    EXPECT_EQ(pt.ValueOrDie(), message);
+  }
+  for (int i = 1024; i < 100000; i+= 5000) {
+    std::string message = std::string(i, 'a');
+    auto ct = cipher->EncryptDeterministically(message, aad);
+    EXPECT_TRUE(ct.ok()) << ct.status();
+    auto pt = cipher->DecryptDeterministically(ct.ValueOrDie(), aad);
+    EXPECT_TRUE(pt.ok()) << pt.status();
+    EXPECT_EQ(pt.ValueOrDie(), message);
+  }
+}
+
+// Checks a range of aad sizes.
+TEST(AesSivBoringSslTest, testEncryptDecryptAadSize) {
+  std::string key(test::HexDecodeOrDie(
+      "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
+      "00112233445566778899aabbccddeefff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"));
+  auto res = AesSivBoringSsl::New(key);
+  EXPECT_TRUE(res.ok()) << res.status();
+  auto cipher = std::move(res.ValueOrDie());
+  std::string message = "Some plaintext";
+  for (int i = 0; i < 1028; ++i) {
+    std::string aad = std::string(i, 'a');
+    auto ct = cipher->EncryptDeterministically(message, aad);
+    EXPECT_TRUE(ct.ok()) << ct.status();
+    auto pt = cipher->DecryptDeterministically(ct.ValueOrDie(), aad);
+    EXPECT_TRUE(pt.ok()) << pt.status();
+    EXPECT_EQ(pt.ValueOrDie(), message);
+  }
+}
+
+TEST(AesSivBoringSslTest, testDecryptModification) {
+  std::string key(test::HexDecodeOrDie(
+      "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
+      "00112233445566778899aabbccddeefff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"));
+  auto res = AesSivBoringSsl::New(key);
+  EXPECT_TRUE(res.ok()) << res.status();
+  auto cipher = std::move(res.ValueOrDie());
+  std::string aad = "Additional data";
+  for (int i = 0; i < 50; ++i) {
+    std::string message = std::string(i, 'a');
+    auto ct = cipher->EncryptDeterministically(message, aad);
+    EXPECT_TRUE(ct.ok()) << ct.status();
+    std::string ciphertext = ct.ValueOrDie();
+    for (size_t b = 0; b < ciphertext.size(); ++b) {
+      for (int bit = 0; bit < 8; ++bit) {
+        std::string modified = ciphertext;
+        modified[b] ^= (1 << bit);
+        auto pt = cipher->DecryptDeterministically(modified, aad);
+        EXPECT_FALSE(pt.ok())
+            << "Modified ciphertext decrypted."
+            << " byte:" << b
+            << " bit:" << bit;
+      }
+    }
+  }
+}
+
+// Test with test vectors from project Wycheproof.
+void WycheproofTest(const rapidjson::Document &root) {
+  for (const rapidjson::Value& test_group : root["testGroups"].GetArray()) {
+    const size_t key_size = test_group["keySize"].GetInt();
+    if (!AesSivBoringSsl::IsValidKeySizeInBytes(key_size / 8)) {
+      // Currently the key size is restricted to two 256-bit AES keys.
+      continue;
+    }
+    for (const rapidjson::Value& test : test_group["tests"].GetArray()) {
+      std::string comment = test["comment"].GetString();
+      std::string key = WycheproofUtil::GetBytes(test["key"]);
+      std::string msg = WycheproofUtil::GetBytes(test["msg"]);
+      std::string ct = WycheproofUtil::GetBytes(test["ct"]);
+      std::string aad = WycheproofUtil::GetBytes(test["aad"]);
+      int id = test["tcId"].GetInt();
+      std::string result = test["result"].GetString();
+      auto cipher = std::move(AesSivBoringSsl::New(key).ValueOrDie());
+
+      // Test encryption.
+      // Encryption should always succeed since msg and aad are valid inputs.
+      std::string encrypted =
+          cipher->EncryptDeterministically(msg, aad).ValueOrDie();
+      std::string encrypted_hex = test::HexEncode(encrypted);
+      std::string ct_hex = test::HexEncode(ct);
+      if (result == "valid" || result == "acceptable") {
+        EXPECT_EQ(ct_hex, encrypted_hex)
+            << "incorrect encryption: " << id << " " << comment;
+      } else {
+        EXPECT_NE(ct_hex, encrypted_hex)
+            << "invalid encryption: " << id << " " << comment;
+      }
+
+      // Test decryption
+      auto decrypted = cipher->DecryptDeterministically(ct, aad);
+      if (decrypted.ok()) {
+        if (result == "invalid") {
+          ADD_FAILURE() << "decrypted invalid ciphertext:" << id;
+        } else {
+          EXPECT_EQ(test::HexEncode(msg),
+                    test::HexEncode(decrypted.ValueOrDie()))
+              << "incorrect decryption: " << id << " " << comment;
+        }
+      } else {
+        EXPECT_NE(result, "valid")
+            << "failed to decrypt: " << id << " " << comment;
+      }
+    }
+  }
+}
+
+TEST(AesSivBoringSslTest, TestVectors) {
+  std::unique_ptr<rapidjson::Document> root =
+      WycheproofUtil::ReadTestVectors("aes_siv_cmac_test.json");
+  WycheproofTest(*root);
+}
+
+}  // namespace
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/subtle/common_enums_test.cc b/cc/subtle/common_enums_test.cc
index 15dc557..e52757e 100644
--- a/cc/subtle/common_enums_test.cc
+++ b/cc/subtle/common_enums_test.cc
@@ -54,8 +54,3 @@
 }  // namespace subtle
 }  // namespace tink
 }  // namespace crypto
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/subtle/ec_util_test.cc b/cc/subtle/ec_util_test.cc
index c4dedd6..8c5f747 100644
--- a/cc/subtle/ec_util_test.cc
+++ b/cc/subtle/ec_util_test.cc
@@ -69,8 +69,3 @@
 }  // namespace subtle
 }  // namespace tink
 }  // namespace crypto
-
-int main(int argc, char* argv[]) {
-  testing::InitGoogleTest(&argc, argv);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/subtle/ecdsa_sign_boringssl.cc b/cc/subtle/ecdsa_sign_boringssl.cc
index 87a7331..584f58b 100644
--- a/cc/subtle/ecdsa_sign_boringssl.cc
+++ b/cc/subtle/ecdsa_sign_boringssl.cc
@@ -109,13 +109,14 @@
 
   // Sign.
   std::unique_ptr<EcdsaSignBoringSsl> sign(
-      new EcdsaSignBoringSsl(key.release(), hash, encoding));
+      new EcdsaSignBoringSsl(std::move(key), hash, encoding));
   return std::move(sign);
 }
 
-EcdsaSignBoringSsl::EcdsaSignBoringSsl(EC_KEY* key, const EVP_MD* hash,
+EcdsaSignBoringSsl::EcdsaSignBoringSsl(bssl::UniquePtr<EC_KEY> key,
+                                       const EVP_MD* hash,
                                        EcdsaSignatureEncoding encoding)
-    : key_(key), hash_(hash), encoding_(encoding) {}
+    : key_(std::move(key)), hash_(hash), encoding_(encoding) {}
 
 util::StatusOr<std::string> EcdsaSignBoringSsl::Sign(
     absl::string_view data) const {
diff --git a/cc/subtle/ecdsa_sign_boringssl.h b/cc/subtle/ecdsa_sign_boringssl.h
index 86105c0..03f1280 100644
--- a/cc/subtle/ecdsa_sign_boringssl.h
+++ b/cc/subtle/ecdsa_sign_boringssl.h
@@ -34,7 +34,6 @@
 // ECDSA signing using Boring SSL, generating signatures in DER-encoding.
 class EcdsaSignBoringSsl : public PublicKeySign {
  public:
- public:
   static crypto::tink::util::StatusOr<std::unique_ptr<EcdsaSignBoringSsl>> New(
       const SubtleUtilBoringSSL::EcKey& ec_key, HashType hash_type,
       EcdsaSignatureEncoding encoding);
@@ -46,7 +45,7 @@
   virtual ~EcdsaSignBoringSsl() {}
 
  private:
-  EcdsaSignBoringSsl(EC_KEY* key, const EVP_MD* hash,
+  EcdsaSignBoringSsl(bssl::UniquePtr<EC_KEY> key, const EVP_MD* hash,
                      EcdsaSignatureEncoding encoding);
 
   bssl::UniquePtr<EC_KEY> key_;
diff --git a/cc/subtle/ecdsa_sign_boringssl_test.cc b/cc/subtle/ecdsa_sign_boringssl_test.cc
index 6a8304c..c9d7648 100644
--- a/cc/subtle/ecdsa_sign_boringssl_test.cc
+++ b/cc/subtle/ecdsa_sign_boringssl_test.cc
@@ -143,8 +143,3 @@
 }  // namespace subtle
 }  // namespace tink
 }  // namespace crypto
-
-int main(int ac, char *av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/subtle/ecdsa_verify_boringssl.cc b/cc/subtle/ecdsa_verify_boringssl.cc
index 7dcb714..7d08ca0 100644
--- a/cc/subtle/ecdsa_verify_boringssl.cc
+++ b/cc/subtle/ecdsa_verify_boringssl.cc
@@ -83,15 +83,6 @@
 util::StatusOr<std::unique_ptr<EcdsaVerifyBoringSsl>> EcdsaVerifyBoringSsl::New(
     const SubtleUtilBoringSSL::EcKey& ec_key, HashType hash_type,
     EcdsaSignatureEncoding encoding) {
-  // Check hash.
-  auto hash_status = SubtleUtilBoringSSL::ValidateSignatureHash(hash_type);
-  if (!hash_status.ok()) {
-    return hash_status;
-  }
-  auto hash_result = SubtleUtilBoringSSL::EvpHash(hash_type);
-  if (!hash_result.ok()) return hash_result.status();
-  const EVP_MD* hash = hash_result.ValueOrDie();
-
   // Check curve.
   auto group_result(SubtleUtilBoringSSL::GetEcGroup(ec_key.curve));
   if (!group_result.ok()) return group_result.status();
@@ -109,8 +100,23 @@
                         absl::StrCat("Invalid public key: ",
                                      SubtleUtilBoringSSL::GetErrors()));
   }
+  return New(std::move(key), hash_type, encoding);
+}
+
+// static
+util::StatusOr<std::unique_ptr<EcdsaVerifyBoringSsl>> EcdsaVerifyBoringSsl::New(
+    bssl::UniquePtr<EC_KEY> ec_key, HashType hash_type,
+    EcdsaSignatureEncoding encoding) {
+  // Check hash.
+  auto hash_status = SubtleUtilBoringSSL::ValidateSignatureHash(hash_type);
+  if (!hash_status.ok()) {
+    return hash_status;
+  }
+  auto hash_result = SubtleUtilBoringSSL::EvpHash(hash_type);
+  if (!hash_result.ok()) return hash_result.status();
+  const EVP_MD* hash = hash_result.ValueOrDie();
   std::unique_ptr<EcdsaVerifyBoringSsl> verify(
-      new EcdsaVerifyBoringSsl(key.release(), hash, encoding));
+      new EcdsaVerifyBoringSsl(ec_key.release(), hash, encoding));
   return std::move(verify);
 }
 
@@ -147,7 +153,8 @@
                         reinterpret_cast<const uint8_t*>(derSig.data()),
                         derSig.size(), key_.get())) {
     // signature is invalid
-    return util::Status(util::error::UNKNOWN, "Signature is not valid.");
+    return util::Status(util::error::INVALID_ARGUMENT,
+                        "Signature is not valid.");
   }
   // signature is valid
   return util::Status::OK;
diff --git a/cc/subtle/ecdsa_verify_boringssl.h b/cc/subtle/ecdsa_verify_boringssl.h
index 7df7317..a8e0e65 100644
--- a/cc/subtle/ecdsa_verify_boringssl.h
+++ b/cc/subtle/ecdsa_verify_boringssl.h
@@ -38,6 +38,10 @@
   New(const SubtleUtilBoringSSL::EcKey& ec_key, HashType hash_type,
       EcdsaSignatureEncoding encoding);
 
+  static crypto::tink::util::StatusOr<std::unique_ptr<EcdsaVerifyBoringSsl>>
+  New(bssl::UniquePtr<EC_KEY> ec_key, HashType hash_type,
+      EcdsaSignatureEncoding encoding);
+
   // Verifies that 'signature' is a digital signature for 'data'.
   crypto::tink::util::Status Verify(
       absl::string_view signature,
diff --git a/cc/subtle/ecdsa_verify_boringssl_test.cc b/cc/subtle/ecdsa_verify_boringssl_test.cc
index 97ea9d6..d11609f 100644
--- a/cc/subtle/ecdsa_verify_boringssl_test.cc
+++ b/cc/subtle/ecdsa_verify_boringssl_test.cc
@@ -16,6 +16,7 @@
 
 #include "tink/subtle/ecdsa_verify_boringssl.h"
 
+#include <iostream>
 #include <string>
 
 #include "absl/strings/str_cat.h"
@@ -38,7 +39,7 @@
 
 class EcdsaVerifyBoringSslTest : public ::testing::Test {};
 
-TEST_F(EcdsaVerifyBoringSslTest, testBasicSigning) {
+TEST_F(EcdsaVerifyBoringSslTest, BasicSigning) {
   subtle::EcdsaSignatureEncoding encodings[2] = {
       EcdsaSignatureEncoding::DER, EcdsaSignatureEncoding::IEEE_P1363};
   for (EcdsaSignatureEncoding encoding : encodings) {
@@ -76,7 +77,7 @@
   }
 }
 
-TEST_F(EcdsaVerifyBoringSslTest, testEncodingsMismatch) {
+TEST_F(EcdsaVerifyBoringSslTest, EncodingsMismatch) {
   subtle::EcdsaSignatureEncoding encodings[2] = {
       EcdsaSignatureEncoding::DER, EcdsaSignatureEncoding::IEEE_P1363};
   for (EcdsaSignatureEncoding encoding : encodings) {
@@ -108,7 +109,7 @@
   }
 }
 
-TEST_F(EcdsaVerifyBoringSslTest, testNewErrors) {
+TEST_F(EcdsaVerifyBoringSslTest, NewErrors) {
   auto ec_key = SubtleUtilBoringSSL::GetNewEcKey(EllipticCurveType::NIST_P256)
                     .ValueOrDie();
   auto verifier_result = EcdsaVerifyBoringSsl::New(
@@ -116,34 +117,12 @@
   EXPECT_FALSE(verifier_result.ok()) << verifier_result.status();
 }
 
-// Integers in Wycheproof are represented as signed bigendian hexadecimal
-// strings in twos complement representation.
-// Integers in EcKey are unsigned and are represented as an array of bytes
-// using bigendian order.
-// GetInteger can assume that val is always 0 or a positive integer, since
-// they are values from the key: a convention in Wycheproof is that parameters
-// in the test group are valid, only values in the test vector itself may
-// be invalid.
-static std::string GetInteger(const rapidjson::Value &val) {
-  std::string hex(val.GetString());
-  // Since val is a hexadecimal integer it can have an odd length.
-  if (hex.size() % 2 == 1) {
-    // Avoid a leading 0 byte.
-    if (hex[0] == '0') {
-      hex = std::string(hex, 1, hex.size()-1);
-    } else {
-      hex = "0" + hex;
-    }
-  }
-  return test::HexDecodeOrDie(hex);
-}
-
 static util::StatusOr<std::unique_ptr<EcdsaVerifyBoringSsl>> GetVerifier(
     const rapidjson::Value& test_group,
     subtle::EcdsaSignatureEncoding encoding) {
   SubtleUtilBoringSSL::EcKey key;
-  key.pub_x = GetInteger(test_group["key"]["wx"]);
-  key.pub_y = GetInteger(test_group["key"]["wy"]);
+  key.pub_x = WycheproofUtil::GetInteger(test_group["key"]["wx"]);
+  key.pub_y = WycheproofUtil::GetInteger(test_group["key"]["wy"]);
   key.curve = WycheproofUtil::GetEllipticCurveType(test_group["key"]["curve"]);
   HashType md = WycheproofUtil::GetHashType(test_group["sha"]);
   auto result = EcdsaVerifyBoringSsl::New(key, md, encoding);
@@ -173,7 +152,7 @@
       std::string curve = test_group["key"]["curve"].GetString();
       if (allow_skipping) {
         std::cout << "Could not construct verifier for curve " << curve
-                << verifier_result.status();
+                  << verifier_result.status();
       } else {
         ADD_FAILURE() << "Could not construct verifier for curve " << curve
                       << verifier_result.status();
@@ -223,22 +202,22 @@
   return failed_tests == 0;
 }
 
-TEST_F(EcdsaVerifyBoringSslTest, testVectorsNistP256) {
+TEST_F(EcdsaVerifyBoringSslTest, WycheproofCurveP256) {
   ASSERT_TRUE(TestSignatures("ecdsa_secp256r1_sha256_test.json", false,
                              subtle::EcdsaSignatureEncoding::DER));
 }
 
-TEST_F(EcdsaVerifyBoringSslTest, testVectorsNistP384) {
+TEST_F(EcdsaVerifyBoringSslTest, WycheproofCurveP384) {
   ASSERT_TRUE(TestSignatures("ecdsa_secp384r1_sha512_test.json", false,
                              subtle::EcdsaSignatureEncoding::DER));
 }
 
-TEST_F(EcdsaVerifyBoringSslTest, testVectorsNistP521) {
+TEST_F(EcdsaVerifyBoringSslTest, WycheproofCurveP521) {
   ASSERT_TRUE(TestSignatures("ecdsa_secp521r1_sha512_test.json", false,
                              subtle::EcdsaSignatureEncoding::DER));
 }
 
-TEST_F(EcdsaVerifyBoringSslTest, testVectorsWithIeeeP1363Encoding) {
+TEST_F(EcdsaVerifyBoringSslTest, WycheproofWithIeeeP1363Encoding) {
   ASSERT_TRUE(TestSignatures("ecdsa_webcrypto_test.json", true,
                              subtle::EcdsaSignatureEncoding::IEEE_P1363));
 }
@@ -247,8 +226,3 @@
 }  // namespace subtle
 }  // namespace tink
 }  // namespace crypto
-
-int main(int ac, char *av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/subtle/ecies_hkdf_recipient_kem_boringssl_test.cc b/cc/subtle/ecies_hkdf_recipient_kem_boringssl_test.cc
index 6fee122..7e9f263 100644
--- a/cc/subtle/ecies_hkdf_recipient_kem_boringssl_test.cc
+++ b/cc/subtle/ecies_hkdf_recipient_kem_boringssl_test.cc
@@ -70,8 +70,3 @@
 }  // namespace subtle
 }  // namespace tink
 }  // namespace crypto
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/subtle/ecies_hkdf_sender_kem_boringssl_test.cc b/cc/subtle/ecies_hkdf_sender_kem_boringssl_test.cc
index c3fd405..3377994 100644
--- a/cc/subtle/ecies_hkdf_sender_kem_boringssl_test.cc
+++ b/cc/subtle/ecies_hkdf_sender_kem_boringssl_test.cc
@@ -14,6 +14,8 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
+#include <iostream>
+
 #include "tink/subtle/ecies_hkdf_sender_kem_boringssl.h"
 #include "tink/subtle/common_enums.h"
 #include "tink/subtle/ecies_hkdf_recipient_kem_boringssl.h"
@@ -80,8 +82,3 @@
 }  // namespace subtle
 }  // namespace tink
 }  // namespace crypto
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/subtle/ed25519_sign_boringssl.cc b/cc/subtle/ed25519_sign_boringssl.cc
new file mode 100644
index 0000000..f880d26
--- /dev/null
+++ b/cc/subtle/ed25519_sign_boringssl.cc
@@ -0,0 +1,67 @@
+// Copyright 2019 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/subtle/ed25519_sign_boringssl.h"
+
+#include <cstring>
+
+#include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
+#include "openssl/curve25519.h"
+#include "tink/public_key_sign.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+
+// static
+util::StatusOr<std::unique_ptr<PublicKeySign>> Ed25519SignBoringSsl::New(
+    absl::string_view private_key) {
+  if (private_key.length() != ED25519_PRIVATE_KEY_LEN) {
+    return util::Status(
+        util::error::INVALID_ARGUMENT,
+        absl::StrFormat("Invalid ED25519 private key size (%d). "
+                        "The only valid size is %d.",
+                        private_key.length(), ED25519_PRIVATE_KEY_LEN));
+  }
+  std::unique_ptr<PublicKeySign> sign(new Ed25519SignBoringSsl(private_key));
+  return std::move(sign);
+}
+
+Ed25519SignBoringSsl::Ed25519SignBoringSsl(absl::string_view private_key)
+    : private_key_(private_key) {}
+
+util::StatusOr<std::string> Ed25519SignBoringSsl::Sign(
+    absl::string_view data) const {
+  data = SubtleUtilBoringSSL::EnsureNonNull(data);
+
+  uint8_t out_sig[ED25519_SIGNATURE_LEN];
+  std::memset(reinterpret_cast<void *>(&out_sig), 0, ED25519_SIGNATURE_LEN);
+
+  if (ED25519_sign(
+          out_sig, reinterpret_cast<const uint8_t *>(data.data()), data.size(),
+          reinterpret_cast<const uint8_t *>(private_key_.data())) != 1) {
+    return util::Status(util::error::INTERNAL, "Signing failed.");
+  }
+
+  return std::string(reinterpret_cast<char *>(out_sig), ED25519_SIGNATURE_LEN);
+}
+
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/subtle/ed25519_sign_boringssl.h b/cc/subtle/ed25519_sign_boringssl.h
new file mode 100644
index 0000000..8eadd79
--- /dev/null
+++ b/cc/subtle/ed25519_sign_boringssl.h
@@ -0,0 +1,52 @@
+// Copyright 2019 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_SUBTLE_ED25519_SIGN_BORINGSSL_H_
+#define TINK_SUBTLE_ED25519_SIGN_BORINGSSL_H_
+
+#include <memory>
+
+#include "absl/strings/string_view.h"
+#include "openssl/curve25519.h"
+#include "tink/public_key_sign.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+
+class Ed25519SignBoringSsl : public PublicKeySign {
+ public:
+  static crypto::tink::util::StatusOr<std::unique_ptr<PublicKeySign>> New(
+      absl::string_view private_key);
+
+  // Computes the signature for 'data'.
+  crypto::tink::util::StatusOr<std::string> Sign(
+      absl::string_view data) const override;
+
+  ~Ed25519SignBoringSsl() override = default;
+
+ private:
+  const std::string private_key_;
+
+  explicit Ed25519SignBoringSsl(absl::string_view private_key);
+};
+
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_SUBTLE_ED25519_SIGN_BORINGSSL_H_
diff --git a/cc/subtle/ed25519_sign_boringssl_test.cc b/cc/subtle/ed25519_sign_boringssl_test.cc
new file mode 100644
index 0000000..1e640f0
--- /dev/null
+++ b/cc/subtle/ed25519_sign_boringssl_test.cc
@@ -0,0 +1,312 @@
+// Copyright 2019 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/subtle/ed25519_sign_boringssl.h"
+
+#include <string>
+
+#include "gtest/gtest.h"
+#include "absl/strings/str_cat.h"
+#include "openssl/curve25519.h"
+#include "tink/public_key_sign.h"
+#include "tink/public_key_verify.h"
+#include "tink/subtle/ed25519_verify_boringssl.h"
+#include "tink/subtle/random.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_util.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+namespace {
+
+class Ed25519SignBoringSslTest : public ::testing::Test {};
+
+TEST_F(Ed25519SignBoringSslTest, testBasicSign) {
+  // Generate a new key pair.
+  uint8_t out_public_key[ED25519_PUBLIC_KEY_LEN];
+  uint8_t out_private_key[ED25519_PRIVATE_KEY_LEN];
+
+  ED25519_keypair(out_public_key, out_private_key);
+
+  std::string public_key(reinterpret_cast<const char *>(out_public_key),
+                    ED25519_PUBLIC_KEY_LEN);
+  std::string private_key(reinterpret_cast<const char *>(out_private_key),
+                     ED25519_PRIVATE_KEY_LEN);
+
+  // Create a new signer.
+  auto signer_result = Ed25519SignBoringSsl::New(private_key);
+  ASSERT_TRUE(signer_result.ok()) << signer_result.status();
+  auto signer = std::move(signer_result.ValueOrDie());
+
+  // Create a new verifier.
+  auto verifier_result = Ed25519VerifyBoringSsl::New(public_key);
+  ASSERT_TRUE(verifier_result.ok()) << verifier_result.status();
+  auto verifier = std::move(verifier_result.ValueOrDie());
+
+  // Sign a message.
+  std::string message = "some data to be signed";
+  std::string signature = signer->Sign(message).ValueOrDie();
+  EXPECT_NE(signature, message);
+  EXPECT_EQ(signature.size(), ED25519_SIGNATURE_LEN);
+  auto status = verifier->Verify(signature, message);
+  EXPECT_TRUE(status.ok()) << status;
+
+  status = verifier->Verify("some bad signature", message);
+  EXPECT_FALSE(status.ok());
+
+  status = verifier->Verify(signature, "some bad message");
+  EXPECT_FALSE(status.ok());
+
+  // Loop 100 times, sign a random message twice using the signer and verify
+  // that the signatures are the same.
+  for (size_t i = 0; i < 100; i++) {
+    message = subtle::Random::GetRandomBytes(i);
+    std::string signature1 = signer->Sign(message).ValueOrDie();
+    std::string signature2 = signer->Sign(message).ValueOrDie();
+    EXPECT_EQ(signature1, signature2);
+    // Verify that the signatures are valid.
+    status = verifier->Verify(signature1, message);
+    EXPECT_TRUE(status.ok()) << status;
+  }
+}
+
+TEST_F(Ed25519SignBoringSslTest, testInvalidPrivateKeys) {
+  // Null private key.
+  const absl::string_view null_private_key;
+  EXPECT_FALSE(Ed25519SignBoringSsl::New(null_private_key).ok());
+
+  for (int keysize = 0; keysize < 128; keysize++) {
+    if (keysize == ED25519_PRIVATE_KEY_LEN) {
+      // Valid key size.
+      continue;
+    }
+    std::string key(keysize, 'x');
+    EXPECT_FALSE(Ed25519SignBoringSsl::New(key).ok());
+  }
+}
+
+TEST_F(Ed25519SignBoringSslTest, testMessageEmptyVersusNullStringView) {
+  // Generate a new key pair.
+  uint8_t out_public_key[ED25519_PUBLIC_KEY_LEN];
+  uint8_t out_private_key[ED25519_PRIVATE_KEY_LEN];
+
+  ED25519_keypair(out_public_key, out_private_key);
+
+  std::string public_key(reinterpret_cast<const char *>(out_public_key),
+                    ED25519_PUBLIC_KEY_LEN);
+  std::string private_key(reinterpret_cast<const char *>(out_private_key),
+                     ED25519_PRIVATE_KEY_LEN);
+
+  // Create a new signer.
+  auto signer_result = Ed25519SignBoringSsl::New(private_key);
+  ASSERT_TRUE(signer_result.ok()) << signer_result.status();
+  auto signer = std::move(signer_result.ValueOrDie());
+
+  // Create a new verifier.
+  auto verifier_result = Ed25519VerifyBoringSsl::New(public_key);
+  ASSERT_TRUE(verifier_result.ok()) << verifier_result.status();
+  auto verifier = std::move(verifier_result.ValueOrDie());
+
+  // Message is a null string_view.
+  const absl::string_view empty_message;
+  auto signature = signer->Sign(empty_message).ValueOrDie();
+  EXPECT_NE(signature, empty_message);
+  EXPECT_EQ(signature.size(), ED25519_SIGNATURE_LEN);
+  auto status = verifier->Verify(signature, empty_message);
+  EXPECT_TRUE(status.ok()) << status;
+
+  // Message is an empty std::string.
+  const std::string message = "";
+  signature = signer->Sign(message).ValueOrDie();
+  EXPECT_EQ(signature.size(), ED25519_SIGNATURE_LEN);
+  EXPECT_NE(signature, message);
+  status = verifier->Verify(signature, message);
+  EXPECT_TRUE(status.ok()) << status;
+
+  // Message is a null ptr.
+  signature = signer->Sign(nullptr).ValueOrDie();
+  EXPECT_EQ(signature.size(), ED25519_SIGNATURE_LEN);
+  status = verifier->Verify(signature, nullptr);
+  EXPECT_TRUE(status.ok()) << status;
+}
+
+typedef struct testVector {
+  std::string public_key;
+  std::string private_key;
+  std::string expected_signature;
+  std::string message;
+} testVector;
+
+TEST_F(Ed25519SignBoringSslTest, testWithTestVectors) {
+  // These test vectors are taken from:
+  // https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-02#section-6.
+  testVector Ed25519Vectors[] = {
+      {
+          /*TEST 1*/
+          /*public_key= */ test::HexDecodeOrDie(
+              "d75a980182b10ab7d54bfed3c964073a"
+              "0ee172f3daa62325af021a68f707511a"),
+          /*private_key=*/
+          test::HexDecodeOrDie("9d61b19deffd5a60ba844af492ec2cc4"
+                               "4449c5697b326919703bac031cae7f60"),
+          /*signature = */
+          test::HexDecodeOrDie("e5564300c360ac729086e2cc806e828a"
+                               "84877f1eb8e5d974d873e06522490155"
+                               "5fb8821590a33bacc61e39701cf9b46b"
+                               "d25bf5f0595bbe24655141438e7a100b"),
+          /*message = */ "",
+      },
+      {
+          /*TEST 2*/
+          /*public_key= */ test::HexDecodeOrDie(
+              "3d4017c3e843895a92b70aa74d1b7ebc"
+              "9c982ccf2ec4968cc0cd55f12af4660c"),
+          /*private_key=*/
+          test::HexDecodeOrDie("4ccd089b28ff96da9db6c346ec114e0f"
+                               "5b8a319f35aba624da8cf6ed4fb8a6fb"),
+          /*signature = */
+          test::HexDecodeOrDie("92a009a9f0d4cab8720e820b5f642540"
+                               "a2b27b5416503f8fb3762223ebdb69da"
+                               "085ac1e43e15996e458f3613d0f11d8c"
+                               "387b2eaeb4302aeeb00d291612bb0c00"),
+          /*message = */ "\x72",
+      },
+      {
+          /*TEST 3*/
+          /*public_key= */ test::HexDecodeOrDie(
+              "fc51cd8e6218a1a38da47ed00230f058"
+              "0816ed13ba3303ac5deb911548908025"),
+          /*private_key=*/
+          test::HexDecodeOrDie("c5aa8df43f9f837bedb7442f31dcb7b1"
+                               "66d38535076f094b85ce3a2e0b4458f7"),
+          /*signature = */
+          test::HexDecodeOrDie("6291d657deec24024827e69c3abe01a3"
+                               "0ce548a284743a445e3680d7db5ac3ac"
+                               "18ff9b538d16f290ae67f760984dc659"
+                               "4a7c15e9716ed28dc027beceea1ec40a"),
+          /*message = */ "\xaf\x82",
+      },
+      {
+          /*TEST 1024*/
+          /*public_key= */ test::HexDecodeOrDie(
+              "278117fc144c72340f67d0f2316e8386"
+              "ceffbf2b2428c9c51fef7c597f1d426e"),
+          /*private_key=*/
+          test::HexDecodeOrDie("f5e5767cf153319517630f226876b86c"
+                               "8160cc583bc013744c6bf255f5cc0ee5"),
+          /*signature = */
+          test::HexDecodeOrDie("0aab4c900501b3e24d7cdf4663326a3a"
+                               "87df5e4843b2cbdb67cbf6e460fec350"
+                               "aa5371b1508f9f4528ecea23c436d94b"
+                               "5e8fcd4f681e30a6ac00a9704a188a03"),
+          /*message = */
+          test::HexDecodeOrDie("08b8b2b733424243760fe426a4b54908"
+                               "632110a66c2f6591eabd3345e3e4eb98"
+                               "fa6e264bf09efe12ee50f8f54e9f77b1"
+                               "e355f6c50544e23fb1433ddf73be84d8"
+                               "79de7c0046dc4996d9e773f4bc9efe57"
+                               "38829adb26c81b37c93a1b270b20329d"
+                               "658675fc6ea534e0810a4432826bf58c"
+                               "941efb65d57a338bbd2e26640f89ffbc"
+                               "1a858efcb8550ee3a5e1998bd177e93a"
+                               "7363c344fe6b199ee5d02e82d522c4fe"
+                               "ba15452f80288a821a579116ec6dad2b"
+                               "3b310da903401aa62100ab5d1a36553e"
+                               "06203b33890cc9b832f79ef80560ccb9"
+                               "a39ce767967ed628c6ad573cb116dbef"
+                               "efd75499da96bd68a8a97b928a8bbc10"
+                               "3b6621fcde2beca1231d206be6cd9ec7"
+                               "aff6f6c94fcd7204ed3455c68c83f4a4"
+                               "1da4af2b74ef5c53f1d8ac70bdcb7ed1"
+                               "85ce81bd84359d44254d95629e9855a9"
+                               "4a7c1958d1f8ada5d0532ed8a5aa3fb2"
+                               "d17ba70eb6248e594e1a2297acbbb39d"
+                               "502f1a8c6eb6f1ce22b3de1a1f40cc24"
+                               "554119a831a9aad6079cad88425de6bd"
+                               "e1a9187ebb6092cf67bf2b13fd65f270"
+                               "88d78b7e883c8759d2c4f5c65adb7553"
+                               "878ad575f9fad878e80a0c9ba63bcbcc"
+                               "2732e69485bbc9c90bfbd62481d9089b"
+                               "eccf80cfe2df16a2cf65bd92dd597b07"
+                               "07e0917af48bbb75fed413d238f5555a"
+                               "7a569d80c3414a8d0859dc65a46128ba"
+                               "b27af87a71314f318c782b23ebfe808b"
+                               "82b0ce26401d2e22f04d83d1255dc51a"
+                               "ddd3b75a2b1ae0784504df543af8969b"
+                               "e3ea7082ff7fc9888c144da2af58429e"
+                               "c96031dbcad3dad9af0dcbaaaf268cb8"
+                               "fcffead94f3c7ca495e056a9b47acdb7"
+                               "51fb73e666c6c655ade8297297d07ad1"
+                               "ba5e43f1bca32301651339e22904cc8c"
+                               "42f58c30c04aafdb038dda0847dd988d"
+                               "cda6f3bfd15c4b4c4525004aa06eeff8"
+                               "ca61783aacec57fb3d1f92b0fe2fd1a8"
+                               "5f6724517b65e614ad6808d6f6ee34df"
+                               "f7310fdc82aebfd904b01e1dc54b2927"
+                               "094b2db68d6f903b68401adebf5a7e08"
+                               "d78ff4ef5d63653a65040cf9bfd4aca7"
+                               "984a74d37145986780fc0b16ac451649"
+                               "de6188a7dbdf191f64b5fc5e2ab47b57"
+                               "f7f7276cd419c17a3ca8e1b939ae49e4"
+                               "88acba6b965610b5480109c8b17b80e1"
+                               "b7b750dfc7598d5d5011fd2dcc5600a3"
+                               "2ef5b52a1ecc820e308aa342721aac09"
+                               "43bf6686b64b2579376504ccc493d97e"
+                               "6aed3fb0f9cd71a43dd497f01f17c0e2"
+                               "cb3797aa2a2f256656168e6c496afc5f"
+                               "b93246f6b1116398a346f1a641f3b041"
+                               "e989f7914f90cc2c7fff357876e506b5"
+                               "0d334ba77c225bc307ba537152f3f161"
+                               "0e4eafe595f6d9d90d11faa933a15ef1"
+                               "369546868a7f3a45a96768d40fd9d034"
+                               "12c091c6315cf4fde7cb68606937380d"
+                               "b2eaaa707b4c4185c32eddcdd306705e"
+                               "4dc1ffc872eeee475a64dfac86aba41c"
+                               "0618983f8741c5ef68d3a101e8a3b8ca"
+                               "c60c905c15fc910840b94c00a0b9d0"),
+      },
+  };
+
+  for (const testVector v : Ed25519Vectors) {
+    // Add the public as a suffix to the private key. This is needed by the
+    // boringssl API.
+    std::string private_key = absl::StrCat(v.private_key, v.public_key);
+
+    // Create a new signer.
+    auto signer_result = Ed25519SignBoringSsl::New(private_key);
+    ASSERT_TRUE(signer_result.ok()) << signer_result.status();
+    auto signer = std::move(signer_result.ValueOrDie());
+
+    // Create a new verifier.
+    auto verifier_result = Ed25519VerifyBoringSsl::New(v.public_key);
+    ASSERT_TRUE(verifier_result.ok()) << verifier_result.status();
+    auto verifier = std::move(verifier_result.ValueOrDie());
+
+    std::string signature = signer->Sign(v.message).ValueOrDie();
+    EXPECT_TRUE(signature == v.expected_signature);
+
+    auto status = verifier->Verify(signature, v.message);
+    EXPECT_TRUE(status.ok()) << status;
+  }
+}
+
+}  // namespace
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/subtle/ed25519_verify_boringssl.cc b/cc/subtle/ed25519_verify_boringssl.cc
new file mode 100644
index 0000000..288f8bf
--- /dev/null
+++ b/cc/subtle/ed25519_verify_boringssl.cc
@@ -0,0 +1,76 @@
+// Copyright 2019 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/subtle/ed25519_verify_boringssl.h"
+
+#include <cstring>
+
+#include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
+#include "openssl/curve25519.h"
+#include "tink/public_key_verify.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+
+// static
+util::StatusOr<std::unique_ptr<PublicKeyVerify>> Ed25519VerifyBoringSsl::New(
+    absl::string_view public_key) {
+  if (public_key.length() != ED25519_PUBLIC_KEY_LEN) {
+    return util::Status(
+        util::error::INVALID_ARGUMENT,
+        absl::StrFormat("Invalid ED25519 public key size (%d). "
+                        "The only valid size is %d.",
+                        public_key.length(), ED25519_PUBLIC_KEY_LEN));
+  }
+  std::unique_ptr<PublicKeyVerify> verify(
+      new Ed25519VerifyBoringSsl(public_key));
+  return std::move(verify);
+}
+
+Ed25519VerifyBoringSsl::Ed25519VerifyBoringSsl(absl::string_view public_key)
+    : public_key_(public_key) {}
+
+util::Status Ed25519VerifyBoringSsl::Verify(absl::string_view signature,
+                                            absl::string_view data) const {
+  signature = SubtleUtilBoringSSL::EnsureNonNull(signature);
+  data = SubtleUtilBoringSSL::EnsureNonNull(data);
+
+  if (signature.size() != ED25519_SIGNATURE_LEN) {
+    return util::Status(
+        util::error::INVALID_ARGUMENT,
+        absl::StrFormat("Invalid ED25519 signature size (%d). "
+                        "The signature must be %d bytes long.",
+                        signature.size(), ED25519_SIGNATURE_LEN));
+  }
+
+  if (1 != ED25519_verify(
+               reinterpret_cast<const uint8_t *>(data.data()), data.size(),
+               reinterpret_cast<const uint8_t *>(signature.data()),
+               reinterpret_cast<const uint8_t *>(public_key_.data()))) {
+    return util::Status(util::error::INVALID_ARGUMENT,
+                        "Signature is not valid.");
+  }
+
+  return util::Status::OK;
+}
+
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/subtle/ed25519_verify_boringssl.h b/cc/subtle/ed25519_verify_boringssl.h
new file mode 100644
index 0000000..a02e51b
--- /dev/null
+++ b/cc/subtle/ed25519_verify_boringssl.h
@@ -0,0 +1,52 @@
+// Copyright 2019 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_SUBTLE_ED25519_VERIFY_BORINGSSL_H_
+#define TINK_SUBTLE_ED25519_VERIFY_BORINGSSL_H_
+
+#include <memory>
+
+#include "absl/strings/string_view.h"
+#include "openssl/curve25519.h"
+#include "tink/public_key_verify.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+
+class Ed25519VerifyBoringSsl : public PublicKeyVerify {
+ public:
+  static crypto::tink::util::StatusOr<std::unique_ptr<PublicKeyVerify>> New(
+      absl::string_view public_key);
+
+  // Verifies that 'signature' is a digital signature for 'data'.
+  crypto::tink::util::Status Verify(absl::string_view signature,
+                                    absl::string_view data) const override;
+
+  ~Ed25519VerifyBoringSsl() override = default;
+
+ private:
+  const std::string public_key_;
+
+  explicit Ed25519VerifyBoringSsl(absl::string_view public_key);
+};
+
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_SUBTLE_ED25519_VERIFY_BORINGSSL_H_
diff --git a/cc/subtle/ed25519_verify_boringssl_test.cc b/cc/subtle/ed25519_verify_boringssl_test.cc
new file mode 100644
index 0000000..79a141f
--- /dev/null
+++ b/cc/subtle/ed25519_verify_boringssl_test.cc
@@ -0,0 +1,223 @@
+// Copyright 2019 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/subtle/ed25519_verify_boringssl.h"
+
+#include <string>
+
+#include "gtest/gtest.h"
+#include "absl/strings/str_cat.h"
+#include "openssl/curve25519.h"
+#include "tink/public_key_sign.h"
+#include "tink/public_key_verify.h"
+#include "tink/subtle/ed25519_sign_boringssl.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/subtle/wycheproof_util.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_util.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+namespace {
+
+class Ed25519VerifyBoringSslTest : public ::testing::Test {};
+
+TEST_F(Ed25519VerifyBoringSslTest, testBasicSign) {
+  // Generate a new key pair.
+  uint8_t out_public_key[ED25519_PUBLIC_KEY_LEN];
+  uint8_t out_private_key[ED25519_PRIVATE_KEY_LEN];
+
+  ED25519_keypair(out_public_key, out_private_key);
+
+  std::string public_key(reinterpret_cast<const char*>(out_public_key),
+                    ED25519_PUBLIC_KEY_LEN);
+  std::string private_key(reinterpret_cast<const char*>(out_private_key),
+                     ED25519_PRIVATE_KEY_LEN);
+
+  // Create a new signer.
+  auto signer_result = Ed25519SignBoringSsl::New(private_key);
+  ASSERT_TRUE(signer_result.ok()) << signer_result.status();
+  auto signer = std::move(signer_result.ValueOrDie());
+
+  // Create a new verifier.
+  auto verifier_result = Ed25519VerifyBoringSsl::New(public_key);
+  ASSERT_TRUE(verifier_result.ok()) << verifier_result.status();
+  auto verifier = std::move(verifier_result.ValueOrDie());
+
+  // Sign a message.
+  std::string message = "some data to be signed";
+  std::string signature = signer->Sign(message).ValueOrDie();
+  EXPECT_NE(signature, message);
+  EXPECT_EQ(signature.size(), ED25519_SIGNATURE_LEN);
+  auto status = verifier->Verify(signature, message);
+  EXPECT_TRUE(status.ok()) << status;
+
+  status = verifier->Verify("some bad signature", message);
+  EXPECT_FALSE(status.ok());
+
+  status = verifier->Verify(signature, "some bad message");
+  EXPECT_FALSE(status.ok());
+}
+
+TEST_F(Ed25519VerifyBoringSslTest, testInvalidPublicKeys) {
+  // Null public key.
+  const absl::string_view null_public_key;
+  EXPECT_FALSE(Ed25519VerifyBoringSsl::New(null_public_key).ok());
+
+  for (int keysize = 0; keysize < 128; keysize++) {
+    if (keysize == ED25519_PUBLIC_KEY_LEN) {
+      // Valid key size.
+      continue;
+    }
+    std::string key(keysize, 'x');
+    EXPECT_FALSE(Ed25519VerifyBoringSsl::New(key).ok());
+  }
+}
+
+TEST_F(Ed25519VerifyBoringSslTest, testMessageEmptyVersusNullStringView) {
+  // Generate a new key pair.
+  uint8_t out_public_key[ED25519_PUBLIC_KEY_LEN];
+  uint8_t out_private_key[ED25519_PRIVATE_KEY_LEN];
+
+  ED25519_keypair(out_public_key, out_private_key);
+
+  std::string public_key(reinterpret_cast<const char*>(out_public_key),
+                    ED25519_PUBLIC_KEY_LEN);
+  std::string private_key(reinterpret_cast<const char*>(out_private_key),
+                     ED25519_PRIVATE_KEY_LEN);
+
+  // Create a new signer.
+  auto signer_result = Ed25519SignBoringSsl::New(private_key);
+  ASSERT_TRUE(signer_result.ok()) << signer_result.status();
+  auto signer = std::move(signer_result.ValueOrDie());
+
+  // Create a new verifier.
+  auto verifier_result = Ed25519VerifyBoringSsl::New(public_key);
+  ASSERT_TRUE(verifier_result.ok()) << verifier_result.status();
+  auto verifier = std::move(verifier_result.ValueOrDie());
+
+  // Message is a null string_view.
+  const absl::string_view empty_message;
+  auto signature = signer->Sign(empty_message).ValueOrDie();
+  EXPECT_NE(signature, empty_message);
+  EXPECT_EQ(signature.size(), ED25519_SIGNATURE_LEN);
+  auto status = verifier->Verify(signature, empty_message);
+  EXPECT_TRUE(status.ok()) << status;
+
+  // Message is an empty std::string.
+  const std::string message = "";
+  signature = signer->Sign(message).ValueOrDie();
+  EXPECT_EQ(signature.size(), ED25519_SIGNATURE_LEN);
+  EXPECT_NE(signature, message);
+  status = verifier->Verify(signature, message);
+  EXPECT_TRUE(status.ok()) << status;
+
+  // Message is a null ptr.
+  signature = signer->Sign(nullptr).ValueOrDie();
+  EXPECT_EQ(signature.size(), ED25519_SIGNATURE_LEN);
+  status = verifier->Verify(signature, nullptr);
+  EXPECT_TRUE(status.ok()) << status;
+}
+
+static util::StatusOr<std::unique_ptr<PublicKeyVerify>> GetVerifier(
+    const rapidjson::Value& test_group) {
+  std::string public_key = WycheproofUtil::GetBytes(test_group["key"]["pk"]);
+  auto result = Ed25519VerifyBoringSsl::New(public_key);
+  if (!result.ok()) {
+    std::cout << "Failed: " << result.status() << "\n";
+  }
+  return result;
+}
+
+// Tests signature verification using the test vectors in the specified file.
+// allow_skipping determines whether it is OK to skip a test because
+// a verfier cannot be constructed. This option can be used for
+// if a file contains test vectors that are not necessarily supported
+// by tink.
+bool TestSignatures(const std::string& filename, bool allow_skipping) {
+  std::unique_ptr<rapidjson::Document> root =
+      WycheproofUtil::ReadTestVectors(filename);
+  std::cout << (*root)["algorithm"].GetString();
+  std::cout << "generator version " << (*root)["generatorVersion"].GetString();
+  int passed_tests = 0;
+  int failed_tests = 0;
+  for (const rapidjson::Value& test_group : (*root)["testGroups"].GetArray()) {
+    auto verifier_result = GetVerifier(test_group);
+    if (!verifier_result.ok()) {
+      std::string curve = test_group["key"]["curve"].GetString();
+      if (allow_skipping) {
+        std::cout << "Could not construct verifier for curve " << curve
+                  << verifier_result.status();
+      } else {
+        ADD_FAILURE() << "Could not construct verifier for curve " << curve
+                      << verifier_result.status();
+        failed_tests += test_group["tests"].GetArray().Size();
+      }
+      continue;
+    }
+
+    auto verifier = std::move(verifier_result.ValueOrDie());
+    for (const rapidjson::Value& test : test_group["tests"].GetArray()) {
+      std::string expected = test["result"].GetString();
+      std::string msg = WycheproofUtil::GetBytes(test["msg"]);
+      std::string sig = WycheproofUtil::GetBytes(test["sig"]);
+      std::string id =
+          absl::StrCat(test["tcId"].GetInt(), " ", test["comment"].GetString());
+      auto status = verifier->Verify(sig, msg);
+      if (expected == "valid") {
+        if (status.ok()) {
+          ++passed_tests;
+        } else {
+          ++failed_tests;
+          ADD_FAILURE() << "Valid signature not verified:" << id
+                        << " status:" << status;
+        }
+      } else if (expected == "invalid") {
+        if (!status.ok()) {
+          ++passed_tests;
+        } else {
+          ++failed_tests;
+          ADD_FAILURE() << "Invalid signature verified:" << id;
+        }
+      } else if (expected == "acceptable") {
+        // The validity of the signature is undefined. Hence the test passes
+        // but we log the result since we might still want to know if the
+        // library is strict or forgiving.
+        ++passed_tests;
+        std::cout << "Acceptable signature:" << id << ":" << status;
+      } else {
+        ++failed_tests;
+        ADD_FAILURE() << "Invalid field result:" << expected;
+      }
+    }
+  }
+  int num_tests = (*root)["numberOfTests"].GetInt();
+  std::cout << "total number of tests: " << num_tests;
+  std::cout << "number of tests passed:" << passed_tests;
+  std::cout << "number of tests failed:" << failed_tests;
+  return failed_tests == 0;
+}
+
+TEST_F(Ed25519VerifyBoringSslTest, WycheproofCurve25519) {
+  ASSERT_TRUE(TestSignatures("eddsa_test.json", false));
+}
+
+}  // namespace
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/subtle/encrypt_then_authenticate_test.cc b/cc/subtle/encrypt_then_authenticate_test.cc
index 56e68e7..cc22996 100644
--- a/cc/subtle/encrypt_then_authenticate_test.cc
+++ b/cc/subtle/encrypt_then_authenticate_test.cc
@@ -260,8 +260,3 @@
 }  // namespace subtle
 }  // namespace tink
 }  // namespace crypto
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/subtle/hkdf_test.cc b/cc/subtle/hkdf_test.cc
index 61efa76..7340e3d 100644
--- a/cc/subtle/hkdf_test.cc
+++ b/cc/subtle/hkdf_test.cc
@@ -116,7 +116,3 @@
 }  // namespace tink
 }  // namespace crypto
 
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/subtle/hmac_boringssl.cc b/cc/subtle/hmac_boringssl.cc
index 16073ac..e018145 100644
--- a/cc/subtle/hmac_boringssl.cc
+++ b/cc/subtle/hmac_boringssl.cc
@@ -48,6 +48,9 @@
     // If this fails then something is wrong with the key manager.
     return util::Status(util::error::INTERNAL, "invalid tag size");
   }
+  if (key_value.size() < MIN_KEY_SIZE) {
+    return util::Status(util::error::INTERNAL, "invalid key size");
+  }
   std::unique_ptr<Mac> hmac(new HmacBoringSsl(md, tag_size, key_value));
   return std::move(hmac);
 }
diff --git a/cc/subtle/hmac_boringssl.h b/cc/subtle/hmac_boringssl.h
index f32d946..27f8861 100644
--- a/cc/subtle/hmac_boringssl.h
+++ b/cc/subtle/hmac_boringssl.h
@@ -49,6 +49,8 @@
   virtual ~HmacBoringSsl() {}
 
  private:
+  // Minimum HMAC key size in bytes.
+  static const size_t MIN_KEY_SIZE = 16;
   HmacBoringSsl() {}
   HmacBoringSsl(const EVP_MD* md, uint32_t tag_size,
                 const std::string& key_value);
diff --git a/cc/subtle/hmac_boringssl_test.cc b/cc/subtle/hmac_boringssl_test.cc
index 433afde..a50212a 100644
--- a/cc/subtle/hmac_boringssl_test.cc
+++ b/cc/subtle/hmac_boringssl_test.cc
@@ -114,6 +114,20 @@
   }
 }
 
+TEST_F(HmacBoringSslTest, testInvalidKeySizes) {
+  size_t tag_size = 16;
+
+  for (int keysize = 0; keysize < 65; keysize++) {
+    std::string key(keysize, 'x');
+    auto hmac_result = HmacBoringSsl::New(HashType::SHA1, tag_size, key);
+    if (keysize >= 16) {
+      EXPECT_TRUE(hmac_result.ok());
+    } else {
+      EXPECT_FALSE(hmac_result.ok());
+    }
+  }
+}
+
 // TODO(bleichen): Stuff to test
 //  - Generate test vectors and share with Wycheproof.
 //  - Tag size wrong for construction
@@ -132,7 +146,3 @@
 }  // namespace tink
 }  // namespace crypto
 
-int main(int ac, char *av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/subtle/ind_cpa_cipher.h b/cc/subtle/ind_cpa_cipher.h
index 201298f..69e460b 100644
--- a/cc/subtle/ind_cpa_cipher.h
+++ b/cc/subtle/ind_cpa_cipher.h
@@ -14,8 +14,8 @@
 //
 ///////////////////////////////////////////////////////////////////////////////
 
-#ifndef TINK_IND_CPA_CIPHER_H_
-#define TINK_IND_CPA_CIPHER_H_
+#ifndef TINK_SUBTLE_IND_CPA_CIPHER_H_
+#define TINK_SUBTLE_IND_CPA_CIPHER_H_
 
 #include "absl/strings/string_view.h"
 #include "tink/util/statusor.h"
@@ -47,4 +47,4 @@
 }  // namespace tink
 }  // namespace crypto
 
-#endif  // TINK_IND_CPA_CIPHER_H_
+#endif  // TINK_SUBTLE_IND_CPA_CIPHER_H_
diff --git a/cc/subtle/nonce_based_streaming_aead.cc b/cc/subtle/nonce_based_streaming_aead.cc
new file mode 100644
index 0000000..44b163e
--- /dev/null
+++ b/cc/subtle/nonce_based_streaming_aead.cc
@@ -0,0 +1,48 @@
+// Copyright 2019 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/subtle/nonce_based_streaming_aead.h"
+
+#include "absl/strings/string_view.h"
+#include "tink/input_stream.h"
+#include "tink/output_stream.h"
+#include "tink/streaming_aead.h"
+#include "tink/subtle/stream_segment_encrypter.h"
+#include "tink/subtle/streaming_aead_encrypting_stream.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+
+crypto::tink::util::StatusOr<std::unique_ptr<crypto::tink::OutputStream>>
+    NonceBasedStreamingAead::NewEncryptingStream(
+        std::unique_ptr<crypto::tink::OutputStream> ciphertext_destination,
+        absl::string_view associated_data) {
+  return StreamingAeadEncryptingStream::New(
+      NewSegmentEncrypter(associated_data), std::move(ciphertext_destination));
+}
+
+crypto::tink::util::StatusOr<std::unique_ptr<crypto::tink::InputStream>>
+    NonceBasedStreamingAead::NewDecryptingStream(
+        std::unique_ptr<crypto::tink::InputStream> ciphertext_source,
+        absl::string_view associated_data) {
+  return util::Status(util::error::UNIMPLEMENTED, "Not implemented yet");
+}
+
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/subtle/nonce_based_streaming_aead.h b/cc/subtle/nonce_based_streaming_aead.h
new file mode 100644
index 0000000..1cf6e11
--- /dev/null
+++ b/cc/subtle/nonce_based_streaming_aead.h
@@ -0,0 +1,62 @@
+// Copyright 2019 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_SUBTLE_NONCE_BASED_STREAMING_AEAD_H_
+#define TINK_SUBTLE_NONCE_BASED_STREAMING_AEAD_H_
+
+#include "absl/strings/string_view.h"
+#include "tink/input_stream.h"
+#include "tink/output_stream.h"
+#include "tink/streaming_aead.h"
+#include "tink/subtle/stream_segment_encrypter.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+
+// An abstract class for StreamingAead using the nonce based online encryption
+// scheme proposed in "Online Authenticated-Encryption and its Nonce-Reuse
+// Misuse-Resistance" by Hoang, Reyhanitabar, Rogaway and Vizár
+// (https://eprint.iacr.org/2015/189.pdf)
+class NonceBasedStreamingAead : public StreamingAead {
+ public:
+  // -----------------------
+  // Methods of StreamingAead-interface implemented by this class.
+  crypto::tink::util::StatusOr<std::unique_ptr<crypto::tink::OutputStream>>
+  NewEncryptingStream(
+      std::unique_ptr<crypto::tink::OutputStream> ciphertext_destination,
+      absl::string_view associated_data) override;
+
+  crypto::tink::util::StatusOr<std::unique_ptr<crypto::tink::InputStream>>
+  NewDecryptingStream(
+      std::unique_ptr<crypto::tink::InputStream> ciphertext_source,
+      absl::string_view associated_data) override;
+
+ protected:
+  // -----------------------
+  // Methods to be implemented by a subclass of this class.
+
+  // Returns a new StreamSegmentEncrypter that uses `associated_data` for AEAD.
+  virtual std::unique_ptr<StreamSegmentEncrypter> NewSegmentEncrypter(
+      absl::string_view associated_data) const = 0;
+};
+
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_SUBTLE_NONCE_BASED_STREAMING_AEAD_H_
diff --git a/cc/subtle/random.h b/cc/subtle/random.h
index 8ae5398..b9d8a9a 100644
--- a/cc/subtle/random.h
+++ b/cc/subtle/random.h
@@ -34,4 +34,4 @@
 }  // namespace tink
 }  // namespace crypto
 
-#endif  // TINK_SUBTLE_HMAC_RANDOM_H_
+#endif  // TINK_SUBTLE_RANDOM_H_
diff --git a/cc/subtle/random_test.cc b/cc/subtle/random_test.cc
index f99d356..62c069b 100644
--- a/cc/subtle/random_test.cc
+++ b/cc/subtle/random_test.cc
@@ -39,8 +39,3 @@
 }  // namespace subtle
 }  // namespace tink
 }  // namespace crypto
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/subtle/rsa_ssa_pkcs1_sign_boringssl.cc b/cc/subtle/rsa_ssa_pkcs1_sign_boringssl.cc
new file mode 100644
index 0000000..b109389
--- /dev/null
+++ b/cc/subtle/rsa_ssa_pkcs1_sign_boringssl.cc
@@ -0,0 +1,112 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/subtle/rsa_ssa_pkcs1_sign_boringssl.h"
+
+#include <vector>
+
+#include "absl/strings/str_cat.h"
+#include "openssl/base.h"
+#include "openssl/digest.h"
+#include "openssl/evp.h"
+#include "openssl/rsa.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+
+// static
+util::StatusOr<std::unique_ptr<PublicKeySign>> RsaSsaPkcs1SignBoringSsl::New(
+    const SubtleUtilBoringSSL::RsaPrivateKey& private_key,
+    const SubtleUtilBoringSSL::RsaSsaPkcs1Params& params) {
+  // Check hash.
+  util::Status sig_hash_valid =
+      SubtleUtilBoringSSL::ValidateSignatureHash(params.hash_type);
+  if (!sig_hash_valid.ok()) return sig_hash_valid;
+  auto sig_hash = SubtleUtilBoringSSL::EvpHash(params.hash_type);
+  if (!sig_hash.ok()) return sig_hash.status();
+
+  // Check RSA's modulus.
+  auto status_or_n = SubtleUtilBoringSSL::str2bn(private_key.n);
+  if (!status_or_n.ok()) return status_or_n.status();
+  auto modulus_status = SubtleUtilBoringSSL::ValidateRsaModulusSize(
+      BN_num_bits(status_or_n.ValueOrDie().get()));
+  if (!modulus_status.ok()) return modulus_status;
+
+  bssl::UniquePtr<RSA> rsa(RSA_new());
+  if (rsa == nullptr) {
+    return util::Status(util::error::INTERNAL, "Could not initialize RSA.");
+  }
+
+  {
+    auto st = SubtleUtilBoringSSL::CopyKey(private_key, rsa.get());
+    if (!st.ok()) return st;
+  }
+
+  {
+    auto st = SubtleUtilBoringSSL::CopyPrimeFactors(private_key, rsa.get());
+    if (!st.ok()) return st;
+  }
+
+  {
+    auto st = SubtleUtilBoringSSL::CopyCrtParams(private_key, rsa.get());
+    if (!st.ok()) return st;
+  }
+
+  if (RSA_check_key(rsa.get()) == 0 || RSA_check_fips(rsa.get()) == 0) {
+    return util::Status(util::error::INVALID_ARGUMENT,
+                        absl::StrCat("Could not load RSA key: ",
+                                     SubtleUtilBoringSSL::GetErrors()));
+  }
+
+  return std::unique_ptr<PublicKeySign>(
+      new RsaSsaPkcs1SignBoringSsl(std::move(rsa), sig_hash.ValueOrDie()));
+}
+
+RsaSsaPkcs1SignBoringSsl::RsaSsaPkcs1SignBoringSsl(
+    bssl::UniquePtr<RSA> private_key, const EVP_MD* sig_hash)
+    : private_key_(std::move(private_key)), sig_hash_(sig_hash) {}
+
+util::StatusOr<std::string> RsaSsaPkcs1SignBoringSsl::Sign(
+    absl::string_view data) const {
+  data = SubtleUtilBoringSSL::EnsureNonNull(data);
+  auto digest_or = boringssl::ComputeHash(data, *sig_hash_);
+  if (!digest_or.ok()) return digest_or.status();
+  std::vector<uint8_t> digest = std::move(digest_or.ValueOrDie());
+
+  std::vector<uint8_t> signature(RSA_size(private_key_.get()));
+  unsigned int signature_length = 0;
+
+  if (RSA_sign(/*hash_nid=*/EVP_MD_type(sig_hash_),
+               /*in=*/digest.data(),
+               /*in_len=*/digest.size(),
+               /*out=*/signature.data(),
+               /*out_len=*/&signature_length,
+               /*rsa=*/private_key_.get()) != 1) {
+    // TODO(b/112581512): Decide if it's safe to propagate the BoringSSL error.
+    // For now, just empty the error stack.
+    SubtleUtilBoringSSL::GetErrors();
+    return util::Status(util::error::INTERNAL, "Signing failed.");
+  }
+
+  return std::string(reinterpret_cast<const char*>(signature.data()),
+                signature_length);
+}
+
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/subtle/rsa_ssa_pkcs1_sign_boringssl.h b/cc/subtle/rsa_ssa_pkcs1_sign_boringssl.h
new file mode 100644
index 0000000..6298cf5
--- /dev/null
+++ b/cc/subtle/rsa_ssa_pkcs1_sign_boringssl.h
@@ -0,0 +1,63 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_SUBTLE_RSA_SSA_PKCS1_SIGN_BORINGSSL_H_
+#define TINK_SUBTLE_RSA_SSA_PKCS1_SIGN_BORINGSSL_H_
+
+#include <memory>
+
+#include "absl/strings/string_view.h"
+#include "openssl/base.h"
+#include "openssl/ec.h"
+#include "openssl/rsa.h"
+#include "tink/public_key_sign.h"
+#include "tink/subtle/common_enums.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+
+// The RSA SSA (Signature Schemes with Appendix) using PKCS1 (Public-Key
+// Cryptography Standards) encoding is defined at
+// https://tools.ietf.org/html/rfc8017#section-8.2). This implemention uses
+// Boring SSL for the underlying cryptographic operations.
+class RsaSsaPkcs1SignBoringSsl : public PublicKeySign {
+ public:
+  static crypto::tink::util::StatusOr<std::unique_ptr<PublicKeySign>> New(
+      const SubtleUtilBoringSSL::RsaPrivateKey& private_key,
+      const SubtleUtilBoringSSL::RsaSsaPkcs1Params& params);
+
+  // Computes the signature for 'data'.
+  crypto::tink::util::StatusOr<std::string> Sign(
+      absl::string_view data) const override;
+
+  ~RsaSsaPkcs1SignBoringSsl() override = default;
+
+ private:
+  const bssl::UniquePtr<RSA> private_key_;
+  const EVP_MD* sig_hash_;  // Owned by BoringSSL.
+
+  RsaSsaPkcs1SignBoringSsl(bssl::UniquePtr<RSA> private_key,
+                           const EVP_MD* sig_hash);
+};
+
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_SUBTLE_RSA_SSA_PKCS1_SIGN_BORINGSSL_H_
diff --git a/cc/subtle/rsa_ssa_pkcs1_sign_boringssl_test.cc b/cc/subtle/rsa_ssa_pkcs1_sign_boringssl_test.cc
new file mode 100644
index 0000000..7ab3e3b
--- /dev/null
+++ b/cc/subtle/rsa_ssa_pkcs1_sign_boringssl_test.cc
@@ -0,0 +1,124 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/subtle/rsa_ssa_pkcs1_sign_boringssl.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/strings/escaping.h"
+#include "openssl/base.h"
+#include "openssl/bn.h"
+#include "openssl/rsa.h"
+#include "tink/subtle/rsa_ssa_pkcs1_verify_boringssl.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/util/test_matchers.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+namespace {
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::testing::IsEmpty;
+using ::testing::Not;
+
+class RsaPkcs1SignBoringsslTest : public ::testing::Test {
+ public:
+  RsaPkcs1SignBoringsslTest() : rsa_f4_(BN_new()) {
+    EXPECT_TRUE(BN_set_u64(rsa_f4_.get(), RSA_F4));
+    EXPECT_THAT(SubtleUtilBoringSSL::GetNewRsaKeyPair(
+                    2048, rsa_f4_.get(), &private_key_, &public_key_),
+                IsOk());
+  }
+
+ protected:
+  bssl::UniquePtr<BIGNUM> rsa_f4_;
+  SubtleUtilBoringSSL::RsaPrivateKey private_key_;
+  SubtleUtilBoringSSL::RsaPublicKey public_key_;
+};
+
+TEST_F(RsaPkcs1SignBoringsslTest, EncodesPkcs1) {
+  SubtleUtilBoringSSL::RsaSsaPkcs1Params params{/*sig_hash=*/HashType::SHA256};
+
+  auto signer_or = RsaSsaPkcs1SignBoringSsl::New(private_key_, params);
+  ASSERT_THAT(signer_or.status(), IsOk());
+
+  auto signature_or = signer_or.ValueOrDie()->Sign("testdata");
+  ASSERT_THAT(signature_or.status(), IsOk());
+  EXPECT_THAT(signature_or.ValueOrDie(), Not(IsEmpty()));
+
+  auto verifier_or = RsaSsaPkcs1VerifyBoringSsl::New(public_key_, params);
+  ASSERT_THAT(verifier_or.status(), IsOk());
+  EXPECT_THAT(
+      verifier_or.ValueOrDie()->Verify(signature_or.ValueOrDie(), "testdata"),
+      IsOk());
+}
+
+TEST_F(RsaPkcs1SignBoringsslTest, EncodesPkcs1WithSeparateHashes) {
+  SubtleUtilBoringSSL::RsaSsaPkcs1Params params{/*sig_hash=*/HashType::SHA256};
+
+  auto signer_or = RsaSsaPkcs1SignBoringSsl::New(private_key_, params);
+  ASSERT_THAT(signer_or.status(), IsOk());
+
+  auto signature_or = signer_or.ValueOrDie()->Sign("testdata");
+  ASSERT_THAT(signature_or.status(), IsOk());
+  EXPECT_THAT(signature_or.ValueOrDie(), Not(IsEmpty()));
+
+  auto verifier_or = RsaSsaPkcs1VerifyBoringSsl::New(public_key_, params);
+  ASSERT_THAT(verifier_or.status(), IsOk());
+  EXPECT_THAT(
+      verifier_or.ValueOrDie()->Verify(signature_or.ValueOrDie(), "testdata"),
+      IsOk());
+}
+
+TEST_F(RsaPkcs1SignBoringsslTest, RejectsUnsafeHash) {
+  SubtleUtilBoringSSL::RsaSsaPkcs1Params params{/*sig_hash=*/HashType::SHA1};
+  ASSERT_THAT(RsaSsaPkcs1SignBoringSsl::New(private_key_, params).status(),
+              StatusIs(util::error::INVALID_ARGUMENT));
+}
+
+TEST_F(RsaPkcs1SignBoringsslTest, RejectsInvalidCrtParams) {
+  SubtleUtilBoringSSL::RsaSsaPkcs1Params params{/*sig_hash=*/HashType::SHA256};
+  ASSERT_THAT(private_key_.crt, Not(IsEmpty()));
+  ASSERT_THAT(private_key_.dq, Not(IsEmpty()));
+  ASSERT_THAT(private_key_.dp, Not(IsEmpty()));
+
+  // Flip a few bits in the CRT parameters; check that creation fails.
+  {
+    SubtleUtilBoringSSL::RsaPrivateKey key = private_key_;
+    key.crt[0] ^= 0x80;
+    auto signer_or = RsaSsaPkcs1SignBoringSsl::New(key, params);
+    EXPECT_THAT(signer_or.status(), StatusIs(util::error::INVALID_ARGUMENT));
+  }
+  {
+    SubtleUtilBoringSSL::RsaPrivateKey key = private_key_;
+    key.dq[0] ^= 0x08;
+    auto signer_or = RsaSsaPkcs1SignBoringSsl::New(key, params);
+    EXPECT_THAT(signer_or.status(), StatusIs(util::error::INVALID_ARGUMENT));
+  }
+  {
+    SubtleUtilBoringSSL::RsaPrivateKey key = private_key_;
+    key.dp[0] ^= 0x04;
+    auto signer_or = RsaSsaPkcs1SignBoringSsl::New(key, params);
+    EXPECT_THAT(signer_or.status(), StatusIs(util::error::INVALID_ARGUMENT));
+  }
+}
+
+}  // namespace
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
+
diff --git a/cc/subtle/rsa_ssa_pkcs1_verify_boringssl.cc b/cc/subtle/rsa_ssa_pkcs1_verify_boringssl.cc
new file mode 100644
index 0000000..e998b13
--- /dev/null
+++ b/cc/subtle/rsa_ssa_pkcs1_verify_boringssl.cc
@@ -0,0 +1,102 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/subtle/rsa_ssa_pkcs1_verify_boringssl.h"
+#include "absl/strings/str_cat.h"
+#include "openssl/bn.h"
+#include "openssl/digest.h"
+#include "openssl/evp.h"
+#include "openssl/rsa.h"
+#include "tink/subtle/common_enums.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/util/errors.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+
+// static
+util::StatusOr<std::unique_ptr<RsaSsaPkcs1VerifyBoringSsl>>
+RsaSsaPkcs1VerifyBoringSsl::New(
+    const SubtleUtilBoringSSL::RsaPublicKey& pub_key,
+    const SubtleUtilBoringSSL::RsaSsaPkcs1Params& params) {
+  // Check hash.
+  auto hash_status =
+      SubtleUtilBoringSSL::ValidateSignatureHash(params.hash_type);
+  if (!hash_status.ok()) {
+    return hash_status;
+  }
+  auto sig_hash_result = SubtleUtilBoringSSL::EvpHash(params.hash_type);
+  if (!sig_hash_result.ok()) return sig_hash_result.status();
+
+  // Check RSA's modulus.
+  auto status_or_n = SubtleUtilBoringSSL::str2bn(pub_key.n);
+  if (!status_or_n.ok()) return status_or_n.status();
+  auto status_or_e = SubtleUtilBoringSSL::str2bn(pub_key.e);
+  if (!status_or_e.ok()) return status_or_e.status();
+  auto modulus_status = SubtleUtilBoringSSL::ValidateRsaModulusSize(
+      BN_num_bits(status_or_n.ValueOrDie().get()));
+  if (!modulus_status.ok()) return modulus_status;
+  bssl::UniquePtr<RSA> rsa(RSA_new());
+  if (rsa.get() == nullptr) {
+    return util::Status(util::error::INTERNAL,
+                        "BoringSsl RSA allocation error");
+  }
+  // Set RSA public key and hence d is nullptr.
+  if (1 != RSA_set0_key(rsa.get(), status_or_n.ValueOrDie().get(),
+                        status_or_e.ValueOrDie().get(), nullptr /* d */)) {
+    return util::Status(util::error::INTERNAL, "Could not set RSA key.");
+  }
+  status_or_n.ValueOrDie().release();
+  status_or_e.ValueOrDie().release();
+  std::unique_ptr<RsaSsaPkcs1VerifyBoringSsl> verify(
+      new RsaSsaPkcs1VerifyBoringSsl(std::move(rsa),
+                                     sig_hash_result.ValueOrDie()));
+  return std::move(verify);
+}
+
+RsaSsaPkcs1VerifyBoringSsl::RsaSsaPkcs1VerifyBoringSsl(bssl::UniquePtr<RSA> rsa,
+                                                       const EVP_MD* sig_hash)
+    : rsa_(std::move(rsa)), sig_hash_(sig_hash) {}
+
+util::Status RsaSsaPkcs1VerifyBoringSsl::Verify(absl::string_view signature,
+                                                absl::string_view data) const {
+  // BoringSSL expects a non-null pointer for data,
+  // regardless of whether the size is 0.
+  data = SubtleUtilBoringSSL::EnsureNonNull(data);
+
+  auto digest_result = boringssl::ComputeHash(data, *sig_hash_);
+  if (!digest_result.ok()) return digest_result.status();
+  auto digest = std::move(digest_result.ValueOrDie());
+
+  if (1 !=
+      RSA_verify(EVP_MD_type(sig_hash_),
+                 /*msg=*/digest.data(),
+                 /*msg_len=*/digest.size(),
+                 /*sig=*/reinterpret_cast<const uint8_t*>(signature.data()),
+                 /*sig_len=*/signature.length(),
+                 /*rsa=*/rsa_.get())) {
+    // Signature is invalid.
+    return util::Status(util::error::INVALID_ARGUMENT,
+                        "Signature is not valid.");
+  }
+
+  return util::Status::OK;
+}
+
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/subtle/rsa_ssa_pkcs1_verify_boringssl.h b/cc/subtle/rsa_ssa_pkcs1_verify_boringssl.h
new file mode 100644
index 0000000..ad32a1c
--- /dev/null
+++ b/cc/subtle/rsa_ssa_pkcs1_verify_boringssl.h
@@ -0,0 +1,66 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_SUBTLE_RSA_SSA_PKCS1_VERIFY_BORINGSSL_H_
+#define TINK_SUBTLE_RSA_SSA_PKCS1_VERIFY_BORINGSSL_H_
+
+#include <memory>
+
+#include "absl/strings/string_view.h"
+#include "openssl/evp.h"
+#include "openssl/rsa.h"
+#include "tink/public_key_verify.h"
+#include "tink/subtle/common_enums.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/util/status.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+
+// RSA SSA (Signature Schemes with Appendix) using PKCS1 (Public-Key
+// Cryptography Standards) encoding is defined at
+// https://tools.ietf.org/html/rfc8017#section-8.2). This implemention uses
+// BoringSSL for the underlying cryptographic operations.
+class RsaSsaPkcs1VerifyBoringSsl : public PublicKeyVerify {
+ public:
+  static crypto::tink::util::StatusOr<
+      std::unique_ptr<RsaSsaPkcs1VerifyBoringSsl>>
+  New(const SubtleUtilBoringSSL::RsaPublicKey& pub_key,
+      const SubtleUtilBoringSSL::RsaSsaPkcs1Params& params);
+
+  // Verifies that 'signature' is a digital signature for 'data'.
+  crypto::tink::util::Status Verify(absl::string_view signature,
+                                    absl::string_view data) const override;
+
+  ~RsaSsaPkcs1VerifyBoringSsl() override = default;
+
+ private:
+  // To reach 128-bit security strength, RSA's modulus must be at least 3072-bit
+  // while 2048-bit RSA key only has 112-bit security. Nevertheless, a 2048-bit
+  // RSA key is considered safe by NIST until 2030 (see
+  // https://www.keylength.com/en/4/).
+  static const size_t kMinModulusSizeInBits = 2048;
+  RsaSsaPkcs1VerifyBoringSsl(bssl::UniquePtr<RSA> rsa, const EVP_MD* sig_hash);
+  const bssl::UniquePtr<RSA> rsa_;
+  const EVP_MD* sig_hash_;  // Owned by BoringSSL.
+};
+
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_SUBTLE_RSA_SSA_PKCS1_VERIFY_BORINGSSL_H_
diff --git a/cc/subtle/rsa_ssa_pkcs1_verify_boringssl_test.cc b/cc/subtle/rsa_ssa_pkcs1_verify_boringssl_test.cc
new file mode 100644
index 0000000..2adf912
--- /dev/null
+++ b/cc/subtle/rsa_ssa_pkcs1_verify_boringssl_test.cc
@@ -0,0 +1,265 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/subtle/rsa_ssa_pkcs1_verify_boringssl.h"
+
+#include <iostream>
+#include <string>
+
+#include "gtest/gtest.h"
+#include "absl/strings/escaping.h"
+#include "absl/strings/str_cat.h"
+#include "include/rapidjson/document.h"
+#include "tink/public_key_sign.h"
+#include "tink/public_key_verify.h"
+#include "tink/subtle/common_enums.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/subtle/wycheproof_util.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_util.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+namespace {
+
+class RsaSsaPkcs1VerifyBoringSslTest : public ::testing::Test {};
+
+// Test vector from
+// https://csrc.nist.gov/Projects/Cryptographic-Algorithm-Validation-Program/Digital-Signatures
+struct NistTestVector {
+  std::string n;
+  std::string e;
+  std::string message;
+  std::string signature;
+  HashType sig_hash;
+};
+
+static const NistTestVector nist_test_vector{
+    absl::HexStringToBytes(
+        "c47abacc2a84d56f3614d92fd62ed36ddde459664b9301dcd1d61781cfcc026bcb2399"
+        "bee7e75681a80b7bf500e2d08ceae1c42ec0b707927f2b2fe92ae852087d25f1d260cc"
+        "74905ee5f9b254ed05494a9fe06732c3680992dd6f0dc634568d11542a705f83ae96d2"
+        "a49763d5fbb24398edf3702bc94bc168190166492b8671de874bb9cecb058c6c8344aa"
+        "8c93754d6effcd44a41ed7de0a9dcd9144437f212b18881d042d331a4618a9e630ef9b"
+        "b66305e4fdf8f0391b3b2313fe549f0189ff968b92f33c266a4bc2cffc897d1937eeb9"
+        "e406f5d0eaa7a14782e76af3fce98f54ed237b4a04a4159a5f6250a296a902880204e6"
+        "1d891c4da29f2d65f34cbb"),
+    absl::HexStringToBytes("49d2a1"),
+    absl::HexStringToBytes(
+        "95123c8d1b236540b86976a11cea31f8bd4e6c54c235147d20ce722b03a6ad756fbd91"
+        "8c27df8ea9ce3104444c0bbe877305bc02e35535a02a58dcda306e632ad30b3dc3ce0b"
+        "a97fdf46ec192965dd9cd7f4a71b02b8cba3d442646eeec4af590824ca98d74fbca934"
+        "d0b6867aa1991f3040b707e806de6e66b5934f05509bea"),
+    absl::HexStringToBytes(
+        "51265d96f11ab338762891cb29bf3f1d2b3305107063f5f3245af376dfcc7027d39365"
+        "de70a31db05e9e10eb6148cb7f6425f0c93c4fb0e2291adbd22c77656afc196858a11e"
+        "1c670d9eeb592613e69eb4f3aa501730743ac4464486c7ae68fd509e896f63884e9424"
+        "f69c1c5397959f1e52a368667a598a1fc90125273d9341295d2f8e1cc4969bf228c860"
+        "e07a3546be2eeda1cde48ee94d062801fe666e4a7ae8cb9cd79262c017b081af874ff0"
+        "0453ca43e34efdb43fffb0bb42a4e2d32a5e5cc9e8546a221fe930250e5f5333e0efe5"
+        "8ffebf19369a3b8ae5a67f6a048bc9ef915bda25160729b508667ada84a0c27e7e26cf"
+        "2abca413e5e4693f4a9405"),
+    HashType::SHA256};
+
+TEST_F(RsaSsaPkcs1VerifyBoringSslTest, BasicVerify) {
+  SubtleUtilBoringSSL::RsaPublicKey pub_key{nist_test_vector.n,
+                                            nist_test_vector.e};
+  SubtleUtilBoringSSL::RsaSsaPkcs1Params params{nist_test_vector.sig_hash};
+
+  auto verifier_result = RsaSsaPkcs1VerifyBoringSsl::New(pub_key, params);
+  ASSERT_TRUE(verifier_result.ok()) << verifier_result.status();
+  auto verifier = std::move(verifier_result.ValueOrDie());
+  auto status =
+      verifier->Verify(nist_test_vector.signature, nist_test_vector.message);
+  EXPECT_TRUE(status.ok()) << status << SubtleUtilBoringSSL::GetErrors();
+}
+
+TEST_F(RsaSsaPkcs1VerifyBoringSslTest, NewErrors) {
+  SubtleUtilBoringSSL::RsaPublicKey nist_pub_key{nist_test_vector.n,
+                                                 nist_test_vector.e};
+  SubtleUtilBoringSSL::RsaSsaPkcs1Params nist_params{nist_test_vector.sig_hash};
+  SubtleUtilBoringSSL::RsaPublicKey small_pub_key{std::string("\x23"),
+                                                  std::string("\x3")};
+  SubtleUtilBoringSSL::RsaSsaPkcs1Params sha1_hash_params{HashType::SHA1};
+
+  {  // Small modulus.
+    auto result = RsaSsaPkcs1VerifyBoringSsl::New(small_pub_key, nist_params);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring,
+                        "only modulus size >= 2048-bit is supported",
+                        result.status().error_message());
+  }
+
+  {  // Use SHA1 for digital signature.
+    auto result =
+        RsaSsaPkcs1VerifyBoringSsl::New(nist_pub_key, sha1_hash_params);
+    EXPECT_FALSE(result.ok());
+    EXPECT_EQ(util::error::INVALID_ARGUMENT, result.status().error_code());
+    EXPECT_PRED_FORMAT2(testing::IsSubstring,
+                        "SHA1 is not safe for digital signature",
+                        result.status().error_message());
+  }
+}
+
+TEST_F(RsaSsaPkcs1VerifyBoringSslTest, Modification) {
+  SubtleUtilBoringSSL::RsaPublicKey pub_key{nist_test_vector.n,
+                                            nist_test_vector.e};
+  SubtleUtilBoringSSL::RsaSsaPkcs1Params params{nist_test_vector.sig_hash};
+
+  auto verifier_result = RsaSsaPkcs1VerifyBoringSsl::New(pub_key, params);
+  ASSERT_TRUE(verifier_result.ok()) << verifier_result.status();
+  auto verifier = std::move(verifier_result.ValueOrDie());
+  // Modify the message.
+  for (std::size_t i = 0; i < nist_test_vector.message.length(); i++) {
+    std::string modified_message = nist_test_vector.message;
+    modified_message[i / 8] ^= 1 << (i % 8);
+    auto status =
+        verifier->Verify(nist_test_vector.signature, modified_message);
+    EXPECT_FALSE(status.ok()) << status << SubtleUtilBoringSSL::GetErrors();
+  }
+  // Modify the signature.
+  for (std::size_t i = 0; i < nist_test_vector.signature.length(); i++) {
+    std::string modified_signature = nist_test_vector.signature;
+    modified_signature[i / 8] ^= 1 << (i % 8);
+    auto status =
+        verifier->Verify(modified_signature, nist_test_vector.message);
+    EXPECT_FALSE(status.ok()) << status << SubtleUtilBoringSSL::GetErrors();
+  }
+  // Truncate the signature.
+  for (std::size_t i = 0; i < nist_test_vector.signature.length(); i++) {
+    std::string truncated_signature(nist_test_vector.signature, 0, i);
+    auto status =
+        verifier->Verify(truncated_signature, nist_test_vector.message);
+    EXPECT_FALSE(status.ok()) << status << SubtleUtilBoringSSL::GetErrors();
+  }
+}
+
+static util::StatusOr<std::unique_ptr<RsaSsaPkcs1VerifyBoringSsl>> GetVerifier(
+    const rapidjson::Value& test_group) {
+  SubtleUtilBoringSSL::RsaPublicKey key;
+  key.n = WycheproofUtil::GetInteger(test_group["n"]);
+  key.e = WycheproofUtil::GetInteger(test_group["e"]);
+
+  HashType md = WycheproofUtil::GetHashType(test_group["sha"]);
+  SubtleUtilBoringSSL::RsaSsaPkcs1Params params;
+  params.hash_type = md;
+
+  auto result = RsaSsaPkcs1VerifyBoringSsl::New(key, params);
+  if (!result.ok()) {
+    std::cout << "Failed: " << result.status() << "\n";
+  }
+  return result;
+}
+
+// Tests signature verification using the test vectors in the specified file.
+// allow_skipping determines whether it is OK to skip a test because
+// a verfier cannot be constructed. This option can be used for
+// if a file contains test vectors that are not necessarily supported
+// by tink.
+bool TestSignatures(const std::string& filename, bool allow_skipping) {
+  std::unique_ptr<rapidjson::Document> root =
+      WycheproofUtil::ReadTestVectors(filename);
+  std::cout << (*root)["algorithm"].GetString();
+  std::cout << "generator version " << (*root)["generatorVersion"].GetString();
+  std::cout << "expected version 0.4.12";
+  int passed_tests = 0;
+  int failed_tests = 0;
+  int group_count = 0;
+  for (const rapidjson::Value& test_group : (*root)["testGroups"].GetArray()) {
+    group_count++;
+    auto verifier_result = GetVerifier(test_group);
+    if (!verifier_result.ok()) {
+      std::string type = test_group["type"].GetString();
+      if (allow_skipping) {
+        std::cout << "Could not construct verifier for " << type << " group "
+                  << group_count << ": " << verifier_result.status();
+      } else {
+        ADD_FAILURE() << "Could not construct verifier for " << type
+                      << " group " << group_count << ": "
+                      << verifier_result.status();
+        failed_tests += test_group["tests"].GetArray().Size();
+      }
+      continue;
+    }
+    auto verifier = std::move(verifier_result.ValueOrDie());
+    for (const rapidjson::Value& test : test_group["tests"].GetArray()) {
+      std::string expected = test["result"].GetString();
+      std::string msg = WycheproofUtil::GetBytes(test["msg"]);
+      std::string sig = WycheproofUtil::GetBytes(test["sig"]);
+      std::string id =
+          absl::StrCat(test["tcId"].GetInt(), " ", test["comment"].GetString());
+      auto status = verifier->Verify(sig, msg);
+      if (expected == "valid") {
+        if (status.ok()) {
+          ++passed_tests;
+        } else {
+          ++failed_tests;
+          ADD_FAILURE() << "Valid signature not verified:" << id
+                        << " status:" << status;
+        }
+      } else if (expected == "invalid") {
+        if (!status.ok()) {
+          ++passed_tests;
+        } else {
+          ++failed_tests;
+          ADD_FAILURE() << "Invalid signature verified:" << id;
+        }
+      } else if (expected == "acceptable") {
+        // The validity of the signature is undefined. Hence the test passes
+        // but we log the result since we might still want to know if the
+        // library is strict or forgiving.
+        ++passed_tests;
+        std::cout << "Acceptable signature:" << id << ":" << status;
+      } else {
+        ++failed_tests;
+        ADD_FAILURE() << "Invalid field result:" << expected;
+      }
+    }
+  }
+  int num_tests = (*root)["numberOfTests"].GetInt();
+  std::cout << "total number of tests: " << num_tests;
+  std::cout << "number of tests passed:" << passed_tests;
+  std::cout << "number of tests failed:" << failed_tests;
+  return failed_tests == 0;
+}
+
+TEST_F(RsaSsaPkcs1VerifyBoringSslTest, WycheproofRsaPkcs12048SHA256) {
+  ASSERT_TRUE(TestSignatures("rsa_signature_2048_sha256_test.json",
+                             /*allow_skipping=*/false));
+}
+
+TEST_F(RsaSsaPkcs1VerifyBoringSslTest, WycheproofRsaPkcs13072SHA256) {
+  ASSERT_TRUE(TestSignatures("rsa_signature_3072_sha256_test.json",
+                             /*allow_skipping=*/false));
+}
+
+TEST_F(RsaSsaPkcs1VerifyBoringSslTest, WycheproofRsaPkcs13072SHA512) {
+  ASSERT_TRUE(TestSignatures("rsa_signature_3072_sha512_test.json",
+                             /*allow_skipping=*/false));
+}
+
+TEST_F(RsaSsaPkcs1VerifyBoringSslTest, WycheproofRsaPkcs14096SHA512) {
+  ASSERT_TRUE(TestSignatures("rsa_signature_4096_sha512_test.json",
+                             /*allow_skipping=*/false));
+}
+
+}  // namespace
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/subtle/rsa_ssa_pss_sign_boringssl.cc b/cc/subtle/rsa_ssa_pss_sign_boringssl.cc
new file mode 100644
index 0000000..6c6d87e
--- /dev/null
+++ b/cc/subtle/rsa_ssa_pss_sign_boringssl.cc
@@ -0,0 +1,116 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/subtle/rsa_ssa_pss_sign_boringssl.h"
+
+#include <vector>
+
+#include "absl/strings/str_cat.h"
+#include "openssl/base.h"
+#include "openssl/evp.h"
+#include "openssl/rsa.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+
+// static
+util::StatusOr<std::unique_ptr<PublicKeySign>> RsaSsaPssSignBoringSsl::New(
+    const SubtleUtilBoringSSL::RsaPrivateKey& private_key,
+    const SubtleUtilBoringSSL::RsaSsaPssParams& params) {
+  // Check hash.
+  util::Status sig_hash_valid =
+      SubtleUtilBoringSSL::ValidateSignatureHash(params.sig_hash);
+  if (!sig_hash_valid.ok()) return sig_hash_valid;
+  auto sig_hash = SubtleUtilBoringSSL::EvpHash(params.sig_hash);
+  if (!sig_hash.ok()) return sig_hash.status();
+  auto mgf1_hash = SubtleUtilBoringSSL::EvpHash(params.mgf1_hash);
+  if (!mgf1_hash.ok()) return mgf1_hash.status();
+
+  // Check RSA's modulus.
+  auto status_or_n = SubtleUtilBoringSSL::str2bn(private_key.n);
+  if (!status_or_n.ok()) return status_or_n.status();
+  auto modulus_status = SubtleUtilBoringSSL::ValidateRsaModulusSize(
+      BN_num_bits(status_or_n.ValueOrDie().get()));
+  if (!modulus_status.ok()) return modulus_status;
+
+  bssl::UniquePtr<RSA> rsa(RSA_new());
+  if (rsa == nullptr) {
+    return util::Status(util::error::INTERNAL, "Could not initialize RSA.");
+  }
+
+  {
+    auto st = SubtleUtilBoringSSL::CopyKey(private_key, rsa.get());
+    if (!st.ok()) return st;
+  }
+  {
+    auto st = SubtleUtilBoringSSL::CopyPrimeFactors(private_key, rsa.get());
+    if (!st.ok()) return st;
+  }
+  {
+    auto st = SubtleUtilBoringSSL::CopyCrtParams(private_key, rsa.get());
+    if (!st.ok()) return st;
+  }
+
+  if (RSA_check_key(rsa.get()) == 0 || RSA_check_fips(rsa.get()) == 0) {
+    return util::Status(util::error::INVALID_ARGUMENT,
+                        absl::StrCat("Could not load RSA key: ",
+                                     SubtleUtilBoringSSL::GetErrors()));
+  }
+
+  return std::unique_ptr<PublicKeySign>(
+      new RsaSsaPssSignBoringSsl(std::move(rsa), sig_hash.ValueOrDie(),
+                                 mgf1_hash.ValueOrDie(), params.salt_length));
+}
+
+RsaSsaPssSignBoringSsl::RsaSsaPssSignBoringSsl(bssl::UniquePtr<RSA> private_key,
+                                               const EVP_MD* sig_hash,
+                                               const EVP_MD* mgf1_hash,
+                                               int32_t salt_length)
+    : private_key_(std::move(private_key)),
+      sig_hash_(sig_hash),
+      mgf1_hash_(mgf1_hash),
+      salt_length_(salt_length) {}
+
+util::StatusOr<std::string> RsaSsaPssSignBoringSsl::Sign(
+    absl::string_view data) const {
+  data = SubtleUtilBoringSSL::EnsureNonNull(data);
+  auto digest_or = boringssl::ComputeHash(data, *sig_hash_);
+  if (!digest_or.ok()) return digest_or.status();
+  std::vector<uint8_t> digest = std::move(digest_or.ValueOrDie());
+
+  std::vector<uint8_t> signature(RSA_size(private_key_.get()));
+  size_t signature_length;
+
+  if (RSA_sign_pss_mgf1(private_key_.get(),
+                        /*out_len=*/&signature_length,
+                        /*out=*/signature.data(), /*max_out=*/signature.size(),
+                        /*in=*/digest.data(), /*in_len=*/digest.size(),
+                        /*md=*/sig_hash_,
+                        /*mgf1_md=*/mgf1_hash_, salt_length_) != 1) {
+    // TODO(b/112581512): Decide if it's safe to propagate the BoringSSL error.
+    // For now, just empty the error stack.
+    SubtleUtilBoringSSL::GetErrors();
+    return util::Status(util::error::INTERNAL, "Signing failed.");
+  }
+  return std::string(reinterpret_cast<const char*>(signature.data()),
+                signature_length);
+}
+
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/subtle/rsa_ssa_pss_sign_boringssl.h b/cc/subtle/rsa_ssa_pss_sign_boringssl.h
new file mode 100644
index 0000000..1ae374b
--- /dev/null
+++ b/cc/subtle/rsa_ssa_pss_sign_boringssl.h
@@ -0,0 +1,66 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_SUBTLE_RSA_SSA_PSS_SIGN_BORINGSSL_H_
+#define TINK_SUBTLE_RSA_SSA_PSS_SIGN_BORINGSSL_H_
+
+#include <memory>
+
+#include "absl/strings/string_view.h"
+#include "openssl/base.h"
+#include "openssl/ec.h"
+#include "openssl/rsa.h"
+#include "tink/public_key_sign.h"
+#include "tink/subtle/common_enums.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+
+// The RSA SSA (Signature Schemes with Appendix) using PSS (Probabilistic
+// Signature Scheme) encoding is defined at
+// https://tools.ietf.org/html/rfc8017#section-8.1). This implemention uses
+// Boring SSL for the underlying cryptographic operations.
+class RsaSsaPssSignBoringSsl : public PublicKeySign {
+ public:
+  static crypto::tink::util::StatusOr<std::unique_ptr<PublicKeySign>> New(
+      const SubtleUtilBoringSSL::RsaPrivateKey& private_key,
+      const SubtleUtilBoringSSL::RsaSsaPssParams& params);
+
+  // Computes the signature for 'data'.
+  crypto::tink::util::StatusOr<std::string> Sign(
+      absl::string_view data) const override;
+
+  ~RsaSsaPssSignBoringSsl() override = default;
+
+ private:
+  const bssl::UniquePtr<RSA> private_key_;
+  const EVP_MD* sig_hash_;   // Owned by BoringSSL.
+  const EVP_MD* mgf1_hash_;  // Owned by BoringSSL.
+  int32_t salt_length_;
+
+  RsaSsaPssSignBoringSsl(bssl::UniquePtr<RSA> private_key,
+                         const EVP_MD* sig_hash, const EVP_MD* mgf1_hash,
+                         int32_t salt_length);
+};
+
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_SUBTLE_RSA_SSA_PSS_SIGN_BORINGSSL_H_
diff --git a/cc/subtle/rsa_ssa_pss_sign_boringssl_test.cc b/cc/subtle/rsa_ssa_pss_sign_boringssl_test.cc
new file mode 100644
index 0000000..ffcdb27
--- /dev/null
+++ b/cc/subtle/rsa_ssa_pss_sign_boringssl_test.cc
@@ -0,0 +1,139 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/subtle/rsa_ssa_pss_sign_boringssl.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/strings/escaping.h"
+#include "openssl/base.h"
+#include "openssl/bn.h"
+#include "openssl/rsa.h"
+#include "tink/subtle/rsa_ssa_pss_verify_boringssl.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/util/test_matchers.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+namespace {
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::testing::IsEmpty;
+using ::testing::Not;
+
+class RsaPssSignBoringsslTest : public ::testing::Test {
+ public:
+  RsaPssSignBoringsslTest() : rsa_f4_(BN_new()) {
+    EXPECT_TRUE(BN_set_u64(rsa_f4_.get(), RSA_F4));
+    EXPECT_THAT(SubtleUtilBoringSSL::GetNewRsaKeyPair(
+                    2048, rsa_f4_.get(), &private_key_, &public_key_),
+                IsOk());
+  }
+
+ protected:
+  bssl::UniquePtr<BIGNUM> rsa_f4_;
+  SubtleUtilBoringSSL::RsaPrivateKey private_key_;
+  SubtleUtilBoringSSL::RsaPublicKey public_key_;
+};
+
+TEST_F(RsaPssSignBoringsslTest, EncodesPss) {
+  SubtleUtilBoringSSL::RsaSsaPssParams params{/*sig_hash=*/HashType::SHA256,
+                                              /*mgf1_hash=*/HashType::SHA256,
+                                              /*salt_length=*/32};
+
+  auto signer_or = RsaSsaPssSignBoringSsl::New(private_key_, params);
+  ASSERT_THAT(signer_or.status(), IsOk());
+
+  auto signature_or = signer_or.ValueOrDie()->Sign("testdata");
+  ASSERT_THAT(signature_or.status(), IsOk());
+  EXPECT_THAT(signature_or.ValueOrDie(), Not(IsEmpty()));
+
+  auto verifier_or = RsaSsaPssVerifyBoringSsl::New(public_key_, params);
+  ASSERT_THAT(verifier_or.status(), IsOk());
+  EXPECT_THAT(
+      verifier_or.ValueOrDie()->Verify(signature_or.ValueOrDie(), "testdata"),
+      IsOk());
+}
+
+TEST_F(RsaPssSignBoringsslTest, EncodesPssWithSeparateHashes) {
+  SubtleUtilBoringSSL::RsaSsaPssParams params{/*sig_hash=*/HashType::SHA256,
+                                              /*mgf1_hash=*/HashType::SHA1,
+                                              /*salt_length=*/32};
+
+  auto signer_or = RsaSsaPssSignBoringSsl::New(private_key_, params);
+  ASSERT_THAT(signer_or.status(), IsOk());
+
+  auto signature_or = signer_or.ValueOrDie()->Sign("testdata");
+  ASSERT_THAT(signature_or.status(), IsOk());
+  EXPECT_THAT(signature_or.ValueOrDie(), Not(IsEmpty()));
+
+  auto verifier_or = RsaSsaPssVerifyBoringSsl::New(public_key_, params);
+  ASSERT_THAT(verifier_or.status(), IsOk());
+  EXPECT_THAT(
+      verifier_or.ValueOrDie()->Verify(signature_or.ValueOrDie(), "testdata"),
+      IsOk());
+}
+
+TEST_F(RsaPssSignBoringsslTest, RejectsInvalidPaddingHash) {
+  SubtleUtilBoringSSL::RsaSsaPssParams params{
+      /*sig_hash=*/HashType::SHA256, /*mgf1_hash=*/HashType::UNKNOWN_HASH,
+      /*salt_length=*/0};
+  ASSERT_THAT(RsaSsaPssSignBoringSsl::New(private_key_, params).status(),
+              StatusIs(util::error::UNIMPLEMENTED));
+}
+
+TEST_F(RsaPssSignBoringsslTest, RejectsUnsafePaddingHash) {
+  SubtleUtilBoringSSL::RsaSsaPssParams params{/*sig_hash=*/HashType::SHA1,
+                                              /*mgf1_hash=*/HashType::SHA1,
+                                              /*salt_length=*/0};
+  ASSERT_THAT(RsaSsaPssSignBoringSsl::New(private_key_, params).status(),
+              StatusIs(util::error::INVALID_ARGUMENT));
+}
+
+TEST_F(RsaPssSignBoringsslTest, RejectsInvalidCrtParams) {
+  SubtleUtilBoringSSL::RsaSsaPssParams params{/*sig_hash=*/HashType::SHA256,
+                                              /*mgf1_hash=*/HashType::SHA256,
+                                              /*salt_length=*/32};
+  ASSERT_THAT(private_key_.crt, Not(IsEmpty()));
+  ASSERT_THAT(private_key_.dq, Not(IsEmpty()));
+  ASSERT_THAT(private_key_.dp, Not(IsEmpty()));
+
+  // Flip a few bits in the CRT parameters; check that creation fails.
+  {
+    SubtleUtilBoringSSL::RsaPrivateKey key = private_key_;
+    key.crt[0] ^= 0x80;
+    auto signer_or = RsaSsaPssSignBoringSsl::New(key, params);
+    EXPECT_THAT(signer_or.status(), StatusIs(util::error::INVALID_ARGUMENT));
+  }
+  {
+    SubtleUtilBoringSSL::RsaPrivateKey key = private_key_;
+    key.dq[0] ^= 0x08;
+    auto signer_or = RsaSsaPssSignBoringSsl::New(key, params);
+    EXPECT_THAT(signer_or.status(), StatusIs(util::error::INVALID_ARGUMENT));
+  }
+  {
+    SubtleUtilBoringSSL::RsaPrivateKey key = private_key_;
+    key.dp[0] ^= 0x04;
+    auto signer_or = RsaSsaPssSignBoringSsl::New(key, params);
+    EXPECT_THAT(signer_or.status(), StatusIs(util::error::INVALID_ARGUMENT));
+  }
+}
+
+}  // namespace
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/subtle/rsa_ssa_pss_verify_boringssl.cc b/cc/subtle/rsa_ssa_pss_verify_boringssl.cc
index 3193e04..bdf0407 100644
--- a/cc/subtle/rsa_ssa_pss_verify_boringssl.cc
+++ b/cc/subtle/rsa_ssa_pss_verify_boringssl.cc
@@ -27,20 +27,6 @@
 namespace tink {
 namespace subtle {
 
-// Computes hash of 'input' using the hash function 'hasher'.
-util::StatusOr<std::string> ComputeHash(absl::string_view input,
-                                   const EVP_MD& hasher) {
-  uint32_t digest_length = EVP_MAX_MD_SIZE;
-  std::unique_ptr<uint8_t[]> digest(new uint8_t[digest_length]);
-  if (EVP_Digest(input.data(), input.length(), digest.get(), &digest_length,
-                 &hasher, nullptr /* ENGINE */) != 1) {
-    return util::Status(util::error::INTERNAL,
-                        absl::StrCat("Openssl internal error computing hash: ",
-                                     SubtleUtilBoringSSL::GetErrors()));
-  }
-  return std::string(reinterpret_cast<const char*>(digest.get()), digest_length);
-}
-
 // static
 util::StatusOr<std::unique_ptr<RsaSsaPssVerifyBoringSsl>>
 RsaSsaPssVerifyBoringSsl::New(
@@ -64,13 +50,9 @@
   if (!status_or_n.ok()) return status_or_n.status();
   auto status_or_e = SubtleUtilBoringSSL::str2bn(pub_key.e);
   if (!status_or_e.ok()) return status_or_e.status();
-  size_t modulus_size = BN_num_bits(status_or_n.ValueOrDie().get());
-  if (modulus_size < kMinModulusSizeInBits) {
-    return ToStatusF(
-        util::error::INVALID_ARGUMENT,
-        "Modulus size is %zu; only modulus size >= 2048-bit is supported",
-        modulus_size);
-  }
+  auto modulus_status = SubtleUtilBoringSSL::ValidateRsaModulusSize(
+      BN_num_bits(status_or_n.ValueOrDie().get()));
+  if (!modulus_status.ok()) return modulus_status;
   bssl::UniquePtr<RSA> rsa(RSA_new());
   if (rsa.get() == nullptr) {
     return util::Status(util::error::INTERNAL,
@@ -104,17 +86,17 @@
   // regardless of whether the size is 0.
   data = SubtleUtilBoringSSL::EnsureNonNull(data);
 
-  auto digest_result = ComputeHash(data, *sig_hash_);
+  auto digest_result = boringssl::ComputeHash(data, *sig_hash_);
   if (!digest_result.ok()) return digest_result.status();
-  std::string digest = digest_result.ValueOrDie();
+  auto digest = std::move(digest_result.ValueOrDie());
 
   if (1 != RSA_verify_pss_mgf1(
-               rsa_.get(), reinterpret_cast<const uint8_t*>(digest.data()),
-               digest.size(), sig_hash_, mgf1_hash_, salt_length_,
-               reinterpret_cast<const uint8_t*>(signature.data()),
+               rsa_.get(), digest.data(), digest.size(), sig_hash_, mgf1_hash_,
+               salt_length_, reinterpret_cast<const uint8_t*>(signature.data()),
                signature.length())) {
     // Signature is invalid.
-    return util::Status(util::error::UNKNOWN, "Signature is not valid.");
+    return util::Status(util::error::INVALID_ARGUMENT,
+                        "Signature is not valid.");
   }
   return util::Status::OK;
 }
diff --git a/cc/subtle/rsa_ssa_pss_verify_boringssl.h b/cc/subtle/rsa_ssa_pss_verify_boringssl.h
index b38f173..b5a3f19 100644
--- a/cc/subtle/rsa_ssa_pss_verify_boringssl.h
+++ b/cc/subtle/rsa_ssa_pss_verify_boringssl.h
@@ -14,8 +14,8 @@
 //
 ///////////////////////////////////////////////////////////////////////////////
 
-#ifndef THIRD_PARTY_TINK_CC_SUBTLE_RSA_SSA_PSS_VERIFY_BORINGSSL_H_
-#define THIRD_PARTY_TINK_CC_SUBTLE_RSA_SSA_PSS_VERIFY_BORINGSSL_H_
+#ifndef TINK_SUBTLE_RSA_SSA_PSS_VERIFY_BORINGSSL_H_
+#define TINK_SUBTLE_RSA_SSA_PSS_VERIFY_BORINGSSL_H_
 
 #include <memory>
 
@@ -48,11 +48,6 @@
   ~RsaSsaPssVerifyBoringSsl() override = default;
 
  private:
-  // To reach 128-bit security strength, RSA's modulus must be at least 3072-bit
-  // while 2048-bit RSA key only has 112-bit security. Nevertheless, a 2048-bit
-  // RSA key is considered safe by NIST until 2030 (see
-  // https://www.keylength.com/en/4/).
-  static const size_t kMinModulusSizeInBits = 2048;
   RsaSsaPssVerifyBoringSsl(bssl::UniquePtr<RSA> rsa, const EVP_MD* sig_hash,
                            const EVP_MD* mgf1_hash, int salt_length);
   const bssl::UniquePtr<RSA> rsa_;
@@ -65,4 +60,4 @@
 }  // namespace tink
 }  // namespace crypto
 
-#endif  // THIRD_PARTY_TINK_CC_SUBTLE_RSA_SSA_PSS_VERIFY_BORINGSSL_H_
+#endif  // TINK_SUBTLE_RSA_SSA_PSS_VERIFY_BORINGSSL_H_
diff --git a/cc/subtle/rsa_ssa_pss_verify_boringssl_test.cc b/cc/subtle/rsa_ssa_pss_verify_boringssl_test.cc
index 9e4be8e..cdd8ba8 100644
--- a/cc/subtle/rsa_ssa_pss_verify_boringssl_test.cc
+++ b/cc/subtle/rsa_ssa_pss_verify_boringssl_test.cc
@@ -32,7 +32,6 @@
 #include "tink/util/test_util.h"
 
 // TODO(quannguyen):
-//  + Add Wycheproof test once it's available.
 //  + Add tests for parameters validation.
 namespace crypto {
 namespace tink {
@@ -82,7 +81,7 @@
     HashType::SHA256,
     32};
 
-TEST_F(RsaSsaPssVerifyBoringSslTest, testBasicVerify) {
+TEST_F(RsaSsaPssVerifyBoringSslTest, BasicVerify) {
   SubtleUtilBoringSSL::RsaPublicKey pub_key{nist_test_vector.n,
                                             nist_test_vector.e};
   SubtleUtilBoringSSL::RsaSsaPssParams params{nist_test_vector.sig_hash,
@@ -97,7 +96,7 @@
   EXPECT_TRUE(status.ok()) << status << SubtleUtilBoringSSL::GetErrors();
 }
 
-TEST_F(RsaSsaPssVerifyBoringSslTest, testNewErrors) {
+TEST_F(RsaSsaPssVerifyBoringSslTest, NewErrors) {
   SubtleUtilBoringSSL::RsaPublicKey nist_pub_key{nist_test_vector.n,
                                                  nist_test_vector.e};
   SubtleUtilBoringSSL::RsaSsaPssParams nist_params{
@@ -127,7 +126,7 @@
   }
 }
 
-TEST_F(RsaSsaPssVerifyBoringSslTest, testModification) {
+TEST_F(RsaSsaPssVerifyBoringSslTest, Modification) {
   SubtleUtilBoringSSL::RsaPublicKey pub_key{nist_test_vector.n,
                                             nist_test_vector.e};
   SubtleUtilBoringSSL::RsaSsaPssParams params{nist_test_vector.sig_hash,
@@ -138,7 +137,7 @@
   ASSERT_TRUE(verifier_result.ok()) << verifier_result.status();
   auto verifier = std::move(verifier_result.ValueOrDie());
   // Modify the message.
-  for (int i = 0; i < nist_test_vector.message.length(); i++) {
+  for (std::size_t i = 0; i < nist_test_vector.message.length(); i++) {
     std::string modified_message = nist_test_vector.message;
     modified_message[i / 8] ^= 1 << (i % 8);
     auto status =
@@ -146,7 +145,7 @@
     EXPECT_FALSE(status.ok()) << status << SubtleUtilBoringSSL::GetErrors();
   }
   // Modify the signature.
-  for (int i = 0; i < nist_test_vector.signature.length(); i++) {
+  for (std::size_t i = 0; i < nist_test_vector.signature.length(); i++) {
     std::string modified_signature = nist_test_vector.signature;
     modified_signature[i / 8] ^= 1 << (i % 8);
     auto status =
@@ -154,7 +153,7 @@
     EXPECT_FALSE(status.ok()) << status << SubtleUtilBoringSSL::GetErrors();
   }
   // Truncate the signature.
-  for (int i = 0; i < nist_test_vector.signature.length(); i++) {
+  for (std::size_t i = 0; i < nist_test_vector.signature.length(); i++) {
     std::string truncated_signature(nist_test_vector.signature, 0, i);
     auto status =
         verifier->Verify(truncated_signature, nist_test_vector.message);
@@ -162,6 +161,121 @@
   }
 }
 
+static util::StatusOr<std::unique_ptr<RsaSsaPssVerifyBoringSsl>> GetVerifier(
+    const rapidjson::Value& test_group) {
+  SubtleUtilBoringSSL::RsaPublicKey key;
+  key.n = WycheproofUtil::GetInteger(test_group["n"]);
+  key.e = WycheproofUtil::GetInteger(test_group["e"]);
+
+  SubtleUtilBoringSSL::RsaSsaPssParams params;
+  params.sig_hash = WycheproofUtil::GetHashType(test_group["sha"]);
+  params.mgf1_hash = WycheproofUtil::GetHashType(test_group["mgfSha"]);
+  params.salt_length = test_group["sLen"].GetInt();
+
+  auto result = RsaSsaPssVerifyBoringSsl::New(key, params);
+  if (!result.ok()) {
+    std::cout << "Failed: " << result.status() << "\n";
+  }
+  return result;
+}
+
+// Tests signature verification using the test vectors in the specified file.
+// allow_skipping determines whether it is OK to skip a test because
+// a verfier cannot be constructed. This option can be used for
+// if a file contains test vectors that are not necessarily supported
+// by tink.
+bool TestSignatures(const std::string& filename, bool allow_skipping) {
+  std::unique_ptr<rapidjson::Document> root =
+      WycheproofUtil::ReadTestVectors(filename);
+  std::cout << (*root)["algorithm"].GetString();
+  std::cout << "generator version " << (*root)["generatorVersion"].GetString();
+  std::cout << "expected version 0.4.12";
+  int passed_tests = 0;
+  int failed_tests = 0;
+  int group_count = 0;
+  for (const rapidjson::Value& test_group : (*root)["testGroups"].GetArray()) {
+    group_count++;
+    auto verifier_result = GetVerifier(test_group);
+    if (!verifier_result.ok()) {
+      std::string type = test_group["type"].GetString();
+      if (allow_skipping) {
+        std::cout << "Could not construct verifier for " << type << " group "
+                  << group_count << ": " << verifier_result.status();
+      } else {
+        ADD_FAILURE() << "Could not construct verifier for " << type
+                      << " group " << group_count << ": "
+                      << verifier_result.status();
+        failed_tests += test_group["tests"].GetArray().Size();
+      }
+      continue;
+    }
+    auto verifier = std::move(verifier_result.ValueOrDie());
+    for (const rapidjson::Value& test : test_group["tests"].GetArray()) {
+      std::string expected = test["result"].GetString();
+      std::string msg = WycheproofUtil::GetBytes(test["msg"]);
+      std::string sig = WycheproofUtil::GetBytes(test["sig"]);
+      std::string id =
+          absl::StrCat(test["tcId"].GetInt(), " ", test["comment"].GetString());
+      auto status = verifier->Verify(sig, msg);
+      if (expected == "valid") {
+        if (status.ok()) {
+          ++passed_tests;
+        } else {
+          ++failed_tests;
+          ADD_FAILURE() << "Valid signature not verified:" << id
+                        << " status:" << status;
+        }
+      } else if (expected == "invalid") {
+        if (!status.ok()) {
+          ++passed_tests;
+        } else {
+          ++failed_tests;
+          ADD_FAILURE() << "Invalid signature verified:" << id;
+        }
+      } else if (expected == "acceptable") {
+        // The validity of the signature is undefined. Hence the test passes
+        // but we log the result since we might still want to know if the
+        // library is strict or forgiving.
+        ++passed_tests;
+        std::cout << "Acceptable signature:" << id << ":" << status;
+      } else {
+        ++failed_tests;
+        ADD_FAILURE() << "Invalid field result:" << expected;
+      }
+    }
+  }
+  int num_tests = (*root)["numberOfTests"].GetInt();
+  std::cout << "total number of tests: " << num_tests;
+  std::cout << "number of tests passed:" << passed_tests;
+  std::cout << "number of tests failed:" << failed_tests;
+  return failed_tests == 0;
+}
+
+TEST_F(RsaSsaPssVerifyBoringSslTest, WycheproofRsaPss2048Sha2560) {
+  ASSERT_TRUE(TestSignatures("rsa_pss_2048_sha256_mgf1_0_test.json",
+                             /*allow_skipping=*/false));
+}
+
+TEST_F(RsaSsaPssVerifyBoringSslTest, WycheproofRsaPss2048Sha25632) {
+  ASSERT_TRUE(TestSignatures("rsa_pss_2048_sha256_mgf1_32_test.json",
+                             /*allow_skipping=*/false));
+}
+
+TEST_F(RsaSsaPssVerifyBoringSslTest, WycheproofRsaPss3072Sha25632) {
+  ASSERT_TRUE(TestSignatures("rsa_pss_3072_sha256_mgf1_32_test.json",
+                             /*allow_skipping=*/false));
+}
+
+TEST_F(RsaSsaPssVerifyBoringSslTest, WycheproofRsaPss4096Sha25632) {
+  ASSERT_TRUE(TestSignatures("rsa_pss_4096_sha256_mgf1_32_test.json",
+                             /*allow_skipping=*/false));
+}
+
+TEST_F(RsaSsaPssVerifyBoringSslTest, WycheproofRsaPss4096Sha51232) {
+  ASSERT_TRUE(TestSignatures("rsa_pss_4096_sha512_mgf1_32_test.json",
+                             /*allow_skipping=*/false));
+}
+
 }  // namespace
 }  // namespace subtle
 }  // namespace tink
diff --git a/cc/subtle/stream_segment_encrypter.h b/cc/subtle/stream_segment_encrypter.h
new file mode 100644
index 0000000..f265d23
--- /dev/null
+++ b/cc/subtle/stream_segment_encrypter.h
@@ -0,0 +1,106 @@
+// Copyright 2019 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_SUBTLE_STREAM_SEGMENT_ENCRYPTER_H_
+#define TINK_SUBTLE_STREAM_SEGMENT_ENCRYPTER_H_
+
+#include <vector>
+
+#include "tink/util/status.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+
+// StreamSegmentEncrypter is a helper class that encrypts individual
+// segments of a stream.
+//
+// Instances of this are passed to an ...EncryptingStream. Each instance
+// of a segment encrypter is used to encrypt one stream.
+//
+// Typically, construction of a new StreamSegmentEncrypter results
+// in a generation of a new symmetric key, which is used to
+// the segments of the stream.  The key itself wrapped with or derived
+// from the key from StreamingAead instance. The wrapped key or the salt
+// used to derive the symmetric key is part of the header.
+//
+// StreamSegmentEncrypter has a state: it keeps the number of segments
+// encrypted so far. This state is used to encrypt each segment with different
+// parameters, so that segments in the ciphertext cannot be switched.
+//
+// Values returned by StreamSegmentEncrypter's methods effectively define
+// the layout of the resulting ciphertext stream:
+//
+//   | other | header | 1st ciphertext segment |
+//   | ......    2nd ciphertext segment  ..... |
+//   | ......    3rd ciphertext segment  ..... |
+//   | ......    ...                     ..... |
+//   | ......    last ciphertext segment |
+//
+// where the following holds:
+//  * each line above, except for the last one,
+//    contains get_ciphertext_segment_size() bytes
+//  * each segment, except for the 1st and the last one,
+//    encrypts get_plaintext_segment_size() bytes of plaintext
+//  * if the ciphertext stream encrypts at least one byte of plaintext,
+//    then the last segment encrypts at least one byte of plaintext
+//  * 'other' is (get_ciphertext_offset() - get_header().size()) bytes long,
+//    and represents potential other bytes already written to the stream;
+//    the purpose of ciphertext offset is to allow alignment of ciphertext
+//    segments with segments of the underlying storage or transmission stream.
+class StreamSegmentEncrypter {
+ public:
+  // Encrypts 'plaintext' as a segment, and writes the resulting ciphertext
+  // to 'ciphertext_buffer', adjusting its size as needed.
+  // 'plaintext' and 'ciphertext_buffer' must refer to distinct and
+  // non-overlapping space.
+  // Encryption uses the current value returned by get_segment_number()
+  // as the segment number, and subsequently increments the current
+  // segment number.
+  virtual util::Status EncryptSegment(
+      const std::vector<uint8_t>& plaintext,
+      bool is_last_segment,
+      std::vector<uint8_t>* ciphertext_buffer) = 0;
+
+  // Returns the header of the ciphertext stream.
+  virtual const std::vector<uint8_t>& get_header() const = 0;
+
+  // Returns the segment number that will be used for encryption
+  // of the next segment.
+  virtual int64_t get_segment_number() const = 0;
+
+  // Returns the size (in bytes) of a plaintext segment.
+  virtual int get_plaintext_segment_size() const = 0;
+
+  // Returns the size (in bytes) of a ciphertext segment.
+  virtual int get_ciphertext_segment_size() const = 0;
+
+  // Returns the offset (in bytes) of the ciphertext within an encrypted stream.
+  // The offset is not smaller than the size of the header.
+  virtual int get_ciphertext_offset() const = 0;
+
+  virtual ~StreamSegmentEncrypter() {}
+
+ protected:
+  // Increments the segment number.
+  virtual void IncSegmentNumber() = 0;
+};
+
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_SUBTLE_STREAM_SEGMENT_ENCRYPTER_H_
diff --git a/cc/subtle/streaming_aead_encrypting_stream.cc b/cc/subtle/streaming_aead_encrypting_stream.cc
new file mode 100644
index 0000000..fd42881
--- /dev/null
+++ b/cc/subtle/streaming_aead_encrypting_stream.cc
@@ -0,0 +1,209 @@
+// Copyright 2019 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/subtle/streaming_aead_encrypting_stream.h"
+
+#include <algorithm>
+#include <cstring>
+
+#include "absl/memory/memory.h"
+#include "tink/output_stream.h"
+#include "tink/subtle/stream_segment_encrypter.h"
+#include "tink/util/statusor.h"
+
+using crypto::tink::OutputStream;
+using crypto::tink::util::Status;
+using crypto::tink::util::StatusOr;
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+
+namespace {
+
+
+// Writes 'contents' the specified 'output_stream', which must be non-null.
+// In case of errors returns the first non-OK status of
+// output_stream->Next()-operation.
+
+util::Status WriteToStream(const std::vector<uint8_t>& contents,
+                           OutputStream* output_stream) {
+  void* buffer;
+  int pos = 0;
+  int remaining = contents.size();
+  int available_space;
+  int available_bytes;
+  while (remaining > 0) {
+    auto next_result = output_stream->Next(&buffer);
+    if (!next_result.ok()) return next_result.status();
+    available_space = next_result.ValueOrDie();
+    available_bytes = std::min(available_space, remaining);
+    memcpy(buffer, contents.data() + pos, available_bytes);
+    remaining -= available_bytes;
+    pos += available_bytes;
+  }
+  if (available_space > available_bytes) {
+    output_stream->BackUp(available_space - available_bytes);
+  }
+  return Status::OK;
+}
+
+}  // anonymous namespace
+
+// static
+StatusOr<std::unique_ptr<OutputStream>> StreamingAeadEncryptingStream::New(
+    std::unique_ptr<StreamSegmentEncrypter> segment_encrypter,
+    std::unique_ptr<OutputStream> ciphertext_destination) {
+  if (segment_encrypter == nullptr) {
+    return Status(util::error::INVALID_ARGUMENT,
+                  "segment_encrypter must be non-null");
+  }
+  if (ciphertext_destination == nullptr) {
+    return Status(util::error::INVALID_ARGUMENT,
+                  "cipertext_destination must be non-null");
+  }
+  std::unique_ptr<StreamingAeadEncryptingStream> enc_stream(
+      new StreamingAeadEncryptingStream());
+  enc_stream->segment_encrypter_ = std::move(segment_encrypter);
+  enc_stream->ct_destination_ = std::move(ciphertext_destination);
+  int first_segment_size =
+      enc_stream->segment_encrypter_->get_plaintext_segment_size() -
+      enc_stream->segment_encrypter_->get_ciphertext_offset();
+  if (first_segment_size <= 0) {
+    return Status(util::error::INTERNAL,
+                  "Size of the first segment must be greater than 0.");
+  }
+  enc_stream->pt_buffer_.resize(first_segment_size);
+  enc_stream->pt_to_encrypt_.resize(0);
+  enc_stream->position_ = 0;
+  enc_stream->is_first_segment_ = true;
+  enc_stream->count_backedup_ = first_segment_size;
+  enc_stream->pt_buffer_offset_ = 0;
+  return {std::move(enc_stream)};
+}
+
+StatusOr<int> StreamingAeadEncryptingStream::Next(void** data) {
+  if (!status_.ok()) return status_;
+
+  // The first call to Next().
+  if (is_first_segment_) {
+    is_first_segment_ = false;
+    count_backedup_ = 0;
+    status_ = WriteToStream(segment_encrypter_->get_header(),
+                            ct_destination_.get());
+    if (!status_.ok()) return status_;
+    *data = pt_buffer_.data();
+    position_ = pt_buffer_.size();
+    return pt_buffer_.size();
+  }
+
+  // If some space was backed up, return it first.
+  if (count_backedup_ > 0) {
+    position_ += count_backedup_;
+    pt_buffer_offset_ = pt_buffer_.size() - pt_buffer_offset_ - count_backedup_;
+    int backedup = count_backedup_;
+    count_backedup_ = 0;
+    *data = pt_buffer_.data() + pt_buffer_offset_;
+    return backedup;
+  }
+
+  // We're past the first segment, and no space was backed up, so we:
+  // 1. encrypt pt_to_encrypt_ (if non-empty) as a not-last segment
+  //    and attempt to write the ciphertext to ct_destination_.
+  // 2. move contents of pt_buffer_ to pt_to_encrypt_ (for later encryption,
+  //    as we don't know yet whether it will be the last segment or not.
+  // 3. prepare and return "fresh" pt_buffer_.
+  //
+  // Step 1.
+  if (!pt_to_encrypt_.empty()) {
+    status_ = segment_encrypter_->EncryptSegment(
+        pt_to_encrypt_, /* is_last_segment = */ false, &ct_buffer_);
+    if (!status_.ok()) return status_;
+    status_ = WriteToStream(ct_buffer_, ct_destination_.get());
+    if (!status_.ok()) return status_;
+  }
+  // Step 2.
+  pt_buffer_.swap(pt_to_encrypt_);
+  // Step 3.
+  pt_buffer_.resize(segment_encrypter_->get_plaintext_segment_size());
+  *data = pt_buffer_.data();
+  pt_buffer_offset_ = 0;
+  position_ += pt_buffer_.size();
+  return pt_buffer_.size();
+}
+
+void StreamingAeadEncryptingStream::BackUp(int count) {
+  if (is_first_segment_ || !status_.ok() || count < 1) return;
+  int curr_buffer_size = pt_buffer_.size() - pt_buffer_offset_;
+  int actual_count = std::min(count, curr_buffer_size - count_backedup_);
+  count_backedup_ += actual_count;
+  position_ -= actual_count;
+}
+
+Status StreamingAeadEncryptingStream::Close() {
+  if (!status_.ok()) return status_;
+  if (is_first_segment_) {  // Next() was never called.
+    status_ = WriteToStream(segment_encrypter_->get_header(),
+                            ct_destination_.get());
+    if (!status_.ok()) return status_;
+  }
+
+  // The last segment encrypts plaintext from pt_to_encrypt_,
+  // unless the current pt_buffer_ has some plaintext bytes.
+  std::vector<uint8_t>* pt_last_segment = &pt_to_encrypt_;
+  if ((!pt_buffer_.empty()) && count_backedup_ < pt_buffer_.size()) {
+    // The last segment encrypts plaintext from pt_buffer_.
+    pt_buffer_.resize(pt_buffer_.size() - count_backedup_);
+    pt_last_segment = &pt_buffer_;
+  }
+  if (pt_last_segment != &pt_to_encrypt_ && (!pt_to_encrypt_.empty())) {
+    // Before writing the last segment we must encrypt pt_to_encrypt_.
+    status_ = segment_encrypter_->EncryptSegment(
+      pt_to_encrypt_, /* is_last_segment = */ false, &ct_buffer_);
+    if (!status_.ok()) {
+      ct_destination_->Close();
+      return status_;
+    }
+    status_ = WriteToStream(ct_buffer_, ct_destination_.get());
+    if (!status_.ok()) {
+      ct_destination_->Close();
+      return status_;
+    }
+  }
+
+  // Encrypt pt_last_segment, write the ciphertext, and close the stream.
+  status_ = segment_encrypter_->EncryptSegment(
+      *pt_last_segment, /* is_last_segment = */ true, &ct_buffer_);
+  if (!status_.ok()) {
+    ct_destination_->Close();
+    return status_;
+  }
+  status_ = WriteToStream(ct_buffer_, ct_destination_.get());
+  if (!status_.ok()) {
+    ct_destination_->Close();
+    return status_;
+  }
+  status_ = Status(util::error::FAILED_PRECONDITION, "Stream closed");
+  return ct_destination_->Close();
+}
+
+int64_t StreamingAeadEncryptingStream::Position() const {
+  return position_;
+}
+
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/subtle/streaming_aead_encrypting_stream.h b/cc/subtle/streaming_aead_encrypting_stream.h
new file mode 100644
index 0000000..ff13d57
--- /dev/null
+++ b/cc/subtle/streaming_aead_encrypting_stream.h
@@ -0,0 +1,78 @@
+// Copyright 2019 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_SUBTLE_STREAMING_AEAD_ENCRYPTING_STREAM_H_
+#define TINK_SUBTLE_STREAMING_AEAD_ENCRYPTING_STREAM_H_
+
+#include <memory>
+#include <vector>
+
+#include "tink/output_stream.h"
+#include "tink/subtle/stream_segment_encrypter.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+
+class NonceBasedStreamingAead;
+
+class StreamingAeadEncryptingStream : public OutputStream {
+ public:
+  // A factory that produces encrypting streams.
+  // The returned stream is a wrapper around 'ciphertext_destination',
+  // such that any bytes written via the wrapper are streaming AEAD-encrypted
+  // via 'nonce_based_streaming_aead' using 'associated_data' as
+  // associated authenticated data.
+  static
+  crypto::tink::util::StatusOr<std::unique_ptr<crypto::tink::OutputStream>>
+      New(std::unique_ptr<StreamSegmentEncrypter> segment_encrypter,
+          std::unique_ptr<crypto::tink::OutputStream> ciphertext_destination);
+
+  // -----------------------
+  // Methods of OutputStream-interface implemented by this class.
+  crypto::tink::util::StatusOr<int> Next(void** data) override;
+  void BackUp(int count) override;
+  crypto::tink::util::Status Close() override;
+  int64_t Position() const override;
+
+ private:
+  StreamingAeadEncryptingStream() {}
+  std::unique_ptr<StreamSegmentEncrypter> segment_encrypter_;
+  std::unique_ptr<crypto::tink::OutputStream> ct_destination_;
+  std::vector<uint8_t> pt_buffer_;  // plaintext buffer
+  std::vector<uint8_t> ct_buffer_;  // ciphertext buffer
+  std::vector<uint8_t> pt_to_encrypt_;  // plaintext to be encrypted
+  int64_t position_;  // number of plaintext bytes written to this stream
+  crypto::tink::util::Status status_;  // status of the stream
+
+  // Counters that describe the state of the data in pt_buffer_.
+  int count_backedup_;    // # bytes in pt_buffer_ that were backed up
+  int pt_buffer_offset_;  // offset at which *data starts in pt_buffer_
+
+  // Flag that indicates user obtained a buffer to write data of
+  // the first segment.
+  // If true, Next() was not called yet, which implies that neither
+  // header has been written to ct_destination_, nor the user had
+  // a chance to write any data to this stream.
+  bool is_first_segment_;
+};
+
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_SUBTLE_STREAMING_AEAD_ENCRYPTING_STREAM_H_
diff --git a/cc/subtle/streaming_aead_encrypting_stream_test.cc b/cc/subtle/streaming_aead_encrypting_stream_test.cc
new file mode 100644
index 0000000..60f0a1b
--- /dev/null
+++ b/cc/subtle/streaming_aead_encrypting_stream_test.cc
@@ -0,0 +1,604 @@
+// Copyright 2019 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/subtle/streaming_aead_encrypting_stream.h"
+
+#include <sstream>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "absl/memory/memory.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "tink/output_stream.h"
+#include "tink/subtle/stream_segment_encrypter.h"
+#include "tink/subtle/random.h"
+#include "tink/util/ostream_output_stream.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+using crypto::tink::OutputStream;
+using crypto::tink::util::OstreamOutputStream;
+using crypto::tink::util::Status;
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+namespace {
+
+// Writes 'contents' the specified 'output_stream', and closes the stream.
+// Returns the status of output_stream->Close()-operation, or a non-OK status
+// of a prior output_stream->Next()-operation, if any.
+Status WriteToStream(OutputStream* output_stream,
+                     absl::string_view contents) {
+  void* buffer;
+  int pos = 0;
+  int remaining = contents.length();
+  int available_space;
+  int available_bytes;
+  while (remaining > 0) {
+    auto next_result = output_stream->Next(&buffer);
+    if (!next_result.ok()) return next_result.status();
+    available_space = next_result.ValueOrDie();
+    available_bytes = std::min(available_space, remaining);
+    memcpy(buffer, contents.data() + pos, available_bytes);
+    remaining -= available_bytes;
+    pos += available_bytes;
+  }
+  if (available_space > available_bytes) {
+    output_stream->BackUp(available_space - available_bytes);
+  }
+  return output_stream->Close();
+}
+
+// Size of the per-segment tag added upon encryption.
+const int kSegmentTagSize = sizeof(int64_t) + 1;
+
+// Bytes for marking whether a given segment is the last one.
+const char kLastSegment = 'l';
+const char kNotLastSegment = 'n';
+
+
+// A dummy encrypter that "encrypts" by just appending to the plaintext
+// the current segment number and a marker byte indicating whether
+// the segment is last one.
+class DummyStreamSegmentEncrypter : public StreamSegmentEncrypter {
+ public:
+  DummyStreamSegmentEncrypter(int pt_segment_size,
+                              int header_size,
+                              int ct_offset) :
+      pt_segment_size_(pt_segment_size),
+      ct_offset_(ct_offset),
+      segment_number_(0) {
+    // Fill the header with 'header_size' copies of letter 'h'
+    header_.resize(0);
+    header_.resize(header_size, static_cast<uint8_t>('h'));
+    generated_output_size_ = header_size;
+  }
+
+  util::Status EncryptSegment(
+      const std::vector<uint8_t>& plaintext,
+      bool is_last_segment,
+      std::vector<uint8_t>* ciphertext_buffer) override {
+    ciphertext_buffer->resize(plaintext.size() + kSegmentTagSize);
+    memcpy(ciphertext_buffer->data(), plaintext.data(), plaintext.size());
+    memcpy(ciphertext_buffer->data() + plaintext.size(),
+           &segment_number_, sizeof(segment_number_));
+    // The last byte of the a ciphertext segment.
+    ciphertext_buffer->back() =
+        is_last_segment ? kLastSegment : kNotLastSegment;
+    generated_output_size_ += ciphertext_buffer->size();
+    IncSegmentNumber();
+    return Status::OK;
+  }
+
+  const std::vector<uint8_t>& get_header() const override {
+    return header_;
+  }
+
+  int64_t get_segment_number() const override {
+    return segment_number_;
+  }
+
+  int get_plaintext_segment_size() const override {
+    return pt_segment_size_;
+  }
+
+  int get_ciphertext_segment_size() const override {
+    return pt_segment_size_ + kSegmentTagSize;
+  }
+
+  int get_ciphertext_offset() const override {
+    return ct_offset_;
+  }
+
+  ~DummyStreamSegmentEncrypter() override {}
+
+  int get_generated_output_size() {
+    return generated_output_size_;
+  }
+
+ protected:
+  void IncSegmentNumber() override {
+    segment_number_++;
+  }
+
+ private:
+  std::vector<uint8_t> header_;
+  int pt_segment_size_;
+  int ct_offset_;
+  int64_t segment_number_;
+  int64_t generated_output_size_;
+};   // class DummyStreamSegmentEncrypter
+
+// References to objects used for test validation.
+// The objects pointed to are not owned by this structure.
+struct ValidationRefs {
+  std::stringbuf* ct_buf;  // buffer that contains the resulting ciphertext
+  DummyStreamSegmentEncrypter* seg_enc;  // segment encrypter
+};
+
+// A helper for creating StreamingAeadEncryptingStream together
+// with references to internal objects, used for test validation.
+std::unique_ptr<OutputStream> GetEncryptingStream(
+    int pt_segment_size, int header_size, int ct_offset, ValidationRefs* refs) {
+  // Prepare ciphertext destination stream.
+  auto ct_stream = absl::make_unique<std::stringstream>();
+  // A reference to the ciphertext buffer, for later validation.
+  refs->ct_buf = ct_stream->rdbuf();
+  std::unique_ptr<OutputStream> ct_destination(
+      absl::make_unique<OstreamOutputStream>(std::move(ct_stream)));
+  auto seg_enc = absl::make_unique<DummyStreamSegmentEncrypter>(
+          pt_segment_size, header_size, ct_offset);
+  // A reference to the segment encrypter, for later validation.
+  refs->seg_enc = seg_enc.get();
+  auto enc_stream = std::move(StreamingAeadEncryptingStream::New(
+      std::move(seg_enc), std::move(ct_destination)).ValueOrDie());
+  EXPECT_EQ(0, enc_stream->Position());
+  return enc_stream;
+}
+
+
+class StreamingAeadEncryptingStreamTest : public ::testing::Test {
+};
+
+TEST_F(StreamingAeadEncryptingStreamTest, WritingStreams) {
+  std::vector<int> pt_sizes = {0, 10, 100, 1000, 10000, 100000, 1000000};
+  std::vector<int> pt_segment_sizes = {64, 100, 128, 1000, 1024};
+  std::vector<int> header_sizes = {5, 10, 32};
+  std::vector<int> ct_offset_deltas = {0, 1, 5, 15};
+  for (auto pt_size : pt_sizes) {
+    for (auto pt_segment_size : pt_segment_sizes) {
+      for (auto header_size : header_sizes) {
+        for (auto offset_delta : ct_offset_deltas) {
+          SCOPED_TRACE(absl::StrCat("pt_size = ", pt_size,
+                                    ", pt_segment_size = ", pt_segment_size,
+                                    ", header_size = ", header_size,
+                                    ", offset_delta = ", offset_delta));
+          // Get an encrypting stream.
+          ValidationRefs refs;
+          auto enc_stream = GetEncryptingStream(pt_segment_size, header_size,
+              /* ct_offset = */ header_size + offset_delta, &refs);
+
+          // First buffer returned by Next();
+          void* buffer;
+          auto next_result = enc_stream->Next(&buffer);
+          EXPECT_TRUE(next_result.ok()) << next_result.status();
+          int buffer_size = next_result.ValueOrDie();
+          EXPECT_EQ(pt_segment_size - header_size - offset_delta, buffer_size);
+          EXPECT_EQ(buffer_size, enc_stream->Position());
+
+          // Backup the entire first buffer.
+          enc_stream->BackUp(buffer_size);
+          EXPECT_EQ(0, enc_stream->Position());
+
+          // Write plaintext to the stream, and close the stream.
+          std::string pt = Random::GetRandomBytes(pt_size);
+          auto status = WriteToStream(enc_stream.get(), pt);
+          EXPECT_TRUE(status.ok()) << status;
+          EXPECT_EQ(enc_stream->Position(), pt.size());
+          EXPECT_EQ(refs.seg_enc->get_generated_output_size(),
+                    refs.ct_buf->str().size());
+          EXPECT_EQ(std::string(header_size, 'h'),
+                    refs.ct_buf->str().substr(0, header_size));
+
+          // Try closing the stream again.
+          status = enc_stream->Close();
+          EXPECT_FALSE(status.ok());
+          EXPECT_EQ(util::error::FAILED_PRECONDITION, status.error_code());
+        }
+      }
+    }
+  }
+}
+
+TEST_F(StreamingAeadEncryptingStreamTest, EmptyPlaintext) {
+  int pt_segment_size = 512;
+  int header_size = 64;
+
+  // Get an encrypting stream.
+  ValidationRefs refs;
+  auto enc_stream = GetEncryptingStream(
+      pt_segment_size, header_size, /* ct_offset = */ header_size, &refs);
+
+  // Close the stream.
+  auto close_status = enc_stream->Close();
+  EXPECT_TRUE(close_status.ok()) << close_status;
+  EXPECT_EQ(refs.seg_enc->get_generated_output_size(),
+            refs.ct_buf->str().size());
+  // Ciphertext contains only the header and an "empty" first block.
+  EXPECT_EQ(header_size + kSegmentTagSize, refs.ct_buf->str().size());
+  // The last segment is marked as such.
+  EXPECT_EQ(kLastSegment, refs.ct_buf->str().back());
+
+  // Try closing the stream again.
+  close_status = enc_stream->Close();
+  EXPECT_FALSE(close_status.ok());
+  EXPECT_EQ(util::error::FAILED_PRECONDITION, close_status.error_code());
+}
+
+TEST_F(StreamingAeadEncryptingStreamTest, EmptyPlaintextWithBackup) {
+  int pt_segment_size = 512;
+  int header_size = 64;
+  void* buffer;
+
+  // Get an encrypting stream.
+  ValidationRefs refs;
+  auto enc_stream = GetEncryptingStream(
+      pt_segment_size, header_size, /* ct_offset = */ header_size, &refs);
+
+  // Get the first block.
+  auto next_result = enc_stream->Next(&buffer);
+  int buffer_size = pt_segment_size - header_size;
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(buffer_size, next_result.ValueOrDie());
+  EXPECT_EQ(buffer_size, enc_stream->Position());
+
+  // Backup the entire block, and close the stream.
+  enc_stream->BackUp(buffer_size);
+  EXPECT_EQ(0, enc_stream->Position());
+  auto close_status = enc_stream->Close();
+  EXPECT_TRUE(close_status.ok()) << close_status;
+  EXPECT_EQ(refs.seg_enc->get_generated_output_size(),
+            refs.ct_buf->str().size());
+  // Ciphertext contains only the header and an "empty" first block.
+  EXPECT_EQ(header_size + kSegmentTagSize, refs.ct_buf->str().size());
+  // The last segment is marked as such.
+  EXPECT_EQ(kLastSegment, refs.ct_buf->str().back());
+
+  // Try closing the stream again.
+  close_status = enc_stream->Close();
+  EXPECT_FALSE(close_status.ok());
+  EXPECT_EQ(util::error::FAILED_PRECONDITION, close_status.error_code());
+}
+
+TEST_F(StreamingAeadEncryptingStreamTest, OneSegmentPlaintext) {
+  int pt_segment_size = 512;
+  int header_size = 64;
+  void* buffer;
+
+  // Get an encrypting stream.
+  ValidationRefs refs;
+  auto enc_stream = GetEncryptingStream(
+      pt_segment_size, header_size, /* ct_offset = */ header_size, &refs);
+
+  // Get the first segment, and close the stream.
+  auto next_result = enc_stream->Next(&buffer);
+  int buffer_size = pt_segment_size - header_size;
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(buffer_size, next_result.ValueOrDie());
+  EXPECT_EQ(buffer_size, enc_stream->Position());
+  auto close_status = enc_stream->Close();
+  EXPECT_TRUE(close_status.ok()) << close_status;
+  EXPECT_EQ(refs.seg_enc->get_generated_output_size(),
+            refs.ct_buf->str().size());
+  // Ciphertext contains only header and a full first block.
+  EXPECT_EQ(pt_segment_size + kSegmentTagSize, refs.ct_buf->str().size());
+  // The last segment is marked as such.
+  EXPECT_EQ(kLastSegment, refs.ct_buf->str().back());
+
+  // Try closing the stream again.
+  close_status = enc_stream->Close();
+  EXPECT_FALSE(close_status.ok());
+  EXPECT_EQ(util::error::FAILED_PRECONDITION, close_status.error_code());
+}
+
+TEST_F(StreamingAeadEncryptingStreamTest, OneSegmentPlaintextWithBackup) {
+  int pt_segment_size = 512;
+  int pt_size = 200;
+  int header_size = 64;
+  void* buffer;
+
+  // Get an encrypting stream.
+  ValidationRefs refs;
+  auto enc_stream = GetEncryptingStream(
+      pt_segment_size, header_size, /* ct_offset = */ header_size, &refs);
+
+  // Get the first block.
+  auto next_result = enc_stream->Next(&buffer);
+  int buffer_size = pt_segment_size - header_size;
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(buffer_size, next_result.ValueOrDie());
+  EXPECT_EQ(buffer_size, enc_stream->Position());
+
+  // Backup so that only pt_size bytes are written, and close the stream.
+  enc_stream->BackUp(buffer_size - pt_size);
+  EXPECT_EQ(pt_size, enc_stream->Position());
+  auto close_status = enc_stream->Close();
+  EXPECT_TRUE(close_status.ok()) << close_status;
+  EXPECT_EQ(refs.seg_enc->get_generated_output_size(),
+            refs.ct_buf->str().size());
+  // Ciphertext contains only the header and partial first block.
+  EXPECT_EQ(header_size + pt_size + kSegmentTagSize, refs.ct_buf->str().size());
+  // The last segment is marked as such.
+  EXPECT_EQ(kLastSegment, refs.ct_buf->str().back());
+
+  // Try closing the stream again.
+  close_status = enc_stream->Close();
+  EXPECT_FALSE(close_status.ok());
+  EXPECT_EQ(util::error::FAILED_PRECONDITION, close_status.error_code());
+}
+
+TEST_F(StreamingAeadEncryptingStreamTest, ManySegmentsPlaintext) {
+  int pt_segment_size = 512;
+  int header_size = 64;
+  void* buffer;
+
+  // Get an encrypting stream.
+  ValidationRefs refs;
+  auto enc_stream = GetEncryptingStream(
+      pt_segment_size, header_size, /* ct_offset = */ header_size, &refs);
+
+  int seg_count = 5;
+  // Get the first segment.
+  auto next_result = enc_stream->Next(&buffer);
+  int first_buffer_size = pt_segment_size - header_size;
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(first_buffer_size, next_result.ValueOrDie());
+  EXPECT_EQ(first_buffer_size, enc_stream->Position());
+
+  // Get remaining segments.
+  for (int i = 1; i < seg_count; i++) {
+    next_result = enc_stream->Next(&buffer);
+    EXPECT_TRUE(next_result.ok()) << next_result.status();
+    EXPECT_EQ(pt_segment_size, next_result.ValueOrDie());
+    EXPECT_EQ(first_buffer_size + i * pt_segment_size, enc_stream->Position());
+  }
+
+  // Close the stream.
+  auto close_status = enc_stream->Close();
+  EXPECT_TRUE(close_status.ok()) << close_status;
+  EXPECT_EQ(refs.seg_enc->get_generated_output_size(),
+            refs.ct_buf->str().size());
+  // Ciphertext contains seg_count full segments.
+  int ct_segment_size = pt_segment_size + kSegmentTagSize;
+  EXPECT_EQ(refs.seg_enc->get_ciphertext_segment_size(), ct_segment_size);
+  EXPECT_EQ(ct_segment_size * seg_count, refs.ct_buf->str().size());
+  // The last segment is marked as such.
+  EXPECT_EQ(kLastSegment, refs.ct_buf->str().back());
+  // The previous segments are marked as not-last ones.
+  for (int i = 1; i < seg_count - 1; i++) {
+    EXPECT_EQ(kNotLastSegment, refs.ct_buf->str()[(ct_segment_size * i)-1]);
+  }
+
+  // Try closing the stream again.
+  close_status = enc_stream->Close();
+  EXPECT_FALSE(close_status.ok());
+  EXPECT_EQ(util::error::FAILED_PRECONDITION, close_status.error_code());
+}
+
+TEST_F(StreamingAeadEncryptingStreamTest, ManySegmentsPlaintextWithBackup) {
+  int pt_segment_size = 512;
+  int backup_size = 100;
+  int header_size = 64;
+  void* buffer;
+
+  // Get an encrypting stream.
+  ValidationRefs refs;
+  auto enc_stream = GetEncryptingStream(
+      pt_segment_size, header_size, /* ct_offset = */ header_size, &refs);
+
+  int seg_count = 5;
+  // Get the first segment.
+  auto next_result = enc_stream->Next(&buffer);
+  int first_buffer_size = pt_segment_size - header_size;
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(first_buffer_size, next_result.ValueOrDie());
+  EXPECT_EQ(first_buffer_size, enc_stream->Position());
+
+  // Get remaining segments.
+  for (int i = 1; i < seg_count; i++) {
+    next_result = enc_stream->Next(&buffer);
+    EXPECT_TRUE(next_result.ok()) << next_result.status();
+    EXPECT_EQ(pt_segment_size, next_result.ValueOrDie());
+    EXPECT_EQ(first_buffer_size + i * pt_segment_size, enc_stream->Position());
+  }
+  // Backup part of the last segment, and close the stream.
+  enc_stream->BackUp(backup_size);
+  EXPECT_EQ(first_buffer_size + (seg_count - 1) * pt_segment_size - backup_size,
+            enc_stream->Position());
+  auto close_status = enc_stream->Close();
+  EXPECT_TRUE(close_status.ok()) << close_status;
+  EXPECT_EQ(refs.seg_enc->get_generated_output_size(),
+            refs.ct_buf->str().size());
+  // Ciphertext contains seg_count full segments, minus the size of the backup.
+  int ct_segment_size = pt_segment_size + kSegmentTagSize;
+  EXPECT_EQ(refs.seg_enc->get_ciphertext_segment_size(), ct_segment_size);
+  EXPECT_EQ(ct_segment_size * seg_count - backup_size,
+            refs.ct_buf->str().size());
+  // The last segment is marked as such.
+  EXPECT_EQ(kLastSegment, refs.ct_buf->str().back());
+  // The previous segments are marked as not-last ones.
+  for (int i = 1; i < seg_count - 1; i++) {
+    EXPECT_EQ(kNotLastSegment, refs.ct_buf->str()[(ct_segment_size * i)-1]);
+  }
+
+  // Try closing the stream again.
+  close_status = enc_stream->Close();
+  EXPECT_FALSE(close_status.ok());
+  EXPECT_EQ(util::error::FAILED_PRECONDITION, close_status.error_code());
+}
+
+TEST_F(StreamingAeadEncryptingStreamTest, ManySegmentsPlaintextWithFullBackup) {
+  int pt_segment_size = 512;
+  int header_size = 64;
+  void* buffer;
+
+  // Get an encrypting stream.
+  ValidationRefs refs;
+  auto enc_stream = GetEncryptingStream(
+      pt_segment_size, header_size, /* ct_offset = */ header_size, &refs);
+
+  int seg_count = 5;
+  // Get the first segment.
+  auto next_result = enc_stream->Next(&buffer);
+  int first_buffer_size = pt_segment_size - header_size;
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(first_buffer_size, next_result.ValueOrDie());
+  EXPECT_EQ(first_buffer_size, enc_stream->Position());
+
+  // Get remaining segments.
+  for (int i = 1; i < seg_count; i++) {
+    next_result = enc_stream->Next(&buffer);
+    EXPECT_TRUE(next_result.ok()) << next_result.status();
+    EXPECT_EQ(pt_segment_size, next_result.ValueOrDie());
+    EXPECT_EQ(first_buffer_size + i * pt_segment_size, enc_stream->Position());
+  }
+  // Backup the entire last segment, and close the stream.
+  enc_stream->BackUp(pt_segment_size);
+  EXPECT_EQ(first_buffer_size + (seg_count - 2) * pt_segment_size,
+            enc_stream->Position());
+  auto close_status = enc_stream->Close();
+  EXPECT_TRUE(close_status.ok()) << close_status;
+  EXPECT_EQ(refs.seg_enc->get_generated_output_size(),
+            refs.ct_buf->str().size());
+  // Ciphertext contains (seg_count - 1) full segments.
+  int ct_segment_size = pt_segment_size + kSegmentTagSize;
+  EXPECT_EQ(refs.seg_enc->get_ciphertext_segment_size(), ct_segment_size);
+  EXPECT_EQ(ct_segment_size * (seg_count - 1), refs.ct_buf->str().size());
+  // The last segment is marked as such.
+  EXPECT_EQ(kLastSegment, refs.ct_buf->str().back());
+  // The previous segments are marked as not-last ones.
+  for (int i = 1; i < seg_count - 1; i++) {
+    EXPECT_EQ(kNotLastSegment, refs.ct_buf->str()[(ct_segment_size * i)-1]);
+  }
+
+  // Try closing the stream again.
+  close_status = enc_stream->Close();
+  EXPECT_FALSE(close_status.ok());
+  EXPECT_EQ(util::error::FAILED_PRECONDITION, close_status.error_code());
+}
+
+TEST_F(StreamingAeadEncryptingStreamTest, BackupAndPosition) {
+  int pt_segment_size = 512;
+  int header_size = 64;
+  void* buffer;
+
+  // Get an encrypting stream.
+  ValidationRefs refs;
+  auto enc_stream = GetEncryptingStream(
+      pt_segment_size, header_size, /* ct_offset = */ header_size, &refs);
+
+  // The first buffer.
+  auto next_result = enc_stream->Next(&buffer);
+  int buffer_size = pt_segment_size - header_size;
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(buffer_size, next_result.ValueOrDie());
+  EXPECT_EQ(buffer_size, enc_stream->Position());
+
+  // BackUp several times, but in total fewer bytes than returned by Next().
+  std::vector<int> backup_sizes = {0, 1, 5, 0, 10, 78, -42, 60, 120, -120};
+  int total_backup_size = 0;
+  for (auto backup_size : backup_sizes) {
+    enc_stream->BackUp(backup_size);
+    total_backup_size += std::max(0, backup_size);
+    EXPECT_EQ(buffer_size - total_backup_size, enc_stream->Position());
+  }
+  EXPECT_LT(total_backup_size, next_result.ValueOrDie());
+
+  // Call Next(), it should succeed (backuped bytes of 1st block).
+  next_result = enc_stream->Next(&buffer);
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(total_backup_size, next_result.ValueOrDie());
+  EXPECT_EQ(buffer_size, enc_stream->Position());
+
+  // BackUp() some bytes, again fewer than returned by Next().
+  backup_sizes = {0, 72, -94, 37, 82};
+  total_backup_size = 0;
+  for (auto backup_size : backup_sizes) {
+    enc_stream->BackUp(backup_size);
+    total_backup_size += std::max(0, backup_size);
+    EXPECT_EQ(buffer_size - total_backup_size, enc_stream->Position());
+  }
+  EXPECT_LT(total_backup_size, next_result.ValueOrDie());
+
+  // Call Next(), it should succeed  (backuped bytes of 1st block).
+  next_result = enc_stream->Next(&buffer);
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(total_backup_size, next_result.ValueOrDie());
+  EXPECT_EQ(buffer_size, enc_stream->Position());
+
+  // Call Next() again, it should return a full block (2nd block).
+  auto prev_position = enc_stream->Position();
+  buffer_size = pt_segment_size;
+  next_result = enc_stream->Next(&buffer);
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(buffer_size, next_result.ValueOrDie());
+  EXPECT_EQ(prev_position + buffer_size, enc_stream->Position());
+
+  // BackUp a few times, with total over the returned buffer_size.
+  backup_sizes = {0, 72, -100, buffer_size / 2, 200, -25, buffer_size / 2, 42};
+  total_backup_size = 0;
+  for (auto backup_size : backup_sizes) {
+    SCOPED_TRACE(absl::StrCat("backup_size = ", backup_size,
+                              ", total_backup_size = ", total_backup_size));
+    enc_stream->BackUp(backup_size);
+    total_backup_size = std::min(buffer_size,
+                                 total_backup_size + std::max(0, backup_size));
+    EXPECT_EQ(prev_position + buffer_size - total_backup_size,
+              enc_stream->Position());
+  }
+  EXPECT_EQ(total_backup_size, buffer_size);
+  EXPECT_EQ(prev_position, enc_stream->Position());
+
+  // Call Next() again, it should return a full block (2nd block);
+  next_result = enc_stream->Next(&buffer);
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(buffer_size, next_result.ValueOrDie());
+  EXPECT_EQ(prev_position + buffer_size, enc_stream->Position());
+  EXPECT_EQ(2 * pt_segment_size - header_size, enc_stream->Position());
+
+  // Backup the entire block, and close the stream.
+  enc_stream->BackUp(buffer_size);
+  EXPECT_EQ(pt_segment_size - header_size, enc_stream->Position());
+  auto close_status = enc_stream->Close();
+  EXPECT_TRUE(close_status.ok()) << close_status;
+  EXPECT_EQ(refs.seg_enc->get_generated_output_size(),
+            refs.ct_buf->str().size());
+  // Ciphertext contains 1st segment (with header), and no traces
+  // of the "empty" (backed-up) block.
+  EXPECT_EQ((pt_segment_size + kSegmentTagSize), refs.ct_buf->str().size());
+
+  // Try closing the stream again.
+  close_status = enc_stream->Close();
+  EXPECT_FALSE(close_status.ok());
+  EXPECT_EQ(util::error::FAILED_PRECONDITION, close_status.error_code());
+}
+
+}  // namespace
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/subtle/subtle_util_boringssl.cc b/cc/subtle/subtle_util_boringssl.cc
index b423f50..0569610 100644
--- a/cc/subtle/subtle_util_boringssl.cc
+++ b/cc/subtle/subtle_util_boringssl.cc
@@ -18,10 +18,12 @@
 #include "absl/strings/str_cat.h"
 #include "absl/strings/substitute.h"
 #include "openssl/bn.h"
+#include "openssl/curve25519.h"
 #include "openssl/ec.h"
 #include "openssl/err.h"
 #include "openssl/rsa.h"
 #include "tink/subtle/common_enums.h"
+#include "tink/util/errors.h"
 
 namespace crypto {
 namespace tink {
@@ -29,11 +31,11 @@
 
 namespace {
 
-size_t ScalarSizeInBytes(const EC_GROUP* group) {
+size_t ScalarSizeInBytes(const EC_GROUP *group) {
   return BN_num_bytes(EC_GROUP_get0_order(group));
 }
 
-size_t FieldElementSizeInBytes(const EC_GROUP* group) {
+size_t FieldElementSizeInBytes(const EC_GROUP *group) {
   unsigned degree_bits = EC_GROUP_get_degree(group);
   return (degree_bits + 7) / 8;
 }
@@ -105,20 +107,21 @@
 }
 
 // static
-util::StatusOr<SubtleUtilBoringSSL::EcKey>
-SubtleUtilBoringSSL::GetNewEcKey(EllipticCurveType curve_type) {
+util::StatusOr<SubtleUtilBoringSSL::EcKey> SubtleUtilBoringSSL::GetNewEcKey(
+    EllipticCurveType curve_type) {
   auto status_or_group(SubtleUtilBoringSSL::GetEcGroup(curve_type));
   if (!status_or_group.ok()) return status_or_group.status();
   bssl::UniquePtr<EC_GROUP> group(status_or_group.ValueOrDie());
   bssl::UniquePtr<EC_KEY> key(EC_KEY_new());
   EC_KEY_set_group(key.get(), group.get());
   EC_KEY_generate_key(key.get());
-  const BIGNUM* priv_key = EC_KEY_get0_private_key(key.get());
-  const EC_POINT* pub_key = EC_KEY_get0_public_key(key.get());
+  const BIGNUM *priv_key = EC_KEY_get0_private_key(key.get());
+  const EC_POINT *pub_key = EC_KEY_get0_public_key(key.get());
   bssl::UniquePtr<BIGNUM> pub_key_x_bn(BN_new());
   bssl::UniquePtr<BIGNUM> pub_key_y_bn(BN_new());
   if (!EC_POINT_get_affine_coordinates_GFp(group.get(), pub_key,
-          pub_key_x_bn.get(), pub_key_y_bn.get(), nullptr)) {
+                                           pub_key_x_bn.get(),
+                                           pub_key_y_bn.get(), nullptr)) {
     return util::Status(util::error::INTERNAL,
                         "EC_POINT_get_affine_coordinates_GFp failed");
   }
@@ -145,6 +148,26 @@
 }
 
 // static
+std::unique_ptr<SubtleUtilBoringSSL::Ed25519Key>
+SubtleUtilBoringSSL::GetNewEd25519Key() {
+  // Generate a new key pair.
+  uint8_t out_public_key[ED25519_PUBLIC_KEY_LEN];
+  uint8_t out_private_key[ED25519_PRIVATE_KEY_LEN];
+
+  ED25519_keypair(out_public_key, out_private_key);
+
+  std::unique_ptr<Ed25519Key> key(new Ed25519Key);
+  key->public_key = std::string(reinterpret_cast<const char *>(out_public_key),
+                           ED25519_PUBLIC_KEY_LEN);
+  std::string tmp = std::string(reinterpret_cast<const char *>(out_private_key),
+                      ED25519_PRIVATE_KEY_LEN);
+  // ED25519_keypair appends the public key at the end of the private key. Keep
+  // the first 32 bytes that contain the private key and discard the public key.
+  key->private_key = tmp.substr(0, 32);
+  return key;
+}
+
+// static
 util::StatusOr<const EVP_MD *> SubtleUtilBoringSSL::EvpHash(
     HashType hash_type) {
   switch (hash_type) {
@@ -186,8 +209,9 @@
   }
   // Get shared point's x coordinate.
   bssl::UniquePtr<BIGNUM> shared_x(BN_new());
-  if (1 != EC_POINT_get_affine_coordinates_GFp(priv_group.get(),
-               shared_point.get(), shared_x.get(), nullptr, nullptr)) {
+  if (1 !=
+      EC_POINT_get_affine_coordinates_GFp(priv_group.get(), shared_point.get(),
+                                          shared_x.get(), nullptr, nullptr)) {
     return util::Status(util::error::INTERNAL,
                         "EC_POINT_get_affine_coordinates_GFp failed");
   }
@@ -368,6 +392,17 @@
 }
 
 // static
+util::Status SubtleUtilBoringSSL::ValidateRsaModulusSize(size_t modulus_size) {
+  if (modulus_size < 2048) {
+    return ToStatusF(
+        util::error::INVALID_ARGUMENT,
+        "Modulus size is %zu; only modulus size >= 2048-bit is supported",
+        modulus_size);
+  }
+  return util::Status::OK;
+}
+
+// static
 std::string SubtleUtilBoringSSL::GetErrors() {
   std::string ret;
   ERR_print_errors_cb(
@@ -450,6 +485,85 @@
   return util::OkStatus();
 }
 
+// static
+util::Status SubtleUtilBoringSSL::CopyKey(
+    const SubtleUtilBoringSSL::RsaPrivateKey &key, RSA *rsa) {
+  auto n = SubtleUtilBoringSSL::str2bn(key.n);
+  auto e = SubtleUtilBoringSSL::str2bn(key.e);
+  auto d = SubtleUtilBoringSSL::str2bn(key.d);
+  if (!n.ok()) return n.status();
+  if (!e.ok()) return e.status();
+  if (!d.ok()) return d.status();
+  if (RSA_set0_key(rsa, n.ValueOrDie().get(), e.ValueOrDie().get(),
+                   d.ValueOrDie().get()) != 1) {
+    return util::Status(util::error::INTERNAL,
+                        absl::StrCat("Could not load RSA key: ",
+                                     SubtleUtilBoringSSL::GetErrors()));
+  }
+  // The RSA object takes ownership when you call RSA_set0_key.
+  n.ValueOrDie().release();
+  e.ValueOrDie().release();
+  d.ValueOrDie().release();
+  return util::OkStatus();
+}
+
+// static
+util::Status SubtleUtilBoringSSL::CopyPrimeFactors(
+    const SubtleUtilBoringSSL::RsaPrivateKey &key, RSA *rsa) {
+  auto p = SubtleUtilBoringSSL::str2bn(key.p);
+  auto q = SubtleUtilBoringSSL::str2bn(key.q);
+  if (!p.ok()) return p.status();
+  if (!q.ok()) return q.status();
+  if (RSA_set0_factors(rsa, p.ValueOrDie().get(), q.ValueOrDie().get()) != 1) {
+    return util::Status(util::error::INTERNAL,
+                        absl::StrCat("Could not load RSA key: ",
+                                     SubtleUtilBoringSSL::GetErrors()));
+  }
+  p.ValueOrDie().release();
+  q.ValueOrDie().release();
+  return util::OkStatus();
+}
+
+// static
+util::Status SubtleUtilBoringSSL::CopyCrtParams(
+    const SubtleUtilBoringSSL::RsaPrivateKey &key, RSA *rsa) {
+  auto dp = SubtleUtilBoringSSL::str2bn(key.dp);
+  auto dq = SubtleUtilBoringSSL::str2bn(key.dq);
+  auto crt = SubtleUtilBoringSSL::str2bn(key.crt);
+  if (!dp.ok()) return dp.status();
+  if (!dq.ok()) return dq.status();
+  if (!crt.ok()) return crt.status();
+  if (RSA_set0_crt_params(rsa, dp.ValueOrDie().get(), dq.ValueOrDie().get(),
+                          crt.ValueOrDie().get()) != 1) {
+    return util::Status(util::error::INTERNAL,
+                        absl::StrCat("Could not load RSA key: ",
+                                     SubtleUtilBoringSSL::GetErrors()));
+  }
+  dp.ValueOrDie().release();
+  dq.ValueOrDie().release();
+  crt.ValueOrDie().release();
+  return util::OkStatus();
+}
+
+namespace boringssl {
+
+util::StatusOr<std::vector<uint8_t>> ComputeHash(absl::string_view input,
+                                                 const EVP_MD &hasher) {
+  input = SubtleUtilBoringSSL::EnsureNonNull(input);
+  std::vector<uint8_t> digest(EVP_MAX_MD_SIZE);
+  uint32_t digest_length = 0;
+  if (EVP_Digest(input.data(), input.length(), digest.data(), &digest_length,
+                 &hasher, /*impl=*/nullptr) != 1) {
+    return util::Status(util::error::INTERNAL,
+                        absl::StrCat("Openssl internal error computing hash: ",
+                                     SubtleUtilBoringSSL::GetErrors()));
+  }
+  digest.resize(digest_length);
+  return digest;
+}
+
+}  // namespace boringssl
+
 }  // namespace subtle
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/subtle/subtle_util_boringssl.h b/cc/subtle/subtle_util_boringssl.h
index f6d3398..38b24e6 100644
--- a/cc/subtle/subtle_util_boringssl.h
+++ b/cc/subtle/subtle_util_boringssl.h
@@ -17,13 +17,15 @@
 #ifndef TINK_SUBTLE_SUBTLE_UTIL_BORINGSSL_H_
 #define TINK_SUBTLE_SUBTLE_UTIL_BORINGSSL_H_
 
+#include <vector>
+
 #include "absl/strings/string_view.h"
-#include "tink/subtle/common_enums.h"
-#include "tink/util/status.h"
-#include "tink/util/statusor.h"
 #include "openssl/bn.h"
 #include "openssl/err.h"
 #include "openssl/evp.h"
+#include "tink/subtle/common_enums.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
 
 namespace crypto {
 namespace tink {
@@ -33,9 +35,14 @@
  public:
   struct EcKey {
     EllipticCurveType curve;
-    std::string pub_x;   // affine coordinates in bigendian representation
+    std::string pub_x;  // affine coordinates in bigendian representation
     std::string pub_y;
-    std::string priv;    // big integer in bigendian represnetation
+    std::string priv;  // big integer in bigendian represnetation
+  };
+
+  struct Ed25519Key {
+    std::string public_key;
+    std::string private_key;
   };
 
   struct RsaPublicKey {
@@ -61,6 +68,15 @@
     int salt_length;
   };
 
+  // Parameters of RSA SSA (Signature Schemes with Appendix) using PKCS1
+  // (Probabilistic Signature Scheme) encoding (see
+  // https://tools.ietf.org/html/rfc8017#section-8.2).
+  struct RsaSsaPkcs1Params {
+    // Hash function used in computing hash of the signing message
+    // (see https://tools.ietf.org/html/rfc8017#section-9.2).
+    HashType hash_type;
+  };
+
   // RSA private key representation.
   struct RsaPrivateKey {
     // Modulus.
@@ -105,14 +121,15 @@
   // Returns BoringSSL's EC_POINT constructed from the curve type, big-endian
   // representation of public key's x-coordinate and y-coordinate.
   static crypto::tink::util::StatusOr<EC_POINT *> GetEcPoint(
-      EllipticCurveType curve,
-      absl::string_view pubx,
-      absl::string_view puby);
+      EllipticCurveType curve, absl::string_view pubx, absl::string_view puby);
 
   // Returns a new EC key for the specified curve.
   static crypto::tink::util::StatusOr<EcKey> GetNewEcKey(
       EllipticCurveType curve_type);
 
+  // Returns a new ED25519 key.
+  static std::unique_ptr<Ed25519Key> GetNewEd25519Key();
+
   // Returns BoringSSL's EC_POINT constructed from curve type, point format and
   // encoded public key's point. The uncompressed point is encoded as
   // 0x04 || x || y where x, y are curve_size_in_bytes big-endian byte array.
@@ -120,9 +137,7 @@
   // curve_size_in_bytes big-endian byte array and if the least significant bit
   // of y is 1, the 1st byte is 0x03, otherwise it's 0x02.
   static crypto::tink::util::StatusOr<EC_POINT *> EcPointDecode(
-      EllipticCurveType curve,
-      EcPointFormat format,
-      absl::string_view encoded);
+      EllipticCurveType curve, EcPointFormat format, absl::string_view encoded);
 
   // Returns the encoded public key based on curve type, point format and
   // BoringSSL's EC_POINT public key point. The uncompressed point is encoded as
@@ -131,16 +146,12 @@
   // curve_size_in_bytes big-endian byte array and if the least significant bit
   // of y is 1, the 1st byte is 0x03, otherwise it's 0x02.
   static crypto::tink::util::StatusOr<std::string> EcPointEncode(
-      EllipticCurveType curve,
-      EcPointFormat format,
-      const EC_POINT *point);
+      EllipticCurveType curve, EcPointFormat format, const EC_POINT *point);
 
   // Returns the ECDH's shared secret based on our private key and peer's public
   // key. Returns error if the public key is not on private key's curve.
   static crypto::tink::util::StatusOr<std::string> ComputeEcdhSharedSecret(
-      EllipticCurveType curve,
-      const BIGNUM *priv_key,
-      const EC_POINT *pub_key);
+      EllipticCurveType curve, const BIGNUM *priv_key, const EC_POINT *pub_key);
 
   // Returns an EVP structure for a hash function.
   // The EVP_MD instances are sigletons owned by BoringSSL.
@@ -151,6 +162,13 @@
   static crypto::tink::util::Status ValidateSignatureHash(
       subtle::HashType sig_hash);
 
+  // Validates whether 'modulus_size' is at least 2048-bit.
+  // To reach 128-bit security strength, RSA's modulus must be at least 3072-bit
+  // while 2048-bit RSA key only has 112-bit security. Nevertheless, a 2048-bit
+  // RSA key is considered safe by NIST until 2030 (see
+  // https://www.keylength.com/en/4/).
+  static crypto::tink::util::Status ValidateRsaModulusSize(size_t modulus_size);
+
   // Return an empty std::string if str.data() is nullptr; otherwise return str.
   static absl::string_view EnsureNonNull(absl::string_view str);
 
@@ -159,8 +177,25 @@
                                        const BIGNUM *e,
                                        RsaPrivateKey *private_key,
                                        RsaPublicKey *public_key);
+
+  // Copies n, e and d into the RSA key.
+  static util::Status CopyKey(const RsaPrivateKey &key, RSA *rsa);
+
+  // Copies the prime factors (p, q) into the RSA key.
+  static util::Status CopyPrimeFactors(const RsaPrivateKey &key, RSA *rsa);
+
+  // Copies the CRT params and dp, dq into the RSA key.
+  static util::Status CopyCrtParams(const RsaPrivateKey &key, RSA *rsa);
 };
 
+namespace boringssl {
+
+// Computes hash of 'input' using the hash function 'hasher'.
+util::StatusOr<std::vector<uint8_t>> ComputeHash(absl::string_view input,
+                                                 const EVP_MD &hasher);
+
+}  // namespace boringssl
+
 }  // namespace subtle
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/subtle/subtle_util_boringssl_test.cc b/cc/subtle/subtle_util_boringssl_test.cc
index f17176d..2ea213e 100644
--- a/cc/subtle/subtle_util_boringssl_test.cc
+++ b/cc/subtle/subtle_util_boringssl_test.cc
@@ -21,7 +21,10 @@
 #include <vector>
 
 #include "gtest/gtest.h"
+#include "absl/strings/escaping.h"
 #include "absl/strings/str_cat.h"
+#include "openssl/curve25519.h"
+#include "openssl/digest.h"
 #include "openssl/ec.h"
 #include "openssl/evp.h"
 #include "openssl/x509.h"
@@ -42,6 +45,7 @@
 using ::crypto::tink::test::StatusIs;
 using ::testing::IsEmpty;
 using ::testing::Not;
+using ::testing::StrEq;
 
 struct EncodingTestVector {
   EcPointFormat format;
@@ -81,7 +85,7 @@
       "6619b9931955d5a89d4d74adf1046bb362192f2ef6bd3e3d2d04dd1f87054a",
       EllipticCurveType::NIST_P521}});
 
-TEST(SubtleUtilBoringSSLTest, testEcPointEncode) {
+TEST(SubtleUtilBoringSSLTest, EcPointEncode) {
   for (const EncodingTestVector& test : encoding_test_vector) {
     std::string x_str = test::HexDecodeOrDie(test.x_hex);
     std::string y_str = test::HexDecodeOrDie(test.y_hex);
@@ -94,8 +98,8 @@
     auto status_or_group = SubtleUtilBoringSSL::GetEcGroup(test.curve);
     bssl::UniquePtr<EC_POINT> point(EC_POINT_new(status_or_group.ValueOrDie()));
     EXPECT_EQ(1, EC_POINT_set_affine_coordinates_GFp(
-        status_or_group.ValueOrDie(), point.get(), x.get(),
-        y.get(), nullptr));
+                     status_or_group.ValueOrDie(), point.get(), x.get(),
+                     y.get(), nullptr));
     auto status_or_string = SubtleUtilBoringSSL::EcPointEncode(
         test.curve, test.format, point.get());
     EXPECT_TRUE(status_or_string.ok());
@@ -103,7 +107,7 @@
   }
 }
 
-TEST(SubtleUtilBoringSSLTest, testEcPointDecode) {
+TEST(SubtleUtilBoringSSLTest, EcPointDecode) {
   for (const EncodingTestVector& test : encoding_test_vector) {
     std::string x_str = test::HexDecodeOrDie(test.x_hex);
     std::string y_str = test::HexDecodeOrDie(test.y_hex);
@@ -117,8 +121,8 @@
     auto status_or_group = SubtleUtilBoringSSL::GetEcGroup(test.curve);
     bssl::UniquePtr<EC_POINT> point(EC_POINT_new(status_or_group.ValueOrDie()));
     EXPECT_EQ(1, EC_POINT_set_affine_coordinates_GFp(
-        status_or_group.ValueOrDie(), point.get(), x.get(),
-        y.get(), nullptr));
+                     status_or_group.ValueOrDie(), point.get(), x.get(),
+                     y.get(), nullptr));
     auto status_or_ec_point = SubtleUtilBoringSSL::EcPointDecode(
         test.curve, test.format, encoded_str);
     EXPECT_TRUE(status_or_ec_point.ok());
@@ -130,11 +134,11 @@
         test.curve, test.format, encoded_str);
     EXPECT_FALSE(status_or_ec_point.ok());
     EXPECT_LE(0, status_or_ec_point.status().error_message().find(
-        "point should start with"));
+                     "point should start with"));
   }
 }
 
-TEST(SubtleUtilBoringSSLTest, testBn2strAndStr2bn) {
+TEST(SubtleUtilBoringSSLTest, Bn2strAndStr2bn) {
   int len = 8;
   std::string bn_str[6] = {"0000000000000000", "0000000000000001",
                       "1000000000000000", "ffffffffffffffff",
@@ -149,7 +153,7 @@
   }
 }
 
-TEST(SubtleUtilBoringSSLTest, testValidateSignatureHash) {
+TEST(SubtleUtilBoringSSLTest, ValidateSignatureHash) {
   EXPECT_TRUE(
       SubtleUtilBoringSSL::ValidateSignatureHash(HashType::SHA256).ok());
   EXPECT_TRUE(
@@ -172,17 +176,17 @@
 }
 
 // Test with test vectors from Wycheproof project.
-bool WycheproofTest(const rapidjson::Value &root) {
+bool WycheproofTest(const rapidjson::Value& root) {
   int errors = 0;
   for (const rapidjson::Value& test_group : root["testGroups"].GetArray()) {
     std::string curve_str = test_group["curve"].GetString();
     // Tink only supports secp256r1, secp384r1 or secp521r1.
-    if (!(curve_str == "secp256r1" || curve_str == "secp384r1"
-          || curve_str == "secp521r1")) {
+    if (!(curve_str == "secp256r1" || curve_str == "secp384r1" ||
+          curve_str == "secp521r1")) {
       continue;
     }
-    EllipticCurveType curve = WycheproofUtil::GetEllipticCurveType(
-        test_group["curve"]);
+    EllipticCurveType curve =
+        WycheproofUtil::GetEllipticCurveType(test_group["curve"]);
     for (const rapidjson::Value& test : test_group["tests"].GetArray()) {
       std::string id = absl::StrCat(test["tcId"].GetInt());
       std::string comment = test["comment"].GetString();
@@ -222,8 +226,8 @@
         continue;
       }
       pub_bytes = pub_bytes.substr(pub_bytes.size() - point_size, point_size);
-      auto status_or_ec_point = SubtleUtilBoringSSL
-          ::EcPointDecode(curve, format, pub_bytes);
+      auto status_or_ec_point =
+          SubtleUtilBoringSSL ::EcPointDecode(curve, format, pub_bytes);
       if (!status_or_ec_point.ok()) {
         if (result == "valid") {
           ADD_FAILURE() << "Could not decode public key with tcId:" << id
@@ -234,10 +238,10 @@
       }
       bssl::UniquePtr<EC_POINT> pub_key(status_or_ec_point.ValueOrDie());
       bssl::UniquePtr<BIGNUM> priv_key(
-          BN_bin2bn(reinterpret_cast<const unsigned char*>(
-              priv_bytes.data()), priv_bytes.size(), nullptr));
-      auto status_or_shared = SubtleUtilBoringSSL
-          ::ComputeEcdhSharedSecret(curve, priv_key.get(), pub_key.get());
+          BN_bin2bn(reinterpret_cast<const unsigned char*>(priv_bytes.data()),
+                    priv_bytes.size(), nullptr));
+      auto status_or_shared = SubtleUtilBoringSSL ::ComputeEcdhSharedSecret(
+          curve, priv_key.get(), pub_key.get());
       if (status_or_shared.ok()) {
         std::string shared = status_or_shared.ValueOrDie();
         if (result == "invalid") {
@@ -259,17 +263,16 @@
   return errors == 0;
 }
 
-
-
-TEST(SubtleUtilBoringSSLTest, testComputeEcdhSharedSecretWithWycheproofTest) {
-  ASSERT_TRUE(WycheproofTest(*WycheproofUtil
-                             ::ReadTestVectors("ecdh_test.json")));
-  ASSERT_TRUE(WycheproofTest(*WycheproofUtil
-                             ::ReadTestVectors("ecdh_secp256r1_test.json")));
-  ASSERT_TRUE(WycheproofTest(*WycheproofUtil
-                             ::ReadTestVectors("ecdh_secp384r1_test.json")));
-  ASSERT_TRUE(WycheproofTest(*WycheproofUtil
-                             ::ReadTestVectors("ecdh_secp521r1_test.json")));
+TEST(SubtleUtilBoringSSLTest, ComputeEcdhSharedSecretWithWycheproofTest) {
+// placeholder_disabled_subtle_test, please ignore
+  ASSERT_TRUE(WycheproofTest(
+      *WycheproofUtil ::ReadTestVectors("ecdh_test.json")));
+  ASSERT_TRUE(WycheproofTest(
+      *WycheproofUtil ::ReadTestVectors("ecdh_secp256r1_test.json")));
+  ASSERT_TRUE(WycheproofTest(
+      *WycheproofUtil ::ReadTestVectors("ecdh_secp384r1_test.json")));
+  ASSERT_TRUE(WycheproofTest(
+      *WycheproofUtil ::ReadTestVectors("ecdh_secp521r1_test.json")));
 }
 
 TEST(CreatesNewRsaKeyPairTest, BasicSanityChecks) {
@@ -373,7 +376,7 @@
 
   // Iterate through a two-element sliding windows, comparing two consecutive
   // elements in the list.
-  for (int i = 0; i < generated_keys.size() - 1; ++i) {
+  for (std::size_t i = 0; i + 1 < generated_keys.size(); ++i) {
     const auto& left = generated_keys[i];
     const auto& right = generated_keys[i + 1];
 
@@ -391,12 +394,116 @@
   }
 }
 
+TEST(CreatesNewEd25519KeyPairTest, BoringSSLPrivateKeySuffix) {
+  // Generate a new key pair.
+  uint8_t out_public_key[ED25519_PUBLIC_KEY_LEN];
+  uint8_t out_private_key[ED25519_PRIVATE_KEY_LEN];
+
+  ED25519_keypair(out_public_key, out_private_key);
+  std::string pk = std::string(reinterpret_cast<const char*>(out_public_key),
+                     ED25519_PUBLIC_KEY_LEN);
+  std::string sk = std::string(reinterpret_cast<const char*>(out_private_key),
+                     ED25519_PRIVATE_KEY_LEN);
+  ASSERT_EQ(pk.length(), 32);
+  ASSERT_EQ(sk.length(), 64);
+  // BoringSSL's ED25519_keypair returns a private key with the last 32-bytes
+  // equal to the public key. If this changes you must update
+  // SubtleUtilBoringSSL::GetNewEd25519Key().
+  ASSERT_EQ(sk.substr(32, std::string::npos), pk);
+}
+
+TEST(CreatesNewEd25519KeyPairTest, KeyIsWellFormed) {
+  auto keypair = SubtleUtilBoringSSL::GetNewEd25519Key();
+  ASSERT_EQ(keypair->public_key.length(), 32);
+  ASSERT_EQ(keypair->private_key.length(), 32);
+  ASSERT_TRUE(keypair->public_key != keypair->private_key);
+}
+
+TEST(CreatesNewEd25519KeyPairTest, GeneratesDifferentKeysEveryTime) {
+  auto keypair1 = SubtleUtilBoringSSL::GetNewEd25519Key();
+  auto keypair2 = SubtleUtilBoringSSL::GetNewEd25519Key();
+  ASSERT_NE(keypair1->public_key, keypair2->public_key);
+  ASSERT_NE(keypair1->private_key, keypair2->private_key);
+  ASSERT_NE(keypair1->public_key, keypair1->private_key);
+}
+
+TEST(SubtleUtilBoringSSLTest, ValidateRsaModulusSize) {
+  SubtleUtilBoringSSL::RsaPublicKey public_key;
+  SubtleUtilBoringSSL::RsaPrivateKey private_key;
+  bssl::UniquePtr<BIGNUM> e(BN_new());
+  BN_set_word(e.get(), RSA_F4);
+  ASSERT_THAT(SubtleUtilBoringSSL::GetNewRsaKeyPair(2048, e.get(), &private_key,
+                                                    &public_key),
+              IsOk());
+  auto n_2048 =
+      std::move(SubtleUtilBoringSSL::str2bn(private_key.n).ValueOrDie());
+  ASSERT_THAT(
+      SubtleUtilBoringSSL::ValidateRsaModulusSize(BN_num_bits(n_2048.get())),
+      IsOk());
+
+  ASSERT_THAT(SubtleUtilBoringSSL::GetNewRsaKeyPair(1024, e.get(), &private_key,
+                                                    &public_key),
+              IsOk());
+  auto n_1024 =
+      std::move(SubtleUtilBoringSSL::str2bn(private_key.n).ValueOrDie());
+  ASSERT_THAT(
+      SubtleUtilBoringSSL::ValidateRsaModulusSize(BN_num_bits(n_1024.get())),
+      Not(IsOk()));
+}
+
+TEST(ComputeHashTest, AcceptsNullStringView) {
+  auto null_hash =
+      boringssl::ComputeHash(absl::string_view(nullptr, 0), *EVP_sha512());
+  auto empty_hash = boringssl::ComputeHash("", *EVP_sha512());
+  std::string str;
+  auto empty_str_hash = boringssl::ComputeHash(str, *EVP_sha512());
+
+  ASSERT_THAT(null_hash.status(), IsOk());
+  ASSERT_THAT(empty_hash.status(), IsOk());
+  ASSERT_THAT(empty_str_hash.status(), IsOk());
+
+  EXPECT_EQ(null_hash.ValueOrDie(), empty_hash.ValueOrDie());
+  EXPECT_EQ(null_hash.ValueOrDie(), empty_str_hash.ValueOrDie());
+}
+
+using ComputeHashSamplesTest = ::testing::TestWithParam<
+    std::tuple<HashType, absl::string_view, absl::string_view>>;
+
+INSTANTIATE_TEST_SUITE_P(
+    NistSampleCases, ComputeHashSamplesTest,
+    ::testing::Values(
+        std::make_tuple(
+            HashType::SHA256, "af397a8b8dd73ab702ce8e53aa9f",
+            "d189498a3463b18e846b8ab1b41583b0b7efc789dad8a7fb885bbf8fb5b45c5c"),
+        std::make_tuple(
+            HashType::SHA256, "59eb45bbbeb054b0b97334d53580ce03f699",
+            "32c38c54189f2357e96bd77eb00c2b9c341ebebacc2945f97804f59a93238288"),
+        std::make_tuple(
+            HashType::SHA512,
+            "16b17074d3e3d97557f9ed77d920b4b1bff4e845b345a922",
+            "6884134582a760046433abcbd53db8ff1a89995862f305b887020f6da6c7b903a3"
+            "14721e972bf438483f452a8b09596298a576c903c91df4a414c7bd20fd1d07"),
+        std::make_tuple(
+            HashType::SHA512,
+            "7651ab491b8fa86f969d42977d09df5f8bee3e5899180b52c968b0db057a6f02a8"
+            "86ad617a84915a",
+            "f35e50e2e02b8781345f8ceb2198f068ba103476f715cfb487a452882c9f0de0c7"
+            "20b2a088a39d06a8a6b64ce4d6470dfeadc4f65ae06672c057e29f14c4daf9")));
+
+TEST_P(ComputeHashSamplesTest, ComputesHash) {
+  const EVP_MD* hasher =
+      SubtleUtilBoringSSL::EvpHash(std::get<0>(GetParam())).ValueOrDie();
+  std::string data = absl::HexStringToBytes(std::get<1>(GetParam()));
+  std::string expected_hash = absl::HexStringToBytes(std::get<2>(GetParam()));
+
+  auto hash_or = boringssl::ComputeHash(data, *hasher);
+  ASSERT_THAT(hash_or.status(), IsOk());
+  std::string hash(reinterpret_cast<char*>(hash_or.ValueOrDie().data()),
+              hash_or.ValueOrDie().size());
+  EXPECT_THAT(hash, StrEq(expected_hash));
+}
+
 }  // namespace
 }  // namespace subtle
 }  // namespace tink
 }  // namespace crypto
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/subtle/wycheproof_util.cc b/cc/subtle/wycheproof_util.cc
index 0659197..dabda2d 100644
--- a/cc/subtle/wycheproof_util.cc
+++ b/cc/subtle/wycheproof_util.cc
@@ -23,9 +23,9 @@
 #include "absl/strings/string_view.h"
 #include "include/rapidjson/document.h"
 #include "include/rapidjson/istreamwrapper.h"
+#include "tink/subtle/common_enums.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
-#include "tink/subtle/common_enums.h"
 
 namespace crypto {
 namespace tink {
@@ -70,8 +70,8 @@
   return HexDecodeOrDie(s);
 }
 
-std::unique_ptr<rapidjson::Document>
-WycheproofUtil::ReadTestVectors(const std::string &filename) {
+std::unique_ptr<rapidjson::Document> WycheproofUtil::ReadTestVectors(
+    const std::string &filename) {
   const std::string kTestVectors = "../wycheproof/testvectors/";
   std::ifstream input_stream;
   input_stream.open(kTestVectors + filename);
@@ -101,8 +101,8 @@
   }
 }
 
-EllipticCurveType
-WycheproofUtil::GetEllipticCurveType(const rapidjson::Value &val) {
+EllipticCurveType WycheproofUtil::GetEllipticCurveType(
+    const rapidjson::Value &val) {
   std::string curve(val.GetString());
   if (curve == "secp256r1") {
     return EllipticCurveType::NIST_P256;
@@ -115,6 +115,20 @@
   }
 }
 
+std::string WycheproofUtil::GetInteger(const rapidjson::Value &val) {
+  std::string hex(val.GetString());
+  // Since val is a hexadecimal integer it can have an odd length.
+  if (hex.size() % 2 == 1) {
+    // Avoid a leading 0 byte.
+    if (hex[0] == '0') {
+      hex = std::string(hex, 1, hex.size() - 1);
+    } else {
+      hex = "0" + hex;
+    }
+  }
+  return HexDecode(hex).ValueOrDie();
+}
+
 }  // namespace subtle
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/subtle/wycheproof_util.h b/cc/subtle/wycheproof_util.h
index f3b696f..20dd806 100644
--- a/cc/subtle/wycheproof_util.h
+++ b/cc/subtle/wycheproof_util.h
@@ -42,6 +42,16 @@
   static HashType GetHashType(const rapidjson::Value &val);
 
   static EllipticCurveType GetEllipticCurveType(const rapidjson::Value &val);
+
+  // Integers in Wycheproof are represented as signed bigendian hexadecimal
+  // strings in twos complement representation.
+  // Integers in EcKey are unsigned and are represented as an array of bytes
+  // using bigendian order.
+  // GetInteger can assume that val is always 0 or a positive integer, since
+  // they are values from the key: a convention in Wycheproof is that parameters
+  // in the test group are valid, only values in the test vector itself may
+  // be invalid.
+  static std::string GetInteger(const rapidjson::Value &val);
 };
 
 }  // namespace subtle
diff --git a/cc/subtle/xchacha20_poly1305_boringssl.cc b/cc/subtle/xchacha20_poly1305_boringssl.cc
new file mode 100644
index 0000000..6376782
--- /dev/null
+++ b/cc/subtle/xchacha20_poly1305_boringssl.cc
@@ -0,0 +1,156 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/subtle/xchacha20_poly1305_boringssl.h"
+
+#include <string>
+#include <vector>
+
+#include "openssl/err.h"
+#include "openssl/evp.h"
+#include "tink/aead.h"
+#include "tink/subtle/random.h"
+#include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/util/errors.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+
+// TODO(azani): Remove after we upgrade BoringSSL.
+EVP_AEAD* EVP_aead_xchacha20_poly1305() {
+  return nullptr;
+}
+
+static const bool IsValidKeySize(uint32_t size_in_bytes) {
+  return size_in_bytes == 32;
+}
+
+XChacha20Poly1305BoringSsl::XChacha20Poly1305BoringSsl(
+    absl::string_view key_value, const EVP_AEAD* aead)
+    : key_(key_value), aead_(aead) {}
+
+util::StatusOr<std::unique_ptr<Aead>> XChacha20Poly1305BoringSsl::New(
+    absl::string_view key_value) {
+  if (!IsValidKeySize(key_value.size())) {
+    return util::Status(util::error::INTERNAL, "Invalid key size");
+  }
+
+  const EVP_AEAD* cipher = EVP_aead_xchacha20_poly1305();
+  if (cipher == nullptr) {
+    return util::Status(util::error::INTERNAL, "Failed to get EVP_AEAD");
+  }
+
+  std::unique_ptr<Aead> aead(new XChacha20Poly1305BoringSsl(key_value, cipher));
+  return std::move(aead);
+}
+
+util::StatusOr<std::string> XChacha20Poly1305BoringSsl::Encrypt(
+    absl::string_view plaintext, absl::string_view additional_data) const {
+  bssl::UniquePtr<EVP_AEAD_CTX> ctx(
+      EVP_AEAD_CTX_new(aead_, reinterpret_cast<const uint8_t*>(key_.data()),
+                       key_.size(), TAG_SIZE));
+  if (ctx.get() == nullptr) {
+    return util::Status(util::error::INTERNAL,
+                        "could not initialize EVP_AEAD_CTX");
+  }
+
+  // BoringSSL expects a non-null pointer for plaintext and additional_data,
+  // regardless of whether the size is 0.
+  plaintext = SubtleUtilBoringSSL::EnsureNonNull(plaintext);
+  additional_data = SubtleUtilBoringSSL::EnsureNonNull(additional_data);
+
+  const std::string nonce = Random::GetRandomBytes(NONCE_SIZE);
+  if (nonce.size() != NONCE_SIZE) {
+    return util::Status(util::error::INTERNAL,
+                        "Failed to get enough random bytes for nonce");
+  }
+
+  size_t ciphertext_size = nonce.size() + plaintext.size() + TAG_SIZE;
+
+  // Write the nonce in the output buffer.
+  std::vector<uint8_t> ct(ciphertext_size + 1);
+  memcpy(&ct[0], reinterpret_cast<const uint8_t*>(nonce.data()), nonce.size());
+  size_t written = nonce.size();
+
+  // Encrypt the plaintext and store it after the nonce.
+  size_t out_len = 0;
+  int ret = EVP_AEAD_CTX_seal(
+      ctx.get(), &ct[written], &out_len, ciphertext_size - written,
+      reinterpret_cast<const uint8_t*>(nonce.data()), nonce.size(),
+      reinterpret_cast<const uint8_t*>(plaintext.data()), plaintext.size(),
+      reinterpret_cast<const uint8_t*>(additional_data.data()),
+      additional_data.size());
+  if (ret != 1) {
+    return util::Status(util::error::INTERNAL, "EVP_AEAD_CTX_seal failed");
+  }
+  written += out_len;
+
+  // Verify that all the expected data has been written.
+  if (written != ciphertext_size) {
+    return util::Status(util::error::INTERNAL, "Incorrect ciphertext size");
+  }
+  return std::string(reinterpret_cast<const char*>(&ct[0]), written);
+}
+
+util::StatusOr<std::string> XChacha20Poly1305BoringSsl::Decrypt(
+    absl::string_view ciphertext, absl::string_view additional_data) const {
+  // BoringSSL expects a non-null pointer for additional_data,
+  // regardless of whether the size is 0.
+  additional_data = SubtleUtilBoringSSL::EnsureNonNull(additional_data);
+
+  if (ciphertext.size() < NONCE_SIZE + TAG_SIZE) {
+    return util::Status(util::error::INTERNAL, "Ciphertext too short");
+  }
+
+  bssl::UniquePtr<EVP_AEAD_CTX> ctx(
+      EVP_AEAD_CTX_new(aead_, reinterpret_cast<const uint8_t*>(key_.data()),
+                       key_.size(), TAG_SIZE));
+  if (ctx.get() == nullptr) {
+    return util::Status(util::error::INTERNAL,
+                        "could not initialize EVP_AEAD_CTX");
+  }
+
+  size_t out_size = ciphertext.size() - NONCE_SIZE - TAG_SIZE;
+  std::vector<uint8_t> out(out_size + 1);
+
+  absl::string_view nonce = ciphertext.substr(0, NONCE_SIZE);
+  absl::string_view encrypted =
+      ciphertext.substr(NONCE_SIZE, out_size + TAG_SIZE);
+
+  size_t len = 0;
+  int ret = EVP_AEAD_CTX_open(
+      ctx.get(), &out[0], &len, out_size,
+      reinterpret_cast<const uint8_t*>(nonce.data()), nonce.size(),
+      reinterpret_cast<const uint8_t*>(encrypted.data()), encrypted.size(),
+      reinterpret_cast<const uint8_t*>(additional_data.data()),
+      additional_data.size());
+  if (ret != 1) {
+    return util::Status(util::error::INTERNAL, "EVP_AEAD_CTX_open failed");
+  }
+
+  if (len != out_size) {
+    return util::Status(util::error::INTERNAL, "Incorrect output size");
+  }
+
+  return std::string(reinterpret_cast<const char*>(&out[0]), out_size);
+}
+
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/subtle/xchacha20_poly1305_boringssl.h b/cc/subtle/xchacha20_poly1305_boringssl.h
new file mode 100644
index 0000000..bf9eb24
--- /dev/null
+++ b/cc/subtle/xchacha20_poly1305_boringssl.h
@@ -0,0 +1,67 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_SUBTLE_XCHACHA20_POLY1305_BORINGSSL_H_
+#define TINK_SUBTLE_XCHACHA20_POLY1305_BORINGSSL_H_
+
+#include <memory>
+
+#include "absl/strings/string_view.h"
+#include "openssl/evp.h"
+#include "tink/aead.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+
+class XChacha20Poly1305BoringSsl : public Aead {
+ public:
+  // Constructs a new Aead cipher for XChacha20-Poly1305.
+  // Currently supported key size is 256 bits.
+  // Currently supported nonce size is 24 bytes.
+  // The tag size is fixed to 16 bytes.
+  static crypto::tink::util::StatusOr<std::unique_ptr<Aead>> New(
+      absl::string_view key_value);
+
+  crypto::tink::util::StatusOr<std::string> Encrypt(
+      absl::string_view plaintext,
+      absl::string_view additional_data) const override;
+
+  crypto::tink::util::StatusOr<std::string> Decrypt(
+      absl::string_view ciphertext,
+      absl::string_view additional_data) const override;
+
+  virtual ~XChacha20Poly1305BoringSsl() {}
+
+ private:
+  // The following constants are in bytes.
+  static const int NONCE_SIZE = 24;
+  static const int TAG_SIZE = 16;
+
+  XChacha20Poly1305BoringSsl() = delete;
+  XChacha20Poly1305BoringSsl(absl::string_view key_value, const EVP_AEAD* aead);
+
+  const std::string key_;
+  const EVP_AEAD* aead_;
+};
+
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_SUBTLE_XCHACHA20_POLY1305_BORINGSSL_H_
diff --git a/cc/subtle/xchacha20_poly1305_boringssl_test.cc b/cc/subtle/xchacha20_poly1305_boringssl_test.cc
new file mode 100644
index 0000000..e623a6f
--- /dev/null
+++ b/cc/subtle/xchacha20_poly1305_boringssl_test.cc
@@ -0,0 +1,220 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#include "tink/subtle/xchacha20_poly1305_boringssl.h"
+
+#include <string>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "absl/strings/str_cat.h"
+#include "openssl/err.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_util.h"
+
+namespace crypto {
+namespace tink {
+namespace subtle {
+namespace {
+
+TEST(XChacha20Poly1305BoringSslTest, testBasic) {
+  std::string key(test::HexDecodeOrDie(
+      "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"));
+  auto res = XChacha20Poly1305BoringSsl::New(key);
+  EXPECT_TRUE(res.ok()) << res.status();
+  auto cipher = std::move(res.ValueOrDie());
+  std::string message = "Some data to encrypt.";
+  std::string aad = "Some data to authenticate.";
+  auto ct = cipher->Encrypt(message, aad);
+  EXPECT_TRUE(ct.ok()) << ct.status();
+  EXPECT_EQ(ct.ValueOrDie().size(),
+            message.size() + 24 /* nonce */ + 16 /* tag */);
+  auto pt = cipher->Decrypt(ct.ValueOrDie(), aad);
+  EXPECT_TRUE(pt.ok()) << pt.status();
+  EXPECT_EQ(pt.ValueOrDie(), message);
+}
+
+TEST(XChacha20Poly1305BoringSslTest, testModification) {
+  std::string key(test::HexDecodeOrDie(
+      "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"));
+  auto cipher = std::move(XChacha20Poly1305BoringSsl::New(key).ValueOrDie());
+  std::string message = "Some data to encrypt.";
+  std::string aad = "Some data to authenticate.";
+  std::string ct = cipher->Encrypt(message, aad).ValueOrDie();
+  EXPECT_TRUE(cipher->Decrypt(ct, aad).ok());
+  // Modify the ciphertext
+  for (size_t i = 0; i < ct.size() * 8; i++) {
+    std::string modified_ct = ct;
+    modified_ct[i / 8] ^= 1 << (i % 8);
+    EXPECT_FALSE(cipher->Decrypt(modified_ct, aad).ok()) << i;
+  }
+  // Modify the additional data
+  for (size_t i = 0; i < aad.size() * 8; i++) {
+    std::string modified_aad = aad;
+    modified_aad[i / 8] ^= 1 << (i % 8);
+    auto decrypted = cipher->Decrypt(ct, modified_aad);
+    EXPECT_FALSE(decrypted.ok()) << i << " pt:" << decrypted.ValueOrDie();
+  }
+  // Truncate the ciphertext
+  for (size_t i = 0; i < ct.size(); i++) {
+    std::string truncated_ct(ct, 0, i);
+    EXPECT_FALSE(cipher->Decrypt(truncated_ct, aad).ok()) << i;
+  }
+}
+
+void TestDecryptWithEmptyAad(crypto::tink::Aead* cipher, absl::string_view ct,
+                             absl::string_view message) {
+  {  // AAD is a null string_view.
+    const absl::string_view aad;
+    auto pt_or_status = cipher->Decrypt(ct, aad);
+    EXPECT_TRUE(pt_or_status.ok()) << pt_or_status.status();
+    auto pt = pt_or_status.ValueOrDie();
+    EXPECT_EQ(message, pt);
+  }
+  {  // AAD is a an empty std::string.
+    auto pt_or_status = cipher->Decrypt(ct, "");
+    EXPECT_TRUE(pt_or_status.ok()) << pt_or_status.status();
+    auto pt = pt_or_status.ValueOrDie();
+    EXPECT_EQ(message, pt);
+  }
+  {  // AAD is a nullptr.
+    auto pt_or_status = cipher->Decrypt(ct, nullptr);
+    EXPECT_TRUE(pt_or_status.ok()) << pt_or_status.status();
+    auto pt = pt_or_status.ValueOrDie();
+    EXPECT_EQ(message, pt);
+  }
+}
+
+TEST(XChacha20Poly1305BoringSslTest, testAadEmptyVersusNullStringView) {
+  const std::string key(test::HexDecodeOrDie(
+      "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"));
+  auto cipher = std::move(XChacha20Poly1305BoringSsl::New(key).ValueOrDie());
+  {  // AAD is a null string_view.
+    const std::string message = "Some data to encrypt.";
+    const absl::string_view aad;
+    auto ct_or_status = cipher->Encrypt(message, aad);
+    EXPECT_TRUE(ct_or_status.ok()) << ct_or_status.status();
+    auto ct = ct_or_status.ValueOrDie();
+    TestDecryptWithEmptyAad(cipher.get(), ct, message);
+  }
+  {  // AAD is a an empty std::string.
+    const std::string message = "Some data to encrypt.";
+    auto ct_or_status = cipher->Encrypt(message, "");
+    EXPECT_TRUE(ct_or_status.ok()) << ct_or_status.status();
+    auto ct = ct_or_status.ValueOrDie();
+    TestDecryptWithEmptyAad(cipher.get(), ct, message);
+  }
+  {  // AAD is a nullptr.
+    const std::string message = "Some data to encrypt.";
+    auto ct_or_status = cipher->Encrypt(message, nullptr);
+    EXPECT_TRUE(ct_or_status.ok()) << ct_or_status.status();
+    auto ct = ct_or_status.ValueOrDie();
+    TestDecryptWithEmptyAad(cipher.get(), ct, message);
+  }
+}
+
+TEST(XChacha20Poly1305BoringSslTest, testMessageEmptyVersusNullStringView) {
+  const std::string key(test::HexDecodeOrDie(
+      "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"));
+  auto cipher = std::move(XChacha20Poly1305BoringSsl::New(key).ValueOrDie());
+  const std::string aad = "Some data to authenticate.";
+  {  // Message is a null string_view.
+    const absl::string_view message;
+    auto ct_or_status = cipher->Encrypt(message, aad);
+    EXPECT_TRUE(ct_or_status.ok());
+    auto ct = ct_or_status.ValueOrDie();
+    auto pt_or_status = cipher->Decrypt(ct, aad);
+    EXPECT_TRUE(pt_or_status.ok()) << pt_or_status.status();
+    auto pt = pt_or_status.ValueOrDie();
+    EXPECT_EQ("", pt);
+  }
+  {  // Message is an empty std::string.
+    const std::string message = "";
+    auto ct_or_status = cipher->Encrypt(message, aad);
+    EXPECT_TRUE(ct_or_status.ok());
+    auto ct = ct_or_status.ValueOrDie();
+    auto pt_or_status = cipher->Decrypt(ct, aad);
+    EXPECT_TRUE(pt_or_status.ok()) << pt_or_status.status();
+    auto pt = pt_or_status.ValueOrDie();
+    EXPECT_EQ("", pt);
+  }
+  {  // Message is a nullptr.
+    auto ct_or_status = cipher->Encrypt(nullptr, aad);
+    EXPECT_TRUE(ct_or_status.ok());
+    auto ct = ct_or_status.ValueOrDie();
+    auto pt_or_status = cipher->Decrypt(ct, aad);
+    EXPECT_TRUE(pt_or_status.ok()) << pt_or_status.status();
+    auto pt = pt_or_status.ValueOrDie();
+    EXPECT_EQ("", pt);
+  }
+}
+
+TEST(XChacha20Poly1305BoringSslTest, testBothMessageAndAadEmpty) {
+  const std::string key(test::HexDecodeOrDie(
+      "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"));
+  auto cipher = std::move(XChacha20Poly1305BoringSsl::New(key).ValueOrDie());
+  {  // Both are null string_view.
+    const absl::string_view message;
+    const absl::string_view aad;
+    auto ct_or_status = cipher->Encrypt(message, aad);
+    EXPECT_TRUE(ct_or_status.ok());
+    auto ct = ct_or_status.ValueOrDie();
+    auto pt_or_status = cipher->Decrypt(ct, aad);
+    EXPECT_TRUE(pt_or_status.ok()) << pt_or_status.status();
+    auto pt = pt_or_status.ValueOrDie();
+    EXPECT_EQ("", pt);
+  }
+  {  // Both are empty std::string.
+    const std::string message = "";
+    const std::string aad = "";
+    auto ct_or_status = cipher->Encrypt(message, aad);
+    EXPECT_TRUE(ct_or_status.ok());
+    auto ct = ct_or_status.ValueOrDie();
+    auto pt_or_status = cipher->Decrypt(ct, aad);
+    EXPECT_TRUE(pt_or_status.ok()) << pt_or_status.status();
+    auto pt = pt_or_status.ValueOrDie();
+    EXPECT_EQ("", pt);
+  }
+  {  // Both are nullptr.
+    auto ct_or_status = cipher->Encrypt(nullptr, nullptr);
+    EXPECT_TRUE(ct_or_status.ok());
+    auto ct = ct_or_status.ValueOrDie();
+    auto pt_or_status = cipher->Decrypt(ct, nullptr);
+    EXPECT_TRUE(pt_or_status.ok()) << pt_or_status.status();
+    auto pt = pt_or_status.ValueOrDie();
+    EXPECT_EQ("", pt);
+  }
+}
+
+TEST(XChacha20Poly1305BoringSslTest, testInvalidKeySizes) {
+  for (int keysize = 0; keysize < 65; keysize++) {
+    if (keysize == 32) {
+      continue;
+    }
+    std::string key(keysize, 'x');
+    auto cipher = XChacha20Poly1305BoringSsl::New(key);
+    EXPECT_FALSE(cipher.ok());
+  }
+  absl::string_view null_string_view;
+  auto nokeycipher = XChacha20Poly1305BoringSsl::New(null_string_view);
+  EXPECT_FALSE(nokeycipher.ok());
+}
+
+}  // namespace
+}  // namespace subtle
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/util/BUILD.bazel b/cc/util/BUILD.bazel
index 0ad2c63..330082f 100644
--- a/cc/util/BUILD.bazel
+++ b/cc/util/BUILD.bazel
@@ -3,6 +3,17 @@
 licenses(["notice"])  # Apache 2.0
 
 cc_library(
+    name = "constants",
+    srcs = ["constants.cc"],
+    hdrs = ["constants.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        "@com_google_absl//absl/base:core_headers",
+    ],
+)
+
+cc_library(
     name = "errors",
     srcs = ["errors.cc"],
     hdrs = ["errors.h"],
@@ -63,6 +74,66 @@
 )
 
 cc_library(
+    name = "file_input_stream",
+    srcs = ["file_input_stream.cc"],
+    hdrs = ["file_input_stream.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        ":errors",
+        ":status",
+        ":statusor",
+        "//cc:input_stream",
+        "@com_google_absl//absl/memory",
+    ],
+)
+
+cc_library(
+    name = "file_output_stream",
+    srcs = ["file_output_stream.cc"],
+    hdrs = ["file_output_stream.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        ":errors",
+        ":status",
+        ":statusor",
+        "//cc:output_stream",
+        "@com_google_absl//absl/memory",
+    ],
+)
+
+cc_library(
+    name = "istream_input_stream",
+    srcs = ["istream_input_stream.cc"],
+    hdrs = ["istream_input_stream.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        ":errors",
+        ":status",
+        ":statusor",
+        "//cc:input_stream",
+        "@com_google_absl//absl/memory",
+    ],
+)
+
+cc_library(
+    name = "ostream_output_stream",
+    srcs = ["ostream_output_stream.cc"],
+    hdrs = ["ostream_output_stream.h"],
+    include_prefix = "tink",
+    strip_include_prefix = "/cc",
+    deps = [
+        ":errors",
+        ":status",
+        ":statusor",
+        "//cc:output_stream",
+        "@com_google_absl//absl/memory",
+    ],
+)
+
+cc_library(
     name = "test_util",
     testonly = 1,
     srcs = ["test_util.cc"],
@@ -76,18 +147,23 @@
         ":statusor",
         "//cc:aead",
         "//cc:cleartext_keyset_handle",
+        "//cc:deterministic_aead",
         "//cc:hybrid_decrypt",
         "//cc:hybrid_encrypt",
         "//cc:keyset_handle",
+        "//cc:input_stream",
         "//cc:mac",
+        "//cc:output_stream",
         "//cc:public_key_sign",
         "//cc:public_key_verify",
+        "//cc:streaming_aead",
         "//cc/aead:aes_gcm_key_manager",
         "//cc/subtle:subtle_util_boringssl",
         "//proto:aes_gcm_cc_proto",
         "//proto:common_cc_proto",
         "//proto:ecdsa_cc_proto",
         "//proto:ecies_aead_hkdf_cc_proto",
+        "//proto:ed25519_cc_proto",
         "//proto:tink_cc_proto",
         "@com_google_absl//absl/memory",
         "@com_google_absl//absl/strings",
@@ -133,6 +209,19 @@
 # tests
 
 cc_test(
+    name = "errors_test",
+    size = "small",
+    srcs = ["errors_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    linkopts = ["-lpthread"],
+    deps = [
+        ":errors",
+        ":status",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
     name = "enums_test",
     size = "small",
     srcs = ["enums_test.cc"],
@@ -147,14 +236,65 @@
 )
 
 cc_test(
-    name = "errors_test",
-    size = "small",
-    srcs = ["errors_test.cc"],
+    name = "file_input_stream_test",
+    size = "medium",
+    srcs = ["file_input_stream_test.cc"],
     copts = ["-Iexternal/gtest/include"],
     linkopts = ["-lpthread"],
     deps = [
-        ":errors",
-        ":status",
+        ":file_input_stream",
+        ":test_util",
+        "//cc/subtle:random",
+        "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "file_output_stream_test",
+    size = "medium",
+    srcs = ["file_output_stream_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    linkopts = ["-lpthread"],
+    deps = [
+        ":file_output_stream",
+        ":test_util",
+        "//cc/subtle:random",
+        "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "istream_input_stream_test",
+    size = "medium",
+    srcs = ["istream_input_stream_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    linkopts = ["-lpthread"],
+    deps = [
+        ":istream_input_stream",
+        ":test_util",
+        "//cc/subtle:random",
+        "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "ostream_output_stream_test",
+    size = "medium",
+    srcs = ["ostream_output_stream_test.cc"],
+    copts = ["-Iexternal/gtest/include"],
+    linkopts = ["-lpthread"],
+    deps = [
+        ":ostream_output_stream",
+        ":test_util",
+        "//cc/subtle:random",
+        "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/strings",
         "@com_google_googletest//:gtest_main",
     ],
 )
diff --git a/cc/util/CMakeLists.txt b/cc/util/CMakeLists.txt
index bd8765e..bf5ddf2 100644
--- a/cc/util/CMakeLists.txt
+++ b/cc/util/CMakeLists.txt
@@ -1,158 +1,72 @@
-# Copyright 2018 The Fuchsia Authors. All rights reserved.
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-# Library: 'tink_util_errors'
-add_library(tink_util_errors errors.cc errors.h)
-tink_export_hdrs(errors.h)
-add_dependencies(tink_util_errors tink_util_status)
-target_link_libraries(tink_util_errors tink_util_status)
 
-# Library: 'tink_util_enums'
-add_library(tink_util_enums enums.cc enums.h)
+# CC Library : constants
+add_library(tink_cc_util_constants constants.h constants.cc)
+tink_export_hdrs(constants.h)
+target_link_libraries(tink_cc_util_constants absl::base)
+
+# CC Library : errors
+add_library(tink_cc_util_errors errors.h errors.cc)
+tink_export_hdrs(errors.h)
+add_dependencies(tink_cc_util_errors tink_cc_util_status)
+target_link_libraries(tink_cc_util_errors tink_cc_util_status)
+
+# CC Library : enums
+add_library(tink_cc_util_enums enums.h enums.cc)
 tink_export_hdrs(enums.h)
 add_dependencies(
-  tink_util_enums
-  tink_subtle_common_enums
+  tink_cc_util_enums
+  tink_cc_subtle_common_enums
   tink_proto_common_lib
   tink_proto_ecdsa_lib
   tink_proto_tink_lib
 )
 target_link_libraries(
-  tink_util_enums
-  tink_subtle_common_enums
+  tink_cc_util_enums
+  tink_cc_subtle_common_enums
   tink_proto_common_lib
   tink_proto_ecdsa_lib
   tink_proto_tink_lib
   absl::strings
 )
 
-# Library: 'tink_util_status'
-add_library(tink_util_status status.cc status.h)
+# CC Library : status
+add_library(tink_cc_util_status status.h status.cc)
 tink_export_hdrs(status.h)
 
-# Library: 'tink_util_statusor'
-add_library(tink_util_statusor statusor.h statusor.h)
+# CC Library : statusor
+add_library(tink_cc_util_statusor statusor.h statusor.h)
 tink_export_hdrs(statusor.h)
-add_dependencies(tink_util_statusor tink_util_status)
-target_link_libraries(tink_util_statusor tink_util_status)
+add_dependencies(tink_cc_util_statusor tink_cc_util_status)
+target_link_libraries(tink_cc_util_statusor tink_cc_util_status)
 
-# Library: 'tink_util_validation'
-add_library(tink_util_validation validation.cc validation.h)
+# CC Library : validation
+add_library(tink_cc_util_validation validation.h validation.cc)
 tink_export_hdrs(validation.h)
 add_dependencies(
-  tink_util_validation
-  tink_util_errors
-  tink_util_status
+  tink_cc_util_validation
+  tink_cc_util_errors
+  tink_cc_util_status
   tink_proto_tink_lib
 )
 target_link_libraries(
-  tink_util_validation
-  tink_util_errors
-  tink_util_status
+  tink_cc_util_validation
+  tink_cc_util_errors
+  tink_cc_util_status
   tink_proto_tink_lib
 )
 
-# Library: 'tink_util_test_util'
-add_library(tink_util_test_util test_util.cc test_util.h)
-tink_export_hdrs(test_util.h)
-add_dependencies(
-  tink_util_test_util
-  tink_util_enums
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_aead
-  tink_cleartext_keyset_handle
-  tink_hybrid_decrypt
-  tink_hybrid_encrypt
-  tink_keyset_handle
-  tink_mac
-  tink_public_key_sign
-  tink_public_key_verify
-  tink_aead_aes_gcm_key_manager
-  tink_subtle_subtle_util_boringssl
-  tink_proto_aes_gcm_lib
-  tink_proto_common_lib
-  tink_proto_ecdsa_lib
-  tink_proto_ecies_aead_hkdf_lib
-  tink_proto_tink_lib
+# CC Library : protobuf_helper
+add_library(tink_cc_util_protobuf_helper protobuf_helper.h)
+set_target_properties(
+  tink_cc_util_protobuf_helper
+  PROPERTIES
+  LINKER_LANGUAGE
+  CXX
 )
-target_link_libraries(
-  tink_util_test_util
-  tink_util_enums
-  tink_util_protobuf_helper
-  tink_util_status
-  tink_util_statusor
-  tink_aead
-  tink_cleartext_keyset_handle
-  tink_hybrid_decrypt
-  tink_hybrid_encrypt
-  tink_keyset_handle
-  tink_mac
-  tink_public_key_sign
-  tink_public_key_verify
-  tink_aead_aes_gcm_key_manager
-  tink_subtle_subtle_util_boringssl
-  tink_proto_aes_gcm_lib
-  tink_proto_common_lib
-  tink_proto_ecdsa_lib
-  tink_proto_ecies_aead_hkdf_lib
-  tink_proto_tink_lib
-  absl::memory
-  absl::strings
-)
-
-# Library: 'tink_util_test_matchers'
-add_library(tink_util_test_matchers test_matchers.h)
-set_target_properties(tink_util_test_matchers PROPERTIES LINKER_LANGUAGE CXX)
-tink_export_hdrs(test_matchers.h)
-add_dependencies(tink_util_test_matchers tink_util_status)
-add_dependencies(tink_util_test_matchers build_external_projects)
-target_link_libraries(tink_util_test_matchers tink_util_status gtest gtest_main)
-
-# Library: 'tink_util_keyset_util'
-add_library(tink_util_keyset_util keyset_util.cc keyset_util.h)
-tink_export_hdrs(keyset_util.h)
-add_dependencies(tink_util_keyset_util tink_keyset_handle tink_proto_tink_lib)
-target_link_libraries(
-  tink_util_keyset_util
-  tink_keyset_handle
-  tink_proto_tink_lib
-  absl::memory
-)
-
-# Library: 'tink_util_protobuf_helper'
-add_library(tink_util_protobuf_helper protobuf_helper.h)
-set_target_properties(tink_util_protobuf_helper PROPERTIES LINKER_LANGUAGE CXX)
 tink_export_hdrs(protobuf_helper.h)
-target_link_libraries(tink_util_protobuf_helper protobuf_lite)
-
-# Test Binary: 'tink_util_enums_test'
-add_executable(tink_util_enums_test enums_test.cc)
-add_dependencies(
-  tink_util_enums_test
-  tink_util_enums
-  tink_subtle_common_enums
-  tink_proto_common_lib
-)
-add_dependencies(tink_util_enums_test build_external_projects)
-target_link_libraries(
-  tink_util_enums_test
-  tink_util_enums
-  tink_subtle_common_enums
-  tink_proto_common_lib
-  gtest gtest_main
-)
-
-# Test Binary: 'tink_util_errors_test'
-add_executable(tink_util_errors_test errors_test.cc)
-add_dependencies(tink_util_errors_test tink_util_errors tink_util_status)
-add_dependencies(tink_util_errors_test build_external_projects)
-target_link_libraries(
-  tink_util_errors_test
-  tink_util_errors
-  tink_util_status
-  gtest gtest_main
-)
+target_link_libraries(tink_cc_util_protobuf_helper protobuf_lite)
 
diff --git a/cc/util/constants.cc b/cc/util/constants.cc
new file mode 100644
index 0000000..32bbf23
--- /dev/null
+++ b/cc/util/constants.cc
@@ -0,0 +1,26 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+#include "tink/util/constants.h"
+
+#include "absl/base/attributes.h"
+
+namespace crypto {
+namespace tink {
+
+ABSL_CONST_INIT const char kTypeGoogleapisCom[] = "type.googleapis.com/";
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/util/constants.h b/cc/util/constants.h
new file mode 100644
index 0000000..0e18b27
--- /dev/null
+++ b/cc/util/constants.h
@@ -0,0 +1,28 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+#ifndef TINK_UTIL_CONSTANTS_H_
+#define TINK_UTIL_CONSTANTS_H_
+
+namespace crypto {
+namespace tink {
+
+extern const char kTypeGoogleapisCom[];
+
+}  // namespace tink
+}  // namespace crypto
+
+
+#endif  // TINK_UTIL_CONSTANTS_H_
diff --git a/cc/util/enums_test.cc b/cc/util/enums_test.cc
index b404da8..5f4f552 100644
--- a/cc/util/enums_test.cc
+++ b/cc/util/enums_test.cc
@@ -56,11 +56,10 @@
 
   // Check that enum conversion covers the entire range of the proto-enum.
   int count = 0;
-  for (int int_type = (int)pb::EllipticCurveType_MIN;
-       int_type <= (int)pb::EllipticCurveType_MAX;
-       int_type++) {
+  for (int int_type = static_cast<int>(pb::EllipticCurveType_MIN);
+       int_type <= static_cast<int>(pb::EllipticCurveType_MAX); int_type++) {
     if (pb::EllipticCurveType_IsValid(int_type)) {
-      pb::EllipticCurveType type = (pb::EllipticCurveType)int_type;
+      pb::EllipticCurveType type = static_cast<pb::EllipticCurveType>(int_type);
       EXPECT_EQ(type,
                 Enums::SubtleToProto(Enums::ProtoToSubtle(type)));
       count++;
@@ -94,11 +93,10 @@
 
   // Check that enum conversion covers the entire range of the proto-enum.
   int count = 0;
-  for (int int_type = (int)pb::HashType_MIN;
-       int_type <= (int)pb::HashType_MAX;
-       int_type++) {
+  for (int int_type = static_cast<int>(pb::HashType_MIN);
+       int_type <= static_cast<int>(pb::HashType_MAX); int_type++) {
     if (pb::HashType_IsValid(int_type)) {
-      pb::HashType type = (pb::HashType)int_type;
+      pb::HashType type = static_cast<pb::HashType>(int_type);
       EXPECT_EQ(type,
                 Enums::SubtleToProto(Enums::ProtoToSubtle(type)));
       count++;
@@ -134,11 +132,10 @@
 
   // Check that enum conversion covers the entire range of the proto-enum.
   int count = 0;
-  for (int int_format = (int)pb::EcPointFormat_MIN;
-       int_format <= (int)pb::EcPointFormat_MAX;
-       int_format++) {
+  for (int int_format = static_cast<int>(pb::EcPointFormat_MIN);
+       int_format <= static_cast<int>(pb::EcPointFormat_MAX); int_format++) {
     if (pb::EcPointFormat_IsValid(int_format)) {
-      pb::EcPointFormat format = (pb::EcPointFormat)int_format;
+      pb::EcPointFormat format = static_cast<pb::EcPointFormat>(int_format);
       EXPECT_EQ(format,
                 Enums::SubtleToProto(Enums::ProtoToSubtle(format)));
       count++;
@@ -163,11 +160,12 @@
             Enums::ProtoToSubtle(pb::EcdsaSignatureEncoding::DER));
   // Check that enum conversion covers the entire range of the proto-enum.
   int count = 0;
-  for (int int_encoding = (int)pb::EcdsaSignatureEncoding_MIN;
-       int_encoding <= (int)pb::EcdsaSignatureEncoding_MAX; int_encoding++) {
+  for (int int_encoding = static_cast<int>(pb::EcdsaSignatureEncoding_MIN);
+       int_encoding <= static_cast<int>(pb::EcdsaSignatureEncoding_MAX);
+       int_encoding++) {
     if (pb::EcdsaSignatureEncoding_IsValid(int_encoding)) {
       pb::EcdsaSignatureEncoding encoding =
-          (pb::EcdsaSignatureEncoding)int_encoding;
+          static_cast<pb::EcdsaSignatureEncoding>(int_encoding);
       EXPECT_EQ(encoding, Enums::SubtleToProto(Enums::ProtoToSubtle(encoding)));
       count++;
     }
@@ -197,11 +195,10 @@
 
   // Check that enum conversion covers the entire range of the proto-enum.
   int count = 0;
-  for (int int_status = (int)pb::KeyStatusType_MIN;
-       int_status <= (int)pb::KeyStatusType_MAX;
-       int_status++) {
+  for (int int_status = static_cast<int>(pb::KeyStatusType_MIN);
+       int_status <= static_cast<int>(pb::KeyStatusType_MAX); int_status++) {
     if (pb::KeyStatusType_IsValid(int_status)) {
-      pb::KeyStatusType status = (pb::KeyStatusType)int_status;
+      pb::KeyStatusType status = static_cast<pb::KeyStatusType>(int_status);
       EXPECT_EQ(status,
                 Enums::KeyStatus(Enums::KeyStatusName(status)));
       count++;
@@ -227,11 +224,10 @@
 
   // Check that enum conversion covers the entire range of the proto-enum.
   int count = 0;
-  for (int int_hash = (int)pb::HashType_MIN;
-       int_hash <= (int)pb::HashType_MAX;
-       int_hash++) {
+  for (int int_hash = static_cast<int>(pb::HashType_MIN);
+       int_hash <= static_cast<int>(pb::HashType_MAX); int_hash++) {
     if (pb::HashType_IsValid(int_hash)) {
-      pb::HashType hash = (pb::HashType)int_hash;
+      pb::HashType hash = static_cast<pb::HashType>(int_hash);
       EXPECT_EQ(hash, Enums::Hash(Enums::HashName(hash)));
       count++;
     }
@@ -268,12 +264,12 @@
 
   // Check that enum conversion covers the entire range of the proto-enum.
   int count = 0;
-  for (int int_type = (int)pb::KeyData::KeyMaterialType_MIN;
-       int_type <= (int)pb::KeyData::KeyMaterialType_MAX;
+  for (int int_type = static_cast<int>(pb::KeyData::KeyMaterialType_MIN);
+       int_type <= static_cast<int>(pb::KeyData::KeyMaterialType_MAX);
        int_type++) {
     if (pb::KeyData::KeyMaterialType_IsValid(int_type)) {
       pb::KeyData::KeyMaterialType type =
-          (pb::KeyData::KeyMaterialType)int_type;
+          static_cast<pb::KeyData::KeyMaterialType>(int_type);
       EXPECT_EQ(type,
                 Enums::KeyMaterial(Enums::KeyMaterialName(type)));
       count++;
@@ -308,11 +304,10 @@
 
   // Check that enum conversion covers the entire range of the proto-enum.
   int count = 0;
-  for (int int_type = (int)pb::OutputPrefixType_MIN;
-       int_type <= (int)pb::OutputPrefixType_MAX;
-       int_type++) {
+  for (int int_type = static_cast<int>(pb::OutputPrefixType_MIN);
+       int_type <= static_cast<int>(pb::OutputPrefixType_MAX); int_type++) {
     if (pb::OutputPrefixType_IsValid(int_type)) {
-      pb::OutputPrefixType type = (pb::OutputPrefixType)int_type;
+      pb::OutputPrefixType type = static_cast<pb::OutputPrefixType>(int_type);
       EXPECT_EQ(type, Enums::OutputPrefix(Enums::OutputPrefixName(type)));
       count++;
     }
@@ -323,8 +318,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/util/errors_test.cc b/cc/util/errors_test.cc
index 9e1822c..4feb6a9 100644
--- a/cc/util/errors_test.cc
+++ b/cc/util/errors_test.cc
@@ -54,8 +54,3 @@
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
-
-int main(int ac, char* av[]) {
-  testing::InitGoogleTest(&ac, av);
-  return RUN_ALL_TESTS();
-}
diff --git a/cc/util/file_input_stream.cc b/cc/util/file_input_stream.cc
new file mode 100644
index 0000000..d0644c5
--- /dev/null
+++ b/cc/util/file_input_stream.cc
@@ -0,0 +1,114 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/util/file_input_stream.h"
+
+#include <unistd.h>
+#include <algorithm>
+
+#include "absl/memory/memory.h"
+#include "tink/input_stream.h"
+#include "tink/util/errors.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace util {
+
+namespace {
+
+// Attempts to close file descriptor fd, while ignoring EINTR.
+// (code borrowed from ZeroCopy-streams)
+int close_ignoring_eintr(int fd) {
+  int result;
+  do {
+    result = close(fd);
+  } while (result < 0 && errno == EINTR);
+  return result;
+}
+
+
+// Attempts to read 'count' bytes of data data from file descriptor fd
+// to 'buf' while ignoring EINTR.
+int read_ignoring_eintr(int fd, void *buf, size_t count) {
+  int result;
+  do {
+    result = read(fd, buf, count);
+  } while (result < 0 && errno == EINTR);
+  return result;
+}
+
+}  // anonymous namespace
+
+FileInputStream::FileInputStream(int file_descriptor, int buffer_size) :
+    buffer_size_(buffer_size > 0 ? buffer_size : 128 * 1024) {  // 128 KB
+  fd_ = file_descriptor;
+  count_in_buffer_ = 0;
+  count_backedup_ = 0;
+  position_ = 0;
+  buffer_ = absl::make_unique<uint8_t[]>(buffer_size_);
+  buffer_offset_ = 0;
+  status_ = Status::OK;
+}
+
+crypto::tink::util::StatusOr<int> FileInputStream::Next(const void** data) {
+  if (!status_.ok()) return status_;
+  if (count_backedup_ > 0) {  // Return the backed-up bytes.
+    buffer_offset_ = buffer_offset_ + (count_in_buffer_ - count_backedup_);
+    count_in_buffer_ = count_backedup_;
+    count_backedup_ = 0;
+    *data = buffer_.get() + buffer_offset_;
+    position_ = position_ + count_in_buffer_;
+    return count_in_buffer_;
+  }
+  // Read new bytes to buffer_.
+  int read_result = read_ignoring_eintr(fd_, buffer_.get(), buffer_size_);
+  if (read_result <= 0) {  // EOF or an I/O error.
+    if (read_result == 0) {
+      status_ = Status(util::error::OUT_OF_RANGE, "EOF");
+    } else {
+      status_ =
+          ToStatusF(util::error::INTERNAL, "I/O error: %d", read_result);
+    }
+    return status_;
+  }
+  buffer_offset_ = 0;
+  count_backedup_ = 0;
+  count_in_buffer_ = read_result;
+  position_ = position_ + count_in_buffer_;
+  *data = buffer_.get();
+  return count_in_buffer_;
+}
+
+void FileInputStream::BackUp(int count) {
+  if (!status_.ok() || count < 1 || count_backedup_ == count_in_buffer_) return;
+  int actual_count = std::min(count, count_in_buffer_ - count_backedup_);
+  count_backedup_ = count_backedup_ + actual_count;
+  position_ = position_ - actual_count;
+}
+
+FileInputStream::~FileInputStream() {
+  close_ignoring_eintr(fd_);
+}
+
+int64_t FileInputStream::Position() const {
+  return position_;
+}
+
+}  // namespace util
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/util/file_input_stream.h b/cc/util/file_input_stream.h
new file mode 100644
index 0000000..5c816b0
--- /dev/null
+++ b/cc/util/file_input_stream.h
@@ -0,0 +1,64 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_UTIL_FILE_INPUT_STREAM_H_
+#define TINK_UTIL_FILE_INPUT_STREAM_H_
+
+#include <memory>
+
+#include "tink/input_stream.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace util {
+
+// An InputStream that reads from a file descriptor.
+class FileInputStream : public crypto::tink::InputStream {
+ public:
+  // Constructs an InputStream that will read from the file specified
+  // via 'file_descriptor', using a buffer of the specified size, if any
+  // (if no legal 'buffer_size' is given, a reasonable default will be used).
+  // Takes the ownership of the file, and will close it upon destruction.
+  explicit FileInputStream(int file_descriptor, int buffer_size = -1);
+
+  ~FileInputStream() override;
+
+  crypto::tink::util::StatusOr<int> Next(const void** data) override;
+
+  void BackUp(int count) override;
+
+  int64_t Position() const override;
+
+ private:
+  util::Status status_;
+  int fd_;
+  std::unique_ptr<uint8_t[]> buffer_;
+  const int buffer_size_;
+  int64_t position_;     // current position in the file (from the beginning)
+
+  // Counters that describe the state of the data in buffer_.
+  int count_in_buffer_;  // # of bytes available in buffer_
+  int count_backedup_;   // # of bytes available in buffer_ that were backed up
+  int buffer_offset_;    // offset at which the returned bytes start in buffer_
+};
+
+}  // namespace util
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_UTIL_FILE_INPUT_STREAM_H_
diff --git a/cc/util/file_input_stream_test.cc b/cc/util/file_input_stream_test.cc
new file mode 100644
index 0000000..441544c
--- /dev/null
+++ b/cc/util/file_input_stream_test.cc
@@ -0,0 +1,204 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/util/file_input_stream.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "absl/memory/memory.h"
+#include "absl/strings/string_view.h"
+#include "tink/subtle/random.h"
+#include "tink/util/test_util.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+// Creates a new test file with the specified 'filename', writes 'size' random
+// bytes to the file, and returns a file descriptor for reading from the file.
+// A copy of the bytes written to the file is returned in 'file_contents'.
+int GetTestFileDescriptor(
+    absl::string_view filename, int size, std::string* file_contents) {
+  std::string full_filename =
+      absl::StrCat(crypto::tink::test::TmpDir(), "/", filename);
+  (*file_contents) = subtle::Random::GetRandomBytes(size);
+  mode_t mode = S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH;
+  int fd = open(full_filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, mode);
+  if (fd == -1) {
+    std::clog << "Cannot create file " << full_filename
+              << " error: " << errno << std::endl;
+    exit(1);
+  }
+  if (write(fd, file_contents->data(), size) != size) {
+    std::clog << "Failed to write " << size << " bytes to file "
+              << full_filename << " error: " << errno << std::endl;
+
+    exit(1);
+  }
+  close(fd);
+  fd = open(full_filename.c_str(), O_RDONLY);
+  if (fd == -1) {
+    std::clog << "Cannot re-open file " << full_filename
+              << " error: " << errno << std::endl;
+    exit(1);
+  }
+  return fd;
+}
+
+// Reads the specified 'input_stream' until no more bytes can be read,
+// and puts the read bytes into 'contents'.
+// Returns the status of the last input_stream->Next()-operation.
+util::Status ReadTillEnd(util::FileInputStream* input_stream,
+                         std::string* contents) {
+  contents->clear();
+  const void* buffer;
+  auto next_result = input_stream->Next(&buffer);
+  while (next_result.ok()) {
+    contents->append(static_cast<const char*>(buffer),
+                     next_result.ValueOrDie());
+    next_result = input_stream->Next(&buffer);
+  }
+  return next_result.status();
+}
+
+class FileInputStreamTest : public ::testing::Test {
+};
+
+TEST_F(FileInputStreamTest, testReadingStreams) {
+  std::vector<int> stream_sizes = {0, 10, 100, 1000, 10000, 100000, 1000000};
+  for (auto stream_size : stream_sizes) {
+    std::string file_contents;
+    std::string filename = absl::StrCat(stream_size, "_reading_test.bin");
+    int input_fd = GetTestFileDescriptor(filename, stream_size, &file_contents);
+    EXPECT_EQ(stream_size, file_contents.size());
+    auto input_stream = absl::make_unique<util::FileInputStream>(input_fd);
+    std::string stream_contents;
+    auto status = ReadTillEnd(input_stream.get(), &stream_contents);
+    EXPECT_EQ(util::error::OUT_OF_RANGE, status.error_code());
+    EXPECT_EQ("EOF", status.error_message());
+    EXPECT_EQ(file_contents, stream_contents);
+  }
+}
+
+TEST_F(FileInputStreamTest, testCustomBufferSizes) {
+  std::vector<int> buffer_sizes = {1, 10, 100, 1000, 10000};
+  int stream_size = 100000;
+  for (auto buffer_size : buffer_sizes) {
+    std::string file_contents;
+    std::string filename = absl::StrCat(buffer_size, "_buffer_size_test.bin");
+    int input_fd = GetTestFileDescriptor(filename, stream_size, &file_contents);
+    EXPECT_EQ(stream_size, file_contents.size());
+    auto input_stream =
+        absl::make_unique<util::FileInputStream>(input_fd, buffer_size);
+    const void* buffer;
+    auto next_result = input_stream->Next(&buffer);
+    EXPECT_TRUE(next_result.ok()) << next_result.status();
+    EXPECT_EQ(buffer_size, next_result.ValueOrDie());
+    EXPECT_EQ(file_contents.substr(0, buffer_size),
+              std::string(static_cast<const char*>(buffer), buffer_size));
+  }
+}
+
+TEST_F(FileInputStreamTest, testBackupAndPosition) {
+  int stream_size = 100000;
+  int buffer_size = 1234;
+  const void* buffer;
+  std::string file_contents;
+  std::string filename = absl::StrCat(buffer_size, "_backup_test.bin");
+  int input_fd = GetTestFileDescriptor(filename, stream_size, &file_contents);
+  EXPECT_EQ(stream_size, file_contents.size());
+
+  // Prepare the stream and do the first call to Next().
+  auto input_stream =
+      absl::make_unique<util::FileInputStream>(input_fd, buffer_size);
+  EXPECT_EQ(0, input_stream->Position());
+  auto next_result = input_stream->Next(&buffer);
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(buffer_size, next_result.ValueOrDie());
+  EXPECT_EQ(buffer_size, input_stream->Position());
+  EXPECT_EQ(file_contents.substr(0, buffer_size),
+            std::string(static_cast<const char*>(buffer), buffer_size));
+
+  // BackUp several times, but in total fewer bytes than returned by Next().
+  std::vector<int> backup_sizes = {0, 1, 5, 0, 10, 100, -42, 400, 20, -100};
+  int total_backup_size = 0;
+  for (auto backup_size : backup_sizes) {
+    input_stream->BackUp(backup_size);
+    total_backup_size += std::max(0, backup_size);
+    EXPECT_EQ(buffer_size - total_backup_size, input_stream->Position());
+  }
+  // Call Next(), it should return exactly the backed up bytes.
+  next_result = input_stream->Next(&buffer);
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(total_backup_size, next_result.ValueOrDie());
+  EXPECT_EQ(buffer_size, input_stream->Position());
+  EXPECT_EQ(
+      file_contents.substr(buffer_size - total_backup_size, total_backup_size),
+      std::string(static_cast<const char*>(buffer), total_backup_size));
+
+  // BackUp() some bytes, again fewer than returned by Next().
+  backup_sizes = {0, 72, -94, 37, 82};
+  total_backup_size = 0;
+  for (auto backup_size : backup_sizes) {
+    input_stream->BackUp(backup_size);
+    total_backup_size += std::max(0, backup_size);
+    EXPECT_EQ(buffer_size - total_backup_size, input_stream->Position());
+  }
+
+  // Call Next(), it should return exactly the backed up bytes.
+  next_result = input_stream->Next(&buffer);
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(total_backup_size, next_result.ValueOrDie());
+  EXPECT_EQ(buffer_size, input_stream->Position());
+  EXPECT_EQ(
+      file_contents.substr(buffer_size - total_backup_size, total_backup_size),
+      std::string(static_cast<const char*>(buffer), total_backup_size));
+
+  // Call Next() again, it should return the second block.
+  next_result = input_stream->Next(&buffer);
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(buffer_size, next_result.ValueOrDie());
+  EXPECT_EQ(2 * buffer_size, input_stream->Position());
+  EXPECT_EQ(
+      file_contents.substr(buffer_size, buffer_size),
+      std::string(static_cast<const char*>(buffer), buffer_size));
+
+  // BackUp a few times, with total over the returned buffer_size.
+  backup_sizes = {0, 72, -100, buffer_size/2, 200, -25, buffer_size, 42};
+  total_backup_size = 0;
+  for (auto backup_size : backup_sizes) {
+    input_stream->BackUp(backup_size);
+    total_backup_size = std::min(buffer_size,
+                                 total_backup_size + std::max(0, backup_size));
+    EXPECT_EQ(2 * buffer_size - total_backup_size, input_stream->Position());
+  }
+
+  // Call Next() again, it should return the second block.
+  next_result = input_stream->Next(&buffer);
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(buffer_size, next_result.ValueOrDie());
+  EXPECT_EQ(2 * buffer_size, input_stream->Position());
+  EXPECT_EQ(
+      file_contents.substr(buffer_size, buffer_size),
+      std::string(static_cast<const char*>(buffer), buffer_size));
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/util/file_output_stream.cc b/cc/util/file_output_stream.cc
new file mode 100644
index 0000000..9d9d0e6
--- /dev/null
+++ b/cc/util/file_output_stream.cc
@@ -0,0 +1,170 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/util/file_output_stream.h"
+
+#include <unistd.h>
+#include <cstring>
+#include <algorithm>
+
+#include "absl/memory/memory.h"
+#include "tink/output_stream.h"
+#include "tink/util/errors.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace util {
+
+namespace {
+
+// Attempts to close file descriptor fd, while ignoring EINTR.
+// (code borrowed from ZeroCopy-streams)
+int close_ignoring_eintr(int fd) {
+  int result;
+  do {
+    result = close(fd);
+  } while (result < 0 && errno == EINTR);
+  return result;
+}
+
+
+// Attempts to write 'count' bytes of data data from 'buf'
+// to file descriptor fd, while ignoring EINTR.
+int write_ignoring_eintr(int fd, const void *buf, size_t count) {
+  int result;
+  do {
+    result = write(fd, buf, count);
+  } while (result < 0 && errno == EINTR);
+  return result;
+}
+
+}  // anonymous namespace
+
+
+FileOutputStream::FileOutputStream(int file_descriptor, int buffer_size) :
+    buffer_size_(buffer_size > 0 ? buffer_size : 128 * 1024) {  // 128 KB
+  fd_ = file_descriptor;
+  count_in_buffer_ = 0;
+  count_backedup_ = 0;
+  buffer_ = nullptr;
+  position_ = 0;
+  buffer_offset_ = 0;
+  status_ = Status::OK;
+}
+
+crypto::tink::util::StatusOr<int> FileOutputStream::Next(void** data) {
+  if (!status_.ok()) return status_;
+
+  if (buffer_ == nullptr) {  // possible only at the first call to Next()
+    buffer_ = absl::make_unique<uint8_t[]>(buffer_size_);
+    *data = buffer_.get();
+    count_in_buffer_ = buffer_size_;
+    position_ = buffer_size_;
+    return buffer_size_;
+  }
+
+  // If some space was backed up, return it first.
+  if (count_backedup_ > 0) {
+    position_ = position_ + count_backedup_;
+    buffer_offset_ = count_in_buffer_;
+    count_in_buffer_ = count_in_buffer_ + count_backedup_;
+    int backedup = count_backedup_;
+    count_backedup_ = 0;
+    *data = buffer_.get() + buffer_offset_;
+    return backedup;
+  }
+
+  // No space was backed up, so count_in_buffer_ == buffer_size_ holds here.
+  // Write the data from the buffer, and return available space in buffer_.
+  // The available space might not span the entire buffer_, as writing
+  // may succeed only for a prefix of buffer_ -- in this case the data still
+  // to be written is shifted in buffer_ and the remaining space is returned.
+  int write_result = write_ignoring_eintr(fd_, buffer_.get(), buffer_size_);
+  if (write_result <= 0) {  // No data written or an I/O error occurred.
+    if (write_result == 0) {
+      return 0;
+    }
+    status_ = ToStatusF(
+        util::error::INTERNAL, "I/O error upon write: %d", errno);
+    return status_;
+  }
+  // Some data was written, so we can return some portion of buffer_.
+  position_ = position_ + write_result;
+  count_in_buffer_ = buffer_size_;
+  count_backedup_ = 0;
+  buffer_offset_ = buffer_size_ - write_result;
+  *data = buffer_.get() + buffer_offset_;
+  if (write_result < buffer_size_) {
+    // Only part of the data was written, shift the remaining data in buffer_.
+    // Using memmove, as source and destination may overlap.
+    std::memmove(buffer_.get(), buffer_.get() + write_result, buffer_offset_);
+  }
+  return write_result;
+}
+
+void FileOutputStream::BackUp(int count) {
+  if (!status_.ok() || count < 1 || count_in_buffer_ == 0) return;
+  int curr_buffer_size = buffer_size_ - buffer_offset_;
+  int actual_count = std::min(count, curr_buffer_size - count_backedup_);
+  count_backedup_ += actual_count;
+  count_in_buffer_ -= actual_count;
+  position_ -= actual_count;
+}
+
+FileOutputStream::~FileOutputStream() {
+  Close().IgnoreError();
+}
+
+Status FileOutputStream::Close() {
+  if (!status_.ok()) return status_;
+  if (count_in_buffer_ > 0) {
+    // Try to write the remaining bytes.
+    int total_written = 0;
+    while (total_written < count_in_buffer_) {
+      int write_result = write_ignoring_eintr(
+          fd_, buffer_.get() + total_written, count_in_buffer_ - total_written);
+      if (write_result < 0) {  // An I/O error occurred.
+        status_ = ToStatusF(
+            util::error::INTERNAL, "I/O error upon write: %d", errno);
+        return status_;
+      } else if (write_result == 0) {  // No progress, hence abort.
+        status_ = ToStatusF(util::error::INTERNAL,
+            "I/O error: failed to write %d bytes before closing.",
+            count_in_buffer_ - total_written);
+        return status_;
+      }
+      // Managed to write some bytes, hence continue.
+      total_written += write_result;
+    }
+  }
+  if (close_ignoring_eintr(fd_) == -1) {
+    status_ = ToStatusF(
+        util::error::INTERNAL, "I/O error upon close: %d", errno);
+    return status_;
+  }
+  status_ = Status(util::error::FAILED_PRECONDITION, "Stream closed");
+  return Status::OK;
+}
+
+int64_t FileOutputStream::Position() const {
+  return position_;
+}
+
+}  // namespace util
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/util/file_output_stream.h b/cc/util/file_output_stream.h
new file mode 100644
index 0000000..43b638a
--- /dev/null
+++ b/cc/util/file_output_stream.h
@@ -0,0 +1,70 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_UTIL_FILE_OUTPUT_STREAM_H_
+#define TINK_UTIL_FILE_OUTPUT_STREAM_H_
+
+#include <memory>
+
+#include "tink/output_stream.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace util {
+
+// An OutputStream that writes to a file descriptor.
+class FileOutputStream : public crypto::tink::OutputStream {
+ public:
+  // Constructs an OutputStream that will write to the file specified
+  // via 'file_descriptor', using a buffer of the specified size, if any
+  // (if no legal 'buffer_size' is given, a reasonable default will be used).
+  // Takes the ownership of the file, and will close it upon destruction.
+  explicit FileOutputStream(int file_descriptor, int buffer_size = -1);
+
+  ~FileOutputStream() override;
+
+  crypto::tink::util::StatusOr<int> Next(void** data) override;
+
+  void BackUp(int count) override;
+
+  crypto::tink::util::Status Close() override;
+
+  int64_t Position() const override;
+
+ private:
+  util::Status status_;
+  int fd_;
+  std::unique_ptr<uint8_t[]> buffer_;
+  const int buffer_size_;
+  int64_t position_;     // current position in the file (from the beginning)
+
+  // Counters that describe the state of the data in buffer_.
+  // count_in_buffer_ is always equal to (buffer_size_ - count_backedup_),
+  // except initially (before the first call to Next()).
+  // In other words, we have an invariant:
+  // (count_in_buffer_ == buffer_size_ - count_backedup_) || buffer_ == nullptr
+  int count_in_buffer_;  // # bytes in buffer_ that will be eventually written
+  int count_backedup_;   // # bytes in buffer_ that were backed up
+  int buffer_offset_;    // offset where the returned *data starts in buffer_
+};
+
+}  // namespace util
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_UTIL_FILE_OUTPUT_STREAM_H_
diff --git a/cc/util/file_output_stream_test.cc b/cc/util/file_output_stream_test.cc
new file mode 100644
index 0000000..8f47f87
--- /dev/null
+++ b/cc/util/file_output_stream_test.cc
@@ -0,0 +1,227 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/util/file_output_stream.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "absl/memory/memory.h"
+#include "absl/strings/string_view.h"
+#include "tink/subtle/random.h"
+#include "tink/util/test_util.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+// Creates a new test file with the specified 'filename', ready for writing.
+int GetTestFileDescriptor(absl::string_view filename) {
+  std::string full_filename =
+      absl::StrCat(crypto::tink::test::TmpDir(), "/", filename);
+  mode_t mode = S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH;
+  int fd = open(full_filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, mode);
+  if (fd == -1) {
+    std::clog << "Cannot create file " << full_filename
+              << " error: " << errno << std::endl;
+    exit(1);
+  }
+  return fd;
+}
+
+// Writes 'contents' the specified 'output_stream', and closes the stream.
+// Returns the status of output_stream->Close()-operation, or a non-OK status
+// of a prior output_stream->Next()-operation, if any.
+util::Status WriteToStream(util::FileOutputStream* output_stream,
+                           absl::string_view contents) {
+  void* buffer;
+  int pos = 0;
+  int remaining = contents.length();
+  int available_space;
+  int available_bytes;
+  while (remaining > 0) {
+    auto next_result = output_stream->Next(&buffer);
+    if (!next_result.ok()) return next_result.status();
+    available_space = next_result.ValueOrDie();
+    available_bytes = std::min(available_space, remaining);
+    memcpy(buffer, contents.data() + pos, available_bytes);
+    remaining -= available_bytes;
+    pos += available_bytes;
+  }
+  if (available_space > available_bytes) {
+    output_stream->BackUp(available_space - available_bytes);
+  }
+  return output_stream->Close();
+}
+
+// Reads the test file specified by 'filename', and returns its contents.
+std::string ReadFile(std::string filename) {
+  std::string full_filename =
+      absl::StrCat(crypto::tink::test::TmpDir(), "/", filename);
+  int fd = open(full_filename.c_str(), O_RDONLY);
+  if (fd == -1) {
+    std::clog << "Cannot open file " << full_filename
+              << " error: " << errno << std::endl;
+    exit(1);
+  }
+  std::string contents;
+  int buffer_size = 128 * 1024;
+  auto buffer = absl::make_unique<uint8_t[]>(buffer_size);
+  int read_result = read(fd, buffer.get(), buffer_size);
+  while (read_result > 0) {
+    std::clog << "Read " << read_result << " bytes" << std::endl;
+    contents.append(reinterpret_cast<const char*>(buffer.get()), read_result);
+    read_result = read(fd, buffer.get(), buffer_size);
+  }
+  if (read_result < 0) {
+    std::clog << "Error reading file " << full_filename
+              << " error: " << errno << std::endl;
+    exit(1);
+  }
+  close(fd);
+  std::clog << "Read in total " << contents.length() << " bytes" << std::endl;
+  return contents;
+}
+
+class FileOutputStreamTest : public ::testing::Test {
+};
+
+TEST_F(FileOutputStreamTest, WritingStreams) {
+  std::vector<int> stream_sizes = {0, 10, 100, 1000, 10000, 100000, 1000000};
+  for (auto stream_size : stream_sizes) {
+    std::string stream_contents = subtle::Random::GetRandomBytes(stream_size);
+    std::string filename = absl::StrCat(stream_size, "_writing_test.bin");
+    int output_fd = GetTestFileDescriptor(filename);
+    auto output_stream = absl::make_unique<util::FileOutputStream>(output_fd);
+    auto status = WriteToStream(output_stream.get(), stream_contents);
+    EXPECT_TRUE(status.ok()) << status;
+    std::string file_contents = ReadFile(filename);
+    EXPECT_EQ(stream_size, file_contents.size());
+    EXPECT_EQ(stream_contents, file_contents);
+  }
+}
+
+TEST_F(FileOutputStreamTest, CustomBufferSizes) {
+  std::vector<int> buffer_sizes = {1, 10, 100, 1000, 10000, 100000, 1000000};
+  int stream_size = 1024 * 1024;
+  std::string stream_contents = subtle::Random::GetRandomBytes(stream_size);
+  for (auto buffer_size : buffer_sizes) {
+    std::string filename = absl::StrCat(buffer_size, "_buffer_size_test.bin");
+    int output_fd = GetTestFileDescriptor(filename);
+    auto output_stream =
+        absl::make_unique<util::FileOutputStream>(output_fd, buffer_size);
+    void* buffer;
+    auto next_result = output_stream->Next(&buffer);
+    EXPECT_TRUE(next_result.ok()) << next_result.status();
+    EXPECT_EQ(buffer_size, next_result.ValueOrDie());
+    output_stream->BackUp(buffer_size);
+    auto status = WriteToStream(output_stream.get(), stream_contents);
+    EXPECT_TRUE(status.ok()) << status;
+    std::string file_contents = ReadFile(filename);
+    EXPECT_EQ(stream_size, file_contents.size());
+    EXPECT_EQ(stream_contents, file_contents);
+  }
+}
+
+
+TEST_F(FileOutputStreamTest, BackupAndPosition) {
+  int stream_size = 1024 * 1024;
+  int buffer_size = 1234;
+  void* buffer;
+  std::string stream_contents = subtle::Random::GetRandomBytes(stream_size);
+  std::string filename = absl::StrCat(buffer_size, "_backup_test.bin");
+  int output_fd = GetTestFileDescriptor(filename);
+
+  // Prepare the stream and do the first call to Next().
+  auto output_stream =
+      absl::make_unique<util::FileOutputStream>(output_fd, buffer_size);
+  EXPECT_EQ(0, output_stream->Position());
+  auto next_result = output_stream->Next(&buffer);
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(buffer_size, next_result.ValueOrDie());
+  EXPECT_EQ(buffer_size, output_stream->Position());
+  std::memcpy(buffer, stream_contents.data(), buffer_size);
+
+  // BackUp several times, but in total fewer bytes than returned by Next().
+  std::vector<int> backup_sizes = {0, 1, 5, 0, 10, 100, -42, 400, 20, -100};
+  int total_backup_size = 0;
+  for (auto backup_size : backup_sizes) {
+    output_stream->BackUp(backup_size);
+    total_backup_size += std::max(0, backup_size);
+    EXPECT_EQ(buffer_size - total_backup_size, output_stream->Position());
+  }
+  EXPECT_LT(total_backup_size, next_result.ValueOrDie());
+
+  // Call Next(), it should succeed.
+  next_result = output_stream->Next(&buffer);
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+
+  // BackUp() some bytes, again fewer than returned by Next().
+  backup_sizes = {0, 72, -94, 37, 82};
+  total_backup_size = 0;
+  for (auto backup_size : backup_sizes) {
+    output_stream->BackUp(backup_size);
+    total_backup_size += std::max(0, backup_size);
+    EXPECT_EQ(buffer_size - total_backup_size, output_stream->Position());
+  }
+  EXPECT_LT(total_backup_size, next_result.ValueOrDie());
+
+  // Call Next(), it should succeed;
+  next_result = output_stream->Next(&buffer);
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+
+  // Call Next() again, it should return a full block.
+  auto prev_position = output_stream->Position();
+  next_result = output_stream->Next(&buffer);
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(buffer_size, next_result.ValueOrDie());
+  EXPECT_EQ(prev_position + buffer_size, output_stream->Position());
+  std::memcpy(buffer, stream_contents.data() + buffer_size, buffer_size);
+
+  // BackUp a few times, with total over the returned buffer_size.
+  backup_sizes = {0, 72, -100, buffer_size / 2, 200, -25, buffer_size / 2, 42};
+  total_backup_size = 0;
+  for (auto backup_size : backup_sizes) {
+    output_stream->BackUp(backup_size);
+    total_backup_size = std::min(buffer_size,
+                                 total_backup_size + std::max(0, backup_size));
+    EXPECT_EQ(prev_position + buffer_size - total_backup_size,
+              output_stream->Position());
+  }
+  EXPECT_EQ(total_backup_size, buffer_size);
+  EXPECT_EQ(prev_position, output_stream->Position());
+
+  // Call Next() again, it should return a full block.
+  next_result = output_stream->Next(&buffer);
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(buffer_size, next_result.ValueOrDie());
+  EXPECT_EQ(prev_position + buffer_size, output_stream->Position());
+  std::memcpy(buffer, stream_contents.data() + buffer_size, buffer_size);
+
+  // Write the remaining stream contents to stream.
+  auto status = WriteToStream(
+      output_stream.get(), stream_contents.substr(output_stream->Position()));
+  EXPECT_TRUE(status.ok()) << status;
+  std::string file_contents = ReadFile(filename);
+  EXPECT_EQ(stream_size, file_contents.size());
+  EXPECT_EQ(stream_contents, file_contents);
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/util/istream_input_stream.cc b/cc/util/istream_input_stream.cc
new file mode 100644
index 0000000..ea34233
--- /dev/null
+++ b/cc/util/istream_input_stream.cc
@@ -0,0 +1,94 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/util/istream_input_stream.h"
+
+#include <unistd.h>
+#include <algorithm>
+#include <cstring>
+#include <istream>
+
+#include "absl/memory/memory.h"
+#include "tink/input_stream.h"
+#include "tink/util/errors.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace util {
+
+IstreamInputStream::IstreamInputStream(std::unique_ptr<std::istream> input,
+                                       int buffer_size) :
+    buffer_size_(buffer_size > 0 ? buffer_size : 128 * 1024) {  // 128 KB
+  input_ = std::move(input);
+  count_in_buffer_ = 0;
+  count_backedup_ = 0;
+  position_ = 0;
+  buffer_ = absl::make_unique<uint8_t[]>(buffer_size_);
+  buffer_offset_ = 0;
+  status_ = Status::OK;
+}
+
+crypto::tink::util::StatusOr<int> IstreamInputStream::Next(const void** data) {
+  if (!status_.ok()) return status_;
+  if (count_backedup_ > 0) {  // Return the backed-up bytes.
+    buffer_offset_ = buffer_offset_ + (count_in_buffer_ - count_backedup_);
+    count_in_buffer_ = count_backedup_;
+    count_backedup_ = 0;
+    *data = buffer_.get() + buffer_offset_;
+    position_ = position_ + count_in_buffer_;
+    return count_in_buffer_;
+  }
+  // Read new bytes to buffer_.
+  input_->read(reinterpret_cast<char*>(buffer_.get()), buffer_size_);
+  int count_read = input_->gcount();
+  if (count_read == 0) {  // Could not read bytes, EOF or an I/O error.
+    if (input_->good()) return count_read;  // No bytes could be read.
+    // If !good(), distinguish EOF from other failures.
+    if (input_->eof()) {
+      status_ = Status(util::error::OUT_OF_RANGE, "EOF");
+    } else {
+      status_ =
+          ToStatusF(util::error::INTERNAL, "I/O error: %s", strerror(errno));
+    }
+    return status_;
+  }
+  buffer_offset_ = 0;
+  count_backedup_ = 0;
+  count_in_buffer_ = count_read;
+  position_ = position_ + count_in_buffer_;
+  *data = buffer_.get();
+  return count_in_buffer_;
+}
+
+void IstreamInputStream::BackUp(int count) {
+  if (!status_.ok() || count < 1 || count_backedup_ == count_in_buffer_) return;
+  int actual_count = std::min(count, count_in_buffer_ - count_backedup_);
+  count_backedup_ = count_backedup_ + actual_count;
+  position_ = position_ - actual_count;
+}
+
+IstreamInputStream::~IstreamInputStream() {
+}
+
+int64_t IstreamInputStream::Position() const {
+  return position_;
+}
+
+}  // namespace util
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/util/istream_input_stream.h b/cc/util/istream_input_stream.h
new file mode 100644
index 0000000..530192a
--- /dev/null
+++ b/cc/util/istream_input_stream.h
@@ -0,0 +1,65 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_UTIL_ISTREAM_INPUT_STREAM_H_
+#define TINK_UTIL_ISTREAM_INPUT_STREAM_H_
+
+#include <istream>
+#include <memory>
+
+#include "tink/input_stream.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace util {
+
+// An InputStream that reads from a std::istream.
+class IstreamInputStream : public crypto::tink::InputStream {
+ public:
+  // Constructs an InputStream that will read from the 'input' istream,
+  // using a buffer of the specified size, if any (if no legal 'buffer_size'
+  // is given, a reasonable default will be used).
+  explicit IstreamInputStream(std::unique_ptr<std::istream> input,
+                              int buffer_size = -1);
+
+  ~IstreamInputStream() override;
+
+  crypto::tink::util::StatusOr<int> Next(const void** data) override;
+
+  void BackUp(int count) override;
+
+  int64_t Position() const override;
+
+ private:
+  util::Status status_;
+  std::unique_ptr<std::istream> input_;
+  std::unique_ptr<uint8_t[]> buffer_;
+  const int buffer_size_;
+  int64_t position_;     // current position in the istream (from the beginning)
+
+  // Counters that describe the state of the data in buffer_.
+  int count_in_buffer_;  // # of bytes available in buffer_
+  int count_backedup_;   // # of bytes available in buffer_ that were backed up
+  int buffer_offset_;    // offset at which the returned bytes start in buffer_
+};
+
+}  // namespace util
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_UTIL_ISTREAM_INPUT_STREAM_H_
diff --git a/cc/util/istream_input_stream_test.cc b/cc/util/istream_input_stream_test.cc
new file mode 100644
index 0000000..42e442d
--- /dev/null
+++ b/cc/util/istream_input_stream_test.cc
@@ -0,0 +1,197 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/util/istream_input_stream.h"
+
+#include <unistd.h>
+#include <fstream>
+#include <iostream>
+#include <istream>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "absl/memory/memory.h"
+#include "absl/strings/string_view.h"
+#include "tink/subtle/random.h"
+#include "tink/util/test_util.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+// Creates a new test file with the specified 'filename', writes 'size' random
+// bytes to the file, and returns an istream for reading from the file.
+// A copy of the bytes written to the file is returned in 'file_contents'.
+std::unique_ptr<std::istream> GetTestIstream(
+    absl::string_view filename, int size, std::string* file_contents) {
+  std::string full_filename =
+      absl::StrCat(crypto::tink::test::TmpDir(), "/", filename);
+  (*file_contents) = subtle::Random::GetRandomBytes(size);
+  std::ofstream output (full_filename, std::ofstream::binary);
+  if (!output.write(file_contents->data(), size) || output.tellp() != size) {
+    std::clog << "Failed to write " << size << " bytes to file "
+              << full_filename << " error: " << errno << std::endl;
+
+    exit(1);
+  }
+  output.close();
+  auto test_istream = absl::make_unique<std::ifstream>(
+      full_filename, std::ofstream::binary);
+  return std::move(test_istream);
+}
+
+// Reads the specified 'input_stream' until no more bytes can be read,
+// and puts the read bytes into 'contents'.
+// Returns the status of the last input_stream->Next()-operation.
+util::Status ReadTillEnd(util::IstreamInputStream* input_stream,
+                         std::string* contents) {
+  contents->clear();
+  const void* buffer;
+  auto next_result = input_stream->Next(&buffer);
+  while (next_result.ok()) {
+    contents->append(static_cast<const char*>(buffer),
+                     next_result.ValueOrDie());
+    next_result = input_stream->Next(&buffer);
+  }
+  return next_result.status();
+}
+
+class IstreamInputStreamTest : public ::testing::Test {
+};
+
+TEST_F(IstreamInputStreamTest, testReadingStreams) {
+  std::vector<int> stream_sizes = {0, 10, 100, 1000, 10000, 100000, 1000000};
+  for (auto stream_size : stream_sizes) {
+    std::string file_contents;
+    std::string filename = absl::StrCat(stream_size, "_reading_test.bin");
+    auto input = GetTestIstream(filename, stream_size, &file_contents);
+    EXPECT_EQ(stream_size, file_contents.size());
+    auto input_stream = absl::make_unique<util::IstreamInputStream>(
+        std::move(input));
+    std::string stream_contents;
+    auto status = ReadTillEnd(input_stream.get(), &stream_contents);
+    EXPECT_EQ(util::error::OUT_OF_RANGE, status.error_code());
+    EXPECT_EQ("EOF", status.error_message());
+    EXPECT_EQ(file_contents, stream_contents);
+  }
+}
+
+TEST_F(IstreamInputStreamTest, testCustomBufferSizes) {
+  std::vector<int> buffer_sizes = {1, 10, 100, 1000, 10000};
+  int stream_size = 100000;
+  for (auto buffer_size : buffer_sizes) {
+    std::string file_contents;
+    std::string filename = absl::StrCat(buffer_size, "_buffer_size_test.bin");
+    auto input = GetTestIstream(filename, stream_size, &file_contents);
+    EXPECT_EQ(stream_size, file_contents.size());
+    auto input_stream = absl::make_unique<util::IstreamInputStream>(
+        std::move(input), buffer_size);
+    const void* buffer;
+    auto next_result = input_stream->Next(&buffer);
+    EXPECT_TRUE(next_result.ok()) << next_result.status();
+    EXPECT_EQ(buffer_size, next_result.ValueOrDie());
+    EXPECT_EQ(file_contents.substr(0, buffer_size),
+              std::string(static_cast<const char*>(buffer), buffer_size));
+  }
+}
+
+TEST_F(IstreamInputStreamTest, testBackupAndPosition) {
+  int stream_size = 100000;
+  int buffer_size = 1234;
+  const void* buffer;
+  std::string file_contents;
+  std::string filename = absl::StrCat(buffer_size, "_backup_test.bin");
+  auto input = GetTestIstream(filename, stream_size, &file_contents);
+  EXPECT_EQ(stream_size, file_contents.size());
+
+  // Prepare the stream and do the first call to Next().
+  auto input_stream = absl::make_unique<util::IstreamInputStream>(
+      std::move(input), buffer_size);
+  EXPECT_EQ(0, input_stream->Position());
+  auto next_result = input_stream->Next(&buffer);
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(buffer_size, next_result.ValueOrDie());
+  EXPECT_EQ(buffer_size, input_stream->Position());
+  EXPECT_EQ(file_contents.substr(0, buffer_size),
+            std::string(static_cast<const char*>(buffer), buffer_size));
+
+  // BackUp several times, but in total fewer bytes than returned by Next().
+  std::vector<int> backup_sizes = {0, 1, 5, 0, 10, 100, -42, 400, 20, -100};
+  int total_backup_size = 0;
+  for (auto backup_size : backup_sizes) {
+    input_stream->BackUp(backup_size);
+    total_backup_size += std::max(0, backup_size);
+    EXPECT_EQ(buffer_size - total_backup_size, input_stream->Position());
+  }
+  // Call Next(), it should return exactly the backed up bytes.
+  next_result = input_stream->Next(&buffer);
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(total_backup_size, next_result.ValueOrDie());
+  EXPECT_EQ(buffer_size, input_stream->Position());
+  EXPECT_EQ(
+      file_contents.substr(buffer_size - total_backup_size, total_backup_size),
+      std::string(static_cast<const char*>(buffer), total_backup_size));
+
+  // BackUp() some bytes, again fewer than returned by Next().
+  backup_sizes = {0, 72, -94, 37, 82};
+  total_backup_size = 0;
+  for (auto backup_size : backup_sizes) {
+    input_stream->BackUp(backup_size);
+    total_backup_size += std::max(0, backup_size);
+    EXPECT_EQ(buffer_size - total_backup_size, input_stream->Position());
+  }
+
+  // Call Next(), it should return exactly the backed up bytes.
+  next_result = input_stream->Next(&buffer);
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(total_backup_size, next_result.ValueOrDie());
+  EXPECT_EQ(buffer_size, input_stream->Position());
+  EXPECT_EQ(
+      file_contents.substr(buffer_size - total_backup_size, total_backup_size),
+      std::string(static_cast<const char*>(buffer), total_backup_size));
+
+  // Call Next() again, it should return the second block.
+  next_result = input_stream->Next(&buffer);
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(buffer_size, next_result.ValueOrDie());
+  EXPECT_EQ(2 * buffer_size, input_stream->Position());
+  EXPECT_EQ(
+      file_contents.substr(buffer_size, buffer_size),
+      std::string(static_cast<const char*>(buffer), buffer_size));
+
+  // BackUp a few times, with total over the returned buffer_size.
+  backup_sizes = {0, 72, -100, buffer_size/2, 200, -25, buffer_size, 42};
+  total_backup_size = 0;
+  for (auto backup_size : backup_sizes) {
+    input_stream->BackUp(backup_size);
+    total_backup_size = std::min(buffer_size,
+                                 total_backup_size + std::max(0, backup_size));
+    EXPECT_EQ(2 * buffer_size - total_backup_size, input_stream->Position());
+  }
+
+  // Call Next() again, it should return the second block.
+  next_result = input_stream->Next(&buffer);
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(buffer_size, next_result.ValueOrDie());
+  EXPECT_EQ(2 * buffer_size, input_stream->Position());
+  EXPECT_EQ(
+      file_contents.substr(buffer_size, buffer_size),
+      std::string(static_cast<const char*>(buffer), buffer_size));
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/util/ostream_output_stream.cc b/cc/util/ostream_output_stream.cc
new file mode 100644
index 0000000..65c1b00
--- /dev/null
+++ b/cc/util/ostream_output_stream.cc
@@ -0,0 +1,135 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/util/ostream_output_stream.h"
+
+#include <cerrno>
+#include <cstring>
+#include <memory>
+#include <ostream>
+
+#include "absl/memory/memory.h"
+#include "tink/output_stream.h"
+#include "tink/util/errors.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace util {
+
+OstreamOutputStream::OstreamOutputStream(std::unique_ptr<std::ostream> output,
+                                         int buffer_size) :
+    buffer_size_(buffer_size > 0 ? buffer_size : 128 * 1024) {  // 128 KB
+  output_ = std::move(output);
+  count_in_buffer_ = 0;
+  count_backedup_ = 0;
+  buffer_ = nullptr;
+  position_ = 0;
+  buffer_offset_ = 0;
+  status_ = Status::OK;
+}
+
+crypto::tink::util::StatusOr<int> OstreamOutputStream::Next(void** data) {
+  if (!status_.ok()) return status_;
+
+  if (buffer_ == nullptr) {  // possible only at the first call to Next()
+    buffer_ = absl::make_unique<uint8_t[]>(buffer_size_);
+    *data = buffer_.get();
+    count_in_buffer_ = buffer_size_;
+    position_ = buffer_size_;
+    return buffer_size_;
+  }
+
+  // If some space was backed up, return it first.
+  if (count_backedup_ > 0) {
+    position_ = position_ + count_backedup_;
+    buffer_offset_ = count_in_buffer_;
+    count_in_buffer_ = count_in_buffer_ + count_backedup_;
+    int backedup = count_backedup_;
+    count_backedup_ = 0;
+    *data = buffer_.get() + buffer_offset_;
+    return backedup;
+  }
+
+  // No space was backed up, so count_in_buffer_ == buffer_size_ holds here.
+  // Write the data from the buffer, and return available space in buffer_.
+  // The available space might not span the entire buffer_, as writing
+  // may succeed only for a prefix of buffer_ -- in this case the data still
+  // to be written is shifted in buffer_ and the remaining space is returned.
+  int write_result = output_->rdbuf()->sputn(
+      reinterpret_cast<char*>(buffer_.get()), buffer_size_);
+  if (write_result == 0) {  // No data written or an I/O error occurred.
+    if (output_->good()) return 0;
+    status_ = ToStatusF(util::error::INTERNAL,
+                        "I/O error upon write: %s", std::strerror(errno));
+    return status_;
+  }
+  // Some data was written, so we can return some portion of buffer_.
+  position_ = position_ + write_result;
+  count_in_buffer_ = buffer_size_;
+  count_backedup_ = 0;
+  buffer_offset_ = buffer_size_ - write_result;
+  *data = buffer_.get() + buffer_offset_;
+  if (write_result < buffer_size_) {
+    // Only part of the data was written, shift the remaining data in buffer_.
+    // Using memmove, as source and destination may overlap.
+    std::memmove(buffer_.get(), buffer_.get() + write_result, buffer_offset_);
+  }
+  return write_result;
+}
+
+void OstreamOutputStream::BackUp(int count) {
+  if (!status_.ok() || count < 1 || count_in_buffer_ == 0) return;
+  int curr_buffer_size = buffer_size_ - buffer_offset_;
+  int actual_count = std::min(count, curr_buffer_size - count_backedup_);
+  count_backedup_ += actual_count;
+  count_in_buffer_ -= actual_count;
+  position_ -= actual_count;
+}
+
+OstreamOutputStream::~OstreamOutputStream() {
+  Close().IgnoreError();
+}
+
+Status OstreamOutputStream::Close() {
+  if (!status_.ok()) return status_;
+  if (count_in_buffer_ > 0) {
+    // Try to write the remaining bytes.
+    output_->write(reinterpret_cast<char*>(buffer_.get()), count_in_buffer_);
+    if (!output_->good()) {  // An I/O error occurred.
+      status_ = ToStatusF(
+          util::error::INTERNAL, "I/O error upon write: %d", errno);
+      return status_;
+    }
+  }
+  output_->flush();
+  if (!output_->good()) {
+    status_ = ToStatusF(
+        util::error::INTERNAL, "I/O error upon flushing: %d", errno);
+    return status_;
+  }
+  status_ = Status(util::error::FAILED_PRECONDITION, "Stream closed");
+  return Status::OK;
+}
+
+int64_t OstreamOutputStream::Position() const {
+  return position_;
+}
+
+}  // namespace util
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/util/ostream_output_stream.h b/cc/util/ostream_output_stream.h
new file mode 100644
index 0000000..be5e2ed
--- /dev/null
+++ b/cc/util/ostream_output_stream.h
@@ -0,0 +1,71 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_UTIL_OSTREAM_OUTPUT_STREAM_H_
+#define TINK_UTIL_OSTREAM_OUTPUT_STREAM_H_
+
+#include <memory>
+#include <ostream>
+
+#include "tink/output_stream.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+namespace util {
+
+// An OutputStream that writes to an ostream.
+class OstreamOutputStream : public crypto::tink::OutputStream {
+ public:
+  // Constructs an OutputStream that will write to the ostream specified
+  // via 'output', using a buffer of the specified size, if any
+  // (if no legal 'buffer_size' is given, a reasonable default will be used).
+  explicit OstreamOutputStream(std::unique_ptr<std::ostream> output,
+                               int buffer_size = -1);
+
+  ~OstreamOutputStream() override;
+
+  crypto::tink::util::StatusOr<int> Next(void** data) override;
+
+  void BackUp(int count) override;
+
+  crypto::tink::util::Status Close() override;
+
+  int64_t Position() const override;
+
+ private:
+  util::Status status_;
+  std::unique_ptr<std::ostream> output_;
+  std::unique_ptr<uint8_t[]> buffer_;
+  const int buffer_size_;
+  int64_t position_;     // current position in the ostream (from the beginning)
+
+  // Counters that describe the state of the data in buffer_.
+  // count_in_buffer_ is always equal to (buffer_size_ - count_backedup_),
+  // except initially (before the first call to Next()).
+  // In other words, we have an invariant:
+  // (count_in_buffer_ == buffer_size_ - count_backedup_) || buffer_ == nullptr
+  int count_in_buffer_;  // # bytes in buffer_ that will be eventually written
+  int count_backedup_;   // # bytes in buffer_ that were backed up
+  int buffer_offset_;    // offset where the returned *data starts in buffer_
+};
+
+}  // namespace util
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_UTIL_OSTREAM_OUTPUT_STREAM_H_
diff --git a/cc/util/ostream_output_stream_test.cc b/cc/util/ostream_output_stream_test.cc
new file mode 100644
index 0000000..d48a79d
--- /dev/null
+++ b/cc/util/ostream_output_stream_test.cc
@@ -0,0 +1,218 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/util/ostream_output_stream.h"
+
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "absl/memory/memory.h"
+#include "absl/strings/string_view.h"
+#include "tink/subtle/random.h"
+#include "tink/util/test_util.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+// Creates a new test ostream which will write to the file 'filename'.
+std::unique_ptr<std::ostream> GetTestOstream(absl::string_view filename) {
+  std::string full_filename =
+      absl::StrCat(crypto::tink::test::TmpDir(), "/", filename);
+  auto test_ostream = absl::make_unique<std::ofstream>(
+      full_filename, std::ofstream::binary);
+  return std::move(test_ostream);
+}
+
+// Writes 'contents' to the specified 'output_stream', and closes the stream.
+// Returns the status of output_stream->Close()-operation, or a non-OK status
+// of a prior output_stream->Next()-operation, if any.
+util::Status WriteToStream(util::OstreamOutputStream* output_stream,
+                           absl::string_view contents) {
+  void* buffer;
+  int pos = 0;
+  int remaining = contents.length();
+  int available_space;
+  int available_bytes;
+  while (remaining > 0) {
+    auto next_result = output_stream->Next(&buffer);
+    if (!next_result.ok()) return next_result.status();
+    available_space = next_result.ValueOrDie();
+    available_bytes = std::min(available_space, remaining);
+    memcpy(buffer, contents.data() + pos, available_bytes);
+    remaining -= available_bytes;
+    pos += available_bytes;
+  }
+  if (available_space > available_bytes) {
+    output_stream->BackUp(available_space - available_bytes);
+  }
+  return output_stream->Close();
+}
+
+// Reads the test file specified by 'filename', and returns its contents.
+std::string ReadFile(std::string filename) {
+  std::string full_filename =
+      absl::StrCat(crypto::tink::test::TmpDir(), "/", filename);
+  std::string contents;
+  int buffer_size = 128 * 1024;
+  auto buffer = absl::make_unique<uint8_t[]>(buffer_size);
+  std::ifstream input(full_filename, std::ifstream::binary);
+  while (input.good()) {
+    input.read(reinterpret_cast<char*>(buffer.get()), buffer_size);
+    int read_result = input.gcount();
+    std::clog << "Read " << read_result << " bytes" << std::endl;
+    contents.append(reinterpret_cast<const char*>(buffer.get()), read_result);
+  }
+  if (!input.eof()) {
+    std::clog << "Error reading ostream " << full_filename
+              << " error: " << errno << std::endl;
+    exit(1);
+  }
+  input.close();
+  std::clog << "Read in total " << contents.length() << " bytes" << std::endl;
+  return contents;
+}
+
+class OstreamOutputStreamTest : public ::testing::Test {
+};
+
+TEST_F(OstreamOutputStreamTest, WritingStreams) {
+  std::vector<int> stream_sizes = {0, 10, 100, 1000, 10000, 100000, 1000000};
+  for (auto stream_size : stream_sizes) {
+    std::string stream_contents = subtle::Random::GetRandomBytes(stream_size);
+    std::string filename = absl::StrCat(stream_size, "_writing_test.bin");
+    auto output = GetTestOstream(filename);
+    auto output_stream = absl::make_unique<util::OstreamOutputStream>(
+        std::move(output));
+    auto status = WriteToStream(output_stream.get(), stream_contents);
+    EXPECT_TRUE(status.ok()) << status;
+    std::string ostream_contents = ReadFile(filename);
+    EXPECT_EQ(stream_size, ostream_contents.size());
+    EXPECT_EQ(stream_contents, ostream_contents);
+  }
+}
+
+TEST_F(OstreamOutputStreamTest, CustomBufferSizes) {
+  std::vector<int> buffer_sizes = {1, 10, 100, 1000, 10000, 100000, 1000000};
+  int stream_size = 1024 * 1024;
+  std::string stream_contents = subtle::Random::GetRandomBytes(stream_size);
+  for (auto buffer_size : buffer_sizes) {
+    std::string filename = absl::StrCat(buffer_size, "_buffer_size_test.bin");
+    auto output = GetTestOstream(filename);
+    auto output_stream = absl::make_unique<util::OstreamOutputStream>(
+        std::move(output), buffer_size);
+    void* buffer;
+    auto next_result = output_stream->Next(&buffer);
+    EXPECT_TRUE(next_result.ok()) << next_result.status();
+    EXPECT_EQ(buffer_size, next_result.ValueOrDie());
+    output_stream->BackUp(buffer_size);
+    auto status = WriteToStream(output_stream.get(), stream_contents);
+    EXPECT_TRUE(status.ok()) << status;
+    std::string ostream_contents = ReadFile(filename);
+    EXPECT_EQ(stream_size, ostream_contents.size());
+    EXPECT_EQ(stream_contents, ostream_contents);
+  }
+}
+
+TEST_F(OstreamOutputStreamTest, BackupAndPosition) {
+  int stream_size = 1024 * 1024;
+  int buffer_size = 1234;
+  void* buffer;
+  std::string stream_contents = subtle::Random::GetRandomBytes(stream_size);
+  std::string filename = absl::StrCat(buffer_size, "_backup_test.bin");
+  auto output = GetTestOstream(filename);
+
+  // Prepare the stream and do the first call to Next().
+  auto output_stream = absl::make_unique<util::OstreamOutputStream>(
+      std::move(output), buffer_size);
+  EXPECT_EQ(0, output_stream->Position());
+  auto next_result = output_stream->Next(&buffer);
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(buffer_size, next_result.ValueOrDie());
+  EXPECT_EQ(buffer_size, output_stream->Position());
+  std::memcpy(buffer, stream_contents.data(), buffer_size);
+
+  // BackUp several times, but in total fewer bytes than returned by Next().
+  std::vector<int> backup_sizes = {0, 1, 5, 0, 10, 100, -42, 400, 20, -100};
+  int total_backup_size = 0;
+  for (auto backup_size : backup_sizes) {
+    output_stream->BackUp(backup_size);
+    total_backup_size += std::max(0, backup_size);
+    EXPECT_EQ(buffer_size - total_backup_size, output_stream->Position());
+  }
+  EXPECT_LT(total_backup_size, next_result.ValueOrDie());
+
+  // Call Next(), it should succeed.
+  next_result = output_stream->Next(&buffer);
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+
+  // BackUp() some bytes, again fewer than returned by Next().
+  backup_sizes = {0, 72, -94, 37, 82};
+  total_backup_size = 0;
+  for (auto backup_size : backup_sizes) {
+    output_stream->BackUp(backup_size);
+    total_backup_size += std::max(0, backup_size);
+    EXPECT_EQ(buffer_size - total_backup_size, output_stream->Position());
+  }
+  EXPECT_LT(total_backup_size, next_result.ValueOrDie());
+
+  // Call Next(), it should succeed;
+  next_result = output_stream->Next(&buffer);
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+
+  // Call Next() again, it should return a full block.
+  auto prev_position = output_stream->Position();
+  next_result = output_stream->Next(&buffer);
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(buffer_size, next_result.ValueOrDie());
+  EXPECT_EQ(prev_position + buffer_size, output_stream->Position());
+  std::memcpy(buffer, stream_contents.data() + buffer_size, buffer_size);
+
+  // BackUp a few times, with total over the returned buffer_size.
+  backup_sizes = {0, 72, -100, buffer_size / 2, 200, -25, buffer_size / 2, 42};
+  total_backup_size = 0;
+  for (auto backup_size : backup_sizes) {
+    output_stream->BackUp(backup_size);
+    total_backup_size = std::min(buffer_size,
+                                 total_backup_size + std::max(0, backup_size));
+    EXPECT_EQ(prev_position + buffer_size - total_backup_size,
+              output_stream->Position());
+  }
+  EXPECT_EQ(total_backup_size, buffer_size);
+  EXPECT_EQ(prev_position, output_stream->Position());
+
+  // Call Next() again, it should return a full block.
+  next_result = output_stream->Next(&buffer);
+  EXPECT_TRUE(next_result.ok()) << next_result.status();
+  EXPECT_EQ(buffer_size, next_result.ValueOrDie());
+  EXPECT_EQ(prev_position + buffer_size, output_stream->Position());
+  std::memcpy(buffer, stream_contents.data() + buffer_size, buffer_size);
+
+  // Write the remaining stream contents to stream.
+  auto status = WriteToStream(
+      output_stream.get(), stream_contents.substr(output_stream->Position()));
+  EXPECT_TRUE(status.ok()) << status;
+  std::string ostream_contents = ReadFile(filename);
+  EXPECT_EQ(stream_size, ostream_contents.size());
+  EXPECT_EQ(stream_contents, ostream_contents);
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/util/statusor.h b/cc/util/statusor.h
index 76d5808..5868ba2 100644
--- a/cc/util/statusor.h
+++ b/cc/util/statusor.h
@@ -68,7 +68,7 @@
   }
 
   // Returns value or crashes if ok() is false.
-  inline const T& ValueOrDie() const {
+  inline const T& ValueOrDie() const& {
     if (!ok()) {
       std::cerr << "Attempting to fetch value of non-OK StatusOr\n";
       std::cerr << status() << std::endl;
@@ -76,7 +76,7 @@
     }
     return value_;
   }
-  inline T& ValueOrDie() {
+  inline T& ValueOrDie() & {
     if (!ok()) {
       std::cerr << "Attempting to fetch value of non-OK StatusOr\n";
       std::cerr << status() << std::endl;
@@ -84,6 +84,22 @@
     }
     return value_;
   }
+  inline const T&& ValueOrDie() const&& {
+    if (!ok()) {
+      std::cerr << "Attempting to fetch value of non-OK StatusOr\n";
+      std::cerr << status() << std::endl;
+      exit(1);
+    }
+    return std::move(value_);
+  }
+  inline T&& ValueOrDie() && {
+    if (!ok()) {
+      std::cerr << "Attempting to fetch value of non-OK StatusOr\n";
+      std::cerr << status() << std::endl;
+      exit(1);
+    }
+    return std::move(value_);
+  }
 
   template <typename U>
   friend class StatusOr;
diff --git a/cc/util/test_util.cc b/cc/util/test_util.cc
index 98cbcc7..2babed3 100644
--- a/cc/util/test_util.cc
+++ b/cc/util/test_util.cc
@@ -18,11 +18,12 @@
 
 #include <stdarg.h>
 #include <stdlib.h>
+#include <cstdlib>
 
 #include "absl/memory/memory.h"
-#include "tink/keyset_handle.h"
-#include "tink/cleartext_keyset_handle.h"
 #include "tink/aead/aes_gcm_key_manager.h"
+#include "tink/cleartext_keyset_handle.h"
+#include "tink/keyset_handle.h"
 #include "tink/subtle/common_enums.h"
 #include "tink/subtle/subtle_util_boringssl.h"
 #include "tink/util/enums.h"
@@ -33,18 +34,19 @@
 #include "proto/common.pb.h"
 #include "proto/ecdsa.pb.h"
 #include "proto/ecies_aead_hkdf.pb.h"
+#include "proto/ed25519.pb.h"
 #include "proto/tink.pb.h"
 
+using crypto::tink::util::Enums;
+using crypto::tink::util::Status;
+using crypto::tink::util::error::Code;
 using google::crypto::tink::AesGcmKeyFormat;
 using google::crypto::tink::EcdsaPrivateKey;
 using google::crypto::tink::EcdsaSignatureEncoding;
 using google::crypto::tink::EciesAeadHkdfPrivateKey;
+using google::crypto::tink::Ed25519PrivateKey;
 using google::crypto::tink::Keyset;
 using google::crypto::tink::OutputPrefixType;
-using crypto::tink::util::Enums;
-using crypto::tink::util::Status;
-using crypto::tink::util::error::Code;
-
 
 namespace crypto {
 namespace tink {
@@ -86,21 +88,47 @@
   return res;
 }
 
-void AddKey(
-    const std::string& key_type,
+#if defined(PLATFORM_GOOGLE)
+std::string TmpDir() { return FLAGS_test_tmpdir; }
+#else
+std::string TmpDir() {
+  // 'bazel test' sets TEST_TMPDIR
+  const char* env = getenv("TEST_TMPDIR");
+  if (env && env[0] != '\0') {
+    return env;
+  }
+  env = getenv("TMPDIR");
+  if (env && env[0] != '\0') {
+    return env;
+  }
+  return "/tmp";
+}
+#endif
+
+void AddKeyData(
+    const google::crypto::tink::KeyData& key_data,
     uint32_t key_id,
-    const portable_proto::MessageLite& new_key,
     google::crypto::tink::OutputPrefixType output_prefix,
     google::crypto::tink::KeyStatusType key_status,
-    google::crypto::tink::KeyData::KeyMaterialType material_type,
     google::crypto::tink::Keyset* keyset) {
   Keyset::Key* key = keyset->add_key();
   key->set_output_prefix_type(output_prefix);
   key->set_key_id(key_id);
   key->set_status(key_status);
-  key->mutable_key_data()->set_type_url(key_type);
-  key->mutable_key_data()->set_key_material_type(material_type);
-  key->mutable_key_data()->set_value(new_key.SerializeAsString());
+  *key->mutable_key_data() = key_data;
+}
+
+void AddKey(const std::string& key_type, uint32_t key_id,
+            const portable_proto::MessageLite& new_key,
+            google::crypto::tink::OutputPrefixType output_prefix,
+            google::crypto::tink::KeyStatusType key_status,
+            google::crypto::tink::KeyData::KeyMaterialType material_type,
+            google::crypto::tink::Keyset* keyset) {
+  google::crypto::tink::KeyData key_data;
+  key_data.set_type_url(key_type);
+  key_data.set_key_material_type(material_type);
+  key_data.set_value(new_key.SerializeAsString());
+  AddKeyData(key_data, key_id, output_prefix, key_status, keyset);
 }
 
 void AddTinkKey(
@@ -205,6 +233,19 @@
   return ecdsa_key;
 }
 
+Ed25519PrivateKey GetEd25519TestPrivateKey() {
+  auto test_key = subtle::SubtleUtilBoringSSL::GetNewEd25519Key();
+  Ed25519PrivateKey ed25519_key;
+  ed25519_key.set_version(0);
+  ed25519_key.set_key_value(test_key->private_key);
+
+  auto public_key = ed25519_key.mutable_public_key();
+  public_key->set_version(0);
+  public_key->set_key_value(test_key->public_key);
+
+  return ed25519_key;
+}
+
 }  // namespace test
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/util/test_util.h b/cc/util/test_util.h
index 3fae82f..165f89d 100644
--- a/cc/util/test_util.h
+++ b/cc/util/test_util.h
@@ -19,21 +19,28 @@
 
 #include <string>
 
+#include "absl/strings/match.h"
+#include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 #include "tink/aead.h"
+#include "tink/deterministic_aead.h"
 #include "tink/hybrid_decrypt.h"
 #include "tink/hybrid_encrypt.h"
+#include "tink/input_stream.h"
 #include "tink/keyset_handle.h"
 #include "tink/mac.h"
-#include "tink/subtle/common_enums.h"
+#include "tink/output_stream.h"
 #include "tink/public_key_sign.h"
 #include "tink/public_key_verify.h"
+#include "tink/streaming_aead.h"
+#include "tink/subtle/common_enums.h"
 #include "tink/util/protobuf_helper.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 #include "proto/common.pb.h"
 #include "proto/ecdsa.pb.h"
 #include "proto/ecies_aead_hkdf.pb.h"
+#include "proto/ed25519.pb.h"
 #include "proto/tink.pb.h"
 
 namespace crypto {
@@ -56,6 +63,19 @@
 // Converts a std::string of bytes into a hexadecimal std::string.
 std::string HexEncode(absl::string_view bytes);
 
+// Returns a temporary directory suitable for temporary testing files.
+std::string TmpDir();
+
+// Adds the given 'keyData' with specified status, key_id, and
+// output_prefix_type to the keyset.
+void AddKeyData(
+    const google::crypto::tink::KeyData& key_data,
+    uint32_t key_id,
+    google::crypto::tink::OutputPrefixType output_prefix,
+    google::crypto::tink::KeyStatusType key_status,
+    google::crypto::tink::Keyset* keyset);
+
+
 // Adds the given 'key' with specified parameters and output_prefix_type=TINK
 // to the specified 'keyset'.
 void AddTinkKey(
@@ -116,56 +136,141 @@
     google::crypto::tink::HashType hash_type,
     google::crypto::tink::EcdsaSignatureEncoding encoding);
 
+// Generates a fresh test key for ED25519.
+google::crypto::tink::Ed25519PrivateKey GetEd25519TestPrivateKey();
+
 // A dummy implementation of Aead-interface.
 // An instance of DummyAead can be identified by a name specified
 // as a parameter of the constructor.
 class DummyAead : public Aead {
  public:
-  DummyAead(absl::string_view aead_name) : aead_name_(aead_name) {}
+  explicit DummyAead(absl::string_view aead_name) : aead_name_(aead_name) {}
 
   // Computes a dummy ciphertext, which is concatenation of provided 'plaintext'
   // with the name of this DummyAead.
   crypto::tink::util::StatusOr<std::string> Encrypt(
       absl::string_view plaintext,
       absl::string_view associated_data) const override {
-    return std::string(plaintext.data(), plaintext.size()).append(aead_name_);
+    return absl::StrCat(aead_name_.size(), ":", associated_data.size(), ":",
+                        aead_name_, associated_data, plaintext);
   }
 
   crypto::tink::util::StatusOr<std::string> Decrypt(
       absl::string_view ciphertext,
       absl::string_view associated_data) const override {
-    std::string c(ciphertext.data(), ciphertext.size());
-    size_t pos = c.rfind(aead_name_);
-    if (pos != std::string::npos &&
-        ciphertext.length() == (unsigned)(aead_name_.length() + pos)) {
-      return c.substr(0, pos);
+    std::string prefix = absl::StrCat(aead_name_.size(), ":", associated_data.size(),
+                                 ":", aead_name_, associated_data);
+    if (!StartsWith(ciphertext, prefix)) {
+      return crypto::tink::util::Status(
+          crypto::tink::util::error::INVALID_ARGUMENT,
+          "Dummy operation failed.");
     }
-    return crypto::tink::util::Status(
-        crypto::tink::util::error::INVALID_ARGUMENT, "Wrong ciphertext.");
+    ciphertext.remove_prefix(prefix.size());
+    return std::string(ciphertext);
   }
 
  private:
   std::string aead_name_;
 };
 
+// A dummy implementation of DeterministicAead-interface.
+// An instance of DummyDeterministicAead can be identified by a name specified
+// as a parameter of the constructor.
+// The implementation is the same as DummyAead.
+class DummyDeterministicAead : public DeterministicAead {
+ public:
+  explicit DummyDeterministicAead(absl::string_view daead_name)
+      : aead_(daead_name) {}
+
+  crypto::tink::util::StatusOr<std::string> EncryptDeterministically(
+      absl::string_view plaintext,
+      absl::string_view associated_data) const override {
+    return aead_.Encrypt(plaintext, associated_data);
+  }
+
+  crypto::tink::util::StatusOr<std::string> DecryptDeterministically(
+      absl::string_view ciphertext,
+      absl::string_view associated_data) const override {
+    return aead_.Decrypt(ciphertext, associated_data);
+  }
+
+ private:
+  DummyAead aead_;
+};
+
+// A dummy implementation of StreamingAead-interface.
+// An instance of DummyStreamingAead can be identified by a name specified
+// as a parameter of the constructor.
+class DummyStreamingAead : public StreamingAead {
+ public:
+  explicit DummyStreamingAead(absl::string_view streaming_aead_name)
+      : streaming_aead_name_(streaming_aead_name) {}
+
+  // Writes to 'ciphertext_destination' the name of this instance
+  // followed by 'associated_data', and returns 'ciphertext_destination'
+  // as the encrypting stream.
+  crypto::tink::util::StatusOr<std::unique_ptr<crypto::tink::OutputStream>>
+  NewEncryptingStream(
+      std::unique_ptr<crypto::tink::OutputStream> ciphertext_destination,
+      absl::string_view associated_data) override {
+    auto header = absl::StrCat(streaming_aead_name_, associated_data);
+    void* buffer;
+    auto next_result = ciphertext_destination->Next(&buffer);
+    if (!next_result.status().ok()) return next_result.status();
+    if (next_result.ValueOrDie() < header.size()) {
+      return crypto::tink::util::Status(
+          crypto::tink::util::error::INTERNAL, "Buffer too small");
+    }
+    memcpy(buffer, header.data(), header.size());
+    ciphertext_destination->BackUp(next_result.ValueOrDie() - header.size());
+    return std::move(ciphertext_destination);
+  }
+
+  // Reads a prefix from 'ciphertext_source' and verifies that it starts
+  // with the name of this instance, followed by 'associated_data'.
+  // Returns 'ciphertext_source' as the decrypting stream.
+  crypto::tink::util::StatusOr<std::unique_ptr<crypto::tink::InputStream>>
+  NewDecryptingStream(
+      std::unique_ptr<crypto::tink::InputStream> ciphertext_source,
+      absl::string_view associated_data) override {
+    auto header = absl::StrCat(streaming_aead_name_, associated_data);
+    const void* buffer;
+    auto next_result = ciphertext_source->Next(&buffer);
+    if (!next_result.status().ok()) return next_result.status();
+    if (next_result.ValueOrDie() < header.size()) {
+      return crypto::tink::util::Status(
+          crypto::tink::util::error::INTERNAL, "Buffer too small");
+    }
+    if (!memcmp(buffer, header.data(), header.size())) {
+      return crypto::tink::util::Status(
+          crypto::tink::util::error::INVALID_ARGUMENT, "Corrupted header");
+    }
+    ciphertext_source->BackUp(next_result.ValueOrDie() - header.size());
+    return std::move(ciphertext_source);
+  }
+
+ private:
+  std::string streaming_aead_name_;
+};
+
 // A dummy implementation of HybridEncrypt-interface.
 // An instance of DummyHybridEncrypt can be identified by a name specified
 // as a parameter of the constructor.
 class DummyHybridEncrypt : public HybridEncrypt {
  public:
-  DummyHybridEncrypt(absl::string_view hybrid_name)
-      : hybrid_name_(hybrid_name) {}
+  explicit DummyHybridEncrypt(absl::string_view hybrid_name)
+      : dummy_aead_(absl::StrCat("DummyHybrid:", hybrid_name)) {}
 
   // Computes a dummy ciphertext, which is concatenation of provided 'plaintext'
   // with the name of this DummyHybridEncrypt.
   crypto::tink::util::StatusOr<std::string> Encrypt(
       absl::string_view plaintext,
       absl::string_view context_info) const override {
-    return std::string(plaintext.data(), plaintext.size()).append(hybrid_name_);
+    return dummy_aead_.Encrypt(plaintext, context_info);
   }
 
  private:
-  std::string hybrid_name_;
+  DummyAead dummy_aead_;
 };
 
 // A dummy implementation of HybridDecrypt-interface.
@@ -173,26 +278,19 @@
 // as a parameter of the constructor.
 class DummyHybridDecrypt : public HybridDecrypt {
  public:
-  DummyHybridDecrypt(absl::string_view hybrid_name)
-      : hybrid_name_(hybrid_name) {}
+  explicit DummyHybridDecrypt(absl::string_view hybrid_name)
+      : dummy_aead_(absl::StrCat("DummyHybrid:", hybrid_name)) {}
 
   // Decrypts a dummy ciphertext, which should be a concatenation
   // of a plaintext with the name of this DummyHybridDecrypt.
   crypto::tink::util::StatusOr<std::string> Decrypt(
       absl::string_view ciphertext,
       absl::string_view context_info) const override {
-    std::string c(ciphertext.data(), ciphertext.size());
-    size_t pos = c.rfind(hybrid_name_);
-    if (pos != std::string::npos &&
-        ciphertext.length() == (unsigned)(hybrid_name_.length() + pos)) {
-      return c.substr(0, pos);
-    }
-    return crypto::tink::util::Status(
-        crypto::tink::util::error::INVALID_ARGUMENT, "Wrong ciphertext.");
+    return dummy_aead_.Decrypt(ciphertext, context_info);
   }
 
  private:
-  std::string hybrid_name_;
+  DummyAead dummy_aead_;
 };
 
 // A dummy implementation of PublicKeySign-interface.
@@ -200,18 +298,18 @@
 // as a parameter of the constructor.
 class DummyPublicKeySign : public PublicKeySign {
  public:
-  DummyPublicKeySign(absl::string_view signature_name)
-      : signature_name_(signature_name) {}
+  explicit DummyPublicKeySign(absl::string_view signature_name)
+      : dummy_aead_(absl::StrCat("DummySign:", signature_name)) {}
 
   // Computes a dummy signature, which is a concatenation of 'data'
   // with the name of this DummyPublicKeySign.
   crypto::tink::util::StatusOr<std::string> Sign(
       absl::string_view data) const override {
-    return std::string(data.data(), data.size()).append(signature_name_);
+    return dummy_aead_.Encrypt("", data);
   }
 
  private:
-  std::string signature_name_;
+  DummyAead dummy_aead_;
 };
 
 // A dummy implementation of PublicKeyVerify-interface.
@@ -219,25 +317,18 @@
 // as a parameter of the constructor.
 class DummyPublicKeyVerify : public PublicKeyVerify {
  public:
-  DummyPublicKeyVerify(absl::string_view signature_name)
-      : signature_name_(signature_name) {}
+  explicit DummyPublicKeyVerify(absl::string_view signature_name)
+      : dummy_aead_(absl::StrCat("DummySign:", signature_name)) {}
 
   // Verifies a dummy signature, should be a concatenation of the name
   // of this DummyPublicKeyVerify with the provided 'data'.
   crypto::tink::util::Status Verify(
       absl::string_view signature, absl::string_view data) const override {
-    size_t pos = signature.rfind(signature_name_);
-    if (pos != std::string::npos &&
-        signature.length() == (unsigned)(signature_name_.length() + pos)) {
-      return crypto::tink::util::Status::OK;
-    }
-    return crypto::tink::util::Status(
-        crypto::tink::util::error::INVALID_ARGUMENT,
-        "Invalid signature.");
+    return dummy_aead_.Decrypt(signature, data).status();
   }
 
  private:
-  std::string signature_name_;
+  DummyAead dummy_aead_;
 };
 
 // A dummy implementation of Mac-interface.
@@ -245,27 +336,23 @@
 // as a parameter of the constructor.
 class DummyMac : public Mac {
  public:
-  DummyMac(const std::string mac_name) : mac_name_(mac_name) {}
+  explicit DummyMac(const std::string& mac_name)
+      : dummy_aead_(absl::StrCat("DummyMac:", mac_name)) {}
 
   // Computes a dummy MAC, which is concatenation of provided 'data'
   // with the name of this DummyMac.
   crypto::tink::util::StatusOr<std::string> ComputeMac(
       absl::string_view data) const override {
-    return std::string(data.data(), data.size()).append(mac_name_);
+    return dummy_aead_.Encrypt("", data);
   }
 
   crypto::tink::util::Status VerifyMac(
       absl::string_view mac,
       absl::string_view data) const override {
-    if (mac == std::string(data.data(), data.size()).append(mac_name_)) {
-      return crypto::tink::util::Status::OK;
-    } else {
-      return crypto::tink::util::Status(
-          crypto::tink::util::error::INVALID_ARGUMENT, "Wrong MAC.");
-    }
+    return dummy_aead_.Decrypt(mac, data).status();
   }
  private:
-  std::string mac_name_;
+  DummyAead dummy_aead_;
 };
 
 
diff --git a/cc/util/validation.cc b/cc/util/validation.cc
index e7b8cea..96a9bef 100644
--- a/cc/util/validation.cc
+++ b/cc/util/validation.cc
@@ -26,6 +26,15 @@
 
 // TODO(przydatek): add more validation checks
 
+util::Status ValidateAesKeySize(uint32_t key_size) {
+  if (key_size != 16 && key_size != 32) {
+    return ToStatusF(util::error::INVALID_ARGUMENT,
+                     "AES key has %d bytes; supported sizes: 16 or 32 bytes.",
+                     key_size);
+  }
+  return util::Status::OK;
+}
+
 util::Status ValidateKeyset(const google::crypto::tink::Keyset& keyset) {
   if (keyset.key_size() < 1) {
     return ToStatusF(util::error::INVALID_ARGUMENT,
diff --git a/cc/util/validation.h b/cc/util/validation.h
index 4ae7e24..a6e3605 100644
--- a/cc/util/validation.h
+++ b/cc/util/validation.h
@@ -25,6 +25,8 @@
 
 // Various validation helpers.
 
+crypto::tink::util::Status ValidateAesKeySize(uint32_t key_size);
+
 crypto::tink::util::Status ValidateKeyset(
     const google::crypto::tink::Keyset& keyset);
 
diff --git a/cc/version.h.templ b/cc/version.h.templ
new file mode 100644
index 0000000..eb33b82
--- /dev/null
+++ b/cc/version.h.templ
@@ -0,0 +1,31 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_VERSION_H_
+#define TINK_VERSION_H_
+
+namespace crypto {
+namespace tink {
+
+class Version {
+ public:
+  static constexpr char kTinkVersion[] = "TINK_VERSION_LABEL";
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_VERSION_H_
diff --git a/cc/version_script.lds b/cc/version_script.lds
index d1b56f5..6fbcc0e 100644
--- a/cc/version_script.lds
+++ b/cc/version_script.lds
@@ -1,6 +1,7 @@
-VERS_1.2 {
+VERS_1.2.2 {
   global:
     *tink*;
+    *absl*;
   local:
     *;
 };
diff --git a/docs/CPP-HOWTO.md b/docs/CPP-HOWTO.md
index 2857fc1..8680b53 100644
--- a/docs/CPP-HOWTO.md
+++ b/docs/CPP-HOWTO.md
@@ -95,10 +95,10 @@
     ```sh
     cd tink
     TARGET_DIR="/usr/local"
-    bazel build -c opt cc:libtink.so
-    bazel build cc:tink_headers cc:tink_deps_headers
     mkdir -p $TARGET_DIR/lib $TARGET_DIR/include
+    bazel build -c opt cc:libtink.so
     sudo cp bazel-bin/cc/libtink.so $TARGET_DIR/lib/
+    bazel build cc:tink_headers cc:tink_deps_headers
     sudo tar xfv bazel-genfiles/cc/tink_headers.tar -C $TARGET_DIR/include/
     sudo tar xfv bazel-genfiles/cc/tink_deps_headers.tar -C $TARGET_DIR/include/
     ```
@@ -278,13 +278,13 @@
 | Primitive          | Implementations                               |
 | ------------------ | --------------------------------------------- |
 | AEAD               | AES-GCM, AES-CTR-HMAC, AES-EAX                |
+| Deterministic AEAD | AES-SIV                                       |
 | MAC                | HMAC-SHA2                                     |
 | Digital Signatures | ECDSA over NIST curves, (Ed25519)             |
 | Hybrid Encryption  | ECIES with AEAD and HKDF                      |
 
-Tink user accesses implementations of a primitive via a factory that corresponds
-to the primitive: AEAD via `AeadFactory`, MAC via `MacFactory`, etc. where each
-factory offers corresponding `getPrimitive(...)` methods.
+The user obtains a primitive by calling the function `getPrimitive<>` of the
+`KeysetHandle`.
 
 ### Symmetric Key Encryption
 
@@ -296,20 +296,19 @@
 ```cpp
     #include "tink/aead.h"
     #include "tink/keyset_handle.h"
-    #include "tink/aead/aead_factory.h"
 
 
     // 1. Get a handle to the key material.
     KeysetHandle keyset_handle = ...;
 
     // 2. Get the primitive.
-    auto aead_result= AeadFactory.GetPrimitive(keyset_handle);
+    auto aead_result= keyset_handle.GetPrimitive<Aead>();
     if (!aead_result.ok()) return aead_result.status();
     auto aead = std::move(aead_result.ValueOrDie());
 
     // 3. Use the primitive.
     auto ciphertext_result = aead.Encrypt(plaintext, aad);
-    if (!ciphertext_result.ok()) return ciphertext.status();
+    if (!ciphertext_result.ok()) return ciphertext_result.status();
     auto ciphertext = std::move(ciphertext_result.ValueOrDie());
 ```
 
@@ -321,19 +320,18 @@
 ```cpp
     #include "tink/hybrid_decrypt.h"
     #include "tink/keyset_handle.h"
-    #include "tink/hybrid/hybrid_decrypt_factory.h"
 
 
     // 1. Get a handle to the key material.
     KeysetHandle keyset_handle = ...;
 
     // 2. Get the primitive.
-    auto hybrid_decrypt_result= HybridDecryptFactory.GetPrimitive(keyset_handle);
+    auto hybrid_decrypt_result = keyset_handle.GetPrimitive<HybridDecrypt>();
     if (!hybrid_decrypt_result.ok()) return hybrid_decrypt_result.status();
     auto hybrid_decrypt = std::move(hybrid_decrypt_result.ValueOrDie());
 
     // 3. Use the primitive.
     auto plaintext_result = hybrid_decrypt.Decrypt(ciphertext, context_info);
-    if (!plaintext_result.ok()) return plaintext.status();
+    if (!plaintext_result.ok()) return plaintext_result.status();
     auto plaintext = std::move(plaintext_result.ValueOrDie());
 ```
diff --git a/docs/JAVA-HACKING.md b/docs/JAVA-HACKING.md
index 297b1d2..1dd6aad 100644
--- a/docs/JAVA-HACKING.md
+++ b/docs/JAVA-HACKING.md
@@ -3,11 +3,10 @@
 ## Building Tink
 
 *   Install [Bazel](https://docs.bazel.build/versions/master/install.html).
-    Version 0.9.0 or newer are known to work correctly with Tink.
 
 *   To build Java, install Android SDK 23 or newer and set the ANDROID_HOME
     environment variable to the path of your Android SDK. On macOS, the SDK is
-    usually installed at /Users/username/Library/Android/sdk/. You also need
+    usually installed at `/Users/username/Library/Android/sdk/`. You also need
     Android SDK Build Tools 24.0.3 or newer.
 
 *   Check out source code and build
@@ -83,7 +82,7 @@
     *   API backward-compatibility guarantee: yes
 
 *   **com.google.crypto.tink.integration.android** This package allows Android
-    users to store keys in private perferences, wrapped with master key in
+    users to store keys in private preferences, wrapped with master key in
     [Android
     Keystore](https://developer.android.com/training/articles/keystore.html).
     The integration with Android Keystore only works on Android M (API level 23)
diff --git a/docs/JAVA-HOWTO.md b/docs/JAVA-HOWTO.md
index d947ff8..83ee50f 100644
--- a/docs/JAVA-HOWTO.md
+++ b/docs/JAVA-HOWTO.md
@@ -16,8 +16,8 @@
 `com.google.crypto.tink`, and the artifact ID is `tink`.
 
 The most recent release is
-[1.2.0](https://github.com/google/tink/releases/tag/v1.2.0), released
-2018-08-09.
+[1.2.2](https://github.com/google/tink/releases/tag/v1.2.2), released
+2019-01-24.
 
 Java developers can add Tink using Maven:
 
@@ -25,7 +25,7 @@
 <dependency>
   <groupId>com.google.crypto.tink</groupId>
   <artifactId>tink</artifactId>
-  <version>1.2.0</version>
+  <version>1.2.2</version>
 </dependency>
 ```
 
@@ -33,7 +33,7 @@
 
 ```
 dependencies {
-  compile 'com.google.crypto.tink:tink-android:1.2.0'
+  compile 'com.google.crypto.tink:tink-android:1.2.2'
 }
 ```
 
@@ -82,10 +82,10 @@
 ## API docs
 
 *   Java:
-    *   [1.2.0](https://google.github.com/tink/javadoc/tink/1.2.0)
+    *   [1.2.1](https://google.github.com/tink/javadoc/tink/1.2.1)
     *   [HEAD-SNAPSHOT](https://google.github.com/tink/javadoc/tink/HEAD-SNAPSHOT)
 *   Android:
-    *   [1.2.0](https://google.github.com/tink/javadoc/tink-android/1.2.0)
+    *   [1.2.1](https://google.github.com/tink/javadoc/tink-android/1.2.1)
     *   [HEAD-SNAPSHOT](https://google.github.com/tink/javadoc/tink-android/HEAD-SNAPSHOT)
 
 ## Important Warnings
@@ -184,7 +184,7 @@
     // and write it to a file.
     String keysetFilename = "my_keyset.json";
     CleartextKeysetHandle.write(keysetHandle, JsonKeysetWriter.withFile(
-        new File(keysetFilename));
+        new File(keysetFilename)));
 ```
 
 Storing cleartext keysets on disk is not recommended. Tink supports encrypting
@@ -192,7 +192,7 @@
 systems](KEY-MANAGEMENT.md).
 
 For example, you can encrypt the key material with a Google Cloud KMS key at
-`gcp-kms:/projects/tink-examples/locations/global/keyRings/foo/cryptoKeys/bar`
+`gcp-kms://projects/tink-examples/locations/global/keyRings/foo/cryptoKeys/bar`
 as follows:
 
 ```java
@@ -258,22 +258,21 @@
 The following table summarizes Java implementations of primitives that are
 currently available or planned (the latter are listed in brackets).
 
-| Primitive          | Implementations                                |
-| ------------------ | ---------------------------------------------- |
-| AEAD               | AES-EAX, AES-GCM, AES-CTR-HMAC, KMS Envelope,  |
-:                    : CHACHA20-POLY1305                              :
-| Streaming AEAD     | AES-GCM-HKDF-STREAMING, AES-CTR-HMAC-STREAMING |
-| Deterministic AEAD | AES-SIV                                        |
-| MAC                | HMAC-SHA2                                      |
-| Digital Signatures | ECDSA over NIST curves, ED25519                |
-| Hybrid Encryption  | ECIES with AEAD and HKDF, (NaCl CryptoBox)     |
+| Primitive          | Implementations                                                 |
+| ------------------ | --------------------------------------------------------------- |
+| AEAD               | AES-EAX, AES-GCM, AES-CTR-HMAC, KMS Envelope, CHACHA20-POLY1305 |
+| Streaming AEAD     | AES-GCM-HKDF-STREAMING, AES-CTR-HMAC-STREAMING                  |
+| Deterministic AEAD | AES-SIV                                                         |
+| MAC                | HMAC-SHA2                                                       |
+| Digital Signatures | ECDSA over NIST curves, ED25519                                 |
+| Hybrid Encryption  | ECIES with AEAD and HKDF, (NaCl CryptoBox)                      |
 
 Exact listings of primitives and their implementations available in a release _x.y.z_ of Tink
 are given in a corresponding [`TinkConfig.TINK_x_y_z`](https://github.com/google/tink/blob/master/java/src/main/java/com/google/crypto/tink/config/TinkConfig.java)-variable.
 
-Tink user accesses implementations of a primitive via a factory that corresponds
-to the primitive: AEAD via `AeadFactory`, MAC via `MacFactory`, etc. where each
-factory offers corresponding `getPrimitive(...)` methods.
+The user obtains a primitive by calling the function `getPrimitive(classObject)`
+of the `KeysetHandle`, where the `classObject` is the class object corresponding
+to the primitive (for example `Aead.class` for AEAD).
 
 ### Symmetric Key Encryption
 
@@ -284,7 +283,6 @@
 ```java
     import com.google.crypto.tink.Aead;
     import com.google.crypto.tink.KeysetHandle;
-    import com.google.crypto.tink.aead.AeadFactory;
     import com.google.crypto.tink.aead.AeadKeyTemplates;
 
     // 1. Generate the key material.
@@ -292,7 +290,7 @@
         AeadKeyTemplates.AES128_GCM);
 
     // 2. Get the primitive.
-    Aead aead = AeadFactory.getPrimitive(keysetHandle);
+    Aead aead = keysetHandle.getPrimitive(Aead.class);
 
     // 3. Use the primitive to encrypt a plaintext,
     byte[] ciphertext = aead.encrypt(plaintext, aad);
@@ -311,7 +309,6 @@
 ```java
     import com.google.crypto.tink.DeterministicAead;
     import com.google.crypto.tink.KeysetHandle;
-    import com.google.crypto.tink.daead.DeterministicAeadFactory;
     import com.google.crypto.tink.daead.DeterministicAeadKeyTemplates;
 
     // 1. Generate the key material.
@@ -320,7 +317,7 @@
 
     // 2. Get the primitive.
     DeterministicAead daead =
-        DeterministicAeadFactory.getPrimitive(keysetHandle);
+       keysetHandle.getPrimitive(DeterministicAead.class);
 
     // 3. Use the primitive to deterministically encrypt a plaintext,
     byte[] ciphertext = daead.encryptDeterministically(plaintext, aad);
@@ -338,7 +335,6 @@
 ```java
     import com.google.crypto.tink.StreamingAead;
     import com.google.crypto.tink.KeysetHandle;
-    import com.google.crypto.tink.streamingaead.StreamingAeadFactory;
     import com.google.crypto.tink.streamingaead.StreamingAeadKeyTemplates;
     import java.nio.ByteBuffer
     import java.nio.channels.FileChannel;
@@ -350,10 +346,11 @@
         StreamingAeadKeyTemplates.AES128_CTR_HMAC_SHA256_4KB);
 
     // 2. Get the primitive.
-    StreamingAead streamingAead = StreamingAeadFactory.getPrimitive(keysetHandle);
+    StreamingAead streamingAead = keysetHandle.getPrimitive(StreamingAead.class);
 
     // 3. Use the primitive to encrypt some data and write the ciphertext to a file,
-    FileChannel ciphertextDestination = new FileOutputStream(ciphertextFileName).getChannel();
+    FileChannel ciphertextDestination =
+        new FileOutputStream(ciphertextFileName).getChannel();
     byte[] aad = ...
     WritableByteChannel encryptingChannel =
         streamingAead.newEncryptingChannel(ciphertextDestination, aad);
@@ -366,9 +363,11 @@
     encryptingChannel.close();
 
     // ... or to decrypt an existing ciphertext stream.
-    FileChannel ciphertextSource = new FileInputStream(ciphertextFileName).getChannel();
+    FileChannel ciphertextSource =
+        new FileInputStream(ciphertextFileName).getChannel();
     byte[] aad = ...
-    ReadableByteChannel decryptingChannel = s.newDecryptingChannel(ciphertextSource, aad);
+    ReadableByteChannel decryptingChannel =
+        s.newDecryptingChannel(ciphertextSource, aad);
     ByteBuffer buffer = ByteBuffer.allocate(chunkSize);
     do {
       buffer.clear();
@@ -392,7 +391,6 @@
 ```java
     import com.google.crypto.tink.KeysetHandle;
     import com.google.crypto.tink.Mac;
-    import com.google.crypto.tink.mac.MacFactory;
     import com.google.crypto.tink.mac.MacKeyTemplates;
 
     // 1. Generate the key material.
@@ -400,7 +398,7 @@
         MacKeyTemplates.HMAC_SHA256_128BITTAG);
 
     // 2. Get the primitive.
-    Mac mac = MacFactory.getPrimitive(keysetHandle);
+    Mac mac = keysetHandle.getPrimitive(Mac.class);
 
     // 3. Use the primitive to compute a tag,
     byte[] tag = mac.computeMac(data);
@@ -418,8 +416,6 @@
     import com.google.crypto.tink.KeysetHandle;
     import com.google.crypto.tink.PublicKeySign;
     import com.google.crypto.tink.PublicKeyVerify;
-    import com.google.crypto.tink.signature.PublicKeySignFactory;
-    import com.google.crypto.tink.signature.PublicKeyVerifyFactory;
     import com.google.crypto.tink.signature.SignatureKeyTemplates;
 
     // SIGNING
@@ -429,8 +425,7 @@
         SignatureKeyTemplates.ECDSA_P256);
 
     // 2. Get the primitive.
-    PublicKeySign signer = PublicKeySignFactory.getPrimitive(
-        privateKeysetHandle);
+    PublicKeySign signer = privateKeysetHandle.getPrimitive(PublicKeySign.class);
 
     // 3. Use the primitive to sign.
     byte[] signature = signer.sign(data);
@@ -442,8 +437,7 @@
         privateKeysetHandle.getPublicKeysetHandle();
 
     // 2. Get the primitive.
-    PublicKeyVerify verifier = PublicKeyVerifyFactory.getPrimitive(
-        publicKeysetHandle);
+    PublicKeyVerify verifier = publicKeysetHandle.getPrimitive(PublicKeyVerify.class);
 
     // 4. Use the primitive to verify.
     verifier.verify(signature, data);
@@ -458,8 +452,6 @@
 ```java
     import com.google.crypto.tink.HybridDecrypt;
     import com.google.crypto.tink.HybridEncrypt;
-    import com.google.crypto.tink.hybrid.HybridDecryptFactory;
-    import com.google.crypto.tink.hybrid.HybridEncryptFactory;
     import com.google.crypto.tink.hybrid.HybridKeyTemplates;
     import com.google.crypto.tink.KeysetHandle;
 
@@ -474,8 +466,8 @@
     // ENCRYPTING
 
     // 2. Get the primitive.
-    HybridEncrypt hybridEncrypt = HybridEncryptFactory.getPrimitive(
-        publicKeysetHandle);
+    HybridEncrypt hybridEncrypt =
+        publicKeysetHandle.getPrimitive(HybridEncrypt.class);
 
     // 3. Use the primitive.
     byte[] ciphertext = hybridEncrypt.encrypt(plaintext, contextInfo);
@@ -483,8 +475,8 @@
     // DECRYPTING
 
     // 2. Get the primitive.
-    HybridDecrypt hybridDecrypt = HybridDecryptFactory.getPrimitive(
-        privateKeysetHandle);
+    HybridDecrypt hybridDecrypt = privateKeysetHandle.getPrimitive(
+        HybridDecrypt.class);
 
     // 3. Use the primitive.
     byte[] plaintext = hybridDecrypt.decrypt(ciphertext, contextInfo);
@@ -513,7 +505,6 @@
     import com.google.crypto.tink.Aead;
     import com.google.crypto.tink.KeysetHandle;
     import com.google.crypto.tink.KmsClients;
-    import com.google.crypto.tink.aead.AeadFactory;
     import com.google.crypto.tink.aead.AeadKeyTemplates;
     import com.google.crypto.tink.integration.gcpkms.GcpKmsClient;
 
@@ -528,7 +519,7 @@
         .withCredentials("credentials.json"));
 
     // 3. Get the primitive.
-    Aead aead = AeadFactory.getPrimitive(keysetHandle);
+    Aead aead = keysetHandle.getPrimitive(Aead.class);
 
     // 4. Use the primitive.
     byte[] ciphertext = aead.encrypt(plaintext, aad);
@@ -610,9 +601,10 @@
     Registry.registerKeyManager(keyManager);
 ```
 
-Afterwards the implementation will be accessed automatically by the `Factory`
-corresponding to the primitive (when keys of the specific key type are in use),
-or can be retrieved directly via `Registry.getKeyManager(keyType)`.
+Afterwards the implementation will be accessed automatically by the
+`keysetHandle.getPrimitive` corresponding to the primitive (when keys of the
+specific key type are in use). It can also be retrieved directly via
+`Registry.getKeyManager(keyType)`.
 
 When defining the protocol buffers for the key material and parameters (step #2
 above), you should provide definitions of three messages:
@@ -684,5 +676,5 @@
     }
 ```
 
-After registering `MyCustomAeadKeyManager` with the Registry we can use it
-via [`AeadFactory`](https://github.com/google/tink/blob/master/java/src/main/java/com/google/crypto/tink/aead/AeadFactory.java).
+After registering `MyCustomAeadKeyManager` with the Registry it will be used
+when a user calls `keysetHandle.getPrimitive(Aead.class)`.
diff --git a/docs/KEY-MANAGEMENT.md b/docs/KEY-MANAGEMENT.md
index 87ca52b..4919478 100644
--- a/docs/KEY-MANAGEMENT.md
+++ b/docs/KEY-MANAGEMENT.md
@@ -2,7 +2,11 @@
 
 In addition to cryptographic operations Tink provides support for key management
 features like key versioning, key rotation, and storing keysets or encrypting
-with master keys in remote key management systems (KMS).
+with master keys in remote key management systems (KMS).  To get a quick
+overview of Tink design, incl. key management features, you can also take a look
+at [slides](Tink-a_cryptographic_library--RealWorldCrypto2019.pdf) from [a talk
+about Tink](https://www.youtube.com/watch?v=pqev9r3rUJs&t=9665) presented at
+[Real World Crypto 2019](https://rwc.iacr.org/2019/).
 
 [Tinkey](TINKEY.md) is a command-line tool that allows managing Tink's key
 material. Tink also provides a rich key management API (e.g., see
diff --git a/docs/PRIMITIVES.md b/docs/PRIMITIVES.md
index 75ce75f..a6fc302 100644
--- a/docs/PRIMITIVES.md
+++ b/docs/PRIMITIVES.md
@@ -2,7 +2,7 @@
 
 [Tink](https://github.com/google/tink) performs cryptographic tasks via
 so-called _primitives_, which provide an abstract representation of the provided
-functionality.  Currently Tink supports the following cryptograhic operations
+functionality.  Currently Tink supports the following cryptographic operations
 via the corresponding primitives:
 
 - authenticated encryption with associated data (primitive: AEAD)
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
index 95f9f18..3c59216 100644
--- a/docs/ROADMAP.md
+++ b/docs/ROADMAP.md
@@ -47,56 +47,58 @@
 
 ### 1.3.0
 
-Tentative release date: December 2018.
+Tentative release date: February 2019.
+
+Tentative new features:
 
 *   Java
 
-    *   P1. Authenticated Public Key Encryption.
-    *   P1. Initial support for strict JOSE.
-    *   P2. JNI for better performance.
+    *   P0. Integration with Cloud KMS/AWS KMS: streaming envelope encryption
+    *   P1. AEAD: XCHACHA20-POLY1305
+    *   P1. Signature: RSA-SSA-PKCS1, RSA-PSS
+    *   P2. Streaming hybrid encryption
 
-*   C++
+*   C++/Obj-C
 
-    *   P0. Feature parity with [Java
-        1.1.0](https://github.com/google/tink/releases/tag/v1.1.0).
-
-*   Objective-C
-
-    *   P0. Feature parity with [Java
-        1.1.0](https://github.com/google/tink/releases/tag/v1.1.0).
-
-*   C#
-
-    *   P0. Initial release, feature parity with [Java
-        1.0.0](https://github.com/google/tink/releases/tag/v1.1.0).
-    *   P1. Integration with Azure Key Vault.
+    *   P0. Integration with Cloud KMS/AWS KMS: key storage and (streaming)
+        envelope encryption
+    *   P0. Streaming AEAD: AES-GCM-HKDF-STREAMING, AES-CTR-HMAC-STREAMING
+    *   P0. Deterministic AEAD: AES-SIV
+    *   P0. Digital signature: ED25519
+    *   P1. AEAD: XCHACHA20-POLY1305
+    *   P1. Signature: RSA-SSA-PKCS1, RSA-PSS
+    *   P2. Nonce reuse resistant AEAD: AES-GCM-SIV
 
 *   Go
 
-    *   P1. Initial release, feature parity with [Java
-        1.0.0](https://github.com/google/tink/releases/tag/v1.0.0).
-    *   P2. Integration with Google Cloud KMS and AWS KMS.
-
-*   Javascript/NodeJS
-
-    *   P1. Initial release, feature parity with [Java
-        1.0.0](https://github.com/google/tink/releases/tag/v1.0.0).
+    *   P0. AEAD: AES-GCM, AES-CTR-HMAC-AEAD
+    *   P0. MAC: HMAC-SHA2
+    *   P0. Signature: ECDSA with NIST curves
+    *   P0. Hybrid encryption: ECIES with NIST curves and AEAD
+    *   P1. AEAD: XCHACHA20-POLY1305
+    *   P1. Integration with Cloud KMS/AWS KMS: key storage and envelope
+        encryption
+    *   P2. Signature: ED25519
+    *   P2. Deterministic AEAD: AES-SIV
 
 ### 1.4.0
 
-Tentative release date: June 2019.
+Tentative release date: August 2019.
 
-*   Java
+Tentative new features:
 
-    *   P0. Stable strict JOSE APIs.
-    *   P1. More streaming APIs: StreamingMac and StreamingAead with append and
-        random write.
-    *   P1. Benchmarking.
+*   Go/Java/C++/Obj-C
 
-*   C++/Objective-C/Go/C#/Javascript/NodeJS
+    *   P0. Benchmarking
+    *   P0. Full integration with Cloud KMS/AWS KMS: key storage, (streaming)
+        envelope encryption, hybrid encryption and digital signature
+    *   P0. Initial support for Cloud HSM/AWS HSM
+    *   P1. Feature parity across platforms.
 
-    *   P0. Feature parity with Java 1.4.0.
+*   JavaScript
 
-*   C++
+    *   P0. Initial release that supports modern browsers
 
-    *   P1. Windows support.
+*   Python
+
+    *   P0. Initial CLIF-based release that can replace Keyczar.
diff --git a/docs/SECURITY-USABILITY.md b/docs/SECURITY-USABILITY.md
index 951f823..248c236 100644
--- a/docs/SECURITY-USABILITY.md
+++ b/docs/SECURITY-USABILITY.md
@@ -1,5 +1,11 @@
 # Tink's Security and Usability Design Goals
 
+The work on Tink is guided by the design goals discussed below.  To get a quick
+overview of Tink design you can also take a look at
+[slides](Tink-a_cryptographic_library--RealWorldCrypto2019.pdf) from [a talk
+about Tink](https://www.youtube.com/watch?v=pqev9r3rUJs&t=9665) presented at
+[Real World Crypto 2019](https://rwc.iacr.org/2019/).
+
 *   **Security** Tink is built on top of existing libraries such as BoringSSL
     and Java Cryptography Architecture, but includes countermeasures to many
     weaknesses in these libraries, which were discovered by Project Wycheproof,
diff --git a/docs/TINKEY.md b/docs/TINKEY.md
index 1b35392..4fdb79e 100644
--- a/docs/TINKEY.md
+++ b/docs/TINKEY.md
@@ -157,8 +157,8 @@
     using default credentials
 
 ```shell
-tinkey convert-keyset --out encrypted-keyset.cfg --in cleartext-keyset.cfg \
---master-key-uri gcp-kms://projects/tink-examples/locations/global/keyRings/foo/cryptoKeys/bar
+tinkey convert-keyset --in cleartext-keyset.cfg --out encrypted-keyset.cfg \
+--new-master-key-uri gcp-kms://projects/tink-examples/locations/global/keyRings/foo/cryptoKeys/bar
 ```
 
 -   Add a new key to an encrypted keyset, using default credentials
diff --git a/docs/Tink-a_cryptographic_library--RealWorldCrypto2019.pdf b/docs/Tink-a_cryptographic_library--RealWorldCrypto2019.pdf
new file mode 100644
index 0000000..ee1d2f1
--- /dev/null
+++ b/docs/Tink-a_cryptographic_library--RealWorldCrypto2019.pdf
Binary files differ
diff --git a/examples/helloworld/android/app/src/main/java/com/helloworld/TinkApplication.java b/examples/helloworld/android/app/src/main/java/com/helloworld/TinkApplication.java
index b28578a..daefd70 100644
--- a/examples/helloworld/android/app/src/main/java/com/helloworld/TinkApplication.java
+++ b/examples/helloworld/android/app/src/main/java/com/helloworld/TinkApplication.java
@@ -20,7 +20,6 @@
 import com.google.crypto.tink.Aead;
 import com.google.crypto.tink.Config;
 import com.google.crypto.tink.KeysetHandle;
-import com.google.crypto.tink.aead.AeadFactory;
 import com.google.crypto.tink.aead.AeadKeyTemplates;
 import com.google.crypto.tink.config.TinkConfig;
 import com.google.crypto.tink.integration.android.AndroidKeysetManager;
@@ -40,7 +39,7 @@
     super.onCreate();
     try {
       Config.register(TinkConfig.TINK_1_0_0);
-      aead = AeadFactory.getPrimitive(getOrGenerateNewKeysetHandle());
+      aead = getOrGenerateNewKeysetHandle().getPrimitive(Aead.class);
     } catch (GeneralSecurityException | IOException e) {
       throw new RuntimeException(e);
     }
diff --git a/examples/helloworld/cc/BUILD.bazel b/examples/helloworld/cc/BUILD.bazel
index 0fcb2d6..a343c52 100644
--- a/examples/helloworld/cc/BUILD.bazel
+++ b/examples/helloworld/cc/BUILD.bazel
@@ -16,7 +16,6 @@
     deps = [
         "//cc",
         "//cc:cleartext_keyset_handle",
-        "//cc/aead:aead_factory",
         "//cc/config:tink_config",
         "//cc/util:status",
     ],
diff --git a/examples/helloworld/cc/hello_world.cc b/examples/helloworld/cc/hello_world.cc
index 8315d62..762667b 100644
--- a/examples/helloworld/cc/hello_world.cc
+++ b/examples/helloworld/cc/hello_world.cc
@@ -32,7 +32,6 @@
 #include "tink/json_keyset_reader.h"
 #include "tink/keyset_handle.h"
 #include "tink/keyset_reader.h"
-#include "tink/aead/aead_factory.h"
 #include "tink/config/tink_config.h"
 
 namespace {
@@ -143,8 +142,7 @@
   auto keyset_handle = ReadKeyset(keyset_filename);
 
   // Get the primitive.
-  auto primitive_result =
-      crypto::tink::AeadFactory::GetPrimitive(*keyset_handle);
+  auto primitive_result = keyset_handle->GetPrimitive<crypto::tink::Aead>();
   if (!primitive_result.ok()) {
     std::clog << "Getting AEAD-primitive from the factory failed: "
               << primitive_result.status().error_message() << std::endl;
diff --git a/examples/helloworld/cc/hello_world_test.sh b/examples/helloworld/cc/hello_world_test.sh
index 93aa7ba..52d6908 100755
--- a/examples/helloworld/cc/hello_world_test.sh
+++ b/examples/helloworld/cc/hello_world_test.sh
@@ -1,9 +1,22 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+################################################################################
+
 #!/bin/bash
 
 #############################################################################
 ##### Tests for hello_world binary.
 
-ROOT_DIR="$TEST_SRCDIR/__main__"
+ROOT_DIR="$TEST_SRCDIR/tink"
 HELLO_WORLD_CLI="$ROOT_DIR/examples/helloworld/cc/hello_world"
 
 KEYSET_FILE="$ROOT_DIR/examples/helloworld/cc/aes128_gcm_test_keyset_json.txt"
diff --git a/examples/helloworld/cc/non_bazel_build_test.sh b/examples/helloworld/cc/non_bazel_build_test.sh
index 15756ca..a6ce4ea 100755
--- a/examples/helloworld/cc/non_bazel_build_test.sh
+++ b/examples/helloworld/cc/non_bazel_build_test.sh
@@ -1,9 +1,22 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+################################################################################
+
 #!/bin/bash
 
 #############################################################################
 ##### Tests for installing and using Tink in a non-Bazel project.
 
-ROOT_DIR="$TEST_SRCDIR/__main__"
+ROOT_DIR="$TEST_SRCDIR/tink"
 TARGET_DIR="$TEST_TMPDIR/my_project"
 HELLO_WORLD_SRC="$ROOT_DIR/examples/helloworld/cc/hello_world.cc"
 KEYSET_FILE="$ROOT_DIR/examples/helloworld/cc/aes128_gcm_test_keyset_json.txt"
diff --git a/examples/helloworld/java/README.md b/examples/helloworld/java/README.md
index 4558d4c..2366293 100644
--- a/examples/helloworld/java/README.md
+++ b/examples/helloworld/java/README.md
@@ -34,7 +34,7 @@
 ```shell
 git clone https://github.com/google/tink
 cd tink
-bazel build ...
+bazel build //examples/helloworld/java/...
 echo foo > foo.txt
 ./bazel-bin/examples/helloworld/java/helloworld encrypt --keyset test.cfg --in foo.txt --out bar.encrypted
 ./bazel-bin/examples/helloworld/java/helloworld decrypt --keyset test.cfg --in bar.encrypted --out foo2.txt
diff --git a/examples/helloworld/java/src/main/java/com/helloworld/Commands.java b/examples/helloworld/java/src/main/java/com/helloworld/Commands.java
index 357a24c..d52b918 100644
--- a/examples/helloworld/java/src/main/java/com/helloworld/Commands.java
+++ b/examples/helloworld/java/src/main/java/com/helloworld/Commands.java
@@ -19,7 +19,6 @@
 import com.google.crypto.tink.JsonKeysetReader;
 import com.google.crypto.tink.JsonKeysetWriter;
 import com.google.crypto.tink.KeysetHandle;
-import com.google.crypto.tink.aead.AeadFactory;
 import com.google.crypto.tink.aead.AeadKeyTemplates;
 import java.io.File;
 import java.io.FileOutputStream;
@@ -61,7 +60,7 @@
       // Read the cleartext keyset from disk.
       // WARNING: reading cleartext keysets is a bad practice. Tink supports reading/writing
       // encrypted keysets, see
-      // https://github.com/google/tink/blob/master/doc/JAVA-HOWTO.md#loading-existing-keysets.
+      // https://github.com/google/tink/blob/master/docs/JAVA-HOWTO.md#loading-existing-keysets.
       return CleartextKeysetHandle.read(JsonKeysetReader.withFile(keyset));
     }
     KeysetHandle handle = KeysetHandle.generateNew(AeadKeyTemplates.AES128_GCM);
@@ -78,7 +77,7 @@
       // 1. Obtain a keyset handle.
       KeysetHandle handle = getKeysetHandle(keyset);
       // 2. Get a primitive.
-      Aead aead = AeadFactory.getPrimitive(handle);
+      Aead aead = handle.getPrimitive(Aead.class);
       // 3. Do crypto. It's that simple!
       byte[] plaintext = Files.readAllBytes(inFile.toPath());
       byte[] ciphertext = aead.encrypt(plaintext, new byte[0] /* additionalData */);
@@ -98,7 +97,7 @@
     @Override
     public void run() throws Exception {
       KeysetHandle handle = getKeysetHandle(keyset);
-      Aead aead = AeadFactory.getPrimitive(handle);
+      Aead aead = handle.getPrimitive(Aead.class);
       byte[] ciphertext = Files.readAllBytes(inFile.toPath());
       byte[] plaintext = aead.decrypt(ciphertext, new byte[0] /* additionalData */);
       FileOutputStream stream = new FileOutputStream(outFile);
diff --git a/go/BUILD.bazel b/go/BUILD.bazel
index 09610f7..78a5827 100644
--- a/go/BUILD.bazel
+++ b/go/BUILD.bazel
@@ -1,3 +1,11 @@
 package(default_visibility = ["//tools/build_defs:internal_pkg"])
 
 licenses(["notice"])  # Apache 2.0
+
+load("@io_bazel_rules_go//go:def.bzl", "nogo")
+
+nogo(
+    name = "tink_nogo",
+    vet = True,
+    visibility = ["//visibility:public"],
+)
diff --git a/go/aead/BUILD.bazel b/go/aead/BUILD.bazel
index 2775f5d..e480dc8 100644
--- a/go/aead/BUILD.bazel
+++ b/go/aead/BUILD.bazel
@@ -1,42 +1,74 @@
+package(default_visibility = ["//tools/build_defs:internal_pkg"])  # keep
+
+licenses(["notice"])  # Apache 2.0 # keep
+
 load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 
 go_library(
     name = "go_default_library",
     srcs = [
-        "aead_config.go",
+        "aead.go",
         "aead_factory.go",
         "aead_key_templates.go",
+        "aes_ctr_hmac_aead_key_manager.go",
         "aes_gcm_key_manager.go",
-        "proto_util.go",
+        "chacha20poly1305_key_manager.go",
+        "kms_envelope_aead.go",
+        "kms_envelope_aead_key_manager.go",
+        "xchacha20poly1305_key_manager.go",
     ],
     importpath = "github.com/google/tink/go/aead",
     visibility = ["//visibility:public"],
     deps = [
+        "//go/format:go_default_library",
+        "//go/keyset:go_default_library",
+        "//go/primitiveset:go_default_library",
+        "//go/registry:go_default_library",
         "//go/subtle/aead:go_default_library",
+        "//go/subtle/mac:go_default_library",
         "//go/subtle/random:go_default_library",
         "//go/tink:go_default_library",
+        "//proto:aes_ctr_go_proto",
+        "//proto:aes_ctr_hmac_aead_go_proto",
         "//proto:aes_gcm_go_proto",
+        "//proto:chacha20_poly1305_go_proto",
+        "//proto:common_go_proto",
+        "//proto:hmac_go_proto",
+        "//proto:kms_envelope_go_proto",
         "//proto:tink_go_proto",
+        "//proto:xchacha20_poly1305_go_proto",
         "@com_github_golang_protobuf//proto:go_default_library",
+        "@org_golang_x_crypto//chacha20poly1305:go_default_library",
     ],
 )
 
 go_test(
-    name = "go_default_xtest",
+    name = "go_default_test",
     srcs = [
-        "aead_config_test.go",
+        "aead_test.go",
         "aead_factory_test.go",
         "aead_key_templates_test.go",
+        "aes_ctr_hmac_aead_key_manager_test.go",
         "aes_gcm_key_manager_test.go",
+        "chacha20poly1305_key_manager_test.go",
+        "xchacha20poly1305_key_manager_test.go",
     ],
+    embed = [":go_default_library"],
     deps = [
-        ":go_default_library",
+        "//go/format:go_default_library",
+        "//go/keyset:go_default_library",
+        "//go/registry:go_default_library",
         "//go/subtle/aead:go_default_library",
         "//go/subtle/random:go_default_library",
+        "//go/testkeyset:go_default_library",
         "//go/testutil:go_default_library",
         "//go/tink:go_default_library",
+        "//proto:aes_ctr_hmac_aead_go_proto",
         "//proto:aes_gcm_go_proto",
+        "//proto:chacha20_poly1305_go_proto",
         "//proto:tink_go_proto",
+        "//proto:xchacha20_poly1305_go_proto",
         "@com_github_golang_protobuf//proto:go_default_library",
+        "@org_golang_x_crypto//chacha20poly1305:go_default_library",
     ],
 )
diff --git a/go/aead/aead.go b/go/aead/aead.go
new file mode 100644
index 0000000..bec3046
--- /dev/null
+++ b/go/aead/aead.go
@@ -0,0 +1,43 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// Package aead provides implementations of the AEAD primitive.
+package aead
+
+import (
+	"fmt"
+
+	"github.com/google/tink/go/registry"
+)
+
+func init() {
+	if err := registry.RegisterKeyManager(newAESCTRHMACAEADKeyManager()); err != nil {
+		panic(fmt.Sprintf("aead.init() failed: %v", err))
+	}
+
+	if err := registry.RegisterKeyManager(newAESGCMKeyManager()); err != nil {
+		panic(fmt.Sprintf("aead.init() failed: %v", err))
+	}
+
+	if err := registry.RegisterKeyManager(newChaCha20Poly1305KeyManager()); err != nil {
+		panic(fmt.Sprintf("aead.init() failed: %v", err))
+	}
+
+	if err := registry.RegisterKeyManager(newXChaCha20Poly1305KeyManager()); err != nil {
+		panic(fmt.Sprintf("aead.init() failed: %v", err))
+	}
+	if err := registry.RegisterKeyManager(newKMSEnvelopeAEADKeyManager()); err != nil {
+		panic(fmt.Sprintf("aead.init() failed: %v", err))
+	}
+}
diff --git a/go/aead/aead_config.go b/go/aead/aead_config.go
deleted file mode 100644
index 93fedd7..0000000
--- a/go/aead/aead_config.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-// Package aead provides implementations of the Aead primitive.
-package aead
-
-import (
-	"github.com/google/tink/go/tink"
-)
-
-// RegisterStandardKeyTypes registers standard Aead key types and their managers
-// with the Registry.
-func RegisterStandardKeyTypes() (bool, error) {
-	return RegisterKeyManager(NewAesGcmKeyManager())
-}
-
-// RegisterKeyManager registers the given keyManager for the key type given in
-// keyManager.KeyType(). It returns true if registration was successful, false if
-// there already exisits a key manager for the key type.
-func RegisterKeyManager(keyManager tink.KeyManager) (bool, error) {
-	return tink.RegisterKeyManager(keyManager)
-}
diff --git a/go/aead/aead_config_test.go b/go/aead/aead_config_test.go
deleted file mode 100644
index 90ca98f..0000000
--- a/go/aead/aead_config_test.go
+++ /dev/null
@@ -1,35 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package aead_test
-
-import (
-	"testing"
-
-	"github.com/google/tink/go/aead"
-	"github.com/google/tink/go/tink"
-)
-
-func TestConfigRegistration(t *testing.T) {
-	success, err := aead.RegisterStandardKeyTypes()
-	if !success || err != nil {
-		t.Errorf("cannot register standard key types")
-	}
-	// check for AES-GCM key manager
-	keyManager, err := tink.GetKeyManager(aead.AesGcmTypeURL)
-	if err != nil {
-		t.Errorf("unexpected error: %s", err)
-	}
-	var _ = keyManager.(*aead.AesGcmKeyManager)
-}
diff --git a/go/aead/aead_factory.go b/go/aead/aead_factory.go
index 0b93233..160d127 100644
--- a/go/aead/aead_factory.go
+++ b/go/aead/aead_factory.go
@@ -17,53 +17,53 @@
 import (
 	"fmt"
 
+	"github.com/google/tink/go/format"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/primitiveset"
+	"github.com/google/tink/go/registry"
 	"github.com/google/tink/go/tink"
 )
 
-// GetPrimitive returns a Aead primitive from the given keyset handle.
-func GetPrimitive(handle *tink.KeysetHandle) (tink.Aead, error) {
-	return GetPrimitiveWithCustomerManager(handle, nil /*keyManager*/)
+// New returns an AEAD primitive from the given keyset handle.
+func New(h *keyset.Handle) (tink.AEAD, error) {
+	return NewWithKeyManager(h, nil /*keyManager*/)
 }
 
-// GetPrimitiveWithCustomerManager returns a Aead primitive from the given
-// keyset handle and custom key manager.
-func GetPrimitiveWithCustomerManager(
-	handle *tink.KeysetHandle, manager tink.KeyManager) (tink.Aead, error) {
-	ps, err := tink.GetPrimitivesWithCustomManager(handle, manager)
+// NewWithKeyManager returns an AEAD primitive from the given keyset handle and custom key manager.
+func NewWithKeyManager(h *keyset.Handle, km registry.KeyManager) (tink.AEAD, error) {
+	ps, err := h.PrimitivesWithKeyManager(km)
 	if err != nil {
 		return nil, fmt.Errorf("aead_factory: cannot obtain primitive set: %s", err)
 	}
-	var ret tink.Aead = newPrimitiveSetAead(ps)
-	return ret, nil
+	return newPrimitiveSet(ps), nil
 }
 
-// primitiveSetAead is an Aead implementation that uses the underlying primitive
-// set for encryption and decryption.
-type primitiveSetAead struct {
-	ps *tink.PrimitiveSet
+// primitiveSet is an AEAD implementation that uses the underlying primitive set for encryption
+// and decryption.
+type primitiveSet struct {
+	ps *primitiveset.PrimitiveSet
 }
 
-// Asserts that primitiveSetAead implements the Aead interface.
-var _ tink.Aead = (*primitiveSetAead)(nil)
+// Asserts that primitiveSet implements the AEAD interface.
+var _ tink.AEAD = (*primitiveSet)(nil)
 
-// newPrimitiveSetAead creates a new instance of primitiveSetAead
-func newPrimitiveSetAead(ps *tink.PrimitiveSet) *primitiveSetAead {
-	ret := new(primitiveSetAead)
+func newPrimitiveSet(ps *primitiveset.PrimitiveSet) *primitiveSet {
+	ret := new(primitiveSet)
 	ret.ps = ps
 	return ret
 }
 
 // Encrypt encrypts the given plaintext with the given additional authenticated data.
 // It returns the concatenation of the primary's identifier and the ciphertext.
-func (a *primitiveSetAead) Encrypt(pt []byte, ad []byte) ([]byte, error) {
-	primary := a.ps.Primary()
-	var p tink.Aead = (primary.Primitive()).(tink.Aead)
+func (a *primitiveSet) Encrypt(pt, ad []byte) ([]byte, error) {
+	primary := a.ps.Primary
+	var p = (primary.Primitive).(tink.AEAD)
 	ct, err := p.Encrypt(pt, ad)
 	if err != nil {
 		return nil, err
 	}
 	var ret []byte
-	ret = append(ret, primary.Identifier()...)
+	ret = append(ret, primary.Prefix...)
 	ret = append(ret, ct...)
 	return ret, nil
 }
@@ -71,16 +71,16 @@
 // Decrypt decrypts the given ciphertext and authenticates it with the given
 // additional authenticated data. It returns the corresponding plaintext if the
 // ciphertext is authenticated.
-func (a *primitiveSetAead) Decrypt(ct []byte, ad []byte) ([]byte, error) {
+func (a *primitiveSet) Decrypt(ct, ad []byte) ([]byte, error) {
 	// try non-raw keys
-	prefixSize := tink.NonRawPrefixSize
+	prefixSize := format.NonRawPrefixSize
 	if len(ct) > prefixSize {
 		prefix := ct[:prefixSize]
 		ctNoPrefix := ct[prefixSize:]
-		entries, err := a.ps.GetPrimitivesWithByteIdentifier(prefix)
+		entries, err := a.ps.EntriesForPrefix(string(prefix))
 		if err == nil {
 			for i := 0; i < len(entries); i++ {
-				var p = (entries[i].Primitive()).(tink.Aead)
+				var p = (entries[i].Primitive).(tink.AEAD)
 				pt, err := p.Decrypt(ctNoPrefix, ad)
 				if err == nil {
 					return pt, nil
@@ -89,10 +89,10 @@
 		}
 	}
 	// try raw keys
-	entries, err := a.ps.GetRawPrimitives()
+	entries, err := a.ps.RawEntries()
 	if err == nil {
 		for i := 0; i < len(entries); i++ {
-			var p = (entries[i].Primitive()).(tink.Aead)
+			var p = (entries[i].Primitive).(tink.AEAD)
 			pt, err := p.Decrypt(ct, ad)
 			if err == nil {
 				return pt, nil
diff --git a/go/aead/aead_factory_test.go b/go/aead/aead_factory_test.go
index b0b39a8..818156c 100644
--- a/go/aead/aead_factory_test.go
+++ b/go/aead/aead_factory_test.go
@@ -21,32 +21,30 @@
 	"testing"
 
 	"github.com/google/tink/go/aead"
-	subtleAead "github.com/google/tink/go/subtle/aead"
+	"github.com/google/tink/go/format"
 	"github.com/google/tink/go/subtle/random"
+	"github.com/google/tink/go/testkeyset"
 	"github.com/google/tink/go/testutil"
 	"github.com/google/tink/go/tink"
+
+	subtleAEAD "github.com/google/tink/go/subtle/aead"
 	tinkpb "github.com/google/tink/proto/tink_go_proto"
 )
 
-func setupFactoryTest() {
-	aead.RegisterStandardKeyTypes()
-}
-
 func TestFactoryMultipleKeys(t *testing.T) {
-	setupFactoryTest()
 	// encrypt with non-raw key
-	keyset := testutil.NewTestAesGcmKeyset(tinkpb.OutputPrefixType_TINK)
+	keyset := testutil.NewTestAESGCMKeyset(tinkpb.OutputPrefixType_TINK)
 	primaryKey := keyset.Key[0]
 	if primaryKey.OutputPrefixType == tinkpb.OutputPrefixType_RAW {
 		t.Errorf("expect a non-raw key")
 	}
-	keysetHandle, _ := tink.CleartextKeysetHandle().ParseKeyset(keyset)
-	a, err := aead.GetPrimitive(keysetHandle)
+	keysetHandle, _ := testkeyset.NewHandle(keyset)
+	a, err := aead.New(keysetHandle)
 	if err != nil {
-		t.Errorf("GetPrimitive failed: %s", err)
+		t.Errorf("aead.New failed: %s", err)
 	}
-	expectedPrefix, _ := tink.GetOutputPrefix(primaryKey)
-	if err := validateAeadFactoryCipher(a, a, expectedPrefix); err != nil {
+	expectedPrefix, _ := format.OutputPrefix(primaryKey)
+	if err := validateAEADFactoryCipher(a, a, expectedPrefix); err != nil {
 		t.Errorf("invalid cipher: %s", err)
 	}
 
@@ -55,50 +53,49 @@
 	if rawKey.OutputPrefixType != tinkpb.OutputPrefixType_RAW {
 		t.Errorf("expect a raw key")
 	}
-	keyset2 := tink.CreateKeyset(rawKey.KeyId, []*tinkpb.Keyset_Key{rawKey})
-	keysetHandle2, _ := tink.CleartextKeysetHandle().ParseKeyset(keyset2)
-	a2, err := aead.GetPrimitive(keysetHandle2)
+	keyset2 := testutil.NewKeyset(rawKey.KeyId, []*tinkpb.Keyset_Key{rawKey})
+	keysetHandle2, _ := testkeyset.NewHandle(keyset2)
+	a2, err := aead.New(keysetHandle2)
 	if err != nil {
-		t.Errorf("GetPrimitive failed: %s", err)
+		t.Errorf("aead.New failed: %s", err)
 	}
-	if err := validateAeadFactoryCipher(a2, a, tink.RawPrefix); err != nil {
+	if err := validateAEADFactoryCipher(a2, a, format.RawPrefix); err != nil {
 		t.Errorf("invalid cipher: %s", err)
 	}
 
 	// encrypt with a random key not in the keyset, decrypt with the keyset should fail
-	keyset2 = testutil.NewTestAesGcmKeyset(tinkpb.OutputPrefixType_TINK)
+	keyset2 = testutil.NewTestAESGCMKeyset(tinkpb.OutputPrefixType_TINK)
 	primaryKey = keyset2.Key[0]
-	expectedPrefix, _ = tink.GetOutputPrefix(primaryKey)
-	keysetHandle2, _ = tink.CleartextKeysetHandle().ParseKeyset(keyset2)
-	a2, err = aead.GetPrimitive(keysetHandle2)
+	expectedPrefix, _ = format.OutputPrefix(primaryKey)
+	keysetHandle2, _ = testkeyset.NewHandle(keyset2)
+	a2, err = aead.New(keysetHandle2)
 	if err != nil {
-		t.Errorf("GetPrimitive failed: %s", err)
+		t.Errorf("aead.New failed: %s", err)
 	}
-	err = validateAeadFactoryCipher(a2, a, expectedPrefix)
+	err = validateAEADFactoryCipher(a2, a, expectedPrefix)
 	if err == nil || !strings.Contains(err.Error(), "decryption failed") {
 		t.Errorf("expect decryption to fail with random key: %s", err)
 	}
 }
 
 func TestFactoryRawKeyAsPrimary(t *testing.T) {
-	setupFactoryTest()
-	keyset := testutil.NewTestAesGcmKeyset(tinkpb.OutputPrefixType_RAW)
+	keyset := testutil.NewTestAESGCMKeyset(tinkpb.OutputPrefixType_RAW)
 	if keyset.Key[0].OutputPrefixType != tinkpb.OutputPrefixType_RAW {
 		t.Errorf("primary key is not a raw key")
 	}
-	keysetHandle, _ := tink.CleartextKeysetHandle().ParseKeyset(keyset)
+	keysetHandle, _ := testkeyset.NewHandle(keyset)
 
-	a, err := aead.GetPrimitive(keysetHandle)
+	a, err := aead.New(keysetHandle)
 	if err != nil {
 		t.Errorf("cannot get primitive from keyset handle: %s", err)
 	}
-	if err := validateAeadFactoryCipher(a, a, tink.RawPrefix); err != nil {
+	if err := validateAEADFactoryCipher(a, a, format.RawPrefix); err != nil {
 		t.Errorf("invalid cipher: %s", err)
 	}
 }
 
-func validateAeadFactoryCipher(encryptCipher tink.Aead,
-	decryptCipher tink.Aead,
+func validateAEADFactoryCipher(encryptCipher tink.AEAD,
+	decryptCipher tink.AEAD,
 	expectedPrefix string) error {
 	prefixSize := len(expectedPrefix)
 	// regular plaintext
@@ -116,7 +113,7 @@
 	if string(ct[:prefixSize]) != expectedPrefix {
 		return fmt.Errorf("incorrect prefix with regular plaintext")
 	}
-	if prefixSize+len(pt)+subtleAead.AesGcmIvSize+subtleAead.AesGcmTagSize != len(ct) {
+	if prefixSize+len(pt)+subtleAEAD.AESGCMIVSize+subtleAEAD.AESGCMTagSize != len(ct) {
 		return fmt.Errorf("lengths of plaintext and ciphertext don't match with regular plaintext")
 	}
 
@@ -134,7 +131,7 @@
 	if string(ct[:prefixSize]) != expectedPrefix {
 		return fmt.Errorf("incorrect prefix with short plaintext")
 	}
-	if prefixSize+len(pt)+subtleAead.AesGcmIvSize+subtleAead.AesGcmTagSize != len(ct) {
+	if prefixSize+len(pt)+subtleAEAD.AESGCMIVSize+subtleAEAD.AESGCMTagSize != len(ct) {
 		return fmt.Errorf("lengths of plaintext and ciphertext don't match with short plaintext")
 	}
 	return nil
diff --git a/go/aead/aead_key_templates.go b/go/aead/aead_key_templates.go
index 2a9abae..4fbfd46 100644
--- a/go/aead/aead_key_templates.go
+++ b/go/aead/aead_key_templates.go
@@ -16,34 +16,92 @@
 
 import (
 	"github.com/golang/protobuf/proto"
+	ctrpb "github.com/google/tink/proto/aes_ctr_go_proto"
+	ctrhmacpb "github.com/google/tink/proto/aes_ctr_hmac_aead_go_proto"
 	gcmpb "github.com/google/tink/proto/aes_gcm_go_proto"
+	commonpb "github.com/google/tink/proto/common_go_proto"
+	hmacpb "github.com/google/tink/proto/hmac_go_proto"
+	kmsenvpb "github.com/google/tink/proto/kms_envelope_go_proto"
 	tinkpb "github.com/google/tink/proto/tink_go_proto"
 )
 
-// This file contains pre-generated KeyTemplate for Aead keys. One can use these templates
-// to generate new Keyset.
+// This file contains pre-generated KeyTemplates for AEAD keys. One can use these templates
+// to generate new Keysets.
 
-// Aes128GcmKeyTemplate is a KeyTemplate of AesGcmKey with the following parameters:
+// AES128GCMKeyTemplate is a KeyTemplate that generates an AES-GCM key with the following parameters:
 //   - Key size: 16 bytes
-func Aes128GcmKeyTemplate() *tinkpb.KeyTemplate {
-	return createAesGcmKeyTemplate(16)
+func AES128GCMKeyTemplate() *tinkpb.KeyTemplate {
+	return createAESGCMKeyTemplate(16)
 }
 
-// Aes256GcmKeyTemplate is a KeyTemplate of AesGcmKey with the following parameters:
+// AES256GCMKeyTemplate is a KeyTemplate that generates an AES-GCM key with the following parameters:
 //   - Key size: 32 bytes
-func Aes256GcmKeyTemplate() *tinkpb.KeyTemplate {
-	return createAesGcmKeyTemplate(32)
+func AES256GCMKeyTemplate() *tinkpb.KeyTemplate {
+	return createAESGCMKeyTemplate(32)
 }
 
-// createAesGcmKeyTemplate creates a new AES-GCM key template with the given key
+// AES128CTRHMACSHA256KeyTemplate is a KeyTemplate that generates an AES-CTR-HMAC-AEAD key with the following parameters:
+//  - AES key size: 16 bytes
+//  - AES CTR IV size: 16 bytes
+//  - HMAC key size: 32 bytes
+//  - HMAC tag size: 16 bytes
+//  - HMAC hash function: SHA256
+func AES128CTRHMACSHA256KeyTemplate() *tinkpb.KeyTemplate {
+	return createAESCTRHMACAEADKeyTemplate(16, 16, 32, 16, commonpb.HashType_SHA256)
+}
+
+// AES256CTRHMACSHA256KeyTemplate is a KeyTemplate that generates an AES-CTR-HMAC-AEAD key with the following parameters:
+//  - AES key size: 32 bytes
+//  - AES CTR IV size: 16 bytes
+//  - HMAC key size: 32 bytes
+//  - HMAC tag size: 32 bytes
+//  - HMAC hash function: SHA256
+func AES256CTRHMACSHA256KeyTemplate() *tinkpb.KeyTemplate {
+	return createAESCTRHMACAEADKeyTemplate(32, 16, 32, 32, commonpb.HashType_SHA256)
+}
+
+// KMSEnvelopeAEADKeyTemplate is a KeyTemplate that generates a KMSEnvelopeAEAD key for a given KEK in remote KMS
+func KMSEnvelopeAEADKeyTemplate(uri string, dekT *tinkpb.KeyTemplate) *tinkpb.KeyTemplate {
+	f := &kmsenvpb.KmsEnvelopeAeadKeyFormat{
+		KekUri:      uri,
+		DekTemplate: dekT,
+	}
+	serializedFormat, _ := proto.Marshal(f)
+	return &tinkpb.KeyTemplate{
+		Value:            serializedFormat,
+		TypeUrl:          kmsEnvelopeAEADTypeURL,
+		OutputPrefixType: tinkpb.OutputPrefixType_TINK,
+	}
+}
+
+// createAESGCMKeyTemplate creates a new AES-GCM key template with the given key
 // size in bytes.
-func createAesGcmKeyTemplate(keySize uint32) *tinkpb.KeyTemplate {
+func createAESGCMKeyTemplate(keySize uint32) *tinkpb.KeyTemplate {
 	format := &gcmpb.AesGcmKeyFormat{
 		KeySize: keySize,
 	}
 	serializedFormat, _ := proto.Marshal(format)
 	return &tinkpb.KeyTemplate{
-		TypeUrl: AesGcmTypeURL,
+		TypeUrl: aesGCMTypeURL,
 		Value:   serializedFormat,
 	}
 }
+
+func createAESCTRHMACAEADKeyTemplate(aesKeySize, ivSize, hmacKeySize, tagSize uint32, hash commonpb.HashType) *tinkpb.KeyTemplate {
+	format := &ctrhmacpb.AesCtrHmacAeadKeyFormat{
+		AesCtrKeyFormat: &ctrpb.AesCtrKeyFormat{
+			Params:  &ctrpb.AesCtrParams{IvSize: ivSize},
+			KeySize: aesKeySize,
+		},
+		HmacKeyFormat: &hmacpb.HmacKeyFormat{
+			Params:  &hmacpb.HmacParams{Hash: hash, TagSize: tagSize},
+			KeySize: hmacKeySize,
+		},
+	}
+	serializedFormat, _ := proto.Marshal(format)
+	return &tinkpb.KeyTemplate{
+		Value:            serializedFormat,
+		TypeUrl:          aesCTRHMACAEADTypeURL,
+		OutputPrefixType: tinkpb.OutputPrefixType_TINK,
+	}
+}
diff --git a/go/aead/aead_key_templates_test.go b/go/aead/aead_key_templates_test.go
index 16aa41b..22dcfbb 100644
--- a/go/aead/aead_key_templates_test.go
+++ b/go/aead/aead_key_templates_test.go
@@ -15,30 +15,44 @@
 package aead_test
 
 import (
+	"bytes"
+	"errors"
 	"fmt"
 	"testing"
 
 	"github.com/golang/protobuf/proto"
 	"github.com/google/tink/go/aead"
+	"github.com/google/tink/go/registry"
+	"github.com/google/tink/go/testutil"
+	"github.com/google/tink/go/tink"
+	ctrhmacpb "github.com/google/tink/proto/aes_ctr_hmac_aead_go_proto"
 	gcmpb "github.com/google/tink/proto/aes_gcm_go_proto"
+	commonpb "github.com/google/tink/proto/common_go_proto"
 	tinkpb "github.com/google/tink/proto/tink_go_proto"
 )
 
-func TestAesGcmKeyTemplates(t *testing.T) {
+func TestAESGCMKeyTemplates(t *testing.T) {
 	// AES-GCM 128 bit
-	template := aead.Aes128GcmKeyTemplate()
-	if err := checkAesGcmKeyTemplate(template, uint32(16)); err != nil {
+	template := aead.AES128GCMKeyTemplate()
+	if err := checkAESGCMKeyTemplate(template, uint32(16)); err != nil {
 		t.Errorf("invalid AES-128 GCM key template: %s", err)
 	}
+	if err := testEncryptDecrypt(template, testutil.AESGCMTypeURL); err != nil {
+		t.Errorf("%v", err)
+	}
+
 	// AES-GCM 256 bit
-	template = aead.Aes256GcmKeyTemplate()
-	if err := checkAesGcmKeyTemplate(template, uint32(32)); err != nil {
+	template = aead.AES256GCMKeyTemplate()
+	if err := checkAESGCMKeyTemplate(template, uint32(32)); err != nil {
 		t.Errorf("invalid AES-256 GCM key template: %s", err)
 	}
+	if err := testEncryptDecrypt(template, testutil.AESGCMTypeURL); err != nil {
+		t.Errorf("%v", err)
+	}
 }
 
-func checkAesGcmKeyTemplate(template *tinkpb.KeyTemplate, keySize uint32) error {
-	if template.TypeUrl != aead.AesGcmTypeURL {
+func checkAESGCMKeyTemplate(template *tinkpb.KeyTemplate, keySize uint32) error {
+	if template.TypeUrl != testutil.AESGCMTypeURL {
 		return fmt.Errorf("incorrect type url")
 	}
 	keyFormat := new(gcmpb.AesGcmKeyFormat)
@@ -51,3 +65,90 @@
 	}
 	return nil
 }
+
+func TestAESCTRHMACAEADKeyTemplates(t *testing.T) {
+	// AES-CTR 128 bit with HMAC SHA-256
+	template := aead.AES128CTRHMACSHA256KeyTemplate()
+	if err := checkAESCTRHMACAEADKeyTemplate(template, 16, 16, 16); err != nil {
+		t.Errorf("invalid AES-128 CTR HMAC SHA256 key template: %s", err)
+	}
+
+	if err := testEncryptDecrypt(template, testutil.AESCTRHMACAEADTypeURL); err != nil {
+		t.Errorf("%v", err)
+	}
+
+	// AES-CTR 256 bit with HMAC SHA-256
+	template = aead.AES256CTRHMACSHA256KeyTemplate()
+	if err := checkAESCTRHMACAEADKeyTemplate(template, 32, 16, 32); err != nil {
+		t.Errorf("invalid AES-256 CTR HMAC SHA256 key template: %s", err)
+	}
+	if err := testEncryptDecrypt(template, testutil.AESCTRHMACAEADTypeURL); err != nil {
+		t.Errorf("%v", err)
+	}
+}
+
+func checkAESCTRHMACAEADKeyTemplate(template *tinkpb.KeyTemplate, keySize, ivSize, tagSize uint32) error {
+	if template.TypeUrl != testutil.AESCTRHMACAEADTypeURL {
+		return fmt.Errorf("incorrect type url")
+	}
+	keyFormat := new(ctrhmacpb.AesCtrHmacAeadKeyFormat)
+	err := proto.Unmarshal(template.Value, keyFormat)
+	if err != nil {
+		return fmt.Errorf("cannot deserialize key format: %s", err)
+	}
+	if keyFormat.AesCtrKeyFormat.KeySize != keySize {
+		return fmt.Errorf("incorrect key size, expect %d, got %d", keySize, keyFormat.AesCtrKeyFormat.KeySize)
+	}
+	if keyFormat.AesCtrKeyFormat.Params.IvSize != ivSize {
+		return fmt.Errorf("incorrect IV size, expect %d, got %d", ivSize, keyFormat.AesCtrKeyFormat.Params.IvSize)
+	}
+	if keyFormat.HmacKeyFormat.KeySize != 32 {
+		return fmt.Errorf("incorrect HMAC key size, expect 32, got %d", keyFormat.HmacKeyFormat.KeySize)
+	}
+	if keyFormat.HmacKeyFormat.Params.TagSize != tagSize {
+		return fmt.Errorf("incorrect HMAC tag size, expect %d, got %d", tagSize, keyFormat.HmacKeyFormat.Params.TagSize)
+	}
+	if keyFormat.HmacKeyFormat.Params.Hash != commonpb.HashType_SHA256 {
+		return fmt.Errorf("incorrect HMAC hash, expect %q, got %q", commonpb.HashType_SHA256, keyFormat.HmacKeyFormat.Params.Hash)
+	}
+	return nil
+}
+
+func testEncryptDecrypt(template *tinkpb.KeyTemplate, typeURL string) error {
+	key, err := registry.NewKey(template)
+	if err != nil {
+		return fmt.Errorf("failed to get key from template, error: %v", err)
+	}
+
+	sk, err := proto.Marshal(key)
+	if err != nil {
+		return fmt.Errorf("failed to serialize key, error: %v", err)
+	}
+
+	p, err := registry.Primitive(typeURL, sk)
+	if err != nil {
+		return fmt.Errorf("failed to get primitive from serialized key, error: %v", err)
+	}
+
+	primitive, ok := p.(tink.AEAD)
+	if !ok {
+		return errors.New("failed to convert AEAD primitive")
+	}
+
+	plaintext := []byte("some data to encrypt")
+	aad := []byte("extra data to authenticate")
+	ciphertext, err := primitive.Encrypt(plaintext, aad)
+	if err != nil {
+		return fmt.Errorf("encryption failed, error: %v", err)
+	}
+	decrypted, err := primitive.Decrypt(ciphertext, aad)
+	if err != nil {
+		return fmt.Errorf("decryption failed, error: %v", err)
+	}
+
+	if bytes.Compare(plaintext, decrypted) != 0 {
+		return fmt.Errorf("decrypted data doesn't match plaintext, got: %q, want: %q", decrypted, plaintext)
+	}
+
+	return nil
+}
diff --git a/go/aead/aead_test.go b/go/aead/aead_test.go
new file mode 100644
index 0000000..366162a
--- /dev/null
+++ b/go/aead/aead_test.go
@@ -0,0 +1,42 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package aead_test
+
+import (
+	"testing"
+
+	"github.com/google/tink/go/registry"
+	"github.com/google/tink/go/testutil"
+)
+
+func TestAeadInit(t *testing.T) {
+	// Check for AES-GCM key manager.
+	_, err := registry.GetKeyManager(testutil.AESGCMTypeURL)
+	if err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+
+	// Check for ChaCha20Poly1305 key manager.
+	_, err = registry.GetKeyManager(testutil.ChaCha20Poly1305TypeURL)
+	if err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+
+	// Check for XChaCha20Poly1305 key manager.
+	_, err = registry.GetKeyManager(testutil.XChaCha20Poly1305TypeURL)
+	if err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+}
diff --git a/go/aead/aes_ctr_hmac_aead_key_manager.go b/go/aead/aes_ctr_hmac_aead_key_manager.go
new file mode 100644
index 0000000..db47891
--- /dev/null
+++ b/go/aead/aes_ctr_hmac_aead_key_manager.go
@@ -0,0 +1,198 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package aead
+
+import (
+	"errors"
+	"fmt"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/registry"
+	"github.com/google/tink/go/subtle/aead"
+	"github.com/google/tink/go/subtle/mac"
+	"github.com/google/tink/go/subtle/random"
+	ctrpb "github.com/google/tink/proto/aes_ctr_go_proto"
+	aeadpb "github.com/google/tink/proto/aes_ctr_hmac_aead_go_proto"
+	commonpb "github.com/google/tink/proto/common_go_proto"
+	hmacpb "github.com/google/tink/proto/hmac_go_proto"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+const (
+	aesCTRHMACAEADKeyVersion = 0
+	aesCTRHMACAEADTypeURL    = "type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey"
+	minHMACKeySizeInBytes    = 16
+	minTagSizeInBytes        = 10
+)
+
+// common errors
+var errInvalidAESCTRHMACAEADKey = fmt.Errorf("aes_ctr_hmac_aead_key_manager: invalid key")
+var errInvalidAESCTRHMACAEADKeyFormat = fmt.Errorf("aes_ctr_hmac_aead_key_manager: invalid key format")
+
+// aesCTRHMACAEADKeyManager is an implementation of KeyManager interface.
+// It generates new AESCTRHMACAEADKey keys and produces new instances of EncryptThenAuthenticate subtle.
+type aesCTRHMACAEADKeyManager struct{}
+
+// Assert that aesCTRHMACAEADKeyManager implements the KeyManager interface.
+var _ registry.KeyManager = (*aesCTRHMACAEADKeyManager)(nil)
+
+// newAESCTRHMACAEADKeyManager creates a new aesCTRHMACAEADKeyManager.
+func newAESCTRHMACAEADKeyManager() *aesCTRHMACAEADKeyManager {
+	return new(aesCTRHMACAEADKeyManager)
+}
+
+// Primitive creates an AEAD for the given serialized AESCTRHMACAEADKey proto.
+func (km *aesCTRHMACAEADKeyManager) Primitive(serializedKey []byte) (interface{}, error) {
+	if len(serializedKey) == 0 {
+		return nil, errInvalidAESCTRHMACAEADKey
+	}
+	key := new(aeadpb.AesCtrHmacAeadKey)
+	if err := proto.Unmarshal(serializedKey, key); err != nil {
+		return nil, errInvalidAESCTRHMACAEADKey
+	}
+	if err := km.validateKey(key); err != nil {
+		return nil, err
+	}
+
+	ctr, err := aead.NewAESCTR(key.AesCtrKey.KeyValue, int(key.AesCtrKey.Params.IvSize))
+	if err != nil {
+		return nil, fmt.Errorf("aes_ctr_hmac_aead_key_manager: cannot create new primitive: %v", err)
+	}
+
+	hmacKey := key.HmacKey
+	hmac, err := mac.NewHMAC(hmacKey.Params.Hash.String(), hmacKey.KeyValue, hmacKey.Params.TagSize)
+	if err != nil {
+		return nil, fmt.Errorf("aes_ctr_hmac_aead_key_manager: cannot create mac primitive, error: %v", err)
+	}
+
+	aead, err := aead.NewEncryptThenAuthenticate(ctr, hmac, int(hmacKey.Params.TagSize))
+	if err != nil {
+		return nil, fmt.Errorf("aes_ctr_hmac_aead_key_manager: cannot create encrypt then authenticate primitive, error: %v", err)
+	}
+	return aead, nil
+}
+
+// NewKey creates a new key according to the given serialized AesCtrHmacAeadKeyFormat.
+func (km *aesCTRHMACAEADKeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) {
+	if len(serializedKeyFormat) == 0 {
+		return nil, errInvalidAESCTRHMACAEADKeyFormat
+	}
+	keyFormat := new(aeadpb.AesCtrHmacAeadKeyFormat)
+	if err := proto.Unmarshal(serializedKeyFormat, keyFormat); err != nil {
+		return nil, errInvalidAESCTRHMACAEADKeyFormat
+	}
+	if err := km.validateKeyFormat(keyFormat); err != nil {
+		return nil, fmt.Errorf("aes_ctr_hmac_aead_key_manager: invalid key format: %v", err)
+	}
+	return &aeadpb.AesCtrHmacAeadKey{
+		Version: aesCTRHMACAEADKeyVersion,
+		AesCtrKey: &ctrpb.AesCtrKey{
+			Version:  aesCTRHMACAEADKeyVersion,
+			KeyValue: random.GetRandomBytes(keyFormat.AesCtrKeyFormat.KeySize),
+			Params:   keyFormat.AesCtrKeyFormat.Params,
+		},
+		HmacKey: &hmacpb.HmacKey{
+			Version:  aesCTRHMACAEADKeyVersion,
+			KeyValue: random.GetRandomBytes(keyFormat.HmacKeyFormat.KeySize),
+			Params:   keyFormat.HmacKeyFormat.Params,
+		},
+	}, nil
+}
+
+// NewKeyData creates a new KeyData according to specification in the given serialized
+// AesCtrHmacAeadKeyFormat.
+// It should be used solely by the key management API.
+func (km *aesCTRHMACAEADKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) {
+	key, err := km.NewKey(serializedKeyFormat)
+	if err != nil {
+		return nil, err
+	}
+	serializedKey, err := proto.Marshal(key)
+	if err != nil {
+		return nil, err
+	}
+	return &tinkpb.KeyData{
+		TypeUrl:         km.TypeURL(),
+		Value:           serializedKey,
+		KeyMaterialType: tinkpb.KeyData_SYMMETRIC,
+	}, nil
+}
+
+// DoesSupport indicates if this key manager supports the given key type.
+func (km *aesCTRHMACAEADKeyManager) DoesSupport(typeURL string) bool {
+	return typeURL == aesCTRHMACAEADTypeURL
+}
+
+// TypeURL returns the key type of keys managed by this key manager.
+func (km *aesCTRHMACAEADKeyManager) TypeURL() string {
+	return aesCTRHMACAEADTypeURL
+}
+
+// validateKey validates the given AesCtrHmacAeadKey proto.
+func (km *aesCTRHMACAEADKeyManager) validateKey(key *aeadpb.AesCtrHmacAeadKey) error {
+	err := keyset.ValidateKeyVersion(key.Version, aesCTRHMACAEADKeyVersion)
+	if err != nil {
+		return fmt.Errorf("aes_ctr_hmac_aead_key_manager: %v", err)
+	}
+
+	// Validate AesCtrKey.
+	keySize := uint32(len(key.AesCtrKey.KeyValue))
+	if err := aead.ValidateAESKeySize(keySize); err != nil {
+		return fmt.Errorf("aes_ctr_hmac_aead_key_manager: %v", err)
+	}
+	params := key.AesCtrKey.Params
+	if params.IvSize < aead.AESCTRMinIVSize || params.IvSize > 16 {
+		return errors.New("aes_ctr_hmac_aead_key_manager: invalid AesCtrHmacAeadKey: IV size out of range")
+	}
+	return nil
+}
+
+// validateKeyFormat validates the given AesCtrHmacAeadKeyFormat proto.
+func (km *aesCTRHMACAEADKeyManager) validateKeyFormat(format *aeadpb.AesCtrHmacAeadKeyFormat) error {
+	// Validate AesCtrKeyFormat.
+	if err := aead.ValidateAESKeySize(format.AesCtrKeyFormat.KeySize); err != nil {
+		return fmt.Errorf("aes_ctr_hmac_aead_key_manager: %s", err)
+	}
+	if format.AesCtrKeyFormat.Params.IvSize < aead.AESCTRMinIVSize || format.AesCtrKeyFormat.Params.IvSize > 16 {
+		return errors.New("aes_ctr_hmac_aead_key_manager: invalid AesCtrHmacAeadKeyFormat: IV size out of range")
+	}
+
+	// Validate HmacKeyFormat.
+	hmacKeyFormat := format.HmacKeyFormat
+	if hmacKeyFormat.KeySize < minHMACKeySizeInBytes {
+		return errors.New("aes_ctr_hmac_aead_key_manager: HMAC KeySize is too small")
+	}
+	if hmacKeyFormat.Params.TagSize < minTagSizeInBytes {
+		return fmt.Errorf("aes_ctr_hmac_aead_key_manager: invalid HmacParams: TagSize %d is too small", hmacKeyFormat.Params.TagSize)
+	}
+
+	maxTagSize := map[commonpb.HashType]uint32{
+		commonpb.HashType_SHA1:   20,
+		commonpb.HashType_SHA256: 32,
+		commonpb.HashType_SHA512: 64}
+
+	tagSize, ok := maxTagSize[hmacKeyFormat.Params.Hash]
+	if !ok {
+		return fmt.Errorf("aes_ctr_hmac_aead_key_manager: invalid HmacParams: HashType %q not supported",
+			hmacKeyFormat.Params.Hash)
+	}
+	if hmacKeyFormat.Params.TagSize > tagSize {
+		return fmt.Errorf("aes_ctr_hmac_aead_key_manager: invalid HmacParams: tagSize %d is too big for HashType %q",
+			hmacKeyFormat.Params.TagSize, hmacKeyFormat.Params.Hash)
+	}
+
+	return nil
+}
diff --git a/go/aead/aes_ctr_hmac_aead_key_manager_test.go b/go/aead/aes_ctr_hmac_aead_key_manager_test.go
new file mode 100644
index 0000000..12c19b6
--- /dev/null
+++ b/go/aead/aes_ctr_hmac_aead_key_manager_test.go
@@ -0,0 +1,86 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package aead_test
+
+import (
+	"testing"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/google/tink/go/aead"
+	"github.com/google/tink/go/registry"
+	"github.com/google/tink/go/testutil"
+	ctrhmacpb "github.com/google/tink/proto/aes_ctr_hmac_aead_go_proto"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+func TestNewKeyMultipleTimes(t *testing.T) {
+	keyTemplate := aead.AES128CTRHMACSHA256KeyTemplate()
+	aeadKeyFormat := new(ctrhmacpb.AesCtrHmacAeadKeyFormat)
+	if err := proto.Unmarshal(keyTemplate.Value, aeadKeyFormat); err != nil {
+		t.Fatalf("cannot unmarshal AES128CTRHMACSHA256 key template")
+	}
+
+	keyManager, err := registry.GetKeyManager(testutil.AESCTRHMACAEADTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain AES-CTR-HMAC-AEAD key manager: %s", err)
+	}
+
+	keys := make(map[string]bool)
+	const numTests = 24
+	for i := 0; i < numTests/2; i++ {
+		k, _ := keyManager.NewKey(keyTemplate.Value)
+		sk, err := proto.Marshal(k)
+		if err != nil {
+			t.Fatalf("cannot serialize key, error: %v", err)
+		}
+
+		key := new(ctrhmacpb.AesCtrHmacAeadKey)
+		proto.Unmarshal(sk, key)
+
+		keys[string(key.AesCtrKey.KeyValue)] = true
+		keys[string(key.HmacKey.KeyValue)] = true
+		if len(key.AesCtrKey.KeyValue) != 16 {
+			t.Errorf("unexpected AES key size, got: %d, want: 16", len(key.AesCtrKey.KeyValue))
+		}
+		if len(key.HmacKey.KeyValue) != 32 {
+			t.Errorf("unexpected HMAC key size, got: %d, want: 32", len(key.HmacKey.KeyValue))
+		}
+	}
+	if len(keys) != numTests {
+		t.Errorf("unexpected number of keys in set, got: %d, want: %d", len(keys), numTests)
+	}
+}
+
+func TestNewKeyWithCorruptedFormat(t *testing.T) {
+	keyTemplate := new(tinkpb.KeyTemplate)
+
+	keyTemplate.TypeUrl = testutil.AESCTRHMACAEADTypeURL
+	keyTemplate.Value = make([]byte, 128)
+
+	keyManager, err := registry.GetKeyManager(testutil.AESCTRHMACAEADTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain AES-CTR-HMAC-AEAD key manager: %s", err)
+	}
+
+	_, err = keyManager.NewKey(keyTemplate.Value)
+	if err == nil {
+		t.Error("NewKey got: success, want: error due to corrupted format")
+	}
+
+	_, err = keyManager.NewKeyData(keyTemplate.Value)
+	if err == nil {
+		t.Error("NewKeyData got: success, want: error due to corrupted format")
+	}
+}
diff --git a/go/aead/aes_gcm_key_manager.go b/go/aead/aes_gcm_key_manager.go
index 283fe03..f53377d 100644
--- a/go/aead/aes_gcm_key_manager.go
+++ b/go/aead/aes_gcm_key_manager.go
@@ -18,100 +18,78 @@
 	"fmt"
 
 	"github.com/golang/protobuf/proto"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/registry"
 	"github.com/google/tink/go/subtle/aead"
 	"github.com/google/tink/go/subtle/random"
-	"github.com/google/tink/go/tink"
 	gcmpb "github.com/google/tink/proto/aes_gcm_go_proto"
 	tinkpb "github.com/google/tink/proto/tink_go_proto"
 )
 
 const (
-	// AesGcmKeyVersion is the maxmimal version of keys that this key manager supports.
-	AesGcmKeyVersion = 0
-
-	// AesGcmTypeURL is the url that this key manager supports.
-	AesGcmTypeURL = "type.googleapis.com/google.crypto.tink.AesGcmKey"
+	aesGCMKeyVersion = 0
+	aesGCMTypeURL    = "type.googleapis.com/google.crypto.tink.AesGcmKey"
 )
 
 // common errors
-var errInvalidAesGcmKey = fmt.Errorf("aes_gcm_key_manager: invalid key")
-var errInvalidAesGcmKeyFormat = fmt.Errorf("aes_gcm_key_manager: invalid key format")
+var errInvalidAESGCMKey = fmt.Errorf("aes_gcm_key_manager: invalid key")
+var errInvalidAESGCMKeyFormat = fmt.Errorf("aes_gcm_key_manager: invalid key format")
 
-// AesGcmKeyManager is an implementation of KeyManager interface.
-// It generates new AesGcmKey keys and produces new instances of AesGcm subtle.
-type AesGcmKeyManager struct{}
+// aesGCMKeyManager is an implementation of KeyManager interface.
+// It generates new AESGCMKey keys and produces new instances of AESGCM subtle.
+type aesGCMKeyManager struct{}
 
-// Assert that aesGcmKeyManager implements the KeyManager interface.
-var _ tink.KeyManager = (*AesGcmKeyManager)(nil)
+// Assert that aesGCMKeyManager implements the KeyManager interface.
+var _ registry.KeyManager = (*aesGCMKeyManager)(nil)
 
-// NewAesGcmKeyManager creates a new aesGcmKeyManager.
-func NewAesGcmKeyManager() *AesGcmKeyManager {
-	return new(AesGcmKeyManager)
+// newAESGCMKeyManager creates a new aesGcmKeyManager.
+func newAESGCMKeyManager() *aesGCMKeyManager {
+	return new(aesGCMKeyManager)
 }
 
-// GetPrimitiveFromSerializedKey creates an AesGcm subtle for the given
-// serialized AesGcmKey proto.
-func (km *AesGcmKeyManager) GetPrimitiveFromSerializedKey(serializedKey []byte) (interface{}, error) {
+// Primitive creates an AESGCM subtle for the given serialized AESGCMKey proto.
+func (km *aesGCMKeyManager) Primitive(serializedKey []byte) (interface{}, error) {
 	if len(serializedKey) == 0 {
-		return nil, errInvalidAesGcmKey
+		return nil, errInvalidAESGCMKey
 	}
 	key := new(gcmpb.AesGcmKey)
 	if err := proto.Unmarshal(serializedKey, key); err != nil {
-		return nil, errInvalidAesGcmKey
-	}
-	return km.GetPrimitiveFromKey(key)
-}
-
-// GetPrimitiveFromKey creates an AesGcm subtle for the given AesGcmKey proto.
-func (km *AesGcmKeyManager) GetPrimitiveFromKey(m proto.Message) (interface{}, error) {
-	key, ok := m.(*gcmpb.AesGcmKey)
-	if !ok {
-		return nil, errInvalidAesGcmKey
+		return nil, errInvalidAESGCMKey
 	}
 	if err := km.validateKey(key); err != nil {
 		return nil, err
 	}
-	ret, err := aead.NewAesGcm(key.KeyValue)
+	ret, err := aead.NewAESGCM(key.KeyValue)
 	if err != nil {
 		return nil, fmt.Errorf("aes_gcm_key_manager: cannot create new primitive: %s", err)
 	}
 	return ret, nil
 }
 
-// NewKeyFromSerializedKeyFormat creates a new key according to specification
-// the given serialized AesGcmKeyFormat.
-func (km *AesGcmKeyManager) NewKeyFromSerializedKeyFormat(serializedKeyFormat []byte) (proto.Message, error) {
+// NewKey creates a new key according to specification the given serialized AESGCMKeyFormat.
+func (km *aesGCMKeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) {
 	if len(serializedKeyFormat) == 0 {
-		return nil, errInvalidAesGcmKeyFormat
+		return nil, errInvalidAESGCMKeyFormat
 	}
 	keyFormat := new(gcmpb.AesGcmKeyFormat)
 	if err := proto.Unmarshal(serializedKeyFormat, keyFormat); err != nil {
-		return nil, errInvalidAesGcmKeyFormat
-	}
-	return km.NewKeyFromKeyFormat(keyFormat)
-}
-
-// NewKeyFromKeyFormat creates a new key according to specification in the
-// given AesGcmKeyFormat.
-func (km *AesGcmKeyManager) NewKeyFromKeyFormat(m proto.Message) (proto.Message, error) {
-	keyFormat, ok := m.(*gcmpb.AesGcmKeyFormat)
-	if !ok {
-		return nil, errInvalidAesGcmKeyFormat
+		return nil, errInvalidAESGCMKeyFormat
 	}
 	if err := km.validateKeyFormat(keyFormat); err != nil {
 		return nil, fmt.Errorf("aes_gcm_key_manager: invalid key format: %s", err)
 	}
 	keyValue := random.GetRandomBytes(keyFormat.KeySize)
 	return &gcmpb.AesGcmKey{
-		Version:  AesGcmKeyVersion,
+		Version:  aesGCMKeyVersion,
 		KeyValue: keyValue,
 	}, nil
 }
 
-// NewKeyData creates a new KeyData according to specification in  the given
-// serialized AesGcmKeyFormat. It should be used solely by the key management API.
-func (km *AesGcmKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) {
-	key, err := km.NewKeyFromSerializedKeyFormat(serializedKeyFormat)
+// NewKeyData creates a new KeyData according to specification in the given serialized
+// AESGCMKeyFormat.
+// It should be used solely by the key management API.
+func (km *aesGCMKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) {
+	key, err := km.NewKey(serializedKeyFormat)
 	if err != nil {
 		return nil, err
 	}
@@ -120,38 +98,38 @@
 		return nil, err
 	}
 	return &tinkpb.KeyData{
-		TypeUrl:         AesGcmTypeURL,
+		TypeUrl:         aesGCMTypeURL,
 		Value:           serializedKey,
 		KeyMaterialType: tinkpb.KeyData_SYMMETRIC,
 	}, nil
 }
 
 // DoesSupport indicates if this key manager supports the given key type.
-func (km *AesGcmKeyManager) DoesSupport(typeURL string) bool {
-	return typeURL == AesGcmTypeURL
+func (km *aesGCMKeyManager) DoesSupport(typeURL string) bool {
+	return typeURL == aesGCMTypeURL
 }
 
-// GetKeyType returns the key type of keys managed by this key manager.
-func (km *AesGcmKeyManager) GetKeyType() string {
-	return AesGcmTypeURL
+// TypeURL returns the key type of keys managed by this key manager.
+func (km *aesGCMKeyManager) TypeURL() string {
+	return aesGCMTypeURL
 }
 
-// validateKey validates the given AesGcmKey.
-func (km *AesGcmKeyManager) validateKey(key *gcmpb.AesGcmKey) error {
-	err := tink.ValidateVersion(key.Version, AesGcmKeyVersion)
+// validateKey validates the given AESGCMKey.
+func (km *aesGCMKeyManager) validateKey(key *gcmpb.AesGcmKey) error {
+	err := keyset.ValidateKeyVersion(key.Version, aesGCMKeyVersion)
 	if err != nil {
 		return fmt.Errorf("aes_gcm_key_manager: %s", err)
 	}
 	keySize := uint32(len(key.KeyValue))
-	if err := aead.ValidateAesKeySize(keySize); err != nil {
+	if err := aead.ValidateAESKeySize(keySize); err != nil {
 		return fmt.Errorf("aes_gcm_key_manager: %s", err)
 	}
 	return nil
 }
 
-// validateKeyFormat validates the given AesGcmKeyFormat.
-func (km *AesGcmKeyManager) validateKeyFormat(format *gcmpb.AesGcmKeyFormat) error {
-	if err := aead.ValidateAesKeySize(format.KeySize); err != nil {
+// validateKeyFormat validates the given AESGCMKeyFormat.
+func (km *aesGCMKeyManager) validateKeyFormat(format *gcmpb.AesGcmKeyFormat) error {
+	if err := aead.ValidateAESKeySize(format.KeySize); err != nil {
 		return fmt.Errorf("aes_gcm_key_manager: %s", err)
 	}
 	return nil
diff --git a/go/aead/aes_gcm_key_manager_test.go b/go/aead/aes_gcm_key_manager_test.go
index 7d21624..1b50a7d 100644
--- a/go/aead/aes_gcm_key_manager_test.go
+++ b/go/aead/aes_gcm_key_manager_test.go
@@ -20,85 +20,73 @@
 	"testing"
 
 	"github.com/golang/protobuf/proto"
-	"github.com/google/tink/go/aead"
-	subteAead "github.com/google/tink/go/subtle/aead"
+	"github.com/google/tink/go/registry"
+	subteAEAD "github.com/google/tink/go/subtle/aead"
 	"github.com/google/tink/go/subtle/random"
 	"github.com/google/tink/go/testutil"
 	gcmpb "github.com/google/tink/proto/aes_gcm_go_proto"
 	tinkpb "github.com/google/tink/proto/tink_go_proto"
 )
 
-var keySizes = []uint32{16, 24, 32}
+var keySizes = []uint32{16, 32}
 
-func TestNewAesGcmKeyManager(t *testing.T) {
-	keyManager := aead.NewAesGcmKeyManager()
-	if keyManager == nil {
-		t.Errorf("NewAesGcmKeyManager() returns nil")
+func TestAESGCMGetPrimitiveBasic(t *testing.T) {
+	keyManager, err := registry.GetKeyManager(testutil.AESGCMTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain AES-GCM key manager: %s", err)
 	}
-}
-
-func TestAesGcmGetPrimitiveBasic(t *testing.T) {
-	keyManager := aead.NewAesGcmKeyManager()
 	for _, keySize := range keySizes {
-		key := testutil.NewAesGcmKey(uint32(keySize))
-		p, err := keyManager.GetPrimitiveFromKey(key)
-		if err != nil {
-			t.Errorf("unexpected error: %s", err)
-		}
-		if err := validateAesGcmPrimitive(p, key); err != nil {
-			t.Errorf("%s", err)
-		}
-
+		key := testutil.NewAESGCMKey(testutil.AESGCMKeyVersion, uint32(keySize))
 		serializedKey, _ := proto.Marshal(key)
-		p, err = keyManager.GetPrimitiveFromSerializedKey(serializedKey)
+		p, err := keyManager.Primitive(serializedKey)
 		if err != nil {
 			t.Errorf("unexpected error: %s", err)
 		}
-		if err := validateAesGcmPrimitive(p, key); err != nil {
+		if err := validateAESGCMPrimitive(p, key); err != nil {
 			t.Errorf("%s", err)
 		}
 	}
 }
 
-func TestAesGcmGetPrimitiveWithInvalidInput(t *testing.T) {
-	keyManager := aead.NewAesGcmKeyManager()
-	// invalid AesGcmKey
-	testKeys := genInvalidAesGcmKeys()
+func TestAESGCMGetPrimitiveWithInvalidInput(t *testing.T) {
+	keyManager, err := registry.GetKeyManager(testutil.AESGCMTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain AES-GCM key manager: %s", err)
+	}
+	// invalid AESGCMKey
+	testKeys := genInvalidAESGCMKeys()
 	for i := 0; i < len(testKeys); i++ {
-		if _, err := keyManager.GetPrimitiveFromKey(testKeys[i]); err == nil {
-			t.Errorf("expect an error in test case %d", i)
-		}
 		serializedKey, _ := proto.Marshal(testKeys[i])
-		if _, err := keyManager.GetPrimitiveFromSerializedKey(serializedKey); err == nil {
+		if _, err := keyManager.Primitive(serializedKey); err == nil {
 			t.Errorf("expect an error in test case %d", i)
 		}
 	}
 	// nil
-	if _, err := keyManager.GetPrimitiveFromKey(nil); err == nil {
-		t.Errorf("expect an error when input is nil")
-	}
-	if _, err := keyManager.GetPrimitiveFromSerializedKey(nil); err == nil {
+	if _, err := keyManager.Primitive(nil); err == nil {
 		t.Errorf("expect an error when input is nil")
 	}
 	// empty array
-	if _, err := keyManager.GetPrimitiveFromSerializedKey([]byte{}); err == nil {
+	if _, err := keyManager.Primitive([]byte{}); err == nil {
 		t.Errorf("expect an error when input is empty")
 	}
 }
 
-func TestAesGcmNewKeyMultipleTimes(t *testing.T) {
-	keyManager := aead.NewAesGcmKeyManager()
-	format := aead.NewAesGcmKeyFormat(32)
+func TestAESGCMNewKeyMultipleTimes(t *testing.T) {
+	keyManager, err := registry.GetKeyManager(testutil.AESGCMTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain AES-GCM key manager: %s", err)
+	}
+	format := testutil.NewAESGCMKeyFormat(32)
 	serializedFormat, _ := proto.Marshal(format)
 	keys := make(map[string]bool)
 	nTest := 26
 	for i := 0; i < nTest; i++ {
-		key, _ := keyManager.NewKeyFromSerializedKeyFormat(serializedFormat)
+		key, _ := keyManager.NewKey(serializedFormat)
 		serializedKey, _ := proto.Marshal(key)
 		keys[string(serializedKey)] = true
 
-		key, _ = keyManager.NewKeyFromKeyFormat(format)
-		serializedKey, _ = proto.Marshal(key)
+		keyData, _ := keyManager.NewKeyData(serializedFormat)
+		serializedKey = keyData.Value
 		keys[string(serializedKey)] = true
 	}
 	if len(keys) != nTest*2 {
@@ -106,67 +94,61 @@
 	}
 }
 
-func TestAesGcmNewKeyBasic(t *testing.T) {
-	keyManager := aead.NewAesGcmKeyManager()
+func TestAESGCMNewKeyBasic(t *testing.T) {
+	keyManager, err := registry.GetKeyManager(testutil.AESGCMTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain AES-GCM key manager: %s", err)
+	}
 	for _, keySize := range keySizes {
-		format := aead.NewAesGcmKeyFormat(uint32(keySize))
-		m, err := keyManager.NewKeyFromKeyFormat(format)
+		format := testutil.NewAESGCMKeyFormat(uint32(keySize))
+		serializedFormat, _ := proto.Marshal(format)
+		m, err := keyManager.NewKey(serializedFormat)
 		if err != nil {
 			t.Errorf("unexpected error: %s", err)
 		}
 		key := m.(*gcmpb.AesGcmKey)
-		if err := validateAesGcmKey(key, format); err != nil {
-			t.Errorf("%s", err)
-		}
-
-		serializedFormat, _ := proto.Marshal(format)
-		m, err = keyManager.NewKeyFromSerializedKeyFormat(serializedFormat)
-		if err != nil {
-			t.Errorf("unexpected error: %s", err)
-		}
-		key = m.(*gcmpb.AesGcmKey)
-		if err := validateAesGcmKey(key, format); err != nil {
+		if err := validateAESGCMKey(key, format); err != nil {
 			t.Errorf("%s", err)
 		}
 	}
 }
 
-func TestAesGcmNewKeyWithInvalidInput(t *testing.T) {
-	keyManager := aead.NewAesGcmKeyManager()
+func TestAESGCMNewKeyWithInvalidInput(t *testing.T) {
+	keyManager, err := registry.GetKeyManager(testutil.AESGCMTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain AES-GCM key manager: %s", err)
+	}
 	// bad format
-	badFormats := genInvalidAesGcmKeyFormats()
+	badFormats := genInvalidAESGCMKeyFormats()
 	for i := 0; i < len(badFormats); i++ {
-		if _, err := keyManager.NewKeyFromKeyFormat(badFormats[i]); err == nil {
-			t.Errorf("expect an error in test case %d", i)
-		}
 		serializedFormat, _ := proto.Marshal(badFormats[i])
-		if _, err := keyManager.NewKeyFromSerializedKeyFormat(serializedFormat); err == nil {
+		if _, err := keyManager.NewKey(serializedFormat); err == nil {
 			t.Errorf("expect an error in test case %d", i)
 		}
 	}
 	// nil
-	if _, err := keyManager.NewKeyFromKeyFormat(nil); err == nil {
-		t.Errorf("expect an error when input is nil")
-	}
-	if _, err := keyManager.NewKeyFromSerializedKeyFormat(nil); err == nil {
+	if _, err := keyManager.NewKey(nil); err == nil {
 		t.Errorf("expect an error when input is nil")
 	}
 	// empty array
-	if _, err := keyManager.NewKeyFromSerializedKeyFormat([]byte{}); err == nil {
+	if _, err := keyManager.NewKey([]byte{}); err == nil {
 		t.Errorf("expect an error when input is empty")
 	}
 }
 
-func TestAesGcmNewKeyDataBasic(t *testing.T) {
-	keyManager := aead.NewAesGcmKeyManager()
+func TestAESGCMNewKeyDataBasic(t *testing.T) {
+	keyManager, err := registry.GetKeyManager(testutil.AESGCMTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain AES-GCM key manager: %s", err)
+	}
 	for _, keySize := range keySizes {
-		format := aead.NewAesGcmKeyFormat(uint32(keySize))
+		format := testutil.NewAESGCMKeyFormat(uint32(keySize))
 		serializedFormat, _ := proto.Marshal(format)
 		keyData, err := keyManager.NewKeyData(serializedFormat)
 		if err != nil {
 			t.Errorf("unexpected error: %s", err)
 		}
-		if keyData.TypeUrl != aead.AesGcmTypeURL {
+		if keyData.TypeUrl != testutil.AESGCMTypeURL {
 			t.Errorf("incorrect type url")
 		}
 		if keyData.KeyMaterialType != tinkpb.KeyData_SYMMETRIC {
@@ -176,15 +158,18 @@
 		if err := proto.Unmarshal(keyData.Value, key); err != nil {
 			t.Errorf("incorrect key value")
 		}
-		if err := validateAesGcmKey(key, format); err != nil {
+		if err := validateAESGCMKey(key, format); err != nil {
 			t.Errorf("%s", err)
 		}
 	}
 }
 
-func TestAesGcmNewKeyDataWithInvalidInput(t *testing.T) {
-	keyManager := aead.NewAesGcmKeyManager()
-	badFormats := genInvalidAesGcmKeyFormats()
+func TestAESGCMNewKeyDataWithInvalidInput(t *testing.T) {
+	keyManager, err := registry.GetKeyManager(testutil.AESGCMTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain AES-GCM key manager: %s", err)
+	}
+	badFormats := genInvalidAESGCMKeyFormats()
 	for i := 0; i < len(badFormats); i++ {
 		serializedFormat, _ := proto.Marshal(badFormats[i])
 		if _, err := keyManager.NewKeyData(serializedFormat); err == nil {
@@ -201,64 +186,70 @@
 	}
 }
 
-func TestAesGcmDoesSupport(t *testing.T) {
-	keyManager := aead.NewAesGcmKeyManager()
-	if !keyManager.DoesSupport(aead.AesGcmTypeURL) {
-		t.Errorf("AesGcmKeyManager must support %s", aead.AesGcmTypeURL)
+func TestAESGCMDoesSupport(t *testing.T) {
+	keyManager, err := registry.GetKeyManager(testutil.AESGCMTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain AES-GCM key manager: %s", err)
+	}
+	if !keyManager.DoesSupport(testutil.AESGCMTypeURL) {
+		t.Errorf("AESGCMKeyManager must support %s", testutil.AESGCMTypeURL)
 	}
 	if keyManager.DoesSupport("some bad type") {
-		t.Errorf("AesGcmKeyManager must support only %s", aead.AesGcmTypeURL)
+		t.Errorf("AESGCMKeyManager must support only %s", testutil.AESGCMTypeURL)
 	}
 }
 
-func TestAesGcmGetKeyType(t *testing.T) {
-	keyManager := aead.NewAesGcmKeyManager()
-	if keyManager.GetKeyType() != aead.AesGcmTypeURL {
+func TestAESGCMTypeURL(t *testing.T) {
+	keyManager, err := registry.GetKeyManager(testutil.AESGCMTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain AES-GCM key manager: %s", err)
+	}
+	if keyManager.TypeURL() != testutil.AESGCMTypeURL {
 		t.Errorf("incorrect key type")
 	}
 }
 
-func genInvalidAesGcmKeys() []proto.Message {
+func genInvalidAESGCMKeys() []proto.Message {
 	return []proto.Message{
-		// not a AesGcmKey
-		aead.NewAesGcmKeyFormat(32),
+		// not a AESGCMKey
+		testutil.NewAESGCMKeyFormat(32),
 		// bad key size
-		aead.NewAesGcmKey(aead.AesGcmKeyVersion, random.GetRandomBytes(17)),
-		aead.NewAesGcmKey(aead.AesGcmKeyVersion, random.GetRandomBytes(25)),
-		aead.NewAesGcmKey(aead.AesGcmKeyVersion, random.GetRandomBytes(33)),
+		testutil.NewAESGCMKey(testutil.AESGCMKeyVersion, 17),
+		testutil.NewAESGCMKey(testutil.AESGCMKeyVersion, 25),
+		testutil.NewAESGCMKey(testutil.AESGCMKeyVersion, 33),
 		// bad version
-		aead.NewAesGcmKey(aead.AesGcmKeyVersion+1, random.GetRandomBytes(16)),
+		testutil.NewAESGCMKey(testutil.AESGCMKeyVersion+1, 16),
 	}
 }
 
-func genInvalidAesGcmKeyFormats() []proto.Message {
+func genInvalidAESGCMKeyFormats() []proto.Message {
 	return []proto.Message{
-		// not AesGcmKeyFormat
-		aead.NewAesGcmKey(aead.AesGcmKeyVersion, random.GetRandomBytes(16)),
+		// not AESGCMKeyFormat
+		testutil.NewAESGCMKey(testutil.AESGCMKeyVersion, 16),
 		// invalid key size
-		aead.NewAesGcmKeyFormat(uint32(15)),
-		aead.NewAesGcmKeyFormat(uint32(23)),
-		aead.NewAesGcmKeyFormat(uint32(31)),
+		testutil.NewAESGCMKeyFormat(uint32(15)),
+		testutil.NewAESGCMKeyFormat(uint32(23)),
+		testutil.NewAESGCMKeyFormat(uint32(31)),
 	}
 }
 
-func validateAesGcmKey(key *gcmpb.AesGcmKey, format *gcmpb.AesGcmKeyFormat) error {
+func validateAESGCMKey(key *gcmpb.AesGcmKey, format *gcmpb.AesGcmKeyFormat) error {
 	if uint32(len(key.KeyValue)) != format.KeySize {
 		return fmt.Errorf("incorrect key size")
 	}
-	if key.Version != aead.AesGcmKeyVersion {
+	if key.Version != testutil.AESGCMKeyVersion {
 		return fmt.Errorf("incorrect key version")
 	}
 	// try to encrypt and decrypt
-	p, err := subteAead.NewAesGcm(key.KeyValue)
+	p, err := subteAEAD.NewAESGCM(key.KeyValue)
 	if err != nil {
 		return fmt.Errorf("invalid key")
 	}
-	return validateAesGcmPrimitive(p, key)
+	return validateAESGCMPrimitive(p, key)
 }
 
-func validateAesGcmPrimitive(p interface{}, key *gcmpb.AesGcmKey) error {
-	cipher := p.(*subteAead.AesGcm)
+func validateAESGCMPrimitive(p interface{}, key *gcmpb.AesGcmKey) error {
+	cipher := p.(*subteAEAD.AESGCM)
 	if !bytes.Equal(cipher.Key, key.KeyValue) {
 		return fmt.Errorf("key and primitive don't match")
 	}
diff --git a/go/aead/chacha20poly1305_key_manager.go b/go/aead/chacha20poly1305_key_manager.go
new file mode 100644
index 0000000..61478dd
--- /dev/null
+++ b/go/aead/chacha20poly1305_key_manager.go
@@ -0,0 +1,122 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package aead
+
+import (
+	"fmt"
+
+	"github.com/golang/protobuf/proto"
+	"golang.org/x/crypto/chacha20poly1305"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/registry"
+	"github.com/google/tink/go/subtle/aead"
+	"github.com/google/tink/go/subtle/random"
+
+	cppb "github.com/google/tink/proto/chacha20_poly1305_go_proto"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+const (
+	chaCha20Poly1305KeyVersion = 0
+	chaCha20Poly1305TypeURL    = "type.googleapis.com/google.crypto.tink.ChaCha20Poly1305Key"
+)
+
+// Common errors.
+var errInvalidChaCha20Poly1305Key = fmt.Errorf("chacha20poly1305_key_manager: invalid key")
+var errInvalidChaCha20Poly1305KeyFormat = fmt.Errorf("chacha20poly1305_key_manager: invalid key format")
+
+// chaCha20Poly1305KeyManager is an implementation of KeyManager interface.
+// It generates new ChaCha20Poly1305Key keys and produces new instances of ChaCha20Poly1305 subtle.
+type chaCha20Poly1305KeyManager struct{}
+
+// Assert that chaCha20Poly1305KeyManager implements the KeyManager interface.
+var _ registry.KeyManager = (*chaCha20Poly1305KeyManager)(nil)
+
+// newChaCha20Poly1305KeyManager creates a new chaCha20Poly1305KeyManager.
+func newChaCha20Poly1305KeyManager() *chaCha20Poly1305KeyManager {
+	return new(chaCha20Poly1305KeyManager)
+}
+
+// Primitive creates an ChaCha20Poly1305 subtle for the given serialized ChaCha20Poly1305Key proto.
+func (km *chaCha20Poly1305KeyManager) Primitive(serializedKey []byte) (interface{}, error) {
+	if len(serializedKey) == 0 {
+		return nil, errInvalidChaCha20Poly1305Key
+	}
+	key := new(cppb.ChaCha20Poly1305Key)
+	if err := proto.Unmarshal(serializedKey, key); err != nil {
+		return nil, errInvalidChaCha20Poly1305Key
+	}
+	if err := km.validateKey(key); err != nil {
+		return nil, err
+	}
+	ret, err := aead.NewChaCha20Poly1305(key.KeyValue)
+	if err != nil {
+		return nil, fmt.Errorf("chacha20poly1305_key_manager: cannot create new primitive: %s", err)
+	}
+	return ret, nil
+}
+
+// NewKey creates a new key, ignoring the specification in the given serialized key format
+// because the key size and other params are fixed.
+func (km *chaCha20Poly1305KeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) {
+	return km.newChaCha20Poly1305Key(), nil
+}
+
+// NewKeyData creates a new KeyData ignoring the specification in the given serialized key format
+// because the key size and other params are fixed.
+// It should be used solely by the key management API.
+func (km *chaCha20Poly1305KeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) {
+	key := km.newChaCha20Poly1305Key()
+	serializedKey, err := proto.Marshal(key)
+	if err != nil {
+		return nil, err
+	}
+	return &tinkpb.KeyData{
+		TypeUrl:         chaCha20Poly1305TypeURL,
+		Value:           serializedKey,
+		KeyMaterialType: tinkpb.KeyData_SYMMETRIC,
+	}, nil
+}
+
+// DoesSupport indicates if this key manager supports the given key type.
+func (km *chaCha20Poly1305KeyManager) DoesSupport(typeURL string) bool {
+	return typeURL == chaCha20Poly1305TypeURL
+}
+
+// TypeURL returns the key type of keys managed by this key manager.
+func (km *chaCha20Poly1305KeyManager) TypeURL() string {
+	return chaCha20Poly1305TypeURL
+}
+
+func (km *chaCha20Poly1305KeyManager) newChaCha20Poly1305Key() *cppb.ChaCha20Poly1305Key {
+	keyValue := random.GetRandomBytes(chacha20poly1305.KeySize)
+	return &cppb.ChaCha20Poly1305Key{
+		Version:  chaCha20Poly1305KeyVersion,
+		KeyValue: keyValue,
+	}
+}
+
+// validateKey validates the given ChaCha20Poly1305Key.
+func (km *chaCha20Poly1305KeyManager) validateKey(key *cppb.ChaCha20Poly1305Key) error {
+	err := keyset.ValidateKeyVersion(key.Version, chaCha20Poly1305KeyVersion)
+	if err != nil {
+		return fmt.Errorf("chacha20poly1305_key_manager: %s", err)
+	}
+	keySize := uint32(len(key.KeyValue))
+	if keySize != chacha20poly1305.KeySize {
+		return fmt.Errorf("chacha20poly1305_key_manager: keySize != %d", chacha20poly1305.KeySize)
+	}
+	return nil
+}
diff --git a/go/aead/chacha20poly1305_key_manager_test.go b/go/aead/chacha20poly1305_key_manager_test.go
new file mode 100644
index 0000000..33b39b6
--- /dev/null
+++ b/go/aead/chacha20poly1305_key_manager_test.go
@@ -0,0 +1,186 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package aead_test
+
+import (
+	"bytes"
+	"fmt"
+	"testing"
+
+	"github.com/golang/protobuf/proto"
+	"golang.org/x/crypto/chacha20poly1305"
+	"github.com/google/tink/go/registry"
+	"github.com/google/tink/go/subtle/random"
+	"github.com/google/tink/go/testutil"
+
+	subteaead "github.com/google/tink/go/subtle/aead"
+	cppb "github.com/google/tink/proto/chacha20_poly1305_go_proto"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+func TestChaCha20Poly1305GetPrimitive(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.ChaCha20Poly1305TypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain ChaCha20Poly1305 key manager: %s", err)
+	}
+	m, _ := km.NewKey(nil)
+	key, _ := m.(*cppb.ChaCha20Poly1305Key)
+	serializedKey, _ := proto.Marshal(key)
+	p, err := km.Primitive(serializedKey)
+	if err != nil {
+		t.Errorf("km.Primitive(%v) = %v; want nil", serializedKey, err)
+	}
+	if err := validateChaCha20Poly1305Primitive(p, key); err != nil {
+		t.Errorf("validateChaCha20Poly1305Primitive(p, key) = %v; want nil", err)
+	}
+}
+
+func TestChaCha20Poly1305GetPrimitiveWithInvalidKeys(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.ChaCha20Poly1305TypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain ChaCha20Poly1305 key manager: %s", err)
+	}
+	invalidKeys := genInvalidChaCha20Poly1305Keys()
+	for _, key := range invalidKeys {
+		serializedKey, _ := proto.Marshal(key)
+		if _, err := km.Primitive(serializedKey); err == nil {
+			t.Errorf("km.Primitive(%v) = _, nil; want _, err", serializedKey)
+		}
+	}
+}
+
+func TestChaCha20Poly1305NewKey(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.ChaCha20Poly1305TypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain ChaCha20Poly1305 key manager: %s", err)
+	}
+	m, err := km.NewKey(nil)
+	if err != nil {
+		t.Errorf("km.NewKey(nil) = _, %v; want _, nil", err)
+	}
+	key, _ := m.(*cppb.ChaCha20Poly1305Key)
+	if err := validateChaCha20Poly1305Key(key); err != nil {
+		t.Errorf("validateChaCha20Poly1305Key(%v) = %v; want nil", key, err)
+	}
+}
+
+func TestChaCha20Poly1305NewKeyData(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.ChaCha20Poly1305TypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain ChaCha20Poly1305 key manager: %s", err)
+	}
+	kd, err := km.NewKeyData(nil)
+	if err != nil {
+		t.Errorf("km.NewKeyData(nil) = _, %v; want _, nil", err)
+	}
+	if kd.TypeUrl != testutil.ChaCha20Poly1305TypeURL {
+		t.Errorf("TypeUrl: %v != %v", kd.TypeUrl, testutil.ChaCha20Poly1305TypeURL)
+	}
+	if kd.KeyMaterialType != tinkpb.KeyData_SYMMETRIC {
+		t.Errorf("KeyMaterialType: %v != SYMMETRIC", kd.KeyMaterialType)
+	}
+	key := new(cppb.ChaCha20Poly1305Key)
+	if err := proto.Unmarshal(kd.Value, key); err != nil {
+		t.Errorf("proto.Unmarshal(%v, key) = %v; want nil", kd.Value, err)
+	}
+	if err := validateChaCha20Poly1305Key(key); err != nil {
+		t.Errorf("validateChaCha20Poly1305Key(%v) = %v; want nil", key, err)
+	}
+}
+
+func TestChaCha20Poly1305DoesSupport(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.ChaCha20Poly1305TypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain ChaCha20Poly1305 key manager: %s", err)
+	}
+	if !km.DoesSupport(testutil.ChaCha20Poly1305TypeURL) {
+		t.Errorf("ChaCha20Poly1305KeyManager must support %s", testutil.ChaCha20Poly1305TypeURL)
+	}
+	if km.DoesSupport("some bad type") {
+		t.Errorf("ChaCha20Poly1305KeyManager must only support %s", testutil.ChaCha20Poly1305TypeURL)
+	}
+}
+
+func TestChaCha20Poly1305TypeURL(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.ChaCha20Poly1305TypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain ChaCha20Poly1305 key manager: %s", err)
+	}
+	if kt := km.TypeURL(); kt != testutil.ChaCha20Poly1305TypeURL {
+		t.Errorf("km.TypeURL() = %s; want %s", kt, testutil.ChaCha20Poly1305TypeURL)
+	}
+}
+
+func genInvalidChaCha20Poly1305Keys() []*cppb.ChaCha20Poly1305Key {
+	return []*cppb.ChaCha20Poly1305Key{
+		// Bad key size.
+		&cppb.ChaCha20Poly1305Key{
+			Version:  testutil.ChaCha20Poly1305KeyVersion,
+			KeyValue: random.GetRandomBytes(17),
+		},
+		&cppb.ChaCha20Poly1305Key{
+			Version:  testutil.ChaCha20Poly1305KeyVersion,
+			KeyValue: random.GetRandomBytes(25),
+		},
+		&cppb.ChaCha20Poly1305Key{
+			Version:  testutil.ChaCha20Poly1305KeyVersion,
+			KeyValue: random.GetRandomBytes(33),
+		},
+		// Bad version.
+		&cppb.ChaCha20Poly1305Key{
+			Version:  testutil.ChaCha20Poly1305KeyVersion + 1,
+			KeyValue: random.GetRandomBytes(chacha20poly1305.KeySize),
+		},
+	}
+}
+
+func validateChaCha20Poly1305Primitive(p interface{}, key *cppb.ChaCha20Poly1305Key) error {
+	cipher := p.(*subteaead.ChaCha20Poly1305)
+	if !bytes.Equal(cipher.Key, key.KeyValue) {
+		return fmt.Errorf("key and primitive don't match")
+	}
+
+	// Try to encrypt and decrypt.
+	pt := random.GetRandomBytes(32)
+	aad := random.GetRandomBytes(32)
+	ct, err := cipher.Encrypt(pt, aad)
+	if err != nil {
+		return fmt.Errorf("encryption failed")
+	}
+	decrypted, err := cipher.Decrypt(ct, aad)
+	if err != nil {
+		return fmt.Errorf("decryption failed")
+	}
+	if !bytes.Equal(decrypted, pt) {
+		return fmt.Errorf("decryption failed")
+	}
+	return nil
+}
+
+func validateChaCha20Poly1305Key(key *cppb.ChaCha20Poly1305Key) error {
+	if key.Version != testutil.ChaCha20Poly1305KeyVersion {
+		return fmt.Errorf("incorrect key version: keyVersion != %d", testutil.ChaCha20Poly1305KeyVersion)
+	}
+	if uint32(len(key.KeyValue)) != chacha20poly1305.KeySize {
+		return fmt.Errorf("incorrect key size: keySize != %d", chacha20poly1305.KeySize)
+	}
+
+	// Try to encrypt and decrypt.
+	p, err := subteaead.NewChaCha20Poly1305(key.KeyValue)
+	if err != nil {
+		return fmt.Errorf("invalid key: %v", key.KeyValue)
+	}
+	return validateChaCha20Poly1305Primitive(p, key)
+}
diff --git a/go/aead/kms_envelope_aead.go b/go/aead/kms_envelope_aead.go
new file mode 100644
index 0000000..fba0a3f
--- /dev/null
+++ b/go/aead/kms_envelope_aead.go
@@ -0,0 +1,138 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package aead
+
+import (
+	"bytes"
+	"encoding/binary"
+	"errors"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/google/tink/go/registry"
+	"github.com/google/tink/go/tink"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+const (
+	lenDEK = 4
+)
+
+// KMSEnvelopeAEAD represents an instance of Envelope AEAD.
+type KMSEnvelopeAEAD struct {
+	dekTemplate *tinkpb.KeyTemplate
+	remote      tink.AEAD
+}
+
+var _ tink.AEAD = (*KMSEnvelopeAEAD)(nil)
+
+// NewKMSEnvelopeAEAD creates an new instance of KMSEnvelopeAEAD
+func NewKMSEnvelopeAEAD(kt tinkpb.KeyTemplate, remote tink.AEAD) *KMSEnvelopeAEAD {
+	return &KMSEnvelopeAEAD{
+		remote:      remote,
+		dekTemplate: &kt,
+	}
+}
+
+// Encrypt implements the tink.AEAD interface for encryption.
+func (a *KMSEnvelopeAEAD) Encrypt(pt, aad []byte) ([]byte, error) {
+	dekM, err := registry.NewKey(a.dekTemplate)
+	if err != nil {
+		return nil, err
+	}
+	dek, err := proto.Marshal(dekM)
+	if err != nil {
+		return nil, err
+	}
+	encryptedDEK, err := a.remote.Encrypt(dek, []byte{0})
+	if err != nil {
+		return nil, err
+	}
+	p, err := registry.Primitive(a.dekTemplate.TypeUrl, dek)
+	if err != nil {
+		return nil, err
+	}
+	primitive, ok := p.(tink.AEAD)
+	if !ok {
+		return nil, errors.New("failed to convert AEAD primitive")
+	}
+
+	payload, err := primitive.Encrypt(pt, aad)
+	if err != nil {
+		return nil, err
+	}
+	return buildCipherText(encryptedDEK, payload)
+
+}
+
+// Decrypt implements the tink.AEAD interface for decryption.
+func (a *KMSEnvelopeAEAD) Decrypt(ct, aad []byte) ([]byte, error) {
+	b := bytes.NewBuffer(ct)
+	bLen := b.Len()
+	l := make([]byte, lenDEK)
+	_, err := b.Read(l)
+	ed, err := binary.ReadVarint(bytes.NewReader(l))
+	if err != nil {
+		return nil, errors.New("invalid ciphertext")
+	}
+	if ed <= 0 || ed > int64(len(ct)-lenDEK) {
+		return nil, errors.New("invalid ciphertext")
+	}
+	encryptedDEK := make([]byte, ed)
+	n, err := b.Read(encryptedDEK)
+	if err != nil || int64(n) != ed {
+		return nil, errors.New("invalid ciphertext")
+	}
+	pl := bLen - lenDEK - int(ed)
+	payload := make([]byte, pl)
+	n, err = b.Read(payload)
+	if err != nil || n != pl {
+		return nil, errors.New("invalid ciphertext")
+	}
+
+	dek, err := a.remote.Decrypt(encryptedDEK, []byte{0})
+	if err != nil {
+		return nil, errors.New("decryption failed")
+	}
+	p, err := registry.Primitive(a.dekTemplate.TypeUrl, dek)
+	if err != nil {
+		return nil, errors.New("decryption failed")
+	}
+	primitive, ok := p.(tink.AEAD)
+	if !ok {
+		return nil, errors.New("failed to convert AEAD primitive")
+	}
+	return primitive.Decrypt(payload, aad)
+}
+
+// buildCipherText builds the cipher text by appending the length DEK, encrypted DEK
+// and the encrypted payload.
+func buildCipherText(encryptedDEK, payload []byte) ([]byte, error) {
+	buf := make([]byte, lenDEK)
+	var b bytes.Buffer
+	_ = binary.PutVarint(buf, int64(len(encryptedDEK)))
+	_, err := b.Write(buf)
+	if err != nil {
+		return nil, err
+	}
+	_, err = b.Write(encryptedDEK)
+	if err != nil {
+		return nil, err
+	}
+	_, err = b.Write(payload)
+	if err != nil {
+		return nil, err
+	}
+	return b.Bytes(), nil
+}
diff --git a/go/aead/kms_envelope_aead_key_manager.go b/go/aead/kms_envelope_aead_key_manager.go
new file mode 100644
index 0000000..da75bde
--- /dev/null
+++ b/go/aead/kms_envelope_aead_key_manager.go
@@ -0,0 +1,121 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package aead
+
+import (
+	"errors"
+	"fmt"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/registry"
+	kmsepb "github.com/google/tink/proto/kms_envelope_go_proto"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+const (
+	kmsEnvelopeAEADKeyVersion = 0
+	kmsEnvelopeAEADTypeURL    = "type.googleapis.com/google.crypto.tink.KmsEnvelopeAeadKey"
+)
+
+// kmsEnvelopeAEADKeyManager is an implementation of KeyManager interface.
+// It generates new KMSEnvelopeAEADKey keys and produces new instances of KMSEnvelopeAEAD subtle.
+type kmsEnvelopeAEADKeyManager struct{}
+
+// Assert that kmsEnvelopeAEADKeyManager implements the KeyManager interface.
+var _ registry.KeyManager = (*kmsEnvelopeAEADKeyManager)(nil)
+
+// newKMSEnvelopeAEADKeyManager creates a new aesGcmKeyManager.
+func newKMSEnvelopeAEADKeyManager() *kmsEnvelopeAEADKeyManager {
+	return new(kmsEnvelopeAEADKeyManager)
+}
+
+// Primitive creates an KMSEnvelopeAEAD subtle for the given serialized KMSEnvelopeAEADKey proto.
+func (km *kmsEnvelopeAEADKeyManager) Primitive(serializedKey []byte) (interface{}, error) {
+	if len(serializedKey) == 0 {
+		return nil, errors.New("kms_envelope_aead_key_manager: invalid key")
+	}
+	key := new(kmsepb.KmsEnvelopeAeadKey)
+	if err := proto.Unmarshal(serializedKey, key); err != nil {
+		return nil, errors.New("kms_envelope_aead_key_manager: invalid key")
+	}
+	if err := km.validateKey(key); err != nil {
+		return nil, err
+	}
+	uri := key.Params.KekUri
+	kmsClient, err := registry.GetKMSClient(uri)
+	if err != nil {
+		return nil, err
+	}
+	backend, err := kmsClient.GetAEAD(uri)
+	if err != nil {
+		return nil, errors.New("kms_envelope_aead_key_manager: invalid aead backend")
+	}
+
+	return NewKMSEnvelopeAEAD(*key.Params.DekTemplate, backend), nil
+}
+
+// NewKey creates a new key according to specification the given serialized KMSEnvelopeAEADKeyFormat.
+func (km *kmsEnvelopeAEADKeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) {
+	if len(serializedKeyFormat) == 0 {
+		return nil, errors.New("kms_envelope_aead_key_manager: invalid key format")
+	}
+	keyFormat := new(kmsepb.KmsEnvelopeAeadKeyFormat)
+	if err := proto.Unmarshal(serializedKeyFormat, keyFormat); err != nil {
+		return nil, errors.New("kms_envelope_aead_key_manager: invalid key format")
+	}
+	return &kmsepb.KmsEnvelopeAeadKey{
+		Version: kmsEnvelopeAEADKeyVersion,
+		Params:  keyFormat,
+	}, nil
+}
+
+// NewKeyData creates a new KeyData according to specification in the given serialized
+// KMSEnvelopeAEADKeyFormat.
+// It should be used solely by the key management API.
+func (km *kmsEnvelopeAEADKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) {
+	key, err := km.NewKey(serializedKeyFormat)
+	if err != nil {
+		return nil, err
+	}
+	serializedKey, err := proto.Marshal(key)
+	if err != nil {
+		return nil, err
+	}
+	return &tinkpb.KeyData{
+		TypeUrl:         kmsEnvelopeAEADTypeURL,
+		Value:           serializedKey,
+		KeyMaterialType: tinkpb.KeyData_REMOTE,
+	}, nil
+}
+
+// DoesSupport indicates if this key manager supports the given key type.
+func (km *kmsEnvelopeAEADKeyManager) DoesSupport(typeURL string) bool {
+	return typeURL == kmsEnvelopeAEADTypeURL
+}
+
+// TypeURL returns the key type of keys managed by this key manager.
+func (km *kmsEnvelopeAEADKeyManager) TypeURL() string {
+	return kmsEnvelopeAEADTypeURL
+}
+
+// validateKey validates the given KmsEnvelopeAeadKey.
+func (km *kmsEnvelopeAEADKeyManager) validateKey(key *kmsepb.KmsEnvelopeAeadKey) error {
+	err := keyset.ValidateKeyVersion(key.Version, kmsEnvelopeAEADKeyVersion)
+	if err != nil {
+		return fmt.Errorf("kms_envelope_aead_key_manager: %s", err)
+	}
+	return nil
+}
diff --git a/go/aead/proto_util.go b/go/aead/proto_util.go
deleted file mode 100644
index f9ffad4..0000000
--- a/go/aead/proto_util.go
+++ /dev/null
@@ -1,34 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package aead
-
-import (
-	gcmpb "github.com/google/tink/proto/aes_gcm_go_proto"
-)
-
-// NewAesGcmKey returns a new AesGcmKey.
-func NewAesGcmKey(version uint32, keyValue []byte) *gcmpb.AesGcmKey {
-	return &gcmpb.AesGcmKey{
-		Version:  version,
-		KeyValue: keyValue,
-	}
-}
-
-// NewAesGcmKeyFormat returns a new AesGcmKeyFormat.
-func NewAesGcmKeyFormat(keySize uint32) *gcmpb.AesGcmKeyFormat {
-	return &gcmpb.AesGcmKeyFormat{
-		KeySize: keySize,
-	}
-}
diff --git a/go/aead/xchacha20poly1305_key_manager.go b/go/aead/xchacha20poly1305_key_manager.go
new file mode 100644
index 0000000..0f27135
--- /dev/null
+++ b/go/aead/xchacha20poly1305_key_manager.go
@@ -0,0 +1,121 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package aead
+
+import (
+	"fmt"
+
+	"github.com/golang/protobuf/proto"
+	"golang.org/x/crypto/chacha20poly1305"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/registry"
+	"github.com/google/tink/go/subtle/aead"
+	"github.com/google/tink/go/subtle/random"
+
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+	xcppb "github.com/google/tink/proto/xchacha20_poly1305_go_proto"
+)
+
+const (
+	xChaCha20Poly1305KeyVersion = 0
+	xChaCha20Poly1305TypeURL    = "type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key"
+)
+
+// Common errors.
+var errInvalidXChaCha20Poly1305Key = fmt.Errorf("xchacha20poly1305_key_manager: invalid key")
+
+// xChaCha20Poly1305KeyManager is an implementation of KeyManager interface.
+// It generates new XChaCha20Poly1305Key keys and produces new instances of XChaCha20Poly1305 subtle.
+type xChaCha20Poly1305KeyManager struct{}
+
+// Assert that xChaCha20Poly1305KeyManager implements the KeyManager interface.
+var _ registry.KeyManager = (*xChaCha20Poly1305KeyManager)(nil)
+
+// newXChaCha20Poly1305KeyManager creates a new xChaCha20Poly1305KeyManager.
+func newXChaCha20Poly1305KeyManager() *xChaCha20Poly1305KeyManager {
+	return new(xChaCha20Poly1305KeyManager)
+}
+
+// Primitive creates an XChaCha20Poly1305 subtle for the given serialized XChaCha20Poly1305Key proto.
+func (km *xChaCha20Poly1305KeyManager) Primitive(serializedKey []byte) (interface{}, error) {
+	if len(serializedKey) == 0 {
+		return nil, errInvalidXChaCha20Poly1305Key
+	}
+	key := new(xcppb.XChaCha20Poly1305Key)
+	if err := proto.Unmarshal(serializedKey, key); err != nil {
+		return nil, errInvalidXChaCha20Poly1305Key
+	}
+	if err := km.validateKey(key); err != nil {
+		return nil, err
+	}
+	ret, err := aead.NewXChaCha20Poly1305(key.KeyValue)
+	if err != nil {
+		return nil, fmt.Errorf("xchacha20poly1305_key_manager: cannot create new primitive: %s", err)
+	}
+	return ret, nil
+}
+
+// NewKey creates a new key, ignoring the specification in the given serialized key format
+// because the key size and other params are fixed.
+func (km *xChaCha20Poly1305KeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) {
+	return km.newXChaCha20Poly1305Key(), nil
+}
+
+// NewKeyData creates a new KeyData, ignoring the specification in the given serialized key format
+// because the key size and other params are fixed.
+// It should be used solely by the key management API.
+func (km *xChaCha20Poly1305KeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) {
+	key := km.newXChaCha20Poly1305Key()
+	serializedKey, err := proto.Marshal(key)
+	if err != nil {
+		return nil, err
+	}
+	return &tinkpb.KeyData{
+		TypeUrl:         xChaCha20Poly1305TypeURL,
+		Value:           serializedKey,
+		KeyMaterialType: tinkpb.KeyData_SYMMETRIC,
+	}, nil
+}
+
+// DoesSupport indicates if this key manager supports the given key type.
+func (km *xChaCha20Poly1305KeyManager) DoesSupport(typeURL string) bool {
+	return typeURL == xChaCha20Poly1305TypeURL
+}
+
+// TypeURL returns the key type of keys managed by this key manager.
+func (km *xChaCha20Poly1305KeyManager) TypeURL() string {
+	return xChaCha20Poly1305TypeURL
+}
+
+func (km *xChaCha20Poly1305KeyManager) newXChaCha20Poly1305Key() *xcppb.XChaCha20Poly1305Key {
+	keyValue := random.GetRandomBytes(chacha20poly1305.KeySize)
+	return &xcppb.XChaCha20Poly1305Key{
+		Version:  xChaCha20Poly1305KeyVersion,
+		KeyValue: keyValue,
+	}
+}
+
+// validateKey validates the given XChaCha20Poly1305Key.
+func (km *xChaCha20Poly1305KeyManager) validateKey(key *xcppb.XChaCha20Poly1305Key) error {
+	err := keyset.ValidateKeyVersion(key.Version, xChaCha20Poly1305KeyVersion)
+	if err != nil {
+		return fmt.Errorf("xchacha20poly1305_key_manager: %s", err)
+	}
+	keySize := uint32(len(key.KeyValue))
+	if keySize != chacha20poly1305.KeySize {
+		return fmt.Errorf("xchacha20poly1305_key_manager: keySize != %d", chacha20poly1305.KeySize)
+	}
+	return nil
+}
diff --git a/go/aead/xchacha20poly1305_key_manager_test.go b/go/aead/xchacha20poly1305_key_manager_test.go
new file mode 100644
index 0000000..30b97e2
--- /dev/null
+++ b/go/aead/xchacha20poly1305_key_manager_test.go
@@ -0,0 +1,186 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package aead_test
+
+import (
+	"bytes"
+	"fmt"
+	"testing"
+
+	"github.com/golang/protobuf/proto"
+	"golang.org/x/crypto/chacha20poly1305"
+	"github.com/google/tink/go/registry"
+	"github.com/google/tink/go/subtle/random"
+	"github.com/google/tink/go/testutil"
+
+	subteaead "github.com/google/tink/go/subtle/aead"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+	xcppb "github.com/google/tink/proto/xchacha20_poly1305_go_proto"
+)
+
+func TestXChaCha20Poly1305GetPrimitive(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.XChaCha20Poly1305TypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain XChaCha20Poly1305 key manager: %s", err)
+	}
+	m, _ := km.NewKey(nil)
+	key, _ := m.(*xcppb.XChaCha20Poly1305Key)
+	serializedKey, _ := proto.Marshal(key)
+	p, err := km.Primitive(serializedKey)
+	if err != nil {
+		t.Errorf("km.Primitive(%v) = %v; want nil", serializedKey, err)
+	}
+	if err := validateXChaCha20Poly1305Primitive(p, key); err != nil {
+		t.Errorf("validateXChaCha20Poly1305Primitive(p, key) = %v; want nil", err)
+	}
+}
+
+func TestXChaCha20Poly1305GetPrimitiveWithInvalidKeys(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.XChaCha20Poly1305TypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain XChaCha20Poly1305 key manager: %s", err)
+	}
+	invalidKeys := genInvalidXChaCha20Poly1305Keys()
+	for _, key := range invalidKeys {
+		serializedKey, _ := proto.Marshal(key)
+		if _, err := km.Primitive(serializedKey); err == nil {
+			t.Errorf("km.Primitive(%v) = _, nil; want _, err", serializedKey)
+		}
+	}
+}
+
+func TestXChaCha20Poly1305NewKey(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.XChaCha20Poly1305TypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain XChaCha20Poly1305 key manager: %s", err)
+	}
+	m, err := km.NewKey(nil)
+	if err != nil {
+		t.Errorf("km.NewKey(nil) = _, %v; want _, nil", err)
+	}
+	key, _ := m.(*xcppb.XChaCha20Poly1305Key)
+	if err := validateXChaCha20Poly1305Key(key); err != nil {
+		t.Errorf("validateXChaCha20Poly1305Key(%v) = %v; want nil", key, err)
+	}
+}
+
+func TestXChaCha20Poly1305NewKeyData(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.XChaCha20Poly1305TypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain XChaCha20Poly1305 key manager: %s", err)
+	}
+	kd, err := km.NewKeyData(nil)
+	if err != nil {
+		t.Errorf("km.NewKeyData(nil) = _, %v; want _, nil", err)
+	}
+	if kd.TypeUrl != testutil.XChaCha20Poly1305TypeURL {
+		t.Errorf("TypeUrl: %v != %v", kd.TypeUrl, testutil.XChaCha20Poly1305TypeURL)
+	}
+	if kd.KeyMaterialType != tinkpb.KeyData_SYMMETRIC {
+		t.Errorf("KeyMaterialType: %v != SYMMETRIC", kd.KeyMaterialType)
+	}
+	key := new(xcppb.XChaCha20Poly1305Key)
+	if err := proto.Unmarshal(kd.Value, key); err != nil {
+		t.Errorf("proto.Unmarshal(%v, key) = %v; want nil", kd.Value, err)
+	}
+	if err := validateXChaCha20Poly1305Key(key); err != nil {
+		t.Errorf("validateXChaCha20Poly1305Key(%v) = %v; want nil", key, err)
+	}
+}
+
+func TestXChaCha20Poly1305DoesSupport(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.XChaCha20Poly1305TypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain XChaCha20Poly1305 key manager: %s", err)
+	}
+	if !km.DoesSupport(testutil.XChaCha20Poly1305TypeURL) {
+		t.Errorf("XChaCha20Poly1305KeyManager must support %s", testutil.XChaCha20Poly1305TypeURL)
+	}
+	if km.DoesSupport("some bad type") {
+		t.Errorf("XChaCha20Poly1305KeyManager must only support %s", testutil.XChaCha20Poly1305TypeURL)
+	}
+}
+
+func TestXChaCha20Poly1305TypeURL(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.XChaCha20Poly1305TypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain XChaCha20Poly1305 key manager: %s", err)
+	}
+	if kt := km.TypeURL(); kt != testutil.XChaCha20Poly1305TypeURL {
+		t.Errorf("km.TypeURL() = %s; want %s", kt, testutil.XChaCha20Poly1305TypeURL)
+	}
+}
+
+func genInvalidXChaCha20Poly1305Keys() []*xcppb.XChaCha20Poly1305Key {
+	return []*xcppb.XChaCha20Poly1305Key{
+		// Bad key size.
+		&xcppb.XChaCha20Poly1305Key{
+			Version:  testutil.XChaCha20Poly1305KeyVersion,
+			KeyValue: random.GetRandomBytes(17),
+		},
+		&xcppb.XChaCha20Poly1305Key{
+			Version:  testutil.XChaCha20Poly1305KeyVersion,
+			KeyValue: random.GetRandomBytes(25),
+		},
+		&xcppb.XChaCha20Poly1305Key{
+			Version:  testutil.XChaCha20Poly1305KeyVersion,
+			KeyValue: random.GetRandomBytes(33),
+		},
+		// Bad version.
+		&xcppb.XChaCha20Poly1305Key{
+			Version:  testutil.XChaCha20Poly1305KeyVersion + 1,
+			KeyValue: random.GetRandomBytes(chacha20poly1305.KeySize),
+		},
+	}
+}
+
+func validateXChaCha20Poly1305Primitive(p interface{}, key *xcppb.XChaCha20Poly1305Key) error {
+	cipher := p.(*subteaead.XChaCha20Poly1305)
+	if !bytes.Equal(cipher.Key, key.KeyValue) {
+		return fmt.Errorf("key and primitive don't match")
+	}
+
+	// Try to encrypt and decrypt.
+	pt := random.GetRandomBytes(32)
+	aad := random.GetRandomBytes(32)
+	ct, err := cipher.Encrypt(pt, aad)
+	if err != nil {
+		return fmt.Errorf("encryption failed")
+	}
+	decrypted, err := cipher.Decrypt(ct, aad)
+	if err != nil {
+		return fmt.Errorf("decryption failed")
+	}
+	if !bytes.Equal(decrypted, pt) {
+		return fmt.Errorf("decryption failed")
+	}
+	return nil
+}
+
+func validateXChaCha20Poly1305Key(key *xcppb.XChaCha20Poly1305Key) error {
+	if key.Version != testutil.XChaCha20Poly1305KeyVersion {
+		return fmt.Errorf("incorrect key version: keyVersion != %d", testutil.XChaCha20Poly1305KeyVersion)
+	}
+	if uint32(len(key.KeyValue)) != chacha20poly1305.KeySize {
+		return fmt.Errorf("incorrect key size: keySize != %d", chacha20poly1305.KeySize)
+	}
+
+	// Try to encrypt and decrypt.
+	p, err := subteaead.NewXChaCha20Poly1305(key.KeyValue)
+	if err != nil {
+		return fmt.Errorf("invalid key: %v", key.KeyValue)
+	}
+	return validateXChaCha20Poly1305Primitive(p, key)
+}
diff --git a/go/daead/BUILD.bazel b/go/daead/BUILD.bazel
new file mode 100644
index 0000000..bc93b6b
--- /dev/null
+++ b/go/daead/BUILD.bazel
@@ -0,0 +1,52 @@
+package(default_visibility = ["//tools/build_defs:internal_pkg"])  # keep
+
+licenses(["notice"])  # Apache 2.0 # keep
+
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+    name = "go_default_library",
+    srcs = [
+        "aes_siv_key_manager.go",
+        "daead.go",
+        "daead_factory.go",
+    ],
+    importpath = "github.com/google/tink/go/daead",
+    visibility = ["//visibility:public"],
+    deps = [
+        "//go/format:go_default_library",
+        "//go/keyset:go_default_library",
+        "//go/registry:go_default_library",
+        "//go/primitiveset:go_default_library",
+        "//go/subtle/daead:go_default_library",
+        "//go/subtle/random:go_default_library",
+        "//go/tink:go_default_library",
+        "//proto:aes_siv_go_proto",
+        "//proto:tink_go_proto",
+        "@com_github_golang_protobuf//proto:go_default_library",
+    ],
+)
+
+go_test(
+    name = "go_default_test",
+    srcs = [
+        "aes_siv_key_manager_test.go",
+        "daead_factory_test.go",
+        "daead_test.go",
+    ],
+    embed = [":go_default_library"],
+    deps = [
+        "//go/format:go_default_library",
+        "//go/keyset:go_default_library",
+        "//go/registry:go_default_library",
+        "//go/subtle/daead:go_default_library",
+        "//go/subtle/random:go_default_library",
+        "//go/testkeyset:go_default_library",
+        "//go/testutil:go_default_library",
+        "//go/tink:go_default_library",
+        "//proto:aes_siv_go_proto",
+        "//proto:tink_go_proto",
+        "@com_github_golang_protobuf//proto:go_default_library",
+    ],
+)
+
diff --git a/go/daead/aes_siv_key_manager.go b/go/daead/aes_siv_key_manager.go
new file mode 100644
index 0000000..c7c4de9
--- /dev/null
+++ b/go/daead/aes_siv_key_manager.go
@@ -0,0 +1,119 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package daead
+
+import (
+	"fmt"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/registry"
+	"github.com/google/tink/go/subtle/daead"
+	"github.com/google/tink/go/subtle/random"
+
+	aspb "github.com/google/tink/proto/aes_siv_go_proto"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+const (
+	aesSIVKeyVersion = 0
+	aesSIVTypeURL    = "type.googleapis.com/google.crypto.tink.AesSivKey"
+)
+
+// aesSIVKeyManager is an implementation of KeyManager interface.
+// It generates new AesSivKey keys and produces new instances of AESSIV subtle.
+type aesSIVKeyManager struct{}
+
+// Assert that aesSIVKeyManager implements the KeyManager interface.
+var _ registry.KeyManager = (*aesSIVKeyManager)(nil)
+
+// newAESSIVKeyManager creates a new aesSIVKeyManager.
+func newAESSIVKeyManager() *aesSIVKeyManager {
+	return new(aesSIVKeyManager)
+}
+
+// Primitive creates an AESSIV subtle for the given serialized AesSivKey proto.
+func (km *aesSIVKeyManager) Primitive(serializedKey []byte) (interface{}, error) {
+	if len(serializedKey) == 0 {
+		return nil, fmt.Errorf("aes_siv_key_manager: invalid key")
+	}
+
+	key := new(aspb.AesSivKey)
+	if err := proto.Unmarshal(serializedKey, key); err != nil {
+		return nil, err
+	}
+	if err := km.validateKey(key); err != nil {
+		return nil, err
+	}
+	ret, err := daead.NewAESSIV(key.KeyValue)
+	if err != nil {
+		return nil, fmt.Errorf("aes_siv_key_manager: cannot create new primitive: %s", err)
+	}
+	return ret, nil
+}
+
+// NewKey creates a new key, ignoring the specification in the given serialized key format
+// because the key size and other params are fixed.
+func (km *aesSIVKeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) {
+	return km.newAesSivKey(), nil
+}
+
+// NewKeyData creates a new KeyData, ignoring the specification in the given serialized key format
+// because the key size and other params are fixed.
+// It should be used solely by the key management API.
+func (km *aesSIVKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) {
+	key := km.newAesSivKey()
+	serializedKey, err := proto.Marshal(key)
+	if err != nil {
+		return nil, err
+	}
+	return &tinkpb.KeyData{
+		TypeUrl:         aesSIVTypeURL,
+		Value:           serializedKey,
+		KeyMaterialType: tinkpb.KeyData_SYMMETRIC,
+	}, nil
+}
+
+// DoesSupport indicates if this key manager supports the given key type.
+func (km *aesSIVKeyManager) DoesSupport(typeURL string) bool {
+	return typeURL == aesSIVTypeURL
+}
+
+// TypeURL returns the key type of keys managed by this key manager.
+func (km *aesSIVKeyManager) TypeURL() string {
+	return aesSIVTypeURL
+}
+
+// validateKey validates the given AesSivKey.
+func (km *aesSIVKeyManager) validateKey(key *aspb.AesSivKey) error {
+	err := keyset.ValidateKeyVersion(key.Version, aesSIVKeyVersion)
+	if err != nil {
+		return fmt.Errorf("aes_siv_key_manager: %s", err)
+	}
+	keySize := uint32(len(key.KeyValue))
+	if keySize != daead.AESSIVKeySize {
+		return fmt.Errorf("aes_siv_key_manager: keySize != %d", daead.AESSIVKeySize)
+	}
+	return nil
+}
+
+// newAesSivKey creates a new AesSivKey.
+func (km *aesSIVKeyManager) newAesSivKey() *aspb.AesSivKey {
+	keyValue := random.GetRandomBytes(daead.AESSIVKeySize)
+	return &aspb.AesSivKey{
+		Version:  aesSIVKeyVersion,
+		KeyValue: keyValue,
+	}
+}
diff --git a/go/daead/aes_siv_key_manager_test.go b/go/daead/aes_siv_key_manager_test.go
new file mode 100644
index 0000000..f07ec8f
--- /dev/null
+++ b/go/daead/aes_siv_key_manager_test.go
@@ -0,0 +1,188 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package daead_test
+
+import (
+	"bytes"
+	"fmt"
+	"testing"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/google/tink/go/registry"
+	"github.com/google/tink/go/subtle/random"
+	"github.com/google/tink/go/testutil"
+
+	subtedaead "github.com/google/tink/go/subtle/daead"
+	aspb "github.com/google/tink/proto/aes_siv_go_proto"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+func TestAESSIVPrimitive(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.AESSIVTypeURL)
+	if err != nil {
+		t.Fatalf("cannot obtain AESSIV key manager: %s", err)
+	}
+	m, err := km.NewKey(nil)
+	if err != nil {
+		t.Errorf("km.NewKey(nil) = _, %v; want _, nil", err)
+	}
+	key, _ := m.(*aspb.AesSivKey)
+	serializedKey, _ := proto.Marshal(key)
+	p, err := km.Primitive(serializedKey)
+	if err != nil {
+		t.Errorf("km.Primitive(%v) = %v; want nil", serializedKey, err)
+	}
+	if err := validateAESSIVPrimitive(p, key); err != nil {
+		t.Errorf("validateAESSIVPrimitive(p, key) = %v; want nil", err)
+	}
+}
+
+func TestAESSIVPrimitiveWithInvalidKeys(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.AESSIVTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain AESSIV key manager: %s", err)
+	}
+	invalidKeys := genInvalidAESSIVKeys()
+	for _, key := range invalidKeys {
+		serializedKey, _ := proto.Marshal(key)
+		if _, err := km.Primitive(serializedKey); err == nil {
+			t.Errorf("km.Primitive(%v) = _, nil; want _, err", serializedKey)
+		}
+	}
+}
+
+func TestAESSIVNewKey(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.AESSIVTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain AESSIV key manager: %s", err)
+	}
+	m, err := km.NewKey(nil)
+	if err != nil {
+		t.Errorf("km.NewKey(nil) = _, %v; want _, nil", err)
+	}
+	key, _ := m.(*aspb.AesSivKey)
+	if err := validateAESSIVKey(key); err != nil {
+		t.Errorf("validateAESSIVKey(%v) = %v; want nil", key, err)
+	}
+}
+
+func TestAESSIVNewKeyData(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.AESSIVTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain AESSIV key manager: %s", err)
+	}
+	kd, err := km.NewKeyData(nil)
+	if err != nil {
+		t.Errorf("km.NewKeyData(nil) = _, %v; want _, nil", err)
+	}
+	if kd.TypeUrl != testutil.AESSIVTypeURL {
+		t.Errorf("TypeUrl: %v != %v", kd.TypeUrl, testutil.AESSIVTypeURL)
+	}
+	if kd.KeyMaterialType != tinkpb.KeyData_SYMMETRIC {
+		t.Errorf("KeyMaterialType: %v != SYMMETRIC", kd.KeyMaterialType)
+	}
+	key := new(aspb.AesSivKey)
+	if err := proto.Unmarshal(kd.Value, key); err != nil {
+		t.Errorf("proto.Unmarshal(%v, key) = %v; want nil", kd.Value, err)
+	}
+	if err := validateAESSIVKey(key); err != nil {
+		t.Errorf("validateAESSIVKey(%v) = %v; want nil", key, err)
+	}
+}
+
+func TestAESSIVDoesSupport(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.AESSIVTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain AESSIV key manager: %s", err)
+	}
+	if !km.DoesSupport(testutil.AESSIVTypeURL) {
+		t.Errorf("AESSIVKeyManager must support %s", testutil.AESSIVTypeURL)
+	}
+	if km.DoesSupport("some bad type") {
+		t.Errorf("AESSIVKeyManager must only support %s", testutil.AESSIVTypeURL)
+	}
+}
+
+func TestAESSIVTypeURL(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.AESSIVTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain AESSIV key manager: %s", err)
+	}
+	if kt := km.TypeURL(); kt != testutil.AESSIVTypeURL {
+		t.Errorf("km.TypeURL() = %s; want %s", kt, testutil.AESSIVTypeURL)
+	}
+}
+
+func validateAESSIVPrimitive(p interface{}, key *aspb.AesSivKey) error {
+	cipher := p.(*subtedaead.AESSIV)
+	// try to encrypt and decrypt
+	pt := random.GetRandomBytes(32)
+	aad := random.GetRandomBytes(32)
+	ct, err := cipher.EncryptDeterministically(pt, aad)
+	if err != nil {
+		return fmt.Errorf("encryption failed")
+	}
+	decrypted, err := cipher.DecryptDeterministically(ct, aad)
+	if err != nil {
+		return fmt.Errorf("decryption failed")
+	}
+	if !bytes.Equal(decrypted, pt) {
+		return fmt.Errorf("decryption failed")
+	}
+	return nil
+}
+
+func validateAESSIVKey(key *aspb.AesSivKey) error {
+	if key.Version != testutil.AESSIVKeyVersion {
+		return fmt.Errorf("incorrect key version: keyVersion != %d", testutil.AESSIVKeyVersion)
+	}
+	if uint32(len(key.KeyValue)) != subtedaead.AESSIVKeySize {
+		return fmt.Errorf("incorrect key size: keySize != %d", subtedaead.AESSIVKeySize)
+	}
+
+	// Try to encrypt and decrypt.
+	p, err := subtedaead.NewAESSIV(key.KeyValue)
+	if err != nil {
+		return fmt.Errorf("invalid key: %v", key.KeyValue)
+	}
+	return validateAESSIVPrimitive(p, key)
+}
+
+func genInvalidAESSIVKeys() []*aspb.AesSivKey {
+	return []*aspb.AesSivKey{
+		// Bad key size.
+		&aspb.AesSivKey{
+			Version:  testutil.AESSIVKeyVersion,
+			KeyValue: random.GetRandomBytes(16),
+		},
+		&aspb.AesSivKey{
+			Version:  testutil.AESSIVKeyVersion,
+			KeyValue: random.GetRandomBytes(32),
+		},
+		&aspb.AesSivKey{
+			Version:  testutil.AESSIVKeyVersion,
+			KeyValue: random.GetRandomBytes(63),
+		},
+		&aspb.AesSivKey{
+			Version:  testutil.AESSIVKeyVersion,
+			KeyValue: random.GetRandomBytes(65),
+		},
+		// Bad version.
+		&aspb.AesSivKey{
+			Version:  testutil.AESSIVKeyVersion + 1,
+			KeyValue: random.GetRandomBytes(subtedaead.AESSIVKeySize),
+		},
+	}
+}
diff --git a/go/tink/private_key_manager.go b/go/daead/daead.go
similarity index 65%
copy from go/tink/private_key_manager.go
copy to go/daead/daead.go
index 7cf169a..4516ae2 100644
--- a/go/tink/private_key_manager.go
+++ b/go/daead/daead.go
@@ -12,16 +12,17 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-package tink
+// Package daead provides implementations of the DeterministicAEAD primitive.
+package daead
 
 import (
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
+	"fmt"
+
+	"github.com/google/tink/go/registry"
 )
 
-// PrivateKeyManager is a special type of KeyManager that understands private key types.
-type PrivateKeyManager interface {
-	KeyManager
-
-	// GetPublicKeyData extracts the public key data from the private key.
-	GetPublicKeyData(serializedKey []byte) (*tinkpb.KeyData, error)
+func init() {
+	if err := registry.RegisterKeyManager(newAESSIVKeyManager()); err != nil {
+		panic(fmt.Sprintf("daead.init() failed: %v", err))
+	}
 }
diff --git a/go/daead/daead_factory.go b/go/daead/daead_factory.go
new file mode 100644
index 0000000..d0a3068
--- /dev/null
+++ b/go/daead/daead_factory.go
@@ -0,0 +1,101 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package daead
+
+import (
+	"fmt"
+
+	"github.com/google/tink/go/format"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/primitiveset"
+	"github.com/google/tink/go/registry"
+	"github.com/google/tink/go/tink"
+)
+
+// New returns a DeterministicAEAD primitive from the given keyset handle.
+func New(h *keyset.Handle) (tink.DeterministicAEAD, error) {
+	return NewWithKeyManager(h, nil /*keyManager*/)
+}
+
+// NewWithKeyManager returns a DeterministicAEAD primitive from the given keyset handle and custom key manager.
+func NewWithKeyManager(h *keyset.Handle, km registry.KeyManager) (tink.DeterministicAEAD, error) {
+	ps, err := h.PrimitivesWithKeyManager(km)
+	if err != nil {
+		return nil, fmt.Errorf("daead_factory: cannot obtain primitive set: %s", err)
+	}
+	ret := new(primitiveSet)
+	ret.ps = ps
+	return tink.DeterministicAEAD(ret), nil
+}
+
+// primitiveSet is an DeterministicAEAD implementation that uses the underlying primitive set
+// for deterministic encryption and decryption.
+type primitiveSet struct {
+	ps *primitiveset.PrimitiveSet
+}
+
+// Asserts that primitiveSet implements the DeterministicAEAD interface.
+var _ tink.DeterministicAEAD = (*primitiveSet)(nil)
+
+// EncryptDeterministically deterministically encrypts plaintext with additionalData as additional authenticated data.
+// It returns the concatenation of the primary's identifier and the ciphertext.
+func (d *primitiveSet) EncryptDeterministically(pt, aad []byte) ([]byte, error) {
+	primary := d.ps.Primary
+	p := (primary.Primitive).(tink.DeterministicAEAD)
+	ct, err := p.EncryptDeterministically(pt, aad)
+	if err != nil {
+		return nil, err
+	}
+
+	var ret []byte
+	ret = append(ret, primary.Prefix...)
+	ret = append(ret, ct...)
+	return ret, nil
+}
+
+// DecryptDeterministically deterministically decrypts ciphertext with additionalData as
+// additional authenticated data. It returns the corresponding plaintext if the
+// ciphertext is authenticated.
+func (d *primitiveSet) DecryptDeterministically(ct, aad []byte) ([]byte, error) {
+	// try non-raw keys
+	prefixSize := format.NonRawPrefixSize
+	if len(ct) > prefixSize {
+		prefix := ct[:prefixSize]
+		ctNoPrefix := ct[prefixSize:]
+		entries, err := d.ps.EntriesForPrefix(string(prefix))
+		if err == nil {
+			for i := 0; i < len(entries); i++ {
+				p := (entries[i].Primitive).(tink.DeterministicAEAD)
+				pt, err := p.DecryptDeterministically(ctNoPrefix, aad)
+				if err == nil {
+					return pt, nil
+				}
+			}
+		}
+	}
+	// try raw keys
+	entries, err := d.ps.RawEntries()
+	if err == nil {
+		for i := 0; i < len(entries); i++ {
+			p := (entries[i].Primitive).(tink.DeterministicAEAD)
+			pt, err := p.DecryptDeterministically(ct, aad)
+			if err == nil {
+				return pt, nil
+			}
+		}
+	}
+	// nothing worked
+	return nil, fmt.Errorf("daead_factory: decryption failed")
+}
diff --git a/go/daead/daead_factory_test.go b/go/daead/daead_factory_test.go
new file mode 100644
index 0000000..2f2d4d7
--- /dev/null
+++ b/go/daead/daead_factory_test.go
@@ -0,0 +1,135 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package daead_test
+
+import (
+	"bytes"
+	"fmt"
+	"strings"
+	"testing"
+
+	"github.com/google/tink/go/daead"
+	"github.com/google/tink/go/format"
+	"github.com/google/tink/go/subtle/random"
+	"github.com/google/tink/go/testkeyset"
+	"github.com/google/tink/go/testutil"
+	"github.com/google/tink/go/tink"
+
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+func TestFactoryMultipleKeys(t *testing.T) {
+	// encrypt with non-raw key.
+	keyset := testutil.NewTestAESSIVKeyset(tinkpb.OutputPrefixType_TINK)
+	primaryKey := keyset.Key[0]
+	if primaryKey.OutputPrefixType == tinkpb.OutputPrefixType_RAW {
+		t.Errorf("expect a non-raw key")
+	}
+	keysetHandle, _ := testkeyset.NewHandle(keyset)
+	d, err := daead.New(keysetHandle)
+	if err != nil {
+		t.Errorf("daead.New failed: %s", err)
+	}
+	expectedPrefix, _ := format.OutputPrefix(primaryKey)
+	if err := validateDAEADFactoryCipher(d, d, expectedPrefix); err != nil {
+		t.Errorf("invalid cipher: %s", err)
+	}
+
+	// encrypt with a non-primary RAW key in keyset and decrypt with the keyset.
+	{
+		rawKey := keyset.Key[1]
+		if rawKey.OutputPrefixType != tinkpb.OutputPrefixType_RAW {
+			t.Errorf("expect a raw key")
+		}
+		keyset2 := testutil.NewKeyset(rawKey.KeyId, []*tinkpb.Keyset_Key{rawKey})
+		keysetHandle2, _ := testkeyset.NewHandle(keyset2)
+		d2, err := daead.New(keysetHandle2)
+		if err != nil {
+			t.Errorf("daead.New failed: %s", err)
+		}
+		if err := validateDAEADFactoryCipher(d2, d, format.RawPrefix); err != nil {
+			t.Errorf("invalid cipher: %s", err)
+		}
+	}
+
+	// encrypt with a random key from a new keyset, decrypt with the original keyset should fail.
+	{
+		keyset2 := testutil.NewTestAESSIVKeyset(tinkpb.OutputPrefixType_TINK)
+		newPK := keyset2.Key[0]
+		if newPK.OutputPrefixType == tinkpb.OutputPrefixType_RAW {
+			t.Errorf("expect a non-raw key")
+		}
+		keysetHandle2, _ := testkeyset.NewHandle(keyset2)
+		d2, err := daead.New(keysetHandle2)
+		if err != nil {
+			t.Errorf("daead.New failed: %s", err)
+		}
+		expectedPrefix, _ = format.OutputPrefix(newPK)
+		err = validateDAEADFactoryCipher(d2, d, expectedPrefix)
+		if err == nil || !strings.Contains(err.Error(), "decryption failed") {
+			t.Errorf("expect decryption to fail with random key: %s", err)
+		}
+	}
+}
+
+func TestFactoryRawKeyAsPrimary(t *testing.T) {
+	keyset := testutil.NewTestAESSIVKeyset(tinkpb.OutputPrefixType_RAW)
+	if keyset.Key[0].OutputPrefixType != tinkpb.OutputPrefixType_RAW {
+		t.Errorf("primary key is not a raw key")
+	}
+	keysetHandle, _ := testkeyset.NewHandle(keyset)
+
+	d, err := daead.New(keysetHandle)
+	if err != nil {
+		t.Errorf("cannot get primitive from keyset handle: %s", err)
+	}
+	if err := validateDAEADFactoryCipher(d, d, format.RawPrefix); err != nil {
+		t.Errorf("invalid cipher: %s", err)
+	}
+}
+
+func validateDAEADFactoryCipher(encryptCipher, decryptCipher tink.DeterministicAEAD, expectedPrefix string) error {
+	prefixSize := len(expectedPrefix)
+	// regular plaintext.
+	pt := random.GetRandomBytes(20)
+	aad := random.GetRandomBytes(20)
+	ct, err := encryptCipher.EncryptDeterministically(pt, aad)
+	if err != nil {
+		return fmt.Errorf("encryption failed with regular plaintext: %s", err)
+	}
+	decrypted, err := decryptCipher.DecryptDeterministically(ct, aad)
+	if err != nil || !bytes.Equal(decrypted, pt) {
+		return fmt.Errorf("decryption failed with regular plaintext: err: %s, pt: %s, decrypted: %s", err, pt, decrypted)
+	}
+	if string(ct[:prefixSize]) != expectedPrefix {
+		return fmt.Errorf("incorrect prefix with regular plaintext")
+	}
+
+	// short plaintext.
+	pt = random.GetRandomBytes(1)
+	ct, err = encryptCipher.EncryptDeterministically(pt, aad)
+	if err != nil {
+		return fmt.Errorf("encryption failed with short plaintext: %s", err)
+	}
+	decrypted, err = decryptCipher.DecryptDeterministically(ct, aad)
+	if err != nil || !bytes.Equal(decrypted, pt) {
+		return fmt.Errorf("decryption failed with short plaintext: err: %s, pt: %s, decrypted: %s",
+			err, pt, decrypted)
+	}
+	if string(ct[:prefixSize]) != expectedPrefix {
+		return fmt.Errorf("incorrect prefix with short plaintext")
+	}
+	return nil
+}
diff --git a/go/mac/mac_config_test.go b/go/daead/daead_test.go
similarity index 67%
copy from go/mac/mac_config_test.go
copy to go/daead/daead_test.go
index 4ea17ea..db4279e 100644
--- a/go/mac/mac_config_test.go
+++ b/go/daead/daead_test.go
@@ -12,23 +12,19 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-package mac_test
+package daead_test
 
 import (
 	"testing"
 
-	"github.com/google/tink/go/mac"
-	"github.com/google/tink/go/tink"
+	"github.com/google/tink/go/registry"
+	"github.com/google/tink/go/testutil"
 )
 
-func TestRegistration(t *testing.T) {
-	success, err := mac.RegisterStandardKeyTypes()
-	if !success || err != nil {
-		t.Errorf("cannot register standard key types")
-	}
-	keyManager, err := tink.GetKeyManager(mac.HmacTypeURL)
+func TestDeterministicAEADInit(t *testing.T) {
+	// Check for AES-SIV key manager.
+	_, err := registry.GetKeyManager(testutil.AESSIVTypeURL)
 	if err != nil {
 		t.Errorf("unexpected error: %s", err)
 	}
-	var _ = keyManager.(*mac.HmacKeyManager)
 }
diff --git a/go/format/BUILD.bazel b/go/format/BUILD.bazel
new file mode 100644
index 0000000..7d98279
--- /dev/null
+++ b/go/format/BUILD.bazel
@@ -0,0 +1,24 @@
+package(default_visibility = ["//tools/build_defs:internal_pkg"])  # keep
+
+licenses(["notice"])  # Apache 2.0 # keep
+
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+    name = "go_default_library",
+    srcs = ["format.go"],
+    importpath = "github.com/google/tink/go/format",
+    visibility = ["//visibility:public"],
+    deps = [
+        "//proto:tink_go_proto",
+    ],
+)
+
+go_test(
+    name = "go_default_test",
+    srcs = ["format_test.go"],
+    embed = [":go_default_library"],
+    deps = [
+        "//proto:tink_go_proto",
+    ],
+)
diff --git a/go/tink/crypto_format.go b/go/format/format.go
similarity index 60%
rename from go/tink/crypto_format.go
rename to go/format/format.go
index fe3946a..aab77f3 100644
--- a/go/tink/crypto_format.go
+++ b/go/format/format.go
@@ -12,7 +12,8 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-package tink
+// Package format contains constants and convenience methods that deal with ciphertext and signature format.
+package format
 
 import (
 	"encoding/binary"
@@ -21,36 +22,35 @@
 	tinkpb "github.com/google/tink/proto/tink_go_proto"
 )
 
-/**
- * Constants and convenience methods that deal with crypto format.
- */
 const (
-	// Prefix size of Tink and Legacy key types.
+	// NonRawPrefixSize is the prefix size of Tink and Legacy key types.
 	NonRawPrefixSize = 5
 
-	// Legacy or Crunchy prefix starts with \x00 and followed by a 4-byte key id.
+	// LegacyPrefixSize is the prefix size of legacy key types.
+	// The prefix starts with \x00 and followed by a 4-byte key id.
 	LegacyPrefixSize = NonRawPrefixSize
-	LegacyStartByte  = byte(0)
+	// LegacyStartByte is the first byte of the prefix of legacy key types.
+	LegacyStartByte = byte(0)
 
-	// Tink prefix starts with \x01 and followed by a 4-byte key id.
+	// TinkPrefixSize is the prefix size of Tink key types.
+	// The prefix starts with \x01 and followed by a 4-byte key id.
 	TinkPrefixSize = NonRawPrefixSize
-	TinkStartByte  = byte(1)
+	// TinkStartByte is the first byte of the prefix of Tink key types.
+	TinkStartByte = byte(1)
 
+	// RawPrefixSize is the prefix size of Raw key types.
 	// Raw prefix is empty.
 	RawPrefixSize = 0
-	RawPrefix     = ""
+	// RawPrefix is the empty prefix of Raw key types.
+	RawPrefix = ""
 )
 
 /*
-GetOutputPrefix generates the prefix of all cryptographic outputs (ciphertexts,
-signatures, MACs, ...)  produced by the specified {@code key}.
-The prefix can be either empty (for RAW-type prefix), or consists
-of a 1-byte indicator of the type of the prefix, followed by 4
-bytes of {@code key.KeyId} in Big Endian encoding.
-@throws error if the prefix type of {@code key} is unknown.
-@return a prefix.
+OutputPrefix generates the prefix of ciphertexts produced by the crypto primitive obtained from key.
+The prefix can be either empty (for RAW-type prefix), or consists of a 1-byte indicator of the type
+of the prefix, followed by 4 bytes of the key ID in big endian encoding.
 */
-func GetOutputPrefix(key *tinkpb.Keyset_Key) (string, error) {
+func OutputPrefix(key *tinkpb.Keyset_Key) (string, error) {
 	switch key.OutputPrefixType {
 	case tinkpb.OutputPrefixType_LEGACY, tinkpb.OutputPrefixType_CRUNCHY:
 		return createOutputPrefix(LegacyPrefixSize, LegacyStartByte, key.KeyId), nil
@@ -63,10 +63,6 @@
 	}
 }
 
-/**
- * Creates an output prefix. It consists of a 1-byte indicator of the type
- * of the prefix, followed by 4 bytes of {@code keyID} in Big Endian encoding.
- */
 func createOutputPrefix(size int, startByte byte, keyID uint32) string {
 	prefix := make([]byte, size)
 	prefix[0] = startByte
diff --git a/go/tink/crypto_format_test.go b/go/format/format_test.go
similarity index 76%
rename from go/tink/crypto_format_test.go
rename to go/format/format_test.go
index 9609ee2..995f6ca 100644
--- a/go/tink/crypto_format_test.go
+++ b/go/format/format_test.go
@@ -12,12 +12,12 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-package tink_test
+package format_test
 
 import (
 	"testing"
 
-	"github.com/google/tink/go/tink"
+	"github.com/google/tink/go/format"
 	tinkpb "github.com/google/tink/proto/tink_go_proto"
 )
 
@@ -39,38 +39,38 @@
 	},
 }
 
-func TestGetOutputPrefix(t *testing.T) {
+func TestOutputPrefix(t *testing.T) {
 	key := new(tinkpb.Keyset_Key)
 	for i, test := range tests {
 		key.KeyId = test.keyID
 		// legacy type
 		key.OutputPrefixType = tinkpb.OutputPrefixType_LEGACY
-		prefix, err := tink.GetOutputPrefix(key)
-		if err != nil || !validatePrefix(prefix, tink.LegacyStartByte, test.result) {
+		prefix, err := format.OutputPrefix(key)
+		if err != nil || !validatePrefix(prefix, format.LegacyStartByte, test.result) {
 			t.Errorf("incorrect legacy prefix in test %d", i)
 		}
 		// crunchy type
 		key.OutputPrefixType = tinkpb.OutputPrefixType_CRUNCHY
-		prefix, err = tink.GetOutputPrefix(key)
-		if err != nil || !validatePrefix(prefix, tink.LegacyStartByte, test.result) {
+		prefix, err = format.OutputPrefix(key)
+		if err != nil || !validatePrefix(prefix, format.LegacyStartByte, test.result) {
 			t.Errorf("incorrect legacy prefix in test %d", i)
 		}
 		// tink type
 		key.OutputPrefixType = tinkpb.OutputPrefixType_TINK
-		prefix, err = tink.GetOutputPrefix(key)
-		if err != nil || !validatePrefix(prefix, tink.TinkStartByte, test.result) {
+		prefix, err = format.OutputPrefix(key)
+		if err != nil || !validatePrefix(prefix, format.TinkStartByte, test.result) {
 			t.Errorf("incorrect tink prefix in test %d", i)
 		}
 		// raw type
 		key.OutputPrefixType = tinkpb.OutputPrefixType_RAW
-		prefix, err = tink.GetOutputPrefix(key)
-		if err != nil || prefix != tink.RawPrefix {
+		prefix, err = format.OutputPrefix(key)
+		if err != nil || prefix != format.RawPrefix {
 			t.Errorf("incorrect raw prefix in test %d", i)
 		}
 	}
 	// unknown prefix type
 	key.OutputPrefixType = tinkpb.OutputPrefixType_UNKNOWN_PREFIX
-	if _, err := tink.GetOutputPrefix(key); err == nil {
+	if _, err := format.OutputPrefix(key); err == nil {
 		t.Errorf("expect an error when prefix type is unknown")
 	}
 }
diff --git a/go/insecurecleartextkeyset/BUILD.bazel b/go/insecurecleartextkeyset/BUILD.bazel
new file mode 100644
index 0000000..2edcfcb
--- /dev/null
+++ b/go/insecurecleartextkeyset/BUILD.bazel
@@ -0,0 +1,29 @@
+package(default_visibility = ["//tools/build_defs:internal_pkg"])  # keep
+
+licenses(["notice"])  # Apache 2.0 # keep
+
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+    name = "go_default_library",
+    srcs = ["insecurecleartextkeyset.go"],
+    importpath = "github.com/google/tink/go/insecurecleartextkeyset",
+    deps = [
+        "//go/internal:go_default_library",
+        "//go/keyset:go_default_library",
+        "//proto:tink_go_proto",
+    ],
+)
+
+go_test(
+    name = "go_default_test",
+    srcs = ["insecurecleartextkeyset_test.go"],
+    embed = [":go_default_library"],
+    deps = [
+        "//go/mac:go_default_library",
+        "//go/testutil:go_default_library",
+        "//go/keyset:go_default_library",
+        "//proto:tink_go_proto",
+        "@com_github_golang_protobuf//proto:go_default_library",
+    ],
+)
diff --git a/go/insecurecleartextkeyset/insecurecleartextkeyset.go b/go/insecurecleartextkeyset/insecurecleartextkeyset.go
new file mode 100644
index 0000000..52197bb
--- /dev/null
+++ b/go/insecurecleartextkeyset/insecurecleartextkeyset.go
@@ -0,0 +1,60 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// Package insecurecleartextkeyset provides methods to read or write cleartext keyset material.
+//
+// This package contains dangerous functions, and is separate from the rest of Tink so that its
+// usage can be restricted and audited.
+package insecurecleartextkeyset
+
+import (
+	"errors"
+
+	"github.com/google/tink/go/internal"
+	"github.com/google/tink/go/keyset"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+var (
+	keysetHandle     = internal.KeysetHandle.(func(*tinkpb.Keyset) *keyset.Handle)
+	errInvalidKeyset = errors.New("insecurecleartextkeyset: invalid keyset")
+	errInvalidHandle = errors.New("insecurecleartextkeyset: invalid handle")
+	errInvalidReader = errors.New("insecurecleartextkeyset: invalid reader")
+	errInvalidWriter = errors.New("insecurecleartextkeyset: invalid writer")
+)
+
+// Read creates a keyset.Handle from a a cleartext keyset obtained via r.
+func Read(r keyset.Reader) (*keyset.Handle, error) {
+	if r == nil {
+		return nil, errInvalidReader
+	}
+	ks, err := r.Read()
+	if err != nil || ks == nil || len(ks.Key) == 0 {
+		return nil, errInvalidKeyset
+	}
+	return keysetHandle(ks), nil
+}
+
+// Write exports the keyset from h to the given writer w without encrypting it.
+// Storing secret key material in an unencrypted fashion is dangerous. If feasible, you should use
+// func keyset.Handle.Write() instead.
+func Write(h *keyset.Handle, w keyset.Writer) error {
+	if h == nil {
+		return errInvalidHandle
+	}
+	if w == nil {
+		return errInvalidWriter
+	}
+	return w.Write(h.Keyset())
+}
diff --git a/go/insecurecleartextkeyset/insecurecleartextkeyset_test.go b/go/insecurecleartextkeyset/insecurecleartextkeyset_test.go
new file mode 100644
index 0000000..e597f70
--- /dev/null
+++ b/go/insecurecleartextkeyset/insecurecleartextkeyset_test.go
@@ -0,0 +1,71 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package insecurecleartextkeyset_test
+
+import (
+	"testing"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/google/tink/go/insecurecleartextkeyset"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/testutil"
+
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+func TestInvalidInput(t *testing.T) {
+	if _, err := insecurecleartextkeyset.Read(nil); err == nil {
+		t.Error("insecurecleartextkeyset.Read should not accept nil as keyset")
+	}
+	if err := insecurecleartextkeyset.Write(nil, &keyset.MemReaderWriter{}); err == nil {
+		t.Error("insecurecleartextkeyset.Write should not accept nil as keyset")
+	}
+	if err := insecurecleartextkeyset.Write(&keyset.Handle{}, nil); err == nil {
+		t.Error("insecurecleartextkeyset.Write should not accept nil as writer")
+	}
+}
+
+func TestHandleFromReader(t *testing.T) {
+	// Create a keyset that contains a single HmacKey.
+	manager := testutil.NewHMACKeysetManager()
+	handle, err := manager.Handle()
+	if handle == nil || err != nil {
+		t.Fatalf("cannot get keyset handle: %v", err)
+	}
+	parsedHandle, err := insecurecleartextkeyset.Read(&keyset.MemReaderWriter{Keyset: handle.Keyset()})
+	if err != nil {
+		t.Fatalf("unexpected error reading keyset: %v", err)
+	}
+	if !proto.Equal(handle.Keyset(), parsedHandle.Keyset()) {
+		t.Errorf("parsed keyset (%s) doesn't match original keyset (%s)", parsedHandle.Keyset(), handle.Keyset())
+	}
+}
+
+func TestWrite(t *testing.T) {
+	keyData := testutil.NewKeyData("some type url", []byte{0}, tinkpb.KeyData_SYMMETRIC)
+	key := testutil.NewKey(keyData, tinkpb.KeyStatusType_ENABLED, 1, tinkpb.OutputPrefixType_TINK)
+	ks := testutil.NewKeyset(1, []*tinkpb.Keyset_Key{key})
+	h, err := insecurecleartextkeyset.Read(&keyset.MemReaderWriter{Keyset: ks})
+	if err != nil {
+		t.Fatalf("unexpected error creating new KeysetHandle: %v", err)
+	}
+	exported := &keyset.MemReaderWriter{}
+	if err := insecurecleartextkeyset.Write(h, exported); err != nil {
+		t.Fatalf("unexpected error writing keyset: %v", err)
+	}
+	if !proto.Equal(exported.Keyset, ks) {
+		t.Errorf("exported keyset (%s) doesn't match original keyset (%s)", exported.Keyset, ks)
+	}
+}
diff --git a/go/integration/gcpkms/BUILD.bazel b/go/integration/gcpkms/BUILD.bazel
new file mode 100644
index 0000000..f043eca
--- /dev/null
+++ b/go/integration/gcpkms/BUILD.bazel
@@ -0,0 +1,43 @@
+package(default_visibility = ["//tools/build_defs:internal_pkg"])
+
+licenses(["notice"])  # Apache 2.0 # keep
+
+
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+
+go_library(
+    name = "go_default_library",
+    srcs = [
+        "gcp_kms_aead.go",
+        "gcp_kms_client.go",
+    ],
+    importpath = "github.com/google/tink/go/integration/gcpkms",
+    visibility = ["//visibility:public"],
+    deps = [
+        "@org_golang_google_api//cloudkms/v1:go_default_library",
+        "@org_golang_x_oauth2//:go_default_library",
+        "@org_golang_x_oauth2//google:go_default_library",
+        "//go/tink:go_default_library",
+        "//go/registry:go_default_library",
+    ],
+)
+
+go_test(
+    name = "go_default_test",
+    srcs = ["gcp_kms_aead_test.go"],
+    data = [
+        "@google_root_pem//file", #keep
+        "//testdata:credentials",
+        "//testdata:ecies_keysets",
+        "@wycheproof//testvectors:all", #keep
+    ],
+    embed = [":go_default_library",],
+    deps = [
+        "//go/aead:go_default_library",
+        "//go/subtle/random:go_default_library",
+        "//go/tink:go_default_library",
+        "//go/keyset:go_default_library",
+        "//go/registry:go_default_library",
+    ],
+)
diff --git a/go/integration/gcpkms/gcp_kms_aead.go b/go/integration/gcpkms/gcp_kms_aead.go
new file mode 100644
index 0000000..e43a98a
--- /dev/null
+++ b/go/integration/gcpkms/gcp_kms_aead.go
@@ -0,0 +1,71 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// Package gcpkms provides integration with the GCP Cloud KMS.
+package gcpkms
+
+import (
+	"encoding/base64"
+
+	"google.golang.org/api/cloudkms/v1"
+
+	"github.com/google/tink/go/tink"
+)
+
+// GCPAEAD represents a GCP KMS service to a particular URI.
+type GCPAEAD struct {
+	keyURI string
+	kms    cloudkms.Service
+}
+
+var _ tink.AEAD = (*GCPAEAD)(nil)
+
+// NewGCPAEAD returns a new GCP KMS service.
+func NewGCPAEAD(keyURI string, kms *cloudkms.Service) *GCPAEAD {
+	return &GCPAEAD{
+		keyURI: keyURI,
+		kms:    *kms,
+	}
+}
+
+// Encrypt AEAD encrypts the plaintext data and uses addtionaldata from authentication.
+func (a *GCPAEAD) Encrypt(plaintext, additionalData []byte) ([]byte, error) {
+
+	req := &cloudkms.EncryptRequest{
+		Plaintext:                   base64.StdEncoding.EncodeToString(plaintext),
+		AdditionalAuthenticatedData: base64.StdEncoding.EncodeToString(additionalData),
+	}
+	resp, err := a.kms.Projects.Locations.KeyRings.CryptoKeys.Encrypt(a.keyURI, req).Do()
+	if err != nil {
+		return nil, err
+	}
+
+	return base64.StdEncoding.DecodeString(resp.Ciphertext)
+}
+
+// Decrypt AEAD decrypts the data and verified the additional data.
+func (a *GCPAEAD) Decrypt(ciphertext, additionalData []byte) ([]byte, error) {
+
+	req := &cloudkms.DecryptRequest{
+		Ciphertext:                  base64.StdEncoding.EncodeToString(ciphertext),
+		AdditionalAuthenticatedData: base64.StdEncoding.EncodeToString(additionalData),
+	}
+	resp, err := a.kms.Projects.Locations.KeyRings.CryptoKeys.Decrypt(a.keyURI, req).Do()
+	if err != nil {
+		return nil, err
+	}
+	return base64.StdEncoding.DecodeString(resp.Plaintext)
+}
diff --git a/go/integration/gcpkms/gcp_kms_aead_test.go b/go/integration/gcpkms/gcp_kms_aead_test.go
new file mode 100644
index 0000000..0e98c68
--- /dev/null
+++ b/go/integration/gcpkms/gcp_kms_aead_test.go
@@ -0,0 +1,98 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package gcpkms
+
+import (
+	"bytes"
+	"errors"
+	"os"
+	"testing"
+
+	"flag"
+	// context is used to cancel outstanding requests
+	// TEST_SRCDIR to read the roots.pem
+	"github.com/google/tink/go/aead"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/registry"
+	"github.com/google/tink/go/subtle/random"
+	"github.com/google/tink/go/tink"
+)
+
+const (
+	keyURI = "gcp-kms://projects/tink-test-infrastructure/locations/global/keyRings/unit-and-integration-testing/cryptoKeys/aead-key"
+)
+
+var (
+	// LINT.IfChange
+	credFile = os.Getenv("TEST_SRCDIR") + "/" + os.Getenv("TEST_WORKSPACE") + "/" + "testdata/credential.json"
+	// LINT.ThenChange(//depot/google3/third_party/tink/copybara/go.bara.sky)
+)
+
+// LINT.IfChange
+func init() {
+	certPath := os.Getenv("TEST_SRCDIR") + "/" + os.Getenv("TEST_WORKSPACE") + "/" + "roots.pem"
+	flag.Set("cacerts", certPath)
+	os.Setenv("SSL_CERT_FILE", certPath)
+}
+
+// LINT.ThenChange(//depot/google3/third_party/tink/copybara/go.bara.sky)
+
+func setupKMS(t *testing.T) {
+	t.Helper()
+	g, err := NewGCPClient(keyURI)
+	if err != nil {
+		t.Errorf("error setting up gcp client: %v", err)
+	}
+	_, err = g.LoadCredentials(credFile)
+	if err != nil {
+		t.Errorf("error loading credentials : %v", err)
+	}
+	registry.RegisterKMSClient(g)
+}
+
+func basicAEADTest(t *testing.T, a tink.AEAD) error {
+	t.Helper()
+	for i := 0; i < 100; i++ {
+		pt := random.GetRandomBytes(20)
+		ad := random.GetRandomBytes(20)
+		ct, err := a.Encrypt(pt, ad)
+		if err != nil {
+			return err
+		}
+		dt, err := a.Decrypt(ct, ad)
+		if err != nil {
+			return err
+		}
+		if !bytes.Equal(dt, pt) {
+			return errors.New("decrypt not inverse of encrypt")
+		}
+	}
+	return nil
+}
+func TestBasicAead(t *testing.T) {
+	setupKMS(t)
+	dek := aead.AES128CTRHMACSHA256KeyTemplate()
+	kh, err := keyset.NewHandle(aead.KMSEnvelopeAEADKeyTemplate(keyURI, dek))
+	if err != nil {
+		t.Errorf("error getting a new keyset handle: %v", err)
+	}
+	a, err := aead.New(kh)
+	if err != nil {
+		t.Errorf("error getting the primitive: %v", err)
+	}
+	if err := basicAEADTest(t, a); err != nil {
+		t.Errorf("error in basic aead tests: %v", err)
+	}
+}
diff --git a/go/integration/gcpkms/gcp_kms_client.go b/go/integration/gcpkms/gcp_kms_client.go
new file mode 100644
index 0000000..8866b06
--- /dev/null
+++ b/go/integration/gcpkms/gcp_kms_client.go
@@ -0,0 +1,133 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package gcpkms
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"strings"
+
+	"google.golang.org/api/cloudkms/v1"
+	"golang.org/x/oauth2/google"
+	"golang.org/x/oauth2"
+	"github.com/google/tink/go/registry"
+	"github.com/google/tink/go/tink"
+)
+
+const (
+	gcpPrefix = "gcp-kms://"
+)
+
+var (
+	errCred = errors.New("invalid credential path")
+)
+
+// GCPClient represents a client that connects to the GCP KMS backend.
+type GCPClient struct {
+	keyURI string
+	kms    *cloudkms.Service
+}
+
+var _ registry.KMSClient = (*GCPClient)(nil)
+
+// NewGCPClient returns a new client to GCP KMS. It does not have an established session.
+func NewGCPClient(URI string) (*GCPClient, error) {
+	if !strings.HasPrefix(strings.ToLower(URI), gcpPrefix) {
+		return nil, fmt.Errorf("key URI must start with %s", gcpPrefix)
+	}
+
+	return &GCPClient{
+		keyURI: URI,
+	}, nil
+
+}
+
+// Supported true if this client does support keyURI
+func (g *GCPClient) Supported(keyURI string) bool {
+	if (len(g.keyURI) > 0) && (strings.Compare(strings.ToLower(g.keyURI), strings.ToLower(keyURI)) == 0) {
+		return true
+	}
+	return ((len(g.keyURI) == 0) && (strings.HasPrefix(strings.ToLower(keyURI), gcpPrefix)))
+}
+
+// LoadCredentials loads the credentials in credentialPath. If credentialPath is  null, loads the
+// default credentials.
+func (g *GCPClient) LoadCredentials(credentialPath string) (interface{}, error) {
+	ctx := context.Background()
+	if len(credentialPath) <= 0 {
+		return nil, errCred
+	}
+	data, err := ioutil.ReadFile(credentialPath)
+	if err != nil {
+		return nil, errCred
+	}
+	creds, err := google.CredentialsFromJSON(ctx, data, "https://www.googleapis.com/auth/cloudkms")
+	if err != nil {
+		return nil, errCred
+	}
+	client := oauth2.NewClient(ctx, creds.TokenSource)
+	kmsService, err := cloudkms.New(client)
+	if err != nil {
+		return nil, err
+	}
+	g.kms = kmsService
+	return g, nil
+}
+
+// LoadDefaultCredentials loads with the default credentials.
+func (g *GCPClient) LoadDefaultCredentials() (interface{}, error) {
+	ctx := context.Background()
+	client, err := google.DefaultClient(ctx, cloudkms.CloudPlatformScope)
+	if err != nil {
+		return nil, err
+	}
+
+	kmsService, err := cloudkms.New(client)
+	if err != nil {
+		return nil, err
+	}
+	g.kms = kmsService
+	return g, nil
+}
+
+// GetAEAD gets an AEAD backend by keyURI.
+func (g *GCPClient) GetAEAD(keyURI string) (tink.AEAD, error) {
+	if len(g.keyURI) > 0 && strings.Compare(strings.ToLower(g.keyURI), strings.ToLower(keyURI)) != 0 {
+		return nil, fmt.Errorf("this client is bound to %s, cannot load keys bound to %s", g.keyURI, keyURI)
+	}
+	uri, err := validateTrimKMSPrefix(g.keyURI, gcpPrefix)
+	if err != nil {
+		return nil, err
+	}
+	return NewGCPAEAD(uri, g.kms), nil
+}
+
+func validateKMSPrefix(keyURI, prefix string) bool {
+	if len(keyURI) > 0 && strings.HasPrefix(strings.ToLower(keyURI), gcpPrefix) {
+		return true
+	}
+	return false
+}
+
+func validateTrimKMSPrefix(keyURI, prefix string) (string, error) {
+	if !validateKMSPrefix(keyURI, prefix) {
+		return "", fmt.Errorf("key URI must start with %s", prefix)
+	}
+	return strings.TrimPrefix(keyURI, prefix), nil
+}
diff --git a/go/internal/BUILD.bazel b/go/internal/BUILD.bazel
new file mode 100644
index 0000000..b8f931d
--- /dev/null
+++ b/go/internal/BUILD.bazel
@@ -0,0 +1,16 @@
+package(default_visibility = ["//tools/build_defs:internal_pkg"])  # keep
+
+licenses(["notice"])  # Apache 2.0 # keep
+
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+    name = "go_default_library",
+    srcs = ["internal.go"],
+    importpath = "github.com/google/tink/go/internal",
+    visibility = [
+        "//go/insecurecleartextkeyset:__pkg__",
+        "//go/keyset:__pkg__",
+        "//go/testkeyset:__pkg__",
+    ],
+)
diff --git a/go/tink/private_key_manager.go b/go/internal/internal.go
similarity index 63%
copy from go/tink/private_key_manager.go
copy to go/internal/internal.go
index 7cf169a..f644270 100644
--- a/go/tink/private_key_manager.go
+++ b/go/internal/internal.go
@@ -12,16 +12,9 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-package tink
+// Package internal provides a coordination point for package keyset, package insecurecleartextkeyset, and package testkeyset.
+// internal must only be imported by these three packages.
+package internal
 
-import (
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
-)
-
-// PrivateKeyManager is a special type of KeyManager that understands private key types.
-type PrivateKeyManager interface {
-	KeyManager
-
-	// GetPublicKeyData extracts the public key data from the private key.
-	GetPublicKeyData(serializedKey []byte) (*tinkpb.KeyData, error)
-}
+// KeysetHandle is the raw constructor for a keyset.Handle.
+var KeysetHandle interface{}
diff --git a/go/keyset/BUILD.bazel b/go/keyset/BUILD.bazel
new file mode 100644
index 0000000..0c76b99
--- /dev/null
+++ b/go/keyset/BUILD.bazel
@@ -0,0 +1,49 @@
+package(default_visibility = ["//tools/build_defs:internal_pkg"])
+
+licenses(["notice"])  # Apache 2.0
+
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+    name = "go_default_library",
+    srcs = [
+        "binary_io.go",
+        "handle.go",
+        "keyset.go",
+        "manager.go",
+        "mem_io.go",
+        "reader.go",
+        "validation.go",
+        "writer.go",
+    ],
+    importpath = "github.com/google/tink/go/keyset",
+    deps = [
+        "//go/internal:go_default_library",
+        "//go/primitiveset:go_default_library",
+        "//go/registry:go_default_library",
+        "//go/subtle/random:go_default_library",
+        "//go/tink:go_default_library",
+        "//proto:tink_go_proto",
+        "@com_github_golang_protobuf//proto:go_default_library",
+    ],
+)
+
+go_test(
+    name = "go_default_test",
+    srcs = [
+        "binary_io_test.go",
+        "handle_test.go",
+        "manager_test.go",
+        "validation_test.go",
+    ],
+    deps = [
+        "//go/keyset:go_default_library",
+        "//go/mac:go_default_library",
+        "//go/subtle/aead:go_default_library",
+        "//go/testkeyset:go_default_library",
+        "//go/testutil:go_default_library",
+        "//proto:tink_go_proto",
+        "@com_github_golang_protobuf//proto:go_default_library",
+    ],
+)
+
diff --git a/go/keyset/binary_io.go b/go/keyset/binary_io.go
new file mode 100644
index 0000000..d898fd3
--- /dev/null
+++ b/go/keyset/binary_io.go
@@ -0,0 +1,93 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package keyset
+
+import (
+	"io"
+	"io/ioutil"
+
+	"github.com/golang/protobuf/proto"
+
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+// BinaryReader deserializes a keyset from binary proto format.
+type BinaryReader struct {
+	r io.Reader
+}
+
+// NewBinaryReader returns new BinaryReader that will read from r.
+func NewBinaryReader(r io.Reader) *BinaryReader {
+	return &BinaryReader{r: r}
+}
+
+// Read parses a (cleartext) keyset from the underlying io.Reader.
+func (bkr *BinaryReader) Read() (*tinkpb.Keyset, error) {
+	keyset := &tinkpb.Keyset{}
+
+	if err := read(bkr.r, keyset); err != nil {
+		return nil, err
+	}
+	return keyset, nil
+}
+
+// ReadEncrypted parses an EncryptedKeyset from the underlying io.Reader.
+func (bkr *BinaryReader) ReadEncrypted() (*tinkpb.EncryptedKeyset, error) {
+	keyset := &tinkpb.EncryptedKeyset{}
+
+	if err := read(bkr.r, keyset); err != nil {
+		return nil, err
+	}
+	return keyset, nil
+}
+
+func read(r io.Reader, msg proto.Message) error {
+	data, err := ioutil.ReadAll(r)
+	if err != nil {
+		return err
+	}
+
+	return proto.Unmarshal(data, msg)
+}
+
+// BinaryWriter serializes a keyset into binary proto format.
+type BinaryWriter struct {
+	w io.Writer
+}
+
+// NewBinaryWriter returns a new BinaryWriter that will write to w.
+func NewBinaryWriter(w io.Writer) *BinaryWriter {
+	return &BinaryWriter{w: w}
+}
+
+// Write writes the keyset to the underlying io.Writer.
+func (bkw *BinaryWriter) Write(keyset *tinkpb.Keyset) error {
+	return write(bkw.w, keyset)
+}
+
+// WriteEncrypted writes the encrypted keyset to the underlying io.Writer.
+func (bkw *BinaryWriter) WriteEncrypted(keyset *tinkpb.EncryptedKeyset) error {
+	return write(bkw.w, keyset)
+}
+
+func write(w io.Writer, msg proto.Message) error {
+	data, err := proto.Marshal(msg)
+	if err != nil {
+		return err
+	}
+
+	_, err = w.Write(data)
+	return err
+}
diff --git a/go/keyset/binary_io_test.go b/go/keyset/binary_io_test.go
new file mode 100644
index 0000000..cfa6d2c
--- /dev/null
+++ b/go/keyset/binary_io_test.go
@@ -0,0 +1,73 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package keyset_test
+
+import (
+	"bytes"
+	"strings"
+	"testing"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/testutil"
+
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+func TestBinaryIOUnencrypted(t *testing.T) {
+	buf := new(bytes.Buffer)
+	w := keyset.NewBinaryWriter(buf)
+	r := keyset.NewBinaryReader(buf)
+
+	manager := testutil.NewHMACKeysetManager()
+	h, err := manager.Handle()
+	if h == nil || err != nil {
+		t.Fatalf("cannot get keyset handle: %v", err)
+	}
+
+	if err := w.Write(h.Keyset()); err != nil {
+		t.Fatalf("cannot write keyset: %v", err)
+	}
+
+	ks2, err := r.Read()
+	if err != nil {
+		t.Fatalf("cannot read keyset: %v", err)
+	}
+
+	if !proto.Equal(h.Keyset(), ks2) {
+		t.Errorf("written keyset (%s) doesn't match read keyset (%s)", h.Keyset(), ks2)
+	}
+}
+
+func TestBinaryIOEncrypted(t *testing.T) {
+	buf := new(bytes.Buffer)
+	w := keyset.NewBinaryWriter(buf)
+	r := keyset.NewBinaryReader(buf)
+
+	kse1 := &tinkpb.EncryptedKeyset{EncryptedKeyset: []byte(strings.Repeat("A", 32))}
+
+	if err := w.WriteEncrypted(kse1); err != nil {
+		t.Fatalf("cannot write encrypted keyset: %v", err)
+	}
+
+	kse2, err := r.ReadEncrypted()
+	if err != nil {
+		t.Fatalf("cannot read encryped keyset: %v", err)
+	}
+
+	if !proto.Equal(kse1, kse2) {
+		t.Errorf("written encryped keyset (%s) doesn't match read encryped keyset (%s)", kse1, kse2)
+	}
+}
diff --git a/go/keyset/handle.go b/go/keyset/handle.go
new file mode 100644
index 0000000..125d885
--- /dev/null
+++ b/go/keyset/handle.go
@@ -0,0 +1,297 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package keyset
+
+import (
+	"errors"
+	"fmt"
+
+	"github.com/golang/protobuf/proto"
+
+	"github.com/google/tink/go/primitiveset"
+	"github.com/google/tink/go/registry"
+	"github.com/google/tink/go/tink"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+var errInvalidKeyset = fmt.Errorf("keyset.Handle: invalid keyset")
+
+// Handle provides access to a Keyset protobuf, to limit the exposure of actual protocol
+// buffers that hold sensitive key material.
+type Handle struct {
+	ks *tinkpb.Keyset
+}
+
+// NewHandle creates a keyset handle that contains a single fresh key generated according
+// to the given KeyTemplate.
+func NewHandle(kt *tinkpb.KeyTemplate) (*Handle, error) {
+	ksm := NewManager()
+	err := ksm.Rotate(kt)
+	if err != nil {
+		return nil, fmt.Errorf("keyset.Handle: cannot generate new keyset: %s", err)
+	}
+	handle, err := ksm.Handle()
+	if err != nil {
+		return nil, fmt.Errorf("keyset.Handle: cannot get keyset handle: %s", err)
+	}
+	return handle, nil
+}
+
+// NewHandleWithNoSecrets creates a new instance of KeysetHandle using the given keyset which does
+// not contain any secret key material.
+func NewHandleWithNoSecrets(ks *tinkpb.Keyset) (*Handle, error) {
+	h := &Handle{ks}
+	if h.hasSecrets() {
+		// If you need to do this, you have to use func insecurecleartextkeyset.Read() instead.
+		return nil, errors.New("importing unencrypted secret key material is forbidden")
+	}
+	return h, nil
+}
+
+// Read tries to create a Handle from an encrypted keyset obtained via reader.
+func Read(reader Reader, masterKey tink.AEAD) (*Handle, error) {
+	encryptedKeyset, err := reader.ReadEncrypted()
+	if err != nil {
+		return nil, err
+	}
+	ks, err := decrypt(encryptedKeyset, masterKey)
+	if err != nil {
+		return nil, err
+	}
+	return &Handle{ks}, nil
+}
+
+// ReadWithNoSecrets tries to create a keyset.Handle from a keyset obtained via reader.
+func ReadWithNoSecrets(reader Reader) (*Handle, error) {
+	ks, err := reader.Read()
+	if err != nil {
+		return nil, err
+	}
+	return NewHandleWithNoSecrets(ks)
+}
+
+// Public returns a Handle of the public keys if the managed keyset contains private keys.
+func (h *Handle) Public() (*Handle, error) {
+	privKeys := h.ks.Key
+	pubKeys := make([]*tinkpb.Keyset_Key, len(privKeys))
+
+	for i := 0; i < len(privKeys); i++ {
+		if privKeys[i] == nil || privKeys[i].KeyData == nil {
+			return nil, errInvalidKeyset
+		}
+		privKeyData := privKeys[i].KeyData
+		pubKeyData, err := publicKeyData(privKeyData)
+		if err != nil {
+			return nil, fmt.Errorf("keyset.Handle: %s", err)
+		}
+		pubKeys[i] = &tinkpb.Keyset_Key{
+			KeyData:          pubKeyData,
+			Status:           privKeys[i].Status,
+			KeyId:            privKeys[i].KeyId,
+			OutputPrefixType: privKeys[i].OutputPrefixType,
+		}
+	}
+	ks := &tinkpb.Keyset{
+		PrimaryKeyId: h.ks.PrimaryKeyId,
+		Key:          pubKeys,
+	}
+	return &Handle{ks}, nil
+}
+
+// Keyset returns the Keyset managed by this handle.
+func (h *Handle) Keyset() *tinkpb.Keyset {
+	return h.ks
+}
+
+// String returns a string representation of the managed keyset.
+// The result does not contain any sensitive key material.
+func (h *Handle) String() string {
+	info, err := getKeysetInfo(h.ks)
+	if err != nil {
+		return ""
+	}
+	return info.String()
+}
+
+// Write encrypts and writes the enclosing keyset.
+func (h *Handle) Write(writer Writer, masterKey tink.AEAD) error {
+	encrypted, err := encrypt(h.Keyset(), masterKey)
+	if err != nil {
+		return err
+	}
+	return writer.WriteEncrypted(encrypted)
+}
+
+// WriteWithNoSecrets exports the keyset in h to the given Writer w returning an error if the keyset
+// contains secret key material.
+func (h *Handle) WriteWithNoSecrets(w Writer) error {
+	if h.hasSecrets() {
+		return errors.New("exporting unencrypted secret key material is forbidden")
+	}
+
+	return w.Write(h.ks)
+}
+
+// Primitives creates a set of primitives corresponding to the keys with
+// status=ENABLED in the keyset of the given keyset handle, assuming all the
+// corresponding key managers are present (keys with status!=ENABLED are skipped).
+//
+// The returned set is usually later "wrapped" into a class that implements
+// the corresponding Primitive-interface.
+func (h *Handle) Primitives() (*primitiveset.PrimitiveSet, error) {
+	return h.PrimitivesWithKeyManager(nil)
+}
+
+// PrimitivesWithKeyManager creates a set of primitives corresponding to
+// the keys with status=ENABLED in the keyset of the given keysetHandle, using
+// the given key manager (instead of registered key managers) for keys supported
+// by it.  Keys not supported by the key manager are handled by matching registered
+// key managers (if present), and keys with status!=ENABLED are skipped.
+//
+// This enables custom treatment of keys, for example providing extra context
+// (e.g. credentials for accessing keys managed by a KMS), or gathering custom
+// monitoring/profiling information.
+//
+// The returned set is usually later "wrapped" into a class that implements
+// the corresponding Primitive-interface.
+func (h *Handle) PrimitivesWithKeyManager(km registry.KeyManager) (*primitiveset.PrimitiveSet, error) {
+	ks := h.Keyset()
+	if err := Validate(ks); err != nil {
+		return nil, fmt.Errorf("registry.PrimitivesWithKeyManager: invalid keyset: %s", err)
+	}
+	primitiveSet := primitiveset.New()
+	for _, key := range ks.Key {
+		if key.Status != tinkpb.KeyStatusType_ENABLED {
+			continue
+		}
+		var primitive interface{}
+		var err error
+		if km != nil && km.DoesSupport(key.KeyData.TypeUrl) {
+			primitive, err = km.Primitive(key.KeyData.Value)
+		} else {
+			primitive, err = registry.PrimitiveFromKeyData(key.KeyData)
+		}
+		if err != nil {
+			return nil, fmt.Errorf("registry.PrimitivesWithKeyManager: cannot get primitive from key: %s", err)
+		}
+		entry, err := primitiveSet.Add(primitive, key)
+		if err != nil {
+			return nil, fmt.Errorf("registry.PrimitivesWithKeyManager: cannot add primitive: %s", err)
+		}
+		if key.KeyId == ks.PrimaryKeyId {
+			primitiveSet.Primary = entry
+		}
+	}
+	return primitiveSet, nil
+}
+
+// hasSecrets checks if the keyset handle contains any key material considered secret.
+// Both symmetric keys and the private key of an assymmetric crypto system are considered secret keys.
+// Also returns true when encountering any errors.
+func (h *Handle) hasSecrets() bool {
+	for _, k := range h.ks.Key {
+		if k == nil || k.KeyData == nil {
+			continue
+		}
+		if k.KeyData.KeyMaterialType == tinkpb.KeyData_ASYMMETRIC_PRIVATE || k.KeyData.KeyMaterialType == tinkpb.KeyData_SYMMETRIC {
+			return true
+		}
+	}
+	return false
+}
+
+func publicKeyData(privKeyData *tinkpb.KeyData) (*tinkpb.KeyData, error) {
+	if privKeyData.KeyMaterialType != tinkpb.KeyData_ASYMMETRIC_PRIVATE {
+		return nil, fmt.Errorf("keyset.Handle: keyset contains a non-private key")
+	}
+	km, err := registry.GetKeyManager(privKeyData.TypeUrl)
+	if err != nil {
+		return nil, err
+	}
+	pkm, ok := km.(registry.PrivateKeyManager)
+	if !ok {
+		return nil, fmt.Errorf("keyset.Handle: %s does not belong to a PrivateKeyManager", privKeyData.TypeUrl)
+	}
+	return pkm.PublicKeyData(privKeyData.Value)
+}
+
+func decrypt(encryptedKeyset *tinkpb.EncryptedKeyset, masterKey tink.AEAD) (*tinkpb.Keyset, error) {
+	if encryptedKeyset == nil || masterKey == nil {
+		return nil, fmt.Errorf("keyset.Handle: invalid encrypted keyset")
+	}
+	decrypted, err := masterKey.Decrypt(encryptedKeyset.EncryptedKeyset, []byte{})
+	if err != nil {
+		return nil, fmt.Errorf("keyset.Handle: decryption failed: %s", err)
+	}
+	keyset := new(tinkpb.Keyset)
+	if err := proto.Unmarshal(decrypted, keyset); err != nil {
+		return nil, errInvalidKeyset
+	}
+	return keyset, nil
+}
+
+func encrypt(keyset *tinkpb.Keyset, masterKey tink.AEAD) (*tinkpb.EncryptedKeyset, error) {
+	serializedKeyset, err := proto.Marshal(keyset)
+	if err != nil {
+		return nil, errInvalidKeyset
+	}
+	encrypted, err := masterKey.Encrypt(serializedKeyset, []byte{})
+	if err != nil {
+		return nil, fmt.Errorf("keyset.Handle: encrypted failed: %s", err)
+	}
+	// get keyset info
+	info, err := getKeysetInfo(keyset)
+	if err != nil {
+		return nil, fmt.Errorf("keyset.Handle: cannot get keyset info: %s", err)
+	}
+	encryptedKeyset := &tinkpb.EncryptedKeyset{
+		EncryptedKeyset: encrypted,
+		KeysetInfo:      info,
+	}
+	return encryptedKeyset, nil
+}
+
+// getKeysetInfo returns a KeysetInfo from a Keyset protobuf.
+func getKeysetInfo(keyset *tinkpb.Keyset) (*tinkpb.KeysetInfo, error) {
+	if keyset == nil {
+		return nil, errors.New("keyset.Handle: keyset must be non nil")
+	}
+	nKey := len(keyset.Key)
+	keyInfos := make([]*tinkpb.KeysetInfo_KeyInfo, nKey)
+	for i, key := range keyset.Key {
+		info, err := getKeyInfo(key)
+		if err != nil {
+			return nil, err
+		}
+		keyInfos[i] = info
+	}
+	return &tinkpb.KeysetInfo{
+		PrimaryKeyId: keyset.PrimaryKeyId,
+		KeyInfo:      keyInfos,
+	}, nil
+}
+
+// getKeyInfo returns a KeyInfo from a Key protobuf.
+func getKeyInfo(key *tinkpb.Keyset_Key) (*tinkpb.KeysetInfo_KeyInfo, error) {
+	if key == nil {
+		return nil, errors.New("keyset.Handle: keyset must be non nil")
+	}
+	return &tinkpb.KeysetInfo_KeyInfo{
+		TypeUrl:          key.KeyData.TypeUrl,
+		Status:           key.Status,
+		KeyId:            key.KeyId,
+		OutputPrefixType: key.OutputPrefixType,
+	}, nil
+}
diff --git a/go/keyset/handle_test.go b/go/keyset/handle_test.go
new file mode 100644
index 0000000..9679b8c
--- /dev/null
+++ b/go/keyset/handle_test.go
@@ -0,0 +1,125 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package keyset_test
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/mac"
+	"github.com/google/tink/go/subtle/aead"
+	"github.com/google/tink/go/testkeyset"
+	"github.com/google/tink/go/testutil"
+
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+func TestNewHandle(t *testing.T) {
+	kt := mac.HMACSHA256Tag128KeyTemplate()
+	kh, err := keyset.NewHandle(kt)
+	if err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+	keyset := kh.Keyset()
+	if len(keyset.Key) != 1 {
+		t.Errorf("incorrect number of keys in the keyset: %d", len(keyset.Key))
+	}
+	key := keyset.Key[0]
+	if keyset.PrimaryKeyId != key.KeyId {
+		t.Errorf("incorrect primary key id, expect %d, got %d", key.KeyId, keyset.PrimaryKeyId)
+	}
+	if key.KeyData.TypeUrl != kt.TypeUrl {
+		t.Errorf("incorrect type url, expect %s, got %s", kt.TypeUrl, key.KeyData.TypeUrl)
+	}
+	if _, err = mac.New(kh); err != nil {
+		t.Errorf("cannot get primitive from generated keyset handle: %s", err)
+	}
+}
+
+func TestNewHandleWithInvalidInput(t *testing.T) {
+	// template unregistered TypeUrl
+	template := mac.HMACSHA256Tag128KeyTemplate()
+	template.TypeUrl = "some unknown TypeUrl"
+	if _, err := keyset.NewHandle(template); err == nil {
+		t.Errorf("expect an error when TypeUrl is not registered")
+	}
+	// nil
+	if _, err := keyset.NewHandle(nil); err == nil {
+		t.Errorf("expect an error when template is nil")
+	}
+}
+
+func TestRead(t *testing.T) {
+	masterKey, err := aead.NewAESGCM([]byte(strings.Repeat("A", 32)))
+	if err != nil {
+		t.Errorf("aead.NewAESGCM(): %v", err)
+	}
+
+	// Create a keyset
+	keyData := testutil.NewKeyData("some type url", []byte{0}, tinkpb.KeyData_SYMMETRIC)
+	key := testutil.NewKey(keyData, tinkpb.KeyStatusType_ENABLED, 1, tinkpb.OutputPrefixType_TINK)
+	ks := testutil.NewKeyset(1, []*tinkpb.Keyset_Key{key})
+	h, _ := testkeyset.NewHandle(ks)
+
+	memKeyset := &keyset.MemReaderWriter{}
+	if err := h.Write(memKeyset, masterKey); err != nil {
+		t.Fatalf("handle.Write(): %v", err)
+	}
+	h2, err := keyset.Read(memKeyset, masterKey)
+	if err != nil {
+		t.Fatalf("keyset.Read(): %v", err)
+	}
+	if !proto.Equal(h.Keyset(), h2.Keyset()) {
+		t.Fatalf("Decrypt failed: got %v, want %v", h2, h)
+	}
+}
+
+func TestReadWithNoSecrets(t *testing.T) {
+	// Create a keyset containing public key material
+	keyData := testutil.NewKeyData("some type url", []byte{0}, tinkpb.KeyData_ASYMMETRIC_PUBLIC)
+	key := testutil.NewKey(keyData, tinkpb.KeyStatusType_ENABLED, 1, tinkpb.OutputPrefixType_TINK)
+	ks := testutil.NewKeyset(1, []*tinkpb.Keyset_Key{key})
+	h, _ := testkeyset.NewHandle(ks)
+
+	memKeyset := &keyset.MemReaderWriter{}
+	if err := h.WriteWithNoSecrets(memKeyset); err != nil {
+		t.Fatalf("handle.WriteWithNoSecrets(): %v", err)
+	}
+	h2, err := keyset.ReadWithNoSecrets(memKeyset)
+	if err != nil {
+		t.Fatalf("keyset.ReadWithNoSecrets(): %v", err)
+	}
+	if !proto.Equal(h.Keyset(), h2.Keyset()) {
+		t.Fatalf("Decrypt failed: got %v, want %v", h2, h)
+	}
+}
+
+func TestWithNoSecretsFunctionsFailWhenHandlingSecretKeyMaterial(t *testing.T) {
+	// Create a keyset containing secret key material (symmetric)
+	keyData := testutil.NewKeyData("some type url", []byte{0}, tinkpb.KeyData_SYMMETRIC)
+	key := testutil.NewKey(keyData, tinkpb.KeyStatusType_ENABLED, 1, tinkpb.OutputPrefixType_TINK)
+	ks := testutil.NewKeyset(1, []*tinkpb.Keyset_Key{key})
+	h, _ := testkeyset.NewHandle(ks)
+
+	if err := h.WriteWithNoSecrets(&keyset.MemReaderWriter{}); err == nil {
+		t.Error("handle.WriteWithNoSecrets() should fail when exporting secret key material")
+	}
+
+	if _, err := keyset.ReadWithNoSecrets(&keyset.MemReaderWriter{Keyset: h.Keyset()}); err == nil {
+		t.Error("keyset.ReadWithNoSecrets should fail when importing secret key material")
+	}
+}
diff --git a/go/tink/private_key_manager.go b/go/keyset/keyset.go
similarity index 63%
copy from go/tink/private_key_manager.go
copy to go/keyset/keyset.go
index 7cf169a..a77cea8 100644
--- a/go/tink/private_key_manager.go
+++ b/go/keyset/keyset.go
@@ -12,16 +12,20 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-package tink
+// Package keyset provides methods to generate, read, write or validate keysets.
+package keyset
 
 import (
+	"github.com/google/tink/go/internal"
 	tinkpb "github.com/google/tink/proto/tink_go_proto"
 )
 
-// PrivateKeyManager is a special type of KeyManager that understands private key types.
-type PrivateKeyManager interface {
-	KeyManager
+// keysetHandle is used by package insecure and package testkeysethandle (via package internal)
+// to create KeysetHandle from cleartext key material.
+func keysetHandle(ks *tinkpb.Keyset) *Handle {
+	return &Handle{ks}
+}
 
-	// GetPublicKeyData extracts the public key data from the private key.
-	GetPublicKeyData(serializedKey []byte) (*tinkpb.KeyData, error)
+func init() {
+	internal.KeysetHandle = keysetHandle
 }
diff --git a/go/keyset/manager.go b/go/keyset/manager.go
new file mode 100644
index 0000000..bca3902
--- /dev/null
+++ b/go/keyset/manager.go
@@ -0,0 +1,92 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package keyset
+
+import (
+	"fmt"
+
+	"github.com/google/tink/go/registry"
+	"github.com/google/tink/go/subtle/random"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+// Manager manages a Keyset-proto, with convenience methods that rotate, disable, enable or destroy keys.
+// Note: It is not thread-safe.
+type Manager struct {
+	ks *tinkpb.Keyset
+}
+
+// NewManager creates a new instance with an empty Keyset.
+func NewManager() *Manager {
+	ret := new(Manager)
+	ret.ks = new(tinkpb.Keyset)
+	return ret
+}
+
+// NewManagerFromHandle creates a new instance from the given Handle.
+func NewManagerFromHandle(kh *Handle) *Manager {
+	ret := new(Manager)
+	ret.ks = kh.ks
+	return ret
+}
+
+// Rotate generates a fresh key using the given key template and
+// sets the new key as the primary key.
+func (km *Manager) Rotate(kt *tinkpb.KeyTemplate) error {
+	if kt == nil {
+		return fmt.Errorf("keyset_manager: cannot rotate, need key template")
+	}
+	keyData, err := registry.NewKeyData(kt)
+	if err != nil {
+		return fmt.Errorf("keyset_manager: cannot create KeyData: %s", err)
+	}
+	keyID := km.newKeyID()
+	outputPrefixType := kt.OutputPrefixType
+	if outputPrefixType == tinkpb.OutputPrefixType_UNKNOWN_PREFIX {
+		outputPrefixType = tinkpb.OutputPrefixType_TINK
+	}
+	key := &tinkpb.Keyset_Key{
+		KeyData:          keyData,
+		Status:           tinkpb.KeyStatusType_ENABLED,
+		KeyId:            keyID,
+		OutputPrefixType: outputPrefixType,
+	}
+	// Set the new key as the primary key
+	km.ks.Key = append(km.ks.Key, key)
+	km.ks.PrimaryKeyId = keyID
+	return nil
+}
+
+// Handle creates a new Handle for the managed keyset.
+func (km *Manager) Handle() (*Handle, error) {
+	return &Handle{km.ks}, nil
+}
+
+// newKeyID generates a key id that has not been used by any key in the keyset.
+func (km *Manager) newKeyID() uint32 {
+	for {
+		ret := random.GetRandomUint32()
+		ok := true
+		for _, key := range km.ks.Key {
+			if key.KeyId == ret {
+				ok = false
+				break
+			}
+		}
+		if ok {
+			return ret
+		}
+	}
+}
diff --git a/go/keyset/manager_test.go b/go/keyset/manager_test.go
new file mode 100644
index 0000000..2074b65
--- /dev/null
+++ b/go/keyset/manager_test.go
@@ -0,0 +1,81 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package keyset_test
+
+import (
+	"testing"
+
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/mac"
+	"github.com/google/tink/go/testutil"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+func TestKeysetManagerBasic(t *testing.T) {
+	// Create a keyset that contains a single HmacKey.
+	ksm := keyset.NewManager()
+	kt := mac.HMACSHA256Tag128KeyTemplate()
+	err := ksm.Rotate(kt)
+	if err != nil {
+		t.Errorf("cannot rotate when key template is available: %s", err)
+	}
+	h, err := ksm.Handle()
+	if err != nil {
+		t.Errorf("cannot get keyset handle: %s", err)
+	}
+	ks := h.Keyset()
+	if len(ks.Key) != 1 {
+		t.Errorf("expect the number of keys in the keyset is 1")
+	}
+	if ks.Key[0].KeyId != ks.PrimaryKeyId ||
+		ks.Key[0].KeyData.TypeUrl != testutil.HMACTypeURL ||
+		ks.Key[0].Status != tinkpb.KeyStatusType_ENABLED ||
+		ks.Key[0].OutputPrefixType != tinkpb.OutputPrefixType_TINK {
+		t.Errorf("incorrect key information: %s", ks.Key[0])
+	}
+}
+
+func TestExistingKeyset(t *testing.T) {
+	// Create a keyset that contains a single HmacKey.
+	ksm1 := keyset.NewManager()
+	kt := mac.HMACSHA256Tag128KeyTemplate()
+	err := ksm1.Rotate(kt)
+	if err != nil {
+		t.Errorf("cannot rotate when key template is available: %s", err)
+	}
+
+	h1, err := ksm1.Handle()
+	if err != nil {
+		t.Errorf("cannot get keyset handle: %s", err)
+	}
+	ks1 := h1.Keyset()
+
+	ksm2 := keyset.NewManagerFromHandle(h1)
+	ksm2.Rotate(kt)
+	h2, err := ksm2.Handle()
+	if err != nil {
+		t.Errorf("cannot get keyset handle: %s", err)
+	}
+	ks2 := h2.Keyset()
+	if len(ks2.Key) != 2 {
+		t.Errorf("expect the number of keys to be 2, got %d", len(ks2.Key))
+	}
+	if ks1.Key[0].String() != ks2.Key[0].String() {
+		t.Errorf("expect the first key in two keysets to be the same")
+	}
+	if ks2.Key[1].KeyId != ks2.PrimaryKeyId {
+		t.Errorf("expect the second key to be primary")
+	}
+}
diff --git a/go/keyset/mem_io.go b/go/keyset/mem_io.go
new file mode 100644
index 0000000..18d6097
--- /dev/null
+++ b/go/keyset/mem_io.go
@@ -0,0 +1,49 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package keyset
+
+import tinkpb "github.com/google/tink/proto/tink_go_proto"
+
+// MemReaderWriter implements keyset.Reader and keyset.Writer for *tinkpb.Keyset and *tinkpb.EncryptedKeyset.
+type MemReaderWriter struct {
+	Keyset          *tinkpb.Keyset
+	EncryptedKeyset *tinkpb.EncryptedKeyset
+}
+
+// MemReaderWriter implements Reader and Writer.
+var _ Reader = &MemReaderWriter{}
+var _ Writer = &MemReaderWriter{}
+
+// Read returns *tinkpb.Keyset from memory.
+func (m *MemReaderWriter) Read() (*tinkpb.Keyset, error) {
+	return m.Keyset, nil
+}
+
+// ReadEncrypted returns *tinkpb.EncryptedKeyset from memory.
+func (m *MemReaderWriter) ReadEncrypted() (*tinkpb.EncryptedKeyset, error) {
+	return m.EncryptedKeyset, nil
+}
+
+// Write keyset to memory.
+func (m *MemReaderWriter) Write(keyset *tinkpb.Keyset) error {
+	m.Keyset = keyset
+	return nil
+}
+
+// WriteEncrypted keyset to memory.
+func (m *MemReaderWriter) WriteEncrypted(keyset *tinkpb.EncryptedKeyset) error {
+	m.EncryptedKeyset = keyset
+	return nil
+}
diff --git a/go/keyset/reader.go b/go/keyset/reader.go
new file mode 100644
index 0000000..5bbc387
--- /dev/null
+++ b/go/keyset/reader.go
@@ -0,0 +1,28 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package keyset
+
+import tinkpb "github.com/google/tink/proto/tink_go_proto"
+
+// Reader knows how to read a Keyset or an EncryptedKeyset from some source.
+// In order to turn a Reader into a KeysetHandle for use, callers must use
+// insecure.KeysetHandle or by keyset.Read (with encryption).
+type Reader interface {
+	// Read returns a (cleartext) Keyset object from the underlying source.
+	Read() (*tinkpb.Keyset, error)
+
+	// ReadEncrypted returns an EncryptedKeyset object from the underlying source.
+	ReadEncrypted() (*tinkpb.EncryptedKeyset, error)
+}
diff --git a/go/keyset/validation.go b/go/keyset/validation.go
new file mode 100644
index 0000000..4c5c8cf
--- /dev/null
+++ b/go/keyset/validation.go
@@ -0,0 +1,87 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package keyset
+
+import (
+	"fmt"
+
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+// ValidateKeyVersion checks whether the given version is valid. The version is valid
+// only if it is the range [0..maxExpected]
+func ValidateKeyVersion(version, maxExpected uint32) error {
+	if version > maxExpected {
+		return fmt.Errorf("key has version %v; only keys with version in range [0..%v] are supported",
+			version, maxExpected)
+	}
+	return nil
+}
+
+// Validate validates the given key set.
+// Returns nil if it is valid; an error otherwise.
+func Validate(keyset *tinkpb.Keyset) error {
+	if keyset == nil {
+		return fmt.Errorf("Validate() called with nil")
+	}
+	if len(keyset.Key) == 0 {
+		return fmt.Errorf("empty keyset")
+	}
+	primaryKeyID := keyset.PrimaryKeyId
+	hasPrimaryKey := false
+	for _, key := range keyset.Key {
+		if err := validateKey(key); err != nil {
+			return err
+		}
+		if key.Status == tinkpb.KeyStatusType_ENABLED && key.KeyId == primaryKeyID {
+			if hasPrimaryKey {
+				return fmt.Errorf("keyset contains multiple primary keys")
+			}
+			hasPrimaryKey = true
+		}
+	}
+	if !hasPrimaryKey {
+		return fmt.Errorf("keyset does not contain a valid primary key")
+	}
+	return nil
+}
+
+/*
+validateKey validates the given key.
+Returns nil if it is valid; an error otherwise.
+*/
+func validateKey(key *tinkpb.Keyset_Key) error {
+	if key == nil {
+		return fmt.Errorf("ValidateKey() called with nil")
+	}
+	if key.KeyId == 0 {
+		return fmt.Errorf("key has zero key id: %d", key.KeyId)
+	}
+	if key.KeyData == nil {
+		return fmt.Errorf("key %d has no key data", key.KeyId)
+	}
+	if key.OutputPrefixType != tinkpb.OutputPrefixType_TINK &&
+		key.OutputPrefixType != tinkpb.OutputPrefixType_LEGACY &&
+		key.OutputPrefixType != tinkpb.OutputPrefixType_RAW &&
+		key.OutputPrefixType != tinkpb.OutputPrefixType_CRUNCHY {
+		return fmt.Errorf("key %d has unknown prefix", key.KeyId)
+	}
+	if key.Status != tinkpb.KeyStatusType_ENABLED &&
+		key.Status != tinkpb.KeyStatusType_DISABLED &&
+		key.Status != tinkpb.KeyStatusType_DESTROYED {
+		return fmt.Errorf("key %d has unknown status", key.KeyId)
+	}
+	return nil
+}
diff --git a/go/keyset/validation_test.go b/go/keyset/validation_test.go
new file mode 100644
index 0000000..a347077
--- /dev/null
+++ b/go/keyset/validation_test.go
@@ -0,0 +1,87 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package keyset_test
+
+import (
+	"testing"
+
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/testutil"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+func TestValidateKeyVersion(t *testing.T) {
+	if keyset.ValidateKeyVersion(2, 1) == nil ||
+		keyset.ValidateKeyVersion(1, 1) != nil ||
+		keyset.ValidateKeyVersion(1, 2) != nil {
+		t.Errorf("incorrect version validation")
+	}
+}
+
+func TestValidate(t *testing.T) {
+	var err error
+	// nil input
+	if err = keyset.Validate(nil); err == nil {
+		t.Errorf("expect an error when keyset is nil")
+	}
+	// empty keyset
+	var emptyKeys []*tinkpb.Keyset_Key
+	if err = keyset.Validate(testutil.NewKeyset(1, emptyKeys)); err == nil {
+		t.Errorf("expect an error when keyset is empty")
+	}
+	// no primary key
+	keys := []*tinkpb.Keyset_Key{
+		testutil.NewDummyKey(1, tinkpb.KeyStatusType_ENABLED, tinkpb.OutputPrefixType_TINK),
+	}
+	if err = keyset.Validate(testutil.NewKeyset(2, keys)); err == nil {
+		t.Errorf("expect an error when there is no primary key")
+	}
+	// primary key is disabled
+	keys = []*tinkpb.Keyset_Key{
+		testutil.NewDummyKey(1, tinkpb.KeyStatusType_ENABLED, tinkpb.OutputPrefixType_TINK),
+		testutil.NewDummyKey(2, tinkpb.KeyStatusType_DISABLED, tinkpb.OutputPrefixType_LEGACY),
+	}
+	if err = keyset.Validate(testutil.NewKeyset(2, keys)); err == nil {
+		t.Errorf("expect an error when primary key is disabled")
+	}
+	// multiple primary keys
+	keys = []*tinkpb.Keyset_Key{
+		testutil.NewDummyKey(1, tinkpb.KeyStatusType_ENABLED, tinkpb.OutputPrefixType_TINK),
+		testutil.NewDummyKey(1, tinkpb.KeyStatusType_ENABLED, tinkpb.OutputPrefixType_LEGACY),
+	}
+	if err = keyset.Validate(testutil.NewKeyset(1, keys)); err == nil {
+		t.Errorf("expect an error when there are multiple primary keys")
+	}
+	// invalid keys
+	invalidKeys := generateInvalidKeys()
+	for i, key := range invalidKeys {
+		err = keyset.Validate(testutil.NewKeyset(1, []*tinkpb.Keyset_Key{key}))
+		if err == nil {
+			t.Errorf("expect an error when validate invalid key %d", i)
+		}
+	}
+}
+
+func generateInvalidKeys() []*tinkpb.Keyset_Key {
+	return []*tinkpb.Keyset_Key{
+		nil,
+		// nil KeyData
+		testutil.NewKey(nil, tinkpb.KeyStatusType_ENABLED, 1, tinkpb.OutputPrefixType_TINK),
+		// unknown status
+		testutil.NewKey(new(tinkpb.KeyData), tinkpb.KeyStatusType_UNKNOWN_STATUS, 1, tinkpb.OutputPrefixType_TINK),
+		// unknown prefix
+		testutil.NewKey(new(tinkpb.KeyData), tinkpb.KeyStatusType_ENABLED, 1, tinkpb.OutputPrefixType_UNKNOWN_PREFIX),
+	}
+}
diff --git a/go/tink/private_key_manager.go b/go/keyset/writer.go
similarity index 64%
copy from go/tink/private_key_manager.go
copy to go/keyset/writer.go
index 7cf169a..129120f 100644
--- a/go/tink/private_key_manager.go
+++ b/go/keyset/writer.go
@@ -12,16 +12,15 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-package tink
+package keyset
 
-import (
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
-)
+import tinkpb "github.com/google/tink/proto/tink_go_proto"
 
-// PrivateKeyManager is a special type of KeyManager that understands private key types.
-type PrivateKeyManager interface {
-	KeyManager
+// Writer knows how to write a Keyset or an EncryptedKeyset to some source.
+type Writer interface {
+	// Write keyset to some storage system.
+	Write(Keyset *tinkpb.Keyset) error
 
-	// GetPublicKeyData extracts the public key data from the private key.
-	GetPublicKeyData(serializedKey []byte) (*tinkpb.KeyData, error)
+	// Write EncryptedKeyset to some storage system.
+	WriteEncrypted(keyset *tinkpb.EncryptedKeyset) error
 }
diff --git a/go/mac/BUILD.bazel b/go/mac/BUILD.bazel
index acd5a2a..9edc52c 100644
--- a/go/mac/BUILD.bazel
+++ b/go/mac/BUILD.bazel
@@ -1,17 +1,24 @@
+package(default_visibility = ["//tools/build_defs:internal_pkg"])  # keep
+
+licenses(["notice"])  # Apache 2.0 # keep
+
 load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 
 go_library(
     name = "go_default_library",
     srcs = [
         "hmac_key_manager.go",
-        "mac_config.go",
+        "mac.go",
         "mac_factory.go",
         "mac_key_templates.go",
-        "proto_util.go",
     ],
     importpath = "github.com/google/tink/go/mac",
     visibility = ["//visibility:public"],
     deps = [
+        "//go/format:go_default_library",
+        "//go/keyset:go_default_library",
+        "//go/primitiveset:go_default_library",
+        "//go/registry:go_default_library",
         "//go/subtle/mac:go_default_library",
         "//go/subtle/random:go_default_library",
         "//go/tink:go_default_library",
@@ -23,18 +30,21 @@
 )
 
 go_test(
-    name = "go_default_xtest",
+    name = "go_default_test",
     srcs = [
         "hmac_key_manager_test.go",
-        "mac_config_test.go",
         "mac_factory_test.go",
         "mac_key_templates_test.go",
+        "mac_test.go",
     ],
+    embed = [":go_default_library"],
     deps = [
-        ":go_default_library",
+        "//go/format:go_default_library",
+        "//go/registry:go_default_library",
         "//go/subtle:go_default_library",
         "//go/subtle/mac:go_default_library",
         "//go/subtle/random:go_default_library",
+        "//go/testkeyset:go_default_library",
         "//go/testutil:go_default_library",
         "//go/tink:go_default_library",
         "//proto:common_go_proto",
diff --git a/go/mac/hmac_key_manager.go b/go/mac/hmac_key_manager.go
index f3ca17e..aff45a1 100644
--- a/go/mac/hmac_key_manager.go
+++ b/go/mac/hmac_key_manager.go
@@ -15,141 +15,124 @@
 package mac
 
 import (
+	"errors"
 	"fmt"
 
 	"github.com/golang/protobuf/proto"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/registry"
 	"github.com/google/tink/go/subtle/mac"
 	"github.com/google/tink/go/subtle/random"
-	"github.com/google/tink/go/tink"
+	commonpb "github.com/google/tink/proto/common_go_proto"
 	hmacpb "github.com/google/tink/proto/hmac_go_proto"
 	tinkpb "github.com/google/tink/proto/tink_go_proto"
 )
 
 const (
-	// HmacTypeURL is the only type URL that this manager supports.
-	HmacTypeURL = "type.googleapis.com/google.crypto.tink.HmacKey"
-
-	// HmacKeyVersion is the maxmimal version of keys that this key manager supports.
-	HmacKeyVersion = uint32(0)
+	hmacKeyVersion = 0
+	hmacTypeURL    = "type.googleapis.com/google.crypto.tink.HmacKey"
 )
 
-var errInvalidHmacKey = fmt.Errorf("hmac_key_manager: invalid key")
-var errInvalidHmacKeyFormat = fmt.Errorf("hmac_key_manager: invalid key format")
+var errInvalidHMACKey = errors.New("hmac_key_manager: invalid key")
+var errInvalidHMACKeyFormat = errors.New("hmac_key_manager: invalid key format")
 
-// HmacKeyManager generates new HmacKeys and produces new instances of Hmac.
-type HmacKeyManager struct{}
+// hmacKeyManager generates new HMAC keys and produces new instances of HMAC.
+type hmacKeyManager struct{}
 
-// Assert that HmacKeyManager implements the KeyManager interface.
-var _ tink.KeyManager = (*HmacKeyManager)(nil)
+// Assert that hmacKeyManager implements the KeyManager interface.
+var _ registry.KeyManager = (*hmacKeyManager)(nil)
 
-// NewHmacKeyManager returns a new HmacKeyManager.
-func NewHmacKeyManager() *HmacKeyManager {
-	return new(HmacKeyManager)
+// newHMACKeyManager returns a new hmacKeyManager.
+func newHMACKeyManager() *hmacKeyManager {
+	return new(hmacKeyManager)
 }
 
-// GetPrimitiveFromSerializedKey constructs a Hmac instance for the given
-// serialized HmacKey.
-func (km *HmacKeyManager) GetPrimitiveFromSerializedKey(serializedKey []byte) (interface{}, error) {
+// Primitive constructs a HMAC instance for the given serialized HMACKey.
+func (km *hmacKeyManager) Primitive(serializedKey []byte) (interface{}, error) {
 	if len(serializedKey) == 0 {
-		return nil, errInvalidHmacKey
+		return nil, errInvalidHMACKey
 	}
 	key := new(hmacpb.HmacKey)
 	if err := proto.Unmarshal(serializedKey, key); err != nil {
-		return nil, errInvalidHmacKey
-	}
-	return km.GetPrimitiveFromKey(key)
-}
-
-// GetPrimitiveFromKey constructs a HMAC instance for the given HmacKey.
-func (km *HmacKeyManager) GetPrimitiveFromKey(m proto.Message) (interface{}, error) {
-	key, ok := m.(*hmacpb.HmacKey)
-	if !ok {
-		return nil, errInvalidHmacKey
+		return nil, errInvalidHMACKey
 	}
 	if err := km.validateKey(key); err != nil {
 		return nil, err
 	}
-	hash := tink.GetHashName(key.Params.Hash)
-	hmac, err := mac.NewHmac(hash, key.KeyValue, key.Params.TagSize)
+	hash := commonpb.HashType_name[int32(key.Params.Hash)]
+	hmac, err := mac.NewHMAC(hash, key.KeyValue, key.Params.TagSize)
 	if err != nil {
 		return nil, err
 	}
 	return hmac, nil
 }
 
-// NewKeyFromSerializedKeyFormat generates a new HmacKey according to specification
-// in the given serialized HmacKeyFormat.
-func (km *HmacKeyManager) NewKeyFromSerializedKeyFormat(serializedKeyFormat []byte) (proto.Message, error) {
+// NewKey generates a new HMACKey according to specification in the given HMACKeyFormat.
+func (km *hmacKeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) {
 	if len(serializedKeyFormat) == 0 {
-		return nil, errInvalidHmacKeyFormat
+		return nil, errInvalidHMACKeyFormat
 	}
 	keyFormat := new(hmacpb.HmacKeyFormat)
 	if err := proto.Unmarshal(serializedKeyFormat, keyFormat); err != nil {
-		return nil, errInvalidHmacKeyFormat
-	}
-	return km.NewKeyFromKeyFormat(keyFormat)
-}
-
-// NewKeyFromKeyFormat generates a new HmacKey according to specification in
-// the given HmacKeyFormat.
-func (km *HmacKeyManager) NewKeyFromKeyFormat(m proto.Message) (proto.Message, error) {
-	keyFormat, ok := m.(*hmacpb.HmacKeyFormat)
-	if !ok {
-		return nil, errInvalidHmacKeyFormat
+		return nil, errInvalidHMACKeyFormat
 	}
 	if err := km.validateKeyFormat(keyFormat); err != nil {
 		return nil, fmt.Errorf("hmac_key_manager: invalid key format: %s", err)
 	}
 	keyValue := random.GetRandomBytes(keyFormat.KeySize)
-	return NewHmacKey(keyFormat.Params, HmacKeyVersion, keyValue), nil
+	return &hmacpb.HmacKey{
+		Version:  hmacKeyVersion,
+		Params:   keyFormat.Params,
+		KeyValue: keyValue,
+	}, nil
 }
 
 // NewKeyData generates a new KeyData according to specification in the given
-// serialized HmacKeyFormat. This should be used solely by the key management API.
-func (km *HmacKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) {
-	key, err := km.NewKeyFromSerializedKeyFormat(serializedKeyFormat)
+// serialized HMACKeyFormat. This should be used solely by the key management API.
+func (km *hmacKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) {
+	key, err := km.NewKey(serializedKeyFormat)
 	if err != nil {
 		return nil, err
 	}
 	serializedKey, err := proto.Marshal(key)
 	if err != nil {
-		return nil, errInvalidHmacKeyFormat
+		return nil, errInvalidHMACKeyFormat
 	}
 
 	return &tinkpb.KeyData{
-		TypeUrl:         HmacTypeURL,
+		TypeUrl:         hmacTypeURL,
 		Value:           serializedKey,
 		KeyMaterialType: tinkpb.KeyData_SYMMETRIC,
 	}, nil
 }
 
 // DoesSupport checks whether this KeyManager supports the given key type.
-func (km *HmacKeyManager) DoesSupport(typeURL string) bool {
-	return typeURL == HmacTypeURL
+func (km *hmacKeyManager) DoesSupport(typeURL string) bool {
+	return typeURL == hmacTypeURL
 }
 
-// GetKeyType returns the type URL of keys managed by this KeyManager.
-func (km *HmacKeyManager) GetKeyType() string {
-	return HmacTypeURL
+// TypeURL returns the type URL of keys managed by this KeyManager.
+func (km *hmacKeyManager) TypeURL() string {
+	return hmacTypeURL
 }
 
-// validateKey validates the given HmacKey. It only validates the version of the
+// validateKey validates the given HMACKey. It only validates the version of the
 // key because other parameters will be validated in primitive construction.
-func (km *HmacKeyManager) validateKey(key *hmacpb.HmacKey) error {
-	err := tink.ValidateVersion(key.Version, HmacKeyVersion)
+func (km *hmacKeyManager) validateKey(key *hmacpb.HmacKey) error {
+	err := keyset.ValidateKeyVersion(key.Version, hmacKeyVersion)
 	if err != nil {
-		return fmt.Errorf("hmac_key_manager: %s", err)
+		return fmt.Errorf("hmac_key_manager: invalid version: %s", err)
 	}
 	keySize := uint32(len(key.KeyValue))
-	hash := tink.GetHashName(key.Params.Hash)
-	return mac.ValidateHmacParams(hash, keySize, key.Params.TagSize)
+	hash := commonpb.HashType_name[int32(key.Params.Hash)]
+	return mac.ValidateHMACParams(hash, keySize, key.Params.TagSize)
 }
 
-// validateKeyFormat validates the given HmacKeyFormat
-func (km *HmacKeyManager) validateKeyFormat(format *hmacpb.HmacKeyFormat) error {
+// validateKeyFormat validates the given HMACKeyFormat
+func (km *hmacKeyManager) validateKeyFormat(format *hmacpb.HmacKeyFormat) error {
 	if format.Params == nil {
 		return fmt.Errorf("null HMAC params")
 	}
-	hash := tink.GetHashName(format.Params.Hash)
-	return mac.ValidateHmacParams(hash, format.KeySize, format.Params.TagSize)
+	hash := commonpb.HashType_name[int32(format.Params.Hash)]
+	return mac.ValidateHMACParams(hash, format.KeySize, format.Params.TagSize)
 }
diff --git a/go/mac/hmac_key_manager_test.go b/go/mac/hmac_key_manager_test.go
index d26bfd5..e2609d0 100644
--- a/go/mac/hmac_key_manager_test.go
+++ b/go/mac/hmac_key_manager_test.go
@@ -21,165 +21,134 @@
 	"testing"
 
 	"github.com/golang/protobuf/proto"
-	"github.com/google/tink/go/mac"
+	"github.com/google/tink/go/registry"
 	subtleMac "github.com/google/tink/go/subtle/mac"
 	"github.com/google/tink/go/subtle/random"
 	"github.com/google/tink/go/subtle"
 	"github.com/google/tink/go/testutil"
-	"github.com/google/tink/go/tink"
 	commonpb "github.com/google/tink/proto/common_go_proto"
 	hmacpb "github.com/google/tink/proto/hmac_go_proto"
 	tinkpb "github.com/google/tink/proto/tink_go_proto"
 )
 
 func TestGetPrimitiveBasic(t *testing.T) {
-	km := mac.NewHmacKeyManager()
-	testKeys := genValidHmacKeys()
+	km, err := registry.GetKeyManager(testutil.HMACTypeURL)
+	if err != nil {
+		t.Errorf("HMAC key manager not found: %s", err)
+	}
+	testKeys := genValidHMACKeys()
 	for i := 0; i < len(testKeys); i++ {
-		key := testKeys[i]
-		p, err := km.GetPrimitiveFromKey(key)
-		if err != nil {
-			t.Errorf("unexpected error in test case %d: %s", i, err)
-		}
-		if err := validateHmacPrimitive(p, key); err != nil {
-			t.Errorf("%s", err)
-		}
-
 		serializedKey, _ := proto.Marshal(testKeys[i])
-		p, err = km.GetPrimitiveFromSerializedKey(serializedKey)
+		p, err := km.Primitive(serializedKey)
 		if err != nil {
 			t.Errorf("unexpected error in test case %d: %s", i, err)
 		}
-		if err := validateHmacPrimitive(p, key); err != nil {
+		if err := validateHMACPrimitive(p, testKeys[i]); err != nil {
 			t.Errorf("%s", err)
 		}
 	}
 }
 
 func TestGetPrimitiveWithInvalidInput(t *testing.T) {
-	km := mac.NewHmacKeyManager()
+	km, err := registry.GetKeyManager(testutil.HMACTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain HMAC key manager: %s", err)
+	}
 	// invalid key
-	testKeys := genInvalidHmacKeys()
+	testKeys := genInvalidHMACKeys()
 	for i := 0; i < len(testKeys); i++ {
-		if _, err := km.GetPrimitiveFromKey(testKeys[i]); err == nil {
-			t.Errorf("expect an error in test case %d", i)
-		}
 		serializedKey, _ := proto.Marshal(testKeys[i])
-		if _, err := km.GetPrimitiveFromSerializedKey(serializedKey); err == nil {
+		if _, err := km.Primitive(serializedKey); err == nil {
 			t.Errorf("expect an error in test case %d", i)
 		}
 	}
-	// nil
-	if _, err := km.GetPrimitiveFromKey(nil); err == nil {
-		t.Errorf("expect an error when input is nil")
-	}
-	if _, err := km.GetPrimitiveFromSerializedKey(nil); err == nil {
+	if _, err := km.Primitive(nil); err == nil {
 		t.Errorf("expect an error when input is nil")
 	}
 	// empty input
-	if _, err := km.GetPrimitiveFromSerializedKey([]byte{}); err == nil {
+	if _, err := km.Primitive([]byte{}); err == nil {
 		t.Errorf("expect an error when input is empty")
 	}
 }
 
 func TestNewKeyMultipleTimes(t *testing.T) {
-	km := mac.NewHmacKeyManager()
-	format := testutil.NewHmacKeyFormat(commonpb.HashType_SHA256, 32)
-	serializedFormat, _ := proto.Marshal(format)
+	km, err := registry.GetKeyManager(testutil.HMACTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain HMAC key manager: %s", err)
+	}
+	serializedFormat, _ := proto.Marshal(testutil.NewHMACKeyFormat(commonpb.HashType_SHA256, 32))
 	keys := make(map[string]bool)
 	nTest := 26
 	for i := 0; i < nTest; i++ {
-		key, err := km.NewKeyFromSerializedKeyFormat(serializedFormat)
-		if err != nil {
-			t.Errorf("unexpected error: %s", err)
-		}
+		key, _ := km.NewKey(serializedFormat)
 		serializedKey, _ := proto.Marshal(key)
 		keys[string(serializedKey)] = true
 
-		key, err = km.NewKeyFromKeyFormat(format)
-		if err != nil {
-			t.Errorf("unexpected error: %s", err)
-		}
-		serializedKey, _ = proto.Marshal(key)
+		keyData, _ := km.NewKeyData(serializedFormat)
+		serializedKey = keyData.Value
 		keys[string(serializedKey)] = true
-
-		keyData, err := km.NewKeyData(serializedFormat)
-		if err != nil {
-			t.Errorf("unexpected error: %s", err)
-		}
-		keys[string(keyData.Value)] = true
 	}
-	if len(keys) != nTest*3 {
+	if len(keys) != nTest*2 {
 		t.Errorf("key is repeated")
 	}
 }
 
 func TestNewKeyBasic(t *testing.T) {
-	km := mac.NewHmacKeyManager()
-	testFormats := genValidHmacKeyFormats()
+	km, err := registry.GetKeyManager(testutil.HMACTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain HMAC key manager: %s", err)
+	}
+	testFormats := genValidHMACKeyFormats()
 	for i := 0; i < len(testFormats); i++ {
-		format := testFormats[i]
-		key, err := km.NewKeyFromKeyFormat(format)
+		serializedFormat, _ := proto.Marshal(testFormats[i])
+		key, err := km.NewKey(serializedFormat)
 		if err != nil {
 			t.Errorf("unexpected error in test case %d: %s", i, err)
 		}
-		if err := validateHmacKey(format, key.(*hmacpb.HmacKey)); err != nil {
-			t.Errorf("%s", err)
-		}
-
-		serializedFormat, _ := proto.Marshal(format)
-		key, err = km.NewKeyFromSerializedKeyFormat(serializedFormat)
-		if err != nil {
-			t.Errorf("unexpected error in test case %d: %s", i, err)
-		}
-		if err := validateHmacKey(format, key.(*hmacpb.HmacKey)); err != nil {
+		if err := validateHMACKey(testFormats[i], key.(*hmacpb.HmacKey)); err != nil {
 			t.Errorf("%s", err)
 		}
 	}
 }
 
 func TestNewKeyWithInvalidInput(t *testing.T) {
-	km := mac.NewHmacKeyManager()
+	km, err := registry.GetKeyManager(testutil.HMACTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain HMAC key manager: %s", err)
+	}
 	// invalid key formats
-	testFormats := genInvalidHmacKeyFormats()
+	testFormats := genInvalidHMACKeyFormats()
 	for i := 0; i < len(testFormats); i++ {
-		format := testFormats[i]
-		if _, err := km.NewKeyFromKeyFormat(format); err == nil {
-			t.Errorf("expect an error in test case %d: %s", i, err)
-		}
-
-		serializedFormat, err := proto.Marshal(format)
+		serializedFormat, err := proto.Marshal(testFormats[i])
 		if err != nil {
 			fmt.Println("Error!")
 		}
-		if _, err := km.NewKeyFromSerializedKeyFormat(serializedFormat); err == nil {
+		if _, err := km.NewKey(serializedFormat); err == nil {
 			t.Errorf("expect an error in test case %d: %s", i, err)
 		}
 	}
-	// nil
-	if _, err := km.NewKeyFromKeyFormat(nil); err == nil {
-		t.Errorf("expect an error when input is nil")
-	}
-	if _, err := km.NewKeyFromSerializedKeyFormat(nil); err == nil {
+	if _, err := km.NewKey(nil); err == nil {
 		t.Errorf("expect an error when input is nil")
 	}
 	// empty input
-	if _, err := km.NewKeyFromSerializedKeyFormat([]byte{}); err == nil {
+	if _, err := km.NewKey([]byte{}); err == nil {
 		t.Errorf("expect an error when input is empty")
 	}
 }
 
 func TestNewKeyDataBasic(t *testing.T) {
-	km := mac.NewHmacKeyManager()
-	testFormats := genValidHmacKeyFormats()
+	km, err := registry.GetKeyManager(testutil.HMACTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain HMAC key manager: %s", err)
+	}
+	testFormats := genValidHMACKeyFormats()
 	for i := 0; i < len(testFormats); i++ {
-		format := testFormats[i]
-		serializedFormat, _ := proto.Marshal(format)
+		serializedFormat, _ := proto.Marshal(testFormats[i])
 		keyData, err := km.NewKeyData(serializedFormat)
 		if err != nil {
 			t.Errorf("unexpected error in test case %d: %s", i, err)
 		}
-		if keyData.TypeUrl != mac.HmacTypeURL {
+		if keyData.TypeUrl != testutil.HMACTypeURL {
 			t.Errorf("incorrect type url in test case %d", i)
 		}
 		if keyData.KeyMaterialType != tinkpb.KeyData_SYMMETRIC {
@@ -189,19 +158,21 @@
 		if err := proto.Unmarshal(keyData.Value, key); err != nil {
 			t.Errorf("invalid key value")
 		}
-		if err := validateHmacKey(format, key); err != nil {
+		if err := validateHMACKey(testFormats[i], key); err != nil {
 			t.Errorf("invalid key")
 		}
 	}
 }
 
 func TestNewKeyDataWithInvalidInput(t *testing.T) {
-	km := mac.NewHmacKeyManager()
+	km, err := registry.GetKeyManager(testutil.HMACTypeURL)
+	if err != nil {
+		t.Errorf("HMAC key manager not found: %s", err)
+	}
 	// invalid key formats
-	testFormats := genInvalidHmacKeyFormats()
+	testFormats := genInvalidHMACKeyFormats()
 	for i := 0; i < len(testFormats); i++ {
-		format := testFormats[i]
-		serializedFormat, _ := proto.Marshal(format)
+		serializedFormat, _ := proto.Marshal(testFormats[i])
 		if _, err := km.NewKeyData(serializedFormat); err == nil {
 			t.Errorf("expect an error in test case %d", i)
 		}
@@ -213,114 +184,115 @@
 }
 
 func TestDoesSupport(t *testing.T) {
-	km := mac.NewHmacKeyManager()
-	if !km.DoesSupport(mac.HmacTypeURL) {
-		t.Errorf("HmacKeyManager must support %s", mac.HmacTypeURL)
+	km, err := registry.GetKeyManager(testutil.HMACTypeURL)
+	if err != nil {
+		t.Errorf("HMAC key manager not found: %s", err)
+	}
+	if !km.DoesSupport(testutil.HMACTypeURL) {
+		t.Errorf("HMACKeyManager must support %s", testutil.HMACTypeURL)
 	}
 	if km.DoesSupport("some bad type") {
-		t.Errorf("HmacKeyManager must support only %s", mac.HmacTypeURL)
+		t.Errorf("HMACKeyManager must support only %s", testutil.HMACTypeURL)
 	}
 }
 
-func TestGetKeyType(t *testing.T) {
-	km := mac.NewHmacKeyManager()
-	if km.GetKeyType() != mac.HmacTypeURL {
+func TestTypeURL(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.HMACTypeURL)
+	if err != nil {
+		t.Errorf("HMAC key manager not found: %s", err)
+	}
+	if km.TypeURL() != testutil.HMACTypeURL {
 		t.Errorf("incorrect GetKeyType()")
 	}
 }
 
-func TestKeyManagerInterface(t *testing.T) {
-	// This line throws an error if Hmac doesn't implement KeyManager interface
-	var _ tink.KeyManager = (*mac.HmacKeyManager)(nil)
-}
-
-func genInvalidHmacKeys() []proto.Message {
-	badVersionKey := testutil.NewHmacKey(commonpb.HashType_SHA256, 32)
+func genInvalidHMACKeys() []proto.Message {
+	badVersionKey := testutil.NewHMACKey(commonpb.HashType_SHA256, 32)
 	badVersionKey.Version++
-	shortKey := testutil.NewHmacKey(commonpb.HashType_SHA256, 32)
+	shortKey := testutil.NewHMACKey(commonpb.HashType_SHA256, 32)
 	shortKey.KeyValue = []byte{1, 1}
 	return []proto.Message{
-		// not a HmacKey
-		mac.NewHmacParams(commonpb.HashType_SHA256, 32),
+		// not a HMACKey
+		testutil.NewHMACParams(commonpb.HashType_SHA256, 32),
 		// bad version
 		badVersionKey,
 		// tag size too big
-		testutil.NewHmacKey(commonpb.HashType_SHA1, 21),
-		testutil.NewHmacKey(commonpb.HashType_SHA256, 33),
-		testutil.NewHmacKey(commonpb.HashType_SHA512, 65),
+		testutil.NewHMACKey(commonpb.HashType_SHA1, 21),
+		testutil.NewHMACKey(commonpb.HashType_SHA256, 33),
+		testutil.NewHMACKey(commonpb.HashType_SHA512, 65),
 		// tag size too small
-		testutil.NewHmacKey(commonpb.HashType_SHA256, 1),
+		testutil.NewHMACKey(commonpb.HashType_SHA256, 1),
 		// key too short
 		shortKey,
 		// unknown hash type
-		testutil.NewHmacKey(commonpb.HashType_UNKNOWN_HASH, 32),
+		testutil.NewHMACKey(commonpb.HashType_UNKNOWN_HASH, 32),
 	}
 }
 
-func genInvalidHmacKeyFormats() []proto.Message {
-	shortKeyFormat := testutil.NewHmacKeyFormat(commonpb.HashType_SHA256, 32)
+func genInvalidHMACKeyFormats() []proto.Message {
+	shortKeyFormat := testutil.NewHMACKeyFormat(commonpb.HashType_SHA256, 32)
 	shortKeyFormat.KeySize = 1
 	return []proto.Message{
-		// not a HmacKeyFormat
-		mac.NewHmacParams(commonpb.HashType_SHA256, 32),
+		// not a HMACKeyFormat
+		testutil.NewHMACParams(commonpb.HashType_SHA256, 32),
 		// tag size too big
-		testutil.NewHmacKeyFormat(commonpb.HashType_SHA1, 21),
-		testutil.NewHmacKeyFormat(commonpb.HashType_SHA256, 33),
-		testutil.NewHmacKeyFormat(commonpb.HashType_SHA512, 65),
+		testutil.NewHMACKeyFormat(commonpb.HashType_SHA1, 21),
+		testutil.NewHMACKeyFormat(commonpb.HashType_SHA256, 33),
+		testutil.NewHMACKeyFormat(commonpb.HashType_SHA512, 65),
 		// tag size too small
-		testutil.NewHmacKeyFormat(commonpb.HashType_SHA256, 1),
+		testutil.NewHMACKeyFormat(commonpb.HashType_SHA256, 1),
 		// key too short
 		shortKeyFormat,
 		// unknown hash type
-		testutil.NewHmacKeyFormat(commonpb.HashType_UNKNOWN_HASH, 32),
+		testutil.NewHMACKeyFormat(commonpb.HashType_UNKNOWN_HASH, 32),
 	}
 }
 
-func genValidHmacKeyFormats() []*hmacpb.HmacKeyFormat {
+func genValidHMACKeyFormats() []*hmacpb.HmacKeyFormat {
 	return []*hmacpb.HmacKeyFormat{
-		testutil.NewHmacKeyFormat(commonpb.HashType_SHA1, 20),
-		testutil.NewHmacKeyFormat(commonpb.HashType_SHA256, 32),
-		testutil.NewHmacKeyFormat(commonpb.HashType_SHA512, 64),
+		testutil.NewHMACKeyFormat(commonpb.HashType_SHA1, 20),
+		testutil.NewHMACKeyFormat(commonpb.HashType_SHA256, 32),
+		testutil.NewHMACKeyFormat(commonpb.HashType_SHA512, 64),
 	}
 }
 
-func genValidHmacKeys() []*hmacpb.HmacKey {
+func genValidHMACKeys() []*hmacpb.HmacKey {
 	return []*hmacpb.HmacKey{
-		testutil.NewHmacKey(commonpb.HashType_SHA1, 20),
-		testutil.NewHmacKey(commonpb.HashType_SHA256, 32),
-		testutil.NewHmacKey(commonpb.HashType_SHA512, 64),
+		testutil.NewHMACKey(commonpb.HashType_SHA1, 20),
+		testutil.NewHMACKey(commonpb.HashType_SHA256, 32),
+		testutil.NewHMACKey(commonpb.HashType_SHA512, 64),
 	}
 }
 
-// Checks whether the given HmacKey matches the given key HmacKeyFormat
-func validateHmacKey(format *hmacpb.HmacKeyFormat, key *hmacpb.HmacKey) error {
+// Checks whether the given HMACKey matches the given key HMACKeyFormat
+func validateHMACKey(format *hmacpb.HmacKeyFormat, key *hmacpb.HmacKey) error {
 	if format.KeySize != uint32(len(key.KeyValue)) ||
 		key.Params.TagSize != format.Params.TagSize ||
 		key.Params.Hash != format.Params.Hash {
 		return fmt.Errorf("key format and generated key do not match")
 	}
-	p, err := subtleMac.NewHmac(tink.GetHashName(key.Params.Hash), key.KeyValue, key.Params.TagSize)
+	p, err := subtleMac.NewHMAC(commonpb.HashType_name[int32(key.Params.Hash)], key.KeyValue, key.Params.TagSize)
 	if err != nil {
 		return fmt.Errorf("cannot create primitive from key: %s", err)
 	}
-	return validateHmacPrimitive(p, key)
+	return validateHMACPrimitive(p, key)
 }
 
-// validateHmacPrimitive checks whether the given primitive matches the given HmacKey
-func validateHmacPrimitive(p interface{}, key *hmacpb.HmacKey) error {
-	hmacPrimitive := p.(*subtleMac.Hmac)
+// validateHMACPrimitive checks whether the given primitive matches the given HMACKey
+func validateHMACPrimitive(p interface{}, key *hmacpb.HmacKey) error {
+	hmacPrimitive := p.(*subtleMac.HMAC)
 	if !bytes.Equal(hmacPrimitive.Key, key.KeyValue) ||
 		hmacPrimitive.TagSize != key.Params.TagSize ||
 		reflect.ValueOf(hmacPrimitive.HashFunc).Pointer() !=
-			reflect.ValueOf(subtle.GetHashFunc(tink.GetHashName(key.Params.Hash))).Pointer() {
+			reflect.ValueOf(subtle.GetHashFunc(commonpb.HashType_name[int32(key.Params.Hash)])).Pointer() {
 		return fmt.Errorf("primitive and key do not matched")
 	}
 	data := random.GetRandomBytes(20)
-	mac, err := hmacPrimitive.ComputeMac(data)
+	mac, err := hmacPrimitive.ComputeMAC(data)
 	if err != nil {
 		return fmt.Errorf("mac computation failed: %s", err)
 	}
-	if valid, err := hmacPrimitive.VerifyMac(mac, data); !valid || err != nil {
+	if err = hmacPrimitive.VerifyMAC(mac, data); err != nil {
 		return fmt.Errorf("mac verification failed: %s", err)
 	}
 	return nil
diff --git a/go/tink/private_key_manager.go b/go/mac/mac.go
similarity index 65%
copy from go/tink/private_key_manager.go
copy to go/mac/mac.go
index 7cf169a..d6a5487 100644
--- a/go/tink/private_key_manager.go
+++ b/go/mac/mac.go
@@ -12,16 +12,17 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-package tink
+// Package mac provides implementations of the MAC primitive.
+package mac
 
 import (
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
+	"fmt"
+
+	"github.com/google/tink/go/registry"
 )
 
-// PrivateKeyManager is a special type of KeyManager that understands private key types.
-type PrivateKeyManager interface {
-	KeyManager
-
-	// GetPublicKeyData extracts the public key data from the private key.
-	GetPublicKeyData(serializedKey []byte) (*tinkpb.KeyData, error)
+func init() {
+	if err := registry.RegisterKeyManager(newHMACKeyManager()); err != nil {
+		panic(fmt.Sprintf("mac.init() failed: %v", err))
+	}
 }
diff --git a/go/mac/mac_config.go b/go/mac/mac_config.go
deleted file mode 100644
index c19c295..0000000
--- a/go/mac/mac_config.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-// Package mac provides implementations of the Mac primitive.
-package mac
-
-import (
-	"github.com/google/tink/go/tink"
-)
-
-// RegisterStandardKeyTypes registers standard Mac key types and their managers
-// with the Registry.
-func RegisterStandardKeyTypes() (bool, error) {
-	return RegisterKeyManager(NewHmacKeyManager())
-}
-
-// RegisterKeyManager registers the given keyManager for the key type given in
-// keyManager.KeyType(). It returns true if registration was successful, false if
-// there already exisits a key manager for the key type.
-func RegisterKeyManager(keyManager tink.KeyManager) (bool, error) {
-	return tink.RegisterKeyManager(keyManager)
-}
diff --git a/go/mac/mac_factory.go b/go/mac/mac_factory.go
index f8d4f57..f8b3377 100644
--- a/go/mac/mac_factory.go
+++ b/go/mac/mac_factory.go
@@ -17,93 +17,90 @@
 import (
 	"fmt"
 
+	"github.com/google/tink/go/format"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/primitiveset"
+	"github.com/google/tink/go/registry"
 	"github.com/google/tink/go/tink"
 )
 
-// GetPrimitive creates a Mac primitive from the given keyset handle.
-func GetPrimitive(handle *tink.KeysetHandle) (tink.Mac, error) {
-	return GetPrimitiveWithCustomerManager(handle, nil /*keyManager*/)
+// New creates a MAC primitive from the given keyset handle.
+func New(h *keyset.Handle) (tink.MAC, error) {
+	return NewWithKeyManager(h, nil /*keyManager*/)
 }
 
-// GetPrimitiveWithCustomerManager creates a Mac primitive from the given
-// keyset handle and a custom key manager.
-func GetPrimitiveWithCustomerManager(
-	handle *tink.KeysetHandle, manager tink.KeyManager) (tink.Mac, error) {
-	ps, err := tink.GetPrimitivesWithCustomManager(handle, manager)
+// NewWithKeyManager creates a MAC primitive from the given keyset handle and a custom key manager.
+func NewWithKeyManager(h *keyset.Handle, km registry.KeyManager) (tink.MAC, error) {
+	ps, err := h.PrimitivesWithKeyManager(km)
 	if err != nil {
 		return nil, fmt.Errorf("mac_factory: cannot obtain primitive set: %s", err)
 	}
-	var mac tink.Mac = newPrimitiveSetMac(ps)
-	return mac, nil
+	return newPrimitiveSet(ps), nil
 }
 
-// primitiveSetMac is a MAC implementation that uses the underlying primitive set to compute and
+// primitiveSet is a MAC implementation that uses the underlying primitive set to compute and
 // verify MACs.
-type primitiveSetMac struct {
-	ps *tink.PrimitiveSet
+type primitiveSet struct {
+	ps *primitiveset.PrimitiveSet
 }
 
-// Asserts that primitiveSetMac implements the Mac interface.
-var _ tink.Mac = (*primitiveSetMac)(nil)
+// Asserts that primitiveSet implements the Mac interface.
+var _ tink.MAC = (*primitiveSet)(nil)
 
-// newPrimitiveSetMac creates a new instance of primitiveSetMac using the given
-// primitive set.
-func newPrimitiveSetMac(ps *tink.PrimitiveSet) *primitiveSetMac {
-	ret := new(primitiveSetMac)
+func newPrimitiveSet(ps *primitiveset.PrimitiveSet) *primitiveSet {
+	ret := new(primitiveSet)
 	ret.ps = ps
 	return ret
 }
 
-// ComputeMac calculates a MAC over the given data using the primary primitive
+// ComputeMAC calculates a MAC over the given data using the primary primitive
 // and returns the concatenation of the primary's identifier and the calculated mac.
-func (m *primitiveSetMac) ComputeMac(data []byte) ([]byte, error) {
-	primary := m.ps.Primary()
-	var primitive tink.Mac = (primary.Primitive()).(tink.Mac)
-	mac, err := primitive.ComputeMac(data)
+func (m *primitiveSet) ComputeMAC(data []byte) ([]byte, error) {
+	primary := m.ps.Primary
+	var primitive = (primary.Primitive).(tink.MAC)
+	mac, err := primitive.ComputeMAC(data)
 	if err != nil {
 		return nil, err
 	}
 	var ret []byte
-	ret = append(ret, primary.Identifier()...)
+	ret = append(ret, primary.Prefix...)
 	ret = append(ret, mac...)
 	return ret, nil
 }
 
-var errInvalidMac = fmt.Errorf("mac_factory: invalid mac")
+var errInvalidMAC = fmt.Errorf("mac_factory: invalid mac")
 
-// VerifyMac verifies whether the given mac is a correct authentication code
+// VerifyMAC verifies whether the given mac is a correct authentication code
 // for the given data.
-func (m *primitiveSetMac) VerifyMac(mac []byte, data []byte) (bool, error) {
+func (m *primitiveSet) VerifyMAC(mac, data []byte) error {
 	// This also rejects raw MAC with size of 4 bytes or fewer. Those MACs are
 	// clearly insecure, thus should be discouraged.
-	prefixSize := tink.NonRawPrefixSize
+	prefixSize := format.NonRawPrefixSize
 	if len(mac) <= prefixSize {
-		return false, errInvalidMac
+		return errInvalidMAC
 	}
 	// try non raw keys
 	prefix := mac[:prefixSize]
 	macNoPrefix := mac[prefixSize:]
-	entries, err := m.ps.GetPrimitivesWithByteIdentifier(prefix)
+	entries, err := m.ps.EntriesForPrefix(string(prefix))
 	if err == nil {
 		for i := 0; i < len(entries); i++ {
-			var p tink.Mac = (entries[i].Primitive()).(tink.Mac)
-			valid, err := p.VerifyMac(macNoPrefix, data)
-			if err == nil && valid {
-				return true, nil
+			var p = (entries[i].Primitive).(tink.MAC)
+			if err = p.VerifyMAC(macNoPrefix, data); err == nil {
+				return nil
 			}
 		}
 	}
 	// try raw keys
-	entries, err = m.ps.GetRawPrimitives()
+	entries, err = m.ps.RawEntries()
 	if err == nil {
 		for i := 0; i < len(entries); i++ {
-			var p tink.Mac = (entries[i].Primitive()).(tink.Mac)
-			valid, err := p.VerifyMac(mac, data)
-			if err == nil && valid {
-				return true, nil
+			var p = (entries[i].Primitive).(tink.MAC)
+			if err = p.VerifyMAC(mac, data); err == nil {
+				return nil
 			}
 		}
 	}
 	// nothing worked
-	return false, errInvalidMac
+	return errInvalidMAC
 }
diff --git a/go/mac/mac_factory_test.go b/go/mac/mac_factory_test.go
index 6c8f5a3..243dc97 100644
--- a/go/mac/mac_factory_test.go
+++ b/go/mac/mac_factory_test.go
@@ -19,26 +19,36 @@
 	"strings"
 	"testing"
 
+	"github.com/google/tink/go/format"
 	"github.com/google/tink/go/mac"
+	"github.com/google/tink/go/testkeyset"
 	"github.com/google/tink/go/testutil"
 	"github.com/google/tink/go/tink"
+
 	tinkpb "github.com/google/tink/proto/tink_go_proto"
 )
 
 func TestFactoryMultipleKeys(t *testing.T) {
 	tagSize := uint32(16)
-	keyset := testutil.NewTestHmacKeyset(tagSize, tinkpb.OutputPrefixType_TINK)
+	keyset := testutil.NewTestHMACKeyset(tagSize, tinkpb.OutputPrefixType_TINK)
 	primaryKey := keyset.Key[0]
 	if primaryKey.OutputPrefixType != tinkpb.OutputPrefixType_TINK {
 		t.Errorf("expect a tink key")
 	}
-	keysetHandle, _ := tink.CleartextKeysetHandle().ParseKeyset(keyset)
-
-	p, err := mac.GetPrimitive(keysetHandle)
+	keysetHandle, err := testkeyset.NewHandle(keyset)
 	if err != nil {
-		t.Errorf("GetPrimitive failed: %s", err)
+		t.Errorf("testkeyset.NewHandle failed: %s", err)
 	}
-	expectedPrefix, _ := tink.GetOutputPrefix(primaryKey)
+
+	p, err := mac.New(keysetHandle)
+	if err != nil {
+		t.Errorf("mac.New failed: %s", err)
+	}
+	expectedPrefix, err := format.OutputPrefix(primaryKey)
+	if err != nil {
+		t.Errorf("format.OutputPrefix failed: %s", err)
+	}
+
 	if err := verifyMacPrimitive(p, p, expectedPrefix, tagSize); err != nil {
 		t.Errorf("invalid primitive: %s", err)
 	}
@@ -48,24 +58,32 @@
 	if rawKey.OutputPrefixType != tinkpb.OutputPrefixType_RAW {
 		t.Errorf("expect a raw key")
 	}
-	keyset2 := tink.CreateKeyset(rawKey.KeyId, []*tinkpb.Keyset_Key{rawKey})
-	keysetHandle2, _ := tink.CleartextKeysetHandle().ParseKeyset(keyset2)
-	p2, err := mac.GetPrimitive(keysetHandle2)
+	keyset2 := testutil.NewKeyset(rawKey.KeyId, []*tinkpb.Keyset_Key{rawKey})
+	keysetHandle2, err := testkeyset.NewHandle(keyset2)
 	if err != nil {
-		t.Errorf("GetPrimitive failed: %s", err)
+		t.Errorf("testkeyset.NewHandle failed: %s", err)
 	}
-	if err := verifyMacPrimitive(p2, p, tink.RawPrefix, tagSize); err != nil {
+
+	p2, err := mac.New(keysetHandle2)
+	if err != nil {
+		t.Errorf("mac.New failed: %s", err)
+	}
+	if err := verifyMacPrimitive(p2, p, format.RawPrefix, tagSize); err != nil {
 		t.Errorf("invalid primitive: %s", err)
 	}
 
 	// mac with a random key not in the keyset, verify with the keyset should fail
-	keyset2 = testutil.NewTestHmacKeyset(tagSize, tinkpb.OutputPrefixType_TINK)
+	keyset2 = testutil.NewTestHMACKeyset(tagSize, tinkpb.OutputPrefixType_TINK)
 	primaryKey = keyset2.Key[0]
-	expectedPrefix, _ = tink.GetOutputPrefix(primaryKey)
-	keysetHandle2, _ = tink.CleartextKeysetHandle().ParseKeyset(keyset2)
-	p2, err = mac.GetPrimitive(keysetHandle2)
+	expectedPrefix, _ = format.OutputPrefix(primaryKey)
+	keysetHandle2, err = testkeyset.NewHandle(keyset2)
 	if err != nil {
-		t.Errorf("cannot get primitive from keyset handle")
+		t.Errorf("testkeyset.NewHandle failed: %s", err)
+	}
+
+	p2, err = mac.New(keysetHandle2)
+	if err != nil {
+		t.Errorf("mac.New: cannot get primitive from keyset handle")
 	}
 	err = verifyMacPrimitive(p2, p, expectedPrefix, tagSize)
 	if err == nil || !strings.Contains(err.Error(), "mac verification failed") {
@@ -75,25 +93,28 @@
 
 func TestFactoryRawKey(t *testing.T) {
 	tagSize := uint32(16)
-	keyset := testutil.NewTestHmacKeyset(tagSize, tinkpb.OutputPrefixType_RAW)
+	keyset := testutil.NewTestHMACKeyset(tagSize, tinkpb.OutputPrefixType_RAW)
 	primaryKey := keyset.Key[0]
 	if primaryKey.OutputPrefixType != tinkpb.OutputPrefixType_RAW {
 		t.Errorf("expect a raw key")
 	}
-	keysetHandle, _ := tink.CleartextKeysetHandle().ParseKeyset(keyset)
-	p, err := mac.GetPrimitive(keysetHandle)
+	keysetHandle, err := testkeyset.NewHandle(keyset)
 	if err != nil {
-		t.Errorf("GetPrimitive failed: %s", err)
+		t.Errorf("testkeyset.NewHandle failed: %s", err)
 	}
-	if err := verifyMacPrimitive(p, p, tink.RawPrefix, tagSize); err != nil {
+	p, err := mac.New(keysetHandle)
+	if err != nil {
+		t.Errorf("mac.New failed: %s", err)
+	}
+	if err := verifyMacPrimitive(p, p, format.RawPrefix, tagSize); err != nil {
 		t.Errorf("invalid primitive: %s", err)
 	}
 }
 
-func verifyMacPrimitive(computePrimitive tink.Mac, verifyPrimitive tink.Mac,
+func verifyMacPrimitive(computePrimitive tink.MAC, verifyPrimitive tink.MAC,
 	expectedPrefix string, tagSize uint32) error {
 	data := []byte("hello")
-	tag, err := computePrimitive.ComputeMac(data)
+	tag, err := computePrimitive.ComputeMAC(data)
 	if err != nil {
 		return fmt.Errorf("mac computation failed: %s", err)
 	}
@@ -104,25 +125,22 @@
 	if prefixSize+int(tagSize) != len(tag) {
 		return fmt.Errorf("incorrect tag length")
 	}
-	valid, err := verifyPrimitive.VerifyMac(tag, data)
-	if !valid || err != nil {
+	if err = verifyPrimitive.VerifyMAC(tag, data); err != nil {
 		return fmt.Errorf("mac verification failed: %s", err)
 	}
 
-	// Modify plaintext or tag and make sure VerifyMac failed.
+	// Modify plaintext or tag and make sure VerifyMAC failed.
 	var dataAndTag []byte
 	dataAndTag = append(dataAndTag, data...)
 	dataAndTag = append(dataAndTag, tag...)
-	valid, err = verifyPrimitive.VerifyMac(dataAndTag[len(data):], dataAndTag[:len(data)])
-	if !valid || err != nil {
+	if err = verifyPrimitive.VerifyMAC(dataAndTag[len(data):], dataAndTag[:len(data)]); err != nil {
 		return fmt.Errorf("mac verification failed: %s", err)
 	}
 	for i := 0; i < len(dataAndTag); i++ {
 		tmp := dataAndTag[i]
 		for j := 0; j < 8; j++ {
 			dataAndTag[i] ^= 1 << uint8(j)
-			valid, err = verifyPrimitive.VerifyMac(dataAndTag[len(data):], dataAndTag[:len(data)])
-			if valid && err == nil {
+			if err = verifyPrimitive.VerifyMAC(dataAndTag[len(data):], dataAndTag[:len(data)]); err == nil {
 				return fmt.Errorf("invalid tag or plaintext, mac should be invalid")
 			}
 			dataAndTag[i] = tmp
diff --git a/go/mac/mac_key_templates.go b/go/mac/mac_key_templates.go
index ebf4c5d..f3e121d 100644
--- a/go/mac/mac_key_templates.go
+++ b/go/mac/mac_key_templates.go
@@ -23,26 +23,24 @@
 
 // This file contains pre-generated KeyTemplate for MAC.
 
-// HmacSha256Tag128KeyTemplate is a KeyTemplate for HmacKey with the following
-// parameters:
+// HMACSHA256Tag128KeyTemplate is a KeyTemplate that generates a HMAC key with the following parameters:
 //   - Key size: 32 bytes
 //   - Tag size: 16 bytes
 //   - Hash function: SHA256
-func HmacSha256Tag128KeyTemplate() *tinkpb.KeyTemplate {
-	return createHmacKeyTemplate(32, 16, commonpb.HashType_SHA256)
+func HMACSHA256Tag128KeyTemplate() *tinkpb.KeyTemplate {
+	return createHMACKeyTemplate(32, 16, commonpb.HashType_SHA256)
 }
 
-// HmacSha256Tag256KeyTemplate is a KeyTemplate for HmacKey with the following
-// parameters:
+// HMACSHA256Tag256KeyTemplate is a KeyTemplate that generates a HMAC key with the following parameters:
 //   - Key size: 32 bytes
 //   - Tag size: 32 bytes
 //   - Hash function: SHA256
-func HmacSha256Tag256KeyTemplate() *tinkpb.KeyTemplate {
-	return createHmacKeyTemplate(32, 32, commonpb.HashType_SHA256)
+func HMACSHA256Tag256KeyTemplate() *tinkpb.KeyTemplate {
+	return createHMACKeyTemplate(32, 32, commonpb.HashType_SHA256)
 }
 
-// createHmacKeyTemplate creates a new KeyTemplate for Hmac using the given parameters.
-func createHmacKeyTemplate(keySize uint32,
+// createHMACKeyTemplate creates a new KeyTemplate for HMAC using the given parameters.
+func createHMACKeyTemplate(keySize uint32,
 	tagSize uint32,
 	hashType commonpb.HashType) *tinkpb.KeyTemplate {
 	params := hmacpb.HmacParams{
@@ -55,7 +53,7 @@
 	}
 	serializedFormat, _ := proto.Marshal(&format)
 	return &tinkpb.KeyTemplate{
-		TypeUrl: HmacTypeURL,
+		TypeUrl: hmacTypeURL,
 		Value:   serializedFormat,
 	}
 }
diff --git a/go/mac/mac_key_templates_test.go b/go/mac/mac_key_templates_test.go
index 50df8b2..173ed46 100644
--- a/go/mac/mac_key_templates_test.go
+++ b/go/mac/mac_key_templates_test.go
@@ -20,19 +20,20 @@
 
 	"github.com/golang/protobuf/proto"
 	"github.com/google/tink/go/mac"
+	"github.com/google/tink/go/testutil"
 	commonpb "github.com/google/tink/proto/common_go_proto"
 	hmacpb "github.com/google/tink/proto/hmac_go_proto"
 	tinkpb "github.com/google/tink/proto/tink_go_proto"
 )
 
 func TestTemplates(t *testing.T) {
-	template := mac.HmacSha256Tag128KeyTemplate()
+	template := mac.HMACSHA256Tag128KeyTemplate()
 	if err := checkTemplate(template, 32, 16, commonpb.HashType_SHA256); err != nil {
-		t.Errorf("incorrect HmacSha256Tag128KeyTemplate: %s", err)
+		t.Errorf("incorrect HMACSHA256Tag128KeyTemplate: %s", err)
 	}
-	template = mac.HmacSha256Tag256KeyTemplate()
+	template = mac.HMACSHA256Tag256KeyTemplate()
 	if err := checkTemplate(template, 32, 32, commonpb.HashType_SHA256); err != nil {
-		t.Errorf("incorrect HmacSha256Tag256KeyTemplate: %s", err)
+		t.Errorf("incorrect HMACSHA256Tag256KeyTemplate: %s", err)
 	}
 }
 
@@ -40,7 +41,7 @@
 	keySize uint32,
 	tagSize uint32,
 	hashType commonpb.HashType) error {
-	if template.TypeUrl != mac.HmacTypeURL {
+	if template.TypeUrl != testutil.HMACTypeURL {
 		return fmt.Errorf("TypeUrl is incorrect")
 	}
 	format := new(hmacpb.HmacKeyFormat)
diff --git a/go/mac/mac_config_test.go b/go/mac/mac_test.go
similarity index 69%
rename from go/mac/mac_config_test.go
rename to go/mac/mac_test.go
index 4ea17ea..816e6ce 100644
--- a/go/mac/mac_config_test.go
+++ b/go/mac/mac_test.go
@@ -17,18 +17,13 @@
 import (
 	"testing"
 
-	"github.com/google/tink/go/mac"
-	"github.com/google/tink/go/tink"
+	"github.com/google/tink/go/registry"
+	"github.com/google/tink/go/testutil"
 )
 
-func TestRegistration(t *testing.T) {
-	success, err := mac.RegisterStandardKeyTypes()
-	if !success || err != nil {
-		t.Errorf("cannot register standard key types")
-	}
-	keyManager, err := tink.GetKeyManager(mac.HmacTypeURL)
+func TestMacInit(t *testing.T) {
+	_, err := registry.GetKeyManager(testutil.HMACTypeURL)
 	if err != nil {
 		t.Errorf("unexpected error: %s", err)
 	}
-	var _ = keyManager.(*mac.HmacKeyManager)
 }
diff --git a/go/mac/proto_util.go b/go/mac/proto_util.go
deleted file mode 100644
index ec3bb8d..0000000
--- a/go/mac/proto_util.go
+++ /dev/null
@@ -1,45 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package mac
-
-import (
-	commonpb "github.com/google/tink/proto/common_go_proto"
-	hmacpb "github.com/google/tink/proto/hmac_go_proto"
-)
-
-// NewHmacParams returns a new HmacParams.
-func NewHmacParams(hashType commonpb.HashType, tagSize uint32) *hmacpb.HmacParams {
-	return &hmacpb.HmacParams{
-		Hash:    hashType,
-		TagSize: tagSize,
-	}
-}
-
-// NewHmacKey returns a new HmacKey.
-func NewHmacKey(params *hmacpb.HmacParams, version uint32, keyValue []byte) *hmacpb.HmacKey {
-	return &hmacpb.HmacKey{
-		Version:  version,
-		Params:   params,
-		KeyValue: keyValue,
-	}
-}
-
-// NewHmacKeyFormat returns a new HmacKeyFormat.
-func NewHmacKeyFormat(params *hmacpb.HmacParams, keySize uint32) *hmacpb.HmacKeyFormat {
-	return &hmacpb.HmacKeyFormat{
-		Params:  params,
-		KeySize: keySize,
-	}
-}
diff --git a/go/primitiveset/BUILD.bazel b/go/primitiveset/BUILD.bazel
new file mode 100644
index 0000000..e20df65
--- /dev/null
+++ b/go/primitiveset/BUILD.bazel
@@ -0,0 +1,27 @@
+package(default_visibility = ["//tools/build_defs:internal_pkg"])  # keep
+
+licenses(["notice"])  # Apache 2.0 # keep
+
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+    name = "go_default_library",
+    srcs = ["primitiveset.go"],
+    importpath = "github.com/google/tink/go/primitiveset",
+    visibility = ["//visibility:public"],
+    deps = [
+        "//go/format:go_default_library",
+        "//proto:tink_go_proto",
+    ],
+)
+
+go_test(
+    name = "go_default_test",
+    srcs = ["primitiveset_test.go"],
+    embed = [":go_default_library"],
+    deps = [
+        "//go/format:go_default_library",
+        "//go/testutil:go_default_library",
+        "//proto:tink_go_proto",
+    ],
+)
diff --git a/go/primitiveset/primitiveset.go b/go/primitiveset/primitiveset.go
new file mode 100644
index 0000000..2c78d17
--- /dev/null
+++ b/go/primitiveset/primitiveset.go
@@ -0,0 +1,96 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// Package primitiveset is a container for a set of primitives (i.e., implementations of cryptographic primitives offered by Tink).
+//
+// It provides also additional properties for the primitives it holds. In particular, one of the primitives in the set can be distinguished as "the primary" one.
+package primitiveset
+
+import (
+	"fmt"
+
+	"github.com/google/tink/go/format"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+// Entry represents a single entry in the keyset. In addition to the actual primitive,
+// it holds the identifier and status of the primitive.
+type Entry struct {
+	Primitive  interface{}
+	Prefix     string
+	PrefixType tinkpb.OutputPrefixType
+	Status     tinkpb.KeyStatusType
+}
+
+func newEntry(p interface{}, prefix string, prefixType tinkpb.OutputPrefixType, status tinkpb.KeyStatusType) *Entry {
+	return &Entry{
+		Primitive:  p,
+		Prefix:     prefix,
+		Status:     status,
+		PrefixType: prefixType,
+	}
+}
+
+// PrimitiveSet is used for supporting key rotation: primitives in a set correspond to keys in a
+// keyset. Users will usually work with primitive instances, which essentially wrap primitive
+// sets. For example an instance of an AEAD-primitive for a given keyset holds a set of
+// AEAD-primitives corresponding to the keys in the keyset, and uses the set members to do the
+// actual crypto operations: to encrypt data the primary AEAD-primitive from the set is used, and
+// upon decryption the ciphertext's prefix determines the id of the primitive from the set.
+
+// PrimitiveSet is a public to allow its use in implementations of custom primitives.
+type PrimitiveSet struct {
+	// Primary entry.
+	Primary *Entry
+
+	// The primitives are stored in a map of (ciphertext prefix, list of primitives sharing the
+	// prefix). This allows quickly retrieving the primitives sharing some particular prefix.
+	Entries map[string][]*Entry
+}
+
+// New returns an empty instance of PrimitiveSet.
+func New() *PrimitiveSet {
+	return &PrimitiveSet{
+		Primary: nil,
+		Entries: make(map[string][]*Entry),
+	}
+}
+
+// RawEntries returns all primitives in the set that have RAW prefix.
+func (ps *PrimitiveSet) RawEntries() ([]*Entry, error) {
+	return ps.EntriesForPrefix(format.RawPrefix)
+}
+
+// EntriesForPrefix returns all primitives in the set that have the given prefix.
+func (ps *PrimitiveSet) EntriesForPrefix(prefix string) ([]*Entry, error) {
+	result, found := ps.Entries[prefix]
+	if !found {
+		return []*Entry{}, nil
+	}
+	return result, nil
+}
+
+// Add creates a new entry in the primitive set and returns the added entry.
+func (ps *PrimitiveSet) Add(p interface{}, key *tinkpb.Keyset_Key) (*Entry, error) {
+	if key == nil || p == nil {
+		return nil, fmt.Errorf("primitive_set: key and primitive must not be nil")
+	}
+	prefix, err := format.OutputPrefix(key)
+	if err != nil {
+		return nil, fmt.Errorf("primitive_set: %s", err)
+	}
+	e := newEntry(p, prefix, key.OutputPrefixType, key.Status)
+	ps.Entries[prefix] = append(ps.Entries[prefix], e)
+	return e, nil
+}
diff --git a/go/tink/primitive_set_test.go b/go/primitiveset/primitiveset_test.go
similarity index 68%
rename from go/tink/primitive_set_test.go
rename to go/primitiveset/primitiveset_test.go
index af2d9ae..ed0448f 100644
--- a/go/tink/primitive_set_test.go
+++ b/go/primitiveset/primitiveset_test.go
@@ -12,19 +12,20 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-package tink_test
+package primitiveset_test
 
 import (
 	"fmt"
 	"reflect"
 	"testing"
 
+	"github.com/google/tink/go/format"
+	"github.com/google/tink/go/primitiveset"
 	"github.com/google/tink/go/testutil"
-	"github.com/google/tink/go/tink"
 	tinkpb "github.com/google/tink/proto/tink_go_proto"
 )
 
-func genKeysForPrimitiveSetTest() []*tinkpb.Keyset_Key {
+func createKeyset() []*tinkpb.Keyset_Key {
 	var keyID0 = 1234543
 	var keyID1 = 7213743
 	var keyID2 = keyID1
@@ -43,34 +44,31 @@
 
 func TestPrimitiveSetBasic(t *testing.T) {
 	var err error
-	ps := tink.NewPrimitiveSet()
-	if ps.Primary() != nil || ps.Primitives() == nil {
+	ps := primitiveset.New()
+	if ps.Primary != nil || ps.Entries == nil {
 		t.Errorf("expect primary to be nil and primitives is initialized")
 	}
 	// generate test keys
-	keys := genKeysForPrimitiveSetTest()
+	keys := createKeyset()
 	// add all test primitives
-	macs := make([]testutil.DummyMac, len(keys))
-	entries := make([]*tink.Entry, len(macs))
+	macs := make([]testutil.DummyMAC, len(keys))
+	entries := make([]*primitiveset.Entry, len(macs))
 	for i := 0; i < len(macs); i++ {
-		macs[i] = testutil.DummyMac{Name: fmt.Sprintf("Mac#%d", i)}
-		entries[i], err = ps.AddPrimitive(macs[i], keys[i])
+		macs[i] = testutil.DummyMAC{Name: fmt.Sprintf("Mac#%d", i)}
+		entries[i], err = ps.Add(macs[i], keys[i])
 		if err != nil {
 			t.Errorf("unexpected error when adding mac%d: %s", i, err)
 		}
 	}
 	// set primary entry
 	primaryID := 2
-	ps.SetPrimary(entries[primaryID])
-	// validate the primitive in primary
-	if !validateEntry(ps.Primary(), macs[primaryID], keys[primaryID].Status, keys[primaryID].OutputPrefixType) {
-		t.Errorf("SetPrimary is not working correctly")
-	}
+	ps.Primary = entries[primaryID]
+
 	// check raw primitive
-	rawMacs := []testutil.DummyMac{macs[3], macs[4]}
+	rawMacs := []testutil.DummyMAC{macs[3], macs[4]}
 	rawStatuses := []tinkpb.KeyStatusType{keys[3].Status, keys[4].Status}
 	rawPrefixTypes := []tinkpb.OutputPrefixType{keys[3].OutputPrefixType, keys[4].OutputPrefixType}
-	rawEntries, err := ps.GetRawPrimitives()
+	rawEntries, err := ps.RawEntries()
 	if err != nil {
 		t.Errorf("unexpected error when getting raw primitives: %s", err)
 	}
@@ -78,10 +76,11 @@
 		t.Errorf("raw primitives do not match input")
 	}
 	// check tink primitives, same id
-	tinkMacs := []testutil.DummyMac{macs[0], macs[5]}
+	tinkMacs := []testutil.DummyMAC{macs[0], macs[5]}
 	tinkStatuses := []tinkpb.KeyStatusType{keys[0].Status, keys[5].Status}
 	tinkPrefixTypes := []tinkpb.OutputPrefixType{keys[0].OutputPrefixType, keys[5].OutputPrefixType}
-	tinkEntries, err := ps.GetPrimitivesWithKey(keys[0])
+	prefix, _ := format.OutputPrefix(keys[0])
+	tinkEntries, err := ps.EntriesForPrefix(prefix)
 	if err != nil {
 		t.Errorf("unexpected error when getting primitives: %s", err)
 	}
@@ -89,10 +88,11 @@
 		t.Errorf("tink primitives do not match the input key")
 	}
 	// check another tink primitive
-	tinkMacs = []testutil.DummyMac{macs[2]}
+	tinkMacs = []testutil.DummyMAC{macs[2]}
 	tinkStatuses = []tinkpb.KeyStatusType{keys[2].Status}
 	tinkPrefixTypes = []tinkpb.OutputPrefixType{keys[2].OutputPrefixType}
-	tinkEntries, err = ps.GetPrimitivesWithKey(keys[2])
+	prefix, _ = format.OutputPrefix(keys[2])
+	tinkEntries, err = ps.EntriesForPrefix(prefix)
 	if err != nil {
 		t.Errorf("unexpected error when getting tink primitives: %s", err)
 	}
@@ -100,11 +100,11 @@
 		t.Errorf("tink primitives do not match the input key")
 	}
 	//check legacy primitives
-	legacyMacs := []testutil.DummyMac{macs[1]}
+	legacyMacs := []testutil.DummyMAC{macs[1]}
 	legacyStatuses := []tinkpb.KeyStatusType{keys[1].Status}
 	legacyPrefixTypes := []tinkpb.OutputPrefixType{keys[1].OutputPrefixType}
-	legacyPrefix, _ := tink.GetOutputPrefix(keys[1])
-	legacyEntries, err := ps.GetPrimitivesWithStringIdentifier(legacyPrefix)
+	legacyPrefix, _ := format.OutputPrefix(keys[1])
+	legacyEntries, err := ps.EntriesForPrefix(legacyPrefix)
 	if err != nil {
 		t.Errorf("unexpected error when getting legacy primitives: %s", err)
 	}
@@ -113,32 +113,25 @@
 	}
 }
 
-func TestGetPrimitivesWithInvalidInput(t *testing.T) {
-	ps := tink.NewPrimitiveSet()
-	if _, err := ps.GetPrimitivesWithKey(nil); err == nil {
-		t.Errorf("expect an error when input is nil")
-	}
-}
-
-func TestAddPrimitiveWithInvalidInput(t *testing.T) {
-	ps := tink.NewPrimitiveSet()
+func TestAddWithInvalidInput(t *testing.T) {
+	ps := primitiveset.New()
 	// nil input
 	key := testutil.NewDummyKey(0, tinkpb.KeyStatusType_ENABLED, tinkpb.OutputPrefixType_TINK)
-	if _, err := ps.AddPrimitive(nil, key); err == nil {
+	if _, err := ps.Add(nil, key); err == nil {
 		t.Errorf("expect an error when primitive input is nil")
 	}
-	if _, err := ps.AddPrimitive(*new(testutil.DummyMac), nil); err == nil {
+	if _, err := ps.Add(*new(testutil.DummyMAC), nil); err == nil {
 		t.Errorf("expect an error when key input is nil")
 	}
 	// unknown prefix type
 	invalidKey := testutil.NewDummyKey(0, tinkpb.KeyStatusType_ENABLED, tinkpb.OutputPrefixType_UNKNOWN_PREFIX)
-	if _, err := ps.AddPrimitive(*new(testutil.DummyMac), invalidKey); err == nil {
+	if _, err := ps.Add(*new(testutil.DummyMAC), invalidKey); err == nil {
 		t.Errorf("expect an error when key is invalid")
 	}
 }
 
-func validateEntryList(entries []*tink.Entry,
-	macs []testutil.DummyMac,
+func validateEntryList(entries []*primitiveset.Entry,
+	macs []testutil.DummyMAC,
 	statuses []tinkpb.KeyStatusType,
 	prefixTypes []tinkpb.OutputPrefixType) bool {
 	if len(entries) != len(macs) {
@@ -152,18 +145,17 @@
 	return true
 }
 
-// Compares an entry with the testutil.DummyMac that was used to create the entry
-func validateEntry(entry *tink.Entry,
-	testMac testutil.DummyMac,
+// Compares an entry with the testutil.DummyMAC that was used to create the entry
+func validateEntry(entry *primitiveset.Entry,
+	testMac testutil.DummyMAC,
 	status tinkpb.KeyStatusType,
 	outputPrefixType tinkpb.OutputPrefixType) bool {
-	if entry.Status() != status || entry.OutputPrefixType() != outputPrefixType {
+	if entry.Status != status || entry.PrefixType != outputPrefixType {
 		return false
 	}
-	var dummyMac = entry.Primitive().(testutil.DummyMac)
-	var m tink.Mac = &dummyMac
+	var dummyMac = entry.Primitive.(testutil.DummyMAC)
 	data := []byte{1, 2, 3, 4, 5}
-	digest, err := m.ComputeMac(data)
+	digest, err := dummyMac.ComputeMAC(data)
 	if err != nil || !reflect.DeepEqual(append(data, testMac.Name...), digest) {
 		return false
 	}
diff --git a/go/registry/BUILD.bazel b/go/registry/BUILD.bazel
new file mode 100644
index 0000000..8241efc
--- /dev/null
+++ b/go/registry/BUILD.bazel
@@ -0,0 +1,39 @@
+package(default_visibility = ["//tools/build_defs:internal_pkg"])  # keep
+
+licenses(["notice"])  # Apache 2.0 # keep
+
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+    name = "go_default_library",
+    srcs = [
+        "key_manager.go",
+        "kms_client.go",
+        "private_key_manager.go",
+        "registry.go",
+    ],
+    importpath = "github.com/google/tink/go/registry",
+    deps = [
+        "//go/tink:go_default_library",
+        "//proto:tink_go_proto",
+        "@com_github_golang_protobuf//proto:go_default_library",
+    ],
+)
+
+go_test(
+    name = "tink_test",
+    size = "small",
+    srcs = ["registry_test.go"],
+    deps = [
+        "//go/aead:go_default_library",
+        "//go/mac:go_default_library",
+        "//go/registry:go_default_library",
+        "//go/subtle/mac:go_default_library",
+        "//go/testutil:go_default_library",
+        "//proto:aes_gcm_go_proto",
+        "//proto:common_go_proto",
+        "//proto:hmac_go_proto",
+        "//proto:tink_go_proto",
+        "@com_github_golang_protobuf//proto:go_default_library",
+    ],
+)
diff --git a/go/registry/key_manager.go b/go/registry/key_manager.go
new file mode 100644
index 0000000..1b74aab
--- /dev/null
+++ b/go/registry/key_manager.go
@@ -0,0 +1,46 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package registry
+
+import (
+	"github.com/golang/protobuf/proto"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+// KeyManager "understands" keys of a specific key types: it can generate keys of a supported type
+// and create primitives for supported keys.  A key type is identified by the global name of the
+// protocol buffer that holds the corresponding key material, and is given by type_url-field of
+// KeyData-protocol buffer.
+type KeyManager interface {
+	// Primitive constructs a primitive instance for the key given in serializedKey, which must be a
+	// serialized key protocol buffer handled by this manager.
+	Primitive(serializedKey []byte) (interface{}, error)
+
+	// NewKey generates a new key according to specification in serializedKeyFormat, which must be
+	// supported by this manager.
+	NewKey(serializedKeyFormat []byte) (proto.Message, error)
+
+	// DoesSupport returns true iff this KeyManager supports key type identified by typeURL.
+	DoesSupport(typeURL string) bool
+
+	// TypeURL returns the type URL that identifes the key type of keys managed by this key manager.
+	TypeURL() string
+
+	// APIs for Key Management
+
+	// NewKeyData generates a new KeyData according to specification in serializedkeyFormat.
+	// This should be used solely by the key management API.
+	NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error)
+}
diff --git a/go/registry/kms_client.go b/go/registry/kms_client.go
new file mode 100644
index 0000000..446c7d5
--- /dev/null
+++ b/go/registry/kms_client.go
@@ -0,0 +1,33 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package registry
+
+import "github.com/google/tink/go/tink"
+
+// KMSClient knows how to produce primitives backed by keys stored in remote KMS services.
+type KMSClient interface {
+	// Supported true if this client does support keyURI
+	Supported(keyURI string) bool
+
+	// LoadCredentials loads the credentials in credentialPath. If credentialPath is null, loads the
+	// default credentials.
+	LoadCredentials(credentialPath string) (interface{}, error)
+
+	// LoadDefaultCredentials loads with the default credentials.
+	LoadDefaultCredentials() (interface{}, error)
+
+	// GetAEAD  gets an AEAD backend by keyURI.
+	GetAEAD(keyURI string) (tink.AEAD, error)
+}
diff --git a/go/tink/private_key_manager.go b/go/registry/private_key_manager.go
similarity index 84%
rename from go/tink/private_key_manager.go
rename to go/registry/private_key_manager.go
index 7cf169a..13abffd 100644
--- a/go/tink/private_key_manager.go
+++ b/go/registry/private_key_manager.go
@@ -12,7 +12,7 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-package tink
+package registry
 
 import (
 	tinkpb "github.com/google/tink/proto/tink_go_proto"
@@ -22,6 +22,6 @@
 type PrivateKeyManager interface {
 	KeyManager
 
-	// GetPublicKeyData extracts the public key data from the private key.
-	GetPublicKeyData(serializedKey []byte) (*tinkpb.KeyData, error)
+	// PublicKeyData extracts the public key data from the private key.
+	PublicKeyData(serializedKey []byte) (*tinkpb.KeyData, error)
 }
diff --git a/go/registry/registry.go b/go/registry/registry.go
new file mode 100644
index 0000000..f5f7306
--- /dev/null
+++ b/go/registry/registry.go
@@ -0,0 +1,130 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// Package registry is a container that for each supported key type holds a corresponding KeyManager
+// object, which can generate new keys or instantiate the primitive corresponding to given key.
+//
+// Registry is initialized at startup, and is later used to instantiate primitives for given keys
+// or keysets. Keeping KeyManagers for all primitives in a single Registry (rather than having a
+// separate KeyManager per primitive) enables modular construction of compound primitives from
+// "simple" ones, e.g., AES-CTR-HMAC AEAD encryption uses IND-CPA encryption and a MAC.
+//
+// Note that regular users will usually not work directly with Registry, but rather
+// via primitive factories, which in the background query the Registry for specific
+// KeyManagers. Registry is public though, to enable configurations with custom
+// primitives and KeyManagers.
+package registry
+
+import (
+	"fmt"
+	"sync"
+
+	"github.com/golang/protobuf/proto"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+var (
+	keyManagersMu sync.RWMutex
+	keyManagers   = make(map[string]KeyManager) // typeURL -> KeyManager
+	kmsClientsMu  sync.RWMutex
+	kmsClients    = []KMSClient{}
+)
+
+// RegisterKeyManager registers the given key manager.
+// Does not allow to overwrite existing key managers.
+func RegisterKeyManager(km KeyManager) error {
+	keyManagersMu.Lock()
+	defer keyManagersMu.Unlock()
+	typeURL := km.TypeURL()
+	if _, existed := keyManagers[typeURL]; existed {
+		return fmt.Errorf("registry.RegisterKeyManager: type %s already registered", typeURL)
+	}
+	keyManagers[typeURL] = km
+	return nil
+}
+
+// GetKeyManager returns the key manager for the given typeURL if existed.
+func GetKeyManager(typeURL string) (KeyManager, error) {
+	keyManagersMu.RLock()
+	defer keyManagersMu.RUnlock()
+	km, existed := keyManagers[typeURL]
+	if !existed {
+		return nil, fmt.Errorf("registry.GetKeyManager: unsupported key type: %s", typeURL)
+	}
+	return km, nil
+}
+
+// NewKeyData generates a new KeyData for the given key template.
+func NewKeyData(kt *tinkpb.KeyTemplate) (*tinkpb.KeyData, error) {
+	if kt == nil {
+		return nil, fmt.Errorf("registry.NewKeyData: invalid key template")
+	}
+	km, err := GetKeyManager(kt.TypeUrl)
+	if err != nil {
+		return nil, err
+	}
+	return km.NewKeyData(kt.Value)
+}
+
+// NewKey generates a new key for the given key template.
+func NewKey(kt *tinkpb.KeyTemplate) (proto.Message, error) {
+	if kt == nil {
+		return nil, fmt.Errorf("registry.NewKey: invalid key template")
+	}
+	km, err := GetKeyManager(kt.TypeUrl)
+	if err != nil {
+		return nil, err
+	}
+	return km.NewKey(kt.Value)
+}
+
+// PrimitiveFromKeyData creates a new primitive for the key given in the given KeyData.
+func PrimitiveFromKeyData(kd *tinkpb.KeyData) (interface{}, error) {
+	if kd == nil {
+		return nil, fmt.Errorf("registry.PrimitiveFromKeyData: invalid key data")
+	}
+	return Primitive(kd.TypeUrl, kd.Value)
+}
+
+// Primitive creates a new primitive for the given serialized key using the KeyManager
+// identified by the given typeURL.
+func Primitive(typeURL string, sk []byte) (interface{}, error) {
+	if len(sk) == 0 {
+		return nil, fmt.Errorf("registry.Primitive: invalid serialized key")
+	}
+	km, err := GetKeyManager(typeURL)
+	if err != nil {
+		return nil, err
+	}
+	return km.Primitive(sk)
+}
+
+// RegisterKMSClient is used to register a new KMS client
+func RegisterKMSClient(k KMSClient) {
+	kmsClientsMu.Lock()
+	defer kmsClientsMu.Unlock()
+	kmsClients = append(kmsClients, k)
+}
+
+// GetKMSClient fetches a KMSClient by a given URI.
+func GetKMSClient(keyURI string) (KMSClient, error) {
+	kmsClientsMu.RLock()
+	defer kmsClientsMu.RUnlock()
+	for _, k := range kmsClients {
+		if k.Supported(keyURI) {
+			return k, nil
+		}
+	}
+	return nil, fmt.Errorf("KMS client supporting %s not found", keyURI)
+}
diff --git a/go/registry/registry_test.go b/go/registry/registry_test.go
new file mode 100644
index 0000000..88e835e
--- /dev/null
+++ b/go/registry/registry_test.go
@@ -0,0 +1,181 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package registry_test
+
+import (
+	"testing"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/google/tink/go/aead"
+	"github.com/google/tink/go/mac"
+	"github.com/google/tink/go/registry"
+	subtleMac "github.com/google/tink/go/subtle/mac"
+	"github.com/google/tink/go/testutil"
+	gcmpb "github.com/google/tink/proto/aes_gcm_go_proto"
+	commonpb "github.com/google/tink/proto/common_go_proto"
+	hmacpb "github.com/google/tink/proto/hmac_go_proto"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+func TestRegisterKeyManager(t *testing.T) {
+	// get HMACKeyManager
+	_, err := registry.GetKeyManager(testutil.HMACTypeURL)
+	if err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+	// get AESGCMKeyManager
+	_, err = registry.GetKeyManager(testutil.AESGCMTypeURL)
+	if err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+	// some random typeurl
+	if _, err = registry.GetKeyManager("some url"); err == nil {
+		t.Errorf("expect an error when a type url doesn't exist in the registry")
+	}
+}
+
+func TestRegisterKeyManagerWithCollision(t *testing.T) {
+	// dummyKeyManager's typeURL is equal to that of AESGCM
+	var dummyKeyManager = new(testutil.DummyAEADKeyManager)
+	// This should fail because overwriting is disallowed.
+	err := registry.RegisterKeyManager(dummyKeyManager)
+	if err == nil {
+		t.Errorf("%s shouldn't be registered again", testutil.AESGCMTypeURL)
+	}
+
+	km, err := registry.GetKeyManager(testutil.AESGCMTypeURL)
+	if err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+	// This should fail because overwriting is disallowed, even with the same key manager.
+	err = registry.RegisterKeyManager(km)
+	if err == nil {
+		t.Errorf("%s shouldn't be registered again", testutil.AESGCMTypeURL)
+	}
+}
+
+func TestNewKeyData(t *testing.T) {
+	// new Keydata from a Hmac KeyTemplate
+	keyData, err := registry.NewKeyData(mac.HMACSHA256Tag128KeyTemplate())
+	if err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+	if keyData.TypeUrl != testutil.HMACTypeURL {
+		t.Errorf("invalid key data")
+	}
+	key := new(hmacpb.HmacKey)
+	if err := proto.Unmarshal(keyData.Value, key); err != nil {
+		t.Errorf("unexpected error when unmarshal HmacKey: %s", err)
+	}
+	// nil
+	if _, err := registry.NewKeyData(nil); err == nil {
+		t.Errorf("expect an error when key template is nil")
+	}
+	// unregistered type url
+	template := &tinkpb.KeyTemplate{TypeUrl: "some url", Value: []byte{0}}
+	if _, err := registry.NewKeyData(template); err == nil {
+		t.Errorf("expect an error when key template contains unregistered typeURL")
+	}
+}
+
+func TestNewKey(t *testing.T) {
+	// aead template
+	aesGcmTemplate := aead.AES128GCMKeyTemplate()
+	key, err := registry.NewKey(aesGcmTemplate)
+	if err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+	var aesGcmKey = key.(*gcmpb.AesGcmKey)
+	aesGcmFormat := new(gcmpb.AesGcmKeyFormat)
+	if err := proto.Unmarshal(aesGcmTemplate.Value, aesGcmFormat); err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+	if aesGcmFormat.KeySize != uint32(len(aesGcmKey.KeyValue)) {
+		t.Errorf("key doesn't match template")
+	}
+	//nil
+	if _, err := registry.NewKey(nil); err == nil {
+		t.Errorf("expect an error when key template is nil")
+	}
+	// unregistered type url
+	template := &tinkpb.KeyTemplate{TypeUrl: "some url", Value: []byte{0}}
+	if _, err := registry.NewKey(template); err == nil {
+		t.Errorf("expect an error when key template is not registered")
+	}
+}
+
+func TestPrimitiveFromKeyData(t *testing.T) {
+	// hmac keydata
+	keyData := testutil.NewHMACKeyData(commonpb.HashType_SHA256, 16)
+	p, err := registry.PrimitiveFromKeyData(keyData)
+	if err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+	var _ *subtleMac.HMAC = p.(*subtleMac.HMAC)
+	// unregistered url
+	keyData.TypeUrl = "some url"
+	if _, err := registry.PrimitiveFromKeyData(keyData); err == nil {
+		t.Errorf("expect an error when typeURL has not been registered")
+	}
+	// unmatched url
+	keyData.TypeUrl = testutil.AESGCMTypeURL
+	if _, err := registry.PrimitiveFromKeyData(keyData); err == nil {
+		t.Errorf("expect an error when typeURL doesn't match key")
+	}
+	// nil
+	if _, err := registry.PrimitiveFromKeyData(nil); err == nil {
+		t.Errorf("expect an error when key data is nil")
+	}
+}
+
+func TestPrimitive(t *testing.T) {
+	// hmac key
+	key := testutil.NewHMACKey(commonpb.HashType_SHA256, 16)
+	serializedKey, _ := proto.Marshal(key)
+	p, err := registry.Primitive(testutil.HMACTypeURL, serializedKey)
+	if err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+	var _ *subtleMac.HMAC = p.(*subtleMac.HMAC)
+	// unregistered url
+	if _, err := registry.Primitive("some url", serializedKey); err == nil {
+		t.Errorf("expect an error when typeURL has not been registered")
+	}
+	// unmatched url
+	if _, err := registry.Primitive(testutil.AESGCMTypeURL, serializedKey); err == nil {
+		t.Errorf("expect an error when typeURL doesn't match key")
+	}
+	// void key
+	if _, err := registry.Primitive(testutil.AESGCMTypeURL, nil); err == nil {
+		t.Errorf("expect an error when key is nil")
+	}
+	if _, err := registry.Primitive(testutil.AESGCMTypeURL, []byte{}); err == nil {
+		t.Errorf("expect an error when key is nil")
+	}
+	if _, err := registry.Primitive(testutil.AESGCMTypeURL, []byte{0}); err == nil {
+		t.Errorf("expect an error when key is nil")
+	}
+}
+
+func TestRegisterKmsClient(t *testing.T) {
+	kms := &testutil.DummyKMSClient{}
+	registry.RegisterKMSClient(kms)
+
+	_, err := registry.GetKMSClient("dummy")
+	if err != nil {
+		t.Errorf("error fetching dummy kms client: %s", err)
+	}
+
+}
diff --git a/go/signature/BUILD.bazel b/go/signature/BUILD.bazel
index b8d0363..a26c81f 100644
--- a/go/signature/BUILD.bazel
+++ b/go/signature/BUILD.bazel
@@ -1,47 +1,66 @@
+package(default_visibility = ["//tools/build_defs:internal_pkg"])  # keep
+
+licenses(["notice"])  # Apache 2.0 # keep
+
 load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 
 go_library(
     name = "go_default_library",
     srcs = [
-        "ecdsa_sign_key_manager.go",
-        "ecdsa_verify_key_manager.go",
-        "proto_util.go",
-        "public_key_sign_factory.go",
-        "public_key_verify_factory.go",
-        "signature_config.go",
+        "ecdsa_signer_key_manager.go",
+        "ecdsa_verifier_key_manager.go",
+        "ed25519_signer_key_manager.go",
+        "ed25519_verifier_key_manager.go",
+        "proto.go",
+        "signature.go",
         "signature_key_templates.go",
+        "signer_factory.go",
+        "verifier_factory.go",
     ],
     importpath = "github.com/google/tink/go/signature",
     visibility = ["//visibility:public"],
     deps = [
+        "//go/format:go_default_library",
+        "//go/keyset:go_default_library",
+        "//go/primitiveset:go_default_library",
+        "//go/registry:go_default_library",
         "//go/subtle:go_default_library",
         "//go/subtle/signature:go_default_library",
         "//go/tink:go_default_library",
         "//proto:common_go_proto",
         "//proto:ecdsa_go_proto",
+        "//proto:ed25519_go_proto",
         "//proto:tink_go_proto",
         "@com_github_golang_protobuf//proto:go_default_library",
+        "@org_golang_x_crypto//ed25519:go_default_library",
     ],
 )
 
 go_test(
-    name = "go_default_xtest",
+    name = "go_default_test",
     srcs = [
-        "ecdsa_sign_key_manager_test.go",
-        "ecdsa_verify_key_manager_test.go",
-        "signature_config_test.go",
+        "ecdsa_signer_key_manager_test.go",
+        "ecdsa_verifier_key_manager_test.go",
+        "ed25519_signer_key_manager_test.go",
+        "ed25519_verifier_key_manager_test.go",
         "signature_factory_test.go",
         "signature_key_templates_test.go",
+        "signature_test.go",
     ],
+    embed = [":go_default_library"],
     deps = [
-        ":go_default_library",
+        "//go/format:go_default_library",
+        "//go/registry:go_default_library",
         "//go/subtle/random:go_default_library",
         "//go/subtle/signature:go_default_library",
+        "//go/testkeyset:go_default_library",
         "//go/testutil:go_default_library",
         "//go/tink:go_default_library",
         "//proto:common_go_proto",
         "//proto:ecdsa_go_proto",
+        "//proto:ed25519_go_proto",
         "//proto:tink_go_proto",
         "@com_github_golang_protobuf//proto:go_default_library",
+        "@org_golang_x_crypto//ed25519:go_default_library",
     ],
 )
diff --git a/go/signature/ecdsa_sign_key_manager.go b/go/signature/ecdsa_sign_key_manager.go
deleted file mode 100644
index 0246ae2..0000000
--- a/go/signature/ecdsa_sign_key_manager.go
+++ /dev/null
@@ -1,175 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package signature
-
-import (
-	"crypto/ecdsa"
-	"crypto/rand"
-	"fmt"
-
-	"github.com/golang/protobuf/proto"
-	subtleSignature "github.com/google/tink/go/subtle/signature"
-	"github.com/google/tink/go/subtle"
-	"github.com/google/tink/go/tink"
-	ecdsapb "github.com/google/tink/proto/ecdsa_go_proto"
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
-)
-
-const (
-	// EcdsaSignKeyVersion is the maximum version of keys that this manager supports.
-	EcdsaSignKeyVersion = 0
-
-	// EcdsaSignTypeURL is the only type URL that this manager supports.
-	EcdsaSignTypeURL = "type.googleapis.com/google.crypto.tink.EcdsaPrivateKey"
-)
-
-// common errors
-var errInvalidEcdsaSignKey = fmt.Errorf("ecdsa_sign_key_manager: invalid key")
-var errInvalidEcdsaSignKeyFormat = fmt.Errorf("ecdsa_sign_key_manager: invalid key format")
-
-// EcdsaSignKeyManager is an implementation of KeyManager interface.
-// It generates new EcdsaPrivateKeys and produces new instances of EcdsaSign subtle.
-type EcdsaSignKeyManager struct{}
-
-// Assert that EcdsaSignKeyManager implements the PrivateKeyManager interface.
-var _ tink.PrivateKeyManager = (*EcdsaSignKeyManager)(nil)
-
-// NewEcdsaSignKeyManager creates a new EcdsaSignKeyManager.
-func NewEcdsaSignKeyManager() *EcdsaSignKeyManager {
-	return new(EcdsaSignKeyManager)
-}
-
-// GetPrimitiveFromSerializedKey creates an EcdsaSign subtle for the given
-// serialized EcdsaPrivateKey proto.
-func (km *EcdsaSignKeyManager) GetPrimitiveFromSerializedKey(serializedKey []byte) (interface{}, error) {
-	if len(serializedKey) == 0 {
-		return nil, errInvalidEcdsaSignKey
-	}
-	key := new(ecdsapb.EcdsaPrivateKey)
-	if err := proto.Unmarshal(serializedKey, key); err != nil {
-		return nil, errInvalidEcdsaSignKey
-	}
-	return km.GetPrimitiveFromKey(key)
-}
-
-// GetPrimitiveFromKey creates an EcdsaSign subtle for the given EcdsaPrivateKey proto.
-func (km *EcdsaSignKeyManager) GetPrimitiveFromKey(m proto.Message) (interface{}, error) {
-	key, ok := m.(*ecdsapb.EcdsaPrivateKey)
-	if !ok {
-		return nil, errInvalidEcdsaSignKey
-	}
-	if err := km.validateKey(key); err != nil {
-		return nil, err
-	}
-	hash, curve, encoding := GetEcdsaParamNames(key.PublicKey.Params)
-	ret, err := subtleSignature.NewEcdsaSign(hash, curve, encoding, key.KeyValue)
-	if err != nil {
-		return nil, fmt.Errorf("ecdsa_sign_key_manager: %s", err)
-	}
-	return ret, nil
-}
-
-// NewKeyFromSerializedKeyFormat creates a new EcdsaPrivateKey according to specification
-// the given serialized EcdsaKeyFormat.
-func (km *EcdsaSignKeyManager) NewKeyFromSerializedKeyFormat(serializedKeyFormat []byte) (proto.Message, error) {
-	if len(serializedKeyFormat) == 0 {
-		return nil, errInvalidEcdsaSignKeyFormat
-	}
-	keyFormat := new(ecdsapb.EcdsaKeyFormat)
-	if err := proto.Unmarshal(serializedKeyFormat, keyFormat); err != nil {
-		return nil, fmt.Errorf("ecdsa_sign_key_manager: invalid key format: %s", err)
-	}
-	return km.NewKeyFromKeyFormat(keyFormat)
-}
-
-// NewKeyFromKeyFormat creates a new key according to specification in the
-// given EcdsaKeyFormat.
-func (km *EcdsaSignKeyManager) NewKeyFromKeyFormat(m proto.Message) (proto.Message, error) {
-	format, ok := m.(*ecdsapb.EcdsaKeyFormat)
-	if !ok {
-		return nil, errInvalidEcdsaSignKeyFormat
-	}
-	if err := km.validateKeyFormat(format); err != nil {
-		return nil, fmt.Errorf("ecdsa_sign_key_manager: %s", err)
-	}
-	// generate key
-	params := format.Params
-	curve := tink.GetCurveName(params.Curve)
-	tmpKey, _ := ecdsa.GenerateKey(subtle.GetCurve(curve), rand.Reader)
-	keyValue := tmpKey.D.Bytes()
-	pub := NewEcdsaPublicKey(EcdsaSignKeyVersion, params, tmpKey.X.Bytes(), tmpKey.Y.Bytes())
-	priv := NewEcdsaPrivateKey(EcdsaSignKeyVersion, pub, keyValue)
-	return priv, nil
-}
-
-// NewKeyData creates a new KeyData according to specification in  the given
-// serialized EcdsaKeyFormat. It should be used solely by the key management API.
-func (km *EcdsaSignKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) {
-	key, err := km.NewKeyFromSerializedKeyFormat(serializedKeyFormat)
-	if err != nil {
-		return nil, err
-	}
-	serializedKey, err := proto.Marshal(key)
-	if err != nil {
-		return nil, errInvalidEcdsaSignKeyFormat
-	}
-	return &tinkpb.KeyData{
-		TypeUrl:         EcdsaSignTypeURL,
-		Value:           serializedKey,
-		KeyMaterialType: tinkpb.KeyData_ASYMMETRIC_PRIVATE,
-	}, nil
-}
-
-// GetPublicKeyData extracts the public key data from the private key.
-func (km *EcdsaSignKeyManager) GetPublicKeyData(serializedPrivKey []byte) (*tinkpb.KeyData, error) {
-	privKey := new(ecdsapb.EcdsaPrivateKey)
-	if err := proto.Unmarshal(serializedPrivKey, privKey); err != nil {
-		return nil, errInvalidEcdsaSignKey
-	}
-	serializedPubKey, err := proto.Marshal(privKey.PublicKey)
-	if err != nil {
-		return nil, errInvalidEcdsaSignKey
-	}
-	return &tinkpb.KeyData{
-		TypeUrl:         EcdsaVerifyTypeURL,
-		Value:           serializedPubKey,
-		KeyMaterialType: tinkpb.KeyData_ASYMMETRIC_PUBLIC,
-	}, nil
-}
-
-// DoesSupport indicates if this key manager supports the given key type.
-func (km *EcdsaSignKeyManager) DoesSupport(typeURL string) bool {
-	return typeURL == EcdsaSignTypeURL
-}
-
-// GetKeyType returns the key type of keys managed by this key manager.
-func (km *EcdsaSignKeyManager) GetKeyType() string {
-	return EcdsaSignTypeURL
-}
-
-// validateKey validates the given EcdsaPrivateKey.
-func (km *EcdsaSignKeyManager) validateKey(key *ecdsapb.EcdsaPrivateKey) error {
-	if err := tink.ValidateVersion(key.Version, EcdsaSignKeyVersion); err != nil {
-		return fmt.Errorf("ecdsa_sign_key_manager: %s", err)
-	}
-	hash, curve, encoding := GetEcdsaParamNames(key.PublicKey.Params)
-	return subtleSignature.ValidateEcdsaParams(hash, curve, encoding)
-}
-
-// validateKeyFormat validates the given EcdsaKeyFormat.
-func (km *EcdsaSignKeyManager) validateKeyFormat(format *ecdsapb.EcdsaKeyFormat) error {
-	hash, curve, encoding := GetEcdsaParamNames(format.Params)
-	return subtleSignature.ValidateEcdsaParams(hash, curve, encoding)
-}
diff --git a/go/signature/ecdsa_sign_key_manager_test.go b/go/signature/ecdsa_sign_key_manager_test.go
deleted file mode 100644
index 27f2804..0000000
--- a/go/signature/ecdsa_sign_key_manager_test.go
+++ /dev/null
@@ -1,392 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package signature_test
-
-import (
-	"fmt"
-	"math/big"
-	"testing"
-
-	"github.com/golang/protobuf/proto"
-	"github.com/google/tink/go/signature"
-	"github.com/google/tink/go/subtle/random"
-	subtleSig "github.com/google/tink/go/subtle/signature"
-	"github.com/google/tink/go/testutil"
-	commonpb "github.com/google/tink/proto/common_go_proto"
-	ecdsapb "github.com/google/tink/proto/ecdsa_go_proto"
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
-)
-
-type ecdsaParams struct {
-	hashType commonpb.HashType
-	curve    commonpb.EllipticCurveType
-}
-
-func TestNewEcdsaSignKeyManager(t *testing.T) {
-	var km *signature.EcdsaSignKeyManager = signature.NewEcdsaSignKeyManager()
-	if km == nil {
-		t.Error("NewEcdsaSignKeyManager returns nil")
-	}
-}
-
-func TestEcdsaSignGetPrimitiveBasic(t *testing.T) {
-	testParams := genValidEcdsaParams()
-	km := signature.NewEcdsaSignKeyManager()
-	for i := 0; i < len(testParams); i++ {
-		key := testutil.NewEcdsaPrivateKey(testParams[i].hashType, testParams[i].curve)
-		tmp, err := km.GetPrimitiveFromKey(key)
-		if err != nil {
-			t.Errorf("unexpect error in test case %d: %s ", i, err)
-		}
-		var _ *subtleSig.EcdsaSign = tmp.(*subtleSig.EcdsaSign)
-
-		serializedKey, _ := proto.Marshal(key)
-		tmp, err = km.GetPrimitiveFromSerializedKey(serializedKey)
-		if err != nil {
-			t.Errorf("unexpect error in test case %d: %s ", i, err)
-		}
-		var _ *subtleSig.EcdsaSign = tmp.(*subtleSig.EcdsaSign)
-	}
-}
-
-func TestEcdsaSignGetPrimitiveWithInvalidInput(t *testing.T) {
-	// invalid params
-	testParams := genInvalidEcdsaParams()
-	km := signature.NewEcdsaSignKeyManager()
-	for i := 0; i < len(testParams); i++ {
-		key := testutil.NewEcdsaPrivateKey(testParams[i].hashType, testParams[i].curve)
-		if _, err := km.GetPrimitiveFromKey(key); err == nil {
-			t.Errorf("expect an error in test case %d", i)
-		}
-		serializedKey, _ := proto.Marshal(key)
-		if _, err := km.GetPrimitiveFromSerializedKey(serializedKey); err == nil {
-			t.Errorf("expect an error in test case %d", i)
-		}
-	}
-	// invalid version
-	key := testutil.NewEcdsaPrivateKey(commonpb.HashType_SHA256,
-		commonpb.EllipticCurveType_NIST_P256)
-	key.Version = signature.EcdsaSignKeyVersion + 1
-	if _, err := km.GetPrimitiveFromKey(key); err == nil {
-		t.Errorf("expect an error when version is invalid")
-	}
-	// nil input
-	if _, err := km.GetPrimitiveFromKey(nil); err == nil {
-		t.Errorf("expect an error when input is nil")
-	}
-	if _, err := km.GetPrimitiveFromSerializedKey(nil); err == nil {
-		t.Errorf("expect an error when input is nil")
-	}
-	if _, err := km.GetPrimitiveFromSerializedKey([]byte{}); err == nil {
-		t.Errorf("expect an error when input is empty slice")
-	}
-}
-
-func TestEcdsaSignNewKeyBasic(t *testing.T) {
-	testParams := genValidEcdsaParams()
-	km := signature.NewEcdsaSignKeyManager()
-	for i := 0; i < len(testParams); i++ {
-		params := signature.NewEcdsaParams(testParams[i].hashType, testParams[i].curve,
-			ecdsapb.EcdsaSignatureEncoding_DER)
-		format := signature.NewEcdsaKeyFormat(params)
-		tmp, err := km.NewKeyFromKeyFormat(format)
-		if err != nil {
-			t.Errorf("unexpected error: %s", err)
-		}
-		var key *ecdsapb.EcdsaPrivateKey = tmp.(*ecdsapb.EcdsaPrivateKey)
-		if err := validateEcdsaPrivateKey(key, params); err != nil {
-			t.Errorf("invalid private key in test case %d: %s", i, err)
-		}
-
-		serializedFormat, _ := proto.Marshal(format)
-		tmp, err = km.NewKeyFromSerializedKeyFormat(serializedFormat)
-		if err != nil {
-			t.Errorf("unexpected error: %s", err)
-		}
-		key = tmp.(*ecdsapb.EcdsaPrivateKey)
-		if err := validateEcdsaPrivateKey(key, params); err != nil {
-			t.Errorf("invalid private key in test case %d: %s", i, err)
-		}
-	}
-}
-
-func TestEcdsaSignNewKeyWithInvalidInput(t *testing.T) {
-	km := signature.NewEcdsaSignKeyManager()
-	// invalid hash and curve type
-	testParams := genInvalidEcdsaParams()
-	for i := 0; i < len(testParams); i++ {
-		params := signature.NewEcdsaParams(testParams[i].hashType, testParams[i].curve,
-			ecdsapb.EcdsaSignatureEncoding_DER)
-		format := signature.NewEcdsaKeyFormat(params)
-		if _, err := km.NewKeyFromKeyFormat(format); err == nil {
-			t.Errorf("expect an error in test case %d", i)
-		}
-		serializedFormat, _ := proto.Marshal(format)
-		if _, err := km.NewKeyFromSerializedKeyFormat(serializedFormat); err == nil {
-			t.Errorf("expect an error in test case %d", i)
-		}
-	}
-	// invalid encoding
-	testParams = genValidEcdsaParams()
-	for i := 0; i < len(testParams); i++ {
-		params := signature.NewEcdsaParams(testParams[i].hashType, testParams[i].curve,
-			ecdsapb.EcdsaSignatureEncoding_UNKNOWN_ENCODING)
-		format := signature.NewEcdsaKeyFormat(params)
-		if _, err := km.NewKeyFromKeyFormat(format); err == nil {
-			t.Errorf("expect an error in test case %d", i)
-		}
-		serializedFormat, _ := proto.Marshal(format)
-		if _, err := km.NewKeyFromSerializedKeyFormat(serializedFormat); err == nil {
-			t.Errorf("expect an error in test case %d", i)
-		}
-	}
-	// nil input
-	if _, err := km.NewKeyFromKeyFormat(nil); err == nil {
-		t.Errorf("expect an error when input is nil")
-	}
-	if _, err := km.NewKeyFromSerializedKeyFormat(nil); err == nil {
-		t.Errorf("expect an error when input is nil")
-	}
-	if _, err := km.NewKeyFromSerializedKeyFormat([]byte{}); err == nil {
-		t.Errorf("expect an error when input is empty slice")
-	}
-}
-
-func TestEcdsaSignNewKeyMultipleTimes(t *testing.T) {
-	km := signature.NewEcdsaSignKeyManager()
-	testParams := genValidEcdsaParams()
-	nTest := 27
-	for i := 0; i < len(testParams); i++ {
-		keys := make(map[string]bool)
-		params := signature.NewEcdsaParams(testParams[i].hashType, testParams[i].curve,
-			ecdsapb.EcdsaSignatureEncoding_DER)
-		format := signature.NewEcdsaKeyFormat(params)
-		serializedFormat, _ := proto.Marshal(format)
-		for j := 0; j < nTest; j++ {
-			key, _ := km.NewKeyFromKeyFormat(format)
-			serializedKey, _ := proto.Marshal(key)
-			keys[string(serializedKey)] = true
-
-			key, _ = km.NewKeyFromSerializedKeyFormat(serializedFormat)
-			serializedKey, _ = proto.Marshal(key)
-			keys[string(serializedKey)] = true
-
-			keyData, _ := km.NewKeyData(serializedFormat)
-			serializedKey = keyData.Value
-			keys[string(serializedKey)] = true
-		}
-		if len(keys) != nTest*3 {
-			t.Errorf("key is repeated with params: %s", params)
-		}
-	}
-}
-
-func TestEcdsaSignNewKeyDataBasic(t *testing.T) {
-	km := signature.NewEcdsaSignKeyManager()
-	testParams := genValidEcdsaParams()
-	for i := 0; i < len(testParams); i++ {
-		params := signature.NewEcdsaParams(testParams[i].hashType, testParams[i].curve,
-			ecdsapb.EcdsaSignatureEncoding_DER)
-		format := signature.NewEcdsaKeyFormat(params)
-		serializedFormat, _ := proto.Marshal(format)
-
-		keyData, err := km.NewKeyData(serializedFormat)
-		if err != nil {
-			t.Errorf("unexpected error in test case  %d: %s", i, err)
-		}
-		if keyData.TypeUrl != signature.EcdsaSignTypeURL {
-			t.Errorf("incorrect type url in test case  %d: expect %s, got %s",
-				i, signature.EcdsaSignTypeURL, keyData.TypeUrl)
-		}
-		if keyData.KeyMaterialType != tinkpb.KeyData_ASYMMETRIC_PRIVATE {
-			t.Errorf("incorrect key material type in test case  %d: expect %s, got %s",
-				i, tinkpb.KeyData_ASYMMETRIC_PRIVATE, keyData.KeyMaterialType)
-		}
-		key := new(ecdsapb.EcdsaPrivateKey)
-		if err := proto.Unmarshal(keyData.Value, key); err != nil {
-			t.Errorf("unexpect error in test case %d: %s", i, err)
-		}
-		if err := validateEcdsaPrivateKey(key, params); err != nil {
-			t.Errorf("invalid private key in test case %d: %s", i, err)
-		}
-	}
-}
-
-func TestEcdsaSignNewKeyDataWithInvalidInput(t *testing.T) {
-	km := signature.NewEcdsaSignKeyManager()
-	testParams := genInvalidEcdsaParams()
-	for i := 0; i < len(testParams); i++ {
-		params := signature.NewEcdsaParams(testParams[i].hashType, testParams[i].curve,
-			ecdsapb.EcdsaSignatureEncoding_DER)
-		format := signature.NewEcdsaKeyFormat(params)
-		serializedFormat, _ := proto.Marshal(format)
-
-		if _, err := km.NewKeyData(serializedFormat); err == nil {
-			t.Errorf("expect an error in test case  %d", i)
-		}
-	}
-	// nil input
-	if _, err := km.NewKeyData(nil); err == nil {
-		t.Errorf("expect an error when input is nil")
-	}
-}
-
-func TestGetPublicKeyDataBasic(t *testing.T) {
-	testParams := genValidEcdsaParams()
-	km := signature.NewEcdsaSignKeyManager()
-	for i := 0; i < len(testParams); i++ {
-		key := testutil.NewEcdsaPrivateKey(testParams[i].hashType, testParams[i].curve)
-		serializedKey, _ := proto.Marshal(key)
-
-		pubKeyData, err := km.GetPublicKeyData(serializedKey)
-		if err != nil {
-			t.Errorf("unexpect error in test case %d: %s ", i, err)
-		}
-		if pubKeyData.TypeUrl != signature.EcdsaVerifyTypeURL {
-			t.Errorf("incorrect type url: %s", pubKeyData.TypeUrl)
-		}
-		if pubKeyData.KeyMaterialType != tinkpb.KeyData_ASYMMETRIC_PUBLIC {
-			t.Errorf("incorrect key material type: %d", pubKeyData.KeyMaterialType)
-		}
-		pubKey := new(ecdsapb.EcdsaPublicKey)
-		if err = proto.Unmarshal(pubKeyData.Value, pubKey); err != nil {
-			t.Errorf("invalid public key: %s", err)
-		}
-	}
-}
-
-func TestGetPublicKeyDataWithInvalidInput(t *testing.T) {
-	km := signature.NewEcdsaSignKeyManager()
-	// modified key
-	key := testutil.NewEcdsaPrivateKey(commonpb.HashType_SHA256,
-		commonpb.EllipticCurveType_NIST_P256)
-	serializedKey, _ := proto.Marshal(key)
-	serializedKey[0] = 0
-	if _, err := km.GetPublicKeyData(serializedKey); err == nil {
-		t.Errorf("expect an error when input is a modified serialized key")
-	}
-	// nil
-	if _, err := km.GetPublicKeyData(nil); err == nil {
-		t.Errorf("expect an error when input is nil")
-	}
-	// empty slice
-	if _, err := km.GetPublicKeyData([]byte{}); err == nil {
-		t.Errorf("expect an error when input is an empty slice")
-	}
-}
-
-var errSmallKey = fmt.Errorf("private key doesn't have adequate size")
-
-func validateEcdsaPrivateKey(key *ecdsapb.EcdsaPrivateKey, params *ecdsapb.EcdsaParams) error {
-	if key.Version != signature.EcdsaSignKeyVersion {
-		return fmt.Errorf("incorrect private key's version: expect %d, got %d",
-			signature.EcdsaSignKeyVersion, key.Version)
-	}
-	publicKey := key.PublicKey
-	if publicKey.Version != signature.EcdsaSignKeyVersion {
-		return fmt.Errorf("incorrect public key's version: expect %d, got %d",
-			signature.EcdsaSignKeyVersion, key.Version)
-	}
-	if params.HashType != publicKey.Params.HashType ||
-		params.Curve != publicKey.Params.Curve ||
-		params.Encoding != publicKey.Params.Encoding {
-		return fmt.Errorf("incorrect params: expect %s, got %s", params, publicKey.Params)
-	}
-	if len(publicKey.X) == 0 || len(publicKey.Y) == 0 {
-		return fmt.Errorf("public points are not initialized")
-	}
-	// check private key's size
-	d := new(big.Int).SetBytes(key.KeyValue)
-	keySize := len(d.Bytes())
-	switch params.Curve {
-	case commonpb.EllipticCurveType_NIST_P256:
-		if keySize < 256/8-8 || keySize > 256/8+1 {
-			return errSmallKey
-		}
-	case commonpb.EllipticCurveType_NIST_P384:
-		if keySize < 384/8-8 || keySize > 384/8+1 {
-			return errSmallKey
-		}
-	case commonpb.EllipticCurveType_NIST_P521:
-		if keySize < 521/8-8 || keySize > 521/8+1 {
-			return errSmallKey
-		}
-	}
-	// try to sign and verify with the key
-	hash, curve, encoding := signature.GetEcdsaParamNames(publicKey.Params)
-	signer, err := subtleSig.NewEcdsaSign(hash, curve, encoding, key.KeyValue)
-	if err != nil {
-		return fmt.Errorf("unexpected error when creating EcdsaSign: %s", err)
-	}
-	verifier, err := subtleSig.NewEcdsaVerify(hash, curve, encoding, publicKey.X, publicKey.Y)
-	if err != nil {
-		return fmt.Errorf("unexpected error when creating EcdsaVerify: %s", err)
-	}
-	data := random.GetRandomBytes(1281)
-	signature, err := signer.Sign(data)
-	if err != nil {
-		return fmt.Errorf("unexpected error when signing: %s", err)
-	}
-	if err := verifier.Verify(signature, data); err != nil {
-		return fmt.Errorf("unexpected error when verifying signature: %s", err)
-	}
-	return nil
-}
-
-func genValidEcdsaParams() []ecdsaParams {
-	return []ecdsaParams{
-		ecdsaParams{
-			hashType: commonpb.HashType_SHA256,
-			curve:    commonpb.EllipticCurveType_NIST_P256,
-		},
-		ecdsaParams{
-			hashType: commonpb.HashType_SHA512,
-			curve:    commonpb.EllipticCurveType_NIST_P384,
-		},
-		ecdsaParams{
-			hashType: commonpb.HashType_SHA512,
-			curve:    commonpb.EllipticCurveType_NIST_P521,
-		},
-	}
-}
-
-func genInvalidEcdsaParams() []ecdsaParams {
-	return []ecdsaParams{
-		ecdsaParams{
-			hashType: commonpb.HashType_SHA1,
-			curve:    commonpb.EllipticCurveType_NIST_P256,
-		},
-		ecdsaParams{
-			hashType: commonpb.HashType_SHA1,
-			curve:    commonpb.EllipticCurveType_NIST_P384,
-		},
-		ecdsaParams{
-			hashType: commonpb.HashType_SHA1,
-			curve:    commonpb.EllipticCurveType_NIST_P521,
-		},
-		ecdsaParams{
-			hashType: commonpb.HashType_SHA256,
-			curve:    commonpb.EllipticCurveType_NIST_P384,
-		},
-		ecdsaParams{
-			hashType: commonpb.HashType_SHA256,
-			curve:    commonpb.EllipticCurveType_NIST_P521,
-		},
-		ecdsaParams{
-			hashType: commonpb.HashType_SHA512,
-			curve:    commonpb.EllipticCurveType_NIST_P256,
-		},
-	}
-}
diff --git a/go/signature/ecdsa_signer_key_manager.go b/go/signature/ecdsa_signer_key_manager.go
new file mode 100644
index 0000000..5d351fe
--- /dev/null
+++ b/go/signature/ecdsa_signer_key_manager.go
@@ -0,0 +1,158 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package signature
+
+import (
+	"crypto/ecdsa"
+	"crypto/rand"
+	"errors"
+	"fmt"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/registry"
+	subtleSignature "github.com/google/tink/go/subtle/signature"
+	"github.com/google/tink/go/subtle"
+	commonpb "github.com/google/tink/proto/common_go_proto"
+	ecdsapb "github.com/google/tink/proto/ecdsa_go_proto"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+const (
+	ecdsaSignerKeyVersion = 0
+	ecdsaSignerTypeURL    = "type.googleapis.com/google.crypto.tink.EcdsaPrivateKey"
+)
+
+// common errors
+var errInvalidECDSASignKey = errors.New("ecdsa_signer_key_manager: invalid key")
+var errInvalidECDSASignKeyFormat = errors.New("ecdsa_signer_key_manager: invalid key format")
+
+// ecdsaSignerKeyManager is an implementation of KeyManager interface.
+// It generates new ECDSAPrivateKeys and produces new instances of ECDSASign subtle.
+type ecdsaSignerKeyManager struct{}
+
+// Assert that ecdsaSignerKeyManager implements the PrivateKeyManager interface.
+var _ registry.PrivateKeyManager = (*ecdsaSignerKeyManager)(nil)
+
+// newECDSASignerKeyManager creates a new ecdsaSignerKeyManager.
+func newECDSASignerKeyManager() *ecdsaSignerKeyManager {
+	return new(ecdsaSignerKeyManager)
+}
+
+// Primitive creates an ECDSASign subtle for the given serialized ECDSAPrivateKey proto.
+func (km *ecdsaSignerKeyManager) Primitive(serializedKey []byte) (interface{}, error) {
+	if len(serializedKey) == 0 {
+		return nil, errInvalidECDSASignKey
+	}
+	key := new(ecdsapb.EcdsaPrivateKey)
+	if err := proto.Unmarshal(serializedKey, key); err != nil {
+		return nil, errInvalidECDSASignKey
+	}
+	if err := km.validateKey(key); err != nil {
+		return nil, err
+	}
+	hash, curve, encoding := getECDSAParamNames(key.PublicKey.Params)
+	ret, err := subtleSignature.NewECDSASigner(hash, curve, encoding, key.KeyValue)
+	if err != nil {
+		return nil, fmt.Errorf("ecdsa_signer_key_manager: %s", err)
+	}
+	return ret, nil
+}
+
+// NewKey creates a new ECDSAPrivateKey according to specification the given serialized ECDSAKeyFormat.
+func (km *ecdsaSignerKeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) {
+	if len(serializedKeyFormat) == 0 {
+		return nil, errInvalidECDSASignKeyFormat
+	}
+	keyFormat := new(ecdsapb.EcdsaKeyFormat)
+	if err := proto.Unmarshal(serializedKeyFormat, keyFormat); err != nil {
+		return nil, fmt.Errorf("ecdsa_signer_key_manager: invalid proto: %s", err)
+	}
+	if err := km.validateKeyFormat(keyFormat); err != nil {
+		return nil, fmt.Errorf("ecdsa_signer_key_manager: invalid key format: %s", err)
+	}
+	// generate key
+	params := keyFormat.Params
+	curve := commonpb.EllipticCurveType_name[int32(params.Curve)]
+	tmpKey, err := ecdsa.GenerateKey(subtle.GetCurve(curve), rand.Reader)
+	if err != nil {
+		return nil, fmt.Errorf("ecdsa_signer_key_manager: cannot generate ECDSA key: %s", err)
+	}
+
+	keyValue := tmpKey.D.Bytes()
+	pub := newECDSAPublicKey(ecdsaSignerKeyVersion, params, tmpKey.X.Bytes(), tmpKey.Y.Bytes())
+	priv := newECDSAPrivateKey(ecdsaSignerKeyVersion, pub, keyValue)
+	return priv, nil
+}
+
+// NewKeyData creates a new KeyData according to specification in  the given
+// serialized ECDSAKeyFormat. It should be used solely by the key management API.
+func (km *ecdsaSignerKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) {
+	key, err := km.NewKey(serializedKeyFormat)
+	if err != nil {
+		return nil, err
+	}
+	serializedKey, err := proto.Marshal(key)
+	if err != nil {
+		return nil, errInvalidECDSASignKeyFormat
+	}
+	return &tinkpb.KeyData{
+		TypeUrl:         ecdsaSignerTypeURL,
+		Value:           serializedKey,
+		KeyMaterialType: tinkpb.KeyData_ASYMMETRIC_PRIVATE,
+	}, nil
+}
+
+// PublicKeyData extracts the public key data from the private key.
+func (km *ecdsaSignerKeyManager) PublicKeyData(serializedPrivKey []byte) (*tinkpb.KeyData, error) {
+	privKey := new(ecdsapb.EcdsaPrivateKey)
+	if err := proto.Unmarshal(serializedPrivKey, privKey); err != nil {
+		return nil, errInvalidECDSASignKey
+	}
+	serializedPubKey, err := proto.Marshal(privKey.PublicKey)
+	if err != nil {
+		return nil, errInvalidECDSASignKey
+	}
+	return &tinkpb.KeyData{
+		TypeUrl:         ecdsaVerifierTypeURL,
+		Value:           serializedPubKey,
+		KeyMaterialType: tinkpb.KeyData_ASYMMETRIC_PUBLIC,
+	}, nil
+}
+
+// DoesSupport indicates if this key manager supports the given key type.
+func (km *ecdsaSignerKeyManager) DoesSupport(typeURL string) bool {
+	return typeURL == ecdsaSignerTypeURL
+}
+
+// TypeURL returns the key type of keys managed by this key manager.
+func (km *ecdsaSignerKeyManager) TypeURL() string {
+	return ecdsaSignerTypeURL
+}
+
+// validateKey validates the given ECDSAPrivateKey.
+func (km *ecdsaSignerKeyManager) validateKey(key *ecdsapb.EcdsaPrivateKey) error {
+	if err := keyset.ValidateKeyVersion(key.Version, ecdsaSignerKeyVersion); err != nil {
+		return fmt.Errorf("ecdsa_signer_key_manager: invalid key: %s", err)
+	}
+	hash, curve, encoding := getECDSAParamNames(key.PublicKey.Params)
+	return subtleSignature.ValidateECDSAParams(hash, curve, encoding)
+}
+
+// validateKeyFormat validates the given ECDSAKeyFormat.
+func (km *ecdsaSignerKeyManager) validateKeyFormat(format *ecdsapb.EcdsaKeyFormat) error {
+	hash, curve, encoding := getECDSAParamNames(format.Params)
+	return subtleSignature.ValidateECDSAParams(hash, curve, encoding)
+}
diff --git a/go/signature/ecdsa_signer_key_manager_test.go b/go/signature/ecdsa_signer_key_manager_test.go
new file mode 100644
index 0000000..d8bcbbc
--- /dev/null
+++ b/go/signature/ecdsa_signer_key_manager_test.go
@@ -0,0 +1,382 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package signature_test
+
+import (
+	"fmt"
+	"math/big"
+	"testing"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/google/tink/go/registry"
+	"github.com/google/tink/go/subtle/random"
+	"github.com/google/tink/go/subtle/signature"
+	"github.com/google/tink/go/testutil"
+	commonpb "github.com/google/tink/proto/common_go_proto"
+	ecdsapb "github.com/google/tink/proto/ecdsa_go_proto"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+type ecdsaParams struct {
+	hashType commonpb.HashType
+	curve    commonpb.EllipticCurveType
+}
+
+func TestECDSASignerGetPrimitiveBasic(t *testing.T) {
+	testParams := genValidECDSAParams()
+	km, err := registry.GetKeyManager(testutil.ECDSASignerTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain ECDSASigner key manager: %s", err)
+	}
+	for i := 0; i < len(testParams); i++ {
+		serializedKey, _ := proto.Marshal(testutil.NewRandomECDSAPrivateKey(testParams[i].hashType, testParams[i].curve))
+		tmp, err := km.Primitive(serializedKey)
+		if err != nil {
+			t.Errorf("unexpect error in test case %d: %s ", i, err)
+		}
+		var _ *signature.ECDSASigner = tmp.(*signature.ECDSASigner)
+	}
+}
+
+func TestECDSASignGetPrimitiveWithInvalidInput(t *testing.T) {
+	// invalid params
+	testParams := genInvalidECDSAParams()
+	km, err := registry.GetKeyManager(testutil.ECDSASignerTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain ECDSASigner key manager: %s", err)
+	}
+	for i := 0; i < len(testParams); i++ {
+		serializedKey, _ := proto.Marshal(testutil.NewRandomECDSAPrivateKey(testParams[i].hashType, testParams[i].curve))
+		if _, err := km.Primitive(serializedKey); err == nil {
+			t.Errorf("expect an error in test case %d", i)
+		}
+	}
+	// invalid version
+	key := testutil.NewRandomECDSAPrivateKey(commonpb.HashType_SHA256,
+		commonpb.EllipticCurveType_NIST_P256)
+	key.Version = testutil.ECDSASignerKeyVersion + 1
+	serializedKey, _ := proto.Marshal(key)
+	if _, err := km.Primitive(serializedKey); err == nil {
+		t.Errorf("expect an error when version is invalid")
+	}
+	// nil input
+	if _, err := km.Primitive(nil); err == nil {
+		t.Errorf("expect an error when input is nil")
+	}
+	if _, err := km.Primitive([]byte{}); err == nil {
+		t.Errorf("expect an error when input is empty slice")
+	}
+}
+
+func TestECDSASignNewKeyBasic(t *testing.T) {
+	testParams := genValidECDSAParams()
+	km, err := registry.GetKeyManager(testutil.ECDSASignerTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain ECDSASigner key manager: %s", err)
+	}
+	for i := 0; i < len(testParams); i++ {
+		params := testutil.NewECDSAParams(testParams[i].hashType, testParams[i].curve,
+			ecdsapb.EcdsaSignatureEncoding_DER)
+		serializedFormat, _ := proto.Marshal(testutil.NewECDSAKeyFormat(params))
+		tmp, err := km.NewKey(serializedFormat)
+		if err != nil {
+			t.Errorf("unexpected error: %s", err)
+		}
+		key := tmp.(*ecdsapb.EcdsaPrivateKey)
+		if err := validateECDSAPrivateKey(key, params); err != nil {
+			t.Errorf("invalid private key in test case %d: %s", i, err)
+		}
+	}
+}
+
+func TestECDSASignNewKeyWithInvalidInput(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.ECDSASignerTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain ECDSASigner key manager: %s", err)
+	}
+	// invalid hash and curve type
+	testParams := genInvalidECDSAParams()
+	for i := 0; i < len(testParams); i++ {
+		params := testutil.NewECDSAParams(testParams[i].hashType, testParams[i].curve,
+			ecdsapb.EcdsaSignatureEncoding_DER)
+		serializedFormat, _ := proto.Marshal(testutil.NewECDSAKeyFormat(params))
+		if _, err := km.NewKey(serializedFormat); err == nil {
+			t.Errorf("expect an error in test case %d", i)
+		}
+	}
+	// invalid encoding
+	testParams = genValidECDSAParams()
+	for i := 0; i < len(testParams); i++ {
+		params := testutil.NewECDSAParams(testParams[i].hashType, testParams[i].curve,
+			ecdsapb.EcdsaSignatureEncoding_UNKNOWN_ENCODING)
+		serializedFormat, _ := proto.Marshal(testutil.NewECDSAKeyFormat(params))
+		if _, err := km.NewKey(serializedFormat); err == nil {
+			t.Errorf("expect an error in test case %d", i)
+		}
+	}
+	// nil input
+	if _, err := km.NewKey(nil); err == nil {
+		t.Errorf("expect an error when input is nil")
+	}
+	if _, err := km.NewKey([]byte{}); err == nil {
+		t.Errorf("expect an error when input is empty slice")
+	}
+}
+
+func TestECDSASignNewKeyMultipleTimes(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.ECDSASignerTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain ECDSASigner key manager: %s", err)
+	}
+	testParams := genValidECDSAParams()
+	nTest := 27
+	for i := 0; i < len(testParams); i++ {
+		keys := make(map[string]bool)
+		params := testutil.NewECDSAParams(testParams[i].hashType, testParams[i].curve,
+			ecdsapb.EcdsaSignatureEncoding_DER)
+		format := testutil.NewECDSAKeyFormat(params)
+		serializedFormat, _ := proto.Marshal(format)
+		for j := 0; j < nTest; j++ {
+			key, _ := km.NewKey(serializedFormat)
+			serializedKey, _ := proto.Marshal(key)
+			keys[string(serializedKey)] = true
+
+			keyData, _ := km.NewKeyData(serializedFormat)
+			serializedKey = keyData.Value
+			keys[string(serializedKey)] = true
+		}
+		if len(keys) != nTest*2 {
+			t.Errorf("key is repeated with params: %s", params)
+		}
+	}
+}
+
+func TestECDSASignNewKeyDataBasic(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.ECDSASignerTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain ECDSASigner key manager: %s", err)
+	}
+	testParams := genValidECDSAParams()
+	for i := 0; i < len(testParams); i++ {
+		params := testutil.NewECDSAParams(testParams[i].hashType, testParams[i].curve,
+			ecdsapb.EcdsaSignatureEncoding_DER)
+		serializedFormat, _ := proto.Marshal(testutil.NewECDSAKeyFormat(params))
+
+		keyData, err := km.NewKeyData(serializedFormat)
+		if err != nil {
+			t.Errorf("unexpected error in test case  %d: %s", i, err)
+		}
+		if keyData.TypeUrl != testutil.ECDSASignerTypeURL {
+			t.Errorf("incorrect type url in test case  %d: expect %s, got %s",
+				i, testutil.ECDSASignerTypeURL, keyData.TypeUrl)
+		}
+		if keyData.KeyMaterialType != tinkpb.KeyData_ASYMMETRIC_PRIVATE {
+			t.Errorf("incorrect key material type in test case  %d: expect %s, got %s",
+				i, tinkpb.KeyData_ASYMMETRIC_PRIVATE, keyData.KeyMaterialType)
+		}
+		key := new(ecdsapb.EcdsaPrivateKey)
+		if err := proto.Unmarshal(keyData.Value, key); err != nil {
+			t.Errorf("unexpect error in test case %d: %s", i, err)
+		}
+		if err := validateECDSAPrivateKey(key, params); err != nil {
+			t.Errorf("invalid private key in test case %d: %s", i, err)
+		}
+	}
+}
+
+func TestECDSASignNewKeyDataWithInvalidInput(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.ECDSASignerTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain ECDSASigner key manager: %s", err)
+	}
+	testParams := genInvalidECDSAParams()
+	for i := 0; i < len(testParams); i++ {
+		params := testutil.NewECDSAParams(testParams[i].hashType, testParams[i].curve,
+			ecdsapb.EcdsaSignatureEncoding_DER)
+		format := testutil.NewECDSAKeyFormat(params)
+		serializedFormat, _ := proto.Marshal(format)
+
+		if _, err := km.NewKeyData(serializedFormat); err == nil {
+			t.Errorf("expect an error in test case  %d", i)
+		}
+	}
+	// nil input
+	if _, err := km.NewKeyData(nil); err == nil {
+		t.Errorf("expect an error when input is nil")
+	}
+}
+
+func TestPublicKeyDataBasic(t *testing.T) {
+	testParams := genValidECDSAParams()
+	km, err := registry.GetKeyManager(testutil.ECDSASignerTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain ECDSASigner key manager: %s", err)
+	}
+	pkm, ok := km.(registry.PrivateKeyManager)
+	if !ok {
+		t.Errorf("cannot obtain private key manager")
+	}
+	for i := 0; i < len(testParams); i++ {
+		key := testutil.NewRandomECDSAPrivateKey(testParams[i].hashType, testParams[i].curve)
+		serializedKey, _ := proto.Marshal(key)
+
+		pubKeyData, err := pkm.PublicKeyData(serializedKey)
+		if err != nil {
+			t.Errorf("unexpect error in test case %d: %s ", i, err)
+		}
+		if pubKeyData.TypeUrl != testutil.ECDSAVerifierTypeURL {
+			t.Errorf("incorrect type url: %s", pubKeyData.TypeUrl)
+		}
+		if pubKeyData.KeyMaterialType != tinkpb.KeyData_ASYMMETRIC_PUBLIC {
+			t.Errorf("incorrect key material type: %d", pubKeyData.KeyMaterialType)
+		}
+		pubKey := new(ecdsapb.EcdsaPublicKey)
+		if err = proto.Unmarshal(pubKeyData.Value, pubKey); err != nil {
+			t.Errorf("invalid public key: %s", err)
+		}
+	}
+}
+
+func TestPublicKeyDataWithInvalidInput(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.ECDSASignerTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain ECDSASigner key manager: %s", err)
+	}
+	pkm, ok := km.(registry.PrivateKeyManager)
+	if !ok {
+		t.Errorf("cannot obtain private key manager")
+	}
+	// modified key
+	key := testutil.NewRandomECDSAPrivateKey(commonpb.HashType_SHA256,
+		commonpb.EllipticCurveType_NIST_P256)
+	serializedKey, _ := proto.Marshal(key)
+	serializedKey[0] = 0
+	if _, err := pkm.PublicKeyData(serializedKey); err == nil {
+		t.Errorf("expect an error when input is a modified serialized key")
+	}
+	// nil
+	if _, err := pkm.PublicKeyData(nil); err == nil {
+		t.Errorf("expect an error when input is nil")
+	}
+	// empty slice
+	if _, err := pkm.PublicKeyData([]byte{}); err == nil {
+		t.Errorf("expect an error when input is an empty slice")
+	}
+}
+
+var errSmallKey = fmt.Errorf("private key doesn't have adequate size")
+
+func validateECDSAPrivateKey(key *ecdsapb.EcdsaPrivateKey, params *ecdsapb.EcdsaParams) error {
+	if key.Version != testutil.ECDSASignerKeyVersion {
+		return fmt.Errorf("incorrect private key's version: expect %d, got %d",
+			testutil.ECDSASignerKeyVersion, key.Version)
+	}
+	publicKey := key.PublicKey
+	if publicKey.Version != testutil.ECDSASignerKeyVersion {
+		return fmt.Errorf("incorrect public key's version: expect %d, got %d",
+			testutil.ECDSASignerKeyVersion, key.Version)
+	}
+	if params.HashType != publicKey.Params.HashType ||
+		params.Curve != publicKey.Params.Curve ||
+		params.Encoding != publicKey.Params.Encoding {
+		return fmt.Errorf("incorrect params: expect %s, got %s", params, publicKey.Params)
+	}
+	if len(publicKey.X) == 0 || len(publicKey.Y) == 0 {
+		return fmt.Errorf("public points are not initialized")
+	}
+	// check private key's size
+	d := new(big.Int).SetBytes(key.KeyValue)
+	keySize := len(d.Bytes())
+	switch params.Curve {
+	case commonpb.EllipticCurveType_NIST_P256:
+		if keySize < 256/8-8 || keySize > 256/8+1 {
+			return errSmallKey
+		}
+	case commonpb.EllipticCurveType_NIST_P384:
+		if keySize < 384/8-8 || keySize > 384/8+1 {
+			return errSmallKey
+		}
+	case commonpb.EllipticCurveType_NIST_P521:
+		if keySize < 521/8-8 || keySize > 521/8+1 {
+			return errSmallKey
+		}
+	}
+	// try to sign and verify with the key
+	hash, curve, encoding := testutil.GetECDSAParamNames(publicKey.Params)
+	signer, err := signature.NewECDSASigner(hash, curve, encoding, key.KeyValue)
+	if err != nil {
+		return fmt.Errorf("unexpected error when creating ECDSASign: %s", err)
+	}
+	verifier, err := signature.NewECDSAVerifier(hash, curve, encoding, publicKey.X, publicKey.Y)
+	if err != nil {
+		return fmt.Errorf("unexpected error when creating ECDSAVerify: %s", err)
+	}
+	data := random.GetRandomBytes(1281)
+	signature, err := signer.Sign(data)
+	if err != nil {
+		return fmt.Errorf("unexpected error when signing: %s", err)
+	}
+
+	if err := verifier.Verify(signature, data); err != nil {
+		return fmt.Errorf("unexpected error when verifying signature: %s", err)
+	}
+	return nil
+}
+
+func genValidECDSAParams() []ecdsaParams {
+	return []ecdsaParams{
+		ecdsaParams{
+			hashType: commonpb.HashType_SHA256,
+			curve:    commonpb.EllipticCurveType_NIST_P256,
+		},
+		ecdsaParams{
+			hashType: commonpb.HashType_SHA512,
+			curve:    commonpb.EllipticCurveType_NIST_P384,
+		},
+		ecdsaParams{
+			hashType: commonpb.HashType_SHA512,
+			curve:    commonpb.EllipticCurveType_NIST_P521,
+		},
+	}
+}
+
+func genInvalidECDSAParams() []ecdsaParams {
+	return []ecdsaParams{
+		ecdsaParams{
+			hashType: commonpb.HashType_SHA1,
+			curve:    commonpb.EllipticCurveType_NIST_P256,
+		},
+		ecdsaParams{
+			hashType: commonpb.HashType_SHA1,
+			curve:    commonpb.EllipticCurveType_NIST_P384,
+		},
+		ecdsaParams{
+			hashType: commonpb.HashType_SHA1,
+			curve:    commonpb.EllipticCurveType_NIST_P521,
+		},
+		ecdsaParams{
+			hashType: commonpb.HashType_SHA256,
+			curve:    commonpb.EllipticCurveType_NIST_P384,
+		},
+		ecdsaParams{
+			hashType: commonpb.HashType_SHA256,
+			curve:    commonpb.EllipticCurveType_NIST_P521,
+		},
+		ecdsaParams{
+			hashType: commonpb.HashType_SHA512,
+			curve:    commonpb.EllipticCurveType_NIST_P256,
+		},
+	}
+}
diff --git a/go/signature/ecdsa_verifier_key_manager.go b/go/signature/ecdsa_verifier_key_manager.go
new file mode 100644
index 0000000..6e3c506
--- /dev/null
+++ b/go/signature/ecdsa_verifier_key_manager.go
@@ -0,0 +1,97 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package signature
+
+import (
+	"fmt"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/registry"
+	subtleSignature "github.com/google/tink/go/subtle/signature"
+	ecdsapb "github.com/google/tink/proto/ecdsa_go_proto"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+const (
+	ecdsaVerifierKeyVersion = 0
+	ecdsaVerifierTypeURL    = "type.googleapis.com/google.crypto.tink.EcdsaPublicKey"
+)
+
+// common errors
+var errInvalidECDSAVerifierKey = fmt.Errorf("ecdsa_verifier_key_manager: invalid key")
+var errECDSAVerifierNotImplemented = fmt.Errorf("ecdsa_verifier_key_manager: not implemented")
+
+// ecdsaVerifierKeyManager is an implementation of KeyManager interface.
+// It doesn't support key generation.
+type ecdsaVerifierKeyManager struct{}
+
+// Assert that ecdsaVerifierKeyManager implements the KeyManager interface.
+var _ registry.KeyManager = (*ecdsaVerifierKeyManager)(nil)
+
+// newECDSAVerifierKeyManager creates a new ecdsaVerifierKeyManager.
+func newECDSAVerifierKeyManager() *ecdsaVerifierKeyManager {
+	return new(ecdsaVerifierKeyManager)
+}
+
+// Primitive creates an ECDSAVerifier subtle for the given serialized ECDSAPublicKey proto.
+func (km *ecdsaVerifierKeyManager) Primitive(serializedKey []byte) (interface{}, error) {
+	if len(serializedKey) == 0 {
+		return nil, errInvalidECDSAVerifierKey
+	}
+	key := new(ecdsapb.EcdsaPublicKey)
+	if err := proto.Unmarshal(serializedKey, key); err != nil {
+		return nil, errInvalidECDSAVerifierKey
+	}
+	if err := km.validateKey(key); err != nil {
+		return nil, fmt.Errorf("ecdsa_verifier_key_manager: %s", err)
+	}
+	hash, curve, encoding := getECDSAParamNames(key.Params)
+	ret, err := subtleSignature.NewECDSAVerifier(hash, curve, encoding, key.X, key.Y)
+	if err != nil {
+		return nil, fmt.Errorf("ecdsa_verifier_key_manager: invalid key: %s", err)
+	}
+	return ret, nil
+}
+
+// NewKey is not implemented.
+func (km *ecdsaVerifierKeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) {
+	return nil, errECDSAVerifierNotImplemented
+}
+
+// NewKeyData creates a new KeyData according to specification in  the given
+// serialized ECDSAKeyFormat. It should be used solely by the key management API.
+func (km *ecdsaVerifierKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) {
+	return nil, errECDSAVerifierNotImplemented
+}
+
+// DoesSupport indicates if this key manager supports the given key type.
+func (km *ecdsaVerifierKeyManager) DoesSupport(typeURL string) bool {
+	return typeURL == ecdsaVerifierTypeURL
+}
+
+// TypeURL returns the key type of keys managed by this key manager.
+func (km *ecdsaVerifierKeyManager) TypeURL() string {
+	return ecdsaVerifierTypeURL
+}
+
+// validateKey validates the given ECDSAPublicKey.
+func (km *ecdsaVerifierKeyManager) validateKey(key *ecdsapb.EcdsaPublicKey) error {
+	if err := keyset.ValidateKeyVersion(key.Version, ecdsaVerifierKeyVersion); err != nil {
+		return fmt.Errorf("ecdsa_verifier_key_manager: %s", err)
+	}
+	hash, curve, encoding := getECDSAParamNames(key.Params)
+	return subtleSignature.ValidateECDSAParams(hash, curve, encoding)
+}
diff --git a/go/signature/ecdsa_verifier_key_manager_test.go b/go/signature/ecdsa_verifier_key_manager_test.go
new file mode 100644
index 0000000..a9fe73e
--- /dev/null
+++ b/go/signature/ecdsa_verifier_key_manager_test.go
@@ -0,0 +1,70 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package signature_test
+
+import (
+	"testing"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/google/tink/go/registry"
+	"github.com/google/tink/go/subtle/signature"
+	"github.com/google/tink/go/testutil"
+	commonpb "github.com/google/tink/proto/common_go_proto"
+)
+
+func TestECDSAVerifyGetPrimitiveBasic(t *testing.T) {
+	testParams := genValidECDSAParams()
+	km, err := registry.GetKeyManager(testutil.ECDSAVerifierTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain ECDSAVerifier key manager: %s", err)
+	}
+	for i := 0; i < len(testParams); i++ {
+		serializedKey, _ := proto.Marshal(testutil.NewRandomECDSAPublicKey(testParams[i].hashType, testParams[i].curve))
+		tmp, err := km.Primitive(serializedKey)
+		if err != nil {
+			t.Errorf("unexpect error in test case %d: %s ", i, err)
+		}
+		var _ *signature.ECDSAVerifier = tmp.(*signature.ECDSAVerifier)
+	}
+}
+
+func TestECDSAVerifyGetPrimitiveWithInvalidInput(t *testing.T) {
+	testParams := genInvalidECDSAParams()
+	km, err := registry.GetKeyManager(testutil.ECDSAVerifierTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain ECDSAVerifier key manager: %s", err)
+	}
+	for i := 0; i < len(testParams); i++ {
+		serializedKey, _ := proto.Marshal(testutil.NewRandomECDSAPrivateKey(testParams[i].hashType, testParams[i].curve))
+		if _, err := km.Primitive(serializedKey); err == nil {
+			t.Errorf("expect an error in test case %d", i)
+		}
+	}
+	// invalid version
+	key := testutil.NewRandomECDSAPublicKey(commonpb.HashType_SHA256,
+		commonpb.EllipticCurveType_NIST_P256)
+	key.Version = testutil.ECDSAVerifierKeyVersion + 1
+	serializedKey, _ := proto.Marshal(key)
+	if _, err := km.Primitive(serializedKey); err == nil {
+		t.Errorf("expect an error when version is invalid")
+	}
+	// nil input
+	if _, err := km.Primitive(nil); err == nil {
+		t.Errorf("expect an error when input is nil")
+	}
+	if _, err := km.Primitive([]byte{}); err == nil {
+		t.Errorf("expect an error when input is empty slice")
+	}
+}
diff --git a/go/signature/ecdsa_verify_key_manager.go b/go/signature/ecdsa_verify_key_manager.go
deleted file mode 100644
index 404a8b2..0000000
--- a/go/signature/ecdsa_verify_key_manager.go
+++ /dev/null
@@ -1,114 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package signature
-
-import (
-	"fmt"
-
-	"github.com/golang/protobuf/proto"
-	subtleSignature "github.com/google/tink/go/subtle/signature"
-	"github.com/google/tink/go/tink"
-	ecdsapb "github.com/google/tink/proto/ecdsa_go_proto"
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
-)
-
-const (
-	// EcdsaVerifyKeyVersion is the maximum version of keys that this manager supports.
-	EcdsaVerifyKeyVersion = 0
-
-	// EcdsaVerifyTypeURL is the only type URL that this manager supports.
-	EcdsaVerifyTypeURL = "type.googleapis.com/google.crypto.tink.EcdsaPublicKey"
-)
-
-// common errors
-var errInvalidEcdsaVerifyKey = fmt.Errorf("ecdsa_verify_key_manager: invalid key")
-var errEcdsaVerifyNotImplemented = fmt.Errorf("ecdsa_verify_key_manager: not implemented")
-
-// EcdsaVerifyKeyManager is an implementation of KeyManager interface.
-// It doesn't support key generation.
-type EcdsaVerifyKeyManager struct{}
-
-// Assert that EcdsaVerifyKeyManager implements the KeyManager interface.
-var _ tink.KeyManager = (*EcdsaVerifyKeyManager)(nil)
-
-// NewEcdsaVerifyKeyManager creates a new EcdsaVerifyKeyManager.
-func NewEcdsaVerifyKeyManager() *EcdsaVerifyKeyManager {
-	return new(EcdsaVerifyKeyManager)
-}
-
-// GetPrimitiveFromSerializedKey creates an EcdsaVerify subtle for the given
-// serialized EcdsaPublicKey proto.
-func (km *EcdsaVerifyKeyManager) GetPrimitiveFromSerializedKey(serializedKey []byte) (interface{}, error) {
-	if len(serializedKey) == 0 {
-		return nil, errInvalidEcdsaVerifyKey
-	}
-	key := new(ecdsapb.EcdsaPublicKey)
-	if err := proto.Unmarshal(serializedKey, key); err != nil {
-		return nil, errInvalidEcdsaVerifyKey
-	}
-	return km.GetPrimitiveFromKey(key)
-}
-
-// GetPrimitiveFromKey creates an EcdsaVerify subtle for the given EcdsaPublicKey proto.
-func (km *EcdsaVerifyKeyManager) GetPrimitiveFromKey(m proto.Message) (interface{}, error) {
-	key, ok := m.(*ecdsapb.EcdsaPublicKey)
-	if !ok {
-		return nil, errInvalidEcdsaVerifyKey
-	}
-	if err := km.validateKey(key); err != nil {
-		return nil, fmt.Errorf("ecdsa_verify_key_manager: %s", err)
-	}
-	hash, curve, encoding := GetEcdsaParamNames(key.Params)
-	ret, err := subtleSignature.NewEcdsaVerify(hash, curve, encoding, key.X, key.Y)
-	if err != nil {
-		return nil, fmt.Errorf("ecdsa_verify_key_manager: invalid key: %s", err)
-	}
-	return ret, nil
-}
-
-// NewKeyFromSerializedKeyFormat is not implemented
-func (km *EcdsaVerifyKeyManager) NewKeyFromSerializedKeyFormat(serializedKeyFormat []byte) (proto.Message, error) {
-	return nil, errEcdsaVerifyNotImplemented
-}
-
-// NewKeyFromKeyFormat is not implemented
-func (km *EcdsaVerifyKeyManager) NewKeyFromKeyFormat(m proto.Message) (proto.Message, error) {
-	return nil, errEcdsaVerifyNotImplemented
-}
-
-// NewKeyData creates a new KeyData according to specification in  the given
-// serialized EcdsaKeyFormat. It should be used solely by the key management API.
-func (km *EcdsaVerifyKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) {
-	return nil, errEcdsaVerifyNotImplemented
-}
-
-// DoesSupport indicates if this key manager supports the given key type.
-func (km *EcdsaVerifyKeyManager) DoesSupport(typeURL string) bool {
-	return typeURL == EcdsaVerifyTypeURL
-}
-
-// GetKeyType returns the key type of keys managed by this key manager.
-func (km *EcdsaVerifyKeyManager) GetKeyType() string {
-	return EcdsaVerifyTypeURL
-}
-
-// validateKey validates the given EcdsaPublicKey.
-func (km *EcdsaVerifyKeyManager) validateKey(key *ecdsapb.EcdsaPublicKey) error {
-	if err := tink.ValidateVersion(key.Version, EcdsaVerifyKeyVersion); err != nil {
-		return fmt.Errorf("ecdsa_verify_key_manager: %s", err)
-	}
-	hash, curve, encoding := GetEcdsaParamNames(key.Params)
-	return subtleSignature.ValidateEcdsaParams(hash, curve, encoding)
-}
diff --git a/go/signature/ecdsa_verify_key_manager_test.go b/go/signature/ecdsa_verify_key_manager_test.go
deleted file mode 100644
index c2a141b..0000000
--- a/go/signature/ecdsa_verify_key_manager_test.go
+++ /dev/null
@@ -1,84 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package signature_test
-
-import (
-	"testing"
-
-	"github.com/golang/protobuf/proto"
-	"github.com/google/tink/go/signature"
-	subtleSig "github.com/google/tink/go/subtle/signature"
-	"github.com/google/tink/go/testutil"
-	commonpb "github.com/google/tink/proto/common_go_proto"
-)
-
-func TestNewEcdsaVerifyKeyManager(t *testing.T) {
-	var km *signature.EcdsaVerifyKeyManager = signature.NewEcdsaVerifyKeyManager()
-	if km == nil {
-		t.Errorf("NewEcdsaVerifyKeyManager returns nil")
-	}
-}
-
-func TestEcdsaVerifyGetPrimitiveBasic(t *testing.T) {
-	testParams := genValidEcdsaParams()
-	km := signature.NewEcdsaVerifyKeyManager()
-	for i := 0; i < len(testParams); i++ {
-		key := testutil.NewEcdsaPublicKey(testParams[i].hashType, testParams[i].curve)
-		tmp, err := km.GetPrimitiveFromKey(key)
-		if err != nil {
-			t.Errorf("unexpect error in test case %d: %s ", i, err)
-		}
-		var _ *subtleSig.EcdsaVerify = tmp.(*subtleSig.EcdsaVerify)
-
-		serializedKey, _ := proto.Marshal(key)
-		tmp, err = km.GetPrimitiveFromSerializedKey(serializedKey)
-		if err != nil {
-			t.Errorf("unexpect error in test case %d: %s ", i, err)
-		}
-		var _ *subtleSig.EcdsaVerify = tmp.(*subtleSig.EcdsaVerify)
-	}
-}
-
-func TestEcdsaVerifyGetPrimitiveWithInvalidInput(t *testing.T) {
-	testParams := genInvalidEcdsaParams()
-	km := signature.NewEcdsaVerifyKeyManager()
-	for i := 0; i < len(testParams); i++ {
-		key := testutil.NewEcdsaPrivateKey(testParams[i].hashType, testParams[i].curve)
-		if _, err := km.GetPrimitiveFromKey(key); err == nil {
-			t.Errorf("expect an error in test case %d", i)
-		}
-		serializedKey, _ := proto.Marshal(key)
-		if _, err := km.GetPrimitiveFromSerializedKey(serializedKey); err == nil {
-			t.Errorf("expect an error in test case %d", i)
-		}
-	}
-	// invalid version
-	key := testutil.NewEcdsaPublicKey(commonpb.HashType_SHA256,
-		commonpb.EllipticCurveType_NIST_P256)
-	key.Version = signature.EcdsaVerifyKeyVersion + 1
-	if _, err := km.GetPrimitiveFromKey(key); err == nil {
-		t.Errorf("expect an error when version is invalid")
-	}
-	// nil input
-	if _, err := km.GetPrimitiveFromKey(nil); err == nil {
-		t.Errorf("expect an error when input is nil")
-	}
-	if _, err := km.GetPrimitiveFromSerializedKey(nil); err == nil {
-		t.Errorf("expect an error when input is nil")
-	}
-	if _, err := km.GetPrimitiveFromSerializedKey([]byte{}); err == nil {
-		t.Errorf("expect an error when input is empty slice")
-	}
-}
diff --git a/go/signature/ed25519_signer_key_manager.go b/go/signature/ed25519_signer_key_manager.go
new file mode 100644
index 0000000..5c4a9a2
--- /dev/null
+++ b/go/signature/ed25519_signer_key_manager.go
@@ -0,0 +1,147 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package signature
+
+import (
+	"crypto/rand"
+	"errors"
+	"fmt"
+
+	"golang.org/x/crypto/ed25519"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/registry"
+	subtleSignature "github.com/google/tink/go/subtle/signature"
+	ed25519pb "github.com/google/tink/proto/ed25519_go_proto"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+const (
+	ed25519SignerKeyVersion = 0
+	ed25519SignerTypeURL    = "type.googleapis.com/google.crypto.tink.Ed25519PrivateKey"
+)
+
+// common errors
+var errInvalidED25519SignKey = errors.New("ed25519_signer_key_manager: invalid key")
+var errInvalidED25519SignKeyFormat = errors.New("ed25519_signer_key_manager: invalid key format")
+
+// ed25519SignerKeyManager is an implementation of KeyManager interface.
+// It generates new ED25519PrivateKeys and produces new instances of ED25519Sign subtle.
+type ed25519SignerKeyManager struct{}
+
+// Assert that ed25519SignerKeyManager implements the PrivateKeyManager interface.
+var _ registry.PrivateKeyManager = (*ed25519SignerKeyManager)(nil)
+
+// newED25519SignerKeyManager creates a new ed25519SignerKeyManager.
+func newED25519SignerKeyManager() *ed25519SignerKeyManager {
+	return new(ed25519SignerKeyManager)
+}
+
+// Primitive creates an ED25519Sign subtle for the given serialized ED25519PrivateKey proto.
+func (km *ed25519SignerKeyManager) Primitive(serializedKey []byte) (interface{}, error) {
+	if len(serializedKey) == 0 {
+		return nil, errInvalidED25519SignKey
+	}
+	key := new(ed25519pb.Ed25519PrivateKey)
+
+	if err := proto.Unmarshal(serializedKey, key); err != nil {
+		return nil, errInvalidED25519SignKey
+	}
+	if err := km.validateKey(key); err != nil {
+		return nil, err
+	}
+
+	ret, err := subtleSignature.NewED25519Signer(key.KeyValue)
+	if err != nil {
+		return nil, fmt.Errorf("ed25519_signer_key_manager: %s", err)
+	}
+	return ret, nil
+}
+
+// NewKey creates a new ED25519PrivateKey according to specification the given serialized ED25519KeyFormat.
+func (km *ed25519SignerKeyManager) NewKey(serializedKey []byte) (proto.Message, error) {
+	public, private, err := ed25519.GenerateKey(rand.Reader)
+	if err != nil {
+		return nil, fmt.Errorf("ed25519_signer_key_manager: cannot generate ED25519 key: %s", err)
+	}
+
+	publicProto := &ed25519pb.Ed25519PublicKey{
+		Version:  ed25519SignerKeyVersion,
+		KeyValue: public,
+	}
+	privateProto := &ed25519pb.Ed25519PrivateKey{
+		Version:   ed25519SignerKeyVersion,
+		PublicKey: publicProto,
+		KeyValue:  private.Seed(),
+	}
+	return privateProto, nil
+}
+
+// NewKeyData creates a new KeyData according to specification in  the given
+// serialized ED25519KeyFormat. It should be used solely by the key management API.
+func (km *ed25519SignerKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) {
+	key, err := km.NewKey(serializedKeyFormat)
+	if err != nil {
+		return nil, err
+	}
+	serializedKey, err := proto.Marshal(key)
+	if err != nil {
+		return nil, errInvalidED25519SignKeyFormat
+	}
+	return &tinkpb.KeyData{
+		TypeUrl:         ed25519SignerTypeURL,
+		Value:           serializedKey,
+		KeyMaterialType: tinkpb.KeyData_ASYMMETRIC_PRIVATE,
+	}, nil
+}
+
+// PublicKeyData extracts the public key data from the private key.
+func (km *ed25519SignerKeyManager) PublicKeyData(serializedPrivKey []byte) (*tinkpb.KeyData, error) {
+	privKey := new(ed25519pb.Ed25519PrivateKey)
+	if err := proto.Unmarshal(serializedPrivKey, privKey); err != nil {
+		return nil, errInvalidED25519SignKey
+	}
+	serializedPubKey, err := proto.Marshal(privKey.PublicKey)
+	if err != nil {
+		return nil, errInvalidED25519SignKey
+	}
+	return &tinkpb.KeyData{
+		TypeUrl:         ed25519VerifierTypeURL,
+		Value:           serializedPubKey,
+		KeyMaterialType: tinkpb.KeyData_ASYMMETRIC_PUBLIC,
+	}, nil
+}
+
+// DoesSupport indicates if this key manager supports the given key type.
+func (km *ed25519SignerKeyManager) DoesSupport(typeURL string) bool {
+	return typeURL == ed25519SignerTypeURL
+}
+
+// TypeURL returns the key type of keys managed by this key manager.
+func (km *ed25519SignerKeyManager) TypeURL() string {
+	return ed25519SignerTypeURL
+}
+
+// validateKey validates the given ED25519PrivateKey.
+func (km *ed25519SignerKeyManager) validateKey(key *ed25519pb.Ed25519PrivateKey) error {
+	if err := keyset.ValidateKeyVersion(key.Version, ed25519SignerKeyVersion); err != nil {
+		return fmt.Errorf("ed25519_signer_key_manager: invalid key: %s", err)
+	}
+	if len(key.KeyValue) != ed25519.SeedSize {
+		return fmt.Errorf("ed2219_signer_key_manager: invalid key length, got %d", len(key.KeyValue))
+	}
+	return nil
+}
diff --git a/go/signature/ed25519_signer_key_manager_test.go b/go/signature/ed25519_signer_key_manager_test.go
new file mode 100644
index 0000000..3b96dfb
--- /dev/null
+++ b/go/signature/ed25519_signer_key_manager_test.go
@@ -0,0 +1,193 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package signature_test
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/google/tink/go/registry"
+	"github.com/google/tink/go/subtle/random"
+	"github.com/google/tink/go/subtle/signature"
+	"github.com/google/tink/go/testutil"
+	ed25519pb "github.com/google/tink/proto/ed25519_go_proto"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+func TestED25519SignerGetPrimitiveBasic(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.ED25519SignerTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain ED25519Signer key manager: %s", err)
+	}
+	pvtKey := testutil.NewED25519PrivateKey()
+	serializedKey, _ := proto.Marshal(pvtKey)
+	tmp, err := km.Primitive(serializedKey)
+	if err != nil {
+		t.Errorf("unexpect error in test case: %s ", err)
+	}
+	var s = tmp.(*signature.ED25519Signer)
+
+	kmPub, err := registry.GetKeyManager(testutil.ED25519VerifierTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain ED25519Signer key manager: %s", err)
+	}
+	pubKey := pvtKey.PublicKey
+	serializedKey, _ = proto.Marshal(pubKey)
+	tmp, err = kmPub.Primitive(serializedKey)
+	if err != nil {
+		t.Errorf("unexpect error in test case: %s ", err)
+	}
+	var v = tmp.(*signature.ED25519Verifier)
+
+	data := random.GetRandomBytes(1281)
+	signature, err := s.Sign(data)
+	if err != nil {
+		t.Errorf("unexpected error when signing: %s", err)
+	}
+
+	if err := v.Verify(signature, data); err != nil {
+		t.Errorf("unexpected error when verifying signature: %s", err)
+	}
+
+}
+
+func TestED25519SignGetPrimitiveWithInvalidInput(t *testing.T) {
+	// invalid params
+	km, err := registry.GetKeyManager(testutil.ED25519SignerTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain ED25519Signer key manager: %s", err)
+	}
+
+	// invalid version
+	key := testutil.NewED25519PrivateKey()
+	key.Version = testutil.ED25519SignerKeyVersion + 1
+	serializedKey, _ := proto.Marshal(key)
+	if _, err := km.Primitive(serializedKey); err == nil {
+		t.Errorf("expect an error when version is invalid")
+	}
+	// nil input
+	if _, err := km.Primitive(nil); err == nil {
+		t.Errorf("expect an error when input is nil")
+	}
+	if _, err := km.Primitive([]byte{}); err == nil {
+		t.Errorf("expect an error when input is empty slice")
+	}
+}
+
+func TestED25519SignNewKeyBasic(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.ED25519SignerTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain ED25519Signer key manager: %s", err)
+	}
+	serializedFormat, _ := proto.Marshal(testutil.NewED25519PrivateKey())
+	tmp, err := km.NewKey(serializedFormat)
+	if err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+	key := tmp.(*ed25519pb.Ed25519PrivateKey)
+	if err := validateED25519PrivateKey(key); err != nil {
+		t.Errorf("invalid private key in test case: %s", err)
+	}
+}
+
+func TestED25519PublicKeyDataBasic(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.ED25519SignerTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain ED25519Signer key manager: %s", err)
+	}
+	pkm, ok := km.(registry.PrivateKeyManager)
+	if !ok {
+		t.Errorf("cannot obtain private key manager")
+	}
+
+	key := testutil.NewED25519PrivateKey()
+	serializedKey, _ := proto.Marshal(key)
+
+	pubKeyData, err := pkm.PublicKeyData(serializedKey)
+	if err != nil {
+		t.Errorf("unexpect error in test case: %s ", err)
+	}
+	if pubKeyData.TypeUrl != testutil.ED25519VerifierTypeURL {
+		t.Errorf("incorrect type url: %s", pubKeyData.TypeUrl)
+	}
+	if pubKeyData.KeyMaterialType != tinkpb.KeyData_ASYMMETRIC_PUBLIC {
+		t.Errorf("incorrect key material type: %d", pubKeyData.KeyMaterialType)
+	}
+	pubKey := new(ed25519pb.Ed25519PublicKey)
+	if err = proto.Unmarshal(pubKeyData.Value, pubKey); err != nil {
+		t.Errorf("invalid public key: %s", err)
+	}
+}
+
+func TestED25519PublicKeyDataWithInvalidInput(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.ED25519SignerTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain ED25519Signer key manager: %s", err)
+	}
+	pkm, ok := km.(registry.PrivateKeyManager)
+	if !ok {
+		t.Errorf("cannot obtain private key manager")
+	}
+	// modified key
+	key := testutil.NewED25519PrivateKey()
+	serializedKey, _ := proto.Marshal(key)
+	serializedKey[0] = 0
+	if _, err := pkm.PublicKeyData(serializedKey); err == nil {
+		t.Errorf("expect an error when input is a modified serialized key")
+	}
+	// nil
+	if _, err := pkm.PublicKeyData(nil); err == nil {
+		t.Errorf("expect an error when input is nil")
+	}
+	// empty slice
+	if _, err := pkm.PublicKeyData([]byte{}); err == nil {
+		t.Errorf("expect an error when input is an empty slice")
+	}
+}
+
+func validateED25519PrivateKey(key *ed25519pb.Ed25519PrivateKey) error {
+	if key.Version != testutil.ED25519SignerKeyVersion {
+		return fmt.Errorf("incorrect private key's version: expect %d, got %d",
+			testutil.ED25519SignerKeyVersion, key.Version)
+	}
+	publicKey := key.PublicKey
+	if publicKey.Version != testutil.ED25519SignerKeyVersion {
+		return fmt.Errorf("incorrect public key's version: expect %d, got %d",
+			testutil.ED25519SignerKeyVersion, key.Version)
+	}
+
+	signer, err := signature.NewED25519Signer(key.KeyValue)
+	if err != nil {
+		return fmt.Errorf("unexpected error when creating ED25519Sign: %s", err)
+	}
+
+	verifier, err := signature.NewED25519Verifier(publicKey.KeyValue)
+	if err != nil {
+		return fmt.Errorf("unexpected error when creating ED25519Verify: %s", err)
+	}
+	for i := 0; i < 100; i++ {
+		data := random.GetRandomBytes(1281)
+		signature, err := signer.Sign(data)
+		if err != nil {
+			return fmt.Errorf("unexpected error when signing: %s", err)
+		}
+
+		if err := verifier.Verify(signature, data); err != nil {
+			return fmt.Errorf("unexpected error when verifying signature: %s", err)
+		}
+	}
+	return nil
+}
diff --git a/go/signature/ed25519_verifier_key_manager.go b/go/signature/ed25519_verifier_key_manager.go
new file mode 100644
index 0000000..3a324ef
--- /dev/null
+++ b/go/signature/ed25519_verifier_key_manager.go
@@ -0,0 +1,99 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package signature
+
+import (
+	"fmt"
+
+	"github.com/golang/protobuf/proto"
+	"golang.org/x/crypto/ed25519"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/registry"
+	subtleSignature "github.com/google/tink/go/subtle/signature"
+	ed25519pb "github.com/google/tink/proto/ed25519_go_proto"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+const (
+	ed25519VerifierKeyVersion = 0
+	ed25519VerifierTypeURL    = "type.googleapis.com/google.crypto.tink.Ed25519PublicKey"
+)
+
+// common errors
+var errInvalidED25519VerifierKey = fmt.Errorf("ed25519_verifier_key_manager: invalid key")
+var errED25519VerifierNotImplemented = fmt.Errorf("ed25519_verifier_key_manager: not implemented")
+
+// ed25519VerifierKeyManager is an implementation of KeyManager interface.
+// It doesn't support key generation.
+type ed25519VerifierKeyManager struct{}
+
+// Assert that ed25519VerifierKeyManager implements the KeyManager interface.
+var _ registry.KeyManager = (*ed25519VerifierKeyManager)(nil)
+
+// newED25519VerifierKeyManager creates a new ed25519VerifierKeyManager.
+func newED25519VerifierKeyManager() *ed25519VerifierKeyManager {
+	return new(ed25519VerifierKeyManager)
+}
+
+// Primitive creates an ED25519Verifier subtle for the given serialized ED25519PublicKey proto.
+func (km *ed25519VerifierKeyManager) Primitive(serializedKey []byte) (interface{}, error) {
+	if len(serializedKey) == 0 {
+		return nil, errInvalidED25519VerifierKey
+	}
+	key := new(ed25519pb.Ed25519PublicKey)
+	if err := proto.Unmarshal(serializedKey, key); err != nil {
+		return nil, errInvalidED25519VerifierKey
+	}
+	if err := km.validateKey(key); err != nil {
+		return nil, fmt.Errorf("ed25519_verifier_key_manager: %s", err)
+	}
+	ret, err := subtleSignature.NewED25519Verifier(key.KeyValue)
+	if err != nil {
+		return nil, fmt.Errorf("ed25519_verifier_key_manager: invalid key: %s", err)
+	}
+	return ret, nil
+}
+
+// NewKey is not implemented.
+func (km *ed25519VerifierKeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) {
+	return nil, errED25519VerifierNotImplemented
+}
+
+// NewKeyData creates a new KeyData according to specification in  the given
+// serialized ED25519KeyFormat. It should be used solely by the key management API.
+func (km *ed25519VerifierKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) {
+	return nil, errED25519VerifierNotImplemented
+}
+
+// DoesSupport indicates if this key manager supports the given key type.
+func (km *ed25519VerifierKeyManager) DoesSupport(typeURL string) bool {
+	return typeURL == ed25519VerifierTypeURL
+}
+
+// TypeURL returns the key type of keys managed by this key manager.
+func (km *ed25519VerifierKeyManager) TypeURL() string {
+	return ed25519VerifierTypeURL
+}
+
+// validateKey validates the given ED25519PublicKey.
+func (km *ed25519VerifierKeyManager) validateKey(key *ed25519pb.Ed25519PublicKey) error {
+	if err := keyset.ValidateKeyVersion(key.Version, ed25519VerifierKeyVersion); err != nil {
+		return fmt.Errorf("ed25519_verifier_key_manager: %s", err)
+	}
+	if len(key.KeyValue) != ed25519.PublicKeySize {
+		return fmt.Errorf("ed25519_verifier_key_manager: invalid key length, required :%d", ed25519.PublicKeySize)
+	}
+	return nil
+}
diff --git a/go/signature/ed25519_verifier_key_manager_test.go b/go/signature/ed25519_verifier_key_manager_test.go
new file mode 100644
index 0000000..d2620c1
--- /dev/null
+++ b/go/signature/ed25519_verifier_key_manager_test.go
@@ -0,0 +1,59 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package signature_test
+
+import (
+	"testing"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/google/tink/go/registry"
+	"github.com/google/tink/go/subtle/signature"
+	"github.com/google/tink/go/testutil"
+)
+
+func TestED25519VerifyGetPrimitiveBasic(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.ED25519VerifierTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain ED25519Verifier key manager: %s", err)
+	}
+	serializedKey, _ := proto.Marshal(testutil.NewED25519PublicKey())
+	tmp, err := km.Primitive(serializedKey)
+	if err != nil {
+		t.Errorf("unexpect error in test case: %s ", err)
+	}
+	var _ *signature.ED25519Verifier = tmp.(*signature.ED25519Verifier)
+}
+
+func TestED25519VerifyGetPrimitiveWithInvalidInput(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.ED25519VerifierTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain ED25519Verifier key manager: %s", err)
+	}
+
+	// invalid version
+	key := testutil.NewED25519PublicKey()
+	key.Version = testutil.ED25519VerifierKeyVersion + 1
+	serializedKey, _ := proto.Marshal(key)
+	if _, err := km.Primitive(serializedKey); err == nil {
+		t.Errorf("expect an error when version is invalid")
+	}
+	// nil input
+	if _, err := km.Primitive(nil); err == nil {
+		t.Errorf("expect an error when input is nil")
+	}
+	if _, err := km.Primitive([]byte{}); err == nil {
+		t.Errorf("expect an error when input is empty slice")
+	}
+}
diff --git a/go/signature/proto.go b/go/signature/proto.go
new file mode 100644
index 0000000..bfb09d1
--- /dev/null
+++ b/go/signature/proto.go
@@ -0,0 +1,52 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package signature
+
+import (
+	commonpb "github.com/google/tink/proto/common_go_proto"
+	ecdsapb "github.com/google/tink/proto/ecdsa_go_proto"
+)
+
+// getECDSAParamNames returns the string representations of each parameter in
+// the given ECDSAParams.
+func getECDSAParamNames(params *ecdsapb.EcdsaParams) (string, string, string) {
+	hashName := commonpb.HashType_name[int32(params.HashType)]
+	curveName := commonpb.EllipticCurveType_name[int32(params.Curve)]
+	encodingName := ecdsapb.EcdsaSignatureEncoding_name[int32(params.Encoding)]
+	return hashName, curveName, encodingName
+}
+
+// newECDSAPrivateKey creates a ECDSAPrivateKey with the specified paramaters.
+func newECDSAPrivateKey(version uint32,
+	publicKey *ecdsapb.EcdsaPublicKey,
+	keyValue []byte) *ecdsapb.EcdsaPrivateKey {
+	return &ecdsapb.EcdsaPrivateKey{
+		Version:   version,
+		PublicKey: publicKey,
+		KeyValue:  keyValue,
+	}
+}
+
+// newECDSAPublicKey creates a ECDSAPublicKey with the specified paramaters.
+func newECDSAPublicKey(version uint32,
+	params *ecdsapb.EcdsaParams,
+	x []byte, y []byte) *ecdsapb.EcdsaPublicKey {
+	return &ecdsapb.EcdsaPublicKey{
+		Version: version,
+		Params:  params,
+		X:       x,
+		Y:       y,
+	}
+}
diff --git a/go/signature/proto_util.go b/go/signature/proto_util.go
deleted file mode 100644
index ac73aae..0000000
--- a/go/signature/proto_util.go
+++ /dev/null
@@ -1,75 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package signature
-
-import (
-	"github.com/google/tink/go/tink"
-	commonpb "github.com/google/tink/proto/common_go_proto"
-	ecdsapb "github.com/google/tink/proto/ecdsa_go_proto"
-)
-
-// NewEcdsaPrivateKey creates a EcdsaPrivateKey with the specified paramaters.
-func NewEcdsaPrivateKey(version uint32,
-	publicKey *ecdsapb.EcdsaPublicKey,
-	keyValue []byte) *ecdsapb.EcdsaPrivateKey {
-	return &ecdsapb.EcdsaPrivateKey{
-		Version:   version,
-		PublicKey: publicKey,
-		KeyValue:  keyValue,
-	}
-}
-
-// NewEcdsaPublicKey creates a EcdsaPublicKey with the specified paramaters.
-func NewEcdsaPublicKey(version uint32,
-	params *ecdsapb.EcdsaParams,
-	x []byte, y []byte) *ecdsapb.EcdsaPublicKey {
-	return &ecdsapb.EcdsaPublicKey{
-		Version: version,
-		Params:  params,
-		X:       x,
-		Y:       y,
-	}
-}
-
-// NewEcdsaParams creates a EcdsaParams with the specified parameters.
-func NewEcdsaParams(hashType commonpb.HashType,
-	curve commonpb.EllipticCurveType,
-	encoding ecdsapb.EcdsaSignatureEncoding) *ecdsapb.EcdsaParams {
-	return &ecdsapb.EcdsaParams{
-		HashType: hashType,
-		Curve:    curve,
-		Encoding: encoding,
-	}
-}
-
-// NewEcdsaKeyFormat creates a EcdsaKeyFormat with the specified parameters.
-func NewEcdsaKeyFormat(params *ecdsapb.EcdsaParams) *ecdsapb.EcdsaKeyFormat {
-	return &ecdsapb.EcdsaKeyFormat{Params: params}
-}
-
-// GetEcdsaSignatureEncodingName returns the name of the EcdsaSignatureEncoding.
-func GetEcdsaSignatureEncodingName(encoding ecdsapb.EcdsaSignatureEncoding) string {
-	ret := ecdsapb.EcdsaSignatureEncoding_name[int32(encoding)]
-	return ret
-}
-
-// GetEcdsaParamNames returns the string representations of each parameter in
-// the given EcdsaParams.
-func GetEcdsaParamNames(params *ecdsapb.EcdsaParams) (string, string, string) {
-	hashName := tink.GetHashName(params.HashType)
-	curveName := tink.GetCurveName(params.Curve)
-	encodingName := GetEcdsaSignatureEncodingName(params.Encoding)
-	return hashName, curveName, encodingName
-}
diff --git a/go/signature/public_key_sign_factory.go b/go/signature/public_key_sign_factory.go
deleted file mode 100644
index 98ebe74..0000000
--- a/go/signature/public_key_sign_factory.go
+++ /dev/null
@@ -1,77 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package signature
-
-import (
-	"fmt"
-
-	"github.com/google/tink/go/tink"
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
-)
-
-// GetPublicKeySignPrimitive returns a PublicKeySign primitive from the given keyset handle.
-func GetPublicKeySignPrimitive(handle *tink.KeysetHandle) (tink.PublicKeySign, error) {
-	return GetPublicKeySignPrimitiveWithCustomerManager(handle, nil /*keyManager*/)
-}
-
-// GetPublicKeySignPrimitiveWithCustomerManager returns a PublicKeySign primitive from the given
-// keyset handle and custom key manager.
-func GetPublicKeySignPrimitiveWithCustomerManager(
-	handle *tink.KeysetHandle, manager tink.KeyManager) (tink.PublicKeySign, error) {
-	ps, err := tink.GetPrimitivesWithCustomManager(handle, manager)
-	if err != nil {
-		return nil, fmt.Errorf("public_key_sign_factory: cannot obtain primitive set: %s", err)
-	}
-	var ret tink.PublicKeySign = newPrimitiveSetPublicKeySign(ps)
-	return ret, nil
-}
-
-// primitiveSetPublicKeySign is an PublicKeySign implementation that uses the
-// underlying primitive set for signing.
-type primitiveSetPublicKeySign struct {
-	ps *tink.PrimitiveSet
-}
-
-// Asserts that primitiveSetPublicKeySign implements the PublicKeySign interface.
-var _ tink.PublicKeySign = (*primitiveSetPublicKeySign)(nil)
-
-// newPrimitiveSetPublicKeySign creates a new instance of primitiveSetPublicKeySign
-func newPrimitiveSetPublicKeySign(ps *tink.PrimitiveSet) *primitiveSetPublicKeySign {
-	ret := new(primitiveSetPublicKeySign)
-	ret.ps = ps
-	return ret
-}
-
-// Sign signs the given data and returns the signature concatenated with
-// the identifier of the primary primitive.
-func (s *primitiveSetPublicKeySign) Sign(data []byte) ([]byte, error) {
-	primary := s.ps.Primary()
-	var signer tink.PublicKeySign = (primary.Primitive()).(tink.PublicKeySign)
-	var signedData []byte
-	if primary.OutputPrefixType() == tinkpb.OutputPrefixType_LEGACY {
-		signedData = append(signedData, data...)
-		signedData = append(signedData, tink.LegacyStartByte)
-	} else {
-		signedData = data
-	}
-	signature, err := signer.Sign(signedData)
-	if err != nil {
-		return nil, err
-	}
-	var ret []byte
-	ret = append(ret, primary.Identifier()...)
-	ret = append(ret, signature...)
-	return ret, nil
-}
diff --git a/go/signature/public_key_verify_factory.go b/go/signature/public_key_verify_factory.go
deleted file mode 100644
index ebf4d39..0000000
--- a/go/signature/public_key_verify_factory.go
+++ /dev/null
@@ -1,94 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package signature
-
-import (
-	"fmt"
-
-	"github.com/google/tink/go/tink"
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
-)
-
-// GetPublicKeyVerifyPrimitive returns a PublicKeyVerify primitive from the given keyset handle.
-func GetPublicKeyVerifyPrimitive(handle *tink.KeysetHandle) (tink.PublicKeyVerify, error) {
-	return GetPublicKeyVerifyPrimitiveWithCustomerManager(handle, nil /*keyManager*/)
-}
-
-// GetPublicKeyVerifyPrimitiveWithCustomerManager returns a PublicKeyVerify primitive from the given
-// keyset handle and custom key manager.
-func GetPublicKeyVerifyPrimitiveWithCustomerManager(
-	handle *tink.KeysetHandle, manager tink.KeyManager) (tink.PublicKeyVerify, error) {
-	ps, err := tink.GetPrimitivesWithCustomManager(handle, manager)
-	if err != nil {
-		return nil, fmt.Errorf("public_key_verify_factory: cannot obtain primitive set: %s", err)
-	}
-	var ret = newprimitiveSetPublicKeyVerify(ps)
-	return ret, nil
-}
-
-// primitiveSetPublicKeyVerify is an PublicKeySign implementation that uses the
-// underlying primitive set for signing.
-type primitiveSetPublicKeyVerify struct {
-	ps *tink.PrimitiveSet
-}
-
-// Asserts that primitiveSetPublicKeyVerify implements the PublicKeyVerify interface.
-var _ tink.PublicKeyVerify = (*primitiveSetPublicKeyVerify)(nil)
-
-// newprimitiveSetPublicKeyVerify creates a new instance of primitiveSetPublicKeyVerify
-func newprimitiveSetPublicKeyVerify(ps *tink.PrimitiveSet) *primitiveSetPublicKeyVerify {
-	ret := new(primitiveSetPublicKeyVerify)
-	ret.ps = ps
-	return ret
-}
-
-var errInvalidSignature = fmt.Errorf("public_key_verify_factory: invalid signature")
-
-// Verify checks whether the given signature is a valid signature of the given data.
-func (v *primitiveSetPublicKeyVerify) Verify(signature []byte, data []byte) error {
-	if len(signature) < tink.NonRawPrefixSize {
-		return errInvalidSignature
-	}
-	// try non-raw keys
-	prefix := signature[:tink.NonRawPrefixSize]
-	signatureNoPrefix := signature[tink.NonRawPrefixSize:]
-	entries, err := v.ps.GetPrimitivesWithByteIdentifier(prefix)
-	if err == nil {
-		for i := 0; i < len(entries); i++ {
-			var signedData []byte
-			if entries[i].OutputPrefixType() == tinkpb.OutputPrefixType_LEGACY {
-				signedData = append(signedData, data...)
-				signedData = append(signedData, tink.LegacyStartByte)
-			} else {
-				signedData = data
-			}
-			var verifier = (entries[i].Primitive()).(tink.PublicKeyVerify)
-			if err := verifier.Verify(signatureNoPrefix, signedData); err == nil {
-				return nil
-			}
-		}
-	}
-	// try raw keys
-	entries, err = v.ps.GetRawPrimitives()
-	if err == nil {
-		for i := 0; i < len(entries); i++ {
-			var verifier = (entries[i].Primitive()).(tink.PublicKeyVerify)
-			if err := verifier.Verify(signature, data); err == nil {
-				return nil
-			}
-		}
-	}
-	return errInvalidSignature
-}
diff --git a/go/signature/signature.go b/go/signature/signature.go
new file mode 100644
index 0000000..47059ac
--- /dev/null
+++ b/go/signature/signature.go
@@ -0,0 +1,40 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// Package signature provides implementations of the Signer and Verifier primitives.
+package signature
+
+import (
+	"fmt"
+
+	"github.com/google/tink/go/registry"
+)
+
+func init() {
+	// ECDSA
+	if err := registry.RegisterKeyManager(newECDSASignerKeyManager()); err != nil {
+		panic(fmt.Sprintf("signature.init() failed: %v", err))
+	}
+	if err := registry.RegisterKeyManager(newECDSAVerifierKeyManager()); err != nil {
+		panic(fmt.Sprintf("signature.init() failed: %v", err))
+	}
+
+	// ED25519
+	if err := registry.RegisterKeyManager(newED25519SignerKeyManager()); err != nil {
+		panic(fmt.Sprintf("signature.init() failed: %v", err))
+	}
+	if err := registry.RegisterKeyManager(newED25519VerifierKeyManager()); err != nil {
+		panic(fmt.Sprintf("signature.init() failed: %v", err))
+	}
+}
diff --git a/go/signature/signature_config.go b/go/signature/signature_config.go
deleted file mode 100644
index ac5d21f..0000000
--- a/go/signature/signature_config.go
+++ /dev/null
@@ -1,37 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-// Package signature provides implementations of the PublicKeySign and PublicKeyVerify primitives.
-package signature
-
-import (
-	"github.com/google/tink/go/tink"
-)
-
-// RegisterStandardKeyTypes registers standard Aead key types and their managers
-// with the Registry.
-func RegisterStandardKeyTypes() (bool, error) {
-	result, err := RegisterKeyManager(NewEcdsaSignKeyManager())
-	if err != nil {
-		return result, err
-	}
-	return RegisterKeyManager(NewEcdsaVerifyKeyManager())
-}
-
-// RegisterKeyManager registers the given keyManager for the key type given in
-// keyManager.KeyType(). It returns true if registration was successful, false if
-// there already exisits a key manager for the key type.
-func RegisterKeyManager(keyManager tink.KeyManager) (bool, error) {
-	return tink.RegisterKeyManager(keyManager)
-}
diff --git a/go/signature/signature_config_test.go b/go/signature/signature_config_test.go
deleted file mode 100644
index fb2c4bf..0000000
--- a/go/signature/signature_config_test.go
+++ /dev/null
@@ -1,42 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package signature_test
-
-import (
-	"testing"
-
-	"github.com/google/tink/go/signature"
-	"github.com/google/tink/go/tink"
-)
-
-func TestSignatureConfigRegistration(t *testing.T) {
-	_, err := signature.RegisterStandardKeyTypes()
-	if err != nil {
-		t.Errorf("cannot register standard key types")
-	}
-	// check for EcdsaSignKeyManager
-	keyManager, err := tink.GetKeyManager(signature.EcdsaSignTypeURL)
-	if err != nil {
-		t.Errorf("unexpected error: %s", err)
-	}
-	var _ = keyManager.(*signature.EcdsaSignKeyManager)
-
-	// check for EcdsaVerifyKeyManager
-	keyManager, err = tink.GetKeyManager(signature.EcdsaVerifyTypeURL)
-	if err != nil {
-		t.Errorf("unexpected error: %s", err)
-	}
-	var _ = keyManager.(*signature.EcdsaVerifyKeyManager)
-}
diff --git a/go/signature/signature_factory_test.go b/go/signature/signature_factory_test.go
index 6bb60aa..87d9f83 100644
--- a/go/signature/signature_factory_test.go
+++ b/go/signature/signature_factory_test.go
@@ -20,39 +20,38 @@
 	"github.com/golang/protobuf/proto"
 	"github.com/google/tink/go/signature"
 	"github.com/google/tink/go/subtle/random"
+	"github.com/google/tink/go/testkeyset"
 	"github.com/google/tink/go/testutil"
-	"github.com/google/tink/go/tink"
 	commonpb "github.com/google/tink/proto/common_go_proto"
 	tinkpb "github.com/google/tink/proto/tink_go_proto"
 )
 
-func TestPublicKeySignVerifyFactory(t *testing.T) {
-	signature.RegisterStandardKeyTypes()
-	tinkPriv, tinkPub := newEcdsaKeysetKeypair(commonpb.HashType_SHA512,
+func TestSignerVerifyFactory(t *testing.T) {
+	tinkPriv, tinkPub := newECDSAKeysetKeypair(commonpb.HashType_SHA512,
 		commonpb.EllipticCurveType_NIST_P521,
 		tinkpb.OutputPrefixType_TINK,
 		1)
-	legacyPriv, legacyPub := newEcdsaKeysetKeypair(commonpb.HashType_SHA256,
+	legacyPriv, legacyPub := newECDSAKeysetKeypair(commonpb.HashType_SHA256,
 		commonpb.EllipticCurveType_NIST_P256,
 		tinkpb.OutputPrefixType_LEGACY,
 		2)
-	rawPriv, rawPub := newEcdsaKeysetKeypair(commonpb.HashType_SHA512,
+	rawPriv, rawPub := newECDSAKeysetKeypair(commonpb.HashType_SHA512,
 		commonpb.EllipticCurveType_NIST_P384,
 		tinkpb.OutputPrefixType_RAW,
 		3)
-	crunchyPriv, crunchyPub := newEcdsaKeysetKeypair(commonpb.HashType_SHA512,
+	crunchyPriv, crunchyPub := newECDSAKeysetKeypair(commonpb.HashType_SHA512,
 		commonpb.EllipticCurveType_NIST_P384,
 		tinkpb.OutputPrefixType_CRUNCHY,
 		4)
 	privKeys := []*tinkpb.Keyset_Key{tinkPriv, legacyPriv, rawPriv, crunchyPriv}
-	privKeyset := tink.CreateKeyset(privKeys[0].KeyId, privKeys)
-	privKeysetHandle, _ := tink.CleartextKeysetHandle().ParseKeyset(privKeyset)
+	privKeyset := testutil.NewKeyset(privKeys[0].KeyId, privKeys)
+	privKeysetHandle, _ := testkeyset.NewHandle(privKeyset)
 	pubKeys := []*tinkpb.Keyset_Key{tinkPub, legacyPub, rawPub, crunchyPub}
-	pubKeyset := tink.CreateKeyset(pubKeys[0].KeyId, pubKeys)
-	pubKeysetHandle, _ := tink.CleartextKeysetHandle().ParseKeyset(pubKeyset)
+	pubKeyset := testutil.NewKeyset(pubKeys[0].KeyId, pubKeys)
+	pubKeysetHandle, _ := testkeyset.NewHandle(pubKeyset)
 
 	// sign some random data
-	signer, err := signature.GetPublicKeySignPrimitive(privKeysetHandle)
+	signer, err := signature.NewSigner(privKeysetHandle)
 	if err != nil {
 		t.Errorf("getting sign primitive failed: %s", err)
 	}
@@ -62,7 +61,7 @@
 		t.Errorf("signing failed: %s", err)
 	}
 	// verify with the same set of public keys should work
-	verifier, err := signature.GetPublicKeyVerifyPrimitive(pubKeysetHandle)
+	verifier, err := signature.NewVerifier(pubKeysetHandle)
 	if err != nil {
 		t.Errorf("getting verify primitive failed: %s", err)
 	}
@@ -70,37 +69,37 @@
 		t.Errorf("verification failed: %s", err)
 	}
 	// verify with random key should fail
-	_, randomPub := newEcdsaKeysetKeypair(commonpb.HashType_SHA512,
+	_, randomPub := newECDSAKeysetKeypair(commonpb.HashType_SHA512,
 		commonpb.EllipticCurveType_NIST_P521,
 		tinkpb.OutputPrefixType_TINK,
 		1)
 	pubKeys = []*tinkpb.Keyset_Key{randomPub}
-	pubKeyset = tink.CreateKeyset(pubKeys[0].KeyId, pubKeys)
-	pubKeysetHandle, _ = tink.CleartextKeysetHandle().ParseKeyset(pubKeyset)
-	verifier, err = signature.GetPublicKeyVerifyPrimitive(pubKeysetHandle)
+	pubKeyset = testutil.NewKeyset(pubKeys[0].KeyId, pubKeys)
+	pubKeysetHandle, _ = testkeyset.NewHandle(pubKeyset)
+	verifier, err = signature.NewVerifier(pubKeysetHandle)
 	if err != nil {
 		t.Errorf("getting verify primitive failed: %s", err)
 	}
-	if err := verifier.Verify(sig, data); err == nil {
+	if err = verifier.Verify(sig, data); err == nil {
 		t.Errorf("verification with random key should fail: %s", err)
 	}
 }
 
-func newEcdsaKeysetKeypair(hashType commonpb.HashType,
+func newECDSAKeysetKeypair(hashType commonpb.HashType,
 	curve commonpb.EllipticCurveType,
 	outputPrefixType tinkpb.OutputPrefixType,
 	keyID uint32) (*tinkpb.Keyset_Key, *tinkpb.Keyset_Key) {
-	key := testutil.NewEcdsaPrivateKey(hashType, curve)
+	key := testutil.NewRandomECDSAPrivateKey(hashType, curve)
 	serializedKey, _ := proto.Marshal(key)
-	keyData := tink.CreateKeyData(signature.EcdsaSignTypeURL,
+	keyData := testutil.NewKeyData(testutil.ECDSASignerTypeURL,
 		serializedKey,
 		tinkpb.KeyData_ASYMMETRIC_PRIVATE)
-	privKey := tink.CreateKey(keyData, tinkpb.KeyStatusType_ENABLED, keyID, outputPrefixType)
+	privKey := testutil.NewKey(keyData, tinkpb.KeyStatusType_ENABLED, keyID, outputPrefixType)
 
 	serializedKey, _ = proto.Marshal(key.PublicKey)
-	keyData = tink.CreateKeyData(signature.EcdsaVerifyTypeURL,
+	keyData = testutil.NewKeyData(testutil.ECDSAVerifierTypeURL,
 		serializedKey,
 		tinkpb.KeyData_ASYMMETRIC_PUBLIC)
-	pubKey := tink.CreateKey(keyData, tinkpb.KeyStatusType_ENABLED, keyID, outputPrefixType)
+	pubKey := testutil.NewKey(keyData, tinkpb.KeyStatusType_ENABLED, keyID, outputPrefixType)
 	return privKey, pubKey
 }
diff --git a/go/signature/signature_key_templates.go b/go/signature/signature_key_templates.go
index 4bdc5ab..ae058b2 100644
--- a/go/signature/signature_key_templates.go
+++ b/go/signature/signature_key_templates.go
@@ -21,50 +21,58 @@
 	tinkpb "github.com/google/tink/proto/tink_go_proto"
 )
 
-// This file contains pre-generated KeyTemplate for PublicKeySign and PublicKeyVerify.
-// One can use these templates to generate new Keyset, using utility functions
-// in either cleartext_keyset_handle or encrypted_keyset_handle.
+// This file contains pre-generated KeyTemplates for Signer and Verifier.
+// One can use these templates to generate new Keysets.
 
-// EcdsaP256KeyTemplate is a KeyTemplate of EcdsaPrivateKey with the following parameters:
+// ECDSAP256KeyTemplate is a KeyTemplate that generates a new ECDSA private key with the following parameters:
 //   - Hash function: SHA256
 //   - Curve: NIST P-256
 //   - Signature encoding: DER
-func EcdsaP256KeyTemplate() *tinkpb.KeyTemplate {
-	return createEcdsaKeyTemplate(commonpb.HashType_SHA256,
+func ECDSAP256KeyTemplate() *tinkpb.KeyTemplate {
+	return createECDSAKeyTemplate(commonpb.HashType_SHA256,
 		commonpb.EllipticCurveType_NIST_P256,
 		ecdsapb.EcdsaSignatureEncoding_DER)
 }
 
-// EcdsaP384KeyTemplate is a KeyTemplate of EcdsaPrivateKey with the following parameters:
+// ECDSAP384KeyTemplate is a KeyTemplate that generates a new ECDSA private key with the following parameters:
 //   - Hash function: SHA512
 //   - Curve: NIST P-384
 //   - Signature encoding: DER
-func EcdsaP384KeyTemplate() *tinkpb.KeyTemplate {
-	return createEcdsaKeyTemplate(commonpb.HashType_SHA512,
+func ECDSAP384KeyTemplate() *tinkpb.KeyTemplate {
+	return createECDSAKeyTemplate(commonpb.HashType_SHA512,
 		commonpb.EllipticCurveType_NIST_P384,
 		ecdsapb.EcdsaSignatureEncoding_DER)
 }
 
-// EcdsaP521KeyTemplate is a KeyTemplate of EcdsaPrivateKey with the following parameters:
+// ECDSAP521KeyTemplate is a KeyTemplate that generates a new ECDSA private key with the following parameters:
 //   - Hash function: SHA512
 //   - Curve: NIST P-521
 //   - Signature encoding: DER
-func EcdsaP521KeyTemplate() *tinkpb.KeyTemplate {
-	return createEcdsaKeyTemplate(commonpb.HashType_SHA512,
+func ECDSAP521KeyTemplate() *tinkpb.KeyTemplate {
+	return createECDSAKeyTemplate(commonpb.HashType_SHA512,
 		commonpb.EllipticCurveType_NIST_P521,
 		ecdsapb.EcdsaSignatureEncoding_DER)
 }
 
-// createEcdsaKeyTemplate creates a KeyTemplate containing a EcdasKeyFormat
+// createECDSAKeyTemplate creates a KeyTemplate containing a EcdasKeyFormat
 // with the given parameters.
-func createEcdsaKeyTemplate(hashType commonpb.HashType,
-	curve commonpb.EllipticCurveType,
-	encoding ecdsapb.EcdsaSignatureEncoding) *tinkpb.KeyTemplate {
-	params := NewEcdsaParams(hashType, curve, encoding)
-	format := NewEcdsaKeyFormat(params)
+func createECDSAKeyTemplate(hashType commonpb.HashType, curve commonpb.EllipticCurveType, encoding ecdsapb.EcdsaSignatureEncoding) *tinkpb.KeyTemplate {
+	params := &ecdsapb.EcdsaParams{
+		HashType: hashType,
+		Curve:    curve,
+		Encoding: encoding,
+	}
+	format := &ecdsapb.EcdsaKeyFormat{Params: params}
 	serializedFormat, _ := proto.Marshal(format)
 	return &tinkpb.KeyTemplate{
-		TypeUrl: EcdsaSignTypeURL,
+		TypeUrl: ecdsaSignerTypeURL,
 		Value:   serializedFormat,
 	}
 }
+
+// ED25519KeyTemplate is a KeyTemplate that generates a new ED25519 private key.
+func ED25519KeyTemplate() *tinkpb.KeyTemplate {
+	return &tinkpb.KeyTemplate{
+		TypeUrl: ed25519SignerTypeURL,
+	}
+}
diff --git a/go/signature/signature_key_templates_test.go b/go/signature/signature_key_templates_test.go
index 4976a9e..01834d0 100644
--- a/go/signature/signature_key_templates_test.go
+++ b/go/signature/signature_key_templates_test.go
@@ -20,17 +20,18 @@
 
 	"github.com/golang/protobuf/proto"
 	"github.com/google/tink/go/signature"
+	"github.com/google/tink/go/testutil"
 	commonpb "github.com/google/tink/proto/common_go_proto"
 	ecdsapb "github.com/google/tink/proto/ecdsa_go_proto"
 	tinkpb "github.com/google/tink/proto/tink_go_proto"
 )
 
-func TestEcdsaKeyTemplates(t *testing.T) {
+func TestECDSAKeyTemplates(t *testing.T) {
 	var template *tinkpb.KeyTemplate
 	var err error
 	// ECDSA P-256
-	template = signature.EcdsaP256KeyTemplate()
-	err = checkEcdsaKeyTemplate(template,
+	template = signature.ECDSAP256KeyTemplate()
+	err = checkECDSAKeyTemplate(template,
 		commonpb.HashType_SHA256,
 		commonpb.EllipticCurveType_NIST_P256,
 		ecdsapb.EcdsaSignatureEncoding_DER)
@@ -38,8 +39,8 @@
 		t.Errorf("invalid ECDSA P-256 key template: %s", err)
 	}
 	// ECDSA P-384
-	template = signature.EcdsaP384KeyTemplate()
-	err = checkEcdsaKeyTemplate(template,
+	template = signature.ECDSAP384KeyTemplate()
+	err = checkECDSAKeyTemplate(template,
 		commonpb.HashType_SHA512,
 		commonpb.EllipticCurveType_NIST_P384,
 		ecdsapb.EcdsaSignatureEncoding_DER)
@@ -47,8 +48,8 @@
 		t.Errorf("invalid ECDSA P-384 key template: %s", err)
 	}
 	// ECDSA P-521
-	template = signature.EcdsaP521KeyTemplate()
-	err = checkEcdsaKeyTemplate(template,
+	template = signature.ECDSAP521KeyTemplate()
+	err = checkECDSAKeyTemplate(template,
 		commonpb.HashType_SHA512,
 		commonpb.EllipticCurveType_NIST_P521,
 		ecdsapb.EcdsaSignatureEncoding_DER)
@@ -57,12 +58,12 @@
 	}
 }
 
-func checkEcdsaKeyTemplate(template *tinkpb.KeyTemplate,
+func checkECDSAKeyTemplate(template *tinkpb.KeyTemplate,
 	hashType commonpb.HashType,
 	curve commonpb.EllipticCurveType,
 	encoding ecdsapb.EcdsaSignatureEncoding) error {
-	if template.TypeUrl != signature.EcdsaSignTypeURL {
-		return fmt.Errorf("incorrect typeurl: expect %s, got %s", signature.EcdsaSignTypeURL, template.TypeUrl)
+	if template.TypeUrl != testutil.ECDSASignerTypeURL {
+		return fmt.Errorf("incorrect typeurl: expect %s, got %s", testutil.ECDSASignerTypeURL, template.TypeUrl)
 	}
 	format := new(ecdsapb.EcdsaKeyFormat)
 	if err := proto.Unmarshal(template.Value, format); err != nil {
diff --git a/go/mac/mac_config_test.go b/go/signature/signature_test.go
similarity index 64%
copy from go/mac/mac_config_test.go
copy to go/signature/signature_test.go
index 4ea17ea..ddf1d60 100644
--- a/go/mac/mac_config_test.go
+++ b/go/signature/signature_test.go
@@ -12,23 +12,25 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-package mac_test
+package signature_test
 
 import (
 	"testing"
 
-	"github.com/google/tink/go/mac"
-	"github.com/google/tink/go/tink"
+	"github.com/google/tink/go/registry"
+	"github.com/google/tink/go/testutil"
 )
 
-func TestRegistration(t *testing.T) {
-	success, err := mac.RegisterStandardKeyTypes()
-	if !success || err != nil {
-		t.Errorf("cannot register standard key types")
-	}
-	keyManager, err := tink.GetKeyManager(mac.HmacTypeURL)
+func TestSignatureInit(t *testing.T) {
+	// check for ECDSASignerKeyManager
+	_, err := registry.GetKeyManager(testutil.ECDSASignerTypeURL)
 	if err != nil {
 		t.Errorf("unexpected error: %s", err)
 	}
-	var _ = keyManager.(*mac.HmacKeyManager)
+
+	// check for ECDSAVerifierKeyManager
+	_, err = registry.GetKeyManager(testutil.ECDSAVerifierTypeURL)
+	if err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
 }
diff --git a/go/signature/signer_factory.go b/go/signature/signer_factory.go
new file mode 100644
index 0000000..5d4cb53
--- /dev/null
+++ b/go/signature/signer_factory.go
@@ -0,0 +1,76 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package signature
+
+import (
+	"fmt"
+
+	"github.com/google/tink/go/format"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/primitiveset"
+	"github.com/google/tink/go/registry"
+	"github.com/google/tink/go/tink"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+// NewSigner returns a Signer primitive from the given keyset handle.
+func NewSigner(h *keyset.Handle) (tink.Signer, error) {
+	return NewSignerWithKeyManager(h, nil /*keyManager*/)
+}
+
+// NewSignerWithKeyManager returns a Signer primitive from the given keyset handle and custom key manager.
+func NewSignerWithKeyManager(h *keyset.Handle, km registry.KeyManager) (tink.Signer, error) {
+	ps, err := h.PrimitivesWithKeyManager(km)
+	if err != nil {
+		return nil, fmt.Errorf("public_key_sign_factory: cannot obtain primitive set: %s", err)
+	}
+	return newSignerSet(ps), nil
+}
+
+// signerSet is an Signer implementation that uses the underlying primitive set for signing.
+type signerSet struct {
+	ps *primitiveset.PrimitiveSet
+}
+
+// Asserts that signerSet implements the Signer interface.
+var _ tink.Signer = (*signerSet)(nil)
+
+func newSignerSet(ps *primitiveset.PrimitiveSet) *signerSet {
+	ret := new(signerSet)
+	ret.ps = ps
+	return ret
+}
+
+// Sign signs the given data and returns the signature concatenated with the identifier of the
+// primary primitive.
+func (s *signerSet) Sign(data []byte) ([]byte, error) {
+	primary := s.ps.Primary
+	var signer = (primary.Primitive).(tink.Signer)
+	var signedData []byte
+	if primary.PrefixType == tinkpb.OutputPrefixType_LEGACY {
+		signedData = append(signedData, data...)
+		signedData = append(signedData, format.LegacyStartByte)
+	} else {
+		signedData = data
+	}
+	signature, err := signer.Sign(signedData)
+	if err != nil {
+		return nil, err
+	}
+	var ret []byte
+	ret = append(ret, primary.Prefix...)
+	ret = append(ret, signature...)
+	return ret, nil
+}
diff --git a/go/signature/verifier_factory.go b/go/signature/verifier_factory.go
new file mode 100644
index 0000000..8375479
--- /dev/null
+++ b/go/signature/verifier_factory.go
@@ -0,0 +1,97 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package signature
+
+import (
+	"errors"
+	"fmt"
+
+	"github.com/google/tink/go/format"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/primitiveset"
+	"github.com/google/tink/go/registry"
+	"github.com/google/tink/go/tink"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+// NewVerifier returns a Verifier primitive from the given keyset handle.
+func NewVerifier(h *keyset.Handle) (tink.Verifier, error) {
+	return NewVerifierWithKeyManager(h, nil /*keyManager*/)
+}
+
+// NewVerifierWithKeyManager returns a Verifier primitive from the given keyset handle and custom key manager.
+func NewVerifierWithKeyManager(h *keyset.Handle, km registry.KeyManager) (tink.Verifier, error) {
+	ps, err := h.PrimitivesWithKeyManager(km)
+	if err != nil {
+		return nil, fmt.Errorf("verifier_factory: cannot obtain primitive set: %s", err)
+	}
+	var ret = newVerifierSet(ps)
+	return ret, nil
+}
+
+// verifierSet is an Signer implementation that uses the
+// underlying primitive set for signing.
+type verifierSet struct {
+	ps *primitiveset.PrimitiveSet
+}
+
+// Asserts that verifierSet implements the Verifier interface.
+var _ tink.Verifier = (*verifierSet)(nil)
+
+func newVerifierSet(ps *primitiveset.PrimitiveSet) *verifierSet {
+	ret := new(verifierSet)
+	ret.ps = ps
+	return ret
+}
+
+var errInvalidSignature = errors.New("verifier_factory: invalid signature")
+
+// Verify checks whether the given signature is a valid signature of the given data.
+func (v *verifierSet) Verify(signature, data []byte) error {
+	prefixSize := format.NonRawPrefixSize
+	if len(signature) < prefixSize {
+		return errInvalidSignature
+	}
+	// try non-raw keys
+	prefix := signature[:prefixSize]
+	signatureNoPrefix := signature[prefixSize:]
+	entries, err := v.ps.EntriesForPrefix(string(prefix))
+	if err == nil {
+		for i := 0; i < len(entries); i++ {
+			var signedData []byte
+			if entries[i].PrefixType == tinkpb.OutputPrefixType_LEGACY {
+				signedData = append(signedData, data...)
+				signedData = append(signedData, format.LegacyStartByte)
+			} else {
+				signedData = data
+			}
+			var verifier = (entries[i].Primitive).(tink.Verifier)
+			if err = verifier.Verify(signatureNoPrefix, signedData); err == nil {
+				return nil
+			}
+		}
+	}
+	// try raw keys
+	entries, err = v.ps.RawEntries()
+	if err == nil {
+		for i := 0; i < len(entries); i++ {
+			var verifier = (entries[i].Primitive).(tink.Verifier)
+			if err = verifier.Verify(signature, data); err == nil {
+				return nil
+			}
+		}
+	}
+	return errInvalidSignature
+}
diff --git a/go/subtle/BUILD.bazel b/go/subtle/BUILD.bazel
index 94a172b..1043084 100644
--- a/go/subtle/BUILD.bazel
+++ b/go/subtle/BUILD.bazel
@@ -2,13 +2,13 @@
 
 go_library(
     name = "go_default_library",
-    srcs = ["util.go"],
+    srcs = ["subtle.go"],
     importpath = "github.com/google/tink/go/subtle",
     visibility = ["//visibility:public"],
 )
 
 go_test(
-    name = "go_default_xtest",
-    srcs = ["util_test.go"],
+    name = "go_default_test",
+    srcs = ["subtle_test.go"],
     deps = [":go_default_library"],
 )
diff --git a/go/subtle/aead/BUILD.bazel b/go/subtle/aead/BUILD.bazel
index dd1139c..37ad08f 100644
--- a/go/subtle/aead/BUILD.bazel
+++ b/go/subtle/aead/BUILD.bazel
@@ -2,23 +2,44 @@
 
 go_library(
     name = "go_default_library",
-    srcs = ["aes_gcm.go"],
+    srcs = [
+        "aead.go",
+        "aes_gcm.go",
+        "aes_ctr.go",
+        "encrypt_then_authenticate.go",
+        "ind_cpa.go",
+        "chacha20poly1305.go",
+        "xchacha20poly1305.go",
+    ],
     importpath = "github.com/google/tink/go/subtle/aead",
     visibility = ["//visibility:public"],
     deps = [
         "//go/subtle/random:go_default_library",
         "//go/tink:go_default_library",
+        "@org_golang_x_crypto//chacha20poly1305:go_default_library",
     ],
 )
 
 go_test(
-    name = "go_default_xtest",
-    srcs = ["aes_gcm_test.go"],
+    name = "go_default_test",
+    srcs = [
+        "aead_test.go",
+        "aes_gcm_test.go",
+        "aes_ctr_test.go",
+        "encrypt_then_authenticate_test.go",
+        "chacha20poly1305_test.go",
+        "chacha20poly1305_vectors_test.go",
+        "xchacha20poly1305_test.go",
+        "xchacha20poly1305_vectors_test.go",
+    ],
     data = [
         "@wycheproof//testvectors:all",  # keep
     ],
     deps = [
         ":go_default_library",
+        "//go/subtle/mac:go_default_library",
         "//go/subtle/random:go_default_library",
+        "//go/tink:go_default_library",
+        "@org_golang_x_crypto//chacha20poly1305:go_default_library",
     ],
 )
diff --git a/go/tink/private_key_manager.go b/go/subtle/aead/aead.go
similarity index 63%
copy from go/tink/private_key_manager.go
copy to go/subtle/aead/aead.go
index 7cf169a..26d83e6 100644
--- a/go/tink/private_key_manager.go
+++ b/go/subtle/aead/aead.go
@@ -12,16 +12,17 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-package tink
+// Package aead provides subtle implementations of the AEAD primitive.
+package aead
 
-import (
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
-)
+import "fmt"
 
-// PrivateKeyManager is a special type of KeyManager that understands private key types.
-type PrivateKeyManager interface {
-	KeyManager
-
-	// GetPublicKeyData extracts the public key data from the private key.
-	GetPublicKeyData(serializedKey []byte) (*tinkpb.KeyData, error)
+// ValidateAESKeySize checks if the given key size is a valid AES key size.
+func ValidateAESKeySize(sizeInBytes uint32) error {
+	switch sizeInBytes {
+	case 16, 32:
+		return nil
+	default:
+		return fmt.Errorf("invalid AES key size; want 16 or 32, got %d", sizeInBytes)
+	}
 }
diff --git a/go/subtle/aead/aead_test.go b/go/subtle/aead/aead_test.go
new file mode 100644
index 0000000..f064902
--- /dev/null
+++ b/go/subtle/aead/aead_test.go
@@ -0,0 +1,47 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package aead_test
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/google/tink/go/subtle/aead"
+)
+
+func TestValidateAESKeySize(t *testing.T) {
+	var i uint32
+	for i = 0; i < 65; i++ {
+		err := aead.ValidateAESKeySize(i)
+		switch i {
+		case 16:
+			fallthrough
+		case 32:
+			// Valid key sizes.
+			if err != nil {
+				t.Errorf("want no error, got %v", err)
+			}
+
+		default:
+			// Invalid key sizes.
+			if err == nil {
+				t.Errorf("invalid key size (%d) should not be accepted", i)
+			}
+			if !strings.Contains(err.Error(), "invalid AES key size; want 16 or 32") {
+				t.Errorf("wrong error message; want a string starting with \"invalid AES key size; want 16 or 32\", got %v", err)
+			}
+		}
+	}
+}
diff --git a/go/subtle/aead/aes_ctr.go b/go/subtle/aead/aes_ctr.go
new file mode 100644
index 0000000..4959b30
--- /dev/null
+++ b/go/subtle/aead/aes_ctr.go
@@ -0,0 +1,110 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package aead
+
+import (
+	"crypto/aes"
+	"crypto/cipher"
+	"fmt"
+
+	"github.com/google/tink/go/subtle/random"
+)
+
+const (
+	// AESCTRMinIVSize is the minimum IV size that this implementation supports.
+	AESCTRMinIVSize = 12
+)
+
+// AESCTR is an implementation of AEAD interface.
+type AESCTR struct {
+	Key    []byte
+	IVSize int
+}
+
+// NewAESCTR returns an AESCTR instance.
+// The key argument should be the AES key, either 16 or 32 bytes to select
+// AES-128 or AES-256.
+// ivSize specifies the size of the IV in bytes.
+func NewAESCTR(key []byte, ivSize int) (*AESCTR, error) {
+	keySize := uint32(len(key))
+	if err := ValidateAESKeySize(keySize); err != nil {
+		return nil, fmt.Errorf("aes_ctr: %s", err)
+	}
+	if ivSize < AESCTRMinIVSize || ivSize > aes.BlockSize {
+		return nil, fmt.Errorf("aes_ctr: invalid IV size: %d", ivSize)
+	}
+	return &AESCTR{Key: key, IVSize: ivSize}, nil
+}
+
+// Encrypt encrypts plaintext using AES in CTR mode.
+// The resulting ciphertext consists of two parts:
+// (1) the IV used for encryption and (2) the actual ciphertext.
+func (a *AESCTR) Encrypt(plaintext []byte) ([]byte, error) {
+	iv := a.newIV()
+	stream, err := newCipher(a.Key, iv)
+	if err != nil {
+		return nil, err
+	}
+
+	ciphertext := make([]byte, a.IVSize+len(plaintext))
+	if n := copy(ciphertext, iv); n != a.IVSize {
+		return nil, fmt.Errorf("aes_ctr: failed to copy IV (copied %d/%d bytes)", n, a.IVSize)
+	}
+
+	stream.XORKeyStream(ciphertext[a.IVSize:], plaintext)
+	return ciphertext, nil
+}
+
+// Decrypt decrypts ciphertext.
+func (a *AESCTR) Decrypt(ciphertext []byte) ([]byte, error) {
+	if len(ciphertext) < a.IVSize {
+		return nil, fmt.Errorf("aes_ctr: ciphertext too short")
+	}
+
+	iv := ciphertext[:a.IVSize]
+	stream, err := newCipher(a.Key, iv)
+	if err != nil {
+		return nil, err
+	}
+
+	plaintext := make([]byte, len(ciphertext)-a.IVSize)
+	stream.XORKeyStream(plaintext, ciphertext[a.IVSize:])
+	return plaintext, nil
+}
+
+// newIV creates a new IV for encryption.
+func (a *AESCTR) newIV() []byte {
+	return random.GetRandomBytes(uint32(a.IVSize))
+}
+
+// newCipher creates a new AES-CTR cipher using the given key, IV and the crypto library.
+func newCipher(key, iv []byte) (cipher.Stream, error) {
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		return nil, fmt.Errorf("aes_ctr: failed to create block cipher, error: %v", err)
+	}
+
+	// If the IV is less than BlockSize bytes we need to pad it with zeros
+	// otherwise NewCTR will panic.
+	if len(iv) < aes.BlockSize {
+		paddedIV := make([]byte, aes.BlockSize)
+		if n := copy(paddedIV, iv); n != len(iv) {
+			return nil, fmt.Errorf("aes_ctr: failed to pad IV")
+		}
+		return cipher.NewCTR(block, paddedIV), nil
+	}
+
+	return cipher.NewCTR(block, iv), nil
+}
diff --git a/go/subtle/aead/aes_ctr_test.go b/go/subtle/aead/aes_ctr_test.go
new file mode 100644
index 0000000..cd4624a
--- /dev/null
+++ b/go/subtle/aead/aes_ctr_test.go
@@ -0,0 +1,247 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package aead_test
+
+import (
+	"bytes"
+	"crypto/aes"
+	"encoding/hex"
+	"strings"
+	"testing"
+
+	"github.com/google/tink/go/subtle/aead"
+	"github.com/google/tink/go/subtle/random"
+)
+
+func TestNewAESCTR(t *testing.T) {
+	key := make([]byte, 64)
+
+	// Test various key sizes with a fixed IV size.
+	for i := 0; i < 64; i++ {
+		k := key[:i]
+		c, err := aead.NewAESCTR(k, aead.AESCTRMinIVSize)
+
+		switch len(k) {
+		case 16:
+			fallthrough
+		case 32:
+			// Valid key sizes.
+			if err != nil {
+				t.Errorf("want: valid cipher (key size=%d), got: error %v", len(k), err)
+			}
+			// Verify that the struct contents are correctly set.
+			if len(c.Key) != len(k) {
+				t.Errorf("want: key size=%d, got: key size=%d", len(k), len(c.Key))
+			}
+			if c.IVSize != aead.AESCTRMinIVSize {
+				t.Errorf("want: IV size=%d, got: IV size=%d", aead.AESCTRMinIVSize, c.IVSize)
+			}
+		default:
+			// Invalid key sizes.
+			if !strings.Contains(err.Error(), "aes_ctr: invalid AES key size; want 16 or 32") {
+				t.Errorf("wrong error message; want a string starting with \"aes_ctr: invalid AES key size; want 16 or 32\", got %v", err)
+			}
+		}
+	}
+
+	// Test different IV sizes with a fixed key.
+	for i := 0; i < 64; i++ {
+		k := key[:16]
+		c, err := aead.NewAESCTR(k, i)
+		if i >= aead.AESCTRMinIVSize && i <= aes.BlockSize {
+			if err != nil {
+				t.Errorf("want: valid cipher (IV size=%d), got: error %v", i, err)
+			}
+			if len(c.Key) != len(k) {
+				t.Errorf("want: key size=%d, got: key size=%d", len(k), len(c.Key))
+			}
+			if c.IVSize != i {
+				t.Errorf("want: IV size=%d, got: IV size=%d", i, c.IVSize)
+			}
+			continue
+		}
+		if !strings.Contains(err.Error(), "aes_ctr: invalid IV size:") {
+			t.Errorf("want: error invalid IV size, got: %v", err)
+		}
+	}
+}
+
+func TestNistTestVector(t *testing.T) {
+	// NIST SP 800-38A pp 55
+	key, err := hex.DecodeString("2b7e151628aed2a6abf7158809cf4f3c")
+	if err != nil {
+		t.Fatalf("failed to hex decode key, error: %v", err)
+	}
+
+	// NIST IV
+	iv := "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
+	// NIST ciphertext blocks
+	c := "874d6191b620e3261bef6864990db6ce" +
+		"9806f66b7970fdff8617187bb9fffdff" +
+		"5ae4df3edbd5d35e5b4f09020db03eab" +
+		"1e031dda2fbe03d1792170a0f3009cee"
+	ciphertext, err := hex.DecodeString(iv + c)
+	if err != nil {
+		t.Fatalf("failed to hex decode ciphertext, error: %v", err)
+	}
+
+	// NIST plaintext blocks
+	p := "6bc1bee22e409f96e93d7e117393172a" +
+		"ae2d8a571e03ac9c9eb76fac45af8e51" +
+		"30c81c46a35ce411e5fbc1191a0a52ef" +
+		"f69f2445df4f9b17ad2b417be66c3710"
+	message, err := hex.DecodeString(p)
+	if err != nil {
+		t.Fatalf("failed to hex decode message, error: %v", err)
+	}
+
+	stream, err := aead.NewAESCTR(key, len(iv)/2)
+	if err != nil {
+		t.Fatalf("failed to create AESCTR instance, error: %v", err)
+	}
+
+	plaintext, err := stream.Decrypt(ciphertext)
+	if err != nil {
+		t.Errorf("failed to decrypt ciphertext, error: %v", err)
+	}
+
+	if bytes.Compare(plaintext, message) != 0 {
+		t.Errorf("plaintext doesn't match message")
+	}
+}
+
+func TestMultipleEncrypt(t *testing.T) {
+	key := random.GetRandomBytes(16)
+
+	stream, err := aead.NewAESCTR(key, aead.AESCTRMinIVSize)
+	if err != nil {
+		t.Fatalf("failed to create AESCTR instance, error: %v", err)
+	}
+
+	plaintext := []byte("Some data to encrypt.")
+	ct1, err := stream.Encrypt(plaintext)
+	if err != nil {
+		t.Errorf("encryption failed, error: %v", err)
+	}
+	ct2, err := stream.Encrypt(plaintext)
+	if err != nil {
+		t.Errorf("encryption failed, error: %v", err)
+	}
+	if bytes.Compare(ct1, ct2) == 0 {
+		t.Error("the two ciphertexts cannot be equal")
+	}
+	// Encrypt 100 times and verify that the result is 100 different ciphertexts.
+	ciphertexts := map[string]bool{}
+	for i := 0; i < 100; i++ {
+		c, err := stream.Encrypt(plaintext)
+		if err != nil {
+			t.Errorf("encryption failed for iteration %d, error: %v", i, err)
+		}
+		ciphertexts[string(c)] = true
+	}
+
+	if len(ciphertexts) != 100 {
+		t.Errorf("got: %d ciphertexts, want: 100 ciphertexts", len(ciphertexts))
+	}
+}
+
+func TestEncryptDecrypt(t *testing.T) {
+	key, err := hex.DecodeString("000102030405060708090a0b0c0d0e0f")
+	if err != nil {
+		t.Fatal("failed to hex decode key")
+	}
+
+	stream, err := aead.NewAESCTR(key, aead.AESCTRMinIVSize)
+	if err != nil {
+		t.Fatalf("failed to get AESCTR instance, error: %v", err)
+	}
+
+	message := []byte("Some data to encrypt.")
+	ciphertext, err := stream.Encrypt(message)
+	if err != nil {
+		t.Errorf("encryption failed, error: %v", err)
+	}
+
+	if len(ciphertext) != len(message)+aead.AESCTRMinIVSize {
+		t.Errorf("ciphertext incorrect size, got: %d, want: %d", len(ciphertext), len(message)+aead.AESCTRMinIVSize)
+	}
+
+	plaintext, err := stream.Decrypt(ciphertext)
+	if err != nil {
+		t.Errorf("decryption failed, error: %v", err)
+	}
+
+	if bytes.Compare(message, plaintext) != 0 {
+		t.Errorf("decryption result mismatch, got: %v, want: %v", plaintext, message)
+	}
+}
+
+func TestEncryptRandomMessage(t *testing.T) {
+	key := random.GetRandomBytes(16)
+
+	stream, err := aead.NewAESCTR(key, aead.AESCTRMinIVSize)
+	if err != nil {
+		t.Errorf("failed to instantiate AESCTR, error: %v", err)
+	}
+
+	for i := 0; i < 256; i++ {
+		message := random.GetRandomBytes(uint32(i))
+		ciphertext, err := stream.Encrypt(message)
+		if err != nil {
+			t.Errorf("encryption failed at iteration %d, error: %v", i, err)
+		}
+		if len(ciphertext) != len(message)+aead.AESCTRMinIVSize {
+			t.Errorf("invalid ciphertext length for i = %d", i)
+		}
+
+		plaintext, err := stream.Decrypt(ciphertext)
+		if err != nil {
+			t.Errorf("decryption failed at iteration %d, error: %v", i, err)
+		}
+
+		if bytes.Compare(plaintext, message) != 0 {
+			t.Errorf("plaintext doesn't match message, i = %d", i)
+		}
+	}
+}
+
+func TestEncryptRandomKeyAndMessage(t *testing.T) {
+	for i := 0; i < 256; i++ {
+		key := random.GetRandomBytes(16)
+
+		stream, err := aead.NewAESCTR(key, aead.AESCTRMinIVSize)
+		if err != nil {
+			t.Errorf("failed to instantiate AESCTR, error: %v", err)
+		}
+
+		message := random.GetRandomBytes(uint32(i))
+		ciphertext, err := stream.Encrypt(message)
+		if err != nil {
+			t.Errorf("encryption failed at iteration %d, error: %v", i, err)
+		}
+		if len(ciphertext) != len(message)+aead.AESCTRMinIVSize {
+			t.Errorf("invalid ciphertext length for i = %d", i)
+		}
+
+		plaintext, err := stream.Decrypt(ciphertext)
+		if err != nil {
+			t.Errorf("decryption failed at iteration %d, error: %v", i, err)
+		}
+
+		if bytes.Compare(plaintext, message) != 0 {
+			t.Errorf("plaintext doesn't match message, i = %d", i)
+		}
+	}
+}
diff --git a/go/subtle/aead/aes_gcm.go b/go/subtle/aead/aes_gcm.go
index 078c610..18b43dc 100644
--- a/go/subtle/aead/aes_gcm.go
+++ b/go/subtle/aead/aes_gcm.go
@@ -1,17 +1,17 @@
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
-
+//
 //      http://www.apache.org/licenses/LICENSE-2.0
-
+//
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+//
 ////////////////////////////////////////////////////////////////////////////////
 
-// Package aead provides subtle implementations of the Aead primitive.
 package aead
 
 import (
@@ -24,48 +24,38 @@
 )
 
 const (
-	// AesGcmIvSize is the only IV size that this implementation supports.
-	AesGcmIvSize = 12
-	// AesGcmTagSize is the only tag size that this implementation supports.
-	AesGcmTagSize = 16
+	// AESGCMIVSize is the only IV size that this implementation supports.
+	AESGCMIVSize = 12
+	// AESGCMTagSize is the only tag size that this implementation supports.
+	AESGCMTagSize = 16
 )
 
-// AesGcm is an implementation of Aead interface.
-type AesGcm struct {
+// AESGCM is an implementation of AEAD interface.
+type AESGCM struct {
 	Key []byte
 }
 
-// Assert that AesGcm implements the Aead interface.
-var _ tink.Aead = (*AesGcm)(nil)
+// Assert that AESGCM implements the AEAD interface.
+var _ tink.AEAD = (*AESGCM)(nil)
 
-// NewAesGcm returns an AesGcm instance.
-// The key argument should be the AES key, either 16, 24, or 32 bytes to select
-// AES-128, AES-192, or AES-256.
-func NewAesGcm(key []byte) (*AesGcm, error) {
+// NewAESGCM returns an AESGCM instance.
+// The key argument should be the AES key, either 16 or 32 bytes to select
+// AES-128 or AES-256.
+func NewAESGCM(key []byte) (*AESGCM, error) {
 	keySize := uint32(len(key))
-	if err := ValidateAesKeySize(keySize); err != nil {
+	if err := ValidateAESKeySize(keySize); err != nil {
 		return nil, fmt.Errorf("aes_gcm: %s", err)
 	}
-	return &AesGcm{Key: key}, nil
+	return &AESGCM{Key: key}, nil
 }
 
-// ValidateAesKeySize checks if the given key size is a valid AES key size.
-func ValidateAesKeySize(sizeInBytes uint32) error {
-	switch sizeInBytes {
-	case 16, 24, 32:
-		return nil
-	default:
-		return fmt.Errorf("invalid AES key size %d", sizeInBytes)
-	}
-}
-
-// Encrypt encrypts {@code pt} with {@code aad} as additional
-// authenticated data. The resulting ciphertext consists of two parts:
+// Encrypt encrypts pt with aad as additional authenticated data.
+// The resulting ciphertext consists of two parts:
 // (1) the IV used for encryption and (2) the actual ciphertext.
 //
 // Note: AES-GCM implementation of crypto library always returns ciphertext with
 // 128-bit tag.
-func (a *AesGcm) Encrypt(pt []byte, aad []byte) ([]byte, error) {
+func (a *AESGCM) Encrypt(pt, aad []byte) ([]byte, error) {
 	// Although Seal() function already checks for plaintext length,
 	// this check is repeated here to avoid panic.
 	if uint64(len(pt)) > (1<<36)-32 {
@@ -83,17 +73,17 @@
 	return ret, nil
 }
 
-// Decrypt decrypts {@code ct} with {@code aad} as the additionalauthenticated data.
-func (a *AesGcm) Decrypt(ct []byte, aad []byte) ([]byte, error) {
-	if len(ct) < AesGcmIvSize+AesGcmTagSize {
+// Decrypt decrypts ct with aad as the additionalauthenticated data.
+func (a *AESGCM) Decrypt(ct, aad []byte) ([]byte, error) {
+	if len(ct) < AESGCMIVSize+AESGCMTagSize {
 		return nil, fmt.Errorf("aes_gcm: ciphertext too short")
 	}
 	cipher, err := a.newCipher(a.Key)
 	if err != nil {
 		return nil, err
 	}
-	iv := ct[:AesGcmIvSize]
-	pt, err := cipher.Open(nil, iv, ct[AesGcmIvSize:], aad)
+	iv := ct[:AESGCMIVSize]
+	pt, err := cipher.Open(nil, iv, ct[AESGCMIVSize:], aad)
 	if err != nil {
 		return nil, fmt.Errorf("aes_gcm: %s", err)
 	}
@@ -101,14 +91,14 @@
 }
 
 // newIV creates a new IV for encryption.
-func (a *AesGcm) newIV() []byte {
-	return random.GetRandomBytes(AesGcmIvSize)
+func (a *AESGCM) newIV() []byte {
+	return random.GetRandomBytes(AESGCMIVSize)
 }
 
 var errCipher = fmt.Errorf("aes_gcm: initializing cipher failed")
 
 // newCipher creates a new AES-GCM cipher using the given key and the crypto library.
-func (a *AesGcm) newCipher(key []byte) (cipher.AEAD, error) {
+func (a *AESGCM) newCipher(key []byte) (cipher.AEAD, error) {
 	aesCipher, err := aes.NewCipher(key)
 	if err != nil {
 		return nil, errCipher
diff --git a/go/subtle/aead/aes_gcm_test.go b/go/subtle/aead/aes_gcm_test.go
index dc4b0d7..bc66033 100644
--- a/go/subtle/aead/aes_gcm_test.go
+++ b/go/subtle/aead/aes_gcm_test.go
@@ -1,14 +1,15 @@
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
-
+//
 //      http://www.apache.org/licenses/LICENSE-2.0
-
+//
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+//
 ////////////////////////////////////////////////////////////////////////////////
 
 package aead_test
@@ -24,39 +25,39 @@
 	"github.com/google/tink/go/subtle/random"
 )
 
-var keySizes = []int{16, 24, 32}
+var keySizes = []int{16, 32}
 
 // Since the tag size depends on the Seal() function of crypto library,
 // this test checks that the tag size is always 128 bit.
-func TestAesGcmTagLength(t *testing.T) {
+func TestAESGCMTagLength(t *testing.T) {
 	for _, keySize := range keySizes {
 		key := random.GetRandomBytes(uint32(keySize))
-		a, _ := aead.NewAesGcm(key)
+		a, _ := aead.NewAESGCM(key)
 		ad := random.GetRandomBytes(32)
 		pt := random.GetRandomBytes(32)
 		ct, _ := a.Encrypt(pt, ad)
-		actualTagSize := len(ct) - aead.AesGcmIvSize - len(pt)
-		if actualTagSize != aead.AesGcmTagSize {
+		actualTagSize := len(ct) - aead.AESGCMIVSize - len(pt)
+		if actualTagSize != aead.AESGCMTagSize {
 			t.Errorf("tag size is not 128 bit, it is %d bit", actualTagSize*8)
 		}
 	}
 }
 
-func TestAesGcmKeySize(t *testing.T) {
+func TestAESGCMKeySize(t *testing.T) {
 	for _, keySize := range keySizes {
-		if _, err := aead.NewAesGcm(make([]byte, keySize)); err != nil {
+		if _, err := aead.NewAESGCM(make([]byte, keySize)); err != nil {
 			t.Errorf("unexpected error when key size is %d btyes", keySize)
 		}
-		if _, err := aead.NewAesGcm(make([]byte, keySize+1)); err == nil {
+		if _, err := aead.NewAESGCM(make([]byte, keySize+1)); err == nil {
 			t.Errorf("expect an error when key size is not supported %d", keySize)
 		}
 	}
 }
 
-func TestAesGcmEncryptDecrypt(t *testing.T) {
+func TestAESGCMEncryptDecrypt(t *testing.T) {
 	for _, keySize := range keySizes {
 		key := random.GetRandomBytes(uint32(keySize))
-		a, err := aead.NewAesGcm(key)
+		a, err := aead.NewAESGCM(key)
 		if err != nil {
 			t.Errorf("unexpected error when creating new cipher: %s", err)
 		}
@@ -78,14 +79,14 @@
 	}
 }
 
-func TestAesGcmLongMessages(t *testing.T) {
+func TestAESGCMLongMessages(t *testing.T) {
 	ptSize := 16
 	for ptSize <= 1<<24 {
 		pt := random.GetRandomBytes(uint32(ptSize))
 		ad := random.GetRandomBytes(uint32(ptSize / 3))
 		for _, keySize := range keySizes {
 			key := random.GetRandomBytes(uint32(keySize))
-			a, _ := aead.NewAesGcm(key)
+			a, _ := aead.NewAESGCM(key)
 			ct, _ := a.Encrypt(pt, ad)
 			decrypted, _ := a.Decrypt(ct, ad)
 			if !bytes.Equal(pt, decrypted) {
@@ -96,11 +97,11 @@
 	}
 }
 
-func TestAesGcmModifyCiphertext(t *testing.T) {
+func TestAESGCMModifyCiphertext(t *testing.T) {
 	ad := random.GetRandomBytes(33)
 	key := random.GetRandomBytes(16)
 	pt := random.GetRandomBytes(32)
-	a, _ := aead.NewAesGcm(key)
+	a, _ := aead.NewAESGCM(key)
 	ct, _ := a.Encrypt(pt, ad)
 	// flipping bits
 	for i := 0; i < len(ct); i++ {
@@ -137,12 +138,12 @@
  * The test simply checks that the multiple ciphertexts of the same
  * message are distinct.
  */
-func TestAesGcmRandomNonce(t *testing.T) {
+func TestAESGCMRandomNonce(t *testing.T) {
 	nSample := 1 << 17
 	key := random.GetRandomBytes(16)
 	pt := []byte{}
 	ad := []byte{}
-	a, _ := aead.NewAesGcm(key)
+	a, _ := aead.NewAESGCM(key)
 	ctSet := make(map[string]bool)
 	for i := 0; i < nSample; i++ {
 		ct, _ := a.Encrypt(pt, ad)
@@ -194,10 +195,10 @@
 	}
 
 	for _, g := range data.TestGroups {
-		if err := aead.ValidateAesKeySize(g.KeySize / 8); err != nil {
+		if err := aead.ValidateAESKeySize(g.KeySize / 8); err != nil {
 			continue
 		}
-		if g.IvSize != aead.AesGcmIvSize*8 {
+		if g.IvSize != aead.AESGCMIVSize*8 {
 			continue
 		}
 		for _, tc := range g.Tests {
@@ -230,9 +231,9 @@
 			combinedCt = append(combinedCt, ct...)
 			combinedCt = append(combinedCt, tag...)
 			// create cipher and do encryption
-			cipher, err := aead.NewAesGcm(key)
+			cipher, err := aead.NewAESGCM(key)
 			if err != nil {
-				t.Errorf("cannot create new instance of AesGcm in test case %d: %s", tc.TcID, err)
+				t.Errorf("cannot create new instance of AESGCM in test case %d: %s", tc.TcID, err)
 				continue
 			}
 			decrypted, err := cipher.Decrypt(combinedCt, aad)
diff --git a/go/subtle/aead/chacha20poly1305.go b/go/subtle/aead/chacha20poly1305.go
new file mode 100644
index 0000000..7ea0f18
--- /dev/null
+++ b/go/subtle/aead/chacha20poly1305.go
@@ -0,0 +1,79 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package aead
+
+import (
+	"errors"
+	"fmt"
+
+	"golang.org/x/crypto/chacha20poly1305"
+	"github.com/google/tink/go/subtle/random"
+	"github.com/google/tink/go/tink"
+)
+
+// ChaCha20Poly1305 is an implementation of AEAD interface.
+type ChaCha20Poly1305 struct {
+	Key []byte
+}
+
+// Assert that ChaCha20Poly1305 implements the AEAD interface.
+var _ tink.AEAD = (*ChaCha20Poly1305)(nil)
+
+// NewChaCha20Poly1305 returns an ChaCha20Poly1305 instance.
+// The key argument should be a 32-bytes key.
+func NewChaCha20Poly1305(key []byte) (*ChaCha20Poly1305, error) {
+	if len(key) != chacha20poly1305.KeySize {
+		return nil, errors.New("chacha20poly1305: bad key length")
+	}
+
+	return &ChaCha20Poly1305{Key: key}, nil
+}
+
+// Encrypt encrypts {@code pt} with {@code aad} as additional
+// authenticated data. The resulting ciphertext consists of two parts:
+// (1) the nonce used for encryption and (2) the actual ciphertext.
+func (ca *ChaCha20Poly1305) Encrypt(pt []byte, aad []byte) ([]byte, error) {
+	c, err := chacha20poly1305.New(ca.Key)
+	if err != nil {
+		return nil, err
+	}
+
+	n := ca.newNonce()
+	ct := c.Seal(nil, n, pt, aad)
+	var ret []byte
+	ret = append(ret, n...)
+	ret = append(ret, ct...)
+	return ret, nil
+}
+
+// Decrypt decrypts {@code ct} with {@code aad} as the additionalauthenticated data.
+func (ca *ChaCha20Poly1305) Decrypt(ct []byte, aad []byte) ([]byte, error) {
+	c, err := chacha20poly1305.New(ca.Key)
+	if err != nil {
+		return nil, err
+	}
+
+	n := ct[:chacha20poly1305.NonceSize]
+	pt, err := c.Open(nil, n, ct[chacha20poly1305.NonceSize:], aad)
+	if err != nil {
+		return nil, fmt.Errorf("ChaCha20Poly1305.Decrypt: %s", err)
+	}
+	return pt, nil
+}
+
+// newNonce creates a new nonce for encryption.
+func (ca *ChaCha20Poly1305) newNonce() []byte {
+	return random.GetRandomBytes(chacha20poly1305.NonceSize)
+}
diff --git a/go/subtle/aead/chacha20poly1305_test.go b/go/subtle/aead/chacha20poly1305_test.go
new file mode 100644
index 0000000..096c60a
--- /dev/null
+++ b/go/subtle/aead/chacha20poly1305_test.go
@@ -0,0 +1,276 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package aead_test
+
+import (
+	"bytes"
+	"encoding/hex"
+	"encoding/json"
+	"math/rand"
+	"os"
+	"testing"
+
+	"golang.org/x/crypto/chacha20poly1305"
+	"github.com/google/tink/go/subtle/aead"
+	"github.com/google/tink/go/subtle/random"
+)
+
+func TestChaCha20Poly1305EncryptDecrypt(t *testing.T) {
+	for i, test := range chaCha20Poly1305Tests {
+		key, _ := hex.DecodeString(test.key)
+		pt, _ := hex.DecodeString(test.plaintext)
+		aad, _ := hex.DecodeString(test.aad)
+		nonce, _ := hex.DecodeString(test.nonce)
+		out, _ := hex.DecodeString(test.out)
+
+		ca, err := aead.NewChaCha20Poly1305(key)
+		if err != nil {
+			t.Errorf("#%d, cannot create new instance of ChaCha20Poly1305: %s", i, err)
+			continue
+		}
+
+		_, err = ca.Encrypt(pt, aad)
+		if err != nil {
+			t.Errorf("#%d, unexpected encryption error: %s", i, err)
+			continue
+		}
+
+		var combinedCt []byte
+		combinedCt = append(combinedCt, nonce...)
+		combinedCt = append(combinedCt, out...)
+		if got, err := ca.Decrypt(combinedCt, aad); err != nil {
+			t.Errorf("#%d, unexpected decryption error: %s", i, err)
+			continue
+		} else if !bytes.Equal(pt, got) {
+			t.Errorf("#%d, plaintext's don't match: got %x vs %x", i, got, pt)
+			continue
+		}
+	}
+}
+
+func TestChaCha20Poly1305EmptyAssociatedData(t *testing.T) {
+	key := random.GetRandomBytes(chacha20poly1305.KeySize)
+	aad := []byte{}
+	badAad := []byte{1, 2, 3}
+
+	ca, err := aead.NewChaCha20Poly1305(key)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	for i := 0; i < 75; i++ {
+		pt := random.GetRandomBytes(uint32(i))
+		// Encrpting with aad as a 0-length array
+		{
+			ct, err := ca.Encrypt(pt, aad)
+			if err != nil {
+				t.Errorf("Encrypt(%x, %x) failed", pt, aad)
+				continue
+			}
+
+			if got, err := ca.Decrypt(ct, aad); err != nil || !bytes.Equal(pt, got) {
+				t.Errorf("Decrypt(Encrypt(pt, %x)): plaintext's don't match: got %x vs %x", aad, got, pt)
+			}
+			if got, err := ca.Decrypt(ct, nil); err != nil || !bytes.Equal(pt, got) {
+				t.Errorf("Decrypt(Encrypt(pt, nil)): plaintext's don't match: got %x vs %x", got, pt)
+			}
+			if _, err := ca.Decrypt(ct, badAad); err == nil {
+				t.Errorf("Decrypt(Encrypt(pt, %x)) = _, nil; want: _, err", badAad)
+			}
+		}
+		// Encrpting with aad equal to null
+		{
+			ct, err := ca.Encrypt(pt, nil)
+			if err != nil {
+				t.Errorf("Encrypt(%x, nil) failed", pt)
+			}
+
+			if got, err := ca.Decrypt(ct, aad); err != nil || !bytes.Equal(pt, got) {
+				t.Errorf("Decrypt(Encrypt(pt, %x)): plaintext's don't match: got %x vs %x; error: %v", aad, got, pt, err)
+			}
+			if got, err := ca.Decrypt(ct, nil); err != nil || !bytes.Equal(pt, got) {
+				t.Errorf("Decrypt(Encrypt(pt, nil)): plaintext's don't match: got %x vs %x; error: %v", got, pt, err)
+			}
+			if _, err := ca.Decrypt(ct, badAad); err == nil {
+				t.Errorf("Decrypt(Encrypt(pt, %x)) = _, nil; want: _, err", badAad)
+			}
+		}
+	}
+}
+
+func TestChaCha20Poly1305LongMessages(t *testing.T) {
+	dataSize := uint32(16)
+	// Encrypts and decrypts messages of size <= 8192.
+	for dataSize <= 1<<24 {
+		pt := random.GetRandomBytes(dataSize)
+		aad := random.GetRandomBytes(dataSize / 3)
+		key := random.GetRandomBytes(chacha20poly1305.KeySize)
+
+		ca, err := aead.NewChaCha20Poly1305(key)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		ct, err := ca.Encrypt(pt, aad)
+		if err != nil {
+			t.Errorf("Encrypt(%x, %x) failed", pt, aad)
+			continue
+		}
+
+		if got, err := ca.Decrypt(ct, aad); err != nil || !bytes.Equal(pt, got) {
+			t.Errorf("Decrypt(Encrypt(pt, %x)): plaintext's don't match: got %x vs %x; error: %v", aad, got, pt, err)
+		}
+
+		dataSize += 5 * dataSize / 11
+	}
+}
+
+func TestChaCha20Poly1305ModifyCiphertext(t *testing.T) {
+	for i, test := range chaCha20Poly1305Tests {
+		key, _ := hex.DecodeString(test.key)
+		pt, _ := hex.DecodeString(test.plaintext)
+		aad, _ := hex.DecodeString(test.aad)
+
+		ca, err := aead.NewChaCha20Poly1305(key)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		ct, err := ca.Encrypt(pt, aad)
+		if err != nil {
+			t.Errorf("#%d: Encrypt failed", i)
+			continue
+		}
+
+		if len(aad) > 0 {
+			alterAadIdx := rand.Intn(len(aad))
+			aad[alterAadIdx] ^= 0x80
+			if _, err := ca.Decrypt(ct, aad); err == nil {
+				t.Errorf("#%d: Decrypt was successful after altering additional data", i)
+				continue
+			}
+			aad[alterAadIdx] ^= 0x80
+		}
+
+		alterCtIdx := rand.Intn(len(ct))
+		ct[alterCtIdx] ^= 0x80
+		if _, err := ca.Decrypt(ct, aad); err == nil {
+			t.Errorf("#%d: Decrypt was successful after altering ciphertext", i)
+			continue
+		}
+		ct[alterCtIdx] ^= 0x80
+	}
+}
+
+// This is a very simple test for the randomness of the nonce.
+// The test simply checks that the multiple ciphertexts of the same message are distinct.
+func TestChaCha20Poly1305RandomNonce(t *testing.T) {
+	key := random.GetRandomBytes(chacha20poly1305.KeySize)
+	ca, err := aead.NewChaCha20Poly1305(key)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	cts := make(map[string]bool)
+	pt, aad := []byte{}, []byte{}
+	for i := 0; i < 1<<10; i++ {
+		ct, err := ca.Encrypt(pt, aad)
+		ctHex := hex.EncodeToString(ct)
+		if err != nil || cts[ctHex] {
+			t.Errorf("TestRandomNonce failed: %v", err)
+		} else {
+			cts[ctHex] = true
+		}
+	}
+}
+
+func TestChaCha20Poly1305WycheproofVectors(t *testing.T) {
+	f, err := os.Open("../../../../wycheproof/testvectors/chacha20_poly1305_test.json")
+	if err != nil {
+		t.Fatalf("cannot open file: %s, make sure that github.com/google/wycheproof is in your gopath", err)
+	}
+	parser := json.NewDecoder(f)
+	data := new(testdata)
+	if err := parser.Decode(data); err != nil {
+		t.Fatalf("cannot decode test data: %s", err)
+	}
+
+	for _, g := range data.TestGroups {
+		if g.KeySize/8 != chacha20poly1305.KeySize {
+			continue
+		}
+		if g.IvSize/8 != chacha20poly1305.NonceSize {
+			continue
+		}
+
+		for _, tc := range g.Tests {
+			key, err := hex.DecodeString(tc.Key)
+			if err != nil {
+				t.Errorf("#%d, cannot decode key: %s", tc.TcID, err)
+			}
+			aad, err := hex.DecodeString(tc.Aad)
+			if err != nil {
+				t.Errorf("#%d, cannot decode aad: %s", tc.TcID, err)
+			}
+			msg, err := hex.DecodeString(tc.Msg)
+			if err != nil {
+				t.Errorf("#%d, cannot decode msg: %s", tc.TcID, err)
+			}
+			ct, err := hex.DecodeString(tc.Ct)
+			if err != nil {
+				t.Errorf("#%d, cannot decode ct: %s", tc.TcID, err)
+			}
+			nonce, err := hex.DecodeString(tc.Iv)
+			if err != nil {
+				t.Errorf("#%d, cannot decode nonce: %s", tc.TcID, err)
+			}
+			tag, err := hex.DecodeString(tc.Tag)
+			if err != nil {
+				t.Errorf("#%d, cannot decode tag: %s", tc.TcID, err)
+			}
+
+			var combinedCt []byte
+			combinedCt = append(combinedCt, nonce...)
+			combinedCt = append(combinedCt, ct...)
+			combinedCt = append(combinedCt, tag...)
+
+			ca, err := aead.NewChaCha20Poly1305(key)
+			if err != nil {
+				t.Errorf("#%d, cannot create new instance of ChaCha20Poly1305: %s", tc.TcID, err)
+				continue
+			}
+
+			_, err = ca.Encrypt(msg, aad)
+			if err != nil {
+				t.Errorf("#%d, unexpected encryption error: %s", tc.TcID, err)
+				continue
+			}
+
+			decrypted, err := ca.Decrypt(combinedCt, aad)
+			if err != nil {
+				if tc.Result == "valid" {
+					t.Errorf("#%d, unexpected error: %s", tc.TcID, err)
+				}
+			} else {
+				if tc.Result == "invalid" {
+					t.Errorf("#%d, decrypted invalid", tc.TcID)
+				}
+				if !bytes.Equal(decrypted, msg) {
+					t.Errorf("#%d, incorrect decryption", tc.TcID)
+				}
+			}
+		}
+	}
+}
diff --git a/go/subtle/aead/chacha20poly1305_vectors_test.go b/go/subtle/aead/chacha20poly1305_vectors_test.go
new file mode 100644
index 0000000..a0c7922
--- /dev/null
+++ b/go/subtle/aead/chacha20poly1305_vectors_test.go
@@ -0,0 +1,349 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package aead_test
+
+var chaCha20Poly1305Tests = []struct {
+	plaintext, aad, key, nonce, out string
+}{
+	{
+		"",
+		"",
+		"808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
+		"070000004041424344454647",
+		"a0784d7a4716f3feb4f64e7f4b39bf04",
+	},
+	{
+		"4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e",
+		"50515253c0c1c2c3c4c5c6c7",
+		"808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
+		"070000004041424344454647",
+		"d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b61161ae10b594f09e26a7e902ecbd0600691",
+	},
+	{
+		"1400000cebccee3bf561b292340fec60",
+		"00000000000000001603030010",
+		"a5117e70953568bf750862df9e6f92af81677c3a188e847917a4a915bda7792e",
+		"129039b5572e8a7a8131f76a",
+		"2b487a2941bc07f3cc76d1a531662588ee7c2598e59778c24d5b27559a80d163",
+	},
+	{
+		"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+		"00000000000000000000000000",
+		"a5117e70953568bf750862df9e6f92af81677c3a188e847917a4a915bda7792e",
+		"129039b5572e8a7a8131f76a",
+		"3f487a25aa70e9c8391763370569c9e83b7650dd1921c8b78869f241f25d2096c910b180930c5b8747fd90959fe8ca2dcadb4fa50fa1439f916b2301e1cc0810d6725775d3ab86721700f96e22709b0a7a8bef32627dd929b2dd3ba15772b669062bb558bc92e6c241a1d60d9f0035e80c335f854815fe1138ab8af653eab3e122135feeec7dfaba1cc24af82a2b7acccdd824899a7e03cc29c25be8a4f56a66673845b93bae1556f09dafc89a0d22af207718e2a6bb022e9d917597295992ea3b750cc0e7a7c3d33b23c5a8aeab45f5bb542f6c9e6c1747ae5a344aff483ba38577ad534b33b3abc7d284776ea33ed488c2a2475648a4fcda561745ea7787ed60f2368deb27c75adce6ff9b6cc6de1f5e72a741e2d59f64751b3ae482d714e0c90e83c671ff98ed611823afb39e6e5019a6ba548a2a72e829c7b7b4a101ac9deb90a25d3e0c50d22e1fc26c7c02296fa13c6d9c14767f68aaf46450a8d0fd5feb60d9d73c6e68623425b4984a79d619dd6bf896459aa77a681ec9c1a97f645e121f47779b051f8948a817f84d1f55da170d5bbbaf2f64e18b97ed3fd822db2819f523314f1e5ac72e8f69bbe6c87c22daddb0e1ac6790f8534071de2f258064b99789bfb165b065b8fe96f9127cd7dca9f7cb0368420f1e802faa3ca23792f2a5b93773dd405e71c320b211b54f7a26626b03c060e1ab87f32ac588abfa056ce090bd7c69913a700c80f325bfe824fa",
+	},
+	{
+		"0967de57eefe1aaa999b9b746d88a1a248000d8734e0e938c6aa87",
+		"e4f0a3a4f90a8250f8806aa319053e8d73c62f150e2f239563037e9cc92823ad18c65111d0d462c954cc6c6ed2aafb45702a5a7e597d13bd8091594ab97cf7d1",
+		"f2db28620582e05f00f31c808475ca3df1c20e340bf14828352499466d79295f",
+		"4349e2131d44dc711148dfe3",
+		"bd06cc144fdc0d8b735fa4452eabbf78fd4ad2966ea41a84f68da40ca2da439777bc2ba6c4ec2de0d003eb",
+	},
+	{
+		"c4c920fb52a56fe66eaa8aa3fa187c543e3db8e5c8094c4313dc4ed35dfc5821c5791d171e8cfe8d37883031a0ad",
+		"85deea3dc4",
+		"05ff881d1e151bab4ca3db7d44880222733fe62686f71ce1e4610f2ea19599a7",
+		"b34710f65aed442e4a40866b",
+		"b154452fb7e85d175dd0b0db08591565c5587a725cf22386922f5d27a01015aba778975510b38754b2182e24352f019b7ad493e1ed255906715644aec6e0",
+	},
+	{
+		"c4b337df5e83823900c6c202e93541cf5bc8c677a9aad8b8d87a4d7221e294e595cbc4f34e462d4e0def50f62491c57f598cf60236cfba0f4908816aea154f80e013732e59a07c668fcc5cb35d2232b7ae29b9e4f874f3417c74ab6689fae6690d5a9766fa13cd8adf293d3d4b70f4f999adde9121d1d29d467d04cf77ea398444d0ea3fe4b7c9c3e106002c76f4260fa204a0c3d5",
+		"72611bef65eb664f24ea94f4d5d3d88c9c9c6da29c9a1991c02833c4c9f6993b57b5",
+		"dd0f2d4bb1c9e5ca5aa5f38d69bc8402f7dbb7229857b4a41b3044d481b7655e",
+		"2bbca0910cc47ca0b8517391",
+		"83aa28d6d98901e2981d21d3758ae4db8cce07fe08d82ca6f036a68daa88a7dda56eeb38040c942bdda0fd2d369eec44bd070e2c9314992f68dc16989a6ac0c3912c378cf3254f4bae74a66b075e828df6f855c0d8a827ffed3c03582c12a9112eeb7be43dfe8bd78beb2d1e56678b99a0372531727cb7f2b98d2f917ec10de93fe86267100c20356e80528c5066688c8b7acba76e591449952343f663993d5b642e59eb0f",
+	},
+	{
+		"a9775b8e42b63335439cf1c79fe8a3560b3baebfdfc9ef239d70da02cea0947817f00659a63a8ee9d67fb1756854cc738f7a326e432191e1916be35f0b78d72268de7c0e180af7ee8aa864f2fc30658baa97f9edb88ace49f5b2a8002a8023925e9fa076a997643340c8253cf88ac8a221c190d94c5e224110cb423a4b65cca9046c1fad0483e1444c0680449148e7b20a778c56d5ae97e679d920c43eed6d42598cf05d10d1a15cd722a0686a871b74fea7cad45562bacf3bda937ac701bc218dac7e9d7d20f955429abdac21d821207febf4d54daea4898837035038bf71c66cef63e90f5d3e51f7fcfe18d41f38540a2c2958dacde16304e4b33da324030f1366f923c337",
+		"74ba3372d308910b5c9c3885f41252d57556",
+		"9cf77bd06a4ed8fb59349791b98ba40b6019611942f5768e8be2ee88477149e3",
+		"b928935c4c966c60fd6583c0",
+		"ec7fd64fd75b254961a2b7fc942470d8620f439258b871d0d00f58028b5e0bee5e139e8108ac439391465d6658f559b1df57aa21cf826ede1a28bc11af885e13eebfc009870928fae8abfdd943a60c54fca93f0502dc23d29c2fd5340f9bc0e6ef2a18b66ef627af95f796d5bbca50de22c8ec802da9397089b25c6ba5262468e3977b45dc112e51896c70731b0a52d7efec7c93b41995823436bf4b0c477ae79684407c9831b487928b2b8303caca752b3edf1f0598e15831155462706f94ef3fa3a9e5f937f37085afa9b4bbf939d275796a61b78f70597acfd25cd87f967021cd99328fc371b5eb5739869520657b30e4a5b0db7c8715cbe275dee78e719b357d3a9731f9eaba95986479bb2004a77822fc115a3d",
+	},
+	{
+		"b3d3128bce6bbf66fd78f1a18352bae56bfcdae18b65c379ee0aeb37ee54fba1270d2df578ec5b75654d16e89fd1cd0acda7ec580dafd2fbbabd32a8112d49383a762db2638928c8d63eb0750f7e7fdd256b35321b072dd5c45f7dd58cc60dc63d3b79a0c4a1689adf180fef968eccbcfa01ee15091ceacd7b67a3082db0ce6aeb470aafe87249c88b58b721e783dde184ccf68de8e05b6347fe6b74ae3adf9a81e9496a5c9332e7ebe908d26ce6b3f0b2a97e9a89d9fdd0d7694585a3241f240d698e69fcc050e7a959ba153f6d06f117848ba05d887134f1b6b994dad9b9e74247513e08a125b1fadfc7394dcd2a6451b504ae3e75e22f2b9bc405747dedb6c43ef4ccdf1a7edaf9451346123eaa63f3af113124f361508e255503a242b96680ae3360c8b13ac1f64d08088bb26b7f617cb0866f11d6fd362b00d86eba3fee68724e302388f119d6f92161ac8ce00d08919377a26974d99575b1032ff0f1976240c785c8b89e9eb2bf005e4be06b5371ffca14683fedfdb49e00e38ff27af1324177faf91599abd5990920797574eb743effdc7decda318ada1419cc8e0bfecf82f9c99792746c2b",
+		"7e8da4f3018f673f8e43bd7a1dee05f8031ec49129c361abbc2a434e9eaf791c3c1d0f3dad767d3bba3ab6d728bbcf2bd994bd03571eae1348f161e6a1da03ddf7121ba4",
+		"7ee32dd501dce849cd492f6e23324c1a4567bfceff9f11d1352bcb8615f1b093",
+		"8998e043d2961afa51ea262a",
+		"ba85e72af18cb5ba85a4a0d6c28b4ac1e5509a3a2fdb0e3255cbc559df5e6a661fc560c756a0264dd99b72c61c51a4b7ad56ca4c8ccb7e8edfc48ff3cceac5d1e8ac5fc87096adc4d0e9a27492857b17604c3a694cfe0e70b22df106c8f3c61f840bcd634964cdb571840e125e381e7dd3a0d97972e965f16f775fa4ce555124318290bf508beb7bd77e633042deb0e863631478fc3dc9122862b3c31264471bcce54e0b74040c8bafd481cf798f332e8940f1134d3027d6f28e771d15e154fc89c6c25fe18a5d312807cc2e623bb1bbb4f0b6ec71d009407eb54bb0759f03682f65d0da8812f84d8e97483f6a8d76a8417efcd9526444abba24288647609791578887ef49780b0b89f51b072cae81c5b5014463da3633dda105b82add0f9c2f065dca46eedd2928be2570493c79a996fa78ea6aec0996497fe2dc444432ade4eaa662ee2255f0f4b92d593288a8e3ffe7a15a10e9d33b0203af23f4c9fd2cfcb6160db63b52810869ff1e65423dbe2c4415884b9f8dec3c968e14cd74f323c89053a96111bc9ce59ec483832c49c53a648e5f0f797f53642ac60170c94b473f1f2e7d8a38e46460b81219b52081263027f74cbf63a75af3a7",
+	},
+	{
+		"68d5ba501e87994ef6bc8042d7c5a99693a835a4796ad044f0e536a0790a7ee1e03832fec0cb4cb688cdf85f92a1f526492acac2949a0684803c24f947a3da27db0c259bd87251603f49bfd1eab4f733dec2f5725cfcf6dc381ad57fbdb0a699bccc34943e86f47dcfb34eba6746ed4508e3b764dfad4117c8169785c63d1e8309531747d90cc4a8bf13622759506c613324c512d10629991dc01fe3fe3d6607907e4f698a1312492674707fc4dde0f701a609d2ac336cc9f38badf1c813f9599148c21b5bd4658249d5010db2e205b3880e863441f2fe357dab2645be1f9e5067616bc335d0457ea6468c5828910cb09f92e5e184e316018e3c464c5ce59cc34608867bd8cbfa7e1286d73a17e3ebb675d097f9b3adfa41ea408d46252a096b3290e70a5be1896d6760a87e439334b863ccb11679ab5763ebe4a9110eb37c4043634b9e44d40cab34b42977475e2faa2ae0c0a38b170776fbb0870a63044aa6679545ac6951579d0581144cdf43f60923b6acaecdb325c864acd2c7b01d6e18b2b3c41c041bb9099cce557b114b84350131e3cee4089648b5691065867e7d38314154355d0e3ef9dc9375eddef922df2a06ad0f0e4357c3ac672932e5a66b16e8bf4b45cd893ea91cb397faadb9d9d7bf86e6ceca3e9176a5baa98b6114a149d3ed8ea176cc4a9380e18d2d9b67045aedeb28b729ba2ece74d759d5ebfb1ebee8ac5f5e79aaf1f98b7f2626e62a81d315a98b3e",
+		"63b90dd89066ad7b61cc39497899a8f14399eace1810f5fe3b76d2501f5d8f83169c5ba602082164d45aad4df3553e36ef29050739fa067470d8c58f3554124bf06df1f27612564a6c04976059d69648ff9b50389556ad052e729563c6a7",
+		"7d5c4314a542aff57a454b274a7999dfdc5f878a159c29be27dabdfcf7c06975",
+		"aeb6159fa88bb1ffd51d036d",
+		"7597f7f44191e815a409754db7fea688e0105c987fa065e621823ea6dea617aed613092ad566c487cfa1a93f556615d2a575fb30ac34b11e19cd908d74545906f929dc9e59f6f1e1e6eaaabe182748ef87057ef7820ffcf254c40237d3ea9ff004472db783ed54b5a294a46cf90519bf89367b04fc01ce544c5bcdd3197eb1237923ce2c0c99921ca959c53b54176d292e97f6d9696ded6054711721aebda543e3e077c90e6f216cdc275b86d45603521c5aab24f08fd06833b0743c388382f941e19e0283ac7c4ef22383e1b9b08572882769c1382bab9ad127e7f3e09b5330b82d3e0c7d6f0df46edc93265999eef8e7afa0cb1db77df7accf5bff8631a320d146a5c751a637a80f627b0c9a41b44f09212f38c154226de02f4906ef34139bbeacc3f06739c8540e37334392d38ba1cbf4bc7debe77c09b35d2200216db15ed4389f43bfd8ae9bf76fd8243c3d869546e16b8e44a6cd1edbd2c58ef890b5a84cda889131e5cd9402ca4d8271052c6b4fe3f2dff54fb77bcb575c315b9109f90b14bc8e109919808a581c1809e2a188d29fd34ce639088a6683f641925f5b4b3529baa34e080bb47fb7ad9b43d0d67c9e6ae7cacb50527fa74e56d0c8b20149f5d332d686d48ebbe634c2b5d35fc84c69a5bcc93b93dedcf9fdf19a1fb9b75f6df9692d16f6c3490377a06294499e4b8ebeaa0cfd840bfa05fde21c0b5e94d13063b3f5da7b537caefe89069cfa9de9eb8f06e4d30125de64716f821bcc8279c0c7ea2e",
+	},
+	{
+		"89c1ee38b6697d0190c87a2aa756892ee09fca095df1e31aeedbda5750f604d9b8f2116e5b8f70ec57ea16fe419f2d213ef72b9be90eb5d7e98f2e398632123e2524ac80b31c6c0a07820848223569602d94fc16a3b1ed8c411bc6c74ed80573fcb1f3afce60b9d5e2c21d04f78665241b613abe12274a5343101a91e91f04e5d1f7959f574e743a10913e0817a32c320467f0178e3b6ad14b856234a4661a755eaf14b5fd88ef0e192e1631d14263d6a954ed388f5709dadc6c0f81d229f630d80be6d593d5e3ad03f9ded53c41abe595981d24ef27ffcc930e4d653743960f4e7ce4e251c88f55c16d2afdaed5e3446d00685c276728ba757520acb9b6bb0732a0e9836878d829e5022794d70ad8440a40a132a8c9ec1d3f0ccaf8c285fff425e9788d6150b74753dedb2ae8b36ff2f310249bd911b9181d8310e00810d42ef94cbb5a9d72a1f0507c1a382f892b23994fbe7360778b7efa9c5e03ac3231a57fecff1c5fa10caf1d26e84db0137049622ebcc3a64841a0e49fa390d1d43550c1346c20d578cff39fb7404fcab0982dde55f0849d312581d0c811a19d46f25e7a5e7e50d74d43760583c5cf335dfc11b2ec964f1dbbd0ed83e18f2027817ea2dffcf2b64a352c4fb8f11eeb4f1bfc01079251254d2112d103a1f12a2270cc026cbeb8b6f3e505abd62496253f93274625786b73997e449c1f35c742a593441252fcc845e1cef1b8f287dd311a0477407ce3b31661f7b2802c79c2d20d06e45f03aca4e47a959c6c1d7a9d377e1577fbf82a115921c3d94e3d9c204aa204a9a5b04d8a2be3269700a035371f4aaf1a42d92b9bfbee74492b106975b36d1e581d6ce2484f09e04fa91586c85f35e2a10f0d3c0afcb05327c1bc9d7429bbcc4627af8f76b86fc561844c2ae3810c84901ac09a1670ed3d31a9daa5d296",
+		"7219bd21a834d917f93a9b45647ec77102578bc2f2a132dfde6489b9095b4f7b740c9c1c4075333ab0ce7f14",
+		"a7f849b054982cc8a4c8e5e53e181feee79e0233e58882839892134ad582da7c",
+		"4c46854e9e101090b1436f90",
+		"ab2e189baf60886bed88eb751bf3560a8bd3cdb6ee621d8c18b5fb3aa418f350048ecf359a7d542daf7090ec8688c3b0fe85914aa49d83be4ae3396f7bdc48051afae6a97fca7b42c0bf612a42d3c79ef6aadceb57f5cfe8d67f89d49add0ea1ffd423da058297239e72a85fa6cd1d82e243a503b1b0e12d7510a9ee98d7921dae2754d7581e52acb8ab9e7f9df3c73410789115cef6ce7c937a5441ad4edf2b7a8c0c6d152d5a5909c4ce839d59594a6163364038c4c71a1507389717f61e2bda1ea66a83ef477762e7834ebcfaa8f2ee61ced1605ba1380108236e1763bf40af5259da07dd3e3d0fb2801868c2e7c839e318678687cbe33384e2ef5750a0a0e2d2e19e869a4277e32a315ed4de79357f6a12a8a25d5b18291316d9bf40dad2d05d1b523ade76650669c700a1c2965f4e51337aa5d45ec7b4981072779401d6d30ed69034053334bccb18425ac68460becf2aeccc75aacd3d6709f07ee10366ed848c8a54904af4ea71fc2117de133f01e1cc031f2a4d0779b997b82682433ee615202d5dfffba6c916f11a00551d56ffde8c36b303263e14adaf45b6eab0bedf344e5214ce52f071d2f40154d788c6870020791a03d2fd4ec5879d9026241954ed45cfddef4937ea3d0d45647f252be31411237983a1be340fc65ebab9a5620abb0e8d475af4e89e842e895eda0cbd283bb5d0bf20236c62d956de733d60ebceb42fc0c9adbf9b69f8d66551b0aca0e260625ad41cad75d752a234af7caf7902c2c5b62f04b6a8e019a6179d44feeb2ad5859ef1c45371e66f1af1fe0de63997266c290e27f0dd62185c53f81e0a50c296a51ace7c90d9cf0dda8b2d7e72a347f64c44262e2a544d1acc7bb05734dc1783bbc1903279092fe7fe434610aa95fc2ce5fc5ee45858f5e8337d8fcb0a468464becb1cef6b7e5ea48ba383ad8a406df9c581f1cac057d8711fcb",
+	},
+	{
+		"2dcfbb59975f217c445f95634d7c0250afe7d8316a70c47dba99ff94167ab74349729ce1d2bd5d161df27a6a6e7cba1e63924fcd03134abdad4952c3c409060d7ca2ee4e5f4c647c3edee7ad5aa1cbbd341a8a372ed4f4db1e469ee250a4efcc46de1aa52a7e22685d0915b7aae075defbff1529d40a04f250a2d4a046c36c8ca18631cb055334625c4919072a8ee5258efb4e6205525455f428f63aeb62c68de9f758ee4b8c50a7d669ae00f89425868f73e894c53ce9b964dff34f42b9dc2bb03519fbc169a397d25197cae5bc50742f3808f474f2add8d1a0281359043e0a395705fbc0a89293fa2a5ddfe6ae5416e65c0a5b4eb83320585b33b26072bc99c9c1948a6a271d64517a433728974d0ff4586a42109d6268f9961a5908d6f2d198875b02ae7866fff3a9361b41842a35dc9477ec32da542b706f8478457649ddfda5dfab1d45aa10efe12c3065566541ebdc2d1db6814826f0cc9e3642e813408df3ebaa3896bb2777e757dc3dbc1d28994a454fcb8d76bc5914f29cfc05dc89f8c734315def58d4d6b0b0136ccd3c05178155e30fcb9f68df9104dc96e0658fa899c0058818da5ec88a723558ae3a6f2f8f523e5af1a73a82ab16198c7ba8341568399d8013fc499e6e7ef61cb8654b48b88aa2a931dc2cdcf245686eed9c8355d620d5e91c1e878a9c7da655e3f29d9b7c3f44ad1c70890eb5f27ca28efff76420cd4e3cebd5c788536ddd365f7ad1dbb91588d58612e43b0460de9260d5f780a245bc8e1a83166df1f3a3506d742c268ab4fc10c6e04bca40295da0ff5420a199dd2fb36045215138c4a2a539ceccc382c8d349a81e13e848708947c4a9e85d861811e75d323896f6da3b2fa807f22bcfc57477e487602cf8e973bc925b1a19732b00d15d38675313a283bbaa75e6793b5af11fe2514bda3abe96cc19b0e58ddbe55e381ec58c31670fec1184d38bbf2d7cde0fcd29e907e780d30130b98e0c9eec44bcb1d0ed18dfda2a64adb523da3102eafe2bd3051353d8148491a290308ed4ec3fa5da5784b481e861360c3b670e256539f96a4c4c4360d0d40260049035f1cfdacb275e7fa847e0df531b466141ac9a3a16e7865947572e4ab732daec23aac6eed1256d796c4d58bf699f20aa4bbae461a16abbe9c1e9",
+		"33791b0d653fb72c2d88519b02bde85a7c51f99cfb4456dfa6f84a61e10b4a14846521",
+		"a0a7b73ca2fc9282a28acc036bd74d7f5cb2a146577a5c29dbc3963fe7ebfd87",
+		"eaa4d916d261676d632455be",
+		"c9a631de470fd04dcbf8ea9f4d8ac37c3988878b6381707ac2c91d3720edbb31576ba90731f433a5e13582aca2b3c76ae75ca8881a463ecfa789910d3a776a9ad4800521c6baa120b2f1afd10f32ef8da63f5b69f5e5fd88ee84bf66b0666b15d05c4050f5358a050b9d5cf1503719f56cd48ceba78f29efe2ae8092e37f5134df526831532f86ccb9339637e2c9e9b9036f83cc058fda23e826a188456e7fd3f4ee20f4e4a3221883fe3232b49db607b90a8956133ab95051c9ec33a908ea7e81a1bfa7bd06c09f0143d07bb23a3feeac7f0d7720269c93e2df19d03605828c8713b84d183c9a50954c12fe3b047511ad15ef03a63355520cbd224d06a34de67a671368e6a8f9feeefe48fc273764a8c69c00314e5d693f159cb5270544f3c4e1760b0529e3303ab308e9a6d03835a3a42aef2df5f7643696f707a574d1dcc676aeecdd9947ebe8c13bcf15d30b2d10d2cd95445a307c1d22d39450615ad38f9302c6eb9dc05764b0503d6a7eaff9feb94834853b47bc25660207be3e7c0e27cb3127b5402cb016396e5ff07ddc3df29861dd68a17f53bf660b23352b739d6da72381b8d19a9fc95da7efb79330a2b360dce4309860af429e3fd10cab235c4acc1d80d9e20d67019375bd161ab65648400f308815afe63cfc717f7d0eea150e687caac25b6603287d44dca4a7cc2f67c3bdd54450bd3170340253b03ba054ec003070eddf9c14fb9dc595e228e4968524900cb5d85af6d1e658a42d744e0e7eb6995023823a8dc33528c6715b2e1aa607782c8e1ddddad72026d657bf122ece8685f6e92236e809139325e4a3c069facf94c10b7896995bba01eb22c7b3a87ea2114a7649d7ed3e83d223e5e785c66a75119beab0968d3eaf0cbcc2d7ede95d024041e6db39a880ce3e19efea32fb89a40a2aae22f407e5fd615e51e48dbd50a8b4ec27ce95e2ba1928bf699d0418705482ed0ed7acc858dfbd690403c74667a88dd5221bb79940c6c4a268379c10343aaefb635982c14f33ad83d47ced9682961540bd4f75804d3d48ba8aa67fb2e3a1db83fbcbe57fec9e4ffb1b575e947f8bd8263c680357960e3a39382974774b5a013f2f8514b3c63c21dbfd314fd5d927d82ba616d76629ac018879f54ff84b5808e94af4fcfe1cf8845b65208ca5510b5b593ce6c109611652cd",
+	},
+	{
+		"c335b055b752e083554b5aa2cbb6556cfcace658d5c11b6b000256fd89e9b24c1e62a2d5b582580acdb2ad9869020465aeeabe83acd9eeacdc44aa652d5cb24bbe542073d6787ea32b2b3c942d40f9db2bb75ed7914c836d902dd2be89840948d82abbaea23952cd648e6191ce5b6cf912cad0a3165410a781e3650b676e5340980eee3b484008acce6a3e9dc5aa96d775677b8bbb8b323c6e9747d6069a169ea904d9f145e29d134cdbb0118647e8fbae638669efb9a55d50ed33568749f5304ece2193b0bfa6fc9a570d209ef61b4c59a2b5485b5aa6ab47d902cf23f7ff71c5210476e0aa727a01809b9f76b6ebcf58a018b3fbbe5f42976111ba58112b1d322f9312da068cdb86277bfcde66cb3607e3ea02a1494439aa56f302671f1f994eb3ab28b937043f5f7f3b3de50673ecea5dee8ba633c45089b852f0d772892525344ede6b521dcad15807b65e7ba348d891d47fc498cf4d50223d2794c64db9fa9b9766edb430be0c38746ab317b38ba9870a6d1fdabb70fcf89790bfe449b97fe01f6c94502aa0889f0a3bb6bdc65f44d1cd64ab88d4a7806b373f5080f9cf60183cf4686694f0059e2bbc5cf21ba0c3e8046e70d815f1444c3094cc29632c429f20aa06b49b0b52c6c7aeb8e34f7bcb53e93c2cfe2d704a5d0416876742c90762730d160e1869d5e0178dc366098ebaf2cae6f1f7563b555a52dcc194a5c8f718d50d27ee76fcce8e8991f4921fae85ea9476e1eab1364403120698b7ce8fd0a49cf79213f360a17cf1950f104494fad80adcc3bb1207bf250d57dcdce6ac8082a312959672361363cc227310b66ee8c04aab7b5cb33a81c0915e9c770a1cfaae2e8f44a0c65703927977a22fe58aef2f366b8be9a50da9376b46ae7562a82391386831febf359039ac326891bc58c0f2c34bdb6858859fc3cb4e392df65cbe2ec4f02c8425bcbdd1ee2562ab7d229d406d79a9c6fe4889c996c2f68d1fb5bbe3a5e867caa4249b934afd3ec71fdb088c54b15252f9dc1b909e121dbdc7d8a16cc00836652dd1f877ce363eed11467966f7ccb8f1a8d48146e69e04ad76a51937ad4f9cda209451eeca90dbdbd65441ce20fabfc8ce400fb4de136154b87a8b65c92740e9bb91d78521b261f806a2c6279c85ef6ac5fe1ea3117ff7c9f9832fc2aa6fab660082eb22344c1a3befe0628b6551f62a5014cd6194c42b8d475a50f2c9fb58c97e43ebb29005ed7fe54f0a4aa10074f1154152a9067d364dd7863fa082976a00db55b26b5ba0ea40eff48b90",
+		"f5ff810a41d4b34751e9942970d4c9f26b33f24689a4b1e4449b243490afc485af468ff01a42376b2bcb949b9f5e8d0b917f511a",
+		"a74271c184a82cb074c14b131fd91eb05870cb7c73c9e511ec8140bfe2f34089",
+		"2403fe689e239c2ed261b381",
+		"af9be893d5fd23aab42e6a2e59a8e7cb13d4f543db02af87cb0802bc1af7c717cd0093cc8244994cf21189146922b69927ffd5745e57118bea07a6afe7c21d952c13ab636b3c2e461dc9ffb3ae701175360156338be94b1fa7115799831019455cfaf5114010fe45f8fb9c77ec50fe06f2c5a32423edccb3b2210ee1200a78e1a3130c567542377827586ca8cf0c14c19fa1449a2cce9c039bb441b04e9c0a3f9a743b31c828032174fcdb7c894349aa68f5adf97dfe9294d24e6b5fed95eb994397883f58487bf5c57b0aea5268be7cee9efeab370f89805ebe5373ab2e93658fc078955ccf68b554dd5605005751ee8531c35ca5336a5d0ce273370c0dc9307779b86e96d2d1daf2620d67d43e1fb7800ccf250ca3c02eb74047c1d2a2bc7f29fff8320301694b80d0fd975f834337d00d5f0e4215044d52aa4ca21e6a9d7e03f186d7cdd5c48e3765dc926fb0a46bb0f05c50d9f69c9c507527a60366b7dc251aae1d6bb0d9c73735dcfab959f6fd4382fe2a1f6ad07affb0601bb9040f81b55a48f6a6c5f8ac4a2acc2b0c9a6c439198f7926460695fa11e0b0b017e39de5cf0d5d5f84d972b5eee7b5d1e0343b5485cd84b92ad892e5b23f3e803f5b363f2398c11c15be9f13e59922b0d49902dc8483fb142850b4226da2fb84e9b434a34f6bb67f575a9e57fde3354bc3077a876e260311bb2481bb139aa9af55df5074749fe532d7b8a554218a90cc7e7ac69db280bae5d55a174dfc8d325b9909a8da1016d4e162fe5ba70cf8726cdf291f5e47083d9929cd5e32021cbfd982fd0975f6f9baf4322b553cb3174b11c007559879f308419ff9e4e18eee8d3640cec8aea082b90f69cf3c7676c28af0265c24c91cd58a06513198892ce6ce1ab3ee9ac0a2e937b973a9cac06a039a54f8d994c13d42c59187f677352e5feb32a417aebec4d852b2595e7e67450e06dbd183279e3b63022a3813b37257b085bf8454d6890875a2950d20210a8df4f9da746722f62687e92f0e9efc3e5d526d65ccfbcc042fcac7964dbe147932c73924bdcdf62f9eae58d29e8567ffed90048bcf0566b952e986efeae4c477944af18bd243c3eccf8d88c06d07279adad037450cb8547a8aa0a74223f4851747c803cb21a2dd027e7080aed75038cdcecbc4639d87763cdd41829a1b72cedf0d722b180d0d492a5740ea7607b95f3201df352fb1ab28149124d2df5d5ec106867897b537302c3431402348f94d28eebc701ae1b49d10adedea38f1654fbc48885e59e6e6dfd413c6b5a97d8c35dfb07a6cdefe317bf61cf91",
+	},
+	{
+		"4aba5a776ace38b6e2578f0007e770d264e39c49f588ca3547ad2888365e3a811994f8836330394587c8458eb0b6611499fd5d8e8527c3cdd4ec550b4a8f8c632384e786b420cb3be911c999c72aad60270aefad31b27a069ecf11e95e9d4c81213308d554d3103de4d9d6ab04830c2b8dfbd8bead52c44c21d5357f72810193b5096809dc7846c1521c6c569f78812c735aea21acaf6dce84a24df7234e8ad857f3e1346b27f5bd436113e2da950e4deff96e9ba8db692c7db723a105ae795da15b910c8286cac6e7dda8c172b70f61b07dfd58596684d61da8772356f180f74c1103ce97cd947eab3d401df44f7fa4cc7cfc25e280fc002873237e64a375b0b4797f4b4613c9f150090f44588ee8250ae44aec6546ec8dba0f0c1eb281cf66fa4eb141617b32b28441f6ddcfdf02d9c34cc62893b2b64dc2c26b74433adb3e888c7fea07b19c8cf39269c2716b9c35b7625d4a141397d6d5034b193d2657c6b2d6b0ba874c467adeaf3d501ad985d13be21c4ff6b326cbb671e4f4973bba49116a0399b6491394f850e4122969e4644c00b442b3da0d6a4bf25ee22d182b3f822fd83878ebcc713cb183651a67ca66677ea81b58b685a3a8e385d5fbb0147ddfecb558d881c914324c794db443b31bc15c361912bbbcba9e418f99f2a416d190cb29684df27c7f3ff6ccf339800efbdc4514ee00d1a89f12373804db4fd66c1affd467f251e73147b3248033327b0f7790fd7861a51773dd4f78b89e4e24b94df9203f4a077091bb9411eec78dfe3e1dfbb67ea1cdf17e1d6936bbb75b74055495449e9cb52f5749404610cd444fea3f0568e0d35a5ef0c395ab7bf0208044b5c4e2517911a9c351efd31f33220972287253fbccb1eb8f46960a36b68a7a6b4f5cbdc86d668bbf555fde8881e7faa9594da425ff8fb54526bf7cdc4af64899530561c06bed7fc04c5d48cd4542779e901bc48fab79d4d13850ad8247f51b9afa7d5a656ada25b6376d837cb0fa1b4016dfcfc158a39290f43f133b352ed52fab2f951509bacb41284fbdd849d8185fb7e7200f8ab2a07ef2b3b927e18e568dbeeba2c7a66e08cebdc6a6069ebe6656a586652f3905ae2bb867529af6a827b494c97b3a378408f44aaefbe86c613e11e7a44020a9ee4b62569dfc4c462300daec7b1424ff1c1849ca1332367470475c14877cbe76c820cc651c18ab3f18852b93994f93b568dc7f7b0eb5f07ffc4c9384c851fa9071c6f68ddea1ccf627f889c0471c76aff9f52b07ab1b86a7671a2b2f6b25c0ddebb66ac95737bf7e2f493f7665b5265eaa5166556cecfdd3062802724ec24f3978b903d0f0c24e1f0b8d967142bccfed0d354279223f4c28684e9ab611e9ef89a3f25993b5a8b3c0354931780501651236a78b58e7d7814f251b053605f4c0a8e7193b9cc1ee5cf7378e6f3c8fd44ec57bd91e62b09fb1d6bab60cbfabcc6792e6a32ea7918a9ec9180d05a7e1546d5d2d8bbfde2a71b4e427c0a4d28d0b6473ae",
+		"921a401db90935c60edda8624a0590d5c46eff3522e35de2872f6f9394e24126fd8143b68a797c995624fba0298b75eef974",
+		"6a4d35ae03bf277f587da4541dcddf92bbd906dff45d5ff23c0f60ae53b062be",
+		"231b5780fedfb06d724450b3",
+		"ba40968282d98849b19d867f8b564ea5a81d657516099362926bca4cb6e9ae02719d10c8061f53008c727a0eeea5e1e36c9e55c117e9434e213316c96840231a1e356b254a9981d4a6ca3c66cfc61018bcaade1a4486506559e6aa3a86bac980d391d835fd5ded98d10f1394d84bf1bbf2cd3397890d704154802f7864ecc753db782fd3d19213ae65ace4770e1bacf32d61c6730aa5adcab4d7e2e437888c11c29abba4890a17a00f67a53b660becd94092df0598df5ac57326f6860593a519e28bd4a39f6481e1a4748881fd5f0456a3cd9f28d1d1e78dc64030cbd8fdb2c5abdab3f13d6ccccd187e71e989f8c486929efcdbf2a763effa95af62db5cef95e9081b818275c69267022fda4b7fdb8c650b491a785b03d4d0186625962b6326ec3f4e176373da4dc1f83a14815adf82c6bffa7c6967d77528d0249754bb4d17656bc4a89449b16152a4a1aea7eb0054a8892f271138971507d2f3b237ba5b620f444544e4a8c2b1ab4f9168762c27478c9f776c47ee2e9ff05bfa35ed127f0cabe7cc053640bb8aa01f8359b74bf89ef43ca94c48fcd201eae39d1835957eeccd6b3a852f4e1bbfef9a469f42c764481ff8408fe5871afeeae7676b58f4202199aad50a596626dff97c8e60d750cc59da9f595ce12ce9afdce14481cb1e39994de8fe4cce07845110d6703dc59d34734e93e9e57e1c52d61f44143a2d290220a4bad5098d098ee65ea4b6757d8a9bf5485aa3d697a7826d4a285186f5da10eff707566c23c6a15033365bcb498c44487c72d96402d1834753fdbf86770239761f03e0dc8963766441da99c0813e4f1df5a1d018c8799861a396562eb24ce305ca15f4022d83ea3c56b68d9a7ceac4742ec0ce50f4d36273df26005ec2b051fa071b319be2d8a5ed26eb75bc1ea83761b8454db234d15d84d6706cd178981c1f156e6d28f774aee3e9a4fade022e71b52b50aa532b8bc7fe464f22d6eb169c69671875d614e987658820c2f584a4fea3008afdcbb646dba3d69020fbf503f121be3480344db23efdda0d255aa058c3ff66abd3a5fe35db977521608bba7eddae72ae801f4fbb12a1de4133039e046ceb8db87e465e5ede1d79a08c857d59076d7ff858942c31e15cbbdae6fc15c3f9545a0825d6ff8583c0aba8a7d143d27b93f6caefb98c0d83bd8715abcab2a49087f55a9daf9090eacdf45be08ad80b5df5070e1719f68c4cc8f8711083f0f7823a09ec092f22df95fe9e95114fdf82a3f6eed0bfc9c0aa65222609442776154a474dbc9e662cd5dce66846572e52417ee5d7eb59287d07ef60a9537fe1f85c7fa74fe84dea0da235ac7574335e6649b54a6bd33397df4bf4a7976c4ab868aa702766d2bc8d2c82c2d1c2653fc8428b8d1e61852ac185a3a0b416dbcf8eb54c44967ff43c44f2b32c6d4a9dbf2c2f3a587b430aef50f0375cdb4c1b319ac9aca486d9bb321141b065f52f7b6decaf1985531ca7bbc3772a561eb1efb8a6297075920bc432131a5b211bf25e35fa31e12833bc77a9de14c7",
+	},
+	{
+		"6c0056937faf1023032df1e2bfacbbc58bb022eba25ffa020d4eb26f0caf0678af5d0b2f0c1b520f4843f107f0adcc7b5dee66ff4d61025bafb4cabb64d133132e3e423a599549a1d83aa8c8e774444462aa44b00b460bbafad5755ea6a872d4e6b40e3f4957e0229288ea79fc2ebe5fd9020fe4481a9f42ef14a196bd136aa3c779e311d0c333624c1ddc484c9aa7259cb609e4d0a826c0bdc7567adac01da23900b30ac4e66c100348584fe200747eb67e6287268947e3509d5d2b5d7bcd977b80a13f660d4f6956a8b938a82db75eab19e5d2a22cb5f3c9131e278eebbe096b5f49d16c983ac240f3fbe821b247cccb2c9e6e59546122677f49f56a07fed56647a6d3e0e09520d49009f54250c10e7c607cd5b4ddf81b5c4110c6490e9baf56418236211856f5a85feaebafacf92c0c7501c052f9dbae3beb7484f90f334f50b68571cedc67763b5161ebfd5a1709cf18c92112a4cf4d8f43d1895204d8a2ba5e14883a7bff75cc6060cabb77d38a909daca2417befd1bfc05a11c432b47f90c807ca4306400f67a0d92218adaca84a584a8bd4395c93f9b6a4bde9583c79204444634a8473b1244cd33cf980e443d82ecfac672b3f60e2e41ecb3c5a445d9e88c0e90c339a31806e6d79ee52bdc6808c73e8b7b24899966664d3c1a9305f31f0483e24e36fa451dc1d3f2eda05af6678971e2bdfb7c1461c9407c5c466f6b5af34d992a37de3809a22ae75275ddba0f4f9cbd4b18c1acd212192e587889a36bd73c860f0abe08bcd8f00f5ecdb95e1d560b586eccf530df0e5f3776d8dae2a01768bf1226b7ceffa7ce4e75879c82dd97db3c64c06d33cebc6b35854618355d80e46fa79c3e9743fce5b974723c421a077e7ec7dba286881dbc1d53d442a1552700fcb33f83f73c69a0a0ebdcf2f5d461649c4d0712c514ded268a31509f83c1ae4ff4a68e676d29727be641aa4487c08d4b90ff78e24c6508d69759751a1a23690ec9f8763621e8b107295b4bb01bd9fcacd8748e24d996fa70ef6f8b0992f4185bec8e920d7643159f9f604fba394b6611bff435998b2f097a9e948430899c8c752a1e83a061983f00f88ebb32da214399167932a1a83c1b47d09f77593b03cf6521520583ea4483e2d33e14ad60584676d1791779b532c085d238df0d3bae735d0078e0eabd63cc90a2e13d023983780afc8f83b1c14437937c16a1b7c41414c48cf4ae49587ad9fa5b16fc949a749e96032248c4667f58e295f999590dae1d99a2cbe3fa45bcf4a1d3f0356d64d40367f64b2c5cca843e5f7dd7b88a85d52328a00622e6c317879607bc036c9006d38652ffe21c83207c00f8348a7d0aaea5aab4c89077df170de6d41052641726eb6925cd85a9ee01a9e636346340e209ea96d17b0eb0921b96662ce9cb430fb6ac348331dd7133875769bbbba99dc49333950e4145a15ddb0789c4d2ccd38878080ca9e57ddc6cd5452790eec45482f8e990392e319609391fce0beba19463a9a00d8f1de9fbf22f23821de7d69fdfbf3019ed61aff79acfc5a6ba663a1e10da2b9ff7149aea43bd6c61a543008402309df0924de72c1cacd2d6120cf422e61fc1de345cc8771934d8be77d9437a09e06a9b2d51c849fd9a200fa714328d34f36b684f33df6968b827df916a599a4bc3367814fec21198e2213ff653cd2a463892966c72ffd42a26b3bb91",
+		"0d55dcd08b54e58916f622f81761ef6a2e19b167ac47d3",
+		"e42e1d6d44138f3d2bf12c951f454686f18d590fd30057405b5a3dc2b317fa97",
+		"1e46a7486c5a03fd6758d938",
+		"fd3c1fac10cc82e49235fd57f5aea0ee7a7bd6d539b138d4b3fb623aee591615c1a61228ef9673113a3a90a3687a12d4c6367d5f7bc67d422fdc4106455084d79c2c42c5e86368dd164bcbce7925bfffe7d96c13a2f49aac8e9d1ada3554e3fdc21aab00455a0f33b0c1fdea91b3588e7ad301bfccf9940027332fbdf966463491f7a33c093e0a13831ea9d2183294f89f414cf7b5876af04fa68d594430194429df74fa5915394427259e832bc545c13400aef6cf16620d48280798a6e49773c9316d79fa1dc758e54cde2e2cdb856092d83f4e9b698385cb976fd6cc2538abe055273a5b34a784182ea5e7d3ac9019a05de5e5afe4308a7ed2d363cd50ed6a52df1c616e4a82f607ced768445d13ae4884f2ae1f9fd8313924e8a1a8a23905c92eb231f638dfa6f4cb27bbb9844e05afbbe2ca4d1a3b3a5b371bf33c9ab6f82a7387d61cf8bf662097624145a983839b0cb9f4bd07556800b4054fb3d0bac94f44bcc9b4ac49c39f5571fac4e02ff09f08b3ed5add4bf8bba934e9feb773c0590b45c45fa036382f3fe9782ad19107d4630321e414b7b442b64f18fdd5219039e5740f34b3ce8925d1afe8a39e35ce8db086060bab63b9720700499f82db19a62897c6d845389461260303f9cf2bc7235a898b4620c2191ef05604a5c8c783d58009533a86b27c12b0772635d34ac53993ccf174c9087073e5e69b26c0c3d9f768507ac4d4e2af847b65e3a6e1b7a6dafb0aefc190871cdae6c60f0b1d6137c351d4cb211870791cf4cb8af2ea446f6401eb9ec8a5bcebccce898d1dfb13454df6b35b81ed6d7637e6e261e004080c60944f3a08e8e5fc7e2e4939e7c2607c8cf07d1d10883ba3ad43e2611826f245df571857ae0a7a867df9659f2082c19f94ce400132e48c7f8de2b102c7f83ba5cd1e785597a0ba0d73bb81bba0c00300d4bcd6ec25fb73105a46122873bfa729c0979d8d314ab7ea52391aabab513dbfd1cf01c2990c0a3612f4511c2bcf0f5a07e659a881a7f99c3f1fc4a46e66904427fe26a4a80a904c047d090c861a075c0ae4e29bfbc18b9620aaa42237f4c6fa76ee7491ee638ab5f1cf0b440759828e1ec519679efc776eb1468999a00f667e87199ad6891e98b95fb682e02517b024a6bb803ed23c944010cb7bad0733eccc12d6ab6030c6e88d510ce92e2f98fdcfaa1e37e41fbfb4e99589c0e8efbefd40473db42b3a73b57b22a2f8c9bdaab16831f1b117dd83a77dd01ee8d0c2e92203adb670f4fd65e618823ad196220d70e014c1aafd8863797c61c16382c2600062683ed3a180c70891717c52da15191b02f25d1715ebf33a5e6037092421989c942082f4b836423cc3e976c9bcda185de36f06265dfc250a27d2de0bc48c73b3bff704f3b386f962522f572108458bdb283c6ab3fd33b3ac13a406268fd5d97e17db9c0f780b4b2a8f761d15a4d8b3a0cd73357ecf4d26a6492ee069f19325823ef50bcb2f73326719a57b67eeef506fe8915a1b1ba1a637592268257b91e9c7c5d33cdd947967efc1952005d82ccef9a3ad7ef8ffbb6b658983d64c51242ba53f8f8963245b87a25aa9324c527e53f8c11d55f30aab598401589acd13f090541b3b057b162190f27910718b02a6b8ddbb8ca6cf40bf0d2848f4b76341bd5e78f476862bcdbe2d1bac84c0566fb45b21388221ecd8483d99fe603646b1a9f38a49230cf4dbe5d7883d73eece01bf",
+	},
+	{
+		"04892b94c65685f2eba438322b29bf8439938590d3e0eb10a29e279d356cb439f6dfcdbc3552af21f7e753221012a649a52bda780bc589ae63b04b981dffd113df9fcf14f17e35e865880a769bb1bf40dc99b9e85e4296c1f2e1590fe02b22bfcaf2d4bb7009a4d692ae4c2d5f0b6d3ca526240368bac55b9b1e6a7b498d3b137f0fcfef1873c5aa2111d7811d45bdc26be1c5d49b8a2f36a999b1f226ec06a5fbd59514485abe696c96ea89dba74b4688101a239b495944e30b3609f73caff3114407599ec5c30a5bad933655de7dddef97018ae15acec46504cd5d417c5052c057ac5f1c6f69781cfdae71db2b4fcac35054a4aa22681027356d68b2bdba721466d130d53ba8f23857631382b2de450232e9ad5551bd7c872ae439e79eabfb057d2bdab8d4ccf02b3003ade2e1f3e514dc92692e4fe5b579c9ee6067995b6c168647ce5a13be8543c23326a3260bb7029d2030ec05e565ced3c5366d20a283a6e95201fd108640d2b96676df712de20e4e12fa53f85f22cb24583844fabcebe40eece11e7221f12c88670bf994ed08e2000236f86258c386b0fccbaab8b68ec6a26fe41491d540193c4c12d1391ab3391de9317f41f505f1f1d09ca9862a6f289a533d2b297d4465c956360371ea3c8ed36e0d1563120654e3a2fd69cd6c9267bfcf92e84cd64e162c84199d6e552b42c33857264b5d7a2e007797cde32934a3f8c68b459cd95bc85e7466ccc9910e8dca65b315c32e43c3a5da908904c42cfc8ab74126919ceeef1054bbdae6ca67b02f1ac5f24808b5eee24577e609a3e3935a24b9ebc1a8dad1fc96abe26012928f2d5782755f3763427dda28867d0b1ad830d3c3f17b9ec278346e5a9480ed23ad44a523a4dd86e65a610ee0de1afab64ace7a3b4918fdc14c6b1ce0ec0903994da9bcf18643d7e0a4e6c08200bb394a89b385d2cb829417eeb0f7dab9fa7306a330f82973cf0917b5ca99b585d2ff0e8584e050077467f5245ecfdd5942e4fc72dc26e5ab2ffc61f996167e68168cee9a6d3ea1e1a696060465e35da8c75a1aa380004faffcb0a992c627fbdcb4e97721271802cdaf08d214ec2fbcb389d75709d7a6b9d35662661c8961f93d4a705e7188613f3769114c55400809cadf60d3b6068c8a5ceef078785171b59be1140c6a754ba1de5ced349df63d67d59d3a8ca3c716ffb506772d57e9e3f2caf7fe346c4ad64aa6c37e43b9bbaa8f58e51bfbac31fa6137728f8e5b728025697e5ad5c8301f6ff39eb2ad595d3cb24257adee88a84fbf1ade4d7550cd9ab94bf48e1424ae83184c35c5a5920157d45805c2e0ad129fc7f0ec3c41b9d6fa04cb8918ef379b0783d1cc2863cd80382585fa05320ca4f9fd90353e490b384ed6c166c6f802cd7bd39aa43667246e8da96992db7537d472c709b01114e95febaac5b1a3c77e1e9a18c2d180e63f0d8fa89f6a1ed63e909e4741af5c2a0e47d4d3f8779b7696358f58060f3f461cceeebb390c92779d30bfdedf1b08ed62dcc05a545bd0ea915f42976e81dd8a50cc4689d8d8007508bf53e7da5bd43c3894968cf0677681c6b818353af6bf8ac205139add1310e5d363ccadbfa0eaf735808325e7f9a6aeb1bee3ebb4a27576a88811859c216b6f84371c43d8063a0d87bd326eb6d81c6896ff534ba2c9c14a51d2cfedf33a5c787279bb4a7ff65706b389756a6191d2f791254233ee047d40d64c2dca878a42f903fd4382f39a89a723fe11848fe37b2008be53f7c2d037981d6462a4eea49df1a2e074957afd3c9dfb4d218a309cab395afe301ccf",
+		"67b5eccb1790babc2dab5e0d1ff3871c3024177d45a2ae",
+		"259603e1c3af3fd0ce3257eb627b02e0c0a48ea2f175de3d8c36570a445e5369",
+		"e14de73c4b17581a7e0d0649",
+		"33522e67ef932da5fa8abe628b51f3abd5049951dbc982ea95b7769652d4830c588fa45e3fcff094c8602b9008d7b2f9bf6c1c4a8cfb515401c7c44a7ec42ccb967722a710199e121a41160b1ec581507e9bd2e2e506b10c4b5a8d6977435aa08e27504957cd49e756e1574c4ccbbdde937de35128b7ee3455d2e665c596c2e97c253c94e405f85eb5de84874c099b4a97eb8f492d28f2e4bc64b228dd5984e76ca08376d7f1355ba8e0fa60fca96635075417d8b436278e0fb91e3bfc7d61ca8c7407086933c061b2d318f46f352099e1d317d6c44098539d1d2c1b7894db668e7a82ff991864fae236570cc420a4229883f1e2242d05aa07e175bc6abe11cc643cf1786a4456a2de8c066fb1a70fe387f149ffbe8cca7b110e256fd0c09b1d3bd7381cfa82fa700c8db1e79809ccf75ea52d0b349264557046e8703a191ddaace00ccfc513db5e78810eaac0a99d7bb1a5725e722d4e595216a0e12f3a7aab2e623ea9e1dad06169914bcd51b643016fea7dc3f2743b1e65877f1fd5581bee5ef206d86494a587ec8462a170746fcedb2c9f99090674ee687382711b4610ddac599732453dc063518aa36f5b4129098fb9fddc02eb8f8cfc2fdf0d904ef4d6d06014f977b29d0e9aab4044ce9c662a18b1a8db1ceea97854e90704430fe9b1046b221b27ac79054fcc68c3abd6fab7da66e255ff0cbd0506c852e961e619615c944cd9a05c25abb63742f5da7bd9939feb0f2f2208c8ce82f551a9d4d70e935dad018e3e4e6998e39670221601c3e34716ba75eb4e2fdf53c4d471c444330514986de45cf44d77f793c17e36a271fc65e6bf08943aef4c66547dc310c7a430e3fe7a54898de48f69f282f52bbdc4daabdb325cec7ab66fce1aea4e2fd932dc1a316c821f5220ea437447feae2fa478adade7cd515a27d8c132d0299b3ca1bc8516c9d9e7c65c38c238c69f03e104eb42a29cacc8d79b808ea6fb233a5056201e3697f81a2d49ccd8b8efd1ab0fd407c16a210767d1d3ca798ee53a4bbf1ce5090d321b1a64fc2c5f013c23829f5b0d2737936ca71595a1d02711c8a7b0e74654e5d76376ae26977dd49c68e3c0a7b36e047d44be42d732c31f681bd7b1b4b339f004ecd847960377acd005debfab13d0fb88355025877630aff753a7cfddf6851e8bcc8ec37b8f9149830f47e6b601098b2ba19a4c0808e31e8927b2525cb82bfddc9b4bcba2b46bbe768ee278fb89010243d16f9679f5ba4f13cfe76b5beb16c7b28daf99b0873098115c2233ee3402ac0f6c899a2cfcc83b2ccc06676999ad48017c4ace507080a26501993327ebdcbd1e2eaaaa99f4998b716cd9e36eb26b4573a03fd1d18047198fdf675ef4f979864ac85d230a011c69d8b6c45e9efbdc2a03f195c9731b4cefa60208ba845c0978e73d082bf6d6a513b93dc805a4f5973f4158f60a200167ca88704a15ac5ab1f38ed455a426f7c6a96b6bfea2ebc1ae1247cfe5ff29ee81bdbcb53b03b89568bae9a6f311d2b20e31c2d91bd18fd93a37be266d0de8015d52e325f78356dea0b77cc76f28e0f06e4ec705d1328340013a77b0b6196f44b7712fff4ae0ac7f6afab9456a95012b7c6d387285487476d189977e28f6c9d1a3f736320d61302c2d627d5a7ac8cde4988056b55eeba27efe7e640f94c115762ad5849423ae138c76f15b47bd2a2bde2c492489b7980aaf1c4e32a155f858d7be4fcd0f8a18e7b5d97c5a08d7885d6d56222ef49542c7f80498a14a8eed1c092543aac3439966d5b5d0cb9e602f4fd795c09d652b64f9ab67e38f48c88d18e30a9774f37e9c77b7a94cc7310d",
+	},
+	{
+		"4ab8068988d4bbe0bf1e5bc2fe1c668cbe58019c958dd2ec97164aea7f3f41c9f747527f1c0e5fdb2cbb9d2ad704b6955cb731f14403dddb1a28c5996707635e4eb5dd6ac33d46eff8e319cfe7cf6443869534ca9812a5b23a6b4ca172afffc064dc2b28197117115431e03c00447f87d9b45172c6f724006270a1d41fa094847cbfac9630c3a785f488c1f5cc407ca6f4cd18bac43cba26ad5bfaccfb8f50784efc0e7fc0b504b43dc5a90a0525b0faf3c8b4b7046fdeb1cad87ec667ce3eb6cb4c358b01393f3ffee949030ef9fd01c1b2b9c5219777eb6ff5b1d7c3ef8d8e3bc2193dfb597cf942c5fc50befa527fac0b44cda2bbb811b06ae87459750295371cd232754e2bb7132807d1225950ce64949b0650531800bd0074177677acad937ee008cc0bbfdf33c6b0552000238494be8be412a3e5cfa359e619d092c76310a76bdcb22abbe6f16b3b116b5f95001d20e42fc3c9ff6723e580f378475788eec265a1ed2087de8cc2eff72184f73fa5dc6e68a56dcfc85350bccb97135386d5b827c2d9aea065708f5c921454d1b9303f21d5adf19e00415acbd86d1e5e42d78505b033a515a435713649c50702f54623cbf31469f355c3be2e30dd8c72b4127764451d79e952ea1f9bb0269da56dc07060d5d9542a9c1258ccefe53fa3f7b6073cd38026256b45c01b6c5dc0d91e3139f30a8d1da7a076738f5bb23352693a8e3cbbb46226fa22416680013f9e3278913d06aee4a62457357f0a68d173a360af5e1411840e34c574b4c6b352f92ce33632911ad8b6710d357b7607ee19679e777baffb8ae3c0fe9786b2e97fdeccb5105ecfe81441f549bc6b50ab84b749fb33f8f6bddcb6bb733d6d5dbc4b29725b8741439b8239e53fa435ea29ed3324202b1bdd07d1987b0e06d8cb51013dad897ef02401290940ce3f2af72c5d1b4c8836299008c10b16c7e3e119e41ec66d9db6929ee09bdeaeda08a50665c052edf77b7dff3d8815046bf71d5015e3bdb29a4f507aeb2e28c536cdcc9b8d1e89849a0683d78f99dbfa90f94aa5dc08587657a8f042d718080de5d4a973f232f78c387b63c7143fc2a4380c491414a18b6c4a7bae2194b62e798ad7ec7d09e409425f6d0973accb17e4d860f8ec0283584cff076d93bd9b0c4873f9c57cddcebe3c3bc8afe793c6cb6b26c4582847b07446b7e1d9757de6bdf0df826cbc502bf88cf3a773866d3ff293034abc4afa3091b2126a278f50e47f2f66ebebb616e342098ab690f7f5828bf8cc4742c677d378893e9f188e8397bee983a9a0998de2a31798330f8db59a8581e1c847589bc0e2d95ffa68e39226cc15cf6cae5c4f5174e7848375391dfabafec202565ec2383721339f04c5c5d1da953d88f18cda65745ee8e99805e35203a6545a0416923b38c5db3c8aa00d64354bed27d7c78c4b257534bd7a18107ebe64d8c27b6afdb330d8efba79fd1fae480cd51fd3626bf8d79fb651b7c6cf752aa737a5123558420d48fc86451b358d270aacfa6c17f343b7a9956e6f64e4990c1b3f1e5097605edf5ce4247819b19f245e9a90758dd42c36699ba5cd7f3ed99a7df7eb155749f4b42d192c47cacb6b2865fb9ef2cfca283865cd06e40cdf7f89d76a9e2eb393e2e0ac0e2776da929f3f8e3d325d075a966d289c51347bd0bd523a5c81edef63ce9b72f5114c88b08b16edbd73f518096240a5b37421843173be8df4ac7c587a17ca6f2916f7d9a10dc75f81bc778a1eb730d12b51555cc414eab9c066113a7edba9a7f1a18092ae47f12f0368ba211feaf34a3b48a7ff5c91b81cf7c95675a4001c95a19d284fe4197fe8823909a123fcec5e45935da12416be1bdf14918414ad19b54a41052f5b8417ddbd207ee01d6a3e62fd9b0321b1c13d91d6ce15ea7b2ea0c670a5f5cb290ca8e62c26c6499104ab8e9fafb05170ede246bbf7313625d1fc9576f1609ffd08852a2f4b73c04f1f4eeecefe3f3eeb2185a618b6dd3e87d9d3fdcb349cc83c21f26b6c662bbb857aa95378e991640a160a23cce76153c134508c68ec54a5",
+		"0d471079ad3c3432b6de852ec71692d12d9df4f984554d458a9dd1f28a2697976da8111ae4454c9a23d1c8eae75bbc14f8b00e7c065bc290f8938282b91a1a26c22b40a6708c40945d087e45633a595beb67d8f1c29a81",
+		"f3dac58738ce057d3140d68d2b3e651c00ff9dbb2ca0f913be50219dd36f23c6",
+		"bb2d033de71d570ddf824e85",
+		"238c4e6be84bfb151557327095c88f6dc2889bce2d6f0329e0c42a5cd7554ab16c8b5a4db26eab30f519c24766b1085e11d40823053ca77adfe2af387b4dcde12bc38502229510606ff086265f45b1087375dc4a022eb0b641101c74ad566ab6f230133b7aa61861aa8202b67beddc30dda506691a42032357010d45adc7ee633b536a2fefb3b2143837bb46db04f66a6e2bc628d6041b3d306ff78e96205ab66847036efa1fb6e6a387cf8d5a105738be7163df9da0db48e3d8fd6a786f0f887968e180ad6888e110fb3d7919c42a7f8c92491d795c813f30ea645fafcddf877f5035f133f864fd0ba1415b3d698f2349ebe03d9e76610355e7fc23221c5c72b1b2628a40b14badf93288fc4abeaff5306d274f21938650ab236a39496d3f8a6e9086eac058e365d4335b51eafac813f9175bb7bebb75605909ec3fde6515694e119f7b6e96aa1d6d6454c3a7dddeacc83bf0c1f5f6c2a9dd2f460f3e5b074a33b8d7904e6988ae43a22a87f0933f812e45c4c518bf83e606bad4c3c55422ab2207e9d3cfcbc5819049f55e35b9663273d9d3a6f8a897fa38b0dca77eb6c344290cc007b68d913187f2cd480a40262623a4e95d90d5701ac2b9d858d70a27f0672f919c2ded1fb89134ac9a8ba6ac62931c832372abb70e811dc50cce264ece65e87338231f18ac007c5f68f3b1c5904ffbb2e1dc361d53914917770d66afe28c547d8cd5896d892cbdadc34cd6af348c93bdb8b072f38b085361e62ded7a38b4368824c759ec7d2cf4caddb9191e5deedc8b8388bc4ba2c0672321bcda3a7343c9ea71ef03750912f35624d81da5fa8a6ee676c4efd99d0c7258b844ded7b35d8c8233a316b508d79c7c0b3edabad5db9543615179b1c111bfd78b79327ac5b4155336d670baa592d441c810cb1b7c07f3d35473a45b57e780b7d997782aeecfc0363976fb608d6967844ed00b63ba75996054d090aeb605c195b1ff86f9d9ab5892d27632cbb59c06b3ccd69d33ed5dea9398f00b7c6404fcfe2fcb5924e4cb75cbcae0a1b084ea8b15eaa5847431e9ab70e4afe15b4c82239f6165e243e3b76d6c91d23b16edecad8bcb16898641f8e323671452034a8ec9b42b29cec0db210bad0444f1c5bf3505cc41d514d5a270d556f0a34333bd06cd6509ba253a6ba7a6db8f1a60c99f0c3d566a038a72f1271a178cc3ff890b0df1e7438c0c1a12d9873643e2d7bfeb92379545de50834abe2a345faf7ca49beeab87ee516dd8598b71196b8cdb15e7200cb5bd814338babd74c565faaf33d9a8ed4209b417345a1ae611880ea22ab2e894d5d14a28fe3835d3b2718125f0e6daabd85327455646290ceab89e579ed5e1d72a0172e4a6d8da70290b5022c941f3866f96cc4218de5d2622d13af6dab15760a1ec5d10918267f9585284058aba611ba07b1d5711cef505869831699bedc2b190fe1d578814065c91d87a8c8dc9b0d4dae0c80cd241f0bda3a6d5e714c894b7a48b1e5eed4555f103eb03c9db30efcb855df422d7451a6d70f28174c7ebff536dd2cd2891f6c3f264d632ca924c4e0d84b37cf8e06e6f2e29efac6cf008cc27f062441278dbc9f09cf44987e0e9ca088a48437b0b89efb9cf00d3d0c5fb449fd4b64e21dc48cf300c2d80a502cb583219f1881e78e647783d91dd2f3b389a1594eefd8ea07d4786f983d13e33cf7a34e4c9a0ec4b791f1666a4eef4e63bde7a241f49b5cf615888bd8130743bc8a6d502bfc73ab64d1184ead9a611832b7e24483a1a0fc475d9ff6166b86a18a3dc96910ff182cf326456c4461ce8acb3467f801890eaf1ce0b24791da9c650876e718c0bf43c475174f9712dd4a228695e8f8b2b23fc4a06358b4a6a8e1afa87a0280c3e098f218f7a6d6bd716f8c105a7eb799ba0220837fa5a96c8a22a826a6f7ea9d7216a24acbc7b0133210cc17c8190507badb421bc54997ff9340cdc1ee415126ac46a4fec9fee12d40f06300f7e397b228250f36d6f0d2ddad5fe1898ea690e4c7cc3a116a70bfaf6d2dc996753fffae40ba5280b8356b7ab4ffbc914ec74eaa070581fdd1d9e5aa2",
+	},
+	{
+		"4d81b652fee892d575bd13dad913d976cf0517c819d5183a72eba995b1f27efe743451721ce34791a15a6b7a6e44f13d4a080563dd1d9d4f0946e5ba3863b9ac970a1fb4ed66458ec1b1092ff5fa6c3f0271a2df8e3f2e97851352be760b6a0e1589c202f00791b1b89ae0ae944ced96bd90754bcfa3e355b735132d407d3b5507fd57f705e8a8bd82886b16d459ac91e921dcb8c5bf0d7cf420a9349ee589a5e2e19ce7c944a54ccc1062a0690f3152300d0bf5cd1871c1391bf6d7007f7ce26018ca2a5c6f76287fd8c8e9e7f93b1806460dd35f7f95989a8b6f9a0aeb7c6b0346955fb50b8735e34f1ecb4859e34ea0f022ff6fb797094206a34cf120b7f4664c531c57da513b296f0671c8e9bf68d9e1674998fe52da04f627f516dee97c2b3c988216e9bd3f58c3b021ac70898651f1cfeaef21c4f417ebe92dcad3aaf50f4277262c356584f816a5a5862f2bd720fac10f1b86033371ed603bc00a30cf4da8f579dd5bfdd571a37af7d2a5cef29f9001bb1605ee87f24ec3b259f381a69b771f78d21c4e43bfc83a916e08830d9885c8ae8ab6367c05f92e5eecaf0488262300f83f4e3bff177590857e149216995bc52311fb9f16f4cd74e07c7868a39b699bdbb7d7dace4c6a53ca7ee6e11741a63a52a1d96995a6dd752356dec6f14761ccfe38a6cd8511204f8f0630a747d6e19a77bb030c61e0828436604a28a7acf4a5e49b7269ac93b93b99e9e2e1c0c47b377f7e44e05ec6659526afbdcd5bb172404ce5a9f8786234114c16f20cda6d4359eb873a4a4d9fdf734e9c40aa4db3ea9a98939210f6c62142dd144eb78191116d194bb766ea96da38321ae27fcdcc196560ac75567297984fabe6072c771899906350f74de6d18518eb6898b934b11e945d94ead02b821fd6682602e03e9c70a1ec67eed33874eb24dc83dd1035fba5928f8f62ba1282907aa8935ae72fcb881b3277ee6bebda8fc75d6cd792677c25f70c87b11e094298b2d5f39904be211ff0980e5b83e8ea4a455622d8be9efdb5aa8466c88ea861407d54d98112faa10293af5e16974861dc9f83b45d21b112cc367894c421f5049e49dd205bd7c15e6a70bc810704e2e3a3659800864912527f8be743acdc474a26246a81fc2bdf669b9be7a2a0c986432e1e44b5675607e7e1ee2a8dcb72d8f1964272926e52f909ede0ac8daa32d1d850158db76b959e4d83c9da4e3bb23fd1f5b26463045d6cf13d187fe74a50c09a654d52d0e2f01d66b9f8b4f4aaf4c69fa62a02aa876f9bc4871aacd26a6c6ccfb9bea09cafbd0268b5b65d60aa23ff504d02fad4719698f8b044ca1bb037ea6af58a06a448080dfdbe6a5d698d5db9da5fb4aed04a46c8fa8b93153bca00a5bf8aab64d2b371d072db2ddb688a9442e948f0b99236828dc115a2fddfa2a29e2d4e02ff0173cf734efd4eb687e3f8712be82abe1fac4be0c1eddda090803fbdce41bccfb58c43038991ba1074b281a09bac5eba58a99a1a9678ba26f8f9e3c63ba095f02cd8f3b56aadc5de60477efbf3dcb54b854f651cc72042bf19268554c61b44f2f338a75de56c3c45b3ba40a697f5f21c4557380c777bcc91a151e5676c2a59606200bd476cf98d20b4cdc64bc3b8670810a014871be018bc32fe239e287cfe8a7cbcd1e8b55e08692ccfb4ef871cf797bc0b1fd7ec37931e35b6bc5d32bbe7ae77b9962c179f96436e4a32f566298d2235acf921e38c3f1942fb7674b65e222d17b95a2e58f072c63aa4bba1ce48c303f4bd24d84963f18c5e670015c52342dcdc9c0b348c7dfac721b568effe2bf2f2e816ca3279bbbed823beede8e12fc5bdccd0f1584deb1f6ea1875e9fb350919b675ccde0178bb83a4aa5232bd5e8e9a1b8daf905c6197367a0d106532297ef89f3bc690b48224592c768bd9c50a63d0881370d475081aef052b444744b33fd3fef674a37898fc950f887ed482d2a51ae615ef5b1dfa3a23257e6a6a319a4e2080b2c4094bb09e4b390d1fcbefc4d6c5dab620f8b05b1bd5d976300b007e2b8120ef8a6c9028b7d925c795058c6bdb6711fc5fc2476b9810d1d81bd24637537716edd3b7068b802c531531df710d3682f9865530e1ed51b3b56d860ba4e972bbc74662cdd1e2ea24f81bf469193afc02b14143a32e9556e3f2ecef97c65",
+		"2538d98b64b6aa9258f9141840a5abef66d6037a10356366a3a294719c10d6c148b04cac66f63ebff052d730f8821f5e5822d869573bcffbdd636c7973433abbf38767597da5186df8ef9df071bc4ecade2633366102313e659db8d8e0f293d379fa2df79f456497",
+		"a5049b0aa153e282457555bf6f82b60fc81aa6fd1c2ea3db031478ffb74b5b5d",
+		"350287a6bed5709dfba3d35c",
+		"849670914f5fe318eb01e8849e536374ec11e813acdbbe6a5e82a506f6aef4f916a3a7fb2e41db3adf990175e21f2386d1805af9bbc32a6ac156b13b1a9505958f68599019c4b7297314229c467114754277b10e9f49a4d12837ef24184629c8902ebe2a23f740dc826b01f8963d47100bf617b314835e436104eb207fa9a1079b8feba06d9369b9aa8222d38d87096b73678bc5db9a1add59394530e678b6ec93a80efc6e8320f2909e3e891306d69b016ade0d30cde64c2c903b401f9d01a29b5cb8619dc68ad6c21900b365a6b657f7d9ca4c145fe598a94eeea741e20a9329996b17aba5d7115c93623f2f5d6927068d0f190b49eb885429d771bbbb3980e9293e4d664a71c3cb629d869dc97e58fc3d328331b11df19a38d61e1705ec4c3d779168abe049e9d675337ff658e00d2d610c8f227d1341d1c41f1c01d8b5d83c4b1b30ae4318da9822f46402ee8cd5cfe9f3f22d90a5ec2d0aaa0baa85e10f5295cc6005c5a0887287b0c867a23da1a4c2196f91fe0bd4f0db1ab324c26fe6088d7583f3cd052b7f6fca38e8b21f98fd07fe78b7657da1f586f1fbd3d2b4079e20f21dccc0d269d53a29deb7c7fb63cc291d1d2c50ff163e08ce612310d3bd622f2416e193078ce4e1463f8a3490578af96ca98e665468281f1af9117a2ed23367df19b570885de9d6594f09aaba4090bdd1079720b08d54311793c97bbe14433b031c865b059cb4f75db74779b82c4f83eb4bd829c62eab995027b548063d7cab7d1a6f9642da6cf7181c0ac71594b97fc2c84b1768f81eb287091f63c76623c61e7ba90c922c74d46b9ae5d8094d9752bc1e8020a82601c356a201e0473d540053c707a88f4baad37826152dd245c4cee6b0019583c61e4327fdf6bdcae53584cdba8a503b835bfb5df9d649705fcc1f09376eec96c3da1e105accc1cbc21d90f527041a9beb85f8cbb1ee8db798838bb45374b741618f83b5d0801a3af2f640abdbe74ec3dc15d6711b4c1480aa8d6084defba82ed221ba359c9744705c4feee0955c27ef468cbb816694516f73fb541e0ad4ccf99ec8b67ef090505d1f7c4c3a8ed7e291c820261f12d92bbc6609da6c275349819848c9112826674f243acb9a29ab73f17c8f8af12c7437c11972c824f00db7ad284e51b9b508a925f0664bb259b4443d56463bffc9e5d845c9b9f79b24c1f457088fadd281f48238866e0b92d6253638eb188bbaa8bf6a81d2b1087904974752697cffb00b4ba05e5b7b842a3d2c0a743e4bd691625788fbe9df14600643b1d161bb2916176b6ee40aee38dbb594ec2735d41369ed3a0c6dd9073f1eb51d1b77eb9a967b53670a8ed755f3b2b73a6cb50a9e1ea7549346646dbe4b801c8aa642779d8761b6c2d2e1a9995e758ab92f07c4eb4a23c042171a4b354f434ced5f6d9ccd26cd6c2506e5023dc076ced15566fdabc7364f4a8594cd6ec404e1a9470f52a83052390e4f7789ade9179b069d9f84ca2c7ac9eea51035db817845aded7405bee90cbe92364c8c7cf8a366cbebd7a972438f2a9881395a8610a2cd0c06c46b60cdae5b1f473f4fd6ec48479cf35101656f05485198a470cd36af22838e7ba3e28863cd8ba7bbba7e3c2625c1106a6be44c9e3d9b9938679b26f0713c62c3757a2dc8b2d9eed5e652220a7711cd220bc91a9afd7c940dd8be71616ebb8b2cb0686dfa161c6ef56994a3cafaec5e79bd0a2531fd1c1a42771acb101a38988bcba51ad85bffcd8c67aebec5b37d526b29f7b9d31388e1e7ad7154f8e65516f0d80a30b88c2b868be2541d19ea1d2bcbadd30e2fbb1b4678bfef7f200e0f8309ac0701000c52ebbcd6fa00cb85c8d3ea9c5aceeb3adcf3773cfb3bfc9ac764d031d7c63ab888e9b03eb9fa74554dab4719d426d0875a508c8c86b22cabfeeb70b0f1461db4e5f639d2a2d28a089dbcc48e3f34394ff1acb887b89f75d3236c8143bb9b06273c3878744340ea1858a9f383f8bbdc259250e23a3c3992bf8b7ca7e1a66913547710402bb538a8866772d11cf4214060ed091d403e1c9ca3af75859259f88656a1cfecfdb49d57c193e60a2223627c681a2fbc7390140aeddc19df035a5207adde4f5736bc542bfdc943ae8b094f4a8701618688fadc2284fb423f602c41ad8ee11e5d9fdfa67fb7dc7d4dce7847d4875b3af667168ebb6082f6911c95",
+	},
+	{
+		"67f0494a728fbfc84e2f4a043e121ee40f3b12b31616c78e157ed970db28674318b08d8b3f4c538d7b9d91b9b0b09ebfebb07201c6398fdbb8684c9390b3d6a8636333a3b086302b24c2e5d47283935d33065efa3fedd5f755218be5d4618d38c5c1db75470ba06bcd853f3f08d39c3cd9fa3618e70b103c2d9b2101fcaf39c1701436b720d723ed5c622d6535c9a10ec4d727abe237e80fd20911ceb84a90285fc6e07f9d036cfa65995f9b6300a927d7d0d2b907bac9d9c4daa87c2438a583fe85029c886f96ed08f5886bf53292cc0265850a1f4ee3e3288b604dc305d0c28ad35e1242f4ff4ae988b6deba48aabcad2fc6cd7eaab0a63510f3f915c4bb9f9719b1d90db123f639d9d4f3227eafcfad769c2b204dd2555dc54e738909122022c4f92f751d25aef6f9a1187750e825c68450e6d1223c2fe88aa27194b492b6788be6eda80b9b9f053cb77c8d9fa15324f23af5147624fc00c66e947b004bf38b31e1343c7cd341b98abe462a5f994e51d343664968624a2ed0dea9d0299d5c5a7e9097fa63d8b3ed96f917f693654766a9adb01110fa3fe0d8e9b102860d5c049df3fe00ccb2ed62ab05583e6aa0a5134d55245d4f643e274def29d3fc86d79979d599458786a8338b0071f6a01609ee6b2e4bba9289e2df780bb27491890d0b5ea650e62df819b8f98aae99a1b8870ce6d3c7785ca957d5b4094946925751f0fda1d62a9aefe3937a912c1b49b4272f87eea7e397feb84c0702929959e38a568460811e5064b1caf5dee53f920c6e19fb16fc9214b5de1cb770b510533f66d8a0e7f6f04ba8ba41869f8018abee31a6042d3919e217359988eaa9db2a10b3caf7aaba43527484d81304f0bef22165f74e9e1031b545ca3d2f74195984cc237b76ddbec85142a06446902339b1883000264031db85fb19b46f320ef3fe316f750f2d3d6070dec5b66ee8ef20701f20965f5171e44c8a99bcbca7afbbd81e30e74c6d48bc4b0d72baf562da6581fafbe14b6cc597f75e53b305036ede219ec56d0c0d29571a9c110ffeeb747fe56f6030dc26c8d3841b868a1ef56840932dad9f3bd7f75573086571f4d9f0d949510a2577d2f8fbed7e850c73ed4c071bf9a656d09dab43a610b49aeaa57333f67d586d4f50683dceee4942db9549f68eef4c5f8df8a2330857cdf2fc4025f2be7d5f0dcdc74a9cb593de91282787b716d416a3ccb8d6d40fa3c70be4ecfda26a5caf3724fad3d98db16ab6d8f26defc68392923b69664b0c2d56f01a549284b042bbd43c8faec940187f190aec08d06f9a62ab03c9f610f64c0010a0939451d5502511dfd3da1fec5a38f64640c7b6db2961def257eee9a3eff944828e9557deba68bd8e42dc7a9c1570e35537993061fa0f5351fd3cf4ec36386ec4cdc5a2882d5f16703b900c5000efa63888d69982e5ecd3e329c8cf5f003e23ce03c55631246ca15ffcadb0fc9d5634252ccda812ba7bf5e343c44244026512062a68374ed4d8add0855dcc22b30148e0cef0f2886be76bafabadf3ae1205b43c6deb8a41c338114895dd6b49deb329ada31b350e02a1bdad4eb05b61b50f9d22fa2863bd607406f552713e302467ddc78213d584b4933202438d63f99d011b97297f5589f35b7e45ccbd76f02453b7a7668c2b1a1f5d1d63eb805c8881771faaf67433eacfb22f9b6fa58b93f9423a5fcf667aeec39751ae17ad36992556431bca77059a29353598dac12bd3036633d2ccadc18f44123e5bc074f4e5ca380095af062fd83b647015259be929011cfbcdc9bc5d0dcf9b688f0f5d74da95746f447a9e1cb5028ccb2827b45129d04cf6990953a6d8ee0e67fe6bdbd8004f4744cae5607fe7ec4a0f14fe603dcead3367b6870d8e751cf57387d04b881f92cce9772d695f19b36e2db2cf6a807c9ee83225f5c09a11b50e99855921a4eced8e631af7c234aa31615c00ccdd7c6ac5ae8fba6e29cc233765a891864c7d73dae08ed1a3c27cd423d8d4efb550597afee8356c12018f496637daec83575f5e38ed2fdbafabafd38483c239d31cb4d104e93d16eacc6050033a3c86929be4ca8914a538bf540b43d7ce7daaea317bee1ab80504846554879f900d312bf2fbb406a0edc5f4f809cbc68675b0b7f09fd1a8a4d52c0929b3a8b9c1dae4b3d599b976867e6a7e8736450dabf5c49c949544386a71419324ea4ce5c4319899ca510f50d07ace57b013655b0929f79dbf3cd629ad17bdd10109b7c53a4f5f04a16e5471e823c898362df43f57ebdd1627b33fd4cafca6cc065d9140acf0454d5f99be47bc87e0f3b4d4320bbf0f21e7c261bb8d5d615963beeaa46bdbe9b83a8277813ffe6132b23564bef5",
+		"74dfdc364097c39ef91c01b707a522e28edb1c11529d5050ff820234e6c0295aa00591e09d547e9671804d7825705ab44b76c59d1315ed1297ef477db070d85076693013bdafa92e2ff6a654660008b176cd4e8ae23b9c792be3f7db54cf2bca385bddf50a8624397cca8ee3cb96944164e3cb461e68",
+		"b3b5ccd7ef49a27d2c6d13c0ae77a37abec2e27e0b2d3530cdbb7f36792a7d2c",
+		"c0494bb7249f864f69beab46",
+		"ed8d6e964bcde1df68e7f362243073941fd68ac77929c8e480c89f519f748b3dc337b1af6231632c975167a8425b174b42c2c60dfc0ec85a0a212bf5c9aada818a83f9664c8712d96de1036b5e5d8c8298786b753638de3a8da958549f16eb9c723355cdf7b999aac464ec39df7d6c1607e81b88b63043d1c847dab618f1b19336911b4b0145c2a694e61db71e021282006d48e37f10f3b6314dd012a07618228532c28ca84a936e0eff83723d117b2f2db857d14af5bbd5948a0e53018b31e57cc2a81f36aa013a844990753ccb347fe98fab294cbd252a8b8f7246276275d2780511fd3cb7baa2fd1548184f968c422230f7ad73ae9dde91295f79f6b799e7d234dfd6573fee6d6ae748b0a8cd7ed4862ebd957390826f276c2afb01fbb4b64b61a1bfc138508efd630e77580867bdc1e96a48a694cf0db6c2a11f05dd0bc8769e7200bb0749f5798b6f3559de55d0c281eb5df22b731fbbc109da9c68f209b888e61240c4c0ca006d105c0a7f43144021547d3316e5a99f6c429f9ea2f17d77dc68bc9d5125b6260f79bc8b3b8061972e6757d87b6544f21645c0b4debe5224f7c48142c09f35b8e144c0c1e6521f04c170519ff744d61abd59a56d25a26c5ed5972191b25e78e2140f3ce68fe17be9e59a79f6c69619a79b83614c670c7736d19c27fd22515fb5b896a6418cc0b4850e85c07b38b995cffafd9f69763cbbcfa9d1bbea6868244a66a5cc82e815fae09f5775d28437634926d571c2b0d200855e09cbdc67d10f85bd4cc334ded4c83aeea57f8e373a950f135997666b653e8de47a3bc0059525720045996bff500a47baeec97808fe971d7693dfde339e8beca3598fbc053121536c30d0af10f8f5d8e5eeaaaa9586d7abb563fd69e88351f93bcc46520f6d97c1a49ba9f8f6a25cdcfc11b2a722910aabe7435ac8f0dcda9f824fdde80850f21a2d4bcbfd2e9fcbd14dec05c117a9796db49e2f0dc55e74c7f0f615bd049fa7d0bfcf197dcda3ef3de90762e6f6f9f8a8936bd04fcf2a97cf18ecc8f2f118ffbf02b67f252097e4289d02f264161f6f90f79e1e1ef8414b01a9e1a77b88c039ad6eda6df1e28fcfe9370f0d574aa9e857dcebb19eb7ce8af9b19b4481c9fb3e1f0db3b02af483f737ce3ea824b2165e7c0fca8585383d4b0a16eab2c7e3ee5c038f939a97bc8e1c093cc5372ee45d81836c988f3ab3e6ee0e5f9549e4b7bc381a2afac2074cf75ed56b0e757e7966cb253d549fb0902da98294c6dd4de3c2e166b7e45098d2729b1393deb68471d4d3218dea3dfd0183b654ae4092a79357945eea4b28cfd06b40d30d1b4b8f19827895f6f908f0fe511f74ec84cbab2483ca4bdfc6ef50178eabad79b18b58529c9328c13c52c2869858cc20ec36ef7717e1c743d13f9607bbdb0b701d9df6aca7366814e883d23e51ee5b0f20ef70e2c4134ab037d213315fddc89009260981329a1872e541767adbd5ee9501e7df4ef0cdfae9769961f8716ee7dfbab0ec89b3f62e987387d5842e124a69b07245d359052ada50cfd67472d27ce2c4eacb5421b62dd7331da54ebf0989803797f4c8c781d0e2e6477b421c7d5cefc8146aacc0012af3f1f7cd71ce2b1045d86bf48c9a13fe469a1865294e160b4975023d0eb24ed26837afefc250a914f86f8b1f5d67d65e9737e841519148d4dd5dbf2b5a8b073861288ec9793d4b113d71c01727f67d791852fc3946dc912d60fc66bffccf4c45d859eed9f0bfc7f89086df5d5cd830ac919aa7cdb4504018052d67f6a3ca012ed69187cd5fbe91875cfade381bff1e804ba59cd59f0f75cb46dcfba234ab9832c3fb9aa8dde19fc1fb30677ac1793a38d94aefd9ffcd4e777e9e4f6d49e0cdac6c16a36bc2f3ed8e23b80350e3be6d866aaafbc8cbf7c69fe44c2aa80651164803150c23ebe262aa669c77ca94d215895d2ee9c3e325a0bf2c61e419a41e0f7b1ba8ee0508307d49301abccd5b74c054b6c7bd1aa67cffeafee033761d8226d9dbd7214b130a867764062cf4da685deefa23693b8549d5ef5e53df85c19bfb3c43c6bd073e7a836f849587a4747e1a9a3c7194f6d5472d2e3e4c81784a3061fc9bd3b94862c4784974d859134369486f2651f1db94f511c6f59f41da0d75307191602730b88e4e6101fc8d392c87687f3be454dd92fb8ec380715bcd88aadb63717cbce4db91a36821a572c363759d8d0a2ab007e5981b78731dfdea20d900b14f0c5ee6a4a9b532ed2134e6edb4dc267f001cb88dbe43aac4aad453b839d035697df7de98ca7a9ee7601228a79004b89796e9ab971aeb8e62c789bb21f38b77b492c57db402bf6a42ad0cee169e9251d865ea3e5f79b1801ef1e53797aa6c7060d6f9486081",
+	},
+	{
+		"04cf92a64cbe135f7fc1d7223b95e41d13f04b482018039f4e7ccacba8aa15ac79a752c5666524e527fb076290ec80a3dccbebfce3ee9b316a65fd130f12bf88b9124d1f7772049e6d0c01fef881a1d44c8dd02f7b6b60e6d15df9e06fb86929cab64842284de09659e19451623525aec2f5dd3e603e24319b1d120bd57b34a0317ce25ac9c2f022a4847306b998b57c8d92baeed0de1f6cfb3177d0acab70de275238f1152813b9ac87bf651f74e1ad079b9bd779ba4374ecba459865b5768d08ae7e1dd691d6821895e8380ac9e5116580e8de3a2c5326e698bf4c4d35d955e45772bae8483d01de2539e8ee1ef9539ee132d80d85fff41dbe406af319c0d7703292587bcf5959f49241e2b03a364e1b682729ed261d0ae45d74d77634afe667413ee210983b042a7ce6dbb61c29d18450fa7176177b5a74f032ea24e1d08b220f6d32a7a836d1241cacda39d6acbd26a62f9dbeaaf7329a291dbf0aed4a2cfcb85ea360947585b1215feaf70ba71eb2d6bb7081b2a21bdcbfdae6ad2513a9dd714d3d06c2c2b7e322a1db2d48f9df1fb44fa066f2bb42b196295ebb3c0898ad55d5b317986afaba0bd5e754cec773821613e908ce2bba6454181f9020b73e758df18c255c87df675cc6bb2b8d2eada44196ac10c26674167f94a79f4be515d8d6a1fd3228dc9a85a355b030845dd4c5f481d5b6e74acc66de730629581b022fbcff61e5dcfb6a7f511aafd577849a6b057021ecbaee53986159c1ba74c3e930c34a159f467f1e9799cd6c1151067c56769e43308c96c8edef8aa7634d909310dba9af2128cdb8c29b24d3ec2a4f43a1ed86d1791c9a670b240e6e719f01827aaa319bd3ff53959a776886a1b7c942a54f141e6bae8576d294e44333e6c5ad90f74863f69bf890126016b318e0f6bd2f0adb9bb861118af5f6cd28dc93d56c8a1dd080b8c810ca29267d410673fe367dd9d1353ae2bf2fd88d57b4202c21aa49f12a01b93acbe260492367bc219d3afb6e6f35502f6529bcbcdddce9fe8632efb034a9eaff8b4a48afb105d04e3fcbbcae010ddd6636992213750b12fb3e01ab72aa957136e0bae591bfb5e0fe819cac82a98ae8df230af399160594540640c6b1d537e7b5f1cc47b08127ae02c35b846de56c4c08773fa18d4436e14b76a7fc4bdee301d0af4880306f2f33328ab79f6f24ec779b2b1928704f09bbc5b0b7108e9a115e4959df79c80eacfb98649a0788867e23b2974b22e654ddab0494bc922ecdf17727d0f0efde9dea7601857d890bfbacbd93f7df794bbc254f50e1e17eaed2f5d5a2e6c58083aff68434730d406fb9fd02b0dd7bfb99a04aea812b6830fe5e05a044ca21c77a174bae8b58eefa11ecfcc1c977bc6218064c9931b5c92f13cfd05799f11e130869c293c1b08dd29c899365014fc8195514b286c97cb6dc4b8633e47751f87fbaba137b6aa04d072ae06c2b2f34448449f60b1272c1efbd4722a2be749a3d2e5450aabef1f7c51bd8324607668a8caf8097c2f358b1b09fd3525d47ec9a7640eb20ffdc17c4f7eff63df75dc7830c471ace3a727feb11533d6e9a2a08106af33069cf482ec63724032e81cab18e12cb5c4c3ddc374e2f75bcc99fc5da09b80a738852a14e8ac552b8471c6ad52e35317b730db2c13c277e06c643e0d0fbea43833de4d2c7a9247ff040e9c56f1ff7ea92049c5341c4d1478a14275a10119d934e8165152b89951bca7ee1399dd8232fdcbf831d8354640e698b68799d060ceb877201b2fb96cec514affeb28721e163e1648164b9e5722271db9b0ee1a7f96819fa1b1590e9daa598d9571ffa3882db9d034056e9b2785a8d13686eba61d7d45cf2e9ecdbc391739ce89297211472be18b21401658c5bf29fc3615924382d802a166d05dafe7876e70a0d081e80c63632da379766928a0555eb5e7a238cfa4da267527c66caf34dd40055f2801b29b3f5604a5bf3d46048bfbec2e24abd2fed2481698a4b5cd71f5d2c12dd473b903c9bdb978eaff7d76fb69951005681ed7b0257054eb3dd6d10097fee51ba7e8d565925e4091cbb78d255c9d3ab4ac0264d172c9bcb0908db1288c9634248f198a1167daa323822058decd83936985f83b08b1e7b942756a7af200af168fb8a091107b4443fd649cdc22106f9b9657c69f19be485c23b2c715b3762c332eccc44f380883357d10019f20612ab6b8f155c2af9e2ec340e5d8f45bf5278ac1fbc9f9f44d2f615d21007d822b244b1c7a0dbc182c7f5912485d6e4d74e90f60a2f964e028c63d49c6aadbf1df170e4914ca514139ba538207b1cf7caaceed4db8423dd1086b2adf15f6c0e50dcf2e12898f53c339a745316904ae03c38b417bcd7f5cd5ea77a4f06e65d56c24f37ebe72d271ac79b6ddd2bb8bd67f0727ead49737aa71af4f620da53769ca3ae878adbaea5a249128074ca3ddbbbaf5a68f9cde2a0e8d69708b0ea7f4c8d2dd4180882bdaacccf2a409a681c551776bd10439fb12b7548342532b371c0e045d8e8c895929464bdd4fe25f0533c66104daaaffed52446094978bcbb389c",
+		"001084c8a5175c0ad43108f9215c35886c82321c800740c6118a3fcd45776a4588ee0e2d056a37d99b40d2bd9c0845088a77240b7e65d18fef105d3488c77910b5873dbbf275948db5",
+		"d614d2e671066e387f7eee07bca9f9313d86e6a4d1371360900d670e11ecf1e7",
+		"674a516f8f59f9067e0485e3",
+		"1ee376e9e3c89b2147bcf75480ff0dec1d0e8cd45ba812f34c84124871d484b4ca87bfc8cf99f85ad452c482933801426e2737a97468809fa36caebebe8eed07a626b3bc3614ef1ceb54f9221ecb16f413f0bd9ed4b3010c40632f05223484af7bf5948c2fb8a3d2ce04c53e3f2682494f3969a0f8eb738cf93c0141799c9e6b68924433f0326991e19626bb19e6fbb5dd46baf39f92e830f9b1ff465a007f031891fb1f1799cc122d3ae7a55624356b5297bd5d948d9ff2e414cd8adf00a53524df43f398938d33c93b2c06bcde2679566c0a7b0177b4a873f35874739d550712d5cfe3d25c19292ba97c01d84224738bb25546e5c252fe5e5f260ca881aaf176a271a6fca2edbb2cf23ae6d4c56c20daadadb8205c2e33881867cd67ae6e59132edccc3601f014b744ff8eb6aef5e09b358607695d3af42ab8fa30e9fdf99ce54427ba9da3699de19f7a8f9be368df47ff0607601a91e7a5fa6e72be50bb32b825427cdeda3972a18a23af290986cde14f5fb9cbddad336f5efcd2d7a0cf3d5b23e54b702352fd5ee52d7e3479441497d56e17d5868574c56cfc421ee47bb00e9c75b84262a1b9e2cbfcccfed9c4c386ef0d2c1be9a7b7556909b5d72a38b7258acdd624de2396c75386e077c34f005f92a2203c82d1072c8998f03b1df22de832ac733977705453b1d72336b8d371cf1ed3923f462ecd22075de5df68c83ab1e6648ede7fd5ee5794a744abcb32af73bcb182cf97d36f37c15535c4107b7c8f2321f9fe0e2b6ccbe74204df3d748c05bc1e0e2c55ae1aee2d4aa4a52e98ca7229d6d06576196ac8e4b14a9ce807075cdc876aaf904c9962741efa8c6caf41e6b87b2ecd6636e2e58f3ecf576e5d8b895162545e618960ff6e336ff17eacd5a1eb335001633fa78c41ed05466d904ef9b81b643a043298c0e291a085e4e67da72e329adfccc407f800709865147db49cbdf4232073b7bc7ad89b3dd901d927ee08ae6497e0f2f9d052ca8d7444d2e2ae2197f930a7b1c8af38d8739ad298464169823684612cb628c484f710cf9c552551b6837b575a43275100bf800b7a3d777adc44d07f67cee5000422b9049dcfbedfccded0f2aa4d189621579b01e3fdaedc4d772dcc593316ca85e7aa248d219dac21c561d318a4936ac0d3bd5c75311486c174e0e2182affdf69bdd6a086534e4a602efba2b9363beeb5346539b45336cbaf479da6b15b226a9ac026482216dedb84ae3443b306820d9f05f78dca7090d727c7481d82c6e5df80e189e24e46f5758e453e542bd91a58eb51a89e07c50afb543c6b998704432e863dc4c0d0236e0672835a7b0b64e14f5ced2904e54da4287597f920bb4d542c35d3b0271cf0eec055656d523d7d2cbd667445d3e8634854f8616b7d7a7f3e14fd32651e9df40e1daedfdff1371f16d5549ed5646adf2d417e4b3a4d145bbe0974ab388c2716861a08296b862e4fd035163281457877eff89dadb160eb2b780414435784804bf4fd36602699d8c2f6a8cbcb509198c38e2df2edaae7bd7c93313ca98a9c2d24419a12ce35b0b3d68c18840e3ff8739d70969927c7db9a6569787bdedf5c99948a9e79b2302a83a71159f4c789b3b3f05f1e574f8a24c899ae3457f8e73f9bd86976fbddd83b1af337eb8da4c0dbac3792921597e18a2fd3a0ac89a270794529d370d36bb6dc7452e754e903781cbf57c8646b92d5d02842e7df229b3d721f9b981f9d61a48f00e53948a5dbc4f739849609d94aba3e3f5f8163d40321576cb8eb8e89953b608a01184d41aafc13f40c47b12240e3ad49413473c26b6843f4514be221c2af632d1a54cba230457f23f00b2608485c381ae03b389ad0a1671fb416de4659cc7f7a9c4b6d9807789c307d061fcf613b96a2d79e5e3e20b863c8b1b75f35c982b40ac8dcb7d2712ef7df94901facef783e8015a9a48574aa6f0cfb0bf6c1a3409028f8d62137c347f5a35ad6a3cd60d71aeb29bae56bb4590f69226fb4e08fab7a9f41e58f4d5784540a70e7a97720c549c8440b089eabd0eb3e4d37a2e54b1160572ce568f4256dd244decec31fec555017ebf488e878945383750eff26a8a1cca73e7d6f52d8cb229d5603360a3bffec23029ee34145c4aade82d486758e0aea9e1b7bf0b4bfbd4fcc96aab66a27fb463b48c6a6c5c5a60253e2fbc5716ef55629277a5f3b89c300e21bf1226241ce0d587fe3f5b11e47f35614169dcfaa375ee1aa589be33a4363765368f5666d155cf72e851d426fa67b982aac4dbbc29356d71deb0715b34e00b9fd8876bbb09ca0701b15615f05cc45e128b3864b26003e6ffe801c4e27402f37b8997e0c29ebc273dc03358cd22fdb68d9cd3b56ff8248a727c2d4ac65acda4d0e0f511bc07ab06cefcf444f1002c151b953d7f7b19695668a86683497c2a2d2e69f19a4997148d2e8d158da859c8f44437d9ce9db92f84a88e89cbffc74c0ef4295088e2543a4f7c6ae9c908bd987bcfd7a074f83ffaf3888bd7f430dc5a5bb70d223c21b1bcd8bff2103408460df864dcc168486f6a66d67ded366c6e10f50bcddada93627cda711764a57ec36035ebc",
+	},
+	{
+		"ce72c93caa49bb9850774149a87fcf8e23a0c53701554468645554553d54190bc6e247712b02097b794bc421ca94afed34742435ca689d2ebef183fb469c060c7f4d7daa508726c9d2eaeb9c7e9a89b30faee8d9168607d4778acfbd27d5caa623475073ce763ca061273cdfc2c692d1747baa8a01b15f783b2e36620400082747599a16cfd6b630fef310c0b9a2912d1d3bb71eec16972745cd8a49cd927014eb0a2abbe0e1ebded4fb9e8d9e2fbabb6a71da5688717ecd3e08160b9a861f86904a41702b2c4fff28ed8cc61d468187b75bde3fcc5c0c0a642215fea83584387fc5a9aaf2f8a91ae535e0027b618a32bd687289c47e9428a1a92649deab825d702b076223b07c08e55c0b60be95937bfd0504c18398e924420f6e20baf07e2b1b858d3e360a461b66517c24e60f9fe314a4a4973c8dbc7e9d2a9f571a1d8235a21073d81ab9f4800b70a5f17f44d593e8792a2507e6a3a41042fb2a5f7e5f028ed2daa88cce28973ecd88bd125d50fad77b1fde61c38272057d9c65fbfc6789ce41315a105af14e277a0c39d75c34aed7538c39160eab1c8c47818743e8111229426c399c5e88c4d894fdaff0315ec885ea019bf9acb785f3380c37201d494a60b583fc130bc0eb9fbe9b90eff95874e35910dc05c761f8006e2f208b786aeb2eeee841f9a82d9966c82956c181caa4dada81dfa2e2d7a25007c2dc7f2dc7ad1bafef14581cadbee4d614a557df4931b9ca105bade8fdfdefc0d96eeda11c08500b1ca827ca670ba07bb0f85af92914c43a6f71226d6e112d487f1ae99b2239a63ee2cd0849d8a9c488a11f82ca334604a2b7260f25373c6db75656527890f9b772c6bfbb9f687f27099ea9d4d1efd874a6ff83cc36c039ed1690408f20394692ff054d9e6eccc6776b6f4b3c5f24b0052334d159f40b470a9b8799bbc0df4dbfe59a5e536624cad193160ef23abef85df2c9b6e6d4fdf16f848a2a446a77044f1162a278866c491982570cbc16041908cdd0efa2cde011526a3c96d4b39a23c5fcc53d8232869cb4dea871f4ac8afc795aeb1b28cb2d7a3669100a1cab2ee1a7f31e2a25a5c6da836e4b771ad57393305faf582adcd26045e26b618d9943358c615fb206258c8993d700adac7440dcd3ef34fdcb065e10e9c9727662b5abee160aa01d2f2ca6c203a76fb01bb08cee9fc1eb6bc7497bb012ed2774a2d263b9dd03d60c307ccf33233ee33eee702c8e3118f9f86174a97462d0e804a24bbd7f4f938c7f105bb23399967288069e1637b60f2f1883d88ce5a874ea4bc0a7ca0f3b568e4bb1407e4bd6f0d3dc8fe91345f8435d7b1be961c45e4b0f1ef2d92d2d30bb78e1fbf72cd2e7ffae76e8c2bce005195c2003bde46108f37ffacdac28fd67a0de62970b347f0ae3f5f3a5b1d3aacb2fcaceecaf2ff4a2aeef6f5a176cc1b74b234f5658ce603bc353e075278a4056540e43033d37a6eb2615453d8206f5cd294423811283bcd5d79c4afe268a547b98977ed5cf24c0f53a0533bc0b2889356cacb67e2f7353060f9e04362859b1c1f02f96bf5457b58e5ce84a6810d39d7c7f53faaec64db5d6ebb90c1412bdd503ec6bc240c277ce1f5f18876feb24eb6a77e5193e33ce141e8720329add079dc9735f0a35d7d85436f1dba6dcff9147777760b5aa2ec9c8b5e9fb4fc602ec8f754c99ab2372ff5963dbff3fda91865108e606b214cf7acab875197e78060eed52a798751998ce7c73cebc4d5f429f6729a5193d7593072d0921ac8127ba6e796107ee7b9fbcf7128ab35fe9f6fe501fa4695c19fd64460685f287acacf5250efc13899bcf80ad5a340d432a0b9449affda5c8fa090f008e01873aae7d5fbc7972451542c5c29cf9cfdf23db736c8a7112536b1b626caa63f3e4117044cdeab612fff8d8c194d19174f56ce761f6587349c48fab30390f231d209461ee7e18007d10d83ea5aacf199f3b00003259747b1d03274d3c3670595604bb4482d345ffe31d3e88c70da16649a2677bfbdbf618de1d651a53d573aada2eee5c01335ce5519a6d18a70f7ff0b1e66bacc162c49f7f29b9d3fe2c7dd85b6b355c9f9141f02baf08d2be87c36f6d2e1b2e90dfcd100886e306b360df0ecb146a6aa5ac5ad05b63a219ea65885894a386248254348ada17908d776f9b438306ad28b208f80d6b9b265500aead945134b9d388ed5d6205edf07c5d8bbfe0916d0943750150e09c76359d24e3317517ea489fd8a501dd93f159f07d19d00e86d952fbdba2db771910143df346b30a30fba908a1abe5349c3f241958f428dece7ad9a91cb42035c43573b87b26c2ab216cb4c21799f6b3d81acd300ff50edd6fe7868b9ba6c160db3418565ada027b46b63e5d4f3411284fde585ed3673b424ec1cdea678e4a43c262991c3c9b988351d6e0a10af1c959cf21b7a288f2e4d7b3b2c11b400b5e036df71fa993b72ce48d0d8598fe4ef1ce70a970f89b55cf4f07906a479bc84a08bf6ab25221de37afebbc47ea0b38b87be128737d7d43cc84d336cc6ffe1677bd802910a2084751f30398dd0ed09589b2befd2f3b40fbc013318c822fa2faec2323fcc52b43161f47aefc557e92df3050dc5f8b1c5a4b2f8bd7b2ba7aaca79dcfa362fbe7781a2e261683a4a862d5f83e34845a8fcf8a1aa73cd521e87cbeb71f20b20698cc34bee3b8628b1a3784596c",
+		"08b3fbd73d157e79ea9f61665d19867dcb8c1598c1d37b793606936d8aecd992a0d46addeae857d488b83be5d1c1639d4d78350e4cb08782b61bef4107c9d3a79d3d85",
+		"a56f38b4bbe83b3d9e562cdf5ef5b30593f08a1a166676c4fb0d5861654e640b",
+		"8726dc5cae9497936658f603",
+		"88420357d1ad70e7c7bfd55b3cfd4bf06cd4e9b4ed5cba681045199a06985956d35fe86b28b9a4599964930d05d230a23c55a6a152f67082a453fc31f68489df05c553f9ae5cdb3f611445db384d79af865e52440a876fc4153d896b7a2318dbc2a4495ecdbb2e9dc68022326d35289e82aa55197aedc266dd91ba3018c7b474ba22b4e773773f3e9890ea84bc16a6b235e4bb69e785c40c1adc15b0e0ef03aa147b0d14e62341e27398b84a53f72c9199cc1c94cbcad2bd31aa69c96b06d01775b8c0f80278a43f526664bdd430164863c9c9140ad87798a5b8f38dfe90d37f54d1137709d5311136b728e6c799da244294daa4c8b44bfb0acc603a16c088a081129a0d2cff55ce1c4ccb486fa0ecc3098ef2196f47c49f9d253112bd5746fd99df5d2be577617dc2519c0ad04ee49ee1d7be3d50492017108fffc9a414ea227af39fe49fb2c895fcf00d927bf4a2d78c466fd44df4768e6775d39fa5c834b60979ca27ee9f00faf37a090838f56275a894ddadd265a8d2de74265e4d8d286639ce8f01eccd4f551cf6b4429eae3f08902b6ce6ef422cf91ce8946d9403fe8064784895b62a7f5df76ea294132c59da6b9f53d4195c1e9000bec499c14cf8bad460aebb024a76ac50616f0dcda71c0f56dd3239b11764f3ed6ed06c049b2ad673e4beea391dbb854fde1f01b1900858b9809259f3906b34f95a1c6ce8d24fdf0cf7c2ab7bde2202a7f1482baa6e51caaccef9f541c377da620bfbc63955cae0e6644ec8ed6878f704f1dea30d6b50d4291892bad19b0234582d50c6cc0b4165322cff24a9dc2ce1be35be0fdb3bb7abb777ff0b2f4cf16277388af5a89220d59f1f45ee9cc2a0fd7af9aa8e9e8d548fd65be4e47e7f8ef58f7701f93a42e7ff78f70e807fb63513157fcba96ad9731b2e8f80da85ef407d5c368ad16f0657620bfc122ba1b10d7ac2bf46d8133a9c6fec1fe04882f3d5765da8f825e1984a4313f72b67d806ed45c000dd3ddedd524d474b9b5788547d0712e8edb4c6c586d0cdf8f2384f1e093a7f6dffea6e79df9cb9398f5d0b9a7cbd63d489430fbfa397a0d03ef916b7702f33a54ebab84a7055b7ec6179b0ab7722f03e126ed343b1cdf2af3763df7e3a070162535514b01ad86c6cb051859aba1cc4766b12c8cd57b73fdd3c65af6961c45395aa7b885dd59e115db885f644e1c94bfa26b3804f767601c86e2c7dcecd4daa59955e6a40991a4b4701e63fc82b46dc0ccf59af40a8583171375551c868436ede535705f2e6380c5899cddfcaf9e94314794bab98846cd5ba9e9afbdbe1ea7fec5e22e7b2aae59fa598f4d6c0cc6f936a616e11bf01a2acc891cbfa2bc53c511a8a3a3da2e3aa5907d123ab2a4a3c0009fdb5235a3c33718fe4c504e1539abac6370e06150c402b5fc2f8c32608db4ce2eca9d1e4b96371ee195f6cd632f5b972385f9d5d357b87c78cb4e2c27aa9851534de14de923543f5fd9d55e34d6e8b7e1f3f2735df80046de01f79d0321066f9bbd76299c7386d285f7bf4ac15e033e89a040710c90f87aacc09fb8159f93c8b4860247eef079e32d05707e88aac734a2eadaa853f528d9986e0af3435b5c5f44ddfdab9b0c9ab3eea97676e920f80d1794740067f9b229fb018c804e595aa997533a5e967cb79ee58eea18995a90ac08333f1c69600b17ef4f454f540dbfa8b502457761bc4daa876d9053ae1f55001b6916ce559dc6268d01841255990e56614e6f4ee4ce04472dff0657360d75da4e83a71c852a2585110e53137e91bd89d64d99b5614ab2a5691c876f15d9931b092fc6729c0732db5cc40f966fe440ff99d7d05b24a872f552c27fb0cf2af443340b153214b407fb9ca3750d9c157aa75763b0b7600959663889d00f392d6ebc12835bd2f03ad802a21d0228f1d2e9731d0f0051eb2d5369ab790d1134c38e28d2bc2d5d57d6d897244742c176559961a1e40c84ee5c8225c8d72b92352a011e3785c262aac115cafccc2fe1b5e81a677a0220f207ebadd786b93f58e40eb6ade68ddda5b66c5f0f6b4b95cdb8241156110ba3303beb79acbd54423315768bb43b4fe8c4a465e50c4e63bce272c4d731ea4c797e14b2de31ce4264e2479179b906f67af4a23c56e817abafedc2c7a65aa45f0c89fcd0baba60561a8d013e2d5e0bdf9fbcc1346d3edb20e6e9f9c410982e1ac43039ad8fd0ebd453a6788376951fc20374b59946a6803498929d9fdf2e0f5e58c441329a79d1232e957b3a9ed17231c663b4819dcb6b4e33d205edaeb7d7ec466930bd84a064b40aa67fd76f6ca005408062b45b5aed6f8161836c7160a8c8313dc9aa1c6d42c2c16972a1065e41aea9c58db7916e1670cb42a8b54d85498561b4401761506860b19b446655f8988101fb4c45067e30edc3f00df8d88ee34111dd6626d605d993ff207be09704fd8dc242ce514bae77cecd20f10d4a38435a3f5e545882fdc224586a04ca6a162e118d23716240fa67892b78faf98a17916471f7f121fb9f85497a0b34bf5aaa4ee1ed8a4681bec55d1b4973d4368600115bea70f20a37c9e942b87f6cd1e2ab70fd401e703e3c8334c75fc338508e06d6370779578fbe737a75954b4701bfd92028ec32d3d7ae606caaf9f049d9774f70efa707c1c1174d9fcb5b0a0ae2a961c6f58e48ba82c2db14ebbbdc24288e42879f547b855c86dea9a3b9877e4b105515bd78cc43465",
+	},
+	{
+		"bf7884fab52251e202afd7b5b46aa53f85bca5fb80a009d8016e276579e401385d853312a884f4aa33cc5fe7360426bbc0ccb7416cc0196e2e40d3a825d5e0825a1394029789acca550bb28b10d847d0a4fe1111be2b7fec6b5294902775128288a784203031ea853c9c104c75571d19552e2a1359a900c5fc9455230968a5920f2ab23f5b9cc49739d4e4ae2c01c7812ff295899b954e9729a3bb330b60c51a8a7759e5131d7d4cf261fa1e62c29f91b4341a4fc968e7f30ca2261702eb328d628b7275a9efc29b50bcb9b27e5844328d5e8256c76949d30b6fea0d5a1c9abca80d3251fcf4ec4db0a5ff2ffd43618aa2e3e1694c2a3c579a2665f443ffb1eb0ce33c09d7285687cd55b6ca9918553bfb36a44860e09ffa0604ef4904a034108370195a986fe165c598305eb08599abbb3df31b1d93162397056d9ba5a1ac2812c582aa356310fafb4058abc5f157802e4a9b4bddb16e75b6db105b7dbc838f820539b76949b1648909104efa67ce28b16a738f1be104d2bd142d3ad1b1c953b6020a1f4cbb84d5c49424befbf2e6ac5c593b783a3f4af75477312528fa78dffd82fe493d821e011642bf1135a5be91fef909383953308dcb61b2f35c2ad259acd1a2e953c0ea6a03a97b384e39c94c33d3846c26b4f9f116abe572d5b7cb81886d6adc2d544630fdc1684bfb32972e051b9a2bd0931de63e025813b923944290fe1ebd5264ee4f25569a2088314e8d4ce8b91c7bd602b9d85acc917d60d30d5ef1cbb055b9ff7b0f999b98caea2517d2de334eb436078c90d41e0e34f11b93e3e643389f43b3afdc4f47a7396cbe0b4bf159ff27618cb835aac6699be1fc7ec840b767836a165fb95d06f2cac4fe15b65714ddb8a095ed4a5b57e63d536405931b6c168683763fe07c32aa4130bff787d4d440746a2dbfc584a502d809076b257482abf7f8ead7741c82b54c41acd41581148aeb4149b0c6eeb39ef7ba091c2e8bc72583b2fdf8ce7fad1bc05aefd6db0360c644a9760a9729a88ee4b2ab123d7238c12435b9f3b4660e74c0fd4a9b00aa614453d84fea01f779e5a924f8e79630a8bb6561ae19c7bc8d88b9d823b98285fdd65d4cc05e443944ed5d3cd4f46c7cafd1dd5deaa519772dd24f508bd2d588a832d5689119a2d506ff11dbf37d57a24e35ff38da18af07eaff5775d12dfe795fd3e1f0ec83c5f283d6cd76532519a15a18d93431893b1b88929159bf8fd21f62b30f4e37d540baab0e30ff3349a08d627ac19303fcae8b8e3fe44eceb66d30697c7ea051bf5afdcd8bfc00d49c8d36164ec9194a78a4d8b78826863e93b6a810354861f4a35ec12e5ac102f74e390d9c0227e67acbbe3254e5b892786e3a88a383ea9726485854a319569a678fa70392cee90c9aa83eee8df6800565bb8e083e78a064c0f8b863120efd799ea57d3073663c0d0e7bfb9b717ca1d6372fdf75a77fd9677791cb899fc8033d6d806de1e6aaeef525ea909666316d9d604c1207cbeb6f427c3acc1b02cf59704fc65135703f2a9529bb2c8fec992c4de53e54b029c3f2a5fdbec1008d1a70dce0c11251003ce62af712b9e4abe631902485404e4933f346f1b4467fceb65baf776d0078aae6a2a1f95b85a441b635663c75b485a8a7cb9a5c12192ac874d940e2d9b88cc05a2db9b5b35df769925da508112ab0b8f64a1408633fd0d81810baf2c846b222736bd826c8cf905b2c35633d6013f5565e0a5ec1492e99613f53530799052a0d70023339d1c394fdf9f73a590a2faf68390d2a823bc3e47a173782b03dacbdadaef1e67fb47a7cad71b6067ce5b5e41fc20ea1fed28578e9bdfa99faa657a754488ed3fc084faa7a05b0f6eb66da0a28e9ab26bb319fa4ee993de840948f94dc1d68d926b783a0bd3396a89970b2c2595de8148e87b87c21f664618af4f567115d403715c3d7d2f66d7a90de2c5237893a4c18c20494e3faf94485ed39ecfe972c36acef0d7ee57bf8755924c790ad02dcc5c4e15aa7db53eb6040244c3ebb7874676782e54dfdddc256018ae6af8cc37450a4cef77f21e2e061062ca0c2a514290c960f5993ec1ce9eea6d09d3293118237e079b6015b966361c3032368174d74ae5cce4148ea2b3690fbd3c28ee544c5c5bd7bc618122979d52c9d3d44eab1f2467f338e695ec5f95998bbe77dffac42bc2809d43a324e0f5feb4ca3d5fd951b7dc8a9e6276ee080079b68849b14c7573cd02c76027a856165d1043acf99554c62fe32896d120974ae71f84986bfa0c28fcc399246bef3ab90f8e55f913aabf339dd7ca6f0861a9ef712e77dd28740615479f39a37e746c7df2b267066d1649fafe0459f665f3d5e7124db43ab1ba5ff94989acc7fe0935e0bbacf718b33103a1355d97ab416d8263ab369e6cf0ee563a77f2f265fc3856b7d54dc0887ed439a421c14f733ec1d6da086536f9539d23cb8026218c5e783423b5f4ac24c8d5d8faa7186dd5ea34afe299e6dbed73ffa8f415da706442a48808a9342d6209f65ca11eba76f8ef26db890da76671971f65bce9e6112c8aa92523dd5295d748e28857acff408c161c0513b37b855a8afb0764d118815bb1b68f8f09156641f7eea994ddea20f4062607b9919d041c880b71592402a4d5b92464b239caf431a99dc67787e76b8e1d7337af004bcb88473cd16b3f7640e8aaa59ad4609f060a2cdc71a4b3ed22c1506a7050a63bd8ed68aa58a8109980bb3f2b9f9fba9599d7620b8c25e8aee739095789af83529cfbfce5941d7f14c8ae30583deafdc7c25fc34e75bbed6ce4f6b47e9647c12333ce08c7db77dc94161cfc43f7ea0bba39def8bf8ae61c6fdcc0de6308af963c6d9ef43916d0cd2cedb970d5937c9fdd4b888cc66e99363b5a04ae62a4349f01c3933ada64080741b1781754e4a722303faef69e382cd9823177e4d5ac83e76017124a7b1a41bcdbb9c4209e7b42c",
+		"eaae1c53919e2029c137a80f91704d0871be2c1870d0333d8bcf7f94",
+		"4c434cddb399e52457113cc7e16f046c3f8301f5b6c296979f8a091a9ea557ea",
+		"b633c1a0e1ddf4727b149b3d",
+		"f1de487001a580cee6edadb1ef6b700c861a70c6ef16274447b8c61bb10d2d1efbf104d5f7d7172c6a5cf9c06d886165a2919ee9418e2e8f803d47832dae5ef232ee300d1f973a6298c22d777a1b16264353cc731a7a683cfe31e0abc704460788c555c0c24f281b81d7761235a955c736f17f213a896b40a034609ca8456ec3cf5906d01121b7580ce19d89347b6a59c81add318df487b2442a7a8b5e30df78467abbf46bcd5ee5b994a39ca5bd8846caba6f02f4f1335b73d4e20be0b6ad85966f86d1bb857713ebf947ae936782f1f4929498bbd66bdd5ad6fa252364a5a6b46180e93b54cc321b3cf63cf23d55392475c6b8c8c9dc707924b55544151c7c55ae0bf391f793e52bed70829fcd32b2926600f65be0943d6a9a96547675426b0dca9cc7b0f5dbc9d5439d0281014c6c159d055d6bd89d67828ba7fd2a0570ba82996037f7dcce297fe6518331270f6fd5ee63d406cc5081472bc5f2298a9208dba9398ccf807ce9af982885897715b3c5742456f756d79c70434a9baf7b4b6664c9d9f5696c5256b74099e593f97a2d4a469cb3430d0c3eb06083398cabd58af598945a85c9235a3fdd9ba7686e54d0de9afb594b1bb030be8e6bb839f6b45699dbcd2f771db64b0c62bbf6c8672fb412d60c00b3d87f82ffff6512e8308877573323c5a2d6a216ce3e2ce07c9763835ae59d44d7958fd873e3995b62b1b347e489ce86e023ae27a6cb03ddec27a38fb233499a714acd89232a91d38abce30299f38f437f7a46df647f2be862c1e7bcc1e4263c2147b13ee5b345b7fcb973f3ac71db8bc12309f67ddb62659bd73fbd20664eadcd23a79233386aeec1a6fcc8c592053954ee53826cb9b6bba22400648887311cdfa5414c96d5956fe193a3729be1434d923a3f9849f6c419f77ea05fb72f3c4f75ccec03b7f7aef8c8e55c8c5480ee505ae1a7594e6a911dfbc39dbb0ae8656f5972eb644c64203a920fe0078f3d050cc5666ed9747c23df7853d6913005d0156e741a5ead3bb1b22e5bd802c303a73a961f0b60d0fa698041c22577b44eba5d6071de4b545d9f5de24944c151de6a189bfdc223e0507c74ff929f06a2e7497e8c63073294b4aba110a006a6e9510a9617405d9ee711831e085940006761822672549d1d1c70e50002c2227f6f304b9a7f11dc05751be2dfd297087044d2e20ecfa0c091478d62c1bf5f0aacd25bb0384853762a51144b77d30418b633c4c10a6eda7b2eac46905641da0b685f85349749a91cdbaa4027fc50eb97a7dea9e8cbb5b5f386ace0363803ba579cd16ef80dc40ba1044b4ecd0e81e382635d7855e2341b18e0ca705ff46990282fe25093a248ca04a1fff64ebee25065350ea4b9e5990da4dd2e28688ab08b6d6fcb54d70f6d74fd7e5e05d21c12f5b140839aa966aea9ee094a923ee5ec704b5b709ff009c20ed89a75468c48b505d07c7a5ba1ad54ed610886c9d84468eaa598c71b017578404c909dbca431703e0cb1cfb975a696a1677bc015a75db007eccdcb21b9e5e119c48f148c2cffcf29e245e52156ba5ba0a8b0031570e4cbe7b3ac4646353594f0c4a9424c9d97845c5e1a4b4016df9be8df3013e5269484cf32258849afbdd733189ea11783f0f64d3aba9b4f48818011e868cc03ecaa44ff0ab83ed12981a6df445294ff672f3a16d6e0d19b90007d4646e967e0fb1763b3c879f548e1103a75c94f3a7f72be78555eafc086c1c58d1761aac60b843704f234c55b951a1303a12705f2120f784c2bc1494432a94c835d908f0edd5cbb169afd2d38087ca5bc5e5df9c3bd970dd2da4fb2a00933538148ebf669a20b5beef0402e53dbfc3a0f289b33b41ca27eb2f036a22f0d02e0617bd01e8c74be264515c9b46b9ab6fc67403a35837844580794088a9d3c14ad9309435daa0396f48017be524856ab6c191350529962ead64bab33171a01bb3c144b23bed406cb05102c693ce5df36eb541c47e871acf56f2b47de687eb9b3511ae83d06b1f69fbcef3225c3469c304741437fcd0ff4ae3484c117f51d24b6ae1363beb7d85d9b61e01e3dee901b90f2d3272eedb384ddb4d3b9594b9c0926595e500f8ce2e5cd407bd7a4e2c8e6f4315bf693e8c961ba5b8a6c7f5030c68a6b995e9d3f9eaee9eebc9d679eaf72a5f1cb6b2fc66edc7dffa2370dd778ea7ff446121999afba7bb35ceabf626c6269bc466d65f7f812c663bcb2fd87d3e09ab7d71e727f66d20ec48a5d2bf0aaf0aca05d1546d6e974f90df85c1393e3d45731f71ec7b5cb6cfb4e5c29976ead6944a99df2045056e198b19905362d4e9b765adb65eb089233a8b3777352665489c9456cceed593c6590d9f3cc4024d0bb92e1a0dc619bf8ae65be77456c18f8171e4d2d846073cf5c57ba93adbc0db9799e3d98934aa6899372acfa4d7d2ea32e20164b79c71d7bd33c94f9a781a25cbcafe563462eeacaec0e8d9d6c0199de85558a3a05d1ee3483351915d8a4e65ca0ab129a2386a9e26aff9b912c588babbcf25f8c467145061b9b8fbbff19d8c6ded8527d457be7c926c8f490bbcd627b3002044b7729a52e94147f95772591616f6074047e758597f410b3100f9efafaa4137dedfd0edfa85b0927804f0b4fcea1a174622116222004d42b36c2c73d04781f2f49d080f351e57154a3980005bcfb0ea34288e2fafec5bfd01e1f7901b3efc71ae58bf8df4cd7c045856103b77bd78073f0174aaaef4a3c0e8b5b46dc92db55478f012dc1b7d513e215e735573257f105d2390b5366f49b61809033c13ed4e1ebe19ab89313c947f2585f0788a0c5de90b41ad0dbbfc604a0d414d0e5390a0f3c9616cfce4097e38e05888b8bc6e55e40368bacdba7e5b76f4bd8fe619746155c30b38807a1ad325b00ecc3dbcf23014e79f1c39af7cdd0dc7ea58ce733e6611b7eae069deb047aeadfc21960e614db19d2e7e0905a9873268b9a24f856c28059321a742cd6cb3d1527",
+	},
+	{
+		"c89c3cadc094bffd5ba06c600dabe30ea19ad037316fc13b895fe0e14ac8841264c1bf25557e22b01f8e102c3af43adb8e0a12bf79d3fa0232dae37ca3688e07294e2c7ecc4e2eebdd3f17173351f2c15b0480d4d77bd70955ba86f82214004b622cc92f7bf81a5837326f6a83612bdf65abb33c268a457c45cb7467e074b342a17c711c748c74abbee31541444020a9ecd4e5125e2a8ea3f6030bd677be18183a8a34af16a85ad48b7015cfb036789c0a5daf68883d0c7e401754b8d56cd00ff605be0cad19e03989f608392c81d636de859e66c2aae403c138bb96a58ba69b9064a83e7d8877067e7f40aa0016e0df9b7f455d292a60eb621b8107a727a3378c4b7509d3ec10526c50fc6c66dd4b015c915e85bbbf701ddaf2258119c8b9a5132eafe61bbf38870f35f375123f766ed0d4f38b9364a86e56cdef6f95a815a8d7c48ff283c77992fc6c070eab7d7c7b517006e5d4af532a7c429912ebaebac27249b4f5112d870d998e1c450b98c05d08c742dc769506f2d7a004c24ebf84c10838b619653e27ffcc4344d8db0435e4cb77c0410cc734e36738a6b5f72a7600632d19c86b40c737830b0f5f104443dbbb031dc7ca51ab318951e7817b5d81de8a9aa7f5db6e2d5e7a3cbd8a8100653c048204ced3af005d00e7de7b445f5acff901c4d46ff133e92ef073aff1d9ebf55befc32f9ec38c9eaa6a1aefc974bec2758297e474cacea2ba4151ab1a3ca0762c64a5ca273169d29b83c164f77f266c01bd5075871e17426068ed7aa58ef0d1f2959b19c604eb6187acc57e2becea2da93ba23159ba73b9226034c7ee2498e0ba34fa8038e5e2c092a73ebd9329ea3d648d6ebd47e1776941ab3130cfc91089fd0a0a36f0ecf68293343f275d2a64c1b7d27ffeb3f667f4a19824706235fa5f3f04952ff08bb183c0f1aa1d1b0edfd2e05ed093543788f5d0ac6532e15f912163275053b202d772f381900e906fe070cdb00421e78c16b7387be91adb7b3b3ea28b92548d69c780ea578e7ac66eeb931eefb4067bcabdb345a7cd2022085fc494f118215adfa2443630bffc9faa8fbd9943c3140d81c7532895734a9dd20e31c326531d06f5623c252139c4cbc882640c457819c63f6ceed4e03872b246a3766df69373ebf5af1116e8d5e1b15745bd9dbdd663fd4352d1238a43d5d1e74b3edddfb1c9d460daeb49afccfa0712b7a4cf8d07ccd0599ef3e4e1c9b5c814f3a6f3a46fc80449b34df87f47ff91fea3618cab2d5c04cb50e8ad199d752d901b21348ae939d39c86cc1bcecbadcc6f0e581a3bb51e070507b41ea4294b35456c69cf55a2a3f1296f0df73abac3a9c81cc303d1e20ad6e9bef48de83fc22dac2cfc01ce9ff3f70e00ee49bab2f282ceb6859f989075814e690e36a8d16354fd6056cbff49c30e49b1570363498531ff0ad0979a4518e9ae271f57f883abf5e301c0e24a83f09335479698911bca90269a28c0e040a98e67c9e55f4c91542f921511dd980270cd490766da22306b48ca9309aad3b2393b7b1e9ac7afeff64204081f9c0a8f6a5396d02eb9009901ca2c0a75ffbdae3a38ccd5007cc4f6bec8fedd64086cce5c039e8abc9e23bd694fc8de4e858c89bd585ebdd422b492eab26f4ebbdc1d17dfbba19b5ac458c31320a161a52dea638548205a6ad4ec54875ca34238c059177bfab2d5be0a98d12b3932d0661d33ec655446d0283224af8ec7f1c6874add03448fd8029a71d3c5aa06951123c9fd881d435845757df50444e6cacc31a8cf7537a778d1184b96c3512cd474f5d1fd1214555789d24c8d173358e36400b2d937595109729d9f35eecb0963c0da60d2eeb52a778876059fa95d820d5d34e7948d389dffd53d34c4083d27c917879b053cc57dc43c8263e5dfe5f33c19dad0a7126ea6e8abdbacb318d37c305a183596ddb25b1934beff13a4f24fbdcc2064de8e0bc639e672ecfe45692e9f8164365e1691784b4f775ef369aeb135ce15135c20da95064c810592ea33316b9767caaef842f948b9573b2205ec57d3026a2f2244c42991462e233061549cf9bc66a7b4a8a0fc61f73883fd24dad02644004989c4721a0aa03d3b0191d7fa4d3da102e541fe463936c9365ba30681e706ca70cb3c8ad5dcc710de59e7d8a6247aa809bba74ff4dd182a38bb31baa337841302c19ed89d65e87bbed05465f4ce0dfe89b44d7e9266a8ca21d984c41109d813ca76eb67dbd4e39aa437ff98050c968ec1e40c534ab51d6b8ea2309fab08b3757e9edc5972bff316f6f2affbff458ac0299613734b30dfdad20f797d172cf295cbcfee3d8ee25485d40380d3480a9372a1a6e5ecd7c4c6a9d34027ea6c197f37e86e757750c9fc24cc7cf814878b8628326c140930dbb2041bd9ee87f36ebfdbdc34522cfd4e50c9cb48dd52d4647a06d08e0f0069c104849bf30c8e61cb693dffbc69fc0ab9c5d502a227d606a1dcd630ebd799acdb1e47ce2ad52ff53f6cf4fbd5f0058fb5db915702675ea44334d42e0b6ddae78b22b5b5f7e5aa36519e31278e37b64312479b14aef9b8f12d8c1f39faf920851bd53b13bae5490c847b3312b2e956c430f1d8deea91cf171dee5017e7709d0346d81600bd5f0c41da3f548c28aa50589b293685ba059cd7f3edefdb5d8cdea364f4a42153b0632ef0b7ba18610b71fc34a781eead1dc5a00ab47b6840590ba44dafc6a16029cf50e089684194d93dc881beb62edb7ccee6304a4e71a35915f109db92690461b9e4ea21257ffb62477c20feaafc7a78e2aac2301b66893157920ce9fb114ab4f534d61bb3d17dfb4d9ef9f79a736f7c1d32ac3998356aefc876d8c38722787d564e980a1f15056cb3fe634d71d2c98e0475c79cab318b73a863362f85aeacdcfc44e61b5aeb870de9ea5b5abd24e8c19ab05e45e1e9b8894deeb9d29d65ae99aa94b5047f3c1168276cc2e491aba52b5b03703ced28c63a167f0cb3e4bb4d8e4f0292cf3ea4376510fa49a1a5efcc00f23c3cdf6402197b81262e66e17bf4307d87ffbc2b37213b316bddd65aa9d64ce6122c4a1545c5966bf4fc4c6ff17ded787ca9a3b3cadee435bbba8f6590dc4ba30895b84d5b4eb94f4b05be3c",
+		"82abb4ff5457b21f359754f151e456e2c0a185c8363d15918bcee0d6d49f12564ac655",
+		"b63b577e719494032062e3f63682098dcdcfe26cedea2a40893c847a331e4ce9",
+		"9f1d08be539f1244f0f69ad2",
+		"88dcdb0309f8c4a96ad5560f8210eda1f5afb31b85b7a8b15525777748967d4ed77c063f65d64ef19b31044f2adc690f5e457faa1abe2e127b38c626eaa94053c9ae1b6b4d0db1f02c8404b50f58210cc9fcc6fa4ecc615631da631031cd6253b4a13a3e88295ffdc775fd4bdf29655d9780dbe02b0a82aad4c4088e90b51f170909c0f98ff93ca3926067ec94be05841603db4f913b7025a9ee34b8d8bc629ed827a2a9857e0814d36b83cba21e670f8f94ceb4be5757e0b8782895b5d8605868e4f584b5bb6a5f3a94edd9b23fc2b6fa06914aec970c260fc370aa245ca68888c90c43eecb68474c9e45c53a7da055f5bfe39b56769fa56264dc8bf4c1616e30262bd501ff9fc5cd78f73ad89e093feba0393a11c6b2cbca765ba025c40dd0417dfa644fce96db5a0362235ad37a317145e7b5f3c7213c7fb3c393be57a1cb55035f06da1f0bf665653c5fe8a0f3ca67dbcbfc59852694d34819d0978cd09b508d103017168f6848258493be737cc24c2112f2afeabf41038bea1f74bc8656d9910b77d33cc691a0d9b12f7c518ecef93423cb4871949a518d2f06e5427823324275b97110f8f88b0d14788741e617f4b194e679a1627da50376a08d4f23b005c0446b46d4f534ed85e4692e7946ec818437089425ee30e47de995e8774b61003801de67939d9fed7bf0cdaf625798d0d0d04a61a2482217b890168e36f20cf1d6b81f9daf1a49a781567c4363ac2f3ebf0252d5adfbed17f98cc264ed2765aa279b7437410ee8b4cf42932e5055f4884deefd2a979ab1328f97cb750b3b7e4615b9c1c61659c90a5ff6d1c736e785587ec85040fb2c6decf789c2707974bfcbd0c7f699627b31e0762321d55bcc6acf1aabbd44abd7766d397bfbb68c424b311611d9eeb6598ca3126f569f688455da8d5ab86eb01f9c96186858c4b5e447aa2b9ca11aa5453f731beed4e09f95bb7376e200212e2f03551b8b09a19d6910f25898d692bc20bf6ed3ac9a0276db560de5c9e264f4db8fec6577042fbbd4510bb7070086508ac451a1fda26582c259412fbf1bd60cd5e921160c2604fde559b5ed4df52b805010b225f999450adadc6e108b70f169a3d8da6efbe1cce1c4908b004e928e3cdfdd0b4c5f742fd72a11c9585aa3517486201b6d9a98739b77970a88072750d29d005a291546f13b576b4249d71f04a9abf8f653ca206d98f738af2a1203bf0975f0a40138df054ee834ab73a3b1d7036567369a7ae15f808904e08adfc84b34a0e1356009d8a82e51c3e8f2170908179bfe47be8ad819cb12e85b6b76bba7c9b9398dfc00f550e32c171b4d5f2d9676063efee0b0b49660c10260ce052dd00addc3359e35c25dc33066d4b05bec7d93f71e0ad7d5ab83d844c7f33137894327f464260688ea4ce9847046e7dd0bfa48d4e15277a9586b4742daf0c5ecc59aceea6867068b03c20aad38d04a814472287d809a9285cd4dcdbf68f3f4ffb794701f4c265b2dff4aee55c9815938689162e08309df150538e60dccc03d495adcc560fb831444b922a6375845cef5dec56eff2910b5bde5f25f0e550ab5a13205de91d20896fe04a8ecc2c83d1371cf381424f8c43d2a5ced374878405f52bb92f4fa3c15d29ec151508488f9b4e42527921e245a8ee4b5d6ee95797f6ec4374d79acd7b467454a1d7eda05a8ae104534b23c46b27581abed6afc3ca555202dda94fc2b93501fe78867730a84f6f726dfd7364bc240b65d6c3022a04e09c89e36a809fbf244cc5522315110e9e33c8a4e1f1396e3e51fcdd53d9ae94fe7bf6c6ccef0ce02048a11441de3c25aa9787c577501977e486f8dfaa4c81e3183e648311148ce5cf3de56878847a9d14c0645777022c158670377dd9553eb63eb17e19ebb06202be8fd9bc2b24878cc86f9938e5996751ad9ca04b636497199f7f27dfa0f5ba2a01c3a491bec6dc5113d127f6aae38fa07ce7539a0c1817f7f0de0da538f4d85ffa394784a42eb50994e28530e3997e3345db28bafbb836fa463d34146d9f46d8d2b28b3954b9bc7f84046828e9b55e2fd663e562aa95caa97873f48f0a003d2251fb3ddbce0b6072fc17e0d3f99b655b8f41e8e6986ef7526544222e2d402489eabed4c219540605b9f5dd321ad902708601e85bc874c11efedd072aab7e10272c87b08b9457223de9fbc3abc2d1346656a524e9c67d79d4053c4257e886d6b430f5b7f57b2e5e92ae69273c1705a3074d5066def69fadea1af8fa9b3bf4890f9cda4b1833e5ed27f22bc4fe4cf452880c7b53320bc7cb748c0af6e7550ffa84e4714ec18d208131ae9e3edc6cd6fa2c60ab8ebc1ee56eafc01fbfba061e55014b9711eb58fdd01f8936d29dd081565de0b175b02989c5ff374e6f58c3383e9bc00d8a93903e6a221c7475e15aaef77594849af877f3807a76e03bdd54ff0b192bf34385d24d858d6f454810ee48141d73e3acf1aa3d19cd4c723a634cd8e25b4fb604c744e408dfd82961e46e8444f001d0991af24b3b6ec57ba41fb45122afc73ec6b25f501f1abd46181247945729337bf5083e5821968502a5a696043ee696c290095feac000957f968ac61ccb572ab2f37008830ab9a81d02456190af99873450b52df1888c3d8b6b13df65a9bb36a4b6d0538a0f179daebca2bed6f94b4670560fc5471c3770f2d004b6a138b8243068d754fd246e9881242638c6675f1611f237146f6e0f72ff2fba96f479fe0a662a81f40928f5400a0bbfb5ed07a87f457d5febdbdd6f323e2a59f749e6fc8a51d08b023734c762a91cc517401be57ffdf6a52b9174ea153abf2190ae2642955c3c02b4a15d72456c9d2f323de6fabbf56dfa3b566f1aa688c86b662bd34cf2511cc4a30621b6f1f1ac382bc1c4fa4c0d4d5a30ae90a5e54a9fb4afc1475e7c612eeb7f0e09e894c2004cd04126df9359d525d7f090e4b531916207c38c3512341c84218c86fc50061043ba1b89ddfb21cd756b391cb53e8c1cd55352be05efe562669e3986c022e30c79a97bdf087889a392e6da0d72cc7ea208aaf23408df23f3a9ea9bf9a935e49c9994a37a5dd0faf1267d5f7db47cf64ae1d3ec166466b2f882eb21698aa375cb50146c0e660e9bbb38d7bbc1c1c6d8333f7031d6a",
+	},
+	{
+		"68ca38fccd585eb14f953358220027046c14ef965478d3d8f206f63fef4fe3571a1b53e774b298c347cc1b69cc426d39575ccfabd5a284c7a87a0698cae9efe40543cb79f5643c3c3057a5fc991235f06f059c44a7200b509a12e864fbd748001a14790f78b54ba80cf0a4a603da9672df32b68652c1d6edd3be51cf969acfb0ae49c026fe0bce0bfc72b1ff4c47712b7a27b2cce888b9bc470b8bdda55a8d53a34d79a25947ad55b95e5406a5c5311fece3ecd46ca590b3b01b9055761da8196b21bbc468681922c66d286c32598b1e3d77f2a91d835ccd9eec231409cb2e74ede9385552517718be9f84f0f9100e368701dfa4843b7222279537306065a54d4edda3a02f1ab9edba3ddeb34dece9d5edc8797103eb942a80cb5ae130ff2e7eddd11f0cecd8f9a615d75963c44238b10ab1230d9db7371d8291feb2912d306efe4f7aea2773903d4be9a00f2bd8c03589e342269a79441c0b42ce9c6fff0a6e4e845876f7e9b342d25351fe2b1233b4f576db90ef1facfa617b96d17aa03fc824973e1c80f15e5344b0516fc28424b7faff47ea1ef4e47f6f7b50e91b8fb14027f05ca7e1bafa266a4b952cd0b9e4cab82bb4d61f99568e14a6772f36296f5d19cb04fa86ff20f04ab61d1a6f01e5282c99fe4c3254da46fb5276317be58e94b1928e3791af27dc6544f6d445dbfc7275fbbea74f98ee4aea647b654909f9fa9c88312d3759099c9d0070e3db6d55506813f8b7abe602964a7dfb9387f58e237dbf50b4185a50b65ac099352dee8695017e4dac644f42aecc3e415333cf76b08fc764a721b45d7b74f6b0a2e43637e5b4849218d3d4c6a01208f345d76af56631590e520d6bcd82627d2446b45b2c68e0be81b3924753a54f47ea27b1e08de2399b34470701c9697eedaf3248db9b28991cdc2c632fd1b376bbda279b6709d5033d1c0a3ee573bdd222ef1afe8a4397a61fc30a4e94bdc55097ecebfef6c00133dc0b72c17e2f93a11eae9fa9f1364f10fa595e8e1623dead10caac068aad3967b9ab2837dabcd8f96a77a25baef16ba84bc93661ed150ffddfbb0369683cd021e8f959c2b531bb1fa95d7a574fe5ff9aae35fb9b4a0a9829c59b932ed8634fc05ca88211da3e30839baadaea8fd9843e0e80d9598a7282500d157ee579cda5d57628e7506617d301c2adec5186708f94f069ed7bdb70cbe88549cefe1673d48c9bbbdc01d2af03945cefe6e25f757750de64cbb9d496a25adf7058f5e32c827fe75e80ba0e834e6a72344dd2aac4228828ed42fd83e4576254af5737dcd9b6c338377d46baccb02d00fdffaac12133ea0e75e791593ef3aded4ae4c9249b8d5cd20aa28cd652b9d750b88111d9b4fbe729e27882206b2f0eb614d7daaf6436816fd80d452ac71c7a7f9e8c595287407c6ab9fe8a242e98da4270b4f1d4ea7243c27f89ed46a567c643f31f967b5f12e518106f3d3e08178078cc714cb6e39079631966a9becd6f02c18e983ceeaa2106ba9043f9985b791027eb5dddceed563106bcdbc48a4ac64bd95e87c708a8cdc33811bcd16c35e193203e70ef2bc7203183fbf60d46bc581f1bdfe62387b3e6c0c4a29130d872c3f8b134e7dcfb080e7e03048c49c0e468dbc44eff4b02e50bc6889cf7600fba58c0ee409ce948aa684baef4956fd8fd4a9c4c49e84e2ff314b7900b179fc66f5fb4affb9ef7a6064354fad8c3d2d50e6f2157576f864a843dda8f547955c4d80a73d4a86b7aaeaecea886927a5ba0e97df740ec7e8b70bb650010df55d4b75f478b07b205b560d45de666d84206c1bffd02ab7b8d1c37f21c47d1711b89d16214d8151a8e75eeeb5c54c39e5a855d578708d314240a064051d8b26c6183ce755be38fe9597dd5b5d198532b1db083a4b856b8dd60bf1db197cf1df852eb6daecffd97287a6cdd4c05307722e0fac798507f75b03e9361d5627ecdb56a3b633938fa61b2673efe6c6e768e4e7055e6c1d55c7113efd3e95151b606bbf169f4296455dccb93da370150c54fc11b3682f092f30381c6ecd218a3d9d39442c8bea61d9a71b174a8b2c56e028689380879cafb7c4bc2691dda0cf6ada039755edf93f851446df9f63267f8b8f030c069fabbe6457d4f63575b5905fb927a5a720d52c351bfbc48f12440a91471697e6b2564b1a2b314fa0e6dff090079637287b635d875f120671561102ad27aa83d9f0cee41bf023bcd703ad670b43ae23bf01713650834cc1e95dd486757f0a4f6fc9337bb95738805ad5e756198579c886eb0ee77e4ba957997dde0eecd84e4c9171c84ad8f0cb23c6a289e037f3a8beeea7965ce34fa47cbd727baa4ac9e6dc3baf17049fd2386674b246aca5ef6b8496f1d17a3175f6fee86299232c7fff682f066cbed895155d475bf9fd4b5571d257534c88c93377b1a600d4c280d42aafda975eb32c740073cffa610b5fd2dda7262a2fff5da7a0f3a875c62949e0c9247827d7a49bd8185bc27967124c34b9725ee961bc8102a029786652c2571be6cf33be63cf867c2b48e5826b31b714a415fe05c27f0862a870d8fb33200719ef4ac8530a4ecf2597b4a7f2e66f078a7505803774889a1cf963083c831f46725a1ec5545d8489e53921d81f80ef99f5e51a2d5992c7769c2a7ec8bd8e0f2fd81de53c7b69b650a2d838b269185c5efd668c470943bd956e3c5e1bba5d3b927b10cee68a75372d4d6fdfa6782c05659281bc9bc56a2123967f4f50cc7ae3379ba21e1617553354b5030b3d3f0092c1824f5d47b97e6b4fedaa90aa2573e1b115ffc72d44fa8209fd8d372c8dc9ee00193b47c2a9a302875da331731713243d02eb5a57d5dc51c35988ffd742ddd75c191f1eb2c2214a1fc47b82db8ea708818262d9583f2b1b98a40b6ff6e94742f25661a51882ef28475aab12d9422b6ac48e341cbd6f38460333b5fa1cfd4d0f43aeb46c21938468fe3f7bc771972246156652d2c58b18c8cecec2dbbc0feb0fae9f6bc022e029111f94e8913c0ad741612a1426b53cff73fbb24fb7b22ab750ba1310ecf339fe12ced6a3fae17b4c429550794a8d68be891b0e30cd28e81de2fb2ecfee58bdf11794951276005eb8a5af21e03c8aaeb595ace652c5ce60a8b98f6897d82698ffbb2e02213e50d9d3f00bb42c8652d22bffb87ec576ef6e206ed6c846fd5136a87f38c9ad899371799f35a6258878418830b04da79fabd80e7290456fe17c0850a4c20e2e657f97f4a53e1a3db65bb5e71bf38eab9f56aa11e6ef71c85b8a28f04c271",
+		"ea196b6a64be4b0799b1c2f72281300c3a2577db44e5d3606b8b02fa8fc46c7aded7e442",
+		"7c86a2c06b7520d60023d18fe73d09c514ed07a91d50d8fd5bff00e7550faed1",
+		"952f492fe00b26028c560fc6",
+		"b3f3294815ce461c8843172efe93f73a8254e58a0e71953e35c15aa89a7bd9dfee967853dcbfba73d3b87fa60449cbcabf13b1206d0cb27d2c3fedcfa695b6d41efda37bb6db35449bd470a23787619ee48f981d3f0b1c8e121725b2289b6d67858a4f9ab41683bdaec8a913ca2cc292a9640efe50fb85a1d1f7b286f45d4448f85b3242f45ab44e3281d759db24dfabbae4259f127d6546ecb914d7e93e2c19230c67fba8a6cba6069023ff7ea3d8a170289c2b4391bb97a7b899228d032b36186dfbb29ae8f0e6c06d753f4c6b21982d49ee682bef50a5c2c8434510c5fa2b9c0349592f33f8d7ad6f7243d42b292aee6d210c61e3f898875b91a17a89148275031b74cb34e628d7b701775dbfcf87c79ab279a73dad14d8eed365eb9f29a007b7d2ccc07ceb8cdcdaece67fa0166e135c9a4b939426882eeca98ab887ed2e4888bbebd5afc9f2da3e9162527262b0fa85903246bc8b80df3060c890ebaa516781a2b2a138b98001287e12a9c68471912dd297bc0beadccdc31a27b7c726baf31510cd355a28e4ef786b30084af66ef135909795aa73814cbbc6552270d5e11d46e9497ba30d6d8cecf343d16e7e3357bc9bbfbc7c1dcaa5fafd8a9b07056129da02e6228886463474c5af1d670bc14cf2868b816cc71578ad807a37477341c8192bfc2e8b1f7bfd58827e041f70384f92bb4c6acc415dde5099a1c2b27b709f9e53d1dab07c87a042ca4af7a2a6ee57b37bf2bb42259d372ecfeaf1dc55ac3a9f211f16fef3b2d5f11dc19fd1f425c14779580b2501ec6e0a84220e7e12baf9e0fee3e8cf499a7fba6721a746f598f04ee8ab4df31fb8fa5ce2d2419d5551155c009f2780cdd225ec2c19f94fb9c8b785ad4574b4da766eabfa696a1994e64a2518d1bcade6390cc683a6e80cf8b163c3e58cfa1134ee743079347f08a89c81478668df32ce9cdd7b853db5cf7af13436f3bbb11bcfa8f6b6d727a1df84f99fb3a5c248b8fd5baf669b68fd9af45298030f3251bf0351fa9b58b0b9fba53ecfd838300790ebd689744c1b7b333fbed76c8fb96fc669ecc6695ff5bf8379dd2a3c270af858cc60894be8922d69fb9707bd2a7825f2eec4a5056e5e91714f4dcfa86974259fcbfd5f20d55923a0a9936fb20e5ae9670e2019336e15f530c0be449fe355a7a02c0938d60720d5b8f4f59d2e4213ad5251c6058312b43d47c44ffc8946a98797f5ace279d3e126da63633c0eff1c412febdd47817aaee466c639e43637c1e179f606780ab490d3f0b3c2d79709f1262305fc87c02f68da2dc32f8c544e7b358c3a5d2c27986a19d13fe736c60a3524e94caa55e853eedeece985d16bfa6c487bed6583436cf82077fcdcf90a05f49db50588f46550f7a0c3a1cfca902d66d25dba8d2c53bb5557cc1d87c8a407898b3c30c4f0852df92d839859c191228d0a47324ea9ec2e0ae84513cbe4ff4aff85e77b8587f1044bcb9775099ebc2f28fbcd1cad58a8ce1f072f2228f559fbfdd8405d86f8262c27c3d95e01016b343c6a4e59dec81b59bb6e3c6109a4cffffa85e9752ed2149b5624417c0dfd1a27bd2630bf59814f15820c43bfa317be59ef6f433c95e8be154a8ae94765bcedadebb717f0d8c24e01e1952bd104ba9620f067554ae0faeb78f13c622c45d97b2b5774a3e30cb07f2cf0e8b19d1266d8a8861f3772305e24ec5c9cb714806c7d705a3bed6385f8be4e12562e17ec3df01afb4ef6f7427c48a1bc0e64fc65eb1c3d3ff2d6687e4c275a019f5ab5c63bbe47e3680fb1802d5835c4d494f0f394de1ae47f81eef005127d0971c4589c456ae6a69855f35635c28b590c1b93f155fabcab59b6c7cd8ea1c4ed1f67093aa782c54329cdcf9bf84a40400de707b894587d6e08cf7fd72fa45b6709a26e97ff5ec1269b8042358f872a79e8c2db1c7ebffac014d6b6f71b0c1c1945ddedaf5b6911668059b61b55eea4737aa307c829309c9ea548fba2bede023849bd61b5a467cd1ab1c61205ce64301e2531e5d58d03c74ecdafe1f5b74627be8716cad0d0a0be60984c9f9dfeae24a6c4949170ce2f589326e0a76c447a578ea3a5e4bd9f18884f18843eb1a78aa2fae06a7569a97551b227c34d429c8e1c8c5417ced93c30dcc607cb32a365d87328aaecb4ce57ab8e74f0d9099e267cfb747a3bca9f76b5f6dfb543bc4b5c06c3646062ec14f511058eb2939601913f8a0f1785249cb72b0bb1c12a9508b23caf490537eec53f614f3e06592eb61f75c1cecfa514cf7b500b0375095d5db74556220131390b77d0db72711c0c7229a5769b1d2b3f5105f3a4370beb1cacbd93ce32f89f1fc833c7949211dd204616c013a3399a22f5325f1a00008f4c8ee7dc5bd7476848721fef843123a6213cb0c0b6ae84233ed01a77a115d06e08990b8e60cfa4f41dbc9505cfae76463278b6c6b5ac7c3b83284caaba4a6a1d739c392528ff5b06bc3b82e98060e3001279a44aabaacc661fb14e7581d1235940cbe067c6b386da09454e0467c785ed0b65d41ff4cf36ba5f63d3ff2b45c11c6c22d3ea8ebbf1d52d770e0ebf2ba0c67c7d3641c145cab474a88119335990137fa82a340c2cc8c453752a3aa801127a47aeefe66d1af1a26ee1cd0e6d935bd548f6ce33a9c204be02ba08f9fa03c685665375db7c0c656ddf3e441ddd96b0d2018beff5086cc63339f26bc8332a5e6a1422bfedb69187a3443c23b630a28b02f8075faf3ff2fbeef6cdf02ba4af47a765003de2254b69f487bb5d038759a33ce6885611198b81b0b6fc5d7a531a7a90dbc3556aa758db1657698cb3698b8207b1c1b589efe5d52790667ac483dde9543953c6392d5eb8afdafa205d325e314f810e9c7722cbf5bb76fd6502733149bf21c60717ff5bc366b85ee9f206bb1f330ea72f61a9766090eabde747b1eb9c046cc8713d5a4f8d4b7dcd7c61f2496c5b467608cd9260382b8f11b04c318a5ebb6411a4c7fa060e08c295c6062ac644bd3d10bcbfcfe2e3748eba66f65d904ff21147faa8475f508f21238d42f62b697249b9fceb905127f7684c8130cb8663f09cd25ea038078e1980237389337d1446c3a77bce41b37b50b9c3a020526e7b7b3bef370cd7af71b225700627060eb65693899d277ed130ec5ed9eee75d4886f31aa93bbf302e0c69c9c4499396b43dceb67c02fafaff8b56698308393a03f60babde883f00de2c66831f024fafaf98b2fcf37a9ce01d4f34e95c9408395716dcf83fe86c7a0f5e3e6741c3b63b6ebe9964f1d5005eeb732ce66402007beb3e6a087053",
+	},
+	{
+		"9100c5b2d7c5d5a854bce55e82f94b89a268da7b66357a661dcf75cba10a1b320ae0e4e1a5b989f9766e57f867a3810a0b5b857191ffd7aece4c796f5694a2617486421940cc12b63a6aaea20d2fac188b318a1c3061cafeae436e04d710654b96a864d674768caee03a50ed6afc06f52d90115df1db5c9f1ecaa4f5da094070b1a447251ad3d4fb0e24e87821ee6d4e7e7eac7059080f77d2b36cacbdac1c6e5063946a376865458c4ebdad3c2afcbba8a82b01b03a7882eee42eab904a19e0aead4ae515b02aa2fee74f3a114bf5b9f320baa35b3225491653f4a69e0d864cbbd031d0805b727e42c2b9530dae0c01cfc6a42af8ca730e1d67b4bb743a072f0a38008b937209d534c2284271344340fae76af2b1dd00cf44b48ab8ee92e8f9cae8845e5a8d338f505cd1c19014018bfb6b7dad487e7c8c32064421982c1a63149ec16f2bf4fe7b50cf3ce1e33d6cdea8e98bf067077c9a0ec1bba6edd5090273ca719ebf6f1a0f3e56f021945cff3c468b2dad92a947a06a024758d7505a4a1bcbe9da3a03e97859da99ed36982a7c23572ab60071566b749dc34bee1d9609e87fe32282cc9adba633c9ddcbf359ef4a83a54af5fbb5699978b487954a907dc9739f4b3f3927e66cf0c338e31c272da0cc7795c72dfe60a5b2e73bfd77b8c6ea58122a913910fe29d3360cef5d398f29b024f0dd225183d538bed2b076989aceaac460e3d45e0ca7941897f151261a024b0adf6d5b62429420144497adde6557a3c53b7723471fb760b6a8b1dcc2b327cd939528f5d7bc16ec00ad99df12f082d82bf9fb7318b3d3ce5b84ab1e38d2ebcb6713c03fd0d62bd083c4af96b4316ee02b6953431c261278aabd96e28f81adf7946e3664446135c825e45ed916ccb941350c84523296cadd5360bfe3e16dda75db10da1f710fe796f3456f0911294a4735cf9968656345b9c3049ca47176194c86f36cf702538df699fcffaa254af15b198ac37eed0837b00cd3547e496ecacf6136c6648a535a235059cd75a3bfd0bc49933b379b72e7a8463c268faaf05f0b27256fb179c9d4c923a13ec6600f83aaa2bee13e30c8e676040c06aefc65ba238a29d403f3a8cc164a0bdcaa1a5f54bc1d35fa4efee0c402eccab1e92f6b0cba94e1bd87898a9dd3957a7eafd9d26bf70866450646090833d4b91c032428bdb9097b409305de669a58e44931b7b428bf1a6dc56177cd944b87b04eabd80c64e287a5758c83db26dbc06f0c772335363ea2fb9f19c833644fe3b3fbbbbf5f9d460412d287eef862ae676f258aa45bc8465667601e9ac46e7d77693936c8d67ccde94e54d746b785ad26aa38ca0500105b6870790235e780ac50b9e3198f5fe678ae3a4ff4f1d4a2177edae183daf2de42625845973fc544907e27a90d868f8634c9d529bbaacbd228a5b4ac7fa68ac208e207a022cce4b24a0b5b5791eaddc6b3b3ef6e5dba41855ff531de9bbca0a39ea743c0732772bd32cd15c4b7f28a6ba579d902331a88920fb970aa75114e14b891d42cb947e9eb14feafccf1393796b21099e52b21773adae8e550f93364b1c438dd7d7fc76994c51860b652974d04a7e6ead207610de149f231422595f4e9ced1674d98d0e15ee841143ad8613f804729524e8a5f30d451611676f70a60c5dcc7127497f4d27f35e7ba0e48f98e9022e0deac400e809170970867a1682c7d2f3ef2c632c44568abff76f4f804841ae462c7247147b6e1debe48802674fd55b2ef1be5b4604d5f60c35358c7d773ab3a3ad0ab81868c6044d4e06a48ddbffacddadf813a2ce09aef34f3b60b666245a032f021b87c81fc506166983f25930cff728d399f6dd48ea1c745ad2da7f2cdd9e3ee915f708db0d1f3481018db1c174ea950ed17247bb8ebc065186758e5403bd4d19a445e4a15519326696e4280bcecd1a903f525bbe1e521f94d79df8db4b35f4ef7bd990c0f2c32789a75f95761ca0064bf251fa00b409a58b979e56d2c44bc2302552f118162891bd78272384c739c0c98bbaca3fc46fbb5bfe123eb25df0e27343e38b5a0c2d0774443af91b64b9d4e0649f20290edb84fcedb3bf4ba491bee8754a32716739e5ab64deb6c9888bb9fd2ada1629a59b16934ec5dee3678dcbdcc7fe5e2f3833da9d1281669b1d108837eaae5180396813883de26b957037623825b0675df431fb06b35191c06229f84cc849ccf1b1e079efc2e575331cd77b3297d2908c048b82b7dd14883f3e707bf6ca38f87c19625bec47c11f54988a97205d27ac51a32f19704391af72021b78cc4461386dc3844a1b45596fede3f70e311eba92b1d9ac221d3dc19f3fdd080c2169348f2cc8c9380e12a7ebf69efa37bda4ca6f7e66919b94532ac43022c0518c04d0a8cd99e0cbac88b7a317a1dac5469534b4fbc64080196b44498e149b0a196bb2d6f59392a21c4a4523ec1ff922a52de790e42810fd9355471169d22b734dde4a3361ecd57e271a92132a8b35cfa91d508d45618ad8c6c1ea209405a3d1d3ee1535caeaa3f20546052fc13aff7a584ff79db1726678344098d8563caa2a2abf6fe5aa03d7af49dccf1b17be85600e7cfdbfff54282394b0fbeafda615185574fdff78d59ec2a26dddba1c531a1ac007cabf5be2e2f0a3dedb9174e0a9da5597c9de6d68911fc66ec9d2b1e3fd71ebb83147ab14384ee303d067f47a324a01fc187f54a98f1b0848fdba2ceb3c18936d503e71887d548c4dbc70b7eecac9ead3393f8cb85a84f1484f2e237b36b6d886f54a0f629e8bb05b0c6839c722149a5b541703aeac04e6eb230a5659b12ed0a668d018f75bc94258218c1f5390b9aee4c0b2836cb76a47da649e2425bcf4cc15c4d51d109e5f78cfdb88137c31b2510264e46f1c4eb6e6b3450ad901ff9517b47a24d508844dc85fc5dbcc079e2d09f301691f401ff5f36500cc66f0617eb4dba389d427c7ac778d78438506608f0961f818a2080ea56d0f61c40fc342b49ee63e730df61f757387b9089e1987977b7fa02d87aec2e4be24b8bdf7fb6286d190f9df870944fa910df32f178ab692fa56b071f57366a3981f51800ab416dc4500abcc19e0c6aaeeb9ca063470993ec749a0bcbd07604516b1d51175ebedbaec8986f67a4d9158f75b5f3bcbe86a83220b4fdf12a0242951f94ac7d52882b1b209b82c4749753ea4d46a60bcc4f3eed033bde2d3d20c25cb46fd907f7052217a0a4db143b2efe8875a59441f4d22ef70d0c244b2de6a7e15581e84c860a6326ae3e3aea6d3972e2de0623d2d852c9e65eed318bd3d86d29595575df60d9050e1740f884796b6657718a294adcf2303adf61c6b23933db93885172e82a78f741b8efc6315a2c88ccb6b11692a346cd82a79334e0c610734e61e6378b5e2ecc161d924778bfcf4475805a0823a0d5a54768d9272ee99b7c4a81b3d5dfe1a2f5ff34",
+		"3c77f30bbb698b1571aeb54653fcae2c23dc16be58603f0c361eedd813ec0c4f63005a1e69e533da93e820e6e4ce1308aa29c60289060ebf24fc9738e8a4874ca4e26a0dc79ee75b8607416bd554737f",
+		"0223c0a6052bb3cdc99a284fa169ba76be2df53b677642a606090a9267a60769",
+		"7d3981073f90c6648c5e9c74",
+		"61ec5230306b70113f67b340575b77ef76d521ff75b754d551e4177591a02351ad382b2a4067f2b3af7e8e15431c7133e98be9d8293d17ef40161dbad9a4f1a4f30cdd557bb9a8b03b5f1b277c850e23ecfa0fc2ab1102e4b1d5e836a606883c3d43527fc3aa26955964b144a9a56cafa7b174d72a0635b80e7b4f871ead3838a955a14c4b8c5c3c66fd86a5e4ff10dfaa92105378bbc5f76ad29727e5bc4779ba3e6dc19bf45020f6ce4dfb3400df05cac51577d58eec21b22839b8f055226b204e641783bb3305b4461172f1c1d48eec56fe6f82aae564ac6688d7b0994747d9b23a24418e69f8a4fc548f854f86baacbdec78b7597b138c453349034c8cad2ff272781e0e6799ef2f8addaf18528736aef21ef8c2d213161e36b2c7815fcfc40747626e0165684e46a9a2275c533d548e52a9952a556168195d602ead86f6bd699e97ca59f4cb2050ff148f5bdfec358dc4542ff2f700db9861dfe5ba377ec7fdc0fcb2501e72fe6873c7cc76b95b4f300857f76e6e6e370119f403b556115b19fee7009f4f6675ad2d174f44002e35ddc360f309f20a3a1dbf39d90d7e5fa2106c53afb0bf445e4cede59cb50b8a7a2c0961d00b2c251f2d815309f74a46a424838ee87f1229273ff3b66dfb79e3b1ce11bd60e061e60e3f37bd7ac896b618cd78388590f44b1a276b965a4b95f2e3a7a175b30fb45dc7a71d4b3a1a33e98af30dbb46a217c50046ac21b8bbe9537c02f05a5780c8a5d796bd6424fd9e9f3ed5932069bc050bf4a1898a0ef0ca756aa2e2269b709cc92e0c5192ab49d692143388ede2bde4923c85eae8f59db5c7711dabeb33743c692be6dfebd815456958b5e1384a109f891f433e7b4a1031d4f30478b05766dd97eb964a28f2f7b55aa6c27c7f4ebf4d47ee8709bf99915426b3896412a855798e392e111789213af537cff7a976b4509e0eb6ffbb8e886a3596a242d16d95109b0ff562c624e06636a3611f804f9b2e252afe8a4e5e868b48e9e734f688f2da2012d7fdfe2d3aca75fd74730a85aae90353417fd52b92d28a5098b6af358a096b859859916bcd5a8f779676c6e04ea461fe62872050af92d08cdf1124bde1e889ace3c923457ecfe0a635ec757907a131ad7c2ca3f60e1317880f843c5e63f4ba59ab2882a492dd1e070b070af6f60e18cca29541206a7b267c3f75a5327fd9b8ffc9b36b57b73b36e586541d15c85253e17a2581e8f8a1518f275cc79afcf2b5c88a16e9bf553e757df089b5db90a9dcdc1867b788fe75abb5161dd7ee1cf37d3f0faa793ddb1bbf1eca13f4220ea63af8ef7c0e7144d999ba1c5a983e74d48cef708c1d28d3c0a168ab87d0ef70f381693f0d438ce013ffa2cba65a8cf6b498a7120209564535b7372690329cdbd74eaa76765962720f06aae58338a10064ad80f5a67395db2c31d36b1f5eb777306395f192599d2f737327afdcd9f14b3f24155a3f974915d3302427494fad756703b13afcd1764ef9735e7dbff920f1253cb668e9f40632aea1e0b4620db162138e4a97e6f0729b14be4a7c3256250d5e7423ba1238c704503c51cfc9cb68db7001b2f597a15e77138beea02e11e0bb98a72f2a77b7260e9172fe7e60483114ddd836addd966b69570db5eb26a0cfc4f8a8b80d26357ed51a70165bc0dd11ad7467688025bdb532e7222ea12f23c44d08d111b0ad4acb2f5b3d6b45c387d541ffc84466ed57acacefb1436ef00bcb5b6211dfd0650113ac369b9f3e4891acb2693c377467b1e9c949cc0ea6c4a72ef9292964275ed397cd2b1ed25fe1aa8f47e90cde362392da5e53893eef6e4f61decae1a75e3b726f0596f09c3cba62aa08bea89984b484d5768296a5afa8b0759dceba530a169d22b81979212b3343db35ce4e4766dd251ea6a47f5033cc090d6577efbed441bb4f8944937e812f12ef17ede76df621bd4cfa31567ade18b74583a2b783279150d584ca13c0d4784b70156afdf9be8ae96666b82def888465cd3df349de427d5f5b3572e4f963d33f968e6780e381ca196bc04a6664fe93fdc8558b21b84130dfa2a646950eb2e927885925af46d7a28d1507bcc3c02ba98318bfebe5b9eea1bd47935ad869eb701cbc35a9aef5efad88ff54eb350a34ccef2e159de8e16135b81105bf799fbd86aa11653b5ef93a1ab1c367231d61b42b8bdb4f04d8d05396d53247d51890be9b56c51cb19eec0fd1e6b8cdc98376b6c6b30963ac7ab02656ff94dec0e3a0eb3f3ffb8bebd99d5889df98e6c77093c370373dd5f17871fb334c7eb12c6ca22deb75bdac9eaf24281c965dffe03da9c940e13fb382fb6be332797813710a7cd2e7720f5b9e53fc0d98fcceeea4a8e9f787e670d60bfc4a849f34571e5d09b9e9c28cdf2b2d888eca9bb31ea8b9239bd19dca86880ad3e12b1583acc3a6d1f0a438ce3b5a337487279dc4ead1b214272d455e6a2c8cce4ae3bb29abfdbe77a67ababeaff5dd9c96b17f589cd4615c0209eba5e4b1c7167b4b739ca4b9957185961529d1082226f85068890c94aa1f1c244259ef7b120e40114926a49c4412b67b4caef1ff3ce6f3aea3c6107b830cd34df9f4d73d7d978b6b9d5c481e9d76e83d649e742b098334838fe50d80975fb567642d3b72c461ef3072ebb1d03c0099e97575bae6a12cd2352d9d296351df6965d736d7568c2911394a73d199743526ba54dd62c56c598f4e78495c0172739274c0b8c96755e489765723a24a8704093a94544f6c8764dcd1ce6b4bf2917cfad27d85e4442b4e5bd577ea1a88c2b79d61cc1be01ee9028235b36444483b4e45da1087bf6d45ca540620de5aacc644a0d5c4b807b582c7b058e140eebca539947502bf73c9abc81a0e3a618b39d3a38c4ff7f94767fd7e6b9eb61e629806bc3d183bdade7e369d180dd2f57fef677e22ce41be7224f11723a85a3f1d14d7b72dc98ccb2816b77e625ce3db3e2c5753af8b079e0d63939079a01910ee4699cb405d4d9c60e4ac86a7fda3a4c9c290662afbdb7678c3a84c87ff83470fa8a416511a06d3216a1445699d7ad7e6980491fd596d39762d576b08fcbf0825243c1fc01ec8300780857c429c607113160a8354f6699b368a87983464472a5754fd58943fca6f6779764fbe6cbb510d5280292df02c4a7ed9acec8c95ad67ebcda71d0f519ac18db9b43b28244cd34fe02c5d694df57410eb54c5e1ca0f8501e7776a811d7ee81eb9d8c80b2ca50a012b5eecd5428af965b217e7fdac80be88a01f76d473105b027eb557a523f13c55e1670ff34627667649573e0f19dda41c525a8c96c2866a88bd73e66c786767e1657960f6676d8a22be1c6024158a0f0e4ec761148b5a3d8ea481d8fed94855be82479ba23213190054f937838f0e35e00aa74c89b294c29ea25ad7e96b4b6fa952ea8f1cbe5397b7c86d0b74ccc25e22c88736b045fe86110bffa0679f28a1f27162b51410498cb7",
+	},
+	{
+		"0fcff2c29cbb5cc40bfd2ec573ecf368275ade6a00e5730b77dab17e437b46524b3814e7f470acff6ddac4e0c6b748ed112657120bca1d83a4ce01e74a473995804d7c74bd28732a02370ac8ef52b600790d1284d82f077cfe096448509dddd0eb5944a882b7d384efdd4dde3003dea910f12de82035651e3ec9668e66435f519da3fa1f5bcda34aaaf028daf3068304f7b1ec18e65136241a9db281e011d27db5cc9c1099405a4430821e2488a228805314983966ce5d806b0f014c21d4c9d6a066e63aa6407ed6c29cfa4a3e22ca913762ca9d31271d9c371fe858f3b22e931814cdbe544b9416e88f6026b12bb8e88d8285beaaa35be1c24339b5f567480d7b16cbcf6160e549ef4570a0702889feaa0ebc54b11735735b6e2850d5715e5087291fe8890432784aa219bacaa2b874b075c9628cfed5e76dfe38426f9693f6bfb2de49b710c101b2dabb7c7c74f12de9ba8f75b8645d25629568d12bfbc7eaada63364b6f56569cf21e54c95d6797e9008f3496c506ecfe5d6a010d168fb7f0e2ee3c423492df36a133fffe9b87d7ac070c32cc131fba6089cb7d904b25812e03cd6048504f7ef1736ee00ee6b7aaedb3dda9c6fd6437772fa5076aca9888ce55e906a62875979bd477aabb2f4598d32342aa10a6d187c6768f213117a9ff6d830603bb7b9b475002e20b2237a4055ae6af6b8d70e343e76265188a0f07e7820dfb3d898684d99966d4bb9e78b0e95f5044dcc12810a89a75b11474c8fc06c6e734407db91a072ffeb2be6773a7c6c3ec939514b43daf29feb3aeb7afa57e96d9cf0492d90bb2c7be613f2208f5f5f5898b0a3db8a967a75d065efcabdd83759c88086583bb3d422c6c6425525a1adbd515199dbe71350b77940813618b88fe139153974c80d968ed4d9e3f97a91b7cce250a7c963f880dc38011250b9a131f2b76b677f78fd0e4cd6f1465182fd1d644dc42db0bcad8df4ae9f456841765af8e1c1775abf85a69577ece6f9e9035e36c88be784397479e713be4f5434aa4c166bc4702a4916c0c003a6baecaa182372a30af6dc7e6fc4912d13e662bd327829f6e85340fe130001babaee64d211d6761bcc52993c162a692a10cbe7434310392b64792a777a2b31341995072a6b7d4538cfde74e609dd1019a9f75cec0896186c0f42e3896d15be87aac5b11642f74e11d5c2f7de9f07f848ff543507ea4d73fa8f5683fc6b41831606352c482c7a5a013c51e0db59d824582c595f17a6d2113528943194d6b5aadcead62516507f178cd0f76729cf8b81fce4e0138ab224bfdbb8f16f8ea6196b90ef90a63f0fbdcbdfb5320984be8a80a26b932d1db7ecf870dd67fe838069136ff9b9ae087779e82cacf1b06a7b310ce6c439047c26fcec0364ea87e4549a544d540256cb7c3ef7282fa792aad89e919dd89519fe910501f5ef88da43232e917730e742ac2539d454e066feb9058f56dd246fdbb674dcab636585a788b338ffe41f4190447a65985acb9613d02669ad4ad888004c65acb0ca315752e58f51c9ae9259f20cbe8a668a207a5a46e30891bc909108f53db8bf6f0f11549e621d4cf4763e0035c867bfe9e1192fc421c080b25289a78f4167fe517852efdb6f3ccfe67ad01b4337da2c18f35bdc151c5dc76ee66efd27d5fc784e4e6829bea4f8a41ec8bf61ff998d178ce9f4a10551687337d7705eac6cd7fabb3f2379e31c1d01e4dc63e475f0fb01d9efa3de400b5177e2c2d68f2ead89e9ecad62cfc97fd0ad5b3391d0248dd2fd7c75dcbd802d3463ef0af21eb77b07a3286a72f1e9439f457630159abde7983a5c74f7dda12b40913632afedadb691d62003c70a46664fbd976457544cef8ea863858505b1c596e7f745d4a5fb657b1c694226afa9756c40d9c49425b323ce17a8531c5919b24010f715b5f27a300ee37334931ca9ff5c83c3f0a87713768ebccaaa15e35c56f3536ba945e5d954c94c885c68325bc4b51fb55d96c8d424849ece9a812af0747d5b1dc240f71609439f65acd1c17086e025e376eeb79a7255680cd692fc4b0f5768d1985fe8a1a387074f58c8bfdea8e5c11ed379b845ce2052a5b24ef0c1a658923eb87adf5b01e6aa59ae6937564ef97421722c67404cb9e5fe07d5bfad2e52ebe6cccb41ceb1eb2760545fb6a3582bc4ca572b0aa4e4f0a2ecc56299f3b485d980501a4e010576615ad518fd2d43c1f79aed013ed1f1e1bdb74357aaf7dc84772c9ec62da43c8ffe11a7fb3eeabc3584a936c37b28a438dfe78f89de6b0d5597ac1bc55057544e68fb49a6e505db69af122c2a3ad06219b7f2a2955db0ebf55c06baac5e0efac609436dee484857f75a8421945484ad0c7650a1d3008cc85c938208f19002b7994524878d6ddf85c763a65cb72a09c3a059657459f13cb584bfbd754fbf2de904517092be4f1786b2bde26ae8eb2d884592fc9e84395408f8117e47d1ab30d5fca167bbf07e41a33c230d240e3aac53cda9f251e24659da57d721288252fe7ff3653ae3e47b86209e9344accef0009b99f2ec7b3845558f1d77b89fc9b61ebc1b589fffd3261f71b9631e87541e22ed100e694854bed771358f10fe452fba61875a605b8080cc39e3eac13708e32518f28e60464c38b782c7c7800df63b6e7e95ced9154ea54e32900f6998f38eb1e51c112b6949e2eb11a96b1ea0a68c1e3b5af750a99c9fdb2cae44c5a1d37686ef87b158d19343e23daf00dd558cfb91e6f2e18f8e806abb2faf80d082f657717d08ca4e9c0d30d9bc30b612bcb1a3a3a3843231059dec344c6c04ce625b3fe064092e00175fd9d38f8fe54c4088efe30d211412be01460a6d4ad8d0a618b00a21de0a383de30ccd72f119b27a08958729a999e8aadff21829cbe8cfe398d90476e33db4c64981383a9aeab4a27f3bcb29d4b3d3b3a6ebdd71d3ac546b8658e269959630de176819b153cd53d2091efbddd2cf9178ba6ee98e1a3df9a095db0a2b713a0988a22239f5f08cc8f9abc3d67d9267f54dd5dedbf01bd490b0b09adb21d4e5aa7707e36cf77034f01bf8c7988a2e8dd7046bb2f486878436371f1258f3f7026afee6d7f6560be67103ad098edc9665e00118d4879f58bdd677cf2e6bc631d5c517acbb6db8a1debb4fe7492b7daf0b7ec7df056637c23caf926a1a589bef1db29cd81f547afd0fc9e459f46108ffdfcfdee43515a771c439dbde9177ceaf296a8749be0146cdca2b26be8c2ebd6cfd9b5032b1f7a375307f54c2f622711f8cf8684afaaf17c4da3e83666c40d26adc239c8d1a40024bbf560db5787ed404763d4e70ec6635c6a4b82c10f8ff7ad42217613c57648716ba94cb33129f3789dc86f9c8ec2e8e90e6bba0dfba1bb3dc3215188979a09f33346a6647099ed0e624c9ae10f83da0def840bdb25b718e8d86a616ff46b5327b1f99c22937920f5b5bbd6b53fa0b32f24befa4a7603234e6d94be51f00189a20b15c49e8ee58434a15ae9d10b9cf0204bfa7ab1fd9e006b22bebd22b036c4bb4c9949cb7ecdf01028d9f12466e144b2dbbf64d95d65347013e192d428678f64f0d9306f97208fb00a70d4615229143dd8890725ee3ba6021d38d6359055aa812edaf",
+		"0c5fb7075f5e15a6733737b614bf46871e29417e4b140bae6e10081623f5c52f557c36b4da4b5a4e82920497514b1e6f745fedbf73f86ee10976f82c6cbd5bc13a917514ddd062",
+		"e70954c812cac03e367e99f7b82a6dcc073d2f679f965d524872756ee58654cc",
+		"5f6267f6b3b21423267310e6",
+		"c53868c0fdc14e891ae1bc257fbb13be210a5d9cdbd9d18fe1b474f9a1929dbba3f25222d8fe8c1be3eef22352100064b922fd9642ad128a202b6382ae0a67c8affb0c5bfa1a80e55c1084cc372485243df872d677a80a3ef1ca3589908bca621f6f50133eb762cb9c05775d13db7dd3eb65ffd3eef96e8dd42928facc68390f6bbc50b17e1ef5ea6310d8756dd177be2cceb63a97bcceaa046794915589ca022d90756b02c22e8634c0ed44192abc3b8b1e2814c855ab27aaae3bdd801a73e6209fdd559ceb59a94fd98a66d12a31a643ca2f4b07ed910bc390f77ab89395d5cd1d783d8940dad4447f0452991b209cfcd998b0c814cebd08f9ff15052818bab0bf51c3b72ac1020d3b0974fbdf4ff941b1ab9c01f284fe82f2fd89c0aeb4b9fbb0a74ece08b3debc7b65e7263e2922fd4aba15ae3cba7885d04127c8e06a67f244e7aa4556f8694a5db6653f6e48d6de54f9e4024d25d3236d4f933205b6a358aa1506f832ef7d556c6a1bfe4aabfce51f3b5ac64bf6ab1e665bddb12fe13db9f07a55db3da3886df36ddb89f3a4939b1e9e5b701301570e3d01c0b947f498dcc6af438cc15e6038cb78a78986da0316cab67bca3e28c95e6b7e6b36cae9202cf4a77a0e15d3c3291d267aeee172dd587a944719b9fbe077603b4d39d4302b9a6415aa07af309a5e1cf7a9379552becdb4bc6a0b5c85d2e63bb141c405afc58a8b2b4188b3883a24eedf98dd50fc54725c440ccdb03514a6f37cab49296b6826b6bc7d7ad8cac0a3425eeb6866d94119acdad468cefe162a29e8831c77aa83321e8ae3e20e968cfe51dbf2b63f4e26c61536e6be4f63d61bbd06af38023b15f4fccb8ae0356d924dbf646bff69d1ac0d6e1c7f40b12d6d16e52d1c15958add5708bd38c514e47fe623a67c9ec211cd625b398fa7fd67a23e6e9f65d42dda2bae94524372fbc1a7e0ab3f1c451c126135536e73c573749aa60177dfb68843752b010e2cb9c1afaf51c94a48cf8ac7aab3fb200aaebcedefc6cccb581848da0121af92d9f4be002f0c2beffdfa65c36bec80e7f62d7009b1eb719d24b96e97059e6b50a52662c2c833738849f342391514349305228b29bfa9c7cf2a931558ca8e704c600148a28bd871465b23af499c11784aa45acd051f276d82789c58b14f12619372be4bc3a285f6cee21d65648d18e61752d6e7957736d3385f8ad36702c451c61ed475997d6d9f11c8be5257d8febce329aa701028aa2b5644b8515a95b5e866780e32754ac2e6f2e31b2c04a4ad35cbcbc25b23e9bf49cb1a5d877ca30880741757c29303af8676546760016f1538991b37cf0cd24ad3b1d877e5e1bd083e4b990af6ff5c0b28e530db3f463d21e76c928c8e1ffaa6c045937ea171a9071827a173e231f50e95430ae4895932c88ce048058ce6d0a50ca5c1842506158e98bb2912a61c7991a2256c97cb9050a4bb3ca32594622756291340561e9e584dd2e096263b6ff8eb898ae86f5f24500320d2d0ebb30d84cb4ef876a877dad23a611b39bf0cba5e22f2850e11c298fa23fed40691b83acc87136f8fa540b1dc40d1b0d0bd489ee9dad785c121955a094a2c6bd3353e142c04f7b88b2eb3305fd00d5eddb391b73fa2b16a6357aaa2abf2059ec979bd3ce06d5fff1c325bbe5c833a101615750613047d8155ac0c3a0734cc6aaeae7cb65d7501cb95f9d6d1161d09c961c0681547faf7983ed2efaf4e0fbb87a06169ecff1d0ee540a9223a73f75584441d4669cac09c2dbdb8aa2aed74eb9a2870f2021eb16e5f5c3e79a24d7110af4bece22a1086d27642550cadfa4f0e03f2c032a2745e1c9277a4f67fa4dc74ba056110fed3a63f643567d079c9430b8d5b3bf57a9b3f02d486d870229fee5462043b6bda8d265c745ddc1b8952bf91828d6db2edcfca7051e74df9dd456dca5e04ba469b9ff6a8130aab3903c05659b8f31cf4ba4c22511493a36541ff9d88c708dfb714d52a3c0356543e6efad37530b598bb63c3724772907abe4cad39c896c62daf5b30cd7d37eb36a7be2494353028c76e8d148b018c7bb755c45d2a33f61944071bae8316881e9aa37e4ec2374aac4f8436ed3c7db2092326538f07fc6644e0239899e3335f73c1e3c4602b12d19d7b639d4968974b6b2703ec1add8cd930cbafff4158f68f06aaac83bb4a2e31466e2ddc247ad71c5f4c49af7defd1394e21819cc24c78380caefb2ce87c0d1050680313037def12ca21cf67bb6692d6e4a9e90a9c9a0b7118ac300c6c6f636337aa25bc59cf1d9749dc183803cc0ccd1ff53210352795c6edb49ff1e5e8ebaee7b3eda6e3c0c340fa60594115e37fab60133b8a3b39d2e63db0bc6a03973e236fca801553912f93feafd8b96766049dd2066f3c5ac9222121ee9d36cbcd8f713adc8779949941f8a8dcc92ade62e46e9f1b292d5f7eced14c3bff50a811cb762ced1f103652773ef946e18569eb5892626627e085d4ffb3102c1586ddf88acbaeed903b22d3e7ccd8b8ddcdfddb872403240bc8e0e46a068f55bbddaf90fffb9a914187aac2ceedf21fefa1fe32fc7bdbb9fd76dcda1fca7b39107d308d11a118e47499dc4092ef0cd28d0d9af84440f095b4feb7adcba198894cd89a324c60ed0b996c520d4b33391bbbef1997256af7ba7ec1069244359066af81543ca23105742fee3480f890373d3205236bed566cd22a62bf69f8c0f27b714f84a203bca1605865e2cc2f9211389e0df7a4b3aab9d10826639357efe1f5fe64a1bd6d06d0b5605658c4d2d12e1bec77e70ea393b0a09043dd7d6684bd53f4c883f2f6928d99ba91873d063d43600f9105d503b11d8dc2b05e34b4fcf18e78b2b6c97d3b2c9249a2f6566ddab2a8a67fed6c9f8af2f4ef98dd579f2d4fb572e178489c503df5d5f03bee9920db347a6e734ed72ec7233387f1579c13725599a33a90915ddf03725dce20fd3806abc1029a20732380596057830ed63b6edcaa4d4418871bbfd58de1d1f2800588ed207f2016e11abd1baf1895f6096e2c75cc5916836a9ddc09cab4c28e53fadbd7d3080088131cc270095315b61011b0cea5b4d64b647bbcea54d20be1eec0992c72fc9c9771cae19191cf6a6f1840acec1deff605626d0a0d79ea8fe0af63ea75e80f8141fa8d7ca6f4c99dc7e78aeacc67762ed0134f1a0b053debfb9ccb145800b9818c2deb46f7124e8655f37c3291af107ed75384afcedb44518ca14cdea341c9657ec638531011cb957ed6b3434b736ae8c8199684cc58862638c5f6c07e1cbe8ae68c5582b1697ca9dbdd01e97023138a9173d6b1294cd99514a28102e6912b1c87ef22cdc611133bcc111e95c355a26b20a3d6f0ead66e932c5e1229b0fc17a7d6f78134c69beb362ca75017b1bf1105ac8970fad48acb8313cb3ff10e9d72c4ff11f95c2dab59575525c98653a9c7d31585a3742267c062d6ffc7a4303a3e81a45bf39e1ce2097623bba70f216aa612c64ba06ed6d596ad6abbdde69d56ab45e25ebcd4e485824449550232be26f987c14008f67c9db9d0f709f567fa44502b9e0839457e5f0aadec0395bf5c38ed8de7529708e58c0a895198fc8b2570fb6e68547630ca7f313526d392ac4776be973205f971854c300454d5",
+	},
+	{
+		"95a17355dfa9d378a18ba20e58aa4b8711ea1d6e3c65e0b2d3c6382892c7d02768437d47ed50bf8edc619c340be7bb1cd1d88b0d3d6bbf1031f738c4be09eb264c686d39b92cc7958e63c9994a84b61b5c412999ace8a9dee0e2a29eeb8dc537f63271af5f3844ed9c0d86e6913c02ed7d2b862a132f08f311aa92fc3757342d89a5dce8dd20d5792d5c60be9862ab168d3140a061489472f2266f297da357064833ef2554c49f8120ff40b961ebcfee1d0f8e7e5722f049485f72c502c9cc4afdbb70517f0fd2a00e12596ffe285d1b37eb998e0e89d756e9491ceb13e83610a3a66122b533c2c3461b3244438f5f7a7af8088881dfdf6a29fb563ce38c4c8632ada8e7e06baa2686dc6aca6bc944e5c14d6e432c4dad554803912b8fddb1c18a59a86bc452914b2efc1599c5597f87a6edcad33a7728827bbaad0a975ecc22b7748d7cc71ec7f51adc8fe0350e67dcfb31af35a8d7b72391642e29c2fa4b796ed8f535f6bc2b1198baf1cec858aac38959f83130af55c21383ebd57d364eeb0e442104004c1599060667ce5e1191e76a89199a386e5c4bf147206e7d6e598bb27a90b3c6a54cccacb39a0ac42bf22eb40bc8ec7925376a6c57d8eac6317578ac052b72ab773f572ad961ee05531cb95ee5a6d70add4176351960fb4bd673f7db9f698616a8dd41823f2f87924c40f131e6c83bc40ab1f92312f46ee86765c306cf4a1d77275ef9668d80f9d9c1ea0aa7b2456bbcf764e009584ef1c0b4b4c683fee3fa2641f48ccf7485a8356fb3dd22f848deefadbef8050de9c5c19e8c449c6f3ec2b1324f80a7d428dc44dbb966d40244c3af03bcb410a57ad1430615e07553a22686f1a62dc6cf090aaac3707ec5b44274b7fe28c7a3a298e7a8adc71e016944875bebb421babd2b64809be3454f25b90723e2cec68467ad2d14744b15de8f9c397a505a340e85998e207cd46fa18d76c46f458af4ac3821c0ac6cd68afb72c376c31daad1a2435fc2bf333260c1a82430edaf2499e7455a93b1301eada2e12365ffcd36a1119664d0c996318a3e55bb2c04dfc5eb251f7fd64f9d83f27ea6577d748e1f85248355ed19867857dc3383e01249cc37684b0eb8e891aa663801e4ac8f0331b38686a19f0d19f6e94c7ac95ec395962be0a4e3c8358d2f6d8f13191e164ad29cd1733bde8c31c7d8ab90366e26cc9a06707dcfa60bfe139a112db827778ac348fdfe26892fed61db7e9849a464e3aad561797b6c778e0688bbbeaf3349727b4670a2d0a08f317b0dc9c4b12ea85c0309d57e754d0c7bd5c83985fb82f776c968189908a8ca83b5944767c2efc3c5f898436de54fe8bb17224012a437896d9fa106a749d12aff657266276129ec5ac12fc7a77eb06296d2a2a876d931e479d3ea201cbb4b1b20bd81471eaa33786c624013e1f07577c2171f38f0511c6924078a40c2d55ce392dd2ab0885e29f4c06907a1597c181b933853838970edad7777ed394c491cde27478eafa5b7a36520aa0779261f94b957e83ce058298dcfa07b08ecc425caeb6c599a11103d7631e77daa0d9d3fc6f42703d57f2c624ecddd56b9a27b848de7dd28f8ed656f1e4decc95a8908217e2f2453ae50b5fc1d9352d735ce5bc2b538eaae25501d449d090df793151811443c64f28d19eeaaac4081e10edca4c4148e723ade8f7e7b988b732ba08b3ce4c8a0d655bac4ff66048148135decd7727a49ac59d82ad470b5479c55d3d8399b790ff033d3ef99d770e1eacecdc140480aeca1e2167553cbbdef2090c7592b40681b733b0a0d127beefd49bcbe8904c975a5ab8b1afe56d7ed7667b5cf92f537ad6972b876843364817c20400524097ac9b405e4b35bbba0d12355a0b54bd763b4491b2acd4e8e4fcaaf8fcfd398499d4c4e81ffa93ca07a5ff51a1540f178f43a931e07e1ad56ab5ce57a2f7dc3ccca114dc9ba8a6934e95f4efe9f3f76947909b280ea5fd795bbbc0feb3ad2b704e305cd9d8f37d178961f77355eedc9d7f77c58e1db2f7797eb8682255939293c3ef7dacd2eab46c4cbbdf929aac301a13f59831a88fab173803399d96dc216abb9f079e79bbfab667ca590266891c8a7ea4bc1724573e5c5a67e9f1341b5bffaa538e240f78da7733237999ac86141b2ac0324f17609b71c885630c90befc3b027a5f01e33979165ce2a00968c414838446c2aba76e1d7fe3707c742f68af21d30e23b637accc848f6c8df820a27bb4e94e5090ac6e008fde7cf3fdd5931fa891335ec8d01b5d6f77db57a87dc35d6701adf7ae0bf82dda6511c83ab4d7d3460b221eeb3d6c4aa537924db5559b1c6739040534fc330f5144c78bf99f5f4faa715e85aebac043e2529197a82ca40f65a8149a9447a9e58c61618600b0c5ab221420c0cee114a133a648dbc2eceb2894ffc329376d1eb3ce7039cf30ff6a53038b23c26c38739fdebc7b919956ca2e468d577dea6621a8d66b78075ad26a6e6d8e20c9b694698540d516ea2bd108625e5fd038b5f1e19c5d5993b82bfe16897c375322dbbca81c81cef6ad900f0ffe5ed02714c208a12f5234d78e32ee07af155ad1e1077a0d8938f426d8f326c751f6ee66c8f707e8493cbfc76f9ddf1ea329e094315a91ba9385e16c890823db0f0231c7f939a042665009d5edd8e48102c515341fa6eea33cc00fb5d82380d735b29f2eec3f61428f7b186d43fcee46b2037ad1aa6974d729848cf1a80dc8ddb0580c9c876def06d8f7642cf45263a655ee77f047fcd76171546319622bf71283f3bf0b519e123a85765779c8bb201e99981ed184e642f63aa61f9cc206bf45fa6e514bfc637671d9cdfba2891bb112a3cff438a6372ee0dd3e7d9f352ce52f8b367b7799e1f963bfe50638f0c74b94873fcd3d66fc1e342a8bd36fb8b88f33eefabb78eca4dc9c89e2c57aaa010f2140dc5ea7c86cebe2f8bf42a167d1d546cc80bfa9258c35af6efb1a090c293a4cf588e4bdf5c090ee7fe38fd7b5551e71e5ce2b0b5a50bab95bc4c257edfc94d37579816b4a2249ba05c991bb2ea02d047e480fc8a8ba71f48f344c6d20d140a64ac20184e45b4eea14d0953370c237ef0a47a7a2f22997715dd3ee8ea52f24ffe12674d571b3bf968454ca051701e411499bc43bb55bbd033f9b81d4baa6c49bdd49614efd20d58175af868ca16a9deaf65216abbdc3beed5f30b209e786a5b4c006f3bd27d93e9d78b51a1a2fb7f5160a0bc1b7df70952ea1573888ddde3d9dd5314b0d0a899a733eb48d5e6c7274667e362e4da6b37c480aa4d0d8730e66483fb1453a3aefad69942ac7f09d3c571b6275590938c541336a121bdd20722550236a9a5e4a37c7de628fceffbc260b1e9b6417c4295907937b13609b8585ebb8f076073abdcf19104ed80ffafe1b09997f115d987a552be5689c70fe125ca702d2ae4d807d5690bc2e90b72cabb0b61ad203b34c68df21c16b92bf8def5680b204ce327214c32e4363d5600f96162a6819dda472acc6441858f396385a16fa5ee52cc0f9ffef3d53c49d535aa37db2cd4b573ff81d74006677969ec1ad891082b5d18ca5b0b9f975574ccffaca72b805c9f7fdd76bfe3dd384dc953255a5b50b7731a137fb9aad42e77d3da1eff5a7b9eda5814993cf2d289bb25ae1680ffcdf419e073d38b4701021adb2019359bb70ff4cca930be7bb979a0678f20665d14803d8753c8ce54cae92feb026486ba747a861daa449863bd38cb4d5831aa6db1e7f404b0c3587aac8765aeecec686066ee7d11321574f04d3f3da571e71222ce07277eca7ff97607",
+		"5e24f34a8d53b17bd0c2aee5369e3276dbd7e7c2ea0990f1300fbbb00831b76655aab1e2fd625ecd",
+		"c1d796f1e651a1ee825855d80206baff6818cc8c247ee6ce62b7531e6e9ac32f",
+		"240cb25aaae4d085bbb747a5",
+		"319e968ad291ea5d4a057c38f7afa4ddb9c9565962fa1a7b231e397a268ad8e0c5030a2df09dc4f99402ddf2e0d06e753bf55e1b318b3e5ff0108de2328d3b8d53e23e08bf7d84d59fededd60d47bbb52736b0491f82c616eb5f779c496abd6499555035e4513c8613e7204e6bff8d06dfecd9ce38c6b83efd8d0e41f84f7cfc9ae07113237987a4b2eaa87f7e0a310155e282e57858244e9071712fa026cb781e5a4bfe6fa1bc480e534096394459a3d1354e2d9a54aac6926a60b388410fd0b53f7a3a9116292f37406369c22ea674418c4deeead171e00f74f5cabae5d24a0686a4bcd8ba99aea613a23edd0a019a319daa3779c212fbdca9d772fc3fe612cf178c2aca2aeaf6bce2433494027a474eff699bba95fc7dcf79ca1d77b1e097439a9050a5cc78e0b78bf2e7f50f959ea2986a59be3880519cd84d0a673acb0432feb1945c603e70748445c74600ccfec60efcf9e4d02a7df5f967de4b473f63b0b0499ff4ba350ec1182f3a0ac17ef9ae28945fc9bc714c49909a7c1e2f311aa6ad7652e22e1f48bb51cf53814a2125152813752d86c7f9468a991d0ac84b1a2f3969b8081c228b7f5760718036e26a10e211ff04ea323acdaaddf9b06a08c92ed663d0fdf13fa601cda45c416c2d3803dd9b5ca29cba57e59cf4ad93176c65c64507b1995d638541c90b381ff758833a2ad67b0de44c280fdfd82b3c6d4353ae30b33768863cd3169a2032f26e37ddd57e7da1673cfc7375bf6e6792495a2b434155d684f2a6f2b919f944469d47be5aa7da74eed69d871e6f65c3ae08904a9ad042ba39905188f0b9158fd14094bd6a408fba6ef57566d69eccda86bb54cd3ca7381f51bffeaf8bcc1ae8df91d22c359888e21b70f640d6f3726a34e6100ee269124747f0ca05110f63deee07e3628bd6aacf926036ccec02c0b6bd7259db52ea8b7a686b36ba1d0296c85e43e25d72ce46c66a1e646301dafd2f4c502281e6f949011cea69459c026c65bd130d6ef06be17b23a9c9a84746e39d017b144135025ac527c1e653f233770cd68e9f232c3b623ceda836843b3e9ea313cc6a57d28ce71ccfb7265ce73b06bce1447220645e6f66caeb06b55129b97c8dd8db54c94d771504d24cedc86a8ec706a9f7dcbbcd7fc7cf38005b2913b1cfb77370bd23183ac7b5ca5135a2738cc91d05b2b22640469e3daeb6a7b0f14fc6652563663520f7754aba624a35e5d24529a6ee9f5ef0d019d83c04f5a93a38b68cbce0cecd42a11aae305475806326aebb4f673791f50c9f90894add51a0fd7c02807efd8c1bd21fa717a860e224bc9fa3f40975fd8d558e4844a09f8920256528450d77e546604e2ce2d38efadaf39a0ea3ea12156174aa8a20481e6c1190e448564675f9ca60bcef37cacec5aa218122e7bd25b571ff10f54979d62018b779a2a3d5d7d6cd56ae31efef2c844ba50ff9da88eba7a8e0d9fc5388a805ba4ad35eaa4798e395d2fe112083cce2f11cc850d25ca5c6e60a9996cee4789ca99d519daedb62f4fb1e535b742a35d71d7390117e93821ff18948a78c1fcdcb90a5f1211327d7ee0663ef16ff446e0e22d8cb7b2d3d05469b1c02864f4a87e2d9715f60c9e7be841e308d0a5f6c50161a4a0464aebafb88e0d2df8cefcead93c9623106d5518a9852f320235594be10c45bc0cf06c9daa007100ff97959357f9be8e49c870d0a11c884213e266c35e9131439fb3654fd5f1abd1e778ccb02b8c262753a22653a09272a0c33b6b2683c9045e8f967af756b98dc1797ff605c64ac5bda8252e9ebfe0e4d8d7ca754fcca5e3de3c4b63678da095281d76d60fa12ff4ca818825f346b9c4e426cee16db5818d78a527a901cd088bc2983f9b83430b50683018996996717a1738439680b68e3f61cbdcd0f0e1a6b436af8fa05d3ce2228054e319bad1dc6ac970c75313c552fc1136fabc302fcd1d09ef1b9138d18133a772cbd9cb197ff58c6e898f9e83e4e27206f3b15b6bf2778aaf9fb38e0d50152f8dbf5763816132a04b4b2e9639584b3dc8ea6d95ade024f9497944200ab0aeab206ef099859b9240aaa15f737c1e0fe6d015d04f47261ade4928e3c2ca21d1f5ab4a3f571f2ed92ebeeebf2493e6e39f0063ba931e165384ee1b5081f5f8d26ec24716757037f5158d35effbe67009080ad7b0381292a513f312eb28328cf5ff47a6599e36c14277c3eb5053c5aca530ff5954c21c03fb3fd5fc0facdac36dd819b0495fde421411e0440991da0cc4a20d294446115c0b79045037fbfacfeac574da3bf192fec4bf38c27cef71d03787430223b6069ba6d9273ec8679736a832277c657862ca791b559a5054ee8c7c07618083f75480c8aa01cb086c7317315911802e6cefb15bbe20494b14d97e3a885806db775c216dc15949e3b724f7cbb30bd2c46bd5a2fd6132352c2b21cc2b47891dd9794975f70a6fa7a0791ee761ccf4c263f27f64790826c1aa656c39483e029baef0855935e7e6c133a4035a3699925fbde131ca62948879373346af35bd7fa52b8d6c3338f213bbd9c79977c0d710028d1d386df614c5faf4a1f8fe5506a9af7059370893ff6d07d91383baba67a617b5d829e0e2eb20e541ed5c34be7ef0eaf6c6f6f52d7ca01933a2a4e8de46e422dc95161ba8ad354f6bc7c8e4cf8ab5e08607530147fcd7c9481afc621c5a3230a05e2c4db79db9e1e73f43556a8e8f0dff7ffe420282212f23d4c5f6f8d2febe129b9fe5ba7ddf27f72ae898a4eba270b5d2bb3b6b06e38c546ba80a9b2bc46097d0b47db5ae72485ef2c6419e856c33c2d66a861b9d474699e730eb8a8992e3ea9c1ed74316687d5d9fc611189eba2aa31af5ba8e81179866dc016bda977c59c595e40001c8ab3a4a44cec00ff84c6dbd9ad4be30bcc080e69b9398089d6ea464a70f536ace3b447693301c94850606d0de1299770b5f45e6d28f8ab83e3ffe52178522eb91fdaa9e4a696674ba0f52ee18e960b04415782f018d67479081b1bf9b4c9b90de026cbb66bf7d9d12cddccdd9b2c8ee2f010892571c6f0c0feac9555c71bf61f9cd69553cf7fc2be8d058e0c3430e134adb1ba28985fdc4f0cf71bd3cd09f5f82f303cded0de62f98404477bdd0a846c6c51e3e82ebf72f475afc8e6388aec57206018ba2528ede194345cc1ee95cb2023793f692f708aac3c9e8a682af36b078f5d6c7a3ed07475e9fe73b95d1eee048ab898edfee3fac4beda45f03eeb64b2128f6df9453ed77c6010e13c0270c068f704f49e62fb7410be90ffee47584ca2efc5287dae1f63bcc1819e7548eb9f0d8a3182f9ed00da3817255a2ff735876b75cd21cb25e86aa4b2893f9e5089dfac76194563f9a14335dd37ef06a501c89623caaf6feb4afb792092dfed515ba7518e278c341834a9dd17b50a0fc860b62ec621b69408cb3fbf7d4ab88a3e367fda84c82357376fa9b1161b739361c313b99dcbf4122f3870c8175093298cf432174217398928983ab6cea4759f18e7a21d71fe1b0f3cda05d241e12db0818b8763bd23d958d6e52981ce8d84cd6d82640d2000874a53c0bd14949ec99e48ce6c954ef0d08e6e319de5ebf7e142f25c0f50ff13f6acecde6a270c8d8de05ef4c310ce9e92f40f6f2b77d6e7aa3f056d4a20f7faa7cd0b93d82e3972343a50a26ff462caada10621bc953b73913944246d2a4da25fa52cc6ee1293c436ab9031ee2dc79cce39f139f44d473c236731257c6f65ca4d383e39cf8d33923afea3c80244021d36e0ed43230c44e7d1a1297d35464861f9149d869f26cc51879027169803e43c898d1b4a2a2480197500",
+	},
+	{
+		"2158abc2472e1b9c061da2c01d0ad9e996fd687cccca331fe8a2baacd12c06f284b1b5cbdfd067e5ed09a60a137ff4a97c5c26482659680ffb22bbcd4ec1bfd272749e52440537320fdd3c225c30ccd98cf221b34b89c247ab7d14f93ed3ccb0486a028c6f3abe7e17fba1742b6d4db85f6e6baaf82df1a3aa059de8d9699821d39bad42d56cc1ec67626092cfad4a2e1cb5d814e2cab78ccf5474a8bd0dc990a877d37de394694af6cadcc57727f393dccba7bf955f4b65b3c00d71cdd701754ed4f231685b7b5e2557239d7e16305be2d81a773765dcea25ea5bf2c15d670f3159409ab5bbf8da121c779132a8ec1480068cb76b68a19152fd83135aeb228b446225f91d1ed4303a4bc16cf3ad8173b30d2a1e75ccafc8c933db231efeae6260d45c7ef230ae2c7b6f986f1c19e2cf260ded9cd99d64a2d03fc5ee3d73509e47ac1c39dcca655839fec75517a9243eb611da8fae3e317e7df66cbb6abd59b16975eb463f509e784e65cd660ef1a4c5027e54b1bc862f397c9cf4e6594d98c2c2830801d3a679220b46881a372cdf3aaa33eb66b91a9f36b6941c0fe1b4d2a437daa50b811f2d8c65b5a69de185d78bb9c2f172dc90a89324c5a2067974aab14f4fbcd06ee95cd49e03717f88480a410afbb4e68b5c79b0211cb69b90604cdfaf08af1ef10cf28f0f630e97ab18d9b5138d9b9ee9154e0b3104a6c164f2a114fa5032eb5c247a6b87880332a0dce7b36982515297a05dc8a4038a09f52b1def7b4fdad8735443fadc462c7c22132f8b9581de2d213bf5c53f7fce34aaeb24263afefead5341a72f88d3acaae6db367c5c14a97d4f9e438e1e11c3c8fde7ee37e5ece5382e8c68b660146046ef96c24caa6bc9fa0a0c88281e4bf01b32df5218cb3750f9c4b8af24cc106abca62d085198d14ba2ded3cafc1fbb17519a696965a1ba5f65720e893f1ef3fbc5200316b9d4615bb23426ae53e1c5a57b2f0ee0d0c83f353b4ebe7a6cb17531d278478b4ca8e6ffdd0cad30ed73d568a2e44972ac88a7e7d665614316d674e84ebc739b645a9a4166477254ba47bc5c2b05ced88e75bf64da21a7f1f71cd946d84de13ca77b7e0dc2f0617d371ed96323a83bb11dfa16f81bbde913d9c259b10f3aeeb6b56cc4775c25f49343cef667763118932c2e8b47ec745ac537b37746ed65fda2d1c11a2de60ec02adcb79152e8a9e614d8715cc4e6b6891d6a0063576560fa3621146308222432ffdbc351c36c37d844a934088fea92ac54920facf870a62e91ba9299dcb6cbdb918e2d54fb642c3f0d60489c4bda489f6c584b64c8f19359ab25f388dbbe636c4d90c048f5ed87024dcf9f98a9e738163f837a07750d61203254a80d120c795f9c3aa791272f9474fe330da81a45be5ac838613d46c25e781606862912ff88af393040605fd4d55d07e2052227c37ceffcdd2d42a08bbab69140dfa4406853799893daf768af546f915a91b81d0da719ebd45b8b5f1641f15621959689e810217bea18e3996c532ac6e4e2e4f289fddd5e5968bd6fa9aec5ca435c532b6c74a7568c8aeff9dd19bfc2fba3b484a191e2faf9a069a24e2e6d928ac0bdf635644cc1ef3bbacc547a8e4f1d42d4bed3b6b8cc56216fa550dc37da9cf4d1d1591d9348594d14adc7a3fde5e5d1a3b9875c85de7df483cdd0baa86dae793e0796d14fef1f649de6079acbec6b6fa5f2cb2bd0481f5316f00dbe5dbc379bc3cd6d13bd8c775a727ef43e6a5fad1051783b22c05a75d64a8394a73fcb430299b015563c8cb0ae0aa4ec750399855411c076d21aeca8656f3d0cae084fb0a1ffc6f73b52a7ea5d4bd6d24e7057a3811719533105fc967439a32241f2d3e3f299da2deb821748cdee1a1c5e71bfdf88d833bade2f505268f375a9e6488cd8e16705cce91d15b60b2fd269a19148296a7be348aa349a12270fbc0d5748e538afeb0598081a4f1349217ceab3c4141d40f765ea2bfffd530fb9606601469fb131a44939be984c07bac8f26d8c068accfdefb729eeb47cfd6ddc646e22031f53a7698c6501d86cbba05e282d64b2f962a1b08b9064078dd1e3f14006f45f599bc8e600cabe6d855fcbae8c3060859202361d929a241f6c0711ac0d050b67a1d44da19e0b0e236adad1f60a327c9c34b2b9c64cdde5b8e4f664f2fc70599d44a63ee2b14d051c27d71231098ecd3d4086038d63e84547dfaa39db1a92785e38b640ea0345062a1c185b25a72862e7ae6574114eba592d6492087e2580dc5d361c473a614d647e66c0a30de806f4976b69a8b92301e68794ee05b96ee116a5fd5edf5eab43dc1103801eec861383f17c2bab9f2d9126c1802b7aee0c909309ee72679ab644abb9c4caa54add283b5954e6f881781e42f849bce6554c7a5e3becc5d5a209805ccd4a0117272a53807e3978ffb19641a9dffd9034490a9284f658599961daf52f24f6464c2099cc9ed3459d84dbde2ebbdbbeef25c882a9beda03573bdd4c6a0143b14d634a1a021d5f9fa23a7ed0f5598ee57e56672814412b6c7c08b8e709fb98575fe2716100d000a20a7e7200d800e556564c7e6a8da9d609b18ff0bb8a8812e96b834a6b534b0d5dc97f5da17f42f8d58e763f1b201625d1a5158c2f9e9e190921637474ae81d278002f197f7211540088931ca8a941794e56067ef4a497fdc6fa713aa9f20c21f23c3a71ae4cc5aed459ca7c020bf55162fbcf56a066546660c5a009b8ad2aaae9651c97b1e145853a10013d1bf68e7df25dd492c328f823ed982da54557502ebc6cc56d4d0bf2881bf3c536ea53b4dcb0886e73b066969dfec343441b9372d7ff38454c4337d45e2b999415ec48f19cd05f0f80c5a61ec369610784f47a5cf3b2a13ff5d8145303ade7189a300936006846812dec9ff15500f8daf47236e724d72619af3a6cb3e854cb8284d5b8843dfe056beaa45c40a4541a98c7507feb27a605d6e07189c8c5554a492a03ce6701d3d2ec782e2c1c8346b54a963435bdda3a93bbac1d837172cebb9cd18903d25cd6bed404eaf18730a6d1c6da0783b5411770ed34f35fa6c11a4292a34565ff1b23d4200ec5a73e6b7905458088fac19f6aafd35e0e791f28bbb2cb0117ca1c3a9e3c4863e487ce5d8c14dd140e9eb4794d87d75b01f683bca84ebdbf19dafab716421bfac9e95755fd346a0cd31e8520a55c7ca652ff63fb4e20ba67fab41e11f7390bc02363162097802c6a9eb18b430d07ea60064d5b546d15bb68cada79c113848136e797577f1783e9b53574f9427be3a28230fdd69d139205dd6c7e9e7f031fb6eab70d69ce905384c5c77d084360aac590a89b2dbb2d339899b13619b455cf9f0cdc08db6c5b5f3223dc3a663ce42bcc8cc6f947f42cdf8dde15a6926b753177513a52be95b1f0b88d2a1ec90e49959b108fe204bbc29199d7382c42ad5dbaff970cbd2dbeade54bd70415e54daa805d396361f525f38efc2bba3fd818f9d7af0594dcc341c20f18c624fe13ce7e7108e1d2fd06c58b03f04642c95e3ba00d4035ea0476ac138f72378d85050bf60dedc90af38e96f67fdc38483a73e847b41d31b894ddcb234f02b0d507bbcb15a8941f9c23b592a291cbeacb3ed213f2f044aa842275a7717757467f121294bba6b357c969e96bfab455c6f328d9e5181d909c3f0543b17d9af7fcac099067b043be79aca8e5a75c3a6d4f6246357a63c516a3ca595447f34b43a055d3070517c67ec36e636aca9ed71a001d4f7b81149124deeb7826dec3697e183d861d544c9c17baff82849d599e9e77ed19f801aa1ce095940674576ff270ac788d00c429187e299a03c6f3a1646a8f7d6290287e70bd1276316ae624da929c67936191abdfba45e2803884e5a3136205a38a841448968a7900709dda033a42969bd3417a8d865d0dbee1f261f4556797dfebab278136a182a63e5ca9789e3f1371808efe06eb0cc5ccfe26c0538d573378035afa39fb7cdf3ad889b277c8c6e84954e74f3ff3140bf13bcb45c822784125d23b5eceb73e",
+		"088fc7ba068f80efd8d4d62813c93c1eba77e9ff400c7781314abc901873ce200295da09245bf8fd2fce254397616151d94b511957c89a881256182ac9e64acb7b25d4a080cc9daf9ac2f231235483fc9fd415f69caf7eaf0597",
+		"78d5f86b071bbf8a185e5e2d54faddd2a9e26983b1e7a74be0f0b979b9f4af31",
+		"d9ce7d249af9496e99c93b36",
+		"ad542824b49fc520f0b7ff8ce2bff8b3d47baacb4a1c95ed56a306483aac551fffba48e8a8f5e4cc536e9266182f6811d070fb9282f5c542cefb4993ccc7044b42cfd6fc71793dc8dd2de23c630f9ceaeddba45efed9d7fca25fcb07d193c000822478b19c2ee9fb31760cfe01475ba8a003db469d1130318a79345a29d054a9f9412dca1edf6d8f1498af5bb6fdbbd3d5f9a244ff176f62742c53779291ef6294df6540d841f4ee8c7c58fc8497ba74d9cf7947add5373427d81ae928305b93dd26cfc65e63b0ed0812ce759511bfbb10aca98f2abdbc9055c4e5ab82637f6a965bb74f592bdf11118b8eb79d50331e76cb4d10c6b4428cd4ec2ef4cb727bdba2b5375f5184d77772d0f9fd3a3c579a4a548b9c2dadc22c805ae959617af49a514b43f47af834313ed2e4d1fcec2c4b9ea87f328fa3d23129a36e6c54bcd08f7e30645de86e98ebb11bcaf99543503eb1e024bc9fd51fe6bd5e6d749033f2452cdf28b3d0f8a304111bdd26dbde641c02fcb15dc21b1a9baac5e86d35b4126ed1cc8a2c3c2a5b94c99fb9b2008daf1a0c090633bf9e31326428c75a50e821b1e72a6504c9d7bcfcaabecd929163d365832e8971f5efebff99ee3f5b95f957e8904d05b410936d8a81c60b4947f8605c58e5b727d491995c76fbe06e556c8ab5cc661a0c09ebc98d61010050f68b31fbe1f9de8f6481b2704204b0164d8433ba4dc1076908c782826e9b555e8d608463581099a466f92bfd6ac9796eacc0ab771a3f11d03806b0f33ec04c69cef6b87d58c11acb5d1374450ce61ba159456b915043c5c17cb03f0ba66d027105bb6fff41e6422f13e2a466f073358bf68149a3b577cfba7ea08b42f83fbc5a2aff17c5ee7dbdac3ff97389f5b8d1f3750e5c9be651209eeb9574127ea81bd7619da16d1cfab85754883543f6474c8c0cc9d5b80e34bf8262d2b4798f9917bcab4b880339397907a5bafe7d149247fd735523df3cbb17ae5e298846ad3bfb7d4f902aa549b7667d3ea945b002e7b209bc83842a7b120d6d27ce80631404371f31d1f61efc5423e1822032a1cbf4fa1a6b6fe79934a202d5add8c6e3595e49be3dd9553a569521c50e9653bc684ef2b73c3526ff7a0843fcac9cc9ecf46e63df5b9328a54c576bd299a366bbdc0f83a9de67b03f1da16244bd6d52e7e4b52c4ed693827735554b05b3a260cd01a41d7c944d0b7b58ae4b0eb052da34bc22b779d7ad46f90f3d4049c097e0adeaf71bbb30ed24b32ff5c7a65177db77492c2571e9cd99f15e613797e319ea7377038d53b28a4cd66a697e5e8f84cf16bd0f0430b34826114b4e1d1ebaaf2939dff7f9f4ce7c0861e51701c42d9cc9e871018b447ccaf4e402e3d63be164dcdf6799314a389ada8bf5e51a35148acf627e51481b9b0e4bec09c9e6d59229721b151fa9adf8323001fcf33afbc9a949643172f39b0d10ef57b37973683fdd9b9eb46e63054fd05ffbef889ff8fc8f251b0ab41fb00757ec1964ef373fceb8f6d148a7f7c89944b3cfc240d091601b23046188ba70a7cdf7b6f96eb93dcd3d24d4aebdc4a29a749bfe3cf5f6e1a025b62982ce188e6b57245d829c9fc1dcaaa5309a8b9557b8824a78eceef6e977721de4065b474ae008642b974001a5565ef5fe4250194e8b861cc45a8691c461817f10b646fb526bf0fe7790bb0db29d1356e8c7a197ec78df8310431d632a032b5490c2a458eb8d4327a9679d7e8ef8739797b0e820e2c567ce3562592e862a1dfcecd50bf77fcfcd00518db65ee0effb9eb3655d5d401a4a47808faa596d17b316f828cbbc14a7e018a0593da9320140a752f3824b5fcb66aa4c3cb94366ee8b821b09e7bea2c04ece15e8a7be1f58463b525e8cfcfc3fdd395ec5b0575094313557e632d0a65e3099e3c653111a5fb4f0eb2aa710229fc055a2bfd8a7147cbecc10823f1244fbb6894af1408ff9047d6483ef83573b5421b9798ee387dc38f166b11de6c33e9785e9b3d9d28bc24c37890e4f8f8ff24cca298b44d6fb1c6aad28cc634a67dd427205285521a172c2a4884ac5b038e261e38faf0086a02aa29195713cea335c47d03d67fa0dec7a8cb21db741519f5f0ba0143f14d71e33d82c75d6a19b3f7a42e6c16d762354daa2670ffa55bd400637de9cddf9e7964a03b4c8956f36bf54d89cf16de23e8c52957b52eb4572a11d1398be72bdb129e2c1abb58c65cc291bb7b0d2dc326c6125a441863a6c92de0f47a355222d58bf10af0d297a86a98b4e933a8f844fc7f1bbc8ba77919dfc50c41219e3db309b92ba056349faa758daf360b8ac05e43fc2069cd46e63fec399cd7764b111467fc65407ac06f5f84a3179930f6215ac5ec906146c19e0d3e162e77a2bca3582128284282b251cdcac03ecc204266ac3a9cfe8d8854008baf89c0ea0096a400d6a0d2f7c681c99462cf0105f7a3dde690ece0438fbb820b9c73c6cdf6208c336831101b904526cf8ac331d879d71615d8b1f750ac7f0ec692d97a5e21e17e194a98c10172b5c4bc1049a8743188ae7c4d70384a7e68c1353aab7882bb91aa383821046ed0ebabb4b2dd126ccb935f48646b299095cdb71ecd5cc402e4635a3f7a3c8a6f54f4076ba028dedb402bcc92f5668dec3d91dda7319f58382017e306237e42480ee2c1f5930564cf16fdf37a3434585336b8e4535bba87311cd47722b9da727250560624a5dde48a2090ee44592d2fc06edda634b600fad9f843c6b2eaa0697b42858afee8191dd2a31e5685bd104188e2ccb057dd0a8d4d1205d7c846f5b8ec0f06bff61c7f47ac4da30e1bc80a4e95af79b14a83e9af2e0f195cb92d14f752a5f12ff90a05765be453075d799694848fcddb07859336ec101c8052bdc273d4abc313cfb351b543fa340dcd01bf32fea59881ddb8f33c6023ccea70532814ce4a2d0c66c846347b86c29dfc34f6fa4db298911d4367c59939020a3d078194e6a3a3c5126c24ed182398468e77fd61a5b1271f5cb2a97868876954c3f7179d6a045f4bd770f681cd82216cd2b1ceeb4e724b3fddeb74481e662fbd7f5dd45bed6d4f89d21b8dd9c1009ad2b0b16954e97993ab8f3fdd9d61f8db102a945591b4552f419971a9e46a792dd8392c8d9502767c82d9b4f69e66071eb579859e9ca070cad5fe3b7fcb77b8474926ea991ce7ad201421f8a79c051b762a066027ab2b9595a1c97ad57f3149f5872ed4d8e99195d47bd3c03bbee590a50a99d8048e912aaeed797977b52f0240a6cf2c865b108456881adbfda60cf701454da17bae879cf098df808f34e50bccaada2d3edeb1aa73cfe3c512d814eb33897b6ff9d67d3d682517cc333c3c2552adc99860b1f0d1076390de9f84fcc9e802581f77e14f5254da01831c70cb8581630dadb44209377d90447a1a21cc8a2d6d897db62d8420afbcc6ed85ce42f3281255bd43e0afd3e86b27d3b957104ef54959282b0e1b381a26f16057246704c7888126055af5a1f494540f01897e8781e1a5c0193b7bef4b5588d0e9b9c8de74dcdb63f03f7b15cf48fbb71c7c3bbe9329e3d326988bad7d0cb85537c1e0b3cd88f37a3c7765f548f99e495ddc29daed8c7f15dadf2e5b79def91dbbea277c51a5da250e66c305604bcce4789ca2df9a10614d72824ba8e4f179f35ccae7119fd962cce13b282f0f970ca6c4776374c4bc438f0de98aa04fb3cf23d2c6800a4a666c15bd20c486e88e688ff9e5fce906b4ae96ec7c3388d7567ce6c8bc61f6d2373b93f9ddbb02b384084b3f28f54c9ddda232d3084daa5fac5ca356ac0059f2fd3fde5d6a9516d0954653b699aa986f70733538e19721daa41329abb95058450e602eb5726ad5a8b81aa474650659c6f7f6f53f8a6e635bf35f4b1191e0dbefad3be756c6141c7d55f007f4fd131e5d5eaa120ba31cc32b8d4c69d4fa784fe0af7dc272898789c774e7995cb252eb6c8e8053c9e7adb59c27f675952d161dba78bdfb15859fdfe4fe4a44c01efd394bf51d43c600aa9a527d9c490971e188e28b980e77a9c6ea0a4ef6bd38d11b47f5745ecdb",
+	},
+	{
+		"9cd1c25b5bdab9b9080db3e5e05dc749e0783087c310777d89307138613bdffe0ca259677c13208420d4690031314a11a97a986d8b0fea143f5b4da0972c9ea3cef80b4b0b2bcf2bff392c306a764113f0d9807be86a9027c6ddc85d096600d85e0b236937f295362bc1679537a8a9278229a36a9433925a105ab719c0b7f11fc31488fa071d3032de97c81540713dc29ae02c2e13be8823183f3cd9f72ef8ba4280b4499ee47c7c7c4492bcb5cf7e4fafaa7ec26906e58146215a3d4f52f792d3abdb718f57ed0b9b7fc7504e45a0fdf01ebf5924a4da6ac635a715879ea75a4983cbd9dab9e47638acc687f16684e184443aa9e81513ae4abbc4d1596b2ca3eef77cc9b0603fe90c0570fe6cf4dff0381a99212fadcf7968934ac1ff7664ed6ee0b61e41f5074dfb774b676c2b57a445f1c5749e95ed062837c727ae2c151c0ccb3a4dc1429bbcb9e62325117aca566b8fca0924b70f4defd7749d0389b90f55f35d1635f8d2efdef514f06fde46db6e11e492c8f4dfb7cb5454cedd0ddd32013a4836321a25110f3a017f18475a86583e192132f8d8fd4c2dcb2a3aa95c3be3a57216bf9727cfd1284eea6fa870c8e689e91982c116ceeee2f8298b55646efad684b96eab883fd3d629437e9a0b6523f47ea5b59474a4766ccd01c13170bb08f47576a0fdb573d4dfb65279c1b79cb535426bcab60f4022dc42e40db29f15a6148b461241bae62070389932f035e7257752ef2d6130503d72344b24d360cae8ec11fa2dcbe04d3b18e66d081b552e93a71dc0094d1046bf4491e318f2ae00debffa0b8ada58c5f23e33fb598829ec2f46ad3894bd7f530210371a02e51ae0a414eb2eee43f3e08126dbdbae04c7de4b7416df32953234a6694ea84e6889f27c74206ab8144a393a2614e92adcc77550dd54827387b619f004c13f6c4a31e8bf525277669db0a0c3c589eda15063f12eb774a13e2aba2f2f7b6e9bc69f8485f1d6fc5773acf83671812412d28704003e78a17da25bacd1d61a6d9cb9f121abc71d023bcafa713b7c954e4e1c524e5bcaefd86c4a843e209eabbd579cde0263fc059ec6ff10017ba54fc9c2a1171d6b06f5d85079167117c12e6e5d0c71c008765fce756fd0f1141fbad6c1d2f32cd8e80429611a9a78dbc8e738d458f9ddce58ab43c77b34db9befb25cc1a588998e8dc2efa75c6883244fbbf9a7b4d6750c81b8d3fdedaf98dc61f49d067c369409f984b155ec347a3bef73e2a44957b0ca0f84c7fc335fd89453759ad0ac2fd9a5b38afa9fbe74daaee7bc52301302fb2286c21fb922f74d756de84519171fbecaa9b869682d431614ff6845126a4034f10253aa244bf89ab8e0dfd1f7fe8fc1a8472a10746d26896c8ece7ef80eb2e910069435518ccf096caeda63ad692455b04e6525bb8bae27197ca5118a57fb9a5d8fcfae1b9eb7874d91eafafa0e4fab5cb4d0173f7e3e58fae369843a641e98f3ee460e8cfe95d98f7fd38a8d2235e9d6050015833e6d7d21d7015c3b1ff42f0d3a3d9a38d373c8524752e06987c9408cca550f08c38c2a9a8d86d5ac7a04bab44254ed15c7b5670e0747788e11b81adb0d29e3d0b50d6a429340ee0d44a8c286fcaf9bc46403d26b4a4af95b021336103c1ae0f1274b33bb8b21c8cfca8a56c639f18a9df45d083fa7019aaa14d1ba50eb9a4112e574cd70969640602096265a87b1f77c0e00bbb501555f1626196611b4a824991cf10ab2874a12a8e0390267eaf9e3f8f99eadfbf40d111a26772cda1f50743c417eeec9c80171a83a730f246cf31c6691c96185d672a0fde9ccd7091c4b455dc93326913497396e0a4992773caeddcd783e534eb0f34b99bf23a2db6ee738381b5fc94ff603be014c507888ff55557793a8c5439b11dc5a347f35a2666eda81cda4d1c3a78fc4f3df3c7bde91d05524791b67142c446f60c3a4022912ddabdf817ca3280b671beaa496c935661e5adf39c1f4650563c5c807c8f21aa59df926199c4e2404690ea8ffd7dd65f637452ff93995fe9c5ac7a322b9bdc756b7ed6f533b9357a4a1ffa379dd096f144e9e0d87330c238ed3c6b08c8478e23b65518ea1e4e64585e5e9fec2f26dd7400ce4c73ff0eacdc3b07e4f34f6316f5b82fefc66e442ecc92bea8c1d58635d644724a3380e71fbbeef4bf3e57c6240ff603d65447f510eaa3c9ac794fd24f844489b7c560c7814fbc307e03f6a213eca5ea40fddf51d8731b74ec5b472bdf8ba59751065ed2461b02c41ef96622e60c0d26f9dc78c24f94372bef7e47cf09ed565ae3a52d39b02ffddf1953f1ff500f1659db9f1c2b23534702c19ec1cb7c18166fcd33997d53874c7cdb4e6c2b4d82751911913434e48b37a61a0971861187e5decb7f5c1ef6988bc1d6f7fd147a623d8bf361b0d7ece88df6e1ff8d037762d232e22e51d8c6ddaa9dc597b23ff9efbbfd416cc53e5543253732a23aba151cecf73b3ecff21c6a9fd1f24211fc21cde9633aae918ff1c6b72468f1de7e0ecb6539fa353c069fcbe8920dfa8e2fb86782e3062462f7eb2a2c441bfac21ab62744b05c70b6fc3c9f8e3a8a0c5a4263ed256a019861ecb28e20ce78e2d93f1a1def669e9652cb35d105bfdd5ff2313d27ab3eb00d1b628b4c20f42efa23390802af96a8f261ded3678ea0b780e1f4a88d23588a4ebb058adbf9a9c62ce2ce2f8264c874c697482e25f8d5a6daca4f57fd97d23c42d7b71ec150d4ee33931db5f7d63abe7d72dc936bb23a367c798e6a01509644284d52f9ae27d7d1bae597b2cbc26139354dcca0fff6d76c6065d661b66ca5eeb9f8d85810a029cb95b17e5173ef8ab92d475a1d3e21799e874ff04dbc962c668ef4be9f94d85b2a99d97c0db8f6b6d63e00e36c325cfab9aceaf7597113bff0086e8fad36eac7c0b443de6d3a8533789616d4c863df7200ba795a3b8d0a2b9568bb32af95fa604a3e3ea778c3dae159e1b612458584564ffda07b8aba9710134242b2d83d23127b51b9e41584c56f667b71bc01060240f3a2bc7e5d438e7095c1236e0e468079a83a5dbdcf132d258e9ed18f94d3c098867d06d3c09544565677b454be34ce567f1c143e2f3153bdc0353d65090dfd8f7af4633b89a781e01f4634dd7b0323ea1f38184e697bfc39a1299eaa278c39a2709cde0a346fea53a61f211112450b318d137fe68f6c102085aedabd2b045fab912da5c58d8019239f3a44b18f4fe30c5352e2e2bf030334a1dde1dcd23178636f1e38ec9e42102d8c54df0b94b207e804eacab3edddf89fabda6c8e1bd4e17ae31a57716c679ee8bc7de4412fec3934c6f3e8b4c1d1447dbba0fbc775dd3258f789ca53f1593cadc710fef6fd282bb41c0468ede5ad5b914e4758b4148b0d0c04c75ff6208ca3e79d92de8abafa4ec70ea7a4e454f0759337ce575c4954584e2bb8444c34e823d27b025d25fc9becfb4391df9882452bca0373164cd76e9af316df3f5bb7532e22557b485217254d5ab72ce349620f03758219b259784d4c9f1c7beac3cf08e624742e768b53b3d60ad0b94442c847b84a516a93d9b7d068c44c43980b4c7e2fb0ac964bf05a11fb2adb4f6d938715dde88061b238321afc7e5e84799b02a94baf3f879f89a98ab474ca12085137d639b837ebe069f6dcd8456141d063eb1c032aa392a44d1d58b1e77aba38a280625ab84e3b123507ea7a692c4acd1756c031fa52d637703ee957a993804c13e296cc20c1de55c9b8c032e50afffc51c02e5c12f48383237cdacd005b09243d9fe05e51cea42b77645e5c6f4e48c10e671d216b90a48f0d8f5c1dda553217f5126646d11a62587eb0a4ee0efdaf0d54bc2eb04cd34f5a529b682ce09a34d5acab2c8db58ed6244f7b024e68a14bcd5d7a7daa4dbcf490485cbd38e6f20e839d2b0142b9d766f9527937bb1a737877edf6122ba306bbfb5379243a6b22bdf85dcf3b079691f0e90b28a4259c1c9d8a02afa5b5a661a0f9dac52435e7d22e3591593d37eb2e10f646b51be2d1a96cd4490289ef642ad93eeffd64d7cf830d60dc4a98c768a9bdbf6ec9923062ff04abf19e8b65b95494a9420971018c7e6268b8fb2021a4ddd103976333fa52389643c711a980664e29a8479aa9c4091c2cc2074ce3ac1ab4afa217d39c6a1",
+		"c22add33457539a957d32dd07ec9110f8cdd2f00ab6ac256b4bc7732f63dd3b867b0ecac262555",
+		"e71f9a3dd457b4064df1d9055889f105af175a2d10dd7b8729da0d0116c2d9fd",
+		"7df9824e774c5f86d83cb5d8",
+		"689683c9e7aa9c48b9fda0cfffea0458ea0c3dedccd21efeb06126f1194780917c9f4f2f44b1daceec3f6b1f75506f4169bdacf12c1f65958784851056fe0b4b42a22aeb043ab35ca73747346ac58c550324c4b849a404c94b8860967b6fc58aff25dad0556f1952c045b91f56ec8eebf6f552c18b2a0641c037e6c6538b289601e1fd5a7bbe7b6e0b224124fec341bf77615183abafb52b3e30082a0abfc2cf224324338c132426011d9f800b382e6b834896ea48a8247f149d92ded7e69c7800096076cd2a729a1fe41c70dafb1f855ffa2ffc27b93e2f5f6827ade7118af60730033675d84de9cde6c260d3d615a945dfe0ed25f33b6cbd2c0e204ee919219d85c7536f4700f06fa61937f8dbbe9bda88db1f4ba8a8d195cd385eec62edd9ce673880800be9aa4430e5c10a5908f6dd349af70f32b32d8db38a7d73821af47b993b622bf168565082d07e88fc48231a440469adeca59263302438ece96d89de11cf8057454d1bfe8e4e36965a4d82618834a0847af39dd8776866d9558a5cff79a1cc9d1e3c22e050677e54ead68b3cf0094daa01330d41bb66708a8bbb8a196fae5c77dc6774629d38905e81d97c5b16d755182f687a8046e55d148419cf9c12139fee50c0533b0f04a805723ce1ea5595fca5b668e58f6b3b396f438308372489b640317cfa3a79392cf6d1afdd8c3359557a83790021a4eb418fa189ad15ba9be0f74182ac76076f102ec171117a3d16ca20b4d200e03e54f1f0ee6308e463a148c0c85aac3ccbe5781cf45b53a313f7c9975a45d1853ed9104a860c08634a8211b87500b5ffa3d8d9d56f22256d485b9b45b24d3873159adb8ae25966cc40f164f342519e88d1ead1e711e1b2bbd4be64c7e83f056f797c2d3a5cf7c5025f92be5637fa7738a1bbba55f761dcd1451ce4b1e85a6628b629a2f7917a86363b01516472c0f8614abe2ad1c9d5501b2a44a68e3eeeb34a64541125bf49138bcd15b7c82dfd40708414b85107d8b982c4f99783a03c707a37787a91a7198063f0e8a2d52dca61755105faaa09c063c7a0849570cba1aa7ddb3600eeba602c7e7c9b90ed00ec731d4d1d8e4bb42f9e9db21616c4aca48dc27b939428834404331288f03c2b5e887103c51748d0257519c3988f6492eb70cabbc2dd8a8a910d737a678d0970ec48bef3b81673bd10b687b37e11d49e7cf90c03c54826ecd833bfd9dbb8174274dd45b139d08371d5d248ee33298193194734c5863adf4bca92bc282bae2f47da5201fc240dd0710a22a8d922faf92c2071a7eede7ee17232d3b6ee5f3ebb1a8b230600b243c860968ab427a5f540912e5e7bfa0271201f288727f2bd5173539d5318e5c1c0a71cba4d9501b91c3bffa7bb61b3713f1751efe94a66e17d2b42da51d13c3df40f4db988dace42a6a1b9d138c4f590b7227990711afbf8f56fa63f2800cc019bbd4a7b3a0983c9b9e5f77562dcad6de96e3b2eb85cd99d28a021a10d6734400a91369236b48ed68528afc68f247d45c79318fc5d634ecb0f3ef8536d8ec2e877adc3308be906c5b96777d0e05970023e5c5dffed12310cc97249e4b95e32451c9acca8394fde699deda57e938bed7167e62e2cb62357f82fbe821ee73b4e09c6e2f512515412c2f27805762a8493e74a3d30bb409e499002a97354381318af28311ce484bdf7c39db53f08f73ca5793945e13fc8c66d503fa95506b37ce134ce2945d75b424ca6367ef4ed47b9cb8ba7de80e773279bf23ac888eb105385ea958b1b49b27c8db6b1e14a5c8ed5d28808a7d0b6bff1a58f24f9c57fd8b8f477a9d1365f89c698b8ba923896181299d474b93e05d3c915b10a69e61910761a6d8644933c593661b0828afeca590ca18e702322d9140d98fcf836c2f7a4f72b59eb529823a52ab05d919c3eee4db2cae1067213c5070450a160fd52fa44bc9bacc5c136701cd7adb1faf484da376477da08f6a4dcaa37af47c7b026c2da9d5fd0b30741357104cb2bc0d3cebd132b5fc7c873ebeceec5492aecab95ab393f35b93b923d2ca071e6bd8522c3ad8598a05e96646504f1620c045aa5734d665acbdda0ef73612be4ca4d95ba069041e042497f7b10445869989ce30f55206a1feb4e64890b7d1f7e9df2e88a352674a52ae4267c06592d425ed1d88101cf94588135892218ac11f3976ab2b47a27f02eb887696c94b13d48b4370eb11222274b5513a0fef905c66d0c1893832ffdb9b333178b65338fd8b81094d8f86f2e4e96a47e72032cd6fd47af87eec295c6e980f595b57f79abeb4654c4039fa03ade732b1e579551898b801ecd6e0fb1c5fd198335834b51673d074a8222640d2a969998f5b878bf897fdcf3426c4e24a7c599e5567643fa79ea5d20e7de581a873ee0181e3632a4e304f9dae09a81f882d4061ec17e588793b160c93a926874d5a8b78727f88de9bc125589a9562db5bb1c01012bbea1b2eeab68877871ce83455db43cc48455effbc71c436aebe362af22c6a319d134f65681c4d0d51f9aa42fb20f48ae3f7065664aeff5d8349624a5d79eb0bef3cbb2a1244ee445f560a6bf7a796b2c950a37dfb85ed5be11e8e305e835c9e077e676aa5ce23edb1f74806278548e3fa35059abc2f032289f9bd76043c8dd1352b6131cf34f66bcd0e7f1d13081f5b08ed0c69136f3b7ad8e05e9fe99a9b73624095f96740c1f40074e5d92ffeccdc0f15502082fdfcfc97a800be511c22b875f2832b2b891cb1aad2a17c7bd0be4427a4549404172f7c14d5e425e14498237c26a7813cd8612d048703cb180f1a6194f688b4644304950b078692faec7a2a5c5bbc482f3a7e8ef2825c4c19032a7a79a2908ca9774c6403e6b15625c485f2dd078902aff769dfee2dca9373704bf63ad981b51f61253910fd48c49ef10e3938f35ca8dd491a8e569baef675df30367b093f1088ebe8f876191dc32055481d074e5e47a4bd728efaea9fee3e83d8556255ffb2fa08194bdc66897d97d1557186d5f873169461494a83368ed8065b9a033fa4c2f07f7c60f945b60479e3c89233d58f674c0c6fa5918150bae0c6de2b65a09ccd490e2ad8571745bc37e70982411af667f3e8e9b9f7f75d863e5fef05c1f0d2acc7c86585a83ee32e0a64a9e67e75b80def5bfeb7cffe6e6822efa7a9cf049689b58336b081c039696e0fd3b2a2a6b0d177c9b3f8fe5cbb1c69ea93c1235b2c5b6934f603127eeafc4ed0728161612acdb2ba894a5ac376c4ef1fa8d49b4722379e5cb39752837395c413dd29a2a88c03849b6fb2221fd85ba6d5a50ba7ee9c09ecc5e6dc66afdaa1b021282cadc68f19529eadab809341187d57cfdfe01d0798ab8a94277b9b868612e575bd98f70de80ebe5f57637c511800373262eb5ac3836b03808ca5d5f732f286a5f18a7b7fb8cd8f60e4debe54731c9c524b84694c5469975443964ed28ccff2f4e8e0cf4c60c1c8a092e986cf12fa90a994e4f26ac89fabe8a0d1e27fdc00f1d3d3fdb73bb76809f93ea113e336cb0a5438147e454e262fbb7d656aa1be1288839bc342b48ba7d0e72c85a2e24be1a97dfb2db85b5d850481e62f3b11a28c6407686e73d550b9f1d0f010602e82af26813d2484a8db2da0814782c8404b2865abfbe3c98a07ffb37eea6de7992cad73a9b81ae96a9acb13ba213eb4111d868cc73b0432d2b6c2d7e0e0ca7ccbdce86d01576e1136871a07c76498eae53fb7ebf2e85fb8561d10dfba740400ef4495ece7eb33ce3bce26344eddd88cf1ed8028ec5fe8e71edda54dbdae08f50f8df6295f6d7ef1163f62262a200456a7777d0565d7f5832fcc7ac144b5c3e0ce3e5c9b7f880a54ed5e80662e96b356ff58f2e372b1dc0d73cb8b96c72caa9e5dd312841a8be23f838bc706d893e1a8a48b2c069874c293c41d00226f73f987aec8686046ac4c0c972c991c38b98cabce30e7255dbf16039b95dc7d103fde630b03441b15bd2c214763fece9d6778d1c6354d2c9478c226175c02cb006006715fffc879a6a2b4111f6234ee330d6c84d453c9ffac08efda1f380110a8ef8c2fe44e2ed644cc3e0146b4d02f76586fbb6d69b827be38b9add444e2bac4d7165007cdbf2ea8c4b967fc1bb70c68b229f19bc3f79cb13ee6265264885f04c09a96583f331ed46de3e5dcaf08313ba6053f3d0c1916a0f",
+	},
+	{
+		"3ab6cbeebc18df951d371e0f3cce2697fb367476bd9d50ca9e668c77636eeb9d24b68be0ce6a75eca194fbde6221755d57e9d3148623de24896a9becd98789fd3d14de0c7e53f81fe7f3fd491472a66b5b797fe19c5d0525c7a111a0289a9e65ae7c712ccf694cb75c490070bca7db17205af9bdb7fee27f9ff41fc78ebd2d3d399e690908b5c064ffc0d5bb67b0d2880bcb45c2ca2741691b6131aa1e5ee758fc50610406216905e13ec049ee92d1f95e16bc283dfd91595ec2037d20ead51d3a362140578a4538c80581b79852b0f6686c1ea66aafffc872024592ec1aaf2650d167a75bace024b261db4ab48b401cf85ec2620dc12a7fc37012af8ac1d6db923d82eee962129bc4ede578782594708357d29118fd10dc6d228bf7e461d2769e556488b776237b6309f3dc2e884cb2df1f43f71c53d389765f805ac053d05fa835e75fab0adb0f13ceeb425637f43556372d728a00fb005f7c5a20cf2b7f776066d60b70b11a848005c6d63dba0c93f139067b39017c997dd6b94c0138c3619e9a6d0e4b8792cb8d58a2ca12ae5d03e7637f2065fbb9e2d1722fd3aaf234488ca157d829e9a3b642458054f3dd58da41d7fba6d2b488a327b776d1aaab1a364c710e755ab22b9cf7abf1eb8949c5ca20c070f275f8959cb00c6d5ab7879003f89f795351a4ef4850e033d929f9a349b9133b2e0bd1cabbdd381594bfa697b845100b96b5fade05db12de040b814ec49489f39f5abd5b37f570cbb516636d5b7378f12872d02d4de20b52ed8ca0b12029a4c084621bbb578b870ca2ea79fd5df1ef8664bfb3b1a1bf038e4ba33f6ccde42c5146470c9dd293aa747d2372db1561617920142ac1d32e4f1fd18e8b9e72b7efb8fefc56d08f00450d23b7e8381849b1385ddcf9310a4850dbd6db7a4992690190655760f557a5027b5ceab3743365ac9041a5c14bed1126c4eca00d7e0a0e0e6f666f64bd1466387150ece5835192149237d5dd25e703e9d3a4f652ae04601d6acf8228e4e86055394c3abc9dccd02f04a60c298d101260b408b2620c137f77e2019fc6eaff1b234c56dfe922b0192656254fe3356143e969f64b7609cbedebcc8cb2b68bcdd9d723b9c14669da6cbfffbca2351de51e87db6afde435ead0017682b8014f91d9734a9ab9b374257273e114a8fffac786d53183ba666d8a67e30c1fe45bb1bdcefb5787afcbad213f8e36e78d30ae1305df96bf450349ade655cccbb17d887f79e00728abb449ea427fd2d0af80e3b5607a74a57dbe5264131f2fc49cb74415974b3d43ff872d4106ff11b680f56be06fdf85ec9dd850b1f77f759337b9a9ce04e611036d3f45743e562abe4b959eba7424a712fcf7c3f3773886aef22f7cf6168efa83cd3ff70b9521cae1b6689b2b8c423d883a007bb138025f2a31db2147691bcb365ac242efe40cd09a746cc501ae0289e80205993b07f86538d486803da14b74fb0db6ebf1c2bb8c36275137d654c1be56c65891cd50f705247d85621fd0d61ade8c05cf4ec15b84e8adbcbe017d7d5743d5e91025e0154a5d9bac7c6b8297490e9c195c5d74e046219c042219817a5c56636c7c4382c6a01d721d88f4b4d20250eb5eae5f3ef481dbf8a3f47a1d51d080bd4cc33f12645c8481e57835b77a85a2d83301172782f22026e69a43376ac4f5b78734c9eb914e6c76c6a12d4127cf195ad030825322a279093cbc40a680355d086a27f3fb7560713b019e7c286d96833dc60590e9a709f2e3c632894668e74ed20e42cd83a23ebea3dc3bcc49d14f8697541780fb2072dee6a5672d0d4e7bdf5cbdacdf5fea9e03c6d9cf0faa1e954172acc26dcd344bb3d9b2e0e6015cc55d19713d795bdb7c21b44b305e69c69fdb7261483f9693f36f45d356462f1ba4498de1c2e8bc3e0a70893acef2006dcd73cf15b265a8a5d4ed792a34a846d8f1d3b9b3bb75f1c5e57a00b36c00203973ef4e2654f6cb29e4445318ed99f0de6ca992281e83ed03feedb66aeed6a461c6f2871ae95343cd9797e58430d5639d7ef5c59c78b29f76a055e18e2b85eff177770c60ca4f2d61e612e617e749b4653e7901b62ba02dcbf50e59219349120ac01e6b8a6e98eb54abd16b921a1ff85898f90fc49a3c8f8f4ae9b0dd32c3e7f2e1527c4feb67a496390f28532f20acc71abb8bb4f71b434104f41e36b705289858a4e8430b8cd9449b0198ca2244923cff1df0f63833373c275572de5a9a77b23e5ff54aebce8e86d02651f26ae32e69001e5f3951967579ebe8574682cef8c12dee0b18bc999f8cc0f07e2ad3ac94d3caf30c1c8a8295756aecbbecbbb4ade8a2b8015e52a0eb1290693c6316d036e0c443fc4ec591c32f7e7f1b3933c921d5812233d3c21ee5528822b59ef2ec7eb62f7b04f40cc8238a473ec37a07e54f8907825ccaa1421c2964d2c756be450dedc011e1cdd9045720421b9a4a00e9d3076c2fd10d71ee36d5c0fd2c7e42396b034a4cd0245027449242dfdc42c8af4a34df1b4150097726c9745247b78bb2bad5fe8af94eb13ee1f41dbd36e56d801a4c9c5b9ca5d3c26f4714b6fe9f69b87567426eb6f4ac97e8c9541eafc19fc90d3b24aae0f76c4f3f81063d206ff695d638048c2cb023147a78332939d2f2470d16f1ed0e5d3d4dde438affb2809488b99815e54938fac3b02deceaffde310cf422f9027f364f5e79da5d2b5af1b4138ac9f9d301f396b220829c1f60cd2b54ef24576e5ba6ccd4802900db1bb4eea57de7787eda0e30fa90cc19f099444488699bf7c442c398c2ed989d084c8cadc97325484e337848c34562b3dea6f7670f935ed3d5216c970e04351651c1c31a34e862821bdbcbde202d91fed38965e31cc3b6f1e52288f327bd0a787ecd92b3b6f535d1d000b0f02d41ee01ca54e4e6179ad7fcbd60f0e41dfa5c9cc7ee4f7de3844fb385ffa3b24092b30be697f1fd32c9faef29ead346e42fe2ab1d312901b678b43b7758edb7eaa1c2d038b4cd6a7dc759a6b12cec955bcf4179006a7ab6e22ef15986df107080d340b8870e2304d57caa87a9961c04655d7d66c7f71ca9260e02aced131d6de65d256d6b487141c51bc86eb1e4721742f07d09e799b30da7b5ba94c8d701ae34271ba06f8ce134a7a9a2598d1570cf05edd9ec868cfa2e41b4c20a8bc4b8bfebd45f5a60408f08e931617746d1464bbe1f3844ab3272ede635f771f9af30e483903ee4d0cdecbaff4d31451e7791dc97c92042fb932fe1c82652c1d682a55912e33de3b1299db076cef594458670dc4f911f4a244e2bec757dad4b0052a41235e2f5e60b929682608c16a61287826218a1ac3cf0d8286555d5b0552754685c365d4342f0d9c45065daf6786179da791a86b50a5edd6fb4b21f09d9747136aacf79ecbf52b00fb88b0630ec7f0a6699901ba4eff913a3ab33ac85a71ebb51ed343eac86eebb3e79c16e664078ccda09e77ef8e0919b8cc447116b65ccbd5200fbfe86e9bac5637b33c9bcac9596b57c14ad5da548e96a8ffad5f5c69247c68d464c770011da7b45a337f138cda6b4e15311879bfaf12af4c61fba596780e6adcd5dadde372823da6014122dbac70f0dd896a8d387d3c74df282a659028d06cfeab3ae22dcd1fc3ce60f69a0d678aeae0e5681952949e31ccb8975cd167c9d012f4b230b1c1f47022eb1a3042951b338a734cdd17db0ed483a621650deb3510efe74191a94611dc212c0c73b117a73b8ae41892cf176742bd98a7cb73dcdc53b42df56d640739852335f8d44d901fc884286b433fc285fd5b3db8df0a8522cea3182c071f559c328b8516c9252681a94eecec7ebf626c0a9014d9aaaa0c694d14855433dae06656657d1f8a939123d28e00513d72bd3802d211ad7c1e06b9228c0d5656edccad5339bcdddd5e01afdc01f10974be3187804324fc513ba583b7b2da1e9096bbe3d078c1adc6c34d92c54e9c49fccdc17d10e66962120ee5d9b1cfe852569436270cf7c4c3bb12568050e2ca4db08bbac16214238413195dd4d936272fca5d56d7551b9b002df1807ed44abc84c66746387b79bc9e830a635c308a7bfad7c2c22cee6d3d0c5ebd8b230837b7ceaefdf71a67a3a8eaae0c36de86b2d96e759b8b53f8b8604775eb7a7e13223cb21033dc87d775628581a954085c2d66c1c8f225b1aa86091061738e7495cb36a5ff032dc678904bfa39a00285cd6947865b6d4805e3411644b4a4c94a6fffe05ef31e156bae6165d801685dcec195552d029d22e5de393a82ddf3cd3de3ad8cd6bba2325a03982204f07fc3c21518ef17a601fd743b27f7191bb446ff61d3c61d7608777990997e911932532e5b3235f13423756f5b6c786720cf6682932c90092",
+		"50772c5a0e156ba13a9d86edc0e600021d56f7d31e7e452a74ad53a6775339c7ca6521d87a8c79b42900a1e9e6a1ec03f7e3d615611c3fd5c9927c40e5b508af1a298794b60148df01e9c9e78ab5ea8198c097fadcd6cfa6694be64e00eefe1a1885aece86f6ad87df766e692b58ebc41982bef5",
+		"93a2561a9904a1787a10e2a668cd6a814f2877a7b512698e94796805875c8d1a",
+		"588d9bc1d98210d9700ef488",
+		"165d8c9eabcd5e93e6eff7be122c8c242e1a7f284790c93324f924efabcec4a4ce48262011b7360c2833143d645ff295453853c92f0c48c6dfc2af7ec58d9bec0d13239c7e5593cdb39d49376c6341263df80c0ed2ed79fe9899d0c07de93f6ea95a5dfd307e49bdb5672b158a4df623ee86d54cd1a0fa9a60ce39d1f5f4b6b0ce9daf2a61a907cff3bdd3f29156ac439638e0910d728843ae17ea7368814ad7734732e7c023d4954e1cd5fd19fc9b76e9bb84b61dd4371478917757b14b366b4bfab4eab0d9de746088ad43d8742e2b9e58faff15c2eff084df5f4316111d5dd7d23cc0b1ee1000253f26cd260aa636f03f64a8342e531ca1515b3beecc3ee07a29184988325322d5c09754c278231f92c0d980adc919d4fccf4a1da1d37f1ddb58ca997d6d700946199fa007c43853b6caf5f8049233584087fb23c3952414ac487e452f0c3898486d04e5b008b843122501f9c8a294da9159a04119ad5c8e9f5c211411e34559d3a7bcf2ac10e0174f94f3f2968c80ebdf4498de172884dbdad0acc3a887f9bfe896a6004d54cc424567d53f1198ba33c56aa460edc6af0e437b34322c1144854bafb2434f00703c1992dbad0ceaa0616aec60a380676ca11558cece57a936959d6c2ffe0647eeffd37524fbafa9691f31499701b202d9dc9980e79ea517089eced779aa45b522c9ad193e63ea8b64e8a942f630d44370f23b7e9acfedac51dd9f139f8806b09a8fbbabc76fec3c3721fad5087a6d41f93973af8d787d8bc74a3122d99ea14e2f30a3c90be4b695c8b269784eefafa52d6a79e785eb47a23d72f037ca572b7029d2f37baabce57658119fb02c5b659e3aadfe0052f1cc3c0afc6fe4624533d9700388713945c20c1d175da53738fc73f48fe57fef8305e796b474b6f8d3fc5040042373a13384237d95bb045ce0c20934a964a8372acedfd6e559aa84180a86311a3996cc17bf7f73e5d85d4db2529989e5836edad490aaa5f56d17326825aa20608fd209903335de4b36b79f68b6a52194f6ea8ce42570533df650e65b50c367f69b9f08c32b3ce3e75318106b8b2c6b6d09369c781fbf2aaa35053af215b621f833814ec4778ac683de0dc22c418b077a917a6e405ccbde9f72ed523aa696be1a6f247b096b9235217bcf19b88d43178cce5a7d82335fccb4c079e00280bfd272b9f16ffefa7fea38d09dfb2e4874553b135052595812aed3fa15096abf1eebf9abd598289e0d156974de4c2654c60825d42b662ca7439816d9d3a0255f40a4965504f643f029da535d4b109e8658ec570e99859382ca0ede0b0495d508c63c7f1eff3f648c60e9b773590cc663a751178ba7603a11985ff519056661b9460c1aabc30e83bb0073a927682a06d1b8050c345f7920c1a37546d79587fae2a92c803a986248f90547f0b6c0ad0552d8260d2a0dc3cc76d092ab76b8c12f05dcf141167a6ea300bc23227933396ef6fe9d51a1ba5a754485950f06cfa6964db2d0fd1d4393cc36f0592fca25ac1a6aacda2a32f548ed20287e3d291661848a62d41504e4fcb1cd1785617fa5786712b3005f1a1041733df6cf838ea3ea0b93685889bc6b2857d80a9bc0e7a66f7fb3d805770402f049889311fc112dccc72a25bd127777fd87bf5ab56d39bfe6be2b45a8301c2f324dcc50b27540200d522c24941701f7293b8877ac84cf35638507c7d912a3a94e4384b68c507412df65d0c4ca8ec2da704bd4483eb2e0d13b68c0c2b68c106a55b9710ad0a1436d655a3cf3c419d5e6f027ddf5dcfc896a5b316a7dae9290a7bf81aed539a647c8c98e24e7ed6a4f7f00a11134ca715e5826625c250500f8f16b40de048b095b5dd08268407f58a91c86c36ca5a2bf4f8fc682adf1bf601da24414c74956e1a8fd2888b5260e980c32f6678a4dc4ff73220c22593d23144b84c2ff56920342248876d15ea54fc100c09a81b802dd15f030bda9aa08727ea49e34f0ca8693e0a06d0af06ea7ceddbf0584adfdebeb20510bbac683451d9f84cf0f4e85c34d979e550e07e7f414d6f1011cb3dc28d0df6d4aac113f2d5b04e4486ee2cdcd4157dafcbbd55e8330a7176d1b231d9f47a63da9ee30fec6cc2c5aba3a8c6154f79997af89d972743255355647235ee939f4f305ec655271e0cd562ff6f401b86dd5826c769298445108ad0d9e13c504551f74c507436911331db60ef0ea99dc259b13cfcb0596fa9b3c95cd7fc3b1611e3b012b6719afbcee7548939676dffc372276aecd08e6a14251407cf995266545427d49ae5ab245cd5d534c52542fc71b3973f0b766f3d234c8baaec8b74eaa8ba90abe160b4504769d02e08d7af4e7ecc167780c619cefa58865169b674b2b1e10d82f6560ba0be41a781f4afa46bd722566d941a8e6f87e4a5c03d89685a22a3470354f2922e2915f9d46288a5e8896ed13617dce694a595e379f25fe621dde8ba73d865976950954e5bd07db147a0fb74f87cb06aba49b073942b82fab33a878651df73df2721ef800b658bdc6c359d396f684598e93f38e79639b8736b02dfcc124fb9fc199c35f2fa1d0dc39939c57286e58a7deed7b6c76e02b99a14d9bbf11f65d8eb7fa096fe4baf0f78cb34736499a0ca550f10d7edc8909dc34b039e3abdf1aa67a51d37a2eaf4c07022897d4d8355d3325bcf392d91d02d462488ead90b366e9645b956c3802e4249d34b5b2b2484a1dec15a9477821df6bef5e1626ec5ee9832fc3bd0b63a3c4100d32fac3e9085f0b5ba43123f54beaa7ccbe6ba68231649f35a28acfcbbf97dea2d6cfd96025032b3950ec8437108d0f07baf1bc89e3afbc2cdbb5031d3cd9e20b19018adda466382059229e4c8c54b455eda4280bde43b36afa96e146e408c7104523d5f565d22ef86d4c7cbf9c6e0d0b30e37b37feb9332939c642eacfe19d0dae1259d3267635051ea5f9b518dd74786e45fb8bdf72cbe3753bd50bea2a961b49cc0e2d589e77fd25ebd962463fc728b1d288c38a79a182b124d345872afbcfe792d259e7e5334311244edc75d05f9a12eadb61fd3ff79fe8c097eb01a4ac1f0c339d3be74be3d96b0b6a15e8868d043a0f2007ee8aa51756d78b7a78ad90fd9a26afbcb51fdc20ed7a3947f715c833e363bb87504d8efc9f8b93a993e2e26430f79f3cce203b09093c9b456b1967212eb0db4f7688d4dccd4a523866f75c9d9e7ce07825ae34399c5607a60b771866a647b6d5e1e20795ca906e451f367d8c40ffe79a2cecfe7aa47a402f8d49be9084661c96ebb11f1b48e7e8abd2978ee626f962e98f99db4eb3c6a52aa2bb2e62194120ce1e773b9db784e8c9b5adcfb70e3bd5717293eebf014e9872c5c1bdf3fb296cb88eab5e97a5ac320092033b49f37d840dac23021c19ab2a89190f3c8dde927f6e6b41874bf71ba7747a616682bd5b3f17a1dad40f4993a1b186ce4f44afb4e36af7715450bac62cb1527eb8db1d87bbc4d9c99415d16660e48efd911e02f5777a77e72733af3c3f5315dd0c785d5212b79c46c3bccd74582c57cfac0d50fc0c85370476913f9d8e8e10d0f6602f2271994972de49ab1a91728713c3cfcedb0e61c270b5fb331a980965bcfe10b41251a0f7915d5943f49fb139626f1c424524f2fba3a407e77dd7513669894fd09fff4185fbb997b4e4677f6ea0b52892f013f1691bdb38eee9307a565e396bab484d91cea9268f49aed29e319b0add900b6a75f7461db5486aaf5366f98df05674361308931de753c70777de73337a996f6d4b0e06d63a69849ba7533bb0e446f062edbd6250e61a49f4120f84efc1cf74c1bd30cc61a2d719fa76991dab119fc814a7c56f48bd584c7935679c53bb0ac78905b5d961fcd89a4b567d17a5182651cb07146aa9a94972ce613e8ff9c878a8433c0244052f09980a52d800e97ba65e8ac186862def58c72b9feec91266e26aa5075b3337c7bb8716b3acafe666ffe2df32b78f9995661d3ba28f8a8780436aae1da2a3e6a0a16dc562b8d5df6f68391aab73a10508e0f55208f974a0505f0fc0d8a55049a7b631fc94fab91459ae1f199527362695b41972e50faee34c5cca9e35e8682099f5e9652f88cfe9fa990ff2154c89c1c2a4ed6bb8a889fecfdf048ee0aae7798c55d6cdfd062cbca97ca289578c832d658ceaf26faba54c9c3ee9eb5bac80698c1441b9cba287f749a5e30d5cc715a01c89353ceab0974ae77fecc1d2dfb31a5101783cbc002c73cd155dfd14685c2f9acc170dc437c649b6b4720b676848a7f9b56cc4787eabe72f6e3f2aed776f9bb1432fba93a63bfa44fbcfcb6eaa9ef4b79b32bdbd68cddbb9897cf5a02c6f99fc765790092edf0d5bca7c55cf232a03fbb6f3eae09b12e09a9b49a538e0589394700d16ebd3",
+	},
+	{
+		"3497e8d61062e6f2084ebf72d00e9a47b550591edeee9746f31ea28039a1646d384c4348af293ab778f92a4807c48fbd14e8dbf3d67339c991dc4aca7dae38b5fb7bfeaaa538611d328b653950f4f664dcd257b345917cd66dc6a1ea75d99f70549d1af9d67b1608077b41576f38bb4c0a13ff4fa47b251142c6fbb79f9a27f43841ed0ebc0416c37f571aef8fd63b99e93ae88db50e9ef7d499ae7433d5686b165579d3598f96d9e7b1c876870310703df8fdf2069beadb34984f676eb7d3840c4c5766dcee3fc39f0739260a499647429339482e232362bc72c92a299cae36e9069cc5f4db8893e2c1b9ec0b4f334de26c951090b9724c2b3b7655d8248bc12a27861e020eb1e4cf6ad0dab903279b6fbdabff761d4ba159c1f631e681f210a8782faa86e08e554b5e30046157a0d1144bd08a691c2cc2dd22f3c3a4e5d44c5d03f7e3e385382ee4683345c0d316d41ee75f87038b49e0ad3ca45121789e7e7b95615e1a9a8dfe02c044c2935a97b141f639448182252ebfc980e0411e5fbcb3c01acd5aa7cc5d67101ffa6ab6acacace5f02d67155c26dedc071ffa66dbad26f67a819d46de0556fdffc1b4ab6d60905d8ef873ea1e51c62571c08b4c6db242e733e02e11e5840ee445c290b2232010b118839b37d4615c4521e8928e9ad475cdb4a3de9928ec7e6daf0e20d22e308347b31e7e877fdacda0c25f2e5c33a329e84707816ff4ffdca30dfc753c2cf883df16016795db34359e9363fac60624ae4d2b30bc1f2f99c23d953779c22ffca145fd08dad83c0f76cf727196799544c6c07483e0a41ca2e1b1da5a730956154f531d292b5a39a229ab13bf24a804eb68786e481c8aebfd3bc557afceadc41d00e1472c3b80ce652be1245089283bf1a1a93abd3325bb6eea121db8c0e1d6c0c31decfe9dba63c89b881824b0531651fc500f2f75ca9e5fdcbb179c9ded5d600a495ea704c2709f4a88c4fadcda4cd82a5b089f25a6fe0161159efe03fb5e0d44bdb5487f25e8c9adacc389860f62b06a6a4f8f104d9171622f70652ace736e8b28b70a4d9fd3fa4b9784d1a6e6811150d0a0601d31d17f6041e58a1058f99b80b0a6cd4f79c79a104b6bb731ecc881bc68e1d99ab358faf43d8504957ea0152e46e27dbfaa17d0f58287276e4fa82ab78a03513d5b4c3199d1362e4fd6447d1c26fadbd011abc69332ed0181952b391f2e8a5c89d68e22a7c451f69a9573b6bb6d918c7e3d52116f3f12f1d43d2af46bb450f58bde1732a268293cfd9cf2b90a844588c1979a30d6ac21aaea4b9e5500ef4a8bcd62bd70cae6acc8839f818d23c615e45daf14335c36dd46817c9b816be60c3848caa812b055da33f45bc01721d6fb7e850fb1e1458f27c70bc34876a955aef11f5703cfacde03a039c3b75b99b2d91fc18b00071a28ce25eb169b946b49858aa0885a4c665deca020a3fbba55d4d9175fd91e7901ec9eec0239806e8305f8238e5270f4af5c94d0008f8a5564636cc33c8a3d3e76db2a7915abe798b0dfbb3e322b33e188c7b188573bddbb9e4a7edbd4bb194b9743c4aceeab449f8affddbc2b109eb3d84f3b2f8b18ea2962680437241d82bb6146674ff1abee7baacc38d5dcd688b425c3e3b0dccdda3e36de755afcf7155d3d7cac2e279baad167e2a743b82ff8ddf3db8ecfa9680ddf468339427a4e9fb8ca4ce6f1e790c24e7269912a9989088c65965b0efe68ed44eb26876674261e3e72042f5995f1a7075b3932f4c23a8027d0db35ce4322122f489995bcc0b3fa32b7298c4c1b3354766c866a2fc0ea5690c58c5e08ae7037f70accb3ca7faefc37d78883f2bcd768285dd2571dbcaead813a0b8ae87cc1df868e93500d414c4418d5c80b919f73b9fd46111a02bfc884f9d30ee14fcfc1d55d54256b9572afad4777b8d8172c911472a22e7461f6f85aca063c19d6fdef3351149ee6864e93cdc54ca5dc7837f0ead91f5e3b155795df5dd1f933cee8671ffc05058353995019e5f6f55d2de6470605a5411afcd7fa5aa8f38d77dbf496d7fa9c5a4d35ab661aa15c77ce42bed44763166160ed5bba954e470c293ca301363f5b837406ea8ea746057588c34acf266030864d8c40e2da88ef04c49205fad1607d456767d30eadd884359bce04c12e35487bc1885d9b104c9fd4dea4ceaf054cf46cb3c77a619ffe963acc9bfcfad0447591ccd32cdd1fccb1fe7080ad75cca2e17f695ce0095a774327123f21e2839773506a9f2d896bde87dc5e35512ad733aa408f8a49e9018d1013cc32f550c968a03308cdbc73ab444f0a79a13450d4de906369da4c6a675d7e338f738358dc238be4f047579c8ba7a60448da541cb9e57f22bfcb8c26280a59b77edd0f5a009a3ef1e2958d6d3c3372840dc6a0c6ab1fe86aeb7590137feacbfdc7da57c77595b8572b45c4677836ec86fd8c4ca8ac351397aaa3aa298d752754507e1cc514d41c3f1ae0a692179218141f65bccb9acf6244730c6d00829455d21371972745b3665f930cf2aa9f0abebe6f7b89094aeb4dbdf7bbbe794f134b6284e289c995ef2929fc1bd39b259259950de29e57cdec15c4a7d33ef6e689596a6ce23301d25c2ace77fe699d90c2329da4d0f471bc093563dc735ac2fdb32c6995606a67bc953534939ed1236003c004d3b47590beabf39a1e4d5d1b00898496e9effda68433da17d1ab3a32aefa3681aeac116c5705077552649153ed15e9d704e67d8819579feb02d91db0d3533182ff43ee5648f5cc9a595ded4772d61e77bd9bffd6f29fc1f478dea44c32d5ce3118bc8860b254fb0bb1e85223bf709a7c0b9a52fd3914f1b1f295fd246bcb568388dee43a32df45e3c798068608a102143b5511746903255b98238003eed68776b46bb0e64af6c9118ecf9896709aaaabefbc1f58bf45b45768345b560ae2cdbe4d7da497736da8013c4098addb4258cafe7823bdbdd715250b707b155248d39fc6773639e4de3b201fd3cdfa1526c4149ee7d15bbee680c956fbdea844b1470a287d430c5c7e2d7b51fa756720397bbe214c19df3399a989958732d93979e361f7266e53a59bcef695435db67cd8749d258e7d582726e1bcad1395e68d7848849fb6d74451a53ae6e8989c64701102959f7fedc6a5cf8352e218396f9181f33037ca74886fae6e57460bbcb71cbe4cbb3d3a81e2090434eb1d6d5baeee4ede251952ad88001ce047279cfe435a4afe97847f798d84ad79a11bd44f09222d2f3b7fdcc47ff8a4c61f40c4629a0f603193e0aa2164579a05726e547c9081abcc0087907f8034469f740a020e19623fad42e9cea64068abb3d6ff2f6680da328061c200e1f646816a5083786ae5b71728a0e5cee14d7a942379c389fa9dbc7afe7e7ae075c061df11e4587bc90f92f1b077c091c43a25e7b3e870ad852c2883aba2632063c4ff74a857ef7267816317f823a8bc5dcda311b513be3a40e6bdeb89210bece50a608e624f00c9d063e0c8878884e45527f50a3ab4447a9a01652322700f087b6f96ddbe96a68ef98656800eda6563015a6d3c0eb1b6a9b21cccd58cdcdd074b73e40a098a980210ef831ec9e881cb42ee07519fbdfa52d9c62766a2046dee7752f880dc9082ed7f050b49ed8d14307b1b811bd87b6db2419418e49885d20fd7ca8fb45a11a1da17ac2304393734b552b5d02a303ddc72d1f456697a287851f207054c18a6262f5349348c806841d21e11fd4e4ed9c01fce1688483e009930079f7d2045a34f98ed83256dec66400a783d58c61619e6e42f6e2c6e6fc69e76651b96aabfe643ac69681955ce595f4696b80dadd1f3910061be6ed0840d47e928dd93e7c3d6932d3ead820d06e2539d9a604a6b53db6bb599da851de7cc060faa9af76d708a9aaf371dbc3eff0fdb99702504c3006f789a49feb730cabe40745837e2c8c17c77f999333798431231b337357637a5efd1eeed891fb7475f2c9f960e67578adf50241287bc5599ee08d0237f08c86ed9b75b62d612a9353e48cb4cb022d78f73fba1fab7f794a5ff64c97e6c91ec464847a81e5a5253989a1ee54a41bcd9b4b77bae6e72421471a7ddf0136edc59b72402d57e542916ee47fb3988b7123c6e8debddff2df171d4ce61e83c3d41f36143c9df97f2f68639f1bfc2a9d1fe175fe9f45e17e5cfebb330d3f06e15e3cf58acaff09ea576d896359a3f06985765824bc499319384e4c458d4326db801c564b0b503552bdbec60752b670d82cc8fce9028ff24ade3e805b81a72701b37d4ccedd72118b20d792739e035bbacc4893ded88619a6c499f246311947e48684a35406c4ef279c71ab2a74f6e5313f7900080f19aec3a39109d4aa41c930c66c84cd2163f4cdd59fe84a86cd8bb6468bce45a56d09490e032da844e6d90b436dd874c1cd32a75d1ae1d3e86d8a2ef948649eb56dd7b360f55ba5dc34a12f9279945436c6fb83d1ed57ba4ae1d9342a3dc2df9baa82fc9fee927c13439ba5bd2ff9f3e6f577b8d2df731db14c51db8a14bb15bf3e125f1ca4cb2fe856c5a576cf995db5010687d0799581c5e76d400c1855bb46680a631cc582f51c589a831",
+		"823d0cd34e7450550da9716c1f456ce0cbc79431483a6214939266581b0e899e4c95719a09c1ef166a618289a6ee6971b6fea3fe380512cb977823b387ac51d341c26d4a835c61eebde37764d2e1d588df7886177e98e3151106c898b3196bf4dbd83f5f",
+		"a4639c22fc7f370d8500a53819102df5e86c541c0ca10e8f6564e50b90c28f34",
+		"34a04df283c45655a52bdd84",
+		"cd8d1b2e5f65ddb3c0da8f12096134da22ad4d541444964077610aafc1f77f8da5ffc75bee807541cb6eb0526e78d57fd88fa9d9608914cf391ae7ccb8eedb0aa711889f9b6192601163b271c90df5d69fef487b6c05a24fc667469cf16cbd5afd58fc830119fc9f61b26dd50a96ed84c96825a615a3aee84ea4c950152323b20884346b25c9e2a6be3a93505ba059fbb114c224bed8f05f54eab76b2c9c23a0fd942eef9696ff67484b542c8347f1b1fd7df7242872b3528c9e45030447b2bc85eaf191963291e4223b75778335e5f1256618ff87bbd68b5a9e5cbd2ca1dc8aff4625c834edf8fb0d879b1f75ba9b85895a6bb4d7569a41bb3be6cdd020065bcc69b44a8fa335d9418ea2d090d8061e042e8e1a6ac03a6d5525079f14274079734ed42c5c9ab9986f0fee6bc9ee6c485e233e9b4d6de70664902529a135a5675ae129353eb2c00b73f226e84fe8c594272d6eceaca28b6da30492c92074250ec80beddb7208f9b5418944305b0864009b3bbb3dfbfb4cc2bba3313f8f7c6c19860f1dc0f5d7aa06e3b551adfc63dddac980a79d72bd2225d54a87a93717291c7b78bdfc5521f7f3239d5564fe9c9559dfefe76b77efc2e75991f31a0134529a6611ab9ef076491f2d2d81ffc5774ba8f8009dd7e5881e09ddf5116fcb5a44e576aef6cea91ebf52c56c742049639392cfb8b280dc2229252e04d8d394ffafa539290acdd8118656e7e1a4f7bfc0bb689448379e8cedff7590a09a3f5a29bf819fd87297b96ca07431a29a07ae126eb9d65e21824c16707db89868e127f17614a536de6ed268b1600a8b02aac2bca54a09b7cccf8e184448df334f95b9f0221187d56da7bd422f09b4d94228098b563df53414a5a86728962a2ea63023d8c3f03847b36db7cd189ccfef3e623b14842b8cccb18b4f80f01b32a4cec48f3009b98ffa25dbad76089c8700e90848da74aeca81d01f4dab2b7e844a3e48bef21f33c92734b821ab382bdf6d0b1048a9866e676b78ac9398678ff626d5c173a15a0a7514b2544405dd54eccaa2791605c87d7117bc9f8c0ad84623a9d3a2b1733304b492d4dec38f7981db9361b03a2837a95fe937976c7f4341a802dbf583366fbe368a3af3f92618046bb55696cf7af1f465a5a57ec5908621f431ffc762f35abe892f772a60a3f75ad8401321f67981e90083fdd1cce40903ce56a629120d6e13c8871523c4d848664331966298c8b31a5bc8174a8c14f61cbe98ae7ee3e90bc832b04318864d19a9b8b6d49a260f42bb120cef9afbe704faecf0f428d917ead9f020f5e9d772bc8f29600f8a7623d8971c1e3c5f1a3b094191e497bd70f85de124137cc4b9fe0617cb73cd44b89aada072625e25976e7aaa5a8fe9d9e3f32db47d1565aaef0e84d256bfce6aedfa1a2dce5a94976a2bb9a0da95941fb7ed444990b0e0e87627e35f3235a998019650a5e5cae804ecab8cf729a5c712f1e7d17486082dd50cbeb2ee1b0be6a7bf08a66ab3cf1fe9f49c7083f5b8ad183f32fb35fb8a41230e4041bcf0e5ef54bc3d21ecc1fceb08d95d745a997e8f2fc3c0f6b1b6c1c02e03ff02ae0d879d13eedd42d9f9949ca7ebb785764162ceb6c6f9944dcb3927b2f4eab23ab566b2b2bcc0c7d77b82579e88203602264064ce98b5b1ed992c1bb13edce579ae7f5e11697b493749f308b33e47512533350df5c07c3dadff656197884f359cdfcb736d29231aea1524b56e06c92f5a98ea663543f67e44003f5b41907a951dd792468c84c5e0e1b46149a5c9751295e153990b78c0cc712889a21b299b0315150dc50aa3b4f7fb0079ddd39d263a754b1dcc595c76ea9fea6c120384afb38d4bd40491c4689b1afc9dd096dd0327c84802bda6bb6b7a8830bc6c06b308ae9665a8666a5551ec954eb72adb827ef38f036c51698a28c92dc1c9e25c267532da2c04c1bf27f5b683ac750c3ef53a8460dc186331549bf82868f9327422c09afe1cd15e161bc41a70cab2f973efcfc8f01a380b86a432e1ae540e09d404d93d22a20dd5f685a52f0acb863dadea236288b1714700f23d1c19e40e219e8ed21f6a393e541abba850ffbbd4030e5f6567b7202fb66d86cc2a0beabd495814f6a50690e8d74cb8b093e4d43261fff80e7a67ca06dfe808899cbef84c09ece01414baac740cbe4c656b17991868e2a136f4785a0de311aeb18cc95ed33fbece22aaed8cc1e47f58cf6c09a6f92c96f37d2d2485b369093506f5e9f8534f8569655277d0399ddd3d33861bd40c71ac53a44d1981cd744d79202322d47a0228356c0e27efa2ff1009cf2a416fb6e8844eb76b8077a4a3961ff193e1c95b222e72688ba48be82ec5da498e58861ea613782ed1ab50a95b5cc236834af98e61528ab18453c20ff978551b81e1bcc0ff4b7092bdd9ab0b946b7324b7361ef05e1f7d7f6a336281b4bb2c671a95a6ab84be6bef1b9c8c3d2536edb8d79b40637e16d7281ec5243016232d7c9fc07ed9dfcf555055d8ae65f12ad150da81f62f2e1e82b3adacf6d623ee4759ad61a09038905bcf1dbbab671dd28fc1d10a0b7eaaef73a5862ab449bd84c8698d061e79fbe52a86739ba945a01353e0f3916667bd7b4356cc65451c7003927f2aa738d98245760550156dda529be741ce3ae1afdea0de35ada26ac241fcb5d518e6ee7f9930baf88bacf8bdaccbecfdb920f3b26285439912a8902ae029b07f28c1dbcfde780cd2bee6c6e5f4520c5c7ff3ab5448ec86cfb270c39586f80041f3764b5dc77dc5ced0695c89671cf90ed34c4067b4bd938b1493c7902dd94be824810a00bbde4915d138fcc7584790bb0b6682fc0799cd415441ac90c1caa008c7fde3ab4a3aae478c64991ebe07e6c4587d3046c9ebb8e125e795f0be9266bcee5a4e4355a2830c5b34e583b0355b34b89c08011db6f6b8371de003074704e8cdda37ce42c7e395b6a37bae3dfbe67bcfd1f125c9a262d56883ddc028773988270aa30c6dd326cbffee589f38286533e1d5c9486011170be591beab5e0ce98837cf91f0a58d69d872e364aa88daf9cfa71bad167129420282d99ed5884a1276dfffb2c4100c74a8b863b063c07937f2e9c12523deac4ea16178863d975e3a5be5efb5ffbea994d07f7ddc5326bed1f5c9415c1d4ee1667e3a581499bb573595158636ad94d84f7c6e4b8efc2b141f2bfab7932a050fd88a8c7b21877cddd488543db5b11138cc808e1248b6e2ef492faa8a32f9d93e3c060b5cec10f03794248f9662ed8c283a8e0eb493824e2750ec75b3b1292d80ce002083a3c64cc487afc31b20f84a778f386b012ef7bef46e638d0f1cd75487ea46e05621d608482637b3e642a9a2c5371bead4386eff968b3e007fc263086d8a930dc76a8431a4e6907ae35c7b3291075d1c723f02e4895714803c0e97d65b04c0f27d01d5d68001bdb3bbd44dfee1eff1754fe8c182cd9bc6ee273beb2a444ca1766f747d86f36cd8cef6eb1dafe0c38b9327a8cac6e83e076099188f02721cc4de3d940c3ef19d9b067be07b890c798a79ee8c44d96c5e05ee5d5202d941a674378386233a83bc85134dc8c46a7531b2b952fb277d8089cfb13e882bcf7545f0605271fe38bf4754f98dfa13fe6b635a62bcf962553882a8f28a9a5fc0b3f85509b702d4a7555d40c4f7d10fbe80d48b4826995fda7d15f14aa9b95fc6526101cf09c97fd74baca6bd26b4fce8a57b0726e0f68118969ec067e9ca39b2ba59fb0d78eb5cec5b872613b1b76763b3217d859bd6d991bbb5448bd4e49dd6597ddec9e46afb3f71d254aba828c91de51904139ab19138e36e6996a207da80323d96077c97a3e8994296376d4dcb602f1e77371efe8b020b7b6f6f7bd2bd733ad9c06c45b77a2893d73b4a8a57707969af74ba06b2fe7d4079bcad1cfeb3689ab95c8b1215fe0a855eb431f67df4ea589dadbf055086924e42cb142c9031e25b81e8e1167a54008ba1ad7fec6794f203b27f3092dd72bb766c9653a72b2e25c965f53487cf3baf74eb7742702380303af8c0a61cca3eec78d4b709e35e2cc5bd586263d9f56fc12454547bc6165e3f070ce7b2bcace5c8cbf52f987568dd90237cf190dabd4ee7a80494692a5379b013611f4eebeef8e1ab9a9c5ba61926095545e19c3dd61b7b404230729aff7d82b6bbbed6b4a926f6e49189e3bccb578fcb3537951fe9c78ac842350ddd80133275ac0bce3a669183776fee8288f874d29190b452d65bb7d8edfedc6fa0ae147102b92041af6dd8a566932e016763b60a5b9b1e3667f228cab075f966d1c525ac19d12046c6409345799adfd7154b6d8b51eeb1eab3a132ac6a2e08acd1a34bbbbdd019195af9f8a93c6ed5463765173e669cb0d42b6cffee1a4b45987853d43c02f920819f45a4fe0905d8c65aca182b4bf56fa0dc51cb53c642fef003d92c13ef4bc1bac571cbe2ba3673a49694f6311b7dfc17a4069759177930b179748d4403c7259e10a5d221cd0a6b745966e598f894e607b779dd5289fbdae0b4348141ad373a62c76aa454b35b39a7be875598bb30007fc300606ee2537cfcd7c22b6149880fb3cd8eb53054d698a0d20f26a5c3ce468255737a68706784",
+	},
+	{
+		"5622aa8d2f308dd468a7e4959ccc01f0e80d91f79df65b8201eb44911f6abc758c6703bb97908fff377395d33f96c328a4541f414b7ac34c6607dd85729afbfe01feba988e4997c6bd2c99fcc35d2467b143a8fcbe6b49247226a9e4c0a4e3c1a29d5931e6f1f7a31d90a0e0edc4479f08ef9bc65ae4eacd0b93b1cb38948dda31e60b18d702bbf5935bd580201d1f280cbbee679fd834aa6be576a37a037eabe989c3c18c7fb61fda8b9ffaa8bf22b57a101c19e850c454353af7af3d755b26ff1ee78b9d9daa78294972d108958682a5a29c8ef260e2289ad9d7d74f32fd4e51e5d9ee828366abccd97dd56e035713a6f3a1985383c0ed5d98c4accac2fa1ba7d30a295670d5224952f7b7554fcbfb426c9496f054834dec48f9b70af3d2b1c6dcda1c4daf3e9601364e57851952c785e65d753be1c22729bbde33aeb1e4748dbe90da6ecf716f05bfc68ad819515dffafd33a909562b95140ecfff1d0747f8e0459fcd3ca6cd8893262614bb4bf4b639285f327e7ac782898781968ec98f6f0f2f3c4bc5f9c4691ffa7ddb3662816f8ad092095b598bd4d10d6b5fc6fabed619eb11dfd4d638f4c0b6cff7194156a411e8ad6d3229320336ad52fd9811c3a1fcd571d1bbbac67c6186737ac7ca1ed9b2bc46e4e578f81c164b09ae5cdd4059a2c22b5e7ce1dade684e49200867f9bb1430aff9b99805cfd31f7e3fecbe898f70a4eded86b8bbeef7050eff6cf8ba71395a7ae2e270a2b58010e56cdf6efc4003da3d8a82e96979ee68694b6113cc9a6e377d40a810063830eb95005a81405e5b7de8de67424845bab1911bc55da6338513742d237a555465fa54b07ba50ed712e7a57a39fdcfe4af50f064ae969823aa1c40cd86a621ec90769d0c1babd33e8388a8bd76689215b9827a5819127bb32ecc80a562a291f3192eff34cad2635e5b0c0bc174add72e2041864953f1fc72be7d28111fba0438d9036da3d5c0f220ccfde2319bb96fcbfae6055ed7f1c1967ee9a78e93bbb77cbf151084d602a5a2f087d49c3134582c1a5d7af24f4c88be26204cc9dbf4368b19470fef49a5823a2d66c65e9b1e8ab56bf5a7bb3220696840a6222caa58a7b39fb792d95d25038a8bd9d916e853cc5459640f8b8468e3d51f05f1b95e996cee40ffb7ae14cb289094f1b77d5573c1aee7c12a6c3a1e31491422f272cc5f510d4f18ab63d3c3f468c5abd61b2fa7ba0768d46392e2a4dc06c7ce79841dca916cd33cc0a700b50fc660e5d1808d8b87e65feb89428055495823b2dc317d6d9e50aa5ef7ab14076174ed32f56abe7d410e58ca40e92f8a31433d0d74ba7b130b1561f2b075fa11ead744d031f34d82f1a64d428f6cccb0a009be24b42937bf3e99a1ef1fabf0fa7335dab52918382abe756d3de229ee8223aca6d7c5de87047838e387d4e472481a4cfd4365256e13aacb518ce5300f18dcb5e0a28477a6fca08a74756ef6bd8933bacc98d02abc7ae60df7cb3e06d41abcc4bd313c543ddcdea2424d98ffc6dcaa83658aae11f5841ffd4f5df42368a0e815d2146a0fe138b223764b133d17cdb08d485e9f3dd2bf2b220d1f4565b02d7b9231d592130e4436849f49b1a70772244fc0c38da372a8c57fc80ad57828410a5a16ac6d14e093997fdd5b26e4cd4b248e0ea221715ae6e112e1b68b09f795540e31b1231244bc922207b906c4f42b5302dd7474286b653b4d1bb657134bab117d6c349fa0f121c2f8dac9cdcef510c1c28545eae0ab163db6cc84ca182feb858c10153d0136f00a01c9c7d0bed892715dd85c4e73627c3a2ef0f43710dfccacffd1d9f118c9fb1a83b2eb328b8da3e955f027d95294038184f7b895d77532c7570cb86fd6b37a5a66659cf1e330db3930f302838706050c0dcd91d532d49c89d144e9a7f864026ec99f50acc02bd5f11ee88495ee8991ec4723b189f84e03d992fd718b5173ea1b033ab7d3568dc4656648fb54d28d3119b0f293a930a772c394f45ee66838f17b73a94eca27033f9d5c2ae22eb813386905dc024673850a087958eed191d04d05798bcf909eff2deb2a0009d223323b290e3d6f71b2797a2bc2590d54294a5992d629336518514032614a04847c3fad8a7d1cfc2f86765b48cf58acf892f68b691fbece38100e6a71487ef5c4ae934f1ba03b4b26a1967f70ef1c697202e4eb22a3a95ab3b7b524f0241ab4d2adf3ee5e3f2974d0bfe4419ef0ab11039ffc26339570e74d260c4d5a16f22cb4f60b03253487f5e46c47836ce29460728086a615f78d631d89a06790928455889f58adc3d0a3a84ceb2ba9cdb00a403080e6567873b985fd59fd9dec71e375013c12c51cb67d599198f36f58fdaf897e85dfe6f9896cf6d35a84cfdc6834dd9447a2a10e1ffa9fa8edfef1db9e8b4a245b211de49e04b7e88977b4e1ac9285f43526f2452181ee0f80efeb1f6b2533b656519ae45652ccefca81c17714476b497e5d8e9fdf6c9f504c7a7fa7afa36df5f4f8da5b4b973b1618fc8d2d43e866b235e5420551d1659e5bd545fb78a3e17d9cbbc8e842f3fe6be07b892453ffd689d5188f26f9e4c545ba0b3132af12a03bce6914015d026d3d7df661c1e6384bbb50dae24abfa78079a2b1ac41c44c7d82a59183f293f12011e781d3cdca2f791afa5b55a9f2d6139587bfd74bfc54ce91e642847a33b48c1b366fd8f08f520b79ad5113a0273735aee71ceae361a97547fc09b22fbe4e4ae4ae13e52d65e0971341aab368d1e917c8f5f2ac57ac119f981b51b7c99ff2be3e16935b7c73e28fb58d332e6f2c36281228c479c4d6095cf15b14baeb0769191dfc649a70471a25d45d4433797a5b8ba31ff567e60ec4d759d99244d0fb5dfef7c2896809938ddde0d2015a4c5ce5ef6cdb5752da1c2a33e5bc78b6b7c6a5af892f0792c28560a357720da3cee3833bbeda8e98e6a8cccc6535831cfc28bc8557b4181a3978bd90eabb34b99eb7e55d9263e6790ca34561d8c87ec4e12b4a38df524318db00a9b5bbde6f5a8644a818a88e91b521d716fa9f95bf70b109b9905bfca926fd42ecb9114c039790abb0392a41ee4c190536a89ae6194befc2dc4bcf7562bcb84f65c99b69612c0511552f53436b6c489204d3881e1f67e0fba3a061165d2955c2e2e12c440d31556250a8a5cc04ee5e09b1d627c14e08bce1a92df7f6475db92a3ee57e4c16c3ae677c44237122818ad457a29595ab528744707f3ab7ccf3d20bd94047e013e647802a7af14cfc7c11441ea6e9b9f960fe69d03911ad2cf3a8f633e0d647c71dc7e188c92e75353fc953d6a30dd0040c39d4355b71524f1a4872fb1ecab22c8293b54bb22a80e1e3d4c886d2988adec26f041dd0565cfa9edfe5ad9aa7da1d3b8f68fda9e9df9dbe98148120af6ff30e6400deca6dc9593dbf06c856d0d582503e7ffa185f87c6e7ac58184bb80b4a1c0c18d669e23f9791365fe807356a5763ea418c39d94311759b29b14324fb6f3104359ae66532779b825f92b7c9ea2ba43ba7de04eaef7a86192bc93e17286f1b6e0a01c33c796ebed8f17692eb9237173a051c14e4869afda2643bb98c9ac4ea94c6bdc1401c80190df6abe988d2f0b2d80cc7bc8362ba25c6e5df4370a43e156aebd6aaf856b3f64d5fefc622d078faed40b760a361966a4765adb809dbcd74b7a41faffad3a64823860e5656874133c7f8a46b5a3ac591906359aa4f171ef6bb2ea6b5f24cfe25c2fc7c1973bd5d3bb5f197002c5ca1bccffb570f0265f5cd949c7386d961ac9c5e18b5d1d6030d8bf4a48c10f12dcdb11924b02b8ab5e91f425ca62bbe42b80c6b6dde3160ebbd55803966716734327058e29bd39874f2eac199067fdbbe8c372c5a688d3615e2b65f4937b67d6a26c64cc2a9e5379cc00925c678f174f538915f912e85b7014c064a73bcc7ddd38e1a9627ffddb4bfd6da764fdbfb45048c9495ab1a4cac5642f6c9ffbe97d33cb26964a23719620df3d85dcfc392c4502759fb31a6a797e99e51e94cf9bc79ac15de4e5cf7a05aeb88a8ab4c3b6f9c52b99794503f2c49cd7e230a67df7403e552523249f29d257b35c0c7712053c3d9eb583a1a7473d7f296d25a66566e4ba8b08de2a31b082e40c8e5b1e93985b324dded3f52511744e7e99f4e3ffd99d8ae17bb5122b37f637c5525558eab18a378f5e2cb56fa003ed3af8d139d16ec4b2ea79c415b0ba4d750ca2cdf653582ee3b65a9825fb9b123593e36e645232163cabda515b959ed0a1419e9894f6c677ac200fd11babe3503ec7bfa319f1b9559d94a6f82945c9ca8667621a5d28920949a1da644cbdb58b84742e9d65e7f2027b99fba4dec46f642bd17e88fa109143b26ba7fe285c89add0b74a369f3d381ad633bfb4f72e1822ff96aaf9a73b3c59a6e457cf40e17c1198c64737037f52d9b3118daa3fa5cd3e3c7738e3b3743c595893289974a4aa0d6bf1446e70964823a7d5cee67b9b25b7125d9ac5d1d61f2a6947c3deec6deb575e2fc5cec60df26de3c0545e5b79156dd6af33a78552d1ee9994cc8501b7dc5fe7a22eadaf201a92e06ef03be705a8bdb4db65392d3628c7cbf44cccac292c93cb5a407a7a5a0d5ac9fd95b0033d6eb719d3f14609190dd40d5aa1b983cd4c4e278cc8a1e7d5fbb0d39060d6cdce8de6a17e2dab973a7fa594205e17edab6514372eb51e03b0ced6402fac0efd3af49fb8214a505cc9f5f0ea5308d7fe6dec369ba154",
+		"9f522375925222a04f5c95ee14b6386412025903ecad0bc3ab78afe1145136b3a3592835ab4ad6faa66be9",
+		"d1ba82b3ced3e9817642aaacedf482e79bedd0560ef2754215ee792514bbf8e6",
+		"bb21211f342379370f2642d3",
+		"1a6683805d3f478ca1c1512b9846468378f83be27393db63956e151ec408368b47334afe610249182f54c4d0a01b704db2aa90a9755b8feb67ef9301f0715d7d6bdfa5cc4497cef1142a43eeb42f7c413e8f489af30d742a706d05a40a0c4a5991f9e2cc5d9fbca6ad3767682e20c146ac35aef38dfb2a77388b738fa022158d5c802e5f0761096bb45b50815ebf09172759521b5c5d459703ebe9ff669ee4d14a86e5d0650b597f4a082ba0aef366a924ea378b91c3262d99f48189eea19c76c0f644079f8415c11033cf24d30d6c149ab13ca5c29deafdc816e457257361c1af4b915da312d2e6c7fc712faa27be3e67c893f9005a0e2c28369991c1dab22d38961d1abd6d94c4d549cf491aa1f8d522be3ffa6d214825a5fde3c94c4e35c29b8d05b2627eb12c9d94f450a85eec6bc963a279a37c2344ca36eb604c4bd11c2bf2ecc0dc16c2c365bbbcad3541bd54f8d0bdbb3ca4a087b62fc19fcc1c13984eab807d2a6a1386643d90d412d027bcd0a638765498cdbb1f4cc1b91b69bd241eab3645f225ece85a56e5008d6094041f8cca6b9a0ae3b15585de6fe0695d79d348f8619431ece40e736957a7627224fe92bbe30df5124f476d97e36b5b08b3787e8e00f0c10013068eb156f82f3494a35d6edd5f7048d1e91954f1013ede22eca8b4ba41699ee08decedde87139180a567c6d169b672af0f12aa09ce20e9cac4e78b8067d31ba4f63606c00d1d787b868cf7643fbb170f8074667c9f7584d36af80b4e6557724013618c28d0dd40bfe9d4b25761b3c99558af528c2d290d04b09821bd7f992c044dd61dde9395bd0c9ddec6d0bf6e044ddf0b4b2d6753f5acf2e9c904caa4e9f310578527b85e6738803758da646919989f735b09c9a5744e63fed2c3982e59fd29d2baeb9771316bf8d29213a4956b66c78d5654436ffdd82d0d572530fd09507b988d13fd743f35333237681f8abbb301a8ea870159f802a57760659094d0e4902036c5a62c563f1fc86c4238e1ce89f5176ecaea194ca112fbdeefbef4fa7c203678cafd34486fe58b2af04f84a1cb620c6e123bfd96301e0a5e5e5abcc95d28b852d0cee2f51faa73e42f22fc335f50de4c3812ee14038633a195083f3944284c1086c34995832c3cceb7d385b4ce86af10685c16005495121105272d1d739c584a07ec7801c3667bb280987a8aa41f9537e9d1812a5dba5b385a0b71d2e9573c6f3e9ebf0bf7267528946a6aa6f43efce908d32525cdc3b825bb11c7239f1de412704d24c17455b9382fd6a873180f0d5d44dc449320973d5cd0d4e67e83946b6ef47e5fc3dabadd80751f1421404e56b1bce748b7bde63c6975ca81f3eaf52586a55242c9745dee3f7c796d4508e818eaa4fa50490c1a79624561b98d2e1139a328806414c905372356a22393ea0da51c83957029edd8c2dfcf46d9564264d74c1c0497034ec018b1dd4c14acebc34b6d2c1a616937c37b8b4a0ee5dcdf787a0de1173798ab929b72e0fa83a6c9b9a99d8024328d9c236a8f57550a4f83e8071eac76adb55939f85f5b5f514174b670a3e8dc2b54656f6201940a81fe4953d2680ae4ec58635ba74d15efab3e06dca6ac269711ef2d4dd49f731e24a92a3b935ebbb3fe8d001cd4062669ae4baa62c2947033afcfaca227d88a11769f87456d5cd1bb6606891e71d63aff9cd5a7d23263a78768ac2ac54ece1441fd37d096cd27e916e68891137fc3cca427febd1947cfb4d7ccfad75b2ec5e809c132111eadf25a73043d68333139bd2435de9941bbc61c5c509897cfc19a21645019eaaccb6d06371e3d0570c09c7556e41a727e44d9bd672fccd1f89cc7d58761c16df8fb75fb8a1dde2caaf088f02dad91b6489114398740e6798f3ea8c7b0cfd974e160a0106d703d9589ab09aae79108e3212f19cb950ea9c0798a1532bc2a065d5900a12054395c0545b0878ac0b1d461f553dccfc2a22bf254ced88dcb538e3889549960b77ba6237ab1458e158f4f46606372e797ec9d9ecc6534acaa1218e7540eef11030bb9c3e5a7816f3b33a590d970619bdd2dc04d5c6f4ec38b7cb4d525234b836eab57f65dd045e02367eede9049e219b8712b8d6fe178080c5f77b821f1a475259ae571a5578eb3b48863162d45486f71a28ecbcedb35b320e5b6401f9e7870aa5418449bf47502626e1f42abf481b48d5a6819c640bfdb64f873d583fc4e40187940a6c3373ea7b47195270a8657898f55568985018abcea9bce1c155d95b426f91a734b2a14ec2c7ca2011a4d30019fd9b3ef63a804e9c30c3de2651c4213e90285a4ba100b31ee402e8a7f23cf9d4dba003bbf982526bc63be5af102dca34e7d362d6fbf6f56046160d7af33b364f2a86074d1c0fdd54aae89b19480efde2a9caef9de7c0f9491e1cf43a48752cef405a0ff16b0fc67bbe433a3c1b9661406c3726092efdc076febd60c436476f24dab1b0b8f8893986d951ed72282990e8b1526f4dcf539b22c01c6a7eb5577cd540a16a81296ebeeb7ddda72e60fcf2840c5b42c5cba30eaea5402f267d1d04bc80da5ef0dd2bf3c7a2be986507617c9bdbc96c6273a0c9e586a0c48c98b4552113149c6f79557fc8ace0b1a512fec3aa09ef191f95c2163113ac5cdd940f0c2120509bc53c3ea493c54703effb902ef752c830c61e85636ca95429bf16937bf6786b3eae1b277bf08dcd69f521a0078d633beb33c9aa0cb33b238e1021ca67df122a403a3698452740bdcac81d22ccfe4ab5f835d1961708d1faf6d40f115f16c6094ea37a7ff15e0534f62c19a6f4ded0967be337cdbdd2a7c58ba16ba2e4c3686e9d075c6fa7d29b2a0335ab4940d2a95c4500295f4db84ae65e46c54b7300909cc5411c725a31fd962d239aa0e2007c285586b4c778e2ac7afec42cd8409a63d7cd9c677031f43f4aaf04258dcf1270c02a4764177aa66db2d8f860eeb1fd06d0b27587537410bcb641f90aaa7bfc6f12bd143f66e7c933a0f3ce6b5048913e1b2d79eaa6c19e7255d5eabd24d5f12426339541a22d600cdfd1781a1a3894740887840aa82e5a461fc324285b0223ac9b95c3eb88160353f168b3d4ae8a2e87b7715b5fd2671f66e6eaaf9365b3d9e3acd9a749faefba6009783771177aa4dc91f72fed7a5bf6b1b7738b84ac0a07b4a5a3f0a9134a39e1e7e3e2f9a92d5644295f31c5a356092bf07c709b4c34305ebf50e857a4f593dd1cce0439d3fd125c1ede1a48f583bbbe0eec7058345129ef78868a96f8a76ba7fbfd1c5eebf75f3e0eeeb9db87474b96f321b87fffc02433513fb467fb74e2fc8feb498d51530c753e9a173e95e0edc5ba9802641a45db281b2e2d87d409057b4fb1925e834e90fa5619ae3a9237d5b104e7ac67c2bdc31001eedb4ec7064b2f72e0379bf8780f67ec4b195db014a2d130e77b1778efe3dc703f1310a566a6d3b5c9b12b1d4e25815493ed1510a516a31ced3b64ca49a783ad63ea71a57290727fa31386d2fbfe41f12d36a618c6c28d8f10405eb3e0a33e8ac2e4133ba75c688c8c9a2bb33c8fa032eaf3ea0d2c27bf89269c4aec55f8232b292e7fa9fc24527184f19187d9d8a3f52335e2feb5dc6d997b9b773a79a31db832b752e5738963ee5d61a1b426414975693f986e165e52d46cb059fdd4f48f008e96d4c1a48306b7c002fd0c861721656074cf11173ca65cbdb694c79f58a3f3365e872b24670b691682c10261eb1ffb2b65da031d070e31542f49704b77970a78bcfb4c4ca517b4c966a4e8e27664704f633e90cb7d7917dc1d3a8b8b7fcf59ea3a8a81305761923cb182cebdd59255803a14ca8a75fd007670d79a25eacda1138d67a0fd1da981529dbf182fc4d7a700ba498e4476a1d415381c9e2ffa3bd46201cf2e454c4aaedbbe3893bb4121a6de02cbecc1f319155eb8c99d1030103bb6194bee51e74fa01f28dbe16092955b9599d5c1f1c3f356e26d48fcad7c4cdf0eef25c25273dd62171785c9d2c5a01b1f3da9b4786b1b399d890e2049b73c12de2fb7177f2bc3d9c645398111ebcfd83b73119897bb994f998f4a6fae1b3d6361e171059dba0bf9de9af7a5a1b21641790baf82a36278945d649cf5d310f3792fdefe8c58986a48118fd94647b786e47733ae703701e18992bc1b143b1da6110a98030bb9895c14d7b8eae1a155a550e219a5b6301b6d26d7956ecfe4c7023eec1ff62538b3606ebc7906a1243bf8357f593b6cfff32e3fc6b51f6a0ffaecb658d526f7a5e9faa6294e4808b779f4832318cc184e49e8957b72bea0d67366e040cf76a85889fc6b04e84afab0d02947d0d83e0de19f12966fa8372f6e82ff402bd7a69195eb1a7864a3375aa9e23736fa4d4b0224647e416474c01f72b7d4af240d7f43395b5b04c8fdef1165ce1d56ee8ba0e350e6ada893e0594facbfb5f0d8829ae203929525951584c21371b86deb0f76ef5daad5e847135a6488b35ea33e3a165fea502975d6421d4567a229bf3ce94605885453610eb9c82f9ea743bee9e14776bc3076a29af268cc72d9092a492d9ff08c345dc2eb2f8003b561d9912ae1198c58107f8b37a08b35075af9863110e6770425e9d59c2dfff9d9942c8bc3bf7904c2a952bcd573706caf1ee14420564ffc433c0f5871c4bda916f2530ac75819ade49fa1de21edacbbf6b7075dba21a84989411c566b7c356b81803c7215ab0f326a6b8910dbc62c1bee3af51f105fcdebc0dbc56a50b22cf81eda563bf8c2eff98b476e8",
+	},
+	{
+		"99444e82c6c4c47070b164f298ffdf6955ee5bcb3070b9aa95ce658db4db084d2056cfe61a93568b44ba7ddcba5d450f4ba0da7b119425a6628b3416663c638692326cacc5c237097db5e537122b465dcb21d8dcb5fe831789b72deff3907685c2e23187a56990221e755930a09f8d6cc065487563cb8cec82b9dc754952fa0b342c92d99522fbb39854e338f470a4b4d5ed2a39b8b6253b7001b0b953abc588d757616c7a5d1f12b1024aa572ef5a47dc8480943aa6cfaaa78064fb2b29830280e46efa418d0cf38f57980146f2482276c9b6b16f865b1606bf1131e894336979a163ba2e70adbdc746be0d38062fafcfe5603e6bbb55717b66a263fbd5cc7476302ea4a0dc6167221f745a26a309f5886934f4258965a0ef0803eaddd05e54008df8a0695a078b797be59f1eef95a658c99a7d52001d4108212ce5f18a39f1173291808c980b0513f1a531e03ad7380372b65572d3967af4c25fe54d99d664cb67e557fff05c12e10143c13b1bfa3e8db093ff832a7978ecd85d3971349e3c9b83939b73f0ad55f1f1162d0c106b99c0ff98442911bc15e9194f5b4ded97e9702b84e31b31380c224f392e5fa5c720a45f64cd7020e25a3931b5871e4c708e77f4729225aa9f48f9d876597d3e79219dddee0efdd16836021dbd21692dafe121217347cc128fc5eb051e6843978ae17478ef714957a84c74656ddd931cbeb43e32fb0a448acf2f90ee98d38522b4fa9aa36be4fa13306e799d4c0cb90ac0f73cbc018146d1b0d6bf48aa446a5e3e0502aae9fcbd196b36b6b7426fc10367febf687f05392fdcf878863de2e47be7e625d0e3e3e94e199f055c0fc65f76c41ede43231873ff10eb854dcd6ac9b550ee8533d16f81eb0e86471d4da69311c47255e78ac8e79ab36ce880d6b135279fbb5a712adc5c3862a356af49e9c10d5b16f4e5dedb80914868111e194745b802a0292c7c8564de28ba8e71a44f7eff6573e5434e65d496cde5b5e62cfa9e2e9ac85a164dbff5767983e71dd2661d37d9027a27674ebe3433731a606db88e0880e91ecea8134421962b3f68915c9f6a5e1992c56750f99bc313fb30cb89384c72571a1a6a5e3c01897b691bd70985352217fa8a67f3252a06205bd1a9931d1cea3736559572561fedbf3ac4c8bff9ebd7f3753ee69a69ecbac4be6357db7f4213b697a828edc716ac01da75c1d46098c7d5d6ae6f3f9a2903588c5b340c9d47c234efea21b700cdb8db4279afa2117677e824e627bf0f2b179c864ba823926a57825478395545f130886bdf2a7c55a2647a888c3998b750343d9cdc602e46b7b09a2fe9ef74db1ffc46fe27c254c927ce51b307e96a571da7f3f907223fbed2daedbcc96197e95edde7859f3b4ec6099f791089e368a68a5ba0917ddf4f50b93c0c839ea36cfc8053811f8fcfe6986e5fa9f743119ecd6c3e5fea1dae3ad7eb465a89e9c68569190688a8d56e4143ceea3b11fbd9de67173d5134ec8b0bd7d16560ba2be52345ebacedc01a2e03e8183ef91317d87b2e15cc6301586ed829d438e4ff1d074408b332c8ce60ccb6790ab08c228807509dd4b39f2c227755f6b039f5cd413ad6f46c9ec2cc6a79457529d297b1d9e74ead9bedd9bd652fb31568a8e2a9e2b89e4e57601bc1d960360232cdb30cb502b950ef930d54c2c0692a684cd44b0472995bd2b41dac1553ae47216253d6640d2653a033a862f3118c5b5d60a662d240bda5f4da51092eff514f61a425c5b14b19517ec1b371d240cc30a0739273b34f18a72a69b1586802a7caa6cc8f5817a8a995695d063c9dd26c3d45feb0f84dc8a0773151cf9a537664f942f351599cfbee0558f441f5c7ad320cabe305f9aba570ddf6407749b6db42f9ce94526a8f4170e735b1dcfc5f0e090af10e039db3747aa9b4f1f26acc34639ac8b60557f7753e2c261a29852932901a4093b7f307319cbb228e26eec289898b3f8ee236032163293b8caf64be3f7ffed236f1da688d958a1bbb79dd45026884904bbb936c1ebca7aa6b0c68aa8b667dc1575729e4ecb4ffa82ddced2f4571bf902c52fc4a0ea3f47aaf5c243ac2a1fc19f825fde5d9fc8d06d97a351eebf4ae1846aa62554d57cffdb3f3377695338f8d598d723289ff3962796e8065632e7da9d8dffe2636cd23eac15a60568eefe3e77c561906555268cfc1e9342417b1cdf090cc16c79939b15a9311b0210094087dea22833f74eb0e35d44259ecf327dc84f3f24b8c2bfce7be0d97e00d2be88a150a0d557ff963b4cda60eb99935951d288768b4b2649b717133517f5e3909744417c9c3102c77ddd285976cba2c89e2b4f297665632d7c8652847c4625038a6670169772de0550066ec6c2018f503cce79a333ecc0a0632334df6959d2e3b052fa47c5c84d15ceabdc80bd6be0ea2a5a8d5e374e0e9a613369ca8d4cae3d9f98755560b27b2f6e47b01ba390f5ddeb732c22b12abd225e26ecdb639b08f3237e488430b3b39f0b63aaaef4907cd003a8f2b4c3bfd721d6c3fd3a5f062d72746606a529ba34251ddec4026f40d262e9d527ad84fecf5bb2cc8601c2a38437098aec2335104842ff1c455e5d17c136ece8d461d7a3bd9a60339c22d71059e09b3603c0565c0345684893b56054ec4d3db0bf15546cafb4a03bd7775c3157e7676bb7bdb7baf3100396c563eba1a12952503eb6ccde6b6d0a42d456743c4ddb97f5994fa08c5fa41315080eb6b928090956bfc6252b232f6e0785d233c3adcbb9370b59c35b0dd66005d516befd1fc843df8e68fab19858b91e2aecd1c8a88b0fa3d4c2fed2995ee87e65976b755fbf44ee183f9fa08848bea325807bce0b7b61e03e50b2c7af9b360532a17a8250cf6068fef0198738c82a5e58961c54017e343fcef7076e823d63b4deee472fada7989ca7a213d06a4e3eb2d44b16e5c94b1588321cf6c45a5a792938b058d667e1730f8386dfedc50ea0a959b78f12f2949b34b181f90bec622515227dfb8a5f6e89d2e559c0ba686153b218d2c50b67503018e22914ce9b49d3bdb7cf38172db1ea130baacd640c111614e3db204b3b50641d8978dc14b2afc27a7efa819cac6bafa8166d1c127e2237520d57ad38a80146217a12363cb1f8a720e328cd8f846d379ada43bd4865e4aa633c479bd448d205b2e43befa63486c717af84a733f1dececc127c047850aeeb8ce677612f5966e23d92c1d3c758aaeef82f862c1154fadd6766e1dfc780bb447732a5968c0c78b9af4a9d669338458b57cbb77910a24678092857c0b903152035bab6b1c73f7b667a08cd0d31128888de3ff1fed24866eb60beac19c1b139f77bf0b9332024999a2d56975e691fd7475fd93622119d0d725bb99c1d6ac604d6b6be09d6d29360fff9f84e5318259a67fec08a006d9772b9410ec6abd4cb828b898c625c2fc35c19cb9a6cd3b0073baec7b5af254d21de8e209539f560bc80ea38e33658a68262622cdf35dcd6618b9e272ac3644c91f27d372c6297d8e37201c6a86a7d3accdf579c15246276a0009ddac4021755f4848d10f714e9da86eba13f461e6a12edb1aef2d6117986120750d609682bfdfcb90ee3cde8be54d45f841a6dee2d5b9fdc4e65edb7ebffcf3cc5c8a4e1c6919ac57568be23bd8283319ce11fca3caf968b057432f163f22e29cac30b8154a646ca0ef4fdbc7770ee1451fdde9e9d651992d94c843d4eb2570975528ad9f8c193f7c681a43df28242547010e30d75fca04f39247c77d6c3715c25fc261ecdba16844bbab23e4d0482bd1565ca9b526ada9b8f5703661a84b23070d85f3e8265b2ce10750c5d798f1a8ef4d51a473ff4d2bf4be615566ac796db9fe61a224bcce05c31ecb9ab7bc43a609944a7c9398a7875609ddbcb556296f548a117847df7d0afe48a5b504e85b0d7ca589103d3197933a744fefca795e1e036f964a4f14554d5cfa0261e25d6e5e02f86e402906d3637a2352459cb1639f20faea6f0e3fbc6a39becb1b1b3a791e32e85e5bee31be685410adf0c11190e20b7a5119b90e83f2cc4f0de8898606bb6e64165c95d4c5eae472daa6836a888ee4d9a79de72b8fb47a9c9c0323a2be9106d4ee9ba8b3858c256032a9caba37af94df4c7b0adc2f8478cb879b6d452d73191b0fc1ce944df3f4809cbf3ad46eceb3ba4abd9679410f45c8aab20dd72626f235e7c0c934b4beb4507def24ebbdd7a507943c81d54bc69df578aacd9ed0bfd3b7809dec345ba084d88fa9c34d80685415a4d5eaef9b88e51432b2b2037186baf123a6257e47aa56d6531923d38178e8264dd315e95bfafd8dacaf901e354b0f58f135d638df2c0f32453205c7aaeeedf8c102e11cfddea9a98d3ac7c385d71b760cf2afeb1ebe1d64f0222b9b101893d11a74ed175297c1dfd188a2565fbecc6bb07b56ce3973322a965dc5a675587890cc65a71efc68fdcdf1a023505ef0bc0e6b12dca5860fcf1c6c94c2e2ec3a72b8a019d69c82d36a73738dc3d17d7fdfe992bc8e18cb5d3437f1f619dd318b95d1a56b6d273ed79ab2655d83e2dd63cb6f1f5987eab6bb21a7b13b84e2c619b36b842192c3f82c755d8af840675b0bd67a655d641b1886c3c9c147ac87615ff3e58085a879b21dd63c1616a3712279ec87d650a2eed665b797ad631f0ec312f343979cbc49b99385cfa92841cba12d52777df565545a1deb07800a15431c0987b4a543fd5ed6832e80ab6f4b4d9c9ec419932a6ded4759f5c7630a0b80139234b8d53117acb4452c60b477ad50157169a89bd796e2308baa9395b513a94747611c7978c82dbdf48d716c3ac181ac2b2a4702c02a324bd4c5e089d989d020ebec9963b5c721a95492158f54973b7fc1828181acb3cc8078ac095136d97221c60b847bd2a52427383ab68cd1f10b92738c13203fdfa0b78baa09c1837be2498667c459",
+		"0ce980442336d0f427db869a6799baa6785b5e030567c588e2a7d2680e96c11b7f415fa27730969e0b1c3973b5f3192d4e773153def6dcc09dae29ac44eac7c42c2666a356fd4262197bd5cf6eeefcbd662d104423ec05c19a2e6ddf1834a3445a09e8b1062a1320a5e8ef13d6ebd03c19e1813ccd86fd68b46a",
+		"1ac8a509db7bf4acb80d8d394a5abf47c273b2093f50f35049e749f3e16cb0fb",
+		"47cc9eea11f9f3f9aafa23bd",
+		"088888333340b3a057b05491fb2402301c8654948aa6d5ee1ec75eb045858c22056fef0873d6675f897126052923a47a30675b266ffb6181cbd29ce2da3720e36a227e4c6e53328d789913c0d9cd149a6e49293996b1be7d6c513b24d876445a950e723ade3efc36907c840b9b8cfdb1503811b4044d931a0009b381fd60a5bf1e73d16348cb57eea672709875fb9d56908dbc729d5d7d322a17a41d0f62c9af9a013ab1e19fb7b6c6e7fa0c0b18bec5e3d3e92546c77e3753193389e5fcdb6a6a1896cba461343e71ef7a156b136b27ae6f45be9368301cfade203e9b53824d70f07de9abfea1968b8ff8489b9804422ba05ac3c3adf23ba0848817fa51febab5e9b5500100310479e710b663f064c1ef101c9a5320367cd8bc6e52081a32f070e7d3fd6f4210cdffdb9fcab1de4af5b06a7c6d191dcc12b25b3053e58952bfd1f723afbf570796946c1df9579ad14ea9c8c30389c1de4d1e845c764fec5eb8faaf4c558c5eb5113018c6a21ef653ac7d7f5b6c7e1a8fd48c6f423e9913436202da176a86731287db7331db055508acc94168888040ee37b3c119c8a0d88360241d68745825fe480324a944d56e7cd0375d4d33a5fe7a3863c2aaa899b2d24f65b70bd804039116fe959c32442c9f0b5470463523eb4336985b71125fe5235cbca0c88a6f92416d038e144de5ff8ef6ca749a9e239f02db505bff8e16fad1cba8b1500445f067a674142b6413e9dc0f432242d8301879bfc11fa86d1ac9992ab12319fea8b703e10a13bfd4b017496222be26b56af3ef67610f904f0ca8a3e7cc249ca8122735a542b289f13922904ff23dd197f8883c7ac77150d7331316ef94e0cf13b6ad95070420513599100b0a6d117640b781c622ed7ef7ead29476b3c835bd9dbda2203930bcee7ac01c3b9c89da405ee436ee652ddcc3e96c7f1a94e200eec9a4a226f3cf7ae5725068916e73b61149497d11dd85157f895669f51978d1bea8fd2afabb18d082365daba2682ef623109988b7d0e27ae57bc14d86603f93b5ac040ae52d8db404ee27e6c34cd4246f40eccf9d3f8637a4615a4006918b01d34709bcbebd02ea72958d54db3e87d69e6d783de2f1841029d6975eb11f9b076c247108797d5368c656f888092b82aa81aa26e164e038b359bd68801c22fc107e4083a9d85fc254b002ece9d4545310b0cb22ec1af04a7ee31d210ede4b605dbdbcb70e4301989422ef46edf63f9c96de9cb3f70638b51df5c0abe79b7af8cd97148f2b7bf394bea0f7bbbf6925f83b901b87a6079f2c3b38a98fe1a86dc7f48bf97553701834f557451df4b41e7db984a34432823585380b45c1b84813d6aa21107cae252923fb4673cf660a541e65610ac0127d238285f53bf329b62169f3e42d5efe268dea62578e97da59a58a1314a1bd46cf7a7cae772814130b51411082e30062fdbda1c9e14d6b2bfff89d0379d32461f3b8e833b105f6a89532ae748b5fb43f283fc86450404e8befb8442b65e338aa0408303a70e9c27a1d923d9f2a06e7c6159c50bf2e3ba5b035420ecbd9d0b5fae478eb1ab72fa714f99d00188bb10e60380fa3a3a318c2d359ea3805c2fa0dde17ee52a504f70d6b466bd38d1dd4196be336a9ab4a9e573d1bc6404018a119f688c1dc2a8ed1433e8a8ebf455ce3808c245f0220f0c12d28c771757763bd111ab829294e2429a6f7a59858dfa1fe0b806e986d40aaff934589fefd75ab91097a979f26bc9352267efb2d82c4738e4e6c451b0d5adc398f546c646b9e6b8fc84e91651a1252d5b805a857c7798d102d1e6f90749252bc53588348ecec0897c79f514442fe3b27608c95d0cba999a7e0fbd7f601689b4dc63ecb9ff553ff12eca3e9b26e3eccbde28770bb6aff7c864ad6be77fc09f81f90df6efd0c4025d0916ab5197ab846dfe6121c462761d9cc87112ebbca197b0a222fd34a15b824b7eda06a56a6ffda760fae5f0b527e2798f01e205a3f47947a4bd190f6abfb1dab2e3a53131af95d593bb57e4f4af506440cf20636d9fccc449d9565bf43dec8b6877337ca5a43900c1dc600c877b290342914e909aad8c5f0755bc25652781535c057ed5ab2ff8ad4322a8edf3fc1b5311dae6361a7395919725f4cd87ce0ccba37c64eb3618f9c5a53644ada569b90cd07184fc048f1b589eb29852909e75e7116ef96a268ea85c2bd257cefdde9222d7eda875a2a3abcd3a02a1fb470ba967b20beb54914b8b0c6ed464ba978088d7f8b30d098966b0bde82a8f1210f5d0c3405c9bc73f703134d0b6ee13326f65fa0b8154f4e30808997d4afbd060285942ca1dededc3410a099881492b5730ab7bdc2a4cfd0068f67766d60b5d4945f121459d2083334ac878d067bef644b9ee427bbbd6c9351d7b019bfc051c05ac301ff3792a1c687546dbf6a07a0cf56717374bfa1191c22b7753f6ae02392f8aac9207d1ad0fcd57c5c8b35817574b7dd90a00cab75f508f8a234eabce6618305f94746cb6a8573389d336bb67e1b0d2b6e9bd3959ef344e1eb245b522c35222813b8c6e82df48987436b5592025e9786ca63b6d1a064223bfacf59ada713c2a3116611393aa8446ea79b3cb21e96d13b659ada2d6524686fd46ec66c1b4d8f5ae7831840c9e3db64d528f83a1cef1e0a586a783f8306cb261ed9c2905493e74d35883fcb39cfc5745c282104cc3ce804999231d13e1bc6f2c022f05999fb57575bbdaf00d7a990e17dd2f8b9dfe66a637b42f58ee49ba60f2dd9718d09d7025b6061b2087bc35f0a8c884f5b67a5e18c2b4e857d3b48b79dc7cab6b72f572d22987566238a7153ed6264578424f1ce091fd05b7f14563fe12c76104d3373367af3ed3aca694a21127b5912c0b7eb1ddf9d4a9f03f660d49f7a7f0fb42797fd112414c3eba2b75a04282dcb9645191fd3dbe376e7f60ab40bb7ca1e991053a1912854a68d7dcf854201d1f2c26c6cfaea32e29d80847e6288274713d2ca973b91dab97884326b280c6f06c65b8fd25d314be29139961051a1d8699467d02b67991baabc9b05629660c243ca3b0477362d5e6bf9eaa33beeb52cf399846c77fcae11a89cbfdb2058e443ddd44fe202a3ba5c2efce937d78b9639781b8b2b99077b433189cf3b0733ed73b59bb194c9a98c5aa0cba6e71d1c5522f193defb9e31fd2cd60f22bedaf7008c2fb0b55a8dd52731dfa2bc69b40f835ae95db040cda6a4a1588a5ba4769edfeb7369c1e9a3b1cda293255b4942881d94d771b7b82460004875e71be64c582f2830c5e80dd6de421a311c5852f4912bea1451b0328d01c7029867cf9af99284cdfc1e1f0aa0d8c19ba9bc035dc270b45724247137da5d3fc4daa09e7014fe1439889968eb23fe124f067825d5f7b304f17a983580e009e0e51630ea0006dbc74a30b512cd9eb4d0b315a0ffdbfb581609ea9661b0007cd234ce43c17c92269a7519bfe99c2ca94b5cd3e7654946e67b37d4270a369266db6804336a446022677a024d44cc02cb04108292dc12f790578a0d61cb6fada738902eed3afdf1850bafcb279f18b5798d7466752c6368a594533baff5dbd17974638ecc41753b184845206c79bbab84dfef148eb7f1390f8cb7346a14c88caf540c241cad11ce8869be3bec85d029ef490fc5edacf94fa962be39a33c8efefcbb6b43960d5bc35f8fb72038af3801466aed141b50e9ac7dcf1921f7a6abaf320ff02ac34bbfac265e05e27495e6e027e673a48a874e6f0c33827a050fa21c2efa789c1e3df2ecda95fc52ca7be35dbf17ff6c73f37cb236e5131542e002913d177ffb21ac450e2542e24b894650007c36c52d90f83731009a7c3239ccf11829cf0fb6510d9924e927f14d6a06f8dc772fc9b028a8bbd2d3388985f3e2609abbd08434c46642b97240c9380a831bbafdc5db77be63a1400cc9a4f7362a689b07a77162022c6ba7a1bb9f0446a0b6b460ebdd9111132694fa5f1b29da39be66c5179849ae9720b2da0a012d4bdfd1b18b8fbef0d5c32b92c351dcf2c599f069c3b53f622fc8e904f27584b2d97d43f779abcde6dc1413c0a677dd187b28cfbcf7fa6316f0967b53977432d45944ce8ebd2e265c0bf6b2870c75ae808fed52aa35421ef55667ecd6f9d279c9b91c9314bd9411bce267d6ad52b1d910b3e65147c3eb6021a0af98707408e66bb11ca5abf5e34b2bc85b144fd06ea56f5d7f8939fe0cfa4862e7f306de069cf85f4aa7aa97c6848594f5a6dbcc718d2af77497f4b9d5ffa217fc301127071e9bc9c2c9222ba90e286506e384f321e622f05d81c114953d0f7e9626b74f4a6bea8cfb86ceb4575e5cf4fb84e9efac8291d1f4153ad3cd9a34ce0ffcfbe30b6829c0f986a4f85d63b602ab99ff3934b1e0c46e55d56eb479b79ca0729beb59aed783e9a3ccd55db8d884733dbd93f9fd7a7209fb92fcc49826b2d4356ca676f01b0981637897b3d2f90f37bfd73b214a398a8e4e2f9e5abec01d8192ca690191255dd8304a2d95a69331288bce00385f462e942f4d694dc3560a263c8ac2b5cd1d2c63b90ec67c32eaf5bd947bd8ac730da9c09ebc6888b0b4f3bead157aa9d31c2802df8ff0e4d69b7abfed6f184bf35a16ffb5677ddfc4682322128932d57fe4c32f21e190e1147d8e673ae407b1dbbca31331310b299e9f3db08ebfd2dad3158562c2e47addcbcc831cef0194ac8ba9778d0103c2955c886d439967bf788eae688f2a7459b0ef3bd16808e8d768b8962a24588d918ceb2cd1cd611b504019f65216beca212f44600cb7fac77216b7645c49f18064a3acdc01399315084dc9ea151ee28534fb31628d190bc540ac6b6aba572ba51aee89544015e6fbca2b3c2330f2ac1f68849e99e1a1f7f523599eaee22720392ea52259e26f1101614d4edae481b3783af4e99082d75dcca549049290731bbadd1ec0a93789ad5c9afe8bae44e35b3e59e562362964",
+	},
+	{
+		"0410d1f8bc890649c250a3819766f4496f339a6384e34acdd72b3a87266edd2a7eae223a372883f978277a108d6e59fca1f35f25d7a9f3aed42d35fa9b12241ac04754f76fd8f0e8ff6af88cd851887a45e89f1c9192ca66bfff605b128575d2ccc9ca3ba1ba23a0251b2cfd6db577b29d17ce2ea998946997f5c4a97a397c46024681a400a54425c071232d269adfc3b1adf15b4586c4dd7b8886f5c1023bc348bc674961ac6e221d914f432c2f06dddcf738227dfcfff88485ed45882809d0e57019461c88683919b87c45e78223c37a5be5f758e4f0dc6add22f2062bc2eb9bdc31b8649af17d526ec339f0e6fc6a41e26299c65276302f982235c3e5205ec1521625ec08a23e766577664b73d18d5533261c859c4cb4346feaf7540a56155c6c3a4874dc86ea42fd518d71221ac65541e2dadd2f8e129e7809f2835f07dfcc4128401dae2b5fac7ced1d9e07e3f348c6cd26f55b3893d4418557a18c366dcd5eadea0dd84ab95437d6f23eb9e5877fb2ad740ee507e2268c39c7186f34e5cee2d0dbba1a940f516a018f23e716a399c317a7a81f89cfabc296c432cba900ad79db67936f76e4d97874fc5f8a9ff84eb7a0f6d629c581ec5c451e27ef1ed468f93bfc68b2e0412a543d89dfdd812d9421236a4be9eb374531556c207340886c7b84d42d651557b952e0982f62c5c383e92dced21905174a5a836acdc3f2393e770d6cdc22c39575a42ea406f36889dc9558aeae5dc5f8b84862850b55bf4accccb6a8ef793d641d6b08235f70ad3b0605eab462afad1af80fa003645f4d302b03d81a7d167e9a8187bee0f76b1cfd7006b2d2b55fedad6e8db1d3ecfe031702dc327ff2b0197337d7542f42702cb276de852b3d72d9acff8a7feb8882028a5e340950e523c41cfa184b3d8878effe56742994e60240e58cbfd01541d39fa007a9f0ecccb409c6cc540354ccf35223677cb74e7ef7330bb60420f7d7bf97de6888cb343cd4fb0928fe5df5f1b018592ccfa7aac6dab57cded573b5950b94fd935f32cf332dd85b2b36501de6687612371dbcfdf77279d647ed8bdcf81fda8b7e0c5ab139330d64695d814fc6f761fd141dfb0c8f74e2d7616db3598d8de40b993fbdd272ca37db27b82aedb08bebc4a8e6d0385ab20fbc20c215ad50fab8e93975bcab3ff38667abb0545b3b3f20e325f01b80a32a3cc3ed51703d4b2826849ee22fddd5b544816599dca0d8fc84feed9f7e90caba53b70bc3f457eb1adb89fd0b67d2c0ab53264430c61d2c4a1b19ea99a9b453fc6b5ebf5fb5ab799134769c9b495c479c828bcc49a8f993c3127d5cbc31afb89c0e78fbc323755457ebf0f3344d3ad1cfc59d186e96ac31a9298e655b3d1df74b95f30fb868631053540388a13d597002f689708d35a2365e309bb96db8b1b94ea4c8060c2b165f7f19e72056409159371ac9c44f6bfaad9b9567094d18c29bbc8aa2c8b5b82735d20f55284fe68186004b4a4fb644fd52d9645b277c1dc238a764005c1d2791ef36e71786cd990ccee4571d9a9b1aec757e479cfa645e320bc33268e05af9cf90e0e616ae7f237c637a99fe15b4ea8a3232262d96855fa248920a28ec03f77ce4dd93925db60ec030a7be455ba9d08edbf6bb717b1a13c3ac1deb9821e21505c0a8971d5ea5dd8e4c9cd3a845a336209af191150ba5d9b8c2c450e3a765e8670d7f846b2461f971fdcd1942704f620a40f4204b99f9035bbd543f64b927cbc7a74f32cbb12c3caef955f169a45374e4479430e08d333c4a877baf41a27a0849ca3a157b6651295fa71ac94b6e3d30b5d160965e93d2a81b4d575cefd264399c9e4e17059f4064465b2d92c96ac27e3b221499b5e642d033992c236b905c072faa1e34495f9890bac6228330e4016c061605bbfc478c30e1b8534c49af54785972aca2d144328b0a540e3b3810a73e26acfa22f48652d53ea521875475ffade8ab50b9f08245fad753350f63dc4e898948ac7dcefe520ca47394f8e993a6d13ff68a2f78cf294f235f5f863bad10c4f5bc41c3ba93cf5e076357f0f7fdc136f34b656b1b8ebb3eed1ac429c7d4edbc902f7f4bc24ea9c9b200b9a9fd7adff0c6445ce1d2171fc031e3e9f8b8d6b448053393c8813d91333d4bdc3bc5bb2b8bff876cd29e8b92cf6f7bc727517b6f57ae031f3040b0637dfb40b8c1fbe44cfb6bb9cd0a445fd9b3daa1da2b1c4a82cb4da1fb8d525e0a4d9ec30e9aa75b951214621c58c1f60c9b97e6c6b330497e7dea790a3cd8158a76d898107ff3a5910707ae60c8a46c633b522aee83736d005de60b9abe202435f8bc4577b0eb08b7f2b617bb5a831e95d6488459bbf15919d764b39684d7cb7c9310f343fbfcfbeeb212a90d96c7a26c1026c5cb171ee4ef839785076e5084026077455c73404a2653f333e9bad555cafc1a9613387a02bb1287c380d7478238bec8943208de585bd18b448b6099565cb3ec70ec6672a778fa6af9d1b17b0970439da24c7bfaa74c85ecd8e5852e42391ab2258024ccf91e37f2f0e86df958b197fafd12f4a45f7990375f1665a14f7f5374ff7740f89677ea8660587fb80916b30629a7aa88213bbf80512421a0a37414a2eb549b81cc85072cdd87e4e69d97ecc63f974e60d20de0233101c3d475d777602b12e2f797e9237570085b0e9f48d4dedf233eb1301ed4621f9736946eadf599bfd79157c0b4cc31bc273f5c6f133a4e3679ff6797d3c9b76aff4bd8ad40726c1703c3d8b78f0974b748d0265b0a75928374f91b48c2d2b2c11d8b6e5efddb75009e4db72e562be59efb0bfa06808c89f585a43d4776ef08947a77f277526777f0b52f1e0b5a03aa560fa45c8f30e584b58ac1fc00b104942b7b86a3cdee1abea349dcaea4e058faeffc567e2c3b03e1c5c4ddc675e25aa15de1442bcf5ee972a8c5204ca5794694759c13a2d716839dda61635043bdf1a09e35cb6d93b4df3b7a00871f79cdb4ee69c79041dd14deb7754107b8fef8589d2d240ac1d8eafc52ea847263512651bbede2fccaf6da816b1b892319817bb6af9fc17078ab6cca95f03cf8426249fd4f2bf91921d39b8cee24af07a52bbe54ca7fc4422a310dbf2149b763ac0060fb2c59154d2cb0da1ad4892279b4e0ce7f5f92c189c3ce48e518ff48c4ffa9bf2b02d4792f84534958dc6bd2914ba010aa32d133f6a07bdbb87a237c7acc3ba5cf101efe947147ed4eb3bfdffe5fefa991c0dc8760586218d286944c52d0f221e0101f74826761d01a20af187f9ec1115e9e98bff6fbd7c8816c15d33c07f51c171490997bf269951218ae92b66fa3150d3bd40336abccb717e18b53e8806fff94009910f202a5041b5396d1c339e6d075bad4ab66a0637d81eed1696e4068024001123204b8371f0bcdf0ce07d79f7c917327f7138a75947846fde68665e9c767fbf96bb3308abffe7a8d05512c81e39fa8dab2334f46ab9543921ca97be31076dc7b2a0d05e90b7f7610d1a391b442398ef56cde3b18737faa8f282572389b4fb3c55cb8ae6737257708c808bc0a414bffae293bc69cba702ce2959e1a30edcdf64985a4b0bcc927c5912f819c71cc9b1ff5d6e5929055be72ea5c8c1a4a591093deb5449b7e6b60109be1ac0cae472ba31e1035ae65f3214f50ad699a077a2de52f7180addde0bd78c2698470b1af13cfbf497d243c9e738c4cdc265356543885c5b933a299f01a5b5a9ecb0b4ddfda0c28573064f6a3f142801795d66bcd5c31868fd3207fee7bd98c47e4da26bee64e1617b20cbaa34e3abbe31126b06d5737fc2b577b19d255a519397f3ff8668d0e7d401a37e368729e4b83c5fbf01c32ec478967605cbc0675f685b5eeeb42fc688216a0667e1204c995c9c485e6f7712d80d88edc9594528b1907790549756dcc8b0d32091f36d2b4009639e68daa130e83a1ea18353ca34f431c548d91c1591ccf8b25eec1f7a3c18ddca71b87bb290a5c13229250c5e193e1352072f6798ec504b3b4c6aa578737332f52baea7bc4468fe6d8dfabb9728cee93fee50c8caa113f5ed7e9b55e21e98d73a377ef68be7e4e965dfa50cf863e6285236f11ce80512c573ae2b55bcb43cf6ebabed6783c250f991f5f68a59dcb2ac13a3c8fba8dbb11c79dc6236809f2d7c4b0ad3cecd24b85f1aaed9748b8c109f2fd98ac8a53bd52f18475598d67305117de8e03b0d988a2847539cc2efad520f86dcd82c08ad4b10e490b9cb03bedc7197bcaca55526cd9c8a5a5f69f7a1697e7e31aa76eee597c386418e89f06b0b9817a83d6cdefaf9594548b33cea1cbb585e55df3d3b66f0b1a88f4b98ea4720f1ef5e6ebe4958078ea0bacb8ad776e325ccb252f81943b9b1c2f54aad3c7baf1bca0dda1355d191f69c5d8163c464898116dc89201032d1e3281c8054882f60522d3a65831bf779a854fb0c195f85aa66522386625658457e74d5c2fcf5234f226da4a579ac1f11f11a1e0a6993a4dfe5c856481ebe9d8d2363401058736f7ad104104aa03f5c91496aaba2fe4072d418d91c2787a9b4ab0cf4bb65681ad0392ef073cf2fc060692b0c0c194c8eed5558098cdfa3317ab02626159e40e5c76fd64b2ef60b8f5f368b6b4fd7ea3d2d3236aa01d9db7c8a01929f9fd38557335b926251ade1a0d47d0c1444e6416218781c1a51e786dbe9297b78fcf0d0304c62929e00744ed4e14af926313a9849b2a464048bead075044bee013cbe318920c4172138560629a0ff4fd229d81bdc7c7fd1086ab17d6efd5b603a1991b33a55ca5b9e2051b7c140f7937adfaf474c2f284489d9b1e8c71d58f126eaa451407eacde9f0e86504f7de3ba4d830199a229de2bf39014baad6dbbc448501588ceb2575db0ddae005b81ba9914bc22b6d600e2c990f7843e553ff29d8008265eba7dac7b5b5a7ba6dc263fe0e262a7b8638a81f4720622c7361554b61d7b04c7f8b133440baeead7d51ac8b77d606fd0eae1c55ce7e8141dfd68d40ae3d8d2dc8a061085b4fb6d8a06263183869154618329be6b01c2890f2b5d0a0f25dcdbbfe2ec3597d79311edb943613fd4b59157df4fc2e1024be03d98ea3cbec7186ea9f4a431dc3743b9f0871b205bc0c1b3a001768",
+		"113b261414b4b7dfa028668ac8b0cde5734120124991c54f4dd16a87d181efe2bc15f6d0caaeaf6ad615f59ec5c2833904a34b4d34109c82e10609b387f995430e8c13d83ac34310d838af9efa32d7fed6224c0a33",
+		"cd762390b93369f1e207eb15deeaeb0036f5331e82480d180f84a76c3e44550b",
+		"e88c14ef96c7768f5dba9de9",
+		"8d6aaa27892a76fb05a2e96cef9a9b4b7ae0670a12cff95f7b076372456889fbd3b9b4fb5fd98b3bd85b247f15009be2f4e7a0329dd118b6872199b314e159618ede0381dd97db28743461ace1a694c0383d8458150a501d6c45f4b50d5b1bd47e61a51f9ed4929bf2e564f201ed0e6825170027d93e482c1ce268459d2f81cab41f0e7ff281430c16b34a29b5c76630dba72ab9e751bae41122b26121d91f2af271a23e818263f46e05fdd52f319d58330bcabf66637a368c0a8aeeb20cad1916d966e5e0b0de74cc67ebe57e3d1fe01e9743d42a931cb4b98bb762ea43ab937d1e5c42eb08fd56e70e911bdcc1ca4ca0604a329c5364b262ce2de282b4732ea657b89300cc7b7127ba4a2d08c13f581f024fd093ac09c2bc245be60c80e102405597fa8082f4d28cc954a93217edffaba3d2a397bb59ee89c8cc0f33eded78f21183bd1acdce64a923dd609a0620d2911f61e81fb2c8ccad8ad9d81157223253a121ea2bc60d6a3670c563fe06bd75688572b3be83cd31dfeac6b17cf8455267b481219c42034b2252977f32b8e6588fb05166498fa37d17c2b002a655b5711bbc21175348225fdcca041b1f97fae48fb1e222c5bb46b5202191c00666b7e1b2d84aca3edbee7a97dc0f6d1330e929226f8a76c155e973c1ab62c867e1f87be37788754e51825ba31af9f4722b5782ef782fbb70c391a664f252d14e49a805e94790135ff6bd881a687f98b42da96fd34bf240eae4914488af739ec15f13f048a7eb5fa94af14e8b6ac5fae714cbef6268b114813ca2a3920a7a9d5eb506a2ca211758de292047eefdb5a97e18530dcd8410495fc42abed91b1204d9b8ba9d6aed11d2d0fa0d931d46f93f2c1a560ef9f5f7cee1497be770d3cb07c534215cec12c1458bb57aab4d95cf4a15a5e3a3bf8e650206d5cac4af3193d169f1a57638d9a50f6b7c6985d42f7138b9226451670d7359351c2affbca65680557693d03458341198b8e13d0ea6abb7496edea3cd4dee2eb93695e668c7c0901c6809b8ef434e88b85a8b22cab6508b9560fae62900056b7c5c29a8c899bed45a2b5159a1d4929476ef350101317f77f02d48a039cf4cf01c56319cbba16fe908c49ed6f3face88867c0ad3703452baa7b86fe58a00ab8f740b4e8055164b0385dd3fa44502ffbb99cdd843bc3287ea468aafe4cc298a3fc180f284dbf78aa09e0a2f7d8593356eab016ad8dc505420edd376b66598a3d0aaa848fd68c4e07419b8b50e40febe2b6b17ad07726fae1f87e86abd01490a0ce24fb57b533c765504ee0a9ca154187bcf5e6828e3addc7597532643cfd992558d63b1acd00e7aa41b9765094217480c08c43f4f0b3f0127120699b7f2a5ac07c655b6143e467777cdad4bc21d4b57da4d8f9b9a7e4523d8c6fba3614b7f7281e80ff0f9004577adcff1b79fe443c80ca9655ecc102d5df6aab2ff6c3401f344b77666c59ac7d5b92bf4f1e2322f74b75e6ef2bf43ad9e018f164ae76a91451e5221bdf5b65a4fbbaa8dc31e6063b451edbbf4965307f8e65bfae87b15f2453083bea8484017228a9cdc6edab1a28834eed8ce07430f776b916b3bdd2340798955ce9ffcf114c3f6a88bcc4c7b6f2e3842426488c340d00f2c4d2d6fd3b6263dcf7a57f5cea6c77efba7013297bd3320accf033acc0833aaa8e8f95cecba469704214f54a1ed581349878a591f9993371f1daf92e55b2a4faf8f952cf785c687a59b3c258daef1b6d7bf9f904123c7384a859933c3ac31e33edf648a1be4d6264ffade860915bd118f0b9aaec2eb8e16b2015fc25e68caac77a3accea53b9b178f6cf48d15029fac12963b4277df037b7a494cb29b1d9e6d2148531a1f7360519cba5657c080254f130a1cc3ccaadb4298d7ea0223897e63d798b4f4909577cf9b491a82de0275a246bb1211bc4144574c8ef176b382262c0e087975cbef33cc616d32e0131a9efdbe8ad3d9cb5f935d3f4f409852acca22ae2a6e7450e9a426ec3b9183f93b4b7f89d850e1c7053c661936e0cde23e831a261b319b430da45772f0fc0113679d06f025983bbf37ecfba35eeca28de5ff4815a490570491266e92faaf8d0ad4ac8df106faff8fe3c8d050ae9dfc03a01ad177c21d7b653509a80369a668a97eaa532dc9867c32aebaf89ed36586e1ebbe1045347766a354a86ec1e8b2f30c8fdfbb6c5d549e7a84db81b73fb828499c5c4be0d4b2b7ffb197133a0ee18abb5a4e371be0ec0a6535507029316f8decde30833ca47493ffcab781d028edfb91c138609baf1054ad52a5d8ccb98b3ca5b138f253d99bd556afd80f71b39f36e0d96fba4e0cbdb18926894968aa825392f12d98b6497ff85a0e4a91c97f37ba1dcad30fe688b54008b925805104a61dc22b712685202ecdb073fad9b10b5b9ee2ff781f23fd41ecdec87f85b369a304b85bd2af126d08f79d8a9e2bff0b18607a95c4efe35941c5493c94e3f2f3902e79f4cfe84c138b83c7f32d7c5a125b28c6107921e8ac92f1af7da015b46a2f9169369cede770292eee8a5f40d080ea1c267c33cb7d4187093d486dc3911bb2d6cae036cb508e81ca783ab5e95cec751e39f3038003081a252eefa7cd913baf136d4e27076251da9cbf0c7d2586fe02b62ec786790ef08fb3ff3d79bd06868eb1abd9875920e14fccf6dc144e898f578b7295fb5f4e84cbf683722ce3597aafe3195e194736fc317ed03ebbb00d956ce89f7a41a334020e1a88da355d3b47d5bd3965a290f6fbf5dfdc8c8e6347b4eb85151e53a960311582235f3b546ca80a670dcb628fef572dfae0c101bc08c80f78d5630a793bdfe402592c316227f2333b386839a67e6ee8d9396fabc9648ea656a407670efaf80966034958f4a70fe7b920c79dea3d5a0ff05f3ed0516537d51a686efcb258520936fdd415345251c9ac1143a41be295cf12da5d4319e78e1c57ce20507490e5213ca7be92afca8ec8b6a07b33571afe6940daa2afb0dd4dcc1c329474ff8e13d740488e5ced552074fff695a04fc1b70755245895a1e9c387fd9514261dbb0f600ae03f4896e795d1e72f421d8572543243d662f6811eb9402b6a3b8dbb0f32de95bb1ac01b1287663d3b6a3f52339a4f6b27789e15519b2b59f2f4fc8fd33ad1a6e4d02cf0ddf8499f45746da424ee78e72847e3cd3833551b6e6fd6b1aa98c688252b57a1d97660ff006ea1b970a0b8fc7d2e313ffd0b0b85299ded47b60cd2fe9bdd7ebace4b0c1072cdf67231a475045990b35ec761e1dc1dfbd0c402296566eb4b9462979d33c9d652a9295ae70943f38adb212b48bd8ebe82722b1712ab6a3be6060297e2aa54e7d0158e4aba6975237e7c7a1e22b29560b8d262125ff2a6e5c1332acd0f6b5ba15b4a82d3631891a01530321830aa8f2e8ab6b41bc5b5356957a4d0c3bc3eab04df7700305a95d0f9cd18d486c675c963876b25b1a0f78e245deb40dedd14dafdaa9d614fb06eb2538c5411e13be116c76fbd3377ff212eb07c5c035612e4cd7a1de2ceafe95832eff88a9bdb3595cc19287fa40b8d244afe9bd24dca40db49893602a59640d7a1b8e7475825b09cb0cee111864deba9d3d1beac03664279910accb9fac534ef099e398d7f6e3235cef7685fd1ae46e47da093135741894273c0c3486197c26057044b10faa57244721328b47e611633d16d3e4776d90309d68ce4a60d3ecda26c9f39c1c6da67ff79fde4977efc5653d79ad86c3b53090003bb72e78aeedcf4c8107185d9aa65221df4e2104640a1a083845c01000370371fea2a6bc8ae43fbe290949da4e559d3867c16df16b143fdc807616f51ebce8d05bb03c2b0bd587b95e3f6a15d907aa9a5b11622ddf4c81ff9fda4bb49d3e9577551bae649cf64ac0cfd646b02f6f16cdefde09a55e77afd16c74e8a3d777d80b7cc42c51f618a3c467968631119f11ca4385f0f5713e37ab1133b692de475db1d44fbfe9d274b9a09e673dac88aea74ba88cde8db3c831e9b5a0f1e40261281e5aea9d4dfd48c5d9e173f4d9cd56fe7fd610909c838bcbe1d6c729e151ecb4caef511a36a14b03cca7ec5d0feacb4647ea5212a11d18cbcbedf78443127680ac0b1bb65120b4197570288226830e2a92b380e32387bbcd3be2c77d6c7722054d849be9de459cc1832ec3ac8e7f60fba9c81cf5fbad37d228eba137a23227d56cd24970340f2b7599aada9d2424cdba8b50c2b97244dc83f7391e2ceba5bc0a11ba547c142126c791265b33a3db6238321a5f3273ffb01e42adee17b898153e41818b91413ec4f6386ab3dd48db875afe659db9eac94d16f850ac179d087d93784d607349e8711f5f96fd514e8d096de8b4a74122ba914520e93a11fa4adf006700e122e2531e1f39340cccbab4862708d69c117d3efbebabc14a0231916ae1ee8285727c9fc980051360346d53dfc76aa5a11fb1fc8f36f95f741e913bd2cd1031e508b320abd2d3a62baa400dc439969eb44e6abf8223b29d4025c3d1ca08d2dbdbbf9927c625270543e8c0cb5ac5bb5d504d224e66a1895719e4f975d819a95e54cecfa59ec8e385aaacbb023772fdddbe093afaf5a75e63a62d51926254e5b47da1e9b05851196644b9180734d05810dcf3502747c4ece652b67674c02aae74f20d07de2ad5993b3a68d10207eab6be5be34e52ada655aa96c1d82df9b24c2acec35e8f0bec9131c20d0ad8936880af87215611b80d07d7a741a12d8145bd05066c6ac171afd8684b92f72237bb0e4ca4aec1ec280e39f36928852d5d8d02fe463acbad8ecefc103083fd4298f399bb254e7bfa166638460b760ccf2b0f5fec0e3875206bdc8ce096274643824acfad71ba06441c74788356caebdd2208f6f077b056fa9d85aa4357e93bf064a776f5f3b0f288d0afdc51558c8f25cbee17247364c2bb24637dd69017f92bbb43024d9c773439626a02bd0cd44136a642c9c5ae593f32eada790c31a6704030f2e07f1173cbc0dabc410bf9864214c298a6283b3631acbf94b8371681ba81eed1aa81ccf258252d7f90fe733ac770b9744d0170cb554b39e6c72e05919cc237f8f4d7f3545f4d2732f4c9473c77401dcba04c0fd33efc73219f31c08dfab26abee9a7cd4ad3584730768fae899fc",
+	},
+	{
+		"9c73ac05648e0c50a3ea3a8eea70841e8e06669c1e7520c5e25e093769c4b005375c0a9cea16ec8e00261ceb96a00924a66fc0c4e4e089c63e93fea857aead8e0ab82af4ce1682cf3c9fbad23fc3f7e632b7aa169834ddd6c7db7e1e892cac93e4d787b2ed0a812aa93bfce8fef3ce30ab794743ad241974ff989288c43e1ba815a25a03acdc2d5517293e161d0c46c8858d0b32b124a6b0bc3838807753288cf6838fa25fbcf876e6368c0342d3cbc860d6fa12faa1c2b7d9fb37504e60dd44e36ce74229dfb80f1545125718dd1f78b31a8aadbb4d6494489ce596fcc2dbdf2ec22157a1d966b61e780d36552daf084739b602861a96ceb67b65b23d40916c02b2c3a38c2a59aaa266e1f8939000dac9b6dc50d1731e87ee833a2cc3cb98c57e5b680a85c1b428289520bb252096efd7723fa8e55d2fd4e16900a435986ab3f3d2bd799471a1bc07c1772ce10d1bb8805a6065b8903999f9393d2ed1a7e1c57a9e3e0e10dfca17a04143814f5f3acfb99a34712a6e0a24a7485279ef343e69d27c77e25b41f9fb833d7cd29cb6a15551d5c77b43d19feb19f2640926a272f81eeadb792bd474ae11f080ada72103f8f7ca733a9b1325b50589be2b2b3023491afec246d336f4e4277592ce9695c68d5f39c8fa4cedaf51776d7ca29ea0ecb89eaefe71e5f3560c68e8dafe7da08cdcd954d626418677b8f3f45b9194474a32f548a4da3bfae6a3e2c0a25f602e3b3a821160c397d77c8bcbd71c5f1e669213af36eeea30d48e12953071f55eac2fe0bd8fa355671fe032f6fc9214632428125a16fc8aea8a9c7fba0d7518b9a4f876349ccb9bbbabcdb2a85fc60b83ee1ddd041967efa4036e5e10e377c9886f40bc0b0b57c7b724795f843f6a072e87e532a04c21445090a360731a2afb896ab795750e5c2c33d58bb714f5be427ca3751df09661402604a09a1eca95a8344d3daa5b99d68e6e6245825704c5d4a73af197d052d7f75778917542261d77735a21cff3f75d6159a3e4b1a7a9854ee376e6b3c8bdaa1f353b957862b2efd50d10a40007026261a546124cef979ad20d8085d53e30f5736b8aebcd3cdaa349ea474af249ac53eef2653ae1fcd5b3095538de9368d307d45df2a19acd44e3b78c2da9d5d9fcc4cb61feac5dd35f66299845bc0018c3d476b6761083baf33a4621e41cfae0e0c642de729fb2d206db6a4b976a635b3fd911b5e9946fddceb6feb2d2f893b2bed590317442037a1d6dc5b5d72910160221cbecb53bc983f1c736c3bfc9757e9e05af1248b28d651f521af67b2a0d7e4bd86a0013338404fabac7b9833c372142e6338a98c0efb7130aae8e34bb0c80937680a7a904aba3be735d41af9462f17b967b13566bcb697579f8a9340429c77baa6e24ae1ac86d8d25ae3cb9112e34a7a948fd141367898c5f33c0635c87de06f603b510cb229df0d0d9a9e107de88b12686c539ed4fc54c8285afde0c8ee502919a125cbcaf4c8c89f56e90d3f641f97c07326956f7b5d87c65b689f39b8b84359ee0f14d2c7ed621ec67f5e2a8ee5faf21c805187edd95e3941ed62fa95a65473a569566d46b87c0d27ca37b6b022a8cca30a4480d392ba15701d1015b3648958cddfb614983211bffc4966ac6c1f691f19bd9fed405a02c06712d62a775f73353f3949c76b6b7757a4ee0410fd6d20071abfe46b09e72b70f9f19b61410ea67037e037934bbefaf09cff018a5c218176d165d1eb5cfd5c46eee7b82fe65ea02e3ed7b18a86ac7b139b7c9df79e1f6e6f85304ad22d97190c7ec12c651fcc835ea434d92ae1444e7cb0dc644efbc2ae70f2f94310805c1d0f2d49643d05e78baa1c54d4fd99137a49efde88dba1374c94208fb4a0ebc1a0090b043610ebc1bb08168ff5bf936ff9834e825eefb9ab73da2b287b06fa2b0ff52f46061b07c1131e4108cde478c767b749b696f3520acd8d3338842d53941282da289dd1e9a0e02aa9be0f127566c9bf2d50a27f6b6ffc9e9880bbfc14ce7eeee70cb0c0ad90fb474efa69b46123638e8405fdef65fa7e0e7b29fa8fe8696edf661f9003a08b4aff85a4a3e6d817655c1d533b834da981b8c37c38abd5977b3ba71b3f57967a471c2eeaf2f6f258431fbb7e92f91814b1db80ea775681f282290db170942bb7b04aa2a331950b74a4b6e337affb4c51c6cd4c4e13ce3095e73e4767c2731f72bdb225ff572163fbd8573378427fda194d165750d487f6bbb63e1378a132fb6ee5115e3c32b2380b096b735bdb4d651853bc7928346fe3ea9df7534f2a4eae1f5ffc4b82ae738db7df0103ba4e68c2a2153bca499bae2439a57778cfc616df16032aa8a19e26597d275d2775b5ea17cb25d204b18028eb25a053e5666ac47c6def151f7d4b68ea62c601d87bfbe04711c24bc34274be6815024d7b7d01e7dae10cea6e485348ab195a83854663cc5826181b688cc9c091dc1e0d491fe51400e20e6f2a51a7d56af258e038bcbc80e2c4ac4b41661bd33229d07b39b59f3aa79d99c1ef41974a33e02a7cacd6fd8f9b99cadd0fd6a031f070bd3a364c64ddda0e9fb94036f374171de0b3f4ee3380780e6d77d50db9d58e670fb4a364827d631226a3491a27602808141ce657ad6e560ad62b088ff086e6f03b8a64bdf7c7d01e7b19289279509a9d6d80e50aef3b05b5561e4556952c46d0b6ab8eae735eccee77e570e1360b7ea38c53ae6b8eb420e4c2663b57827228392db6e79105a47f7d89e06ecfebdd63783101d3bfb5f494785acfdfed41f8166faefdf0b49260222c4080ec2c6e4f949f41784f076ce37fc7a34fa4e547bb44e6b9359b4b95cd67d64e4402ac83973bd50f8adc7c6e4c34019bd8f6d3843bba3d7155890712e0ed5134e00db877398d86b459f312a6272431f01b057446bfb1b8053acf181bac79408c7708f3a0867a64e06d7786849bb874a6bdf8fd6daaa572d5648ae100f4318d6b3a811bb0fb709168e817ed83c0622a7e5b17ebf5cd5ecb21d9ac32ddddb039083144c93cb55a95ad72732132d54bb120639d1620ebd142b58d75835b35cc6367012c93c6772963e9ac852c71c0dda2246ab845469997fc170d8f62334bc5aa4ce23e036967674303ec6f75bd3d17d197d026de69beda70bc59d2ff95a899d28ac7e5e42f4d37233996a8e6d3b0b86b80df49ea8e145b4a6e3e39f3d6c3c6518bac45baf97cde23037709d737b242b8918ca31f90fe59ff2c83e2f347a954d3559a8e4f075c620ad36be20b1e24b3afa156cf3255192171ad0474e4adc9b7f35436325b92945665f038611e5d14bdfe7b7d20c09642323346a717f460dfe7b5062a0098be66febe9f5fccfc747aeaeff81ba08e5dd2b1a489c998ea9970afaf9aa03859073707a686c492fb3f7ddb27897ba5e75e578bd82114b2ba85525a2002927909c970a04035334b64b1169c3a923211e0999db8baa26b6537cdcf57c051c0ca1b317a5b66ad96cb5ebd57994f99ab202348d8ddeb343312f1f26ab2442b8c5f5cf6bab394418ef2fed68c3e60275e836027515b6b946e5d86d91fdaf49c2a5182d5051726840a156a8653cabda25e1dd9af693533d782caa09295952ebfe6a194fbc8bb7fc2c0da5914a506c6f31490928dc5d6554890f5eb268b09d671bb6b6d7416dd36e7b78ffc5c86b34fab43d22909a87e5239643d5fef373650e291be56b89b9d90431d8c9fa44fdf4f83a1689d59d6ef833b1ce31a44197b36ab298d53b51ae3f8387087dcb0571c340874c1524ba0d576bdb88101c1fc387d25b5c0dad0b4d309255ad5d5b1e209ba56db0c927bd209399a8a3b5c8663c9ac199a76ea4f49e364a4b93a569b3400e20f0d748adf7db46a07efc68e43802a5d1a914759eb2abe8fe3e8d67f2cd7612bd4d5a6a4535b1e5b3ad4d97e54f3db7f8512c9603d87e01160b6908d8df1b952c750071abb1565e5ea3f643f233faeb84278187ff0089150bf21ee4d13979fdae796f592ac5b88869aecc5be1c64665edc8ececc87502d36720b73859313607aaa561d56a195dd3c7292fa8f0750ddd3df9ca056fccd9d6ec900f45c1454c6ceaad4154c69e288dc85735b8cc42950a3c5f0fab2be8811779905c3ad5a9a6bf56e7141d863caa4e93e0065f229b695efb790926618b3eda1b9a15f143bbb09aa3c4b72900617793417df364185cc213d5cc3a375778117212266356e214f085d8a7aed908256c4aa25faebabc70ce913c08c89380da06920069e8e27dd867567f152f883a9bd2dcfb8097b7f065482d6d11c0edebc67feb3068cead403503c04b324885ce1a62c99af9808a5ec8b7cbd978b8c43e37b06e9f7e1ce0b31fa0fe52e8842002e6e99cdf69263d31de080b56c0cf94f77f0397fd1f77b13e17af90ff33b00119999df802c33534a13d3ff7fd0e8cf58e8f8c8bae033cec1aec7d191f2d1a39c7b731c97a67fd1ca43c13a24b9f97d92e2364dc26a1c9408d4659ac7373e53a2a1704a47e01c0223ed4c489735b62a27ec67ea46747e4f48d3da101b0863bda9d3f7f1b413f3e7f130208875e6a29dc30a78198ef658c7ca32d7d53b4b92e51f8ad6d39ecabb800adc0870b2ab0e85b5769f346ce7fc371ad40c561f9f3b2f2a01f2b8ccae48c78a41383cfc36b2a1bd41d61a39c24144965d9aa5ecc5d506c7c7cf9476085bf049942d35caefd77821ad925b7fd3a006213abc1e008114c848d45cbedcb8af264cdc5c07bc338fddd1123940e5d95717040325048439dccd1e298bead22b011ef76d26a390a68161b8bab29e8409a5880cca9c8104694e1282c9fd64f50e73ec6b9a9ffc31115de9cc0088400a2dc806f85487fcbdd60f409ffca584fb197156b40142e512a0dedea1571ebb74d6b26d3b4a59e9105929a055cf3540e8a6a79ca7ea71ba8b40893c9797e81c6e9a7999d4d382e52cac95727bcac354616ae1094552b3d0a33d0d3ac4e547237fc0cd54944039b0eccf335889f6aceb518de496e0986783c564be8a4a05bdc9c67b1e5abb480b98173ef091259d8c772b611e0c09758fceea3e59243406edfa71fc452d4450b55b8fa5ecb543692c6eda3a6ad3bfea929a18ebbe5ce2ac4754989c71dced37286cdd1512107e4e7f4878da1c28b4beb2dd9a712a8d1d61d1a5fe5382db8aab4857b05a783e98e77711c1933a7641fd43dc6e6e597bd03b11ce8e94aa094fe250f03cc92ed5b0a5e7723911e87b0f3c476d9aa0d96adbfb395a8fd353cfb5a4cfe27deeb82e849f90bdb17928b0a5702e4010f7aaece2d43772a78b325d2ff24f9de0f7bc65974d2348c64",
+		"bf96bbc17abcd1f56a9f22ad164d25ca72f8c996f1a7a66d6effe140336da4f20460b47e1c8573872496343be35a055552ceec437692b0e4919224c4ffc8b603286a8245eff5cc148b004f6e5a54c4ac22b0f09842a07cd332a09732694d3591b8b7d6a7ada2bb38a30aa7fd5e6baa811b9a195d3a96306d",
+		"aa2f714d3a184a9883f4199e8e33fbc9c92b36fff2d59f07a9d0d335d7476e81",
+		"36c79f9f14d431cc8c077439",
+		"873d0617c986dc9d83e9cdfc50b1f916626a9d9e1c595dc7ccd99d1e993d25d89b04a893c89e205952eef8f1733054bbb55fa5e1b07135787d4fcfae226737b50cafa2c11276e8708451be9b4d7f662e98ef6b705c5c4fc64588728eab1dfee22a0a92bae61828a7394977b0ae8a3b6d0126a23583fec025becf0a72a28891391ac1495732a7a4a1d43a63ed8eb37b280b6d886096fbc4f77aadbc5e441e996334d0e10cd7f3dbba9bb7efb147297986509a07735385c681e0543186dc166291edc3b4664f5c8ffb0965c85bc30ff5e7769a69609c69ebb68f35d104bafe3dbd3e2a40e13865f19bca3612e48592aa930eaee29440b4ebc1c0a59f1c54519857c929709b086bfddd6d4a30940b592be48e0067976099efe71f45f956182dbb300e8076e1207baa32d59c1afef7f34171bd66099d2d7f07b39d16d0f8b085185bf2554c6ad66bcd656f07979e8f19575a116f5c4fb9700ec3b46a3254f28afa1ed51348c1af6dba26fd398098a76d7bfa2ff195eebab41330ef290bf75205a2ee570a2fa46bbaa74aa6ba68a0e63e2731dc1974eb44794f3c89ba58cf96f7a070fcca678185711d97cd9d7d8202351ed589e0b05a7a190e60ae4aa109254a7bcf7013f8addd07a64145e21226795ff7c7b1c225f40ed7c3552da8eb18b9bc9bc70c2e7ecb10c8b20c54f04b6e27b5044a7a67b558407eb330f2083444375c022565c45fe817dc00c7d24c23db320d15949b0b64fbbaedd310e73e423fcebe6e1e98a5cd232d97e6466642e5e3b23f06525ac1cdf8688650cd366b1b7ba2a9033e62d836b14bb73717757b76b9673671bd3d3b2a56628f5a309f3b86ad32abac0590c50f7c5a22e0a920d88dc9fbcb3add08b900a2a2fae4178aa100a0e645ab428e0e79bd90baf4af2755e48262b64838a6fbc21226e323c0a1ba5703e30738fc7b5a7df9eabec6199df5ff6ad58f9df5a734ccd6509e53ecb3de1c881732e26e52ab848a0335b04b25f2254aaf8c130c78b0c9a40b60d402673ac7ec7311d0b00c45bd176bc73ad81c2478611804f59e3c145110aacce922e473ef346f8acaabdbb9f313dd3f8d0a937d0c048e5af789e2e09a816146f9ea28170909caf2572a2f6e2d0d511242909de2815e9ec586b2d12183ddbeb7dd70f32424097e2ec28b4ba62cf78f547e2057a4c050cccdf6b582172343742ec8c85e2847efb1595bccf89ece3b3ebba824d2f097b1987ec26c6e5710544739d54a714060fa91b7995cff0161415eaf55758078772c0271d9d282354e47a25b673eb11497a6ed8db82267d65ad47412300ed525af96f943c5336b1de88676dc346e7339230032463d305b0442f934018bdf0242768511d20474c6ecc82fd752c0c0ca5cee1f3e06e679fa5835540f97870d47ccc6bab233290be7a3bbd4a73f1dc7682049bf7b3cbfb6687479c18d246e3c07161df5c889ee95d39cccd989625a8c9e80f951f8b1832f6378e05daa8566477d7fe547e49ae6e822a68de4df9fc4d6500d5219c3d3bd8887bd7f695151ba378da17c2e750399f7482973510a386721c59683a86003edb9f0ce1ea89bd7bb8a25c222df7ebedcc1b56c8ce18f367b2cae720e0591b477f6ffb498c3d7ce59cabb1b01d7cba84d7180b4b2a165d4b889a6ac361720e768f2913aa50b0b5c88e55c35bb4df4fbc4460338809605f1fd445a2bcd97ec1d2f269b5e779a18c8f215bbc5555c745424484ee5436119eb8754f5e9e91f51fe715353596baa1fbb0a690e99691636e6027cbd4b7be752bc278661e2677070ddc12dccc262d3dd47160345de51359ee8dcf2f61044f95dfdaf323881b2bbff68af6572348f786f6e52d1309cff871ad58148307d7eaedc93ef037922b6092ac62171433adc4934884efdee3052ebd60ee115f76f9dbd0eab7c4c0a77b4ce8078209d23d81d957335f331965b556ebd54732327b5aacc899f9ed0edacad9eb98cb845867f249efb0e1a5fa2483227f78decbf7f1f32d060ab0c01eb985d83920b2cc24b5f9a0d5d869e980129d3b78277fb87e5cda61e340a729d86b6617b8828dffc7c37d4c38080ef3515c2784935973dd184e0a8160f84bb78bcd8a5e691760be4a4d41ed6512ee436ce24650c0e17e7d74b5e01cc39b21e21514a84db262d673f24a82cfd5dfe2a162976171c538b24af16429bf8ed5fa8e37f89ec6e7d63ea1d83ac1087cf89e8f43161f225108889e922493d973e36b510074533cb1cb22174d21c4076959e4191a5df880a8b868b95a9cb5151a7ad47375fcd87725660cc0b59c88ceb86984941268493c49b8aa2baa8c531ecf497853ffc3d26b926a379e72188e246d42073041fbca453bd558f328881c8f8d9e099e898a912530c4be499f2b32229c359ea10e0befe6d94cba5ddafe51d164898166e890b22fd1eebd5724451511dce1f8f7431d712a3f1e50fa5f609da686253311af255b84b2106b09b803e94b51729cfa0826869945d46b9606547e7e33fd9961cf15b400d0f5e01d8fd4d92a83ae526934059d4514b9e0005317a70466aa0b6086d5fcfed201d958a0de55fd23f0919ea29b8aa02440031a9fc206b9feef362a73430a4204869354ec81b6fff92eca97e7f1bb12d25228eae466b8137b4806895ce34b57dc14bdcd107fe160776b0e5daab150ba06976eb884eaa574da393af4de355381c7caa4f611a2ee70a0c78df93a4276f55e6281997b4aeb36888a6d9638cc95444047e5202f41f8bdd787f1ff44a648cc7d39f05e49e5d6989fedb194c526780709763da81a780db0d1534a466cce57e11dd3a4c0e273d9873af1040d52a90e20101e1f80ef296d45769d204cd5417a84e022b6b336675d36d9cbdb16b0cbb08f5e240012967c8067c92f97f981cd19d449084400d76adfb7c610abb73bf21e161db04debe6665fca79d71c8cc50adc3ecf0e52d07773478ca97b8e9821a5704dc58acc647a5bc618d2b681f17942c46c266c73ec211ca403a7d47e42e12c775b370cd500d70a4aac7124f5f6d2d4ca78e1c17a96426c326bb60379ceb0c84a86200f3b450e5e9aaa11f45440f5260eee7675a8b9c47fbc58cf18a651a1dc7b39a911442504f12c103054bb50f15381e512dc6e3af7b414b3db26fe767d83a2a53d7181fec8f6b196c7874befd6628b31797ee3c9260c7b7853b137893e36696e2a47277add98462ea9a0edeb7d2d3c0f2805fd7db64c2c7eff353ff2b36f4de862a42779ffd4dbe77b6a79bc9f4ea3e909474ead915fa3fa990bc82b83a670b163e79300b627fb91c4502e96bb9dde00f716ae6ad14dac647c9f7c2e5b2e505708b5fee996b8e9113a8f4f2caaf414061ee72e76b8bf47ec4f781bd7c589adebc2c267448247e30d659998d8037783494a1fdadcc819d7ad7ea2674f75e10639c3d3055046a00814ddda0e463185454a4455d60b9780250183d591c3db6f27373cd2ce4f02f206ae10a8c32d71226e7cb8d5b05909445977164983c0073434d6c0f2bb62bda66a16792d6e53a49ccb5ac3e285a6baba935f30e9d1ddb812a018ce04f29e2009ad678ba72b6a7112d6e7cfcd3ee7b058ec954a6fd7fd01018a6eba6209687c3130de58147b07bcfa02ec1caf30b59daf87db4618b4a5fad34cbc8014a7529b9458e05eccb9a77ef1621aa95513c6fa4003b0877ffa6d48805e7867dcf53447caf348228ce926233f65d553146584d6ff3dc3ed3296db9bfe69dec6a07add13037b3aade118b2ac3c52350b9691a6cb32356ad93377059fb8ceab68de38d96876d6d383db01f3cf620e47cbfd471bf6dd1f601210482f7c3bdd4c3bd37dd0a7507e1f0fe515151634813dd4ecefe97b52eda28e7a7129993b0af311abd3a07bc463f3cbbcb4fb0eb265a5835663fdbab0d8b8b5a73837ac98ced6582348fdeb41ac8ea9e36f9818ab9c0a41bac1389a6b518ea17df043dd50550f32471645791bf59855ed695b84919aa5cb688e569122786660f06e3a919ef9cf18c355bb397b86710c367362cddb0239aa1d32d489328e4bf92b3abdc3d0dacd76ef1a1efa28fdb848e708aed6780e2d8efb19a2e26fea56b4440dc3eafd796896d73fd150bbd967871f5e6ee5db58995f2f85cc2a15077d7d472bec2e30430af6891193ef03dfc7761e2b3b3b54a72d4f1084a8fc541526fdeb0633dcba14e9485b43065aee8750397ea88d9ff13417149e0fa145be666e6f4afdabe7ad8e4864e777c20ee7a2842db44dedee22f3ce2f97d72919b9ff6059352083be816a7515c48c5140a99af8e81b9e18b10074dc73dab55fae66261421629c8e323d8134f08beefbda555660a51e4b55a9ba4573bdf0396cc413145a941c4175aa672586f7676027f9fe211db87fe07a23962f5b1ad8f566f0d5b13c5146457276f307a02e1e13d00c5032a06d225248215e4bc4be1b672f1eaff16ca95da42513fc4315c7a6663f9101aba80224acbf0c87fd3a2ee9dedd1808c1247c5bebf3cb8d77377a508ddb484ed91203a438ef5ed3ca14e087102bc5f3828d8c3437ecf5c92eeec0331ed93ae33520740abae9b7bfc45f097da70adbb9b9b879e46a7d655dbf75d89773f737b66fd8a8c13506cff7b44bd85dee279ea7053f3ed8447fe79c400cf23726fae800449d27af5e342ecf776378e2eb449a3af27a40fe4a9806487b81c942bfe1a4b0fc146c971a13f83669e0189e337cc9fa2024864436189a9165ade6b864698ecb797ea05fed0d60f0ab4b92cbae36c72ccb5aa45337cc02dd086afed9e5522ecdb75ccf389fcd63c5a4abbf60908e39cb3268c76a08687588be67a856a841eeaaee8ed016f6640ef0f5acce12ab8bb58dda380696e3fb22d0bae0788c4fb79d00cfa5ae3e479dcf7d08b45f4592c2d2a7f8081d5a9398659613ba4932ebfd7382d516b2648ec4ff4477648069b9b2e4decc89547c16ab82a0ad9cf293fee5adb17cea4c95ab7b8e386dcae6acac63ad0d1d13656dfd97d5623dbe45230de597751321bbe5a03c879c303fd7a0d837d48141decb6df4f0865717628c85dbfda29df9a8a69b2c956c75fc66e45c08960c23bbbc706e48395057f989dfe675305067b3ed8d046db339e504d5b2bc978ab4dc261d8afb325c5e794ec79d63d8db53f9dd24b623fbcc202679fae8f7d39f7f7e0667b142c714b6a723996e5254ad2ebafd63c3577f8909981ce6b3eb1a6ad67a4e93c45ac3b34587d153ec5ab67a2697a9741610d5a176cb9b5856bdccb98f69421061c84811dd6660495d9f30548efaa69e36ead246d997c95bad0ca3fdc1a08b4be31b12daf211d3e29d585cdac48af8f2268ec304bb35d",
+	},
+	{
+		"ceb1f819497c0d631a9c9616655f419b5e3470fd3b19cd0e4fa556bd26cd9df57e960ec7121b2a2cb7c0421c1f84b77eb8277bf341490190ee574d1424eb09a281176a933394bfea5502077486bef23ee66e3127b732b7a58a04b9aeefc35170dabb030d4fc3f8a4c5ff194bbd0b89a379baca30ec81d576868f25755276e62c31e93a80ac322571313ebcee494592c3ff5cf3ecdec962645887d9aafdbfd62ea910af5542d4c7731283625bc9f41ec85012b42edb1792339e6cdd9c2bb3cad4c4792a064df17a5f74dcbb3dd0d90620ebba4fc6d1e1f9704dd60c798ad64d4e5077549d68cefdddaab81a7a91209b7ddbea43accb3d1c191328929dffdfeb4f5740ecbf0ee99cb9a1b73333d7ceb0b2b8f35f84307b9d44a42fe1a30ecdf2650dde251bc8c1d46978089c50d64c028f40611370ddb0b481df9624ed63165370f4788bbc396026b268c2023e0f04cd4f66e0bf439074c46f0ae85d6dfeb0ddf22868af61c8d5133097156fa61a3cf5801db5c3ad29871d336f7aa06d2a7d5f52e50eb3aee3c7de7bdc4d21f68a1776a7cc3954f5c071282febc89c1545fc672a0a1bd8eee2b769be048ab58ea12b356d658a6225fb8a55e752f1fc97ed64c2f87f9ae661514f1f56d9d4e47b001ae865a44b8a9fd5df8628d183bfbee781b6661c9cc76debe6c3c5bba840bbc228206673aa05498a8c715b0f3019f6b2d05cce6c233b5809ff1dc4a75d7f69859fcff94ad442d460b32f6fe348659518c16385e49fddee9efab2455732aedcd17dd51b5117efb2ca1e21ae6787437f48a7042d46e11be4dbcd2932ffd70fd154e4eca5fcdc57c6fa79746100b8e1485fe575a5c79089a25eb2d55d89e42eddc81b82c4f7da8bf153ff5353b7349b161911bbe0a14483fff6585d7f3c8b5c04a6dfc99db9548f0c53e25f0b16fa212f0bdd10ad2193ac18eb09972795f42b3bd3f4d98c4868989c4af7a760f1c88ffda59faac73256df1d607644f56a70303d6409c9ad716149bb58f01b4ab8ab475e4af1257d47049aa77adf9ce54fcd22b3d6ec60484da903a6991ff052ca37b01428d5916fd92c17530bb3385a805b0d57476e9f9417a23ab1c12a038b61b3a0898831f9615d10b468c3edc24448d09b8f3e3a2355dc5e069e880929eabcc97344fb6ca5587c5ac1404783848f531f1e915941e7359fedd328f7fd12b3c685f8c1f29d1a6ef7dbae3e5e32cdb251eb43aa2d2ae0cc18b3f40fb006c2778cba387e5852ec4f2d9b8e8ccd5b3e1f4781c974aca940c45d35d30d3b9584c750bd45a80f32f73dcd85c99ae107b92888839c342cdcf88911cb974d611b14b1d85a59e88c502559d6eef3b7f5addf7d307bb25c57aae669767db6d798ca887124e159b0317e09076cfdbe61aa9ddeda189036703b1cd9b1998f88325910a37ef1fc2e227a382ae635e847df8625b99eb6ef0ef10ce7a2a5762ad7d03a7a4e2b767c4df0b477d6e9601dc8e6438184f97193ea7d7a8c22f1b6fac1f0740f1beb8b68db40e0b22940cff2261273aa0be43df561b88184a9377e6a27f27942dd04abb9448b6b6ecb3a60f14dd39b58b8d94e1991cf9d3a071ba42e0e1d71eb211ca466a70fd4724a34639707feefbfd73dd9680d76a214924642a063b38b85cf30eb763fbfe889f34b20fa4a10ba214d938a5a092c6e9b73b13bd664c75b34f746aa360593c0f8dee0f328f0ad4a3e40d498490007e573b8204a1ce7a550deecfb15f18ed5ea6cb5dd95a68adfe4cab37c13b383f8273b1971580016a8df02a3f4f431c9de9e7ebb33244512080fc5852278081b9f4434109c3427441329e8071d19d0fbb74fb6ea73fbfc7c0ac1012d3a0948d94d7ceae9b0112ec43a16cb582f9c53e7eb0ad15e05ceda108fdb3dc9e585a332018d1cb19e4a75d86041308fdd8476c88e4826931601a3a5dce06fc16512f4669f10183d5a8d15bace4649abcac07358089aeb1e9b8fc3776f3239d5442d3be33d532097e13651af7c9a5b465ace9e626889800318447b8876b45dbbe1989e1eecbfb5cdf5067c71a0d7b7fba6555d0edede12f7228d7f9841dc532274f24060b1f52da6fbaa179b81ce962723f43601d248f8f4d5778c1653e038c8d27828836d562968004003810e9aa9318edf3260272b54fca2e012f6c04abe92c2e6152f3c3e973c7e9abe8c3467bdc246f0226d1b7669bd577bb317c571aa8758bfb694fe4dd17ce78f091cf6c6de3cb601a9d177128fce8d42e652b490d90c4f8fa04ddc71cac300d3dff699be3250bfdb2136edb0057af3ebcca77ba5b3ca34531810c5e2d4c5b5b3bc4e71ee9e30cac067b7706c326357fe0ad2a4bd9cd811b4e9d696bd9b4b70579ae246381210f879c769e5f9cc3cf8d70e9c94ab74a55f5d7bf61a17418b6edb6db4147fc40cf98c75de85421b7d192919add48e5334ebce2a06e56b915447fe085b7dcd677659dd55de1f705c389975e56e0338a2ef07ccf5ec3786407e8449d9011641786f1ecd4d3d3da975d61f5a442293e6119ab20686ea8cc7681010421226838a95a157e2de948c536aabadafcd4095dfda48e5613272289a8238dc945e5f1ef30075d5de096131740cdf23da1fb8b9fa009e5b321083cd93bba9271909460c09bbe1e8c54319394ff85c291814e21215816d4791f01424abbe4cc4c792d0d04db1b812f4d24b44caa76de2bc50f4d1d1611862512d87fcebd3c0b2659082b2423bc5360d107ad7b8e8ba7438ae4509105d6b618af25e75c51e272aafaaddf1e5a227f2b2a2c96a8a83dec23223cb428136a30b290181ee20a819cf52f6c03798e7294a89f3b5137693d5a8b7a0ea38d78e43008fc4eeaf6d077ebffd3ef7952620e0af1395c38a289832df391d1710ab5b103a1ffeea8c06684c03a74399cd63797c770e3f0136d8331611502d21fb883136a82f2034358880392fc3d2fc274b799e59b89f8f90d2a5a123d3c21e5bf3540323743858fdb8912c7c6329a3aea241075ae097ebb23c8cd50f4ff46b42486e65bda6beba5f4fe6dbb30f7e61b1bf690c9f00f7513c83274cd21bb71563257a20cc38da2b88c1063bd0849c8243058ee205853342085a8edb7545f0d96a6af936a3d4612b95676665eb02e72e0875100dfa444f039eddde1422ceed8d38e6c3dbba25064f8c6cb5786f9ca67712b7840cfbd40f99b1edadd4bb9a61f48124cf3b49d68bd642404eb1dcf428eeabadfba6810a4032f8ed06b38867a7098c7744d54dcfab8f0ff941ecee69da9916d54097e080cad86dd08bf53833fec4aa4399f7124586223ec70e2c31e8c647be06df9e86a976f37901e9b134e775de2a0fd53d545c5f92236dbf5455859c138b7bb1112427049d29ed4f5dd5c43cffd3113c276d9bba910879e55efe817189fc239a204a9ebe738c0dd161d10d60a51e9dcc8c38861d41ff029ffd841086803320a17ebf5ff14b6cc2ac3dcf0ce2eea9af7ae23597233599c2321dd2b99e06d93f84989e75e30a388f47079c2af545d96f270e064a43a00c76bddf2f5be5089a69a138de844216148a1eb0b413f58d831d9b8967df297455e7538442388cdda12d157fb25896c6e2b47696c76b234a88bed4f09dfd64f2e4b77627ef03049030190fe271a5a853591ee9218a0c6b12cb3f02683d665b211dd1480cd44c9c0566ace7d751902babae14cc3821374bec774d54b4b4afd5d1811ede556a7a5ad02642a878d2d32380e7efb9082604f49d51495105f827d77945b5cfaf2f2980566b28ce3dfbf1bee2e077eb067bdfa4cc28f5d2211ca99a615e69118d9391e3feb9b13cb4a2fa9682718189ec612db889228aaa3f3345a091aeb11f41420240fbb47caf567646d9e7c762d3288f8bb2b1165cf049a191db5042fa9185fcd180b04d3007c376e0aa3d427d66d10918821f74736816044366463df7cb3ac94cea167cf1daf2d1842f130295e40bad672a22da9238ded69e241395f04d5e3c3875b8294faafbd3d90ed56ff3e01c5a0a3e349d761273143686aa26d408620c7d1a35ccc430a09e3f750d3256298c6068c0fdded270f308f79d2fcba591d723ac0cef703d8f0e7c051bae5b453abbadfab98bcc297ed4201b03ebc195c2e441cfd3b10c63c08868db36c320707ecd6a37593661d70a81f30e6db4a32f98e4fe6b950ace55923631c8f95138781fa2af78d8104fe39242f1fff6942e8e782dfa0d37c863caff9492f8e5cb70046d207c4630cc29c20e1ac105aef093261d8d335456961e552ab14d107cbe14e9de912f0e5d58d16b729270208204469f917af4e710123c3bc38a4b3f485f2926f058344db105b9239829441a2d8ababf04aea615c0e350846d9bc3b5faecdbeb450f38f615f119ad1b5dc748e88107ec2fae01f0915174feec37b3e7248ed2699d0a5fb2fc785f17d6275fbea867aad815acc8a6fd3ca4ea7357d197e5a30082ad5f35a9d894c0aebb206c6487163c9cc20442c040e6aab33d7b4b221e4ba4cbabd975836e353129559d8ddcb3c97876cdba360da0e0c1dd5b0cff7957a444027db985ebefb6154453a221076c997d3954b347f49308d2ee14d1676b75ab6ef365f3de54aaf398fd96b9040253813ba734829bc78a6db59e3f1c0ab4c878a72d6b8681157919130fd3171126994dcdcdcf68955ad64af8156702c92f7a715ce6f7ddfb70f60e80c92691efbfdebc8cae252108fb6c0010d303d9027d4a5e63413b5fb2316d32fb93c3ea52a2a7df50cc0058c76c58d73f5bb041d9fb9f3c3cda9bee0c0920079ce4f1ef8698ced664ce2e2b3b86027ae2b3bcbbae5bf7ea3693d9429cf94938dd3a2763d3f53937c46763ffee6579d018358bc69182b1c7158a09b18352ea618c11c45f07fe97cb65faca535f43237879ae3e0a31efd14679daf8fd2ce25eb8f32218fa20afc586a98fd908d3fd804cabbf56dcae272328011b252dfd83e5f0a5fdebc6acb04c5540255e1322de5fce9db5aa4cdccd74dde8990ae51cefd6c1edc1879971d3efb1f94dc41b2b23e9c9d89415b46189914a229b2f3e8b05ff78c68711385a00e9534dae6f79d15842aaec575e4ee0f098028bc74016cd3f8e93c6a0cb21a0b574ee63e367343ca9de28003d76e02d0ee2b8d622cfa3615d3628fd02499eb7bd8c1aa1f34edd9c2d059c6a7c7c978a5e4f60801e03e17c3a09793c5217f310a30db1965b8e328893cef20f4a899aa8d9fa28f7fe0a733813ed7466046776a874273ecfb57158483f4a588ad4f232adec5ba4ea651822780596de09fd54b1717bf04130619979a0e3d12ab7c35d64afb8099a1d21bc952653742f50c8e1c244d10374329cedd27fbefd37815a9b3112a4cb2fc587c4ebda381b2b01fced45cdf0b9ff8ca7d10b65ce42e728de183a82e369486a2e3345664e70674a5dac174d6616d90de8e472b62759df057119875483cfbfb103041751747f9cd12bb31e91caf79eb2db1168026a4707dc618f30",
+		"e45eef9561f3acb3672b4f38570256e8cc4d877e2998e72b022e33de8fc20f7320fe0882f2b53559e084923786e8205336a7d15f3fb88a41e7bd20767f2feaa02df2221fa7577988db0bbf61f3dfb429868688c53e130725d0279c505686f083",
+		"475a44cde0cc931edf9a44b0c1e0001766f09ade023dfe6b59a6af800e549b55",
+		"7812a320691ca8442767a51a",
+		"eaa577bd67fe79ce4586f43355c94528e306c1678946e4f7a907d2a8ee7f4281270502522119a8b09b6f05d864921cb515fddf6a1000fc2f67b52d0627998591e2acf5b6faf71c278e5754b2703662ce670dd049da8d6e280c2b84d6a9b29ce28980563c40e03381a49c54608b72faec9b272ef05cfa41957d9eaf3e944b22610c725d8efea90aaac6e782848d368ffc08784d7fe37ea1effbbbb34952def29fc511fb10a1282bb0b6334328e4d00529a44de3259b522553a07d524dc75f431cc9670127c15670c0df419826617cfb5ebdd8788d5f528a9eb1e61324eac5c1746f339aae2e2e2fae598642a389da671482128acf2d69814258d83de98f186468136868b729aa5f0874fef2ff2575a1f87439d64e049e4d0637e9c99ecb7275417af654541306615f30b75a6caaa563e4790dfb28fe9f0e7881ea2d885eefdba99efa7f878925ce7d33e86d888154a1b03189429fe20af8fa3a68d65ced9b690a709031121425cfcd7e1890ed9614f9dc3ecbd0e38c6c84e453e3204978ddc1ef8d7fc6cae28c61a472d8e089e23209f0c36e80c994af771e6505e72ba90e5543f6bad6dcd31fdd468b13533a0254e44797825764ac1f63747d8d6ca019ff16fa732068ee94be382c46b168050ba725379df31a98ab81ec8eb266a3c3f2e1cd95e5f12b3bc79b8b435e4d94098c6184631cec57e9d8913458889223a2a4541f34d2f9df380f34c3e541fc587f0a6cf08c82e99476060eb84709a292f4c7a8551bda3a9eb6735787dbb9d7f1e83937c2e0e49f2cf6e0ab0ad84c40fbafc3c7e61886a8629bea816972fa0afd0f617b6340b1af19e341875e97565c8eb0b25fcf68696ee674d2abdc29396bfd0f282543d2b72a239c6470f76d3b5bff6d1d064e6e2d06f9deef2aae8a259c034373efc820f9a2fdbce36cc27f35dd6386de3b49509d0c305757257f8674d958c580a09e768c0f6ef237416fd53c31511badb2e7cdfee636508482f01899e72052b46b5d844799cf94708520178cfec2b61c8980fa7dfaad8915b0b75ce6eb57ed4a01edcb4a35c1dfcdf8d60f3191bbcdfd522a0e321ea41c2cd87a303522d0f98b82dcbe53232ecbf0e2528de7e1be75569584bf2ec574687fde67ffe9827ebbe78f2e5bc4fb368f3c9b0f588c97f7a139bd82fe86eb605b8e29cee75d07b510da1b24fd62cd2fb366f1621e7dbf268b15937f7f7ea4acf6e615775a32c90733769996dd2c5aebe08ecba73e0bc4781d33971992b2764c1b08aa972859cb61b003406479423254a01ea85a348ef249d408157cc0962d1e24cd9c426e6e6a3784dec6fe935be1f6730b01e8683d97e21d8774b2e2655f85db7149e930a44524d4f86004cd687d8a528b6ceadd890707458cab62809110ee28f61a7277ed79dc41e573fd4a59fabf15393ed4c21bf4d5138ac843e80bbf5e1c39ac2d7f2147f35996eb51a9e835db63faaa196b8aef1823ad72523fbfcb35b5560582a48a25ab770e7528e4b3ef291e6f62f5fac916e2162b3b56304287e46839858daf322b0de083d1691d6bda44d66d085ef0d0ad364eebacdd0a43a4456035e58910d0b2dacce45b1c0beabc784f3620a3e4390c345df6117b86d4fc386523b7ceeaecc21233a2865ec6b63bffba6689fb3323402119db8f0665a4730b2e26ca6411db04f1bcc78ce6272159ed2665a286f1ad7758d6d90090a6fd320e697dafbdfef575077e282b825bd64a4dbcf92d1fc0c6f795154e8466ee4b318f2d44b6f81c52523ab68ff8367e01090c2623e00b4008e784049df873a35c29e0abcfae7acbf27236adba0b913d19a15b4af4996669aba4c656c317084347ca962ac8df15cd2f849f522016eb92de4de62944b917d88200ef9aa2def0d13e5f4ae09d2eb4a2d0800af1d704cb01975f6d59768a2b50e39e78116147fd6dcdfbc08354c1b4033bf6772fa127856a4072556a9f07bd7516d01ef41bcb519005c0a3b2a04400427ec033f1b52fe5fdc1aed8e2521fd0fff663e203defc39d7546281a98a502b8a470af16cc62a6581c9985d7ca516864b799fcc55a803ce80711484f6b81591d2402bb1499c95dfb1dee9846679c22853be87c84b4547138dc4fd46b4e79ad12773a5392540a595954112f0cb1d9be4d4eb3aaa4286b6c01520558d58587d9d7f0df3a0282011ce01c9c17111d10ad61b3675b1826c1ad37fc562bdde951b43f890555d6f74ac4fbdb9abbe8bc1e80bb6d52c13de8960a3ff8f65201265e82981dbe39e0d65cf3f1fb6c56e11f9786210383d0150a5e0cbbdb52ca8b2bc45c12fb572657380df369082685b3de9847d5014beaeef815d63e203cc911061eb53d89a312d187f9f02760bfa71083fb643f5d8c324c410070b7ebde250a185e7359837899bb1568a43fa3418f39c12feb03b148b924bfb98b99352b1fbad3f07ac8e4302f85d1fe9ee4bf7507972670ff8beca105cdeb037f1cc4f944d6ca869d0281653de5ee93a7362420fdba8b01a375ff08fe27873655953ec1c00f53613c6ab8b244e2fc1b6babdca5311428d06f57aa4882dc870165deff75ba877dd2a04d1799f26ebfac97a1be53a83ab77dbc2cd4aa45bd779f61b1283eae1a1866ec8a9c150dd0a4deceb2ddea1bc0f4206cd435600a8f190b999b952337d9eb2bdeb3aba2cb2e7000319056629dc1f00901f0880278509417223a3ea0919fcdcf12bff0771c7cc725bdca292068478ccb2e1f35ae8964e0601789a73e7e7c1769ba53f865910fc3d0085c922d7f7849d27b6e7503d521371351f9d7dfd5afc5df0effdf6ac49617fa228501ad72154a73e07781dc4b07765dbfa721d95cf1dc41e161cbd34fc7883a25e3ba6b03e504b2c3b98c8b12ff629b965c2aefc26d74faff7f784baf09c3fc38c487a9d1f5818261162f97e9dff70cf42eb5dbcd7bebb66d68f26d917ddf2a3efc0db1e3372b170b4cd18da507e44c467943f73648dba74db1053b53f989e481c3054bac22c6342fca2c26d30a859a1312e9c353bf921f68136de2b1589747bc765153927c31ebe749dcdff98b5da84c4b66085451b4c87fe1ba2142f98636bcb268c33f7b8c2b96a6525298814578377aa189dd73d5bb27ec5cd2110d8751c18a3110273df2595d4c3a00809bdeda70d86c4a8169b7010c9cdeabfbc3dd3266518226d0ade9bcc4825f18198c854de329fb8fe456dd3bf35d89bd9d2384f3f3282f6872351a18a2f852bf173ea4426de6d01b3ef4b4685aa82df7dc45b99617a8b8c8a0c65a2237b3eaae8267e1f6c453f485432529d973924a080f6a1cc2cc18f804f53209383ce3601ad9361afc331707be1c88b4370404cb7fe0bc538df04adc5c8d9ced94b4c474b19619a53dca3fddb434cac09ce10c0293fea04e8e1b19fd3ff3d174baa988d91cb604fadc59ac0b61f4f87bfd07eee20f7f3ffd96766dd6f3555cd48da7ecd71d2fef34ab082678bfc4dd007669b3fc7a937a5a46269baa7e4e4e43eff1b2b847ea70b6c6c23905d6fb2fbccd944251087ac00c35c2eedba30641797d36ef9d3cb1afc0e3e8930f5b605a847ee77106995bd44047294d04350194369c5a7bf246d1108e1d18d9a638be0c051f695ce86579db613cd8922e86c683c91800b9a34fe6339e0dd79472daa662f78f04f0151a3acd18f11faa4e1216222843b521fb998c8490ab8bab27fde36395b456501307d07b484b453b189fa339282a634af30fea99c9af8f877e61871fe743238b2cee6cb69dbd17d574b5106ebe4b0fde4ef42fab469a5ba7d62c23b67d857f1af6ac981c320db70cdbb6be41bbca60bb7a159ee1c85cb82e0a220064359c06c660b75de6b49839eea68c80283b75d9d627aa4500c0c0f21edafe4a2cf7ee079d5310479da06ba58b142614fe69cb236c51447d63db31cdff91485b46325c26d40dc6d608d46a5e2fb01df06064a022ddf6d5cce0147d5b2a5aba5f9fadc5e778010a924e00a13e21daeea2cd330f45536ef4f42c2e77be00bb53b3f9a93d3eb327dbf30baccee5d26849cfad654ff3ef2b035b78dd3ef42de3302e5514551a968a205b823dffb040ac9452ae3efb43219b02436d0761ca11470405510e534d56caeaacc40eaf9c47a39475adad266f5ddc813e71223800dd46fa7c02b078353f870049806ed7ba57b40b7c3c6272296667500c4b97dd2d7026698b6bc4985bc01be99e0097013a2632c71740888ffaf902a02bf644b38cf9a42528880d9dd142de967cc2ad3e1f1737f0cb8dc5c59c252496e8cfe4e53c82f4a28d9ba2bfa62b6415ba3e5e09040d7f3e3abfeba53e46575e8817ac5eca806ec8a84c7cf77c9fa86c9dd2940f5b96b25a92d4a8f894d4717c8f80a62a35a51d8511f1e822fd79e6fc27cc3f3097d9e3272447de6f223971657ded9e660ee4f8836359742ce7616fd0ca2de6656c71b212b34b8edc71ff36bc84ac4af58eb1adcba4b2c0cb31468dbd2c2b7ee6752981ee1d152c4e4a9b25b2ce87796820def34b662381806d2e4fc77f0b69d7a87de43d94d62a6a6526a7f8c588392890e96f9c51bb58b4f438eb5d197477ce9b160d1c898c89ab408b3c1d648be93b531a5bb4988592c5a8999ae3acbe586d947fe6dd507cddb92dff4974ae17ab99aad5aec9d07b96bd29489876f51afa67570e86b69321d9e565d86001514638403f86666dbf93f18e0a62bf65db333bb85a3ae12d8411aa3c2a423a29bacbbfeebb8a5bafd90436bfded16f992232360211086a3084d9fd1980dd96631820a2cf25c3ac5c19d164cf5ab9a852399491962100ca4fd640146b7ea5460b4fb9e46bf8d23d508a4eeb8a3e9fad8249ece3648c2ec7705a7414eb8e8d602549204cb437f589161fe40de1447d14efa4d738b775d0333526c845cef5ffcbaf5c957df1d8022176b56eeb198e7ad2dfc3d7ea46b125ed432cd04c77efc011a2dad8573345080d7c3cdf5cc160fbc86c4ee1959ee1b8258056b0f3d9343c22dbb2f7858c5f162f08cffdca1acc866aa68e5f1c00b74f66544e8a61e429335adf6f73e32fa87e48e1adf15bb6c7aeacc93713dbc31cdccc9b0e52f922842679494039c395cc1d95eb97ae4df3bb8aba9a2584d97a236f87cb22f00c0a078b045044a5c456e22b2b94a76a559de2672c880660f9785b76bcc2aaed780e05212415c6e73880ca110654ed155a1004af45d5f15ae8e5bfd4817440c5d3d5589eea2c6c344ca0d85d91460638b37f877ea4cbbed35ea75678ef2335a5922cc8541987cc256c8f58045028d33a1c4899cc32265c619ac782ff998a478996be6a0c5b102a664831b395a884f18e77885d860d6b236c52a8066d2ced25432bce79a31b23117f405ef4ebdf3517de98d288f8c3baf04b63b6817c46c14b646308e9f97170b7dbbf9d1a36480338d8eb7466df56feb6baef42cba75512954fd7e33961d247b7393726e46c6e94e156d5776a89ad3e288554470ca0bc4cf4d2d2b0c01ae4fcafcb65ccd6ead03df1d4d6577bb",
+	},
+	{
+		"228eabb5ad8b4ff13b10d13b27372bc2152dff149859ba47d9c89b741d4a5340d8fff5858a4576c55547007d7e2b3f94583ea8f0976237712bd2e5481c3988f5387e7ac2c3f18718388795b7b2d44b0a13f3faaa55311b800301c9203a511572cf8f349280bbabb9424070f415bbfe28aef8d20329ee842cef4d4c299e619b6ef1cf00718aab2accec9ac00155be2903b6fb07dfe98b0bd8d8580176b99ce4aa6be51cf59046c17ce1817d363fa63af5a241d48bcce064a438651af102ff9c6de4b86374fe24f1dfa66e16e51550dbb791af425d8fa601c70c1bb90e1a557bfe0dde730b0364eba9d2018ee751699ee219e13fa8874070935b29a1767e1d748bfbe796fe4b81a71e823605d39fa4b5b885f4610c34d1a090fa4106785e7a035a629958ad1b00cb9d36d171d575268efa1bef064fc0a6dfbae8e532466035a0c2cef96fe9f93b872f0cf804811e927b39818189412868fb104e2d56ae62f77031f0df1ae91aa11826991ca7b8af22f130a47a72cce36ddc319b32dffd294f2e192e490249ea1a6f8437173ce6392d16dda888a98bf685bc91b89b8ee1eabdfb1806fd61f018d1744fe8b03521de4bff86d4a811ca2ecd5be668e9c752a6c26aacc0cc9dd89d112785c25ca6a0a7a5267b4e37457c04a0626c8a29be30ec28ddacf47a84918bab164d07bdedae62132ab04a6f2c4e108eba9ab878caa4a1a7509521d427ad7f3dfa86fae8345dfb5e0d46ce3a94dec84f7880c7422468ea74fe0b4825b8c762b34d5d9b82ba96e0c7dcae01718ccac0044a87476ff031e3ee3c2c13f5f375a841d243c38cd9a354b6525527de1fe7e36a6e2ad95e5bbc4c97e85f8cdcd5341da777e03451838807d5dd2eb4fd15976783c140e21cfc2eb3e58e40c16374de0aecbe3e3d41c64417a472cba18762080a2348ec3f441bf229a932ea0ca7c816938655d0c81b14dfbf86aa600d0c68172fb0046ef51f601ec89309d43ad1eacd583f9d205bb1ff1a37a97b44b5e35be4945f52897eb2a74645b01a7f82054cda44e9fa9f9af9bad1a235155718713bacd08d354f3fdd95858db0040fb551e9f93ae399d5dc53a67e88bcd5a02d104dfd9d824cdd5fe262ed9266fc47b7e640f2c9d9c7a62c6d24b429fa55560aa254a824a0858482e771144d6d5b05539cf71d75bec3a22be75655e1ababec4dff9472a019f6220067374dd49252282e4945a407084633ef9c88d14833bd95335107d36afdf56a642cb739bf0a61ed53a6915baed78e9d74166ebc492b517c7c594fe6564550bb7108f43012551e65fbafc0a9874e46fb64b5b7aee0082a5d617a43b8bf9473309c6761aebc7f13b72ed460b522a6b0875b67353c705f99d1d9dc899870fcc90c632aba1fa9ced6d7a2368dc4dd3d4b38a5807415e00de6b9ea70525a6c1b67d04521efeeefc6c591fc5256d990a1123522864a029430bb7ea00dd80d283fdd6d61cc5b509221e28f73386803d97a38fb0182fd95b3b91353c6eb60ef2b3d5c8c0ab8dc9cd9be2b4cf69450d00e88cb0f0bc9a4be82b71148a37237ceaf945ab94c365625f58171eb15c1bb244a87335550d813d28f241a3296520046e65aff3291555786d7c871ec8a2d10d4b44429041c3cd6ab60f0def742de3d28393c5aca92b150697ac15504ee66d8a2aa01a6c63d7c719d6d4f94af2ed1d8670e3231a0e481095e425e6231c43ad36e3b7a3478f6a61563f5aa13237beb8a891dbb29013c325f7f91c1b055fb83c436fdf8aef49ec457946e6ab7e955427373fd9c743acfd4b9609569b591ec79c7ea7276de103a35a4a8a05c91f59e04689ba1ddd570b18ed046f785d7e4ff9fce7115ac814fe126f781828877208ddfbb2ebc919e6d1f6eb417f38bfbf22ac9633f75e58e560b85d88d0e4fad9b2e68c9ebf9675819d50c30c8982bbbc2f41e02690390bf0e16979b24e648bf15b18800aaef58c3c465f38cfd1e47bf1266c17b69523b7868d2138cb95c4bce0dd3ceb7c2267b868b6e12888d5a489fc0091b295b56a1c328b54fe1119aaf1e6d7dd52fa450b52fbfc8b84c2200ebe209060b655cad288562786673121691809366af37b76567762d1fc24f1fad3128b43c8d10e9b6954b2efcbe40124fc0a5b670dd6dd544e30263a551825282aa06be3817a8eeacf31ca8b25cba011d60b78d3d2462810764e4acb566ff371005f5481c9d36c991527143af2c44cc8cfc59c920bb4a281f2ed4d494d30ba4d900edf59e23be2f763072255cb6f1e8b24ab1d305fbfb2429cff8bda303617c034e71a17230d0e860420dbcf9fea4ab48557e4d50797179496936ec6c97686fe6d9115809e14069244d251d4bc9c8931e47e06ec051e709ba1df526b55d959b37a6f3408833aaac80cfc9cb99915eb7d83e26998f0da2492b986fe0f5047b2cab6e6d33a117df21e6a8ec7f394a3712885dab176a4d6095e5cf75dbd3f0077e5e74b1ff8b902072380cf172562884de852ff5f07c55856224fb3df8eb44764ab9284944b86ab6f176a863cdd0e7ab5616a14692f6cbf41bc63113b27689fc2fb145736aaf2a5b26d2bef3a2a59ef8bb3f3e4d360a4251d0736482e9ed7e189fc48c0973b6649988228c2ac72b23826a61cfa06b11f13c8555be6e433d87e20113eb74c94f0e51719a7b38c59eba300089d06b9bc2a72017668e5aa3153ca4282718f1762642e7c1be1f865cd9b65c6387c8fe496f1e60d5acbb78c2f71cea1f35dc955b1e7d1cdc9ca339765995d9e05dd729cdf58aa2a1451b633c374e5b6c2af1c8486ee4250a875e80e1f359c15130eb1e2575c0c7badb2af61378527fa24347ebb12c10bbb36e3c94619556b2c641d0ebb691b2706cdd667f55b8fff8fb46e3ac72f3682661a4bac2391075ff5145eb07d69d77437adec2d096c1c89208ab3e7a9ea6a0ff4a5bc1846b3683bd7c6ec4520c3c95861a5856b0191e4221c9819c67273c66729728f6035e79c0dae8842df4c0c27ada1ad18b34efcd55b94ef120762e87e8c5afdec80d5788e83f0d1533cdd7aea8f27f33266e007b274f6d48c59bcfad607e8b298be2b17322be88558c60033452826778f167f318b660607bfb2f285cadb385399636acb8f5350d819511b5e7931c5f8483529d3ab3fdb5ae2dde0ada918f1327c6c0dfbbf5ed3c8afef171910dd0169022b3cad5b08084dd5e8eb8ef1ecb17e48bf69f80e3db0ae1cc7b73d94b89696e3c3443ecb4c7ca12568201744d1858d90ff759f2d264d49edf47772bd0e0990c14dcf8c8a4c2dafa44dc6e92f4c66b03bdc4f68f28ca2d0811a433e184cced99a8e5614ca83c46ec18b47e0c7ae91037ae06c6d6d0f3dee19711c21cddafb5869416d23c5219296acda7774891877f3f8d46155d39f43ed10500ede3afa26943b83b800b54a9752250ec6ae173e920002f365d692a9b3a2f9b27124ac97b8e81b70e8c0bb7022d07ee97e962810962b03fc019695b5399f77aab414327cfc5dedd51e99453179c42ae85a42f8e06e0cec6f937224dd019c77c5a0ba32ad08107216a9c758138b730bd5b5f4b613f192839514a8621634d9dbd5840e728c1ef4a2c8bbfadc376dd80d13dcb327ce55ab536a43b570789f5c5e135ac0af79b54232613d0e989ae695aeb358c671ae71d508b58a793e19c58c3d204cdc9a021ecc634bcb0bd6a1917554ea3bd688adab8163260a914fc01d7ce05a497a5c5836cf9401cb6aa35cd008470bdecfb97a511c905badd01bbb4d0c05867661debd2162beeccd52399d5a70a929405293916f33ed0d03f8b850f4bdd77b1fb6283118d71de629577383c81cad086f4099ce7476cb787f73c96431a0df4156f7826fce9045f7e7c97bbfd618b845595203cdc8df4638430fac74a07bc5f773486731d8ad29c06695704cbe2882077a85d543551b7ba81b181ccb93d2b3071b1a38f3c762b42df8246aa64cecbdc772830ac79e766fa99e8c65225f28297a32526df9b51227bd368253737f013ae18435a912bc18cc4a95216ce449865e8bd8bc759dce9d4af52f9e789eafa37023e91946952202dfb7243cab7db2f9f98bb66f19750c547a2bf2e2ba92862ab66f33fcf465ffc41d23f0b891a3b28b3f68ea48dde6ad4802902abd22b0d7d9101bd61471c5d88ee9d9477b7cf9f6ac52e0f520c79278da22938745446f1e647ae478ecba416b941aa31f979d0633efe72910bebb8988de1d0013616f31c5da163eb6c07022649ac57422627a5642618f53103adc9918f9992c5b085e10d2744f9934bfbb994a710d6cd387c325e94278f97d5582864f1bb29a1400aaf674ea8fb99a3b42e4ac50418fd804a5b1471eaac4642d4aa338fd3d5d0dd84372b2c32c5cfe7f319acf731a9787b048cedee3833300dde639cb1386c8fbca4bae8d67fb7bd72d1696a0212e27e166e6b04a79e34b47c98502ed0bdbd8d61777537f72df569fe5ed30071b57e8724e98ccb88c07f0458cf32298cefb6ed672b255e581ac756789b57e950d57174bffd3f47bdbe4b168e7e3f1a6df508d4202d327947facfbf9526a9e5fc1a5abb179902d4584deae6cb2900391e080d3f3540b87c3a873ccfaee5b4aaff0e6516a867ea00b4d5e680fee6b91defc65c240614a1409bdd0f49c2c4f3c1d258d77abfc17a749660f49547adb236730e5a7a22fbbabdd8ca079a8efa5b605332db12f455868ab67a1ffd27d1339bdf8d150189cfbf6199c6fc27c05788138a63267eb8ac086e27286b4ef99ee9d92cfedab5ce9916675f128f206a1733f47a597232067aa12da20c7b9cab6575d7634f8c31e9a29948b528681f3f9c13b9f585ebfbff8c28a299a43e4409b31b6c02a79eeb493734fe5f9c1d9e3830572eb54229b5cf525768f695acff48c76b4a6e0936b7406ab69f06d33d3f04946db9d7966ea6e8c50ede5abadda28149edef5223a6938d5c32933070d234043feddbd65c81be218f9d7c497a1ecac30bb9162e60a9bbbcdb4fec4b212050610e2b376aadf58b3c9207860d2650d0310ae6606a8f1b266b6a13b68c3306ed413224abdf19371bac3ea1b964f28996fc70f666ff118c6a7c9f2108d327f5145919c03832f754de35f5979ae72130e39126499037d6fbb3751cbb4843b05d9dc91dd5fc1429da491f72e3069313ea243933b47109af247fcbe0c70f9024ac5a41815655ab309fcaa282d03596ba59cfee0e40f7bd657689453e98d562442fa4c585f970b6983a581b0b8eb1c5e780b3f5c1abb326213c6b5fd440c2187066ddf55f4eabf88804139392c45979440c6f05b7222bd95e963832d7fa4a4760273cc075e8b8feeccb917e8feaf7d3f766d9ae880487e69bc01872ba62b91b8af5dbffdd93fdc95e8f47ed793fc070a5991f2e9ea61439662dab218f643c1959171937aa160008a548f51f87b58f2c4fae5aed556f26bb9cd1dc2b3518458e2f5ec5d974c6e11a0ed639958cc8c1db771cc8cc8bee8727bf6452f47c9782acf548856a0e67841c3dbdb1c98572a4fc8e6cc8195a504019b4930d302a90dc20d8628ae6c90e0206cbb3d05025744db4e115cd3b650e5519a1624acbf226ebca8875b05183b2584e65289f8b9cec3f7d010cb9671a0e80bb70ca8763f1722d79e8decb6b9023baf64b5981e745c06546cc1e",
+		"ade72c2ea29cf829ffe99c2d63840b2eef9b51a9919c02128347d2e88e9f063b86326928cf6252ce4beefbae7206dc61a22d0b33c90d464d551835e3b73c1e3d6e88663deab80c35a607e4180ec079b0ee84e3b7922904e7423acaf976e837",
+		"43348cf32211d7daa300de8a4218543c8e3c7373ad10950765c39760f80b733c",
+		"e4709d225a552e90fb357413",
+		"562050bfb40451f27b1181c389508550a0f46b53d14ca73143da9dae3d3d2b466e9618db39e3219675d2b6eadded7dd9c741d7c9bf3c5619a521189607acbcf6b3964d469d966fa134444aa06d80749c873f0f976e0c5efc5be8d00a2729f03eda6a7b8630575df8b3a19388ff88daf0d00bb3e7c35a525ded90a4511ce815fe6c8904406cf72d7bfa14ca533566f7b54268835285c5402e22a63f98b5d90c86dae0a76d65eacc1ba85b3f5a1499d5f3432dd5455fab9e8bfbd266e99283c2bddf9b556410956b2f061603d1fc91194766f90da841699ba7da3d53ed5abdd8e98034f8fe734446d92b458a731aa4c578552ec1ac5d1baaccc4153a67b48a290602d5f955d61a08436b27cfb0786a80afef76e1266310a42d90feeb3bcc40ae5c4506432dcc92f7e5758ceaf277255401f5c5f4b10df93a249e38edd9effe7bacdf7fecc451d3b2cea77c9bab0403450c41929775b8c0ace46f6928f4d9cf3adf86832d298ea32b236d3201464e2ff506ef01da0e1e389e26e2b3ddc553b369b48d1aa5dd43edd5cab065e276aeff72a4c43206063fc7eea3bcc783ba2221f5b615a7a43a75cecda6bca5aa159e9208bf66af61e2e465c2daee630c4c62077ea6ef0e8b4b4e272d4e93a5f5284f9da463e1a60f815a8a31698ecdc09dff2b62f00e37aea5fd4b07a110cef27e12466c1814d3b10017cb9b8e12f2f38f10cbe31296de2570d5662b16639fcdc05db81e0d48178d055ef873501148d00903ec771400fa4873c5579dc3265028f531538f6dab1e5607a15c8b90cbfa4835107cba6f453bbdc71d08c7e423f58b44be38a9c8a610469f2551ee6177edf639cde35fe8e02f76b7ed106d691a876a4fda3b42d8ace3e0d3d4e026206c5d7d4d56fdda9dcd30fd7b74217fab3c617903f1aeffb8363443ed128af94c391810e327704d6f655e57dece97658d41e074029823850ddf7c5937af41c64465046d8544bba65c691ac69121bd272107f7eef8cfdb6a25da5da16d1033cede09129d51f6abfe63905a6fba9a64d7832fa35825447150595a60163af848eea878fb31a5fb97b1859efbfcc8586eebce8cfe64386461a9b88aa5efc1db43c64dfd5d4a45aa74803fd178f9e16a3f59acfb6e13a564d645cedd73890d0a82fb6dffeef527694a7cf2a89aed9750c3675a67505bff77de8d046087bd39a85c90aedb085e99baf04c7e3bf92e350b332da1b8af85550a00d68904ca426da61add864496d6ff442bb0b848e9aa463bb0c2085cff1a83a47d6f702bd184cfb5c139752754c8978d27b58d364bd88722b9097ee3a6ae28eabb14ca7c31e40461101e92448dbbc63b55cfe56efd078d0058c5e6146c73bcd949c4b3ec9f881b9a5f7b41ca83301261e0c674f2d35d96761baa00ce0675c082bf73dc52dc726a3e605067569a372d2bb47fc8fe1e74f00078ce6f352a6d9d97fd2834670ba3a45aa6751eafc7ed6694e1e07542860c8ea516f296ee901a3ee16b00b40419c74bf6db12c7230325e85a918f412bc2f6469c1a13a5aa77f028e327749efd05b91053f49d9f1edf49aa552c58c68257233a168db60ac55b4086ddaea275b078869cda7b69493c4b371b4e9c8361357a7ac7d3d3bbb464c960addfa8df2b208b21b090d540c440241598212d33273203d484e0930e22469c2a8e866579a4a2b3db8f8344dbf8baa1b97be0c4d976f6aaf14cc09ec52630139b894b2b6f4dad3a205a7b286253f1522b1d6e43bfa37beaf06f831c6f0945cefb2593b9b298da13b0d910582086c5d7e256ed4067bfb476dbe01bcddb437d46ba716d6ace2ff9912c8e460ad33ab3d8f97b7b08dd4ba9e01968d1949ff85b4b9d5b8da291fc0f90ab1eab1d246f67d76092b7a37528ceb388dd76f8a8f0aabb7490f02a2c8bc6498cb26350d859c466dd611bf0ceb81a8b7899c67742c22697ccee21c4963acb003d15c1a2078112bab05595917584e417db3872a0ff0a29138bbca7314449b19827525340370d7e48fdf9f7c6b4a280e78d00775a291081a5e78e7a00ff915015dd5af5f0a45690baba8b1b503bf85f326c23136f4424be4a559aed03fbc81400ac27a33dadb2155d1704950d98043dcd86df1eee78f3f266c4d14deb8126708f74b59aa15e8b497c6a52924a473f999aaf0abd3d148fee8503a1568efec7bfb0bd463402f563e4019cc9c9e1eb498aa54dcb659f43b86df0a34de4e51ec558bbbade3d69511d3fea2baf44f67e85ada7398d7f72ecadcd9e981f82b0743ed74bd33088ba4cbc85b0c99dc5382c599706dd2d51aa9f470c25a98e7e8248dec216a155495630662bf6ba0b7a4baa2cdad30e9ce3e1a65e3c23d69d5f946606ee8504dd70830aa5a8ddd84f10e064695469727d2efeb46186c9d3b7a170057636f05b9ec4c2de7d935fba504a1e7eddf7a5a95226b253b0b9eccec976ca3c57599850db40c27a51ae755c1f30d392467cb74e5c8235861d11d0f8461b0e1d84f5718d64ea92da62f4de184a6499dba473e82b3d197305de0e494f118a263237c7b4c0652327977edb427ccded35552c00a5804b9557ccf2bca2484d9da2c33f6c1bbf2c666ea10b4644a21e3905e5c4eb417ac3572e783428d23dd7222e75c356b99e8183d033034e29e618c90e66ec2f1e9fca47d82c1cffda8ad14c96045159d9437e91ecef41d24cff89009ff57e18c1a422860aa9cd31dd2a85b07422c72a5decc614a9742e62a4988f394421b6918e51c2412d749bb53b1e8fed7b2ef0873ffe14fa77bc366bbd5fa1432be465f5e25266c6c12b55df1f19b1a491acfc5c9019f122c422243d751d8eaa8ff721397915171556e999b34425f7d3ad6f6c3323b8133b4618c65ac16cb5941edc979472734bdccafc73c08939c0b1e306ae3015faa9cfa09ed6560269a1dc54c2c046a12a178144f4381f7b6fd3fd2d28f778d444d9f7a0dae00ea96c6969b78ef326a962d23275f1518f0e6a2469440612f3710b53538fe99a6179471be8c5b2d682ab3e9a5126e41ed6de000cd9e92fec3974e0f4cb2d2245d03d6ee80d6a793b16efa829d75c796f34d4e918250f457703559bb48ff78f0896be1bda403b7f1fd6a319d68478ff70d88238f2b8afc7d20e51757bb9db3bffb35a8040fc0db913c4f03d48619af7fd24cb8986b3e139058be3cc253b3de9b3bb3f8dab7b8818638279b2e6a0c29cfe16fa7250d3c74362ffa07e2977cf562140fe28afba8f61d81f7c73bdd4a2faddb00752bb049d0a57d05c6475c7387e6716ee31974169930c9fd830cef138659cf56f2212de185186c3d683fc6b7fd36e7821f69d0de041a569765066dc4a1934870a7b80f174e8f9e484942e62404a42b21658467873865ef94fc262c231527f39e82dfec91215947b99567daf75c6a28073ee4e67d4307e4b35b46f85433abd9812f35438b34598ff3b6dbd60b60747ad64565391df45ac80b272d0141702ab807fa27c6a6ba2f42c3facfae0c773940cb2943bb1353b41298258bc0d07542b69483e17ab9ce709e4160b80a0968dae9af8fc7c0324c753ca4a11a6df32dfa79a87b445c988154bb3c503e6884cf6d8f5e062a16b4ff230fbda109a6127d35e3bf2b29bfd3b18ba275af773b1981d603300035e046ef023d51874aa105d136bfcc9c7323bd0513a6b2b397ffea71afb7a8d4695411d86164917099eef504f6cff3c5cefb88f23f56c4ae3e2b09a3f353fa55630f45f06c29e8912e8c3c4f493f25eda781680585580595bba43dca9cfd400d9eaf5081d2c6697da59e012dfd0b875336b88fe16609c2e9876737b9afb868ed52417ed0c6b359d582d585ff82d98edd4e63c6b65cf43d4f69eee2af4819157b8a433966953862d1ff2c6d0cba382644a1b0033ddb7be3d1fa9a204042d7b821b293bd659dca980c108ad1db740800b9bd2fc1a163f9b4066f7604f160a7910bd947cb48ce6c81e680fc6571ff0cd12a3ded9c8cd560970ca5cb480a70a8322d5072edcd257604eba8dcf55f9ec97ea2b14fdcc72fbf615131836fb14e42b8d7171d0a06d2fb3caec2e0759e86b0d8f21e312d9211ed7fe0b48669934ffb892baf1db9aa457c07820723e5446420334bf6479f2099e01ef8adf273adfdd9ed0b741931284515d69c211cc2efead8339e450b13be71b35c36c1f00c2b8ed0cfa9792e422912e14b5b1455ef6abdbbec0035480c6cb69d21321d12ee19d528dd48f43b142cf0502eae5304ce52b7fb827552db9ab885b93e83d56a33346135aef11b7e48efca7cd52e2499a7edab0bd0562862187ff4599b2446bff11c37181092fbb05d0e05220ca6bc37f529d6599e8c29acb9f25616c27df291d4fb07430188e6470df7002f73cfe5fe6907dab0b4f90bb58130fe90241c29c6063a22c9f45d032b282eb92c93736692bd5cbde2a17552e942b595b08e6ba0c91a03b9079e9117fbba8f26ce6c5d0500c69bb6e22e3562a50baece49109c2d42b6714250665afd0f0a7e951182012f21aef4b917cd434d9ca22661437608e32666497516be34652500def6c28ef8f56f2273de5416142ce9606faf7df92ab779ed6aa74cb99bb1bfe758ffd344e1d31f479807326d1a7b98f6811e275545d69198707b0fbf027dc6a5e4815d62ef191535569a452c27c4e25ecf139df949d70dd5935bddc04f33b2f0bcf5073c51fc51c15067963a20569b5659f0e7413b347d6d5ee38a92b7e6e656c199149f07ebafe5281db6b1b2ecd9e0384b6f5a8e27ecea9a0249c61b16564964054f5f9621471a98de132e102f518c1419829e2ae2c8c5fffd1270f0a0b33a383437b0034783d50bce8bd7420c059d16364eecbd55b6ac8df8a70382734d8127f4f5895cc9e508b13c000ea053ab59b87ee639745418ffc566ceebad37a17b842d24d3423ac3f086142c622eceaadc4106f8c90c5dae1f52f407fa0bf1e6bf9385cbcbf3b61006ea3b1e66b693ce704577ca9598587f41e05d36d1de424e0e51290a5f2e2f99f1960c0253a046a49b19eef249ca2dda2af1e8dd78411088eff1e9c23c31bd20abd4fc9e7eab19500827d202f76270fe9f90e95309516343e0fca48e5a12182e91c78ebf2cdd4644629afdc90bbccb77546cd765135910ba1cd8a3e3c00fa77e585865e898bfecd06c01a0a4d7be483801099c61941c4967154af5620b171b426cf229df59d2944ba50754140c3f305c16956953be376fe6e7cf31a2e9c276bb09cc24c4b86b2b26f039b0d8511853adcb7feb8502e7641a34e3242bf2c538006bb1983345ec3cacbf219ef10efc1681d52e6e1b1c60bb556b6b8a63d1d1f6869077841d1b816f3165a35833e33d39a8c6e62a2f7c482c395768fc6a0e3cbfc7a1a6d64da53adad66c8016f76eaa73df1b8ef83012ecbe75c92a8e39b48169433f951a539b28a034d5fdd00639a5e3e17ef14dafe869064d130c90c68be4d5ceddabed1bc94e97e2cdf7313f780cd6e175a9e3eba3eaed896fe464073fcf07ae7b5bd41d58c3160f66ac95a76fdaa7a8cbaebb304fe3c8f03cef927a1182ac2281c3b32378813b24bb99e42cb0774331ad78b74d46b8ce48bbf4ef8431a82d4240edfd61b910c38570ba0bfbd4a41665117e6d5f5a97908462e62d0b76160d06aa56cc6e17aaf4607ba8263648f2a0077e306c25486f5f39a75",
+	},
+	{
+		"2f6210063cb3071b3d49339185c2cef8357b08ca826d8d1acd852540c16540f1c850f70404fe1f414853d3cd15a1c64a1cce149e3ca1b80926de4ae8438ad90bdad010decf2f201782f3e49794aae1b079f54eb59607bebde508a528927e346d4e444b1d736b34f65e198df2c36fa23c64f1f1fbf8b0b8ddb85d054bdb39b8297d0347f16f7be7cd9474c058e36294485386434b36fb28ee582e393367f15ce5f5a3d6641fbd31b331f10b1554a05da726a0f35c9b1b4af3498426b17582966a266cce452900f85af1046f45a4ccedca6ce02607fb70fa45f420f66aa38cd4c9f8a30e21a3067b940aebdaaeb7c77824a79e2ba20f26e70346dd6de96942b261e5c08288c7fe1cd1e9f680a0bdf8c46497f007a616eea95ccc17463559f8973eb919c68017e25100d9d1a196ca65fb615502076bf0b0c8bcc70ef22006895ebfa2243fba0791bae0625b762cc1718d1673948264454a200c58122d5e9b8b1e3eb05df8b7eeb297510e0d7dcf7f0be5f29f6756e4b177f109891e6825a9866359e35b10d20da7231bb5a0ea34abd0264b377d2fe9f420f27d3e5aa2e8e00541c46052966ef9b989ae5974e2054409507b867f647aa057f7deb19ac6929f0856005aec6e53a5f702fe6be403afed532b73d38fed73e6e551987f182a1e20801e7a6c8ccd1184cf0fefb4139fa166ca15395902ac40e7fed8661602853682a3b0ee307dffb44d0ea3012142a2880cb7c166ba6ea6a16c7e0882808db8023068f060e5ef1432fdb8331ffad6a7078d686d47d613e94291f1c4117e7c13aee4030fcaf223fcefdb300ed606b5dd931e4adbf45dc437eeb5fbff337812e15c15f026071423f6ef5305c559baa2ecd8ecc7cd498b043740ff3673774855d45d45fa64591d5b4970600ec91ab1b6f39d7dc0e709c41e49c355bd3b9d120ffb57095fb127bafa971a086135b917285794e83e9dac5ce76fb1a4aa4fb6b94a0dc3a9beea64b8817ec1e2b37af9dbd18ec30f2b6f6c12df1db6896c6c43b67a066038f0c4f17142b254f62c4dd1fedb950d07047919e397d06d033cb0bab6b61aefa6dee01720926b16beb9e8bc947dca9b8143b565da85d2dec182987838b267de9047f5b0d961c7971aaf54ae2c1e4aad61ff123c84e41a4566b2bd9e64247cf46b72a444d36bdced1a309b464ee5f4afe406eb68eb05ae51b76bf01b906c0ffbdeb440b11f1c9e3a4c3a809a1f7449047b356c663a1ab7f286a70d16141d11f2d151a4f06d422ab97cab539c1f9da09ad20c000c27b8fead5f0cc37329d466fa260aea934c154dc9c0a065df3d057a0f117a1c38321ae59226a8054f7d6b49a3753436c249838b0924f0e861f5627106dd8d3f0fa724a1cecda71d4a1267ed889b234ae4a7d5edcbc5d52cba389dc0152aff24d224c6a0f16dbd3b7f242807bf4b51a3f22690bdeb66eaa59e8766b3b265d784899d247a0ae1b58a06dd91c529e3691b09f9d9f55fc39afd4a00b0fc668880ef25a46a30861fba8cfd4b51262eba4138b41a2d13ddc71128c8c1242e49a51d6f49879fcfa7595ba4a4adcad3670b0b1b26382f03ff402bc70150f54bf513ba3e9a590e41b269e55616af297ebb3499e16cc8e46c0810330a602955553c0f93d668a1181a0bfd7021ad9a9f68ce39493b012da70a3dda149d0369f23f788616e0272efa322b6a54d804f340d32c890e2eb7b538f48f4c9293b584d22d0ae80d321607644271b81a76ac5b49d8e457069b0c3e909b8a222e3fa6016cb1e979e300804742f2005c68acb7b1849c088b3714c9c7af54e9de9390df0041c87924c8fa6b0aec6b6754171e059cba0d27f221f0b9d044a3aed8338dd8745651981e4b0329376f908b86ae9022699d495bbe3a148f7eb73d56eacb2e5e2180f63fcbfa680369f88eefa71f1210bc5b6b7b957f0a1437476a2112998033197673e470dbe7d9d476c97b95db8b5136f6cccc75d6e0ac1e4ace30e34e64fcc4d7e135b2c80e863ed701d3b28c25e982f1b5f8c895a4e6df7216c3c07abf8551a0ba0469c88aa7a08c7b5218a03b9b91f0935985373f65aa56286ad0e7ef2288a926f172b098123c136455b3a0f04590839e16bade7b6434a3cf048abe2612684c03dafd9cec39af508e63f07ea881014697bc24122058b5ef5d3fae835216d055f0cdf1dc06a12c95041d13ac9e15f235d11747f16ffce1cc3b8f508da520e395edd471f3759d8879ba9c2558b1188d822fd4739ed0546b0ce3bb9988db7c1dc8518ebbc62c4440e6e0653f917dcc13aca1864b71dbb67dbe7117474c936414e4f3cfab1f13eb05f3504484ce11977ab21ec523f97ba1b7ecb8fe384b634c30561cdb752fc67a2316bfa7e4d03f5f825d24a556a0460d8cfe0cc54a6f117ac52d553a5d1bb48031732716436675c5c3996b1939b127c6b0338bfaa29c7467cac9a127e455a715c9ce2b0c35a0d2f83a3d1273ee39399e6cc4980e610c752bd51652b96bf9cf34c7fa41fc9b13f5d55007483e4082ddac4675baa7822fd257452411b01de0e5e5da26e17539d64a89dd93c71d15a4c95b1a83039cb2d5f3f7fa04a817e48dfcbfb3de34ecb47f7592123caf27e17982fbfc8597af5b8aa6558f4e6c73db69328e47677afbe6ef8df82c3d1f0db6a108b2279f61822908d7b856432c32ac5ec0f3c53befab2a7ca356b9c2636f646b228b0a830d348be4ece2271814d477d4c73c0fb6e83a338b90ec4ef45cb25f7e3d6a014a9e8d2e8a6f55a383291a57f15667a73ea1daca31c7182523ca85a107efa2518d2f7f179ed4ba21fed479ef2be09669817133b2384bd85b155dfc1c4c9e6dd9ceecf06cc1ab8ebf7f07aeaae7441468b5471aed93f248a84f44c59be33274b11f651de010ab9f8fb24d3a99914e0147951c34280e7dd15ec196f9a4c86e55e7d373c7e31e6672d1b3ac6a45fa6c8c9088c0b8963d89f4ff1feea3e85cf9cf2f6c97128afd845bb131c6f62b3282bbba42745080fd457f1d3322058f1bd4be876bd01269546d1a853310b165926c1fd4e07054deb5d3fbe8f6007711d435994005aba95918c3df4cd390b165fcd139dd418ebbf661b6de57b655698a8a02ca8fad73e8c536c7110957c36e5494a831d536eccb97a2a9ef58fe58e2885aad170720ffcc57c7de601ea1cf723577a30aad8fd544317e33897c8b6c04e5191bec391ab990e197f10038c0726d371677e4a54c28d7ca5c6046e7cc4acde565b91f7f72af6109a0614160d3ae97e9257b8f71a4663b00c681e793cbb478306e97b0e04711eae7722b4845dadf2fff5bbe71ff24acffea2ee67df99bf62a098ddae9d4ebd3bc5dff04a2d9e3d1d83e8f493db3f63c9e24231b1dbe1147c79f21b0730c842f6983330c5c17dd34556d7e932074cfbe98f2dab5b0ebfd778a1e28fe2bac2d942f61a08b787ebfcdeb3d600bb130ca4922a4ffd38ffc4a1a1a7218451e45da4da67ad81ef898ece3d54cef877cb9d09f5dcf72eccbbc06e62f1e2b4d64059b0a807329780b155ce1614b68de04387d6108ef4dd3ab54b9da72e528d6eac3e16a360ae3421f3f23808a8b5e8ec3dbefcbca3c9f76905850033d78d9283bba9272c475b4e3b4d7643e62c2cc259ebbf168f890de88e82f8b26a7654ee31fe055e45609c70ae02b4942ee15678cd158f4c9e8d351d102ddf7a942458c6125e1457bea0d86ca38cf0c26e474b2b5cca77eb57ad0867cad7d25efc2b250e79396637ea3e948dbb855029cc9b452955bd04ad5a0d0514d4d773c0f298df7bc235a3ac64383a1fbd8a397a158e936b3ba81895a51daa89f51e4ae7a71a53794ff715a42f4fc3dcc9fd56df7bea4ab782534d3760e7b15605fc4dad16911656983c0ab77bce9445bbeb1537c55fef57a32c8f1404306a0a2ca7b73348cd99d0f9948875531cbb0ef7c036cd201614c33293d746c44140e0e8f82421c5bdf2bf428b249597df949fafdb5ccfe1618323f56a6ab9abab9a84a3beb6696ca918af244d34cc1cd95bbca4a87c860a0fa9ff6a04a905b0338a53f230bd5ee9c60e0e0332ca200c15dca0be5936b858d0a7b2e540b8958432e9767396c55d5cc35b60062580023b5cb2f9a5e9a1feba59a19f9a5a251e9d0e8500955a5df21da95213ced2260a2ed8f3d4b295c36cef750c89cf21985c302d5cc577aab7855409a912dbcf1d0a9800df4aa692a78607a40fd6d5a82305c58fcb3d2a82b27e8c5b91681aae62a2bf31ed55c494dbdc38eba30e83c6044945df76705228eede8470369f2e9941ddcb2f239fb3ff6bfcdb0efb5ec50f981adf0e8b213769ffbbea364b08cf8cd69abbfa2a6fe9865cc48558134a57bb5526b9d047e14a379d246de82d3d64f3c810ede280c768dd8bee25af287d5a8d94045ddbf5981382bc716ad9aedfcd66e0ab496172a24efe80649db8e1e83675fc8451e22c6564d8d6dfb285af7fec802b35f19dd8308c68952a11770247fcfecc4ed0e8a445c17b1573f0b4e3ed350f13269ceb572943fc435563459d5044699f1542335b03be6077af156b8c5a6a9f71078ad820cec4642427a9b187ee1b17036d5a5e6108cee8a7d444342eaec3afa64e77c71d3c2b3153d4e2dbb30df2b66b4d14cc45d3a4eda7e911d697e5763e23ee05311a20626df55549b8533c6ebe79737abf472f9cff08bec590943bdeb819d3f923f45b81f9a0cba1f3f800a261842d10cb4cbdba456c7fe5f0abb4a8b58891d97cfd6b669e2708922f1934809d51a1589e5f12e3bb82c9ac3e7e44e3f6e6cd63d428da624fd2f46eec38ff798a90d228efe50c9b67c63796347c8a2b53478f27605999a03c8e1f18b70e92419f646a7f49670aa12d324751aec17d0208fc296955b3098241189af8172d39a6819415cafb107c1842b369f174d6f37dd31cd728dfd0ab10f93609006342b6e4d6ccbfd1ed2bea2fdf5411442b04b1fe218916f159b20242f80b535b4e0a3024c6eff6a40bd0d3db24e51f5ff9c14e1b4a650ca4170ee70f0a3a5a58349a7d0b7a63af86347351696870b95231f76d8c5c6a20736907726341dcbb76672871d18c2157c094b929fd29d34f5bcaacd82706f89a60000cd341d98eb830b73a12335b69f3e0131ded3ce12c98bbd960d2d0696d40696a13ab43925374498d868cd8f070c9039ea6407fc2d92b9c39fe7c935bbcfcc5c0980952fb7dac79042951f49a1af828b138a87401c4104bc28cdf1e39dbd3fa63dd4d5f5ae9d85f032a43ad353bc5e6746e5a76326ab1f4e79103116ce70bc0b459200f32f85e461291e347dda92e421778b849e37a3ecb0b31ec6818e828dd3148dc74313aba43cc9d8b9a36a9dc4e229488060eb6c109f8ad6201958adec6d3bb3b04e5e558a272d44cb98e18f7a0ad8fa6ac3667a62f150830aa930f6166baac6b9081b44304988fbe1698a5b746255de26bb5988aca90bb6523cad68a7572f615f4aa58f932d8a749615cf0a7724e99de042268ceb31433e6df0a61547d576a6201b36b348c028ded5f7e94d1cd2eafc141088ff42cb3dafbbe4c402b93aa9d955df8d9d9fb57c75ac65c2c837acc44bbd4d4aff1888aed46c73d625ad7fff035e8ca0fe411c73ed8135b6b8e17a039ec74e9de0d64cb442bf8a676c0a666f68f21066332cd921ae0ed766f0516a8e19b82cf98e78add0373737a3419e13aa902310c44feae5fdf8bc64e80dce772686a31f141bcce452041bf545b908ef4a2b000e7beaf378e2afdccbbcaa42e330e5024400cf2852d3444718",
+		"fd5008477b0855f6f2486fd4f74b9fb4f6e19726c6996bc66893183bd76054d5b05c1c2b64722256ba912ab2dcca66d2abfdf972966438fff7513acfb18ea461eac08c4e32aea4ed3fcf9f1c9905ee4402e7b6984bef974340d212f160b6524b76de99a98d3e96cc0d35e8a63ad7ea3cbea1d40a906c4dd03e5fc19e1513e9",
+		"390a5e75c9ff4ad38fb6205ff47f209294337c1f25ff54a3c01eee8e1e220257",
+		"8bf183347ec1ca4bceff3374",
+		"19fa2641519e21293094e9d767ee1237f9e0715dc57172794867c3bbe2cb647f9b28a8d3f85c0ff557b91bad66f5ea16e0107757b0277fdd3ca05bf47c19bcb92a958a57e8c142a51af29bddb20af84377b6db65f77494e0dc4d2634a776b3a5d777319873bc0dacbbd4b9ebccfae849fa7e9769cdf54660ecca0d5cf4fa5190713726d54d02b3a3f21857125b8a808c0ca2f99d11dc430ed5113ee49ff8f00bcc08f0370dd510e8100e1285659a7b2c7457a6049f2af7786c4db1471ce5bd164e11c7a2165e83e03a135ae2b3429f82f677de044a067e99e0bda2d65a7270d629c00e1d528212d3aeb2896e58ee5145a93ed06a9c00705ad5c5988d3a192304c1d17661d45257c5d16799ef70771964435b12e3b2ee9d5b467c3b1992f45b7a59871b40d8daa1c280747ecb3d170257b91df1f549ce6d66455b5b6f60b7c6e95c92a67e20cffe8599ceb183de53f1dedfe19bae836447af8e053ba419660e0912cad064d6125b9e978e8d0d5f28f8a4e43ca3cdf2d4c0e9a11221d8184e9eb6c90761b0beac82d0d22793279aedb1c7db3632adbee323bc3bbde4801152694831abf5676979af26af7dcbadfba1cad1306b635840cbca76c558b37db0803b4c12befa27d16f21506b07ade4a838d6beba1816eb29ed5e3c4f132a752fc747bd9ba879156e87e6c1584e911da9f796e1fa4a055e427272559e4bd6d0f54b8257100f8a55d84c27b702bb1fe2f995425c85fd48b0a0610db5b39f7a5031407a12dae9f508b21b1378f14952d1beb2dea81d016b2d9b7f1a67b814569b69c0e619adea02a8683242d63a11d3317d060e5b4d85df5ad73127541ba5314715d187990735aa81f438f8b94070ec506ba536274d98b766c1694e54367891a602b99e370425b47a70b819277a249fa429c5bbd0530267f987e6022f25030c30f3baeedc0d13c95f3d5e4b2b87465d179a3a23b9f9e76a42ceea55226ce072f9488392f40621289124d786109d2498e74fb37e2ef466fe8bf3016d96e34204c32978775765aa80461cac48518157f86d59f6187bad4ee62fba1ddbe166b29452f4a59af1e057300c353440644a8e40ae8171ea028be2fa315804abf518847c7945e8228b7766cfdb08d3a3116b59aab8e94b6d8c8c9ef442c2dc7f923bc2cd3e5c663baca7dded976bf191fe36da16948c89c385fe71434f4aa5dd15fe0e925d2459e3b068b9d82a9cc8b8f9786bd9f5fef9baaaf2d67027d9bfd58bb2c58ec7c746b747ab62f9242e4b53ed14d6fc75f5280eca0de23717c97a2293826e19cc8eb47f946421516c349dc4ba49225b91e4e868874bdebd373700df1f3792aaa140597e58b88f90e163397dbad3941705b53d754e3e0c9003df836a7fb8d23f40362fcb5f3947a4281b24240be4ee89aa8e917b194f94345eeca224df0adc15f22a617b6427f29410bc48ea3f92216163785723efc36301d23ed52780c6fd7924bcfaa03269b13582b7c7ea9c0e4a451f38a469fbdb585dcb7c81452da77945ebe27eb26ff6e8c7b2decea289aac5af74746dc257c9bea44a0847f02c4f586e1d76f39d5bf952355a0875f177a666d1d354ad86ce5ec0aba2c2b20cab050eaffd31095395132f5af80a2d2d53b77bda49f948bbb37bdf31c8a690476488e14e542ff6841e7fbfc2eb84795696562d079dc1612274b6dff362567084f793f0bc2dd8de23392d05aeeeeac6991c9f74387153a4b7da94790375e336a00c8293bad0fcef2dd1880e7094e2e53f738247c860780ebe308410ca02ae409ae720e841f48c9677acc6e7d4ccd18c219c400f8b7e1257f692e09eaef96802b17a1cb7d93eb81d3bfcbc7af4cdf05b98e22556b3d1a8b56d6d83bb5f5724696f8f329839dbe477483ec3c09fa2e0628faeba1bf285c224bea3f6cdc7bbd768133c6ef1da14f248cc3b819b196588811b073a7291817bd1e89c65760435d8d17cbf9423744a92143e0f956e2977b39c54fdead5a57f3a04a0facca01bbf44d3b1fb9c4fa83ae1046985e3f26aa0a437999004dd8adc04c5111759849f919b93558dbc559173a23b069b59f800096d9fcf077c7640f59170bb9a6fffe64778bac272365d27ea62aa956559e90edd3f6393cc8775597bcf7d91990ab9511973d948324a27261059e93f4b5dd2f70caf12e1a08e0493cb05588618764391f355379578cf94dd33e616136eea997ec11c0d4ff064ff51a767e5558433a2e3a9a74c232d8e187f47b8cca010709eb9fea0dac8f1ea53bf18822e154ecd929c83b0eac366e30fffbd5ba6a46d734f58d26e7f5df538e18b3d827884aa857a680823131bcf30a76f1a555bcabb17b02b53aefad96fe76f7312da69719434c580d3ff1bcdcd594e6375935003d5d732cc577e11ea2abb1d04259f50aed4c3af9866e8c4a52a09809046ee330f05c4403acbc297a9416c5208fadb31ed4eb7a3b01b87bf08c75cf44c2b0df84df30872d021d6567ea649859268e5e1b5b6405e1b41e350a32c1af13722959c17c01b52c42241313b26b25995a1c89a53e248488724d280647226195746901929501df36d1e94815d7fe6c4ca2731f3181293217f71b9d7f59c2474856972013924ae4796db4cbd22d8905a6043c959941ca6b556c53d1688c439036c715d33a47a7dfc2fe40e53424c5093020d2e85e4b04aa4c704ea5bfe5a2384878da38319c59d41d66b6add2a443d9ea11edd8d18fa41004251653857733b388b453943eb33df93dcd5d549757fa2967ef0f9a5105836c48826c47fcccb2d9bc349032b286962136b848632bdcf186a08cbeaa52d195efcfc3a440bac154971d11ff4994f293b14fb8c3214ebe7ab8b3d0f2fe0b03ed7b145fafd7730a173e3cc1847f0cdf2cf629f5ea81a07bef716b1a67dd9e3b7a52fea1aaa7a393f53b5bdb5988df78a57a9dad19a8253316835acab8a6b9a9fb42d97bf29b2443322f46de386fd82bd3453ed68e2370c6eac4497b1bde7b42d569c452f377bd38bd50fa5a6792ef5c9ec6c647001149b86fedb3e2f18d4271e9cc4801aa16ecddb31b6a795fecabc613bfbc8e4f5636d71e74595c841fd11b6a6bc7f169317c1added56b82a71fc36d774bb4d661685363e9da5fd2e1f357006dc5b5bbf8b42ee3f869e75a541586fba558a8f490d641b78c27368b9b4c2db046354e9358ae9140e91cd95ebeffc6c0d2676a3ff4ab10d463bf32bed97023a80a79df191ab9858c43537a03072a17c30b1bd99efbd361590ed6b7d5b0ec4e2326fa35904ab9a48596f44491cbbc0112890f9386ed04dec30126be359a05e99b2b77fa2c8f6b7460a6cd590d71c73b2a1b23312ff89306b6e41c76ddc0a099bfa79498e36ae5cf0c560b8854dff32d2b690ce0ac4aabfa723ac6f2e97ad1083235196b464ad67fdd649aec01695d55c8b4bb198f30630ca635aa5a1915f3718341bcfd8b522f764015fa5479004d28eceea7fe67df7ee24a97a9708d528b89589f1899f13242a0d00f7464c3cdfce213699340e754533b934f4a8410224e111f31cf8e54d7b5e90cd8c68bf96edbc8d183894deefdf4fcc1a83162a3f6341dcd9a9aecf171c0df28257a68b1af1b67c54c43c3cff27fed89cc64bc46e23a49ec74a9efbab7981d9f0a018247441e4f0f5b5f68ba9325582f92de4cca4a5f878a0c5c387581e64324e3246d8f3205c838a29f1abeea24446e496421f0e742d411adb55f70272ae4a992e825a3d327e44b8b3762b25aa451d07eb4eac0322b431fa676462632daba2aba7bdeee1b438f051d21d4b1897e2ac2f95ee7c23f9996a805de8fffb3b30b855cd6c5b84c011accf4bf94d304d944079f04b5cadf8fcd6751c22a0f9165ab98998b2d89e6514641f1f3b91b8c0bf057d69c3d893fc4e041e06a2229e2ee58082ffb58cb920972ede58483287d0ace94c1becef26a410b93e4ff402e61dcc574b790d49679f18f4e2004f8b7cc357faba34a80e56821bb5b883d1a8b49c6605002152f270bbc36bc79095644e29ab08cc988deda765d67e4fff12b726d5de135ff9d0cbd9d5f9d440e548836633b93a38330d638468b59a32642da3375cdf70b062d14b46a78569c24a706e179baa2058dcae5c61fb6cadd9e015b017f26e9dbe3e6366cf5f1ec839aa3bbb21dd6c9b8e910245fa95b09b7d6cbf08a4c6c84bef257a70389be962dad14d97a893c128b73bf6580689e540d004f21edf8403f36b1ad7c9a2e83ffceb141af59700c316c8c1e3347187f24819c2ff0c9f9a2360dce354f3374374eab1643d2d8831310a8e3ca6768200ea7759822b82f7027cd450479fcc7f6d04802b15735a137ad489f1e1ee78434a253a9dd16684ad58fc91960cde6754f82e8b38edd5e798fdbbbf8fc2e2380a4e21dd94f8c1c063b18f29d8cd8d89f65deac5640799d4ca2caa29c1e72ad8bc417490d11e4051d94956fbc74289857e5f8e9e87b9a2d83074a994de0b10bc7782f6650cfbdb8c835c81cd88bdce5f04ca939b3c5cd010d4dc5d51224fcacbca9851694b8bf55b22dead859d023eee5a7ad3436a912c3fc0284456d5d72ea5f1afa8545c856676ac2dd9a057028bd3ca0f50e7070fa74152f13997c95c1834c3e67504f1a4165d2b49a96919b88f72caed60f56ca7ab5a3204fb12ad3592c725fdebb048732fc189c7dfed185c6c184a626e07d7356860d00389862d5b9701eaa4e5f7889e6db0f54633369b8d26805c08471de8fc3f8fa1fb0b0711d9e015add5373f7f8b64abaddbac3399c756244b1b07c579d33e4967e5e0cf16de29cb8a7efad07ff9039ca305772a6e45c76bd9b77e24949556766a8b8425c5e595efb431bde4ee222f9eb3fc2d002a1e2d14db2b23135266c942eea33bffd30eb0218405373240e0cd3040436ca895093bf056fd001c00ba59d90502042e6e6c0167105051628895c8164c9ab959400898309cabafdef12be53604fa57df44e0a90a81bd63c331291a93bffefe809e80db0679568f6e94e0d8e2edec0087c35bcb3c4f4725e6013bcf197156cd9d90612423348123383e45c14d27d8833f56ddb04083c069fd6e282fe69c940840f5f747dfb72ad72fd8cf9f3ded15c9e2f4727fd60b4f40e95dbe77a89b47dde7d5326942600554905d9dade9d145ab6da802643f2081678392609c2fdd1b79dd8caec137cbed315374c6f05c0758070f3bb17e23d81ccc39c6aa89913897e487fde889c5aacd422278f8571641cc4f0a93d9768aef9e45d6bd187d1ba637ce0fbd3c573d6778cf7bf5188c00dcdf13be3fd599143952b376220283e34e014e83b214bd5f64eb0ecb098ae8bef883949907cc36e22ece60b893b963cfa73d120513e285aaf70ce5add34edbdac60b3aa7b385b90e339058fb9b3cf984b06f79788016035c5ce490f2de7995b98a8c1c9c80f29603ae2b7fc41886663163e604275cb085f8453b27f4d795b9bad19ade2f98a1c99b43a7581bd991e5d0e5e1a6e713acc522ba9fe8302658a9782558e35436e714ac6bc85ad1d3cd008f24106901fa954f5fefb61210d6f8dc9ff35c480f1d14e59c0e501917a31ee9d00c6bdb06a00af5a8b08c3928cc5f37476248223627cb77eaf0e96213cb0a13e97d3fe9b9814d462690e8d68d02655a32fc271ee73db4f88a33386ea88a5857e15a28d9b3e3a96f00c7cd85aa53f9282ab8c8ca6d6a8afed43aa87fe7fc1ad59b0f0db2dd25c20af96e8c282c19fc883ef01a4060398926a1c82f07bcd3bc314580d7636b623b7bad8ddba05850291a6344df0f346fa4a321a85ee3e9c",
+	},
+	{
+		"67c6697351ff4aec29cdbaabf2fbe3467cc254f81be8e78d765a2e63339fc99a66320db73158a35a255d051758e95ed4abb2cdc69bb454110e827441213ddc8770e93ea141e1fc673e017e97eadc6b968f385c2aecb03bfb32af3c54ec18db5c021afe43fbfaaa3afb29d1e6053c7c9475d8be6189f95cbba8990f95b1ebf1b305eff700e9a13ae5ca0bcbd0484764bd1f231ea81c7b64c514735ac55e4b79633b706424119e09dcaad4acf21b10af3b33cde3504847155cbb6f2219ba9b7df50be11a1c7f23f829f8a41b13b5ca4ee8983238e0794d3d34bc5f4e77facb6c05ac86212baa1a55a2be70b5733b045cd33694b3afe2f0e49e4f321549fd824ea90870d4b28a2954489a0abcd50e18a844ac5bf38e4cd72d9b",
+		"0942e506c433afcda3847f2dad",
+		"a5117e70953568bf750862df9e6f92af81677c3a188e847917a4a915bda7792e",
+		"129039b5572e8a7a8131f76a",
+		"588e1356fb8fa32410dad99cf7922aae47b4042502c92f3afe33dc22c1c2e90caf22bc37a254f8dd62a09582c70194f9616982639415178e9fe95740c0f1d497a69b69d4924a7a15290187f9c8acf09cf5b3b3188ecde2d2807207f5bb6a6d3504314b1b47684cf8ba8807eb9a3c497c79ebe1e4c1eca2aa90328563e201425227fca8ee05dcc05fd6c98128626c1e71d2fb3a21860567093db1012dfabe13055c48219d2a301c8a5a49033a811d8d9413bafbb2eefc177226fe578e93c2ef1f309416dc98843bfac387debb1b610b1d2366178ce7212a7312057a3d058357a629f18c78e129e60979a2310455a76207be5611e8b4b840629564020c17f5c9446882e23f610e931246ec434e62de765bf22954cfae02b2ff4b4086fbbd1b6cec23e45481eac5a25d",
+	},
+	{
+		"67c6697351ff4aec29cdbaabf2fbe3467cc254f81be8e78d765a2e63339fc99a66320db73158a35a255d051758e95ed4abb2cdc69bb454110e827441213ddc8770e93ea141e1fc673e017e97eadc6b968f385c2aecb03bfb32af3c54ec18db5c021afe43fbfaaa3afb29d1e6053c7c9475d8be6189f95cbba8990f95b1ebf1b305eff700e9a13ae5ca0bcbd0484764bd1f231ea81c7b64c514735ac55e4b79633b706424119e09dcaad4acf21b10af3b33cde3504847155cbb6f2219ba9b7df50be11a1c7f23f829f8a41b13b5ca4ee8983238e0794d3d34bc5f4e77facb6c05ac86212baa1a55a2be70b5733b045cd33694b3afe2f0e49e4f321549fd824ea90870d4b28a2954489a0abcd50e18a844ac5bf38e4cd72d9b0942e506c433afcda3847f2dadd47647de321cec4ac430f62023856cfbb20704f4ec0bb920ba86c33e05f1ecd96733b79950a3e314",
+		"d3d934f75ea0f210a8f6059401",
+		"a5117e70953568bf750862df9e6f92af81677c3a188e847917a4a915bda7792e",
+		"129039b5572e8a7a8131f76a",
+		"588e1356fb8fa32410dad99cf7922aae47b4042502c92f3afe33dc22c1c2e90caf22bc37a254f8dd62a09582c70194f9616982639415178e9fe95740c0f1d497a69b69d4924a7a15290187f9c8acf09cf5b3b3188ecde2d2807207f5bb6a6d3504314b1b47684cf8ba8807eb9a3c497c79ebe1e4c1eca2aa90328563e201425227fca8ee05dcc05fd6c98128626c1e71d2fb3a21860567093db1012dfabe13055c48219d2a301c8a5a49033a811d8d9413bafbb2eefc177226fe578e93c2ef1f309416dc98843bfac387debb1b610b1d2366178ce7212a7312057a3d058357a629f18c78e129e60979a2310455a76207be5611e8b4b840629564020c17f5c9446882e23f610e931246ec434e62de765bf22954cfae02b2ff7c59dfe246e4bb2d6a8afcebdc2beeaabf2a3f43f95a5ea639853f38719875ecdd2bbc0d81bb2a5ed59553b1e76b6365b74f618f685eb7731024bbf6794c3f4c7c5a1cf925",
+	},
+	{
+		"67c6697351ff4aec29cdbaabf2fbe3467cc254f81be8e78d765a2e63339fc99a66320db73158a35a255d051758e95ed4abb2cdc69bb454110e827441213ddc8770e93ea141e1fc673e017e97eadc6b968f385c2aecb03bfb32af3c54ec18db5c021afe43fbfaaa3afb29d1e6053c7c9475d8be6189f95cbba8990f95b1ebf1b305eff700e9a13ae5ca0bcbd0484764bd1f231ea81c7b64c514735ac55e4b79633b706424119e09dcaad4acf21b10af3b33cde3504847155cbb6f2219ba9b7df50be11a1c7f23f829f8a41b13b5ca4ee8983238e0794d3d34bc5f4e77facb6c05ac86212baa1a55a2be70b5733b045cd33694b3afe2f0e49e4f321549fd824ea90870d4b28a2954489a0abcd50e18a844ac5bf38e4cd72d9b0942e506c433afcda3847f2dadd47647de321cec4ac430f62023856cfbb20704f4ec0bb920ba86c33e05f1ecd96733b79950a3e314",
+		"d3d934f75ea0f210a8f6059401beb4bc4478fa4969e623d01ada696a7e4c7e5125b34884533a94fb319990325744ee9bbce9e525cf08f5e9e25e5360aad2b2d085fa54d835e8d466826498d9a8877565705a8a3f62802944de7ca5894e5759d351adac869580ec17e485f18c0c66f17cc07cbb",
+		"a5117e70953568bf750862df9e6f92af81677c3a188e847917a4a915bda7792e",
+		"129039b5572e8a7a8131f76a",
+		"588e1356fb8fa32410dad99cf7922aae47b4042502c92f3afe33dc22c1c2e90caf22bc37a254f8dd62a09582c70194f9616982639415178e9fe95740c0f1d497a69b69d4924a7a15290187f9c8acf09cf5b3b3188ecde2d2807207f5bb6a6d3504314b1b47684cf8ba8807eb9a3c497c79ebe1e4c1eca2aa90328563e201425227fca8ee05dcc05fd6c98128626c1e71d2fb3a21860567093db1012dfabe13055c48219d2a301c8a5a49033a811d8d9413bafbb2eefc177226fe578e93c2ef1f309416dc98843bfac387debb1b610b1d2366178ce7212a7312057a3d058357a629f18c78e129e60979a2310455a76207be5611e8b4b840629564020c17f5c9446882e23f610e931246ec434e62de765bf22954cfae02b2ff7c59dfe246e4bb2d6a8afcebdc2beeaabf2a3f43f95a5ea639853f38719875ecdd2bbc0d81bb2a5ed59553b1e76b6365b74f618f68a12d0f1cc99e132db9014100d9668c91",
+	},
+	{
+		"67c6697351ff4aec29cdbaabf2fbe3467cc254f81be8e78d765a2e63339fc99a66320db73158a35a255d051758e95ed4abb2cdc69bb454110e827441213ddc8770e93ea141e1fc673e017e97eadc6b968f385c2aecb03bfb32af3c54ec18db5c021afe43fbfaaa3afb29d1e6053c7c9475d8be6189f95cbba8990f95b1ebf1b305eff700e9a13ae5ca0bcbd0484764bd1f231ea81c7b64c514735ac55e4b79633b706424119e09dcaad4acf21b10af3b33cde3504847155cbb6f2219ba9b7df50be11a1c7f23f829f8a41b13b5ca4ee8983238e0794d3d34bc5f4e77facb6c05ac86212baa1a55a2be70b5733b045cd33694b3afe2f0e49e4f321549fd824ea90870d4b28a2954489a0abcd50e18a844ac5bf38e4cd72d9b0942e506c433afcda3847f2dadd47647de321cec4ac430f62023856cfbb20704f4ec0bb920ba86c33e05f1ecd96733b79950a3e314d3d934f75ea0f210a8f6059401beb4bc4478fa4969e623d01ada696a7e4c7e5125b34884533a94fb319990325744ee9b",
+		"bc",
+		"a5117e70953568bf750862df9e6f92af81677c3a188e847917a4a915bda7792e",
+		"129039b5572e8a7a8131f76a",
+		"588e1356fb8fa32410dad99cf7922aae47b4042502c92f3afe33dc22c1c2e90caf22bc37a254f8dd62a09582c70194f9616982639415178e9fe95740c0f1d497a69b69d4924a7a15290187f9c8acf09cf5b3b3188ecde2d2807207f5bb6a6d3504314b1b47684cf8ba8807eb9a3c497c79ebe1e4c1eca2aa90328563e201425227fca8ee05dcc05fd6c98128626c1e71d2fb3a21860567093db1012dfabe13055c48219d2a301c8a5a49033a811d8d9413bafbb2eefc177226fe578e93c2ef1f309416dc98843bfac387debb1b610b1d2366178ce7212a7312057a3d058357a629f18c78e129e60979a2310455a76207be5611e8b4b840629564020c17f5c9446882e23f610e931246ec434e62de765bf22954cfae02b2ff7c59dfe246e4bb2d6a8afcebdc2beeaabf2a3f43f95a5ea639853f38719875ecdd2bbc0d81bb2a5ed59553b1e76b6365b74f618f68d1f05b5662cd6e04de896d3ef5dae4149485a5a2093ff4ec74b20b5e5bf8e61b5c65515938c202beab3eea5a498d2f32d4d00a24b826b6efb16013ef54cbe170",
+	},
+	{
+		"67c6697351ff4aec29cdbaabf2fbe3467cc254f81be8e78d765a2e63339fc99a66320db73158a35a255d051758e95ed4abb2cdc69bb454110e827441213ddc8770e93ea141e1fc673e017e97eadc6b968f385c2aecb03bfb32af3c54ec18db5c021afe43fbfaaa3afb29d1e6053c7c9475d8be6189f95cbba8990f95b1ebf1b305eff700e9a13ae5ca0bcbd0484764bd1f231ea81c7b64c514735ac55e4b79633b706424119e09dcaad4acf21b10af3b33cde3504847155cbb6f2219ba9b7df50be11a1c7f23f829f8a41b13b5ca4ee8983238e0794d3d34bc5f4e77facb6c05ac86212baa1a55a2be70b5733b045cd33694b3afe2f0e49e4f321549fd824ea90870d4b28a2954489a0abcd50e18a844ac5bf38e4cd72d9b0942e506c433afcda3847f2dadd47647de321cec4ac430f62023856cfbb20704f4ec0bb920ba86c33e05f1ecd96733b79950a3e314d3d934f75ea0f210a8f6059401beb4bc4478fa4969e623d01ada696a7e4c7e5125b34884533a94fb319990325744ee9bbce9e525cf08f5e9e25e5360aad2b2d085fa54d835e8d466826498d9a8877565705a8a3f62802944de7ca5894e5759d351adac869580ec17e485f18c0c66f17cc0",
+		"7cbb22fce466da610b63af62bc83b4692f3affaf271693ac071fb86d11342d",
+		"a5117e70953568bf750862df9e6f92af81677c3a188e847917a4a915bda7792e",
+		"129039b5572e8a7a8131f76a",
+		"588e1356fb8fa32410dad99cf7922aae47b4042502c92f3afe33dc22c1c2e90caf22bc37a254f8dd62a09582c70194f9616982639415178e9fe95740c0f1d497a69b69d4924a7a15290187f9c8acf09cf5b3b3188ecde2d2807207f5bb6a6d3504314b1b47684cf8ba8807eb9a3c497c79ebe1e4c1eca2aa90328563e201425227fca8ee05dcc05fd6c98128626c1e71d2fb3a21860567093db1012dfabe13055c48219d2a301c8a5a49033a811d8d9413bafbb2eefc177226fe578e93c2ef1f309416dc98843bfac387debb1b610b1d2366178ce7212a7312057a3d058357a629f18c78e129e60979a2310455a76207be5611e8b4b840629564020c17f5c9446882e23f610e931246ec434e62de765bf22954cfae02b2ff7c59dfe246e4bb2d6a8afcebdc2beeaabf2a3f43f95a5ea639853f38719875ecdd2bbc0d81bb2a5ed59553b1e76b6365b74f618f68d1f05b5662cd6e04de896d3ef5dae4149485a5a2093ff4ec74b20b5e5bf8e61b5c65515938c202beab3eea5a498d2f32c38dbb37d04f8272e741da2802c54a9d9aaf8ecf38b36fc9ad0079523f6a4abd5281a22697a3180bc02662a7c13ee23599d18e5c48300dbb831509df4c172f53e524b3c15124a87ac73e5028cde6c94d8d",
+	},
+	{
+		"67c6697351ff4aec29cdbaabf2fbe3467cc254f81be8e78d765a2e63339fc99a66320db73158a35a255d051758e95ed4abb2cdc69bb454110e827441213ddc8770e93ea141e1fc673e017e97eadc6b968f385c2aecb03bfb32af3c54ec18db5c021afe43fbfaaa3afb29d1e6053c7c9475d8be6189f95cbba8990f95b1ebf1b305eff700e9a13ae5ca0bcbd0484764bd1f231ea81c7b64c514735ac55e4b79633b706424119e09dcaad4acf21b10af3b33cde3504847155cbb6f2219ba9b7df50be11a1c7f23f829f8a41b13b5ca4ee8983238e0794d3d34bc5f4e77facb6c05ac86212baa1a55a2be70b5733b045cd33694b3afe2f0e49e4f321549fd824ea90870d4b28a2954489a0abcd50e18a844ac5bf38e4cd72d9b0942e506c433afcda3847f2dadd47647de321cec4ac430f62023856cfbb20704f4ec0bb920ba86c33e05f1ecd96733b79950a3e314d3d934f75ea0f210a8f6059401beb4bc4478fa4969e623d01ada696a7e4c7e5125b34884533a94fb319990325744ee9bbce9e525",
+		"",
+		"a5117e70953568bf750862df9e6f92af81677c3a188e847917a4a915bda7792e",
+		"129039b5572e8a7a8131f76a",
+		"588e1356fb8fa32410dad99cf7922aae47b4042502c92f3afe33dc22c1c2e90caf22bc37a254f8dd62a09582c70194f9616982639415178e9fe95740c0f1d497a69b69d4924a7a15290187f9c8acf09cf5b3b3188ecde2d2807207f5bb6a6d3504314b1b47684cf8ba8807eb9a3c497c79ebe1e4c1eca2aa90328563e201425227fca8ee05dcc05fd6c98128626c1e71d2fb3a21860567093db1012dfabe13055c48219d2a301c8a5a49033a811d8d9413bafbb2eefc177226fe578e93c2ef1f309416dc98843bfac387debb1b610b1d2366178ce7212a7312057a3d058357a629f18c78e129e60979a2310455a76207be5611e8b4b840629564020c17f5c9446882e23f610e931246ec434e62de765bf22954cfae02b2ff7c59dfe246e4bb2d6a8afcebdc2beeaabf2a3f43f95a5ea639853f38719875ecdd2bbc0d81bb2a5ed59553b1e76b6365b74f618f68d1f05b5662cd6e04de896d3ef5dae4149485a5a2093ff4ec74b20b5e5bf8e61b5c65515938c202beab3eea5a498d2f32c38dbb370a9bbc3187cc260ddac991f94ce4f0d5",
+	},
+	{
+		"0fb826ddb2eb5e708de203d0438be12cf708d635ebdbae56278be09077009586b9bc646ba7c2db35a5de05e86ae71461efea96dac64430edcf117d461113cccacf303576f310ab98efb180599894ba877e50614494923163a3afa9b4c2757f91a6b40799c5b331b464b10dfc45c783c317e408ab76390e19e8b7ceaa2c4d3bd201436bc6f69c7a5a4d8756924ed95665bd5e1034971e4d80d51b2a",
+		"026866d46aa940309fdcabf92a324fbc",
+		"a5117e70953568bf750862df9e6f92af81677c3a188e847917a4a915bda7792e",
+		"129039b5572e8a7a8131f76a",
+		"30f05cf8189bb7b8b4f560e746e228c4cc7e86e8f2fa66e1afe212d1855db51070acd5eb34ce80b2e223957df50fde4c2531d97fc9e573725e7a5e47f0dfc4da1942620320bb2deaf8b17937bae4218d04db8e76f6efe84a117292159507c9f8a09fb2c17921d7762510dbf1dac7b62b1bd7572e3e2cf008d01c445c7fa78833235034281ae180e051451c6a64f22ca9708634bd0d604e4cfcd971b13742efa5b6363e662a875daccb2b00",
+	},
+	{
+		"c7d4f8790e4c47d4daecbddf5939973521ddbf3b832e564afc66f03b5583c41c58bd956609dc3ae3c8f7c2213059575236168dba44e3044049f47c9e7840bbd0fd5036062d70e9f567ac1797056ee93c8476f6c959fa09a3ee854166c6fc36c34d6cca7adcb36f435f86db65f4c4a1793b974294914b377fd179e697751c5ac289243c65d8aca93732849c27483da083d4e218652d4fe5fec8cb953ee7f00070143dd6ece97f241b03c0424bfee2cfd2c4e738f2361df0ffe8863dcf763d408a7a167763959b7f985bc1e359a4b22c6899645ad0814bcf69d10c38474978d1c48e482723e3a6bb3f689f980c51c474eb28cfbba91a8a12eb964b32dfc303a3524ccb752f71316ed9d007e521cb5a0cf429c79d4351b02ee7fb60c7be636a10af3586dfa7b74d80875466a820c0b514e97cb12cce615ab55cba7c1b1de72bcd1cb1acc368f944ef4eaa986e6a4d8253c9337f9795d94df193c90cb0b0387dcde929905223d441717ed9dfe826613bf094ba872993d41b269e27d74e5f541b497eac9ba180dc12ffb6f1e7dc5223cce6dd541071282b97c6526e15b2c330fb41dc96e25d72f45c28e543053766d11d44252db54e584c14abbb295d7e5a58bf36eea1936095ef897a338eb1995fcedd85fc92d354dfe7ff9a115c186bb4d7a1a27835030d248c87571a38f17906cefe0261d15740b9",
+		"56",
+		"a5117e70953568bf750862df9e6f92af81677c3a188e847917a4a915bda7792e",
+		"129039b5572e8a7a8131f76a",
+		"f89c825ca43cae1ce3fbdee85c505edd1aabefe69a0f9efd740f027aa7dee48a91ad24e69ad061648f0a52b4afb19d7ffccdc21f4b4247dfd89f5f9f998cb3c02b226173fedb6f8770aceef9271e7236fefd19fb3b87d08a5c587ac7918e80aa4b477f22602189811e270d686bc4949137a41d11d95ec96ee9d26c6126f6e923ab37638b34d1538d2e46d6df6216da4f193a3cecb731e632e109ced643056a1673059355d2d1314df35ded8364efed7de490201090a6f2d1751748585f64d26041637ba3723cbc4b60e226f10a19699d223075bc1f27d82e7f560c0db630ea670b3f8a70a8950894af4d1c7b3f674a3fa00d19ee4cc2b6174c1d259a297424bf2c3943a29a16a9830ce11abaa79cd2eb77b53a02b365b1838e7bfd5ae1bd044ffc885c61c6b2186a357e8b8f732b7ab96517969aeb70c7b493bbaca9462a61815a3c6135c748bf9c8487ac0631807aa69243fa09cd3b8efb63f8d4e090ad30b6c2f08bf4e82f191cedfa5cbe2b42268d67ecd105918181e44fc9879efd642d20be84e6f74717e03fb94fcbaa6ed3b307431d2a9384b8a2b3e5825ffce8d99af48f177e43bb4272226d8a5edd37d53807f768feb9e0733b437a1d0f84779ab68a1804e92a5eecca56364f0fa6dca152203b249fdc8fbd950fdc37c1887596308a90ba3a5751c7096bfbd1cb177bb17847b33c4379b43938a67674459cd9a06e3017ccac5b",
+	},
+	{
+		"135a28170fe89066da7bcff3a9ccc1b27dfe942a6f47b23835ef746aaea63dc10066d90f4e697528e5451b8e11dd408fdbd4b94a1c6c82515bf7bc099df9cb9d5fa4acad0d22d5f267f18078cec107a995c1f3b12d7603886dbf910ab85ca7180053c50e759b00dc8c81555a425c03d71df6894a6c8cd2d94b64e303c08a1bc1dee1cf537ccf300850856292e1656aff5bf349c87f1ca1ca8085cd400fe901edcad04146a0714ef0f6b083d715edd670e020385f3cda29bc5ff6fc6edffe5ca9ce9def6e0e3d5f04ede2db02cfb2",
+		"73afd2ab0e0e8537cae42dc6530dc4afb6934ca6",
+		"a5117e70953568bf750862df9e6f92af81677c3a188e847917a4a915bda7792e",
+		"129039b5572e8a7a8131f76a",
+		"2c125232a59879aee36cacc4aca5085a4688c4f776667a8fbd86862b5cfb1d57c976688fdd652eafa2b88b1b8e358aa2110ff6ef13cdc1ceca9c9f087c35c38d89d6fbd8de89538070f17916ecb19ca3ef4a1c834f0bdaa1df62aaabef2e117106787056c909e61ecd208357dd5c363f11c5d6cf24992cc873cf69f59360a820fcf290bd90b2cab24c47286acb4e1033962b6d41e562a206a94796a8ab1c6b8bade804ff9bdf5ba6062d2c1f8fe0f4dfc05720bd9a612b92c26789f9f6a7ce43f5e8e3aee99a9cd7d6c11eaa611983c36935b0dda57d898a60a0ab7c4b54",
+	},
+}
diff --git a/go/subtle/aead/encrypt_then_authenticate.go b/go/subtle/aead/encrypt_then_authenticate.go
new file mode 100644
index 0000000..31cc693
--- /dev/null
+++ b/go/subtle/aead/encrypt_then_authenticate.go
@@ -0,0 +1,115 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package aead
+
+import (
+	"encoding/binary"
+	"errors"
+	"fmt"
+
+	"github.com/google/tink/go/tink"
+)
+
+// EncryptThenAuthenticate performs an encrypt-then-MAC operation on plaintext
+// and additional authenticated data (aad). The MAC is computed over (aad ||
+// ciphertext || size of aad). This implementation is based on
+// http://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05.
+type EncryptThenAuthenticate struct {
+	indCPACipher INDCPACipher
+	mac          tink.MAC
+	tagSize      int
+}
+
+const (
+	minTagSizeInBytes = 10
+)
+
+// Assert that EncryptThenAuthenticate implements the AEAD interface.
+var _ tink.AEAD = (*EncryptThenAuthenticate)(nil)
+
+// uint64ToByte stores a uint64 to a slice of bytes in big endian format.
+func uint64ToByte(n uint64) []byte {
+	buf := make([]byte, 8)
+	binary.BigEndian.PutUint64(buf, n)
+	return buf
+}
+
+// NewEncryptThenAuthenticate returns a new instance of EncryptThenAuthenticate.
+func NewEncryptThenAuthenticate(indCPACipher INDCPACipher, mac tink.MAC, tagSize int) (*EncryptThenAuthenticate, error) {
+	if tagSize < minTagSizeInBytes {
+		return nil, fmt.Errorf("encrypt_then_authenticate: tag size too small")
+	}
+	return &EncryptThenAuthenticate{indCPACipher, mac, tagSize}, nil
+}
+
+// Encrypt encrypts plaintext with additionalData as additional authenticated
+// data. The resulting ciphertext allows for checking authenticity and
+// integrity of additional data, but does not guarantee its secrecy.
+//
+// The plaintext is encrypted with an INDCPACipher, then MAC is computed over
+// (additionalData || ciphertext || n) where n is additionalData's length
+// in bits represented as a 64-bit bigendian unsigned integer. The final
+// ciphertext format is (IND-CPA ciphertext || mac).
+func (e *EncryptThenAuthenticate) Encrypt(plaintext, additionalData []byte) ([]byte, error) {
+	ciphertext, err := e.indCPACipher.Encrypt(plaintext)
+	if err != nil {
+		return nil, fmt.Errorf("encrypt_then_authenticate: %v", err)
+	}
+
+	toAuthData := append(additionalData, ciphertext...)
+	aadSizeInBits := uint64(len(additionalData)) * 8
+	toAuthData = append(toAuthData, uint64ToByte(aadSizeInBits)...)
+
+	tag, err := e.mac.ComputeMAC(toAuthData)
+	if err != nil {
+		return nil, fmt.Errorf("encrypt_then_authenticate: %v", err)
+	}
+
+	if len(tag) != e.tagSize {
+		return nil, errors.New("encrypt_then_authenticate: invalid tag size")
+	}
+
+	ciphertext = append(ciphertext, tag...)
+	return ciphertext, nil
+}
+
+// Decrypt decrypts ciphertext with additionalData as additional authenticated
+// data.
+func (e *EncryptThenAuthenticate) Decrypt(ciphertext, additionalData []byte) ([]byte, error) {
+	if len(ciphertext) < e.tagSize {
+		return nil, errors.New("ciphertext too short")
+	}
+
+	// payload contains everything except the tag.
+	payload := ciphertext[:len(ciphertext)-e.tagSize]
+
+	// Authenticate the following data:
+	// additionalData || payload || aadSizeInBits
+	toAuthData := append(additionalData, payload...)
+	aadSizeInBits := uint64(len(additionalData)) * 8
+	toAuthData = append(toAuthData, uint64ToByte(aadSizeInBits)...)
+
+	err := e.mac.VerifyMAC(ciphertext[len(ciphertext)-e.tagSize:], toAuthData)
+	if err != nil {
+		return nil, fmt.Errorf("encrypt_then_authenticate: %v", err)
+	}
+
+	plaintext, err := e.indCPACipher.Decrypt(payload)
+	if err != nil {
+		return nil, fmt.Errorf("encrypt_then_authenticate: %v", err)
+	}
+
+	return plaintext, nil
+}
diff --git a/go/subtle/aead/encrypt_then_authenticate_test.go b/go/subtle/aead/encrypt_then_authenticate_test.go
new file mode 100644
index 0000000..da4e7ac
--- /dev/null
+++ b/go/subtle/aead/encrypt_then_authenticate_test.go
@@ -0,0 +1,332 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package aead_test
+
+import (
+	"bytes"
+	"encoding/hex"
+	"testing"
+
+	"github.com/google/tink/go/subtle/aead"
+	"github.com/google/tink/go/subtle/mac"
+	"github.com/google/tink/go/subtle/random"
+	"github.com/google/tink/go/tink"
+)
+
+func createAEADWithKeys(encryptionKey []byte, ivSize int, hashAlgo string, macKey []byte, tagSize int) (tink.AEAD, error) {
+	ctr, err := aead.NewAESCTR(encryptionKey, ivSize)
+	if err != nil {
+		return nil, err
+	}
+
+	mac, err := mac.NewHMAC(hashAlgo, macKey, uint32(tagSize))
+	if err != nil {
+		return nil, err
+	}
+
+	cipher, err := aead.NewEncryptThenAuthenticate(ctr, mac, tagSize)
+	if err != nil {
+		return nil, err
+	}
+	return cipher, nil
+}
+
+func createAEAD(keySize, ivSize int, hashAlgo string, macKeySize int, tagSize int) (tink.AEAD, error) {
+	encryptionKey := random.GetRandomBytes(uint32(keySize))
+	ctr, err := aead.NewAESCTR(encryptionKey, ivSize)
+	if err != nil {
+		return nil, err
+	}
+
+	macKey := random.GetRandomBytes(uint32(macKeySize))
+	mac, err := mac.NewHMAC(hashAlgo, macKey, uint32(tagSize))
+	if err != nil {
+		return nil, err
+	}
+
+	cipher, err := aead.NewEncryptThenAuthenticate(ctr, mac, tagSize)
+	if err != nil {
+		return nil, err
+	}
+	return cipher, nil
+}
+
+// Copied from
+// https://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05.
+// We use CTR but the RFC uses CBC mode, so it's not possible to compare
+// plaintexts. However, the tests are still valueable to ensure that we correcly
+// compute HMAC over ciphertext and aad.
+var rfcTestVectors = []struct {
+	macKey        string
+	encryptionKey string
+	ciphertext    string
+	aad           string
+	hashAlgo      string
+	ivSize        int
+	tagSize       int
+}{
+	{"000102030405060708090a0b0c0d0e0f",
+		"101112131415161718191a1b1c1d1e1f",
+		"1af38c2dc2b96ffdd86694092341bc04" +
+			"c80edfa32ddf39d5ef00c0b468834279" +
+			"a2e46a1b8049f792f76bfe54b903a9c9" +
+			"a94ac9b47ad2655c5f10f9aef71427e2" +
+			"fc6f9b3f399a221489f16362c7032336" +
+			"09d45ac69864e3321cf82935ac4096c8" +
+			"6e133314c54019e8ca7980dfa4b9cf1b" +
+			"384c486f3a54c51078158ee5d79de59f" +
+			"bd34d848b3d69550a67646344427ade5" +
+			"4b8851ffb598f7f80074b9473c82e2db" +
+			"652c3fa36b0a7c5b3219fab3a30bc1c4",
+		"546865207365636f6e64207072696e63" +
+			"69706c65206f66204175677573746520" +
+			"4b6572636b686f666673",
+		"SHA256", 16, 16},
+	{"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+		"202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
+		"1af38c2dc2b96ffdd86694092341bc04" +
+			"4affaaadb78c31c5da4b1b590d10ffbd" +
+			"3dd8d5d302423526912da037ecbcc7bd" +
+			"822c301dd67c373bccb584ad3e9279c2" +
+			"e6d12a1374b77f077553df829410446b" +
+			"36ebd97066296ae6427ea75c2e0846a1" +
+			"1a09ccf5370dc80bfecbad28c73f09b3" +
+			"a3b75e662a2594410ae496b2e2e6609e" +
+			"31e6e02cc837f053d21f37ff4f51950b" +
+			"be2638d09dd7a4930930806d0703b1f6" +
+			"4dd3b4c088a7f45c216839645b2012bf" +
+			"2e6269a8c56a816dbc1b267761955bc5",
+		"546865207365636f6e64207072696e63" +
+			"69706c65206f66204175677573746520" +
+			"4b6572636b686f666673",
+		"SHA512", 16, 32},
+}
+
+func hexDecodeOrDie(data string) []byte {
+	decoded, err := hex.DecodeString(data)
+	if err != nil {
+		panic(err)
+	}
+	return decoded
+}
+
+func TestETARFCTestVectors(t *testing.T) {
+	for _, v := range rfcTestVectors {
+		macKey := hexDecodeOrDie(v.macKey)
+		encryptionKey := hexDecodeOrDie(v.encryptionKey)
+		ciphertext := hexDecodeOrDie(v.ciphertext)
+		aad := hexDecodeOrDie(v.aad)
+
+		cipher, err := createAEADWithKeys(encryptionKey, v.ivSize, v.hashAlgo, macKey, v.tagSize)
+		if err != nil {
+			t.Fatalf("failed to create AEAD from RFC test vector: %v", v)
+		}
+
+		if _, err := cipher.Decrypt(ciphertext, aad); err != nil {
+			t.Errorf("decryption failed to RFC test vector: %v, error: %v", v, err)
+		}
+	}
+}
+
+func TestETAEncryptDecrypt(t *testing.T) {
+	const keySize = 16
+	const ivSize = 12
+	const macKeySize = 16
+	const tagSize = 16
+
+	cipher, err := createAEAD(keySize, ivSize, "SHA1", macKeySize, tagSize)
+	if err != nil {
+		t.Fatalf("got: %v, want: success", err)
+	}
+
+	message := []byte("Some data to encrypt.")
+	aad := []byte("Some data to authenticate.")
+
+	ciphertext, err := cipher.Encrypt(message, aad)
+	if err != nil {
+		t.Fatalf("encryption failed, error: %v", err)
+	}
+
+	if len(ciphertext) != len(message)+ivSize+tagSize {
+		t.Errorf("invalid ciphertext size, got: %d, want: %d", len(ciphertext), len(message)+ivSize+tagSize)
+	}
+
+	plaintext, err := cipher.Decrypt(ciphertext, aad)
+	if err != nil {
+		t.Fatalf("decryption failed, error: %v", err)
+	}
+
+	if bytes.Compare(plaintext, message) != 0 {
+		t.Errorf("invalid plaintext, got: %q, want: %q", plaintext, message)
+	}
+}
+
+func TestETAEncryptDecryptRandomMessage(t *testing.T) {
+	const keySize = 16
+	const ivSize = 12
+	const macKeySize = 16
+	const tagSize = 16
+
+	cipher, err := createAEAD(keySize, ivSize, "SHA1", macKeySize, tagSize)
+	if err != nil {
+		t.Fatalf("got: %v, want: success", err)
+	}
+
+	for i := 0; i < 256; i++ {
+		message := random.GetRandomBytes(uint32(i))
+		aad := random.GetRandomBytes(uint32(i))
+
+		ciphertext, err := cipher.Encrypt(message, aad)
+		if err != nil {
+			t.Fatalf("encryption failed, error: %v", err)
+		}
+
+		if len(ciphertext) != len(message)+ivSize+tagSize {
+			t.Errorf("invalid ciphertext size, got: %d, want: %d", len(ciphertext), len(message)+ivSize+tagSize)
+		}
+
+		plaintext, err := cipher.Decrypt(ciphertext, aad)
+		if err != nil {
+			t.Fatalf("decryption failed, error: %v", err)
+		}
+
+		if bytes.Compare(plaintext, message) != 0 {
+			t.Errorf("invalid plaintext, got: %q, want: %q", plaintext, message)
+		}
+	}
+}
+
+func TestETAMultipleEncrypt(t *testing.T) {
+	const keySize = 16
+	const ivSize = 12
+	const macKeySize = 16
+	const tagSize = 16
+
+	cipher, err := createAEAD(keySize, ivSize, "SHA1", macKeySize, tagSize)
+	if err != nil {
+		t.Fatalf("got: %v, want: success", err)
+	}
+
+	message := []byte("Some data to encrypt.")
+	aad := []byte("Some data to authenticate.")
+
+	ciphertext1, err := cipher.Encrypt(message, aad)
+	if err != nil {
+		t.Fatalf("encryption failed, error: %v", err)
+	}
+
+	ciphertext2, err := cipher.Encrypt(message, aad)
+	if err != nil {
+		t.Fatalf("encryption failed, error: %v", err)
+	}
+
+	if bytes.Compare(ciphertext1, ciphertext2) == 0 {
+		t.Error("ciphertexts must not be the same")
+	}
+}
+
+func TestETAInvalidTagSize(t *testing.T) {
+	const keySize = 16
+	const ivSize = 12
+	const macKeySize = 16
+	const tagSize = 9 // Invalid!
+
+	if _, err := createAEAD(keySize, ivSize, "SHA1", macKeySize, tagSize); err == nil {
+		t.Error("got: success, want: error invalid tag size")
+	}
+}
+
+func TestETADecryptModifiedCiphertext(t *testing.T) {
+	const keySize = 16
+	const ivSize = 12
+	const macKeySize = 16
+	const tagSize = 16
+
+	cipher, err := createAEAD(keySize, ivSize, "SHA1", macKeySize, tagSize)
+	if err != nil {
+		t.Fatalf("got: %v, want: success", err)
+	}
+
+	message := []byte("Some data to encrypt.")
+	aad := []byte("Some data to authenticate.")
+	ciphertext, err := cipher.Encrypt(message, aad)
+	if err != nil {
+		t.Fatalf("encryption failed, error: %v", err)
+	}
+
+	// Modify the ciphertext and try to decrypt.
+	modct := make([]byte, len(ciphertext))
+	copy(modct, ciphertext)
+	for i := 0; i < len(ciphertext)*8; i++ {
+		// Save the byte to be modified.
+		b := modct[i/8]
+		modct[i/8] ^= (1 << uint(i%8))
+		if bytes.Compare(ciphertext, modct) == 0 {
+			t.Errorf("modified ciphertext shouldn't be the same as aad")
+		}
+		if _, err := cipher.Decrypt(modct, aad); err == nil {
+			t.Errorf("successfully decrypted modified ciphertext (i = %d)", i)
+		}
+		// Restore the modified byte.
+		modct[i/8] = b
+	}
+
+	// Modify the additional authenticated data.
+	modaad := make([]byte, len(aad))
+	copy(modaad, aad)
+	for i := 0; i < len(aad)*8; i++ {
+		// Save the byte to be modified.
+		b := modaad[i/8]
+		modaad[i/8] ^= (1 << uint(i%8))
+		if bytes.Compare(aad, modaad) == 0 {
+			t.Errorf("modified aad shouldn't be the same as aad")
+		}
+		if _, err := cipher.Decrypt(ciphertext, modaad); err == nil {
+			t.Errorf("successfully decrypted with modified aad (i = %d)", i)
+		}
+		// Restore the modified byte.
+		modaad[i/8] = b
+	}
+
+	// Truncate the ciphertext.
+	trunct := make([]byte, len(ciphertext))
+	copy(trunct, ciphertext)
+	for i := 1; i <= len(ciphertext); i++ {
+		trunct = trunct[:len(ciphertext)-i]
+		if _, err := cipher.Decrypt(trunct, aad); err == nil {
+			t.Errorf("successfully decrypted truncated ciphertext (i = %d)", i)
+		}
+	}
+}
+
+func TestETAEmptyParams(t *testing.T) {
+	const keySize = 16
+	const ivSize = 12
+	const macKeySize = 16
+	const tagSize = 16
+
+	cipher, err := createAEAD(keySize, ivSize, "SHA1", macKeySize, tagSize)
+	if err != nil {
+		t.Fatalf("got: %v, want: success", err)
+	}
+
+	message := []byte("Some data to encrypt.")
+	if _, err := cipher.Encrypt(message, []byte{}); err != nil {
+		t.Errorf("encryption failed with empty aad")
+	}
+	if _, err := cipher.Encrypt([]byte{}, []byte{}); err != nil {
+		t.Errorf("encryption failed with empty ciphertext and aad")
+	}
+}
diff --git a/go/subtle/aead/ind_cpa.go b/go/subtle/aead/ind_cpa.go
new file mode 100644
index 0000000..606c9bf
--- /dev/null
+++ b/go/subtle/aead/ind_cpa.go
@@ -0,0 +1,28 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package aead
+
+// INDCPACipher provides an interface for symmetric key ciphers that are
+// indistinguishable against chosen-plaintext attacks. Said primitives do not
+// provide authentication, thus should not be used directly, but only to
+// construct safer primitives such as AEAD.
+type INDCPACipher interface {
+	// Encrypt encrypts plaintext. The resulting ciphertext is indistinguishable under
+	// chosen-plaintext attack. However, it does not have integrity protection.
+	Encrypt(plaintext []byte) ([]byte, error)
+
+	// Decrypt decrypts ciphertext and returns the resulting plaintext.
+	Decrypt(ciphertext []byte) ([]byte, error)
+}
diff --git a/go/subtle/aead/xchacha20poly1305.go b/go/subtle/aead/xchacha20poly1305.go
new file mode 100644
index 0000000..03b7e1d
--- /dev/null
+++ b/go/subtle/aead/xchacha20poly1305.go
@@ -0,0 +1,79 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package aead
+
+import (
+	"errors"
+	"fmt"
+
+	"golang.org/x/crypto/chacha20poly1305"
+	"github.com/google/tink/go/subtle/random"
+	"github.com/google/tink/go/tink"
+)
+
+// XChaCha20Poly1305 is an implementation of AEAD interface.
+type XChaCha20Poly1305 struct {
+	Key []byte
+}
+
+// Assert that XChaCha20Poly1305 implements the AEAD interface.
+var _ tink.AEAD = (*XChaCha20Poly1305)(nil)
+
+// NewXChaCha20Poly1305 returns an XChaCha20Poly1305 instance.
+// The key argument should be a 32-bytes key.
+func NewXChaCha20Poly1305(key []byte) (*XChaCha20Poly1305, error) {
+	if len(key) != chacha20poly1305.KeySize {
+		return nil, errors.New("xchacha20poly1305: bad key length")
+	}
+
+	return &XChaCha20Poly1305{Key: key}, nil
+}
+
+// Encrypt encrypts {@code pt} with {@code aad} as additional
+// authenticated data. The resulting ciphertext consists of two parts:
+// (1) the nonce used for encryption and (2) the actual ciphertext.
+func (x *XChaCha20Poly1305) Encrypt(pt []byte, aad []byte) ([]byte, error) {
+	c, err := chacha20poly1305.NewX(x.Key)
+	if err != nil {
+		return nil, err
+	}
+
+	n := x.newNonce()
+	ct := c.Seal(nil, n, pt, aad)
+	var ret []byte
+	ret = append(ret, n...)
+	ret = append(ret, ct...)
+	return ret, nil
+}
+
+// Decrypt decrypts {@code ct} with {@code aad} as the additionalauthenticated data.
+func (x *XChaCha20Poly1305) Decrypt(ct []byte, aad []byte) ([]byte, error) {
+	c, err := chacha20poly1305.NewX(x.Key)
+	if err != nil {
+		return nil, err
+	}
+
+	n := ct[:chacha20poly1305.NonceSizeX]
+	pt, err := c.Open(nil, n, ct[chacha20poly1305.NonceSizeX:], aad)
+	if err != nil {
+		return nil, fmt.Errorf("XChaCha20Poly1305.Decrypt: %s", err)
+	}
+	return pt, nil
+}
+
+// newNonce creates a new nonce for encryption.
+func (x *XChaCha20Poly1305) newNonce() []byte {
+	return random.GetRandomBytes(chacha20poly1305.NonceSizeX)
+}
diff --git a/go/subtle/aead/xchacha20poly1305_test.go b/go/subtle/aead/xchacha20poly1305_test.go
new file mode 100644
index 0000000..6b72133
--- /dev/null
+++ b/go/subtle/aead/xchacha20poly1305_test.go
@@ -0,0 +1,197 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package aead_test
+
+import (
+	"bytes"
+	"encoding/hex"
+	"math/rand"
+	"testing"
+
+	"golang.org/x/crypto/chacha20poly1305"
+	"github.com/google/tink/go/subtle/aead"
+	"github.com/google/tink/go/subtle/random"
+)
+
+func TestXChaCha20Poly1305EncryptDecrypt(t *testing.T) {
+	for i, test := range xChaCha20Poly1305Tests {
+		key, _ := hex.DecodeString(test.key)
+		pt, _ := hex.DecodeString(test.plaintext)
+		aad, _ := hex.DecodeString(test.aad)
+		nonce, _ := hex.DecodeString(test.nonce)
+		out, _ := hex.DecodeString(test.out)
+		tag, _ := hex.DecodeString(test.tag)
+
+		x, err := aead.NewXChaCha20Poly1305(key)
+		if err != nil {
+			t.Errorf("#%d, cannot create new instance of XChaCha20Poly1305: %s", i, err)
+			continue
+		}
+
+		_, err = x.Encrypt(pt, aad)
+		if err != nil {
+			t.Errorf("#%d, unexpected encryption error: %s", i, err)
+			continue
+		}
+
+		var combinedCt []byte
+		combinedCt = append(combinedCt, nonce...)
+		combinedCt = append(combinedCt, out...)
+		combinedCt = append(combinedCt, tag...)
+		if got, err := x.Decrypt(combinedCt, aad); err != nil {
+			t.Errorf("#%d, unexpected decryption error: %s", i, err)
+			continue
+		} else if !bytes.Equal(pt, got) {
+			t.Errorf("#%d, plaintext's don't match: got %x vs %x", i, got, pt)
+			continue
+		}
+	}
+}
+
+func TestXChaCha20Poly1305EmptyAssociatedData(t *testing.T) {
+	key := random.GetRandomBytes(chacha20poly1305.KeySize)
+	aad := []byte{}
+	badAad := []byte{1, 2, 3}
+
+	x, err := aead.NewXChaCha20Poly1305(key)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	for i := 0; i < 75; i++ {
+		pt := random.GetRandomBytes(uint32(i))
+		// Encrpting with aad as a 0-length array
+		{
+			ct, err := x.Encrypt(pt, aad)
+			if err != nil {
+				t.Errorf("Encrypt(%x, %x) failed", pt, aad)
+				continue
+			}
+
+			if got, err := x.Decrypt(ct, aad); err != nil || !bytes.Equal(pt, got) {
+				t.Errorf("Decrypt(Encrypt(pt, %x)): plaintext's don't match: got %x vs %x", aad, got, pt)
+			}
+			if got, err := x.Decrypt(ct, nil); err != nil || !bytes.Equal(pt, got) {
+				t.Errorf("Decrypt(Encrypt(pt, nil)): plaintext's don't match: got %x vs %x", got, pt)
+			}
+			if _, err := x.Decrypt(ct, badAad); err == nil {
+				t.Errorf("Decrypt(Encrypt(pt, %x)) = _, nil; want: _, err", badAad)
+			}
+		}
+		// Encrpting with aad equal to null
+		{
+			ct, err := x.Encrypt(pt, nil)
+			if err != nil {
+				t.Errorf("Encrypt(%x, nil) failed", pt)
+			}
+
+			if got, err := x.Decrypt(ct, aad); err != nil || !bytes.Equal(pt, got) {
+				t.Errorf("Decrypt(Encrypt(pt, %x)): plaintext's don't match: got %x vs %x; error: %v", aad, got, pt, err)
+			}
+			if got, err := x.Decrypt(ct, nil); err != nil || !bytes.Equal(pt, got) {
+				t.Errorf("Decrypt(Encrypt(pt, nil)): plaintext's don't match: got %x vs %x; error: %v", got, pt, err)
+			}
+			if _, err := x.Decrypt(ct, badAad); err == nil {
+				t.Errorf("Decrypt(Encrypt(pt, %x)) = _, nil; want: _, err", badAad)
+			}
+		}
+	}
+}
+
+func TestXChaCha20Poly1305LongMessages(t *testing.T) {
+	dataSize := uint32(16)
+	// Encrypts and decrypts messages of size <= 8192.
+	for dataSize <= 1<<24 {
+		pt := random.GetRandomBytes(dataSize)
+		aad := random.GetRandomBytes(dataSize / 3)
+		key := random.GetRandomBytes(chacha20poly1305.KeySize)
+
+		x, err := aead.NewXChaCha20Poly1305(key)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		ct, err := x.Encrypt(pt, aad)
+		if err != nil {
+			t.Errorf("Encrypt(%x, %x) failed", pt, aad)
+			continue
+		}
+
+		if got, err := x.Decrypt(ct, aad); err != nil || !bytes.Equal(pt, got) {
+			t.Errorf("Decrypt(Encrypt(pt, %x)): plaintext's don't match: got %x vs %x; error: %v", aad, got, pt, err)
+		}
+
+		dataSize += 5 * dataSize / 11
+	}
+}
+
+func TestXChaCha20Poly1305ModifyCiphertext(t *testing.T) {
+	for i, test := range xChaCha20Poly1305Tests {
+		key, _ := hex.DecodeString(test.key)
+		pt, _ := hex.DecodeString(test.plaintext)
+		aad, _ := hex.DecodeString(test.aad)
+
+		x, err := aead.NewXChaCha20Poly1305(key)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		ct, err := x.Encrypt(pt, aad)
+		if err != nil {
+			t.Errorf("#%d: Encrypt failed", i)
+			continue
+		}
+
+		if len(aad) > 0 {
+			alterAadIdx := rand.Intn(len(aad))
+			aad[alterAadIdx] ^= 0x80
+			if _, err := x.Decrypt(ct, aad); err == nil {
+				t.Errorf("#%d: Decrypt was successful after altering additional data", i)
+				continue
+			}
+			aad[alterAadIdx] ^= 0x80
+		}
+
+		alterCtIdx := rand.Intn(len(ct))
+		ct[alterCtIdx] ^= 0x80
+		if _, err := x.Decrypt(ct, aad); err == nil {
+			t.Errorf("#%d: Decrypt was successful after altering ciphertext", i)
+			continue
+		}
+		ct[alterCtIdx] ^= 0x80
+	}
+}
+
+// This is a very simple test for the randomness of the nonce.
+// The test simply checks that the multiple ciphertexts of the same message are distinct.
+func TestXChaCha20Poly1305RandomNonce(t *testing.T) {
+	key := random.GetRandomBytes(chacha20poly1305.KeySize)
+	x, err := aead.NewXChaCha20Poly1305(key)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	cts := make(map[string]bool)
+	pt, aad := []byte{}, []byte{}
+	for i := 0; i < 1<<10; i++ {
+		ct, err := x.Encrypt(pt, aad)
+		ctHex := hex.EncodeToString(ct)
+		if err != nil || cts[ctHex] {
+			t.Errorf("TestRandomNonce failed: %v", err)
+		} else {
+			cts[ctHex] = true
+		}
+	}
+}
diff --git a/go/subtle/aead/xchacha20poly1305_vectors_test.go b/go/subtle/aead/xchacha20poly1305_vectors_test.go
new file mode 100644
index 0000000..863974b
--- /dev/null
+++ b/go/subtle/aead/xchacha20poly1305_vectors_test.go
@@ -0,0 +1,488 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package aead_test
+
+// # Test vectors generated from libsodium with this code:
+//
+// #include <stdio.h>
+// #include <sodium.h>
+// #include <stdlib.h>
+//
+// void hexdump(const uint8_t *in, size_t in_len) {
+//   for (size_t i = 0; i < in_len; i++) {
+//     printf("%02x", in[i]);
+//   }
+//   printf("\n");
+// }
+//
+// int main() {
+//   uint8_t nonce[24];
+//   uint8_t key[32];
+//   uint8_t m[64], c[64];
+//   uint8_t ad[16], tag[16];
+//
+//   for (size_t ad_len = 0; ad_len < sizeof(ad); ad_len += 4) {
+//     for (size_t m_len = 0; m_len < sizeof(m); m_len += 5) {
+//       randombytes(nonce, sizeof(nonce));
+//       randombytes(key, sizeof(key));
+//       randombytes(m, m_len);
+//       randombytes(ad, ad_len);
+//
+//       unsigned long long tag_len = sizeof(tag);
+//
+//       if (crypto_aead_xchacha20poly1305_ietf_encrypt_detached(
+//               c, tag, &tag_len, m, m_len, ad, ad_len, NULL, nonce, key)) {
+//         abort();
+//       }
+//
+//       printf("KEY: ");
+//       hexdump(key, sizeof(key));
+//       printf("NONCE: ");
+//       hexdump(nonce, sizeof(nonce));
+//       printf("IN: ");
+//       hexdump(m, m_len);
+//       printf("AD: ");
+//       hexdump(ad, ad_len);
+//       printf("CT: ");
+//       hexdump(c, m_len);
+//       printf("TAG: ");
+//       hexdump(tag, sizeof(tag));
+//       printf("\n");
+//     }
+//   }
+//
+//   return 0;
+// }
+
+var xChaCha20Poly1305Tests = []struct {
+	key, nonce, plaintext, aad, out, tag string
+}{
+	{
+		"1f4774fbe6324700d62dd6a104e7b3ca7160cfd958413f2afdb96695475f007e",
+		"029174e5102710975a8a4a936075eb3e0f470d436884d250",
+		"",
+		"",
+		"",
+		"f55cf0949af356f977479f1f187d7291",
+	},
+	{
+		"eb27969c7abf9aff79348e1e77f1fcba7508ceb29a7471961b017aef9ceaf1c2",
+		"990009311eab3459c1bee84b5b860bb5bdf93c7bec8767e2",
+		"e7ec3d4b9f",
+		"",
+		"66bd484861",
+		"07e31b4dd0f51f0819a0641c86380f32",
+	},
+	{
+		"4b6d89dbd7d019c0e1683d4c2a497305c778e2089ddb0f383f2c7fa2a5a52153",
+		"97525eb02a8d347fcf38c81b1be5c3ba59406241cf251ba6",
+		"074db54ef9fbc680b41a",
+		"",
+		"1221898afd6f516f770f",
+		"75e7182e7d715f5a32ee6733fd324539",
+	},
+	{
+		"766997b1dc6c3c73b1f50e8c28c0fcb90f206258e685aff320f2d4884506c8f4",
+		"30e7a9454892ef304776b6dc3d2c2f767ed97041b331c173",
+		"b8250c93ac6cf28902137b4522cc67",
+		"",
+		"e2a13eeff8831a35d9336cb3b5c5d9",
+		"62fdf67735cad0172f9b88603b5f3c13",
+	},
+	{
+		"6585031b5649fcabd9d4971d4ac5646fc7dca22f991dfa7dac39647001004e20",
+		"705ee25d03fec430e24c9c6ccaa633f5b86dd43682778278",
+		"9a4ca0633886a742e0241f132e8f90794c34dfd4",
+		"",
+		"0a8e6fd4cd1640be77c4c87dde4ae6222c887ed7",
+		"edc4fbc91dfa07021e74ae0d9d1c98dc",
+	},
+	{
+		"dfc6f7c86a10a319ebcb6362997e585f55b67f3434f47dc4039c2d67973e3077",
+		"6097f30fd75229d928454c7d59a2d2c58bfddcb14c16438e",
+		"74c946a7f0733377e852a23087506a28dccef86e101a4359c0",
+		"",
+		"6e8ea0bb4c2f1323841d8e236816c61c3295866b75cefb5c25",
+		"f16c0e9487ca7de5e7cb2a1b8bb370fc",
+	},
+	{
+		"59b8d488773767c4804d918709cfec6c69a193371145bb94f183899851aaadac",
+		"ad5bdf8f190ca2d2cc02a75bb62aa22274cb3c98fe2d25f2",
+		"066b9ed10f16d3dc132b409aae02d8cac209dd9b4fb789c4d34725ab2a1f",
+		"",
+		"2bbd4542489006df66ad1462a932524642b139ddcbf86b6b480e9e6d976c",
+		"ca4835419ba029bc57010a8cc8bca80c",
+	},
+	{
+		"8c0cb4633cf8dc6b4b9552d1035f85517cb1ba4c36bcbc43338a8c6c7d15ce20",
+		"8418b9655a0376fadefa3cdf8805815c4f7b56f467a74a95",
+		"50c205a9c5d4088ba8e59a96fcd837f5170669854547678288199f1078ff2a81f0b19a",
+		"",
+		"8b55a12df1a85dd3fb19c34ab047a85849d15a30225bb5360bad1f0a8f5f2bd49f5898",
+		"bce13201df6e4a7e6d896262e45d969d",
+	},
+	{
+		"b45386a75a5772e34bd193e1946f69ebfb90c37ae4581d39c9669d75e4584f50",
+		"9fb763d0926585b5f726af9b8e3babdb331e9aa97f8d99ed",
+		"64df0e341145d9e4a0d090153591a74893bc36cb9dae1e9570d8fee62e907cf004f9d8a360343483",
+		"",
+		"3146d8a5c898edd832ec9d126e93b3a433ec97dc47dce0e1985bda88c88c6aeca46fc7d9a68e30ab",
+		"44fdb0d69abd8068442cb2ea6df8b2f2",
+	},
+	{
+		"f2efbd358dd353639a162be39a957d27c0175d5ab72aeba4a266aeda434e4a58",
+		"65a6f7ebe48de78beb183b518589a0afacf71b40a949fa59",
+		"f7473947996e6682a3b9c720f03cfaf26bbcdaf76c83342d2ad922435e227a5d1eacbd9bd6ea1727ec19fb0e42",
+		"",
+		"778a0fb701b9d671ccfaf1454e8928158ede9bb4395119356a8133036840c1bcbb8fe5e19922fbbcf8b18596e7",
+		"9d195a89fdd29ca271405d3330f996f9",
+	},
+	{
+		"9dd674fb4a30a7bb85fc78050479ab0e2c3cc9f9f5b8689a7a67413aca304b21",
+		"ad9e8fe15940694725f232e88f79cda7c82fe1b8aae58ba4",
+		"7272bb6609cbd1399a0b89f6ea255165f99330aeb170ac88fccdd8e226df0952407e35718fb5edc9e987faabb271cc69f7e7",
+		"",
+		"846901650cb38974463a18c367676e1579ebdaf3e96b57224e842f5d5f678f3270b9a15f01241795662befb3db0768800e25",
+		"900004db3613acbeb33d65d74dd437d7",
+	},
+	{
+		"280cbe7380a0d8bb4d8dd4476012f2eeb388a37b8b71067969abb99f6a888007",
+		"2e1854617c67002599e6b077a812c326deb22fe29d093cbb",
+		"d0901ec3d31ece2832685ff577f383bdff26c31341ea254acee7c5929a5df74fea2aa964524dc680b2f55fbd4fea900e956c304cc4ac3c",
+		"",
+		"546370726cc63068d3520d67f4f57f65d03b9ecec21c2a8c7b1133089ad28b07025a7181bddeb4a49f514fac1a44f64ee3af33d778fb98",
+		"39084e33e42a1b05f58da65ba487d138",
+	},
+	{
+		"887564f75afa78f595cdadcea7340d20f5c5a2df169d0ad14b15fe32ce337004",
+		"54c11df13d1f444da80b0964caeb59474b17b23a650a33f5",
+		"f0f008eece79ecb24b715dff8a3456dfe253924b99f98f2f1b18564cced50925fca860d1c2d4785bdf4a964c76c3079efa6b37c4ba2cacc534fb590c",
+		"",
+		"32bb077268568d569b39e8ccdeeeb447ef424eaa2ffab565209a19b16a25952f897e5405bb0d67d8c9005d1c0b32687164d17fa4d0f412b80414c025",
+		"0bac7c0f8dce12917fbd4ed1738ac0cc",
+	},
+	{
+		"21c6aa88eb1a320d251f71a4b312ca75347040990d869a1dd2a1982c30fda2c7",
+		"7dead2f1a3d9d45a9124a40efe8994300976991a4417ef4d",
+		"",
+		"e1bf7de4",
+		"",
+		"341e9d0687006f981bced2f985f953e6",
+	},
+	{
+		"0c97b9a65ffcd80b8f7c20c3904d0d6dd8809a7f97d7f46d39a12c198a85da5d",
+		"1f2c1dbc5f52fc9c8f9ca7695515d01d15904b86f703fba3",
+		"ecaf65b66d",
+		"bd8a6f18",
+		"8d1b2b0e38",
+		"27a7c7ac8bda627085414f0f31206a07",
+	},
+	{
+		"4ab5e3595f39c4379a924e5f8ebcf3279075c08d18daff01d9ddfa40e03faf12",
+		"94e6ddc294f5f1531924ec018823343ebcc220a88ea5ee33",
+		"c91b73abe5316c3effc6",
+		"c576f6ea",
+		"abe960fbc64b339c53b1",
+		"7ebae48a2ff10117069324f04619ad6f",
+	},
+	{
+		"a1e6146c71c2ea22300e9063455f621e15bd5bf1a3762e17f845e1aba5dd5a9c",
+		"82ddb6929abff8a9ad03dfb86c0bb3e7c092d45ebfa60a1b",
+		"f011f32ccc2955158c117f53cf7b12",
+		"5d14bc05",
+		"44592321c665f51e9ffea052df1fea",
+		"d556798b97f9b647729801419424affc",
+	},
+	{
+		"7a1af30362c27fd55b8c24b7fca324d350decee1d1f8fae56b66253a9dd127dd",
+		"61201d6247992002e24e1a893180d4f0c19a3ae4cc74bf0c",
+		"5c7150b6a4daa362e62f82f676fdc4c4b558df64",
+		"00c49210",
+		"27d9e2730b6809c08efbd4b0d24639c7b67486f3",
+		"5889fdee25379960038778e36b2cedb2",
+	},
+	{
+		"0b3fd9073e545ac44a7967263ead139c9547f7a54f06228fd3c8609fa2620784",
+		"6450e1097d6f9ea76eb42e8e65972d501041c3a58baf8770",
+		"d679ae442b0351e5bff9906b099d45aab4f6aea5306a7a794f",
+		"318d292b",
+		"a3f9ee45316d7b0f948a26145ee4fd0552bc6dc25e577e777a",
+		"0068a401a194b8417ec0e198baa81830",
+	},
+	{
+		"047c7d378fe80c02ee48df6f679a859253aed534fdcdd87023eb3d2f93fcafe3",
+		"ed240b0ff6f8ac585b3ea1ab2dab8080fc2f6401b010c5d0",
+		"7288afb4e0fa5c58602090a75c10d84b5f5f1c0e03498519afe457251aa7",
+		"e4310302",
+		"87906b14ca3e32ab01523b31ae0bb74590ce9e1df0811e743a2c7a93415a",
+		"3a0abeab93792b1ffe768d316da74741",
+	},
+	{
+		"1ad4e42acc5dfd07eb0a2456e9103cd0e150a36c667eb2f2b73c0d1ac1089ce3",
+		"48efb52387284c5d38b4940c75f0c39a3f81f60bfebb48cb",
+		"da7edb5b3193b4484f09efa85fcf85600968ecdc537d3829a469c866ee67b0df677866",
+		"446be8e3",
+		"b76457ca99e95b6539b12f1d6bdac55a6d5c6469b1ff274459363ec05241f7e6e5d3ce",
+		"06880ee508ce929da5a81f8b9de0031c",
+	},
+	{
+		"702a554c1b703d4dd69ad51234293ab787a01e15bdb3ce88bf89e18c01a67164",
+		"ea535d9c371241b9850b8b4a596b63db79eea60bd2cd9fbb",
+		"a97156e9b39d05c00b811552d22088d7ee090a117a7f08adac574820d592021f16207720d49fb5fd",
+		"ba5790e3",
+		"8d0b2b04479c33287096f0c6276a73f6c037edc1a2b28f8d3b2b8e6d4c5f9dc5113309dd3ecb15e6",
+		"3cf303305e12924d29c223976699fb73",
+	},
+	{
+		"1bb7303fefa4d8d344bb9a215901b2314324bf1f3aeb9df5d1c1532c3a55ebf1",
+		"a304551e5f0dc98995ddfee6215a9995023a3696debfd302",
+		"6cf6819ce3e7ed9d4f85f4a5699701dbcaf3161adc210c0b7825ddfd83d6d7c685db62f68b3801ccc8a786066d",
+		"901c5feb",
+		"bc5ef09c111f76e54f897e6fce4aee1d25b6ed934f641ed5262d0c5eed45f610a6aea3b58b7771e34256d43a16",
+		"b83f73f7995ba1b243dbf48ddfeb8e3a",
+	},
+	{
+		"24b294f6cbac10d87158d1c6aca83b337d596132afac7633f69a3b3e58823f11",
+		"805772ff619cc6fcc5ec0e9965435d6f74a2290c055ec754",
+		"65e8581286868caabcec1a9814db00b805edc660b94ee3babc6ce19a3ca868bd322105484d59b4ce02ced4071bc16642a1f2",
+		"7ae1c561",
+		"fe1d463b1466e8e411f0b0700f90760472ee5141f3e5afef43fd729f1623dca75cd4d00576765b335f8b2b77b00527599cb3",
+		"111d8540fd5ec04b9ba16ed810133026",
+	},
+	{
+		"38e63e8b6402ac3f6d1641a1e3b74d2074be0fe41129975a3ff62b74ca52af05",
+		"228d671b036710cbdaa72e9bf1d9ed6982b0bb3428a69fd6",
+		"20a8d18878924d09aac32853c10e73dbd741134b7050ae6999839f2dbc727cb0052b5497c4bbd2a89e716278f15c81b871953614a49693",
+		"e9e6ac73",
+		"80e0fe8eb26e5df229c6d939c944d440a37aa3cabf76eab5b9a420095513021ea4241ab367f6f44a20817b14631549ae6c96aa963970e1",
+		"1e80fbafcc7168e0494fce4cd76d692c",
+	},
+	{
+		"4325dd8406fdb8431a81f1b5db3603995256de36121019724cca2190c87a6e83",
+		"dcbf3077b36d5d678d668fd2d0c99284c780b55c4658ea75",
+		"4f599ad04f79be9add10fdc649b8be53e1062ea5e9c2bed22265dc6fb30d5ab4fd4425b38ff14d8e68013405bec1eff8c9ef3069902e492aac73dcd9",
+		"6fa0d757",
+		"7decbdc7043495c59ecc64e720436bb0708b586a46f8745f74391477f5a2520905dfcebc3765a330999013d309dfaa997bf70bab6a0b8f4f2a2a3cdf",
+		"051ec4ecce208d9be0cd17f434e13be3",
+	},
+	{
+		"2d3d9ed4bc9eb9668733bafbb73e88be2cd17021c3a23be69b981d9f0df71df1",
+		"84cae69639240c82b58895997511f145e474ebe1b008f391",
+		"",
+		"64db597c26a4c3da",
+		"",
+		"2a22c4a962d46a719014ab7b0ffaf6d3",
+	},
+	{
+		"09ec4e79a02db53b19b54dd2d3592afc92c74ef57d1e0f51f3726a6631b1b73f",
+		"2907ced16e0777fedb1e2de30df11b3fd712af41dd714a4b",
+		"b6e50cd4ea",
+		"b5488e9b7f339b7b",
+		"0163e75330",
+		"e29401c6d756adcc516580ae656852aa",
+	},
+	{
+		"9d5ac25a417b8a57b85332979e8a7cbad23617bb27772bbccc2acb0acae7b755",
+		"ff152421688dd6af7fef87817b508493a32d97a06fbda4f3",
+		"92f4b9bc809be77e6a0d",
+		"892b793f7a6e0727",
+		"bcc594f59de8ee8c22c6",
+		"1a8275816c0d32a1b6cfd41fa3889558",
+	},
+	{
+		"eccf80c5f744d2ecc932f95ade0d9fe9327e19795023db1846d68d04720a2401",
+		"abc050fad8876589633b222d6a0f2e0bf709f73610aa23ee",
+		"45a380e438405314510c166bac6840",
+		"c32c9a1ce6852046",
+		"9fa452dc9ca04c16ff7bde9925e246",
+		"3d5e826162fa78de3fc043af26044a08",
+	},
+	{
+		"b1912d6bc3cff47f0c3beccff85d7cd915b70ab88d0d3a8a59e994e1b0da8ac8",
+		"d8756090a42eea14ff25be890e66bfe4949fad498776ea20",
+		"e2f85df2ebcfa6045bd521abfe8af37fc88a0be1",
+		"4576bb59b78032c8",
+		"5eb6324aa48e0a4f72f5cb0a4917faf93af4209c",
+		"774f8077f039588495045fee07950e14",
+	},
+	{
+		"85162b111c9f3163f57c2cbc311a1e9aeed9dd6136b5784bc9c0b5052f8bffbd",
+		"23cdb8b546bb8a5a746b24446f0ab4199f0543d915ff51f1",
+		"dc81000077d5743beef09ac91663885d984212bbccf3dbe6f3",
+		"3084f3e9c4d0a15f",
+		"692d17ae0b524ec6edc0cf49b69ac90c99bed44691f7ae63b7",
+		"efe72ff84b3bccb4d83a27ddc574bc21",
+	},
+	{
+		"b05ca358d8ca79f51283d83e2673bfb741c379ba271a773b8dd9c6a108e758d3",
+		"9a53ad79f535c6e9da011463063c896f2ec7645e6e3548fc",
+		"44e793742c774020e7349c996418042dc0dc30ee2bfd2654008c8929a436",
+		"71ab5948c5e0f4c6",
+		"c5eddb7aeaa175b5f3dab68cf746f2acaf56fc62b29804629e25e2d63879",
+		"bec3b7a8b8dad22ff3d14d26273294d2",
+	},
+	{
+		"abb5136a01354c765a96e832df58bec3b088bd19dc4d6bd6674f2f02007ebdaa",
+		"71267ac9f4fe5caa1d52cd85948a170a778f0141d54dbffe",
+		"afb526fe41c4e2a767ce77c4145b9d054268f5f3b279237dec97f8bc46f9d158868b86",
+		"047baa2b04748b62",
+		"0032d4c1e65da2266539464c5d3c2b1618454a6af0e7f1e3cfc87845c75f2f4ae8b03f",
+		"b526a95a33f17ab61f2cdfc1e2dd486a",
+	},
+	{
+		"bb826ed38008a0d7fb34c0c1a1a1149d2cad16b691d5129cc83f5eff2b3e5748",
+		"4e02fe0915d81e9d5a62e5b3551b9db882e3873c0aaa230d",
+		"20270d291a8d9791b0f5e35a64387bb4237bad61169841d7e1667c994ad49869c7d5580ffa752a2d",
+		"db852a275081e29b",
+		"d740012efb7e1bb986ce2c535134a45f658b92163c109bdecf1ce5b836879fe9e006a56be1fac8d7",
+		"21e931042e7df80695262198a06286c9",
+	},
+	{
+		"938d2c59f6f3e2e7316726537932372e05e8c1b5577aae0ee870bf712ff001ab",
+		"fb4d71cf7eb2f70df9759a64c76a36b75203f88bf64f4edb",
+		"8910415d674a93c54c8f5e4aa88e59648d9a0a5039a66837d58ab14f0665a5f6d9af9b839f9033d0fe8bc58f19",
+		"a3fca278a63bf944",
+		"1905c6987a702980b7f87f1ed2d3ae073abe1401b23434f3db43b5c37c979c2068ce9a92afedcdc218003848ea",
+		"1bd712f64777381f68be5ccc73f364a3",
+	},
+	{
+		"dd0521842f498d23236692a22db0eb2f0f14fef57577e5fb194503e206b0973d",
+		"519e0eee8f86c75c7a364e0905a5d10d82073e11b91083a5",
+		"61ff13acb99c5a7fd1921ec787c8de23c1a712ff002b08cecc644a78c47341eab78e7680380c93c7d53d5e56ef050d6ff192",
+		"bb5c4e5ae8f7e461",
+		"9bfdb0fd195fa5d37da3416b3b1e8f67bd2a456eb0317c02aabf9aac9d833a19bda299e6388e7b7119be235761477a34d49e",
+		"0f0c03b8423583cb8305a74f622fa1f9",
+	},
+	{
+		"189bd84be3fb02723539b29cf76d41507c8b85b7217777ee1fb8f84a24aa7fee",
+		"ef1bf39f22ba2edf86853505c24fafdf62c1a067963c63ba",
+		"d5f96e240b5dd77b9fb2bf11c154fcbff312a791c3eb0717684e4fd84bf943e788050b47e76c427f42f3e5344b2636091603ba3b1d7a91",
+		"93368a8e0900c7b6",
+		"c55a8b7f587bee4f97514582c5115582abffd6312914d76c2568be6836f62ba098789ed897c9a7508a5dc214bf8c218664f29941ccdfd6",
+		"78f87352dcb1143038c95dc6e7352cfd",
+	},
+	{
+		"23a2dbfcd02d265805169fa86e6927c7d49c9a24d2707884e18955e32dafc542",
+		"305c7851f46f23ea8d832d5ed09d266714fd14f82ba0f69c",
+		"224de94a938d49cad46144e657e548bd86690a1b57b81558095eace59df1c552600dea389aaa609304fbc1eadf2241f2118c8bdf04522e1898efe1d4",
+		"0075b20502bd29b2",
+		"8e10c59369bbb0d72958100b05788498f59588795e075b8bce21d92d320206348b04010ced9b8cd3d651e825488915ce4a6e4f1af2f4d2f77b955376",
+		"c39f0595ae8112dea6ef96df1c12458b",
+	},
+	{
+		"264e3c3f47bdf795cdde57d9a30be5a4da8b18463c0e3e05df28b7bf4e56410b",
+		"3ee09b6e205c261bf48ac53a9ba0afa460a5d5c0f2d80be8",
+		"",
+		"8eeec09d8972cb8ab0069554",
+		"",
+		"245a034d84edab9fa6f0decb6b984766",
+	},
+	{
+		"d8ba98a272b5f91797b04b114311c3b92b7f2e3bb72edb7f78ed311b9f8ea2ad",
+		"481de9a06eee76a501e3c2b9d7423d90596193ad9d8a6564",
+		"9ee1a3134d",
+		"928653701f6d6c8429b08c0d",
+		"459a07898f",
+		"9188ec8d8e3bd91dcfda48fcc76773f7",
+	},
+	{
+		"ac9afd627a745df682bb003517056f07876eb94d2f8c610c61b6ac0d34ec4ec0",
+		"eaae7b8704530db1e8c3dcc968a00604a333c7c27ba51b16",
+		"f7c3f6ee2e9c03394dc8",
+		"796620b367d5f041821baf69",
+		"d4a69005790cc91d8d34",
+		"e4c83def113afcf83a1ea8cb204a0eae",
+	},
+	{
+		"ea1a07c1fd60a5421f1fb6c43b4318090e290c97aa3bfa037e6fc5ee00fd47d4",
+		"37327805cce92b38a669affbca1de92e068727fcf6fbb09a",
+		"7002ca765b91913ee719e7521ef5ac",
+		"64e7c48fc3041eac0734737f",
+		"9d8857a8c52a9ab3bf44b024b191b6",
+		"d072c31714a7d0fe1596fd443a96e715",
+	},
+	{
+		"b3beb34fe0229fc8f49b354e941025bde6a788f25017a60e8a49591ed5d7e7da",
+		"dd0e9fec76de1f6efb022b12164f7e9248b8e8c01d14ac02",
+		"acf360d7529a42be1f132f74745a940da9e823f2",
+		"1489ca8d852f0a8547dbe8bc",
+		"2e8718372d6e8167213cf112dc41c80377244f5a",
+		"e4f31e8f84b9356999dc60989009e698",
+	},
+	{
+		"9357cecd10bab8d2e42ed88c0386204827c3b76e9e51150d09fd4e3b4e0e1e6f",
+		"81f2106a5379e0ed861cf76b3cf95afb17515478b5cbcae9",
+		"ee51a0f25d091288b5e2b91ad11d491329e48b35a18a3a8685",
+		"b80cb677f4b409cd1537363b",
+		"f681f19fa8de1fdea3538001a46f30fa6333b76d6439337e68",
+		"afad5e6d282d9df6d8119c32237b3e60",
+	},
+	{
+		"9f868600fbf81e40398b7dfb201fcae35d34bba10908860b0b2bf8b942b4e8fa",
+		"2ddcc13c97185614095d437900b8c0a9170e0a4a50e46ba5",
+		"133fa3ac176fee6df67472752e41c6834f13300c0064ff5b190f903b7ac7",
+		"0d61321fbee8bb1f3f5cb454",
+		"b93abb311ec0bf018dc300c7d511b42ade72780373186e231820b44f22f0",
+		"f8bd2f649a337783ff911e37966037bd",
+	},
+	{
+		"05affcdfce0a28539924370db8d80a78b835254778ec41acbff52bfab092fa33",
+		"3edaeb185f7273b1a7cccba54f84c5f7d6583433b49d3694",
+		"7657581faad266cc1037962a380c8aa5306f88000427d0a05397696b503790ad2643c6",
+		"d7c213e9e6f4a40f3e5b662c",
+		"5eb19080aadc89f2329da4f5c41dc60568651c424c1b05d827f2bfb8dbff42c5a08224",
+		"2da20087b5674f0b967d1baa664bbd82",
+	},
+	{
+		"645ed60ec74ddfe1f02694792db4436c262d20405d8645cd9755d64876219799",
+		"d83665b44c1fdf567299f2b8501e9c0e7ae2dda0bb8f2c82",
+		"ceee69d32ad4667a00909964d9611bf34fd98be41ad7f0feaaaff8169060d64cf310c13bcb9394cf",
+		"57379f8f44191ec9cf3b1a07",
+		"4496a0666f0f895ebce224b448a04502f2ae7b354d868b7c54295bf051162e82c530c767d1ffd2cc",
+		"1ffc56da4fb961ffdfabe66d82ec8f29",
+	},
+	{
+		"06624c9a75bb7dbe224a3f23791281f53c40b407a14161a3f82f34924623dc02",
+		"e647b8b4739bf542a81d72d695e1cd6ba348fa593987ac47",
+		"2658763f8d70e8c3303582d66ba3d736ce9d407e9507f6c6627e382d0144da157d73d0aee10ef034083cdd9013",
+		"75536443a6c2189a57d553bb",
+		"305cab5c2f9a6edccac307d6965febe3c86f2a1e31ac8c74e88924a10c2a29106bce980c803b7886985bba8ec5",
+		"8c12bb58c84175b9f601b704d0f8a25c",
+	},
+	{
+		"63aeb46083100bbcc430f4f09bcc34410df9cfd5883d629e4af8645ffabb89c2",
+		"b09830874dc549195a5d6da93b9dcc12aa1ec8af201c96bd",
+		"1b3c9050e0a062f5a5cff7bec8706864cf8648142ec5cb1f9867ace384e9b2bba33aab8dc83e83b2d2fac70cd5189f2b5ab5",
+		"7dcc05b0940198bd5c68cdf1",
+		"d8b22e5d381de08a50b163c00dbbca6c07d61c80199cebd52234c7bd4f7ed0a90d47ef05617cdb8e3f782875ae629c0f0ad6",
+		"194077f0e6d415bf7307d171e8484a9c",
+	},
+	{
+		"4826c1bf8b48088fece4008922173c500ff45790f945b1027f36110da4fecc92",
+		"3a78fc7397944d762303b0a75974ac92a60e250bf112600a",
+		"d26e3a2b92120ff8056bb992660cc8a2364792589c16a518b8d232b8184aed05ba8d4fd0b2ad2b928cd873e11905a21ffece5f1e63c974",
+		"904d2cd3e50f7bfb9352f142",
+		"21f4cf679662fad36f57945fc0c0753c3791261eb58d643278dfe1f14bfb585c5a01370ba96f18dc3f6b6945a2c6997330b24f12f5219a",
+		"95397c54428f9d069c511b5c82e0151c",
+	},
+	{
+		"ec526c03d8a08e8a63751112428a76399c399e8b83d98c9247c73164805ac8fe",
+		"2cc1a6ae89c2a091415fa2964b44a0e5da629d40d77b77f1",
+		"567377f5b6df5442e70bc9a31bc450bd4febfcf89d7ca611353c7e612d8b7e36e859f6365ec7e5e99e9e0e882532666dd7203d06f6e25439ed871237",
+		"35575b56716868b66cd21e24",
+		"6b738274fe974438f1f5fca8ef1ee7df664f1e72bc54ccd3fb58c4a3df67ef9a73261df41ffe9c52aeafc8be4f6524baf9efb1558d4a57defec7bee3",
+		"92599d4b14a795e8c375ec2a8960b4dc",
+	},
+}
diff --git a/go/subtle/daead/BUILD.bazel b/go/subtle/daead/BUILD.bazel
new file mode 100644
index 0000000..6f690e4
--- /dev/null
+++ b/go/subtle/daead/BUILD.bazel
@@ -0,0 +1,27 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+    name = "go_default_library",
+    srcs = [
+        "aes_siv.go",
+    ],
+    importpath = "github.com/google/tink/go/subtle/daead",
+    visibility = ["//visibility:public"],
+    deps = [
+        "//go/tink:go_default_library",
+    ],
+)
+
+go_test(
+    name = "go_default_test",
+    srcs = [
+        "aes_siv_test.go",
+    ],
+    data = [
+        "@wycheproof//testvectors:all",  # keep
+    ],
+    deps = [
+        ":go_default_library",
+        "//go/subtle/random:go_default_library",
+    ],
+)
diff --git a/go/subtle/daead/aes_siv.go b/go/subtle/daead/aes_siv.go
new file mode 100644
index 0000000..b2fc913
--- /dev/null
+++ b/go/subtle/daead/aes_siv.go
@@ -0,0 +1,252 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// Package daead provides subtle implementations of the DeterministicAEAD primitive.
+package daead
+
+import (
+	"crypto/aes"
+	"crypto/cipher"
+	"errors"
+	"fmt"
+	"math"
+
+	"github.com/google/tink/go/tink"
+)
+
+// AESSIV is an implemenatation of AES-SIV-CMAC as defined in
+// https://tools.ietf.org/html/rfc5297 .
+// AESSIV implements a deterministic encryption with additional
+// data (i.e. the DeterministicAEAD interface). Hence the implementation
+// below is restricted to one AD component.
+//
+// Security:
+// =========
+// Chatterjee, Menezes and Sarkar analyze AES-SIV in Section 5.1 of
+// https://www.math.uwaterloo.ca/~ajmeneze/publications/tightness.pdf
+// Their analysis shows that AES-SIV is susceptible to an attack in
+// a multi-user setting. Concretely, if an attacker knows the encryption
+// of a message m encrypted and authenticated with k different keys,
+// then it is possible  to find one of the MAC keys in time 2^b / k
+// where b is the size of the MAC key. A consequence of this attack
+// is that 128-bit MAC keys give unsufficient security.
+// Since 192-bit AES keys are not supported by tink for voodoo reasons
+// and RFC 5297 only supports same size encryption and MAC keys this
+// implies that keys must be 64 bytes (2*256 bits) long.
+type AESSIV struct {
+	K1     []byte
+	K2     []byte
+	CmacK1 []byte
+	CmacK2 []byte
+	Cipher cipher.Block
+}
+
+// AESSIVKeySize is the key size in bytes.
+const AESSIVKeySize = 64
+
+// Assert that AESSIV implements the DeterministicAEAD interface.
+var _ tink.DeterministicAEAD = (*AESSIV)(nil)
+
+// NewAESSIV returns an AESSIV instance.
+func NewAESSIV(key []byte) (*AESSIV, error) {
+	if len(key) != AESSIVKeySize {
+		return nil, fmt.Errorf("aes_siv: invalid key size %d", len(key))
+	}
+
+	k1 := key[:32]
+	k2 := key[32:]
+	c, err := aes.NewCipher(k1)
+	if err != nil {
+		return nil, fmt.Errorf("aes_siv: aes.NewCipher(%s) failed, %v", k1, err)
+	}
+
+	block := make([]byte, aes.BlockSize)
+	c.Encrypt(block, block)
+	multiplyByX(block)
+	cmacK1 := make([]byte, aes.BlockSize)
+	copy(cmacK1, block)
+	multiplyByX(block)
+	cmacK2 := make([]byte, aes.BlockSize)
+	copy(cmacK2, block)
+
+	return &AESSIV{
+		K1:     k1,
+		K2:     k2,
+		CmacK1: cmacK1,
+		CmacK2: cmacK2,
+		Cipher: c,
+	}, nil
+}
+
+// multiplyByX multiplies an element in GF(2^128) by its generator.
+// This functions is incorrectly named "doubling" in section 2.3 of RFC 5297.
+func multiplyByX(block []byte) {
+	carry := block[0] >> 7
+	for i := 0; i < aes.BlockSize-1; i++ {
+		block[i] = (block[i] << 1) | (block[i+1] >> 7)
+	}
+
+	if carry == 1 {
+		block[aes.BlockSize-1] = (block[aes.BlockSize-1] << 1) ^ 0x87
+	} else {
+		block[aes.BlockSize-1] = (block[aes.BlockSize-1] << 1)
+	}
+}
+
+// EncryptDeterministically deterministically encrypts plaintext with additionalData as
+// additional authenticated data.
+func (asc *AESSIV) EncryptDeterministically(pt, aad []byte) ([]byte, error) {
+	siv := make([]byte, aes.BlockSize)
+	asc.s2v(pt, aad, siv)
+
+	ct := make([]byte, len(pt)+aes.BlockSize)
+	copy(ct[:aes.BlockSize], siv)
+	if err := asc.ctrCrypt(siv, pt, ct[aes.BlockSize:]); err != nil {
+		return nil, err
+	}
+
+	return ct, nil
+}
+
+// DecryptDeterministically deterministically decrypts ciphertext with additionalData as
+// additional authenticated data.
+func (asc *AESSIV) DecryptDeterministically(ct, aad []byte) ([]byte, error) {
+	if len(ct) < aes.BlockSize {
+		return nil, errors.New("aes_siv: ciphertext is too short")
+	}
+
+	pt := make([]byte, len(ct)-aes.BlockSize)
+	siv := ct[:aes.BlockSize]
+	asc.ctrCrypt(siv, ct[aes.BlockSize:], pt)
+	s2v := make([]byte, aes.BlockSize)
+	asc.s2v(pt, aad, s2v)
+
+	diff := byte(0)
+	for i := 0; i < aes.BlockSize; i++ {
+		diff |= siv[i] ^ s2v[i]
+	}
+	if diff != 0 {
+		return nil, errors.New("aes_siv: invalid ciphertext")
+	}
+
+	return pt, nil
+}
+
+// ctrCrypt encrypts (or decrypts) the bytes in in using an SIV and writes the result to out.
+func (asc *AESSIV) ctrCrypt(siv, in, out []byte) error {
+	// siv might be used outside of ctrCrypt(), so making a copy of it.
+	iv := make([]byte, aes.BlockSize)
+	copy(iv, siv)
+	iv[8] &= 0x7f
+	iv[12] &= 0x7f
+
+	c, err := aes.NewCipher(asc.K2)
+	if err != nil {
+		return fmt.Errorf("aes_siv: aes.NewCipher(%s) failed, %v", asc.K2, err)
+	}
+
+	steam := cipher.NewCTR(c, iv)
+	steam.XORKeyStream(out, in)
+	return nil
+}
+
+// s2v is a Pseudo-Random Function (PRF) construction: https://tools.ietf.org/html/rfc5297.
+func (asc *AESSIV) s2v(msg, aad, siv []byte) {
+	block := make([]byte, aes.BlockSize)
+	asc.cmac(block, block)
+	multiplyByX(block)
+
+	aadMac := make([]byte, aes.BlockSize)
+	asc.cmac(aad, aadMac)
+	xorBlock(aadMac, block)
+
+	if len(msg) >= aes.BlockSize {
+		asc.cmacLong(msg, block, siv)
+	} else {
+		multiplyByX(block)
+		for i := 0; i < len(msg); i++ {
+			block[i] ^= msg[i]
+		}
+		block[len(msg)] ^= 0x80
+		asc.cmac(block, siv)
+	}
+}
+
+// cmacLong computes CMAC(XorEnd(data, last)), where XorEnd xors the bytes in last to the last bytes in data.
+// The size of the data must be at least 16 bytes.
+func (asc *AESSIV) cmacLong(data, last, mac []byte) {
+	block := make([]byte, aes.BlockSize)
+	copy(block, data[:aes.BlockSize])
+
+	idx := aes.BlockSize
+	for aes.BlockSize <= len(data)-idx {
+		asc.Cipher.Encrypt(block, block)
+		xorBlock(data[idx:idx+aes.BlockSize], block)
+		idx += aes.BlockSize
+	}
+
+	remaining := len(data) - idx
+	for i := 0; i < aes.BlockSize-remaining; i++ {
+		block[remaining+i] ^= last[i]
+	}
+	if remaining == 0 {
+		xorBlock(asc.CmacK1, block)
+	} else {
+		asc.Cipher.Encrypt(block, block)
+		for i := 0; i < remaining; i++ {
+			block[i] ^= last[aes.BlockSize-remaining+i]
+			block[i] ^= data[idx+i]
+		}
+		block[remaining] ^= 0x80
+		xorBlock(asc.CmacK2, block)
+	}
+
+	asc.Cipher.Encrypt(mac, block)
+}
+
+// cmac computes a CMAC of some data.
+func (asc *AESSIV) cmac(data, mac []byte) {
+	numBs := int(math.Ceil(float64(len(data)) / aes.BlockSize))
+	if numBs == 0 {
+		numBs = 1
+	}
+	lastBSize := len(data) - (numBs-1)*aes.BlockSize
+
+	block := make([]byte, aes.BlockSize)
+	idx := 0
+	for i := 0; i < numBs-1; i++ {
+		xorBlock(data[idx:idx+aes.BlockSize], block)
+		asc.Cipher.Encrypt(block, block)
+		idx += aes.BlockSize
+	}
+	for j := 0; j < lastBSize; j++ {
+		block[j] ^= data[idx+j]
+	}
+
+	if lastBSize == aes.BlockSize {
+		xorBlock(asc.CmacK1, block)
+	} else {
+		block[lastBSize] ^= 0x80
+		xorBlock(asc.CmacK2, block)
+	}
+
+	asc.Cipher.Encrypt(mac, block)
+}
+
+// xorBlock sets block[i] = x[i] ^ block[i].
+func xorBlock(x, block []byte) {
+	for i := 0; i < aes.BlockSize; i++ {
+		block[i] ^= x[i]
+	}
+}
diff --git a/go/subtle/daead/aes_siv_test.go b/go/subtle/daead/aes_siv_test.go
new file mode 100644
index 0000000..9cc98c0
--- /dev/null
+++ b/go/subtle/daead/aes_siv_test.go
@@ -0,0 +1,319 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package daead_test
+
+import (
+	"bytes"
+	"encoding/hex"
+	"encoding/json"
+	"os"
+	"testing"
+
+	"github.com/google/tink/go/subtle/daead"
+	"github.com/google/tink/go/subtle/random"
+)
+
+type testData struct {
+	Algorithm        string
+	GeneratorVersion string
+	NumberOfTests    uint32
+	TestGroups       []*testGroup
+}
+
+type testGroup struct {
+	KeySize uint32
+	Type    string
+	Tests   []*testCase
+}
+
+type testCase struct {
+	TcID   uint32
+	Key    string
+	Aad    string
+	Msg    string
+	Ct     string
+	Result string
+}
+
+func TestAESSIV_EncryptDecrypt(t *testing.T) {
+	keyStr :=
+		"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" +
+			"00112233445566778899aabbccddeefff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
+	key, _ := hex.DecodeString(keyStr)
+	msg := []byte("Some data to encrypt.")
+	aad := []byte("Additional data")
+
+	a, err := daead.NewAESSIV(key)
+	if err != nil {
+		t.Errorf("NewAESSIV(key) = _, %v, want _, nil", err)
+	}
+
+	ct, err := a.EncryptDeterministically(msg, aad)
+	if err != nil {
+		t.Errorf("Unexpected encryption error: %v", err)
+	}
+
+	if pt, err := a.DecryptDeterministically(ct, aad); err != nil {
+		t.Errorf("Unexpected decryption error: %v", err)
+	} else if !bytes.Equal(pt, msg) {
+		t.Errorf("Mismatched plaintexts: got %v, want %v", pt, msg)
+	}
+}
+
+func TestAESSIV_EmptyPlaintext(t *testing.T) {
+	keyStr :=
+		"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" +
+			"00112233445566778899aabbccddeefff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
+	key, _ := hex.DecodeString(keyStr)
+	aad := []byte("Additional data")
+
+	a, err := daead.NewAESSIV(key)
+	if err != nil {
+		t.Errorf("NewAESSIV(key) = _, %v, want _, nil", err)
+	}
+
+	ct, err := a.EncryptDeterministically(nil, aad)
+	if err != nil {
+		t.Errorf("Unexpected encryption error: %v", err)
+	}
+	if pt, err := a.DecryptDeterministically(ct, aad); err != nil {
+		t.Errorf("Unexpected decryption error: %v", err)
+	} else if !bytes.Equal(pt, []byte{}) {
+		t.Errorf("Mismatched plaintexts: got %v, want []", pt)
+	}
+
+	ct, err = a.EncryptDeterministically([]byte{}, aad)
+	if err != nil {
+		t.Errorf("Unexpected encryption error: %v", err)
+	}
+	if pt, err := a.DecryptDeterministically(ct, aad); err != nil {
+		t.Errorf("Unexpected decryption error: %v", err)
+	} else if !bytes.Equal(pt, []byte{}) {
+		t.Errorf("Mismatched plaintexts: got %v, want []", pt)
+	}
+}
+
+func TestAESSIV_EmptyAdditionalData(t *testing.T) {
+	keyStr :=
+		"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" +
+			"00112233445566778899aabbccddeefff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
+	key, _ := hex.DecodeString(keyStr)
+
+	a, err := daead.NewAESSIV(key)
+	if err != nil {
+		t.Errorf("NewAESSIV(key) = _, %v, want _, nil", err)
+	}
+
+	ct, err := a.EncryptDeterministically(nil, nil)
+	if err != nil {
+		t.Errorf("Unexpected encryption error: %v", err)
+	}
+
+	if pt, err := a.DecryptDeterministically(ct, nil); err != nil {
+		t.Errorf("Unexpected decryption error: %v", err)
+	} else if !bytes.Equal(pt, []byte{}) {
+		t.Errorf("Mismatched plaintexts: got %v, want []", pt)
+	}
+
+	if pt, err := a.DecryptDeterministically(ct, []byte{}); err != nil {
+		t.Errorf("Unexpected decryption error: %v", err)
+	} else if !bytes.Equal(pt, []byte{}) {
+		t.Errorf("Mismatched plaintexts: got %v, want []", pt)
+	}
+}
+
+func TestAESSIV_KeySizes(t *testing.T) {
+	keyStr :=
+		"198371900187498172316311acf81d238ff7619873a61983d619c87b63a1987f" +
+			"987131819803719b847126381cd763871638aa71638176328761287361231321" +
+			"812731321de508761437195ff231765aa4913219873ac6918639816312130011" +
+			"abc900bba11400187984719827431246bbab1231eb4145215ff7141436616beb" +
+			"9817298148712fed3aab61000ff123313e"
+	key, _ := hex.DecodeString(keyStr)
+
+	for i := 0; i < len(key); i++ {
+		_, err := daead.NewAESSIV(key[:i])
+		if i == daead.AESSIVKeySize && err != nil {
+			t.Errorf("Rejected valid key size: %v, %v", i, err)
+		}
+		if i != daead.AESSIVKeySize && err == nil {
+			t.Errorf("Allowed invalid key size: %v", i)
+		}
+	}
+}
+
+func TestAESSIV_MessageSizes(t *testing.T) {
+	keyStr :=
+		"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" +
+			"00112233445566778899aabbccddeefff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
+	key, _ := hex.DecodeString(keyStr)
+	aad := []byte("Additional data")
+
+	a, err := daead.NewAESSIV(key)
+	if err != nil {
+		t.Errorf("NewAESSIV(key) = _, %v, want _, nil", err)
+	}
+
+	for i := uint32(0); i < 1024; i++ {
+		msg := random.GetRandomBytes(i)
+		ct, err := a.EncryptDeterministically(msg, aad)
+		if err != nil {
+			t.Errorf("Unexpected encryption error: %v", err)
+		}
+		if pt, err := a.DecryptDeterministically(ct, aad); err != nil {
+			t.Errorf("Unexpected decryption error: %v", err)
+		} else if !bytes.Equal(pt, msg) {
+			t.Errorf("Mismatched plaintexts: got %v, want %v", pt, msg)
+		}
+	}
+
+	for i := uint32(1024); i < 100000; i += 5000 {
+		msg := random.GetRandomBytes(i)
+		ct, err := a.EncryptDeterministically(msg, aad)
+		if err != nil {
+			t.Errorf("Unexpected encryption error: %v", err)
+		}
+		if pt, err := a.DecryptDeterministically(ct, aad); err != nil {
+			t.Errorf("Unexpected decryption error: %v", err)
+		} else if !bytes.Equal(pt, msg) {
+			t.Errorf("Mismatched plaintexts: got %v, want %v", pt, msg)
+		}
+	}
+}
+
+func TestAESSIV_AdditionalDataSizes(t *testing.T) {
+	keyStr :=
+		"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" +
+			"00112233445566778899aabbccddeefff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
+	key, _ := hex.DecodeString(keyStr)
+	msg := []byte("Some data to encrypt.")
+
+	a, err := daead.NewAESSIV(key)
+	if err != nil {
+		t.Errorf("NewAESSIV(key) = _, %v, want _, nil", err)
+	}
+
+	for i := uint32(0); i < 1024; i++ {
+		aad := random.GetRandomBytes(i)
+		ct, err := a.EncryptDeterministically(msg, aad)
+		if err != nil {
+			t.Errorf("Unexpected encryption error: %v", err)
+		}
+		if pt, err := a.DecryptDeterministically(ct, aad); err != nil {
+			t.Errorf("Unexpected decryption error: %v", err)
+		} else if !bytes.Equal(pt, msg) {
+			t.Errorf("Mismatched plaintexts: got %v, want %v", pt, msg)
+		}
+	}
+}
+
+func TestAESSIV_CiphertextModifications(t *testing.T) {
+	keyStr :=
+		"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" +
+			"00112233445566778899aabbccddeefff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
+	key, _ := hex.DecodeString(keyStr)
+	aad := []byte("Additional data")
+
+	a, err := daead.NewAESSIV(key)
+	if err != nil {
+		t.Errorf("NewAESSIV(key) = _, %v, want _, nil", err)
+	}
+
+	for i := uint32(0); i < 50; i++ {
+		msg := random.GetRandomBytes(i)
+		ct, err := a.EncryptDeterministically(msg, aad)
+		if err != nil {
+			t.Errorf("Unexpected encryption error: %v", err)
+		}
+		for j := 0; j < len(ct); j++ {
+			for b := uint32(0); b < 8; b++ {
+				ct[j] ^= 1 << b
+				if _, err := a.DecryptDeterministically(ct, aad); err == nil {
+					t.Errorf("Modified ciphertext decrypted: byte %d, bit %d", j, b)
+				}
+				ct[j] ^= 1 << b
+			}
+		}
+	}
+}
+
+func TestAESSIV_WycheproofVectors(t *testing.T) {
+	f, err := os.Open("../../../../wycheproof/testvectors/aes_siv_cmac_test.json")
+	if err != nil {
+		t.Fatalf("Cannot open file: %s, make sure that github.com/google/wycheproof is in your gopath.", err)
+	}
+	parser := json.NewDecoder(f)
+	data := new(testData)
+	if err := parser.Decode(data); err != nil {
+		t.Fatalf("Cannot decode test data: %s", err)
+	}
+
+	for _, g := range data.TestGroups {
+		if g.KeySize/8 != daead.AESSIVKeySize {
+			continue
+		}
+
+		for _, tc := range g.Tests {
+			key, err := hex.DecodeString(tc.Key)
+			if err != nil {
+				t.Errorf("#%d, cannot decode key: %s", tc.TcID, err)
+			}
+			aad, err := hex.DecodeString(tc.Aad)
+			if err != nil {
+				t.Errorf("#%d, cannot decode aad: %s", tc.TcID, err)
+			}
+			msg, err := hex.DecodeString(tc.Msg)
+			if err != nil {
+				t.Errorf("#%d, cannot decode msg: %s", tc.TcID, err)
+			}
+			ct, err := hex.DecodeString(tc.Ct)
+			if err != nil {
+				t.Errorf("#%d, cannot decode ct: %s", tc.TcID, err)
+			}
+
+			a, err := daead.NewAESSIV(key)
+			if err != nil {
+				t.Errorf("NewAESSIV(key) = _, %v, want _, nil", err)
+				continue
+			}
+
+			// EncryptDeterministically should always succeed since msg and aad are valid inputs.
+			gotCt, err := a.EncryptDeterministically(msg, aad)
+			if err != nil {
+				t.Errorf("#%d, unexpected encryption error: %v", tc.TcID, err)
+			} else {
+				if tc.Result == "valid" && !bytes.Equal(gotCt, ct) {
+					t.Errorf("#%d, incorrect encryption: got %v, want %v", tc.TcID, gotCt, ct)
+				}
+				if tc.Result == "invalid" && bytes.Equal(gotCt, ct) {
+					t.Errorf("#%d, invalid encryption: got %v, want %v", tc.TcID, gotCt, ct)
+				}
+			}
+
+			pt, err := a.DecryptDeterministically(ct, aad)
+			if tc.Result == "valid" {
+				if err != nil {
+					t.Errorf("#%d, unexpected decryption error: %v", tc.TcID, err)
+				} else if !bytes.Equal(pt, msg) {
+					t.Errorf("#%d, incorrect decryption: got %v, want %v", tc.TcID, pt, msg)
+				}
+			} else {
+				if err == nil {
+					t.Errorf("#%d, decryption error expected: got nil", tc.TcID)
+				}
+			}
+		}
+	}
+}
diff --git a/go/subtle/hybrid/BUILD.bazel b/go/subtle/hybrid/BUILD.bazel
new file mode 100644
index 0000000..0bc34ea
--- /dev/null
+++ b/go/subtle/hybrid/BUILD.bazel
@@ -0,0 +1,38 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+    name = "go_default_library",
+    srcs = [
+        "ecies_aead_hkdf_dem_helper.go",
+        "ecies_aead_hkdf_hybrid_decrypt.go",
+        "ecies_aead_hkdf_hybrid_encrypt.go",
+        "ecies_hkdf_recipient_kem.go",
+        "ecies_hkdf_sender_kem.go",
+        "elliptic_curves.go",
+        "hkdf.go"
+        ],
+    importpath = "github.com/google/tink/go/subtle/hybrid",
+    visibility = ["//visibility:public"],
+    deps = [
+        "//go/subtle:go_default_library",
+        "//go/tink:go_default_library",
+        "@org_golang_x_crypto//hkdf:go_default_library",
+    ],
+)
+
+go_test(
+    name = "go_default_test",
+    srcs = [
+            "elliptic_curves_test.go",
+            "hkdf_test.go"
+            ],
+    data = ["@wycheproof//testvectors:all",],
+    embed = [":go_default_library",],
+    deps = [
+        "//go/subtle/random:go_default_library",
+        "@org_golang_x_crypto//hkdf:go_default_library",
+    ],
+)
+
+
+
diff --git a/go/tink/private_key_manager.go b/go/subtle/hybrid/ecies_aead_hkdf_dem_helper.go
similarity index 64%
copy from go/tink/private_key_manager.go
copy to go/subtle/hybrid/ecies_aead_hkdf_dem_helper.go
index 7cf169a..0d3f370 100644
--- a/go/tink/private_key_manager.go
+++ b/go/subtle/hybrid/ecies_aead_hkdf_dem_helper.go
@@ -12,16 +12,12 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-package tink
+package hybrid
 
-import (
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
-)
+import "github.com/google/tink/go/tink"
 
-// PrivateKeyManager is a special type of KeyManager that understands private key types.
-type PrivateKeyManager interface {
-	KeyManager
-
-	// GetPublicKeyData extracts the public key data from the private key.
-	GetPublicKeyData(serializedKey []byte) (*tinkpb.KeyData, error)
+// EciesAeadHkdfDemHelper a helper for DEM (data encapsulation mechanism) of ECIES-AEAD-HKDF.
+type EciesAeadHkdfDemHelper interface {
+	getSymmetricKeySize() uint32
+	getAead(symmetricKeyValue []byte) (tink.AEAD, error)
 }
diff --git a/go/subtle/hybrid/ecies_aead_hkdf_hybrid_decrypt.go b/go/subtle/hybrid/ecies_aead_hkdf_hybrid_decrypt.go
new file mode 100644
index 0000000..50ccdb6
--- /dev/null
+++ b/go/subtle/hybrid/ecies_aead_hkdf_hybrid_decrypt.go
@@ -0,0 +1,71 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package hybrid
+
+import (
+	"errors"
+
+	"github.com/google/tink/go/tink"
+)
+
+// EciesAeadHkdfHybridDecrypt is an instance of ECIES decryption with HKDF-KEM (key encapsulation mechanism)
+// and AEAD-DEM (data encapsulation mechanism).
+type EciesAeadHkdfHybridDecrypt struct {
+	privateKey   *ECPrivateKey
+	hkdfSalt     []byte
+	hkdfHMACAlgo string
+	pointFormat  string
+	demHelper    EciesAeadHkdfDemHelper
+}
+
+var _ tink.HybridDecrypt = (*EciesAeadHkdfHybridDecrypt)(nil)
+
+// NewEciesAeadHkdfHybridDecrypt returns ECIES decryption construct with HKDF-KEM (key encapsulation mechanism)
+// and AEAD-DEM (data encapsulation mechanism).
+func NewEciesAeadHkdfHybridDecrypt(pvt *ECPrivateKey, hkdfSalt []byte, hkdfHMACAlgo string, ptFormat string, demHelper EciesAeadHkdfDemHelper) (*EciesAeadHkdfHybridDecrypt, error) {
+	return &EciesAeadHkdfHybridDecrypt{
+		privateKey:   pvt,
+		hkdfSalt:     hkdfSalt,
+		hkdfHMACAlgo: hkdfHMACAlgo,
+		pointFormat:  ptFormat,
+		demHelper:    demHelper,
+	}, nil
+}
+
+// Decrypt is used to decrypt using ECIES with a HKDF-KEM and AEAD-DEM mechanisms.
+func (e *EciesAeadHkdfHybridDecrypt) Decrypt(ciphertext, contextInfo []byte) ([]byte, error) {
+	curve := e.privateKey.PublicKey.Curve
+
+	headerSize, err := encodingSizeInBytes(curve, e.pointFormat)
+	if err != nil {
+		return nil, err
+	}
+	if len(ciphertext) < headerSize {
+		return nil, errors.New("ciphertext too short")
+	}
+	kemBytes := ciphertext[:headerSize]
+	rKem := &ECIESHKDFRecipientKem{
+		recipientPrivateKey: e.privateKey,
+	}
+	symmetricKey, err := rKem.decapsulate(kemBytes, e.hkdfHMACAlgo, e.hkdfSalt, contextInfo, e.demHelper.getSymmetricKeySize(), e.pointFormat)
+	if err != nil {
+		return nil, err
+	}
+	aead, err := e.demHelper.getAead(symmetricKey)
+	if err != nil {
+		return nil, err
+	}
+	return aead.Decrypt(ciphertext[headerSize:], []byte{})
+}
diff --git a/go/subtle/hybrid/ecies_aead_hkdf_hybrid_encrypt.go b/go/subtle/hybrid/ecies_aead_hkdf_hybrid_encrypt.go
new file mode 100644
index 0000000..8964344
--- /dev/null
+++ b/go/subtle/hybrid/ecies_aead_hkdf_hybrid_encrypt.go
@@ -0,0 +1,75 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package hybrid
+
+import (
+	"bytes"
+
+	"github.com/google/tink/go/tink"
+)
+
+// EciesAeadHkdfHybridEncrypt is an instance of ECIES encryption with HKDF-KEM (key encapsulation mechanism)
+// and AEAD-DEM (data encapsulation mechanism).
+type EciesAeadHkdfHybridEncrypt struct {
+	publicKey    *ECPublicKey
+	hkdfSalt     []byte
+	hkdfHMACAlgo string
+	pointFormat  string
+	demHelper    EciesAeadHkdfDemHelper
+}
+
+var _ tink.HybridEncrypt = (*EciesAeadHkdfHybridEncrypt)(nil)
+
+// NewEciesAeadHkdfHybridEncrypt returns ECIES encryption construct with HKDF-KEM (key encapsulation mechanism)
+// and AEAD-DEM (data encapsulation mechanism).
+func NewEciesAeadHkdfHybridEncrypt(pub *ECPublicKey, hkdfSalt []byte, hkdfHMACAlgo string, ptFormat string, demHelper EciesAeadHkdfDemHelper) (*EciesAeadHkdfHybridEncrypt, error) {
+	c, err := getCurve(pub.Curve.Params().Name)
+	if err != nil {
+		return nil, err
+	}
+	return &EciesAeadHkdfHybridEncrypt{
+		publicKey: &ECPublicKey{
+			Curve: c,
+			Point: pub.Point,
+		},
+		hkdfSalt:     hkdfSalt,
+		hkdfHMACAlgo: hkdfHMACAlgo,
+		pointFormat:  ptFormat,
+		demHelper:    demHelper,
+	}, nil
+}
+
+// Encrypt is used to encrypt using ECIES with a HKDF-KEM and AEAD-DEM mechanisms.
+func (e *EciesAeadHkdfHybridEncrypt) Encrypt(plaintext, contextInfo []byte) ([]byte, error) {
+	var b bytes.Buffer
+	sKem := &ECIESHKDFSenderKem{
+		recipientPublicKey: e.publicKey,
+	}
+	kemKey, err := sKem.encapsulate(e.hkdfHMACAlgo, e.hkdfSalt, contextInfo, e.demHelper.getSymmetricKeySize(), e.pointFormat)
+	if err != nil {
+		return nil, err
+	}
+	aead, err := e.demHelper.getAead(kemKey.SymmetricKey)
+	if err != nil {
+		return nil, err
+	}
+	ct, err := aead.Encrypt(plaintext, []byte{})
+	if err != nil {
+		return nil, err
+	}
+	b.Write(kemKey.Kem)
+	b.Write(ct)
+	return b.Bytes(), nil
+}
diff --git a/go/subtle/hybrid/ecies_hkdf_recipient_kem.go b/go/subtle/hybrid/ecies_hkdf_recipient_kem.go
new file mode 100644
index 0000000..93f3ea3
--- /dev/null
+++ b/go/subtle/hybrid/ecies_hkdf_recipient_kem.go
@@ -0,0 +1,37 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package hybrid
+
+// ECIESHKDFRecipientKem represents a HKDF-based KEM (key encapsulation mechanism)
+// for ECIES recipient.
+type ECIESHKDFRecipientKem struct {
+	recipientPrivateKey *ECPrivateKey
+}
+
+// decapsulate uses the KEM to generate a new HKDF-based key.
+func (s *ECIESHKDFRecipientKem) decapsulate(kem []byte, hashAlg string, salt []byte, info []byte, keySize uint32, pointFormat string) ([]byte, error) {
+	ephemeralPvt, err := GenerateECDHKeyPair(s.recipientPrivateKey.PublicKey.Curve)
+	if err != nil {
+		return nil, err
+	}
+
+	secret, err := ComputeSharedSecret(&ephemeralPvt.PublicKey.Point, s.recipientPrivateKey)
+	if err != nil {
+		return nil, err
+	}
+	i := append(kem, secret...)
+
+	return computeHKDF(hashAlg, i, salt, info, keySize)
+}
diff --git a/go/subtle/hybrid/ecies_hkdf_sender_kem.go b/go/subtle/hybrid/ecies_hkdf_sender_kem.go
new file mode 100644
index 0000000..8422e97
--- /dev/null
+++ b/go/subtle/hybrid/ecies_hkdf_sender_kem.go
@@ -0,0 +1,55 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package hybrid
+
+// KEMKey represents a KEM managed key.
+type KEMKey struct {
+	Kem, SymmetricKey []byte
+}
+
+// ECIESHKDFSenderKem represents HKDF-based ECIES-KEM (key encapsulation mechanism)
+//for ECIES sender.
+type ECIESHKDFSenderKem struct {
+	recipientPublicKey *ECPublicKey
+}
+
+// GenerateKey a HDKF based KEM.
+func (s *ECIESHKDFSenderKem) encapsulate(hashAlg string, salt []byte, info []byte, keySize uint32, pointFormat string) (*KEMKey, error) {
+
+	pvt, err := GenerateECDHKeyPair(s.recipientPublicKey.Curve)
+	if err != nil {
+		return nil, err
+	}
+	pub := pvt.PublicKey
+	secret, err := ComputeSharedSecret(&s.recipientPublicKey.Point, pvt)
+	if err != nil {
+		return nil, err
+	}
+
+	sdata, err := pointEncode(pub.Curve, pointFormat, pub.Point)
+	if err != nil {
+		return nil, err
+	}
+	sKey, err := computeHKDF(hashAlg, secret, salt, info, keySize)
+	if err != nil {
+		return nil, err
+	}
+
+	return &KEMKey{
+		Kem:          sdata,
+		SymmetricKey: sKey,
+	}, nil
+
+}
diff --git a/go/subtle/hybrid/elliptic_curves.go b/go/subtle/hybrid/elliptic_curves.go
new file mode 100644
index 0000000..9ee065d
--- /dev/null
+++ b/go/subtle/hybrid/elliptic_curves.go
@@ -0,0 +1,280 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package hybrid
+
+import (
+	"bytes"
+	"crypto/elliptic"
+	"crypto/rand"
+	"errors"
+	"fmt"
+	"math/big"
+)
+
+// ECPublicKey represents a elliptic curve public key.
+type ECPublicKey struct {
+	elliptic.Curve
+	Point ECPoint
+}
+
+// ECPrivateKey represents a elliptic curve public key.
+type ECPrivateKey struct {
+	PublicKey ECPublicKey
+	D         *big.Int
+}
+
+// GetECPrivateKey converts a stored private key to ECPrivateKey.
+func GetECPrivateKey(c elliptic.Curve, b []byte) *ECPrivateKey {
+	d := new(big.Int)
+	d.SetBytes(b)
+
+	x, y := c.Params().ScalarBaseMult(b)
+	pub := ECPublicKey{
+		Curve: c,
+		Point: ECPoint{
+			X: x,
+			Y: y,
+		},
+	}
+	return &ECPrivateKey{
+		PublicKey: pub,
+		D:         d,
+	}
+
+}
+
+// ECPoint represents a point on the elliptic curve.
+type ECPoint struct {
+	X, Y *big.Int
+}
+
+func (p *ECPrivateKey) getParams() *elliptic.CurveParams {
+	return p.PublicKey.Curve.Params()
+}
+
+func getModulus(c elliptic.Curve) *big.Int {
+	return c.Params().P
+}
+
+func fieldSizeInBits(c elliptic.Curve) int {
+	t := big.NewInt(1)
+	r := t.Sub(getModulus(c), t)
+	return r.BitLen()
+}
+
+func fieldSizeInBytes(c elliptic.Curve) int {
+	return (fieldSizeInBits(c) + 7) / 8
+}
+
+func encodingSizeInBytes(c elliptic.Curve, p string) (int, error) {
+	cSize := fieldSizeInBytes(c)
+	switch p {
+	case "UNCOMPRESSED":
+		return 2*cSize + 1, nil
+	case "DO_NOT_USE_CRUNCHY_UNCOMPRESSED":
+		return 2 * cSize, nil
+	case "COMPRESSED":
+		return cSize + 1, nil
+	}
+	return 0, fmt.Errorf("invalid point format :%s", p)
+
+}
+
+// pointEncode encodes a point into the format specified.
+func pointEncode(c elliptic.Curve, pFormat string, pt ECPoint) ([]byte, error) {
+	if !c.IsOnCurve(pt.X, pt.Y) {
+		return nil, errors.New("curve check failed")
+	}
+	cSize := fieldSizeInBytes(c)
+	y := pt.Y.Bytes()
+	x := pt.X.Bytes()
+	switch pFormat {
+	case "UNCOMPRESSED":
+		encoded := make([]byte, 2*cSize+1)
+		copy(encoded[1+2*cSize-len(y):], y)
+		copy(encoded[1+cSize-len(x):], x)
+		encoded[0] = 4
+		return encoded, nil
+	case "DO_NOT_USE_CRUNCHY_UNCOMPRESSED":
+		encoded := make([]byte, 2*cSize)
+		if len(x) > cSize {
+			x = bytes.Replace(x, []byte("\x00"), []byte{}, -1)
+		}
+		if len(y) > cSize {
+			y = bytes.Replace(y, []byte("\x00"), []byte{}, -1)
+		}
+		copy(encoded[2*cSize-len(y):], y)
+		copy(encoded[cSize-len(x):], x)
+		return encoded, nil
+	case "COMPRESSED":
+		encoded := make([]byte, cSize+1)
+		copy(encoded[1+cSize-len(x):], x)
+		encoded[0] = 2
+		if pt.Y.Bit(0) > 0 {
+			encoded[0] = 3
+		}
+		return encoded, nil
+	}
+	return nil, errors.New("invalid point format")
+
+}
+
+// pointDecode decodes a encoded point to return an ECPoint
+func pointDecode(c elliptic.Curve, pFormat string, e []byte) (*ECPoint, error) {
+	cSize := fieldSizeInBytes(c)
+	x, y := new(big.Int), new(big.Int)
+	switch pFormat {
+	case "UNCOMPRESSED":
+		if len(e) != (2*cSize + 1) {
+			return nil, errors.New("invalid point size")
+		}
+		if e[0] != 4 {
+			return nil, errors.New("invalid point format")
+		}
+		x.SetBytes(e[1 : cSize+1])
+		y.SetBytes(e[cSize+1:])
+		if !c.IsOnCurve(x, y) {
+			return nil, errors.New("invalid point")
+		}
+		return &ECPoint{
+			X: x,
+			Y: y,
+		}, nil
+	case "DO_NOT_USE_CRUNCHY_UNCOMPRESSED":
+		if len(e) != 2*cSize {
+			return nil, errors.New("invalid point size")
+		}
+		x.SetBytes(e[:cSize])
+		y.SetBytes(e[cSize:])
+		if !c.IsOnCurve(x, y) {
+			return nil, errors.New("invalid point")
+		}
+		return &ECPoint{
+			X: x,
+			Y: y,
+		}, nil
+	case "COMPRESSED":
+		if len(e) != cSize+1 {
+			return nil, errors.New("compressed point has wrong length")
+		}
+		lsb := false
+		if e[0] == 2 {
+			lsb = false
+		} else if e[0] == 3 {
+			lsb = true
+		} else {
+			return nil, errors.New("invalid format")
+		}
+		x := new(big.Int)
+		x.SetBytes(e[1:])
+		if (x.Sign() == -1) || (x.Cmp(c.Params().P) != -1) {
+			return nil, errors.New("x is out of range")
+		}
+		y := getY(x, lsb, c)
+		return &ECPoint{
+			X: x,
+			Y: y,
+		}, nil
+	}
+	return nil, fmt.Errorf("invalid format: %s", pFormat)
+}
+
+func getY(x *big.Int, lsb bool, c elliptic.Curve) *big.Int {
+	// y² = x³ - 3x + b
+	x3 := new(big.Int).Mul(x, x)
+	x3.Mul(x3, x)
+
+	threeX := new(big.Int).Lsh(x, 1)
+	threeX.Add(threeX, x)
+	b := c.Params().B
+	p := c.Params().P
+
+	x3.Sub(x3, threeX)
+	x3.Add(x3, b)
+	x3.ModSqrt(x3, p)
+	e := uint(1)
+	if lsb {
+		e = 0
+	}
+	if e == x3.Bit(0) {
+		x3 := x3.Sub(p, x3)
+		x3.Mod(x3, p)
+	}
+	return x3
+}
+
+func validatePublicPoint(pub *ECPoint, priv *ECPrivateKey) error {
+	if priv.PublicKey.Curve.IsOnCurve(pub.X, pub.Y) {
+		return nil
+	}
+	return errors.New("invalid public key")
+}
+
+// ComputeSharedSecret is used to compute a shared secret using given private key and peer public key.
+func ComputeSharedSecret(pub *ECPoint, priv *ECPrivateKey) ([]byte, error) {
+	if err := validatePublicPoint(pub, priv); err != nil {
+		return nil, err
+	}
+
+	x, y := priv.PublicKey.Curve.ScalarMult(pub.X, pub.Y, priv.D.Bytes())
+
+	if x == nil {
+		return nil, errors.New("shared key compute error")
+	}
+	// check if x,y are on the curve
+	if err := validatePublicPoint(&ECPoint{X: x, Y: y}, priv); err != nil {
+		return nil, errors.New("invalid shared key")
+	}
+	return x.Bytes(), nil
+}
+
+func maxSharedKeyLength(pub *ECPublicKey) int {
+	return (pub.Curve.Params().BitSize + 7) / 8
+}
+
+// GenerateECDHKeyPair will create a new private key for a given curve.
+func GenerateECDHKeyPair(c elliptic.Curve) (*ECPrivateKey, error) {
+	p, x, y, err := elliptic.GenerateKey(c, rand.Reader)
+	if err != nil {
+		return nil, err
+	}
+	return &ECPrivateKey{
+		PublicKey: ECPublicKey{
+			Curve: c,
+			Point: ECPoint{
+				X: x,
+				Y: y,
+			},
+		},
+		D: new(big.Int).SetBytes(p),
+	}, nil
+
+}
+
+// getCurve returns the elliptic.Curve for a given standard curve name.
+func getCurve(c string) (elliptic.Curve, error) {
+	switch c {
+	case "secp224r1", "NIST_P224", "P-224":
+		return elliptic.P224(), nil
+	case "secp256r1", "NIST_P256", "P-256":
+		return elliptic.P256(), nil
+	case "secp384r1", "NIST_P384", "P-384":
+		return elliptic.P384(), nil
+	case "secp521r1", "NIST_521", "P-521":
+		return elliptic.P521(), nil
+	default:
+		return nil, errors.New("unsupported curve")
+	}
+}
diff --git a/go/subtle/hybrid/elliptic_curves_test.go b/go/subtle/hybrid/elliptic_curves_test.go
new file mode 100644
index 0000000..5aee983
--- /dev/null
+++ b/go/subtle/hybrid/elliptic_curves_test.go
@@ -0,0 +1,639 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package hybrid
+
+import (
+	"bytes"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/x509"
+	"encoding/hex"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"math/big"
+	"os"
+	"reflect"
+	"strings"
+	"testing"
+)
+
+type testEC1 struct {
+	elliptic.Curve
+	pubX, pubY string
+}
+
+type testEC2 struct {
+	elliptic.Curve
+	pointFormat string
+	encoded     string
+	X, Y        string
+}
+
+type testData struct {
+	Algorithm        string
+	GeneratorVersion string
+	NumberOfTests    uint32
+	Schema           string
+	TestGroups       []*testGroup
+}
+
+type testGroup struct {
+	Curve    string
+	Encoding string
+	Type     string
+	Tests    []*testcase
+}
+
+type testcase struct {
+	Comment string
+	Public  string
+	Private string
+	Shared  string
+	Result  string
+	Flags   []string
+	TcID    uint32
+}
+
+// Test cases same as the java tests from
+// //third_party/tink/java/src/test/java/com/google/crypto/tink/subtle/EllipticCurvesTest.java
+var (
+	testVectors = []string{"../../../../wycheproof/testvectors/ecdh_test.json",
+		"../../../../wycheproof/testvectors/ecdh_test.json",
+	}
+	tEC1 = []testEC1{
+		{
+			elliptic.P256(),
+			"700c48f77f56584c5cc632ca65640db91b6bacce3a4df6b42ce7cc838833d287",
+			"db71e509e3fd9b060ddb20ba5c51dcc5948d46fbf640dfe0441782cab85fa4ac",
+		},
+		{
+			elliptic.P256(),
+			"809f04289c64348c01515eb03d5ce7ac1a8cb9498f5caa50197e58d43a86a7ae",
+			"b29d84e811197f25eba8f5194092cb6ff440e26d4421011372461f579271cda3",
+		},
+		{
+			elliptic.P256(),
+			"df3989b9fa55495719b3cf46dccd28b5153f7808191dd518eff0c3cff2b705ed",
+			"422294ff46003429d739a33206c8752552c8ba54a270defc06e221e0feaf6ac4",
+		},
+		{
+			elliptic.P256(),
+			"356c5a444c049a52fee0adeb7e5d82ae5aa83030bfff31bbf8ce2096cf161c4b",
+			"57d128de8b2a57a094d1a001e572173f96e8866ae352bf29cddaf92fc85b2f92",
+		},
+		{
+			elliptic.P384(),
+			"a7c76b970c3b5fe8b05d2838ae04ab47697b9eaf52e764592efda27fe7513272" +
+				"734466b400091adbf2d68c58e0c50066",
+			"ac68f19f2e1cb879aed43a9969b91a0839c4c38a49749b661efedf243451915e" +
+				"d0905a32b060992b468c64766fc8437a",
+		},
+		{
+			elliptic.P384(),
+			"30f43fcf2b6b00de53f624f1543090681839717d53c7c955d1d69efaf0349b736" +
+				"3acb447240101cbb3af6641ce4b88e0",
+			"25e46c0c54f0162a77efcc27b6ea792002ae2ba82714299c860857a68153ab62e" +
+				"525ec0530d81b5aa15897981e858757",
+		},
+		{
+			elliptic.P521(),
+			"000000685a48e86c79f0f0875f7bc18d25eb5fc8c0b07e5da4f4370f3a9490340" +
+				"854334b1e1b87fa395464c60626124a4e70d0f785601d37c09870ebf176666877a2" +
+				"046d",
+			"000001ba52c56fc8776d9e8f5db4f0cc27636d0b741bbe05400697942e80b7398" +
+				"84a83bde99e0f6716939e632bc8986fa18dccd443a348b6c3e522497955a4f3c302" +
+				"f676",
+		},
+		{
+			elliptic.P521(),
+			"000001df277c152108349bc34d539ee0cf06b24f5d3500677b4445453ccc21409" +
+				"453aafb8a72a0be9ebe54d12270aa51b3ab7f316aa5e74a951c5e53f74cd95fc29a" +
+				"ee7a",
+			"0000013d52f33a9f3c14384d1587fa8abe7aed74bc33749ad9c570b471776422c" +
+				"7d4505d9b0a96b3bfac041e4c6a6990ae7f700e5b4a6640229112deafa0cd8bb0d0" +
+				"89b0",
+		},
+		{
+			elliptic.P521(),
+			"00000092db3142564d27a5f0006f819908fba1b85038a5bc2509906a497daac67" +
+				"fd7aee0fc2daba4e4334eeaef0e0019204b471cd88024f82115d8149cc0cf4f7ce1" +
+				"a4d5",
+			"0000016bad0623f517b158d9881841d2571efbad63f85cbe2e581960c5d670601" +
+				"a6760272675a548996217e4ab2b8ebce31d71fca63fcc3c08e91c1d8edd91cf6fe8" +
+				"45f8",
+		},
+		{
+			elliptic.P521(),
+			"0000004f38816681771289ce0cb83a5e29a1ab06fc91f786994b23708ff08a08a" +
+				"0f675b809ae99e9f9967eb1a49f196057d69e50d6dedb4dd2d9a81c02bdcc8f7f51" +
+				"8460",
+			"0000009efb244c8b91087de1eed766500f0e81530752d469256ef79f6b965d8a2" +
+				"232a0c2dbc4e8e1d09214bab38485be6e357c4200d073b52f04e4a16fc6f5247187" +
+				"aecb",
+		},
+		{
+			elliptic.P521(),
+			"000001a32099b02c0bd85371f60b0dd20890e6c7af048c8179890fda308b359db" +
+				"bc2b7a832bb8c6526c4af99a7ea3f0b3cb96ae1eb7684132795c478ad6f962e4a6f" +
+				"446d",
+			"0000017627357b39e9d7632a1370b3e93c1afb5c851b910eb4ead0c9d387df67c" +
+				"de85003e0e427552f1cd09059aad0262e235cce5fba8cedc4fdc1463da76dcd4b6d" +
+				"1a46",
+		},
+	}
+	tEC2 = []testEC2{
+		// NIST_P256
+		{
+			elliptic.P256(),
+			"UNCOMPRESSED",
+			"04" +
+				"b0cfc7bc02fc980d858077552947ffb449b10df8949dee4e56fe21e016dcb25a" +
+				"1886ccdca5487a6772f9401888203f90587cc00a730e2b83d5c6f89b3b568df7",
+			"79974177209371530366349631093481213364328002500948308276357601809416549347930",
+			"11093679777528052772423074391650378811758820120351664471899251711300542565879",
+		},
+		{
+			elliptic.P256(),
+			"DO_NOT_USE_CRUNCHY_UNCOMPRESSED",
+			"b0cfc7bc02fc980d858077552947ffb449b10df8949dee4e56fe21e016dcb25a" +
+				"1886ccdca5487a6772f9401888203f90587cc00a730e2b83d5c6f89b3b568df7",
+			"79974177209371530366349631093481213364328002500948308276357601809416549347930",
+			"11093679777528052772423074391650378811758820120351664471899251711300542565879",
+		},
+		{
+			elliptic.P256(),
+			"COMPRESSED",
+			"03b0cfc7bc02fc980d858077552947ffb449b10df8949dee4e56fe21e016dcb25a",
+			"79974177209371530366349631093481213364328002500948308276357601809416549347930",
+			"11093679777528052772423074391650378811758820120351664471899251711300542565879",
+		},
+		// Exceptional point: x==0
+		{
+			elliptic.P256(),
+			"UNCOMPRESSED",
+			"04" +
+				"0000000000000000000000000000000000000000000000000000000000000000" +
+				"66485c780e2f83d72433bd5d84a06bb6541c2af31dae871728bf856a174f93f4",
+			"0",
+			"46263761741508638697010950048709651021688891777877937875096931459006746039284",
+		},
+		{
+			elliptic.P256(),
+			"DO_NOT_USE_CRUNCHY_UNCOMPRESSED",
+			"0000000000000000000000000000000000000000000000000000000000000000" +
+				"66485c780e2f83d72433bd5d84a06bb6541c2af31dae871728bf856a174f93f4",
+			"0",
+			"46263761741508638697010950048709651021688891777877937875096931459006746039284",
+		},
+		{
+			elliptic.P256(),
+			"COMPRESSED",
+			"020000000000000000000000000000000000000000000000000000000000000000",
+			"0",
+			"46263761741508638697010950048709651021688891777877937875096931459006746039284",
+		},
+		// Exceptional point: x==-3
+		{
+			elliptic.P256(),
+			"UNCOMPRESSED",
+			"04" +
+				"ffffffff00000001000000000000000000000000fffffffffffffffffffffffc" +
+				"19719bebf6aea13f25c96dfd7c71f5225d4c8fc09eb5a0ab9f39e9178e55c121",
+			"115792089210356248762697446949407573530086143415290314195533631308867097853948",
+			"11508551065151498768481026661199445482476508121209842448718573150489103679777",
+		},
+		{
+			elliptic.P256(),
+			"DO_NOT_USE_CRUNCHY_UNCOMPRESSED",
+			"ffffffff00000001000000000000000000000000fffffffffffffffffffffffc" +
+				"19719bebf6aea13f25c96dfd7c71f5225d4c8fc09eb5a0ab9f39e9178e55c121",
+			"115792089210356248762697446949407573530086143415290314195533631308867097853948",
+			"11508551065151498768481026661199445482476508121209842448718573150489103679777",
+		},
+		{
+			elliptic.P256(),
+			"COMPRESSED",
+			"03ffffffff00000001000000000000000000000000fffffffffffffffffffffffc",
+			"115792089210356248762697446949407573530086143415290314195533631308867097853948",
+			"11508551065151498768481026661199445482476508121209842448718573150489103679777",
+		},
+		// NIST_P384
+		{
+			elliptic.P384(),
+			"UNCOMPRESSED",
+			"04aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a" +
+				"385502f25dbf55296c3a545e3872760ab73617de4a96262c6f5d9e98bf9292dc" +
+				"29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e" +
+				"5f",
+			"2624703509579968926862315674456698189185292349110921338781561590" +
+				"0925518854738050089022388053975719786650872476732087",
+			"8325710961489029985546751289520108179287853048861315594709205902" +
+				"480503199884419224438643760392947333078086511627871",
+		},
+		{
+			elliptic.P384(),
+			"COMPRESSED",
+			"03aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a" +
+				"385502f25dbf55296c3a545e3872760ab7",
+			"2624703509579968926862315674456698189185292349110921338781561590" +
+				"0925518854738050089022388053975719786650872476732087",
+			"8325710961489029985546751289520108179287853048861315594709205902" +
+				"480503199884419224438643760392947333078086511627871",
+		},
+		// x = 0
+		{
+			elliptic.P384(),
+			"UNCOMPRESSED",
+			"0400000000000000000000000000000000000000000000000000000000000000" +
+				"00000000000000000000000000000000003cf99ef04f51a5ea630ba3f9f960dd" +
+				"593a14c9be39fd2bd215d3b4b08aaaf86bbf927f2c46e52ab06fb742b8850e52" +
+				"1e",
+			"0",
+			"9384923975005507693384933751151973636103286582194273515051780595" +
+				"652610803541482195894618304099771370981414591681054",
+		},
+		{
+			elliptic.P384(),
+			"COMPRESSED",
+			"0200000000000000000000000000000000000000000000000000000000000000" +
+				"0000000000000000000000000000000000",
+			"0",
+			"9384923975005507693384933751151973636103286582194273515051780595" +
+				"652610803541482195894618304099771370981414591681054",
+		},
+		// x = 2
+		{
+			elliptic.P384(),
+			"UNCOMPRESSED",
+			"0400000000000000000000000000000000000000000000000000000000000000" +
+				"0000000000000000000000000000000002732152442fb6ee5c3e6ce1d920c059" +
+				"bc623563814d79042b903ce60f1d4487fccd450a86da03f3e6ed525d02017bfd" +
+				"b3",
+			"2",
+			"1772015366480916228638409476801818679957736647795608728422858375" +
+				"4887974043472116432532980617621641492831213601947059",
+		},
+		{
+			elliptic.P384(),
+			"COMPRESSED",
+			"0300000000000000000000000000000000000000000000000000000000000000" +
+				"0000000000000000000000000000000002",
+			"2",
+			"1772015366480916228638409476801818679957736647795608728422858375" +
+				"4887974043472116432532980617621641492831213601947059",
+		},
+		// x = -3
+		{
+			elliptic.P384(),
+			"UNCOMPRESSED",
+			"04ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" +
+				"feffffffff0000000000000000fffffffc2de9de09a95b74e6b2c430363e1afb" +
+				"8dff7164987a8cfe0a0d5139250ac02f797f81092a9bdc0e09b574a8f43bf80c" +
+				"17",
+			"3940200619639447921227904010014361380507973927046544666794829340" +
+				"4245721771496870329047266088258938001861606973112316",
+			"7066741234775658874139271223692271325950306561732202191471600407" +
+				"582071247913794644254895122656050391930754095909911",
+		},
+		{
+			elliptic.P384(),
+			"COMPRESSED",
+			"03ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" +
+				"feffffffff0000000000000000fffffffc",
+			"3940200619639447921227904010014361380507973927046544666794829340" +
+				"4245721771496870329047266088258938001861606973112316",
+			"7066741234775658874139271223692271325950306561732202191471600407" +
+				"582071247913794644254895122656050391930754095909911",
+		},
+		// NIST_P521
+		{
+			elliptic.P521(),
+			"UNCOMPRESSED",
+			"0400c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b" +
+				"4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2" +
+				"e5bd66011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd" +
+				"17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94" +
+				"769fd16650",
+			"2661740802050217063228768716723360960729859168756973147706671368" +
+				"4188029449964278084915450806277719023520942412250655586621571135" +
+				"45570916814161637315895999846",
+			"3757180025770020463545507224491183603594455134769762486694567779" +
+				"6155444774405563166912344050129455395621444445372894285225856667" +
+				"29196580810124344277578376784",
+		},
+		{
+			elliptic.P521(),
+			"COMPRESSED",
+			"0200c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b" +
+				"4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2" +
+				"e5bd66",
+			"2661740802050217063228768716723360960729859168756973147706671368" +
+				"4188029449964278084915450806277719023520942412250655586621571135" +
+				"45570916814161637315895999846",
+			"3757180025770020463545507224491183603594455134769762486694567779" +
+				"6155444774405563166912344050129455395621444445372894285225856667" +
+				"29196580810124344277578376784",
+		},
+		// x = 0
+		{
+			elliptic.P521(),
+			"UNCOMPRESSED",
+			"0400000000000000000000000000000000000000000000000000000000000000" +
+				"0000000000000000000000000000000000000000000000000000000000000000" +
+				"00000000d20ec9fea6b577c10d26ca1bb446f40b299e648b1ad508aad068896f" +
+				"ee3f8e614bc63054d5772bf01a65d412e0bcaa8e965d2f5d332d7f39f846d440" +
+				"ae001f4f87",
+			"0",
+			"2816414230262626695230339754503506208598534788872316917808418392" +
+				"0894686826982898181454171638541149642517061885689521392260532032" +
+				"30035588176689756661142736775",
+		},
+		{
+			elliptic.P521(),
+			"COMPRESSED",
+			"0300000000000000000000000000000000000000000000000000000000000000" +
+				"0000000000000000000000000000000000000000000000000000000000000000" +
+				"000000",
+			"0",
+			"2816414230262626695230339754503506208598534788872316917808418392" +
+				"0894686826982898181454171638541149642517061885689521392260532032" +
+				"30035588176689756661142736775",
+		},
+		// x = 1
+		{
+			elliptic.P521(),
+			"UNCOMPRESSED",
+			"0400000000000000000000000000000000000000000000000000000000000000" +
+				"0000000000000000000000000000000000000000000000000000000000000000" +
+				"0000010010e59be93c4f269c0269c79e2afd65d6aeaa9b701eacc194fb3ee03d" +
+				"f47849bf550ec636ebee0ddd4a16f1cd9406605af38f584567770e3f272d688c" +
+				"832e843564",
+			"1",
+			"2265505274322546447629271557184988697103589068170534253193208655" +
+				"0778100463909972583865730916407864371153050622267306901033104806" +
+				"9570407113457901669103973732",
+		},
+		{
+			elliptic.P521(),
+			"COMPRESSED",
+			"0200000000000000000000000000000000000000000000000000000000000000" +
+				"0000000000000000000000000000000000000000000000000000000000000000" +
+				"000001",
+			"1",
+			"2265505274322546447629271557184988697103589068170534253193208655" +
+				"0778100463909972583865730916407864371153050622267306901033104806" +
+				"9570407113457901669103973732",
+		},
+		// x = 2
+		{
+			elliptic.P521(),
+			"UNCOMPRESSED",
+			"0400000000000000000000000000000000000000000000000000000000000000" +
+				"0000000000000000000000000000000000000000000000000000000000000000" +
+				"00000200d9254fdf800496acb33790b103c5ee9fac12832fe546c632225b0f7f" +
+				"ce3da4574b1a879b623d722fa8fc34d5fc2a8731aad691a9a8bb8b554c95a051" +
+				"d6aa505acf",
+			"2",
+			"2911448509017565583245824537994174021964465504209366849707937264" +
+				"0417919148200722009442607963590225526059407040161685364728526719" +
+				"10134103604091376779754756815",
+		},
+		{
+			elliptic.P521(),
+			"COMPRESSED",
+			"0300000000000000000000000000000000000000000000000000000000000000" +
+				"0000000000000000000000000000000000000000000000000000000000000000" +
+				"000002",
+			"2",
+			"2911448509017565583245824537994174021964465504209366849707937264" +
+				"0417919148200722009442607963590225526059407040161685364728526719" +
+				"10134103604091376779754756815",
+		},
+		// x = -2
+		{
+			elliptic.P521(),
+			"UNCOMPRESSED",
+			"0401ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" +
+				"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" +
+				"fffffd0010e59be93c4f269c0269c79e2afd65d6aeaa9b701eacc194fb3ee03d" +
+				"f47849bf550ec636ebee0ddd4a16f1cd9406605af38f584567770e3f272d688c" +
+				"832e843564",
+			"6864797660130609714981900799081393217269435300143305409394463459" +
+				"1855431833976560521225596406614545549772963113914808580371219879" +
+				"99716643812574028291115057149",
+			"2265505274322546447629271557184988697103589068170534253193208655" +
+				"0778100463909972583865730916407864371153050622267306901033104806" +
+				"9570407113457901669103973732",
+		},
+		{
+			elliptic.P521(),
+			"COMPRESSED",
+			"0201ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" +
+				"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" +
+				"fffffd",
+			"6864797660130609714981900799081393217269435300143305409394463459" +
+				"1855431833976560521225596406614545549772963113914808580371219879" +
+				"99716643812574028291115057149",
+			"2265505274322546447629271557184988697103589068170534253193208655" +
+				"0778100463909972583865730916407864371153050622267306901033104806" +
+				"9570407113457901669103973732",
+		},
+	}
+)
+
+func TestPointOnCurve(t *testing.T) {
+	for i := 0; i < len(tEC1); i++ {
+		x, y, ye := new(big.Int), new(big.Int), new(big.Int)
+		x.SetString(tEC1[i].pubX, 16)
+		y.SetString(tEC1[i].pubY, 16)
+		ye.Sub(y, big.NewInt(1))
+		if !tEC1[i].Curve.IsOnCurve(x, y) {
+			t.Fatalf("valid points not on curve for test case :%d", i)
+		}
+		if tEC1[i].Curve.IsOnCurve(x, ye) {
+			t.Fatalf("invalid points is on curve for test case :%d", i)
+		}
+	}
+
+}
+
+func TestPointEncode(t *testing.T) {
+	for i := 0; i < len(tEC2); i++ {
+		x, y := new(big.Int), new(big.Int)
+		x.SetString(tEC2[i].X, 10)
+		y.SetString(tEC2[i].Y, 10)
+		p := ECPoint{
+			X: x,
+			Y: y,
+		}
+		encodedpoint, err := pointEncode(tEC2[i].Curve, tEC2[i].pointFormat, p)
+		if err != nil {
+			t.Errorf("error in point encoding in test case %d : %v", i, err)
+		}
+		want, err := hex.DecodeString(tEC2[i].encoded)
+		if err != nil {
+			t.Errorf("error reading encoded point in test case %d", i)
+		}
+		if !bytes.Equal(encodedpoint, want) {
+			t.Errorf("mismatch point encoding in test case %d", i)
+		}
+	}
+}
+
+func TestPointDecode(t *testing.T) {
+	for i := 0; i < len(tEC2); i++ {
+		x, y := new(big.Int), new(big.Int)
+		x.SetString(tEC2[i].X, 10)
+		y.SetString(tEC2[i].Y, 10)
+		e, err := hex.DecodeString(tEC2[i].encoded)
+		if err != nil {
+			t.Errorf("error reading encoded point in test case %d", i)
+		}
+		pt, err := pointDecode(tEC2[i].Curve, tEC2[i].pointFormat, e)
+		if err != nil {
+			t.Errorf("error in point decoding in test case %d: %v", i, err)
+		}
+		spt := ECPoint{
+			X: x,
+			Y: y,
+		}
+
+		if pt.X.Cmp(spt.X) != 0 || pt.Y.Cmp(spt.Y) != 0 {
+			t.Errorf("mismatch point decoding in test case %d", i)
+		}
+	}
+}
+
+func checkFlag(t *testing.T, flags []string, check []string) bool {
+	t.Helper()
+	for _, f := range flags {
+		for _, c := range check {
+			if strings.Compare(f, c) == 0 {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+// getX509PublicKey converts a stored public key to ECPublicKey.
+func getX509PublicKey(t *testing.T, b []byte) (*ECPublicKey, error) {
+	t.Helper()
+	pkey, err := x509.ParsePKIXPublicKey(b)
+	if err != nil {
+		return nil, err
+	}
+	ecdsaP, ok := pkey.(*ecdsa.PublicKey)
+	if !ok {
+		return nil, errors.New("invalid elliptic curve key")
+	}
+	return &ECPublicKey{
+		Curve: ecdsaP.Curve,
+		Point: ECPoint{
+			X: ecdsaP.X,
+			Y: ecdsaP.Y,
+		},
+	}, nil
+}
+
+func TestVectors(t *testing.T) {
+	for _, i := range testVectors {
+		f, err := os.Open(i)
+		if err != nil {
+			t.Fatalf("cannot open file: %s, make sure that github.com/google/wycheproof is in your gopath", err)
+		}
+		parser := json.NewDecoder(f)
+		data := new(testData)
+		if err := parser.Decode(data); err != nil {
+			t.Fatalf("cannot decode test data: %s", err)
+		}
+
+		for _, g := range data.TestGroups {
+			curve, err := getCurve(g.Curve)
+			if err != nil {
+				t.Logf("unsupported curve: %s", g.Curve)
+				continue
+			}
+			for _, test := range g.Tests {
+
+				tcID := fmt.Sprintf("testcase %d (%s)", test.TcID, test.Comment)
+				pvtHex := test.Private
+				if len(test.Private)%2 == 1 {
+					pvtHex = fmt.Sprintf("0%s", test.Private)
+				}
+				pvt, err := hex.DecodeString(pvtHex)
+				if err != nil {
+					t.Errorf("error decoding from hex private key in test case %s: %v", tcID, err)
+				}
+				pvtKey := GetECPrivateKey(curve, pvt)
+				p, err := hex.DecodeString(test.Public)
+				if err != nil {
+					t.Errorf("error decoding from hex public key in test case %s: %v", tcID, err)
+				}
+				pubKey := &ECPublicKey{}
+				var errPub error
+				switch data.Schema {
+				case "ecdh_test_schema.json":
+					pubKey, errPub = getX509PublicKey(t, p)
+				case "ecdh_ecpoint_test_schema.json":
+					ptFormat := "UNCOMPRESSED"
+					pt := &ECPoint{}
+					if checkFlag(t, test.Flags, []string{"CompressedPoint"}) {
+						ptFormat = "COMPRESSED"
+					}
+					pt, errPub = pointDecode(curve, ptFormat, p)
+					pubKey = &ECPublicKey{
+						Curve: curve,
+						Point: *pt,
+					}
+				default:
+					errPub = errors.New("invalid schema")
+				}
+				if errPub != nil && test.Result != "valid" {
+					t.Logf("test case %s failing as expected for invalid result : %v", tcID, err)
+					continue
+
+				}
+
+				if reflect.DeepEqual(&ECPublicKey{}, pubKey) {
+					t.Logf("error decoding public key in test case %s: %v", tcID, err)
+					// Some test vectors have incorrect public key encoding which
+					// leads to runtime errors. For more details please see the
+					// java test file referenced above.
+					continue
+				}
+				cShared, err := ComputeSharedSecret(&pubKey.Point, pvtKey)
+				got := strings.TrimLeft(hex.EncodeToString(cShared), "0")
+				want := strings.TrimLeft(test.Shared, "0")
+				if test.Result == "invalid" {
+					if err != nil { // shared secret was not computed
+						continue
+					}
+					if strings.Compare(got, want) == 0 && checkFlag(t, test.Flags, []string{"WrongOrder", "WeakPublicKey", "UnnamedCurve"}) {
+						fmt.Printf("test case %s accepted invalid parameters but shared secret is correct\n", tcID)
+						continue
+					}
+					t.Errorf("test case %s accepted invalid parameters, shared secret: %s", tcID, want)
+				} else if strings.Compare(got, want) != 0 {
+					t.Errorf("test case %s incorrect shared secret, want: %s, got: %s", tcID, want, got)
+				}
+				fmt.Printf("test :%s done\n", tcID)
+
+			}
+			fmt.Printf("curve :%s done\n", g.Curve)
+		}
+	}
+}
diff --git a/go/subtle/hybrid/hkdf.go b/go/subtle/hybrid/hkdf.go
new file mode 100644
index 0000000..7266835
--- /dev/null
+++ b/go/subtle/hybrid/hkdf.go
@@ -0,0 +1,78 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// Package hybrid provides subtle implementations of the HKDF and EC primitives.
+package hybrid
+
+import (
+	"errors"
+	"fmt"
+	"io"
+
+	"golang.org/x/crypto/hkdf"
+	"github.com/google/tink/go/subtle"
+)
+
+const (
+	// Minimum tag size in bytes. This provides minimum 80-bit security strength.
+	minTagSizeInBytes = uint32(10)
+)
+
+// Maximum tag size in bytes for each hash type
+var maxTagSizeInBytes = map[string]uint32{
+	"SHA1":   uint32(20),
+	"SHA256": uint32(32),
+	"SHA512": uint32(64),
+}
+
+var errHKDFInvalidInput = errors.New("HKDF: invalid input")
+
+// ValidateHKDFParams validates parameters of HKDF constructor.
+func validateHKDFParams(hash string, keySize uint32, tagSize uint32) error {
+	// validate tag size
+	maxTagSize, found := maxTagSizeInBytes[hash]
+	if !found {
+		return fmt.Errorf("invalid hash algorithm")
+	}
+	if tagSize > 255*maxTagSize {
+		return fmt.Errorf("tag size too big")
+	}
+	if tagSize < minTagSizeInBytes {
+		return fmt.Errorf("tag size too small")
+	}
+	return nil
+}
+
+// computeHKDF extracts a pseudorandom key.
+func computeHKDF(hashAlg string, key []byte, salt []byte, info []byte, tagSize uint32) ([]byte, error) {
+	keySize := uint32(len(key))
+	if err := validateHKDFParams(hashAlg, keySize, tagSize); err != nil {
+		return nil, fmt.Errorf("hkdf: %s", err)
+	}
+	hashFunc := subtle.GetHashFunc(hashAlg)
+	if salt == nil || len(salt) == 0 {
+		salt = make([]byte, tagSize)
+	}
+
+	if hashFunc == nil {
+		return nil, fmt.Errorf("hkdf: invalid hash algorithm")
+	}
+	result := make([]byte, tagSize)
+	kdf := hkdf.New(hashFunc, key, salt, info)
+	n, err := io.ReadFull(kdf, result)
+	if n != len(result) || err != nil {
+		return nil, fmt.Errorf("compute of hkdf failed")
+	}
+	return result, nil
+}
diff --git a/go/subtle/hybrid/hkdf_test.go b/go/subtle/hybrid/hkdf_test.go
new file mode 100644
index 0000000..6dab025
--- /dev/null
+++ b/go/subtle/hybrid/hkdf_test.go
@@ -0,0 +1,160 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package hybrid
+
+import (
+	"encoding/hex"
+	"fmt"
+	"strings"
+	"testing"
+
+	"github.com/google/tink/go/subtle/random"
+)
+
+// Tests sourced from
+// //third_party/tink/java/src/test/java/com/google/crypto/tink/subtle/HkdfTest.java
+
+var hkdfTests = []struct {
+	hashAlg     string
+	key         string
+	salt        string
+	info        string
+	tagSize     uint32
+	expectedKDF string
+}{
+	{
+		hashAlg:     "SHA256",
+		key:         "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
+		salt:        "000102030405060708090a0b0c",
+		info:        "f0f1f2f3f4f5f6f7f8f9",
+		tagSize:     42,
+		expectedKDF: "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865",
+	},
+	{
+		hashAlg: "SHA256",
+		key: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" +
+			"202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f" +
+			"404142434445464748494a4b4c4d4e4f",
+		salt: "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f" +
+			"808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f" +
+			"a0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
+		info: "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf" +
+			"d0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeef" +
+			"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+		tagSize: 82,
+		expectedKDF: "b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c" +
+			"59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71" +
+			"cc30c58179ec3e87c14c01d5c1f3434f1d87",
+	},
+	{
+		hashAlg: "SHA256",
+		key:     "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
+		salt:    "",
+		info:    "",
+		tagSize: 42,
+		expectedKDF: "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d" +
+			"9d201395faa4b61a96c8",
+	},
+	{
+		hashAlg:     "SHA1",
+		key:         "0b0b0b0b0b0b0b0b0b0b0b",
+		salt:        "000102030405060708090a0b0c",
+		info:        "f0f1f2f3f4f5f6f7f8f9",
+		tagSize:     42,
+		expectedKDF: "085a01ea1b10f36933068b56efa5ad81a4f14b822f5b091568a9cdd4f155fda2c22e422478d305f3f896",
+	},
+	{
+		hashAlg: "SHA1",
+		key: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" +
+			"202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f" +
+			"404142434445464748494a4b4c4d4e4f",
+		salt: "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f" +
+			"808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f" +
+			"a0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
+		info: "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf" +
+			"d0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeef" +
+			"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+		tagSize: 82,
+		expectedKDF: "0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ffe" +
+			"8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9dfeb15c5e" +
+			"927336d0441f4c4300e2cff0d0900b52d3b4",
+	},
+	{
+		hashAlg: "SHA1",
+		key:     "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
+		salt:    "",
+		info:    "",
+		tagSize: 42,
+		expectedKDF: "0ac1af7002b3d761d1e55298da9d0506b9ae52057220a306e07b6b87e8df21d0" +
+			"ea00033de03984d34918",
+	},
+	{
+		hashAlg: "SHA1",
+		key:     "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c",
+		salt:    "",
+		info:    "",
+		tagSize: 42,
+		expectedKDF: "2c91117204d745f3500d636a62f64f0ab3bae548aa53d423b0d1f27ebba6f5e5" +
+			"673a081d70cce7acfc48",
+	},
+}
+
+func TestHKDFBasic(t *testing.T) {
+	for ti, test := range hkdfTests {
+		k, _ := hex.DecodeString(test.key)
+		s, _ := hex.DecodeString(test.salt)
+		i, _ := hex.DecodeString(test.info)
+
+		result, err := computeHKDF(test.hashAlg, k, s, i, test.tagSize)
+		r := hex.EncodeToString(result)
+		fmt.Printf("Test no: %d\n", ti)
+		fmt.Printf("Length of tag :%d\n", test.tagSize)
+		fmt.Printf("Length of result :%d\n", len(r))
+		fmt.Printf("Length of expected :%d\n\n\n", len(test.expectedKDF))
+		if err != nil {
+			t.Errorf("mac computation failed in test case %d: %s", ti, err)
+		}
+		if r != test.expectedKDF {
+			t.Errorf("incorrect hkdf in test case %d: expect %s, got %s",
+				ti, test.expectedKDF, r)
+		}
+	}
+}
+
+func TestNewHMACWithInvalidInput(t *testing.T) {
+	// invalid hash algorithm
+	_, err := computeHKDF("SHA224", random.GetRandomBytes(16), nil, nil, 32)
+	if err == nil || !strings.Contains(err.Error(), "invalid hash algorithm") {
+		t.Errorf("expect an error when hash algorithm is invalid")
+	}
+	// tag too short
+	_, err = computeHKDF("SHA256", random.GetRandomBytes(16), nil, nil, 9)
+	if err == nil || !strings.Contains(err.Error(), "tag size too small") {
+		t.Errorf("expect an error when tag size is too small")
+	}
+	// tag too big
+	_, err = computeHKDF("SHA1", random.GetRandomBytes(16), nil, nil, 5101)
+	if err == nil || !strings.Contains(err.Error(), "tag size too big") {
+		t.Errorf("expect an error when tag size is too big")
+	}
+	_, err = computeHKDF("SHA256", random.GetRandomBytes(16), nil, nil, 8162)
+	if err == nil || !strings.Contains(err.Error(), "tag size too big") {
+		t.Errorf("expect an error when tag size is too big")
+	}
+	_, err = computeHKDF("SHA512", random.GetRandomBytes(16), nil, nil, 16323)
+	if err == nil || !strings.Contains(err.Error(), "tag size too big") {
+		t.Errorf("expect an error when tag size is too big")
+	}
+}
diff --git a/go/subtle/mac/BUILD.bazel b/go/subtle/mac/BUILD.bazel
index 121abd7..efb076e 100644
--- a/go/subtle/mac/BUILD.bazel
+++ b/go/subtle/mac/BUILD.bazel
@@ -12,7 +12,7 @@
 )
 
 go_test(
-    name = "go_default_xtest",
+    name = "go_default_test",
     srcs = ["hmac_test.go"],
     deps = [
         ":go_default_library",
diff --git a/go/subtle/mac/hmac.go b/go/subtle/mac/hmac.go
index 57b68cd..5ddd447 100644
--- a/go/subtle/mac/hmac.go
+++ b/go/subtle/mac/hmac.go
@@ -1,14 +1,15 @@
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
-
+//
 //      http://www.apache.org/licenses/LICENSE-2.0
-
+//
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+//
 ////////////////////////////////////////////////////////////////////////////////
 
 // Package mac provides subtle implementations of the Mac primitive.
@@ -16,6 +17,7 @@
 
 import (
 	"crypto/hmac"
+	"errors"
 	"fmt"
 	"hash"
 
@@ -38,37 +40,37 @@
 	"SHA512": uint32(64),
 }
 
-var errHmacInvalidInput = fmt.Errorf("hmac: invalid input")
+var errHMACInvalidInput = errors.New("HMAC: invalid input")
 
-// Hmac implementation of interface tink.Mac
-type Hmac struct {
+// HMAC implementation of interface tink.MAC
+type HMAC struct {
 	HashFunc func() hash.Hash
 	Key      []byte
 	TagSize  uint32
 }
 
-// This makes sure that Hmac implements the tink.Mac interface
-var _ tink.Mac = (*Hmac)(nil)
+// This makes sure that HMAC implements the tink.MAC interface
+var _ tink.MAC = (*HMAC)(nil)
 
-// NewHmac creates a new instance of Hmac with the specified key and tag size.
-func NewHmac(hashAlg string, key []byte, tagSize uint32) (*Hmac, error) {
+// NewHMAC creates a new instance of HMAC with the specified key and tag size.
+func NewHMAC(hashAlg string, key []byte, tagSize uint32) (*HMAC, error) {
 	keySize := uint32(len(key))
-	if err := ValidateHmacParams(hashAlg, keySize, tagSize); err != nil {
+	if err := ValidateHMACParams(hashAlg, keySize, tagSize); err != nil {
 		return nil, fmt.Errorf("hmac: %s", err)
 	}
 	hashFunc := subtle.GetHashFunc(hashAlg)
 	if hashFunc == nil {
 		return nil, fmt.Errorf("hmac: invalid hash algorithm")
 	}
-	return &Hmac{
+	return &HMAC{
 		HashFunc: hashFunc,
 		Key:      key,
 		TagSize:  tagSize,
 	}, nil
 }
 
-// ValidateHmacParams validates parameters of Hmac constructor.
-func ValidateHmacParams(hash string, keySize uint32, tagSize uint32) error {
+// ValidateHMACParams validates parameters of HMAC constructor.
+func ValidateHMACParams(hash string, keySize uint32, tagSize uint32) error {
 	// validate tag size
 	maxTagSize, found := maxTagSizeInBytes[hash]
 	if !found {
@@ -87,26 +89,31 @@
 	return nil
 }
 
-// ComputeMac computes message authentication code (MAC) for the given data.
-func (h *Hmac) ComputeMac(data []byte) ([]byte, error) {
+// ComputeMAC computes message authentication code (MAC) for the given data.
+func (h *HMAC) ComputeMAC(data []byte) ([]byte, error) {
 	if data == nil {
-		return nil, errHmacInvalidInput
+		return nil, errHMACInvalidInput
 	}
 	mac := hmac.New(h.HashFunc, h.Key)
-	mac.Write(data)
+	if _, err := mac.Write(data); err != nil {
+		return nil, err
+	}
 	tag := mac.Sum(nil)
 	return tag[:h.TagSize], nil
 }
 
-// VerifyMac verifies whether the given MAC is a correct authentication code (MAC)
+// VerifyMAC verifies whether the given MAC is a correct authentication code (MAC)
 // the given data.
-func (h *Hmac) VerifyMac(mac []byte, data []byte) (bool, error) {
+func (h *HMAC) VerifyMAC(mac []byte, data []byte) error {
 	if mac == nil || data == nil {
-		return false, errHmacInvalidInput
+		return errHMACInvalidInput
 	}
-	expectedMAC, err := h.ComputeMac(data)
+	expectedMAC, err := h.ComputeMAC(data)
 	if err != nil {
-		return false, err
+		return err
 	}
-	return hmac.Equal(expectedMAC, mac), nil
+	if hmac.Equal(expectedMAC, mac) {
+		return nil
+	}
+	return errors.New("HMAC: invalid MAC")
 }
diff --git a/go/subtle/mac/hmac_test.go b/go/subtle/mac/hmac_test.go
index 3c9d11e..b6cd11b 100644
--- a/go/subtle/mac/hmac_test.go
+++ b/go/subtle/mac/hmac_test.go
@@ -1,14 +1,15 @@
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
-
+//
 //      http://www.apache.org/licenses/LICENSE-2.0
-
+//
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+//
 ////////////////////////////////////////////////////////////////////////////////
 
 package mac_test
@@ -56,13 +57,13 @@
 	},
 }
 
-func TestHmacBasic(t *testing.T) {
+func TestHMACBasic(t *testing.T) {
 	for i, test := range hmacTests {
-		cipher, err := mac.NewHmac(test.hashAlg, test.key, test.tagSize)
+		cipher, err := mac.NewHMAC(test.hashAlg, test.key, test.tagSize)
 		if err != nil {
 			t.Errorf("cannot create new mac in test case %d: %s", i, err)
 		}
-		mac, err := cipher.ComputeMac(test.data)
+		mac, err := cipher.ComputeMAC(test.data)
 		if err != nil {
 			t.Errorf("mac computation failed in test case %d: %s", i, err)
 		}
@@ -70,80 +71,79 @@
 			t.Errorf("incorrect mac in test case %d: expect %s, got %s",
 				i, test.expectedMac[:(test.tagSize*2)], hex.EncodeToString(mac))
 		}
-		valid, err := cipher.VerifyMac(mac, test.data)
-		if !valid || err != nil {
+		if err := cipher.VerifyMAC(mac, test.data); err != nil {
 			t.Errorf("mac verification failed in test case %d: %s", i, err)
 		}
 	}
 }
 
-func TestNewHmacWithInvalidInput(t *testing.T) {
+func TestNewHMACWithInvalidInput(t *testing.T) {
 	// invalid hash algorithm
-	_, err := mac.NewHmac("SHA224", random.GetRandomBytes(16), 32)
+	_, err := mac.NewHMAC("SHA224", random.GetRandomBytes(16), 32)
 	if err == nil || !strings.Contains(err.Error(), "invalid hash algorithm") {
 		t.Errorf("expect an error when hash algorithm is invalid")
 	}
 	// key too short
-	_, err = mac.NewHmac("SHA256", random.GetRandomBytes(1), 32)
+	_, err = mac.NewHMAC("SHA256", random.GetRandomBytes(1), 32)
 	if err == nil || !strings.Contains(err.Error(), "key too short") {
 		t.Errorf("expect an error when key is too short")
 	}
 	// tag too short
-	_, err = mac.NewHmac("SHA256", random.GetRandomBytes(16), 9)
+	_, err = mac.NewHMAC("SHA256", random.GetRandomBytes(16), 9)
 	if err == nil || !strings.Contains(err.Error(), "tag size too small") {
 		t.Errorf("expect an error when tag size is too small")
 	}
 	// tag too big
-	_, err = mac.NewHmac("SHA1", random.GetRandomBytes(16), 21)
+	_, err = mac.NewHMAC("SHA1", random.GetRandomBytes(16), 21)
 	if err == nil || !strings.Contains(err.Error(), "tag size too big") {
 		t.Errorf("expect an error when tag size is too big")
 	}
-	_, err = mac.NewHmac("SHA256", random.GetRandomBytes(16), 33)
+	_, err = mac.NewHMAC("SHA256", random.GetRandomBytes(16), 33)
 	if err == nil || !strings.Contains(err.Error(), "tag size too big") {
 		t.Errorf("expect an error when tag size is too big")
 	}
-	_, err = mac.NewHmac("SHA512", random.GetRandomBytes(16), 65)
+	_, err = mac.NewHMAC("SHA512", random.GetRandomBytes(16), 65)
 	if err == nil || !strings.Contains(err.Error(), "tag size too big") {
 		t.Errorf("expect an error when tag size is too big")
 	}
 }
 
-func TestComputeMacWithInvalidInput(t *testing.T) {
-	cipher, err := mac.NewHmac("SHA256", random.GetRandomBytes(16), 32)
+func TestComputeMACWithInvalidInput(t *testing.T) {
+	cipher, err := mac.NewHMAC("SHA256", random.GetRandomBytes(16), 32)
 	if err != nil {
-		t.Errorf("unexpected error when creating new Hmac")
+		t.Errorf("unexpected error when creating new HMAC")
 	}
-	if _, err := cipher.ComputeMac(nil); err == nil {
+	if _, err := cipher.ComputeMAC(nil); err == nil {
 		t.Errorf("expect an error when input is nil")
 	}
 }
 
-func TestVerifyMacWithInvalidInput(t *testing.T) {
-	cipher, err := mac.NewHmac("SHA256", random.GetRandomBytes(16), 32)
+func TestVerifyMACWithInvalidInput(t *testing.T) {
+	cipher, err := mac.NewHMAC("SHA256", random.GetRandomBytes(16), 32)
 	if err != nil {
-		t.Errorf("unexpected error when creating new Hmac")
+		t.Errorf("unexpected error when creating new HMAC")
 	}
-	if _, err := cipher.VerifyMac(nil, []byte{1}); err == nil {
+	if err := cipher.VerifyMAC(nil, []byte{1}); err == nil {
 		t.Errorf("expect an error when mac is nil")
 	}
-	if _, err := cipher.VerifyMac([]byte{1}, nil); err == nil {
+	if err := cipher.VerifyMAC([]byte{1}, nil); err == nil {
 		t.Errorf("expect an error when data is nil")
 	}
 }
 
-func TestHmacModification(t *testing.T) {
+func TestHMACModification(t *testing.T) {
 	for i, test := range hmacTests {
-		cipher, err := mac.NewHmac(test.hashAlg, test.key, test.tagSize)
+		cipher, err := mac.NewHMAC(test.hashAlg, test.key, test.tagSize)
 		if err != nil {
 			t.Errorf("cannot create new mac in test case %d: %s", i, err)
 		}
-		mac, _ := cipher.ComputeMac(test.data)
+		mac, _ := cipher.ComputeMAC(test.data)
 		for i := 0; i < len(mac); i++ {
 			tmp := mac[i]
 			for j := 0; j < 8; j++ {
 				mac[i] ^= 1 << uint8(j)
-				valid, _ := cipher.VerifyMac(mac, test.data)
-				if valid {
+				err := cipher.VerifyMAC(mac, test.data)
+				if err == nil {
 					t.Errorf("test case %d: modified MAC should be invalid", i)
 				}
 				mac[i] = tmp
@@ -152,17 +152,17 @@
 	}
 }
 
-func TestHmacTruncation(t *testing.T) {
+func TestHMACTruncation(t *testing.T) {
 	for i, test := range hmacTests {
-		cipher, err := mac.NewHmac(test.hashAlg, test.key, test.tagSize)
+		cipher, err := mac.NewHMAC(test.hashAlg, test.key, test.tagSize)
 		if err != nil {
 			t.Errorf("cannot create new mac in test case %d: %s", i, err)
 		}
-		mac, _ := cipher.ComputeMac(test.data)
+		mac, _ := cipher.ComputeMAC(test.data)
 		for i := 1; i < len(mac); i++ {
 			tmp := mac[:i]
-			valid, _ := cipher.VerifyMac(tmp, test.data)
-			if valid {
+			err := cipher.VerifyMAC(tmp, test.data)
+			if err == nil {
 				t.Errorf("test case %d: truncated MAC should be invalid", i)
 			}
 		}
diff --git a/go/subtle/random/BUILD.bazel b/go/subtle/random/BUILD.bazel
index b660471..298bd1d 100644
--- a/go/subtle/random/BUILD.bazel
+++ b/go/subtle/random/BUILD.bazel
@@ -8,7 +8,7 @@
 )
 
 go_test(
-    name = "go_default_xtest",
+    name = "go_default_test",
     srcs = ["random_test.go"],
     deps = [":go_default_library"],
 )
diff --git a/go/subtle/random/random.go b/go/subtle/random/random.go
index dd54f82..93f141d 100644
--- a/go/subtle/random/random.go
+++ b/go/subtle/random/random.go
@@ -1,14 +1,15 @@
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
-
+//
 //      http://www.apache.org/licenses/LICENSE-2.0
-
+//
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+//
 ////////////////////////////////////////////////////////////////////////////////
 
 // Package random provides functions that generate random numbers or bytes.
diff --git a/go/subtle/random/random_test.go b/go/subtle/random/random_test.go
index 67b4c3b..4e0ecc4 100644
--- a/go/subtle/random/random_test.go
+++ b/go/subtle/random/random_test.go
@@ -1,14 +1,15 @@
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
-
+//
 //      http://www.apache.org/licenses/LICENSE-2.0
-
+//
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+//
 ////////////////////////////////////////////////////////////////////////////////
 
 package random_test
diff --git a/go/subtle/signature/BUILD.bazel b/go/subtle/signature/BUILD.bazel
index fa5e80e..9f8185f 100644
--- a/go/subtle/signature/BUILD.bazel
+++ b/go/subtle/signature/BUILD.bazel
@@ -4,8 +4,10 @@
     name = "go_default_library",
     srcs = [
         "ecdsa.go",
-        "ecdsa_sign.go",
-        "ecdsa_verify.go",
+        "ecdsa_signer.go",
+        "ecdsa_verifier.go",
+        "ed25519_signer.go",
+        "ed25519_verifier.go",
         "encoding.go",
     ],
     importpath = "github.com/google/tink/go/subtle/signature",
@@ -13,13 +15,15 @@
     deps = [
         "//go/subtle:go_default_library",
         "//go/tink:go_default_library",
+        "@org_golang_x_crypto//ed25519:go_default_library",
     ],
 )
 
 go_test(
-    name = "go_default_xtest",
+    name = "go_default_test",
     srcs = [
-        "ecdsa_sign_verify_test.go",
+        "ecdsa_signer_verifier_test.go",
+        "ed25519_signer_verifier_test.go",
         "ecdsa_test.go",
     ],
     data = [
@@ -29,5 +33,6 @@
         ":go_default_library",
         "//go/subtle:go_default_library",
         "//go/subtle/random:go_default_library",
+        "@org_golang_x_crypto//ed25519:go_default_library",
     ],
 )
diff --git a/go/subtle/signature/ecdsa.go b/go/subtle/signature/ecdsa.go
index 7bcedef..da9a8a8 100644
--- a/go/subtle/signature/ecdsa.go
+++ b/go/subtle/signature/ecdsa.go
@@ -1,64 +1,74 @@
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
-
+//
 //      http://www.apache.org/licenses/LICENSE-2.0
-
+//
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+//
 ////////////////////////////////////////////////////////////////////////////////
 
-// Package signature provides subtle implementations of the PublicKeySign and PublicKeyVerify primitives.
+// Package signature provides subtle implementations of the Signer and Verifier primitives.
 package signature
 
 import (
+	"errors"
 	"fmt"
 	"math/big"
 )
 
-var errUnsupportedEncoding = fmt.Errorf("ecdsa: unsupported encoding")
+var errUnsupportedEncoding = errors.New("ecdsa: unsupported encoding")
 
-// EcdsaSignature is a struct holding r and s values of an ECDSA signature.
-type EcdsaSignature struct {
+// ECDSASignature is a struct holding r and s values of an ECDSA signature.
+type ECDSASignature struct {
 	R, S *big.Int
 }
 
-// NewEcdsaSignature creates a new ecdsaSignature object.
-func NewEcdsaSignature(r, s *big.Int) *EcdsaSignature {
-	return &EcdsaSignature{R: r, S: s}
+// NewECDSASignature creates a new ecdsaSignature object.
+func NewECDSASignature(r, s *big.Int) *ECDSASignature {
+	return &ECDSASignature{R: r, S: s}
 }
 
-// EncodeEcdsaSignature converts the signature to the given encoding format.
+// EncodeECDSASignature converts the signature to the given encoding format.
 // Only DER encoding is supported now.
-func (sig *EcdsaSignature) EncodeEcdsaSignature(encoding string) ([]byte, error) {
+func (sig *ECDSASignature) EncodeECDSASignature(encoding string) ([]byte, error) {
 	switch encoding {
 	case "DER":
-		return asn1encode(sig)
+		enc, err := asn1encode(sig)
+		if err != nil {
+			return nil, fmt.Errorf("ecdsa: can't convert ECDSA signature to %s encoding: %v", encoding, err)
+		}
+		return enc, nil
 	default:
 		return nil, errUnsupportedEncoding
 	}
 }
 
-// DecodeEcdsaSignature creates a new ECDSA signature using the given byte slice.
+// DecodeECDSASignature creates a new ECDSA signature using the given byte slice.
 // The function assumes that the byte slice is the concatenation of the BigEndian
 // representation of two big integer r and s.
-func DecodeEcdsaSignature(encodedBytes []byte,
-	encoding string) (*EcdsaSignature, error) {
+func DecodeECDSASignature(encodedBytes []byte,
+	encoding string) (*ECDSASignature, error) {
 	switch encoding {
 	case "DER":
-		return asn1decode(encodedBytes)
+		sig, err := asn1decode(encodedBytes)
+		if err != nil {
+			return nil, fmt.Errorf("ecdsa: %s", err)
+		}
+		return sig, nil
 	default:
 		return nil, errUnsupportedEncoding
 	}
 }
 
-// ValidateEcdsaParams validates ECDSA parameters.
+// ValidateECDSAParams validates ECDSA parameters.
 // The hash's strength must not be weaker than the curve's strength.
 // Only DER encoding is supported now.
-func ValidateEcdsaParams(hashAlg string, curve string, encoding string) error {
+func ValidateECDSAParams(hashAlg string, curve string, encoding string) error {
 	switch encoding {
 	case "DER":
 		break
@@ -68,11 +78,11 @@
 	switch curve {
 	case "NIST_P256":
 		if hashAlg != "SHA256" {
-			return fmt.Errorf("invalid hash type, expect SHA-256")
+			return errors.New("invalid hash type, expect SHA-256")
 		}
 	case "NIST_P384", "NIST_P521":
 		if hashAlg != "SHA512" {
-			return fmt.Errorf("invalid hash type, expect SHA-512")
+			return errors.New("invalid hash type, expect SHA-512")
 		}
 	default:
 		return fmt.Errorf("unsupported curve: %s", curve)
diff --git a/go/subtle/signature/ecdsa_sign.go b/go/subtle/signature/ecdsa_sign.go
deleted file mode 100644
index a58a076..0000000
--- a/go/subtle/signature/ecdsa_sign.go
+++ /dev/null
@@ -1,84 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-
-//      http://www.apache.org/licenses/LICENSE-2.0
-
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-////////////////////////////////////////////////////////////////////////////////
-
-package signature
-
-import (
-	"crypto/ecdsa"
-	"crypto/rand"
-	"fmt"
-	"hash"
-	"math/big"
-
-	"github.com/google/tink/go/subtle"
-	"github.com/google/tink/go/tink"
-)
-
-// EcdsaSign is an implementation of PublicKeySign for ECDSA.
-// At the moment, the implementation only accepts DER encoding.
-type EcdsaSign struct {
-	privateKey *ecdsa.PrivateKey
-	hashFunc   func() hash.Hash
-	encoding   string
-}
-
-// Assert that ecdsaSign implements the PublicKeySign interface.
-var _ tink.PublicKeySign = (*EcdsaSign)(nil)
-
-// NewEcdsaSign creates a new instance of EcdsaSign.
-func NewEcdsaSign(hashAlg string,
-	curve string,
-	encoding string,
-	keyValue []byte) (*EcdsaSign, error) {
-	privKey := new(ecdsa.PrivateKey)
-	c := subtle.GetCurve(curve)
-	privKey.PublicKey.Curve = c
-	privKey.D = new(big.Int).SetBytes(keyValue)
-	privKey.PublicKey.X, privKey.PublicKey.Y = c.ScalarBaseMult(keyValue)
-	return NewEcdsaSignFromPrivateKey(hashAlg, encoding, privKey)
-}
-
-// NewEcdsaSignFromPrivateKey creates a new instance of EcdsaSign
-func NewEcdsaSignFromPrivateKey(hashAlg string,
-	encoding string,
-	privateKey *ecdsa.PrivateKey) (*EcdsaSign, error) {
-	if privateKey.Curve == nil {
-		return nil, fmt.Errorf("ecdsa_sign: invalid curve")
-	}
-	curve := subtle.ConvertCurveName(privateKey.Curve.Params().Name)
-	if err := ValidateEcdsaParams(hashAlg, curve, encoding); err != nil {
-		return nil, fmt.Errorf("ecdsa_sign: %s", err)
-	}
-	hashFunc := subtle.GetHashFunc(hashAlg)
-	return &EcdsaSign{
-		privateKey: privateKey,
-		hashFunc:   hashFunc,
-		encoding:   encoding,
-	}, nil
-}
-
-// Sign computes a signature for the given data.
-func (e *EcdsaSign) Sign(data []byte) ([]byte, error) {
-	hashed := subtle.ComputeHash(e.hashFunc, data)
-	r, s, err := ecdsa.Sign(rand.Reader, e.privateKey, hashed)
-	if err != nil {
-		return nil, fmt.Errorf("ecdsa_sign: signing failed: %s", err)
-	}
-	// format the signature
-	sig := NewEcdsaSignature(r, s)
-	ret, err := sig.EncodeEcdsaSignature(e.encoding)
-	if err != nil {
-		return nil, fmt.Errorf("ecdsa_sign: signing failed: %s", err)
-	}
-	return ret, nil
-}
diff --git a/go/subtle/signature/ecdsa_signer.go b/go/subtle/signature/ecdsa_signer.go
new file mode 100644
index 0000000..6964329
--- /dev/null
+++ b/go/subtle/signature/ecdsa_signer.go
@@ -0,0 +1,89 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package signature
+
+import (
+	"crypto/ecdsa"
+	"crypto/rand"
+	"errors"
+	"fmt"
+	"hash"
+	"math/big"
+
+	"github.com/google/tink/go/subtle"
+	"github.com/google/tink/go/tink"
+)
+
+// ECDSASigner is an implementation of Signer for ECDSA.
+// At the moment, the implementation only accepts DER encoding.
+type ECDSASigner struct {
+	privateKey *ecdsa.PrivateKey
+	hashFunc   func() hash.Hash
+	encoding   string
+}
+
+// Assert that ecdsaSign implements the Signer interface.
+var _ tink.Signer = (*ECDSASigner)(nil)
+
+// NewECDSASigner creates a new instance of ECDSASigner.
+func NewECDSASigner(hashAlg string,
+	curve string,
+	encoding string,
+	keyValue []byte) (*ECDSASigner, error) {
+	privKey := new(ecdsa.PrivateKey)
+	c := subtle.GetCurve(curve)
+	privKey.PublicKey.Curve = c
+	privKey.D = new(big.Int).SetBytes(keyValue)
+	privKey.PublicKey.X, privKey.PublicKey.Y = c.ScalarBaseMult(keyValue)
+	return NewECDSASignerFromPrivateKey(hashAlg, encoding, privKey)
+}
+
+// NewECDSASignerFromPrivateKey creates a new instance of ECDSASigner
+func NewECDSASignerFromPrivateKey(hashAlg string,
+	encoding string,
+	privateKey *ecdsa.PrivateKey) (*ECDSASigner, error) {
+	if privateKey.Curve == nil {
+		return nil, errors.New("ecdsa_signer: privateKey.Curve can't be nil")
+	}
+	curve := subtle.ConvertCurveName(privateKey.Curve.Params().Name)
+	if err := ValidateECDSAParams(hashAlg, curve, encoding); err != nil {
+		return nil, fmt.Errorf("ecdsa_signer: %s", err)
+	}
+	hashFunc := subtle.GetHashFunc(hashAlg)
+	return &ECDSASigner{
+		privateKey: privateKey,
+		hashFunc:   hashFunc,
+		encoding:   encoding,
+	}, nil
+}
+
+// Sign computes a signature for the given data.
+func (e *ECDSASigner) Sign(data []byte) ([]byte, error) {
+	hashed, err := subtle.ComputeHash(e.hashFunc, data)
+	if err != nil {
+		return nil, err
+	}
+	r, s, err := ecdsa.Sign(rand.Reader, e.privateKey, hashed)
+	if err != nil {
+		return nil, fmt.Errorf("ecdsa_signer: signing failed: %s", err)
+	}
+	// format the signature
+	sig := NewECDSASignature(r, s)
+	ret, err := sig.EncodeECDSASignature(e.encoding)
+	if err != nil {
+		return nil, fmt.Errorf("ecdsa_signer: signing failed: %s", err)
+	}
+	return ret, nil
+}
diff --git a/go/subtle/signature/ecdsa_sign_verify_test.go b/go/subtle/signature/ecdsa_signer_verifier_test.go
similarity index 79%
rename from go/subtle/signature/ecdsa_sign_verify_test.go
rename to go/subtle/signature/ecdsa_signer_verifier_test.go
index 89bfdf0..3b5e882 100644
--- a/go/subtle/signature/ecdsa_sign_verify_test.go
+++ b/go/subtle/signature/ecdsa_signer_verifier_test.go
@@ -1,14 +1,15 @@
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
-
+//
 //      http://www.apache.org/licenses/LICENSE-2.0
-
+//
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+//
 ////////////////////////////////////////////////////////////////////////////////
 
 package signature_test
@@ -34,38 +35,36 @@
 	encoding := "DER"
 	priv, _ := ecdsa.GenerateKey(subtle.GetCurve(curve), rand.Reader)
 	// Use the private key and public key directly to create new instances
-	signer, err := subtleSignature.NewEcdsaSignFromPrivateKey(hash, encoding, priv)
+	signer, err := subtleSignature.NewECDSASignerFromPrivateKey(hash, encoding, priv)
 	if err != nil {
-		t.Errorf("unexpected error when creating EcdsaSign: %s", err)
+		t.Errorf("unexpected error when creating ECDSASigner: %s", err)
 	}
-	verifier, err := subtleSignature.NewEcdsaVerifyFromPublicKey(hash, encoding, &priv.PublicKey)
+	verifier, err := subtleSignature.NewECDSAVerifierFromPublicKey(hash, encoding, &priv.PublicKey)
 	if err != nil {
-		t.Errorf("unexpected error when creating EcdsaVerify: %s", err)
+		t.Errorf("unexpected error when creating ECDSAVerifier: %s", err)
 	}
 	signature, err := signer.Sign(data)
 	if err != nil {
 		t.Errorf("unexpected error when signing: %s", err)
 	}
-	err = verifier.Verify(signature, data)
-	if err != nil {
+	if err := verifier.Verify(signature, data); err != nil {
 		t.Errorf("unexpected error when verifying: %s", err)
 	}
 
 	// Use byte slices to create new instances
-	signer, err = subtleSignature.NewEcdsaSign(hash, curve, encoding, priv.D.Bytes())
+	signer, err = subtleSignature.NewECDSASigner(hash, curve, encoding, priv.D.Bytes())
 	if err != nil {
-		t.Errorf("unexpected error when creating EcdsaSign: %s", err)
+		t.Errorf("unexpected error when creating ECDSASigner: %s", err)
 	}
-	verifier, err = subtleSignature.NewEcdsaVerify(hash, curve, encoding, priv.X.Bytes(), priv.Y.Bytes())
+	verifier, err = subtleSignature.NewECDSAVerifier(hash, curve, encoding, priv.X.Bytes(), priv.Y.Bytes())
 	if err != nil {
-		t.Errorf("unexpected error when creating EcdsaVerify: %s", err)
+		t.Errorf("unexpected error when creating ECDSAVerifier: %s", err)
 	}
 	signature, err = signer.Sign(data)
 	if err != nil {
 		t.Errorf("unexpected error when signing: %s", err)
 	}
-	err = verifier.Verify(signature, data)
-	if err != nil {
+	if err = verifier.Verify(signature, data); err != nil {
 		t.Errorf("unexpected error when verifying: %s", err)
 	}
 }
@@ -126,7 +125,7 @@
 		if err != nil {
 			t.Errorf("cannot decode wy: %s", err)
 		}
-		verifier, err := subtleSignature.NewEcdsaVerify(hash, curve, encoding, x.Bytes(), y.Bytes())
+		verifier, err := subtleSignature.NewECDSAVerifier(hash, curve, encoding, x.Bytes(), y.Bytes())
 		if err != nil {
 			continue
 		}
diff --git a/go/subtle/signature/ecdsa_test.go b/go/subtle/signature/ecdsa_test.go
index f2b6606..f804d64 100644
--- a/go/subtle/signature/ecdsa_test.go
+++ b/go/subtle/signature/ecdsa_test.go
@@ -1,14 +1,15 @@
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
-
+//
 //      http://www.apache.org/licenses/LICENSE-2.0
-
+//
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+//
 ////////////////////////////////////////////////////////////////////////////////
 
 package signature_test
@@ -31,12 +32,12 @@
 
 var _ = fmt.Println
 
-func TestEncodeDecode(t *testing.T) {
+func TestECDSAEncodeDecode(t *testing.T) {
 	nTest := 1000
 	for i := 0; i < nTest; i++ {
-		sig := newRandomSignature()
+		sig := newECDSARandomSignature()
 		encoding := "DER"
-		encoded, err := sig.EncodeEcdsaSignature(encoding)
+		encoded, err := sig.EncodeECDSASignature(encoding)
 		if err != nil {
 			t.Errorf("unexpected error during encoding: %s", err)
 		}
@@ -52,7 +53,7 @@
 		if len(encoded) != int(encoded[1])+2 {
 			t.Errorf("incorrect length, expected %d, got %d", len(encoded), encoded[1]+2)
 		}
-		decodedSig, err := DecodeEcdsaSignature(encoded, encoding)
+		decodedSig, err := DecodeECDSASignature(encoded, encoding)
 		if err != nil {
 			t.Errorf("unexpected error during decoding: %s", err)
 		}
@@ -62,46 +63,46 @@
 	}
 }
 
-func TestEncodeWithInvalidInput(t *testing.T) {
-	sig := newRandomSignature()
-	_, err := sig.EncodeEcdsaSignature("UNKNOWN_ENCODING")
+func TestECDSAEncodeWithInvalidInput(t *testing.T) {
+	sig := newECDSARandomSignature()
+	_, err := sig.EncodeECDSASignature("UNKNOWN_ENCODING")
 	if err == nil {
 		t.Errorf("expect an error when encoding is invalid")
 	}
 }
 
-func TestDecodeWithInvalidInput(t *testing.T) {
-	var sig *EcdsaSignature
+func TestECDSADecodeWithInvalidInput(t *testing.T) {
+	var sig *ECDSASignature
 	var encoded []byte
 	encoding := "DER"
 
 	// modified first byte
-	sig = newRandomSignature()
-	encoded, _ = sig.EncodeEcdsaSignature(encoding)
+	sig = newECDSARandomSignature()
+	encoded, _ = sig.EncodeECDSASignature(encoding)
 	encoded[0] = 0x31
-	if _, err := DecodeEcdsaSignature(encoded, encoding); err == nil {
+	if _, err := DecodeECDSASignature(encoded, encoding); err == nil {
 		t.Errorf("expect an error when first byte is not 0x30")
 	}
 	// modified tag
-	sig = newRandomSignature()
-	encoded, _ = sig.EncodeEcdsaSignature(encoding)
+	sig = newECDSARandomSignature()
+	encoded, _ = sig.EncodeECDSASignature(encoding)
 	encoded[2] = encoded[2] + 1
-	if _, err := DecodeEcdsaSignature(encoded, encoding); err == nil {
+	if _, err := DecodeECDSASignature(encoded, encoding); err == nil {
 		t.Errorf("expect an error when tag is modified")
 	}
 	// modified length
-	sig = newRandomSignature()
-	encoded, _ = sig.EncodeEcdsaSignature(encoding)
+	sig = newECDSARandomSignature()
+	encoded, _ = sig.EncodeECDSASignature(encoding)
 	encoded[1] = encoded[1] + 1
-	if _, err := DecodeEcdsaSignature(encoded, encoding); err == nil {
+	if _, err := DecodeECDSASignature(encoded, encoding); err == nil {
 		t.Errorf("expect an error when length is modified")
 	}
 	// append unused 0s
-	sig = newRandomSignature()
-	encoded, _ = sig.EncodeEcdsaSignature(encoding)
+	sig = newECDSARandomSignature()
+	encoded, _ = sig.EncodeECDSASignature(encoding)
 	tmp := make([]byte, len(encoded)+4)
 	copy(tmp, encoded)
-	if _, err := DecodeEcdsaSignature(tmp, encoding); err == nil {
+	if _, err := DecodeECDSASignature(tmp, encoding); err == nil {
 		t.Errorf("expect an error when unused 0s are appended to signature")
 	}
 	// a struct with three numbers
@@ -111,27 +112,27 @@
 		Z: new(big.Int).SetBytes(random.GetRandomBytes(32)),
 	}
 	encoded, _ = asn1.Marshal(randomStruct)
-	if _, err := DecodeEcdsaSignature(encoded, encoding); err == nil {
-		t.Errorf("expect an error when input is not an EcdsaSignature")
+	if _, err := DecodeECDSASignature(encoded, encoding); err == nil {
+		t.Errorf("expect an error when input is not an ECDSASignature")
 	}
 }
 
-func TestValidateParams(t *testing.T) {
-	params := genValidParams()
+func TestECDSAValidateParams(t *testing.T) {
+	params := genECDSAValidParams()
 	for i := 0; i < len(params); i++ {
-		if err := ValidateEcdsaParams(params[i].hash, params[i].curve, params[i].encoding); err != nil {
+		if err := ValidateECDSAParams(params[i].hash, params[i].curve, params[i].encoding); err != nil {
 			t.Errorf("unexpected error for valid params: %s, i = %d", err, i)
 		}
 	}
-	params = genInvalidParams()
+	params = genECDSAInvalidParams()
 	for i := 0; i < len(params); i++ {
-		if err := ValidateEcdsaParams(params[i].hash, params[i].curve, params[i].encoding); err == nil {
+		if err := ValidateECDSAParams(params[i].hash, params[i].curve, params[i].encoding); err == nil {
 			t.Errorf("expect an error when params are invalid, i = %d", i)
 		}
 	}
 }
 
-func genInvalidParams() []paramsTest {
+func genECDSAInvalidParams() []paramsTest {
 	return []paramsTest{
 		// invalid encoding
 		paramsTest{hash: "SHA256", curve: "NIST_P256", encoding: "UNKNOWN_ENCODING"},
@@ -146,7 +147,7 @@
 	}
 }
 
-func genValidParams() []paramsTest {
+func genECDSAValidParams() []paramsTest {
 	return []paramsTest{
 		paramsTest{hash: "SHA256", curve: "NIST_P256", encoding: "DER"},
 		paramsTest{hash: "SHA512", curve: "NIST_P384", encoding: "DER"},
@@ -154,8 +155,8 @@
 	}
 }
 
-func newRandomSignature() *EcdsaSignature {
+func newECDSARandomSignature() *ECDSASignature {
 	r := new(big.Int).SetBytes(random.GetRandomBytes(32))
 	s := new(big.Int).SetBytes(random.GetRandomBytes(32))
-	return NewEcdsaSignature(r, s)
+	return NewECDSASignature(r, s)
 }
diff --git a/go/subtle/signature/ecdsa_verifier.go b/go/subtle/signature/ecdsa_verifier.go
new file mode 100644
index 0000000..7ab5dc3
--- /dev/null
+++ b/go/subtle/signature/ecdsa_verifier.go
@@ -0,0 +1,84 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package signature
+
+import (
+	"crypto/ecdsa"
+	"errors"
+	"fmt"
+	"hash"
+	"math/big"
+
+	"github.com/google/tink/go/subtle"
+	"github.com/google/tink/go/tink"
+)
+
+var errInvalidECDSASignature = errors.New("ecdsa_verifier: invalid signature")
+
+// ECDSAVerifier is an implementation of Verifier for ECDSA.
+// At the moment, the implementation only accepts signatures with strict DER encoding.
+type ECDSAVerifier struct {
+	publicKey *ecdsa.PublicKey
+	hashFunc  func() hash.Hash
+	encoding  string
+}
+
+// Assert that ECDSAVerifier implements the Verifier interface.
+var _ tink.Verifier = (*ECDSAVerifier)(nil)
+
+// NewECDSAVerifier creates a new instance of ECDSAVerifier.
+func NewECDSAVerifier(hashAlg string, curve string, encoding string, x []byte, y []byte) (*ECDSAVerifier, error) {
+	publicKey := &ecdsa.PublicKey{
+		Curve: subtle.GetCurve(curve),
+		X:     new(big.Int).SetBytes(x),
+		Y:     new(big.Int).SetBytes(y),
+	}
+	return NewECDSAVerifierFromPublicKey(hashAlg, encoding, publicKey)
+}
+
+// NewECDSAVerifierFromPublicKey creates a new instance of ECDSAVerifier.
+func NewECDSAVerifierFromPublicKey(hashAlg string, encoding string, publicKey *ecdsa.PublicKey) (*ECDSAVerifier, error) {
+	if publicKey.Curve == nil {
+		return nil, errors.New("ecdsa_verifier: invalid curve")
+	}
+	curve := subtle.ConvertCurveName(publicKey.Curve.Params().Name)
+	if err := ValidateECDSAParams(hashAlg, curve, encoding); err != nil {
+		return nil, fmt.Errorf("ecdsa_verifier: %s", err)
+	}
+	hashFunc := subtle.GetHashFunc(hashAlg)
+	return &ECDSAVerifier{
+		publicKey: publicKey,
+		hashFunc:  hashFunc,
+		encoding:  encoding,
+	}, nil
+}
+
+// Verify verifies whether the given signature is valid for the given data.
+// It returns an error if the signature is not valid; nil otherwise.
+func (e *ECDSAVerifier) Verify(signatureBytes, data []byte) error {
+	signature, err := DecodeECDSASignature(signatureBytes, e.encoding)
+	if err != nil {
+		return fmt.Errorf("ecdsa_verifier: %s", err)
+	}
+	hashed, err := subtle.ComputeHash(e.hashFunc, data)
+	if err != nil {
+		return err
+	}
+	valid := ecdsa.Verify(e.publicKey, hashed, signature.R, signature.S)
+	if !valid {
+		return errInvalidECDSASignature
+	}
+	return nil
+}
diff --git a/go/subtle/signature/ecdsa_verify.go b/go/subtle/signature/ecdsa_verify.go
deleted file mode 100644
index 3b96a26..0000000
--- a/go/subtle/signature/ecdsa_verify.go
+++ /dev/null
@@ -1,84 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-
-//      http://www.apache.org/licenses/LICENSE-2.0
-
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-////////////////////////////////////////////////////////////////////////////////
-
-package signature
-
-import (
-	"crypto/ecdsa"
-	"fmt"
-	"hash"
-	"math/big"
-
-	"github.com/google/tink/go/subtle"
-	"github.com/google/tink/go/tink"
-)
-
-var errInvalidSignature = fmt.Errorf("ecdsa_verify: invalid signature")
-
-// EcdsaVerify is an implementation of PublicKeyVerify for ECDSA.
-// At the moment, the implementation only accepts signatures with strict DER encoding.
-type EcdsaVerify struct {
-	publicKey *ecdsa.PublicKey
-	hashFunc  func() hash.Hash
-	encoding  string
-}
-
-// Assert that EcdsaVerify implements the PublicKeyVerify interface.
-var _ tink.PublicKeyVerify = (*EcdsaVerify)(nil)
-
-// NewEcdsaVerify creates a new instance of EcdsaVerify.
-func NewEcdsaVerify(hashAlg string,
-	curve string,
-	encoding string,
-	x []byte,
-	y []byte) (*EcdsaVerify, error) {
-	publicKey := &ecdsa.PublicKey{
-		Curve: subtle.GetCurve(curve),
-		X:     new(big.Int).SetBytes(x),
-		Y:     new(big.Int).SetBytes(y),
-	}
-	return NewEcdsaVerifyFromPublicKey(hashAlg, encoding, publicKey)
-}
-
-// NewEcdsaVerifyFromPublicKey creates a new instance of EcdsaVerify.
-func NewEcdsaVerifyFromPublicKey(hashAlg string, encoding string,
-	publicKey *ecdsa.PublicKey) (*EcdsaVerify, error) {
-	if publicKey.Curve == nil {
-		return nil, fmt.Errorf("ecdsa_verify: invalid curve")
-	}
-	curve := subtle.ConvertCurveName(publicKey.Curve.Params().Name)
-	if err := ValidateEcdsaParams(hashAlg, curve, encoding); err != nil {
-		return nil, fmt.Errorf("ecdsa_verify: %s", err)
-	}
-	hashFunc := subtle.GetHashFunc(hashAlg)
-	return &EcdsaVerify{
-		publicKey: publicKey,
-		hashFunc:  hashFunc,
-		encoding:  encoding,
-	}, nil
-}
-
-// Verify verifies whether the given signature is valid for the given data.
-// It returns an error if the signature is not valid; nil otherwise.
-func (e *EcdsaVerify) Verify(signatureBytes []byte, data []byte) error {
-	signature, err := DecodeEcdsaSignature(signatureBytes, e.encoding)
-	if err != nil {
-		return errInvalidSignature
-	}
-	hashed := subtle.ComputeHash(e.hashFunc, data)
-	valid := ecdsa.Verify(e.publicKey, hashed, signature.R, signature.S)
-	if valid {
-		return nil
-	}
-	return errInvalidSignature
-}
diff --git a/go/subtle/signature/ed25519_signer.go b/go/subtle/signature/ed25519_signer.go
new file mode 100644
index 0000000..64c4486
--- /dev/null
+++ b/go/subtle/signature/ed25519_signer.go
@@ -0,0 +1,51 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package signature
+
+import (
+	"golang.org/x/crypto/ed25519"
+
+	"github.com/google/tink/go/tink"
+)
+
+// ED25519Signer is an implementation of Signer for ED25519.
+type ED25519Signer struct {
+	privateKey *ed25519.PrivateKey
+}
+
+// Assert that ed25519Sign implements the Signer interface.
+var _ tink.Signer = (*ED25519Signer)(nil)
+
+// NewED25519Signer creates a new instance of ED25519Signer.
+func NewED25519Signer(keyValue []byte) (*ED25519Signer, error) {
+	p := ed25519.NewKeyFromSeed(keyValue)
+	return NewED25519SignerFromPrivateKey(&p)
+}
+
+// NewED25519SignerFromPrivateKey creates a new instance of ED25519Signer
+func NewED25519SignerFromPrivateKey(privateKey *ed25519.PrivateKey) (*ED25519Signer, error) {
+	return &ED25519Signer{
+		privateKey: privateKey,
+	}, nil
+}
+
+// Sign computes a signature for the given data.
+func (e *ED25519Signer) Sign(data []byte) ([]byte, error) {
+	r := ed25519.Sign(*e.privateKey, data)
+	if len(r) != ed25519.SignatureSize {
+		return nil, errInvalidED25519Signature
+	}
+	return r, nil
+}
diff --git a/go/subtle/signature/ed25519_signer_verifier_test.go b/go/subtle/signature/ed25519_signer_verifier_test.go
new file mode 100644
index 0000000..9bb40fc
--- /dev/null
+++ b/go/subtle/signature/ed25519_signer_verifier_test.go
@@ -0,0 +1,231 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package signature_test
+
+import (
+	"bytes"
+	"crypto/rand"
+	"encoding/hex"
+	"encoding/json"
+	"fmt"
+	"os"
+	"testing"
+
+	"golang.org/x/crypto/ed25519"
+
+	"github.com/google/tink/go/subtle/random"
+	subtleSignature "github.com/google/tink/go/subtle/signature"
+)
+
+func TestED25519Deterministic(t *testing.T) {
+	data := random.GetRandomBytes(20)
+	public, priv, err := ed25519.GenerateKey(rand.Reader)
+	if err != nil {
+		t.Errorf("key generation error: %s", err)
+	}
+
+	// Use the private key and public key directly to create new instances
+	signer, verifier, err := newSignerVerifier(t, &priv, &public)
+	if err != nil {
+		t.Errorf("unexpected error when creating ED25519 Signer and Verifier: %s", err)
+	}
+	sign1, err := signer.Sign(data)
+	if err != nil {
+		t.Errorf("unexpected error when signing: %s", err)
+	}
+	if err := verifier.Verify(sign1, data); err != nil {
+		t.Errorf("unexpected error when verifying: %s", err)
+	}
+
+	sign2, err := signer.Sign(data)
+	if err != nil {
+		t.Errorf("unexpected error when signing: %s", err)
+	}
+	if err := verifier.Verify(sign2, data); err != nil {
+		t.Errorf("unexpected error when verifying: %s", err)
+	}
+	if bytes.Compare(sign1, sign2) != 0 {
+		t.Error("deterministic signature check failure")
+	}
+
+}
+
+func TestEd25519VerifyModifiedSignature(t *testing.T) {
+	data := random.GetRandomBytes(20)
+	public, priv, err := ed25519.GenerateKey(rand.Reader)
+	if err != nil {
+		t.Errorf("key generation error: %s", err)
+	}
+	// Use the private key and public key directly to create new instances
+	signer, verifier, err := newSignerVerifier(t, &priv, &public)
+
+	sign, err := signer.Sign(data)
+	if err != nil {
+		t.Errorf("unexpected error when signing: %s", err)
+	}
+
+	for i := 0; i < len(sign); i++ {
+		for j := 0; j < 8; j++ {
+			sign[i] = byte(sign[i] ^ (1 << uint32(j)))
+			if err := verifier.Verify(sign, data); err == nil {
+				t.Errorf("unexpected error when verifying: %s", err)
+			}
+		}
+	}
+}
+func TestEd25519VerifyModifiedMessage(t *testing.T) {
+	data := random.GetRandomBytes(20)
+	public, priv, err := ed25519.GenerateKey(rand.Reader)
+	if err != nil {
+		t.Errorf("key generation error: %s", err)
+	}
+
+	// Use the private key and public key directly to create new instances
+	signer, verifier, err := newSignerVerifier(t, &priv, &public)
+
+	sign, err := signer.Sign(data)
+	if err != nil {
+		t.Errorf("unexpected error when signing: %s", err)
+	}
+
+	for i := 0; i < len(data); i++ {
+		for j := 0; j < 8; j++ {
+			data[i] = byte(data[i] ^ (1 << uint32(j)))
+			if err := verifier.Verify(sign, data); err == nil {
+				t.Errorf("unexpected error when verifying: %s", err)
+			}
+		}
+	}
+}
+func TestED25519SignVerify(t *testing.T) {
+	public, priv, err := ed25519.GenerateKey(rand.Reader)
+	if err != nil {
+		t.Errorf("key generation error: %s", err)
+	}
+
+	// Use the private key and public key directly to create new instances
+	signer, verifier, err := newSignerVerifier(t, &priv, &public)
+	if err != nil {
+		t.Errorf("unexpected error when creating ED25519 Signer and Verifier: %s", err)
+	}
+	for i := 0; i < 100; i++ {
+		data := random.GetRandomBytes(20)
+		signature, err := signer.Sign(data)
+		if err != nil {
+			t.Errorf("unexpected error when signing: %s", err)
+		}
+		if err := verifier.Verify(signature, data); err != nil {
+			t.Errorf("unexpected error when verifying: %s", err)
+		}
+
+		// Use byte slices to create new instances
+		signer, err = subtleSignature.NewED25519Signer(priv[:ed25519.SeedSize])
+		if err != nil {
+			t.Errorf("unexpected error when creating ED25519 Signer: %s", err)
+		}
+
+		signature, err = signer.Sign(data)
+		if err != nil {
+			t.Errorf("unexpected error when signing: %s", err)
+		}
+		if err = verifier.Verify(signature, data); err != nil {
+			t.Errorf("unexpected error when verifying: %s", err)
+		}
+	}
+
+}
+
+type testDataED25519 struct {
+	Algorithm        string
+	GeneratorVersion string
+	NumberOfTests    uint32
+	TestGroups       []*testGroupED25519
+}
+
+type testGroupED25519 struct {
+	KeyDer string
+	KeyPem string
+	Sha    string
+	Type   string
+	Key    *testKeyED25519
+	Tests  []*testcaseED25519
+}
+
+type testKeyED25519 struct {
+	Sk string
+}
+
+type testcaseED25519 struct {
+	Comment string
+	Message string
+	Result  string
+	Sig     string
+	TcID    uint32
+}
+
+func TestVectorsED25519(t *testing.T) {
+	// signing tests are same between ecdsa and ed25519
+	f, err := os.Open("../../../../wycheproof/testvectors/eddsa_test.json")
+	if err != nil {
+		t.Errorf("cannot open file: %s", err)
+	}
+	parser := json.NewDecoder(f)
+	content := new(testDataED25519)
+	if err := parser.Decode(content); err != nil {
+		t.Errorf("cannot decode content of file: %s", err)
+	}
+	for _, g := range content.TestGroups {
+		pvtKey, err := hex.DecodeString(g.Key.Sk)
+		if err != nil {
+			t.Errorf("cannot decode private key: %s", err)
+		}
+
+		private := ed25519.PrivateKey(pvtKey)
+		public := private.Public()
+
+		verifier, err := subtleSignature.NewED25519Verifier(public.(ed25519.PublicKey))
+		if err != nil {
+			continue
+		}
+		for _, tc := range g.Tests {
+			message, err := hex.DecodeString(tc.Message)
+			if err != nil {
+				t.Errorf("cannot decode message in test case %d: %s", tc.TcID, err)
+			}
+			sig, err := hex.DecodeString(tc.Sig)
+			if err != nil {
+				t.Errorf("cannot decode signature in test case %d: %s", tc.TcID, err)
+			}
+			err = verifier.Verify(sig, message)
+			if (tc.Result == "valid" && err != nil) ||
+				(tc.Result == "invalid" && err == nil) {
+				fmt.Println("failed in test case ", tc.TcID, err)
+			}
+		}
+	}
+}
+
+func newSignerVerifier(t *testing.T, pvtKey *ed25519.PrivateKey, pubKey *ed25519.PublicKey) (*subtleSignature.ED25519Signer, *subtleSignature.ED25519Verifier, error) {
+	t.Helper()
+	signer, err := subtleSignature.NewED25519SignerFromPrivateKey(pvtKey)
+	if err != nil {
+		return nil, nil, err
+	}
+	verifier, err := subtleSignature.NewED25519VerifierFromPublicKey(pubKey)
+	if err != nil {
+		return nil, nil, err
+	}
+	return signer, verifier, nil
+}
diff --git a/go/subtle/signature/ed25519_verifier.go b/go/subtle/signature/ed25519_verifier.go
new file mode 100644
index 0000000..c627048
--- /dev/null
+++ b/go/subtle/signature/ed25519_verifier.go
@@ -0,0 +1,60 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package signature
+
+import (
+	"errors"
+	"fmt"
+
+	"golang.org/x/crypto/ed25519"
+
+	"github.com/google/tink/go/tink"
+)
+
+var errInvalidED25519Signature = errors.New("ed25519: invalid signature")
+
+// ED25519Verifier is an implementation of Verifier for ED25519.
+// At the moment, the implementation only accepts signatures with strict DER encoding.
+type ED25519Verifier struct {
+	publicKey *ed25519.PublicKey
+}
+
+// Assert that ED25519Verifier implements the Verifier interface.
+var _ tink.Verifier = (*ED25519Verifier)(nil)
+
+// NewED25519Verifier creates a new instance of ED25519Verifier.
+func NewED25519Verifier(pub []byte) (*ED25519Verifier, error) {
+	publicKey := ed25519.PublicKey(pub)
+	return NewED25519VerifierFromPublicKey(&publicKey)
+}
+
+// NewED25519VerifierFromPublicKey creates a new instance of ED25519Verifier.
+func NewED25519VerifierFromPublicKey(publicKey *ed25519.PublicKey) (*ED25519Verifier, error) {
+	return &ED25519Verifier{
+		publicKey: publicKey,
+	}, nil
+}
+
+// Verify verifies whether the given signature is valid for the given data.
+// It returns an error if the signature is not valid; nil otherwise.
+func (e *ED25519Verifier) Verify(signature, data []byte) error {
+	if len(signature) != ed25519.SignatureSize {
+		return fmt.Errorf("the length of the signature is not %d", ed25519.SignatureSize)
+	}
+	if !ed25519.Verify(*e.publicKey, data, signature) {
+		return errInvalidED25519Signature
+	}
+	return nil
+}
diff --git a/go/subtle/signature/encoding.go b/go/subtle/signature/encoding.go
index 46af293..0d6f0f8 100644
--- a/go/subtle/signature/encoding.go
+++ b/go/subtle/signature/encoding.go
@@ -1,14 +1,15 @@
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
-
+//
 //      http://www.apache.org/licenses/LICENSE-2.0
-
+//
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+//
 ////////////////////////////////////////////////////////////////////////////////
 
 package signature
@@ -20,7 +21,7 @@
 )
 
 // asn1encode encodes the given ECDSA signature using ASN.1 encoding.
-func asn1encode(sig *EcdsaSignature) ([]byte, error) {
+func asn1encode(sig *ECDSASignature) ([]byte, error) {
 	ret, err := asn1.Marshal(*sig)
 	if err != nil {
 		return nil, fmt.Errorf("asn.1 encoding failed")
@@ -36,9 +37,9 @@
 // that the input follows strict DER encoding: after unmarshalling the signature bytes,
 // we marshal the obtained signature object again. Since DER encoding is deterministic,
 // we expect that the obtained bytes would be equal to the input.
-func asn1decode(b []byte) (*EcdsaSignature, error) {
+func asn1decode(b []byte) (*ECDSASignature, error) {
 	// parse the signature
-	sig := new(EcdsaSignature)
+	sig := new(ECDSASignature)
 	_, err := asn1.Unmarshal(b, sig)
 	if err != nil {
 		return nil, errAsn1Decoding
diff --git a/go/subtle/util.go b/go/subtle/subtle.go
similarity index 86%
rename from go/subtle/util.go
rename to go/subtle/subtle.go
index 34e7e1a..45b100f 100644
--- a/go/subtle/util.go
+++ b/go/subtle/subtle.go
@@ -12,7 +12,7 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-// Package subtle provides utility functions.
+// Package subtle provides common methods needed in subtle implementations.
 package subtle
 
 import (
@@ -21,10 +21,13 @@
 	"crypto/sha256"
 	"crypto/sha512"
 	"encoding/hex"
+	"errors"
 	"hash"
 	"math/big"
 )
 
+var errNilHashFunc = errors.New("nil hash function")
+
 // ConvertHashName converts different forms of a hash name to the
 // hash name that tink recognizes.
 func ConvertHashName(name string) string {
@@ -85,11 +88,18 @@
 }
 
 // ComputeHash calculates a hash of the given data using the given hash function.
-func ComputeHash(hashFunc func() hash.Hash, data []byte) []byte {
+func ComputeHash(hashFunc func() hash.Hash, data []byte) ([]byte, error) {
+	if hashFunc == nil {
+		return nil, errNilHashFunc
+	}
 	h := hashFunc()
-	h.Write(data)
-	ret := h.Sum(nil)
-	return ret
+
+	_, err := h.Write(data)
+	if err != nil {
+		return nil, err
+	}
+
+	return h.Sum(nil), nil
 }
 
 // NewBigIntFromHex returns a big integer from a hex string.
diff --git a/go/subtle/util_test.go b/go/subtle/subtle_test.go
similarity index 69%
rename from go/subtle/util_test.go
rename to go/subtle/subtle_test.go
index 8b75ea1..6cc7b5f 100644
--- a/go/subtle/util_test.go
+++ b/go/subtle/subtle_test.go
@@ -16,6 +16,7 @@
 
 import (
 	"encoding/hex"
+	"hash"
 	"testing"
 
 	"github.com/google/tink/go/subtle"
@@ -41,25 +42,29 @@
 
 func TestComputeHash(t *testing.T) {
 	data := []byte("Hello")
-	// SHA1
-	expectedMac := "f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0"
-	hashFunc := subtle.GetHashFunc("SHA1")
-	if hashFunc == nil || hex.EncodeToString(subtle.ComputeHash(hashFunc, data)) != expectedMac {
-		t.Errorf("invalid hash function for SHA1")
+	var tests = []struct {
+		hashFunc     func() hash.Hash
+		expectedHash string
+	}{
+		{subtle.GetHashFunc("SHA1"), "f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0"},
+		{subtle.GetHashFunc("SHA256"), "185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969"},
+		{subtle.GetHashFunc("SHA512"), "3615f80c9d293ed7402687f94b22d58e529b8cc7916f8fac7fddf7fbd5af4cf777d3d795a7a00a16bf7e7f3fb9561ee9baae480da9fe7a18769e71886b03f315"},
 	}
-	// SHA256
-	expectedMac = "185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969"
-	hashFunc = subtle.GetHashFunc("SHA256")
-	if hashFunc == nil || hex.EncodeToString(subtle.ComputeHash(hashFunc, data)) != expectedMac {
-		t.Errorf("invalid hash function for SHA256")
+
+	for _, tt := range tests {
+		hashFunc := tt.hashFunc
+		if hashFunc == nil {
+			t.Fatal("got nil hash func")
+		}
+		hashed, err := subtle.ComputeHash(hashFunc, data)
+		if err != nil {
+			t.Fatalf("got error: %q", err)
+		}
+		if gotHash := hex.EncodeToString(hashed); gotHash != tt.expectedHash {
+			t.Fatalf("Expected: %s. Got: %s", tt.expectedHash, gotHash)
+		}
 	}
-	// SHA512
-	expectedMac = "3615f80c9d293ed7402687f94b22d58e529b8cc7916f8fac7fddf7fbd5af4cf" +
-		"777d3d795a7a00a16bf7e7f3fb9561ee9baae480da9fe7a18769e71886b03f315"
-	hashFunc = subtle.GetHashFunc("SHA512")
-	if hashFunc == nil || hex.EncodeToString(subtle.ComputeHash(hashFunc, data)) != expectedMac {
-		t.Errorf("invalid hash function for SHA512")
-	}
+
 	// unknown
 	if subtle.GetHashFunc("UNKNOWN_HASH") != nil {
 		t.Errorf("unexpected result for invalid hash types")
diff --git a/go/testkeyset/BUILD.bazel b/go/testkeyset/BUILD.bazel
new file mode 100644
index 0000000..b11683a
--- /dev/null
+++ b/go/testkeyset/BUILD.bazel
@@ -0,0 +1,18 @@
+package(default_visibility = ["//tools/build_defs:internal_pkg"])  # keep
+
+licenses(["notice"])  # Apache 2.0 # keep
+
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+    name = "go_default_library",
+    testonly = 1,
+    srcs = ["testkeyset.go"],
+    importpath = "github.com/google/tink/go/testkeyset",
+    deps = [
+        "//go/internal:go_default_library",
+        "//go/keyset:go_default_library",
+        "//proto:tink_go_proto",
+        "@com_github_golang_protobuf//proto:go_default_library",
+    ],
+)
diff --git a/go/testkeyset/testkeyset.go b/go/testkeyset/testkeyset.go
new file mode 100644
index 0000000..6d8fdcf
--- /dev/null
+++ b/go/testkeyset/testkeyset.go
@@ -0,0 +1,65 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// Package testkeyset provides for test code methods to read or write cleartext keyset material.
+package testkeyset
+
+import (
+	"errors"
+
+	"github.com/google/tink/go/internal"
+	"github.com/google/tink/go/keyset"
+	tinkpb "github.com/google/tink/proto/tink_go_proto"
+)
+
+var (
+	keysetHandle     = internal.KeysetHandle.(func(*tinkpb.Keyset) *keyset.Handle)
+	errInvalidKeyset = errors.New("cleartextkeyset: invalid keyset")
+	errInvalidHandle = errors.New("cleartextkeyset: invalid handle")
+	errInvalidReader = errors.New("cleartextkeyset: invalid reader")
+	errInvalidWriter = errors.New("cleartextkeyset: invalid writer")
+)
+
+// NewHandle creates a new instance of NewHandle using the given keyset.
+func NewHandle(ks *tinkpb.Keyset) (*keyset.Handle, error) {
+	if ks == nil || len(ks.Key) == 0 {
+		return nil, errInvalidKeyset
+	}
+	return keysetHandle(ks), nil
+}
+
+// Read creates a keyset.Handle from a cleartext keyset obtained via r.
+func Read(r keyset.Reader) (*keyset.Handle, error) {
+	if r == nil {
+		return nil, errInvalidReader
+	}
+	ks, err := r.Read()
+	if err != nil || ks == nil || len(ks.Key) == 0 {
+		return nil, errInvalidKeyset
+	}
+	return keysetHandle(ks), nil
+}
+
+// Write exports the keyset from h to the given writer w without encrypting it.
+// Storing secret key material in an unencrypted fashion is dangerous. If feasible, you should use
+// func keyset.Handle.Write() instead.
+func Write(h *keyset.Handle, w keyset.Writer) error {
+	if h == nil {
+		return errInvalidHandle
+	}
+	if w == nil {
+		return errInvalidWriter
+	}
+	return w.Write(h.Keyset())
+}
diff --git a/go/testutil/BUILD.bazel b/go/testutil/BUILD.bazel
index eb4acce..e8872d0 100644
--- a/go/testutil/BUILD.bazel
+++ b/go/testutil/BUILD.bazel
@@ -2,27 +2,37 @@
 
 go_library(
     name = "go_default_library",
-    srcs = ["testutil.go"],
+    srcs = [
+        "constant.go",
+        "testutil.go",
+    ],
     importpath = "github.com/google/tink/go/testutil",
     visibility = ["//visibility:public"],
     deps = [
         "//go/aead:go_default_library",
+        "//go/daead:go_default_library",
+        "//go/keyset:go_default_library",
         "//go/mac:go_default_library",
+        "//go/registry:go_default_library",
         "//go/signature:go_default_library",
         "//go/subtle:go_default_library",
+        "//go/subtle/daead:go_default_library",
         "//go/subtle/random:go_default_library",
         "//go/tink:go_default_library",
         "//proto:aes_gcm_go_proto",
+        "//proto:aes_siv_go_proto",
         "//proto:common_go_proto",
         "//proto:ecdsa_go_proto",
+        "//proto:ed25519_go_proto",
         "//proto:hmac_go_proto",
         "//proto:tink_go_proto",
         "@com_github_golang_protobuf//proto:go_default_library",
+        "@org_golang_x_crypto//ed25519:go_default_library",
     ],
 )
 
 go_test(
-    name = "go_default_xtest",
+    name = "go_default_test",
     srcs = ["testutil_test.go"],
     deps = [
         "//go/testutil:go_default_library",
diff --git a/go/testutil/constant.go b/go/testutil/constant.go
new file mode 100644
index 0000000..9973ece
--- /dev/null
+++ b/go/testutil/constant.go
@@ -0,0 +1,80 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package testutil
+
+const (
+	// AEAD
+
+	// AESCTRHMACAEADKeyVersion is the maxmimal version of AES-CTR-HMAC-AEAD keys that Tink supports.
+	AESCTRHMACAEADKeyVersion = 0
+	// AESCTRHMACAEADTypeURL is the type URL of AES-CTR-HMAC-AEAD keys that Tink supports.
+	AESCTRHMACAEADTypeURL = "type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey"
+
+	// AESGCMKeyVersion is the maxmimal version of AES-GCM keys.
+	AESGCMKeyVersion = 0
+	// AESGCMTypeURL is the type URL of AES-GCM keys that Tink supports.
+	AESGCMTypeURL = "type.googleapis.com/google.crypto.tink.AesGcmKey"
+
+	// ChaCha20Poly1305KeyVersion is the maxmimal version of ChaCha20Poly1305 keys that Tink supports.
+	ChaCha20Poly1305KeyVersion = 0
+	// ChaCha20Poly1305TypeURL is the type URL of ChaCha20Poly1305 keys.
+	ChaCha20Poly1305TypeURL = "type.googleapis.com/google.crypto.tink.ChaCha20Poly1305Key"
+
+	// KMSEnvelopeAEADKeyVersion is the maxmimal version of KMSEnvelopeAEAD keys that Tink supports.
+	KMSEnvelopeAEADKeyVersion = 0
+	// KMSEnvelopeAEADTypeURL is the type URL of KMSEnvelopeAEAD keys.
+	KMSEnvelopeAEADTypeURL = "type.googleapis.com/google.crypto.tink.KmsEnvelopeAeadKey"
+
+	// XChaCha20Poly1305KeyVersion is the maxmimal version of XChaCha20Poly1305 keys that Tink supports.
+	XChaCha20Poly1305KeyVersion = 0
+	// XChaCha20Poly1305TypeURL is the type URL of XChaCha20Poly1305 keys.
+	XChaCha20Poly1305TypeURL = "type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key"
+
+	// DeterministicAEAD
+
+	// AESSIVKeyVersion is the maxmimal version of AES-SIV keys that Tink supports.
+	AESSIVKeyVersion = 0
+	// AESSIVTypeURL is the type URL of AES-SIV keys.
+	AESSIVTypeURL = "type.googleapis.com/google.crypto.tink.AesSivKey"
+
+	// MAC
+
+	// HMACKeyVersion is the maxmimal version of HMAC keys that Tink supports.
+	HMACKeyVersion = 0
+	// HMACTypeURL is the type URL of HMAC keys.
+	HMACTypeURL = "type.googleapis.com/google.crypto.tink.HmacKey"
+
+	// Digital signatures
+
+	// ECDSASignerKeyVersion is the maximum version of ECDSA private keys that Tink supports.
+	ECDSASignerKeyVersion = 0
+	// ECDSASignerTypeURL is the type URL of ECDSA private keys.
+	ECDSASignerTypeURL = "type.googleapis.com/google.crypto.tink.EcdsaPrivateKey"
+
+	// ECDSAVerifierKeyVersion is the maximum version of ECDSA public keys that Tink supports.
+	ECDSAVerifierKeyVersion = 0
+	// ECDSAVerifierTypeURL is the type URL of ECDSA public keys.
+	ECDSAVerifierTypeURL = "type.googleapis.com/google.crypto.tink.EcdsaPublicKey"
+
+	// ED25519SignerKeyVersion is the maximum version of ED25519 private keys that Tink supports.
+	ED25519SignerKeyVersion = 0
+	// ED25519SignerTypeURL is the type URL of ED25519 private keys.
+	ED25519SignerTypeURL = "type.googleapis.com/google.crypto.tink.Ed25519PrivateKey"
+
+	// ED25519VerifierKeyVersion is the maximum version of ED25519 public keys that Tink supports.
+	ED25519VerifierKeyVersion = 0
+	// ED25519VerifierTypeURL is the type URL of ED25519 public keys.
+	ED25519VerifierTypeURL = "type.googleapis.com/google.crypto.tink.Ed25519PublicKey"
+)
diff --git a/go/testutil/testutil.go b/go/testutil/testutil.go
index e1c724d..9a98324 100644
--- a/go/testutil/testutil.go
+++ b/go/testutil/testutil.go
@@ -12,7 +12,7 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-// Package testutil provides test utilities.
+// Package testutil provides common methods needed in test code.
 package testutil
 
 import (
@@ -21,116 +21,151 @@
 	"fmt"
 
 	"github.com/golang/protobuf/proto"
-	"github.com/google/tink/go/aead"
+	"golang.org/x/crypto/ed25519"
+	"github.com/google/tink/go/keyset"
 	"github.com/google/tink/go/mac"
-	"github.com/google/tink/go/signature"
+	"github.com/google/tink/go/registry"
 	"github.com/google/tink/go/subtle/random"
 	"github.com/google/tink/go/subtle"
 	"github.com/google/tink/go/tink"
+
+	subtedaead "github.com/google/tink/go/subtle/daead"
 	gcmpb "github.com/google/tink/proto/aes_gcm_go_proto"
+	aspb "github.com/google/tink/proto/aes_siv_go_proto"
 	commonpb "github.com/google/tink/proto/common_go_proto"
 	ecdsapb "github.com/google/tink/proto/ecdsa_go_proto"
+	ed25519pb "github.com/google/tink/proto/ed25519_go_proto"
 	hmacpb "github.com/google/tink/proto/hmac_go_proto"
 	tinkpb "github.com/google/tink/proto/tink_go_proto"
 )
 
-// DummyAeadKeyManager is a dummy implementation of the KeyManager interface.
-// It returns DummyAead when GetPrimitive() functions are called.
-type DummyAeadKeyManager struct{}
+// DummyAEADKeyManager is a dummy implementation of the KeyManager interface.
+// It returns DummyAEAD when GetPrimitive() functions are called.
+type DummyAEADKeyManager struct{}
 
-var _ tink.KeyManager = (*DummyAeadKeyManager)(nil)
+var _ registry.KeyManager = (*DummyAEADKeyManager)(nil)
 
-// GetPrimitiveFromSerializedKey constructs a primitive instance for the key given in
+// Primitive constructs a primitive instance for the key given in
 // serializedKey, which must be a serialized key protocol buffer handled by this manager.
-func (km *DummyAeadKeyManager) GetPrimitiveFromSerializedKey(serializedKey []byte) (interface{}, error) {
-	return new(DummyAead), nil
+func (km *DummyAEADKeyManager) Primitive(serializedKey []byte) (interface{}, error) {
+	return new(DummyAEAD), nil
 }
 
-// GetPrimitiveFromKey constructs a primitive instance for the key given in {@code key}.
-func (km *DummyAeadKeyManager) GetPrimitiveFromKey(m proto.Message) (interface{}, error) {
-	return new(DummyAead), nil
-}
-
-// NewKeyFromSerializedKeyFormat Generates a new key according to specification in {@code serializedKeyFormat},
-// which must be a serialized key format protocol buffer handled by this manager.
-func (km *DummyAeadKeyManager) NewKeyFromSerializedKeyFormat(serializedKeyFormat []byte) (proto.Message, error) {
+// NewKey generates a new key according to specification in serializedKeyFormat.
+func (km *DummyAEADKeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) {
 	return nil, fmt.Errorf("not implemented")
 }
 
-// NewKeyFromKeyFormat generates a new key according to specification in {@code keyFormat}.
-func (km *DummyAeadKeyManager) NewKeyFromKeyFormat(m proto.Message) (proto.Message, error) {
+// NewKeyData generates a new KeyData according to specification in serializedkeyFormat.
+func (km *DummyAEADKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) {
 	return nil, fmt.Errorf("not implemented")
 }
 
-// NewKeyData generates a new {@code KeyData} according to specification in {@code serializedkeyFormat}.
-func (km *DummyAeadKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) {
-	return nil, fmt.Errorf("not implemented")
+// DoesSupport returns true iff this KeyManager supports key type identified by typeURL.
+func (km *DummyAEADKeyManager) DoesSupport(typeURL string) bool {
+	return typeURL == AESGCMTypeURL
 }
 
-// DoesSupport returns true iff this KeyManager supports key type identified by {@code typeURL}.
-func (km *DummyAeadKeyManager) DoesSupport(typeURL string) bool {
-	return typeURL == aead.AesGcmTypeURL
+// TypeURL returns the type URL.
+func (km *DummyAEADKeyManager) TypeURL() string {
+	return AESGCMTypeURL
 }
 
-// GetKeyType returns the type URL.
-func (km *DummyAeadKeyManager) GetKeyType() string {
-	return aead.AesGcmTypeURL
-}
-
-// DummyAead is a dummy implementation of Aead interface.
-type DummyAead struct{}
+// DummyAEAD is a dummy implementation of AEAD interface.
+type DummyAEAD struct{}
 
 // Encrypt encrypts the plaintext.
-func (a *DummyAead) Encrypt(plaintext []byte, additionalData []byte) ([]byte, error) {
+func (a *DummyAEAD) Encrypt(plaintext []byte, additionalData []byte) ([]byte, error) {
 	return nil, fmt.Errorf("dummy aead encrypt")
 }
 
 // Decrypt decrypts the ciphertext.
-func (a *DummyAead) Decrypt(ciphertext []byte, additionalData []byte) ([]byte, error) {
+func (a *DummyAEAD) Decrypt(ciphertext []byte, additionalData []byte) ([]byte, error) {
 	return nil, fmt.Errorf("dummy aead decrypt")
 }
 
-// DummyMac is a dummy implementation of Mac interface.
-type DummyMac struct {
+// DummyMAC is a dummy implementation of Mac interface.
+type DummyMAC struct {
 	Name string
 }
 
-// ComputeMac Computes message authentication code (MAC) for {@code data}.
-func (h *DummyMac) ComputeMac(data []byte) ([]byte, error) {
+// ComputeMAC computes message authentication code (MAC) for {@code data}.
+func (h *DummyMAC) ComputeMAC(data []byte) ([]byte, error) {
 	var m []byte
 	m = append(m, data...)
 	m = append(m, h.Name...)
 	return m, nil
 }
 
-// VerifyMac verifies whether {@code mac} is a correct authentication code (MAC) for {@code data}.
-func (h *DummyMac) VerifyMac(mac []byte, data []byte) (bool, error) {
-	return true, nil
+// VerifyMAC verifies whether {@code mac} is a correct authentication code (MAC) for {@code data}.
+func (h *DummyMAC) VerifyMAC(mac []byte, data []byte) error {
+	return nil
 }
 
-// NewTestAesGcmKeyset creates a new Keyset containing an AesGcmKey.
-func NewTestAesGcmKeyset(primaryOutputPrefixType tinkpb.OutputPrefixType) *tinkpb.Keyset {
-	keyData := NewAesGcmKeyData(16)
+// DummyKMSClient is a dummy implementation of a KMS Client.
+type DummyKMSClient struct{}
+
+var _ registry.KMSClient = (*DummyKMSClient)(nil)
+
+// Supported true if this client does support keyURI
+func (d *DummyKMSClient) Supported(keyURI string) bool {
+	if keyURI == "dummy" {
+		return true
+	}
+	return false
+}
+
+// LoadCredentials loads the credentials in credentialPath. If credentialPath is null, loads the
+// default credentials.
+func (d *DummyKMSClient) LoadCredentials(credentialPath string) (interface{}, error) {
+	return d, nil
+}
+
+// LoadDefaultCredentials loads with the default credentials.
+func (d *DummyKMSClient) LoadDefaultCredentials() (interface{}, error) {
+	return d, nil
+}
+
+// GetAEAD gets an Aead backend by keyURI.
+func (d *DummyKMSClient) GetAEAD(keyURI string) (tink.AEAD, error) {
+	return &DummyAEAD{}, nil
+}
+
+// NewTestAESGCMKeyset creates a new Keyset containing an AESGCMKey.
+func NewTestAESGCMKeyset(primaryOutputPrefixType tinkpb.OutputPrefixType) *tinkpb.Keyset {
+	keyData := NewAESGCMKeyData(16)
 	return NewTestKeyset(keyData, primaryOutputPrefixType)
 }
 
-// NewTestHmacKeyset creates a new Keyset containing a HmacKey.
-func NewTestHmacKeyset(tagSize uint32,
+// NewTestAESSIVKeyset creates a new Keyset containing an AesSivKey.
+func NewTestAESSIVKeyset(primaryOutputPrefixType tinkpb.OutputPrefixType) *tinkpb.Keyset {
+	keyValue := random.GetRandomBytes(subtedaead.AESSIVKeySize)
+	key := &aspb.AesSivKey{
+		Version:  AESSIVKeyVersion,
+		KeyValue: keyValue,
+	}
+	serializedKey, _ := proto.Marshal(key)
+	keyData := NewKeyData(AESSIVTypeURL, serializedKey, tinkpb.KeyData_SYMMETRIC)
+	return NewTestKeyset(keyData, primaryOutputPrefixType)
+}
+
+// NewTestHMACKeyset creates a new Keyset containing a HMACKey.
+func NewTestHMACKeyset(tagSize uint32,
 	primaryOutputPrefixType tinkpb.OutputPrefixType) *tinkpb.Keyset {
-	keyData := NewHmacKeyData(commonpb.HashType_SHA256, tagSize)
+	keyData := NewHMACKeyData(commonpb.HashType_SHA256, tagSize)
 	return NewTestKeyset(keyData, primaryOutputPrefixType)
 }
 
 // NewTestKeyset creates a new test Keyset.
 func NewTestKeyset(keyData *tinkpb.KeyData,
 	primaryOutputPrefixType tinkpb.OutputPrefixType) *tinkpb.Keyset {
-	primaryKey := tink.CreateKey(keyData, tinkpb.KeyStatusType_ENABLED, 42, primaryOutputPrefixType)
-	rawKey := tink.CreateKey(keyData, tinkpb.KeyStatusType_ENABLED, 43, tinkpb.OutputPrefixType_RAW)
-	legacyKey := tink.CreateKey(keyData, tinkpb.KeyStatusType_ENABLED, 44, tinkpb.OutputPrefixType_LEGACY)
-	tinkKey := tink.CreateKey(keyData, tinkpb.KeyStatusType_ENABLED, 45, tinkpb.OutputPrefixType_TINK)
-	crunchyKey := tink.CreateKey(keyData, tinkpb.KeyStatusType_ENABLED, 46, tinkpb.OutputPrefixType_CRUNCHY)
+	primaryKey := NewKey(keyData, tinkpb.KeyStatusType_ENABLED, 42, primaryOutputPrefixType)
+	rawKey := NewKey(keyData, tinkpb.KeyStatusType_ENABLED, 43, tinkpb.OutputPrefixType_RAW)
+	legacyKey := NewKey(keyData, tinkpb.KeyStatusType_ENABLED, 44, tinkpb.OutputPrefixType_LEGACY)
+	tinkKey := NewKey(keyData, tinkpb.KeyStatusType_ENABLED, 45, tinkpb.OutputPrefixType_TINK)
+	crunchyKey := NewKey(keyData, tinkpb.KeyStatusType_ENABLED, 46, tinkpb.OutputPrefixType_CRUNCHY)
 	keys := []*tinkpb.Keyset_Key{primaryKey, rawKey, legacyKey, tinkKey, crunchyKey}
-	return tink.CreateKeyset(primaryKey.KeyId, keys)
+	return NewKeyset(primaryKey.KeyId, keys)
 }
 
 // NewDummyKey returns a dummy key that doesn't contain actual key material.
@@ -143,91 +178,230 @@
 	}
 }
 
-// NewEcdsaPrivateKey creates an EcdsaPrivateKey with a randomly generated key material.
-func NewEcdsaPrivateKey(hashType commonpb.HashType, curve commonpb.EllipticCurveType) *ecdsapb.EcdsaPrivateKey {
-	curveName := commonpb.EllipticCurveType_name[int32(curve)]
-	priv, _ := ecdsa.GenerateKey(subtle.GetCurve(curveName), rand.Reader)
-	params := signature.NewEcdsaParams(hashType,
-		curve,
-		ecdsapb.EcdsaSignatureEncoding_DER)
-	publicKey := signature.NewEcdsaPublicKey(signature.EcdsaVerifyKeyVersion,
-		params, priv.X.Bytes(), priv.Y.Bytes())
-	return signature.NewEcdsaPrivateKey(signature.EcdsaSignKeyVersion,
-		publicKey, priv.D.Bytes())
+// NewECDSAParams creates a ECDSAParams with the specified parameters.
+func NewECDSAParams(hashType commonpb.HashType,
+	curve commonpb.EllipticCurveType,
+	encoding ecdsapb.EcdsaSignatureEncoding) *ecdsapb.EcdsaParams {
+	return &ecdsapb.EcdsaParams{
+		HashType: hashType,
+		Curve:    curve,
+		Encoding: encoding,
+	}
 }
 
-// NewEcdsaPrivateKeyData creates a KeyData containing an EcdsaPrivateKey with a randomly generated key material.
-func NewEcdsaPrivateKeyData(hashType commonpb.HashType, curve commonpb.EllipticCurveType) *tinkpb.KeyData {
-	key := NewEcdsaPrivateKey(hashType, curve)
+// NewECDSAKeyFormat creates a ECDSAKeyFormat with the specified parameters.
+func NewECDSAKeyFormat(params *ecdsapb.EcdsaParams) *ecdsapb.EcdsaKeyFormat {
+	return &ecdsapb.EcdsaKeyFormat{Params: params}
+}
+
+// NewECDSAPrivateKey creates a ECDSAPrivateKey with the specified paramaters.
+func NewECDSAPrivateKey(version uint32,
+	publicKey *ecdsapb.EcdsaPublicKey,
+	keyValue []byte) *ecdsapb.EcdsaPrivateKey {
+	return &ecdsapb.EcdsaPrivateKey{
+		Version:   version,
+		PublicKey: publicKey,
+		KeyValue:  keyValue,
+	}
+}
+
+// NewECDSAPublicKey creates a ECDSAPublicKey with the specified paramaters.
+func NewECDSAPublicKey(version uint32,
+	params *ecdsapb.EcdsaParams,
+	x []byte, y []byte) *ecdsapb.EcdsaPublicKey {
+	return &ecdsapb.EcdsaPublicKey{
+		Version: version,
+		Params:  params,
+		X:       x,
+		Y:       y,
+	}
+}
+
+// NewRandomECDSAPrivateKey creates an ECDSAPrivateKey with a randomly generated key material.
+func NewRandomECDSAPrivateKey(hashType commonpb.HashType, curve commonpb.EllipticCurveType) *ecdsapb.EcdsaPrivateKey {
+	curveName := commonpb.EllipticCurveType_name[int32(curve)]
+	priv, _ := ecdsa.GenerateKey(subtle.GetCurve(curveName), rand.Reader)
+	params := NewECDSAParams(hashType, curve, ecdsapb.EcdsaSignatureEncoding_DER)
+	publicKey := NewECDSAPublicKey(ECDSAVerifierKeyVersion, params, priv.X.Bytes(), priv.Y.Bytes())
+	return NewECDSAPrivateKey(ECDSASignerKeyVersion, publicKey, priv.D.Bytes())
+}
+
+// NewRandomECDSAPrivateKeyData creates a KeyData containing an ECDSAPrivateKey with a randomly generated key material.
+func NewRandomECDSAPrivateKeyData(hashType commonpb.HashType, curve commonpb.EllipticCurveType) *tinkpb.KeyData {
+	key := NewRandomECDSAPrivateKey(hashType, curve)
 	serializedKey, _ := proto.Marshal(key)
 	return &tinkpb.KeyData{
-		TypeUrl:         signature.EcdsaSignTypeURL,
+		TypeUrl:         ECDSASignerTypeURL,
 		Value:           serializedKey,
 		KeyMaterialType: tinkpb.KeyData_ASYMMETRIC_PRIVATE,
 	}
 }
 
-// NewEcdsaPublicKey creates an EcdsaPublicKey with the specified parameters.
-func NewEcdsaPublicKey(hashType commonpb.HashType, curve commonpb.EllipticCurveType) *ecdsapb.EcdsaPublicKey {
-	return NewEcdsaPrivateKey(hashType, curve).PublicKey
+// NewRandomECDSAPublicKey creates an ECDSAPublicKe with a randomly generated key material.
+func NewRandomECDSAPublicKey(hashType commonpb.HashType, curve commonpb.EllipticCurveType) *ecdsapb.EcdsaPublicKey {
+	return NewRandomECDSAPrivateKey(hashType, curve).PublicKey
 }
 
-// NewAesGcmKey creates a randomly generated AesGcmKey.
-func NewAesGcmKey(keySize uint32) *gcmpb.AesGcmKey {
-	keyValue := random.GetRandomBytes(keySize)
-	return aead.NewAesGcmKey(aead.AesGcmKeyVersion, keyValue)
+// GetECDSAParamNames returns the string representations of each parameter in
+// the given ECDSAParams.
+func GetECDSAParamNames(params *ecdsapb.EcdsaParams) (string, string, string) {
+	hashName := commonpb.HashType_name[int32(params.HashType)]
+	curveName := commonpb.EllipticCurveType_name[int32(params.Curve)]
+	encodingName := ecdsapb.EcdsaSignatureEncoding_name[int32(params.Encoding)]
+	return hashName, curveName, encodingName
 }
 
-// NewAesGcmKeyData creates a KeyData containing a randomly generated AesGcmKey.
-func NewAesGcmKeyData(keySize uint32) *tinkpb.KeyData {
-	keyValue := random.GetRandomBytes(keySize)
-	key := aead.NewAesGcmKey(aead.AesGcmKeyVersion, keyValue)
+// NewED25519PrivateKey creates an ED25519PrivateKey with a randomly generated key material.
+func NewED25519PrivateKey() *ed25519pb.Ed25519PrivateKey {
+	public, private, _ := ed25519.GenerateKey(rand.Reader)
+	publicProto := &ed25519pb.Ed25519PublicKey{
+		Version:  ED25519SignerKeyVersion,
+		KeyValue: public,
+	}
+	return &ed25519pb.Ed25519PrivateKey{
+		Version:   ED25519SignerKeyVersion,
+		PublicKey: publicProto,
+		KeyValue:  private.Seed(),
+	}
+}
+
+// NewED25519PrivateKeyData creates a KeyData containing an ED25519PrivateKey with a randomly generated key material.
+func NewED25519PrivateKeyData() *tinkpb.KeyData {
+	key := NewED25519PrivateKey()
 	serializedKey, _ := proto.Marshal(key)
-	return tink.CreateKeyData(aead.AesGcmTypeURL, serializedKey, tinkpb.KeyData_SYMMETRIC)
+	return &tinkpb.KeyData{
+		TypeUrl:         ED25519SignerTypeURL,
+		Value:           serializedKey,
+		KeyMaterialType: tinkpb.KeyData_ASYMMETRIC_PRIVATE,
+	}
 }
 
-// NewSerializedAesGcmKey creates a AesGcmKey with randomly generated key material.
-func NewSerializedAesGcmKey(keySize uint32) []byte {
-	key := NewAesGcmKey(keySize)
+// NewED25519PublicKey creates an ED25519PublicKey with a randomly generated key material.
+func NewED25519PublicKey() *ed25519pb.Ed25519PublicKey {
+	return NewED25519PrivateKey().PublicKey
+}
+
+// NewAESGCMKey creates a randomly generated AESGCMKey.
+func NewAESGCMKey(keyVersion uint32, keySize uint32) *gcmpb.AesGcmKey {
+	keyValue := random.GetRandomBytes(keySize)
+	return &gcmpb.AesGcmKey{
+		Version:  keyVersion,
+		KeyValue: keyValue,
+	}
+}
+
+// NewAESGCMKeyData creates a KeyData containing a randomly generated AESGCMKey.
+func NewAESGCMKeyData(keySize uint32) *tinkpb.KeyData {
+	key := NewAESGCMKey(AESGCMKeyVersion, keySize)
+	serializedKey, _ := proto.Marshal(key)
+	return NewKeyData(AESGCMTypeURL, serializedKey, tinkpb.KeyData_SYMMETRIC)
+}
+
+// NewSerializedAESGCMKey creates a AESGCMKey with randomly generated key material.
+func NewSerializedAESGCMKey(keySize uint32) []byte {
+	key := NewAESGCMKey(AESGCMKeyVersion, keySize)
 	serializedKey, err := proto.Marshal(key)
 	if err != nil {
-		panic(fmt.Sprintf("cannot marshal AesGcmKey: %s", err))
+		panic(fmt.Sprintf("cannot marshal AESGCMKey: %s", err))
 	}
 	return serializedKey
 }
 
-// NewHmacKey creates a new HmacKey with the specified parameters.
-func NewHmacKey(hashType commonpb.HashType, tagSize uint32) *hmacpb.HmacKey {
-	params := mac.NewHmacParams(hashType, tagSize)
+// NewAESGCMKeyFormat returns a new AESGCMKeyFormat.
+func NewAESGCMKeyFormat(keySize uint32) *gcmpb.AesGcmKeyFormat {
+	return &gcmpb.AesGcmKeyFormat{
+		KeySize: keySize,
+	}
+}
+
+// NewHMACParams returns a new HMACParams.
+func NewHMACParams(hashType commonpb.HashType, tagSize uint32) *hmacpb.HmacParams {
+	return &hmacpb.HmacParams{
+		Hash:    hashType,
+		TagSize: tagSize,
+	}
+}
+
+// NewHMACKey creates a new HMACKey with the specified parameters.
+func NewHMACKey(hashType commonpb.HashType, tagSize uint32) *hmacpb.HmacKey {
+	params := NewHMACParams(hashType, tagSize)
 	keyValue := random.GetRandomBytes(20)
-	return mac.NewHmacKey(params, mac.HmacKeyVersion, keyValue)
+	return &hmacpb.HmacKey{
+		Version:  HMACKeyVersion,
+		Params:   params,
+		KeyValue: keyValue,
+	}
 }
 
-// NewHmacKeyFormat creates a new HmacKeyFormat with the specified parameters.
-func NewHmacKeyFormat(hashType commonpb.HashType, tagSize uint32) *hmacpb.HmacKeyFormat {
-	params := mac.NewHmacParams(hashType, tagSize)
+// NewHMACKeyFormat creates a new HMACKeyFormat with the specified parameters.
+func NewHMACKeyFormat(hashType commonpb.HashType, tagSize uint32) *hmacpb.HmacKeyFormat {
+	params := NewHMACParams(hashType, tagSize)
 	keySize := uint32(20)
-	return mac.NewHmacKeyFormat(params, keySize)
+	return &hmacpb.HmacKeyFormat{
+		Params:  params,
+		KeySize: keySize,
+	}
 }
 
-// NewHmacKeysetManager returns a new KeysetManager that contains a HmacKey.
-func NewHmacKeysetManager() *tink.KeysetManager {
-	macTemplate := mac.HmacSha256Tag128KeyTemplate()
-	manager := tink.NewKeysetManager(macTemplate, nil, nil)
-	err := manager.Rotate()
+// NewHMACKeysetManager returns a new KeysetManager that contains a HMACKey.
+func NewHMACKeysetManager() *keyset.Manager {
+	ksm := keyset.NewManager()
+	kt := mac.HMACSHA256Tag128KeyTemplate()
+	err := ksm.Rotate(kt)
 	if err != nil {
 		panic(fmt.Sprintf("cannot rotate keyset manager: %s", err))
 	}
-	return manager
+	return ksm
 }
 
-// NewHmacKeyData returns a new KeyData that contains a HmacKey.
-func NewHmacKeyData(hashType commonpb.HashType, tagSize uint32) *tinkpb.KeyData {
-	key := NewHmacKey(hashType, tagSize)
+// NewHMACKeyData returns a new KeyData that contains a HMACKey.
+func NewHMACKeyData(hashType commonpb.HashType, tagSize uint32) *tinkpb.KeyData {
+	key := NewHMACKey(hashType, tagSize)
 	serializedKey, _ := proto.Marshal(key)
 	return &tinkpb.KeyData{
-		TypeUrl:         mac.HmacTypeURL,
+		TypeUrl:         HMACTypeURL,
 		Value:           serializedKey,
 		KeyMaterialType: tinkpb.KeyData_SYMMETRIC,
 	}
 }
+
+// NewKeyData creates a new KeyData with the specified parameters.
+func NewKeyData(typeURL string,
+	value []byte,
+	materialType tinkpb.KeyData_KeyMaterialType) *tinkpb.KeyData {
+	return &tinkpb.KeyData{
+		TypeUrl:         typeURL,
+		Value:           value,
+		KeyMaterialType: materialType,
+	}
+}
+
+// NewKey creates a new Key with the specified parameters.
+func NewKey(keyData *tinkpb.KeyData,
+	status tinkpb.KeyStatusType,
+	keyID uint32,
+	prefixType tinkpb.OutputPrefixType) *tinkpb.Keyset_Key {
+	return &tinkpb.Keyset_Key{
+		KeyData:          keyData,
+		Status:           status,
+		KeyId:            keyID,
+		OutputPrefixType: prefixType,
+	}
+}
+
+// NewKeyset creates a new Keyset with the specified parameters.
+func NewKeyset(primaryKeyID uint32,
+	keys []*tinkpb.Keyset_Key) *tinkpb.Keyset {
+	return &tinkpb.Keyset{
+		PrimaryKeyId: primaryKeyID,
+		Key:          keys,
+	}
+}
+
+// NewEncryptedKeyset creates a new EncryptedKeyset with a specified parameters.
+func NewEncryptedKeyset(encryptedKeySet []byte, info *tinkpb.KeysetInfo) *tinkpb.EncryptedKeyset {
+	return &tinkpb.EncryptedKeyset{
+		EncryptedKeyset: encryptedKeySet,
+		KeysetInfo:      info,
+	}
+}
diff --git a/go/testutil/testutil_test.go b/go/testutil/testutil_test.go
index 84a6410..e9490ac 100644
--- a/go/testutil/testutil_test.go
+++ b/go/testutil/testutil_test.go
@@ -22,25 +22,25 @@
 	"github.com/google/tink/go/tink"
 )
 
-func TestDummyAead(t *testing.T) {
-	// Assert that DummyAead implements the Aead interface.
-	var _ tink.Aead = (*testutil.DummyAead)(nil)
+func TestDummyAEAD(t *testing.T) {
+	// Assert that DummyAEAD implements the AEAD interface.
+	var _ tink.AEAD = (*testutil.DummyAEAD)(nil)
 }
 
-func TestDummyMac(t *testing.T) {
-	// Assert that DummyMac implements the Aead interface.
-	var _ tink.Mac = (*testutil.DummyMac)(nil)
+func TestDummyMAC(t *testing.T) {
+	// Assert that DummyMAC implements the AEAD interface.
+	var _ tink.MAC = (*testutil.DummyMAC)(nil)
 	// try to compute mac
 	data := []byte{1, 2, 3, 4, 5}
-	dummyMac := &testutil.DummyMac{Name: "Mac12347"}
-	digest, err := dummyMac.ComputeMac(data)
+	dummyMAC := &testutil.DummyMAC{Name: "Mac12347"}
+	digest, err := dummyMAC.ComputeMAC(data)
 	if err != nil {
 		t.Errorf("unexpected error: %s", err)
 	}
-	if !bytes.Equal(append(data, dummyMac.Name...), digest) {
+	if !bytes.Equal(append(data, dummyMAC.Name...), digest) {
 		t.Errorf("incorrect digest")
 	}
-	if valid, err := dummyMac.VerifyMac(nil, nil); !valid || err != nil {
-		t.Errorf("unexpected result of VerifyMac")
+	if err := dummyMAC.VerifyMAC(nil, nil); err != nil {
+		t.Errorf("unexpected result of VerifyMAC")
 	}
 }
diff --git a/go/tink/BUILD.bazel b/go/tink/BUILD.bazel
index f3bc901..e78b7db 100644
--- a/go/tink/BUILD.bazel
+++ b/go/tink/BUILD.bazel
@@ -1,62 +1,20 @@
+package(default_visibility = ["//tools/build_defs:internal_pkg"])  # keep
+
+licenses(["notice"])  # Apache 2.0 # keep
+
 load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 
 go_library(
     name = "go_default_library",
     srcs = [
         "aead.go",
-        "cleartext_keyset_handle.go",
-        "crypto_format.go",
-        "key_manager.go",
-        "keyset_handle.go",
-        "keyset_manager.go",
+        "deterministic_aead.go",
+        "hybrid_decrypt.go",
+        "hybrid_encrypt.go",
         "mac.go",
-        "primitive_set.go",
-        "private_key_manager.go",
-        "public_key_sign.go",
-        "public_key_verify.go",
-        "registry.go",
-        "util.go",
+        "signer.go",
+        "verifier.go",
     ],
     importpath = "github.com/google/tink/go/tink",
     visibility = ["//visibility:public"],
-    deps = [
-        "//go/subtle/random:go_default_library",
-        "//proto:common_go_proto",
-        "//proto:tink_go_proto",
-        "@com_github_golang_protobuf//proto:go_default_library",
-    ],
-)
-
-go_test(
-    name = "go_default_test",
-    srcs = ["keyset_handle_test.go"],
-    embed = [":go_default_library"],
-    deps = [
-        "//proto:tink_go_proto",
-    ],
-)
-
-go_test(
-    name = "go_default_xtest",
-    srcs = [
-        "cleartext_keyset_handle_test.go",
-        "crypto_format_test.go",
-        "keyset_manager_test.go",
-        "primitive_set_test.go",
-        "registry_test.go",
-        "util_test.go",
-    ],
-    deps = [
-        ":go_default_library",
-        "//go/aead:go_default_library",
-        "//go/mac:go_default_library",
-        "//go/subtle/aead:go_default_library",
-        "//go/subtle/mac:go_default_library",
-        "//go/testutil:go_default_library",
-        "//proto:aes_gcm_go_proto",
-        "//proto:common_go_proto",
-        "//proto:hmac_go_proto",
-        "//proto:tink_go_proto",
-        "@com_github_golang_protobuf//proto:go_default_library",
-    ],
 )
diff --git a/go/tink/aead.go b/go/tink/aead.go
index 7dff612..7fee396 100644
--- a/go/tink/aead.go
+++ b/go/tink/aead.go
@@ -1,34 +1,35 @@
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
-
+//
 //      http://www.apache.org/licenses/LICENSE-2.0
-
+//
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+//
 ////////////////////////////////////////////////////////////////////////////////
 
-// Package tink defines interfaces for the crypto primitives that Tink supports.
+// Package tink provides the abstract interfaces of the primitives which Tink supports.
 package tink
 
 /*
-Aead is the interface for authenticated encryption with additional authenticated data.
+AEAD is the interface for authenticated encryption with additional authenticated data.
 Implementations of this interface are secure against adaptive chosen ciphertext attacks.
 Encryption with additional data ensures authenticity and integrity of that data, but not
 its secrecy. (see RFC 5116, https://tools.ietf.org/html/rfc5116)
 */
-type Aead interface {
-	// Encrypt encrypts {@code plaintext} with {@code additionalData} as additional
+type AEAD interface {
+	// Encrypt encrypts plaintext with additionalData as additional
 	// authenticated data. The resulting ciphertext allows for checking
-	// authenticity and integrity of additional data ({@code additionalData}),
-	// but does not guarantee its secrecy.
-	Encrypt(plaintext []byte, additionalData []byte) ([]byte, error)
+	// authenticity and integrity of additional data additionalData,
+	// but there are no guarantees wrt. secrecy of that data.
+	Encrypt(plaintext, additionalData []byte) ([]byte, error)
 
-	// Decrypt decrypts {@code ciphertext} with {@code additionalData} as additional
+	// Decrypt decrypts ciphertext with {@code additionalData} as additional
 	// authenticated data. The decryption verifies the authenticity and integrity
 	// of the additional data, but there are no guarantees wrt. secrecy of that data.
-	Decrypt(ciphertext []byte, additionalData []byte) ([]byte, error)
+	Decrypt(ciphertext, additionalData []byte) ([]byte, error)
 }
diff --git a/go/tink/cleartext_keyset_handle.go b/go/tink/cleartext_keyset_handle.go
deleted file mode 100644
index a889e5c..0000000
--- a/go/tink/cleartext_keyset_handle.go
+++ /dev/null
@@ -1,73 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package tink
-
-import (
-	"fmt"
-	"sync"
-
-	"github.com/golang/protobuf/proto"
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
-)
-
-// cleartextKeysetHandle provides utilities to creates keyset handles from
-// cleartext keysets. This API allows loading cleartext keysets, thus its usage
-// should be restricted.
-var ckhInstance *cleartextKeysetHandle
-var cleartextKeysetHandleOnce sync.Once
-
-type cleartextKeysetHandle struct{}
-
-// CleartextKeysetHandle returns the single instance of cleartextKeysetHandle.
-func CleartextKeysetHandle() *cleartextKeysetHandle {
-	cleartextKeysetHandleOnce.Do(func() {
-		ckhInstance = new(cleartextKeysetHandle)
-	})
-	return ckhInstance
-}
-
-var errInvalidKeyset = fmt.Errorf("cleartext_keyset_handle: invalid keyset")
-
-// ParseSerializedKeyset creates a new keyset handle from the given serialized keyset.
-func (kh *cleartextKeysetHandle) ParseSerializedKeyset(serialized []byte) (*KeysetHandle, error) {
-	if len(serialized) == 0 {
-		return nil, errInvalidKeyset
-	}
-	keyset := new(tinkpb.Keyset)
-	if err := proto.Unmarshal(serialized, keyset); err != nil {
-		return nil, errInvalidKeyset
-	}
-	return newKeysetHandle(keyset, nil)
-}
-
-// ParseKeyset creates a new keyset handle from the given keyset.
-func (kh *cleartextKeysetHandle) ParseKeyset(keyset *tinkpb.Keyset) (*KeysetHandle, error) {
-	return newKeysetHandle(keyset, nil)
-}
-
-// GenerateNew creates a keyset handle that contains a single fresh key generated
-// according to the given key template.
-func (kh *cleartextKeysetHandle) GenerateNew(template *tinkpb.KeyTemplate) (*KeysetHandle, error) {
-	manager := NewKeysetManager(template, nil, nil)
-	err := manager.Rotate()
-	if err != nil {
-		return nil, fmt.Errorf("cleartext_keyset_handle: cannot rotate keyset manager: %s", err)
-	}
-	handle, err := manager.GetKeysetHandle()
-	if err != nil {
-		return nil, fmt.Errorf("cleartext_keyset_handle: cannot get keyset handle: %s", err)
-	}
-	return handle, nil
-}
diff --git a/go/tink/cleartext_keyset_handle_test.go b/go/tink/cleartext_keyset_handle_test.go
deleted file mode 100644
index 30fc90c..0000000
--- a/go/tink/cleartext_keyset_handle_test.go
+++ /dev/null
@@ -1,132 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package tink_test
-
-import (
-	"fmt"
-	"testing"
-
-	"github.com/golang/protobuf/proto"
-	"github.com/google/tink/go/mac"
-	"github.com/google/tink/go/testutil"
-	"github.com/google/tink/go/tink"
-)
-
-func setupCleartextKeysetHandleTest() {
-	if _, err := mac.RegisterStandardKeyTypes(); err != nil {
-		panic(fmt.Sprintf("cannot register mac key types: %s", err))
-	}
-}
-
-func TestCleartextKeysetHandleParseBasic(t *testing.T) {
-	setupCleartextKeysetHandleTest()
-
-	// Create a keyset that contains a single HmacKey.
-	manager := testutil.NewHmacKeysetManager()
-	handle, err := manager.GetKeysetHandle()
-	if handle == nil || err != nil {
-		t.Errorf("cannot get keyset handle: %s", err)
-	}
-	serializedKeyset, err := proto.Marshal(handle.Keyset())
-	if err != nil {
-		t.Errorf("cannot serialize keyset: %s", err)
-	}
-	// create handle rom serialized keyset
-	parsedHandle, err := tink.CleartextKeysetHandle().ParseSerializedKeyset(serializedKeyset)
-	if err != nil {
-		t.Errorf("unexpected error: %s", err)
-	}
-	if handle.Keyset().String() != parsedHandle.Keyset().String() {
-		t.Errorf("parsed keyset doesn't match the original")
-	}
-	// create handle from keyset
-	parsedHandle, err = tink.CleartextKeysetHandle().ParseKeyset(handle.Keyset())
-	if err != nil {
-		t.Errorf("unexpected error: %s", err)
-	}
-	if handle.Keyset().String() != parsedHandle.Keyset().String() {
-		t.Errorf("parsed keyset doesn't match the original")
-	}
-}
-
-func TestCleartextKeysetHandleParseWithInvalidInput(t *testing.T) {
-	setupCleartextKeysetHandleTest()
-
-	manager := testutil.NewHmacKeysetManager()
-	handle, err := manager.GetKeysetHandle()
-	if handle == nil || err != nil {
-		t.Errorf("cannot get keyset handle: %s", err)
-	}
-	serializedKeyset, err := proto.Marshal(handle.Keyset())
-	if err != nil {
-		t.Errorf("cannot serialize keyset: %s", err)
-	}
-	serializedKeyset[0] = 0
-	_, err = tink.CleartextKeysetHandle().ParseSerializedKeyset(serializedKeyset)
-	if err == nil {
-		t.Errorf("expect an error when input is an invalid serialized keyset")
-	}
-	_, err = tink.CleartextKeysetHandle().ParseSerializedKeyset([]byte{})
-	if err == nil {
-		t.Errorf("expect an error when input is an empty slice")
-	}
-	_, err = tink.CleartextKeysetHandle().ParseSerializedKeyset(nil)
-	if err == nil {
-		t.Errorf("expect an error when input is nil")
-	}
-	_, err = tink.CleartextKeysetHandle().ParseKeyset(nil)
-	if err == nil {
-		t.Errorf("expect an error when input is nil")
-	}
-}
-
-func TestCleartextKeysetHandleGenerateNewBasic(t *testing.T) {
-	setupCleartextKeysetHandleTest()
-
-	macTemplate := mac.HmacSha256Tag128KeyTemplate()
-	handle, err := tink.CleartextKeysetHandle().GenerateNew(macTemplate)
-	if err != nil {
-		t.Errorf("unexpected error: %s", err)
-	}
-	keyset := handle.Keyset()
-	if len(keyset.Key) != 1 {
-		t.Errorf("incorrect number of keys in the keyset: %d", len(keyset.Key))
-	}
-	key := keyset.Key[0]
-	if keyset.PrimaryKeyId != key.KeyId {
-		t.Errorf("incorrect primary key id, expect %d, got %d", key.KeyId, keyset.PrimaryKeyId)
-	}
-	if key.KeyData.TypeUrl != macTemplate.TypeUrl {
-		t.Errorf("incorrect type url, expect %s, got %s", macTemplate.TypeUrl, key.KeyData.TypeUrl)
-	}
-	if _, err = mac.GetPrimitive(handle); err != nil {
-		t.Errorf("cannot get primitive from generated keyset handle: %s", err)
-	}
-}
-
-func TestCleartextKeysetHandleGenerateNewWithInvalidInput(t *testing.T) {
-	setupCleartextKeysetHandleTest()
-
-	// template unregistered TypeUrl
-	template := mac.HmacSha256Tag128KeyTemplate()
-	template.TypeUrl = "some unknown TypeUrl"
-	if _, err := tink.CleartextKeysetHandle().GenerateNew(template); err == nil {
-		t.Errorf("expect an error when TypeUrl is not registered")
-	}
-	// nil
-	if _, err := tink.CleartextKeysetHandle().GenerateNew(nil); err == nil {
-		t.Errorf("expect an error when template is nil")
-	}
-}
diff --git a/go/tink/deterministic_aead.go b/go/tink/deterministic_aead.go
new file mode 100644
index 0000000..cb1d6a1
--- /dev/null
+++ b/go/tink/deterministic_aead.go
@@ -0,0 +1,47 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package tink
+
+/*
+DeterministicAEAD is the interface for deterministic authenticated encryption with associated data.
+
+Warning:
+Unlike AEAD, implementations of this interface are not semantically secure, because
+encrypting the same plaintex always yields the same ciphertext.
+
+Security guarantees:
+Implementations of this interface provide 128-bit security level against multi-user attacks
+with up to 2^32 keys. That means if an adversary obtains 2^32 ciphertexts of the same message
+encrypted under 2^32 keys, they need to do 2^128 computations to obtain a single key.
+
+Encryption with associated data ensures authenticity (who the sender is) and integrity (the
+data has not been tampered with) of that data, but not its secrecy.
+
+References:
+https://tools.ietf.org/html/rfc5116
+https://tools.ietf.org/html/rfc5297#section-1.3
+*/
+type DeterministicAEAD interface {
+	// EncryptDeterministically deterministically encrypts plaintext with additionalData as
+	// additional authenticated data. The resulting ciphertext allows for checking
+	// authenticity and integrity of additional data additionalData,
+	// but there are no guarantees wrt. secrecy of that data.
+	EncryptDeterministically(plaintext, additionalData []byte) ([]byte, error)
+
+	// DecryptDeterministically deterministically decrypts ciphertext with additionalData as
+	// additional authenticated data. The decryption verifies the authenticity and integrity
+	// of the additional data, but there are no guarantees wrt. secrecy of that data.
+	DecryptDeterministically(ciphertext, additionalData []byte) ([]byte, error)
+}
diff --git a/go/tink/hybrid_decrypt.go b/go/tink/hybrid_decrypt.go
new file mode 100644
index 0000000..4012100
--- /dev/null
+++ b/go/tink/hybrid_decrypt.go
@@ -0,0 +1,66 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package tink
+
+/*
+HybridDecrypt Interface for hybrid decryption.
+
+Hybrid Encryption combines the efficiency of symmetric encryption with the convenience of
+public-key encryption: to encrypt a message a fresh symmetric key is generated and used to
+encrypt the actual plaintext data, while the recipient’s public key is used to encrypt the
+symmetric key only, and the final ciphertext consists of the symmetric ciphertext and the
+encrypted symmetric key.
+
+WARNING
+
+Hybrid Encryption does not provide authenticity, that is the recipient of an encrypted message
+does not know the identity of the sender. Similar to general public-key encryption schemes the
+security goal of Hybrid Encryption is to provide privacy only. In other words, Hybrid Encryption
+is secure if and only if the recipient can accept anonymous messages or can rely on other
+mechanisms to authenticate the sender.
+
+Security guarantees
+
+The functionality of Hybrid Encryption is represented as a pair of primitives (interfaces):
+HybridEncrypt for encryption of data, and HybridDecrypt for decryption.
+Implementations of these interfaces are secure against adaptive chosen ciphertext attacks. In
+addition to plaintext the encryption takes an extra parameter contextInfo, which
+usually is public data implicit from the context, but should be bound to the resulting
+ciphertext, i.e. the ciphertext allows for checking the integrity of contextInfo (but
+there are no guarantees wrt. the secrecy or authenticity of contextInfo).
+
+contextInfo can be empty or null, but to ensure the correct decryption of a ciphertext
+the same value must be provided for the decryption operation as was used during encryption
+(HybridEncrypt).
+
+A concrete instantiation of this interface can implement the binding of contextInfo to
+the ciphertext in various ways, for example:
+
+
+  use contextInfo as "associated data"-input for the employed AEAD symmetric
+      encryption (cf. https://tools.ietf.org/html/rfc5116).
+  use contextInfo as "CtxInfo"-input for HKDF (if the implementation uses HKDF as key
+      derivation function, cf. https://tools.ietf.org/html/rfc5869).
+
+*/
+type HybridDecrypt interface {
+	/**
+	 * Decrypt operation: decrypts ciphertext verifying the integrity of contextInfo.
+	 * returns resulting plaintext
+	 */
+	Decrypt(ciphertext, contextInfo []byte) ([]byte, error)
+}
diff --git a/go/tink/hybrid_encrypt.go b/go/tink/hybrid_encrypt.go
new file mode 100644
index 0000000..4e1d87c
--- /dev/null
+++ b/go/tink/hybrid_encrypt.go
@@ -0,0 +1,63 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package tink
+
+/*
+HybridEncrypt is the Interface for hybrid encryption.
+
+Hybrid Encryption combines the efficiency of symmetric encryption with the convenience of
+public-key encryption: to encrypt a message a fresh symmetric key is generated and used to
+encrypt the actual plaintext data, while the recipient’s public key is used to encrypt the
+symmetric key only, and the final ciphertext consists of the symmetric ciphertext and the
+encrypted symmetric key.
+
+WARNING
+
+Hybrid Encryption does not provide authenticity, that is the recipient of an encrypted message
+does not know the identity of the sender. Similar to general public-key encryption schemes the
+security goal of Hybrid Encryption is to provide privacy only. In other words, Hybrid Encryption
+is secure if and only if the recipient can accept anonymous messages or can rely on other
+mechanisms to authenticate the sender.
+
+Security guarantees
+
+The functionality of Hybrid Encryption is represented as a pair of primitives (interfaces):
+HybridEncrypt for encryption of data, and HybridDecrypt for decryption.
+Implementations of these interfaces are secure against adaptive chosen ciphertext attacks. In
+addition to plaintext the encryption takes an extra parameter contextInfo, which
+usually is public data implicit from the context, but should be bound to the resulting
+ciphertext, i.e. the ciphertext allows for checking the integrity of contextInfo (but
+there are no guarantees wrt. the secrecy or authenticity of contextInfo).
+
+contextInfo can be empty or null, but to ensure the correct decryption of a ciphertext
+the same value must be provided for the decryption operation as was used during encryption (cf.
+HybridEncrypt).
+
+A concrete instantiation of this interface can implement the binding of contextInfo to
+the ciphertext in various ways, for example:
+
+
+  use contextInfo as "associated data"-input for the employed AEAD symmetric
+      encryption (cf. https://tools.ietf.org/html/rfc5116).
+  use contextInfo as "CtxInfo"-input for HKDF (if the implementation uses HKDF as key
+      derivation function, cf. https://tools.ietf.org/html/rfc5869).
+*/
+type HybridEncrypt interface {
+	// Encrypt operation: encrypts plaintext} binding contextInfo to the resulting
+	// ciphertext. Returns resulting ciphertext
+	Encrypt(plaintext, contextInfo []byte) ([]byte, error)
+}
diff --git a/go/tink/key_manager.go b/go/tink/key_manager.go
deleted file mode 100644
index 9d49aa6..0000000
--- a/go/tink/key_manager.go
+++ /dev/null
@@ -1,55 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package tink
-
-import (
-	"github.com/golang/protobuf/proto"
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
-)
-
-/*
-KeyManager "understands" keys of a specific key types: it can
-generate keys of a supported type and create primitives for
-supported keys.  A key type is identified by the global name of the
-protocol buffer that holds the corresponding key material, and is
-given by type_url-field of KeyData-protocol buffer.
-*/
-type KeyManager interface {
-	// GetPrimitiveFromSerializedKey constructs a primitive instance for the key given in
-	// serializedKey, which must be a serialized key protocol buffer handled by this manager.
-	GetPrimitiveFromSerializedKey(serializedKey []byte) (interface{}, error)
-
-	// GetPrimitiveFromKey constructs a primitive instance for the key given in {@code key}.
-	GetPrimitiveFromKey(key proto.Message) (interface{}, error)
-
-	// NewKeyFromSerializedKeyFormat Generates a new key according to specification in {@code serializedKeyFormat},
-	// which must be a serialized key format protocol buffer handled by this manager.
-	NewKeyFromSerializedKeyFormat(serializedKeyFormat []byte) (proto.Message, error)
-
-	// NewKeyFromKeyFormat generates a new key according to specification in {@code keyFormat}.
-	NewKeyFromKeyFormat(keyFormat proto.Message) (proto.Message, error)
-
-	// DoesSupport returns true iff this KeyManager supports key type identified by {@code typeURL}.
-	DoesSupport(typeURL string) bool
-
-	// GetKeyType returns the type URL that identifes the key type of keys managed by this KeyManager.
-	GetKeyType() string
-
-	// APIs for Key Management
-
-	// NewKeyData generates a new {@code KeyData} according to specification in {@code serializedkeyFormat}.
-	// This should be used solely by the key management API.
-	NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error)
-}
diff --git a/go/tink/keyset_handle.go b/go/tink/keyset_handle.go
deleted file mode 100644
index 1d1a0fb..0000000
--- a/go/tink/keyset_handle.go
+++ /dev/null
@@ -1,109 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package tink
-
-import (
-	"fmt"
-
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
-)
-
-var errKeysetHandleInvalidKeyset = fmt.Errorf("keyset_handle: invalid keyset")
-
-// KeysetHandle provides abstracted access to Keysets, to limit the exposure
-// of actual protocol buffers that hold sensitive key material.
-type KeysetHandle struct {
-	keyset          *tinkpb.Keyset
-	encryptedKeyset *tinkpb.EncryptedKeyset
-}
-
-// newKeysetHandle creates a new instance of KeysetHandle using the given keyset
-// and encrypted keyset. The given keyset must not be nil. Otherwise, an error will
-// be returned.
-func newKeysetHandle(keyset *tinkpb.Keyset,
-	encryptedKeyset *tinkpb.EncryptedKeyset) (*KeysetHandle, error) {
-	if keyset == nil || len(keyset.Key) == 0 {
-		return nil, errKeysetHandleInvalidKeyset
-	}
-	return &KeysetHandle{
-		keyset:          keyset,
-		encryptedKeyset: encryptedKeyset,
-	}, nil
-}
-
-// GetPublicKeysetHandle returns a KeysetHandle of the public keys if the managed
-// keyset contains private keys.
-func (h *KeysetHandle) GetPublicKeysetHandle() (*KeysetHandle, error) {
-	privKeys := h.keyset.Key
-	pubKeys := make([]*tinkpb.Keyset_Key, len(privKeys))
-
-	for i := 0; i < len(privKeys); i++ {
-		if privKeys[i] == nil || privKeys[i].KeyData == nil {
-			return nil, errKeysetHandleInvalidKeyset
-		}
-		privKeyData := privKeys[i].KeyData
-		if privKeyData.KeyMaterialType != tinkpb.KeyData_ASYMMETRIC_PRIVATE {
-			return nil, fmt.Errorf("keyset_handle: keyset contains a non-private key")
-		}
-		pubKeyData, err := getPublicKeyData(privKeyData.TypeUrl, privKeyData.Value)
-		if err != nil {
-			return nil, fmt.Errorf("keyset_handle: %s", err)
-		}
-		if err := h.validateKeyData(pubKeyData); err != nil {
-			return nil, fmt.Errorf("keyset_handle: %s", err)
-		}
-		pubKeys[i] = &tinkpb.Keyset_Key{
-			KeyData:          pubKeyData,
-			Status:           privKeys[i].Status,
-			KeyId:            privKeys[i].KeyId,
-			OutputPrefixType: privKeys[i].OutputPrefixType,
-		}
-	}
-	pubKeyset := &tinkpb.Keyset{
-		PrimaryKeyId: h.keyset.PrimaryKeyId,
-		Key:          pubKeys,
-	}
-	return newKeysetHandle(pubKeyset, nil)
-}
-
-// Keyset returns the Keyset component of this handle.
-func (h *KeysetHandle) Keyset() *tinkpb.Keyset {
-	return h.keyset
-}
-
-// EncryptedKeyset returns the EncryptedKeyset component of this handle.
-func (h *KeysetHandle) EncryptedKeyset() *tinkpb.EncryptedKeyset {
-	return h.encryptedKeyset
-}
-
-// KeysetInfo returns a KeysetInfo of the Keyset of this handle.
-// KeysetInfo doesn't contain actual key material.
-func (h *KeysetHandle) KeysetInfo() (*tinkpb.KeysetInfo, error) {
-	return GetKeysetInfo(h.keyset)
-}
-
-// String returns the string representation of the KeysetInfo.
-func (h *KeysetHandle) String() string {
-	info, err := h.KeysetInfo()
-	if err != nil {
-		return ""
-	}
-	return info.String()
-}
-
-func (h *KeysetHandle) validateKeyData(keyData *tinkpb.KeyData) error {
-	_, err := GetPrimitiveFromKeyData(keyData)
-	return err
-}
diff --git a/go/tink/keyset_handle_test.go b/go/tink/keyset_handle_test.go
deleted file mode 100644
index 9d17c16..0000000
--- a/go/tink/keyset_handle_test.go
+++ /dev/null
@@ -1,59 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package tink
-
-import (
-	"testing"
-
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
-)
-
-func TestNewKeysetHandleBasic(t *testing.T) {
-	keyData := CreateKeyData("some type url", []byte{0}, tinkpb.KeyData_SYMMETRIC)
-	key := CreateKey(keyData, tinkpb.KeyStatusType_ENABLED, 1, tinkpb.OutputPrefixType_TINK)
-	keyset := CreateKeyset(1, []*tinkpb.Keyset_Key{key})
-	keysetInfo, _ := GetKeysetInfo(keyset)
-	encryptedKeyset := CreateEncryptedKeyset([]byte{1}, keysetInfo)
-	h, err := newKeysetHandle(keyset, encryptedKeyset)
-	if err != nil {
-		t.Errorf("unexpected error when creating new KeysetHandle")
-	}
-	// test Keyset()
-	if h.Keyset() != keyset {
-		t.Errorf("Keyset() returns incorrect value")
-	}
-	// test EncryptedKeyset()
-	if h.EncryptedKeyset() != encryptedKeyset {
-		t.Errorf("EncryptedKeyset() returns incorrect value")
-	}
-	// test KeysetInfo()
-	tmp, _ := h.KeysetInfo()
-	if tmp.String() != keysetInfo.String() {
-		t.Errorf("KeysetInfo() returns incorrect value")
-	}
-	// test String()
-	if h.String() != keysetInfo.String() {
-		t.Errorf("String() returns incorrect value")
-	}
-}
-
-func TestNewKeysetHandleWithInvalidInput(t *testing.T) {
-	if _, err := newKeysetHandle(nil, nil); err == nil {
-		t.Errorf("NewKeysetHandle should not accept nil as Keyset")
-	}
-	if _, err := newKeysetHandle(new(tinkpb.Keyset), nil); err == nil {
-		t.Errorf("unexpected error: %s", err)
-	}
-}
diff --git a/go/tink/keyset_manager.go b/go/tink/keyset_manager.go
deleted file mode 100644
index da90b19..0000000
--- a/go/tink/keyset_manager.go
+++ /dev/null
@@ -1,187 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package tink
-
-import (
-	"bytes"
-	"fmt"
-
-	proto "github.com/golang/protobuf/proto"
-	"github.com/google/tink/go/subtle/random"
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
-)
-
-// emptyAad is the additional authenticated data that is used in the encryption
-// and decryption of keysets
-var emptyAad = []byte{}
-
-// KeysetManager manages a Keyset-proto, with convenience methods that rotate,
-// disable, enable or destroy keys.
-// Note: It is not thread-safe.
-type KeysetManager struct {
-	keyTemplate *tinkpb.KeyTemplate
-	masterKey   Aead
-	keyset      *tinkpb.Keyset
-}
-
-// NewKeysetManager creates a new instance of keyset manager.
-func NewKeysetManager(keyTemplate *tinkpb.KeyTemplate,
-	masterKey Aead,
-	keyset *tinkpb.Keyset) *KeysetManager {
-	ret := new(KeysetManager)
-	ret.SetKeyTemplate(keyTemplate)
-	ret.SetMasterKey(masterKey)
-	ret.SetKeyset(keyset)
-	return ret
-}
-
-// Rotate generates a fresh key using the key template of the current keyset manager
-// and sets the new key as the primary key.
-func (km *KeysetManager) Rotate() error {
-	return km.RotateWithTemplate(km.keyTemplate)
-}
-
-// RotateWithTemplate generates a fresh key using the given key template and
-// sets the new key as the primary key.
-func (km *KeysetManager) RotateWithTemplate(keyTemplate *tinkpb.KeyTemplate) error {
-	if keyTemplate == nil {
-		return fmt.Errorf("keyset_manager: cannot rotate, need key template")
-	}
-	keyData, err := NewKeyData(keyTemplate)
-	if err != nil {
-		return fmt.Errorf("keyset_manager: cannot create KeyData: %s", err)
-	}
-	keyID := km.newKeyID()
-	outputPrefixType := keyTemplate.OutputPrefixType
-	if outputPrefixType == tinkpb.OutputPrefixType_UNKNOWN_PREFIX {
-		outputPrefixType = tinkpb.OutputPrefixType_TINK
-	}
-	key := &tinkpb.Keyset_Key{
-		KeyData:          keyData,
-		Status:           tinkpb.KeyStatusType_ENABLED,
-		KeyId:            keyID,
-		OutputPrefixType: outputPrefixType,
-	}
-	// Set the new key as the primary key
-	km.keyset.Key = append(km.keyset.Key, key)
-	km.keyset.PrimaryKeyId = keyID
-	return nil
-}
-
-// GetKeysetHandle creates a new KeysetHandle for the managed keyset.
-func (km *KeysetManager) GetKeysetHandle() (*KeysetHandle, error) {
-	if km.masterKey == nil {
-		return newKeysetHandle(km.keyset, nil)
-	}
-	encryptedKeyset, err := EncryptKeyset(km.keyset, km.masterKey)
-	if err != nil {
-		return nil, err
-	}
-	return newKeysetHandle(km.keyset, encryptedKeyset)
-}
-
-// SetKeyTemplate sets the key template of the manager.
-func (km *KeysetManager) SetKeyTemplate(template *tinkpb.KeyTemplate) {
-	km.keyTemplate = template
-}
-
-// SetMasterKey sets the master key of the manager.
-func (km *KeysetManager) SetMasterKey(masterKey Aead) {
-	km.masterKey = masterKey
-}
-
-// SetKeyset sets the keyset of the manager. If the input is nil, it will use
-// an empty keyset as the input instead.
-func (km *KeysetManager) SetKeyset(keyset *tinkpb.Keyset) {
-	if keyset == nil {
-		km.keyset = new(tinkpb.Keyset)
-	} else {
-		km.keyset = keyset
-	}
-}
-
-// KeyTemplate returns the key template of the manager.
-func (km *KeysetManager) KeyTemplate() *tinkpb.KeyTemplate {
-	return km.keyTemplate
-}
-
-// MasterKey returns the master key of the manager.
-func (km *KeysetManager) MasterKey() Aead {
-	return km.masterKey
-}
-
-// Keyset returns the keyset of the manager.
-func (km *KeysetManager) Keyset() *tinkpb.Keyset {
-	return km.keyset
-}
-
-// newKeyID generates a key id that has not been used by any key in the keyset.
-func (km *KeysetManager) newKeyID() uint32 {
-	for {
-		ret := random.GetRandomUint32()
-		ok := true
-		for _, key := range km.keyset.Key {
-			if key.KeyId == ret {
-				ok = false
-				break
-			}
-		}
-		if ok {
-			return ret
-		}
-	}
-}
-
-// EncryptKeyset encrypts the given keyset using the given master key.
-func EncryptKeyset(keyset *tinkpb.Keyset,
-	masterKey Aead) (*tinkpb.EncryptedKeyset, error) {
-	serializedKeyset, err := proto.Marshal(keyset)
-	if err != nil {
-		return nil, fmt.Errorf("keyset_manager: invalid keyset")
-	}
-	encrypted, err := masterKey.Encrypt(serializedKeyset, emptyAad)
-	if err != nil {
-		return nil, fmt.Errorf("keyset_manager: encrypted failed: %s", err)
-	}
-	// check if we can decrypt, to detect errors
-	decrypted, err := masterKey.Decrypt(encrypted, emptyAad)
-	if err != nil || !bytes.Equal(decrypted, serializedKeyset) {
-		return nil, fmt.Errorf("keyset_manager: encryption failed: %s", err)
-	}
-	// get keyset info
-	info, err := GetKeysetInfo(keyset)
-	if err != nil {
-		return nil, fmt.Errorf("keyset_manager: cannot get keyset info: %s", err)
-	}
-	encryptedKeyset := &tinkpb.EncryptedKeyset{
-		EncryptedKeyset: encrypted,
-		KeysetInfo:      info,
-	}
-	return encryptedKeyset, nil
-}
-
-// DecryptKeyset decrypts the given keyset using the given master key
-func DecryptKeyset(encryptedKeyset *tinkpb.EncryptedKeyset,
-	masterKey Aead) (*tinkpb.Keyset, error) {
-	decrypted, err := masterKey.Decrypt(encryptedKeyset.EncryptedKeyset, []byte{})
-	if err != nil {
-		return nil, fmt.Errorf("keyset_manager: decryption failed: %s", err)
-	}
-	keyset := new(tinkpb.Keyset)
-	if err := proto.Unmarshal(decrypted, keyset); err != nil {
-		return nil, fmt.Errorf("keyset_manager: invalid encrypted keyset")
-	}
-	return keyset, nil
-}
diff --git a/go/tink/keyset_manager_test.go b/go/tink/keyset_manager_test.go
deleted file mode 100644
index 89ff651..0000000
--- a/go/tink/keyset_manager_test.go
+++ /dev/null
@@ -1,153 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package tink_test
-
-import (
-	"fmt"
-	"strings"
-	"testing"
-
-	"github.com/google/tink/go/aead"
-	"github.com/google/tink/go/mac"
-	"github.com/google/tink/go/testutil"
-	"github.com/google/tink/go/tink"
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
-)
-
-func setupKeysetManagerTest() {
-	_, err := mac.RegisterStandardKeyTypes()
-	if err != nil {
-		panic(fmt.Sprintf("cannot register mac key types: %s", err))
-	}
-	_, err = aead.RegisterStandardKeyTypes()
-	if err != nil {
-		panic(fmt.Sprintf("cannot register aead key types: %s", err))
-	}
-}
-
-func TestKeysetManagerBasic(t *testing.T) {
-	setupKeysetManagerTest()
-
-	manager := tink.NewKeysetManager(nil, nil, nil)
-	err := manager.Rotate()
-	if err == nil || !strings.Contains(err.Error(), "need key template") {
-		t.Errorf("expect an error when key template is nil")
-	}
-	// Create a keyset that contains a single HmacKey.
-	template := mac.HmacSha256Tag128KeyTemplate()
-	manager = tink.NewKeysetManager(template, nil, nil)
-	err = manager.Rotate()
-	if err != nil {
-		t.Errorf("cannot rotate when key template is available: %s", err)
-	}
-	keyset := manager.Keyset()
-	if len(keyset.Key) != 1 {
-		t.Errorf("expect the number of keys in the keyset is 1")
-	}
-	if keyset.Key[0].KeyId != keyset.PrimaryKeyId ||
-		keyset.Key[0].KeyData.TypeUrl != mac.HmacTypeURL ||
-		keyset.Key[0].Status != tinkpb.KeyStatusType_ENABLED ||
-		keyset.Key[0].OutputPrefixType != tinkpb.OutputPrefixType_TINK {
-		t.Errorf("incorrect key information: %s", keyset.Key[0])
-	}
-}
-
-func TestEncryptedKeyset(t *testing.T) {
-	setupKeysetManagerTest()
-	macTemplate := mac.HmacSha256Tag128KeyTemplate()
-	aesTemplate := aead.Aes128GcmKeyTemplate()
-	keyData, err := tink.NewKeyData(aesTemplate)
-	if err != nil {
-		t.Errorf("cannot create new key data: %s", err)
-	}
-	p, err := tink.GetPrimitiveFromKeyData(keyData)
-	if p == nil || err != nil {
-		t.Errorf("cannot get primitive from key data: %s", err)
-	}
-	masterKey := p.(tink.Aead)
-	manager := tink.NewKeysetManager(macTemplate, masterKey, nil)
-	err = manager.Rotate()
-	if err != nil {
-		t.Errorf("cannot rotate when key template is available: %s", err)
-	}
-	handle, err := manager.GetKeysetHandle()
-	if handle == nil || err != nil {
-		t.Errorf("cannot get keyset handle: %s", err)
-	}
-	info, err := handle.KeysetInfo()
-	if info == nil || err != nil {
-		t.Errorf("cannot get keyset info: %s", err)
-	}
-	if len(info.KeyInfo) != 1 {
-		t.Errorf("incorrect number of keys: %v", len(info.KeyInfo))
-	}
-	if info.PrimaryKeyId != info.KeyInfo[0].KeyId {
-		t.Errorf("incorrect primary key id: %d >< %d", info.PrimaryKeyId, info.KeyInfo[0].KeyId)
-	}
-	if info.KeyInfo[0].TypeUrl != mac.HmacTypeURL ||
-		info.KeyInfo[0].Status != tinkpb.KeyStatusType_ENABLED ||
-		info.KeyInfo[0].OutputPrefixType != tinkpb.OutputPrefixType_TINK {
-		t.Errorf("incorrect key info: %s", info.KeyInfo[0])
-	}
-}
-
-func TestExistingKeyset(t *testing.T) {
-	// Create a keyset that contains a single HmacKey.
-	macTemplate := mac.HmacSha256Tag128KeyTemplate()
-	manager1 := tink.NewKeysetManager(macTemplate, nil, nil)
-	err := manager1.Rotate()
-	if err != nil {
-		t.Errorf("cannot rotate when key template is available: %s", err)
-	}
-	handle1, err := manager1.GetKeysetHandle()
-	if err != nil {
-		t.Errorf("cannot get keyset handle: %s", err)
-	}
-	keyset1 := handle1.Keyset()
-
-	manager2 := tink.NewKeysetManager(nil, nil, keyset1)
-	manager2.RotateWithTemplate(macTemplate)
-	handle2, err := manager2.GetKeysetHandle()
-	if err != nil {
-		t.Errorf("cannot get keyset handle: %s", err)
-	}
-	keyset2 := handle2.Keyset()
-	if len(keyset2.Key) != 2 {
-		t.Errorf("expect the number of keys to be 2, got %d", len(keyset2.Key))
-	}
-	if keyset1.Key[0].String() != keyset2.Key[0].String() {
-		t.Errorf("expect the first key in two keysets to be the same")
-	}
-	if keyset2.Key[1].KeyId != keyset2.PrimaryKeyId {
-		t.Errorf("expect the second key to be primary")
-	}
-}
-
-/**
- * Tests that when encryption with KMS failed, an exception is thrown.
- */
-func TestFaultyKms(t *testing.T) {
-	var masterKey tink.Aead = new(testutil.DummyAead)
-	template := mac.HmacSha256Tag128KeyTemplate()
-	manager := tink.NewKeysetManager(template, masterKey, nil)
-	err := manager.Rotate()
-	if err != nil {
-		t.Errorf("cannot rotate when key template is available: %s", err)
-	}
-	_, err = manager.GetKeysetHandle()
-	if err == nil || !strings.Contains(err.Error(), "dummy") {
-		t.Errorf("expect an error with dummy aead: %s", err)
-	}
-}
diff --git a/go/tink/mac.go b/go/tink/mac.go
index d51ac10..c63e353 100644
--- a/go/tink/mac.go
+++ b/go/tink/mac.go
@@ -1,28 +1,30 @@
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
-
+//
 //      http://www.apache.org/licenses/LICENSE-2.0
-
+//
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+//
 ////////////////////////////////////////////////////////////////////////////////
 
 package tink
 
 /*
-Mac is the interface for MACs (Message Authentication Codes).
+MAC is the interface for MACs (Message Authentication Codes).
 This interface should be used for authentication only, and not for other purposes
 (for example, it should not be used to generate pseudorandom bytes).
 */
-type Mac interface {
+type MAC interface {
 
-	// ComputeMac Computes message authentication code (MAC) for {@code data}.
-	ComputeMac(data []byte) ([]byte, error)
+	// ComputeMAC computes message authentication code (MAC) for code data.
+	ComputeMAC(data []byte) ([]byte, error)
 
-	// VerifyMac verifies whether {@code mac} is a correct authentication code (MAC) for {@code data}.
-	VerifyMac(mac []byte, data []byte) (bool, error)
+	// Verify returns nil if mac is a correct authentication code (MAC) for data,
+	// otherwise it returns an error.
+	VerifyMAC(mac, data []byte) error
 }
diff --git a/go/tink/primitive_set.go b/go/tink/primitive_set.go
deleted file mode 100644
index e60d06b..0000000
--- a/go/tink/primitive_set.go
+++ /dev/null
@@ -1,161 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package tink
-
-import (
-	"fmt"
-
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
-)
-
-// Entry represents a single entry in the keyset. In addition to the actual primitive,
-// it holds the identifier and status of the primitive.
-type Entry struct {
-	primitive        interface{}
-	identifier       string
-	status           tinkpb.KeyStatusType
-	outputPrefixType tinkpb.OutputPrefixType
-}
-
-// NewEntry creates a new instance of Entry using the given information.
-func NewEntry(p interface{}, id string, stt tinkpb.KeyStatusType,
-	outputPrefixType tinkpb.OutputPrefixType) *Entry {
-	return &Entry{
-		primitive:        p,
-		identifier:       id,
-		status:           stt,
-		outputPrefixType: outputPrefixType,
-	}
-}
-
-// Primitive returns the crypto primitive associated with the key entry.
-func (e *Entry) Primitive() interface{} {
-	return e.primitive
-}
-
-// Status returns the status of the key entry.
-func (e *Entry) Status() tinkpb.KeyStatusType {
-	return e.status
-}
-
-// Identifier returns the identifier of the key entry.
-func (e *Entry) Identifier() string {
-	return e.identifier
-}
-
-// OutputPrefixType returns the OutputPrefixType of the key entry.
-func (e *Entry) OutputPrefixType() tinkpb.OutputPrefixType {
-	return e.outputPrefixType
-}
-
-/*
-PrimitiveSet is a container class for a set of primitives (i.e. implementations of cryptographic
-primitives offered by Tink).  It provides also additional properties for the primitives
-it holds.  In particular, one of the primitives in the set can be distinguished as
-"the primary" one. <p>
-
-PrimitiveSet is an auxiliary class used for supporting key rotation: primitives in a set
-correspond to keys in a keyset.  Users will usually work with primitive instances,
-which essentially wrap primitive sets.  For example an instance of an Aead-primitive
-for a given keyset holds a set of Aead-primitives corresponding to the keys in the keyset,
-and uses the set members to do the actual crypto operations: to encrypt data the primary
-Aead-primitive from the set is used, and upon decryption the ciphertext's prefix
-determines the id of the primitive from the set. <p>
-
-PrimitiveSet is a public class to allow its use in implementations of custom primitives.
-*/
-type PrimitiveSet struct {
-	// Primary entry.
-	primary *Entry
-
-	// The primitives are stored in a map of
-	// (ciphertext prefix, list of primitives sharing the prefix).
-	// This allows quickly retrieving the primitives sharing some particular prefix.
-	// Because all RAW keys are using an empty prefix, this also quickly allows retrieving them.
-	primitives map[string][]*Entry
-}
-
-// NewPrimitiveSet returns an empty instance of PrimitiveSet.
-func NewPrimitiveSet() *PrimitiveSet {
-	return &PrimitiveSet{
-		primary:    nil,
-		primitives: make(map[string][]*Entry),
-	}
-}
-
-// GetRawPrimitives returns all primitives in the set that have RAW prefix.
-func (ps *PrimitiveSet) GetRawPrimitives() ([]*Entry, error) {
-	return ps.GetPrimitivesWithStringIdentifier(RawPrefix)
-}
-
-// GetPrimitivesWithKey returns all primitives in the set that have prefix equal
-// to that of the given key.
-func (ps *PrimitiveSet) GetPrimitivesWithKey(key *tinkpb.Keyset_Key) ([]*Entry, error) {
-	if key == nil {
-		return nil, fmt.Errorf("primitive_set: key must not be nil")
-	}
-	id, err := GetOutputPrefix(key)
-	if err != nil {
-		return nil, fmt.Errorf("primitive_set: %s", err)
-	}
-	return ps.GetPrimitivesWithStringIdentifier(id)
-}
-
-// GetPrimitivesWithByteIdentifier returns all primitives in the set that have
-// the given prefix.
-func (ps *PrimitiveSet) GetPrimitivesWithByteIdentifier(id []byte) ([]*Entry, error) {
-	return ps.GetPrimitivesWithStringIdentifier(string(id))
-}
-
-// GetPrimitivesWithStringIdentifier returns all primitives in the set that have
-// the given prefix.
-func (ps *PrimitiveSet) GetPrimitivesWithStringIdentifier(id string) ([]*Entry, error) {
-	result, found := ps.primitives[id]
-	if !found {
-		return []*Entry{}, nil
-	}
-	return result, nil
-}
-
-// Primitives returns all primitives of the set.
-func (ps *PrimitiveSet) Primitives() map[string][]*Entry {
-	return ps.primitives
-}
-
-// Primary returns the entry with the primary primitive.
-func (ps *PrimitiveSet) Primary() *Entry {
-	return ps.primary
-}
-
-// SetPrimary sets the primary entry of the set to the given entry.
-func (ps *PrimitiveSet) SetPrimary(e *Entry) {
-	ps.primary = e
-}
-
-// AddPrimitive creates a new entry in the primitive set using the given information
-// and returns the added entry.
-func (ps *PrimitiveSet) AddPrimitive(primitive interface{},
-	key *tinkpb.Keyset_Key) (*Entry, error) {
-	if key == nil || primitive == nil {
-		return nil, fmt.Errorf("primitive_set: key and primitive must not be nil")
-	}
-	id, err := GetOutputPrefix(key)
-	if err != nil {
-		return nil, fmt.Errorf("primitive_set: %s", err)
-	}
-	e := NewEntry(primitive, id, key.Status, key.OutputPrefixType)
-	ps.primitives[id] = append(ps.primitives[id], e)
-	return e, nil
-}
diff --git a/go/tink/public_key_verify.go b/go/tink/public_key_verify.go
deleted file mode 100644
index 82e0a1b..0000000
--- a/go/tink/public_key_verify.go
+++ /dev/null
@@ -1,30 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package tink
-
-/*
-PublicKeyVerify is the verifying interface for digital signature.
-Implementations of this interface are secure against adaptive chosen-message attacks.
-Signing data ensures authenticity and integrity of that data, but not its secrecy.
-*/
-type PublicKeyVerify interface {
-	/**
-	 * Verifies whether {@code signature} is a valid signature for {@code data}.
-	 *
-	 * @return an error if {@code signature} is not a valid signature for
-	 * {@code data}; nil otherwise.
-	 */
-	Verify(signature []byte, data []byte) error
-}
diff --git a/go/tink/registry.go b/go/tink/registry.go
deleted file mode 100644
index 66b5dbc..0000000
--- a/go/tink/registry.go
+++ /dev/null
@@ -1,226 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package tink
-
-import (
-	"fmt"
-	"sync"
-
-	"github.com/golang/protobuf/proto"
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
-)
-
-var (
-	keyManagers = newKeyManagerMap()
-)
-
-// Mapping between typeURL and KeyManager.
-// Using mutex for concurrency read and write
-type keyManagerMap struct {
-	sync.RWMutex
-	m map[string]KeyManager
-}
-
-// NewKeyManagerMap creates a new instance of keyManagerMap.
-func newKeyManagerMap() *keyManagerMap {
-	kmMap := new(keyManagerMap)
-	kmMap.m = make(map[string]KeyManager)
-	return kmMap
-}
-
-// Get returns whether the specified typeURL exists in the map and
-// the corresponding value if it exists.
-func (kmMap *keyManagerMap) Get(typeURL string) (KeyManager, bool) {
-	kmMap.RLock()
-	defer kmMap.RUnlock()
-	km, existed := kmMap.m[typeURL]
-	return km, existed
-}
-
-// Put associates the given keyManager with the given typeURL in the map.
-func (kmMap *keyManagerMap) Put(typeURL string, keyManager KeyManager) {
-	kmMap.Lock()
-	defer kmMap.Unlock()
-	kmMap.m[typeURL] = keyManager
-}
-
-// RegisterKeyManager registers the given key manager.
-// It does nothing if there already exists a key manager with the same type url.
-// It returns true if the key manager is registered; false otherwise.
-func RegisterKeyManager(manager KeyManager) (bool, error) {
-	if manager == nil {
-		return false, fmt.Errorf("registry: invalid key manager")
-	}
-	typeURL := manager.GetKeyType()
-	// try to get the key manager with the given typeURL, return false if there is
-	_, existed := keyManagers.Get(typeURL)
-	if existed {
-		return false, nil
-	}
-	// add the manager
-	keyManagers.Put(typeURL, manager)
-	return true, nil
-}
-
-// GetKeyManager returns the key manager for the given type url if existed.
-func GetKeyManager(typeURL string) (KeyManager, error) {
-	manager, existed := keyManagers.Get(typeURL)
-	if !existed {
-		return nil, fmt.Errorf("registry: unsupported key type: %s", typeURL)
-	}
-	return manager, nil
-}
-
-// NewKeyData generates a new KeyData for the given KeyTemplate.
-func NewKeyData(template *tinkpb.KeyTemplate) (*tinkpb.KeyData, error) {
-	if template == nil {
-		return nil, fmt.Errorf("registry: invalid key template")
-	}
-	manager, err := GetKeyManager(template.TypeUrl)
-	if err != nil {
-		return nil, err
-	}
-	return manager.NewKeyData(template.Value)
-}
-
-// NewKeyFromKeyTemplate generates a new key for the given KeyTemplate.
-func NewKeyFromKeyTemplate(template *tinkpb.KeyTemplate) (proto.Message, error) {
-	if template == nil {
-		return nil, fmt.Errorf("registry: invalid key template")
-	}
-	manager, err := GetKeyManager(template.TypeUrl)
-	if err != nil {
-		return nil, err
-	}
-	return manager.NewKeyFromSerializedKeyFormat(template.Value)
-}
-
-// NewKeyFromKeyFormat generates a new key for the given KeyFormat using the
-// KeyManager identified by the given typeURL.
-func NewKeyFromKeyFormat(typeURL string,
-	format proto.Message) (proto.Message, error) {
-	manager, err := GetKeyManager(typeURL)
-	if err != nil {
-		return nil, err
-	}
-	return manager.NewKeyFromKeyFormat(format)
-}
-
-// GetPrimitiveFromKey creates a new primitive for the given key using the KeyManager
-// identified by the given typeURL.
-func GetPrimitiveFromKey(typeURL string,
-	key proto.Message) (interface{}, error) {
-	manager, err := GetKeyManager(typeURL)
-	if err != nil {
-		return nil, err
-	}
-	return manager.GetPrimitiveFromKey(key)
-}
-
-// GetPrimitiveFromKeyData creates a new primitive for the key given in the given KeyData.
-func GetPrimitiveFromKeyData(keyData *tinkpb.KeyData) (interface{}, error) {
-	if keyData == nil {
-		return nil, fmt.Errorf("registry: invalid key data")
-	}
-	return GetPrimitiveFromSerializedKey(keyData.TypeUrl, keyData.Value)
-}
-
-// GetPrimitiveFromSerializedKey creates a new primitive for the given serialized key
-// using the KeyManager identified by the given typeURL.
-func GetPrimitiveFromSerializedKey(typeURL string,
-	serializedKey []byte) (interface{}, error) {
-	if len(serializedKey) == 0 {
-		return nil, fmt.Errorf("registry: invalid serialized key")
-	}
-	manager, err := GetKeyManager(typeURL)
-	if err != nil {
-		return nil, err
-	}
-	return manager.GetPrimitiveFromSerializedKey(serializedKey)
-}
-
-// GetPrimitives creates a set of primitives corresponding to the keys with
-// status=ENABLED in the keyset of the given keysetHandle, assuming all the
-// corresponding key managers are present (keys with status!=ENABLED are skipped).
-//
-// The returned set is usually later "wrapped" into a class that implements
-// the corresponding Primitive-interface.
-func GetPrimitives(keysetHandle *KeysetHandle) (*PrimitiveSet, error) {
-	return GetPrimitivesWithCustomManager(keysetHandle, nil)
-}
-
-// GetPrimitivesWithCustomManager creates a set of primitives corresponding to
-// the keys with status=ENABLED in the keyset of the given keysetHandle, using
-// the given customManager (instead of registered key managers) for keys supported
-// by it.  Keys not supported by the customManager are handled by matching registered
-// key managers (if present), and keys with status!=ENABLED are skipped. <p>
-//
-// This enables custom treatment of keys, for example providing extra context
-// (e.g. credentials for accessing keys managed by a KMS), or gathering custom
-// monitoring/profiling information.
-//
-// The returned set is usually later "wrapped" into a class that implements
-// the corresponding Primitive-interface.
-func GetPrimitivesWithCustomManager(
-	keysetHandle *KeysetHandle, customManager KeyManager) (*PrimitiveSet, error) {
-	// TODO(thaidn): check that all keys are of the same primitive
-	if keysetHandle == nil {
-		return nil, fmt.Errorf("registry: invalid keyset handle")
-	}
-	keyset := keysetHandle.Keyset()
-	if err := ValidateKeyset(keyset); err != nil {
-		return nil, fmt.Errorf("registry: invalid keyset: %s", err)
-	}
-	primitiveSet := NewPrimitiveSet()
-	for _, key := range keyset.Key {
-		if key.Status != tinkpb.KeyStatusType_ENABLED {
-			continue
-		}
-		var primitive interface{}
-		var err error
-		if customManager != nil && customManager.DoesSupport(key.KeyData.TypeUrl) {
-			primitive, err = customManager.GetPrimitiveFromSerializedKey(key.KeyData.Value)
-		} else {
-			primitive, err = GetPrimitiveFromKeyData(key.KeyData)
-		}
-		if err != nil {
-			return nil, fmt.Errorf("registry: cannot get primitive from key: %s", err)
-		}
-		entry, err := primitiveSet.AddPrimitive(primitive, key)
-		if err != nil {
-			return nil, fmt.Errorf("registry: cannot add primitive: %s", err)
-		}
-		if key.KeyId == keyset.PrimaryKeyId {
-			primitiveSet.SetPrimary(entry)
-		}
-	}
-	return primitiveSet, nil
-}
-
-// getPublicKeyData is Convenience method for extracting the public key data
-// from the given serialized private key. It looks up a PrivateKeyManager
-// identified by the given typeURL, and calls the manager's GetPublicKeyData() method.
-func getPublicKeyData(typeURL string,
-	serializedPrivKey []byte) (*tinkpb.KeyData, error) {
-	keyManager, err := GetKeyManager(typeURL)
-	if err != nil {
-		return nil, err
-	}
-	privateKeyManager, ok := keyManager.(PrivateKeyManager)
-	if !ok {
-		return nil, fmt.Errorf("registry: %s is not belong to a PrivateKeyManager", typeURL)
-	}
-	return privateKeyManager.GetPublicKeyData(serializedPrivKey)
-}
diff --git a/go/tink/registry_test.go b/go/tink/registry_test.go
deleted file mode 100644
index 7690a09..0000000
--- a/go/tink/registry_test.go
+++ /dev/null
@@ -1,325 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package tink_test
-
-import (
-	"testing"
-
-	"github.com/golang/protobuf/proto"
-	"github.com/google/tink/go/aead"
-	"github.com/google/tink/go/mac"
-	subtleAead "github.com/google/tink/go/subtle/aead"
-	subtleMac "github.com/google/tink/go/subtle/mac"
-	"github.com/google/tink/go/testutil"
-	"github.com/google/tink/go/tink"
-	gcmpb "github.com/google/tink/proto/aes_gcm_go_proto"
-	commonpb "github.com/google/tink/proto/common_go_proto"
-	hmacpb "github.com/google/tink/proto/hmac_go_proto"
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
-)
-
-func TestBasic(t *testing.T) {
-	// try to put a HmacKeyManager
-	hmacManager := mac.NewHmacKeyManager()
-	typeURL := mac.HmacTypeURL
-	tink.RegisterKeyManager(hmacManager)
-	tmp, existed := tink.GetKeyManager(typeURL)
-	if existed != nil {
-		t.Errorf("a HmacKeyManager should be found")
-	}
-	var _ = tmp.(*mac.HmacKeyManager)
-	// Get type url that doesn't exist
-	if _, existed := tink.GetKeyManager("some url"); existed == nil {
-		t.Errorf("unknown typeURL shouldn't exist in the map")
-	}
-}
-
-func setupRegistryTests() {
-	_, err := mac.RegisterStandardKeyTypes()
-	if err != nil {
-		panic("cannot register Mac key types")
-	}
-	_, err = aead.RegisterStandardKeyTypes()
-	if err != nil {
-		panic("cannot register Aead key types")
-	}
-}
-
-func TestKeyManagerRegistration(t *testing.T) {
-	var km tink.KeyManager
-	var err error
-	// register mac and aead types.
-	setupRegistryTests()
-	// get HmacKeyManager
-	km, err = tink.GetKeyManager(mac.HmacTypeURL)
-	if err != nil {
-		t.Errorf("unexpected error: %s", err)
-	}
-	var _ *mac.HmacKeyManager = km.(*mac.HmacKeyManager)
-	// get AesGcmKeyManager
-	km, err = tink.GetKeyManager(aead.AesGcmTypeURL)
-	if err != nil {
-		t.Errorf("unexpected error: %s", err)
-	}
-	var _ *aead.AesGcmKeyManager = km.(*aead.AesGcmKeyManager)
-	// some random typeurl
-	if _, err = tink.GetKeyManager("some url"); err == nil {
-		t.Errorf("expect an error when a type url doesn't exist in the registry")
-	}
-}
-
-func TestKeyManagerRegistrationWithCollision(t *testing.T) {
-	// register mac and aead types.
-	setupRegistryTests()
-	// dummyKeyManager's typeURL is equal to that of AesGcm
-	var dummyKeyManager tink.KeyManager = new(testutil.DummyAeadKeyManager)
-	// this should not overwrite the existing manager.
-	ok, err := tink.RegisterKeyManager(dummyKeyManager)
-	if ok || err != nil {
-		t.Errorf("AES_GCM_TYPE_URL shouldn't be registered again")
-	}
-	km, err := tink.GetKeyManager(aead.AesGcmTypeURL)
-	if err != nil {
-		t.Errorf("unexpected error: %s", err)
-	}
-	var _ *aead.AesGcmKeyManager = km.(*aead.AesGcmKeyManager)
-}
-
-func TestNewKeyData(t *testing.T) {
-	setupRegistryTests()
-	// new Keydata from a Hmac KeyTemplate
-	keyData, err := tink.NewKeyData(mac.HmacSha256Tag128KeyTemplate())
-	if err != nil {
-		t.Errorf("unexpected error: %s", err)
-	}
-	if keyData.TypeUrl != mac.HmacTypeURL {
-		t.Errorf("invalid key data")
-	}
-	key := new(hmacpb.HmacKey)
-	if err := proto.Unmarshal(keyData.Value, key); err != nil {
-		t.Errorf("unexpected error when unmarshal HmacKey: %s", err)
-	}
-	// nil
-	if _, err := tink.NewKeyData(nil); err == nil {
-		t.Errorf("expect an error when key template is nil")
-	}
-	// unregistered type url
-	template := &tinkpb.KeyTemplate{TypeUrl: "some url", Value: []byte{0}}
-	if _, err := tink.NewKeyData(template); err == nil {
-		t.Errorf("expect an error when key template contains unregistered typeURL")
-	}
-}
-
-func TestNewKeyFromKeyTemplate(t *testing.T) {
-	setupRegistryTests()
-	// aead template
-	aesGcmTemplate := aead.Aes128GcmKeyTemplate()
-	key, err := tink.NewKeyFromKeyTemplate(aesGcmTemplate)
-	if err != nil {
-		t.Errorf("unexpected error: %s", err)
-	}
-	var aesGcmKey *gcmpb.AesGcmKey = key.(*gcmpb.AesGcmKey)
-	aesGcmFormat := new(gcmpb.AesGcmKeyFormat)
-	if err := proto.Unmarshal(aesGcmTemplate.Value, aesGcmFormat); err != nil {
-		t.Errorf("unexpected error: %s", err)
-	}
-	if aesGcmFormat.KeySize != uint32(len(aesGcmKey.KeyValue)) {
-		t.Errorf("key doesn't match template")
-	}
-	//nil
-	if _, err := tink.NewKeyFromKeyTemplate(nil); err == nil {
-		t.Errorf("expect an error when key template is nil")
-	}
-	// unregistered type url
-	template := &tinkpb.KeyTemplate{TypeUrl: "some url", Value: []byte{0}}
-	if _, err := tink.NewKeyFromKeyTemplate(template); err == nil {
-		t.Errorf("expect an error when key template is not registered")
-	}
-}
-
-func TestNewKeyFromKeyFormat(t *testing.T) {
-	setupRegistryTests()
-	// use aes-gcm key format
-	format := aead.NewAesGcmKeyFormat(16)
-	key, err := tink.NewKeyFromKeyFormat(aead.AesGcmTypeURL, format)
-	if err != nil {
-		t.Errorf("unexpected error: %s", err)
-	}
-	var aesGcmKey *gcmpb.AesGcmKey = key.(*gcmpb.AesGcmKey)
-	if uint32(len(aesGcmKey.KeyValue)) != format.KeySize {
-		t.Errorf("key doesn't match format")
-	}
-	// unregistered url
-	if _, err := tink.NewKeyFromKeyFormat("some url", format); err == nil {
-		t.Errorf("expect an error when typeURL has not been registered")
-	}
-	// unmatched url
-	if _, err := tink.NewKeyFromKeyFormat(mac.HmacTypeURL, format); err == nil {
-		t.Errorf("expect an error when typeURL doesn't match format")
-	}
-	// nil format
-	if _, err := tink.NewKeyFromKeyFormat(mac.HmacTypeURL, nil); err == nil {
-		t.Errorf("expect an error when format is nil")
-	}
-}
-
-func TestGetPrimitiveFromKey(t *testing.T) {
-	setupRegistryTests()
-	// hmac key
-	key := testutil.NewHmacKey(commonpb.HashType_SHA256, 16)
-	p, err := tink.GetPrimitiveFromKey(mac.HmacTypeURL, key)
-	if err != nil {
-		t.Errorf("unexpected error: %s", err)
-	}
-	var _ *subtleMac.Hmac = p.(*subtleMac.Hmac)
-	// unregistered url
-	if _, err := tink.GetPrimitiveFromKey("some url", key); err == nil {
-		t.Errorf("expect an error when typeURL has not been registered")
-	}
-	// unmatched url
-	if _, err := tink.GetPrimitiveFromKey(aead.AesGcmTypeURL, key); err == nil {
-		t.Errorf("expect an error when typeURL doesn't match key")
-	}
-	// nil key
-	if _, err := tink.GetPrimitiveFromKey(aead.AesGcmTypeURL, nil); err == nil {
-		t.Errorf("expect an error when key is nil")
-	}
-}
-
-func TestGetPrimitiveFromKeyData(t *testing.T) {
-	setupRegistryTests()
-	// hmac keydata
-	keyData := testutil.NewHmacKeyData(commonpb.HashType_SHA256, 16)
-	p, err := tink.GetPrimitiveFromKeyData(keyData)
-	if err != nil {
-		t.Errorf("unexpected error: %s", err)
-	}
-	var _ *subtleMac.Hmac = p.(*subtleMac.Hmac)
-	// unregistered url
-	keyData.TypeUrl = "some url"
-	if _, err := tink.GetPrimitiveFromKeyData(keyData); err == nil {
-		t.Errorf("expect an error when typeURL has not been registered")
-	}
-	// unmatched url
-	keyData.TypeUrl = aead.AesGcmTypeURL
-	if _, err := tink.GetPrimitiveFromKeyData(keyData); err == nil {
-		t.Errorf("expect an error when typeURL doesn't match key")
-	}
-	// nil
-	if _, err := tink.GetPrimitiveFromKeyData(nil); err == nil {
-		t.Errorf("expect an error when key data is nil")
-	}
-}
-
-func TestGetPrimitiveFromSerializedKey(t *testing.T) {
-	setupRegistryTests()
-	// hmac key
-	key := testutil.NewHmacKey(commonpb.HashType_SHA256, 16)
-	serializedKey, _ := proto.Marshal(key)
-	p, err := tink.GetPrimitiveFromSerializedKey(mac.HmacTypeURL, serializedKey)
-	if err != nil {
-		t.Errorf("unexpected error: %s", err)
-	}
-	var _ *subtleMac.Hmac = p.(*subtleMac.Hmac)
-	// unregistered url
-	if _, err := tink.GetPrimitiveFromSerializedKey("some url", serializedKey); err == nil {
-		t.Errorf("expect an error when typeURL has not been registered")
-	}
-	// unmatched url
-	if _, err := tink.GetPrimitiveFromSerializedKey(aead.AesGcmTypeURL, serializedKey); err == nil {
-		t.Errorf("expect an error when typeURL doesn't match key")
-	}
-	// void key
-	if _, err := tink.GetPrimitiveFromSerializedKey(aead.AesGcmTypeURL, nil); err == nil {
-		t.Errorf("expect an error when key is nil")
-	}
-	if _, err := tink.GetPrimitiveFromSerializedKey(aead.AesGcmTypeURL, []byte{}); err == nil {
-		t.Errorf("expect an error when key is nil")
-	}
-	if _, err := tink.GetPrimitiveFromSerializedKey(aead.AesGcmTypeURL, []byte{0}); err == nil {
-		t.Errorf("expect an error when key is nil")
-	}
-}
-
-func TestGetPrimitives(t *testing.T) {
-	setupRegistryTests()
-	// valid input
-	template1 := aead.Aes128GcmKeyTemplate()
-	template2 := aead.Aes256GcmKeyTemplate()
-	keyData1, _ := tink.NewKeyData(template1)
-	keyData2, _ := tink.NewKeyData(template2)
-	keyset := tink.CreateKeyset(2, []*tinkpb.Keyset_Key{
-		tink.CreateKey(keyData1, tinkpb.KeyStatusType_ENABLED, 1, tinkpb.OutputPrefixType_TINK),
-		tink.CreateKey(keyData2, tinkpb.KeyStatusType_ENABLED, 2, tinkpb.OutputPrefixType_TINK),
-	})
-	handle, _ := tink.CleartextKeysetHandle().ParseKeyset(keyset)
-	ps, err := tink.GetPrimitives(handle)
-	if err != nil {
-		t.Errorf("unexpected error: %s", err)
-	}
-	var aesGcm *subtleAead.AesGcm = ps.Primary().Primitive().(*subtleAead.AesGcm)
-	if len(aesGcm.Key) != 32 {
-		t.Errorf("primitive doesn't match input keyset handle")
-	}
-	// custom manager
-	customManager := new(testutil.DummyAeadKeyManager)
-	ps, err = tink.GetPrimitivesWithCustomManager(handle, customManager)
-	if err != nil {
-		t.Errorf("unexpected error: %s", err)
-	}
-	var _ *testutil.DummyAead = ps.Primary().Primitive().(*testutil.DummyAead)
-	// keysethandle is nil
-	if _, err := tink.GetPrimitives(nil); err == nil {
-		t.Errorf("expect an error when keysethandle is nil")
-	}
-	// keyset is empty
-	keyset = tink.CreateKeyset(1, []*tinkpb.Keyset_Key{})
-	handle, _ = tink.CleartextKeysetHandle().ParseKeyset(keyset)
-	if _, err := tink.GetPrimitives(handle); err == nil {
-		t.Errorf("expect an error when keyset is empty")
-	}
-	keyset = tink.CreateKeyset(1, nil)
-	handle, _ = tink.CleartextKeysetHandle().ParseKeyset(keyset)
-	if _, err := tink.GetPrimitives(handle); err == nil {
-		t.Errorf("expect an error when keyset is empty")
-	}
-	// no primary key
-	keyset = tink.CreateKeyset(3, []*tinkpb.Keyset_Key{
-		tink.CreateKey(keyData1, tinkpb.KeyStatusType_ENABLED, 1, tinkpb.OutputPrefixType_TINK),
-		tink.CreateKey(keyData2, tinkpb.KeyStatusType_ENABLED, 2, tinkpb.OutputPrefixType_TINK),
-	})
-	handle, _ = tink.CleartextKeysetHandle().ParseKeyset(keyset)
-	if _, err := tink.GetPrimitives(handle); err == nil {
-		t.Errorf("expect an error when there is no primary key")
-	}
-	// there is primary key but it is disabled
-	keyset = tink.CreateKeyset(1, []*tinkpb.Keyset_Key{
-		tink.CreateKey(keyData1, tinkpb.KeyStatusType_DISABLED, 1, tinkpb.OutputPrefixType_TINK),
-		tink.CreateKey(keyData2, tinkpb.KeyStatusType_ENABLED, 2, tinkpb.OutputPrefixType_TINK),
-	})
-	handle, _ = tink.CleartextKeysetHandle().ParseKeyset(keyset)
-	if _, err := tink.GetPrimitives(handle); err == nil {
-		t.Errorf("expect an error when primary key is disabled")
-	}
-	// multiple primary keys
-	keyset = tink.CreateKeyset(1, []*tinkpb.Keyset_Key{
-		tink.CreateKey(keyData1, tinkpb.KeyStatusType_ENABLED, 1, tinkpb.OutputPrefixType_TINK),
-		tink.CreateKey(keyData2, tinkpb.KeyStatusType_ENABLED, 1, tinkpb.OutputPrefixType_TINK),
-	})
-	handle, _ = tink.CleartextKeysetHandle().ParseKeyset(keyset)
-	if _, err := tink.GetPrimitives(handle); err == nil {
-		t.Errorf("expect an error when there are multiple primary keys")
-	}
-}
diff --git a/go/tink/public_key_sign.go b/go/tink/signer.go
similarity index 65%
rename from go/tink/public_key_sign.go
rename to go/tink/signer.go
index 9ac21f6..9a399d4 100644
--- a/go/tink/public_key_sign.go
+++ b/go/tink/signer.go
@@ -14,16 +14,10 @@
 
 package tink
 
-/*
-PublicKeySign is the signing interface for digital signature.
-Implementations of this interface are secure against adaptive chosen-message attacks.
-Signing data ensures authenticity and integrity of that data, but not its secrecy.
-*/
-type PublicKeySign interface {
-	/**
-	 * Computes the signature for {@code data}.
-	 *
-	 * @return the signature of {$code data}.
-	 */
+// Signer is the signing interface for digital signature.
+// Implementations of this interface are secure against adaptive chosen-message attacks.
+// Signing data ensures authenticity and integrity of that data, but not its secrecy.
+type Signer interface {
+	// Computes the digital signature for data.
 	Sign(data []byte) ([]byte, error)
 }
diff --git a/go/tink/util.go b/go/tink/util.go
deleted file mode 100644
index 2411e2a..0000000
--- a/go/tink/util.go
+++ /dev/null
@@ -1,174 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package tink
-
-import (
-	"fmt"
-
-	commonpb "github.com/google/tink/proto/common_go_proto"
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
-)
-
-// ValidateVersion checks whether the given version is valid. The version is valid
-// only if it is the range [0..maxExpected]
-func ValidateVersion(version uint32, maxExpected uint32) error {
-	if version > maxExpected {
-		msg := fmt.Sprintf("key has version %v; "+
-			"only keys with version in range [0..%v] are supported",
-			version, maxExpected)
-		return fmt.Errorf(msg)
-	}
-	return nil
-}
-
-// GetHashName returns the name of the HashType.
-func GetHashName(hashType commonpb.HashType) string {
-	return commonpb.HashType_name[int32(hashType)]
-}
-
-// GetCurveName returns the name of the EllipticCurveType.
-func GetCurveName(curve commonpb.EllipticCurveType) string {
-	return commonpb.EllipticCurveType_name[int32(curve)]
-}
-
-// GetKeysetInfo returns a KeysetInfo from a Keyset protobuf.
-func GetKeysetInfo(keyset *tinkpb.Keyset) (*tinkpb.KeysetInfo, error) {
-	if keyset == nil {
-		return nil, fmt.Errorf("Gettinkpb.KeysetInfo() called with nil")
-	}
-	nKey := len(keyset.Key)
-	keyInfos := make([]*tinkpb.KeysetInfo_KeyInfo, nKey)
-	for i, key := range keyset.Key {
-		info, err := GetKeyInfo(key)
-		if err != nil {
-			return nil, err
-		}
-		keyInfos[i] = info
-	}
-	return &tinkpb.KeysetInfo{
-		PrimaryKeyId: keyset.PrimaryKeyId,
-		KeyInfo:      keyInfos,
-	}, nil
-}
-
-// GetKeyInfo returns a KeyInfo from a Key protobuf.
-func GetKeyInfo(key *tinkpb.Keyset_Key) (*tinkpb.KeysetInfo_KeyInfo, error) {
-	if key == nil {
-		return nil, fmt.Errorf("GetKeyInfo() called with nil")
-	}
-	return &tinkpb.KeysetInfo_KeyInfo{
-		TypeUrl:          key.KeyData.TypeUrl,
-		Status:           key.Status,
-		KeyId:            key.KeyId,
-		OutputPrefixType: key.OutputPrefixType,
-	}, nil
-}
-
-// ValidateKeyset validates the given key set.
-// Returns nil if it is valid; an error otherwise.
-func ValidateKeyset(keyset *tinkpb.Keyset) error {
-	if keyset == nil {
-		return fmt.Errorf("ValidateKeyset() called with nil")
-	}
-	if len(keyset.Key) == 0 {
-		return fmt.Errorf("empty keyset")
-	}
-	primaryKeyID := keyset.PrimaryKeyId
-	hasPrimaryKey := false
-	for _, key := range keyset.Key {
-		if err := ValidateKey(key); err != nil {
-			return err
-		}
-		if key.Status == tinkpb.KeyStatusType_ENABLED && key.KeyId == primaryKeyID {
-			if hasPrimaryKey {
-				return fmt.Errorf("keyset contains multiple primary keys")
-			}
-			hasPrimaryKey = true
-		}
-	}
-	if !hasPrimaryKey {
-		return fmt.Errorf("keyset does not contain a valid primary key")
-	}
-	return nil
-}
-
-/*
-ValidateKey validates the given key.
-Returns nil if it is valid; an error otherwise.
-*/
-func ValidateKey(key *tinkpb.Keyset_Key) error {
-	if key == nil {
-		return fmt.Errorf("ValidateKey() called with nil")
-	}
-	if key.KeyId <= 0 {
-		return fmt.Errorf("key has non-positive key id: %d", key.KeyId)
-	}
-	if key.KeyData == nil {
-		return fmt.Errorf("key %d has no key data", key.KeyId)
-	}
-	if key.OutputPrefixType != tinkpb.OutputPrefixType_TINK &&
-		key.OutputPrefixType != tinkpb.OutputPrefixType_LEGACY &&
-		key.OutputPrefixType != tinkpb.OutputPrefixType_RAW &&
-		key.OutputPrefixType != tinkpb.OutputPrefixType_CRUNCHY {
-		return fmt.Errorf("key %d has unknown prefix", key.KeyId)
-	}
-	if key.Status != tinkpb.KeyStatusType_ENABLED &&
-		key.Status != tinkpb.KeyStatusType_DISABLED &&
-		key.Status != tinkpb.KeyStatusType_DESTROYED {
-		return fmt.Errorf("key %d has unknown status", key.KeyId)
-	}
-	return nil
-}
-
-// CreateKeyData creates a new KeyData with the specified parameters.
-func CreateKeyData(typeURL string,
-	value []byte,
-	materialType tinkpb.KeyData_KeyMaterialType) *tinkpb.KeyData {
-	return &tinkpb.KeyData{
-		TypeUrl:         typeURL,
-		Value:           value,
-		KeyMaterialType: materialType,
-	}
-}
-
-// CreateKey creates a new Key with the specified parameters.
-func CreateKey(keyData *tinkpb.KeyData,
-	status tinkpb.KeyStatusType,
-	keyID uint32,
-	prefixType tinkpb.OutputPrefixType) *tinkpb.Keyset_Key {
-	return &tinkpb.Keyset_Key{
-		KeyData:          keyData,
-		Status:           status,
-		KeyId:            keyID,
-		OutputPrefixType: prefixType,
-	}
-}
-
-// CreateKeyset creates a new Keyset with the specified parameters.
-func CreateKeyset(primaryKeyID uint32,
-	keys []*tinkpb.Keyset_Key) *tinkpb.Keyset {
-	return &tinkpb.Keyset{
-		PrimaryKeyId: primaryKeyID,
-		Key:          keys,
-	}
-}
-
-// CreateEncryptedKeyset creates a new EncryptedKeyset with a specified parameters.
-func CreateEncryptedKeyset(encryptedKeySet []byte, info *tinkpb.KeysetInfo) *tinkpb.EncryptedKeyset {
-	return &tinkpb.EncryptedKeyset{
-		EncryptedKeyset: encryptedKeySet,
-		KeysetInfo:      info,
-	}
-}
diff --git a/go/tink/util_test.go b/go/tink/util_test.go
deleted file mode 100644
index 78e6a0c..0000000
--- a/go/tink/util_test.go
+++ /dev/null
@@ -1,144 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package tink_test
-
-import (
-	"testing"
-
-	"github.com/google/tink/go/testutil"
-	"github.com/google/tink/go/tink"
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
-)
-
-func TestValidateVersion(t *testing.T) {
-	if tink.ValidateVersion(2, 1) == nil ||
-		tink.ValidateVersion(1, 1) != nil ||
-		tink.ValidateVersion(1, 2) != nil {
-		t.Errorf("incorrect version validation")
-	}
-}
-
-func TestGetKeyInfo(t *testing.T) {
-	_, err := tink.GetKeyInfo(nil)
-	if err == nil {
-		t.Errorf("expect an error when input is nil")
-	}
-	keyData := tink.CreateKeyData("some url", []byte{1}, tinkpb.KeyData_SYMMETRIC)
-	key := tink.CreateKey(keyData, tinkpb.KeyStatusType_ENABLED, 1, tinkpb.OutputPrefixType_TINK)
-	info, err := tink.GetKeyInfo(key)
-	if err != nil {
-		t.Errorf("unexpected error")
-	}
-	if !compareKeyInfo(info, key) {
-		t.Errorf("KeyInfo mismatched")
-	}
-}
-
-func TestGetKeysetInfo(t *testing.T) {
-	_, err := tink.GetKeysetInfo(nil)
-	if err == nil {
-		t.Errorf("expect an error when input is nil")
-	}
-	keyData := tink.CreateKeyData("some url", []byte{1}, tinkpb.KeyData_SYMMETRIC)
-	key := tink.CreateKey(keyData, tinkpb.KeyStatusType_ENABLED, 1, tinkpb.OutputPrefixType_TINK)
-	keyset := tink.CreateKeyset(1, []*tinkpb.Keyset_Key{key})
-	keysetInfo, err := tink.GetKeysetInfo(keyset)
-	if err != nil {
-		t.Error("This should not error here")
-	}
-	if keysetInfo.PrimaryKeyId != keyset.PrimaryKeyId {
-		t.Errorf("PrimaryKeyId mismatched")
-	}
-	for i, keyInfo := range keysetInfo.KeyInfo {
-		if !compareKeyInfo(keyInfo, keyset.Key[i]) {
-			t.Errorf("KeyInfo mismatched")
-		}
-	}
-}
-
-func TestValidateKey(t *testing.T) {
-	invalidKeys := generateInvalidKeys()
-	for i, key := range invalidKeys {
-		if err := tink.ValidateKey(key); err == nil {
-			t.Errorf("expect an error for invalid key #%d", i)
-		}
-	}
-}
-
-func TestValidateKeyset(t *testing.T) {
-	var err error
-	// nil input
-	if err = tink.ValidateKeyset(nil); err == nil {
-		t.Errorf("expect an error when keyset is nil")
-	}
-	// empty keyset
-	emptyKeys := make([]*tinkpb.Keyset_Key, 0)
-	if err = tink.ValidateKeyset(tink.CreateKeyset(1, emptyKeys)); err == nil {
-		t.Errorf("expect an error when keyset is empty")
-	}
-	// no primary key
-	keys := []*tinkpb.Keyset_Key{
-		testutil.NewDummyKey(1, tinkpb.KeyStatusType_ENABLED, tinkpb.OutputPrefixType_TINK),
-	}
-	if err = tink.ValidateKeyset(tink.CreateKeyset(2, keys)); err == nil {
-		t.Errorf("expect an error when there is no primary key")
-	}
-	// primary key is disabled
-	keys = []*tinkpb.Keyset_Key{
-		testutil.NewDummyKey(1, tinkpb.KeyStatusType_ENABLED, tinkpb.OutputPrefixType_TINK),
-		testutil.NewDummyKey(2, tinkpb.KeyStatusType_DISABLED, tinkpb.OutputPrefixType_LEGACY),
-	}
-	if err = tink.ValidateKeyset(tink.CreateKeyset(2, keys)); err == nil {
-		t.Errorf("expect an error when primary key is disabled")
-	}
-	// multiple primary keys
-	keys = []*tinkpb.Keyset_Key{
-		testutil.NewDummyKey(1, tinkpb.KeyStatusType_ENABLED, tinkpb.OutputPrefixType_TINK),
-		testutil.NewDummyKey(1, tinkpb.KeyStatusType_ENABLED, tinkpb.OutputPrefixType_LEGACY),
-	}
-	if err = tink.ValidateKeyset(tink.CreateKeyset(1, keys)); err == nil {
-		t.Errorf("expect an error when there are multiple primary keys")
-	}
-	// invalid keys
-	invalidKeys := generateInvalidKeys()
-	for i, key := range invalidKeys {
-		err = tink.ValidateKeyset(tink.CreateKeyset(1, []*tinkpb.Keyset_Key{key}))
-		if err == nil {
-			t.Errorf("expect an error when validate invalid key %d", i)
-		}
-	}
-}
-
-func generateInvalidKeys() []*tinkpb.Keyset_Key {
-	return []*tinkpb.Keyset_Key{
-		nil,
-		// nil KeyData
-		tink.CreateKey(nil, tinkpb.KeyStatusType_ENABLED, 1, tinkpb.OutputPrefixType_TINK),
-		// unknown status
-		tink.CreateKey(new(tinkpb.KeyData), tinkpb.KeyStatusType_UNKNOWN_STATUS, 1, tinkpb.OutputPrefixType_TINK),
-		// unknown prefix
-		tink.CreateKey(new(tinkpb.KeyData), tinkpb.KeyStatusType_ENABLED, 1, tinkpb.OutputPrefixType_UNKNOWN_PREFIX),
-	}
-}
-
-func compareKeyInfo(info *tinkpb.KeysetInfo_KeyInfo, key *tinkpb.Keyset_Key) bool {
-	if info.TypeUrl != key.KeyData.TypeUrl ||
-		info.Status != key.Status ||
-		info.KeyId != key.KeyId ||
-		info.OutputPrefixType != key.OutputPrefixType {
-		return false
-	}
-	return true
-}
diff --git a/go/tink/private_key_manager.go b/go/tink/verifier.go
similarity index 62%
copy from go/tink/private_key_manager.go
copy to go/tink/verifier.go
index 7cf169a..88aacc7 100644
--- a/go/tink/private_key_manager.go
+++ b/go/tink/verifier.go
@@ -14,14 +14,10 @@
 
 package tink
 
-import (
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
-)
-
-// PrivateKeyManager is a special type of KeyManager that understands private key types.
-type PrivateKeyManager interface {
-	KeyManager
-
-	// GetPublicKeyData extracts the public key data from the private key.
-	GetPublicKeyData(serializedKey []byte) (*tinkpb.KeyData, error)
+// Verifier is the verifying interface for digital signature.
+// Implementations of this interface are secure against adaptive chosen-message attacks.
+// Signing data ensures authenticity and integrity of that data, but not its secrecy.
+type Verifier interface {
+	// Verifies returns nil if signature is a valid signature for data; otherwise returns an error.
+	Verify(signature, data []byte) error
 }
diff --git a/java/BUILD.bazel b/java/BUILD.bazel
index d46a8c4..5589e78 100644
--- a/java/BUILD.bazel
+++ b/java/BUILD.bazel
@@ -13,13 +13,17 @@
     "//proto:chacha20_poly1305_java_proto",
     "//proto:common_java_proto",
     "//proto:config_java_proto",
+    "//proto:empty_java_proto",
     "//proto:ecdsa_java_proto",
+    "//proto:rsa_ssa_pkcs1_java_proto",
+    "//proto:rsa_ssa_pss_java_proto",
     "//proto:ecies_aead_hkdf_java_proto",
     "//proto:ed25519_java_proto",
     "//proto:hmac_java_proto",
     "//proto:kms_aead_java_proto",
     "//proto:kms_envelope_java_proto",
     "//proto:tink_java_proto",
+    "//proto:xchacha20_poly1305_java_proto",
 ]
 
 lite_proto_deps = [
@@ -33,13 +37,17 @@
     "//proto:chacha20_poly1305_java_proto_lite",
     "//proto:common_java_proto_lite",
     "//proto:config_java_proto_lite",
+    "//proto:empty_java_proto_lite",
     "//proto:ecdsa_java_proto_lite",
+    "//proto:rsa_ssa_pkcs1_java_proto_lite",
+    "//proto:rsa_ssa_pss_java_proto_lite",
     "//proto:ecies_aead_hkdf_java_proto_lite",
     "//proto:ed25519_java_proto_lite",
     "//proto:hmac_java_proto_lite",
     "//proto:kms_aead_java_proto_lite",
     "//proto:kms_envelope_java_proto_lite",
     "//proto:tink_java_proto_lite",
+    "//proto:xchacha20_poly1305_java_proto_lite",
 ]
 
 subtle_deps = [
@@ -48,6 +56,7 @@
     "//java/src/main/java/com/google/crypto/tink/subtle:aead",
     "//java/src/main/java/com/google/crypto/tink/subtle:daead",
     "//java/src/main/java/com/google/crypto/tink/subtle:hybrid",
+    "//java/src/main/java/com/google/crypto/tink/subtle:keywrap",
     "//java/src/main/java/com/google/crypto/tink/subtle:mac",
     "//java/src/main/java/com/google/crypto/tink/subtle:signature",
     "//java/src/main/java/com/google/crypto/tink/subtle:streaming",
@@ -137,13 +146,13 @@
 gen_maven_jar_rules(
     name = "maven",
     doctitle = "Tink Cryptography API",
-    deps = full_proto_deps + java_deps + cleartext_deps,
+    deps = full_proto_deps + java_deps + cleartext_deps + subtle_deps,
 )
 
 gen_maven_jar_rules(
     name = "maven-android",
     doctitle = "Tink Cryptography API for Android",
-    deps = lite_proto_deps + android_deps + cleartext_android_deps,
+    deps = lite_proto_deps + android_deps + cleartext_android_deps + subtle_deps,
 )
 
 # TEST
diff --git a/java/src/main/java/com/google/crypto/tink/BUILD.bazel b/java/src/main/java/com/google/crypto/tink/BUILD.bazel
index f902858..e49ee55 100644
--- a/java/src/main/java/com/google/crypto/tink/BUILD.bazel
+++ b/java/src/main/java/com/google/crypto/tink/BUILD.bazel
@@ -3,6 +3,8 @@
 package(default_visibility = ["//tools/build_defs:internal_pkg"])
 
 load("//tools/build_defs:javac.bzl", "JAVACOPTS_OSS")
+load("//:tink_version.bzl", "TINK_VERSION_LABEL")
+load("//tools:common.bzl", "template_rule")
 
 full_protos = [
     "//proto:common_java_proto",
@@ -25,6 +27,7 @@
         "DeterministicAead.java",
         "HybridDecrypt.java",
         "HybridEncrypt.java",
+        "KeyWrap.java",
         "Mac.java",
         "PublicKeySign.java",
         "PublicKeyVerify.java",
@@ -53,11 +56,18 @@
 filegroup(
     name = "srcs",
     srcs = glob(
-        [
-            "*.java",
-        ],
+        ["*.java"],
         exclude = CLEARTEXT_KEYSET_HANDLE_SRCS,
-    ),
+    ) + [":version_java"],
+)
+
+template_rule(
+    name = "version_java",
+    src = "Version.java.templ",
+    out = "Version.java",
+    substitutions = {
+        "TINK_VERSION_LABEL": "%s" % TINK_VERSION_LABEL,
+    },
 )
 
 java_library(
@@ -76,15 +86,13 @@
 filegroup(
     name = "android_srcs",
     srcs = glob(
-        [
-            "*.java",
-        ],
+        ["*.java"],
         exclude = CLEARTEXT_KEYSET_HANDLE_SRCS + [
             # TextFormat doesn't work with lite protos
             "TextFormatKeysetReaders.java",
             "TextFormatKeysetWriters.java",
         ],
-    ),
+    ) + [":version_java"],
 )
 
 java_library(
diff --git a/java/src/main/java/com/google/crypto/tink/Catalogue.java b/java/src/main/java/com/google/crypto/tink/Catalogue.java
index 209a464..bff2264 100644
--- a/java/src/main/java/com/google/crypto/tink/Catalogue.java
+++ b/java/src/main/java/com/google/crypto/tink/Catalogue.java
@@ -39,4 +39,7 @@
    */
   public KeyManager<P> getKeyManager(String typeUrl, String primitiveName, int minVersion)
       throws GeneralSecurityException;
+
+  /** Returns a new primitive wrapper for this primitive. */
+  public PrimitiveWrapper<P> getPrimitiveWrapper() throws GeneralSecurityException;
 }
diff --git a/java/src/main/java/com/google/crypto/tink/CleartextKeysetHandle.java b/java/src/main/java/com/google/crypto/tink/CleartextKeysetHandle.java
index 81d72cb..cba215b 100644
--- a/java/src/main/java/com/google/crypto/tink/CleartextKeysetHandle.java
+++ b/java/src/main/java/com/google/crypto/tink/CleartextKeysetHandle.java
@@ -58,6 +58,12 @@
   }
 
   /**
+   * @return the keyset underlying this {@code keysetHandle}.
+   */
+  public static Keyset getKeyset(KeysetHandle keysetHandle) {
+    return keysetHandle.getKeyset();
+  }
+  /**
    * Serializes and writes the {@link Keyset} managed by {@code handle} to {@code keysetWriter}.
    *
    * @throws IOException
diff --git a/java/src/main/java/com/google/crypto/tink/Config.java b/java/src/main/java/com/google/crypto/tink/Config.java
index dd61849..13b9029 100644
--- a/java/src/main/java/com/google/crypto/tink/Config.java
+++ b/java/src/main/java/com/google/crypto/tink/Config.java
@@ -79,11 +79,11 @@
    *     {@link com.google.crypto.tink.KeyManager} or {@link com.google.crypto.tink.Catalogue} that
    *     can handle the entry. In both cases the error message should show how to resolve it.
    */
-  @SuppressWarnings({"rawtypes", "unchecked"})
   public static void registerKeyType(KeyTypeEntry entry) throws GeneralSecurityException {
     validate(entry);
-    Catalogue catalogue = Registry.getCatalogue(entry.getCatalogueName());
-    KeyManager keyManager =
+    Catalogue<?> catalogue = Registry.getCatalogue(entry.getCatalogueName());
+    Registry.registerPrimitiveWrapper(catalogue.getPrimitiveWrapper());
+    KeyManager<?> keyManager =
         catalogue.getKeyManager(
             entry.getTypeUrl(), entry.getPrimitiveName(), entry.getKeyManagerVersion());
     Registry.registerKeyManager(keyManager, entry.getNewKeyAllowed());
diff --git a/java/src/main/java/com/google/crypto/tink/KeyManager.java b/java/src/main/java/com/google/crypto/tink/KeyManager.java
index 8c11e70..a561a2b 100644
--- a/java/src/main/java/com/google/crypto/tink/KeyManager.java
+++ b/java/src/main/java/com/google/crypto/tink/KeyManager.java
@@ -81,6 +81,14 @@
   /** @return the version number of this KeyManager. */
   int getVersion();
 
+  /**
+   * Returns the primitive class object of the P. Should be implemented as {@code return P.class;}
+   * when implementing a key manager for primitive {$code P}.
+   *
+   * @return {@code P.class}
+   */
+  Class<P> getPrimitiveClass();
+
   // APIs for Key Management
 
   /**
diff --git a/java/src/main/java/com/google/crypto/tink/KeyManagerBase.java b/java/src/main/java/com/google/crypto/tink/KeyManagerBase.java
new file mode 100644
index 0000000..7a3d5fa
--- /dev/null
+++ b/java/src/main/java/com/google/crypto/tink/KeyManagerBase.java
@@ -0,0 +1,218 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink;
+
+import com.google.crypto.tink.annotations.Alpha;
+import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.MessageLite;
+import java.security.GeneralSecurityException;
+
+/**
+ * A utility class to implement a {@code KeyManager}.
+ *
+ * <p>Implementing many of the methods in the {@code KeyManager} can be repetitive. This is an
+ * internal utility class to implement these methods. In order to instantiate it, one calls
+ * the constructor with class objects of the protos used and the type URL:
+ *
+ * <pre> {@code
+ * class MyConcreteKeyManager
+ *     extends KeyManagerBase<ConcretePrimitive, ConcreteKeyProto, ConcreteKeyFormatProto> {
+ *   public MyConcreteKeyManager() {
+ *     super(ConcreteKeyProto.class, ConcreteKeyFormatProto.class, TYPE_URL);
+ *   }
+ *   [...]
+ * }</pre>
+ * Furthermore, one implements all the abstract methods in this class.
+ *
+ * This code is currently Alpha and may change without warning.
+ */
+@Alpha
+public abstract class KeyManagerBase<
+        P, KeyProto extends MessageLite, KeyFormatProto extends MessageLite>
+    implements KeyManager<P> {
+  protected KeyManagerBase(
+      final Class<P> primitiveClass,
+      final Class<KeyProto> keyProtoClass,
+      final Class<KeyFormatProto> keyFormatProtoClass,
+      final String typeUrl) {
+    this.primitiveClass = primitiveClass;
+    this.keyProtoClass = keyProtoClass;
+    this.keyFormatProtoClass = keyFormatProtoClass;
+    this.typeUrl = typeUrl;
+  }
+
+  private final Class<P> primitiveClass;
+  private final Class<KeyProto> keyProtoClass;
+  private final Class<KeyFormatProto> keyFormatProtoClass;
+  private final String typeUrl;
+
+  @SuppressWarnings("unchecked")
+  private static <Casted> Casted castOrSecurityException(
+      Object objectToCast, String exceptionText, Class<Casted> classObject)
+      throws GeneralSecurityException {
+    if (!classObject.isInstance(objectToCast)) {
+      throw new GeneralSecurityException(exceptionText);
+    }
+    return (Casted) objectToCast;
+  }
+
+  @Override
+  public final P getPrimitive(ByteString serializedKey) throws GeneralSecurityException {
+    try {
+      KeyProto keyProto = parseKeyProto(serializedKey);
+      return validateKeyAndGetPrimitive(keyProto);
+    } catch (InvalidProtocolBufferException e) {
+      throw new GeneralSecurityException(
+          "Failures parsing proto of type " + keyProtoClass.getName(), e);
+    }
+  }
+
+  @Override
+  public final P getPrimitive(MessageLite key) throws GeneralSecurityException {
+    return validateKeyAndGetPrimitive(
+        castOrSecurityException(
+            key, "Expected proto of type " + keyProtoClass.getName(), keyProtoClass));
+  }
+
+  /**
+   * @param serializedKeyFormat serialized {@code AesGcmKeyFormat} proto
+   * @return new {@code AesGcmKey} proto
+   */
+  @Override
+  public final MessageLite newKey(ByteString serializedKeyFormat) throws GeneralSecurityException {
+    try {
+      return validateFormatAndCreateKey(parseKeyFormatProto(serializedKeyFormat));
+    } catch (InvalidProtocolBufferException e) {
+      throw new GeneralSecurityException(
+          "Failures parsing proto of type " + keyFormatProtoClass.getName(), e);
+    }
+  }
+
+  /**
+   * @param keyFormat {@code AesGcmKeyFormat} proto
+   * @return new {@code AesGcmKey} proto
+   */
+  @Override
+  public final MessageLite newKey(MessageLite keyFormat) throws GeneralSecurityException {
+    return validateFormatAndCreateKey(
+        castOrSecurityException(
+            keyFormat,
+            "Expected proto of type " + keyFormatProtoClass.getName(),
+            keyFormatProtoClass));
+  }
+
+  @Override
+  public final boolean doesSupport(String typeUrl) {
+    return typeUrl.equals(getKeyType());
+  }
+
+  @Override
+  public final String getKeyType() {
+    return typeUrl;
+  }
+
+  /**
+   * @param serializedKeyFormat serialized {@code AesGcmKeyFormat} proto
+   * @return {@code KeyData} proto with a new {@code AesGcmKey} proto
+   */
+  @Override
+  public final KeyData newKeyData(ByteString serializedKeyFormat) throws GeneralSecurityException {
+    KeyFormatProto format;
+    try {
+      format = parseKeyFormatProto(serializedKeyFormat);
+    } catch (InvalidProtocolBufferException e) {
+      throw new GeneralSecurityException("Unexpected proto", e);
+    }
+    KeyProto key = validateFormatAndCreateKey(format);
+    return KeyData.newBuilder()
+        .setTypeUrl(getKeyType())
+        .setValue(key.toByteString())
+        .setKeyMaterialType(keyMaterialType())
+        .build();
+  }
+
+  @Override
+  public final Class<P> getPrimitiveClass() {
+    return primitiveClass;
+  }
+
+  /**
+   * Checks if the given {@code keyProto} is a valid key. Throws a GeneralSecurityException if it is
+   * not.
+   */
+  protected abstract void validateKey(KeyProto keyProto) throws GeneralSecurityException;
+
+  /**
+   * Checks if the given {@code keyProto} is a valid key format. Throws a GeneralSecurityException
+   * if it is not.
+   */
+  protected abstract void validateKeyFormat(KeyFormatProto keyProto)
+      throws GeneralSecurityException;
+
+  /** Returns the {@code KeyMaterialType} for this proto. */
+  protected abstract KeyMaterialType keyMaterialType();
+
+  /**
+   * Creates a primitive from a given key. Only called with validated {@code validatedKeyProto}s.
+   */
+  protected abstract P getPrimitiveFromKey(KeyProto validatedKeyProto)
+      throws GeneralSecurityException;
+
+  /** Creates a key proto after validating */
+  private P validateKeyAndGetPrimitive(KeyProto keyProto) throws GeneralSecurityException {
+    validateKey(keyProto);
+    return getPrimitiveFromKey(keyProto);
+  }
+
+  /**
+   * Creates a new key for a given format. Only called with validated {@code
+   * validatedKeyFormatProto}s. The returned {@code KeyProto} will be validated.
+   */
+  protected abstract KeyProto newKeyFromFormat(KeyFormatProto validatedKeyFormatProto)
+      throws GeneralSecurityException;
+
+  /**
+   * Validates the given {@code KeyFormatProto}, uses it to create a new key, validates it, then
+   * returns
+   */
+  private KeyProto validateFormatAndCreateKey(KeyFormatProto keyFormatProto)
+      throws GeneralSecurityException {
+    validateKeyFormat(keyFormatProto);
+    KeyProto result = newKeyFromFormat(keyFormatProto);
+    validateKey(result);
+    return result;
+  }
+
+  /**
+   * Parses a serialized key proto.
+   *
+   * <p>Should be implemented as {code return MyKeyProto.parseFrom(byteString);}.
+   */
+  protected abstract KeyProto parseKeyProto(ByteString byteString)
+      throws InvalidProtocolBufferException;
+
+  /**
+   * Parses a serialized key format proto.
+   *
+   * <p>Should be implemented as {code return MyKeyFormatProto.parseFrom(byteString);}.
+   */
+  protected abstract KeyFormatProto parseKeyFormatProto(ByteString byteString)
+      throws InvalidProtocolBufferException;
+}
diff --git a/java/src/main/java/com/google/crypto/tink/KeyWrap.java b/java/src/main/java/com/google/crypto/tink/KeyWrap.java
new file mode 100644
index 0000000..7ae735a
--- /dev/null
+++ b/java/src/main/java/com/google/crypto/tink/KeyWrap.java
@@ -0,0 +1,61 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink;
+
+import java.security.GeneralSecurityException;
+
+/**
+ * Interface for symmetric Key wrapping.
+ * A key wrap algorithm is a primitive specifically meant for encrypting
+ * key material. Primitives implementing the interface may either be
+ * deterministic or non-deterministic.
+ *
+ * The interface is somewhat limited. It does not allow additional
+ * data during key wrapping. The security guarantees are not including
+ * a multi user setting. The reason for these limitations is that
+ * it allows to include KWP, with the plan to allow rotation to other
+ * algorithms.
+ *
+ * <h2>Requirements</h2>
+ * Primitives implementing use key sizes of 128-bits or higher.
+ * Key wrapping includes an integrity check.
+ * The minimal strength of the integrity check is about 64 bits.
+ * In particular, the minimal key strength allows KWP to be included.
+ *
+ * <h2>Key size of the wrapped key.</h2>
+ * Valid key sizes are in the range 16 .. 4096 bytes.
+ * The lower bound assures a low probability of key collisions,
+ * and hence allows deterministic key wrappings to be used.
+ *
+ * @since 1.?.?
+ */
+public interface KeyWrap {
+  /**
+   * Wraps some key material {@code data}.
+   *
+   * @param data the key to wrap. 
+   * @return the wrapped key
+   */
+  byte[] wrap(final byte[] data) throws GeneralSecurityException;
+
+  /**
+   * Unwraps a wrapped key.
+   *
+   * @throws GeneralSecurityException if {@code data} fails the integrity check.
+   */
+  byte[] unwrap(final byte[] data) throws GeneralSecurityException;
+}
diff --git a/java/src/main/java/com/google/crypto/tink/KeysetHandle.java b/java/src/main/java/com/google/crypto/tink/KeysetHandle.java
index c3c81ff..ab9be1d 100644
--- a/java/src/main/java/com/google/crypto/tink/KeysetHandle.java
+++ b/java/src/main/java/com/google/crypto/tink/KeysetHandle.java
@@ -16,6 +16,7 @@
 
 package com.google.crypto.tink;
 
+import com.google.crypto.tink.annotations.Alpha;
 import com.google.crypto.tink.proto.EncryptedKeyset;
 import com.google.crypto.tink.proto.KeyData;
 import com.google.crypto.tink.proto.KeyTemplate;
@@ -92,6 +93,48 @@
     return new KeysetHandle(decrypt(encryptedKeyset, masterKey));
   }
 
+  /**
+   * Tries to create a {@link KeysetHandle} from a keyset, obtained via {@code reader}, which
+   * contains no secret key material.
+   *
+   * <p>This can be used to load public keysets or envelope encryption keysets. Users that need to
+   * load cleartext keysets can use {@link CleartextKeysetHandle}.
+   *
+   * @return a new {@link KeysetHandle} from {@code serialized} that is a serialized {@link Keyset}
+   * @throws GeneralSecurityException
+   */
+  public static final KeysetHandle readNoSecret(KeysetReader reader)
+      throws GeneralSecurityException, IOException {
+    try {
+      Keyset keyset = reader.read();
+      assertNoSecretKeyMaterial(keyset);
+      return KeysetHandle.fromKeyset(keyset);
+    } catch (InvalidProtocolBufferException e) {
+      throw new GeneralSecurityException("invalid keyset");
+    }
+  }
+
+  /**
+   * Tries to create a {@link KeysetHandle} from a serialized keyset which contains no secret key
+   * material.
+   *
+   * <p>This can be used to load public keysets or envelope encryption keysets. Users that need to
+   * load cleartext keysets can use {@link CleartextKeysetHandle}.
+   *
+   * @return a new {@link KeysetHandle} from {@code serialized} that is a serialized {@link Keyset}
+   * @throws GeneralSecurityException
+   */
+  public static final KeysetHandle readNoSecret(final byte[] serialized)
+      throws GeneralSecurityException {
+    try {
+      Keyset keyset = Keyset.parseFrom(serialized);
+      assertNoSecretKeyMaterial(keyset);
+      return KeysetHandle.fromKeyset(keyset);
+    } catch (InvalidProtocolBufferException e) {
+      throw new GeneralSecurityException("invalid keyset");
+    }
+  }
+
   /** Serializes, encrypts with {@code masterKey} and writes the keyset to {@code outputStream}. */
   public void write(KeysetWriter keysetWriter, Aead masterKey)
       throws GeneralSecurityException, IOException {
@@ -168,6 +211,7 @@
     return publicKeyData;
   }
 
+  @SuppressWarnings("deprecation")
   private static void validate(KeyData keyData) throws GeneralSecurityException {
     // This will throw GeneralSecurityException if the keyData is invalid.
     Registry.getPrimitive(keyData);
@@ -183,6 +227,21 @@
   }
 
   /**
+   * Validates that {@code keyset} doesn't contain any secret key material.
+   *
+   * @throws GeneralSecurityException if {@code keyset} contains secret key material.
+   */
+  private static void assertNoSecretKeyMaterial(Keyset keyset) throws GeneralSecurityException {
+    for (Keyset.Key key : keyset.getKeyList()) {
+      if (key.getKeyData().getKeyMaterialType() == KeyData.KeyMaterialType.UNKNOWN_KEYMATERIAL
+          || key.getKeyData().getKeyMaterialType() == KeyData.KeyMaterialType.SYMMETRIC
+          || key.getKeyData().getKeyMaterialType() == KeyData.KeyMaterialType.ASYMMETRIC_PRIVATE) {
+        throw new GeneralSecurityException("keyset contains secret key material");
+      }
+    }
+  }
+
+  /**
    * Validates that an keyset handle contains enough key material to build a keyset on.
    *
    * @throws GeneralSecurityException
@@ -204,4 +263,28 @@
       throw new GeneralSecurityException("empty keyset");
     }
   }
+
+  /**
+   * Returns a primitive from this keyset, using the global registry to create resources creating
+   * the primitive.
+   */
+  public <P> P getPrimitive(Class<P> classObject) throws GeneralSecurityException {
+    PrimitiveSet<P> primitiveSet = Registry.getPrimitives(this, classObject);
+    return Registry.wrap(primitiveSet);
+  }
+
+  /**
+   * Returns a primitive from this keyset, using the given {@code customKeyManager} and the global
+   * registry to get resources creating the primitive. The given keyManager will take precedence
+   * when creating primitives over the globally registered keyManagers.
+   */
+  @Alpha
+  public <P> P getPrimitive(KeyManager<P> customKeyManager, Class<P> classObject)
+      throws GeneralSecurityException {
+    if (customKeyManager == null) {
+      throw new IllegalArgumentException("customKeyManager must be non-null.");
+    }
+    PrimitiveSet<P> primitiveSet = Registry.getPrimitives(this, customKeyManager, classObject);
+    return Registry.wrap(primitiveSet);
+  }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/KeysetManager.java b/java/src/main/java/com/google/crypto/tink/KeysetManager.java
index e98330f..45703f0 100644
--- a/java/src/main/java/com/google/crypto/tink/KeysetManager.java
+++ b/java/src/main/java/com/google/crypto/tink/KeysetManager.java
@@ -65,8 +65,7 @@
   @GuardedBy("this")
   public synchronized KeysetManager rotate(KeyTemplate keyTemplate)
       throws GeneralSecurityException {
-    Keyset.Key key = newKey(keyTemplate);
-    keysetBuilder.addKey(key).setPrimaryKeyId(key.getKeyId());
+    addNewKey(keyTemplate, true);
     return this;
   }
 
@@ -78,11 +77,26 @@
    */
   @GuardedBy("this")
   public synchronized KeysetManager add(KeyTemplate keyTemplate) throws GeneralSecurityException {
-    keysetBuilder.addKey(newKey(keyTemplate));
+    addNewKey(keyTemplate, false);
     return this;
   }
 
   /**
+   * Generates a fresh key using {@code keyTemplate} and returns the {@code keyId} of it. In case
+   * {@isPrimary} is true the generated key will be the new primary.
+   */
+  @GuardedBy("this")
+  public synchronized int addNewKey(KeyTemplate keyTemplate, boolean asPrimary)
+      throws GeneralSecurityException {
+    Keyset.Key key = newKey(keyTemplate);
+    keysetBuilder.addKey(key);
+    if (asPrimary) {
+      keysetBuilder.setPrimaryKeyId(key.getKeyId());
+    }
+    return key.getKeyId();
+  }
+
+  /**
    * Sets the key with {@code keyId} as primary.
    *
    * @throws GeneralSecurityException if the key is not found or not enabled
diff --git a/java/src/main/java/com/google/crypto/tink/NoSecretKeysetHandle.java b/java/src/main/java/com/google/crypto/tink/NoSecretKeysetHandle.java
index f189ccf..ddae0ce 100644
--- a/java/src/main/java/com/google/crypto/tink/NoSecretKeysetHandle.java
+++ b/java/src/main/java/com/google/crypto/tink/NoSecretKeysetHandle.java
@@ -26,7 +26,9 @@
  * Static methods for reading cleartext keysets that don't contain any secret key material.
  *
  * @since 1.0.0
+ * @deprecated use {@link KeysetHandle#readNoSecret} instead
  */
+@Deprecated
 public final class NoSecretKeysetHandle {
   /**
    * @return a new keyset handle from {@code serialized} which is a serialized {@link Keyset}.
diff --git a/java/src/main/java/com/google/crypto/tink/PrimitiveSet.java b/java/src/main/java/com/google/crypto/tink/PrimitiveSet.java
index 4137c47..816e50f 100644
--- a/java/src/main/java/com/google/crypto/tink/PrimitiveSet.java
+++ b/java/src/main/java/com/google/crypto/tink/PrimitiveSet.java
@@ -127,9 +127,14 @@
       new ConcurrentHashMap<java.lang.String, List<Entry<P>>>();
 
   private Entry<P> primary;
+  private final Class<P> primitiveClass;
 
-  protected static <P> PrimitiveSet<P> newPrimitiveSet() {
-    return new PrimitiveSet<P>();
+  private PrimitiveSet(Class<P> primitiveClass) {
+    this.primitiveClass = primitiveClass;
+  }
+
+  public static <P> PrimitiveSet<P> newPrimitiveSet(Class<P> primitiveClass) {
+    return new PrimitiveSet<P>(primitiveClass);
   }
 
   /** @return the entries with primitives identified by the ciphertext prefix of {@code key}. */
@@ -138,7 +143,7 @@
   }
 
   /** Sets given Entry {@code primary} as the primary one. */
-  protected void setPrimary(final Entry<P> primary) {
+  public void setPrimary(final Entry<P> primary) {
     this.primary = primary;
   }
 
@@ -147,7 +152,7 @@
    *
    * @return the added entry
    */
-  protected Entry<P> addPrimitive(final P primitive, Keyset.Key key)
+  public Entry<P> addPrimitive(final P primitive, Keyset.Key key)
       throws GeneralSecurityException {
     Entry<P> entry =
         new Entry<P>(
@@ -168,4 +173,8 @@
     }
     return entry;
   }
+
+  public Class<P> getPrimitiveClass() {
+    return primitiveClass;
+  }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/PrimitiveWrapper.java b/java/src/main/java/com/google/crypto/tink/PrimitiveWrapper.java
new file mode 100644
index 0000000..248c0fd
--- /dev/null
+++ b/java/src/main/java/com/google/crypto/tink/PrimitiveWrapper.java
@@ -0,0 +1,42 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink;
+
+import java.security.GeneralSecurityException;
+
+/**
+ * Basic interface for wrapping a primitive.
+ *
+ * <p>A PrimitiveSet can be wrapped by a single primitive in order to fulfil a cryptographic task.
+ * This is done by the PrimitiveWrapper. Whenever a new primitive type is added to Tink, the user
+ * should define a new PrimitiveWrapper and register it by calling
+ * {@code com.google.crypto.tink.Registry#registerPrimitiveWrapper}.
+ */
+public interface PrimitiveWrapper<P> {
+  /**
+   * Wraps a {@code PrimitiveSet} and returns a single instance.
+   *
+   * This has to be implemented when a new primitive type is added. */
+  P wrap(PrimitiveSet<P> set) throws GeneralSecurityException;
+
+  /**
+   * Returns the primitive class object of the primitive managed. Used for internal management.
+   * Should be implemented as {@code return P.class;} when implementing a key manager for
+   * primitive {$code P}.
+   */
+  Class<P> getPrimitiveClass();
+}
diff --git a/java/src/main/java/com/google/crypto/tink/Registry.java b/java/src/main/java/com/google/crypto/tink/Registry.java
index 9bef9b2..28be54c 100644
--- a/java/src/main/java/com/google/crypto/tink/Registry.java
+++ b/java/src/main/java/com/google/crypto/tink/Registry.java
@@ -55,14 +55,12 @@
  * AeadConfig.register();
  * }</pre>
  *
- * <p>After the Registry has been initialized, one can use {@link
- * com.google.crypto.tink.aead.AeadFactory}, {@link com.google.crypto.tink.mac.MacFactory}, etc., to
- * obtain corresponding primitive instances. For example, here's how to obtain an {@link Aead}
- * primitive:
+ * <p>After the Registry has been initialized, one can use {@keysetHandle.getPrimitive} to get a
+ * primitive. For example, to obtain an {@link Aead} primitive:
  *
  * <pre>{@code
  * KeysetHandle keysetHandle = ...;
- * Aead aead = AeadFactory.getPrimitive(keysetHandle);
+ * Aead aead = keysetHandle.getPrimitive(Aead.class);
  * }</pre>
  *
  * @since 1.0.0
@@ -80,6 +78,10 @@
   @SuppressWarnings("rawtypes")
   private static final ConcurrentMap<String, Catalogue> catalogueMap =
       new ConcurrentHashMap<String, Catalogue>(); //  name -> catalogue mapping
+
+  private static final ConcurrentMap<Class<?>, PrimitiveWrapper<?>> primitiveWrapperMap =
+      new ConcurrentHashMap<Class<?>, PrimitiveWrapper<?>>();
+
   /**
    * Resets the registry.
    *
@@ -92,6 +94,7 @@
     keyManagerMap.clear();
     newKeyAllowedMap.clear();
     catalogueMap.clear();
+    primitiveWrapperMap.clear();
   }
 
   /**
@@ -104,8 +107,7 @@
    * @throws GeneralSecurityException if there's an existing catalogue is not an instance of the
    *     same class as {@code catalogue}
    */
-  @SuppressWarnings("unchecked")
-  public static synchronized <P> void addCatalogue(String catalogueName, Catalogue<P> catalogue)
+  public static synchronized void addCatalogue(String catalogueName, Catalogue<?> catalogue)
       throws GeneralSecurityException {
     if (catalogueName == null) {
       throw new IllegalArgumentException("catalogueName must be non-null.");
@@ -114,7 +116,7 @@
       throw new IllegalArgumentException("catalogue must be non-null.");
     }
     if (catalogueMap.containsKey(catalogueName.toLowerCase())) {
-      Catalogue<P> existing = catalogueMap.get(catalogueName.toLowerCase());
+      Catalogue<?> existing = catalogueMap.get(catalogueName.toLowerCase());
       if (!catalogue.getClass().equals(existing.getClass())) {
         logger.warning(
             "Attempted overwrite of a catalogueName catalogue for name " + catalogueName);
@@ -130,13 +132,12 @@
    *
    * @throws GeneralSecurityException if cannot find any catalogue
    */
-  public static <P> Catalogue<P> getCatalogue(String catalogueName)
+  public static Catalogue<?> getCatalogue(String catalogueName)
       throws GeneralSecurityException {
     if (catalogueName == null) {
       throw new IllegalArgumentException("catalogueName must be non-null.");
     }
-    @SuppressWarnings("unchecked")
-    Catalogue<P> catalogue = catalogueMap.get(catalogueName.toLowerCase());
+    Catalogue<?> catalogue = catalogueMap.get(catalogueName.toLowerCase());
     if (catalogue == null) {
       String error = String.format("no catalogue found for %s. ", catalogueName);
       if (catalogueName.toLowerCase().startsWith("tinkaead")) {
@@ -163,6 +164,16 @@
   }
 
   /**
+   * Helper method to check if an instance is not null; taken from guava's Precondition.java
+   */
+  private static <T> T checkNotNull(T reference) {
+    if (reference == null) {
+      throw new NullPointerException();
+    }
+    return reference;
+  }
+
+  /**
    * Tries to register {@code manager} for {@code manager.getKeyType()}. Users can generate new keys
    * with this manager using the {@link Registry#newKey} methods.
    *
@@ -258,11 +269,67 @@
   }
 
   /**
-   * @return a {@link KeyManager} for the given {@code typeUrl} (if found).
-   *     <p>TODO(przydatek): find a way for verifying the primitive type.
+   * Tries to register {@code wrapper} as a new SetWrapper for primitive {@code P}.
+   *
+   * <p>If no SetWrapper is registered for {@code P} registers the given one. If already is a
+   * SetWrapper registered which is of the same class ass the passed in set wrapper, the call is
+   * silently ignored. If the new set wrapper is of a different type, the call fails with a {@code
+   * GeneralSecurityException}.
+   *
+   * @throws GeneralSecurityException if there's an existing key manager is not an instance of the
+   *     class of {@code manager}, or the registration tries to re-enable the generation of new
+   *     keys.
    */
   @SuppressWarnings("unchecked")
+  public static synchronized <P> void registerPrimitiveWrapper(final PrimitiveWrapper<P> wrapper)
+      throws GeneralSecurityException {
+    if (wrapper == null) {
+      throw new IllegalArgumentException("wrapper must be non-null");
+    }
+    Class<P> classObject = wrapper.getPrimitiveClass();
+    if (primitiveWrapperMap.containsKey(classObject)) {
+      PrimitiveWrapper<P> existingWrapper =
+          (PrimitiveWrapper<P>) (primitiveWrapperMap.get(classObject));
+      if (!wrapper.getClass().equals(existingWrapper.getClass())) {
+        logger.warning(
+            "Attempted overwrite of a registered SetWrapper for type " + classObject.toString());
+        throw new GeneralSecurityException(
+            String.format(
+                "SetWrapper for primitive (%s) is already registered to be %s, "
+                    + "cannot be re-registered with %s",
+                classObject.getName(),
+                existingWrapper.getClass().getName(),
+                wrapper.getClass().getName()));
+      }
+    }
+    primitiveWrapperMap.put(classObject, wrapper);
+  }
+
+  /**
+   * @return a {@link KeyManager} for the given {@code typeUrl} (if found).
+   * @deprecated Use {@code getKeyManager(typeUrl, Primitive.class)} or
+   * {@code getUntypedKeyManager typeUrl} instead.
+   */
+  @Deprecated
   public static <P> KeyManager<P> getKeyManager(String typeUrl) throws GeneralSecurityException {
+    return getKeyManagerInternal(typeUrl, null);
+  }
+
+  /** @return a {@link KeyManager} for the given {@code typeUrl} (if found). */
+  public static KeyManager<?> getUntypedKeyManager(String typeUrl)
+      throws GeneralSecurityException {
+    return getKeyManagerInternal(typeUrl, null);
+  }
+
+  /** @return a {@link KeyManager} for the given {@code typeUrl} (if found). */
+  public static <P> KeyManager<P> getKeyManager(String typeUrl, Class<P> primitiveClass)
+      throws GeneralSecurityException {
+    return getKeyManagerInternal(typeUrl, checkNotNull(primitiveClass));
+  }
+
+  @SuppressWarnings("unchecked")
+  private static <P> KeyManager<P> getKeyManagerInternal(String typeUrl, Class<P> primitiveClass)
+      throws GeneralSecurityException {
     KeyManager<P> manager = keyManagerMap.get(typeUrl);
     if (manager == null) {
       throw new GeneralSecurityException(
@@ -270,6 +337,15 @@
               + typeUrl
               + ".  Check the configuration of the registry.");
     }
+    if (primitiveClass != null && !manager.getPrimitiveClass().equals(primitiveClass)) {
+      throw new GeneralSecurityException(
+          "Primitive type "
+              + manager.getPrimitiveClass().getName()
+              + " of keymanager for type "
+              + typeUrl
+              + " does not match requested primitive type "
+              + primitiveClass.getName());
+    }
     return manager;
   }
 
@@ -283,9 +359,9 @@
    *
    * @return a new {@link KeyData}
    */
-  public static synchronized <P> KeyData newKeyData(KeyTemplate keyTemplate)
+  public static synchronized KeyData newKeyData(KeyTemplate keyTemplate)
       throws GeneralSecurityException {
-    KeyManager<P> manager = getKeyManager(keyTemplate.getTypeUrl());
+    KeyManager<?> manager = getKeyManager(keyTemplate.getTypeUrl());
     if (newKeyAllowedMap.get(keyTemplate.getTypeUrl()).booleanValue()) {
       return manager.newKeyData(keyTemplate.getValue());
     } else {
@@ -302,9 +378,9 @@
    *
    * @return a new key
    */
-  public static synchronized <P> MessageLite newKey(KeyTemplate keyTemplate)
+  public static synchronized MessageLite newKey(KeyTemplate keyTemplate)
       throws GeneralSecurityException {
-    KeyManager<P> manager = getKeyManager(keyTemplate.getTypeUrl());
+    KeyManager<?> manager = getKeyManager(keyTemplate.getTypeUrl());
     if (newKeyAllowedMap.get(keyTemplate.getTypeUrl()).booleanValue()) {
       return manager.newKey(keyTemplate.getValue());
     } else {
@@ -321,9 +397,9 @@
    *
    * @return a new key
    */
-  public static synchronized <P> MessageLite newKey(String typeUrl, MessageLite format)
+  public static synchronized MessageLite newKey(String typeUrl, MessageLite format)
       throws GeneralSecurityException {
-    KeyManager<P> manager = getKeyManager(typeUrl);
+    KeyManager<?> manager = getKeyManager(typeUrl);
     if (newKeyAllowedMap.get(typeUrl).booleanValue()) {
       return manager.newKey(format);
     } else {
@@ -340,11 +416,14 @@
    *
    * @return a new key
    */
-  @SuppressWarnings("unchecked")
-  public static <P> KeyData getPublicKeyData(String typeUrl, ByteString serializedPrivateKey)
+  public static KeyData getPublicKeyData(String typeUrl, ByteString serializedPrivateKey)
       throws GeneralSecurityException {
-    PrivateKeyManager<P> manager = (PrivateKeyManager) getKeyManager(typeUrl);
-    return manager.getPublicKeyData(serializedPrivateKey);
+    KeyManager<?> manager = getKeyManager(typeUrl);
+    if (!(manager instanceof PrivateKeyManager)) {
+      throw new GeneralSecurityException(
+          "manager for key type " + typeUrl + " is not a PrivateKeyManager");
+    }
+    return ((PrivateKeyManager) manager).getPublicKeyData(serializedPrivateKey);
   }
 
   /**
@@ -354,11 +433,31 @@
    * KeyManager#getPrimitive} with {@code key} as the parameter.
    *
    * @return a new primitive
+   * @deprecated Use {@code getPrimitive(typeUrl, key, P.class)} instead.
    */
+  @Deprecated
   @SuppressWarnings("TypeParameterUnusedInFormals")
   public static <P> P getPrimitive(String typeUrl, MessageLite key)
       throws GeneralSecurityException {
-    KeyManager<P> manager = getKeyManager(typeUrl);
+    return getPrimitiveInternal(typeUrl, key, null);
+  }
+
+  /**
+   * Convenience method for creating a new primitive for the key given in {@code key}.
+   *
+   * <p>It looks up a {@link KeyManager} identified by {@code type_url}, and calls {@link
+   * KeyManager#getPrimitive} with {@code key} as the parameter.
+   *
+   * @return a new primitive
+   */
+  public static <P> P getPrimitive(String typeUrl, MessageLite key, Class<P> primitiveClass)
+      throws GeneralSecurityException {
+    return getPrimitiveInternal(typeUrl, key, checkNotNull(primitiveClass));
+  }
+
+  private static <P> P getPrimitiveInternal(
+      String typeUrl, MessageLite key, Class<P> primitiveClass) throws GeneralSecurityException {
+    KeyManager<P> manager = getKeyManagerInternal(typeUrl, primitiveClass);
     return manager.getPrimitive(key);
   }
 
@@ -366,42 +465,110 @@
    * Convenience method for creating a new primitive for the key given in {@code proto}.
    *
    * <p>It looks up a {@link KeyManager} identified by {@code type_url}, and calls {@link
-   * KeyManager#getPrimitive} with {@code serialized} as the parameter.
+   * KeyManager#getPrimitive} with {@code serializedKey} as the parameter.
    *
    * @return a new primitive
+   * @deprecated Use {@code getPrimitive(typeUrl, serializedKey, Primitive.class} instead.
    */
+  @Deprecated
   @SuppressWarnings("TypeParameterUnusedInFormals")
-  public static <P> P getPrimitive(String typeUrl, ByteString serialized)
+  public static <P> P getPrimitive(String typeUrl, ByteString serializedKey)
       throws GeneralSecurityException {
-    KeyManager<P> manager = getKeyManager(typeUrl);
-    return manager.getPrimitive(serialized);
+    return getPrimitiveInternal(typeUrl, serializedKey, null);
   }
 
   /**
-   * Convenience method for creating a new primitive for the key given in {@code proto}.
+   * Convenience method for creating a new primitive for the key given in {@code serializedKey}.
    *
    * <p>It looks up a {@link KeyManager} identified by {@code type_url}, and calls {@link
    * KeyManager#getPrimitive} with {@code serialized} as the parameter.
    *
    * @return a new primitive
    */
-  @SuppressWarnings("TypeParameterUnusedInFormals")
-  public static <P> P getPrimitive(String typeUrl, byte[] serialized)
+  public static <P> P getPrimitive(
+      String typeUrl, ByteString serializedKey, Class<P> primitiveClass)
       throws GeneralSecurityException {
-    return getPrimitive(typeUrl, ByteString.copyFrom(serialized));
+    return getPrimitiveInternal(typeUrl, serializedKey, checkNotNull(primitiveClass));
+  }
+
+  private static <P> P getPrimitiveInternal(
+      String typeUrl, ByteString serializedKey, Class<P> primitiveClass)
+      throws GeneralSecurityException {
+    KeyManager<P> manager = getKeyManagerInternal(typeUrl, primitiveClass);
+    return manager.getPrimitive(serializedKey);
   }
 
   /**
-   * Convenience method for creating a new primitive for the key given in {@code proto}.
+   * Convenience method for creating a new primitive for the key given in {@code serializedKey}.
+   *
+   * <p>It looks up a {@link KeyManager} identified by {@code type_url}, and calls {@link
+   * KeyManager#getPrimitive} with {@code serialized} as the parameter.
+   *
+   * @deprecated Use {@code getPrimitive(typeUrl, serializedKey, Primitive.class)} instead.
+   * @return a new primitive
+   */
+  @Deprecated
+  @SuppressWarnings("TypeParameterUnusedInFormals")
+  public static <P> P getPrimitive(String typeUrl, byte[] serializedKey)
+      throws GeneralSecurityException {
+    return getPrimitive(typeUrl, ByteString.copyFrom(serializedKey));
+  }
+  /**
+   * Convenience method for creating a new primitive for the key given in {@code serializedKey}.
+   *
+   * <p>It looks up a {@link KeyManager} identified by {@code type_url}, and calls {@link
+   * KeyManager#getPrimitive} with {@code serialized} as the parameter.
+   *
+   * @return a new primitive
+   */
+  public static <P> P getPrimitive(String typeUrl, byte[] serializedKey, Class<P> primitiveClass)
+      throws GeneralSecurityException {
+    return getPrimitive(typeUrl, ByteString.copyFrom(serializedKey), primitiveClass);
+  }
+
+  /**
+   * Convenience method for creating a new primitive for the key given in {@code keyData}.
+   *
+   * <p>It looks up a {@link KeyManager} identified by {@code keyData.type_url}, and calls {@link
+   * KeyManager#getPrimitive} with {@code keyData.value} as the parameter.
+   *
+   * @return a new primitive
+   * @deprecated Use {@code getPrimitive(keyData, Primitive.class)} instead.
+   */
+  @Deprecated
+  @SuppressWarnings("TypeParameterUnusedInFormals")
+  public static <P> P getPrimitive(KeyData keyData) throws GeneralSecurityException {
+    return getPrimitive(keyData.getTypeUrl(), keyData.getValue());
+  }
+
+  /**
+   * Convenience method for creating a new primitive for the key given in {@code keyData}.
    *
    * <p>It looks up a {@link KeyManager} identified by {@code keyData.type_url}, and calls {@link
    * KeyManager#getPrimitive} with {@code keyData.value} as the parameter.
    *
    * @return a new primitive
    */
-  @SuppressWarnings("TypeParameterUnusedInFormals")
-  public static <P> P getPrimitive(KeyData keyData) throws GeneralSecurityException {
-    return getPrimitive(keyData.getTypeUrl(), keyData.getValue());
+  public static <P> P getPrimitive(KeyData keyData, Class<P> primitiveClass)
+      throws GeneralSecurityException {
+    return getPrimitive(keyData.getTypeUrl(), keyData.getValue(), primitiveClass);
+  }
+
+  /**
+   * Creates a set of primitives corresponding to the keys with status=ENABLED in the keyset given
+   * in {@code keysetHandle}, assuming all the corresponding key managers are present (keys with
+   * status!=ENABLED are skipped).
+   *
+   * <p>The returned set is usually later "wrapped" into a class that implements the corresponding
+   * Primitive-interface.
+   *
+   * @return a PrimitiveSet with all instantiated primitives
+   * @deprecated Use {@code getPrimitives(keysetHandle, Primitive.class)} instead.
+   */
+  @Deprecated
+  public static <P> PrimitiveSet<P> getPrimitives(KeysetHandle keysetHandle)
+      throws GeneralSecurityException {
+    return getPrimitives(keysetHandle, /* customManager= */ (KeyManager<P>) null);
   }
 
   /**
@@ -414,9 +581,32 @@
    *
    * @return a PrimitiveSet with all instantiated primitives
    */
-  public static <P> PrimitiveSet<P> getPrimitives(KeysetHandle keysetHandle)
+  public static <P> PrimitiveSet<P> getPrimitives(
+      KeysetHandle keysetHandle, Class<P> primitiveClass) throws GeneralSecurityException {
+    return getPrimitives(keysetHandle, /* customManager= */ null, primitiveClass);
+  }
+
+  /**
+   * Creates a set of primitives corresponding to the keys with status=ENABLED in the keyset given
+   * in {@code keysetHandle}, using {@code customManager} (instead of registered key managers) for
+   * keys supported by it. Keys not supported by {@code customManager} are handled by matching
+   * registered key managers (if present), and keys with status!=ENABLED are skipped.
+   *
+   * <p>This enables custom treatment of keys, for example providing extra context (e.g.,
+   * credentials for accessing keys managed by a KMS), or gathering custom monitoring/profiling
+   * information.
+   *
+   * <p>The returned set is usually later "wrapped" into a class that implements the corresponding
+   * Primitive-interface.
+   *
+   * @return a PrimitiveSet with all instantiated primitives
+   * @deprecated Use {@code getPrimitives(keysetHandle, customManager, Primitive.class)} instead.
+   */
+  @Deprecated
+  public static <P> PrimitiveSet<P> getPrimitives(
+      KeysetHandle keysetHandle, final KeyManager<P> customManager)
       throws GeneralSecurityException {
-    return getPrimitives(keysetHandle, /* customManager= */ null);
+    return getPrimitivesInternal(keysetHandle, customManager, null);
   }
 
   /**
@@ -435,17 +625,25 @@
    * @return a PrimitiveSet with all instantiated primitives
    */
   public static <P> PrimitiveSet<P> getPrimitives(
-      KeysetHandle keysetHandle, final KeyManager<P> customManager)
+      KeysetHandle keysetHandle, final KeyManager<P> customManager, Class<P> primitiveClass)
+      throws GeneralSecurityException {
+    return getPrimitivesInternal(keysetHandle, customManager, checkNotNull(primitiveClass));
+  }
+
+  private static <P> PrimitiveSet<P> getPrimitivesInternal(
+      KeysetHandle keysetHandle, final KeyManager<P> customManager, Class<P> primitiveClass)
       throws GeneralSecurityException {
     Util.validateKeyset(keysetHandle.getKeyset());
-    PrimitiveSet<P> primitives = PrimitiveSet.newPrimitiveSet();
+    PrimitiveSet<P> primitives = PrimitiveSet.newPrimitiveSet(primitiveClass);
     for (Keyset.Key key : keysetHandle.getKeyset().getKeyList()) {
       if (key.getStatus() == KeyStatusType.ENABLED) {
         P primitive;
         if (customManager != null && customManager.doesSupport(key.getKeyData().getTypeUrl())) {
           primitive = customManager.getPrimitive(key.getKeyData().getValue());
         } else {
-          primitive = getPrimitive(key.getKeyData().getTypeUrl(), key.getKeyData().getValue());
+          primitive =
+              getPrimitiveInternal(
+                  key.getKeyData().getTypeUrl(), key.getKeyData().getValue(), primitiveClass);
         }
         PrimitiveSet.Entry<P> entry = primitives.addPrimitive(primitive, key);
         if (key.getKeyId() == keysetHandle.getKeyset().getPrimaryKeyId()) {
@@ -455,4 +653,20 @@
     }
     return primitives;
   }
+
+  /**
+   * Looks up the globally registered PrimitiveWrapper for this primitive and wraps the given
+   * PrimitiveSet with it.
+   */
+  public static <P> P wrap(PrimitiveSet<P> primitiveSet)
+      throws GeneralSecurityException {
+    @SuppressWarnings("unchecked") // We know that we only inserted Class<P> -> PrimitiveWrapper<P>
+    PrimitiveWrapper<P> wrapper =
+        (PrimitiveWrapper<P>) primitiveWrapperMap.get(primitiveSet.getPrimitiveClass());
+    if (wrapper == null) {
+      throw new GeneralSecurityException(
+          "No wrapper found for " + primitiveSet.getPrimitiveClass().getName());
+    }
+    return wrapper.wrap(primitiveSet);
+  }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/Util.java b/java/src/main/java/com/google/crypto/tink/Util.java
index 3b55a00..21372e8 100644
--- a/java/src/main/java/com/google/crypto/tink/Util.java
+++ b/java/src/main/java/com/google/crypto/tink/Util.java
@@ -77,14 +77,15 @@
    * @throws GeneralSecurityException if {@code keyset} is invalid.
    */
   public static void validateKeyset(Keyset keyset) throws GeneralSecurityException {
-    if (keyset.getKeyCount() == 0) {
-      throw new GeneralSecurityException("empty keyset");
-    }
-
     int primaryKeyId = keyset.getPrimaryKeyId();
     boolean hasPrimaryKey = false;
     boolean containsOnlyPublicKeyMaterial = true;
+    int nonDestroyedKeys = 0;
     for (Keyset.Key key : keyset.getKeyList()) {
+      if (key.getStatus() == KeyStatusType.DESTROYED) {
+        continue;
+      }
+      ++nonDestroyedKeys;
       validateKey(key);
       if (key.getStatus() == KeyStatusType.ENABLED && key.getKeyId() == primaryKeyId) {
         if (hasPrimaryKey) {
@@ -97,6 +98,9 @@
       }
       // TODO(thaidn): use TypeLiteral to ensure that all keys are of the same primitive.
     }
+    if (nonDestroyedKeys == 0) {
+      throw new GeneralSecurityException("empty keyset");
+    }
     if (!hasPrimaryKey && !containsOnlyPublicKeyMaterial) {
       throw new GeneralSecurityException("keyset doesn't contain a valid primary key");
     }
diff --git a/go/tink/private_key_manager.go b/java/src/main/java/com/google/crypto/tink/Version.java.templ
similarity index 64%
copy from go/tink/private_key_manager.go
copy to java/src/main/java/com/google/crypto/tink/Version.java.templ
index 7cf169a..fef9bf8 100644
--- a/go/tink/private_key_manager.go
+++ b/java/src/main/java/com/google/crypto/tink/Version.java.templ
@@ -1,3 +1,5 @@
+// Copyright 2018 Google Inc.
+//
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
@@ -12,16 +14,16 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-package tink
+package com.google.crypto.tink;
 
-import (
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
-)
-
-// PrivateKeyManager is a special type of KeyManager that understands private key types.
-type PrivateKeyManager interface {
-	KeyManager
-
-	// GetPublicKeyData extracts the public key data from the private key.
-	GetPublicKeyData(serializedKey []byte) (*tinkpb.KeyData, error)
+/**
+ * Version of the Tink library.
+ *
+ * @since 1.2.1
+ */
+public final class Version {
+  /**
+   * Version of the current Tink release.
+   */
+  public static final String TINK_VERSION = "TINK_VERSION_LABEL";
 }
diff --git a/java/src/main/java/com/google/crypto/tink/aead/AeadCatalogue.java b/java/src/main/java/com/google/crypto/tink/aead/AeadCatalogue.java
index 12433a4..a4aa607 100644
--- a/java/src/main/java/com/google/crypto/tink/aead/AeadCatalogue.java
+++ b/java/src/main/java/com/google/crypto/tink/aead/AeadCatalogue.java
@@ -19,6 +19,7 @@
 import com.google.crypto.tink.Aead;
 import com.google.crypto.tink.Catalogue;
 import com.google.crypto.tink.KeyManager;
+import com.google.crypto.tink.PrimitiveWrapper;
 import java.security.GeneralSecurityException;
 
 /** A catalogue of {@link Aead} key managers. */
@@ -63,9 +64,16 @@
         return new KmsAeadKeyManager();
       case KmsEnvelopeAeadKeyManager.TYPE_URL:
         return new KmsEnvelopeAeadKeyManager();
+      case XChaCha20Poly1305KeyManager.TYPE_URL:
+        return new XChaCha20Poly1305KeyManager();
       default:
         throw new GeneralSecurityException(
             String.format("No support for primitive 'Aead' with key type '%s'.", typeUrl));
     }
   }
+
+  @Override
+  public PrimitiveWrapper<Aead> getPrimitiveWrapper() {
+    return new AeadWrapper();
+  }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/aead/AeadConfig.java b/java/src/main/java/com/google/crypto/tink/aead/AeadConfig.java
index 2a2280f..422b98a 100644
--- a/java/src/main/java/com/google/crypto/tink/aead/AeadConfig.java
+++ b/java/src/main/java/com/google/crypto/tink/aead/AeadConfig.java
@@ -32,8 +32,6 @@
  * AeadConfig.register();
  * }</pre>
  *
- * <p>For more information on how to obtain and use instances of Aead, see {@link AeadFactory}.
- *
  * @since 1.0.0
  */
 public final class AeadConfig {
@@ -43,6 +41,7 @@
   public static final String KMS_AEAD_TYPE_URL = KmsAeadKeyManager.TYPE_URL;
   public static final String KMS_ENVELOPE_AEAD_TYPE_URL = KmsEnvelopeAeadKeyManager.TYPE_URL;
   public static final String CHACHA20_POLY1305_TYPE_URL = ChaCha20Poly1305KeyManager.TYPE_URL;
+  public static final String XCHACHA20_POLY1305_TYPE_URL = XChaCha20Poly1305KeyManager.TYPE_URL;
 
   private static final String CATALOGUE_NAME = "TinkAead";
   private static final String PRIMITIVE_NAME = "Aead";
@@ -97,6 +96,9 @@
           .addEntry(
               Config.getTinkKeyTypeEntry(
                   CATALOGUE_NAME, PRIMITIVE_NAME, "KmsEnvelopeAeadKey", 0, true))
+          .addEntry(
+              Config.getTinkKeyTypeEntry(
+                  CATALOGUE_NAME, PRIMITIVE_NAME, "XChaCha20Poly1305Key", 0, true))
           .setConfigName("TINK_AEAD")
           .build();
 
diff --git a/java/src/main/java/com/google/crypto/tink/aead/AeadFactory.java b/java/src/main/java/com/google/crypto/tink/aead/AeadFactory.java
index 3c06195..d5475b1 100644
--- a/java/src/main/java/com/google/crypto/tink/aead/AeadFactory.java
+++ b/java/src/main/java/com/google/crypto/tink/aead/AeadFactory.java
@@ -17,46 +17,37 @@
 package com.google.crypto.tink.aead;
 
 import com.google.crypto.tink.Aead;
-import com.google.crypto.tink.CryptoFormat;
 import com.google.crypto.tink.KeyManager;
 import com.google.crypto.tink.KeysetHandle;
 import com.google.crypto.tink.PrimitiveSet;
 import com.google.crypto.tink.Registry;
-import com.google.crypto.tink.subtle.Bytes;
 import java.security.GeneralSecurityException;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.logging.Logger;
 
 /**
- * Static methods for obtaining {@link Aead} instances.
+ * Deprecated class to create {@code Aead} primitives. Instead of using this class, make sure that
+ * the {@code AeadWrapper} is registered in your binary, then call {@code
+ * keysetHandle.GetPrimitive(Aead.class)} instead. The required registration happens automatically
+ * if you called one of the following in your binary:
  *
- * <h3>Usage</h3>
+ * <ul>
+ *   <li>{@code HybridConfig.register()}
+ *   <li>{@code AeadConfig.register()}
+ *   <li>{@code TinkConfig.register()}
+ * </ul>
  *
- * <pre>{@code
- * KeysetHandle keysetHandle = ...;
- * Aead aead = AeadFactory.getPrimitive(keysetHandle);
- * byte[] plaintext = ...;
- * byte[] aad = ...;
- * byte[] ciphertext = aead.encrypt(plaintext, aad);
- * }</pre>
- *
- * <p>The returned primitive works with a keyset (rather than a single key). To encrypt a plaintext,
- * it uses the primary key in the keyset, and prepends to the ciphertext a certain prefix associated
- * with the primary key. To decrypt, the primitive uses the prefix of the ciphertext to efficiently
- * select the right key in the set. If the keys associated with the prefix do not work, the
- * primitive tries all keys with {@link com.google.crypto.tink.proto.OutputPrefixType#RAW}.
- *
+ * @deprecated Use {@code keysetHandle.GetPrimitive(Aead.class)} after registering the {@code
+ *     AeadWrapper} instead.
  * @since 1.0.0
  */
+@Deprecated
 public final class AeadFactory {
-  private static final Logger logger = Logger.getLogger(AeadFactory.class.getName());
-
   /**
    * @return a Aead primitive from a {@code keysetHandle}.
    * @throws GeneralSecurityException
+   * @deprecated Use {@code keysetHandle.GetPrimitive(Aead.class)} after registering the {@code
+   *     AeadWrapper} instead.
    */
+  @Deprecated
   public static Aead getPrimitive(KeysetHandle keysetHandle) throws GeneralSecurityException {
     return getPrimitive(keysetHandle, /* keyManager= */ null);
   }
@@ -64,61 +55,14 @@
   /**
    * @return a Aead primitive from a {@code keysetHandle} and a custom {@code keyManager}.
    * @throws GeneralSecurityException
+   * @deprecated Use {@code keysetHandle.GetPrimitive(keyManager, Aead.class)} after registering the
+   *     {@code AeadWrapper} instead.
    */
+  @Deprecated
   public static Aead getPrimitive(KeysetHandle keysetHandle, final KeyManager<Aead> keyManager)
       throws GeneralSecurityException {
-    final PrimitiveSet<Aead> pset = Registry.getPrimitives(keysetHandle, keyManager);
-    validate(pset);
-    return new Aead() {
-      @Override
-      public byte[] encrypt(final byte[] plaintext, final byte[] associatedData)
-          throws GeneralSecurityException {
-        return Bytes.concat(
-            pset.getPrimary().getIdentifier(),
-            pset.getPrimary().getPrimitive().encrypt(plaintext, associatedData));
-      }
-
-      @Override
-      public byte[] decrypt(final byte[] ciphertext, final byte[] associatedData)
-          throws GeneralSecurityException {
-        if (ciphertext.length > CryptoFormat.NON_RAW_PREFIX_SIZE) {
-          byte[] prefix = Arrays.copyOfRange(ciphertext, 0, CryptoFormat.NON_RAW_PREFIX_SIZE);
-          byte[] ciphertextNoPrefix =
-              Arrays.copyOfRange(ciphertext, CryptoFormat.NON_RAW_PREFIX_SIZE, ciphertext.length);
-          List<PrimitiveSet.Entry<Aead>> entries = pset.getPrimitive(prefix);
-          for (PrimitiveSet.Entry<Aead> entry : entries) {
-            try {
-              return entry.getPrimitive().decrypt(ciphertextNoPrefix, associatedData);
-            } catch (GeneralSecurityException e) {
-              logger.info("ciphertext prefix matches a key, but cannot decrypt: " + e.toString());
-              continue;
-            }
-          }
-        }
-
-        // Let's try all RAW keys.
-        List<PrimitiveSet.Entry<Aead>> entries = pset.getRawPrimitives();
-        for (PrimitiveSet.Entry<Aead> entry : entries) {
-          try {
-            return entry.getPrimitive().decrypt(ciphertext, associatedData);
-          } catch (GeneralSecurityException e) {
-            continue;
-          }
-        }
-        // nothing works.
-        throw new GeneralSecurityException("decryption failed");
-      }
-    };
-  }
-
-  // Check that all primitives in <code>pset</code> are Aead instances.
-  private static void validate(final PrimitiveSet<Aead> pset) throws GeneralSecurityException {
-    for (Collection<PrimitiveSet.Entry<Aead>> entries : pset.getAll()) {
-      for (PrimitiveSet.Entry<Aead> entry : entries) {
-        if (!(entry.getPrimitive() instanceof Aead)) {
-          throw new GeneralSecurityException("invalid AEAD key material");
-        }
-      }
-    }
+    Registry.registerPrimitiveWrapper(new AeadWrapper());
+    PrimitiveSet<Aead> aeadSet = Registry.getPrimitives(keysetHandle, keyManager, Aead.class);
+    return Registry.wrap(aeadSet);
   }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/aead/AeadKeyTemplates.java b/java/src/main/java/com/google/crypto/tink/aead/AeadKeyTemplates.java
index 84ec050..d9aeb01 100644
--- a/java/src/main/java/com/google/crypto/tink/aead/AeadKeyTemplates.java
+++ b/java/src/main/java/com/google/crypto/tink/aead/AeadKeyTemplates.java
@@ -40,7 +40,7 @@
  * <pre>{@code
  * Config.register(AeadConfig.TINK_1_1_0);
  * KeysetHandle handle = KeysetHandle.generateNew(AeadKeyTemplates.AES128_GCM);
- * Aead aead = AeadFactory.getPrimitive(handle);
+ * Aead aead = handle.getPrimitive(Aead.class);
  * }</pre>
  *
  * @since 1.0.0
@@ -139,6 +139,18 @@
           .build();
 
   /**
+   * A {@link KeyTemplate} that generates new instances of {@link
+   * com.google.crypto.tink.proto.XChaCha20Poly1305Key}.
+   *
+   * @since 1.3.0
+   */
+  public static final KeyTemplate XCHACHA20_POLY1305 =
+      KeyTemplate.newBuilder()
+          .setTypeUrl(XChaCha20Poly1305KeyManager.TYPE_URL)
+          .setOutputPrefixType(OutputPrefixType.TINK)
+          .build();
+
+  /**
    * @return a {@link KeyTemplate} containing a {@link AesGcmKeyFormat} with some specified
    *     parameters.
    */
diff --git a/java/src/main/java/com/google/crypto/tink/aead/AeadWrapper.java b/java/src/main/java/com/google/crypto/tink/aead/AeadWrapper.java
new file mode 100644
index 0000000..0a0f216
--- /dev/null
+++ b/java/src/main/java/com/google/crypto/tink/aead/AeadWrapper.java
@@ -0,0 +1,95 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.aead;
+
+import com.google.crypto.tink.Aead;
+import com.google.crypto.tink.CryptoFormat;
+import com.google.crypto.tink.PrimitiveSet;
+import com.google.crypto.tink.PrimitiveWrapper;
+import com.google.crypto.tink.subtle.Bytes;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Logger;
+
+/**
+ * AeadWrapper is the implementation of SetWrapper for the Aead primitive.
+ *
+ * <p>Key rotation works as follows: each ciphertext is prefixed with the keyId. When decrypting, we
+ * first try all primitives whose keyId starts with the prefix of the ciphertext. If none of these
+ * succeed, we try the raw primitives. If any succeeds, we return the ciphertext, otherwise we
+ * simply throw a GeneralSecurityException.
+ */
+public class AeadWrapper implements PrimitiveWrapper<Aead> {
+  private static final Logger logger = Logger.getLogger(AeadWrapper.class.getName());
+
+  private static class WrappedAead implements Aead {
+    private final PrimitiveSet<Aead> pSet;
+    private WrappedAead(PrimitiveSet<Aead> pSet) {
+      this.pSet = pSet;
+    }
+
+    @Override
+    public byte[] encrypt(final byte[] plaintext, final byte[] associatedData)
+        throws GeneralSecurityException {
+      return Bytes.concat(
+          pSet.getPrimary().getIdentifier(),
+          pSet.getPrimary().getPrimitive().encrypt(plaintext, associatedData));
+    }
+
+    @Override
+    public byte[] decrypt(final byte[] ciphertext, final byte[] associatedData)
+        throws GeneralSecurityException {
+      if (ciphertext.length > CryptoFormat.NON_RAW_PREFIX_SIZE) {
+        byte[] prefix = Arrays.copyOfRange(ciphertext, 0, CryptoFormat.NON_RAW_PREFIX_SIZE);
+        byte[] ciphertextNoPrefix =
+            Arrays.copyOfRange(ciphertext, CryptoFormat.NON_RAW_PREFIX_SIZE, ciphertext.length);
+        List<PrimitiveSet.Entry<Aead>> entries = pSet.getPrimitive(prefix);
+        for (PrimitiveSet.Entry<Aead> entry : entries) {
+          try {
+            return entry.getPrimitive().decrypt(ciphertextNoPrefix, associatedData);
+          } catch (GeneralSecurityException e) {
+            logger.info("ciphertext prefix matches a key, but cannot decrypt: " + e.toString());
+            continue;
+          }
+        }
+      }
+
+      // Let's try all RAW keys.
+      List<PrimitiveSet.Entry<Aead>> entries = pSet.getRawPrimitives();
+      for (PrimitiveSet.Entry<Aead> entry : entries) {
+        try {
+          return entry.getPrimitive().decrypt(ciphertext, associatedData);
+        } catch (GeneralSecurityException e) {
+          continue;
+        }
+      }
+      // nothing works.
+      throw new GeneralSecurityException("decryption failed");
+    }
+  }
+
+  @Override
+  public Aead wrap(final PrimitiveSet<Aead> pset) throws GeneralSecurityException {
+    return new WrappedAead(pset);
+  }
+
+  @Override
+  public Class<Aead> getPrimitiveClass() {
+    return Aead.class;
+  }
+}
diff --git a/java/src/main/java/com/google/crypto/tink/aead/AesCtrHmacAeadKeyManager.java b/java/src/main/java/com/google/crypto/tink/aead/AesCtrHmacAeadKeyManager.java
index 8253cdc..b1006f0 100644
--- a/java/src/main/java/com/google/crypto/tink/aead/AesCtrHmacAeadKeyManager.java
+++ b/java/src/main/java/com/google/crypto/tink/aead/AesCtrHmacAeadKeyManager.java
@@ -17,7 +17,7 @@
 package com.google.crypto.tink.aead;
 
 import com.google.crypto.tink.Aead;
-import com.google.crypto.tink.KeyManager;
+import com.google.crypto.tink.KeyManagerBase;
 import com.google.crypto.tink.Mac;
 import com.google.crypto.tink.Registry;
 import com.google.crypto.tink.mac.MacConfig;
@@ -25,91 +25,44 @@
 import com.google.crypto.tink.proto.AesCtrHmacAeadKeyFormat;
 import com.google.crypto.tink.proto.AesCtrKey;
 import com.google.crypto.tink.proto.HmacKey;
-import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
 import com.google.crypto.tink.subtle.EncryptThenAuthenticate;
 import com.google.crypto.tink.subtle.IndCpaCipher;
 import com.google.crypto.tink.subtle.Validators;
 import com.google.protobuf.ByteString;
 import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
 import java.security.GeneralSecurityException;
-import java.util.logging.Logger;
 
 /**
- * This key manager generates new {@link AesCtrHmacAeadKey} keys and produces new instances
- * of {@link EncryptThenAuthenticate}.
+ * This key manager generates new {@link AesCtrHmacAeadKey} keys and produces new instances of
+ * {@link EncryptThenAuthenticate}.
  */
-class AesCtrHmacAeadKeyManager implements KeyManager<Aead> {
-  AesCtrHmacAeadKeyManager() throws GeneralSecurityException {
+class AesCtrHmacAeadKeyManager
+    extends KeyManagerBase<Aead, AesCtrHmacAeadKey, AesCtrHmacAeadKeyFormat> {
+  public AesCtrHmacAeadKeyManager() throws GeneralSecurityException {
+    super(Aead.class, AesCtrHmacAeadKey.class, AesCtrHmacAeadKeyFormat.class, TYPE_URL);
     Registry.registerKeyManager(new AesCtrKeyManager());
   }
 
-  private static final Logger logger =
-      Logger.getLogger(AesCtrHmacAeadKeyManager.class.getName());
-
   private static final int VERSION = 0;
 
-  public static final String TYPE_URL =
-      "type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey";
+  public static final String TYPE_URL = "type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey";
 
-  /**
-   * @param serializedKey  serialized {@code AesCtrHmacAeadKey} proto
-   */
   @Override
-  public Aead getPrimitive(ByteString serializedKey) throws GeneralSecurityException {
-    try {
-      AesCtrHmacAeadKey keyProto = AesCtrHmacAeadKey.parseFrom(serializedKey);
-      return getPrimitive(keyProto);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("expected serialized AesCtrHmacAeadKey proto", e);
-    }
-  }
-
-  /**
-   * @param key  {@code AesCtrHmacAeadKey} proto
-   */
-  @Override
-  public Aead getPrimitive(MessageLite key) throws GeneralSecurityException {
-    if (!(key instanceof AesCtrHmacAeadKey)) {
-      throw new GeneralSecurityException("expected AesCtrHmacAeadKey proto");
-    }
-    AesCtrHmacAeadKey keyProto = (AesCtrHmacAeadKey) key;
-    validate(keyProto);
+  public Aead getPrimitiveFromKey(AesCtrHmacAeadKey keyProto) throws GeneralSecurityException {
     return new EncryptThenAuthenticate(
-        (IndCpaCipher) Registry.getPrimitive(
-            AesCtrKeyManager.TYPE_URL, keyProto.getAesCtrKey()),
-        (Mac) Registry.getPrimitive(MacConfig.HMAC_TYPE_URL, keyProto.getHmacKey()),
+        Registry.getPrimitive(
+            AesCtrKeyManager.TYPE_URL, keyProto.getAesCtrKey(), IndCpaCipher.class),
+        Registry.getPrimitive(MacConfig.HMAC_TYPE_URL, keyProto.getHmacKey(), Mac.class),
         keyProto.getHmacKey().getParams().getTagSize());
   }
 
-  /**
-   * @param serializedKeyFormat  serialized {@code AesCtrHmacAeadKeyFormat} proto
-   * @return new {@code AesCtrHmacAeadKey} proto
-   */
   @Override
-  public MessageLite newKey(ByteString serializedKeyFormat) throws GeneralSecurityException {
-    try {
-      AesCtrHmacAeadKeyFormat format = AesCtrHmacAeadKeyFormat.parseFrom(serializedKeyFormat);
-      return newKey(format);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("expected serialized AesCtrHmacAeadKeyFormat proto", e);
-    }
-  }
-
-  /**
-   * @param keyFormat  {@code AesCtrHmacAeadKeyFormat} proto
-   * @return new {@code AesCtrHmacAeadKey} proto
-   */
-  @Override
-  public MessageLite newKey(MessageLite keyFormat) throws GeneralSecurityException {
-    if (!(keyFormat instanceof AesCtrHmacAeadKeyFormat)) {
-      throw new GeneralSecurityException("expected AesCtrHmacAeadKeyFormat proto");
-    }
-    AesCtrHmacAeadKeyFormat format = (AesCtrHmacAeadKeyFormat) keyFormat;
-    AesCtrKey aesCtrKey = (AesCtrKey) Registry.newKey(
-        AesCtrKeyManager.TYPE_URL, format.getAesCtrKeyFormat());
-    HmacKey hmacKey = (HmacKey) Registry.newKey(
-        MacConfig.HMAC_TYPE_URL, format.getHmacKeyFormat());
+  public AesCtrHmacAeadKey newKeyFromFormat(AesCtrHmacAeadKeyFormat format)
+      throws GeneralSecurityException {
+    AesCtrKey aesCtrKey =
+        (AesCtrKey) Registry.newKey(AesCtrKeyManager.TYPE_URL, format.getAesCtrKeyFormat());
+    HmacKey hmacKey = (HmacKey) Registry.newKey(MacConfig.HMAC_TYPE_URL, format.getHmacKeyFormat());
     return AesCtrHmacAeadKey.newBuilder()
         .setAesCtrKey(aesCtrKey)
         .setHmacKey(hmacKey)
@@ -117,40 +70,35 @@
         .build();
   }
 
-  /**
-   * @param serializedKeyFormat  serialized {@code AesCtrHmacAeadKeyFormat} proto
-   * @return {@code KeyData} proto with a new {@code AesCtrHmacAeadKey} proto
-   */
-  @Override
-  public KeyData newKeyData(ByteString serializedKeyFormat) throws GeneralSecurityException {
-    AesCtrHmacAeadKey key = (AesCtrHmacAeadKey) newKey(serializedKeyFormat);
-    return KeyData.newBuilder()
-        .setTypeUrl(TYPE_URL)
-        .setValue(key.toByteString())
-        .setKeyMaterialType(KeyData.KeyMaterialType.SYMMETRIC)
-        .build();
-  }
-
-  @Override
-  public boolean doesSupport(String typeUrl) {
-    return typeUrl.equals(TYPE_URL);
-  }
-
-  @Override
-  public String getKeyType() {
-    return TYPE_URL;
-  }
-
   @Override
   public int getVersion() {
     return VERSION;
   }
 
-  private void validate(AesCtrHmacAeadKeyFormat format) throws GeneralSecurityException {
+  @Override
+  protected KeyMaterialType keyMaterialType() {
+    return KeyMaterialType.SYMMETRIC;
+  }
+
+  @Override
+  protected AesCtrHmacAeadKey parseKeyProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return AesCtrHmacAeadKey.parseFrom(byteString);
+  }
+
+  @Override
+  protected AesCtrHmacAeadKeyFormat parseKeyFormatProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return AesCtrHmacAeadKeyFormat.parseFrom(byteString);
+  }
+
+  @Override
+  protected void validateKeyFormat(AesCtrHmacAeadKeyFormat format) throws GeneralSecurityException {
     Validators.validateAesKeySize(format.getAesCtrKeyFormat().getKeySize());
   }
 
-  private void validate(AesCtrHmacAeadKey key) throws GeneralSecurityException {
+  @Override
+  protected void validateKey(AesCtrHmacAeadKey key) throws GeneralSecurityException {
     Validators.validateVersion(key.getVersion(), VERSION);
   }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/aead/AesCtrKeyManager.java b/java/src/main/java/com/google/crypto/tink/aead/AesCtrKeyManager.java
index 5f58578..f8cfde8 100644
--- a/java/src/main/java/com/google/crypto/tink/aead/AesCtrKeyManager.java
+++ b/java/src/main/java/com/google/crypto/tink/aead/AesCtrKeyManager.java
@@ -16,25 +16,28 @@
 
 package com.google.crypto.tink.aead;
 
-import com.google.crypto.tink.KeyManager;
+import com.google.crypto.tink.KeyManagerBase;
 import com.google.crypto.tink.proto.AesCtrKey;
 import com.google.crypto.tink.proto.AesCtrKeyFormat;
 import com.google.crypto.tink.proto.AesCtrParams;
-import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
 import com.google.crypto.tink.subtle.AesCtrJceCipher;
 import com.google.crypto.tink.subtle.IndCpaCipher;
 import com.google.crypto.tink.subtle.Random;
 import com.google.crypto.tink.subtle.Validators;
 import com.google.protobuf.ByteString;
 import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
 import java.security.GeneralSecurityException;
 
 /**
  * This key manager generates new {@code AesCtrKey} keys and produces new instances of {@code
  * AesCtrJceCipher}.
  */
-class AesCtrKeyManager implements KeyManager<IndCpaCipher> {
+class AesCtrKeyManager extends KeyManagerBase<IndCpaCipher, AesCtrKey, AesCtrKeyFormat> {
+  public AesCtrKeyManager() {
+    super(IndCpaCipher.class, AesCtrKey.class, AesCtrKeyFormat.class, TYPE_URL);
+  }
+
   private static final int VERSION = 0;
 
   static final String TYPE_URL = "type.googleapis.com/google.crypto.tink.AesCtrKey";
@@ -48,54 +51,14 @@
   // 2^-33 (i.e., less than one in eight billion).
   private static final int MIN_IV_SIZE_IN_BYTES = 12;
 
-  /** @param serializedKey serialized {@code AesCtrKey} proto */
   @Override
-  public AesCtrJceCipher getPrimitive(ByteString serializedKey) throws GeneralSecurityException {
-    try {
-      AesCtrKey keyProto = AesCtrKey.parseFrom(serializedKey);
-      return getPrimitive(keyProto);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("expected serialized AesCtrKey proto", e);
-    }
-  }
-
-  /** @param key {@code AesCtrKey} proto */
-  @Override
-  public AesCtrJceCipher getPrimitive(MessageLite key) throws GeneralSecurityException {
-    if (!(key instanceof AesCtrKey)) {
-      throw new GeneralSecurityException("expected AesCtrKey proto");
-    }
-    AesCtrKey keyProto = (AesCtrKey) key;
-    validate(keyProto);
+  public IndCpaCipher getPrimitiveFromKey(AesCtrKey keyProto) throws GeneralSecurityException {
     return new AesCtrJceCipher(
         keyProto.getKeyValue().toByteArray(), keyProto.getParams().getIvSize());
   }
 
-  /**
-   * @param serializedKeyFormat serialized {@code AesCtrKeyFormat} proto
-   * @return new {@code AesCtrKey} proto
-   */
   @Override
-  public MessageLite newKey(ByteString serializedKeyFormat) throws GeneralSecurityException {
-    try {
-      AesCtrKeyFormat format = AesCtrKeyFormat.parseFrom(serializedKeyFormat);
-      return newKey(format);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("expected serialized AesCtrKeyFormat proto", e);
-    }
-  }
-
-  /**
-   * @param keyFormat {@code AesCtrKeyFormat} proto
-   * @return new {@code AesCtrKey} proto
-   */
-  @Override
-  public MessageLite newKey(MessageLite keyFormat) throws GeneralSecurityException {
-    if (!(keyFormat instanceof AesCtrKeyFormat)) {
-      throw new GeneralSecurityException("expected AesCtrKeyFormat proto");
-    }
-    AesCtrKeyFormat format = (AesCtrKeyFormat) keyFormat;
-    validate(format);
+  public AesCtrKey newKeyFromFormat(AesCtrKeyFormat format) throws GeneralSecurityException {
     return AesCtrKey.newBuilder()
         .setParams(format.getParams())
         .setKeyValue(ByteString.copyFrom(Random.randBytes(format.getKeySize())))
@@ -103,42 +66,37 @@
         .build();
   }
 
-  /**
-   * @param serializedKeyFormat serialized {@code AesCtrKeyFormat} proto
-   * @return {@code KeyData} proto with a new {@code AesCtrKey} proto
-   */
-  @Override
-  public KeyData newKeyData(ByteString serializedKeyFormat) throws GeneralSecurityException {
-    AesCtrKey key = (AesCtrKey) newKey(serializedKeyFormat);
-    return KeyData.newBuilder()
-        .setTypeUrl(TYPE_URL)
-        .setValue(key.toByteString())
-        .setKeyMaterialType(KeyData.KeyMaterialType.SYMMETRIC)
-        .build();
-  }
-
-  @Override
-  public boolean doesSupport(String typeUrl) {
-    return typeUrl.equals(TYPE_URL);
-  }
-
-  @Override
-  public String getKeyType() {
-    return TYPE_URL;
-  }
-
   @Override
   public int getVersion() {
     return VERSION;
   }
 
-  private void validate(AesCtrKey key) throws GeneralSecurityException {
+  @Override
+  protected KeyMaterialType keyMaterialType() {
+    return KeyMaterialType.SYMMETRIC;
+  }
+
+  @Override
+  protected AesCtrKey parseKeyProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return AesCtrKey.parseFrom(byteString);
+  }
+
+  @Override
+  protected AesCtrKeyFormat parseKeyFormatProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return AesCtrKeyFormat.parseFrom(byteString);
+  }
+
+  @Override
+  protected void validateKey(AesCtrKey key) throws GeneralSecurityException {
     Validators.validateVersion(key.getVersion(), VERSION);
     Validators.validateAesKeySize(key.getKeyValue().size());
     validate(key.getParams());
   }
 
-  private void validate(AesCtrKeyFormat format) throws GeneralSecurityException {
+  @Override
+  protected void validateKeyFormat(AesCtrKeyFormat format) throws GeneralSecurityException {
     Validators.validateAesKeySize(format.getKeySize());
     validate(format.getParams());
   }
diff --git a/java/src/main/java/com/google/crypto/tink/aead/AesEaxKeyManager.java b/java/src/main/java/com/google/crypto/tink/aead/AesEaxKeyManager.java
index dca1852..00edb58 100644
--- a/java/src/main/java/com/google/crypto/tink/aead/AesEaxKeyManager.java
+++ b/java/src/main/java/com/google/crypto/tink/aead/AesEaxKeyManager.java
@@ -17,74 +17,37 @@
 package com.google.crypto.tink.aead;
 
 import com.google.crypto.tink.Aead;
-import com.google.crypto.tink.KeyManager;
+import com.google.crypto.tink.KeyManagerBase;
 import com.google.crypto.tink.proto.AesEaxKey;
 import com.google.crypto.tink.proto.AesEaxKeyFormat;
-import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
 import com.google.crypto.tink.subtle.AesEaxJce;
 import com.google.crypto.tink.subtle.Random;
 import com.google.crypto.tink.subtle.Validators;
 import com.google.protobuf.ByteString;
 import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
 import java.security.GeneralSecurityException;
 
 /**
  * This key manager generates new {@code AesEaxKey} keys and produces new instances of {@code
  * AesEaxJce}.
  */
-class AesEaxKeyManager implements KeyManager<Aead> {
+class AesEaxKeyManager extends KeyManagerBase<Aead, AesEaxKey, AesEaxKeyFormat> {
+  public AesEaxKeyManager() {
+    super(Aead.class, AesEaxKey.class, AesEaxKeyFormat.class, TYPE_URL);
+  }
+
   private static final int VERSION = 0;
 
   public static final String TYPE_URL = "type.googleapis.com/google.crypto.tink.AesEaxKey";
 
-  /** @param serializedKey serialized {@code AesEaxKey} proto */
   @Override
-  public Aead getPrimitive(ByteString serializedKey) throws GeneralSecurityException {
-    try {
-      AesEaxKey keyProto = AesEaxKey.parseFrom(serializedKey);
-      return getPrimitive(keyProto);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("expected serialized AesEaxKey proto", e);
-    }
-  }
-
-  /** @param key {@code AesEaxKey} proto */
-  @Override
-  public Aead getPrimitive(MessageLite key) throws GeneralSecurityException {
-    if (!(key instanceof AesEaxKey)) {
-      throw new GeneralSecurityException("expected AesEaxKey proto");
-    }
-    AesEaxKey keyProto = (AesEaxKey) key;
-    validate(keyProto);
+  public Aead getPrimitiveFromKey(AesEaxKey keyProto) throws GeneralSecurityException {
     return new AesEaxJce(keyProto.getKeyValue().toByteArray(), keyProto.getParams().getIvSize());
   }
 
-  /**
-   * @param serializedKeyFormat serialized {@code AesEaxKeyFormat} proto
-   * @return new {@code AesEaxKey} proto
-   */
   @Override
-  public MessageLite newKey(ByteString serializedKeyFormat) throws GeneralSecurityException {
-    try {
-      AesEaxKeyFormat format = AesEaxKeyFormat.parseFrom(serializedKeyFormat);
-      return newKey(format);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("expected serialized AesEaxKeyFormat proto", e);
-    }
-  }
-
-  /**
-   * @param keyFormat {@code AesEaxKeyFormat} proto
-   * @return new {@code AesEaxKey} proto
-   */
-  @Override
-  public MessageLite newKey(MessageLite keyFormat) throws GeneralSecurityException {
-    if (!(keyFormat instanceof AesEaxKeyFormat)) {
-      throw new GeneralSecurityException("expected AesEaxKeyFormat proto");
-    }
-    AesEaxKeyFormat format = (AesEaxKeyFormat) keyFormat;
-    validate(format);
+  public AesEaxKey newKeyFromFormat(AesEaxKeyFormat format) throws GeneralSecurityException {
     return AesEaxKey.newBuilder()
         .setKeyValue(ByteString.copyFrom(Random.randBytes(format.getKeySize())))
         .setParams(format.getParams())
@@ -92,37 +55,30 @@
         .build();
   }
 
-  /**
-   * @param serializedKeyFormat serialized {@code AesEaxKeyFormat} proto
-   * @return {@code KeyData} proto with a new {@code AesEaxKey} proto
-   */
-  @Override
-  public KeyData newKeyData(ByteString serializedKeyFormat) throws GeneralSecurityException {
-    AesEaxKey key = (AesEaxKey) newKey(serializedKeyFormat);
-    return KeyData.newBuilder()
-        .setTypeUrl(TYPE_URL)
-        .setValue(key.toByteString())
-        .setKeyMaterialType(KeyData.KeyMaterialType.SYMMETRIC)
-        .build();
-  }
-
-  @Override
-  public boolean doesSupport(String typeUrl) {
-    return typeUrl.equals(TYPE_URL);
-  }
-
-  @Override
-  public String getKeyType() {
-    return TYPE_URL;
-  }
-
   @Override
   public int getVersion() {
     return VERSION;
   }
 
+  @Override
+  protected KeyMaterialType keyMaterialType() {
+    return KeyMaterialType.SYMMETRIC;
+  }
 
-  private void validate(AesEaxKey key) throws GeneralSecurityException {
+  @Override
+  protected AesEaxKey parseKeyProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return AesEaxKey.parseFrom(byteString);
+  }
+
+  @Override
+  protected AesEaxKeyFormat parseKeyFormatProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return AesEaxKeyFormat.parseFrom(byteString);
+  }
+
+  @Override
+  protected void validateKey(AesEaxKey key) throws GeneralSecurityException {
     Validators.validateVersion(key.getVersion(), VERSION);
     Validators.validateAesKeySize(key.getKeyValue().size());
     if (key.getParams().getIvSize() != 12 && key.getParams().getIvSize() != 16) {
@@ -130,7 +86,8 @@
     }
   }
 
-  private void validate(AesEaxKeyFormat format) throws GeneralSecurityException {
+  @Override
+  protected void validateKeyFormat(AesEaxKeyFormat format) throws GeneralSecurityException {
     Validators.validateAesKeySize(format.getKeySize());
     if (format.getParams().getIvSize() != 12 && format.getParams().getIvSize() != 16) {
       throw new GeneralSecurityException("invalid IV size; acceptable values have 12 or 16 bytes");
diff --git a/java/src/main/java/com/google/crypto/tink/aead/AesGcmKeyManager.java b/java/src/main/java/com/google/crypto/tink/aead/AesGcmKeyManager.java
index ee480f1..c7aa4f3 100644
--- a/java/src/main/java/com/google/crypto/tink/aead/AesGcmKeyManager.java
+++ b/java/src/main/java/com/google/crypto/tink/aead/AesGcmKeyManager.java
@@ -17,115 +17,74 @@
 package com.google.crypto.tink.aead;
 
 import com.google.crypto.tink.Aead;
-import com.google.crypto.tink.KeyManager;
+import com.google.crypto.tink.KeyManagerBase;
 import com.google.crypto.tink.proto.AesGcmKey;
 import com.google.crypto.tink.proto.AesGcmKeyFormat;
-import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
 import com.google.crypto.tink.subtle.AesGcmJce;
 import com.google.crypto.tink.subtle.Random;
 import com.google.crypto.tink.subtle.Validators;
 import com.google.protobuf.ByteString;
 import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
 import java.security.GeneralSecurityException;
 
 /**
  * This key manager generates new {@code AesGcmKey} keys and produces new instances of {@code
  * AesGcmJce}.
  */
-class AesGcmKeyManager implements KeyManager<Aead> {
+class AesGcmKeyManager extends KeyManagerBase<Aead, AesGcmKey, AesGcmKeyFormat> {
+  public AesGcmKeyManager() {
+    super(Aead.class, AesGcmKey.class, AesGcmKeyFormat.class, TYPE_URL);
+  }
+
   private static final int VERSION = 0;
 
   public static final String TYPE_URL = "type.googleapis.com/google.crypto.tink.AesGcmKey";
 
-  /** @param serializedKey serialized {@code AesGcmKey} proto */
-  @Override
-  public Aead getPrimitive(ByteString serializedKey) throws GeneralSecurityException {
-    try {
-      AesGcmKey keyProto = AesGcmKey.parseFrom(serializedKey);
-      return getPrimitive(keyProto);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("expected AesGcmKey proto");
-    }
-  }
-
   /** @param key {@code AesGcmKey} proto */
   @Override
-  public Aead getPrimitive(MessageLite key) throws GeneralSecurityException {
-    if (!(key instanceof AesGcmKey)) {
-      throw new GeneralSecurityException("expected AesGcmKey proto");
-    }
-    AesGcmKey keyProto = (AesGcmKey) key;
-    validate(keyProto);
-    return new AesGcmJce(keyProto.getKeyValue().toByteArray());
+  protected Aead getPrimitiveFromKey(AesGcmKey key) throws GeneralSecurityException {
+    return new AesGcmJce(key.getKeyValue().toByteArray());
   }
 
-  /**
-   * @param serializedKeyFormat serialized {@code AesGcmKeyFormat} proto
-   * @return new {@code AesGcmKey} proto
-   */
   @Override
-  public MessageLite newKey(ByteString serializedKeyFormat) throws GeneralSecurityException {
-    try {
-      AesGcmKeyFormat format = AesGcmKeyFormat.parseFrom(serializedKeyFormat);
-      return newKey(format);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("expected serialized AesGcmKeyFormat proto", e);
-    }
-  }
-
-  /**
-   * @param keyFormat {@code AesGcmKeyFormat} proto
-   * @return new {@code AesGcmKey} proto
-   */
-  @Override
-  public MessageLite newKey(MessageLite keyFormat) throws GeneralSecurityException {
-    if (!(keyFormat instanceof AesGcmKeyFormat)) {
-      throw new GeneralSecurityException("expected AesGcmKeyFormat proto");
-    }
-    AesGcmKeyFormat format = (AesGcmKeyFormat) keyFormat;
-    validate(format);
+  protected AesGcmKey newKeyFromFormat(AesGcmKeyFormat format) throws GeneralSecurityException {
     return AesGcmKey.newBuilder()
         .setKeyValue(ByteString.copyFrom(Random.randBytes(format.getKeySize())))
         .setVersion(VERSION)
         .build();
   }
 
-  /**
-   * @param serializedKeyFormat serialized {@code AesGcmKeyFormat} proto
-   * @return {@code KeyData} proto with a new {@code AesGcmKey} proto
-   */
-  @Override
-  public KeyData newKeyData(ByteString serializedKeyFormat) throws GeneralSecurityException {
-    AesGcmKey key = (AesGcmKey) newKey(serializedKeyFormat);
-    return KeyData.newBuilder()
-        .setTypeUrl(TYPE_URL)
-        .setValue(key.toByteString())
-        .setKeyMaterialType(KeyData.KeyMaterialType.SYMMETRIC)
-        .build();
-  }
-
-  @Override
-  public boolean doesSupport(String typeUrl) {
-    return typeUrl.equals(TYPE_URL);
-  }
-
-  @Override
-  public String getKeyType() {
-    return TYPE_URL;
-  }
-
   @Override
   public int getVersion() {
     return VERSION;
   }
 
-  private void validate(AesGcmKey key) throws GeneralSecurityException {
+  @Override
+  protected KeyMaterialType keyMaterialType() {
+    return KeyMaterialType.SYMMETRIC;
+  }
+
+  @Override
+  protected AesGcmKey parseKeyProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return AesGcmKey.parseFrom(byteString);
+  }
+
+  @Override
+  protected AesGcmKeyFormat parseKeyFormatProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return AesGcmKeyFormat.parseFrom(byteString);
+  }
+
+  @Override
+  protected void validateKey(AesGcmKey key) throws GeneralSecurityException {
     Validators.validateVersion(key.getVersion(), VERSION);
     Validators.validateAesKeySize(key.getKeyValue().size());
   }
 
-  private void validate(AesGcmKeyFormat format) throws GeneralSecurityException {
+  @Override
+  protected void validateKeyFormat(AesGcmKeyFormat format) throws GeneralSecurityException {
     Validators.validateAesKeySize(format.getKeySize());
   }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/aead/BUILD.bazel b/java/src/main/java/com/google/crypto/tink/aead/BUILD.bazel
index bed18b4..5685b9d 100644
--- a/java/src/main/java/com/google/crypto/tink/aead/BUILD.bazel
+++ b/java/src/main/java/com/google/crypto/tink/aead/BUILD.bazel
@@ -21,10 +21,12 @@
     "//proto:chacha20_poly1305_java_proto",
     "//proto:common_java_proto",
     "//proto:config_java_proto",
+    "//proto:empty_java_proto",
     "//proto:hmac_java_proto",
     "//proto:kms_aead_java_proto",
     "//proto:kms_envelope_java_proto",
     "//proto:tink_java_proto",
+    "//proto:xchacha20_poly1305_java_proto",
 ]
 
 lite_protos = [
@@ -35,10 +37,12 @@
     "//proto:chacha20_poly1305_java_proto_lite",
     "//proto:common_java_proto_lite",
     "//proto:config_java_proto_lite",
+    "//proto:empty_java_proto_lite",
     "//proto:hmac_java_proto_lite",
     "//proto:kms_aead_java_proto_lite",
     "//proto:kms_envelope_java_proto_lite",
     "//proto:tink_java_proto_lite",
+    "//proto:xchacha20_poly1305_java_proto_lite",
 ]
 
 java_library(
diff --git a/java/src/main/java/com/google/crypto/tink/aead/ChaCha20Poly1305KeyManager.java b/java/src/main/java/com/google/crypto/tink/aead/ChaCha20Poly1305KeyManager.java
index 4fc9dff..4217be0 100644
--- a/java/src/main/java/com/google/crypto/tink/aead/ChaCha20Poly1305KeyManager.java
+++ b/java/src/main/java/com/google/crypto/tink/aead/ChaCha20Poly1305KeyManager.java
@@ -17,22 +17,27 @@
 package com.google.crypto.tink.aead;
 
 import com.google.crypto.tink.Aead;
-import com.google.crypto.tink.KeyManager;
+import com.google.crypto.tink.KeyManagerBase;
 import com.google.crypto.tink.proto.ChaCha20Poly1305Key;
-import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.Empty;
+import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
 import com.google.crypto.tink.subtle.ChaCha20Poly1305;
 import com.google.crypto.tink.subtle.Random;
 import com.google.crypto.tink.subtle.Validators;
 import com.google.protobuf.ByteString;
 import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
 import java.security.GeneralSecurityException;
 
 /**
  * This instance of {@code KeyManager} generates new {@code ChaCha20Poly1305} keys and produces new
  * instances of {@code ChaCha20Poly1305}.
  */
-class ChaCha20Poly1305KeyManager implements KeyManager<Aead> {
+class ChaCha20Poly1305KeyManager
+    extends KeyManagerBase<Aead, ChaCha20Poly1305Key, Empty> {
+  public ChaCha20Poly1305KeyManager() {
+    super(Aead.class, ChaCha20Poly1305Key.class, Empty.class, TYPE_URL);
+  }
+
   /** Type url that this manager supports */
   public static final String TYPE_URL =
       "type.googleapis.com/google.crypto.tink.ChaCha20Poly1305Key";
@@ -43,71 +48,47 @@
   private static final int VERSION = 0;
 
   @Override
-  public Aead getPrimitive(ByteString serialized) throws GeneralSecurityException {
-    try {
-      ChaCha20Poly1305Key keyProto = ChaCha20Poly1305Key.parseFrom(serialized);
-      return getPrimitive(keyProto);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("invalid ChaCha20Poly1305 key", e);
-    }
-  }
-
-  @Override
-  public Aead getPrimitive(MessageLite key) throws GeneralSecurityException {
-    if (!(key instanceof ChaCha20Poly1305Key)) {
-      throw new GeneralSecurityException("expected ChaCha20Poly1305Key proto");
-    }
-    ChaCha20Poly1305Key keyProto = (ChaCha20Poly1305Key) key;
-    validate(keyProto);
+  public Aead getPrimitiveFromKey(ChaCha20Poly1305Key keyProto) throws GeneralSecurityException {
     return new ChaCha20Poly1305(keyProto.getKeyValue().toByteArray());
   }
 
   @Override
-  public MessageLite newKey(ByteString unused) throws GeneralSecurityException {
-    return newKey();
-  }
-
-  @Override
-  public MessageLite newKey(MessageLite unused) throws GeneralSecurityException {
-    return newKey();
-  }
-
-  @Override
-  public KeyData newKeyData(ByteString unused) throws GeneralSecurityException {
-    ChaCha20Poly1305Key key = newKey();
-    return KeyData.newBuilder()
-        .setTypeUrl(TYPE_URL)
-        .setValue(key.toByteString())
-        .setKeyMaterialType(KeyData.KeyMaterialType.SYMMETRIC)
+  protected ChaCha20Poly1305Key newKeyFromFormat(Empty unused) throws GeneralSecurityException {
+    return ChaCha20Poly1305Key.newBuilder()
+        .setVersion(VERSION)
+        .setKeyValue(ByteString.copyFrom(Random.randBytes(KEY_SIZE_IN_BYTES)))
         .build();
   }
 
   @Override
-  public boolean doesSupport(String typeUrl) {
-    return TYPE_URL.equals(typeUrl);
-  }
-
-  @Override
-  public String getKeyType() {
-    return TYPE_URL;
-  }
-
-  @Override
   public int getVersion() {
     return VERSION;
   }
 
-  private ChaCha20Poly1305Key newKey() throws GeneralSecurityException {
-    return ChaCha20Poly1305Key.newBuilder()
-        .setVersion(VERSION)
-        .setKeyValue(ByteString.copyFrom(Random.randBytes(KEY_SIZE_IN_BYTES)))
-        .build();
+  @Override
+  protected KeyMaterialType keyMaterialType() {
+    return KeyMaterialType.SYMMETRIC;
   }
 
-  private void validate(ChaCha20Poly1305Key keyProto) throws GeneralSecurityException {
+  @Override
+  protected ChaCha20Poly1305Key parseKeyProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return ChaCha20Poly1305Key.parseFrom(byteString);
+  }
+
+  @Override
+  protected Empty parseKeyFormatProto(ByteString byteString) throws InvalidProtocolBufferException {
+    return Empty.parseFrom(byteString);
+  }
+
+  @Override
+  protected void validateKey(ChaCha20Poly1305Key keyProto) throws GeneralSecurityException {
     Validators.validateVersion(keyProto.getVersion(), VERSION);
     if (keyProto.getKeyValue().size() != KEY_SIZE_IN_BYTES) {
       throw new GeneralSecurityException("invalid ChaCha20Poly1305Key: incorrect key length");
     }
   }
+
+  @Override
+  protected void validateKeyFormat(Empty unused) throws GeneralSecurityException {}
 }
diff --git a/java/src/main/java/com/google/crypto/tink/aead/KmsAeadKeyManager.java b/java/src/main/java/com/google/crypto/tink/aead/KmsAeadKeyManager.java
index 43f91e9..a7abfc9 100644
--- a/java/src/main/java/com/google/crypto/tink/aead/KmsAeadKeyManager.java
+++ b/java/src/main/java/com/google/crypto/tink/aead/KmsAeadKeyManager.java
@@ -17,109 +17,41 @@
 package com.google.crypto.tink.aead;
 
 import com.google.crypto.tink.Aead;
-import com.google.crypto.tink.KeyManager;
+import com.google.crypto.tink.KeyManagerBase;
 import com.google.crypto.tink.KmsClient;
 import com.google.crypto.tink.KmsClients;
-import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
 import com.google.crypto.tink.proto.KmsAeadKey;
 import com.google.crypto.tink.proto.KmsAeadKeyFormat;
 import com.google.crypto.tink.subtle.Validators;
 import com.google.protobuf.ByteString;
 import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
 import java.security.GeneralSecurityException;
 
 /**
- * This key manager produces new instances of {@code Aead} that forwards encrypt/decrypt
- * requests to a key residing in a remote KMS.
+ * This key manager produces new instances of {@code Aead} that forwards encrypt/decrypt requests to
+ * a key residing in a remote KMS.
  */
-class KmsAeadKeyManager implements KeyManager<Aead> {
+class KmsAeadKeyManager extends KeyManagerBase<Aead, KmsAeadKey, KmsAeadKeyFormat> {
+  public KmsAeadKeyManager() {
+    super(Aead.class, KmsAeadKey.class, KmsAeadKeyFormat.class, TYPE_URL);
+  }
+
   private static final int VERSION = 0;
 
   public static final String TYPE_URL =
       "type.googleapis.com/google.crypto.tink.KmsAeadKey";
 
-  /**
-   * @param serializedKey  serialized {@code KmsAeadKey} proto
-   */
   @Override
-  public Aead getPrimitive(ByteString serializedKey) throws GeneralSecurityException {
-    try {
-      KmsAeadKey keyProto = KmsAeadKey.parseFrom(serializedKey);
-      return getPrimitive(keyProto);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("expected KmsAeadKey proto", e);
-    }
-  }
-
-  /**
-   * @param key  {@code KmsAeadKey} proto
-   */
-  @Override
-  public Aead getPrimitive(MessageLite key) throws GeneralSecurityException {
-    if (!(key instanceof KmsAeadKey)) {
-      throw new GeneralSecurityException("expected KmsAeadKey proto");
-    }
-    KmsAeadKey keyProto = (KmsAeadKey) key;
-    validate(keyProto);
+  public Aead getPrimitiveFromKey(KmsAeadKey keyProto) throws GeneralSecurityException {
     String keyUri = keyProto.getParams().getKeyUri();
     KmsClient kmsClient = KmsClients.get(keyUri);
     return kmsClient.getAead(keyUri);
   }
 
-  /**
-   * @param serializedKeyFormat  serialized {@code KmsAeadKeyFormat} proto
-   * @return new {@code KmsAeadKey} proto
-   */
   @Override
-  public MessageLite newKey(ByteString serializedKeyFormat) throws GeneralSecurityException {
-    try {
-      KmsAeadKeyFormat format = KmsAeadKeyFormat.parseFrom(serializedKeyFormat);
-      return newKey(format);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("expected serialized KmsAeadKeyFormat proto", e);
-    }
-  }
-
-  /**
-   * @param keyFormat  {@code KmsAeadKeyFormat} proto
-   * @return new {@code KmsAeadKey} proto
-   */
-  @Override
-  public MessageLite newKey(MessageLite keyFormat)
-      throws GeneralSecurityException {
-    if (!(keyFormat instanceof KmsAeadKeyFormat)) {
-      throw new GeneralSecurityException("expected KmsAeadKeyFormat proto");
-    }
-    KmsAeadKeyFormat format = (KmsAeadKeyFormat) keyFormat;
-    return KmsAeadKey.newBuilder()
-        .setParams(format)
-        .setVersion(VERSION)
-        .build();
-  }
-
-  /**
-   * @param serializedKeyFormat  serialized {@code KmsAeadKeyFormat} proto
-   * @return {@code KeyData} with a new {@code KmsAeadKey} proto
-   */
-  @Override
-  public KeyData newKeyData(ByteString serializedKeyFormat) throws GeneralSecurityException {
-    KmsAeadKey key = (KmsAeadKey) newKey(serializedKeyFormat);
-    return KeyData.newBuilder()
-        .setTypeUrl(TYPE_URL)
-        .setValue(key.toByteString())
-        .setKeyMaterialType(KeyData.KeyMaterialType.REMOTE)
-        .build();
-  }
-
-  @Override
-  public boolean doesSupport(String typeUrl) {
-    return typeUrl.equals(TYPE_URL);
-  }
-
-  @Override
-  public String getKeyType() {
-    return TYPE_URL;
+  public KmsAeadKey newKeyFromFormat(KmsAeadKeyFormat format) throws GeneralSecurityException {
+    return KmsAeadKey.newBuilder().setParams(format).setVersion(VERSION).build();
   }
 
   @Override
@@ -127,7 +59,28 @@
     return VERSION;
   }
 
-  private static void validate(KmsAeadKey key) throws GeneralSecurityException {
+  @Override
+  protected KeyMaterialType keyMaterialType() {
+    return KeyMaterialType.REMOTE;
+  }
+
+  @Override
+  protected KmsAeadKey parseKeyProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return KmsAeadKey.parseFrom(byteString);
+  }
+
+  @Override
+  protected KmsAeadKeyFormat parseKeyFormatProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return KmsAeadKeyFormat.parseFrom(byteString);
+  }
+
+  @Override
+  protected void validateKey(KmsAeadKey key) throws GeneralSecurityException {
     Validators.validateVersion(key.getVersion(), VERSION);
   }
+
+  @Override
+  protected void validateKeyFormat(KmsAeadKeyFormat format) {}
 }
diff --git a/java/src/main/java/com/google/crypto/tink/aead/KmsEnvelopeAead.java b/java/src/main/java/com/google/crypto/tink/aead/KmsEnvelopeAead.java
index f9e514d..0eddb87 100644
--- a/java/src/main/java/com/google/crypto/tink/aead/KmsEnvelopeAead.java
+++ b/java/src/main/java/com/google/crypto/tink/aead/KmsEnvelopeAead.java
@@ -53,7 +53,7 @@
     // Wrap it with remote.
     byte[] encryptedDek = remote.encrypt(dek, EMPTY_AAD);
     // Use DEK to encrypt plaintext.
-    Aead aead = Registry.getPrimitive(dekTemplate.getTypeUrl(), dek);
+    Aead aead = Registry.getPrimitive(dekTemplate.getTypeUrl(), dek, Aead.class);
     byte[] payload = aead.encrypt(plaintext, associatedData);
     // Build ciphertext protobuf and return result.
     return buildCiphertext(encryptedDek, payload);
@@ -75,7 +75,7 @@
       // Use remote to decrypt encryptedDek.
       byte[] dek = remote.decrypt(encryptedDek, EMPTY_AAD);
       // Use DEK to decrypt payload.
-      Aead aead = Registry.getPrimitive(dekTemplate.getTypeUrl(), dek);
+      Aead aead = Registry.getPrimitive(dekTemplate.getTypeUrl(), dek, Aead.class);
       return aead.decrypt(payload, associatedData);
     } catch (IndexOutOfBoundsException
              | BufferUnderflowException
diff --git a/java/src/main/java/com/google/crypto/tink/aead/KmsEnvelopeAeadKeyManager.java b/java/src/main/java/com/google/crypto/tink/aead/KmsEnvelopeAeadKeyManager.java
index 804b636..cc86464 100644
--- a/java/src/main/java/com/google/crypto/tink/aead/KmsEnvelopeAeadKeyManager.java
+++ b/java/src/main/java/com/google/crypto/tink/aead/KmsEnvelopeAeadKeyManager.java
@@ -17,110 +17,73 @@
 package com.google.crypto.tink.aead;
 
 import com.google.crypto.tink.Aead;
-import com.google.crypto.tink.KeyManager;
+import com.google.crypto.tink.KeyManagerBase;
 import com.google.crypto.tink.KmsClient;
 import com.google.crypto.tink.KmsClients;
-import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
 import com.google.crypto.tink.proto.KmsEnvelopeAeadKey;
 import com.google.crypto.tink.proto.KmsEnvelopeAeadKeyFormat;
 import com.google.crypto.tink.subtle.Validators;
 import com.google.protobuf.ByteString;
 import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
 import java.security.GeneralSecurityException;
 
 /**
  * This key manager generates new {@code KmsEnvelopeAeadKey} keys and produces new instances of
  * {@code KmsEnvelopeAead}.
  */
-class KmsEnvelopeAeadKeyManager implements KeyManager<Aead> {
+class KmsEnvelopeAeadKeyManager
+    extends KeyManagerBase<Aead, KmsEnvelopeAeadKey, KmsEnvelopeAeadKeyFormat> {
+  public KmsEnvelopeAeadKeyManager() {
+    super(Aead.class, KmsEnvelopeAeadKey.class, KmsEnvelopeAeadKeyFormat.class, TYPE_URL);
+  }
 
   private static final int VERSION = 0;
 
   public static final String TYPE_URL = "type.googleapis.com/google.crypto.tink.KmsEnvelopeAeadKey";
 
-  /** @param serializedKey serialized {@code KmsEnvelopeAeadKey} proto */
   @Override
-  public Aead getPrimitive(ByteString serializedKey) throws GeneralSecurityException {
-    try {
-      KmsEnvelopeAeadKey keyProto = KmsEnvelopeAeadKey.parseFrom(serializedKey);
-      return getPrimitive(keyProto);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("expected serialized KmSEnvelopeAeadKey proto", e);
-    }
-  }
-
-  /** @param key {@code KmsEnvelopeAeadKey} proto */
-  @Override
-  public Aead getPrimitive(MessageLite key) throws GeneralSecurityException {
-    if (!(key instanceof KmsEnvelopeAeadKey)) {
-      throw new GeneralSecurityException("expected KmsEnvelopeAeadKey proto");
-    }
-    KmsEnvelopeAeadKey keyProto = (KmsEnvelopeAeadKey) key;
-    validate(keyProto);
+  public Aead getPrimitiveFromKey(KmsEnvelopeAeadKey keyProto) throws GeneralSecurityException {
     String keyUri = keyProto.getParams().getKekUri();
     KmsClient kmsClient = KmsClients.get(keyUri);
     Aead remote = kmsClient.getAead(keyUri);
     return new KmsEnvelopeAead(keyProto.getParams().getDekTemplate(), remote);
   }
 
-  /**
-   * @param serializedKeyFormat serialized {@code KmsEnvelopeAeadKeyFormat} proto
-   * @return new {@code KmsEnvelopeAeadKey} proto
-   */
   @Override
-  public MessageLite newKey(ByteString serializedKeyFormat) throws GeneralSecurityException {
-    try {
-      KmsEnvelopeAeadKeyFormat format = KmsEnvelopeAeadKeyFormat.parseFrom(serializedKeyFormat);
-      return newKey(format);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("expected serialized KmsEnvelopeAeadKeyFormat proto", e);
-    }
-  }
-
-  /**
-   * @param keyFormat {@code KmsEnvelopeAeadKeyFormat} proto
-   * @return new {@code KmsEnvelopeAeadKey} proto
-   */
-  @Override
-  public MessageLite newKey(MessageLite keyFormat) throws GeneralSecurityException {
-    if (!(keyFormat instanceof KmsEnvelopeAeadKeyFormat)) {
-      throw new GeneralSecurityException("expected KmsEnvelopeAeadKeyFormat proto");
-    }
-    KmsEnvelopeAeadKeyFormat format = (KmsEnvelopeAeadKeyFormat) keyFormat;
+  public KmsEnvelopeAeadKey newKeyFromFormat(KmsEnvelopeAeadKeyFormat format)
+      throws GeneralSecurityException {
     return KmsEnvelopeAeadKey.newBuilder().setParams(format).setVersion(VERSION).build();
   }
 
-  /**
-   * @param serializedKeyFormat serialized {@code KmsEnvelopeAeadKeyFormat} proto
-   * @return {@code KeyData} with a new {@code KmsEnvelopeAeadKey} proto
-   */
-  @Override
-  public KeyData newKeyData(ByteString serializedKeyFormat) throws GeneralSecurityException {
-    KmsEnvelopeAeadKey key = (KmsEnvelopeAeadKey) newKey(serializedKeyFormat);
-    return KeyData.newBuilder()
-        .setTypeUrl(TYPE_URL)
-        .setValue(key.toByteString())
-        .setKeyMaterialType(KeyData.KeyMaterialType.REMOTE)
-        .build();
-  }
-
-  @Override
-  public boolean doesSupport(String typeUrl) {
-    return typeUrl.equals(TYPE_URL);
-  }
-
-  @Override
-  public String getKeyType() {
-    return TYPE_URL;
-  }
-
   @Override
   public int getVersion() {
     return VERSION;
   }
 
-  private void validate(KmsEnvelopeAeadKey key) throws GeneralSecurityException {
+  @Override
+  protected KeyMaterialType keyMaterialType() {
+    return KeyMaterialType.REMOTE;
+  }
+
+  @Override
+  protected KmsEnvelopeAeadKey parseKeyProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return KmsEnvelopeAeadKey.parseFrom(byteString);
+  }
+
+  @Override
+  protected KmsEnvelopeAeadKeyFormat parseKeyFormatProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return KmsEnvelopeAeadKeyFormat.parseFrom(byteString);
+  }
+
+  @Override
+  protected void validateKey(KmsEnvelopeAeadKey key) throws GeneralSecurityException {
     Validators.validateVersion(key.getVersion(), VERSION);
   }
+
+  @Override
+  protected void validateKeyFormat(KmsEnvelopeAeadKeyFormat format)
+      throws GeneralSecurityException {}
 }
diff --git a/java/src/main/java/com/google/crypto/tink/aead/XChaCha20Poly1305KeyManager.java b/java/src/main/java/com/google/crypto/tink/aead/XChaCha20Poly1305KeyManager.java
new file mode 100644
index 0000000..51d33bb
--- /dev/null
+++ b/java/src/main/java/com/google/crypto/tink/aead/XChaCha20Poly1305KeyManager.java
@@ -0,0 +1,91 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.aead;
+
+import com.google.crypto.tink.Aead;
+import com.google.crypto.tink.KeyManagerBase;
+import com.google.crypto.tink.proto.Empty;
+import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
+import com.google.crypto.tink.proto.XChaCha20Poly1305Key;
+import com.google.crypto.tink.subtle.Random;
+import com.google.crypto.tink.subtle.Validators;
+import com.google.crypto.tink.subtle.XChaCha20Poly1305;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.security.GeneralSecurityException;
+
+/**
+ * This instance of {@code KeyManager} generates new {@code XChaCha20Poly1305} keys and produces new
+ * instances of {@code XChaCha20Poly1305}.
+ */
+class XChaCha20Poly1305KeyManager extends KeyManagerBase<Aead, XChaCha20Poly1305Key, Empty> {
+  public XChaCha20Poly1305KeyManager() {
+    super(Aead.class, XChaCha20Poly1305Key.class, Empty.class, TYPE_URL);
+  }
+
+  public static final String TYPE_URL =
+      "type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key";
+
+  private static final int KEY_SIZE_IN_BYTES = 32;
+
+  private static final int VERSION = 0;
+
+  @Override
+  public Aead getPrimitiveFromKey(XChaCha20Poly1305Key keyProto) throws GeneralSecurityException {
+    return new XChaCha20Poly1305(keyProto.getKeyValue().toByteArray());
+  }
+
+  @Override
+  public int getVersion() {
+    return VERSION;
+  }
+
+  @Override
+  protected XChaCha20Poly1305Key newKeyFromFormat(Empty unused) throws GeneralSecurityException {
+    return XChaCha20Poly1305Key.newBuilder()
+        .setVersion(VERSION)
+        .setKeyValue(ByteString.copyFrom(Random.randBytes(KEY_SIZE_IN_BYTES)))
+        .build();
+  }
+
+  @Override
+  protected KeyMaterialType keyMaterialType() {
+    return KeyMaterialType.SYMMETRIC;
+  }
+
+  @Override
+  protected XChaCha20Poly1305Key parseKeyProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return XChaCha20Poly1305Key.parseFrom(byteString);
+  }
+
+  @Override
+  protected Empty parseKeyFormatProto(ByteString byteString) throws InvalidProtocolBufferException {
+    return Empty.parseFrom(byteString);
+  }
+
+  @Override
+  protected void validateKey(XChaCha20Poly1305Key keyProto) throws GeneralSecurityException {
+    Validators.validateVersion(keyProto.getVersion(), VERSION);
+    if (keyProto.getKeyValue().size() != KEY_SIZE_IN_BYTES) {
+      throw new GeneralSecurityException("invalid XChaCha20Poly1305Key: incorrect key length");
+    }
+  }
+
+  @Override
+  protected void validateKeyFormat(Empty empty) throws GeneralSecurityException {}
+}
diff --git a/java/src/main/java/com/google/crypto/tink/daead/AesSivKeyManager.java b/java/src/main/java/com/google/crypto/tink/daead/AesSivKeyManager.java
index ec9b3b1..7f3ebce 100644
--- a/java/src/main/java/com/google/crypto/tink/daead/AesSivKeyManager.java
+++ b/java/src/main/java/com/google/crypto/tink/daead/AesSivKeyManager.java
@@ -17,16 +17,15 @@
 package com.google.crypto.tink.daead;
 
 import com.google.crypto.tink.DeterministicAead;
-import com.google.crypto.tink.KeyManager;
+import com.google.crypto.tink.KeyManagerBase;
 import com.google.crypto.tink.proto.AesSivKey;
 import com.google.crypto.tink.proto.AesSivKeyFormat;
-import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
 import com.google.crypto.tink.subtle.AesSiv;
 import com.google.crypto.tink.subtle.Random;
 import com.google.crypto.tink.subtle.Validators;
 import com.google.protobuf.ByteString;
 import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
 import java.security.GeneralSecurityException;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
@@ -35,94 +34,53 @@
  * This key manager generates new {@code AesSivKey} keys and produces new instances of {@code
  * AesSiv}.
  */
-class AesSivKeyManager implements KeyManager<DeterministicAead> {
+class AesSivKeyManager extends KeyManagerBase<DeterministicAead, AesSivKey, AesSivKeyFormat> {
+  public AesSivKeyManager() {
+    super(DeterministicAead.class, AesSivKey.class, AesSivKeyFormat.class, TYPE_URL);
+  }
+
   private static final int VERSION = 0;
 
   public static final String TYPE_URL = "type.googleapis.com/google.crypto.tink.AesSivKey";
 
-  /** @param serializedKey serialized {@code AesSivKey} proto */
   @Override
-  public DeterministicAead getPrimitive(ByteString serializedKey) throws GeneralSecurityException {
-    try {
-      AesSivKey keyProto = AesSivKey.parseFrom(serializedKey);
-      return getPrimitive(keyProto);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("expected AesSivKey proto");
-    }
-  }
-
-  /** @param key {@code AesSivKey} proto */
-  @Override
-  public DeterministicAead getPrimitive(MessageLite key) throws GeneralSecurityException {
-    if (!(key instanceof AesSivKey)) {
-      throw new GeneralSecurityException("expected AesSivKey proto");
-    }
-    AesSivKey keyProto = (AesSivKey) key;
-    validate(keyProto);
+  protected DeterministicAead getPrimitiveFromKey(AesSivKey keyProto)
+      throws GeneralSecurityException {
     return new AesSiv(keyProto.getKeyValue().toByteArray());
   }
 
-  /**
-   * @param serializedKeyFormat serialized {@code AesSivKeyFormat} proto
-   * @return new {@code AesSivKey} proto
-   */
   @Override
-  public MessageLite newKey(ByteString serializedKeyFormat) throws GeneralSecurityException {
-    try {
-      AesSivKeyFormat format = AesSivKeyFormat.parseFrom(serializedKeyFormat);
-      return newKey(format);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("expected serialized AesSivKeyFormat proto", e);
-    }
-  }
-
-  /**
-   * @param keyFormat {@code AesSivKeyFormat} proto
-   * @return new {@code AesSivKey} proto
-   */
-  @Override
-  public MessageLite newKey(MessageLite keyFormat) throws GeneralSecurityException {
-    if (!(keyFormat instanceof AesSivKeyFormat)) {
-      throw new GeneralSecurityException("expected AesSivKeyFormat proto");
-    }
-    AesSivKeyFormat format = (AesSivKeyFormat) keyFormat;
-    validate(format);
+  public AesSivKey newKeyFromFormat(AesSivKeyFormat format) throws GeneralSecurityException {
     return AesSivKey.newBuilder()
         .setKeyValue(ByteString.copyFrom(Random.randBytes(format.getKeySize())))
         .setVersion(VERSION)
         .build();
   }
 
-  /**
-   * @param serializedKeyFormat serialized {@code AesSivKeyFormat} proto
-   * @return {@code KeyData} proto with a new {@code AesSivKey} proto
-   */
-  @Override
-  public KeyData newKeyData(ByteString serializedKeyFormat) throws GeneralSecurityException {
-    AesSivKey key = (AesSivKey) newKey(serializedKeyFormat);
-    return KeyData.newBuilder()
-        .setTypeUrl(TYPE_URL)
-        .setValue(key.toByteString())
-        .setKeyMaterialType(KeyData.KeyMaterialType.SYMMETRIC)
-        .build();
-  }
-
-  @Override
-  public boolean doesSupport(String typeUrl) {
-    return typeUrl.equals(TYPE_URL);
-  }
-
-  @Override
-  public String getKeyType() {
-    return TYPE_URL;
-  }
-
   @Override
   public int getVersion() {
     return VERSION;
   }
 
-  private void validate(AesSivKey key) throws GeneralSecurityException {
+  @Override
+  protected KeyMaterialType keyMaterialType() {
+    return KeyMaterialType.SYMMETRIC;
+  }
+
+  @Override
+  protected AesSivKey parseKeyProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return AesSivKey.parseFrom(byteString);
+  }
+
+  @Override
+  protected AesSivKeyFormat parseKeyFormatProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return AesSivKeyFormat.parseFrom(byteString);
+  }
+
+  @Override
+  protected void validateKey(AesSivKey key) throws GeneralSecurityException {
     Validators.validateVersion(key.getVersion(), VERSION);
     if (key.getKeyValue().size() != 64) {
       throw new InvalidKeyException(
@@ -130,7 +88,8 @@
     }
   }
 
-  private void validate(AesSivKeyFormat format) throws GeneralSecurityException {
+  @Override
+  protected void validateKeyFormat(AesSivKeyFormat format) throws GeneralSecurityException {
     if (format.getKeySize() != 64) {
       throw new InvalidAlgorithmParameterException(
           "invalid key size: " + format.getKeySize() + ". Valid keys must have 64 bytes.");
diff --git a/java/src/main/java/com/google/crypto/tink/daead/DeterministicAeadCatalogue.java b/java/src/main/java/com/google/crypto/tink/daead/DeterministicAeadCatalogue.java
index d48d6cc..cbe75d8 100644
--- a/java/src/main/java/com/google/crypto/tink/daead/DeterministicAeadCatalogue.java
+++ b/java/src/main/java/com/google/crypto/tink/daead/DeterministicAeadCatalogue.java
@@ -19,6 +19,7 @@
 import com.google.crypto.tink.Catalogue;
 import com.google.crypto.tink.DeterministicAead;
 import com.google.crypto.tink.KeyManager;
+import com.google.crypto.tink.PrimitiveWrapper;
 import java.security.GeneralSecurityException;
 
 /** A catalogue of {@link DeterministicAead} key managers. */
@@ -60,4 +61,9 @@
                 "No support for primitive 'DeterministicAead' with key type '%s'.", typeUrl));
     }
   }
+
+  @Override
+  public PrimitiveWrapper<DeterministicAead> getPrimitiveWrapper() {
+    return new DeterministicAeadWrapper();
+  }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/daead/DeterministicAeadConfig.java b/java/src/main/java/com/google/crypto/tink/daead/DeterministicAeadConfig.java
index 6b5f605..a63223c 100644
--- a/java/src/main/java/com/google/crypto/tink/daead/DeterministicAeadConfig.java
+++ b/java/src/main/java/com/google/crypto/tink/daead/DeterministicAeadConfig.java
@@ -92,5 +92,6 @@
   public static void register() throws GeneralSecurityException {
     Registry.addCatalogue(CATALOGUE_NAME, new DeterministicAeadCatalogue());
     Config.register(LATEST);
+    Registry.registerPrimitiveWrapper(new DeterministicAeadWrapper());
   }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/daead/DeterministicAeadFactory.java b/java/src/main/java/com/google/crypto/tink/daead/DeterministicAeadFactory.java
index 4c79bea..438b2b5 100644
--- a/java/src/main/java/com/google/crypto/tink/daead/DeterministicAeadFactory.java
+++ b/java/src/main/java/com/google/crypto/tink/daead/DeterministicAeadFactory.java
@@ -16,47 +16,37 @@
 
 package com.google.crypto.tink.daead;
 
-import com.google.crypto.tink.CryptoFormat;
 import com.google.crypto.tink.DeterministicAead;
 import com.google.crypto.tink.KeyManager;
 import com.google.crypto.tink.KeysetHandle;
 import com.google.crypto.tink.PrimitiveSet;
 import com.google.crypto.tink.Registry;
-import com.google.crypto.tink.subtle.Bytes;
 import java.security.GeneralSecurityException;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.logging.Logger;
 
 /**
- * Static methods for obtaining {@link DeterministicAead} instances.
+ * Deprecated class to create {@code DeterministicAead} primitives. Instead of using this class,
+ * make sure that the {@code DeterministicAeadWrapper} is registered in your binary, then call
+ * {@code keysetHandle.GetPrimitive(DeterministicAead.class)} instead. The required registration
+ * happens automatically if you called one of the following in your binary:
  *
- * <h3>Usage</h3>
+ * <ul>
+ *   <li>{@code DeterministicAeadConfig.register()}
+ *   <li>{@code TinkConfig.register()}
+ * </ul>
  *
- * <pre>{@code
- * KeysetHandle keysetHandle = ...;
- * DeterministicAead daead = DeterministicAeadFactory.getPrimitive(keysetHandle);
- * byte[] plaintext = ...;
- * byte[] aad = ...;
- * byte[] ciphertext = daead.encrypt(plaintext, aad);
- * }</pre>
- *
- * <p>The returned primitive works with a keyset (rather than a single key). To encrypt a plaintext,
- * it uses the primary key in the keyset, and prepends to the ciphertext a certain prefix associated
- * with the primary key. To decrypt, the primitive uses the prefix of the ciphertext to efficiently
- * select the right key in the set. If the keys associated with the prefix do not work, the
- * primitive tries all keys with {@link com.google.crypto.tink.proto.OutputPrefixType#RAW}.
- *
+ * @deprecated Use {@code keysetHandle.GetPrimitive(DeterministicAead.class)} after registering the
+ *     {@code DeterministicAeadWrapper} instead.
  * @since 1.1.0
  */
+@Deprecated
 public final class DeterministicAeadFactory {
-  private static final Logger logger = Logger.getLogger(DeterministicAeadFactory.class.getName());
-
   /**
    * @return a DeterministicAead primitive from a {@code keysetHandle}.
    * @throws GeneralSecurityException
+   * @deprecated Use {@code keysetHandle.GetPrimitive(DeterministicAead.class)} after registering
+   *     the {@code DeterministicAeadWrapper} instead.
    */
+  @Deprecated
   public static DeterministicAead getPrimitive(KeysetHandle keysetHandle)
       throws GeneralSecurityException {
     return getPrimitive(keysetHandle, /* keyManager= */ null);
@@ -66,69 +56,16 @@
    * @return a DeterministicAead primitive from a {@code keysetHandle} and a custom {@code
    *     keyManager}.
    * @throws GeneralSecurityException
+   * @deprecated Use {@code keysetHandle.GetPrimitive(keyManager, DeterministicAead.class)} after
+   *     registering the {@code DeterministicAeadWrapper} instead.
    */
+  @Deprecated
   public static DeterministicAead getPrimitive(
       KeysetHandle keysetHandle, final KeyManager<DeterministicAead> keyManager)
       throws GeneralSecurityException {
+    Registry.registerPrimitiveWrapper(new DeterministicAeadWrapper());
     final PrimitiveSet<DeterministicAead> primitives =
-        Registry.getPrimitives(keysetHandle, keyManager);
-    validate(primitives);
-    return new DeterministicAead() {
-      @Override
-      public byte[] encryptDeterministically(final byte[] plaintext, final byte[] associatedData)
-          throws GeneralSecurityException {
-        return Bytes.concat(
-            primitives.getPrimary().getIdentifier(),
-            primitives
-                .getPrimary()
-                .getPrimitive()
-                .encryptDeterministically(plaintext, associatedData));
-      }
-
-      @Override
-      public byte[] decryptDeterministically(final byte[] ciphertext, final byte[] associatedData)
-          throws GeneralSecurityException {
-        if (ciphertext.length > CryptoFormat.NON_RAW_PREFIX_SIZE) {
-          byte[] prefix = Arrays.copyOfRange(ciphertext, 0, CryptoFormat.NON_RAW_PREFIX_SIZE);
-          byte[] ciphertextNoPrefix =
-              Arrays.copyOfRange(ciphertext, CryptoFormat.NON_RAW_PREFIX_SIZE, ciphertext.length);
-          List<PrimitiveSet.Entry<DeterministicAead>> entries = primitives.getPrimitive(prefix);
-          for (PrimitiveSet.Entry<DeterministicAead> entry : entries) {
-            try {
-              return entry
-                  .getPrimitive()
-                  .decryptDeterministically(ciphertextNoPrefix, associatedData);
-            } catch (GeneralSecurityException e) {
-              logger.info("ciphertext prefix matches a key, but cannot decrypt: " + e.toString());
-              continue;
-            }
-          }
-        }
-
-        // Let's try all RAW keys.
-        List<PrimitiveSet.Entry<DeterministicAead>> entries = primitives.getRawPrimitives();
-        for (PrimitiveSet.Entry<DeterministicAead> entry : entries) {
-          try {
-            return entry.getPrimitive().decryptDeterministically(ciphertext, associatedData);
-          } catch (GeneralSecurityException e) {
-            continue;
-          }
-        }
-        // nothing works.
-        throw new GeneralSecurityException("decryption failed");
-      }
-    };
-  }
-
-  // Check that all primitives in <code>pset</code> are DeterministicAead instances.
-  private static void validate(final PrimitiveSet<DeterministicAead> pset)
-      throws GeneralSecurityException {
-    for (Collection<PrimitiveSet.Entry<DeterministicAead>> entries : pset.getAll()) {
-      for (PrimitiveSet.Entry<DeterministicAead> entry : entries) {
-        if (!(entry.getPrimitive() instanceof DeterministicAead)) {
-          throw new GeneralSecurityException("invalid Deterministic AEAD key material");
-        }
-      }
-    }
+        Registry.getPrimitives(keysetHandle, keyManager, DeterministicAead.class);
+    return Registry.wrap(primitives);
   }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/daead/DeterministicAeadWrapper.java b/java/src/main/java/com/google/crypto/tink/daead/DeterministicAeadWrapper.java
new file mode 100644
index 0000000..5e18766
--- /dev/null
+++ b/java/src/main/java/com/google/crypto/tink/daead/DeterministicAeadWrapper.java
@@ -0,0 +1,102 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.daead;
+
+import com.google.crypto.tink.CryptoFormat;
+import com.google.crypto.tink.DeterministicAead;
+import com.google.crypto.tink.PrimitiveSet;
+import com.google.crypto.tink.PrimitiveWrapper;
+import com.google.crypto.tink.subtle.Bytes;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Logger;
+
+/**
+ * The implementation of {@code PrimitiveWrapper<DeterministicAead>}.
+ *
+ * <p>The created primitive works with a keyset (rather than a single key). To encrypt a plaintext,
+ * it uses the primary key in the keyset, and prepends to the ciphertext a certain prefix associated
+ * with the primary key. To decrypt, the primitive uses the prefix of the ciphertext to efficiently
+ * select the right key in the set. If the keys associated with the prefix do not work, the
+ * primitive tries all keys with {@link com.google.crypto.tink.proto.OutputPrefixType#RAW}.
+ */
+public final class DeterministicAeadWrapper implements PrimitiveWrapper<DeterministicAead> {
+  private static final Logger logger = Logger.getLogger(DeterministicAeadWrapper.class.getName());
+
+  private static class WrappedDeterministicAead implements DeterministicAead {
+    private PrimitiveSet<DeterministicAead> primitives;
+
+    public WrappedDeterministicAead(PrimitiveSet<DeterministicAead> primitives) {
+      this.primitives = primitives;
+    }
+
+    @Override
+    public byte[] encryptDeterministically(final byte[] plaintext, final byte[] associatedData)
+        throws GeneralSecurityException {
+      return Bytes.concat(
+          primitives.getPrimary().getIdentifier(),
+          primitives
+              .getPrimary()
+              .getPrimitive()
+              .encryptDeterministically(plaintext, associatedData));
+    }
+
+    @Override
+    public byte[] decryptDeterministically(final byte[] ciphertext, final byte[] associatedData)
+        throws GeneralSecurityException {
+      if (ciphertext.length > CryptoFormat.NON_RAW_PREFIX_SIZE) {
+        byte[] prefix = Arrays.copyOfRange(ciphertext, 0, CryptoFormat.NON_RAW_PREFIX_SIZE);
+        byte[] ciphertextNoPrefix =
+            Arrays.copyOfRange(ciphertext, CryptoFormat.NON_RAW_PREFIX_SIZE, ciphertext.length);
+        List<PrimitiveSet.Entry<DeterministicAead>> entries = primitives.getPrimitive(prefix);
+        for (PrimitiveSet.Entry<DeterministicAead> entry : entries) {
+          try {
+            return entry
+                .getPrimitive()
+                .decryptDeterministically(ciphertextNoPrefix, associatedData);
+          } catch (GeneralSecurityException e) {
+            logger.info("ciphertext prefix matches a key, but cannot decrypt: " + e.toString());
+            continue;
+          }
+        }
+      }
+
+      // Let's try all RAW keys.
+      List<PrimitiveSet.Entry<DeterministicAead>> entries = primitives.getRawPrimitives();
+      for (PrimitiveSet.Entry<DeterministicAead> entry : entries) {
+        try {
+          return entry.getPrimitive().decryptDeterministically(ciphertext, associatedData);
+        } catch (GeneralSecurityException e) {
+          continue;
+        }
+      }
+      // nothing works.
+      throw new GeneralSecurityException("decryption failed");
+    }
+  }
+
+  @Override
+  public DeterministicAead wrap(final PrimitiveSet<DeterministicAead> primitives) {
+    return new WrappedDeterministicAead(primitives);
+  }
+
+  @Override
+  public Class<DeterministicAead> getPrimitiveClass() {
+    return DeterministicAead.class;
+  }
+}
diff --git a/java/src/main/java/com/google/crypto/tink/hybrid/BUILD.bazel b/java/src/main/java/com/google/crypto/tink/hybrid/BUILD.bazel
index 7f2fa9a..54aa3b1 100644
--- a/java/src/main/java/com/google/crypto/tink/hybrid/BUILD.bazel
+++ b/java/src/main/java/com/google/crypto/tink/hybrid/BUILD.bazel
@@ -20,6 +20,7 @@
     "//proto:common_java_proto",
     "//proto:config_java_proto",
     "//proto:ecies_aead_hkdf_java_proto",
+    "//proto:empty_java_proto",
     "//proto:hmac_java_proto",
     "//proto:tink_java_proto",
 ]
@@ -31,6 +32,7 @@
     "//proto:common_java_proto_lite",
     "//proto:config_java_proto_lite",
     "//proto:ecies_aead_hkdf_java_proto_lite",
+    "//proto:empty_java_proto",
     "//proto:hmac_java_proto_lite",
     "//proto:tink_java_proto_lite",
 ]
diff --git a/java/src/main/java/com/google/crypto/tink/hybrid/EciesAeadHkdfPrivateKeyManager.java b/java/src/main/java/com/google/crypto/tink/hybrid/EciesAeadHkdfPrivateKeyManager.java
index 0a853e8..fc3c636 100644
--- a/java/src/main/java/com/google/crypto/tink/hybrid/EciesAeadHkdfPrivateKeyManager.java
+++ b/java/src/main/java/com/google/crypto/tink/hybrid/EciesAeadHkdfPrivateKeyManager.java
@@ -17,6 +17,7 @@
 package com.google.crypto.tink.hybrid;
 
 import com.google.crypto.tink.HybridDecrypt;
+import com.google.crypto.tink.KeyManagerBase;
 import com.google.crypto.tink.PrivateKeyManager;
 import com.google.crypto.tink.proto.EciesAeadHkdfKeyFormat;
 import com.google.crypto.tink.proto.EciesAeadHkdfParams;
@@ -24,13 +25,13 @@
 import com.google.crypto.tink.proto.EciesAeadHkdfPublicKey;
 import com.google.crypto.tink.proto.EciesHkdfKemParams;
 import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
 import com.google.crypto.tink.subtle.EciesAeadHkdfDemHelper;
 import com.google.crypto.tink.subtle.EciesAeadHkdfHybridDecrypt;
 import com.google.crypto.tink.subtle.EllipticCurves;
 import com.google.crypto.tink.subtle.Validators;
 import com.google.protobuf.ByteString;
 import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
 import java.security.GeneralSecurityException;
 import java.security.KeyPair;
 import java.security.interfaces.ECPrivateKey;
@@ -41,31 +42,22 @@
  * This key manager generates new {@code EciesAeadHkdfPrivateKey} keys and produces new instances of
  * {@code EciesAeadHkdfHybridDecrypt}.
  */
-class EciesAeadHkdfPrivateKeyManager implements PrivateKeyManager<HybridDecrypt> {
+class EciesAeadHkdfPrivateKeyManager
+    extends KeyManagerBase<HybridDecrypt, EciesAeadHkdfPrivateKey, EciesAeadHkdfKeyFormat>
+    implements PrivateKeyManager<HybridDecrypt> {
+  public EciesAeadHkdfPrivateKeyManager() {
+    super(
+        HybridDecrypt.class, EciesAeadHkdfPrivateKey.class, EciesAeadHkdfKeyFormat.class, TYPE_URL);
+  }
+
   private static final int VERSION = 0;
 
   public static final String TYPE_URL =
       "type.googleapis.com/google.crypto.tink.EciesAeadHkdfPrivateKey";
 
-  /** @param serializedKey serialized {@code EciesAeadHkdfPrivateKey} proto */
   @Override
-  public HybridDecrypt getPrimitive(ByteString serializedKey) throws GeneralSecurityException {
-    try {
-      EciesAeadHkdfPrivateKey recipientKeyProto = EciesAeadHkdfPrivateKey.parseFrom(serializedKey);
-      return getPrimitive(recipientKeyProto);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("expected serialized EciesAeadHkdfPrivateKey proto", e);
-    }
-  }
-
-  /** @param recipientKey {@code EciesAeadHkdfPrivateKey} proto */
-  @Override
-  public HybridDecrypt getPrimitive(MessageLite recipientKey) throws GeneralSecurityException {
-    if (!(recipientKey instanceof EciesAeadHkdfPrivateKey)) {
-      throw new GeneralSecurityException("expected EciesAeadHkdfPrivateKey proto");
-    }
-    EciesAeadHkdfPrivateKey recipientKeyProto = (EciesAeadHkdfPrivateKey) recipientKey;
-    validate(recipientKeyProto);
+  public HybridDecrypt getPrimitiveFromKey(EciesAeadHkdfPrivateKey recipientKeyProto)
+      throws GeneralSecurityException {
     EciesAeadHkdfParams eciesParams = recipientKeyProto.getPublicKey().getParams();
     EciesHkdfKemParams kemParams = eciesParams.getKemParams();
 
@@ -83,31 +75,9 @@
         demHelper);
   }
 
-  /**
-   * @param serializedKeyFormat serialized {@code EciesAeadHkdfKeyFormat} proto
-   * @return new {@code EciesAeadHkdfPrivateKey} proto
-   */
   @Override
-  public MessageLite newKey(ByteString serializedKeyFormat) throws GeneralSecurityException {
-    try {
-      EciesAeadHkdfKeyFormat eciesKeyFormat = EciesAeadHkdfKeyFormat.parseFrom(serializedKeyFormat);
-      return newKey(eciesKeyFormat);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("invalid EciesAeadHkdf key format", e);
-    }
-  }
-
-  /**
-   * @param keyFormat {@code EciesAeadHkdfKeyFormat} proto
-   * @return new {@code EciesAeadHkdfPrivateKey} proto
-   */
-  @Override
-  public MessageLite newKey(MessageLite keyFormat) throws GeneralSecurityException {
-    if (!(keyFormat instanceof EciesAeadHkdfKeyFormat)) {
-      throw new GeneralSecurityException("expected EciesAeadHkdfKeyFormat proto");
-    }
-    EciesAeadHkdfKeyFormat eciesKeyFormat = (EciesAeadHkdfKeyFormat) keyFormat;
-    HybridUtil.validate(eciesKeyFormat.getParams());
+  public EciesAeadHkdfPrivateKey newKeyFromFormat(EciesAeadHkdfKeyFormat eciesKeyFormat)
+      throws GeneralSecurityException {
     EciesHkdfKemParams kemParams = eciesKeyFormat.getParams().getKemParams();
     KeyPair keyPair =
         EllipticCurves.generateKeyPair(HybridUtil.toCurveType(kemParams.getCurveType()));
@@ -132,20 +102,6 @@
         .build();
   }
 
-  /**
-   * @param serializedKeyFormat serialized {@code EciesAeadHkdfKeyFormat} proto
-   * @return {@code KeyData} with a new {@code EciesAeadHkdfPrivateKey} proto
-   */
-  @Override
-  public KeyData newKeyData(ByteString serializedKeyFormat) throws GeneralSecurityException {
-    EciesAeadHkdfPrivateKey key = (EciesAeadHkdfPrivateKey) newKey(serializedKeyFormat);
-    return KeyData.newBuilder()
-        .setTypeUrl(TYPE_URL)
-        .setValue(key.toByteString())
-        .setKeyMaterialType(KeyData.KeyMaterialType.ASYMMETRIC_PRIVATE)
-        .build();
-  }
-
   @Override
   public KeyData getPublicKeyData(ByteString serializedKey) throws GeneralSecurityException {
     try {
@@ -161,13 +117,20 @@
   }
 
   @Override
-  public boolean doesSupport(String typeUrl) {
-    return TYPE_URL.equals(typeUrl);
+  protected KeyMaterialType keyMaterialType() {
+    return KeyMaterialType.ASYMMETRIC_PRIVATE;
   }
 
   @Override
-  public String getKeyType() {
-    return TYPE_URL;
+  protected EciesAeadHkdfPrivateKey parseKeyProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return EciesAeadHkdfPrivateKey.parseFrom(byteString);
+  }
+
+  @Override
+  protected EciesAeadHkdfKeyFormat parseKeyFormatProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return EciesAeadHkdfKeyFormat.parseFrom(byteString);
   }
 
   @Override
@@ -175,9 +138,16 @@
     return VERSION;
   }
 
-  private void validate(EciesAeadHkdfPrivateKey keyProto) throws GeneralSecurityException {
+  @Override
+  protected void validateKey(EciesAeadHkdfPrivateKey keyProto) throws GeneralSecurityException {
     // TODO(b/74249437): add more checks.
     Validators.validateVersion(keyProto.getVersion(), VERSION);
     HybridUtil.validate(keyProto.getPublicKey().getParams());
   }
+
+  @Override
+  protected void validateKeyFormat(EciesAeadHkdfKeyFormat eciesKeyFormat)
+      throws GeneralSecurityException {
+    HybridUtil.validate(eciesKeyFormat.getParams());
+  }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/hybrid/EciesAeadHkdfPublicKeyManager.java b/java/src/main/java/com/google/crypto/tink/hybrid/EciesAeadHkdfPublicKeyManager.java
index 8085300..94806dd 100644
--- a/java/src/main/java/com/google/crypto/tink/hybrid/EciesAeadHkdfPublicKeyManager.java
+++ b/java/src/main/java/com/google/crypto/tink/hybrid/EciesAeadHkdfPublicKeyManager.java
@@ -17,18 +17,18 @@
 package com.google.crypto.tink.hybrid;
 
 import com.google.crypto.tink.HybridEncrypt;
-import com.google.crypto.tink.KeyManager;
+import com.google.crypto.tink.KeyManagerBase;
 import com.google.crypto.tink.proto.EciesAeadHkdfParams;
 import com.google.crypto.tink.proto.EciesAeadHkdfPublicKey;
 import com.google.crypto.tink.proto.EciesHkdfKemParams;
-import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.Empty;
+import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
 import com.google.crypto.tink.subtle.EciesAeadHkdfDemHelper;
 import com.google.crypto.tink.subtle.EciesAeadHkdfHybridEncrypt;
 import com.google.crypto.tink.subtle.EllipticCurves;
 import com.google.crypto.tink.subtle.Validators;
 import com.google.protobuf.ByteString;
 import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
 import java.security.GeneralSecurityException;
 import java.security.interfaces.ECPublicKey;
 
@@ -36,31 +36,20 @@
  * This key manager produces new instances of {@code EciesAeadHkdfHybridEncrypt}. It doesn't support
  * key generation.
  */
-class EciesAeadHkdfPublicKeyManager implements KeyManager<HybridEncrypt> {
+class EciesAeadHkdfPublicKeyManager
+    extends KeyManagerBase<HybridEncrypt, EciesAeadHkdfPublicKey, Empty> {
+  public EciesAeadHkdfPublicKeyManager() {
+    super(HybridEncrypt.class, EciesAeadHkdfPublicKey.class, Empty.class, TYPE_URL);
+  }
+
   private static final int VERSION = 0;
 
   public static final String TYPE_URL =
       "type.googleapis.com/google.crypto.tink.EciesAeadHkdfPublicKey";
 
-  /** @param serializedKey serialized {@code EciesAeadHkdfPublicKey} proto */
   @Override
-  public HybridEncrypt getPrimitive(ByteString serializedKey) throws GeneralSecurityException {
-    try {
-      EciesAeadHkdfPublicKey recipientKeyProto = EciesAeadHkdfPublicKey.parseFrom(serializedKey);
-      return getPrimitive(recipientKeyProto);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("expected serialized EciesAeadHkdfPublicKey proto", e);
-    }
-  }
-
-  /** @param recipientKey {@code EciesAeadHkdfPublicKey} proto */
-  @Override
-  public HybridEncrypt getPrimitive(MessageLite recipientKey) throws GeneralSecurityException {
-    if (!(recipientKey instanceof EciesAeadHkdfPublicKey)) {
-      throw new GeneralSecurityException("expected EciesAeadHkdfPublicKey proto");
-    }
-    EciesAeadHkdfPublicKey recipientKeyProto = (EciesAeadHkdfPublicKey) recipientKey;
-    validate(recipientKeyProto);
+  protected HybridEncrypt getPrimitiveFromKey(EciesAeadHkdfPublicKey recipientKeyProto)
+      throws GeneralSecurityException {
     EciesAeadHkdfParams eciesParams = recipientKeyProto.getParams();
     EciesHkdfKemParams kemParams = eciesParams.getKemParams();
     ECPublicKey recipientPublicKey =
@@ -78,51 +67,39 @@
         demHelper);
   }
 
-  /**
-   * @param serializedKeyFormat serialized {@code EciesAeadHkdfKeyFormat} proto
-   * @return new {@code EciesAeadHkdfPublicKey} proto
-   */
   @Override
-  public MessageLite newKey(ByteString serializedKeyFormat) throws GeneralSecurityException {
+  public EciesAeadHkdfPublicKey newKeyFromFormat(Empty format) throws GeneralSecurityException {
     throw new GeneralSecurityException("Not implemented.");
   }
 
-  /**
-   * @param keyFormat {@code EciesAeadHkdfKeyFormat} proto
-   * @return new {@code EciesAeadHkdfPublicKey} proto
-   */
-  @Override
-  public MessageLite newKey(MessageLite keyFormat) throws GeneralSecurityException {
-    throw new GeneralSecurityException("Not implemented.");
-  }
-
-  /**
-   * @param serializedKeyFormat serialized {@code EciesAeadHkdfKeyFormat} proto
-   * @return {@code KeyData} with a new {@code EciesAeadHkdfPrivateKey} proto
-   */
-  @Override
-  public KeyData newKeyData(ByteString serializedKeyFormat) throws GeneralSecurityException {
-    throw new GeneralSecurityException("Not implemented.");
-  }
-
-  @Override
-  public boolean doesSupport(String typeUrl) {
-    return TYPE_URL.equals(typeUrl);
-  }
-
-  @Override
-  public String getKeyType() {
-    return TYPE_URL;
-  }
-
   @Override
   public int getVersion() {
     return VERSION;
   }
 
-  private void validate(EciesAeadHkdfPublicKey key) throws GeneralSecurityException {
+  @Override
+  protected KeyMaterialType keyMaterialType() {
+    return KeyMaterialType.ASYMMETRIC_PUBLIC;
+  }
+
+  @Override
+  protected EciesAeadHkdfPublicKey parseKeyProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return EciesAeadHkdfPublicKey.parseFrom(byteString);
+  }
+
+  @Override
+  protected Empty parseKeyFormatProto(ByteString byteString) throws InvalidProtocolBufferException {
+    return Empty.parseFrom(byteString);
+  }
+
+  @Override
+  protected void validateKey(EciesAeadHkdfPublicKey key) throws GeneralSecurityException {
     // TODO(b/74251423): add more checks.
     Validators.validateVersion(key.getVersion(), VERSION);
     HybridUtil.validate(key.getParams());
   }
+
+  @Override
+  protected void validateKeyFormat(Empty unused) throws GeneralSecurityException {}
 }
diff --git a/java/src/main/java/com/google/crypto/tink/hybrid/HybridDecryptCatalogue.java b/java/src/main/java/com/google/crypto/tink/hybrid/HybridDecryptCatalogue.java
index aa9b7c5..acf1bf9 100644
--- a/java/src/main/java/com/google/crypto/tink/hybrid/HybridDecryptCatalogue.java
+++ b/java/src/main/java/com/google/crypto/tink/hybrid/HybridDecryptCatalogue.java
@@ -19,6 +19,7 @@
 import com.google.crypto.tink.Catalogue;
 import com.google.crypto.tink.HybridDecrypt;
 import com.google.crypto.tink.KeyManager;
+import com.google.crypto.tink.PrimitiveWrapper;
 import java.security.GeneralSecurityException;
 
 /** A catalogue of {@link HybridDecrypt} key managers. */
@@ -59,4 +60,9 @@
             String.format("No support for primitive 'HybridEncrypt' with key type '%s'.", typeUrl));
     }
   }
+
+  @Override
+  public PrimitiveWrapper<HybridDecrypt> getPrimitiveWrapper() {
+    return new HybridDecryptWrapper();
+  }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/hybrid/HybridDecryptFactory.java b/java/src/main/java/com/google/crypto/tink/hybrid/HybridDecryptFactory.java
index 396213b..2e5731a 100644
--- a/java/src/main/java/com/google/crypto/tink/hybrid/HybridDecryptFactory.java
+++ b/java/src/main/java/com/google/crypto/tink/hybrid/HybridDecryptFactory.java
@@ -15,45 +15,37 @@
 ////////////////////////////////////////////////////////////////////////////////
 package com.google.crypto.tink.hybrid;
 
-import com.google.crypto.tink.CryptoFormat;
 import com.google.crypto.tink.HybridDecrypt;
 import com.google.crypto.tink.KeyManager;
 import com.google.crypto.tink.KeysetHandle;
 import com.google.crypto.tink.PrimitiveSet;
 import com.google.crypto.tink.Registry;
 import java.security.GeneralSecurityException;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.logging.Logger;
 
 /**
- * Static methods for obtaining {@link HybridDecrypt} instances.
+ * Deprecated class to create {@code HybridDecrypt} primitives. Instead of using this class, make
+ * sure that the {@code HybridDecryptWrapper} is registered in your binary, then call {@code
+ * keysetHandle.GetPrimitive(HybridDecrypt.class)} instead. The required registration happens
+ * automatically if you called one of the following in your binary:
  *
- * <h3>Usage</h3>
+ * <ul>
+ *   <li>{@code HybridConfig.register()}
+ *   <li>{@code TinkConfig.register()}
+ * </ul>
  *
- * <pre>{@code
- * KeysetHandle keysetHandle = ...;
- * HybridDecrypt hybridDecrypt = HybridDecryptFactory.getPrimitive(keysetHandle);
- * byte[] ciphertext = ...;
- * byte[] contextInfo = ...;
- * byte[] plaintext = hybridDecrypt.decrypt(ciphertext, contextInfo);
- * }</pre>
- *
- * <p>The returned primitive works with a keyset (rather than a single key). To decrypt, the
- * primitive uses the prefix of the ciphertext to efficiently select the right key in the set. If
- * the keys associated with the prefix do not work, the primitive tries all keys with {@link
- * com.google.crypto.tink.proto.OutputPrefixType#RAW}.
- *
+ * @deprecated Use {@code keysetHandle.GetPrimitive(HybridDecrypt.class)} after registering the
+ *     {@code HybridDecryptWrapper} instead.
  * @since 1.0.0
  */
+@Deprecated
 public final class HybridDecryptFactory {
-  private static final Logger logger = Logger.getLogger(HybridDecryptFactory.class.getName());
-
   /**
    * @return a HybridDecrypt primitive from a {@code keysetHandle}.
    * @throws GeneralSecurityException
+   * @deprecated Use {@code keysetHandle.GetPrimitive(HybridDecrypt.class)} after registering the
+   *     {@code HybridDecryptWrapper} instead.
    */
+  @Deprecated
   public static HybridDecrypt getPrimitive(KeysetHandle keysetHandle)
       throws GeneralSecurityException {
     return getPrimitive(keysetHandle, /* keyManager= */ null);
@@ -61,54 +53,16 @@
   /**
    * @return a HybridDecrypt primitive from a {@code keysetHandle} and a custom {@code keyManager}.
    * @throws GeneralSecurityException
+   * @deprecated Use {@code keysetHandle.GetPrimitive(keyManager, HybridDecrypt.class)} after
+   *     registering the {@code HybridDecryptWrapper} instead.
    */
+  @Deprecated
   public static HybridDecrypt getPrimitive(
       KeysetHandle keysetHandle, final KeyManager<HybridDecrypt> keyManager)
       throws GeneralSecurityException {
-    final PrimitiveSet<HybridDecrypt> primitives = Registry.getPrimitives(keysetHandle, keyManager);
-    validate(primitives);
-    return new HybridDecrypt() {
-      @Override
-      public byte[] decrypt(final byte[] ciphertext, final byte[] contextInfo)
-          throws GeneralSecurityException {
-        if (ciphertext.length > CryptoFormat.NON_RAW_PREFIX_SIZE) {
-          byte[] prefix = Arrays.copyOfRange(ciphertext, 0, CryptoFormat.NON_RAW_PREFIX_SIZE);
-          byte[] ciphertextNoPrefix =
-              Arrays.copyOfRange(ciphertext, CryptoFormat.NON_RAW_PREFIX_SIZE, ciphertext.length);
-          List<PrimitiveSet.Entry<HybridDecrypt>> entries = primitives.getPrimitive(prefix);
-          for (PrimitiveSet.Entry<HybridDecrypt> entry : entries) {
-            try {
-              return entry.getPrimitive().decrypt(ciphertextNoPrefix, contextInfo);
-            } catch (GeneralSecurityException e) {
-              logger.info("ciphertext prefix matches a key, but cannot decrypt: " + e.toString());
-              continue;
-            }
-          }
-        }
-        // Let's try all RAW keys.
-        List<PrimitiveSet.Entry<HybridDecrypt>> entries = primitives.getRawPrimitives();
-        for (PrimitiveSet.Entry<HybridDecrypt> entry : entries) {
-          try {
-            return entry.getPrimitive().decrypt(ciphertext, contextInfo);
-          } catch (GeneralSecurityException e) {
-            continue;
-          }
-        }
-        // nothing works.
-        throw new GeneralSecurityException("decryption failed");
-      }
-    };
-  }
-
-  // Check that all primitives in <code>pset</code> are HybridDecrypt instances.
-  private static void validate(final PrimitiveSet<HybridDecrypt> pset)
-      throws GeneralSecurityException {
-    for (Collection<PrimitiveSet.Entry<HybridDecrypt>> entries : pset.getAll()) {
-      for (PrimitiveSet.Entry<HybridDecrypt> entry : entries) {
-        if (!(entry.getPrimitive() instanceof HybridDecrypt)) {
-          throw new GeneralSecurityException("invalid HybridDecrypt key material");
-        }
-      }
-    }
+    Registry.registerPrimitiveWrapper(new HybridDecryptWrapper());
+    final PrimitiveSet<HybridDecrypt> primitives =
+        Registry.getPrimitives(keysetHandle, keyManager, HybridDecrypt.class);
+    return Registry.wrap(primitives);
   }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/hybrid/HybridDecryptWrapper.java b/java/src/main/java/com/google/crypto/tink/hybrid/HybridDecryptWrapper.java
new file mode 100644
index 0000000..dd72b09
--- /dev/null
+++ b/java/src/main/java/com/google/crypto/tink/hybrid/HybridDecryptWrapper.java
@@ -0,0 +1,85 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+package com.google.crypto.tink.hybrid;
+
+import com.google.crypto.tink.CryptoFormat;
+import com.google.crypto.tink.HybridDecrypt;
+import com.google.crypto.tink.PrimitiveSet;
+import com.google.crypto.tink.PrimitiveWrapper;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Logger;
+
+/**
+ * The implementation of {@code PrimitiveWrapper<HybridDecrypt>}.
+ *
+ * <p>The returned primitive works with a keyset (rather than a single key). To decrypt, the
+ * primitive uses the prefix of the ciphertext to efficiently select the right key in the set. If
+ * the keys associated with the prefix do not work, the primitive tries all keys with {@link
+ * com.google.crypto.tink.proto.OutputPrefixType#RAW}.
+ */
+public final class HybridDecryptWrapper implements PrimitiveWrapper<HybridDecrypt> {
+  private static final Logger logger = Logger.getLogger(HybridDecryptWrapper.class.getName());
+
+  private static class WrappedHybridDecrypt implements HybridDecrypt {
+    private final PrimitiveSet<HybridDecrypt> primitives;
+
+    public WrappedHybridDecrypt(final PrimitiveSet<HybridDecrypt> primitives) {
+      this.primitives = primitives;
+    }
+
+    @Override
+    public byte[] decrypt(final byte[] ciphertext, final byte[] contextInfo)
+        throws GeneralSecurityException {
+      if (ciphertext.length > CryptoFormat.NON_RAW_PREFIX_SIZE) {
+        byte[] prefix = Arrays.copyOfRange(ciphertext, 0, CryptoFormat.NON_RAW_PREFIX_SIZE);
+        byte[] ciphertextNoPrefix =
+            Arrays.copyOfRange(ciphertext, CryptoFormat.NON_RAW_PREFIX_SIZE, ciphertext.length);
+        List<PrimitiveSet.Entry<HybridDecrypt>> entries = primitives.getPrimitive(prefix);
+        for (PrimitiveSet.Entry<HybridDecrypt> entry : entries) {
+          try {
+            return entry.getPrimitive().decrypt(ciphertextNoPrefix, contextInfo);
+          } catch (GeneralSecurityException e) {
+            logger.info("ciphertext prefix matches a key, but cannot decrypt: " + e.toString());
+            continue;
+          }
+        }
+      }
+      // Let's try all RAW keys.
+      List<PrimitiveSet.Entry<HybridDecrypt>> entries = primitives.getRawPrimitives();
+      for (PrimitiveSet.Entry<HybridDecrypt> entry : entries) {
+        try {
+          return entry.getPrimitive().decrypt(ciphertext, contextInfo);
+        } catch (GeneralSecurityException e) {
+          continue;
+        }
+      }
+      // nothing works.
+      throw new GeneralSecurityException("decryption failed");
+    }
+  }
+
+  @Override
+  public HybridDecrypt wrap(final PrimitiveSet<HybridDecrypt> primitives) {
+    return new WrappedHybridDecrypt(primitives);
+  }
+
+  @Override
+  public Class<HybridDecrypt> getPrimitiveClass() {
+    return HybridDecrypt.class;
+  }
+}
diff --git a/java/src/main/java/com/google/crypto/tink/hybrid/HybridEncryptCatalogue.java b/java/src/main/java/com/google/crypto/tink/hybrid/HybridEncryptCatalogue.java
index c266f01..f404e92 100644
--- a/java/src/main/java/com/google/crypto/tink/hybrid/HybridEncryptCatalogue.java
+++ b/java/src/main/java/com/google/crypto/tink/hybrid/HybridEncryptCatalogue.java
@@ -19,6 +19,7 @@
 import com.google.crypto.tink.Catalogue;
 import com.google.crypto.tink.HybridEncrypt;
 import com.google.crypto.tink.KeyManager;
+import com.google.crypto.tink.PrimitiveWrapper;
 import java.security.GeneralSecurityException;
 
 /** A catalogue of {@link HybridEncrypt} key managers. */
@@ -59,4 +60,9 @@
             String.format("No support for primitive 'HybridEncrypt' with key type '%s'.", typeUrl));
     }
   }
+
+  @Override
+  public PrimitiveWrapper<HybridEncrypt> getPrimitiveWrapper() {
+    return new HybridEncryptWrapper();
+  }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/hybrid/HybridEncryptFactory.java b/java/src/main/java/com/google/crypto/tink/hybrid/HybridEncryptFactory.java
index 2390b43..4e0ad35 100644
--- a/java/src/main/java/com/google/crypto/tink/hybrid/HybridEncryptFactory.java
+++ b/java/src/main/java/com/google/crypto/tink/hybrid/HybridEncryptFactory.java
@@ -20,37 +20,32 @@
 import com.google.crypto.tink.KeysetHandle;
 import com.google.crypto.tink.PrimitiveSet;
 import com.google.crypto.tink.Registry;
-import com.google.crypto.tink.subtle.Bytes;
 import java.security.GeneralSecurityException;
-import java.util.Collection;
-import java.util.logging.Logger;
 
 /**
- * Static methods for obtaining {@link HybridEncrypt} instances.
+ * Deprecated class to create {@code HybridEncrypt} primitives. Instead of using this class, make
+ * sure that the {@code HybridEncryptWrapper} is registered in your binary, then call {@code
+ * keysetHandle.GetPrimitive(HybridEncrypt.class)} instead. The required registration happens
+ * automatically if you called one of the following in your binary:
  *
- * <h3>Usage</h3>
+ * <ul>
+ *   <li>{@code HybridConfig.register()}
+ *   <li>{@code TinkConfig.register()}
+ * </ul>
  *
- * <pre>{@code
- * KeysetHandle keysetHandle = ...;
- * HybridEncrypt hybridEncrypt = HybridEncryptFactory.getPrimitive(keysetHandle);
- * byte[] plaintext = ...;
- * byte[] contextInfo = ...;
- * byte[] ciphertext = hybridEncrypt.encrypt(plaintext, contextInfo);
- * }</pre>
- *
- * <p>The returned primitive works with a keyset (rather than a single key). To encrypt a plaintext,
- * it uses the primary key in the keyset, and prepends to the ciphertext a certain prefix associated
- * with the primary key.
- *
+ * @deprecated Use {@code keysetHandle.GetPrimitive(HybridEncrypt.class)} after registering the
+ *     {@code HybridEncryptWrapper} instead.
  * @since 1.0.0
  */
+@Deprecated
 public final class HybridEncryptFactory {
-  private static final Logger logger = Logger.getLogger(HybridEncryptFactory.class.getName());
-
   /**
    * @return a HybridEncrypt primitive from a {@code keysetHandle}.
    * @throws GeneralSecurityException
+   * @deprecated Use {@code keysetHandle.GetPrimitive(HybridEncrypt.class)} after registering the
+   *     {@code HybridEncryptWrapper} instead.
    */
+  @Deprecated
   public static HybridEncrypt getPrimitive(KeysetHandle keysetHandle)
       throws GeneralSecurityException {
     return getPrimitive(keysetHandle, /* keyManager= */ null);
@@ -59,32 +54,16 @@
   /**
    * @return a HybridEncrypt primitive from a {@code keysetHandle} and a custom {@code keyManager}.
    * @throws GeneralSecurityException
+   * @deprecated Use {@code keysetHandle.GetPrimitive(keyManager, HybridEncrypt.class)} after
+   *     registering the {@code HybridEncryptWrapper} instead.
    */
+  @Deprecated
   public static HybridEncrypt getPrimitive(
       KeysetHandle keysetHandle, final KeyManager<HybridEncrypt> keyManager)
       throws GeneralSecurityException {
-    final PrimitiveSet<HybridEncrypt> primitives = Registry.getPrimitives(keysetHandle, keyManager);
-    validate(primitives);
-    return new HybridEncrypt() {
-      @Override
-      public byte[] encrypt(final byte[] plaintext, final byte[] contextInfo)
-          throws GeneralSecurityException {
-        return Bytes.concat(
-            primitives.getPrimary().getIdentifier(),
-            primitives.getPrimary().getPrimitive().encrypt(plaintext, contextInfo));
-      }
-    };
-  }
-
-  // Check that all primitives in <code>pset</code> are HybridEncrypt instances.
-  private static void validate(final PrimitiveSet<HybridEncrypt> pset)
-      throws GeneralSecurityException {
-    for (Collection<PrimitiveSet.Entry<HybridEncrypt>> entries : pset.getAll()) {
-      for (PrimitiveSet.Entry<HybridEncrypt> entry : entries) {
-        if (!(entry.getPrimitive() instanceof HybridEncrypt)) {
-          throw new GeneralSecurityException("invalid HybridEncrypt key material");
-        }
-      }
-    }
+    Registry.registerPrimitiveWrapper(new HybridEncryptWrapper());
+    final PrimitiveSet<HybridEncrypt> primitives =
+        Registry.getPrimitives(keysetHandle, keyManager, HybridEncrypt.class);
+    return Registry.wrap(primitives);
   }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/hybrid/HybridEncryptWrapper.java b/java/src/main/java/com/google/crypto/tink/hybrid/HybridEncryptWrapper.java
new file mode 100644
index 0000000..87ed5e3
--- /dev/null
+++ b/java/src/main/java/com/google/crypto/tink/hybrid/HybridEncryptWrapper.java
@@ -0,0 +1,57 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+package com.google.crypto.tink.hybrid;
+
+import com.google.crypto.tink.HybridEncrypt;
+import com.google.crypto.tink.PrimitiveSet;
+import com.google.crypto.tink.PrimitiveWrapper;
+import com.google.crypto.tink.subtle.Bytes;
+import java.security.GeneralSecurityException;
+
+/**
+ * The implementation of {@code PrimitiveWrapper<HybridEncrypt>}.
+ *
+ * <p>The returned primitive works with a keyset (rather than a single key). To encrypt a plaintext,
+ * it uses the primary key in the keyset, and prepends to the ciphertext a certain prefix associated
+ * with the primary key.
+ */
+public final class HybridEncryptWrapper implements PrimitiveWrapper<HybridEncrypt> {
+  private static class WrappedHybridEncrypt implements HybridEncrypt {
+    final PrimitiveSet<HybridEncrypt> primitives;
+
+    public WrappedHybridEncrypt(final PrimitiveSet<HybridEncrypt> primitives) {
+      this.primitives = primitives;
+    }
+
+    @Override
+    public byte[] encrypt(final byte[] plaintext, final byte[] contextInfo)
+        throws GeneralSecurityException {
+      return Bytes.concat(
+          primitives.getPrimary().getIdentifier(),
+          primitives.getPrimary().getPrimitive().encrypt(plaintext, contextInfo));
+    }
+  }
+
+  @Override
+  public HybridEncrypt wrap(final PrimitiveSet<HybridEncrypt> primitives) {
+    return new WrappedHybridEncrypt(primitives);
+  }
+
+  @Override
+  public Class<HybridEncrypt> getPrimitiveClass() {
+    return HybridEncrypt.class;
+  }
+}
diff --git a/java/src/main/java/com/google/crypto/tink/hybrid/RegistryEciesAeadHkdfDemHelper.java b/java/src/main/java/com/google/crypto/tink/hybrid/RegistryEciesAeadHkdfDemHelper.java
index b3665f1..f152a7f 100644
--- a/java/src/main/java/com/google/crypto/tink/hybrid/RegistryEciesAeadHkdfDemHelper.java
+++ b/java/src/main/java/com/google/crypto/tink/hybrid/RegistryEciesAeadHkdfDemHelper.java
@@ -91,7 +91,7 @@
           .mergeFrom(aesGcmKey)
           .setKeyValue(ByteString.copyFrom(symmetricKeyValue, 0, symmetricKeySize))
           .build();
-      return Registry.getPrimitive(demKeyTypeUrl, aeadKey);
+      return Registry.getPrimitive(demKeyTypeUrl, aeadKey, Aead.class);
     } else if (demKeyTypeUrl.equals(AeadConfig.AES_CTR_HMAC_AEAD_TYPE_URL)) {
       byte[] aesCtrKeyValue = Arrays.copyOfRange(symmetricKeyValue, 0, aesCtrKeySize);
       byte[] hmacKeyValue = Arrays.copyOfRange(symmetricKeyValue, aesCtrKeySize, symmetricKeySize);
@@ -111,7 +111,7 @@
               .setAesCtrKey(aesCtrKey)
               .setHmacKey(hmacKey)
               .build();
-      return Registry.getPrimitive(demKeyTypeUrl, aeadKey);
+      return Registry.getPrimitive(demKeyTypeUrl, aeadKey, Aead.class);
     } else {
       throw new GeneralSecurityException("unknown DEM key type");
     }
diff --git a/java/src/main/java/com/google/crypto/tink/integration/android/AndroidKeysetManager.java b/java/src/main/java/com/google/crypto/tink/integration/android/AndroidKeysetManager.java
index 29d7428..3eda5ac 100644
--- a/java/src/main/java/com/google/crypto/tink/integration/android/AndroidKeysetManager.java
+++ b/java/src/main/java/com/google/crypto/tink/integration/android/AndroidKeysetManager.java
@@ -39,9 +39,6 @@
  *
  * <p>This class reads and writes to shared preferences, thus is best not to run on the UI thread.
  *
- * <p>On Android M or newer, the keysets are encrypted with master keys generated and stored in <a
- * href="https://developer.android.com/training/articles/keystore.html">Android Keystore</a>.
- *
  * <h3>Usage</h3>
  *
  * <pre>{@code
@@ -51,7 +48,7 @@
  *    .withKeyTemplate(SignatureKeyTemplates.ECDSA_P256)
  *    .withMasterKeyUri(masterKeyUri)
  *    .build();
- * PublicKeySign signer = PublicKeySignFactory.getPrimitive(manager.getKeysetHandle());
+ * PublicKeySign signer = manager.getKeysetHandle().getPrimitive(PublicKeySign.class);
  * }</pre>
  *
  * <p>This will read a keyset stored in the {@code my_keyset_name} preference of the {@code
@@ -63,21 +60,21 @@
  * {@code my_keyset_name} preference of the {@code my_pref_file_name} shared preferences file.
  *
  * <p>On Android M or newer and if a master key URI is set with {@link
- * AndroidKeysetManager.Builder#withKeyTemplate}, the keyset will be encrypted when written to
- * storage and decrypted when read.
+ * AndroidKeysetManager.Builder#withMasterKeyUri}, the keyset is encrypted with a master key
+ * generated and stored in <a
+ * href="https://developer.android.com/training/articles/keystore.html">Android Keystore</a>. When
+ * Tink cannot decrypt the keyset it would assume that it is not encrypted.
  *
  * <p>The master key URI must start with {@code android-keystore://}. If the master key doesn't
  * exist, a fresh one is generated. Usage of Android Keystore can be disabled with {@link
  * AndroidKeysetManager.Builder#doNotUseKeystore}.
  *
- * <p>If a master key URI is not chosen or on Android L or older, the keyset will be stored in
+ * <p>On Android L or older, or when the master key URI is not set, the keyset will be stored in
  * cleartext in private preferences which, thanks to the security of the Android framework, no other
  * apps can read or write.
  *
- * <p>When Tink cannot decrypt some keyset it would assume that the keyset is in cleartext.
- *
- * <p>The resulting manager supports all operations that supported by {@link KeysetManager}. For
- * example to rotate the keyset, one can do:
+ * <p>The resulting manager supports all operations supported by {@link KeysetManager}. For example
+ * to rotate the keyset, one can do:
  *
  * <pre>{@code
  * manager.rotate(SignatureKeyTemplates.ECDSA_P256);
@@ -113,11 +110,15 @@
     }
 
     useKeystore = builder.useKeystore;
-    masterKey = builder.masterKey;
-    if (useKeystore && masterKey == null) {
+    if (useKeystore && builder.masterKeyUri == null) {
       throw new IllegalArgumentException(
           "need a master key URI, please set it with Builder#masterKeyUri");
     }
+    if (shouldUseKeystore()) {
+      masterKey = AndroidKeystoreKmsClient.getOrGenerateNewAeadKey(builder.masterKeyUri);
+    } else {
+      masterKey = null;
+    }
 
     keyTemplate = builder.keyTemplate;
     keysetManager = readOrGenerateNewKeyset();
@@ -127,7 +128,7 @@
   public static final class Builder {
     private KeysetReader reader = null;
     private KeysetWriter writer = null;
-    private Aead masterKey = null;
+    private String masterKeyUri = null;
     private boolean useKeystore = true;
     private KeyTemplate keyTemplate = null;
 
@@ -153,8 +154,12 @@
      * <p>Only master keys stored in Android Keystore is supported. The URI must start with {@code
      * android-keystore://}.
      */
-    public Builder withMasterKeyUri(String val) throws GeneralSecurityException, IOException {
-      masterKey = AndroidKeystoreKmsClient.getOrGenerateNewAeadKey(val);
+    public Builder withMasterKeyUri(String val) {
+      if (!val.startsWith(AndroidKeystoreKmsClient.PREFIX)) {
+        throw new IllegalArgumentException(
+            "key URI must start with " + AndroidKeystoreKmsClient.PREFIX);
+      }
+      masterKeyUri = val;
       return this;
     }
 
diff --git a/java/src/main/java/com/google/crypto/tink/integration/awskms/AwsKmsClient.java b/java/src/main/java/com/google/crypto/tink/integration/awskms/AwsKmsClient.java
index 4aac539..31be1d6 100644
--- a/java/src/main/java/com/google/crypto/tink/integration/awskms/AwsKmsClient.java
+++ b/java/src/main/java/com/google/crypto/tink/integration/awskms/AwsKmsClient.java
@@ -21,7 +21,7 @@
 import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
 import com.amazonaws.auth.PropertiesFileCredentialsProvider;
 import com.amazonaws.services.kms.AWSKMS;
-import com.amazonaws.services.kms.AWSKMSClient;
+import com.amazonaws.services.kms.AWSKMSClientBuilder;
 import com.google.auto.service.AutoService;
 import com.google.crypto.tink.Aead;
 import com.google.crypto.tink.KmsClient;
@@ -109,13 +109,10 @@
   }
 
   /** Loads AWS credentials from a provider. */
-  // new AWSKMSClient(provider) is deprecated, but some clients have not upgraded their AWS
-  // libraries.
-  @SuppressWarnings("deprecation")
-  private KmsClient withCredentialsProvider(AWSCredentialsProvider provider)
+  public KmsClient withCredentialsProvider(AWSCredentialsProvider provider)
       throws GeneralSecurityException {
     try {
-      this.client = new AWSKMSClient(provider);
+      this.client = AWSKMSClientBuilder.standard().withCredentials(provider).build();
       return this;
     } catch (AmazonServiceException e) {
       throw new GeneralSecurityException("cannot load credentials from provider", e);
diff --git a/java/src/main/java/com/google/crypto/tink/integration/gcpkms/GcpKmsClient.java b/java/src/main/java/com/google/crypto/tink/integration/gcpkms/GcpKmsClient.java
index a21ff34..fcf7f81 100644
--- a/java/src/main/java/com/google/crypto/tink/integration/gcpkms/GcpKmsClient.java
+++ b/java/src/main/java/com/google/crypto/tink/integration/gcpkms/GcpKmsClient.java
@@ -24,6 +24,7 @@
 import com.google.auto.service.AutoService;
 import com.google.crypto.tink.Aead;
 import com.google.crypto.tink.KmsClient;
+import com.google.crypto.tink.Version;
 import com.google.crypto.tink.subtle.Validators;
 import java.io.File;
 import java.io.FileInputStream;
@@ -41,7 +42,7 @@
   /** The prefix of all keys stored in Google Cloud KMS. */
   public static final String PREFIX = "gcp-kms://";
 
-  private static final String APPLICATION_NAME = "Tink";
+  private static final String APPLICATION_NAME = "Tink/" + Version.TINK_VERSION;
   private CloudKMS client;
   private String keyUri;
 
diff --git a/java/src/main/java/com/google/crypto/tink/mac/HmacKeyManager.java b/java/src/main/java/com/google/crypto/tink/mac/HmacKeyManager.java
index 0f73803..1262971 100644
--- a/java/src/main/java/com/google/crypto/tink/mac/HmacKeyManager.java
+++ b/java/src/main/java/com/google/crypto/tink/mac/HmacKeyManager.java
@@ -16,26 +16,28 @@
 
 package com.google.crypto.tink.mac;
 
-import com.google.crypto.tink.KeyManager;
+import com.google.crypto.tink.KeyManagerBase;
 import com.google.crypto.tink.Mac;
 import com.google.crypto.tink.proto.HashType;
 import com.google.crypto.tink.proto.HmacKey;
 import com.google.crypto.tink.proto.HmacKeyFormat;
 import com.google.crypto.tink.proto.HmacParams;
-import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
 import com.google.crypto.tink.subtle.MacJce;
 import com.google.crypto.tink.subtle.Random;
 import com.google.crypto.tink.subtle.Validators;
 import com.google.protobuf.ByteString;
 import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
 import java.security.GeneralSecurityException;
 import javax.crypto.spec.SecretKeySpec;
 
 /**
  * This key manager generates new {@code HmacKey} keys and produces new instances of {@code MacJce}.
  */
-class HmacKeyManager implements KeyManager<Mac> {
+class HmacKeyManager extends KeyManagerBase<Mac, HmacKey, HmacKeyFormat> {
+  public HmacKeyManager() {
+    super(Mac.class, HmacKey.class, HmacKeyFormat.class, TYPE_URL);
+  }
   /** Type url that this manager does support. */
   public static final String TYPE_URL = "type.googleapis.com/google.crypto.tink.HmacKey";
   /** Current version of this key manager. Keys with version equal or smaller are supported. */
@@ -49,23 +51,7 @@
 
   /** @param serializedKey serialized {@code HmacKey} proto */
   @Override
-  public Mac getPrimitive(ByteString serializedKey) throws GeneralSecurityException {
-    try {
-      HmacKey keyProto = HmacKey.parseFrom(serializedKey);
-      return getPrimitive(keyProto);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("expected serialized HmacKey proto", e);
-    }
-  }
-
-  /** @param key {@code HmacKey} proto */
-  @Override
-  public Mac getPrimitive(MessageLite key) throws GeneralSecurityException {
-    if (!(key instanceof HmacKey)) {
-      throw new GeneralSecurityException("expected HmacKey proto");
-    }
-    HmacKey keyProto = (HmacKey) key;
-    validate(keyProto);
+  public Mac getPrimitiveFromKey(HmacKey keyProto) throws GeneralSecurityException {
     HashType hash = keyProto.getParams().getHash();
     byte[] keyValue = keyProto.getKeyValue().toByteArray();
     SecretKeySpec keySpec = new SecretKeySpec(keyValue, "HMAC");
@@ -87,26 +73,7 @@
    * @return new {@code HmacKey} proto
    */
   @Override
-  public MessageLite newKey(ByteString serializedKeyFormat) throws GeneralSecurityException {
-    try {
-      HmacKeyFormat format = HmacKeyFormat.parseFrom(serializedKeyFormat);
-      return newKey(format);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("expected serialized HmacKeyFormat proto", e);
-    }
-  }
-
-  /**
-   * @param keyFormat {@code HmacKeyFormat} proto
-   * @return new {@code HmacKey} proto
-   */
-  @Override
-  public MessageLite newKey(MessageLite keyFormat) throws GeneralSecurityException {
-    if (!(keyFormat instanceof HmacKeyFormat)) {
-      throw new GeneralSecurityException("expected HmacKeyFormat proto");
-    }
-    HmacKeyFormat format = (HmacKeyFormat) keyFormat;
-    validate(format);
+  public HmacKey newKeyFromFormat(HmacKeyFormat format) throws GeneralSecurityException {
     return HmacKey.newBuilder()
         .setVersion(VERSION)
         .setParams(format.getParams())
@@ -114,36 +81,30 @@
         .build();
   }
 
-  /**
-   * @param serializedKeyFormat serialized {@code HmacKeyFormat} proto
-   * @return {@code KeyData} with a new {@code HmacKey} proto
-   */
-  @Override
-  public KeyData newKeyData(ByteString serializedKeyFormat) throws GeneralSecurityException {
-    HmacKey key = (HmacKey) newKey(serializedKeyFormat);
-    return KeyData.newBuilder()
-        .setTypeUrl(TYPE_URL)
-        .setValue(key.toByteString())
-        .setKeyMaterialType(KeyData.KeyMaterialType.SYMMETRIC)
-        .build();
-  }
-
-  @Override
-  public boolean doesSupport(String typeUrl) {
-    return typeUrl.equals(TYPE_URL);
-  }
-
-  @Override
-  public String getKeyType() {
-    return TYPE_URL;
-  }
-
   @Override
   public int getVersion() {
     return VERSION;
   }
 
-  private void validate(HmacKey key) throws GeneralSecurityException {
+  @Override
+  protected KeyMaterialType keyMaterialType() {
+    return KeyMaterialType.SYMMETRIC;
+  }
+
+  @Override
+  protected HmacKey parseKeyProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return HmacKey.parseFrom(byteString);
+  }
+
+  @Override
+  protected HmacKeyFormat parseKeyFormatProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return HmacKeyFormat.parseFrom(byteString);
+  }
+
+  @Override
+  protected void validateKey(HmacKey key) throws GeneralSecurityException {
     Validators.validateVersion(key.getVersion(), VERSION);
     if (key.getKeyValue().size() < MIN_KEY_SIZE_IN_BYTES) {
       throw new GeneralSecurityException("key too short");
@@ -151,7 +112,8 @@
     validate(key.getParams());
   }
 
-  private void validate(HmacKeyFormat format) throws GeneralSecurityException {
+  @Override
+  protected void validateKeyFormat(HmacKeyFormat format) throws GeneralSecurityException {
     if (format.getKeySize() < MIN_KEY_SIZE_IN_BYTES) {
       throw new GeneralSecurityException("key too short");
     }
diff --git a/java/src/main/java/com/google/crypto/tink/mac/MacCatalogue.java b/java/src/main/java/com/google/crypto/tink/mac/MacCatalogue.java
index 61db4c2..8b2bd0c 100644
--- a/java/src/main/java/com/google/crypto/tink/mac/MacCatalogue.java
+++ b/java/src/main/java/com/google/crypto/tink/mac/MacCatalogue.java
@@ -19,6 +19,7 @@
 import com.google.crypto.tink.Catalogue;
 import com.google.crypto.tink.KeyManager;
 import com.google.crypto.tink.Mac;
+import com.google.crypto.tink.PrimitiveWrapper;
 import java.security.GeneralSecurityException;
 
 /** A catalogue of {@link Mac} key managers. */
@@ -58,4 +59,9 @@
             String.format("No support for primitive 'Mac' with key type '%s'.", typeUrl));
     }
   }
+
+  @Override
+  public PrimitiveWrapper<Mac> getPrimitiveWrapper() {
+    return new MacWrapper();
+  }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/mac/MacFactory.java b/java/src/main/java/com/google/crypto/tink/mac/MacFactory.java
index 1b43aba..85f1d46 100644
--- a/java/src/main/java/com/google/crypto/tink/mac/MacFactory.java
+++ b/java/src/main/java/com/google/crypto/tink/mac/MacFactory.java
@@ -16,48 +16,39 @@
 
 package com.google.crypto.tink.mac;
 
-import com.google.crypto.tink.CryptoFormat;
 import com.google.crypto.tink.KeyManager;
 import com.google.crypto.tink.KeysetHandle;
 import com.google.crypto.tink.Mac;
 import com.google.crypto.tink.PrimitiveSet;
 import com.google.crypto.tink.Registry;
-import com.google.crypto.tink.proto.OutputPrefixType;
-import com.google.crypto.tink.subtle.Bytes;
 import java.security.GeneralSecurityException;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.logging.Logger;
 
 /**
- * Static methods for obtaining {@link Mac} instances.
+ * Deprecated class to create {@code Mac} primitives. Instead of using this class, make sure that
+ * the {@code MacWrapper} is registered in your binary, then call {@code
+ * keysetHandle.GetPrimitive(Mac.class)} instead. The required registration happens automatically if
+ * you called one of the following in your binary:
  *
- * <h3>Usage</h3>
+ * <ul>
+ *   <li>{@code MacConfig.register()}
+ *   <li>{@code HybridConfig.register()}
+ *   <li>{@code AeadConfig.register()}
+ *   <li>{@code TinkConfig.register()}
+ * </ul>
  *
- * <pre>{@code
- * KeysetHandle keysetHandle = ...;
- * Mac mac = MacFactory.getPrimitive(keysetHandle);
- * byte[] data = ...;
- * byte[] tag = mac.computeMac(data);
- *
- * }</pre>
- *
- * <p>The returned primitive works with a keyset (rather than a single key). To compute a MAC tag,
- * it uses the primary key in the keyset, and prepends to the tag a certain prefix associated with
- * the primary key. To verify a tag, the primitive uses the prefix of the tag to efficiently select
- * the right key in the set. If the keys associated with the prefix do not validate the tag, the
- * primitive tries all keys with {@link com.google.crypto.tink.proto.OutputPrefixType#RAW}.
- *
+ * @deprecated Use {@code keysetHandle.GetPrimitive(Mac.class)} after registering the {@code
+ *     MacWrapper} instead.
  * @since 1.0.0
  */
+@Deprecated
 public final class MacFactory {
-  private static final Logger logger = Logger.getLogger(MacFactory.class.getName());
-
   /**
    * @return a Mac primitive from a {@code keysetHandle}.
    * @throws GeneralSecurityException
+   * @deprecated Use {@code keysetHandle.GetPrimitive(Mac.class)} after registering the {@code
+   *     MacWrapper} instead.
    */
+  @Deprecated
   public static Mac getPrimitive(KeysetHandle keysetHandle) throws GeneralSecurityException {
     return getPrimitive(keysetHandle, /* keyManager= */ null);
   }
@@ -65,75 +56,15 @@
   /**
    * @return a Mac primitive from a {@code keysetHandle} and a custom {@code keyManager}.
    * @throws GeneralSecurityException
+   * @deprecated Use {@code keysetHandle.GetPrimitive(keyManager, Mac.class)} after registering the
+   *     {@code MacWrapper} instead.
    */
+  @Deprecated
   public static Mac getPrimitive(KeysetHandle keysetHandle, final KeyManager<Mac> keyManager)
       throws GeneralSecurityException {
-    final PrimitiveSet<Mac> primitives = Registry.getPrimitives(keysetHandle, keyManager);
-    validate(primitives);
-    final byte[] formatVersion = new byte[] {CryptoFormat.LEGACY_START_BYTE};
-    return new Mac() {
-      @Override
-      public byte[] computeMac(final byte[] data) throws GeneralSecurityException {
-        if (primitives.getPrimary().getOutputPrefixType().equals(OutputPrefixType.LEGACY)) {
-          return Bytes.concat(
-              primitives.getPrimary().getIdentifier(),
-              primitives.getPrimary().getPrimitive().computeMac(Bytes.concat(data, formatVersion)));
-        }
-        return Bytes.concat(
-            primitives.getPrimary().getIdentifier(),
-            primitives.getPrimary().getPrimitive().computeMac(data));
-      }
-
-      @Override
-      public void verifyMac(final byte[] mac, final byte[] data) throws GeneralSecurityException {
-        if (mac.length <= CryptoFormat.NON_RAW_PREFIX_SIZE) {
-          // This also rejects raw MAC with size of 4 bytes or fewer. Those MACs are
-          // clearly insecure, thus should be discouraged.
-          throw new GeneralSecurityException("tag too short");
-        }
-        byte[] prefix = Arrays.copyOfRange(mac, 0, CryptoFormat.NON_RAW_PREFIX_SIZE);
-        byte[] macNoPrefix = Arrays.copyOfRange(mac, CryptoFormat.NON_RAW_PREFIX_SIZE, mac.length);
-        List<PrimitiveSet.Entry<Mac>> entries = primitives.getPrimitive(prefix);
-        for (PrimitiveSet.Entry<Mac> entry : entries) {
-          try {
-            if (entry.getOutputPrefixType().equals(OutputPrefixType.LEGACY)) {
-              entry.getPrimitive().verifyMac(macNoPrefix, Bytes.concat(data, formatVersion));
-            } else {
-              entry.getPrimitive().verifyMac(macNoPrefix, data);
-            }
-            // If there is no exception, the MAC is valid and we can return.
-            return;
-          } catch (GeneralSecurityException e) {
-            logger.info("tag prefix matches a key, but cannot verify: " + e.toString());
-            // Ignored as we want to continue verification with the remaining keys.
-          }
-        }
-
-        // None "non-raw" key matched, so let's try the raw keys (if any exist).
-        entries = primitives.getRawPrimitives();
-        for (PrimitiveSet.Entry<Mac> entry : entries) {
-          try {
-            entry.getPrimitive().verifyMac(mac, data);
-            // If there is no exception, the MAC is valid and we can return.
-            return;
-          } catch (GeneralSecurityException ignored) {
-            // Ignored as we want to continue verification with other raw keys.
-          }
-        }
-        // nothing works.
-        throw new GeneralSecurityException("invalid MAC");
-      }
-    };
-  }
-
-  // Check that all primitives in <code>pset</code> are Mac instances.
-  private static void validate(final PrimitiveSet<Mac> pset) throws GeneralSecurityException {
-    for (Collection<PrimitiveSet.Entry<Mac>> entries : pset.getAll()) {
-      for (PrimitiveSet.Entry<Mac> entry : entries) {
-        if (!(entry.getPrimitive() instanceof Mac)) {
-          throw new GeneralSecurityException("invalid MAC key material");
-        }
-      }
-    }
+    Registry.registerPrimitiveWrapper(new MacWrapper());
+    final PrimitiveSet<Mac> primitives =
+        Registry.getPrimitives(keysetHandle, keyManager, Mac.class);
+    return Registry.wrap(primitives);
   }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/mac/MacWrapper.java b/java/src/main/java/com/google/crypto/tink/mac/MacWrapper.java
new file mode 100644
index 0000000..9a75e40
--- /dev/null
+++ b/java/src/main/java/com/google/crypto/tink/mac/MacWrapper.java
@@ -0,0 +1,112 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.mac;
+
+import com.google.crypto.tink.CryptoFormat;
+import com.google.crypto.tink.Mac;
+import com.google.crypto.tink.PrimitiveSet;
+import com.google.crypto.tink.PrimitiveWrapper;
+import com.google.crypto.tink.proto.OutputPrefixType;
+import com.google.crypto.tink.subtle.Bytes;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Logger;
+
+/**
+ * MacWrapper is the implementation of PrimitiveWrapper for the Mac primitive.
+ *
+ * <p>The returned primitive works with a keyset (rather than a single key). To compute a MAC tag,
+ * it uses the primary key in the keyset, and prepends to the tag a certain prefix associated with
+ * the primary key. To verify a tag, the primitive uses the prefix of the tag to efficiently select
+ * the right key in the set. If the keys associated with the prefix do not validate the tag, the
+ * primitive tries all keys with {@link com.google.crypto.tink.proto.OutputPrefixType#RAW}.
+ */
+public final class MacWrapper implements PrimitiveWrapper<Mac> {
+  private static final Logger logger = Logger.getLogger(MacWrapper.class.getName());
+
+  private static class WrappedMac implements Mac {
+    private final PrimitiveSet<Mac> primitives;
+    private final byte[] formatVersion = new byte[] {CryptoFormat.LEGACY_START_BYTE};
+
+    private WrappedMac(PrimitiveSet<Mac> primitives) {
+      this.primitives = primitives;
+    }
+
+    @Override
+    public byte[] computeMac(final byte[] data) throws GeneralSecurityException {
+      if (primitives.getPrimary().getOutputPrefixType().equals(OutputPrefixType.LEGACY)) {
+        return Bytes.concat(
+            primitives.getPrimary().getIdentifier(),
+            primitives.getPrimary().getPrimitive().computeMac(Bytes.concat(data, formatVersion)));
+      }
+      return Bytes.concat(
+          primitives.getPrimary().getIdentifier(),
+          primitives.getPrimary().getPrimitive().computeMac(data));
+    }
+
+    @Override
+    public void verifyMac(final byte[] mac, final byte[] data) throws GeneralSecurityException {
+      if (mac.length <= CryptoFormat.NON_RAW_PREFIX_SIZE) {
+        // This also rejects raw MAC with size of 4 bytes or fewer. Those MACs are
+        // clearly insecure, thus should be discouraged.
+        throw new GeneralSecurityException("tag too short");
+      }
+      byte[] prefix = Arrays.copyOfRange(mac, 0, CryptoFormat.NON_RAW_PREFIX_SIZE);
+      byte[] macNoPrefix = Arrays.copyOfRange(mac, CryptoFormat.NON_RAW_PREFIX_SIZE, mac.length);
+      List<PrimitiveSet.Entry<Mac>> entries = primitives.getPrimitive(prefix);
+      for (PrimitiveSet.Entry<Mac> entry : entries) {
+        try {
+          if (entry.getOutputPrefixType().equals(OutputPrefixType.LEGACY)) {
+            entry.getPrimitive().verifyMac(macNoPrefix, Bytes.concat(data, formatVersion));
+          } else {
+            entry.getPrimitive().verifyMac(macNoPrefix, data);
+          }
+          // If there is no exception, the MAC is valid and we can return.
+          return;
+        } catch (GeneralSecurityException e) {
+          logger.info("tag prefix matches a key, but cannot verify: " + e.toString());
+          // Ignored as we want to continue verification with the remaining keys.
+        }
+      }
+
+      // None "non-raw" key matched, so let's try the raw keys (if any exist).
+      entries = primitives.getRawPrimitives();
+      for (PrimitiveSet.Entry<Mac> entry : entries) {
+        try {
+          entry.getPrimitive().verifyMac(mac, data);
+          // If there is no exception, the MAC is valid and we can return.
+          return;
+        } catch (GeneralSecurityException ignored) {
+          // Ignored as we want to continue verification with other raw keys.
+        }
+      }
+      // nothing works.
+      throw new GeneralSecurityException("invalid MAC");
+    }
+  }
+
+  @Override
+  public Mac wrap(final PrimitiveSet<Mac> primitives) throws GeneralSecurityException {
+    return new WrappedMac(primitives);
+  }
+
+  @Override
+  public Class<Mac> getPrimitiveClass() {
+    return Mac.class;
+  }
+}
diff --git a/java/src/main/java/com/google/crypto/tink/signature/BUILD.bazel b/java/src/main/java/com/google/crypto/tink/signature/BUILD.bazel
index 04bd252..ba973a8 100644
--- a/java/src/main/java/com/google/crypto/tink/signature/BUILD.bazel
+++ b/java/src/main/java/com/google/crypto/tink/signature/BUILD.bazel
@@ -18,6 +18,9 @@
     "//proto:config_java_proto",
     "//proto:ecdsa_java_proto",
     "//proto:ed25519_java_proto",
+    "//proto:empty_java_proto",
+    "//proto:rsa_ssa_pkcs1_java_proto",
+    "//proto:rsa_ssa_pss_java_proto",
     "//proto:tink_java_proto",
 ]
 
@@ -26,6 +29,9 @@
     "//proto:config_java_proto_lite",
     "//proto:ecdsa_java_proto_lite",
     "//proto:ed25519_java_proto_lite",
+    "//proto:empty_java_proto_lite",
+    "//proto:rsa_ssa_pkcs1_java_proto_lite",
+    "//proto:rsa_ssa_pss_java_proto_lite",
     "//proto:tink_java_proto_lite",
 ]
 
diff --git a/java/src/main/java/com/google/crypto/tink/signature/EcdsaSignKeyManager.java b/java/src/main/java/com/google/crypto/tink/signature/EcdsaSignKeyManager.java
index affc81a..411d7f7 100644
--- a/java/src/main/java/com/google/crypto/tink/signature/EcdsaSignKeyManager.java
+++ b/java/src/main/java/com/google/crypto/tink/signature/EcdsaSignKeyManager.java
@@ -16,6 +16,7 @@
 
 package com.google.crypto.tink.signature;
 
+import com.google.crypto.tink.KeyManagerBase;
 import com.google.crypto.tink.PrivateKeyManager;
 import com.google.crypto.tink.PublicKeySign;
 import com.google.crypto.tink.proto.EcdsaKeyFormat;
@@ -23,12 +24,12 @@
 import com.google.crypto.tink.proto.EcdsaPrivateKey;
 import com.google.crypto.tink.proto.EcdsaPublicKey;
 import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
 import com.google.crypto.tink.subtle.EcdsaSignJce;
 import com.google.crypto.tink.subtle.EllipticCurves;
 import com.google.crypto.tink.subtle.Validators;
 import com.google.protobuf.ByteString;
 import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
 import java.security.GeneralSecurityException;
 import java.security.KeyPair;
 import java.security.interfaces.ECPrivateKey;
@@ -39,32 +40,22 @@
  * This key manager generates new {@code EcdsaPrivateKey} keys and produces new instances of {@code
  * EcdsaSignJce}.
  */
-class EcdsaSignKeyManager implements PrivateKeyManager<PublicKeySign> {
+class EcdsaSignKeyManager
+    extends KeyManagerBase<PublicKeySign, EcdsaPrivateKey, EcdsaKeyFormat>
+    implements PrivateKeyManager<PublicKeySign> {
+  public EcdsaSignKeyManager() {
+    super(PublicKeySign.class, EcdsaPrivateKey.class, EcdsaKeyFormat.class, TYPE_URL);
+  }
+
   /** Type url that this manager supports */
   public static final String TYPE_URL = "type.googleapis.com/google.crypto.tink.EcdsaPrivateKey";
 
   /** Current version of this key manager. Keys with greater version are not supported. */
   private static final int VERSION = 0;
 
-  /** @param serializedKey serialized {@code EcdsaPrivateKey} proto */
   @Override
-  public PublicKeySign getPrimitive(ByteString serializedKey) throws GeneralSecurityException {
-    try {
-      EcdsaPrivateKey privKeyProto = EcdsaPrivateKey.parseFrom(serializedKey);
-      return getPrimitive(privKeyProto);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("expected serialized EcdsaPrivateKey proto", e);
-    }
-  }
-
-  /** @param key {@code EcdsaPrivateKey} proto */
-  @Override
-  public PublicKeySign getPrimitive(MessageLite key) throws GeneralSecurityException {
-    if (!(key instanceof EcdsaPrivateKey)) {
-      throw new GeneralSecurityException("expected EcdsaPrivateKey proto");
-    }
-    EcdsaPrivateKey keyProto = (EcdsaPrivateKey) key;
-    validateKey(keyProto);
+  public PublicKeySign getPrimitiveFromKey(EcdsaPrivateKey keyProto)
+      throws GeneralSecurityException {
     ECPrivateKey privateKey =
         EllipticCurves.getEcPrivateKey(
             SigUtil.toCurveType(keyProto.getPublicKey().getParams().getCurve()),
@@ -75,32 +66,10 @@
         SigUtil.toEcdsaEncoding(keyProto.getPublicKey().getParams().getEncoding()));
   }
 
-  /**
-   * @param serializedKeyFormat serialized {@code EcdsaKeyFormat} proto
-   * @return new {@code EcdsaPrivateKey} proto
-   */
   @Override
-  public MessageLite newKey(ByteString serializedKeyFormat) throws GeneralSecurityException {
-    try {
-      EcdsaKeyFormat ecdsaKeyFormat = EcdsaKeyFormat.parseFrom(serializedKeyFormat);
-      return newKey(ecdsaKeyFormat);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("expected EcdsaKeyFormat proto", e);
-    }
-  }
-
-  /**
-   * @param keyFormat {@code EcdsaKeyFormat} proto
-   * @return new {@code EcdsaPrivateKey} proto
-   */
-  @Override
-  public MessageLite newKey(MessageLite keyFormat) throws GeneralSecurityException {
-    if (!(keyFormat instanceof EcdsaKeyFormat)) {
-      throw new GeneralSecurityException("expected EcdsaKeyFormat proto");
-    }
-    EcdsaKeyFormat format = (EcdsaKeyFormat) keyFormat;
+  public EcdsaPrivateKey newKeyFromFormat(EcdsaKeyFormat format)
+      throws GeneralSecurityException {
     EcdsaParams ecdsaParams = format.getParams();
-    SigUtil.validateEcdsaParams(ecdsaParams);
     KeyPair keyPair = EllipticCurves.generateKeyPair(SigUtil.toCurveType(ecdsaParams.getCurve()));
     ECPublicKey pubKey = (ECPublicKey) keyPair.getPublic();
     ECPrivateKey privKey = (ECPrivateKey) keyPair.getPrivate();
@@ -123,20 +92,6 @@
         .build();
   }
 
-  /**
-   * @param serializedKeyFormat serialized {@code EcdsaKeyFormat} proto
-   * @return {@code KeyData} with a new {@code EcdsaPrivateKey} proto
-   */
-  @Override
-  public KeyData newKeyData(ByteString serializedKeyFormat) throws GeneralSecurityException {
-    EcdsaPrivateKey key = (EcdsaPrivateKey) newKey(serializedKeyFormat);
-    return KeyData.newBuilder()
-        .setTypeUrl(TYPE_URL)
-        .setValue(key.toByteString())
-        .setKeyMaterialType(KeyData.KeyMaterialType.ASYMMETRIC_PRIVATE)
-        .build();
-  }
-
   @Override
   public KeyData getPublicKeyData(ByteString serializedKey) throws GeneralSecurityException {
     try {
@@ -152,13 +107,20 @@
   }
 
   @Override
-  public String getKeyType() {
-    return TYPE_URL;
+  protected KeyMaterialType keyMaterialType() {
+    return KeyMaterialType.ASYMMETRIC_PRIVATE;
   }
 
   @Override
-  public boolean doesSupport(String typeUrl) {
-    return TYPE_URL.equals(typeUrl);
+  protected EcdsaPrivateKey parseKeyProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return EcdsaPrivateKey.parseFrom(byteString);
+  }
+
+  @Override
+  protected EcdsaKeyFormat parseKeyFormatProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return EcdsaKeyFormat.parseFrom(byteString);
   }
 
   @Override
@@ -166,8 +128,14 @@
     return VERSION;
   }
 
-  private void validateKey(EcdsaPrivateKey privKey) throws GeneralSecurityException {
+  @Override
+  protected void validateKey(EcdsaPrivateKey privKey) throws GeneralSecurityException {
     Validators.validateVersion(privKey.getVersion(), VERSION);
     SigUtil.validateEcdsaParams(privKey.getPublicKey().getParams());
   }
+
+  @Override
+  protected void validateKeyFormat(EcdsaKeyFormat format) throws GeneralSecurityException {
+    SigUtil.validateEcdsaParams(format.getParams());
+  }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/signature/EcdsaVerifyKeyManager.java b/java/src/main/java/com/google/crypto/tink/signature/EcdsaVerifyKeyManager.java
index c0a650e..275d01c 100644
--- a/java/src/main/java/com/google/crypto/tink/signature/EcdsaVerifyKeyManager.java
+++ b/java/src/main/java/com/google/crypto/tink/signature/EcdsaVerifyKeyManager.java
@@ -16,16 +16,16 @@
 
 package com.google.crypto.tink.signature;
 
-import com.google.crypto.tink.KeyManager;
+import com.google.crypto.tink.KeyManagerBase;
 import com.google.crypto.tink.PublicKeyVerify;
 import com.google.crypto.tink.proto.EcdsaPublicKey;
-import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.Empty;
+import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
 import com.google.crypto.tink.subtle.EcdsaVerifyJce;
 import com.google.crypto.tink.subtle.EllipticCurves;
 import com.google.crypto.tink.subtle.Validators;
 import com.google.protobuf.ByteString;
 import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
 import java.security.GeneralSecurityException;
 import java.security.interfaces.ECPublicKey;
 
@@ -33,30 +33,18 @@
  * This key manager produces new instances of {@code EcdsaVerifyJce}. It doesn't support key
  * generation.
  */
-class EcdsaVerifyKeyManager implements KeyManager<PublicKeyVerify> {
+class EcdsaVerifyKeyManager extends KeyManagerBase<PublicKeyVerify, EcdsaPublicKey, Empty> {
+  public EcdsaVerifyKeyManager() {
+    super(PublicKeyVerify.class, EcdsaPublicKey.class, Empty.class, TYPE_URL);
+  }
+
   public static final String TYPE_URL = "type.googleapis.com/google.crypto.tink.EcdsaPublicKey";
   /** Current version of this key manager. Keys with greater version are not supported. */
   private static final int VERSION = 0;
 
-  /** @param serializedKey serialized {@code EcdsaPublicKey} proto */
   @Override
-  public PublicKeyVerify getPrimitive(ByteString serializedKey) throws GeneralSecurityException {
-    try {
-      EcdsaPublicKey pubKey = EcdsaPublicKey.parseFrom(serializedKey);
-      return getPrimitive(pubKey);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("expected serialized EcdsaPublicKey proto", e);
-    }
-  }
-
-  /** @param key {@code EcdsaPublicKey} proto */
-  @Override
-  public PublicKeyVerify getPrimitive(MessageLite key) throws GeneralSecurityException {
-    if (!(key instanceof EcdsaPublicKey)) {
-      throw new GeneralSecurityException("expected EcdsaPublicKey proto");
-    }
-    EcdsaPublicKey keyProto = (EcdsaPublicKey) key;
-    validate(keyProto);
+  public PublicKeyVerify getPrimitiveFromKey(EcdsaPublicKey keyProto)
+      throws GeneralSecurityException {
     ECPublicKey publicKey =
         EllipticCurves.getEcPublicKey(
             SigUtil.toCurveType(keyProto.getParams().getCurve()),
@@ -68,50 +56,39 @@
         SigUtil.toEcdsaEncoding(keyProto.getParams().getEncoding()));
   }
 
-  /**
-   * @param serializedKeyFormat serialized {@code EcdsaKeyFormat} proto
-   * @return new {@code EcdsaPublicKey} proto
-   */
   @Override
-  public MessageLite newKey(ByteString serializedKeyFormat) throws GeneralSecurityException {
+  public EcdsaPublicKey newKeyFromFormat(Empty unused) throws GeneralSecurityException {
     throw new GeneralSecurityException("Not implemented");
   }
 
-  /**
-   * @param keyFormat {@code EcdsaKeyFormat} proto
-   * @return new {@code EcdsaPublicKey} proto
-   */
-  @Override
-  public MessageLite newKey(MessageLite keyFormat) throws GeneralSecurityException {
-    throw new GeneralSecurityException("Not implemented");
-  }
-
-  /**
-   * @param serializedKeyFormat serialized {@code EcdsaKeyFormat} proto
-   * @return {@code KeyData} with a new {@code EcdsaPublicKey} proto
-   */
-  @Override
-  public KeyData newKeyData(ByteString serialized) throws GeneralSecurityException {
-    throw new GeneralSecurityException("Not implemented");
-  }
-
-  @Override
-  public boolean doesSupport(String typeUrl) {
-    return TYPE_URL.equals(typeUrl);
-  }
-
-  @Override
-  public String getKeyType() {
-    return TYPE_URL;
-  }
-
   @Override
   public int getVersion() {
     return VERSION;
   }
 
-  private void validate(EcdsaPublicKey pubKey) throws GeneralSecurityException {
+  @Override
+  protected KeyMaterialType keyMaterialType() {
+    return KeyMaterialType.ASYMMETRIC_PUBLIC;
+  }
+
+  @Override
+  protected EcdsaPublicKey parseKeyProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return EcdsaPublicKey.parseFrom(byteString);
+  }
+
+  @Override
+  protected Empty parseKeyFormatProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return Empty.parseFrom(byteString);
+  }
+
+  @Override
+  protected void validateKey(EcdsaPublicKey pubKey) throws GeneralSecurityException {
     Validators.validateVersion(pubKey.getVersion(), VERSION);
     SigUtil.validateEcdsaParams(pubKey.getParams());
   }
+
+  @Override
+  protected void validateKeyFormat(Empty unused) throws GeneralSecurityException {}
 }
diff --git a/java/src/main/java/com/google/crypto/tink/signature/Ed25519PrivateKeyManager.java b/java/src/main/java/com/google/crypto/tink/signature/Ed25519PrivateKeyManager.java
index 2007035..0c565ce 100644
--- a/java/src/main/java/com/google/crypto/tink/signature/Ed25519PrivateKeyManager.java
+++ b/java/src/main/java/com/google/crypto/tink/signature/Ed25519PrivateKeyManager.java
@@ -16,66 +16,53 @@
 
 package com.google.crypto.tink.signature;
 
+import com.google.crypto.tink.KeyManagerBase;
 import com.google.crypto.tink.PrivateKeyManager;
 import com.google.crypto.tink.PublicKeySign;
 import com.google.crypto.tink.proto.Ed25519PrivateKey;
 import com.google.crypto.tink.proto.Ed25519PublicKey;
+import com.google.crypto.tink.proto.Empty;
 import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
 import com.google.crypto.tink.subtle.Ed25519Sign;
 import com.google.crypto.tink.subtle.Validators;
 import com.google.protobuf.ByteString;
 import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
 import java.security.GeneralSecurityException;
 
 /**
  * This instance of {@code KeyManager} generates new {@code Ed25519PrivateKey} keys and produces new
  * instances of {@code Ed25519Sign}.
  */
-class Ed25519PrivateKeyManager implements PrivateKeyManager<PublicKeySign> {
-  /** Type url that this manager supports */
+class Ed25519PrivateKeyManager
+    extends KeyManagerBase<PublicKeySign, Ed25519PrivateKey, Empty>
+    implements PrivateKeyManager<PublicKeySign> {
+  public Ed25519PrivateKeyManager() {
+    super(PublicKeySign.class, Ed25519PrivateKey.class, Empty.class, TYPE_URL);
+  }
+
   public static final String TYPE_URL = "type.googleapis.com/google.crypto.tink.Ed25519PrivateKey";
 
-  /** Current version of this key manager. Keys with greater version are not supported. */
   private static final int VERSION = 0;
 
   @Override
-  public PublicKeySign getPrimitive(ByteString serialized) throws GeneralSecurityException {
-    try {
-      Ed25519PrivateKey keyProto = Ed25519PrivateKey.parseFrom(serialized);
-      return getPrimitive(keyProto);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("invalid Ed25519 private key", e);
-    }
-  }
-
-  @Override
-  public PublicKeySign getPrimitive(MessageLite key) throws GeneralSecurityException {
-    if (!(key instanceof Ed25519PrivateKey)) {
-      throw new GeneralSecurityException("expected Ed25519PrivateKey proto");
-    }
-    Ed25519PrivateKey keyProto = (Ed25519PrivateKey) key;
-    validate(keyProto);
+  public PublicKeySign getPrimitiveFromKey(Ed25519PrivateKey keyProto)
+      throws GeneralSecurityException {
     return new Ed25519Sign(keyProto.getKeyValue().toByteArray());
   }
 
   @Override
-  public MessageLite newKey(ByteString unused) throws GeneralSecurityException {
-    return newKey();
-  }
-
-  @Override
-  public MessageLite newKey(MessageLite unused) throws GeneralSecurityException {
-    return newKey();
-  }
-
-  @Override
-  public KeyData newKeyData(ByteString unused) throws GeneralSecurityException {
-    Ed25519PrivateKey key = newKey();
-    return KeyData.newBuilder()
-        .setTypeUrl(TYPE_URL)
-        .setValue(key.toByteString())
-        .setKeyMaterialType(KeyData.KeyMaterialType.ASYMMETRIC_PRIVATE)
+  public Ed25519PrivateKey newKeyFromFormat(Empty unused) throws GeneralSecurityException {
+    Ed25519Sign.KeyPair keyPair = Ed25519Sign.KeyPair.newKeyPair();
+    Ed25519PublicKey publicKey =
+        Ed25519PublicKey.newBuilder()
+            .setVersion(VERSION)
+            .setKeyValue(ByteString.copyFrom(keyPair.getPublicKey()))
+            .build();
+    return Ed25519PrivateKey.newBuilder()
+        .setVersion(VERSION)
+        .setKeyValue(ByteString.copyFrom(keyPair.getPrivateKey()))
+        .setPublicKey(publicKey)
         .build();
   }
 
@@ -94,13 +81,20 @@
   }
 
   @Override
-  public boolean doesSupport(String typeUrl) {
-    return TYPE_URL.equals(typeUrl);
+  protected KeyMaterialType keyMaterialType() {
+    return KeyMaterialType.ASYMMETRIC_PRIVATE;
   }
 
   @Override
-  public String getKeyType() {
-    return TYPE_URL;
+  protected Ed25519PrivateKey parseKeyProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return Ed25519PrivateKey.parseFrom(byteString);
+  }
+
+  @Override
+  protected Empty parseKeyFormatProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return Empty.parseFrom(byteString);
   }
 
   @Override
@@ -108,24 +102,14 @@
     return VERSION;
   }
 
-  private Ed25519PrivateKey newKey() throws GeneralSecurityException {
-    Ed25519Sign.KeyPair keyPair = Ed25519Sign.KeyPair.newKeyPair();
-    Ed25519PublicKey publicKey =
-        Ed25519PublicKey.newBuilder()
-            .setVersion(VERSION)
-            .setKeyValue(ByteString.copyFrom(keyPair.getPublicKey()))
-            .build();
-    return Ed25519PrivateKey.newBuilder()
-        .setVersion(VERSION)
-        .setKeyValue(ByteString.copyFrom(keyPair.getPrivateKey()))
-        .setPublicKey(publicKey)
-        .build();
-  }
-
-  private void validate(Ed25519PrivateKey keyProto) throws GeneralSecurityException {
+  @Override
+  protected void validateKey(Ed25519PrivateKey keyProto) throws GeneralSecurityException {
     Validators.validateVersion(keyProto.getVersion(), VERSION);
     if (keyProto.getKeyValue().size() != Ed25519Sign.SECRET_KEY_LEN) {
       throw new GeneralSecurityException("invalid Ed25519 private key: incorrect key length");
     }
   }
+
+  @Override
+  protected void validateKeyFormat(Empty unused) {}
 }
diff --git a/java/src/main/java/com/google/crypto/tink/signature/Ed25519PublicKeyManager.java b/java/src/main/java/com/google/crypto/tink/signature/Ed25519PublicKeyManager.java
index 842c406..f6050ad 100644
--- a/java/src/main/java/com/google/crypto/tink/signature/Ed25519PublicKeyManager.java
+++ b/java/src/main/java/com/google/crypto/tink/signature/Ed25519PublicKeyManager.java
@@ -16,82 +16,70 @@
 
 package com.google.crypto.tink.signature;
 
-import com.google.crypto.tink.KeyManager;
+import com.google.crypto.tink.KeyManagerBase;
 import com.google.crypto.tink.PublicKeyVerify;
 import com.google.crypto.tink.proto.Ed25519PublicKey;
-import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.Empty;
+import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
 import com.google.crypto.tink.subtle.Ed25519Verify;
 import com.google.crypto.tink.subtle.Validators;
 import com.google.protobuf.ByteString;
 import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
 import java.security.GeneralSecurityException;
 
 /**
  * This key manager produces new instances of {@code Ed25519Verify}. It doesn't support key
  * generation.
  */
-class Ed25519PublicKeyManager implements KeyManager<PublicKeyVerify> {
-  /** Type url that this manager supports */
+class Ed25519PublicKeyManager extends KeyManagerBase<PublicKeyVerify, Ed25519PublicKey, Empty> {
+  public Ed25519PublicKeyManager() {
+    super(PublicKeyVerify.class, Ed25519PublicKey.class, Empty.class, TYPE_URL);
+  }
   public static final String TYPE_URL = "type.googleapis.com/google.crypto.tink.Ed25519PublicKey";
 
-  /** Current version of this key manager. Keys with greater version are not supported. */
   private static final int VERSION = 0;
 
   @Override
-  public PublicKeyVerify getPrimitive(ByteString serialized) throws GeneralSecurityException {
-    try {
-      Ed25519PublicKey keyProto = Ed25519PublicKey.parseFrom(serialized);
-      return getPrimitive(keyProto);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("invalid Ed25519 public key", e);
-    }
-  }
-
-  @Override
-  public PublicKeyVerify getPrimitive(MessageLite key) throws GeneralSecurityException {
-    if (!(key instanceof Ed25519PublicKey)) {
-      throw new GeneralSecurityException("expected Ed25519PublicKey proto");
-    }
-    Ed25519PublicKey keyProto = (Ed25519PublicKey) key;
-    validate(keyProto);
+  public PublicKeyVerify getPrimitiveFromKey(Ed25519PublicKey keyProto)
+      throws GeneralSecurityException {
     return new Ed25519Verify(keyProto.getKeyValue().toByteArray());
   }
 
   @Override
-  public MessageLite newKey(ByteString unused) throws GeneralSecurityException {
+  protected Ed25519PublicKey newKeyFromFormat(Empty unused) throws GeneralSecurityException {
     throw new GeneralSecurityException("Not implemented");
   }
 
   @Override
-  public MessageLite newKey(MessageLite unused) throws GeneralSecurityException {
-    throw new GeneralSecurityException("Not implemented");
-  }
-
-  @Override
-  public KeyData newKeyData(ByteString unused) throws GeneralSecurityException {
-    throw new GeneralSecurityException("Not implemented");
-  }
-
-  @Override
-  public boolean doesSupport(String typeUrl) {
-    return TYPE_URL.equals(typeUrl);
-  }
-
-  @Override
-  public String getKeyType() {
-    return TYPE_URL;
-  }
-
-  @Override
   public int getVersion() {
     return VERSION;
   }
 
-  private void validate(Ed25519PublicKey keyProto) throws GeneralSecurityException {
+  @Override
+  protected KeyMaterialType keyMaterialType() {
+    return KeyMaterialType.ASYMMETRIC_PUBLIC;
+  }
+
+  @Override
+  protected Ed25519PublicKey parseKeyProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return Ed25519PublicKey.parseFrom(byteString);
+  }
+
+  @Override
+  protected Empty parseKeyFormatProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return Empty.parseFrom(byteString);
+  }
+
+  @Override
+  protected void validateKey(Ed25519PublicKey keyProto) throws GeneralSecurityException {
     Validators.validateVersion(keyProto.getVersion(), VERSION);
     if (keyProto.getKeyValue().size() != Ed25519Verify.PUBLIC_KEY_LEN) {
       throw new GeneralSecurityException("invalid Ed25519 public key: incorrect key length");
     }
   }
+
+  @Override
+  protected void validateKeyFormat(Empty unused) throws GeneralSecurityException {}
 }
diff --git a/java/src/main/java/com/google/crypto/tink/signature/PublicKeySignCatalogue.java b/java/src/main/java/com/google/crypto/tink/signature/PublicKeySignCatalogue.java
index cca6aeb..62d5550 100644
--- a/java/src/main/java/com/google/crypto/tink/signature/PublicKeySignCatalogue.java
+++ b/java/src/main/java/com/google/crypto/tink/signature/PublicKeySignCatalogue.java
@@ -18,6 +18,7 @@
 
 import com.google.crypto.tink.Catalogue;
 import com.google.crypto.tink.KeyManager;
+import com.google.crypto.tink.PrimitiveWrapper;
 import com.google.crypto.tink.PublicKeySign;
 import java.security.GeneralSecurityException;
 
@@ -56,9 +57,18 @@
         return new EcdsaSignKeyManager();
       case Ed25519PrivateKeyManager.TYPE_URL:
         return new Ed25519PrivateKeyManager();
+      case RsaSsaPkcs1SignKeyManager.TYPE_URL:
+        return new RsaSsaPkcs1SignKeyManager();
+      case RsaSsaPssSignKeyManager.TYPE_URL:
+        return new RsaSsaPssSignKeyManager();
       default:
         throw new GeneralSecurityException(
             String.format("No support for primitive 'PublicKeySign' with key type '%s'.", typeUrl));
     }
   }
+
+  @Override
+  public PrimitiveWrapper<PublicKeySign> getPrimitiveWrapper() {
+    return new PublicKeySignWrapper();
+  }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/signature/PublicKeySignFactory.java b/java/src/main/java/com/google/crypto/tink/signature/PublicKeySignFactory.java
index 51f89da..3312fbc 100644
--- a/java/src/main/java/com/google/crypto/tink/signature/PublicKeySignFactory.java
+++ b/java/src/main/java/com/google/crypto/tink/signature/PublicKeySignFactory.java
@@ -16,40 +16,37 @@
 
 package com.google.crypto.tink.signature;
 
-import com.google.crypto.tink.CryptoFormat;
 import com.google.crypto.tink.KeyManager;
 import com.google.crypto.tink.KeysetHandle;
 import com.google.crypto.tink.PrimitiveSet;
 import com.google.crypto.tink.PublicKeySign;
 import com.google.crypto.tink.Registry;
-import com.google.crypto.tink.proto.OutputPrefixType;
-import com.google.crypto.tink.subtle.Bytes;
 import java.security.GeneralSecurityException;
-import java.util.Collection;
 
 /**
- * Static methods for obtaining {@link PublicKeySign} instances.
+ * Deprecated class to create {@code PublicKeySign} primitives. Instead of using this class, make
+ * sure that the {@code PublicKeySignWrapper} is registered in your binary, then call {@code
+ * keysetHandle.GetPrimitive(PublicKeySign.class)} instead. The required registration happens
+ * automatically if you called one of the following in your binary:
  *
- * <h3>Usage</h3>
+ * <ul>
+ *   <li>{@code SignatureConfig.register()}
+ *   <li>{@code TinkConfig.register()}
+ * </ul>
  *
- * <pre>{@code
- * KeysetHandle keysetHandle = ...;
- * PublicKeySign signer = PublicKeySignFactory.getPrimitive(keysetHandle);
- * byte[] data = ...;
- * byte[] signature = signer.sign(data);
- * }</pre>
- *
- * <p>The returned primitive works with a keyset (rather than a single key). To sign a message, it
- * uses the primary key in the keyset, and prepends to the signature a certain prefix associated
- * with the primary key.
- *
+ * @deprecated Use {@code keysetHandle.GetPrimitive(PublicKeySign.class)} after registering the
+ *     {@code PublicKeySignWrapper} instead.
  * @since 1.0.0
  */
+@Deprecated
 public final class PublicKeySignFactory {
   /**
    * @return a PublicKeySign primitive from a {@code keysetHandle}.
    * @throws GeneralSecurityException
+   * @deprecated Use {@code keysetHandle.GetPrimitive(PublicKeySign.class)} after registering the
+   *     {@code PublicKeySignWrapper} instead.
    */
+  @Deprecated
   public static PublicKeySign getPrimitive(KeysetHandle keysetHandle)
       throws GeneralSecurityException {
     return getPrimitive(keysetHandle, /* keyManager= */ null);
@@ -58,37 +55,16 @@
   /**
    * @return a PublicKeySign primitive from a {@code keysetHandle} and a custom {@code keyManager}.
    * @throws GeneralSecurityException
+   * @deprecated Use {@code keysetHandle.GetPrimitive(keyManager, PublicKeySign.class)} after
+   *     registering the {@code PublicKeySignWrapper} instead.
    */
+  @Deprecated
   public static PublicKeySign getPrimitive(
       KeysetHandle keysetHandle, final KeyManager<PublicKeySign> keyManager)
       throws GeneralSecurityException {
-    final PrimitiveSet<PublicKeySign> primitives = Registry.getPrimitives(keysetHandle, keyManager);
-    validate(primitives);
-    return new PublicKeySign() {
-      @Override
-      public byte[] sign(final byte[] data) throws GeneralSecurityException {
-        if (primitives.getPrimary().getOutputPrefixType().equals(OutputPrefixType.LEGACY)) {
-          byte[] formatVersion = new byte[] {CryptoFormat.LEGACY_START_BYTE};
-          return Bytes.concat(
-              primitives.getPrimary().getIdentifier(),
-              primitives.getPrimary().getPrimitive().sign(Bytes.concat(data, formatVersion)));
-        }
-        return Bytes.concat(
-            primitives.getPrimary().getIdentifier(),
-            primitives.getPrimary().getPrimitive().sign(data));
-      }
-    };
-  }
-
-  // Check that all primitives in <code>pset</code> are PublicKeySign instances.
-  private static void validate(final PrimitiveSet<PublicKeySign> pset)
-      throws GeneralSecurityException {
-    for (Collection<PrimitiveSet.Entry<PublicKeySign>> entries : pset.getAll()) {
-      for (PrimitiveSet.Entry<PublicKeySign> entry : entries) {
-        if (!(entry.getPrimitive() instanceof PublicKeySign)) {
-          throw new GeneralSecurityException("invalid PublicKeySign key material");
-        }
-      }
-    }
+    Registry.registerPrimitiveWrapper(new PublicKeySignWrapper());
+    final PrimitiveSet<PublicKeySign> primitives =
+        Registry.getPrimitives(keysetHandle, keyManager, PublicKeySign.class);
+    return Registry.wrap(primitives);
   }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/signature/PublicKeySignWrapper.java b/java/src/main/java/com/google/crypto/tink/signature/PublicKeySignWrapper.java
new file mode 100644
index 0000000..4c509da
--- /dev/null
+++ b/java/src/main/java/com/google/crypto/tink/signature/PublicKeySignWrapper.java
@@ -0,0 +1,65 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.signature;
+
+import com.google.crypto.tink.CryptoFormat;
+import com.google.crypto.tink.PrimitiveSet;
+import com.google.crypto.tink.PrimitiveWrapper;
+import com.google.crypto.tink.PublicKeySign;
+import com.google.crypto.tink.proto.OutputPrefixType;
+import com.google.crypto.tink.subtle.Bytes;
+import java.security.GeneralSecurityException;
+
+/**
+ * The implementation of {@code PrimitiveWrapper<PublicKeySign>}.
+ *
+ * <p>The returned primitive works with a keyset (rather than a single key). To sign a message, it
+ * uses the primary key in the keyset, and prepends to the signature a certain prefix associated
+ * with the primary key.
+ */
+public final class PublicKeySignWrapper implements PrimitiveWrapper<PublicKeySign> {
+  private static class WrappedPublicKeySign implements PublicKeySign {
+    private final PrimitiveSet<PublicKeySign> primitives;
+
+    public WrappedPublicKeySign(final PrimitiveSet<PublicKeySign> primitives) {
+      this.primitives = primitives;
+    }
+
+    @Override
+    public byte[] sign(final byte[] data) throws GeneralSecurityException {
+      if (primitives.getPrimary().getOutputPrefixType().equals(OutputPrefixType.LEGACY)) {
+        byte[] formatVersion = new byte[] {CryptoFormat.LEGACY_START_BYTE};
+        return Bytes.concat(
+            primitives.getPrimary().getIdentifier(),
+            primitives.getPrimary().getPrimitive().sign(Bytes.concat(data, formatVersion)));
+      }
+      return Bytes.concat(
+          primitives.getPrimary().getIdentifier(),
+          primitives.getPrimary().getPrimitive().sign(data));
+    }
+  }
+
+  @Override
+  public PublicKeySign wrap(final PrimitiveSet<PublicKeySign> primitives) {
+    return new WrappedPublicKeySign(primitives);
+  }
+
+  @Override
+  public Class<PublicKeySign> getPrimitiveClass() {
+    return PublicKeySign.class;
+  }
+}
diff --git a/java/src/main/java/com/google/crypto/tink/signature/PublicKeyVerifyCatalogue.java b/java/src/main/java/com/google/crypto/tink/signature/PublicKeyVerifyCatalogue.java
index f343cec..a384657 100644
--- a/java/src/main/java/com/google/crypto/tink/signature/PublicKeyVerifyCatalogue.java
+++ b/java/src/main/java/com/google/crypto/tink/signature/PublicKeyVerifyCatalogue.java
@@ -18,6 +18,7 @@
 
 import com.google.crypto.tink.Catalogue;
 import com.google.crypto.tink.KeyManager;
+import com.google.crypto.tink.PrimitiveWrapper;
 import com.google.crypto.tink.PublicKeyVerify;
 import java.security.GeneralSecurityException;
 
@@ -56,10 +57,19 @@
         return new EcdsaVerifyKeyManager();
       case Ed25519PublicKeyManager.TYPE_URL:
         return new Ed25519PublicKeyManager();
+      case RsaSsaPkcs1VerifyKeyManager.TYPE_URL:
+        return new RsaSsaPkcs1VerifyKeyManager();
+      case RsaSsaPssVerifyKeyManager.TYPE_URL:
+        return new RsaSsaPssVerifyKeyManager();
       default:
         throw new GeneralSecurityException(
             String.format(
                 "No support for primitive 'PublicKeyVerify' with key type '%s'.", typeUrl));
     }
   }
+
+  @Override
+  public PrimitiveWrapper<PublicKeyVerify> getPrimitiveWrapper() {
+    return new PublicKeyVerifyWrapper();
+  }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/signature/PublicKeyVerifyFactory.java b/java/src/main/java/com/google/crypto/tink/signature/PublicKeyVerifyFactory.java
index f12dc13..eceac6c 100644
--- a/java/src/main/java/com/google/crypto/tink/signature/PublicKeyVerifyFactory.java
+++ b/java/src/main/java/com/google/crypto/tink/signature/PublicKeyVerifyFactory.java
@@ -16,45 +16,37 @@
 
 package com.google.crypto.tink.signature;
 
-import com.google.crypto.tink.CryptoFormat;
 import com.google.crypto.tink.KeyManager;
 import com.google.crypto.tink.KeysetHandle;
 import com.google.crypto.tink.PrimitiveSet;
 import com.google.crypto.tink.PublicKeyVerify;
 import com.google.crypto.tink.Registry;
-import com.google.crypto.tink.proto.OutputPrefixType;
-import com.google.crypto.tink.subtle.Bytes;
 import java.security.GeneralSecurityException;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.logging.Logger;
 
 /**
- * Static methods for obtaining {@link PublicKeyVerify} instances.
+ * Deprecated class to create {@code PublicKeyVerify} primitives. Instead of using this class, make
+ * sure that the {@code PublicKeyVerifyWrapper} is registered in your binary, then call {@code
+ * keysetHandle.GetPrimitive(PublicKeyVerify.class)} instead. The required registration happens
+ * automatically if you called one of the following in your binary:
  *
- * <h3>Usage</h3>
+ * <ul>
+ *   <li>{@code SignatureConfig.register()}
+ *   <li>{@code TinkConfig.register()}
+ * </ul>
  *
- * <pre>{@code
- * KeysetHandle keysetHandle = ...;
- * PublicKeyVerify verifier = PublicKeyVerifyFactory.getPrimitive(keysetHandle);
- * verifier.verify(signature, data);
- * }</pre>
- *
- * <p>The returned primitive works with a keyset (rather than a single key). To verify a signature,
- * the primitive uses the prefix of the signature to efficiently select the right key in the set. If
- * there is no key associated with the prefix or if the keys associated with the prefix do not work,
- * the primitive tries all keys with {@link com.google.crypto.tink.proto.OutputPrefixType#RAW}.
- *
+ * @deprecated Use {@code keysetHandle.GetPrimitive(PublicKeyVerify.class)} after registering the
+ *     {@code PublicKeyVerifyWrapper} instead.
  * @since 1.0.0
  */
+@Deprecated
 public final class PublicKeyVerifyFactory {
-  private static final Logger logger = Logger.getLogger(PublicKeyVerifyFactory.class.getName());
-
   /**
    * @return a PublicKeyVerify primitive from a {@code keysetHandle}.
    * @throws GeneralSecurityException
+   * @deprecated Use {@code keysetHandle.GetPrimitive(PublicKeyVerify.class)} after registering the
+   *     {@code PublicKeyVerifyWrapper} instead.
    */
+  @Deprecated
   public static PublicKeyVerify getPrimitive(KeysetHandle keysetHandle)
       throws GeneralSecurityException {
     return getPrimitive(keysetHandle, /* keyManager= */ null);
@@ -64,69 +56,16 @@
    * @return a PublicKeyVerify primitive from a {@code keysetHandle} and a custom {@code
    *     keyManager}.
    * @throws GeneralSecurityException
+   * @deprecated Use {@code keysetHandle.GetPrimitive(keyManager, PublicKeyVerify.class)} after
+   *     registering the {@code PublicKeyVerifyWrapper} instead.
    */
+  @Deprecated
   public static PublicKeyVerify getPrimitive(
       KeysetHandle keysetHandle, final KeyManager<PublicKeyVerify> keyManager)
       throws GeneralSecurityException {
+    Registry.registerPrimitiveWrapper(new PublicKeyVerifyWrapper());
     final PrimitiveSet<PublicKeyVerify> primitives =
-        Registry.getPrimitives(keysetHandle, keyManager);
-    validate(primitives);
-    return new PublicKeyVerify() {
-      @Override
-      public void verify(final byte[] signature, final byte[] data)
-          throws GeneralSecurityException {
-        if (signature.length <= CryptoFormat.NON_RAW_PREFIX_SIZE) {
-          // This also rejects raw signatures with size of 4 bytes or fewer. We're not aware of any
-          // schemes that output signatures that small.
-          throw new GeneralSecurityException("signature too short");
-        }
-        byte[] prefix = Arrays.copyOfRange(signature, 0, CryptoFormat.NON_RAW_PREFIX_SIZE);
-        byte[] sigNoPrefix =
-            Arrays.copyOfRange(signature, CryptoFormat.NON_RAW_PREFIX_SIZE, signature.length);
-        List<PrimitiveSet.Entry<PublicKeyVerify>> entries = primitives.getPrimitive(prefix);
-        for (PrimitiveSet.Entry<PublicKeyVerify> entry : entries) {
-          try {
-            if (entry.getOutputPrefixType().equals(OutputPrefixType.LEGACY)) {
-              final byte[] formatVersion = new byte[] {CryptoFormat.LEGACY_START_BYTE};
-              final byte[] dataWithFormatVersion = Bytes.concat(data, formatVersion);
-              entry.getPrimitive().verify(sigNoPrefix, dataWithFormatVersion);
-            } else {
-              entry.getPrimitive().verify(sigNoPrefix, data);
-            }
-            // If there is no exception, the signature is valid and we can return.
-            return;
-          } catch (GeneralSecurityException e) {
-            logger.info("signature prefix matches a key, but cannot verify: " + e.toString());
-            // Ignored as we want to continue verification with the remaining keys.
-          }
-        }
-
-        // None "non-raw" key matched, so let's try the raw keys (if any exist).
-        entries = primitives.getRawPrimitives();
-        for (PrimitiveSet.Entry<PublicKeyVerify> entry : entries) {
-          try {
-            entry.getPrimitive().verify(signature, data);
-            // If there is no exception, the signature is valid and we can return.
-            return;
-          } catch (GeneralSecurityException e) {
-            // Ignored as we want to continue verification with raw keys.
-          }
-        }
-        // nothing works.
-        throw new GeneralSecurityException("invalid signature");
-      }
-    };
-  }
-
-  // Check that all primitives in <code>pset</code> are PublicKeyVerify instances.
-  private static void validate(final PrimitiveSet<PublicKeyVerify> pset)
-      throws GeneralSecurityException {
-    for (Collection<PrimitiveSet.Entry<PublicKeyVerify>> entries : pset.getAll()) {
-      for (PrimitiveSet.Entry<PublicKeyVerify> entry : entries) {
-        if (!(entry.getPrimitive() instanceof PublicKeyVerify)) {
-          throw new GeneralSecurityException("invalid PublicKeyVerify key material");
-        }
-      }
-    }
+        Registry.getPrimitives(keysetHandle, keyManager, PublicKeyVerify.class);
+    return Registry.wrap(primitives);
   }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/signature/PublicKeyVerifyWrapper.java b/java/src/main/java/com/google/crypto/tink/signature/PublicKeyVerifyWrapper.java
new file mode 100644
index 0000000..996ae03
--- /dev/null
+++ b/java/src/main/java/com/google/crypto/tink/signature/PublicKeyVerifyWrapper.java
@@ -0,0 +1,103 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.signature;
+
+import com.google.crypto.tink.CryptoFormat;
+import com.google.crypto.tink.PrimitiveSet;
+import com.google.crypto.tink.PrimitiveWrapper;
+import com.google.crypto.tink.PublicKeyVerify;
+import com.google.crypto.tink.proto.OutputPrefixType;
+import com.google.crypto.tink.subtle.Bytes;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Logger;
+
+/**
+ * The implementation of {@code PrimitiveWrapper<DeterministicAead>}.
+ *
+ * <p>The returned primitive works with a keyset (rather than a single key). To verify a signature,
+ * the primitive uses the prefix of the signature to efficiently select the right key in the set. If
+ * there is no key associated with the prefix or if the keys associated with the prefix do not work,
+ * the primitive tries all keys with {@link com.google.crypto.tink.proto.OutputPrefixType#RAW}.
+ *
+ * @since 1.0.0
+ */
+public final class PublicKeyVerifyWrapper implements PrimitiveWrapper<PublicKeyVerify> {
+  private static final Logger logger = Logger.getLogger(PublicKeyVerifyWrapper.class.getName());
+
+  private static class WrappedPublicKeyVerify implements PublicKeyVerify {
+    private final PrimitiveSet<PublicKeyVerify> primitives;
+
+    public WrappedPublicKeyVerify(PrimitiveSet<PublicKeyVerify> primitives) {
+      this.primitives = primitives;
+    }
+
+    @Override
+    public void verify(final byte[] signature, final byte[] data) throws GeneralSecurityException {
+      if (signature.length <= CryptoFormat.NON_RAW_PREFIX_SIZE) {
+        // This also rejects raw signatures with size of 4 bytes or fewer. We're not aware of any
+        // schemes that output signatures that small.
+        throw new GeneralSecurityException("signature too short");
+      }
+      byte[] prefix = Arrays.copyOfRange(signature, 0, CryptoFormat.NON_RAW_PREFIX_SIZE);
+      byte[] sigNoPrefix =
+          Arrays.copyOfRange(signature, CryptoFormat.NON_RAW_PREFIX_SIZE, signature.length);
+      List<PrimitiveSet.Entry<PublicKeyVerify>> entries = primitives.getPrimitive(prefix);
+      for (PrimitiveSet.Entry<PublicKeyVerify> entry : entries) {
+        try {
+          if (entry.getOutputPrefixType().equals(OutputPrefixType.LEGACY)) {
+            final byte[] formatVersion = new byte[] {CryptoFormat.LEGACY_START_BYTE};
+            final byte[] dataWithFormatVersion = Bytes.concat(data, formatVersion);
+            entry.getPrimitive().verify(sigNoPrefix, dataWithFormatVersion);
+          } else {
+            entry.getPrimitive().verify(sigNoPrefix, data);
+          }
+          // If there is no exception, the signature is valid and we can return.
+          return;
+        } catch (GeneralSecurityException e) {
+          logger.info("signature prefix matches a key, but cannot verify: " + e.toString());
+          // Ignored as we want to continue verification with the remaining keys.
+        }
+      }
+
+      // None "non-raw" key matched, so let's try the raw keys (if any exist).
+      entries = primitives.getRawPrimitives();
+      for (PrimitiveSet.Entry<PublicKeyVerify> entry : entries) {
+        try {
+          entry.getPrimitive().verify(signature, data);
+          // If there is no exception, the signature is valid and we can return.
+          return;
+        } catch (GeneralSecurityException e) {
+          // Ignored as we want to continue verification with raw keys.
+        }
+      }
+      // nothing works.
+      throw new GeneralSecurityException("invalid signature");
+    }
+  }
+
+  @Override
+  public PublicKeyVerify wrap(final PrimitiveSet<PublicKeyVerify> primitives) {
+    return new WrappedPublicKeyVerify(primitives);
+  }
+
+  @Override
+  public Class<PublicKeyVerify> getPrimitiveClass() {
+    return PublicKeyVerify.class;
+  }
+}
diff --git a/java/src/main/java/com/google/crypto/tink/signature/RsaSsaPkcs1SignKeyManager.java b/java/src/main/java/com/google/crypto/tink/signature/RsaSsaPkcs1SignKeyManager.java
new file mode 100644
index 0000000..ef5cfe6
--- /dev/null
+++ b/java/src/main/java/com/google/crypto/tink/signature/RsaSsaPkcs1SignKeyManager.java
@@ -0,0 +1,197 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.signature;
+
+import com.google.crypto.tink.KeyManagerBase;
+import com.google.crypto.tink.PrivateKeyManager;
+import com.google.crypto.tink.PublicKeySign;
+import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
+import com.google.crypto.tink.proto.RsaSsaPkcs1KeyFormat;
+import com.google.crypto.tink.proto.RsaSsaPkcs1Params;
+import com.google.crypto.tink.proto.RsaSsaPkcs1PrivateKey;
+import com.google.crypto.tink.proto.RsaSsaPkcs1PublicKey;
+import com.google.crypto.tink.subtle.EngineFactory;
+import com.google.crypto.tink.subtle.RsaSsaPkcs1SignJce;
+import com.google.crypto.tink.subtle.RsaSsaPkcs1VerifyJce;
+import com.google.crypto.tink.subtle.Validators;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.math.BigInteger;
+import java.nio.charset.Charset;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.RSAKeyGenParameterSpec;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+
+/**
+ * This key manager generates new {@code RsaSsaPkcs1PrivateKey} keys and produces new instances of
+ * {@code RsaSsaPkcs1SignJce}.
+ */
+class RsaSsaPkcs1SignKeyManager
+    extends KeyManagerBase<PublicKeySign, RsaSsaPkcs1PrivateKey, RsaSsaPkcs1KeyFormat>
+    implements PrivateKeyManager<PublicKeySign> {
+  public RsaSsaPkcs1SignKeyManager() {
+    super(PublicKeySign.class, RsaSsaPkcs1PrivateKey.class, RsaSsaPkcs1KeyFormat.class, TYPE_URL);
+  }
+
+  public static final String TYPE_URL =
+      "type.googleapis.com/google.crypto.tink.RsaSsaPkcs1PrivateKey";
+
+  private static final int VERSION = 0;
+
+  private static final Charset UTF_8 = Charset.forName("UTF-8");
+
+  /** Test message. */
+  private static final byte[] TEST_MESSAGE = "Tink and Wycheproof.".getBytes(UTF_8);
+
+  @Override
+  public PublicKeySign getPrimitiveFromKey(RsaSsaPkcs1PrivateKey keyProto)
+      throws GeneralSecurityException {
+    validateKey(keyProto);
+    KeyFactory kf = EngineFactory.KEY_FACTORY.getInstance("RSA");
+    RSAPrivateCrtKey privateKey =
+        (RSAPrivateCrtKey)
+            kf.generatePrivate(
+                new RSAPrivateCrtKeySpec(
+                    new BigInteger(1, keyProto.getPublicKey().getN().toByteArray()),
+                    new BigInteger(1, keyProto.getPublicKey().getE().toByteArray()),
+                    new BigInteger(1, keyProto.getD().toByteArray()),
+                    new BigInteger(1, keyProto.getP().toByteArray()),
+                    new BigInteger(1, keyProto.getQ().toByteArray()),
+                    new BigInteger(1, keyProto.getDp().toByteArray()),
+                    new BigInteger(1, keyProto.getDq().toByteArray()),
+                    new BigInteger(1, keyProto.getCrt().toByteArray())));
+    // Sign and verify a test message to make sure that the key is correct.
+    RsaSsaPkcs1SignJce signer =
+        new RsaSsaPkcs1SignJce(
+            privateKey, SigUtil.toHashType(keyProto.getPublicKey().getParams().getHashType()));
+    RSAPublicKey publicKey =
+        (RSAPublicKey)
+            kf.generatePublic(
+                new RSAPublicKeySpec(
+                    new BigInteger(1, keyProto.getPublicKey().getN().toByteArray()),
+                    new BigInteger(1, keyProto.getPublicKey().getE().toByteArray())));
+    RsaSsaPkcs1VerifyJce verifier =
+        new RsaSsaPkcs1VerifyJce(
+            publicKey, SigUtil.toHashType(keyProto.getPublicKey().getParams().getHashType()));
+    try {
+      verifier.verify(signer.sign(TEST_MESSAGE), TEST_MESSAGE);
+    } catch (GeneralSecurityException e) {
+      throw new RuntimeException(
+          "Security bug: signing with private key followed by verifying with public key failed"
+              + e);
+    }
+    return signer;
+  }
+
+  /**
+   * @param serializedKeyFormat serialized {@code RsaSsaPkcs1KeyFormat} proto
+   * @return new {@code RsaSsaPkcs1PrivateKey} proto
+   */
+  @Override
+  public RsaSsaPkcs1PrivateKey newKeyFromFormat(RsaSsaPkcs1KeyFormat format)
+      throws GeneralSecurityException {
+    validateKeyFormat(format);
+    RsaSsaPkcs1Params params = format.getParams();
+    KeyPairGenerator keyGen = EngineFactory.KEY_PAIR_GENERATOR.getInstance("RSA");
+    RSAKeyGenParameterSpec spec =
+        new RSAKeyGenParameterSpec(
+            format.getModulusSizeInBits(),
+            new BigInteger(1, format.getPublicExponent().toByteArray()));
+    keyGen.initialize(spec);
+    KeyPair keyPair = keyGen.generateKeyPair();
+    RSAPublicKey pubKey = (RSAPublicKey) keyPair.getPublic();
+    RSAPrivateCrtKey privKey = (RSAPrivateCrtKey) keyPair.getPrivate();
+
+    // Creates RsaSsaPkcs1PublicKey.
+    RsaSsaPkcs1PublicKey pkcs1PubKey =
+        RsaSsaPkcs1PublicKey.newBuilder()
+            .setVersion(VERSION)
+            .setParams(params)
+            .setE(ByteString.copyFrom(pubKey.getPublicExponent().toByteArray()))
+            .setN(ByteString.copyFrom(pubKey.getModulus().toByteArray()))
+            .build();
+
+    // Creates RsaSsaPkcs1PrivateKey.
+    return RsaSsaPkcs1PrivateKey.newBuilder()
+        .setVersion(VERSION)
+        .setPublicKey(pkcs1PubKey)
+        .setD(ByteString.copyFrom(privKey.getPrivateExponent().toByteArray()))
+        .setP(ByteString.copyFrom(privKey.getPrimeP().toByteArray()))
+        .setQ(ByteString.copyFrom(privKey.getPrimeQ().toByteArray()))
+        .setDp(ByteString.copyFrom(privKey.getPrimeExponentP().toByteArray()))
+        .setDq(ByteString.copyFrom(privKey.getPrimeExponentQ().toByteArray()))
+        .setCrt(ByteString.copyFrom(privKey.getCrtCoefficient().toByteArray()))
+        .build();
+  }
+
+  @Override
+  protected KeyMaterialType keyMaterialType() {
+    return KeyMaterialType.ASYMMETRIC_PRIVATE;
+  }
+
+  @Override
+  protected RsaSsaPkcs1PrivateKey parseKeyProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return RsaSsaPkcs1PrivateKey.parseFrom(byteString);
+  }
+
+  @Override
+  protected RsaSsaPkcs1KeyFormat parseKeyFormatProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return RsaSsaPkcs1KeyFormat.parseFrom(byteString);
+  }
+
+  @Override
+  public KeyData getPublicKeyData(ByteString serializedKey) throws GeneralSecurityException {
+    try {
+      RsaSsaPkcs1PrivateKey privKeyProto = RsaSsaPkcs1PrivateKey.parseFrom(serializedKey);
+      return KeyData.newBuilder()
+          .setTypeUrl(RsaSsaPkcs1VerifyKeyManager.TYPE_URL)
+          .setValue(privKeyProto.getPublicKey().toByteString())
+          .setKeyMaterialType(KeyData.KeyMaterialType.ASYMMETRIC_PUBLIC)
+          .build();
+    } catch (InvalidProtocolBufferException e) {
+      throw new GeneralSecurityException("expected serialized RsaSsaPkcs1PrivateKey proto", e);
+    }
+  }
+
+  @Override
+  public int getVersion() {
+    return VERSION;
+  }
+
+  @Override
+  protected void validateKeyFormat(RsaSsaPkcs1KeyFormat keyFormat) throws GeneralSecurityException {
+    SigUtil.validateRsaSsaPkcs1Params(keyFormat.getParams());
+    Validators.validateRsaModulusSize(keyFormat.getModulusSizeInBits());
+  }
+
+  @Override
+  protected void validateKey(RsaSsaPkcs1PrivateKey privKey) throws GeneralSecurityException {
+    Validators.validateVersion(privKey.getVersion(), VERSION);
+    Validators.validateRsaModulusSize(
+        (new BigInteger(1, privKey.getPublicKey().getN().toByteArray())).bitLength());
+    SigUtil.validateRsaSsaPkcs1Params(privKey.getPublicKey().getParams());
+  }
+}
diff --git a/java/src/main/java/com/google/crypto/tink/signature/RsaSsaPkcs1VerifyKeyManager.java b/java/src/main/java/com/google/crypto/tink/signature/RsaSsaPkcs1VerifyKeyManager.java
new file mode 100644
index 0000000..430bfe7
--- /dev/null
+++ b/java/src/main/java/com/google/crypto/tink/signature/RsaSsaPkcs1VerifyKeyManager.java
@@ -0,0 +1,99 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.signature;
+
+import com.google.crypto.tink.KeyManagerBase;
+import com.google.crypto.tink.PublicKeyVerify;
+import com.google.crypto.tink.proto.Empty;
+import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
+import com.google.crypto.tink.proto.RsaSsaPkcs1PublicKey;
+import com.google.crypto.tink.subtle.EngineFactory;
+import com.google.crypto.tink.subtle.RsaSsaPkcs1VerifyJce;
+import com.google.crypto.tink.subtle.Validators;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.RSAPublicKeySpec;
+
+/**
+ * This key manager produces new instances of {@code RsaSsaPkcs1VerifyJce}. It doesn't support key
+ * generation.
+ */
+class RsaSsaPkcs1VerifyKeyManager
+    extends KeyManagerBase<PublicKeyVerify, RsaSsaPkcs1PublicKey, Empty> {
+  public RsaSsaPkcs1VerifyKeyManager() {
+    super(PublicKeyVerify.class, RsaSsaPkcs1PublicKey.class, Empty.class, TYPE_URL);
+  }
+
+  private static final int VERSION = 0;
+  public static final String TYPE_URL =
+      "type.googleapis.com/google.crypto.tink.RsaSsaPkcs1PublicKey";
+
+  @Override
+  public PublicKeyVerify getPrimitiveFromKey(RsaSsaPkcs1PublicKey keyProto)
+      throws GeneralSecurityException {
+    validateKey(keyProto);
+    KeyFactory kf = EngineFactory.KEY_FACTORY.getInstance("RSA");
+    BigInteger modulus = new BigInteger(1, keyProto.getN().toByteArray());
+    BigInteger exponent = new BigInteger(1, keyProto.getE().toByteArray());
+    RSAPublicKey publicKey =
+        (RSAPublicKey) kf.generatePublic(new RSAPublicKeySpec(modulus, exponent));
+    return new RsaSsaPkcs1VerifyJce(
+        publicKey, SigUtil.toHashType(keyProto.getParams().getHashType()));
+  }
+
+  @Override
+  public RsaSsaPkcs1PublicKey newKeyFromFormat(Empty serializedKeyFormat)
+      throws GeneralSecurityException {
+    throw new GeneralSecurityException("Not implemented");
+  }
+
+  @Override
+  public int getVersion() {
+    return VERSION;
+  }
+
+  @Override
+  protected KeyMaterialType keyMaterialType() {
+    return KeyMaterialType.ASYMMETRIC_PUBLIC;
+  }
+
+  @Override
+  protected RsaSsaPkcs1PublicKey parseKeyProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return RsaSsaPkcs1PublicKey.parseFrom(byteString);
+  }
+
+  @Override
+  protected Empty parseKeyFormatProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return Empty.parseFrom(byteString);
+  }
+
+  @Override
+  protected void validateKey(RsaSsaPkcs1PublicKey pubKey) throws GeneralSecurityException {
+    Validators.validateVersion(pubKey.getVersion(), VERSION);
+    Validators.validateRsaModulusSize((new BigInteger(1, pubKey.getN().toByteArray())).bitLength());
+    SigUtil.validateRsaSsaPkcs1Params(pubKey.getParams());
+  }
+
+  @Override
+  protected void validateKeyFormat(Empty unused) {}
+}
diff --git a/java/src/main/java/com/google/crypto/tink/signature/RsaSsaPssSignKeyManager.java b/java/src/main/java/com/google/crypto/tink/signature/RsaSsaPssSignKeyManager.java
new file mode 100644
index 0000000..8d85cdd
--- /dev/null
+++ b/java/src/main/java/com/google/crypto/tink/signature/RsaSsaPssSignKeyManager.java
@@ -0,0 +1,204 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.signature;
+
+import com.google.crypto.tink.KeyManagerBase;
+import com.google.crypto.tink.PrivateKeyManager;
+import com.google.crypto.tink.PublicKeySign;
+import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
+import com.google.crypto.tink.proto.RsaSsaPssKeyFormat;
+import com.google.crypto.tink.proto.RsaSsaPssParams;
+import com.google.crypto.tink.proto.RsaSsaPssPrivateKey;
+import com.google.crypto.tink.proto.RsaSsaPssPublicKey;
+import com.google.crypto.tink.subtle.EngineFactory;
+import com.google.crypto.tink.subtle.RsaSsaPssSignJce;
+import com.google.crypto.tink.subtle.RsaSsaPssVerifyJce;
+import com.google.crypto.tink.subtle.Validators;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.math.BigInteger;
+import java.nio.charset.Charset;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.RSAKeyGenParameterSpec;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+
+/**
+ * This key manager generates new {@code RsaSsaPssPrivateKey} keys and produces new instances of
+ * {@code RsaSsaPssSignJce}.
+ */
+class RsaSsaPssSignKeyManager
+    extends KeyManagerBase<PublicKeySign, RsaSsaPssPrivateKey, RsaSsaPssKeyFormat>
+    implements PrivateKeyManager<PublicKeySign> {
+  public RsaSsaPssSignKeyManager() {
+    super(PublicKeySign.class, RsaSsaPssPrivateKey.class, RsaSsaPssKeyFormat.class, TYPE_URL);
+  }
+
+  public static final String TYPE_URL =
+      "type.googleapis.com/google.crypto.tink.RsaSsaPssPrivateKey";
+
+  private static final int VERSION = 0;
+
+  private static final Charset UTF_8 = Charset.forName("UTF-8");
+
+  /** Test message. */
+  private static final byte[] TEST_MESSAGE = "Tink and Wycheproof.".getBytes(UTF_8);
+
+  @Override
+  public PublicKeySign getPrimitiveFromKey(RsaSsaPssPrivateKey keyProto)
+      throws GeneralSecurityException {
+    KeyFactory kf = EngineFactory.KEY_FACTORY.getInstance("RSA");
+    RSAPrivateCrtKey privateKey =
+        (RSAPrivateCrtKey)
+            kf.generatePrivate(
+                new RSAPrivateCrtKeySpec(
+                    new BigInteger(1, keyProto.getPublicKey().getN().toByteArray()),
+                    new BigInteger(1, keyProto.getPublicKey().getE().toByteArray()),
+                    new BigInteger(1, keyProto.getD().toByteArray()),
+                    new BigInteger(1, keyProto.getP().toByteArray()),
+                    new BigInteger(1, keyProto.getQ().toByteArray()),
+                    new BigInteger(1, keyProto.getDp().toByteArray()),
+                    new BigInteger(1, keyProto.getDq().toByteArray()),
+                    new BigInteger(1, keyProto.getCrt().toByteArray())));
+    RsaSsaPssParams params = keyProto.getPublicKey().getParams();
+    // Sign and verify a test message to make sure that the key is correct.
+    RsaSsaPssSignJce signer =
+        new RsaSsaPssSignJce(
+            privateKey,
+            SigUtil.toHashType(params.getSigHash()),
+            SigUtil.toHashType(params.getMgf1Hash()),
+            params.getSaltLength());
+    RSAPublicKey publicKey =
+        (RSAPublicKey)
+            kf.generatePublic(
+                new RSAPublicKeySpec(
+                    new BigInteger(1, keyProto.getPublicKey().getN().toByteArray()),
+                    new BigInteger(1, keyProto.getPublicKey().getE().toByteArray())));
+    RsaSsaPssVerifyJce verifier =
+        new RsaSsaPssVerifyJce(
+            publicKey,
+            SigUtil.toHashType(params.getSigHash()),
+            SigUtil.toHashType(params.getMgf1Hash()),
+            params.getSaltLength());
+    try {
+      verifier.verify(signer.sign(TEST_MESSAGE), TEST_MESSAGE);
+    } catch (GeneralSecurityException e) {
+      throw new RuntimeException(
+          "Security bug: signing with private key followed by verifying with public key failed"
+              + e);
+    }
+    return signer;
+  }
+
+  /**
+   * @param serializedKeyFormat serialized {@code RsaSsaPssKeyFormat} proto
+   * @return new {@code RsaSsaPssPrivateKey} proto
+   */
+  @Override
+  protected RsaSsaPssPrivateKey newKeyFromFormat(RsaSsaPssKeyFormat format)
+      throws GeneralSecurityException {
+    RsaSsaPssParams params = format.getParams();
+    Validators.validateRsaModulusSize(format.getModulusSizeInBits());
+    Validators.validateSignatureHash(SigUtil.toHashType(params.getSigHash()));
+    KeyPairGenerator keyGen = EngineFactory.KEY_PAIR_GENERATOR.getInstance("RSA");
+    RSAKeyGenParameterSpec spec =
+        new RSAKeyGenParameterSpec(
+            format.getModulusSizeInBits(),
+            new BigInteger(1, format.getPublicExponent().toByteArray()));
+    keyGen.initialize(spec);
+    KeyPair keyPair = keyGen.generateKeyPair();
+    RSAPublicKey pubKey = (RSAPublicKey) keyPair.getPublic();
+    RSAPrivateCrtKey privKey = (RSAPrivateCrtKey) keyPair.getPrivate();
+
+    // Creates RsaSsaPssPublicKey.
+    RsaSsaPssPublicKey pssPubKey =
+        RsaSsaPssPublicKey.newBuilder()
+            .setVersion(VERSION)
+            .setParams(params)
+            .setE(ByteString.copyFrom(pubKey.getPublicExponent().toByteArray()))
+            .setN(ByteString.copyFrom(pubKey.getModulus().toByteArray()))
+            .build();
+
+    // Creates RsaSsaPssPrivateKey.
+    return RsaSsaPssPrivateKey.newBuilder()
+        .setVersion(VERSION)
+        .setPublicKey(pssPubKey)
+        .setD(ByteString.copyFrom(privKey.getPrivateExponent().toByteArray()))
+        .setP(ByteString.copyFrom(privKey.getPrimeP().toByteArray()))
+        .setQ(ByteString.copyFrom(privKey.getPrimeQ().toByteArray()))
+        .setDp(ByteString.copyFrom(privKey.getPrimeExponentP().toByteArray()))
+        .setDq(ByteString.copyFrom(privKey.getPrimeExponentQ().toByteArray()))
+        .setCrt(ByteString.copyFrom(privKey.getCrtCoefficient().toByteArray()))
+        .build();
+  }
+
+  @Override
+  public KeyData getPublicKeyData(ByteString serializedKey) throws GeneralSecurityException {
+    try {
+      RsaSsaPssPrivateKey privKeyProto = RsaSsaPssPrivateKey.parseFrom(serializedKey);
+      return KeyData.newBuilder()
+          .setTypeUrl(RsaSsaPssVerifyKeyManager.TYPE_URL)
+          .setValue(privKeyProto.getPublicKey().toByteString())
+          .setKeyMaterialType(KeyData.KeyMaterialType.ASYMMETRIC_PUBLIC)
+          .build();
+    } catch (InvalidProtocolBufferException e) {
+      throw new GeneralSecurityException("expected serialized RsaSsaPssPrivateKey proto", e);
+    }
+  }
+
+  @Override
+  protected KeyMaterialType keyMaterialType() {
+    return KeyMaterialType.ASYMMETRIC_PRIVATE;
+  }
+
+  @Override
+  protected RsaSsaPssPrivateKey parseKeyProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return RsaSsaPssPrivateKey.parseFrom(byteString);
+  }
+
+  @Override
+  protected RsaSsaPssKeyFormat parseKeyFormatProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return RsaSsaPssKeyFormat.parseFrom(byteString);
+  }
+
+  @Override
+  public int getVersion() {
+    return VERSION;
+  }
+
+  @Override
+  protected void validateKeyFormat(RsaSsaPssKeyFormat format) throws GeneralSecurityException {
+    SigUtil.validateRsaSsaPssParams(format.getParams());
+    Validators.validateRsaModulusSize(format.getModulusSizeInBits());
+  }
+
+  @Override
+  protected void validateKey(RsaSsaPssPrivateKey privKey) throws GeneralSecurityException {
+    Validators.validateVersion(privKey.getVersion(), VERSION);
+    Validators.validateRsaModulusSize(
+        new BigInteger(1, privKey.getPublicKey().getN().toByteArray()).bitLength());
+    SigUtil.validateRsaSsaPssParams(privKey.getPublicKey().getParams());
+  }
+}
diff --git a/java/src/main/java/com/google/crypto/tink/signature/RsaSsaPssVerifyKeyManager.java b/java/src/main/java/com/google/crypto/tink/signature/RsaSsaPssVerifyKeyManager.java
new file mode 100644
index 0000000..c2a7490
--- /dev/null
+++ b/java/src/main/java/com/google/crypto/tink/signature/RsaSsaPssVerifyKeyManager.java
@@ -0,0 +1,101 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.signature;
+
+import com.google.crypto.tink.KeyManagerBase;
+import com.google.crypto.tink.PublicKeyVerify;
+import com.google.crypto.tink.proto.Empty;
+import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
+import com.google.crypto.tink.proto.RsaSsaPssParams;
+import com.google.crypto.tink.proto.RsaSsaPssPublicKey;
+import com.google.crypto.tink.subtle.EngineFactory;
+import com.google.crypto.tink.subtle.RsaSsaPssVerifyJce;
+import com.google.crypto.tink.subtle.Validators;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.RSAPublicKeySpec;
+
+/**
+ * This key manager produces new instances of {@code RsaSsaPssVerifyJce}. It doesn't support key
+ * generation.
+ */
+class RsaSsaPssVerifyKeyManager extends KeyManagerBase<PublicKeyVerify, RsaSsaPssPublicKey, Empty> {
+  public RsaSsaPssVerifyKeyManager() {
+    super(PublicKeyVerify.class, RsaSsaPssPublicKey.class, Empty.class, TYPE_URL);
+  }
+
+  public static final String TYPE_URL = "type.googleapis.com/google.crypto.tink.RsaSsaPssPublicKey";
+
+  private static final int VERSION = 0;
+
+  @Override
+  public PublicKeyVerify getPrimitiveFromKey(RsaSsaPssPublicKey keyProto)
+      throws GeneralSecurityException {
+    KeyFactory kf = EngineFactory.KEY_FACTORY.getInstance("RSA");
+    BigInteger modulus = new BigInteger(1, keyProto.getN().toByteArray());
+    BigInteger exponent = new BigInteger(1, keyProto.getE().toByteArray());
+    RSAPublicKey publicKey =
+        (RSAPublicKey) kf.generatePublic(new RSAPublicKeySpec(modulus, exponent));
+    RsaSsaPssParams params = keyProto.getParams();
+    return new RsaSsaPssVerifyJce(
+        publicKey,
+        SigUtil.toHashType(params.getSigHash()),
+        SigUtil.toHashType(params.getMgf1Hash()),
+        params.getSaltLength());
+  }
+
+  @Override
+  protected RsaSsaPssPublicKey newKeyFromFormat(Empty unused) throws GeneralSecurityException {
+    throw new GeneralSecurityException("Not implemented");
+  }
+
+  @Override
+  public int getVersion() {
+    return VERSION;
+  }
+
+  @Override
+  protected KeyMaterialType keyMaterialType() {
+    return KeyMaterialType.ASYMMETRIC_PUBLIC;
+  }
+
+  @Override
+  protected RsaSsaPssPublicKey parseKeyProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return RsaSsaPssPublicKey.parseFrom(byteString);
+  }
+
+  @Override
+  protected Empty parseKeyFormatProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return Empty.parseFrom(byteString);
+  }
+
+  @Override
+  protected void validateKey(RsaSsaPssPublicKey pubKey) throws GeneralSecurityException {
+    Validators.validateVersion(pubKey.getVersion(), VERSION);
+    Validators.validateRsaModulusSize(new BigInteger(1, pubKey.getN().toByteArray()).bitLength());
+    SigUtil.validateRsaSsaPssParams(pubKey.getParams());
+  }
+
+  @Override
+  protected void validateKeyFormat(Empty unused) throws GeneralSecurityException {}
+}
diff --git a/java/src/main/java/com/google/crypto/tink/signature/SigUtil.java b/java/src/main/java/com/google/crypto/tink/signature/SigUtil.java
index 3939798..1223bb7 100644
--- a/java/src/main/java/com/google/crypto/tink/signature/SigUtil.java
+++ b/java/src/main/java/com/google/crypto/tink/signature/SigUtil.java
@@ -20,8 +20,11 @@
 import com.google.crypto.tink.proto.EcdsaSignatureEncoding;
 import com.google.crypto.tink.proto.EllipticCurveType;
 import com.google.crypto.tink.proto.HashType;
+import com.google.crypto.tink.proto.RsaSsaPkcs1Params;
+import com.google.crypto.tink.proto.RsaSsaPssParams;
 import com.google.crypto.tink.subtle.EllipticCurves;
 import com.google.crypto.tink.subtle.Enums;
+import com.google.crypto.tink.subtle.Validators;
 import java.security.GeneralSecurityException;
 
 final class SigUtil {
@@ -65,6 +68,46 @@
     }
   }
 
+  /**
+   * Validates RsaSsaPkcs1's parameters. As SHA1 is unsafe, we will only support SHA256 and SHA512
+   * for digital signature.
+   *
+   * @param params the RsaSsaPkcs1Params protocol buffer.
+   * @throws GeneralSecurityException iff it's invalid.
+   */
+  public static void validateRsaSsaPkcs1Params(RsaSsaPkcs1Params params)
+      throws GeneralSecurityException {
+    Validators.validateSignatureHash(toHashType(params.getHashType()));
+  }
+
+  /**
+   * Validates RsaSsaPss's parameters.
+   *
+   * <ul>
+   *   <li>As SHA1 is unsafe, we will only support SHA256 and SHA512 for digital signature.
+   *   <li>The most common use case is that MGF1 hash is the same as signature hash. This is
+   *       recommended by RFC https://tools.ietf.org/html/rfc8017#section-8.1. While using different
+   *       hashes doesn't cause security vulnerabilities, there is also no good reason to support
+   *       different hashes. Furthermore:
+   *       <ul>
+   *         <li>Golang does not support different hashes.
+   *         <li>BoringSSL supports different hashes just because of historical reason. There is no
+   *             real use case.
+   *         <li>Conscrypt/BouncyCastle do not support different hashes.
+   *       </ul>
+   * </ul>
+   *
+   * @param params the RsaSsaPssParams protocol buffer.
+   * @throws GeneralSecurityException iff it's invalid.
+   */
+  public static void validateRsaSsaPssParams(RsaSsaPssParams params)
+      throws GeneralSecurityException {
+    Validators.validateSignatureHash(toHashType(params.getSigHash()));
+    if (params.getSigHash() != params.getMgf1Hash()) {
+      throw new GeneralSecurityException("MGF1 hash is different from signature hash");
+    }
+  }
+
   /** Converts protobuf enum {@code HashType} to raw Java enum {@code Enums.HashType}. */
   public static Enums.HashType toHashType(HashType hash) throws GeneralSecurityException {
     switch (hash) {
diff --git a/java/src/main/java/com/google/crypto/tink/signature/SignatureConfig.java b/java/src/main/java/com/google/crypto/tink/signature/SignatureConfig.java
index 2e6cad6..c4ee94f 100644
--- a/java/src/main/java/com/google/crypto/tink/signature/SignatureConfig.java
+++ b/java/src/main/java/com/google/crypto/tink/signature/SignatureConfig.java
@@ -88,10 +88,34 @@
                   PUBLIC_KEY_SIGN_CATALOGUE_NAME, "PublicKeySign", "Ed25519PrivateKey", 0, true))
           .addEntry(
               Config.getTinkKeyTypeEntry(
+                  PUBLIC_KEY_SIGN_CATALOGUE_NAME,
+                  "PublicKeySign",
+                  "RsaSsaPkcs1PrivateKey",
+                  0,
+                  true))
+          .addEntry(
+              Config.getTinkKeyTypeEntry(
+                  PUBLIC_KEY_SIGN_CATALOGUE_NAME, "PublicKeySign", "RsaSsaPssPrivateKey", 0, true))
+          .addEntry(
+              Config.getTinkKeyTypeEntry(
                   PUBLIC_KEY_VERIFY_CATALOGUE_NAME, "PublicKeyVerify", "EcdsaPublicKey", 0, true))
           .addEntry(
               Config.getTinkKeyTypeEntry(
                   PUBLIC_KEY_VERIFY_CATALOGUE_NAME, "PublicKeyVerify", "Ed25519PublicKey", 0, true))
+          .addEntry(
+              Config.getTinkKeyTypeEntry(
+                  PUBLIC_KEY_VERIFY_CATALOGUE_NAME,
+                  "PublicKeyVerify",
+                  "RsaSsaPkcs1PublicKey",
+                  0,
+                  true))
+          .addEntry(
+              Config.getTinkKeyTypeEntry(
+                  PUBLIC_KEY_VERIFY_CATALOGUE_NAME,
+                  "PublicKeyVerify",
+                  "RsaSsaPssPublicKey",
+                  0,
+                  true))
           .build();
 
   static {
diff --git a/java/src/main/java/com/google/crypto/tink/signature/SignatureKeyTemplates.java b/java/src/main/java/com/google/crypto/tink/signature/SignatureKeyTemplates.java
index 0c82087..fc015c7 100644
--- a/java/src/main/java/com/google/crypto/tink/signature/SignatureKeyTemplates.java
+++ b/java/src/main/java/com/google/crypto/tink/signature/SignatureKeyTemplates.java
@@ -23,6 +23,13 @@
 import com.google.crypto.tink.proto.HashType;
 import com.google.crypto.tink.proto.KeyTemplate;
 import com.google.crypto.tink.proto.OutputPrefixType;
+import com.google.crypto.tink.proto.RsaSsaPkcs1KeyFormat;
+import com.google.crypto.tink.proto.RsaSsaPkcs1Params;
+import com.google.crypto.tink.proto.RsaSsaPssKeyFormat;
+import com.google.crypto.tink.proto.RsaSsaPssParams;
+import com.google.protobuf.ByteString;
+import java.math.BigInteger;
+import java.security.spec.RSAKeyGenParameterSpec;
 
 /**
  * Pre-generated {@link KeyTemplate} for {@link com.google.crypto.tink.PublicKeySign} and {@link
@@ -138,23 +145,129 @@
           .build();
 
   /**
-   * @return a {@link KeyTemplate} containing a {@link HmacKeyFormat} with some specified
+   * @return a {@link KeyTemplate} containing a {@link EcdsaKeyFormat} with some specified
    *     parameters.
    */
   public static KeyTemplate createEcdsaKeyTemplate(
       HashType hashType, EllipticCurveType curve, EcdsaSignatureEncoding encoding) {
-    EcdsaParams params = EcdsaParams.newBuilder()
-        .setHashType(hashType)
-        .setCurve(curve)
-        .setEncoding(encoding)
-        .build();
-    EcdsaKeyFormat format = EcdsaKeyFormat.newBuilder()
-        .setParams(params)
-        .build();
+    EcdsaParams params =
+        EcdsaParams.newBuilder()
+            .setHashType(hashType)
+            .setCurve(curve)
+            .setEncoding(encoding)
+            .build();
+    EcdsaKeyFormat format = EcdsaKeyFormat.newBuilder().setParams(params).build();
     return KeyTemplate.newBuilder()
         .setValue(format.toByteString())
         .setTypeUrl(EcdsaSignKeyManager.TYPE_URL)
         .setOutputPrefixType(OutputPrefixType.TINK)
         .build();
   }
+
+  /**
+   * A {@link KeyTemplate} that generates new instances of {@link
+   * com.google.crypto.tink.proto.RsaSsaPkcs1PrivateKey} with the following parameters:
+   *
+   * <ul>
+   *   <li>Hash function: SHA256.
+   *   <li>Modulus size: 3072 bit.
+   *   <li>Public exponent: 65537 (aka F4).
+   * </ul>
+   */
+  public static final KeyTemplate RSA_SSA_PKCS1_3072_SHA256_F4 =
+      createRsaSsaPkcs1KeyTemplate(HashType.SHA256, 3072, RSAKeyGenParameterSpec.F4);
+
+  /**
+   * A {@link KeyTemplate} that generates new instances of {@link
+   * com.google.crypto.tink.proto.RsaSsaPkcs1PrivateKey} with the following parameters:
+   *
+   * <ul>
+   *   <li>Hash function: SHA512.
+   *   <li>Modulus size: 4096 bit.
+   *   <li>Public exponent: 65537 (aka F4).
+   * </ul>
+   */
+  public static final KeyTemplate RSA_SSA_PKCS1_4096_SHA512_F4 =
+      createRsaSsaPkcs1KeyTemplate(HashType.SHA512, 4096, RSAKeyGenParameterSpec.F4);
+
+  /**
+   * @return a {@link KeyTemplate} containing a {@link RsaSsaPkcs1KeyFormat} with some specified
+   *     parameters.
+   */
+  public static KeyTemplate createRsaSsaPkcs1KeyTemplate(
+      HashType hashType, int modulusSize, BigInteger publicExponent) {
+    RsaSsaPkcs1Params params = RsaSsaPkcs1Params.newBuilder().setHashType(hashType).build();
+    RsaSsaPkcs1KeyFormat format =
+        RsaSsaPkcs1KeyFormat.newBuilder()
+            .setParams(params)
+            .setModulusSizeInBits(modulusSize)
+            .setPublicExponent(ByteString.copyFrom(publicExponent.toByteArray()))
+            .build();
+    return KeyTemplate.newBuilder()
+        .setValue(format.toByteString())
+        .setTypeUrl(RsaSsaPkcs1SignKeyManager.TYPE_URL)
+        .setOutputPrefixType(OutputPrefixType.TINK)
+        .build();
+  }
+
+  /**
+   * A {@link KeyTemplate} that generates new instances of {@link
+   * com.google.crypto.tink.proto.RsaSsaPssPrivateKey} with the following parameters:
+   *
+   * <ul>
+   *   <li>Signature hash: SHA256.
+   *   <li>MGF1 hash: SHA256.
+   *   <li>Salt length: 32 (i.e., SHA256's output length).
+   *   <li>Modulus size: 3072 bit.
+   *   <li>Public exponent: 65537 (aka F4).
+   * </ul>
+   */
+  public static final KeyTemplate RSA_SSA_PSS_3072_SHA256_SHA256_32_F4 =
+      createRsaSsaPssKeyTemplate(
+          HashType.SHA256, HashType.SHA256, 32, 3072, RSAKeyGenParameterSpec.F4);
+
+  /**
+   * A {@link KeyTemplate} that generates new instances of {@link
+   * com.google.crypto.tink.proto.RsaSsaPssPrivateKey} with the following parameters:
+   *
+   * <ul>
+   *   <li>Signature hash: SHA512.
+   *   <li>MGF1 hash: SHA512.
+   *   <li>Salt length: 64 (i.e., SHA512's output length).
+   *   <li>Modulus size: 4096 bit.
+   *   <li>Public exponent: 65537 (aka F4).
+   * </ul>
+   */
+  public static final KeyTemplate RSA_SSA_PSS_4096_SHA512_SHA512_64_F4 =
+      createRsaSsaPssKeyTemplate(
+          HashType.SHA512, HashType.SHA512, 64, 4096, RSAKeyGenParameterSpec.F4);
+
+  /**
+   * @return a {@link KeyTemplate} containing a {@link RsaSsaPssKeyFormat} with some specified
+   *     parameters.
+   */
+  public static KeyTemplate createRsaSsaPssKeyTemplate(
+      HashType sigHash,
+      HashType mgf1Hash,
+      int saltLength,
+      int modulusSize,
+      BigInteger publicExponent) {
+    RsaSsaPssParams params =
+        RsaSsaPssParams.newBuilder()
+            .setSigHash(sigHash)
+            .setMgf1Hash(mgf1Hash)
+            .setSaltLength(saltLength)
+            .build();
+    RsaSsaPssKeyFormat format =
+        RsaSsaPssKeyFormat.newBuilder()
+            .setParams(params)
+            .setModulusSizeInBits(modulusSize)
+            .setPublicExponent(ByteString.copyFrom(publicExponent.toByteArray()))
+            .build();
+    return KeyTemplate.newBuilder()
+        .setValue(format.toByteString())
+        .setTypeUrl(RsaSsaPssSignKeyManager.TYPE_URL)
+        .setOutputPrefixType(OutputPrefixType.TINK)
+        .build();
+  }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/streamingaead/AesCtrHmacStreamingKeyManager.java b/java/src/main/java/com/google/crypto/tink/streamingaead/AesCtrHmacStreamingKeyManager.java
index cebe18a..329f6c7 100644
--- a/java/src/main/java/com/google/crypto/tink/streamingaead/AesCtrHmacStreamingKeyManager.java
+++ b/java/src/main/java/com/google/crypto/tink/streamingaead/AesCtrHmacStreamingKeyManager.java
@@ -16,27 +16,35 @@
 
 package com.google.crypto.tink.streamingaead;
 
-import com.google.crypto.tink.KeyManager;
+import com.google.crypto.tink.KeyManagerBase;
 import com.google.crypto.tink.StreamingAead;
 import com.google.crypto.tink.proto.AesCtrHmacStreamingKey;
 import com.google.crypto.tink.proto.AesCtrHmacStreamingKeyFormat;
 import com.google.crypto.tink.proto.AesCtrHmacStreamingParams;
 import com.google.crypto.tink.proto.HashType;
 import com.google.crypto.tink.proto.HmacParams;
-import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
 import com.google.crypto.tink.subtle.AesCtrHmacStreaming;
 import com.google.crypto.tink.subtle.Random;
 import com.google.crypto.tink.subtle.Validators;
 import com.google.protobuf.ByteString;
 import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
 import java.security.GeneralSecurityException;
 
 /**
  * This key manager generates new {@code AesCtrHmacStreamingKey} keys and produces new instances of
  * {@code AesCtrHmacStreaming}.
  */
-class AesCtrHmacStreamingKeyManager implements KeyManager<StreamingAead> {
+class AesCtrHmacStreamingKeyManager
+    extends KeyManagerBase<StreamingAead, AesCtrHmacStreamingKey, AesCtrHmacStreamingKeyFormat> {
+  public AesCtrHmacStreamingKeyManager() {
+    super(
+        StreamingAead.class,
+        AesCtrHmacStreamingKey.class,
+        AesCtrHmacStreamingKeyFormat.class,
+        TYPE_URL);
+  }
+
   private static final int VERSION = 0;
 
   public static final String TYPE_URL =
@@ -47,23 +55,8 @@
 
   /** @param serializedKey serialized {@code AesCtrHmacStreamingKey} proto */
   @Override
-  public StreamingAead getPrimitive(ByteString serializedKey) throws GeneralSecurityException {
-    try {
-      AesCtrHmacStreamingKey keyProto = AesCtrHmacStreamingKey.parseFrom(serializedKey);
-      return getPrimitive(keyProto);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("expected AesCtrHmacStreamingKey proto", e);
-    }
-  }
-
-  /** @param key {@code AesCtrHmacStreamingKey} proto */
-  @Override
-  public StreamingAead getPrimitive(MessageLite key) throws GeneralSecurityException {
-    if (!(key instanceof AesCtrHmacStreamingKey)) {
-      throw new GeneralSecurityException("expected AesCtrHmacStreamingKey proto");
-    }
-    AesCtrHmacStreamingKey keyProto = (AesCtrHmacStreamingKey) key;
-    validate(keyProto);
+  public StreamingAead getPrimitiveFromKey(AesCtrHmacStreamingKey keyProto)
+      throws GeneralSecurityException {
     return new AesCtrHmacStreaming(
         keyProto.getKeyValue().toByteArray(),
         StreamingAeadUtil.toHmacAlgo(
@@ -81,28 +74,8 @@
    * @return new {@code AesCtrHmacStreamingKey} proto
    */
   @Override
-  public MessageLite newKey(ByteString serializedKeyFormat) throws GeneralSecurityException {
-    try {
-      AesCtrHmacStreamingKeyFormat format =
-          AesCtrHmacStreamingKeyFormat.parseFrom(serializedKeyFormat);
-      return newKey(format);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException(
-          "expected serialized AesCtrHmacStreamingKeyFormat proto", e);
-    }
-  }
-
-  /**
-   * @param keyFormat {@code AesCtrHmacStreamingKeyFormat} proto
-   * @return new {@code AesCtrHmacStreamingKey} proto
-   */
-  @Override
-  public MessageLite newKey(MessageLite keyFormat) throws GeneralSecurityException {
-    if (!(keyFormat instanceof AesCtrHmacStreamingKeyFormat)) {
-      throw new GeneralSecurityException("expected AesCtrHmacStreamingKeyFormat proto");
-    }
-    AesCtrHmacStreamingKeyFormat format = (AesCtrHmacStreamingKeyFormat) keyFormat;
-    validate(format);
+  public AesCtrHmacStreamingKey newKeyFromFormat(AesCtrHmacStreamingKeyFormat format)
+      throws GeneralSecurityException {
     return AesCtrHmacStreamingKey.newBuilder()
         .setKeyValue(ByteString.copyFrom(Random.randBytes(format.getKeySize())))
         .setParams(format.getParams())
@@ -110,36 +83,30 @@
         .build();
   }
 
-  /**
-   * @param serializedKeyFormat serialized {@code AesCtrHmacStreamingKeyFormat} proto
-   * @return {@code KeyData} proto with a new {@code AesCtrHmacStreamingKey} proto
-   */
-  @Override
-  public KeyData newKeyData(ByteString serializedKeyFormat) throws GeneralSecurityException {
-    AesCtrHmacStreamingKey key = (AesCtrHmacStreamingKey) newKey(serializedKeyFormat);
-    return KeyData.newBuilder()
-        .setTypeUrl(TYPE_URL)
-        .setValue(key.toByteString())
-        .setKeyMaterialType(KeyData.KeyMaterialType.SYMMETRIC)
-        .build();
-  }
-
-  @Override
-  public boolean doesSupport(String typeUrl) {
-    return typeUrl.equals(TYPE_URL);
-  }
-
-  @Override
-  public String getKeyType() {
-    return TYPE_URL;
-  }
-
   @Override
   public int getVersion() {
     return VERSION;
   }
 
-  private void validate(AesCtrHmacStreamingKey key) throws GeneralSecurityException {
+  @Override
+  protected KeyMaterialType keyMaterialType() {
+    return KeyMaterialType.SYMMETRIC;
+  }
+
+  @Override
+  protected AesCtrHmacStreamingKey parseKeyProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return AesCtrHmacStreamingKey.parseFrom(byteString);
+  }
+
+  @Override
+  protected AesCtrHmacStreamingKeyFormat parseKeyFormatProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return AesCtrHmacStreamingKeyFormat.parseFrom(byteString);
+  }
+
+  @Override
+  protected void validateKey(AesCtrHmacStreamingKey key) throws GeneralSecurityException {
     Validators.validateVersion(key.getVersion(), VERSION);
     if (key.getKeyValue().size() < 16) {
       throw new GeneralSecurityException("key_value must have at least 16 bytes");
@@ -151,7 +118,9 @@
     validate(key.getParams());
   }
 
-  private void validate(AesCtrHmacStreamingKeyFormat format) throws GeneralSecurityException {
+  @Override
+  protected void validateKeyFormat(AesCtrHmacStreamingKeyFormat format)
+      throws GeneralSecurityException {
     if (format.getKeySize() < 16) {
       throw new GeneralSecurityException("key_size must be at least 16 bytes");
     }
diff --git a/java/src/main/java/com/google/crypto/tink/streamingaead/AesGcmHkdfStreamingKeyManager.java b/java/src/main/java/com/google/crypto/tink/streamingaead/AesGcmHkdfStreamingKeyManager.java
index c34cc5c..bfad923 100644
--- a/java/src/main/java/com/google/crypto/tink/streamingaead/AesGcmHkdfStreamingKeyManager.java
+++ b/java/src/main/java/com/google/crypto/tink/streamingaead/AesGcmHkdfStreamingKeyManager.java
@@ -16,26 +16,34 @@
 
 package com.google.crypto.tink.streamingaead;
 
-import com.google.crypto.tink.KeyManager;
+import com.google.crypto.tink.KeyManagerBase;
 import com.google.crypto.tink.StreamingAead;
 import com.google.crypto.tink.proto.AesGcmHkdfStreamingKey;
 import com.google.crypto.tink.proto.AesGcmHkdfStreamingKeyFormat;
 import com.google.crypto.tink.proto.AesGcmHkdfStreamingParams;
 import com.google.crypto.tink.proto.HashType;
-import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
 import com.google.crypto.tink.subtle.AesGcmHkdfStreaming;
 import com.google.crypto.tink.subtle.Random;
 import com.google.crypto.tink.subtle.Validators;
 import com.google.protobuf.ByteString;
 import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
 import java.security.GeneralSecurityException;
 
 /**
  * This key manager generates new {@code AesGcmHkdfStreamingKey} keys and produces new instances of
  * {@code AesGcmHkdfStreaming}.
  */
-class AesGcmHkdfStreamingKeyManager implements KeyManager<StreamingAead> {
+class AesGcmHkdfStreamingKeyManager
+    extends KeyManagerBase<StreamingAead, AesGcmHkdfStreamingKey, AesGcmHkdfStreamingKeyFormat> {
+  public AesGcmHkdfStreamingKeyManager() {
+    super(
+        StreamingAead.class,
+        AesGcmHkdfStreamingKey.class,
+        AesGcmHkdfStreamingKeyFormat.class,
+        TYPE_URL);
+  }
+
   private static final int VERSION = 0;
 
   public static final String TYPE_URL =
@@ -43,23 +51,8 @@
 
   /** @param serializedKey serialized {@code AesGcmHkdfStreamingKey} proto */
   @Override
-  public StreamingAead getPrimitive(ByteString serializedKey) throws GeneralSecurityException {
-    try {
-      AesGcmHkdfStreamingKey keyProto = AesGcmHkdfStreamingKey.parseFrom(serializedKey);
-      return getPrimitive(keyProto);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException("expected AesGcmHkdfStreamingKey proto");
-    }
-  }
-
-  /** @param key {@code AesGcmHkdfStreamingKey} proto */
-  @Override
-  public StreamingAead getPrimitive(MessageLite key) throws GeneralSecurityException {
-    if (!(key instanceof AesGcmHkdfStreamingKey)) {
-      throw new GeneralSecurityException("expected AesGcmHkdfStreamingKey proto");
-    }
-    AesGcmHkdfStreamingKey keyProto = (AesGcmHkdfStreamingKey) key;
-    validate(keyProto);
+  public StreamingAead getPrimitiveFromKey(AesGcmHkdfStreamingKey keyProto)
+      throws GeneralSecurityException {
     return new AesGcmHkdfStreaming(
         keyProto.getKeyValue().toByteArray(),
         StreamingAeadUtil.toHmacAlgo(
@@ -74,28 +67,8 @@
    * @return new {@code AesGcmHkdfStreamingKey} proto
    */
   @Override
-  public MessageLite newKey(ByteString serializedKeyFormat) throws GeneralSecurityException {
-    try {
-      AesGcmHkdfStreamingKeyFormat format =
-          AesGcmHkdfStreamingKeyFormat.parseFrom(serializedKeyFormat);
-      return newKey(format);
-    } catch (InvalidProtocolBufferException e) {
-      throw new GeneralSecurityException(
-          "expected serialized AesGcmHkdfStreamingKeyFormat proto", e);
-    }
-  }
-
-  /**
-   * @param keyFormat {@code AesGcmHkdfStreamingKeyFormat} proto
-   * @return new {@code AesGcmHkdfStreamingKey} proto
-   */
-  @Override
-  public MessageLite newKey(MessageLite keyFormat) throws GeneralSecurityException {
-    if (!(keyFormat instanceof AesGcmHkdfStreamingKeyFormat)) {
-      throw new GeneralSecurityException("expected AesGcmHkdfStreamingKeyFormat proto");
-    }
-    AesGcmHkdfStreamingKeyFormat format = (AesGcmHkdfStreamingKeyFormat) keyFormat;
-    validate(format);
+  public AesGcmHkdfStreamingKey newKeyFromFormat(AesGcmHkdfStreamingKeyFormat format)
+      throws GeneralSecurityException {
     return AesGcmHkdfStreamingKey.newBuilder()
         .setKeyValue(ByteString.copyFrom(Random.randBytes(format.getKeySize())))
         .setParams(format.getParams())
@@ -103,41 +76,37 @@
         .build();
   }
 
-  /**
-   * @param serializedKeyFormat serialized {@code AesGcmHkdfStreamingKeyFormat} proto
-   * @return {@code KeyData} proto with a new {@code AesGcmHkdfStreamingKey} proto
-   */
-  @Override
-  public KeyData newKeyData(ByteString serializedKeyFormat) throws GeneralSecurityException {
-    AesGcmHkdfStreamingKey key = (AesGcmHkdfStreamingKey) newKey(serializedKeyFormat);
-    return KeyData.newBuilder()
-        .setTypeUrl(TYPE_URL)
-        .setValue(key.toByteString())
-        .setKeyMaterialType(KeyData.KeyMaterialType.SYMMETRIC)
-        .build();
-  }
-
-  @Override
-  public boolean doesSupport(String typeUrl) {
-    return typeUrl.equals(TYPE_URL);
-  }
-
-  @Override
-  public String getKeyType() {
-    return TYPE_URL;
-  }
-
   @Override
   public int getVersion() {
     return VERSION;
   }
 
-  private void validate(AesGcmHkdfStreamingKey key) throws GeneralSecurityException {
+  @Override
+  protected KeyMaterialType keyMaterialType() {
+    return KeyMaterialType.SYMMETRIC;
+  }
+
+  @Override
+  protected AesGcmHkdfStreamingKey parseKeyProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return AesGcmHkdfStreamingKey.parseFrom(byteString);
+  }
+
+  @Override
+  protected AesGcmHkdfStreamingKeyFormat parseKeyFormatProto(ByteString byteString)
+      throws InvalidProtocolBufferException {
+    return AesGcmHkdfStreamingKeyFormat.parseFrom(byteString);
+  }
+
+  @Override
+  protected void validateKey(AesGcmHkdfStreamingKey key) throws GeneralSecurityException {
     Validators.validateVersion(key.getVersion(), VERSION);
     validate(key.getParams());
   }
 
-  private void validate(AesGcmHkdfStreamingKeyFormat format) throws GeneralSecurityException {
+  @Override
+  protected void validateKeyFormat(AesGcmHkdfStreamingKeyFormat format)
+      throws GeneralSecurityException {
     if (format.getKeySize() < 16) {
       throw new GeneralSecurityException("key_size must be at least 16 bytes");
     }
diff --git a/java/src/main/java/com/google/crypto/tink/streamingaead/StreamingAeadCatalogue.java b/java/src/main/java/com/google/crypto/tink/streamingaead/StreamingAeadCatalogue.java
index ac6352a..d8b1b54 100644
--- a/java/src/main/java/com/google/crypto/tink/streamingaead/StreamingAeadCatalogue.java
+++ b/java/src/main/java/com/google/crypto/tink/streamingaead/StreamingAeadCatalogue.java
@@ -18,6 +18,7 @@
 
 import com.google.crypto.tink.Catalogue;
 import com.google.crypto.tink.KeyManager;
+import com.google.crypto.tink.PrimitiveWrapper;
 import com.google.crypto.tink.StreamingAead;
 import java.security.GeneralSecurityException;
 
@@ -61,4 +62,9 @@
             String.format("No support for primitive 'StreamingAead' with key type '%s'.", typeUrl));
     }
   }
+
+  @Override
+  public PrimitiveWrapper<StreamingAead> getPrimitiveWrapper() {
+    return new StreamingAeadWrapper();
+  }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/streamingaead/StreamingAeadFactory.java b/java/src/main/java/com/google/crypto/tink/streamingaead/StreamingAeadFactory.java
index 51c35dd..4f5d1bd 100644
--- a/java/src/main/java/com/google/crypto/tink/streamingaead/StreamingAeadFactory.java
+++ b/java/src/main/java/com/google/crypto/tink/streamingaead/StreamingAeadFactory.java
@@ -22,39 +22,24 @@
 import com.google.crypto.tink.Registry;
 import com.google.crypto.tink.StreamingAead;
 import java.security.GeneralSecurityException;
-import java.util.Collection;
-import java.util.logging.Logger;
 
 /**
- * Static methods for obtaining {@link StreamingAead} instances.
+ * Deprecated class to create {@code StreamingAead} primitives. Instead of using this class, make
+ * sure that the {@code StreamingAeadWrapper} is registered in your binary, then call {@code
+ * keysetHandle.GetPrimitive(StreamingAead.class)}. The required registration happens automatically
+ * if you called one of the following in your binary:
  *
- * <h3>Usage</h3>
+ * <ul>
+ *   <li>{@code StreamingAeadConfig.register()}
+ *   <li>{@code TinkConfig.register()}
+ * </ul>
  *
- * <pre>{@code
- * KeysetHandle keysetHandle = ...;
- * StreamingAead streamingAead = StreamingAeadFactory.getPrimitive(keysetHandle);
- * java.nio.channels.FileChannel ciphertextDestination =
- *     new FileOutputStream(ciphertextFile).getChannel();
- * byte[] aad = ...
- * WritableByteChannel encryptingChannel = s.newEncryptingChannel(ciphertextDestination, aad);
- *
- * while ( ... ) {
- *   int r = encryptingChannel.write(buffer);
- *   ...
- * }
- * encryptingChannel.close();
- * }</pre>
- *
- * <p>The returned primitive works with a keyset (rather than a single key). To encrypt a plaintext,
- * it uses the primary key in the keyset. To decrypt, the primitive tries the enabled keys from the
- * keyset to select the right key for decryption. All keys in a keyset of StreamingAead have type
- * {@link com.google.crypto.tink.proto.OutputPrefixType#RAW}.
- *
+ * @deprecated Use {@code keysetHandle.GetPrimitive(StreamingAead.class)} after registering the
+ *     {@code StreamingAeadWrapper} instead.
  * @since 1.1.0
  */
+@Deprecated
 public final class StreamingAeadFactory {
-  private static final Logger logger = Logger.getLogger(StreamingAeadFactory.class.getName());
-
   /**
    * @return a StreamingAead primitive from a {@code keysetHandle}.
    * @throws GeneralSecurityException
@@ -72,20 +57,9 @@
       KeysetHandle keysetHandle,
       final KeyManager<StreamingAead> keyManager)
       throws GeneralSecurityException {
-    final PrimitiveSet<StreamingAead> primitives = Registry.getPrimitives(keysetHandle, keyManager);
-    validate(primitives);
-    return new StreamingAeadHelper(primitives);
-  }
-
-  // Check that all primitives in <code>pset</code> are StreamingAead instances.
-  private static void validate(final PrimitiveSet<StreamingAead> pset)
-      throws GeneralSecurityException {
-    for (Collection<PrimitiveSet.Entry<StreamingAead>> entries : pset.getAll()) {
-      for (PrimitiveSet.Entry<StreamingAead> entry : entries) {
-        if (!(entry.getPrimitive() instanceof StreamingAead)) {
-          throw new GeneralSecurityException("invalid StreamingAead key material");
-        }
-      }
-    }
+    Registry.registerPrimitiveWrapper(new StreamingAeadWrapper());
+    final PrimitiveSet<StreamingAead> primitives =
+        Registry.getPrimitives(keysetHandle, keyManager, StreamingAead.class);
+    return Registry.wrap(primitives);
   }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/streamingaead/StreamingAeadKeyTemplates.java b/java/src/main/java/com/google/crypto/tink/streamingaead/StreamingAeadKeyTemplates.java
index be408de..2fd14cc 100644
--- a/java/src/main/java/com/google/crypto/tink/streamingaead/StreamingAeadKeyTemplates.java
+++ b/java/src/main/java/com/google/crypto/tink/streamingaead/StreamingAeadKeyTemplates.java
@@ -35,7 +35,7 @@
  * <pre>{@code
  * Config.register(StreamingAeadConfig.TINK_1_0_0);
  * KeysetHandle handle = KeysetHandle.generateNew(StreamingAeadKeyTemplates.AES128_GCM_HKDF_4KB);
- * StreamingAead ags = StreamingAeadFactory.getPrimitive(handle);
+ * StreamingAead ags = handle.getPrimitive(StreamingAead.class);
  * }</pre>
  *
  * @since 1.1.0
diff --git a/java/src/main/java/com/google/crypto/tink/streamingaead/StreamingAeadWrapper.java b/java/src/main/java/com/google/crypto/tink/streamingaead/StreamingAeadWrapper.java
new file mode 100644
index 0000000..cc65086
--- /dev/null
+++ b/java/src/main/java/com/google/crypto/tink/streamingaead/StreamingAeadWrapper.java
@@ -0,0 +1,47 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.streamingaead;
+
+import com.google.crypto.tink.PrimitiveSet;
+import com.google.crypto.tink.PrimitiveWrapper;
+import com.google.crypto.tink.StreamingAead;
+import java.security.GeneralSecurityException;
+
+/**
+ * StreamingAeadWrapper is the implementation of PrimitiveWrapper for the StreamingAead primitive.
+ *
+ * <p>The returned primitive works with a keyset (rather than a single key). To encrypt a plaintext,
+ * it uses the primary key in the keyset. To decrypt, the primitive tries the enabled keys from the
+ * keyset to select the right key for decryption. All keys in a keyset of StreamingAead have type
+ * {@link com.google.crypto.tink.proto.OutputPrefixType#RAW}.
+ */
+public final class StreamingAeadWrapper implements PrimitiveWrapper<StreamingAead> {
+  /**
+   * @return a StreamingAead primitive from a {@code keysetHandle}.
+   * @throws GeneralSecurityException
+   */
+  @Override
+  public StreamingAead wrap(final PrimitiveSet<StreamingAead> primitives)
+      throws GeneralSecurityException {
+    return new StreamingAeadHelper(primitives);
+  }
+
+  @Override
+  public Class<StreamingAead> getPrimitiveClass() {
+    return StreamingAead.class;
+  }
+}
diff --git a/java/src/main/java/com/google/crypto/tink/subtle/AesSiv.java b/java/src/main/java/com/google/crypto/tink/subtle/AesSiv.java
index 02c3e12..621883a 100644
--- a/java/src/main/java/com/google/crypto/tink/subtle/AesSiv.java
+++ b/java/src/main/java/com/google/crypto/tink/subtle/AesSiv.java
@@ -29,8 +29,9 @@
 /**
  * AES-SIV, as described in <a href="https://tools.ietf.org/html/rfc5297">RFC 5297</a>.
  *
- * <p>To meet the security requirements of {@link DeterministicAead}, this cipher can only be used
- * with 256-bit keys.
+ * <p>Each AES-SIV key consists of two sub keys. To meet the security requirements of {@link
+ * DeterministicAead}, each sub key must be 256 bits. The total size of ASE-SIV keys is then 512
+ * bits.
  *
  * @since 1.1.0
  */
diff --git a/java/src/main/java/com/google/crypto/tink/subtle/AesUtil.java b/java/src/main/java/com/google/crypto/tink/subtle/AesUtil.java
index 5bfa861..86acb69 100644
--- a/java/src/main/java/com/google/crypto/tink/subtle/AesUtil.java
+++ b/java/src/main/java/com/google/crypto/tink/subtle/AesUtil.java
@@ -37,15 +37,26 @@
    * order.
    */
   static byte[] dbl(final byte[] value) {
+    if (value.length != BLOCK_SIZE) {
+      throw new IllegalArgumentException("value must be a block.");
+    }
+
+    // Note that >> is an arithmetical shift, which copies the leftmost bit to fill the
+    // blanks created by shifting. For instance, x >> 7 will equal 0xFF if (x & 1), and 0x00
+    // otherwise. This is a bit hard to read, but the operation is branchless, which is valuable
+    // in this context.
+
     // Shift left by one.
-    byte[] res = new byte[value.length];
-    for (int i = 0; i < res.length; i++) {
+    byte[] res = new byte[BLOCK_SIZE];
+    for (int i = 0; i < BLOCK_SIZE; i++) {
       res[i] = (byte) (0xFE & (value[i] << 1));
-      if (i < res.length - 1) {
+      if (i < BLOCK_SIZE - 1) {
         res[i] |= (byte) (0x01 & (value[i + 1] >> 7));
       }
     }
-    res[15] ^= (byte) (0x87 & (value[0] >> 7));
+    // And handle the modulus if needed (0x87 is the binary representation of the polynomial,
+    // minus the x^128 part).
+    res[BLOCK_SIZE - 1] ^= (byte) (0x87 & (value[0] >> 7));
     return res;
   }
 
diff --git a/java/src/main/java/com/google/crypto/tink/subtle/BUILD.bazel b/java/src/main/java/com/google/crypto/tink/subtle/BUILD.bazel
index 255bfbc..90a76e0 100644
--- a/java/src/main/java/com/google/crypto/tink/subtle/BUILD.bazel
+++ b/java/src/main/java/com/google/crypto/tink/subtle/BUILD.bazel
@@ -42,18 +42,21 @@
         "AesEaxJce.java",
         "AesGcmJce.java",
         "ChaCha20.java",
+        "ChaCha20Base.java",
         "ChaCha20Poly1305.java",
+        "ChaCha20Poly1305Base.java",
         "EncryptThenAuthenticate.java",
         "IndCpaCipher.java",
         "Poly1305.java",
-        "Snuffle.java",
-        "SnufflePoly1305.java",
+        "XChaCha20.java",
+        "XChaCha20Poly1305.java",
     ],
     javacopts = JAVACOPTS_OSS,
     deps = [
-        ":subtle",
         ":mac",
+        ":subtle",
         "//java/src/main/java/com/google/crypto/tink:primitives",
+        "//java/src/main/java/com/google/crypto/tink/annotations",
     ],
 )
 
@@ -102,6 +105,8 @@
         "Ed25519Verify.java",
         "RsaSsaPkcs1SignJce.java",
         "RsaSsaPkcs1VerifyJce.java",
+        "RsaSsaPssSignJce.java",
+        "RsaSsaPssVerifyJce.java",
     ],
     javacopts = JAVACOPTS_OSS,
     deps = [
@@ -156,6 +161,20 @@
     ],
 )
 
+# KeyWrap subtle
+java_library(
+    name = "keywrap",
+    srcs = [
+        "Kwp.java",
+    ],
+    javacopts = JAVACOPTS_OSS,
+    deps = [
+        ":subtle",
+        "//java/src/main/java/com/google/crypto/tink:primitives",
+        "@com_google_code_findbugs_jsr305",
+    ],
+)
+
 # x25519 subtle
 
 java_library(
diff --git a/java/src/main/java/com/google/crypto/tink/subtle/ChaCha20.java b/java/src/main/java/com/google/crypto/tink/subtle/ChaCha20.java
index 5d0561d..27ff32d 100644
--- a/java/src/main/java/com/google/crypto/tink/subtle/ChaCha20.java
+++ b/java/src/main/java/com/google/crypto/tink/subtle/ChaCha20.java
@@ -16,36 +16,37 @@
 
 package com.google.crypto.tink.subtle;
 
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
 import java.security.InvalidKeyException;
 
 /**
- * A stream cipher based on RFC7539 (i.e., uses 96-bit random nonces)
- * https://tools.ietf.org/html/rfc7539
+ * A stream cipher, as described in RFC 8439 https://tools.ietf.org/html/rfc8439, section 2.4.
  *
  * <p>This cipher is meant to be used to construct an AEAD with Poly1305.
  */
-class ChaCha20 extends Snuffle {
-  private static final byte[] ZERO_16_BYTES = new byte[16];
-
+class ChaCha20 extends ChaCha20Base {
   ChaCha20(final byte[] key, int initialCounter) throws InvalidKeyException {
     super(key, initialCounter);
   }
 
-  /**
-   * Returns the initial state from {@code nonce} and {@code counter}.
-   *
-   * <p>ChaCha20 has a different logic than XChaCha20, because the former uses a 12-byte nonce, but
-   * the later uses 24-byte.
-   */
-  private int[] createInitialState(final byte[] nonce, int counter) {
-    // Set the initial state based on https://tools.ietf.org/html/rfc7539#section-2.3
-    int[] state = new int[Snuffle.BLOCK_SIZE_IN_INTS];
-    setSigma(state);
-    setKey(state, key.getBytes());
+  @Override
+  int[] createInitialState(final int[] nonce, int counter) {
+    if (nonce.length != nonceSizeInBytes() / 4) {
+      throw new IllegalArgumentException(
+          String.format("ChaCha20 uses 96-bit nonces, but got a %d-bit nonce", nonce.length * 32));
+    }
+    // Set the initial state based on https://tools.ietf.org/html/rfc8439#section-2.3
+    int[] state = new int[ChaCha20Base.BLOCK_SIZE_IN_INTS];
+    // The first four words (0-3) are constants: 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574.
+    // The next eight words (4-11) are taken from the 256-bit key by reading the bytes in
+    // little-endian order, in 4-byte chunks.
+    ChaCha20Base.setSigmaAndKey(state, this.key);
+    // Word 12 is a block counter. Since each block is 64-byte, a 32-bit word is enough for 256
+    // gigabytes of data. Ref: https://tools.ietf.org/html/rfc8439#section-2.3.
     state[12] = counter;
-    System.arraycopy(toIntArray(ByteBuffer.wrap(nonce)), 0, state, 13, nonceSizeInBytes() / 4);
+    // Words 13-15 are a nonce, which must not be repeated for the same key. The 13th word is the
+    // first 32 bits of the input nonce taken as a little-endian integer, while the 15th word is the
+    // last 32 bits.
+    System.arraycopy(nonce, 0, state, 13, nonce.length);
     return state;
   }
 
@@ -53,50 +54,4 @@
   int nonceSizeInBytes() {
     return 12;
   }
-
-  @Override
-  ByteBuffer getKeyStreamBlock(final byte[] nonce, int counter) {
-    int[] state = createInitialState(nonce, counter);
-    int[] workingState = state.clone();
-    shuffleState(workingState);
-    for (int i = 0; i < state.length; i++) {
-      state[i] += workingState[i];
-    }
-    ByteBuffer out = ByteBuffer.allocate(BLOCK_SIZE_IN_BYTES).order(ByteOrder.LITTLE_ENDIAN);
-    out.asIntBuffer().put(state, 0, BLOCK_SIZE_IN_INTS);
-    return out;
-  }
-
-  private static void setSigma(int[] state) {
-    System.arraycopy(Snuffle.SIGMA, 0, state, 0, SIGMA.length);
-  }
-
-  private static void setKey(int[] state, final byte[] key) {
-    int[] keyInt = toIntArray(ByteBuffer.wrap(key));
-    System.arraycopy(keyInt, 0, state, 4, keyInt.length);
-  }
-
-  private static void shuffleState(final int[] state) {
-    for (int i = 0; i < 10; i++) {
-      quarterRound(state, 0, 4, 8, 12);
-      quarterRound(state, 1, 5, 9, 13);
-      quarterRound(state, 2, 6, 10, 14);
-      quarterRound(state, 3, 7, 11, 15);
-      quarterRound(state, 0, 5, 10, 15);
-      quarterRound(state, 1, 6, 11, 12);
-      quarterRound(state, 2, 7, 8, 13);
-      quarterRound(state, 3, 4, 9, 14);
-    }
-  }
-
-  static void quarterRound(int[] x, int a, int b, int c, int d) {
-    x[a] += x[b];
-    x[d] = rotateLeft(x[d] ^ x[a], 16);
-    x[c] += x[d];
-    x[b] = rotateLeft(x[b] ^ x[c], 12);
-    x[a] += x[b];
-    x[d] = rotateLeft(x[d] ^ x[a], 8);
-    x[c] += x[d];
-    x[b] = rotateLeft(x[b] ^ x[c], 7);
-  }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/subtle/ChaCha20Base.java b/java/src/main/java/com/google/crypto/tink/subtle/ChaCha20Base.java
new file mode 100644
index 0000000..454d24e
--- /dev/null
+++ b/java/src/main/java/com/google/crypto/tink/subtle/ChaCha20Base.java
@@ -0,0 +1,168 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.subtle;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.IntBuffer;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+
+/**
+ * Abstract base class for ChaCha20 and XChaCha20.
+ *
+ * <p>ChaCha20 and XChaCha20 have two differences: the size of the nonce and the initial state of
+ * the block function that produces a key stream block from a key, a nonce, and a counter.
+ *
+ * <p>Concrete implementations of this class are meant to be used to construct an {@link
+ * com.google.crypto.tink.Aead} with {@link com.google.crypto.tink.subtle.Poly1305}.
+ */
+abstract class ChaCha20Base implements IndCpaCipher {
+  public static final int BLOCK_SIZE_IN_INTS = 16;
+  public static final int BLOCK_SIZE_IN_BYTES = BLOCK_SIZE_IN_INTS * 4;
+  public static final int KEY_SIZE_IN_INTS = 8;
+  public static final int KEY_SIZE_IN_BYTES = KEY_SIZE_IN_INTS * 4;
+  private static final int[] SIGMA =
+      toIntArray(
+          new byte[] {
+            'e', 'x', 'p', 'a', 'n', 'd', ' ', '3', '2', '-', 'b', 'y', 't', 'e', ' ', 'k'
+          });
+  int[] key;
+  private final int initialCounter;
+
+  ChaCha20Base(final byte[] key, int initialCounter) throws InvalidKeyException {
+    if (key.length != KEY_SIZE_IN_BYTES) {
+      throw new InvalidKeyException("The key length in bytes must be 32.");
+    }
+    this.key = toIntArray(key);
+    this.initialCounter = initialCounter;
+  }
+
+  /** Returns the initial state from {@code nonce} and {@code counter}. */
+  abstract int[] createInitialState(final int[] nonce, int counter);
+
+  /**
+   * The size of the randomly generated nonces.
+   *
+   * <p>ChaCha20 uses 12-byte nonces, but XChaCha20 use 24-byte nonces.
+   */
+  abstract int nonceSizeInBytes();
+
+  @Override
+  public byte[] encrypt(final byte[] plaintext) throws GeneralSecurityException {
+    if (plaintext.length > Integer.MAX_VALUE - nonceSizeInBytes()) {
+      throw new GeneralSecurityException("plaintext too long");
+    }
+    ByteBuffer ciphertext = ByteBuffer.allocate(nonceSizeInBytes() + plaintext.length);
+    encrypt(ciphertext, plaintext);
+    return ciphertext.array();
+  }
+
+  void encrypt(ByteBuffer output, final byte[] plaintext) throws GeneralSecurityException {
+    if (output.remaining() - nonceSizeInBytes() < plaintext.length) {
+      throw new IllegalArgumentException("Given ByteBuffer output is too small");
+    }
+
+    byte[] nonce = Random.randBytes(nonceSizeInBytes());
+    output.put(nonce);
+    process(nonce, output, ByteBuffer.wrap(plaintext));
+  }
+
+  @Override
+  public byte[] decrypt(final byte[] ciphertext) throws GeneralSecurityException {
+    return decrypt(ByteBuffer.wrap(ciphertext));
+  }
+
+  byte[] decrypt(ByteBuffer ciphertext) throws GeneralSecurityException {
+    if (ciphertext.remaining() < nonceSizeInBytes()) {
+      throw new GeneralSecurityException("ciphertext too short");
+    }
+    byte[] nonce = new byte[nonceSizeInBytes()];
+    ciphertext.get(nonce);
+    ByteBuffer plaintext = ByteBuffer.allocate(ciphertext.remaining());
+    process(nonce, plaintext, ciphertext);
+    return plaintext.array();
+  }
+
+  private void process(final byte[] nonce, ByteBuffer output, ByteBuffer input)
+      throws GeneralSecurityException {
+    int length = input.remaining();
+    int numBlocks = (length / BLOCK_SIZE_IN_BYTES) + 1;
+    for (int i = 0; i < numBlocks; i++) {
+      ByteBuffer keyStreamBlock = chacha20Block(nonce, i + initialCounter);
+      if (i == numBlocks - 1) {
+        // last block
+        Bytes.xor(output, input, keyStreamBlock, length % BLOCK_SIZE_IN_BYTES);
+      } else {
+        Bytes.xor(output, input, keyStreamBlock, BLOCK_SIZE_IN_BYTES);
+      }
+    }
+  }
+
+  // https://tools.ietf.org/html/rfc8439#section-2.3.
+  ByteBuffer chacha20Block(final byte[] nonce, int counter) {
+    int[] state = createInitialState(toIntArray(nonce), counter);
+    int[] workingState = state.clone();
+    shuffleState(workingState);
+    for (int i = 0; i < state.length; i++) {
+      state[i] += workingState[i];
+    }
+    ByteBuffer out = ByteBuffer.allocate(BLOCK_SIZE_IN_BYTES).order(ByteOrder.LITTLE_ENDIAN);
+    out.asIntBuffer().put(state, 0, BLOCK_SIZE_IN_INTS);
+    return out;
+  }
+
+  static void setSigmaAndKey(int[] state, final int[] key) {
+    System.arraycopy(SIGMA, 0, state, 0, SIGMA.length);
+    System.arraycopy(key, 0, state, SIGMA.length, KEY_SIZE_IN_INTS);
+  }
+
+  static void shuffleState(final int[] state) {
+    for (int i = 0; i < 10; i++) {
+      quarterRound(state, 0, 4, 8, 12);
+      quarterRound(state, 1, 5, 9, 13);
+      quarterRound(state, 2, 6, 10, 14);
+      quarterRound(state, 3, 7, 11, 15);
+      quarterRound(state, 0, 5, 10, 15);
+      quarterRound(state, 1, 6, 11, 12);
+      quarterRound(state, 2, 7, 8, 13);
+      quarterRound(state, 3, 4, 9, 14);
+    }
+  }
+
+  static void quarterRound(int[] x, int a, int b, int c, int d) {
+    x[a] += x[b];
+    x[d] = rotateLeft(x[d] ^ x[a], 16);
+    x[c] += x[d];
+    x[b] = rotateLeft(x[b] ^ x[c], 12);
+    x[a] += x[b];
+    x[d] = rotateLeft(x[d] ^ x[a], 8);
+    x[c] += x[d];
+    x[b] = rotateLeft(x[b] ^ x[c], 7);
+  }
+
+  static int[] toIntArray(final byte[] input) {
+    IntBuffer intBuffer = ByteBuffer.wrap(input).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer();
+    int[] ret = new int[intBuffer.remaining()];
+    intBuffer.get(ret);
+    return ret;
+  }
+
+  private static int rotateLeft(int x, int y) {
+    return (x << y) | (x >>> -y);
+  }
+}
diff --git a/java/src/main/java/com/google/crypto/tink/subtle/ChaCha20Poly1305.java b/java/src/main/java/com/google/crypto/tink/subtle/ChaCha20Poly1305.java
index 1ba2b38..6a6266f 100644
--- a/java/src/main/java/com/google/crypto/tink/subtle/ChaCha20Poly1305.java
+++ b/java/src/main/java/com/google/crypto/tink/subtle/ChaCha20Poly1305.java
@@ -16,21 +16,23 @@
 
 package com.google.crypto.tink.subtle;
 
+
 import java.security.InvalidKeyException;
 
 /**
- * ChaCha20-Poly1305, as described in <a href="https://tools.ietf.org/html/rfc7539#section-2.8">RFC
- * 7539, section 2.8</a>.
+ * ChaCha20Poly1305 AEAD construction, as described in <a
+ * href="https://tools.ietf.org/html/rfc8439#section-2.8">RFC 8439, section 2.8</a>.
  *
  * @since 1.1.0
  */
-public final class ChaCha20Poly1305 extends SnufflePoly1305 {
+public final class ChaCha20Poly1305 extends ChaCha20Poly1305Base {
   public ChaCha20Poly1305(final byte[] key) throws InvalidKeyException {
     super(key);
   }
 
   @Override
-  Snuffle createSnuffleInstance(final byte[] key, int initialCounter) throws InvalidKeyException {
+  ChaCha20Base newChaCha20Instance(final byte[] key, int initialCounter)
+      throws InvalidKeyException {
     return new ChaCha20(key, initialCounter);
   }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/subtle/SnufflePoly1305.java b/java/src/main/java/com/google/crypto/tink/subtle/ChaCha20Poly1305Base.java
similarity index 75%
rename from java/src/main/java/com/google/crypto/tink/subtle/SnufflePoly1305.java
rename to java/src/main/java/com/google/crypto/tink/subtle/ChaCha20Poly1305Base.java
index bcb7290..340916e 100644
--- a/java/src/main/java/com/google/crypto/tink/subtle/SnufflePoly1305.java
+++ b/java/src/main/java/com/google/crypto/tink/subtle/ChaCha20Poly1305Base.java
@@ -27,35 +27,30 @@
 import javax.crypto.AEADBadTagException;
 
 /**
- * An {@link Aead} construction with a {@link Snuffle} and {@link Poly1305}, following RFC 7539,
- * section 2.8.
+ * Abstract base class for class of ChaCha20Poly1305 and XChaCha20Poly1305, following RFC 8439
+ * https://tools.ietf.org/html/rfc8439.
  *
  * <p>This implementation produces ciphertext with the following format: {@code nonce ||
  * actual_ciphertext || tag} and only decrypts the same format.
  */
-abstract class SnufflePoly1305 implements Aead {
-  private final byte[] key;
-  private final Snuffle snuffle;
-  private final Snuffle macKeysnuffle;
+abstract class ChaCha20Poly1305Base implements Aead {
+  private final ChaCha20Base chacha20;
+  private final ChaCha20Base macKeyChaCha20;
 
-  SnufflePoly1305(final byte[] key) throws InvalidKeyException {
-    this.key = key.clone();
-    this.snuffle = createSnuffleInstance(key, 1);
-    this.macKeysnuffle = createSnuffleInstance(key, 0);
+  public ChaCha20Poly1305Base(final byte[] key) throws InvalidKeyException {
+    this.chacha20 = newChaCha20Instance(key, 1);
+    this.macKeyChaCha20 = newChaCha20Instance(key, 0);
   }
 
-  abstract Snuffle createSnuffleInstance(final byte[] key, int initialCounter)
+  abstract ChaCha20Base newChaCha20Instance(final byte[] key, int initialCounter)
       throws InvalidKeyException;
 
   /**
    * Encrypts the {@code plaintext} with Poly1305 authentication based on {@code associatedData}.
    *
    * <p>Please note that nonce is randomly generated hence keys need to be rotated after encrypting
-   * a certain number of messages depending on the nonce size of the underlying {@link Snuffle}.
-   * Reference: Using 96-bit random nonces, it is possible to encrypt, with a single key, up to 2^32
-   * messages with probability of collision <= 2^-32 whereas using 192-bit random nonces, the number
-   * of messages that can be encrypted with the same key is up to 2^80 with the same probability of
-   * collusion.
+   * a certain number of messages depending on the nonce size of the underlying {@link
+   * ChaCha20Base}.
    *
    * @param plaintext data to encrypt
    * @param associatedData associated authenticated data
@@ -64,11 +59,12 @@
   @Override
   public byte[] encrypt(final byte[] plaintext, final byte[] associatedData)
       throws GeneralSecurityException {
-    if (plaintext.length > Integer.MAX_VALUE - snuffle.nonceSizeInBytes() - MAC_TAG_SIZE_IN_BYTES) {
+    if (plaintext.length
+        > Integer.MAX_VALUE - chacha20.nonceSizeInBytes() - MAC_TAG_SIZE_IN_BYTES) {
       throw new GeneralSecurityException("plaintext too long");
     }
     ByteBuffer ciphertext =
-        ByteBuffer.allocate(plaintext.length + snuffle.nonceSizeInBytes() + MAC_TAG_SIZE_IN_BYTES);
+        ByteBuffer.allocate(plaintext.length + chacha20.nonceSizeInBytes() + MAC_TAG_SIZE_IN_BYTES);
 
     encrypt(ciphertext, plaintext, associatedData);
     return ciphertext.array();
@@ -77,20 +73,20 @@
   private void encrypt(ByteBuffer output, final byte[] plaintext, final byte[] associatedData)
       throws GeneralSecurityException {
     if (output.remaining()
-        < plaintext.length + snuffle.nonceSizeInBytes() + MAC_TAG_SIZE_IN_BYTES) {
+        < plaintext.length + chacha20.nonceSizeInBytes() + MAC_TAG_SIZE_IN_BYTES) {
       throw new IllegalArgumentException("Given ByteBuffer output is too small");
     }
     int firstPosition = output.position();
-    snuffle.encrypt(output, plaintext);
+    chacha20.encrypt(output, plaintext);
     output.position(firstPosition);
-    byte[] nonce = new byte[snuffle.nonceSizeInBytes()];
+    byte[] nonce = new byte[chacha20.nonceSizeInBytes()];
     output.get(nonce);
     output.limit(output.limit() - MAC_TAG_SIZE_IN_BYTES);
     byte[] aad = associatedData;
     if (aad == null) {
       aad = new byte[0];
     }
-    byte[] tag = Poly1305.computeMac(getMacKey(nonce), macDataRfc7539(aad, output));
+    byte[] tag = Poly1305.computeMac(getMacKey(nonce), macDataRfc8439(aad, output));
     output.limit(output.limit() + MAC_TAG_SIZE_IN_BYTES);
     output.put(tag);
   }
@@ -124,7 +120,7 @@
    */
   private byte[] decrypt(ByteBuffer ciphertext, final byte[] associatedData)
       throws GeneralSecurityException {
-    if (ciphertext.remaining() < snuffle.nonceSizeInBytes() + MAC_TAG_SIZE_IN_BYTES) {
+    if (ciphertext.remaining() < chacha20.nonceSizeInBytes() + MAC_TAG_SIZE_IN_BYTES) {
       throw new GeneralSecurityException("ciphertext too short");
     }
     int firstPosition = ciphertext.position();
@@ -134,25 +130,33 @@
     // rewind to read ciphertext and compute tag.
     ciphertext.position(firstPosition);
     ciphertext.limit(ciphertext.limit() - MAC_TAG_SIZE_IN_BYTES);
-    byte[] nonce = new byte[snuffle.nonceSizeInBytes()];
+    byte[] nonce = new byte[chacha20.nonceSizeInBytes()];
     ciphertext.get(nonce);
     byte[] aad = associatedData;
     if (aad == null) {
       aad = new byte[0];
     }
     try {
-      Poly1305.verifyMac(getMacKey(nonce), macDataRfc7539(aad, ciphertext), tag);
+      Poly1305.verifyMac(getMacKey(nonce), macDataRfc8439(aad, ciphertext), tag);
     } catch (GeneralSecurityException ex) {
       throw new AEADBadTagException(ex.toString());
     }
 
     // rewind to decrypt the ciphertext.
     ciphertext.position(firstPosition);
-    return snuffle.decrypt(ciphertext);
+    return chacha20.decrypt(ciphertext);
   }
 
-  /** Prepares the input to MAC, following RFC 7539, section 2.8. */
-  static byte[] macDataRfc7539(final byte[] aad, ByteBuffer ciphertext) {
+  /** The MAC key is the first 32 bytes of the first key stream block */
+  private byte[] getMacKey(final byte[] nonce) throws GeneralSecurityException {
+    ByteBuffer firstBlock = macKeyChaCha20.chacha20Block(nonce, 0 /* counter */);
+    byte[] result = new byte[MAC_KEY_SIZE_IN_BYTES];
+    firstBlock.get(result);
+    return result;
+  }
+
+  /** Prepares the input to MAC, following RFC 8439, section 2.8. */
+  private static byte[] macDataRfc8439(final byte[] aad, ByteBuffer ciphertext) {
     int aadPaddedLen = (aad.length % 16 == 0) ? aad.length : (aad.length + 16 - aad.length % 16);
     int ciphertextLen = ciphertext.remaining();
     int ciphertextPaddedLen =
@@ -167,12 +171,4 @@
     macData.putLong(ciphertextLen);
     return macData.array();
   }
-
-  /** The MAC key is the first 32 bytes of the first key stream block */
-  private byte[] getMacKey(final byte[] nonce) throws InvalidKeyException {
-    ByteBuffer firstBlock = macKeysnuffle.getKeyStreamBlock(nonce, 0 /* counter */);
-    byte[] result = new byte[MAC_KEY_SIZE_IN_BYTES];
-    firstBlock.get(result);
-    return result;
-  }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/subtle/EllipticCurves.java b/java/src/main/java/com/google/crypto/tink/subtle/EllipticCurves.java
index e8fa7da..52b17ca 100644
--- a/java/src/main/java/com/google/crypto/tink/subtle/EllipticCurves.java
+++ b/java/src/main/java/com/google/crypto/tink/subtle/EllipticCurves.java
@@ -935,9 +935,8 @@
     checkPointOnCurve(publicPoint, myPrivateKey.getParams().getCurve());
     // Explicitly reconstruct the peer public key using private key's spec.
     ECParameterSpec privSpec = myPrivateKey.getParams();
-    EllipticCurve privCurve = privSpec.getCurve();
     ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(publicPoint, privSpec);
-    KeyFactory kf = KeyFactory.getInstance("EC");
+    KeyFactory kf = EngineFactory.KEY_FACTORY.getInstance("EC");
     PublicKey publicKey = kf.generatePublic(publicKeySpec);
     KeyAgreement ka = EngineFactory.KEY_AGREEMENT.getInstance("ECDH");
     ka.init(myPrivateKey);
diff --git a/java/src/main/java/com/google/crypto/tink/subtle/Kwp.java b/java/src/main/java/com/google/crypto/tink/subtle/Kwp.java
new file mode 100644
index 0000000..b2c0c03
--- /dev/null
+++ b/java/src/main/java/com/google/crypto/tink/subtle/Kwp.java
@@ -0,0 +1,228 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.subtle;
+
+import com.google.crypto.tink.KeyWrap;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Implements the key wrapping primitive KWP defined in NIST SP 800 38f.
+ * The same encryption mode is also defined in RFC 5649. The NIST document is used here
+ * as a primary reference, since it contains a security analysis and further
+ * recommendations. In particular, Section 8 of NIST SP 800 38f suggests that the
+ * allowed key sizes may be restricted. The implementation in this class
+ * requires that the key sizes are in the range MIN_WRAP_KEY_SIZE and MAX_WRAP_KEY_SIZE.
+ *
+ * <p>The minimum of 16 bytes has been chosen, because 128 bit keys are the smallest
+ * key sizes used in tink. Additionally, wrapping short keys with KWP does not use
+ * the function W and hence prevents using security arguments based on the assumption
+ * that W is strong pseudorandom. (I.e. one consequence of using a strong pseudorandom
+ * permutation as an underlying function is that leaking partial information about
+ * decrypted bytes is not useful for an attack.)
+ *
+ * <p>The upper bound for the key size is somewhat arbitrary. Setting an upper bound is
+ * motivated by the analysis in section A.4 of NIST SP 800 38f: forgeries of long
+ * messages is simpler than forgeries of short message.
+ *
+ * @since 1.?.?
+ */
+public class Kwp implements KeyWrap {
+  private final SecretKey aesKey;
+
+  static final int MIN_WRAP_KEY_SIZE = 16;
+  static final int MAX_WRAP_KEY_SIZE = 4096;
+  static final int ROUNDS = 6;
+  static final byte[] PREFIX = new byte[]{(byte) 0xa6, (byte) 0x59, (byte) 0x59, (byte) 0xa6};
+
+  /**
+   * Construct a new Instance for KWP.
+   * @param key the wrapping key. This is an AES key.
+   *   Supported key sizes are 128 and 256 bits.
+   */
+  public Kwp(final byte[] key) throws GeneralSecurityException {
+    if (key.length != 16 && key.length != 32) {
+      throw new GeneralSecurityException("Unsupported key length");
+    }
+    aesKey = new SecretKeySpec(key, "AES");
+  }
+
+  /**
+   * Returns the size of a wrapped key for a given input size.
+   * @param inputSize the size of the key to wrap in bytes.
+   */
+  private int wrappingSize(int inputSize) {
+    int paddingSize = 7 - (inputSize + 7) % 8;
+    return inputSize + paddingSize + 8;
+  }
+
+  /**
+   * Computes the pseudorandom permutation W over the IV
+   * concatenated with zero padded key material.
+   * @param iv an IV of size 8.
+   * @param key the key to wrap.
+   *            The pseudorandom permutation W is only defined for
+   *            inputs with a size that is a multiple of 8 bytes and
+   *            that is at least 24 bytes long. Hence computeW is undefined
+   *            for keys of size 8 bytes or shorter.
+   */
+  private byte[] computeW(final byte[] iv, final byte[] key)
+      throws GeneralSecurityException {
+    // Checks the parameter sizes for which W is defined.
+    // Note, that the caller ensures stricter limits.
+    if (key.length <= 8 || key.length > Integer.MAX_VALUE - 16 || iv.length != 8) {
+      throw new GeneralSecurityException("computeW called with invalid parameters");
+    }
+    byte[] data = new byte[wrappingSize(key.length)];
+    System.arraycopy(iv, 0, data, 0, iv.length);
+    System.arraycopy(key, 0, data, 8, key.length); 
+    int blocks = data.length / 8 - 1;
+    Cipher aes = EngineFactory.CIPHER.getInstance("AES/ECB/NoPadding");
+    aes.init(Cipher.ENCRYPT_MODE, aesKey);
+    byte[] block = new byte[16];
+    System.arraycopy(data, 0, block, 0, 8);
+    for (int i = 0; i < ROUNDS; i++) {
+      for (int j = 0; j < blocks; j++) {
+        System.arraycopy(data, 8 * (j + 1), block, 8, 8);
+        int length = aes.doFinal(block, 0, 16, block);
+        assert length == 16;
+        // xor the round constant in bigendian order to the left half of block. 
+        int roundConst = i * blocks + j + 1;
+        for (int b = 0; b < 4; b++) {
+          block[7 - b] ^= (byte) (roundConst & 0xff);
+          roundConst >>>= 8;
+        }
+        System.arraycopy(block, 8, data, 8 * (j + 1), 8);
+      }
+    }
+    System.arraycopy(block, 0, data, 0, 8);
+    return data;
+  }
+
+  /**
+   * Compute the inverse of the pseudorandom permutation W.
+   * @param wrapped the input data to invert. This is the wrapped key.
+   * @return the concatenation of the IV followed by a potentially
+   *         zero padded key.
+   *         invertW does not perform an integrity check.
+   */
+  private byte[] invertW(final byte[] wrapped) throws GeneralSecurityException {
+    // Checks the input size for which invertW is defined.
+    // The caller ensures stricter limits
+    if (wrapped.length < 24 || wrapped.length % 8 != 0) {
+      throw new GeneralSecurityException("Incorrect data size");
+    }
+    byte[] data = Arrays.copyOf(wrapped, wrapped.length);
+    int blocks = data.length / 8 - 1;
+    Cipher aes = EngineFactory.CIPHER.getInstance("AES/ECB/NoPadding");
+    aes.init(Cipher.DECRYPT_MODE, aesKey);
+    byte[] block = new byte[16];
+    System.arraycopy(data, 0, block, 0, 8);
+    for (int i = ROUNDS - 1; i >= 0; i--) {
+      for (int j = blocks - 1; j >= 0; j--) {
+        System.arraycopy(data, 8 * (j + 1), block, 8, 8);
+        // xor the round constant in bigendian order to the left half of block. 
+        int roundConst = i * blocks + j + 1;
+        for (int b = 0; b < 4; b++) {
+          block[7 - b] ^= (byte) (roundConst & 0xff);
+          roundConst >>>= 8;
+        }
+
+        int length = aes.doFinal(block, 0, 16, block);
+        assert length == 16;
+        System.arraycopy(block, 8, data, 8 * (j + 1), 8);
+      }
+    }
+    System.arraycopy(block, 0, data, 0, 8);
+    return data;
+  }
+
+  /**
+   * Wraps some key material {@code data}.
+   *
+   * @param data the key to wrap. 
+   * @return the wrapped key
+   */
+  @Override
+  public byte[] wrap(final byte[] data) throws GeneralSecurityException {
+    if (data.length < MIN_WRAP_KEY_SIZE) {
+      throw new GeneralSecurityException("Key size of key to wrap too small");
+    }
+    if (data.length > MAX_WRAP_KEY_SIZE) {
+      throw new GeneralSecurityException("Key size of key to wrap too large");
+    }
+    byte[] iv = new byte[8];
+    System.arraycopy(PREFIX, 0, iv, 0, PREFIX.length);
+    for (int i = 0; i < 4; i++) {
+      iv[4 + i] = (byte) ((data.length >> (8 * (3 - i))) & 0xff);
+    }
+    return computeW(iv, data);
+  }
+
+  /**
+   * Unwraps a wrapped key.
+   *
+   * @throws GeneralSecurityException if {@code data} fails the integrity check.
+   */
+  @Override
+  public byte[] unwrap(final byte[] data) throws GeneralSecurityException {
+    if (data.length < wrappingSize(MIN_WRAP_KEY_SIZE)) {
+      throw new GeneralSecurityException("Wrapped key size is too small");
+    }
+    if (data.length > wrappingSize(MAX_WRAP_KEY_SIZE)) {
+      throw new GeneralSecurityException("Wrapped key size is too large");
+    }
+    if (data.length % 8 != 0) {
+      throw new GeneralSecurityException(
+          "Wrapped key size must be a multiple of 8 bytes");
+    }
+    byte[] unwrapped = invertW(data);
+    // Check the padding.
+    // W has been designed to be a strong pseudorandom permutation.
+    // Hence leaking any amount of information about improperly padded keys
+    // would not be a vulnerability. This means that here we don't have to go to
+    // some extra length to assure that the code is constant time. 
+    boolean ok = true;
+    for (int i = 0; i < 4; i++) {
+      if (PREFIX[i] != unwrapped[i]) {
+        ok = false;
+      }
+    }
+    int encodedSize = 0;
+    for (int i = 4; i < 8; i++) {
+      encodedSize = (encodedSize << 8) + (unwrapped[i] & 0xff);
+    }
+    if (wrappingSize(encodedSize) != unwrapped.length) {
+      ok = false;
+    } else {
+      for (int j = 8 + encodedSize; j < unwrapped.length; j++) {
+        if (unwrapped[j] != 0) {
+          ok = false;
+        }
+      }
+    }
+    if (ok) {
+      return Arrays.copyOfRange(unwrapped, 8, 8 + encodedSize);
+    } else {
+      throw new BadPaddingException("Invalid padding");
+    }
+  }
+}
diff --git a/java/src/main/java/com/google/crypto/tink/subtle/RsaSsaPkcs1SignJce.java b/java/src/main/java/com/google/crypto/tink/subtle/RsaSsaPkcs1SignJce.java
index be743e9..99f1fee 100644
--- a/java/src/main/java/com/google/crypto/tink/subtle/RsaSsaPkcs1SignJce.java
+++ b/java/src/main/java/com/google/crypto/tink/subtle/RsaSsaPkcs1SignJce.java
@@ -36,6 +36,8 @@
 
   public RsaSsaPkcs1SignJce(final RSAPrivateCrtKey priv, HashType hash)
       throws GeneralSecurityException {
+    Validators.validateSignatureHash(hash);
+    Validators.validateRsaModulusSize(priv.getModulus().bitLength());
     this.privateKey = priv;
     this.signatureAlgorithm = SubtleUtil.toRsaSsaPkcs1Algo(hash);
     KeyFactory kf = EngineFactory.KEY_FACTORY.getInstance("RSA");
diff --git a/java/src/main/java/com/google/crypto/tink/subtle/RsaSsaPkcs1VerifyJce.java b/java/src/main/java/com/google/crypto/tink/subtle/RsaSsaPkcs1VerifyJce.java
index 18ac34d..cc51fa5 100644
--- a/java/src/main/java/com/google/crypto/tink/subtle/RsaSsaPkcs1VerifyJce.java
+++ b/java/src/main/java/com/google/crypto/tink/subtle/RsaSsaPkcs1VerifyJce.java
@@ -18,8 +18,9 @@
 
 import com.google.crypto.tink.PublicKeyVerify;
 import com.google.crypto.tink.subtle.Enums.HashType;
+import java.math.BigInteger;
 import java.security.GeneralSecurityException;
-import java.security.Signature;
+import java.security.MessageDigest;
 import java.security.interfaces.RSAPublicKey;
 
 /**
@@ -27,28 +28,84 @@
  * with JCE.
  */
 public final class RsaSsaPkcs1VerifyJce implements PublicKeyVerify {
+  private static final String ASN_PREFIX_SHA256 = "3031300d060960864801650304020105000420";
+  private static final String ASN_PREFIX_SHA512 = "3051300d060960864801650304020305000440";
+
   private final RSAPublicKey publicKey;
-  private final String signatureAlgorithm;
+  private final HashType hash;
 
   public RsaSsaPkcs1VerifyJce(final RSAPublicKey pubKey, HashType hash)
       throws GeneralSecurityException {
+    Validators.validateSignatureHash(hash);
+    Validators.validateRsaModulusSize(pubKey.getModulus().bitLength());
     this.publicKey = pubKey;
-    this.signatureAlgorithm = SubtleUtil.toRsaSsaPkcs1Algo(hash);
+    this.hash = hash;
   }
 
   @Override
   public void verify(final byte[] signature, final byte[] data) throws GeneralSecurityException {
-    Signature verifier = EngineFactory.SIGNATURE.getInstance(signatureAlgorithm);
-    verifier.initVerify(publicKey);
-    verifier.update(data);
-    boolean verified = false;
-    try {
-      verified = verifier.verify(signature);
-    } catch (java.lang.RuntimeException ex) {
-      verified = false;
+    // The algorithm is described at (https://tools.ietf.org/html/rfc8017#section-8.2). As signature
+    // verification is a public operation,  throwing different exception messages doesn't give
+    // attacker any useful information.
+    BigInteger e = publicKey.getPublicExponent();
+    BigInteger n = publicKey.getModulus();
+    int nLengthInBytes = (n.bitLength() + 7) / 8;
+
+    // Step 1. Length checking.
+    if (nLengthInBytes != signature.length) {
+      throw new GeneralSecurityException("invalid signature's length");
     }
-    if (!verified) {
-      throw new GeneralSecurityException("Invalid signature");
+
+    // Step 2. RSA verification.
+    BigInteger s = SubtleUtil.bytes2Integer(signature);
+    if (s.compareTo(n) >= 0) {
+      throw new GeneralSecurityException("signature out of range");
+    }
+    BigInteger m = s.modPow(e, n);
+    byte[] em = SubtleUtil.integer2Bytes(m, nLengthInBytes);
+
+    // Step 3. PKCS1 encoding.
+    byte[] expectedEm = emsaPkcs1(data, nLengthInBytes, hash);
+
+    // Step 4. Compare the results.
+    if (!Bytes.equal(em, expectedEm)) {
+      throw new GeneralSecurityException("invalid signature");
+    }
+  }
+
+  // https://tools.ietf.org/html/rfc8017#section-9.2.
+  private byte[] emsaPkcs1(byte[] m, int emLen, HashType hash) throws GeneralSecurityException {
+    Validators.validateSignatureHash(hash);
+    MessageDigest digest =
+        EngineFactory.MESSAGE_DIGEST.getInstance(SubtleUtil.toDigestAlgo(this.hash));
+    digest.update(m);
+    byte[] h = digest.digest();
+    byte[] asnPrefix = toAsnPrefix(hash);
+    int tLen = asnPrefix.length + h.length;
+    if (emLen < tLen + 11) {
+      throw new GeneralSecurityException("intended encoded message length too short");
+    }
+    byte[] em = new byte[emLen];
+    int offset = 0;
+    em[offset++] = 0x00;
+    em[offset++] = 0x01;
+    for (int i = 0; i < emLen - tLen - 3; i++) {
+      em[offset++] = (byte) 0xff;
+    }
+    em[offset++] = 0x00;
+    System.arraycopy(asnPrefix, 0, em, offset, asnPrefix.length);
+    System.arraycopy(h, 0, em, offset + asnPrefix.length, h.length);
+    return em;
+  }
+
+  private byte[] toAsnPrefix(HashType hash) throws GeneralSecurityException {
+    switch (hash) {
+      case SHA256:
+        return Hex.decode(ASN_PREFIX_SHA256);
+      case SHA512:
+        return Hex.decode(ASN_PREFIX_SHA512);
+      default:
+        throw new GeneralSecurityException("Unsupported hash " + hash);
     }
   }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/subtle/RsaSsaPssSignJce.java b/java/src/main/java/com/google/crypto/tink/subtle/RsaSsaPssSignJce.java
new file mode 100644
index 0000000..773e49c
--- /dev/null
+++ b/java/src/main/java/com/google/crypto/tink/subtle/RsaSsaPssSignJce.java
@@ -0,0 +1,138 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.subtle;
+
+import com.google.crypto.tink.PublicKeySign;
+import com.google.crypto.tink.subtle.Enums.HashType;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.RSAPublicKeySpec;
+import javax.crypto.Cipher;
+
+/**
+ * RsaSsaPss (i.e. RSA Signature Schemes with Appendix (SSA) with PSS encoding) signing with JCE.
+ */
+public final class RsaSsaPssSignJce implements PublicKeySign {
+  private final RSAPrivateCrtKey privateKey;
+  private final RSAPublicKey publicKey;
+  private final HashType sigHash;
+  private final HashType mgf1Hash;
+  private final int saltLength;
+  private static final String RAW_RSA_ALGORITHM = "RSA/ECB/NOPADDING";
+
+  public RsaSsaPssSignJce(
+      final RSAPrivateCrtKey priv, HashType sigHash, HashType mgf1Hash, int saltLength)
+      throws GeneralSecurityException {
+    Validators.validateSignatureHash(sigHash);
+    Validators.validateRsaModulusSize(priv.getModulus().bitLength());
+    this.privateKey = priv;
+    KeyFactory kf = EngineFactory.KEY_FACTORY.getInstance("RSA");
+    this.publicKey =
+        (RSAPublicKey)
+            kf.generatePublic(new RSAPublicKeySpec(priv.getModulus(), priv.getPublicExponent()));
+    this.sigHash = sigHash;
+    this.mgf1Hash = mgf1Hash;
+    this.saltLength = saltLength;
+  }
+
+  @Override
+  public byte[] sign(final byte[] data) throws GeneralSecurityException {
+    // https://tools.ietf.org/html/rfc8017#section-8.1.1.
+    int modBits = publicKey.getModulus().bitLength();
+
+    byte[] em = emsaPssEncode(data, modBits - 1);
+    return rsasp1(em);
+  }
+
+  private byte[] rsasp1(byte[] m) throws GeneralSecurityException {
+    Cipher decryptCipher = EngineFactory.CIPHER.getInstance(RAW_RSA_ALGORITHM);
+    decryptCipher.init(Cipher.DECRYPT_MODE, this.privateKey);
+    byte[] c = decryptCipher.doFinal(m);
+    // To make sure the private key operation is correct, we check the result with public key
+    // operation.
+    Cipher encryptCipher = EngineFactory.CIPHER.getInstance(RAW_RSA_ALGORITHM);
+    encryptCipher.init(Cipher.ENCRYPT_MODE, this.publicKey);
+    byte[] m0 = encryptCipher.doFinal(c);
+    if (!new BigInteger(1, m).equals(new BigInteger(1, m0))) {
+      throw new java.lang.RuntimeException("Security bug: RSA signature computation error");
+    }
+    return c;
+  }
+
+  // https://tools.ietf.org/html/rfc8017#section-9.1.1.
+  private byte[] emsaPssEncode(byte[] m, int emBits) throws GeneralSecurityException {
+    // Step 1. Length checking.
+    // This step is unnecessary because Java's byte[] only supports up to 2^31 -1 bytes while the
+    // input limitation for the hash function is far larger (2^61 - 1 for SHA-1).
+
+    // Step 2. Compute hash.
+    Validators.validateSignatureHash(sigHash);
+    MessageDigest digest =
+        EngineFactory.MESSAGE_DIGEST.getInstance(SubtleUtil.toDigestAlgo(this.sigHash));
+    byte[] mHash = digest.digest(m);
+
+    // Step 3. Check emLen.
+    int hLen = digest.getDigestLength();
+    int emLen = (emBits - 1) / 8 + 1;
+    if (emLen < hLen + this.saltLength + 2) {
+      throw new GeneralSecurityException("encoding error");
+    }
+
+    // Step 4. Generate random salt.
+    byte[] salt = Random.randBytes(this.saltLength);
+
+    // Step 5. Compute M'.
+    byte[] mPrime = new byte[8 + hLen + this.saltLength];
+    System.arraycopy(mHash, 0, mPrime, 8, hLen);
+    System.arraycopy(salt, 0, mPrime, 8 + hLen, salt.length);
+
+    // Step 6. Compute H.
+    byte[] h = digest.digest(mPrime);
+
+    // Step 7, 8. Generate DB.
+    byte[] db = new byte[emLen - hLen - 1];
+    db[emLen - this.saltLength - hLen - 2] = (byte) 0x01;
+    System.arraycopy(salt, 0, db, emLen - this.saltLength - hLen - 1, salt.length);
+
+    // Step 9. Compute dbMask.
+    byte[] dbMask = SubtleUtil.mgf1(h, emLen - hLen - 1, this.mgf1Hash);
+
+    // Step 10. Compute maskedDb.
+    byte[] maskedDb = new byte[emLen - hLen - 1];
+    for (int i = 0; i < maskedDb.length; i++) {
+      maskedDb[i] = (byte) (db[i] ^ dbMask[i]);
+    }
+
+    // Step 11. Set the leftmost 8 * emLen - emBits bits of the leftmost octet in maskedDB to zero.
+    for (int i = 0; i < (long) emLen * 8 - emBits; i++) {
+      int bytePos = i / 8;
+      int bitPos = 7 - i % 8;
+      maskedDb[bytePos] = (byte) (maskedDb[bytePos] & ~(1 << bitPos));
+    }
+
+    // Step 12. Generate EM.
+    byte[] em = new byte[maskedDb.length + hLen + 1];
+    System.arraycopy(maskedDb, 0, em, 0, maskedDb.length);
+    System.arraycopy(h, 0, em, maskedDb.length, h.length);
+    em[maskedDb.length + hLen] = (byte) 0xbc;
+    return em;
+  }
+}
diff --git a/java/src/main/java/com/google/crypto/tink/subtle/RsaSsaPssVerifyJce.java b/java/src/main/java/com/google/crypto/tink/subtle/RsaSsaPssVerifyJce.java
new file mode 100644
index 0000000..cc034ac
--- /dev/null
+++ b/java/src/main/java/com/google/crypto/tink/subtle/RsaSsaPssVerifyJce.java
@@ -0,0 +1,153 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.subtle;
+
+import com.google.crypto.tink.PublicKeyVerify;
+import com.google.crypto.tink.subtle.Enums.HashType;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.interfaces.RSAPublicKey;
+import java.util.Arrays;
+
+/**
+ * RsaSsaPss (i.e. RSA Signature Schemes with Appendix (SSA) using PSS encoding) verifying with JCE.
+ */
+public final class RsaSsaPssVerifyJce implements PublicKeyVerify {
+  private final RSAPublicKey publicKey;
+  private final HashType sigHash;
+  private final HashType mgf1Hash;
+  private final int saltLength;
+
+  public RsaSsaPssVerifyJce(
+      final RSAPublicKey pubKey, HashType sigHash, HashType mgf1Hash, int saltLength)
+      throws GeneralSecurityException {
+    Validators.validateSignatureHash(sigHash);
+    Validators.validateRsaModulusSize(pubKey.getModulus().bitLength());
+    this.publicKey = pubKey;
+    this.sigHash = sigHash;
+    this.mgf1Hash = mgf1Hash;
+    this.saltLength = saltLength;
+  }
+
+  @Override
+  public void verify(final byte[] signature, final byte[] data) throws GeneralSecurityException {
+    // The algorithm is described at (https://tools.ietf.org/html/rfc8017#section-8.1.2). As
+    // signature verification is a public operation,  throwing different exception messages doesn't
+    // give attacker any useful information.
+    BigInteger e = publicKey.getPublicExponent();
+    BigInteger n = publicKey.getModulus();
+    int nLengthInBytes = (n.bitLength() + 7) / 8;
+    int mLen = (n.bitLength() - 1 + 7) / 8;
+
+    // Step 1. Length checking.
+    if (nLengthInBytes != signature.length) {
+      throw new GeneralSecurityException("invalid signature's length");
+    }
+
+    // Step 2. RSA verification.
+    BigInteger s = SubtleUtil.bytes2Integer(signature);
+    if (s.compareTo(n) >= 0) {
+      throw new GeneralSecurityException("signature out of range");
+    }
+    BigInteger m = s.modPow(e, n);
+    byte[] em = SubtleUtil.integer2Bytes(m, mLen);
+
+    // Step 3. PSS encoding verification.
+    emsaPssVerify(data, em, n.bitLength() - 1);
+  }
+
+  // https://tools.ietf.org/html/rfc8017#section-9.1.2.
+  private void emsaPssVerify(byte[] m, byte[] em, int emBits) throws GeneralSecurityException {
+    // Step 1. Length checking.
+    // This step is unnecessary because Java's byte[] only supports up to 2^31 -1 bytes while the
+    // input limitation for the hash function is far larger (2^61 - 1 for SHA-1).
+
+    // Step 2. Compute hash.
+    Validators.validateSignatureHash(sigHash);
+    MessageDigest digest =
+        EngineFactory.MESSAGE_DIGEST.getInstance(SubtleUtil.toDigestAlgo(this.sigHash));
+    byte[] mHash = digest.digest(m);
+    int hLen = digest.getDigestLength();
+
+    int emLen = em.length;
+
+    // Step 3. Check emLen.
+    if (emLen < hLen + this.saltLength + 2) {
+      throw new GeneralSecurityException("inconsistent");
+    }
+
+    // Step 4. Check right most byte of EM.
+    if (em[em.length - 1] != (byte) 0xbc) {
+      throw new GeneralSecurityException("inconsistent");
+    }
+
+    // Step 5. Extract maskedDb and H from EM.
+    byte[] maskedDb = Arrays.copyOf(em, emLen - hLen - 1);
+    byte[] h = Arrays.copyOfRange(em, maskedDb.length, maskedDb.length + hLen);
+
+    // Step 6. Check whether the leftmost 8 * emLen - emBits bits of the leftmost octet in maskedDB
+    // are all zeros.
+    for (int i = 0; i < (long) emLen * 8 - emBits; i++) {
+      int bytePos = i / 8;
+      int bitPos = 7 - i % 8;
+      if (((maskedDb[bytePos] >> bitPos) & 1) != 0) {
+        throw new GeneralSecurityException("inconsistent");
+      }
+    }
+
+    // Step 7. Compute dbMask.
+    byte[] dbMask = SubtleUtil.mgf1(h, emLen - hLen - 1, mgf1Hash);
+
+    // Step 8. Compute db.
+    byte[] db = new byte[dbMask.length];
+    for (int i = 0; i < db.length; i++) {
+      db[i] = (byte) (dbMask[i] ^ maskedDb[i]);
+    }
+
+    // Step 9. Set the leftmost 8*emLen - emBits bits of the leftmost octet in DB to zero.
+    for (int i = 0; i <= (long) emLen * 8 - emBits; i++) {
+      int bytePos = i / 8;
+      int bitPos = 7 - i % 8;
+      db[bytePos] = (byte) (db[bytePos] & ~(1 << bitPos));
+    }
+
+    // Step 10. Check db.
+    for (int i = 0; i < emLen - hLen - this.saltLength - 2; i++) {
+      if (db[i] != 0) {
+        throw new GeneralSecurityException("inconsistent");
+      }
+    }
+    if (db[emLen - hLen - this.saltLength - 2] != (byte) 0x01) {
+      throw new GeneralSecurityException("inconsistent");
+    }
+
+    // Step 11. Extract salt from db.
+    byte[] salt = Arrays.copyOfRange(db, db.length - this.saltLength, db.length);
+
+    // Step 12. Generate M'.
+    byte[] mPrime = new byte[8 + hLen + this.saltLength];
+    System.arraycopy(mHash, 0, mPrime, 8, mHash.length);
+    System.arraycopy(salt, 0, mPrime, 8 + hLen, salt.length);
+
+    // Step 13. Compute H'
+    byte[] hPrime = digest.digest(mPrime);
+    if (!Bytes.equal(hPrime, h)) {
+      throw new GeneralSecurityException("inconsistent");
+    }
+  }
+}
diff --git a/java/src/main/java/com/google/crypto/tink/subtle/Snuffle.java b/java/src/main/java/com/google/crypto/tink/subtle/Snuffle.java
deleted file mode 100644
index ad48673..0000000
--- a/java/src/main/java/com/google/crypto/tink/subtle/Snuffle.java
+++ /dev/null
@@ -1,150 +0,0 @@
-// Copyright 2017 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-package com.google.crypto.tink.subtle;
-
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.IntBuffer;
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-
-/**
- * Abstract base class for class of ChaCha20 and its variants.
- *
- * <p>Variants of Snuffle have two differences: the size of the nonce and the block function that
- * produces a key stream block from a key, a nonce, and a counter. Subclasses of this class
- * specifying these two information by overriding {@link #nonceSizeInBytes} and {@link
- * #getKeyStreamBlock}.
- *
- * <p>Concrete implementations of this class are meant to be used to construct an {@link
- * com.google.crypto.tink.Aead} with {@link com.google.crypto.tink.subtle.Poly1305}. The base class
- * of these Aead constructions is {@link com.google.crypto.tink.subtle.SnufflePoly1305}. For
- * example, {@link com.google.crypto.tink.subtle.ChaCha20} is a subclass of this class and a
- * concrete Snuffle implementation, and {@link com.google.crypto.tink.subtle.ChaCha20Poly1305} is a
- * subclass of {@link com.google.crypto.tink.subtle.SnufflePoly1305} and a concrete Aead
- * construction.
- */
-abstract class Snuffle implements IndCpaCipher {
-  public static final int BLOCK_SIZE_IN_INTS = 16;
-  public static final int BLOCK_SIZE_IN_BYTES = BLOCK_SIZE_IN_INTS * 4;
-  public static final int KEY_SIZE_IN_INTS = 8;
-  public static final int KEY_SIZE_IN_BYTES = KEY_SIZE_IN_INTS * 4;
-  static final int[] SIGMA =
-      toIntArray(
-          ByteBuffer.wrap(
-              new byte[] {
-                'e', 'x', 'p', 'a', 'n', 'd', ' ', '3', '2', '-', 'b', 'y', 't', 'e', ' ', 'k'
-              }));
-
-  final ImmutableByteArray key;
-  private final int initialCounter;
-
-  /**
-   * Returns a key stream block from {@code nonce} and {@code counter}.
-   *
-   * <p>From this function, the Snuffle encryption function can be constructed using the counter
-   * mode of operation. For example, the ChaCha20 block function and how it can be used to
-   * construct the ChaCha20 encryption function are described in section 2.3 and 2.4 of RFC 7539.
-   */
-  abstract ByteBuffer getKeyStreamBlock(final byte[] nonce, int counter);
-
-  /**
-   * The size of the randomly generated nonces.
-   *
-   * <p>ChaCha20 uses 12-byte nonces, but XSalsa20 and XChaCha20 use 24-byte nonces.
-   */
-  abstract int nonceSizeInBytes();
-
-  Snuffle(final byte[] key, int initialCounter) throws InvalidKeyException {
-    if (key.length != KEY_SIZE_IN_BYTES) {
-      throw new InvalidKeyException("The key length in bytes must be 32.");
-    }
-    this.key = ImmutableByteArray.of(key);
-    this.initialCounter = initialCounter;
-  }
-
-  @Override
-  public byte[] encrypt(final byte[] plaintext) throws GeneralSecurityException {
-    if (plaintext.length > Integer.MAX_VALUE - nonceSizeInBytes()) {
-      throw new GeneralSecurityException("plaintext too long");
-    }
-    ByteBuffer ciphertext = ByteBuffer.allocate(
-        nonceSizeInBytes() + plaintext.length);
-    encrypt(ciphertext, plaintext);
-    return ciphertext.array();
-  }
-
-  void encrypt(ByteBuffer output, final byte[] plaintext) throws GeneralSecurityException {
-    if (output.remaining() - nonceSizeInBytes() < plaintext.length) {
-      throw new IllegalArgumentException("Given ByteBuffer output is too small");
-    }
-
-    byte[] nonce = Random.randBytes(nonceSizeInBytes());
-    output.put(nonce);
-    process(nonce, output, ByteBuffer.wrap(plaintext));
-  }
-
-  @Override
-  public byte[] decrypt(final byte[] ciphertext) throws GeneralSecurityException {
-    return decrypt(ByteBuffer.wrap(ciphertext));
-  }
-
-  byte[] decrypt(ByteBuffer ciphertext) throws GeneralSecurityException {
-    if (ciphertext.remaining() < nonceSizeInBytes()) {
-      throw new GeneralSecurityException("ciphertext too short");
-    }
-    byte[] nonce = new byte[nonceSizeInBytes()];
-    ciphertext.get(nonce);
-    ByteBuffer plaintext = ByteBuffer.allocate(ciphertext.remaining());
-    process(nonce, plaintext, ciphertext);
-    return plaintext.array();
-  }
-
-  private void process(final byte[] nonce, ByteBuffer output, ByteBuffer input)
-      throws GeneralSecurityException {
-    int length = input.remaining();
-    int numBlocks = (length / BLOCK_SIZE_IN_BYTES) + 1;
-    for (int i = 0; i < numBlocks; i++) {
-      ByteBuffer keyStreamBlock = getKeyStreamBlock(nonce, i + initialCounter);
-      if (i == numBlocks - 1) {
-        // last block
-        Bytes.xor(
-            output,
-            input,
-            keyStreamBlock,
-            length % BLOCK_SIZE_IN_BYTES);
-      } else {
-        Bytes.xor(
-            output,
-            input,
-            keyStreamBlock,
-            BLOCK_SIZE_IN_BYTES);
-      }
-    }
-  }
-
-  static int rotateLeft(int x, int y) {
-    return (x << y) | (x >>> -y);
-  }
-
-  static int[] toIntArray(ByteBuffer in) {
-    IntBuffer intBuffer = in.order(ByteOrder.LITTLE_ENDIAN).asIntBuffer();
-    int[] ret = new int[intBuffer.remaining()];
-    intBuffer.get(ret);
-    return ret;
-  }
-}
diff --git a/java/src/main/java/com/google/crypto/tink/subtle/StreamingAeadDecryptingStream.java b/java/src/main/java/com/google/crypto/tink/subtle/StreamingAeadDecryptingStream.java
index 40ab8a8..b306c3b 100644
--- a/java/src/main/java/com/google/crypto/tink/subtle/StreamingAeadDecryptingStream.java
+++ b/java/src/main/java/com/google/crypto/tink/subtle/StreamingAeadDecryptingStream.java
@@ -255,6 +255,39 @@
     return false;
   }
 
+  /**
+   * Skips over and discards <code>n</code> bytes of plaintext from the input stream. The
+   * implementation reads and decrypts the plaintext that is skipped. Hence skipping a large number
+   * of bytes is slow.
+   *
+   * <p>Returns the number of bytes skipped. This number can be smaller than the number of bytes
+   * requested. This can happend for a number of reasons: e.g., this happens when the underlying
+   * stream is non-blocking and not enough bytes are available or when the stream reaches the end of
+   * the stream.
+   *
+   * @throws IOException when an exception occurs while reading from @code{in} or when the
+   *     ciphertext is corrupt. Currently all corrupt ciphertext will be detected. However this
+   *     behaviour may change.
+   */
+  @Override
+  public long skip(long n) throws IOException {
+    long maxSkipBufferSize = ciphertextSegmentSize;
+    long remaining = n;
+    if (n <= 0) {
+      return 0;
+    }
+    int size = (int) Math.min(maxSkipBufferSize, remaining);
+    byte[] skipBuffer = new byte[size];
+    while (remaining > 0) {
+      int bytesRead = read(skipBuffer, 0, (int) Math.min(size, remaining));
+      if (bytesRead <= 0) {
+        break;
+      }
+      remaining -= bytesRead;
+    }
+    return n - remaining;
+  }
+
   /* Returns the state of the channel. */
   @Override
   public synchronized String toString() {
diff --git a/java/src/main/java/com/google/crypto/tink/subtle/SubtleUtil.java b/java/src/main/java/com/google/crypto/tink/subtle/SubtleUtil.java
index ec9dfae..a56b6ac 100644
--- a/java/src/main/java/com/google/crypto/tink/subtle/SubtleUtil.java
+++ b/java/src/main/java/com/google/crypto/tink/subtle/SubtleUtil.java
@@ -17,7 +17,10 @@
 package com.google.crypto.tink.subtle;
 
 import com.google.crypto.tink.subtle.Enums.HashType;
+import java.math.BigInteger;
 import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.util.Arrays;
 
 /** Helper methods. */
 public class SubtleUtil {
@@ -39,7 +42,7 @@
    * Returns the RSA SSA (Signature with Appendix) PKCS1 algorithm name corresponding to a hash
    * type.
    *
-   * @param hash the hash type
+   * @param hash the hash type.
    * @return the JCE's RSA SSA PKCS1 algorithm name for the hash.
    * @throw GeneralSecurityException if {@code hash} is not supported or is not safe for digital
    *     signature.
@@ -50,6 +53,25 @@
   }
 
   /**
+   * Returns the digest algorithm name corresponding to a hash type.
+   *
+   * @param hash the hash type.
+   * @return theh JCE's hash algorithm name.
+   * @throw GeneralSecurityException if {@code hash} is not supported.
+   */
+  public static String toDigestAlgo(HashType hash) throws GeneralSecurityException {
+    switch (hash) {
+      case SHA1:
+        return "SHA-1";
+      case SHA256:
+        return "SHA-256";
+      case SHA512:
+        return "SHA-512";
+    }
+    throw new GeneralSecurityException("Unsupported hash " + hash);
+  }
+
+  /**
    * Best-effort checks that this is Android.
    *
    * @return true if running on Android.
@@ -63,4 +85,68 @@
       return false;
     }
   }
+
+  /**
+   * Converts an byte array to a nonnegative integer
+   * (https://tools.ietf.org/html/rfc8017#section-4.1).
+   *
+   * @param bs the byte array to be converted to integer.
+   * @return the corresponding integer.
+   */
+  public static BigInteger bytes2Integer(byte[] bs) {
+    return new BigInteger(1, bs);
+  }
+
+  /**
+   * Converts a nonnegative integer to a byte array of a specified length
+   * (https://tools.ietf.org/html/rfc8017#section-4.2).
+   *
+   * @param num nonnegative integer to be converted.
+   * @param intendedLength intended length of the resulting integer.
+   * @return the corresponding byte array of length {@code intendedLength}.
+   */
+  public static byte[] integer2Bytes(BigInteger num, int intendedLength)
+      throws GeneralSecurityException {
+    byte[] b = num.toByteArray();
+    if (b.length == intendedLength) {
+      return b;
+    }
+    if (b.length > intendedLength + 1 /* potential leading zero */) {
+      throw new GeneralSecurityException("integer too large");
+    }
+    if (b.length == intendedLength + 1) {
+      if (b[0] == 0 /* leading zero */) {
+        return Arrays.copyOfRange(b, 1, b.length);
+      } else {
+        throw new GeneralSecurityException("integer too large");
+      }
+    }
+    // Left zero pad b.
+    byte[] res = new byte[intendedLength];
+    System.arraycopy(b, 0, res, intendedLength - b.length, b.length);
+    return res;
+  }
+
+  /** Computes MGF1 as defined at https://tools.ietf.org/html/rfc8017#appendix-B.2.1. */
+  public static byte[] mgf1(byte[] mgfSeed, int maskLen, HashType mgfHash)
+      throws GeneralSecurityException {
+    MessageDigest digest =
+        EngineFactory.MESSAGE_DIGEST.getInstance(SubtleUtil.toDigestAlgo(mgfHash));
+    int hLen = digest.getDigestLength();
+    // Step 1. Check maskLen.
+    // As max integer is only 2^31 - 1 which is smaller than the limit 2^32, this step is skipped.
+
+    // Step 2, 3. Compute t.
+    byte[] t = new byte[maskLen];
+    int tPos = 0;
+    for (int counter = 0; counter <= (maskLen - 1) / hLen; counter++) {
+      digest.reset();
+      digest.update(mgfSeed);
+      digest.update(SubtleUtil.integer2Bytes(BigInteger.valueOf(counter), 4));
+      byte[] c = digest.digest();
+      System.arraycopy(c, 0, t, tPos, Math.min(c.length, t.length - tPos));
+      tPos += c.length;
+    }
+    return t;
+  }
 }
diff --git a/java/src/main/java/com/google/crypto/tink/subtle/Validators.java b/java/src/main/java/com/google/crypto/tink/subtle/Validators.java
index 42280c5..b5bb2b8 100644
--- a/java/src/main/java/com/google/crypto/tink/subtle/Validators.java
+++ b/java/src/main/java/com/google/crypto/tink/subtle/Validators.java
@@ -30,6 +30,12 @@
  */
 public final class Validators {
   private static final String TYPE_URL_PREFIX = "type.googleapis.com/";
+  /**
+   * To reach 128-bit security strength, RSA's modulus must be at least 3072-bit while 2048-bit RSA
+   * key only has 112-bit security. Nevertheless, a 2048-bit RSA key is considered safe by NIST
+   * until 2030 (see https://www.keylength.com/en/4/).
+   */
+  private static final int MIN_RSA_MODULUS_SIZE = 2048;
   /** @throws GeneralSecurityException if {@code typeUrl} is in invalid format. */
   public static void validateTypeUrl(String typeUrl) throws GeneralSecurityException {
     if (!typeUrl.startsWith(TYPE_URL_PREFIX)) {
@@ -78,8 +84,24 @@
         return;
       case SHA1:
         throw new GeneralSecurityException("SHA1 is not safe for signature");
-      default:
-        throw new GeneralSecurityException("Unsupported hash " + hash);
+    }
+    throw new GeneralSecurityException("Unsupported hash " + hash);
+  }
+
+  /**
+   * Validates whether {@code modulusSize} is at least 2048-bit.
+   *
+   * <p>To reach 128-bit security strength, RSA's modulus must be at least 3072-bit while 2048-bit
+   * RSA key only has 112-bit security. Nevertheless, a 2048-bit RSA key is considered safe by NIST
+   * until 2030 (see https://www.keylength.com/en/4/).
+   *
+   * @throws GeneralSecurityException if {@code modulusSize} is less than 2048-bit.
+   */
+  public static void validateRsaModulusSize(int modulusSize) throws GeneralSecurityException {
+    if (modulusSize < MIN_RSA_MODULUS_SIZE) {
+      throw new GeneralSecurityException(
+          String.format(
+              "Modulus size is %d; only modulus size >= 2048-bit is supported", modulusSize));
     }
   }
 
diff --git a/java/src/main/java/com/google/crypto/tink/subtle/XChaCha20.java b/java/src/main/java/com/google/crypto/tink/subtle/XChaCha20.java
new file mode 100644
index 0000000..8c9aefe
--- /dev/null
+++ b/java/src/main/java/com/google/crypto/tink/subtle/XChaCha20.java
@@ -0,0 +1,81 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.subtle;
+
+import com.google.crypto.tink.annotations.Alpha;
+import java.security.InvalidKeyException;
+import java.util.Arrays;
+
+/**
+ * {@link XChaCha20} stream cipher based on
+ * https://download.libsodium.org/doc/advanced/xchacha20.html and
+ * https://tools.ietf.org/html/draft-arciszewski-xchacha-01.
+ *
+ * <p>This cipher is meant to be used to construct an AEAD with Poly1305.
+ */
+@Alpha
+class XChaCha20 extends ChaCha20Base {
+  /**
+   * Constructs a new XChaCha20 cipher with the supplied {@code key}.
+   *
+   * @throws IllegalArgumentException when {@code key} length is not {@link
+   *     ChaCha20Base#KEY_SIZE_IN_BYTES}.
+   */
+  XChaCha20(byte[] key, int initialCounter) throws InvalidKeyException {
+    super(key, initialCounter);
+  }
+
+  @Override
+  int[] createInitialState(final int[] nonce, int counter) {
+    if (nonce.length != nonceSizeInBytes() / 4) {
+      throw new IllegalArgumentException(
+          String.format(
+              "XChaCha20 uses 192-bit nonces, but got a %d-bit nonce", nonce.length * 32));
+    }
+    // Set the initial state based on
+    // https://tools.ietf.org/html/draft-arciszewski-xchacha-01#section-2.3.
+    int[] state = new int[ChaCha20Base.BLOCK_SIZE_IN_INTS];
+    ChaCha20Base.setSigmaAndKey(state, hChaCha20(this.key, nonce));
+    state[12] = counter;
+    state[13] = 0;
+    state[14] = nonce[4];
+    state[15] = nonce[5];
+    return state;
+  }
+
+  @Override
+  int nonceSizeInBytes() {
+    return 24;
+  }
+
+  // See https://tools.ietf.org/html/draft-arciszewski-xchacha-01#section-2.2.
+  static int[] hChaCha20(final int[] key, final int[] nonce) {
+    int[] state = new int[ChaCha20Base.BLOCK_SIZE_IN_INTS];
+    ChaCha20Base.setSigmaAndKey(state, key);
+    state[12] = nonce[0];
+    state[13] = nonce[1];
+    state[14] = nonce[2];
+    state[15] = nonce[3];
+    ChaCha20Base.shuffleState(state);
+    // state[0] = state[0], state[1] = state[1], state[2] = state[2], state[3] = state[3]
+    state[4] = state[12];
+    state[5] = state[13];
+    state[6] = state[14];
+    state[7] = state[15];
+    return Arrays.copyOf(state, ChaCha20Base.KEY_SIZE_IN_INTS);
+  }
+}
diff --git a/java/src/main/java/com/google/crypto/tink/subtle/XChaCha20Poly1305.java b/java/src/main/java/com/google/crypto/tink/subtle/XChaCha20Poly1305.java
new file mode 100644
index 0000000..ccfd5ff
--- /dev/null
+++ b/java/src/main/java/com/google/crypto/tink/subtle/XChaCha20Poly1305.java
@@ -0,0 +1,37 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.subtle;
+
+import com.google.crypto.tink.annotations.Alpha;
+import java.security.InvalidKeyException;
+
+/**
+ * XChaCha20Poly1305 AEAD construction, as described in
+ * https://tools.ietf.org/html/draft-arciszewski-xchacha-01.
+ */
+@Alpha
+public final class XChaCha20Poly1305 extends ChaCha20Poly1305Base {
+  public XChaCha20Poly1305(final byte[] key) throws InvalidKeyException {
+    super(key);
+  }
+
+  @Override
+  ChaCha20Base newChaCha20Instance(final byte[] key, int initialCounter)
+      throws InvalidKeyException {
+    return new XChaCha20(key, initialCounter);
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/CleartextKeysetHandleTest.java b/java/src/test/java/com/google/crypto/tink/CleartextKeysetHandleTest.java
index 2634775..1158e6b 100644
--- a/java/src/test/java/com/google/crypto/tink/CleartextKeysetHandleTest.java
+++ b/java/src/test/java/com/google/crypto/tink/CleartextKeysetHandleTest.java
@@ -21,7 +21,6 @@
 import static org.junit.Assert.fail;
 
 import com.google.crypto.tink.config.TinkConfig;
-import com.google.crypto.tink.mac.MacFactory;
 import com.google.crypto.tink.mac.MacKeyTemplates;
 import com.google.crypto.tink.proto.KeyTemplate;
 import com.google.crypto.tink.proto.Keyset;
@@ -47,14 +46,10 @@
     // Create a keyset that contains a single HmacKey.
     KeyTemplate template = MacKeyTemplates.HMAC_SHA256_128BITTAG;
     KeysetHandle handle = KeysetHandle.generateNew(template);
-    Keyset keyset = handle.getKeyset();
+    Keyset keyset = CleartextKeysetHandle.getKeyset(handle);
     handle = CleartextKeysetHandle.parseFrom(keyset.toByteArray());
     assertEquals(keyset, handle.getKeyset());
-    try {
-      Mac unused = MacFactory.getPrimitive(handle);
-    } catch (GeneralSecurityException e) {
-      fail("instantiation should succeed: " + e.toString());
-    }
+    handle.getPrimitive(Mac.class);
   }
 
   @Test
@@ -74,11 +69,7 @@
     Keyset.Key key2 = keyset2.getKey(0);
     assertEquals(keyset2.getPrimaryKeyId(), key2.getKeyId());
     assertEquals(template.getTypeUrl(), key2.getKeyData().getTypeUrl());
-    try {
-      Mac unused = MacFactory.getPrimitive(handle2);
-    } catch (GeneralSecurityException e) {
-      fail("instantiation should succeed: " + e.toString());
-    }
+    Mac unused = handle2.getPrimitive(Mac.class);
   }
 
   @Test
diff --git a/java/src/test/java/com/google/crypto/tink/IntegrationTest.java b/java/src/test/java/com/google/crypto/tink/IntegrationTest.java
index 2453917..3241a18 100644
--- a/java/src/test/java/com/google/crypto/tink/IntegrationTest.java
+++ b/java/src/test/java/com/google/crypto/tink/IntegrationTest.java
@@ -20,8 +20,6 @@
 import static org.junit.Assert.assertEquals;
 
 import com.google.crypto.tink.config.TinkConfig;
-import com.google.crypto.tink.hybrid.HybridDecryptFactory;
-import com.google.crypto.tink.hybrid.HybridEncryptFactory;
 import com.google.crypto.tink.subtle.Random;
 import java.io.File;
 import java.security.GeneralSecurityException;
@@ -49,13 +47,17 @@
       System.out.println("testWithTinkeyEciesAesGcmHkdf doesn't work on Android, skipping");
       return;
     }
-    HybridDecrypt hybridDecrypt = HybridDecryptFactory.getPrimitive(
-        CleartextKeysetHandle.read(BinaryKeysetReader.withFile(
-            new File("testdata/ecies_private_keyset2.bin"))));
+    HybridDecrypt hybridDecrypt =
+        CleartextKeysetHandle.read(
+                BinaryKeysetReader.withFile(
+                    new File("testdata/ecies_private_keyset2.bin")))
+            .getPrimitive(HybridDecrypt.class);
 
-    HybridEncrypt hybridEncrypt = HybridEncryptFactory.getPrimitive(
-        CleartextKeysetHandle.read(BinaryKeysetReader.withFile(
-            new File("testdata/ecies_public_keyset2.bin"))));
+    HybridEncrypt hybridEncrypt =
+        CleartextKeysetHandle.read(
+                BinaryKeysetReader.withFile(
+                    new File("testdata/ecies_public_keyset2.bin")))
+            .getPrimitive(HybridEncrypt.class);
 
     byte[] plaintext = Random.randBytes(20);
     byte[] contextInfo = Random.randBytes(20);
@@ -80,13 +82,17 @@
       return;
     }
 
-    HybridDecrypt hybridDecrypt = HybridDecryptFactory.getPrimitive(
-        CleartextKeysetHandle.read(BinaryKeysetReader.withFile(
-            new File("testdata/ecies_private_keyset.bin"))));
+    HybridDecrypt hybridDecrypt =
+        CleartextKeysetHandle.read(
+                BinaryKeysetReader.withFile(
+                    new File("testdata/ecies_private_keyset.bin")))
+            .getPrimitive(HybridDecrypt.class);
 
-    HybridEncrypt hybridEncrypt = HybridEncryptFactory.getPrimitive(
-        CleartextKeysetHandle.read(BinaryKeysetReader.withFile(
-            new File("testdata/ecies_public_keyset.bin"))));
+    HybridEncrypt hybridEncrypt =
+        CleartextKeysetHandle.read(
+                BinaryKeysetReader.withFile(
+                    new File("testdata/ecies_public_keyset.bin")))
+            .getPrimitive(HybridEncrypt.class);
 
     byte[] plaintext = Random.randBytes(20);
     byte[] contextInfo = Random.randBytes(20);
diff --git a/java/src/test/java/com/google/crypto/tink/JsonKeysetReaderTest.java b/java/src/test/java/com/google/crypto/tink/JsonKeysetReaderTest.java
index a09d39d..2a86e3f 100644
--- a/java/src/test/java/com/google/crypto/tink/JsonKeysetReaderTest.java
+++ b/java/src/test/java/com/google/crypto/tink/JsonKeysetReaderTest.java
@@ -21,7 +21,6 @@
 
 import com.google.crypto.tink.aead.AeadKeyTemplates;
 import com.google.crypto.tink.config.TinkConfig;
-import com.google.crypto.tink.mac.MacFactory;
 import com.google.crypto.tink.mac.MacKeyTemplates;
 import com.google.crypto.tink.proto.KeyTemplate;
 import com.google.crypto.tink.subtle.Random;
@@ -76,8 +75,8 @@
   }
 
   private void assertKeysetHandle(KeysetHandle handle1, KeysetHandle handle2) throws Exception {
-    Mac mac1 = MacFactory.getPrimitive(handle1);
-    Mac mac2 = MacFactory.getPrimitive(handle2);
+    Mac mac1 = handle1.getPrimitive(Mac.class);
+    Mac mac2 = handle2.getPrimitive(Mac.class);
     byte[] message = Random.randBytes(20);
 
     assertThat(handle2.getKeyset()).isEqualTo(handle1.getKeyset());
diff --git a/java/src/test/java/com/google/crypto/tink/KeyManagerBaseTest.java b/java/src/test/java/com/google/crypto/tink/KeyManagerBaseTest.java
new file mode 100644
index 0000000..37e4cab
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/KeyManagerBaseTest.java
@@ -0,0 +1,246 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.crypto.tink.TestUtil.DummyAead;
+import com.google.crypto.tink.proto.AesGcmKey;
+import com.google.crypto.tink.proto.AesGcmKeyFormat;
+import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
+import com.google.crypto.tink.subtle.Random;
+import com.google.crypto.tink.subtle.Validators;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.MessageLite;
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests the methods implemented in KeyManagerBase using the concrete implementation above. */
+@RunWith(JUnit4.class)
+public final class KeyManagerBaseTest {
+  /** Keymanager for testing. Only produces dummy aeads, and wants the key size to be exactly 16. */
+  static class TestKeyManager extends KeyManagerBase<Aead, AesGcmKey, AesGcmKeyFormat> {
+    public TestKeyManager() {
+      super(Aead.class, AesGcmKey.class, AesGcmKeyFormat.class, TYPE_URL);
+    }
+
+    private static final int VERSION = 0;
+
+    public static final String TYPE_URL = "type.googleapis.com/google.crypto.tink.AesGcmKey";
+
+    @Override
+    protected Aead getPrimitiveFromKey(AesGcmKey key) throws GeneralSecurityException {
+      return new DummyAead();
+    }
+
+    @Override
+    protected AesGcmKey newKeyFromFormat(AesGcmKeyFormat format) throws GeneralSecurityException {
+      return AesGcmKey.newBuilder()
+          .setKeyValue(ByteString.copyFrom(Random.randBytes(format.getKeySize())))
+          .setVersion(VERSION)
+          .build();
+    }
+
+    @Override
+    public int getVersion() {
+      return VERSION;
+    }
+
+    @Override
+    protected KeyMaterialType keyMaterialType() {
+      return KeyMaterialType.SYMMETRIC;
+    }
+
+    @Override
+    protected AesGcmKey parseKeyProto(ByteString byteString) throws InvalidProtocolBufferException {
+      return AesGcmKey.parseFrom(byteString);
+    }
+
+    @Override
+    protected AesGcmKeyFormat parseKeyFormatProto(ByteString byteString)
+        throws InvalidProtocolBufferException {
+      return AesGcmKeyFormat.parseFrom(byteString);
+    }
+
+    private void throwIfNot16(int size) throws GeneralSecurityException {
+      if (size != 16) {
+        throw new InvalidAlgorithmParameterException("invalid key size; only size 16 is good.");
+      }
+    }
+
+    @Override
+    protected void validateKey(AesGcmKey key) throws GeneralSecurityException {
+      Validators.validateVersion(key.getVersion(), VERSION);
+      throwIfNot16(key.getKeyValue().size());
+    }
+
+    @Override
+    protected void validateKeyFormat(AesGcmKeyFormat format) throws GeneralSecurityException {
+      throwIfNot16(format.getKeySize());
+    }
+  }
+
+  @Test
+  public void getPrimitive_ByteString_works() throws Exception {
+    TestKeyManager keyManager = new TestKeyManager();
+    MessageLite key = keyManager.newKey(AesGcmKeyFormat.newBuilder().setKeySize(16).build());
+    keyManager.getPrimitive(key.toByteString());
+  }
+
+  @Test
+  public void getPrimitive_ByteString_throwsInvalidKey() throws Exception {
+    TestKeyManager keyManager = new TestKeyManager();
+    MessageLite notAKey = AesGcmKey.getDefaultInstance();
+    try {
+      keyManager.getPrimitive(notAKey.toByteString());
+      fail("expected GeneralSecurityException");
+    } catch (GeneralSecurityException e) {
+      assertThat(e.toString()).contains("invalid key size");
+    }
+  }
+
+  @Test
+  public void getPrimitive_MessageLite_works() throws Exception {
+    TestKeyManager keyManager = new TestKeyManager();
+    MessageLite key = keyManager.newKey(AesGcmKeyFormat.newBuilder().setKeySize(16).build());
+    keyManager.getPrimitive(key);
+  }
+
+  @Test
+  public void getPrimitive_MessageLite_throwsWrongProto() throws Exception {
+    TestKeyManager keyManager = new TestKeyManager();
+    MessageLite notAKey = AesGcmKeyFormat.getDefaultInstance();
+    try {
+      keyManager.getPrimitive(notAKey);
+      fail("expected GeneralSecurityException");
+    } catch (GeneralSecurityException e) {
+      assertThat(e.toString()).contains("Expected proto of type");
+    }
+  }
+
+  @Test
+  public void getPrimitive_MessageLite_throwsInvalidKey() throws Exception {
+    TestKeyManager keyManager = new TestKeyManager();
+    MessageLite notAKey = AesGcmKey.getDefaultInstance();
+    try {
+      keyManager.getPrimitive(notAKey);
+      fail("expected GeneralSecurityException");
+    } catch (GeneralSecurityException e) {
+      assertThat(e.toString()).contains("invalid key size");
+    }
+  }
+
+  @Test
+  public void newKey_ByteString_works() throws Exception {
+    TestKeyManager keyManager = new TestKeyManager();
+    keyManager.newKey(AesGcmKeyFormat.newBuilder().setKeySize(16).build().toByteString());
+  }
+
+  @Test
+  public void newKey_ByteString_throwsInvalidKeySize() throws Exception {
+    TestKeyManager keyManager = new TestKeyManager();
+    try {
+      keyManager.newKey(AesGcmKeyFormat.newBuilder().setKeySize(17).build().toByteString());
+      fail("expected GeneralSecurityException");
+    } catch (GeneralSecurityException e) {
+      assertThat(e.toString()).contains("invalid key size");
+    }
+  }
+
+  @Test
+  public void newKey_MessageLite_works() throws Exception {
+    TestKeyManager keyManager = new TestKeyManager();
+    keyManager.newKey(AesGcmKeyFormat.newBuilder().setKeySize(16).build());
+  }
+
+  @Test
+  public void newKey_MessageLite_throwsWrongProto() throws Exception {
+    TestKeyManager keyManager = new TestKeyManager();
+    try {
+      keyManager.newKey(AesGcmKey.getDefaultInstance());
+      fail("expected GeneralSecurityException");
+    } catch (GeneralSecurityException e) {
+      assertThat(e.toString()).contains("Expected proto of type");
+    }
+  }
+
+  @Test
+  public void doesSupport_returnsTrue() throws Exception {
+    assertThat(new TestKeyManager().doesSupport("type.googleapis.com/google.crypto.tink.AesGcmKey"))
+        .isTrue();
+  }
+
+  @Test
+  public void doesSupport_returnsFalse() throws Exception {
+    assertThat(new TestKeyManager().doesSupport("type.googleapis.com/SomeOtherKey")).isFalse();
+  }
+
+  @Test
+  public void getKeyType() throws Exception {
+    assertThat(new TestKeyManager().getKeyType())
+        .isEqualTo("type.googleapis.com/google.crypto.tink.AesGcmKey");
+  }
+
+  @Test
+  public void newKeyData_works() throws Exception {
+    TestKeyManager keyManager = new TestKeyManager();
+    keyManager.newKeyData(AesGcmKeyFormat.newBuilder().setKeySize(16).build().toByteString());
+  }
+
+  @Test
+  public void newKeyData_typeUrlCorrect() throws Exception {
+    TestKeyManager keyManager = new TestKeyManager();
+    assertThat(
+            keyManager
+                .newKeyData(AesGcmKeyFormat.newBuilder().setKeySize(16).build().toByteString())
+                .getTypeUrl())
+        .isEqualTo("type.googleapis.com/google.crypto.tink.AesGcmKey");
+  }
+
+  @Test
+  public void newKeyData_valueLengthCorrect() throws Exception {
+    TestKeyManager keyManager = new TestKeyManager();
+    // We allow the keysize to be bigger than 16 since proto serialized adds some overhead.
+    assertThat(
+            keyManager
+                .newKeyData(AesGcmKeyFormat.newBuilder().setKeySize(16).build().toByteString())
+                .getValue()
+                .size())
+        .isAtLeast(16);
+  }
+
+  @Test
+  public void newKeyData_keyMaterialTypeCorrect() throws Exception {
+    TestKeyManager keyManager = new TestKeyManager();
+    assertThat(
+            keyManager
+                .newKeyData(AesGcmKeyFormat.newBuilder().setKeySize(16).build().toByteString())
+                .getKeyMaterialType())
+        .isEqualTo(KeyMaterialType.SYMMETRIC);
+  }
+
+  @Test
+  public void getPrimitiveClass() throws Exception {
+    TestKeyManager keyManager = new TestKeyManager();
+    assertThat(keyManager.getPrimitiveClass()).isEqualTo(Aead.class);
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/KeysetHandleTest.java b/java/src/test/java/com/google/crypto/tink/KeysetHandleTest.java
index 504fa67..a7df307 100644
--- a/java/src/test/java/com/google/crypto/tink/KeysetHandleTest.java
+++ b/java/src/test/java/com/google/crypto/tink/KeysetHandleTest.java
@@ -143,4 +143,162 @@
       assertExceptionContains(e, "empty keyset");
     }
   }
+
+  @Test
+  public void testGetPrimitive_basic() throws Exception {
+    Keyset keyset =
+        TestUtil.createKeyset(
+            TestUtil.createKey(
+                Registry.newKeyData(AeadKeyTemplates.AES128_GCM),
+                42,
+                KeyStatusType.ENABLED,
+                OutputPrefixType.TINK));
+    KeysetHandle handle = KeysetHandle.fromKeyset(keyset);
+    byte[] message = Random.randBytes(20);
+    byte[] aad = Random.randBytes(20);
+    Aead aead = handle.getPrimitive(Aead.class);
+    assertArrayEquals(aead.decrypt(aead.encrypt(message, aad), aad), message);
+  }
+
+  // Tests that getPrimitive does correct wrapping and not just return the primary. For this, we
+  // simply add a raw, non-primary key and encrypt directly with it.
+  @Test
+  public void testGetPrimitive_wrappingDoneCorrectly() throws Exception {
+    KeyData rawKeyData = Registry.newKeyData(AeadKeyTemplates.AES128_GCM);
+    Keyset keyset =
+        TestUtil.createKeyset(
+            TestUtil.createKey(
+                Registry.newKeyData(AeadKeyTemplates.AES128_GCM),
+                42,
+                KeyStatusType.ENABLED,
+                OutputPrefixType.TINK),
+            TestUtil.createKey(
+                rawKeyData,
+                43,
+                KeyStatusType.ENABLED,
+                OutputPrefixType.RAW));
+    KeysetHandle handle = KeysetHandle.fromKeyset(keyset);
+    byte[] message = Random.randBytes(20);
+    byte[] aad = Random.randBytes(20);
+    Aead aeadToEncrypt = Registry.getPrimitive(rawKeyData, Aead.class);
+    Aead aead = handle.getPrimitive(Aead.class);
+    assertArrayEquals(aead.decrypt(aeadToEncrypt.encrypt(message, aad), aad), message);
+  }
+
+  @Test
+  public void testGetPrimitive_customKeyManager() throws Exception {
+    Keyset keyset =
+        TestUtil.createKeyset(
+            TestUtil.createKey(
+                Registry.newKeyData(AeadKeyTemplates.AES128_GCM),
+                42,
+                KeyStatusType.ENABLED,
+                OutputPrefixType.TINK));
+    KeysetHandle handle = KeysetHandle.fromKeyset(keyset);
+    // The TestKeyManager accepts AES128_GCM keys, but creates a DummyAead which always fails.
+    Aead aead = handle.getPrimitive(new KeyManagerBaseTest.TestKeyManager(), Aead.class);
+    try {
+      aead.encrypt(new byte[0], new byte[0]);
+      fail("Expected GeneralSecurityException");
+    } catch (GeneralSecurityException e) {
+      assertExceptionContains(e, "dummy");
+    }
+  }
+
+  @Test
+  public void testGetPrimitive_nullKeyManager_throwsInvalidArgument() throws Exception {
+    Keyset keyset =
+        TestUtil.createKeyset(
+            TestUtil.createKey(
+                Registry.newKeyData(AeadKeyTemplates.AES128_GCM),
+                42,
+                KeyStatusType.ENABLED,
+                OutputPrefixType.TINK));
+    KeysetHandle handle = KeysetHandle.fromKeyset(keyset);
+    try {
+      handle.getPrimitive(null, Aead.class);
+      fail("Expected GeneralSecurityException");
+    } catch (IllegalArgumentException e) {
+      assertExceptionContains(e, "customKeyManager");
+    }
+  }
+
+  @Test
+  public void readNoSecretShouldWork() throws Exception {
+    KeysetHandle privateHandle = KeysetHandle.generateNew(SignatureKeyTemplates.ECDSA_P256);
+    Keyset keyset = privateHandle.getPublicKeysetHandle().getKeyset();
+    Keyset keyset2 = KeysetHandle.readNoSecret(keyset.toByteArray()).getKeyset();
+    Keyset keyset3 =
+        KeysetHandle.readNoSecret(BinaryKeysetReader.withBytes(keyset.toByteArray())).getKeyset();
+
+    assertEquals(keyset, keyset2);
+    assertEquals(keyset, keyset3);
+  }
+
+  @Test
+  public void readNoSecretFailForTypeSymmetric() throws Exception {
+    String keyValue = "01234567890123456";
+    Keyset keyset =
+        TestUtil.createKeyset(
+            TestUtil.createKey(
+                TestUtil.createHmacKeyData(keyValue.getBytes("UTF-8"), 16),
+                42,
+                KeyStatusType.ENABLED,
+                OutputPrefixType.TINK));
+    try {
+      KeysetHandle unused = KeysetHandle.readNoSecret(keyset.toByteArray());
+      fail("Expected GeneralSecurityException");
+    } catch (GeneralSecurityException e) {
+      assertExceptionContains(e, "keyset contains secret key material");
+    }
+
+    try {
+      KeysetHandle unused =
+          KeysetHandle.readNoSecret(BinaryKeysetReader.withBytes(keyset.toByteArray()));
+      fail("Expected GeneralSecurityException");
+    } catch (GeneralSecurityException e) {
+      assertExceptionContains(e, "keyset contains secret key material");
+    }
+  }
+
+  @Test
+  public void readNoSecretForTypeAssymmetricPrivate() throws Exception {
+    Keyset keyset = KeysetHandle.generateNew(SignatureKeyTemplates.ECDSA_P256).getKeyset();
+
+    try {
+      KeysetHandle unused = KeysetHandle.readNoSecret(keyset.toByteArray());
+      fail("Expected GeneralSecurityException");
+    } catch (GeneralSecurityException e) {
+      assertExceptionContains(e, "keyset contains secret key material");
+    }
+
+    try {
+      KeysetHandle unused =
+          KeysetHandle.readNoSecret(BinaryKeysetReader.withBytes(keyset.toByteArray()));
+      fail("Expected GeneralSecurityException");
+    } catch (GeneralSecurityException e) {
+      assertExceptionContains(e, "keyset contains secret key material");
+    }
+  }
+
+  @Test
+  public void readNoSecretFailWithEmptyKeyset() throws Exception {
+    try {
+      KeysetHandle unused = KeysetHandle.readNoSecret(new byte[0]);
+      fail("Expected GeneralSecurityException");
+    } catch (GeneralSecurityException e) {
+      assertExceptionContains(e, "empty keyset");
+    }
+  }
+
+  @Test
+  public void readNoSecretFailWithInvalidKeyset() throws Exception {
+    byte[] proto = new byte[] {0x00, 0x01, 0x02};
+    try {
+      KeysetHandle unused = KeysetHandle.readNoSecret(proto);
+      fail("Expected GeneralSecurityException");
+    } catch (GeneralSecurityException e) {
+      assertExceptionContains(e, "invalid");
+    }
+  }
 }
diff --git a/java/src/test/java/com/google/crypto/tink/KeysetManagerTest.java b/java/src/test/java/com/google/crypto/tink/KeysetManagerTest.java
index 59a4cbc..a1bce5f 100644
--- a/java/src/test/java/com/google/crypto/tink/KeysetManagerTest.java
+++ b/java/src/test/java/com/google/crypto/tink/KeysetManagerTest.java
@@ -17,6 +17,7 @@
 package com.google.crypto.tink;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import com.google.crypto.tink.config.TinkConfig;
@@ -617,6 +618,49 @@
     TestUtil.assertHmacKey(MacKeyTemplates.HMAC_SHA256_256BITTAG, keyset.getKey(1));
   }
 
+  @Test
+  public void testAddNewKey_onePrimary() throws Exception {
+    KeysetManager keysetManager = KeysetManager.withEmptyKeyset();
+    int keyId = keysetManager.addNewKey(MacKeyTemplates.HMAC_SHA256_128BITTAG, true);
+    Keyset keyset = keysetManager.getKeysetHandle().getKeyset();
+    assertThat(keyset.getKeyCount()).isEqualTo(1);
+    assertThat(keyset.getPrimaryKeyId()).isEqualTo(keyId);
+    TestUtil.assertHmacKey(MacKeyTemplates.HMAC_SHA256_128BITTAG, keyset.getKey(0));
+  }
+
+  @Test
+  public void testAddNewKey_onePrimaryAnotherPrimary() throws Exception {
+    KeysetManager keysetManager = KeysetManager.withEmptyKeyset();
+    keysetManager.addNewKey(MacKeyTemplates.HMAC_SHA256_128BITTAG, true);
+    int primaryKeyId = keysetManager.addNewKey(MacKeyTemplates.HMAC_SHA256_128BITTAG, true);
+    Keyset keyset = keysetManager.getKeysetHandle().getKeyset();
+    assertThat(keyset.getKeyCount()).isEqualTo(2);
+    assertThat(keyset.getPrimaryKeyId()).isEqualTo(primaryKeyId);
+  }
+
+  @Test
+  public void testAddNewKey_primaryThenNonPrimary() throws Exception {
+    KeysetManager keysetManager = KeysetManager.withEmptyKeyset();
+    int primaryKeyId = keysetManager.addNewKey(MacKeyTemplates.HMAC_SHA256_128BITTAG, true);
+    keysetManager.addNewKey(MacKeyTemplates.HMAC_SHA256_128BITTAG, false);
+    Keyset keyset = keysetManager.getKeysetHandle().getKeyset();
+    assertThat(keyset.getKeyCount()).isEqualTo(2);
+    assertThat(keyset.getPrimaryKeyId()).isEqualTo(primaryKeyId);
+  }
+
+  @Test
+  public void testAddNewKey_addThenDestroy() throws Exception {
+    KeysetManager keysetManager = KeysetManager.withEmptyKeyset();
+    keysetManager.addNewKey(MacKeyTemplates.HMAC_SHA256_128BITTAG, true);
+    int secondaryKeyId = keysetManager.addNewKey(MacKeyTemplates.HMAC_SHA256_128BITTAG, false);
+    keysetManager.destroy(secondaryKeyId);
+    Keyset keyset = keysetManager.getKeysetHandle().getKeyset();
+    assertThat(keyset.getKeyCount()).isEqualTo(2);
+    // One of the two keys is destroyed and doesn't have keyData anymore.
+    assertTrue(!keyset.getKey(0).hasKeyData() || !keyset.getKey(1).hasKeyData());
+  }
+
+
   private void manipulateKeyset(KeysetManager manager) {
     try {
       KeyTemplate template = MacKeyTemplates.HMAC_SHA256_128BITTAG;
diff --git a/java/src/test/java/com/google/crypto/tink/PrimitiveSetTest.java b/java/src/test/java/com/google/crypto/tink/PrimitiveSetTest.java
index 666a38a..b2dc31a 100644
--- a/java/src/test/java/com/google/crypto/tink/PrimitiveSetTest.java
+++ b/java/src/test/java/com/google/crypto/tink/PrimitiveSetTest.java
@@ -66,7 +66,7 @@
 
   @Test
   public void testBasicFunctionality() throws Exception {
-    PrimitiveSet<Mac> pset = PrimitiveSet.newPrimitiveSet();
+    PrimitiveSet<Mac> pset = PrimitiveSet.newPrimitiveSet(Mac.class);
     Key key1 =
         Key.newBuilder()
             .setKeyId(1)
@@ -131,7 +131,7 @@
 
   @Test
   public void testDuplicateKeys() throws Exception {
-    PrimitiveSet<Mac> pset = PrimitiveSet.newPrimitiveSet();
+    PrimitiveSet<Mac> pset = PrimitiveSet.newPrimitiveSet(Mac.class);
     Key key1 =
         Key.newBuilder()
             .setKeyId(1)
@@ -235,7 +235,7 @@
 
   @Test
   public void testAddInvalidKey() throws Exception {
-    PrimitiveSet<Mac> pset = PrimitiveSet.newPrimitiveSet();
+    PrimitiveSet<Mac> pset = PrimitiveSet.newPrimitiveSet(Mac.class);
     Key key1 = Key.newBuilder().setKeyId(1).setStatus(KeyStatusType.ENABLED).build();
     try {
       pset.addPrimitive(new DummyMac1(), key1);
diff --git a/java/src/test/java/com/google/crypto/tink/RegistryTest.java b/java/src/test/java/com/google/crypto/tink/RegistryTest.java
index 35ed9e0..6550513 100644
--- a/java/src/test/java/com/google/crypto/tink/RegistryTest.java
+++ b/java/src/test/java/com/google/crypto/tink/RegistryTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.crypto.tink.TestUtil.assertExceptionContains;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.fail;
 
 import com.google.crypto.tink.TestUtil.DummyAead;
@@ -34,6 +35,7 @@
 import com.google.crypto.tink.proto.KeyTemplate;
 import com.google.crypto.tink.proto.Keyset;
 import com.google.crypto.tink.proto.OutputPrefixType;
+import com.google.crypto.tink.signature.SignatureKeyTemplates;
 import com.google.crypto.tink.subtle.AesEaxJce;
 import com.google.crypto.tink.subtle.EncryptThenAuthenticate;
 import com.google.crypto.tink.subtle.MacJce;
@@ -41,7 +43,7 @@
 import com.google.protobuf.MessageLite;
 import java.security.GeneralSecurityException;
 import java.util.List;
-import org.junit.BeforeClass;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -95,11 +97,16 @@
     public int getVersion() {
       return 0;
     }
+
+    @Override
+    public Class<Aead> getPrimitiveClass() {
+      return Aead.class;
+    }
   }
 
-  @BeforeClass
-  public static void setUp() throws GeneralSecurityException {
-    Config.register(TinkConfig.TINK_1_0_0);
+  @Before
+  public void setUp() throws GeneralSecurityException {
+    TinkConfig.register();
   }
 
   private void testGetKeyManager_shouldWork(String typeUrl, String className) throws Exception {
@@ -107,15 +114,27 @@
   }
 
   @Test
-  public void testGetKeyManager_shouldWork() throws Exception {
+  public void testGetKeyManager_legacy_shouldWork() throws Exception {
     testGetKeyManager_shouldWork(AeadConfig.AES_CTR_HMAC_AEAD_TYPE_URL, "AesCtrHmacAeadKeyManager");
     testGetKeyManager_shouldWork(AeadConfig.AES_EAX_TYPE_URL, "AesEaxKeyManager");
     testGetKeyManager_shouldWork(MacConfig.HMAC_TYPE_URL, "HmacKeyManager");
   }
 
   @Test
-  public void testGetKeyManager_wrongType_shouldThrowException() throws Exception {
-    // TODO(thaidn): make this assignment throw some exception.
+  public void testGetKeyManager_shouldWorkAesEax() throws Exception {
+    assertThat(
+            Registry.getKeyManager(AeadConfig.AES_EAX_TYPE_URL, Aead.class).getClass().toString())
+        .contains("AesEaxKeyManager");
+  }
+
+  @Test
+  public void testGetKeyManager_shouldWorkHmac() throws Exception {
+    assertThat(Registry.getKeyManager(MacConfig.HMAC_TYPE_URL, Mac.class).getClass().toString())
+        .contains("HmacKeyManager");
+  }
+
+  @Test
+  public void testGetKeyManager_legacy_wrongType_shouldThrowException() throws Exception {
     KeyManager<Aead> wrongType = Registry.getKeyManager(MacConfig.HMAC_TYPE_URL);
     KeyTemplate template = MacKeyTemplates.HMAC_SHA256_128BITTAG;
     HmacKey hmacKey = (HmacKey) Registry.newKey(template);
@@ -129,7 +148,19 @@
   }
 
   @Test
-  public void testGetKeyManager_badTypeUrl_shouldThrowException() throws Exception {
+  public void testGetKeyManager_wrongType_shouldThrowException() throws Exception {
+    try {
+      Registry.getKeyManager(MacConfig.HMAC_TYPE_URL, Aead.class);
+      fail("Expected GeneralSecurityException");
+    } catch (GeneralSecurityException e) {
+      assertExceptionContains(e, "Primitive type com.google.crypto.tink.Mac");
+      assertExceptionContains(
+          e, "does not match requested primitive type com.google.crypto.tink.Aead");
+    }
+  }
+
+  @Test
+  public void testGetKeyManager_legacy_badTypeUrl_shouldThrowException() throws Exception {
     String badTypeUrl = "bad type URL";
 
     try {
@@ -142,6 +173,25 @@
   }
 
   @Test
+  public void testGetKeyManager_badTypeUrl_shouldThrowException() throws Exception {
+    String badTypeUrl = "bad type URL";
+
+    try {
+      Registry.getKeyManager(badTypeUrl, Aead.class);
+      fail("Expected GeneralSecurityException.");
+    } catch (GeneralSecurityException e) {
+      assertExceptionContains(e, "No key manager found");
+      assertExceptionContains(e, badTypeUrl);
+    }
+  }
+
+  @Test
+  public void testGetUntypedKeyManager_shouldWorkHmac() throws Exception {
+    assertThat(Registry.getUntypedKeyManager(MacConfig.HMAC_TYPE_URL).getClass().toString())
+        .contains("HmacKeyManager");
+  }
+
+  @Test
   public void testRegisterKeyManager_keyManagerIsNull_shouldThrowException() throws Exception {
     try {
       Registry.registerKeyManager(null);
@@ -155,26 +205,14 @@
   public void testRegisterKeyManager_MoreRestrictedNewKeyAllowed_shouldWork() throws Exception {
     String typeUrl = "someTypeUrl";
     Registry.registerKeyManager(new CustomAeadKeyManager(typeUrl));
-
-    try {
-      Registry.registerKeyManager(new CustomAeadKeyManager(typeUrl), false);
-    } catch (GeneralSecurityException e) {
-      throw new AssertionError(
-          "repeated registrations of the same key manager should work", e);
-    }
+    Registry.registerKeyManager(new CustomAeadKeyManager(typeUrl), false);
   }
 
   @Test
   public void testRegisterKeyManager_SameNewKeyAllowed_shouldWork() throws Exception {
     String typeUrl = "someOtherTypeUrl";
     Registry.registerKeyManager(new CustomAeadKeyManager(typeUrl));
-
-    try {
-      Registry.registerKeyManager(new CustomAeadKeyManager(typeUrl), true);
-    } catch (GeneralSecurityException e) {
-      throw new AssertionError(
-          "repeated registrations of the same key manager should work", e);
-    }
+    Registry.registerKeyManager(new CustomAeadKeyManager(typeUrl), true);
   }
 
   @Test
@@ -224,12 +262,11 @@
     String differentTypeUrl = "differentTypeUrl";
     try {
       Registry.registerKeyManager(differentTypeUrl, new CustomAeadKeyManager(typeUrl));
+      fail("Should throw an exception.");
     } catch (GeneralSecurityException e) {
       assertExceptionContains(e,
           "Manager does not support key type " + differentTypeUrl);
-      return;
     }
-    fail("Should throw an exception.");
   }
 
   @Test
@@ -276,7 +313,29 @@
   }
 
   @Test
-  public void testGetPrimitive_AesGcm_shouldWork() throws Exception {
+  public void testGetPublicKeyData_shouldWork() throws Exception {
+    KeyData privateKeyData = Registry.newKeyData(SignatureKeyTemplates.ECDSA_P256);
+    KeyData publicKeyData = Registry.getPublicKeyData(privateKeyData.getTypeUrl(),
+        privateKeyData.getValue());
+    PublicKeyVerify verifier = Registry.<PublicKeyVerify>getPrimitive(publicKeyData);
+    PublicKeySign signer = Registry.<PublicKeySign>getPrimitive(privateKeyData);
+    byte[] message = "Nice test message".getBytes(UTF_8);
+    verifier.verify(signer.sign(message), message);
+  }
+
+  @Test
+  public void testGetPublicKeyData_shouldThrow() throws Exception {
+    KeyData keyData = Registry.newKeyData(MacKeyTemplates.HMAC_SHA256_128BITTAG);
+    try {
+      Registry.getPublicKeyData(keyData.getTypeUrl(), keyData.getValue());
+      fail("Expected GeneralSecurityException.");
+    } catch (GeneralSecurityException e) {
+      assertThat(e.toString()).contains("not a PrivateKeyManager");
+    }
+  }
+
+  @Test
+  public void testGetPrimitive_legacy_AesGcm_shouldWork() throws Exception {
     KeyTemplate template = AeadKeyTemplates.AES128_EAX;
     AesEaxKey aesEaxKey = (AesEaxKey) Registry.newKey(template);
     KeyData aesEaxKeyData = Registry.newKeyData(template);
@@ -289,7 +348,20 @@
   }
 
   @Test
-  public void testGetPrimitive_Hmac_shouldWork() throws Exception {
+  public void testGetPrimitive_AesGcm_shouldWork() throws Exception {
+    KeyTemplate template = AeadKeyTemplates.AES128_EAX;
+    AesEaxKey aesEaxKey = (AesEaxKey) Registry.newKey(template);
+    KeyData aesEaxKeyData = Registry.newKeyData(template);
+    Aead aead = Registry.getPrimitive(aesEaxKeyData, Aead.class);
+
+    assertThat(aesEaxKey.getKeyValue().size()).isEqualTo(16);
+    assertThat(aesEaxKeyData.getTypeUrl()).isEqualTo(AeadConfig.AES_EAX_TYPE_URL);
+    // This might break when we add native implementations.
+    assertThat(aead.getClass()).isEqualTo(AesEaxJce.class);
+  }
+
+  @Test
+  public void testGetPrimitive_legacy_Hmac_shouldWork() throws Exception {
     KeyTemplate template = MacKeyTemplates.HMAC_SHA256_128BITTAG;
     HmacKey hmacKey = (HmacKey) Registry.newKey(template);
     KeyData hmacKeyData = Registry.newKeyData(template);
@@ -304,7 +376,22 @@
   }
 
   @Test
-  public void testGetPrimitives_shouldWork() throws Exception {
+  public void testGetPrimitive_Hmac_shouldWork() throws Exception {
+    KeyTemplate template = MacKeyTemplates.HMAC_SHA256_128BITTAG;
+    HmacKey hmacKey = (HmacKey) Registry.newKey(template);
+    KeyData hmacKeyData = Registry.newKeyData(template);
+    Mac mac = Registry.getPrimitive(hmacKeyData, Mac.class);
+
+    assertThat(hmacKey.getKeyValue().size()).isEqualTo(32);
+    assertThat(hmacKey.getParams().getTagSize()).isEqualTo(16);
+    assertThat(hmacKey.getParams().getHash()).isEqualTo(HashType.SHA256);
+    assertThat(hmacKeyData.getTypeUrl()).isEqualTo(MacConfig.HMAC_TYPE_URL);
+    // This might break when we add native implementations.
+    assertThat(mac.getClass()).isEqualTo(MacJce.class);
+  }
+
+  @Test
+  public void testGetPrimitives_legacy_shouldWork() throws Exception {
     // Create a keyset, and get a PrimitiveSet.
     KeyTemplate template1 = AeadKeyTemplates.AES128_EAX;
     KeyTemplate template2 = AeadKeyTemplates.AES128_CTR_HMAC_SHA256;
@@ -343,6 +430,45 @@
   }
 
   @Test
+  public void testGetPrimitives_shouldWork() throws Exception {
+    // Create a keyset, and get a PrimitiveSet.
+    KeyTemplate template1 = AeadKeyTemplates.AES128_EAX;
+    KeyTemplate template2 = AeadKeyTemplates.AES128_CTR_HMAC_SHA256;
+    KeyData key1 = Registry.newKeyData(template1);
+    KeyData key2 = Registry.newKeyData(template1);
+    KeyData key3 = Registry.newKeyData(template2);
+    KeysetHandle keysetHandle =
+        KeysetHandle.fromKeyset(
+            Keyset.newBuilder()
+                .addKey(
+                    Keyset.Key.newBuilder()
+                        .setKeyData(key1)
+                        .setKeyId(1)
+                        .setStatus(KeyStatusType.ENABLED)
+                        .setOutputPrefixType(OutputPrefixType.TINK)
+                        .build())
+                .addKey(
+                    Keyset.Key.newBuilder()
+                        .setKeyData(key2)
+                        .setKeyId(2)
+                        .setStatus(KeyStatusType.ENABLED)
+                        .setOutputPrefixType(OutputPrefixType.TINK)
+                        .build())
+                .addKey(
+                    Keyset.Key.newBuilder()
+                        .setKeyData(key3)
+                        .setKeyId(3)
+                        .setStatus(KeyStatusType.ENABLED)
+                        .setOutputPrefixType(OutputPrefixType.TINK)
+                        .build())
+                .setPrimaryKeyId(2)
+                .build());
+    PrimitiveSet<Aead> aeadSet = Registry.getPrimitives(keysetHandle, Aead.class);
+
+    assertThat(aeadSet.getPrimary().getPrimitive().getClass()).isEqualTo(AesEaxJce.class);
+  }
+
+  @Test
   public void testGetPrimitives_WithSomeNonEnabledKeys_shouldWork() throws Exception {
     // Try a keyset with some keys non-ENABLED.
     KeyTemplate template1 = AeadKeyTemplates.AES128_EAX;
@@ -376,14 +502,14 @@
                         .build())
                 .setPrimaryKeyId(3)
                 .build());
-    PrimitiveSet<Aead> aeadSet = Registry.getPrimitives(keysetHandle);
+    PrimitiveSet<Aead> aeadSet = Registry.getPrimitives(keysetHandle, Aead.class);
 
     assertThat(aeadSet.getPrimary().getPrimitive().getClass())
         .isEqualTo(EncryptThenAuthenticate.class);
   }
 
   @Test
-  public void testGetPrimitives_CustomManager_shouldWork() throws Exception {
+  public void testGetPrimitives_legacy_CustomManager_shouldWork() throws Exception {
     // Create a keyset.
     KeyTemplate template1 = AeadKeyTemplates.AES128_EAX;
     KeyTemplate template2 = AeadKeyTemplates.AES128_CTR_HMAC_SHA256;
@@ -417,6 +543,47 @@
     List<PrimitiveSet.Entry<Aead>> aead2List =
         aeadSet.getPrimitive(keysetHandle.getKeyset().getKey(1));
 
+    assertThat(aead1List).hasSize(1);
+    assertThat(aead1List.get(0).getPrimitive().getClass()).isEqualTo(DummyAead.class);
+    assertThat(aead2List).hasSize(1);
+    assertThat(aead2List.get(0).getPrimitive().getClass()).isEqualTo(EncryptThenAuthenticate.class);
+  }
+
+  @Test
+  public void testGetPrimitives_CustomManager_shouldWork() throws Exception {
+    // Create a keyset.
+    KeyTemplate template1 = AeadKeyTemplates.AES128_EAX;
+    KeyTemplate template2 = AeadKeyTemplates.AES128_CTR_HMAC_SHA256;
+    KeyData key1 = Registry.newKeyData(template1);
+    KeyData key2 = Registry.newKeyData(template2);
+    KeysetHandle keysetHandle =
+        KeysetHandle.fromKeyset(
+            Keyset.newBuilder()
+                .addKey(
+                    Keyset.Key.newBuilder()
+                        .setKeyData(key1)
+                        .setKeyId(1)
+                        .setStatus(KeyStatusType.ENABLED)
+                        .setOutputPrefixType(OutputPrefixType.TINK)
+                        .build())
+                .addKey(
+                    Keyset.Key.newBuilder()
+                        .setKeyData(key2)
+                        .setKeyId(2)
+                        .setStatus(KeyStatusType.ENABLED)
+                        .setOutputPrefixType(OutputPrefixType.TINK)
+                        .build())
+                .setPrimaryKeyId(2)
+                .build());
+
+    // Get a PrimitiveSet using a custom key manager for key1.
+    KeyManager<Aead> customManager = new CustomAeadKeyManager(AeadConfig.AES_EAX_TYPE_URL);
+    PrimitiveSet<Aead> aeadSet = Registry.getPrimitives(keysetHandle, customManager, Aead.class);
+    List<PrimitiveSet.Entry<Aead>> aead1List =
+        aeadSet.getPrimitive(keysetHandle.getKeyset().getKey(0));
+    List<PrimitiveSet.Entry<Aead>> aead2List =
+        aeadSet.getPrimitive(keysetHandle.getKeyset().getKey(1));
+
     assertThat(aead1List.size()).isEqualTo(1);
     assertThat(aead1List.get(0).getPrimitive().getClass()).isEqualTo(DummyAead.class);
     assertThat(aead2List.size()).isEqualTo(1);
@@ -427,7 +594,7 @@
   public void testGetPrimitives_EmptyKeyset_shouldThrowException() throws Exception {
     // Empty keyset.
     try {
-      Registry.getPrimitives(KeysetHandle.fromKeyset(Keyset.newBuilder().build()));
+      Registry.getPrimitives(KeysetHandle.fromKeyset(Keyset.getDefaultInstance()), Aead.class);
       fail("Invalid keyset. Expect GeneralSecurityException");
     } catch (GeneralSecurityException e) {
       assertExceptionContains(e, "empty keyset");
@@ -452,7 +619,7 @@
 
     // No primary key.
     try {
-      Registry.getPrimitives(keysetHandle);
+      Registry.getPrimitives(keysetHandle, Aead.class);
       fail("Invalid keyset. Expect GeneralSecurityException");
     } catch (GeneralSecurityException e) {
       assertExceptionContains(e, "keyset doesn't contain a valid primary key");
@@ -478,7 +645,7 @@
                 .build());
 
     try {
-      Registry.getPrimitives(keysetHandle);
+      Registry.getPrimitives(keysetHandle, Mac.class);
       fail("Invalid keyset. Expect GeneralSecurityException");
     } catch (GeneralSecurityException e) {
       assertExceptionContains(e, "keyset doesn't contain a valid primary key");
@@ -511,33 +678,87 @@
                 .build());
 
     try {
-      Registry.getPrimitives(keysetHandle);
+      Registry.getPrimitives(keysetHandle, Aead.class);
       fail("Invalid keyset. Expect GeneralSecurityException");
     } catch (GeneralSecurityException e) {
       assertExceptionContains(e, "keyset contains multiple primary keys");
     }
   }
 
-  private static class Catalogue1 implements Catalogue {
+  @Test
+  public void testGetPrimitives_KeysetWithKeyForWrongPrimitive_shouldThrowException()
+      throws Exception {
+    // Try a keyset with some keys non-ENABLED.
+    KeyData key1 = Registry.newKeyData(AeadKeyTemplates.AES128_EAX);
+    KeyData key2 = Registry.newKeyData(MacKeyTemplates.HMAC_SHA256_128BITTAG);
+    KeyData key3 = Registry.newKeyData(AeadKeyTemplates.AES128_EAX);
+    KeysetHandle keysetHandle =
+        KeysetHandle.fromKeyset(
+            Keyset.newBuilder()
+                .addKey(
+                    Keyset.Key.newBuilder()
+                        .setKeyData(key1)
+                        .setKeyId(1)
+                        .setStatus(KeyStatusType.ENABLED)
+                        .setOutputPrefixType(OutputPrefixType.TINK)
+                        .build())
+                .addKey(
+                    Keyset.Key.newBuilder()
+                        .setKeyData(key2)
+                        .setKeyId(2)
+                        .setStatus(KeyStatusType.ENABLED)
+                        .setOutputPrefixType(OutputPrefixType.TINK)
+                        .build())
+                .addKey(
+                    Keyset.Key.newBuilder()
+                        .setKeyData(key3)
+                        .setKeyId(3)
+                        .setStatus(KeyStatusType.ENABLED)
+                        .setOutputPrefixType(OutputPrefixType.TINK)
+                        .build())
+                .setPrimaryKeyId(3)
+                .build());
+    try {
+      Registry.getPrimitives(keysetHandle, Aead.class);
+      fail("Non Aead keys. Expected GeneralSecurityException");
+    } catch (GeneralSecurityException e) {
+      assertExceptionContains(e, "Primitive type com.google.crypto.tink.Mac");
+      assertExceptionContains(e, "not match requested primitive type com.google.crypto.tink.Aead");
+    }
+  }
+
+  private static class Catalogue1 implements Catalogue<Aead> {
     @Override
-    @SuppressWarnings({"rawtypes", "unchecked"})
-    public KeyManager getKeyManager(String typeUrl, String primitiveName, int minVersion) {
+    public KeyManager<Aead> getKeyManager(String typeUrl, String primitiveName, int minVersion) {
+      return null;
+    }
+
+    @Override
+    public PrimitiveWrapper<Aead> getPrimitiveWrapper() {
       return null;
     }
   }
 
-  private static class Catalogue2 implements Catalogue {
+  private static class Catalogue2 implements Catalogue<Aead> {
     @Override
-    @SuppressWarnings({"rawtypes", "unchecked"})
-    public KeyManager getKeyManager(String typeUrl, String primitiveName, int minVersion) {
+    public KeyManager<Aead> getKeyManager(String typeUrl, String primitiveName, int minVersion) {
+      return null;
+    }
+
+    @Override
+    public PrimitiveWrapper<Aead> getPrimitiveWrapper() {
       return null;
     }
   }
 
-  private static class Catalogue3 implements Catalogue {
+  private static class Catalogue3 implements Catalogue<Aead> {
     @Override
-    @SuppressWarnings({"rawtypes", "unchecked"})
-    public KeyManager getKeyManager(String typeUrl, String primitiveName, int minVersion) {
+    public KeyManager<Aead> getKeyManager(String typeUrl, String primitiveName, int minVersion) {
+      return null;
+    }
+
+    @Override
+    public PrimitiveWrapper<Aead> getPrimitiveWrapper() {
       return null;
     }
   }
@@ -606,4 +827,55 @@
     assertThat(count).isEqualTo(2);
   }
   // TODO(przydatek): Add more tests for creation of PrimitiveSets.
+
+  @Test
+  public void testWrap_wrapperRegistered() throws Exception {
+    KeyData key = Registry.newKeyData(AeadKeyTemplates.AES128_EAX);
+    KeysetHandle keysetHandle =
+        KeysetHandle.fromKeyset(
+            Keyset.newBuilder()
+                .addKey(
+                    Keyset.Key.newBuilder()
+                        .setKeyData(key)
+                        .setKeyId(1)
+                        .setStatus(KeyStatusType.ENABLED)
+                        .setOutputPrefixType(OutputPrefixType.TINK)
+                        .build())
+                .setPrimaryKeyId(1)
+                .build());
+
+    // Get a PrimitiveSet using a custom key manager for key1.
+    KeyManager<Aead> customManager = new CustomAeadKeyManager(AeadConfig.AES_EAX_TYPE_URL);
+    PrimitiveSet<Aead> aeadSet = Registry.getPrimitives(keysetHandle, customManager, Aead.class);
+    Registry.wrap(aeadSet);
+  }
+
+  @Test
+  public void testWrap_noWrapperRegistered_throws() throws Exception {
+    KeyData key = Registry.newKeyData(AeadKeyTemplates.AES128_EAX);
+    Registry.reset();
+    KeysetHandle keysetHandle =
+        KeysetHandle.fromKeyset(
+            Keyset.newBuilder()
+                .addKey(
+                    Keyset.Key.newBuilder()
+                        .setKeyData(key)
+                        .setKeyId(1)
+                        .setStatus(KeyStatusType.ENABLED)
+                        .setOutputPrefixType(OutputPrefixType.TINK)
+                        .build())
+                .setPrimaryKeyId(1)
+                .build());
+
+    // Get a PrimitiveSet using a custom key manager for key1.
+    KeyManager<Aead> customManager = new CustomAeadKeyManager(AeadConfig.AES_EAX_TYPE_URL);
+    PrimitiveSet<Aead> aeadSet = Registry.getPrimitives(keysetHandle, customManager, Aead.class);
+    try {
+      Registry.wrap(aeadSet);
+      fail("Expected GeneralSecurityException.");
+    } catch (GeneralSecurityException e) {
+      assertExceptionContains(e, "No wrapper found");
+      assertExceptionContains(e, "Aead");
+    }
+  }
 }
diff --git a/java/src/test/java/com/google/crypto/tink/StreamingTestUtil.java b/java/src/test/java/com/google/crypto/tink/StreamingTestUtil.java
index 380841b..e8c563d 100644
--- a/java/src/test/java/com/google/crypto/tink/StreamingTestUtil.java
+++ b/java/src/test/java/com/google/crypto/tink/StreamingTestUtil.java
@@ -452,7 +452,7 @@
     assertEquals(plaintext.length, decryptedSize);
   }
 
-    /**
+  /**
    * Encrypts and decrypts some plaintext in a stream and checks that the expected plaintext is
    * returned.
    *
@@ -531,6 +531,85 @@
     }
   }
 
+  /**
+   * Encrypts and decrypts some plaintext in a stream using skips and checks that the expected
+   * plaintext is returned for the parts not skipped.
+   *
+   * @param ags the StreamingAead test object.
+   * @param firstSegmentOffset number of bytes prepended to the ciphertext stream.
+   * @param plaintextSize the size of the plaintext
+   * @param chunkSize decryption skips and reads chunks of this size.
+   */
+  public static void testSkipWithStream(
+      StreamingAead ags, int firstSegmentOffset, int plaintextSize, int chunkSize)
+      throws Exception {
+    byte[] aad = TestUtil.hexDecode("aabbccddeeff");
+    byte[] plaintext = generatePlaintext(plaintextSize);
+    byte[] ciphertext = encryptWithStream(ags, plaintext, aad, firstSegmentOffset);
+
+    // Runs this part twice skips the chunk number i if skipChunk == i % 2.
+    for (int skipChunk = 0; skipChunk < 2; skipChunk++) {
+      // Construct an InputStream from the ciphertext where the first
+      // firstSegmentOffset bytes have already been read.
+      InputStream ctStream = new ByteArrayInputStream(ciphertext);
+      ctStream.read(new byte[firstSegmentOffset]);
+
+      // Construct an InputStream that returns the plaintext.
+      InputStream ptStream = ags.newDecryptingStream(ctStream, aad);
+      int decryptedSize = 0;
+      int chunkNumber = 0;
+      while (true) {
+        if (chunkNumber % 2 == skipChunk) {
+          int bytesSkipped = (int) ptStream.skip(chunkSize);
+          if (bytesSkipped < 0) {
+            fail("skip must not return a negative integer (not even at eof).");
+          }
+          if (bytesSkipped == 0) {
+            // The implementation here is blocking. Hence getting 0 here implies that
+            // the end of the stream has been reached. However, this has not been
+            // verified yet.
+            assertEquals("Expecting end of stream after a 0-byte skip.", -1, ptStream.read());
+            break;
+          }
+          decryptedSize += bytesSkipped;
+          if (decryptedSize < plaintextSize) {
+            // The stream is blocking. Hence we expect the number of requested
+            // bytes unless the end of the stream has been reached.
+            assertEquals("Size of skipped chunk is invalid", chunkSize, bytesSkipped);
+          }
+        } else {
+          byte[] chunk = new byte[chunkSize];
+          int read = ptStream.read(chunk);
+          if (read == -1) {
+            break;
+          }
+          byte[] expected = Arrays.copyOfRange(plaintext, decryptedSize, decryptedSize + read);
+          TestUtil.assertByteArrayEquals(expected, Arrays.copyOf(chunk, read));
+          decryptedSize += read;
+          if (read < chunkSize && decryptedSize < plaintextSize) {
+            // read should block until either all requested bytes are read, the end of the stream
+            // has been reached or an error occurred.
+            fail("read did not return enough bytes");
+          }
+        }
+        chunkNumber += 1;
+      }
+      assertEquals("Size of decryption does not match plaintext", plaintextSize, decryptedSize);
+    }
+
+    // Checks whether skipping at the end of a broken ciphertext is detected.
+    InputStream brokenCtStream = new ByteArrayInputStream(ciphertext, 0, ciphertext.length - 1);
+    brokenCtStream.read(new byte[firstSegmentOffset]);
+    InputStream brokenPtStream = ags.newDecryptingStream(brokenCtStream, aad);
+    try {
+      brokenPtStream.skip(2 * plaintextSize);
+      brokenPtStream.read();
+      fail("Failed to detect invalid ciphertext");
+    } catch (IOException ex) {
+      // expected
+    }
+  }
+
   // Methods for testEncryptSingleBytes.
 
   private static void testEncryptSingleBytesWithChannel(StreamingAead ags, int plaintextSize)
@@ -914,10 +993,9 @@
   // Methods for testFileEncryption.
 
   /** Encrypt some plaintext to a file, then decrypt from the file */
-  private static void testFileEncryptionWithChannel(StreamingAead ags, File tmpFile)
-      throws Exception {
+  private static void testFileEncryptionWithChannel(
+      StreamingAead ags, File tmpFile, int plaintextSize) throws Exception {
     byte[] aad = TestUtil.hexDecode("aabbccddeeff");
-    int plaintextSize = 1 << 18;
     SeekableByteBufferChannel plaintext =
         new SeekableByteBufferChannel(generatePlaintext(plaintextSize));
 
@@ -989,10 +1067,9 @@
    * Encrypts some plaintext to a file using FileOutputStream, then decrypt with a FileInputStream.
    * Reading and writing is done byte by byte.
    */
-  private static void testFileEncryptionWithStream(StreamingAead ags, File tmpFile)
-      throws Exception {
+  private static void testFileEncryptionWithStream(
+      StreamingAead ags, File tmpFile, int plaintextSize) throws Exception {
     byte[] aad = TestUtil.hexDecode("aabbccddeeff");
-    int plaintextSize = 1 << 15;
     byte[] pt = generatePlaintext(plaintextSize);
     FileOutputStream ctStream = new FileOutputStream(tmpFile);
     WritableByteChannel channel = Channels.newChannel(ctStream);
@@ -1030,8 +1107,9 @@
     assertEquals(plaintextSize, decryptedSize);
   }
 
-  public static void testFileEncryption(StreamingAead ags, File tmpFile) throws Exception {
-    testFileEncryptionWithChannel(ags, tmpFile);
-    testFileEncryptionWithStream(ags, tmpFile);
+  public static void testFileEncryption(StreamingAead ags, File tmpFile, int plaintextSize)
+      throws Exception {
+    testFileEncryptionWithChannel(ags, tmpFile, plaintextSize);
+    testFileEncryptionWithStream(ags, tmpFile, plaintextSize);
   }
 }
diff --git a/java/src/test/java/com/google/crypto/tink/TestUtil.java b/java/src/test/java/com/google/crypto/tink/TestUtil.java
index 6324aad..8ed92a9 100644
--- a/java/src/test/java/com/google/crypto/tink/TestUtil.java
+++ b/java/src/test/java/com/google/crypto/tink/TestUtil.java
@@ -22,7 +22,6 @@
 import static org.junit.Assert.assertTrue;
 
 import com.google.crypto.tink.aead.AeadConfig;
-import com.google.crypto.tink.aead.AeadFactory;
 import com.google.crypto.tink.daead.DeterministicAeadConfig;
 import com.google.crypto.tink.hybrid.HybridKeyTemplates;
 import com.google.crypto.tink.mac.MacConfig;
@@ -58,6 +57,10 @@
 import com.google.crypto.tink.proto.Keyset.Key;
 import com.google.crypto.tink.proto.KeysetInfo;
 import com.google.crypto.tink.proto.OutputPrefixType;
+import com.google.crypto.tink.proto.RsaSsaPkcs1Params;
+import com.google.crypto.tink.proto.RsaSsaPkcs1PublicKey;
+import com.google.crypto.tink.proto.RsaSsaPssParams;
+import com.google.crypto.tink.proto.RsaSsaPssPublicKey;
 import com.google.crypto.tink.streamingaead.StreamingAeadConfig;
 import com.google.crypto.tink.subtle.EllipticCurves;
 import com.google.crypto.tink.subtle.Hex;
@@ -73,10 +76,24 @@
 import java.security.interfaces.ECPublicKey;
 import java.security.spec.ECParameterSpec;
 import java.security.spec.ECPoint;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 import javax.crypto.Cipher;
 
 /** Test helpers. */
 public class TestUtil {
+  /** A place holder to keep mutated bytes and its description. */
+  public static class BytesMutation {
+    public byte[] value;
+    public String description;
+
+    public BytesMutation(byte[] value, String description) {
+      this.value = value;
+      this.description = description;
+    }
+  }
+
   // This GCP KMS CryptoKey is restricted to the service account in {@code SERVICE_ACCOUNT_FILE}.
   public static final String RESTRICTED_CRYPTO_KEY_URI =
       String.format(
@@ -349,6 +366,45 @@
   }
 
   /**
+   * @return a {@code RsaSsaPkcs1PublicKey} constructed from {@code modulus}, {@code exponent} and
+   *     {@code hashType}.
+   */
+  public static RsaSsaPkcs1PublicKey createRsaSsaPkcs1PubKey(
+      byte[] modulus, byte[] exponent, HashType hashType) throws Exception {
+    final int version = 0;
+    RsaSsaPkcs1Params params = RsaSsaPkcs1Params.newBuilder().setHashType(hashType).build();
+
+    return RsaSsaPkcs1PublicKey.newBuilder()
+        .setVersion(version)
+        .setParams(params)
+        .setN(ByteString.copyFrom(modulus))
+        .setE(ByteString.copyFrom(exponent))
+        .build();
+  }
+
+  /**
+   * Returns a {@code RsaSsaPssPublicKey} constructed from {@code modulus}, {@code exponent}, {@code
+   * sigHash}, {@code mgf1Hash} and {@code saltLength}.
+   */
+  public static RsaSsaPssPublicKey createRsaSsaPssPubKey(
+      byte[] modulus, byte[] exponent, HashType sigHash, HashType mgf1Hash, int saltLength)
+      throws Exception {
+    final int version = 0;
+    RsaSsaPssParams params =
+        RsaSsaPssParams.newBuilder()
+            .setSigHash(sigHash)
+            .setMgf1Hash(mgf1Hash)
+            .setSaltLength(saltLength)
+            .build();
+
+    return RsaSsaPssPublicKey.newBuilder()
+        .setVersion(version)
+        .setParams(params)
+        .setN(ByteString.copyFrom(modulus))
+        .setE(ByteString.copyFrom(exponent))
+        .build();
+  }
+  /**
    * @return a freshly generated {@code EciesAeadHkdfPrivateKey} constructed with specified
    *     parameters.
    */
@@ -427,18 +483,8 @@
         .build();
   }
 
-  /**
-   * Runs basic tests against an Aead primitive. The given keysetHandle should be an Aead key type.
-   */
-  public static void runBasicAeadFactoryTests(KeysetHandle keysetHandle) throws Exception {
-    runBasicAeadFactoryTests(keysetHandle, /* keyManager*/ null);
-  }
-  /**
-   * Runs basic tests against an Aead primitive. The given keysetHandle should be an Aead key type.
-   */
-  public static void runBasicAeadFactoryTests(
-      KeysetHandle keysetHandle, KeyManager<Aead> keyManager) throws Exception {
-    Aead aead = AeadFactory.getPrimitive(keysetHandle, keyManager);
+  /** Runs basic tests against an Aead primitive. */
+  public static void runBasicAeadTests(Aead aead) throws Exception {
     byte[] plaintext = Random.randBytes(20);
     byte[] associatedData = Random.randBytes(20);
     byte[] ciphertext = aead.encrypt(plaintext, associatedData);
@@ -483,6 +529,14 @@
     }
   }
 
+  /**
+   * Best-effort checks that this is running under tsan. Returns false in doubt and externally to
+   * google.
+   */
+  public static boolean isTsan() {
+    return false;
+  }
+
   /** Returns whether we should skip a test with some AES key size. */
   public static boolean shouldSkipTestWithAesKeySize(int keySizeInBytes)
       throws NoSuchAlgorithmException {
@@ -590,9 +644,7 @@
     assertEquals(keyManagerVersion, entry.getKeyManagerVersion());
   }
 
-  /**
-   * Convert an array of long to an array of int.
-   */
+  /** Convert an array of long to an array of int. */
   public static int[] twoCompInt(long[] a) {
     int[] ret = new int[a.length];
     for (int i = 0; i < a.length; i++) {
@@ -600,4 +652,32 @@
     }
     return ret;
   }
+
+  /**
+   * Generates mutations of {@code bytes}, e.g., flipping bits and truncating.
+   *
+   * @return a list of pairs of mutated value and mutation description.
+   */
+  public static List<BytesMutation> generateMutations(byte[] bytes) {
+    List<BytesMutation> res = new ArrayList<BytesMutation>();
+
+    // Flip bits.
+    for (int i = 0; i < bytes.length; i++) {
+      for (int j = 0; j < 8; j++) {
+        byte[] modifiedBytes = Arrays.copyOf(bytes, bytes.length);
+        modifiedBytes[i] = (byte) (modifiedBytes[i] ^ (1 << j));
+        res.add(new BytesMutation(modifiedBytes, String.format("Flip bit %d of data", i)));
+      }
+    }
+
+    // Truncate bytes.
+    for (int i = 0; i < bytes.length; i++) {
+      byte[] modifiedBytes = Arrays.copyOf(bytes, i);
+      res.add(new BytesMutation(modifiedBytes, String.format("Truncate upto %d bytes of data", i)));
+    }
+
+    // Append an extra byte.
+    res.add(new BytesMutation(Arrays.copyOf(bytes, bytes.length + 1), "Append an extra zero byte"));
+    return res;
+  }
 }
diff --git a/java/src/test/java/com/google/crypto/tink/UtilTest.java b/java/src/test/java/com/google/crypto/tink/UtilTest.java
index 4d7f64a..909b980 100644
--- a/java/src/test/java/com/google/crypto/tink/UtilTest.java
+++ b/java/src/test/java/com/google/crypto/tink/UtilTest.java
@@ -21,6 +21,8 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.google.crypto.tink.aead.AeadKeyTemplates;
+import com.google.crypto.tink.config.TinkConfig;
 import com.google.crypto.tink.proto.KeyData;
 import com.google.crypto.tink.proto.KeyStatusType;
 import com.google.crypto.tink.proto.Keyset;
@@ -134,6 +136,16 @@
     }
   }
 
+  @Test
+  public void testValidateKeyset_withDestroyedKey() throws Exception {
+    TinkConfig.register();
+    KeysetManager keysetManager = KeysetManager.withEmptyKeyset();
+    keysetManager.addNewKey(AeadKeyTemplates.AES128_GCM, true);
+    int secondaryKey = keysetManager.addNewKey(AeadKeyTemplates.AES128_GCM, false);
+    keysetManager.destroy(secondaryKey);
+    Util.validateKeyset(CleartextKeysetHandle.getKeyset(keysetManager.getKeysetHandle()));
+  }
+
   /** Tests that getKeysetInfo doesn't contain key material. */
   @Test
   public void testGetKeysetInfo() throws Exception {
diff --git a/java/src/test/java/com/google/crypto/tink/VersionTest.java b/java/src/test/java/com/google/crypto/tink/VersionTest.java
new file mode 100644
index 0000000..f725263
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/VersionTest.java
@@ -0,0 +1,40 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for Version. */
+@RunWith(JUnit4.class)
+public class VersionTest {
+  @Test
+  public void testVersionFormat() throws Exception {
+    // The regex represents Semantic Versioning syntax (www.semver.org),
+    // i.e. three dot-separated numbers, with an optional suffix
+    // that starts with a hyphen, to cover alpha/beta releases and
+    // release candiates, for example:
+    //   1.2.3
+    //   1.2.3-beta
+    //   1.2.3-RC1
+    String versionRegex = "[0-9]+[.][0-9]+[.][0-9]+(-[A-Za-z0-9]+)?";
+    assertThat(Version.TINK_VERSION).matches(versionRegex);
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/aead/AeadConfigTest.java b/java/src/test/java/com/google/crypto/tink/aead/AeadConfigTest.java
index e102f04..bfa7381 100644
--- a/java/src/test/java/com/google/crypto/tink/aead/AeadConfigTest.java
+++ b/java/src/test/java/com/google/crypto/tink/aead/AeadConfigTest.java
@@ -196,7 +196,7 @@
   @Test
   public void testConfigContents_LATEST() throws Exception {
     RegistryConfig config = AeadConfig.LATEST;
-    assertEquals(7, config.getEntryCount());
+    assertEquals(8, config.getEntryCount());
     assertEquals("TINK_AEAD", config.getConfigName());
 
     TestUtil.verifyConfigEntry(
@@ -248,5 +248,12 @@
         "type.googleapis.com/google.crypto.tink.KmsEnvelopeAeadKey",
         true,
         0);
+    TestUtil.verifyConfigEntry(
+        config.getEntry(7),
+        "TinkAead",
+        "Aead",
+        "type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key",
+        true,
+        0);
   }
 }
diff --git a/java/src/test/java/com/google/crypto/tink/aead/AeadFactoryTest.java b/java/src/test/java/com/google/crypto/tink/aead/AeadFactoryTest.java
index 97ec67a..76d5cfd 100644
--- a/java/src/test/java/com/google/crypto/tink/aead/AeadFactoryTest.java
+++ b/java/src/test/java/com/google/crypto/tink/aead/AeadFactoryTest.java
@@ -64,7 +64,7 @@
                     42,
                     KeyStatusType.ENABLED,
                     OutputPrefixType.TINK)));
-    TestUtil.runBasicAeadFactoryTests(keysetHandle);
+    TestUtil.runBasicAeadTests(keysetHandle.getPrimitive(Aead.class));
   }
 
   @Test
@@ -102,7 +102,7 @@
 
     KeysetHandle keysetHandle =
         TestUtil.createKeysetHandle(TestUtil.createKeyset(primary, raw, legacy, tink));
-    Aead aead = AeadFactory.getPrimitive(keysetHandle);
+    Aead aead = keysetHandle.getPrimitive(Aead.class);
     byte[] plaintext = Random.randBytes(20);
     byte[] associatedData = Random.randBytes(20);
     byte[] ciphertext = aead.encrypt(plaintext, associatedData);
@@ -116,7 +116,7 @@
     // encrypt with a non-primary RAW key and decrypt with the keyset
     KeysetHandle keysetHandle2 =
         TestUtil.createKeysetHandle(TestUtil.createKeyset(raw, legacy, tink));
-    Aead aead2 = AeadFactory.getPrimitive(keysetHandle2);
+    Aead aead2 = keysetHandle2.getPrimitive(Aead.class);
     ciphertext = aead2.encrypt(plaintext, associatedData);
     assertArrayEquals(plaintext, aead.decrypt(ciphertext, associatedData));
 
@@ -130,7 +130,7 @@
             KeyStatusType.ENABLED,
             OutputPrefixType.TINK);
     keysetHandle2 = TestUtil.createKeysetHandle(TestUtil.createKeyset(random));
-    aead2 = AeadFactory.getPrimitive(keysetHandle2);
+    aead2 = keysetHandle2.getPrimitive(Aead.class);
     ciphertext = aead2.encrypt(plaintext, associatedData);
     try {
       aead.decrypt(ciphertext, associatedData);
@@ -167,7 +167,7 @@
             OutputPrefixType.LEGACY);
     KeysetHandle keysetHandle =
         TestUtil.createKeysetHandle(TestUtil.createKeyset(primary, raw, legacy));
-    Aead aead = AeadFactory.getPrimitive(keysetHandle);
+    Aead aead = keysetHandle.getPrimitive(Aead.class);
     byte[] plaintext = Random.randBytes(20);
     byte[] associatedData = Random.randBytes(20);
     byte[] ciphertext = aead.encrypt(plaintext, associatedData);
@@ -190,7 +190,7 @@
             KeyStatusType.ENABLED,
             OutputPrefixType.RAW);
     KeysetHandle keysetHandle = TestUtil.createKeysetHandle(TestUtil.createKeyset(primary));
-    Aead aead = AeadFactory.getPrimitive(keysetHandle);
+    Aead aead = keysetHandle.getPrimitive(Aead.class);
     byte[] plaintext = Random.randBytes(1);
     byte[] associatedData = Random.randBytes(20);
     byte[] ciphertext = aead.encrypt(plaintext, associatedData);
@@ -218,19 +218,19 @@
 
     KeysetHandle keysetHandle = TestUtil.createKeysetHandle(TestUtil.createKeyset(valid, invalid));
     try {
-      AeadFactory.getPrimitive(keysetHandle);
+      keysetHandle.getPrimitive(Aead.class);
       fail("Expected GeneralSecurityException");
     } catch (GeneralSecurityException e) {
-      assertExceptionContains(e, "invalid AEAD key material");
+      assertExceptionContains(e, "Primitive type com.google.crypto.tink.DeterministicAead");
     }
 
     // invalid as the primary key.
     keysetHandle = TestUtil.createKeysetHandle(TestUtil.createKeyset(invalid, valid));
     try {
-      AeadFactory.getPrimitive(keysetHandle);
+      keysetHandle.getPrimitive(Aead.class);
       fail("Expected GeneralSecurityException");
     } catch (GeneralSecurityException e) {
-      assertExceptionContains(e, "invalid AEAD key material");
+      assertExceptionContains(e, "Primitive type com.google.crypto.tink.DeterministicAead");
     }
   }
 }
diff --git a/java/src/test/java/com/google/crypto/tink/aead/AeadIntegrationTest.java b/java/src/test/java/com/google/crypto/tink/aead/AeadIntegrationTest.java
new file mode 100644
index 0000000..d67bb9d
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/aead/AeadIntegrationTest.java
@@ -0,0 +1,241 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.aead;
+
+import static com.google.crypto.tink.TestUtil.assertExceptionContains;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.crypto.tink.Aead;
+import com.google.crypto.tink.CryptoFormat;
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.daead.DeterministicAeadConfig;
+import com.google.crypto.tink.proto.KeyStatusType;
+import com.google.crypto.tink.proto.Keyset.Key;
+import com.google.crypto.tink.proto.OutputPrefixType;
+import com.google.crypto.tink.subtle.Random;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for AeadIntegration. */
+@RunWith(JUnit4.class)
+public class AeadIntegrationTest {
+  private static final int AES_KEY_SIZE = 16;
+  private static final int HMAC_KEY_SIZE = 20;
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    AeadConfig.register();
+    DeterministicAeadConfig.register(); // need this for testInvalidKeyMaterial.
+  }
+
+  @Test
+  public void testBasicAesCtrHmacAead() throws Exception {
+    byte[] aesCtrKeyValue = Random.randBytes(AES_KEY_SIZE);
+    byte[] hmacKeyValue = Random.randBytes(HMAC_KEY_SIZE);
+    int ivSize = 12;
+    int tagSize = 16;
+    KeysetHandle keysetHandle =
+        TestUtil.createKeysetHandle(
+            TestUtil.createKeyset(
+                TestUtil.createKey(
+                    TestUtil.createAesCtrHmacAeadKeyData(
+                        aesCtrKeyValue, ivSize, hmacKeyValue, tagSize),
+                    42,
+                    KeyStatusType.ENABLED,
+                    OutputPrefixType.TINK)));
+    Aead aead = keysetHandle.getPrimitive(Aead.class);
+    byte[] plaintext = Random.randBytes(20);
+    byte[] associatedData = Random.randBytes(20);
+    byte[] ciphertext = aead.encrypt(plaintext, associatedData);
+    byte[] decrypted = aead.decrypt(ciphertext, associatedData);
+    assertArrayEquals(plaintext, decrypted);
+  }
+
+  @Test
+  public void testMultipleKeys() throws Exception {
+    byte[] aesCtrKeyValue = Random.randBytes(AES_KEY_SIZE);
+    byte[] hmacKeyValue = Random.randBytes(HMAC_KEY_SIZE);
+    int ivSize = 12;
+    int tagSize = 16;
+
+    Key primary =
+        TestUtil.createKey(
+            TestUtil.createAesCtrHmacAeadKeyData(aesCtrKeyValue, ivSize, hmacKeyValue, tagSize),
+            42,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.TINK);
+    Key raw =
+        TestUtil.createKey(
+            TestUtil.createAesCtrHmacAeadKeyData(aesCtrKeyValue, ivSize, hmacKeyValue, tagSize),
+            43,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.RAW);
+    Key legacy =
+        TestUtil.createKey(
+            TestUtil.createAesCtrHmacAeadKeyData(aesCtrKeyValue, ivSize, hmacKeyValue, tagSize),
+            44,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.LEGACY);
+
+    Key tink =
+        TestUtil.createKey(
+            TestUtil.createAesCtrHmacAeadKeyData(aesCtrKeyValue, ivSize, hmacKeyValue, tagSize),
+            45,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.TINK);
+
+    KeysetHandle keysetHandle =
+        TestUtil.createKeysetHandle(TestUtil.createKeyset(primary, raw, legacy, tink));
+    Aead aead = keysetHandle.getPrimitive(Aead.class);
+    byte[] plaintext = Random.randBytes(20);
+    byte[] associatedData = Random.randBytes(20);
+    byte[] ciphertext = aead.encrypt(plaintext, associatedData);
+    byte[] prefix = Arrays.copyOfRange(ciphertext, 0, CryptoFormat.NON_RAW_PREFIX_SIZE);
+
+    assertArrayEquals(prefix, CryptoFormat.getOutputPrefix(primary));
+    assertArrayEquals(plaintext, aead.decrypt(ciphertext, associatedData));
+    assertEquals(
+        CryptoFormat.NON_RAW_PREFIX_SIZE + plaintext.length + ivSize + tagSize, ciphertext.length);
+
+    // encrypt with a non-primary RAW key and decrypt with the keyset
+    KeysetHandle keysetHandle2 =
+        TestUtil.createKeysetHandle(TestUtil.createKeyset(raw, legacy, tink));
+    Aead aead2 = keysetHandle2.getPrimitive(Aead.class);
+    ciphertext = aead2.encrypt(plaintext, associatedData);
+    assertArrayEquals(plaintext, aead.decrypt(ciphertext, associatedData));
+
+    // encrypt with a random key not in the keyset, decrypt with the keyset should fail
+    byte[] aesCtrKeyValue2 = Random.randBytes(AES_KEY_SIZE);
+    byte[] hmacKeyValue2 = Random.randBytes(HMAC_KEY_SIZE);
+    Key random =
+        TestUtil.createKey(
+            TestUtil.createAesCtrHmacAeadKeyData(aesCtrKeyValue2, ivSize, hmacKeyValue2, tagSize),
+            44,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.TINK);
+    keysetHandle2 = TestUtil.createKeysetHandle(TestUtil.createKeyset(random));
+    aead2 = keysetHandle2.getPrimitive(Aead.class);
+    ciphertext = aead2.encrypt(plaintext, associatedData);
+    try {
+      aead.decrypt(ciphertext, associatedData);
+      fail("Expected GeneralSecurityException");
+    } catch (GeneralSecurityException e) {
+      assertExceptionContains(e, "decryption failed");
+    }
+  }
+
+  @Test
+  public void testRawKeyAsPrimary() throws Exception {
+    byte[] aesCtrKeyValue = Random.randBytes(AES_KEY_SIZE);
+    byte[] hmacKeyValue = Random.randBytes(HMAC_KEY_SIZE);
+    int ivSize = 12;
+    int tagSize = 16;
+
+    Key primary =
+        TestUtil.createKey(
+            TestUtil.createAesCtrHmacAeadKeyData(aesCtrKeyValue, ivSize, hmacKeyValue, tagSize),
+            42,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.RAW);
+    Key raw =
+        TestUtil.createKey(
+            TestUtil.createAesCtrHmacAeadKeyData(aesCtrKeyValue, ivSize, hmacKeyValue, tagSize),
+            43,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.RAW);
+    Key legacy =
+        TestUtil.createKey(
+            TestUtil.createAesCtrHmacAeadKeyData(aesCtrKeyValue, ivSize, hmacKeyValue, tagSize),
+            44,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.LEGACY);
+    KeysetHandle keysetHandle =
+        TestUtil.createKeysetHandle(TestUtil.createKeyset(primary, raw, legacy));
+    Aead aead = keysetHandle.getPrimitive(Aead.class);
+    byte[] plaintext = Random.randBytes(20);
+    byte[] associatedData = Random.randBytes(20);
+    byte[] ciphertext = aead.encrypt(plaintext, associatedData);
+    assertArrayEquals(plaintext, aead.decrypt(ciphertext, associatedData));
+    assertEquals(
+        CryptoFormat.RAW_PREFIX_SIZE + plaintext.length + ivSize + tagSize, ciphertext.length);
+  }
+
+  @Test
+  public void testSmallPlaintextWithRawKey() throws Exception {
+    byte[] aesCtrKeyValue = Random.randBytes(AES_KEY_SIZE);
+    byte[] hmacKeyValue = Random.randBytes(HMAC_KEY_SIZE);
+    int ivSize = 12;
+    int tagSize = 16;
+
+    Key primary =
+        TestUtil.createKey(
+            TestUtil.createAesCtrHmacAeadKeyData(aesCtrKeyValue, ivSize, hmacKeyValue, tagSize),
+            42,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.RAW);
+    KeysetHandle keysetHandle = TestUtil.createKeysetHandle(TestUtil.createKeyset(primary));
+    Aead aead = keysetHandle.getPrimitive(Aead.class);
+    byte[] plaintext = Random.randBytes(1);
+    byte[] associatedData = Random.randBytes(20);
+    byte[] ciphertext = aead.encrypt(plaintext, associatedData);
+    assertArrayEquals(plaintext, aead.decrypt(ciphertext, associatedData));
+    assertEquals(
+        CryptoFormat.RAW_PREFIX_SIZE + plaintext.length + ivSize + tagSize, ciphertext.length);
+  }
+
+  @Test
+  public void testInvalidKeyMaterial() throws Exception {
+    byte[] aesCtrKeyValue = Random.randBytes(AES_KEY_SIZE);
+    byte[] hmacKeyValue = Random.randBytes(HMAC_KEY_SIZE);
+    int ivSize = 12;
+    int tagSize = 16;
+
+    Key valid =
+        TestUtil.createKey(
+            TestUtil.createAesCtrHmacAeadKeyData(aesCtrKeyValue, ivSize, hmacKeyValue, tagSize),
+            42,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.RAW);
+    Key invalid =
+        TestUtil.createKey(
+            TestUtil.createAesSivKeyData(64), 43, KeyStatusType.ENABLED, OutputPrefixType.TINK);
+
+    KeysetHandle keysetHandle = TestUtil.createKeysetHandle(TestUtil.createKeyset(valid, invalid));
+    try {
+      keysetHandle.getPrimitive(Aead.class);
+      fail("Expected GeneralSecurityException");
+    } catch (GeneralSecurityException e) {
+      assertExceptionContains(e, "Primitive type com.google.crypto.tink.DeterministicAead");
+    }
+
+    // invalid as the primary key.
+    keysetHandle = TestUtil.createKeysetHandle(TestUtil.createKeyset(invalid, valid));
+    try {
+      keysetHandle.getPrimitive(Aead.class);
+      fail("Expected GeneralSecurityException");
+    } catch (GeneralSecurityException e) {
+      assertExceptionContains(e, "Primitive type com.google.crypto.tink.DeterministicAead");
+    }
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/aead/AeadKeyTemplatesTest.java b/java/src/test/java/com/google/crypto/tink/aead/AeadKeyTemplatesTest.java
index 025d314..8cc6383 100644
--- a/java/src/test/java/com/google/crypto/tink/aead/AeadKeyTemplatesTest.java
+++ b/java/src/test/java/com/google/crypto/tink/aead/AeadKeyTemplatesTest.java
@@ -177,6 +177,14 @@
   }
 
   @Test
+  public void testXCHACHA20_POLY1305() throws Exception {
+    KeyTemplate template = AeadKeyTemplates.XCHACHA20_POLY1305;
+    assertEquals(XChaCha20Poly1305KeyManager.TYPE_URL, template.getTypeUrl());
+    assertEquals(OutputPrefixType.TINK, template.getOutputPrefixType());
+    assertTrue(template.getValue().isEmpty()); // Empty format.
+  }
+
+  @Test
   public void testCreateKmsAeadKeyTemplate() throws Exception {
     // Intentionally using "weird" or invalid values for parameters,
     // to test that the function correctly puts them in the resulting template.
diff --git a/java/src/test/java/com/google/crypto/tink/aead/AeadWrapperTest.java b/java/src/test/java/com/google/crypto/tink/aead/AeadWrapperTest.java
new file mode 100644
index 0000000..3843d88
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/aead/AeadWrapperTest.java
@@ -0,0 +1,205 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.aead;
+
+import static com.google.crypto.tink.TestUtil.assertExceptionContains;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.crypto.tink.Aead;
+import com.google.crypto.tink.CryptoFormat;
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.Registry;
+import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.proto.KeyStatusType;
+import com.google.crypto.tink.proto.Keyset.Key;
+import com.google.crypto.tink.proto.OutputPrefixType;
+import com.google.crypto.tink.subtle.Random;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for AeadWrapper */
+@RunWith(JUnit4.class)
+public class AeadWrapperTest {
+  private static final int AES_KEY_SIZE = 16;
+  private static final int HMAC_KEY_SIZE = 20;
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    AeadConfig.register();
+  }
+
+  @Test
+  public void testBasicAesCtrHmacAead() throws Exception {
+    byte[] aesCtrKeyValue = Random.randBytes(AES_KEY_SIZE);
+    byte[] hmacKeyValue = Random.randBytes(HMAC_KEY_SIZE);
+    int ivSize = 12;
+    int tagSize = 16;
+    KeysetHandle keysetHandle =
+        TestUtil.createKeysetHandle(
+            TestUtil.createKeyset(
+                TestUtil.createKey(
+                    TestUtil.createAesCtrHmacAeadKeyData(
+                        aesCtrKeyValue, ivSize, hmacKeyValue, tagSize),
+                    42,
+                    KeyStatusType.ENABLED,
+                    OutputPrefixType.TINK)));
+    Aead aead = new AeadWrapper().wrap(Registry.getPrimitives(keysetHandle));
+    byte[] plaintext = Random.randBytes(20);
+    byte[] associatedData = Random.randBytes(20);
+    byte[] ciphertext = aead.encrypt(plaintext, associatedData);
+    byte[] decrypted = aead.decrypt(ciphertext, associatedData);
+    assertArrayEquals(plaintext, decrypted);
+  }
+
+  @Test
+  public void testMultipleKeys() throws Exception {
+    byte[] aesCtrKeyValue = Random.randBytes(AES_KEY_SIZE);
+    byte[] hmacKeyValue = Random.randBytes(HMAC_KEY_SIZE);
+    int ivSize = 12;
+    int tagSize = 16;
+
+    Key primary =
+        TestUtil.createKey(
+            TestUtil.createAesCtrHmacAeadKeyData(aesCtrKeyValue, ivSize, hmacKeyValue, tagSize),
+            42,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.TINK);
+    Key raw =
+        TestUtil.createKey(
+            TestUtil.createAesCtrHmacAeadKeyData(aesCtrKeyValue, ivSize, hmacKeyValue, tagSize),
+            43,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.RAW);
+    Key legacy =
+        TestUtil.createKey(
+            TestUtil.createAesCtrHmacAeadKeyData(aesCtrKeyValue, ivSize, hmacKeyValue, tagSize),
+            44,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.LEGACY);
+
+    Key tink =
+        TestUtil.createKey(
+            TestUtil.createAesCtrHmacAeadKeyData(aesCtrKeyValue, ivSize, hmacKeyValue, tagSize),
+            45,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.TINK);
+
+    KeysetHandle keysetHandle =
+        TestUtil.createKeysetHandle(TestUtil.createKeyset(primary, raw, legacy, tink));
+    Aead aead = new AeadWrapper().wrap(Registry.getPrimitives(keysetHandle));
+    byte[] plaintext = Random.randBytes(20);
+    byte[] associatedData = Random.randBytes(20);
+    byte[] ciphertext = aead.encrypt(plaintext, associatedData);
+    byte[] prefix = Arrays.copyOfRange(ciphertext, 0, CryptoFormat.NON_RAW_PREFIX_SIZE);
+
+    assertArrayEquals(prefix, CryptoFormat.getOutputPrefix(primary));
+    assertArrayEquals(plaintext, aead.decrypt(ciphertext, associatedData));
+    assertEquals(
+        CryptoFormat.NON_RAW_PREFIX_SIZE + plaintext.length + ivSize + tagSize, ciphertext.length);
+
+    // encrypt with a non-primary RAW key and decrypt with the keyset
+    KeysetHandle keysetHandle2 =
+        TestUtil.createKeysetHandle(TestUtil.createKeyset(raw, legacy, tink));
+    Aead aead2 = new AeadWrapper().wrap(Registry.getPrimitives(keysetHandle2));
+    ciphertext = aead2.encrypt(plaintext, associatedData);
+    assertArrayEquals(plaintext, aead.decrypt(ciphertext, associatedData));
+
+    // encrypt with a random key not in the keyset, decrypt with the keyset should fail
+    byte[] aesCtrKeyValue2 = Random.randBytes(AES_KEY_SIZE);
+    byte[] hmacKeyValue2 = Random.randBytes(HMAC_KEY_SIZE);
+    Key random =
+        TestUtil.createKey(
+            TestUtil.createAesCtrHmacAeadKeyData(aesCtrKeyValue2, ivSize, hmacKeyValue2, tagSize),
+            44,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.TINK);
+    keysetHandle2 = TestUtil.createKeysetHandle(TestUtil.createKeyset(random));
+    aead2 = new AeadWrapper().wrap(Registry.getPrimitives(keysetHandle2));
+    ciphertext = aead2.encrypt(plaintext, associatedData);
+    try {
+      aead.decrypt(ciphertext, associatedData);
+      fail("Expected GeneralSecurityException");
+    } catch (GeneralSecurityException e) {
+      assertExceptionContains(e, "decryption failed");
+    }
+  }
+
+  @Test
+  public void testRawKeyAsPrimary() throws Exception {
+    byte[] aesCtrKeyValue = Random.randBytes(AES_KEY_SIZE);
+    byte[] hmacKeyValue = Random.randBytes(HMAC_KEY_SIZE);
+    int ivSize = 12;
+    int tagSize = 16;
+
+    Key primary =
+        TestUtil.createKey(
+            TestUtil.createAesCtrHmacAeadKeyData(aesCtrKeyValue, ivSize, hmacKeyValue, tagSize),
+            42,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.RAW);
+    Key raw =
+        TestUtil.createKey(
+            TestUtil.createAesCtrHmacAeadKeyData(aesCtrKeyValue, ivSize, hmacKeyValue, tagSize),
+            43,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.RAW);
+    Key legacy =
+        TestUtil.createKey(
+            TestUtil.createAesCtrHmacAeadKeyData(aesCtrKeyValue, ivSize, hmacKeyValue, tagSize),
+            44,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.LEGACY);
+    KeysetHandle keysetHandle =
+        TestUtil.createKeysetHandle(TestUtil.createKeyset(primary, raw, legacy));
+    Aead aead = new AeadWrapper().wrap(Registry.getPrimitives(keysetHandle));
+    byte[] plaintext = Random.randBytes(20);
+    byte[] associatedData = Random.randBytes(20);
+    byte[] ciphertext = aead.encrypt(plaintext, associatedData);
+    assertArrayEquals(plaintext, aead.decrypt(ciphertext, associatedData));
+    assertEquals(
+        CryptoFormat.RAW_PREFIX_SIZE + plaintext.length + ivSize + tagSize, ciphertext.length);
+  }
+
+  @Test
+  public void testSmallPlaintextWithRawKey() throws Exception {
+    byte[] aesCtrKeyValue = Random.randBytes(AES_KEY_SIZE);
+    byte[] hmacKeyValue = Random.randBytes(HMAC_KEY_SIZE);
+    int ivSize = 12;
+    int tagSize = 16;
+
+    Key primary =
+        TestUtil.createKey(
+            TestUtil.createAesCtrHmacAeadKeyData(aesCtrKeyValue, ivSize, hmacKeyValue, tagSize),
+            42,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.RAW);
+    KeysetHandle keysetHandle = TestUtil.createKeysetHandle(TestUtil.createKeyset(primary));
+    Aead aead = new AeadWrapper().wrap(Registry.getPrimitives(keysetHandle));
+    byte[] plaintext = Random.randBytes(1);
+    byte[] associatedData = Random.randBytes(20);
+    byte[] ciphertext = aead.encrypt(plaintext, associatedData);
+    assertArrayEquals(plaintext, aead.decrypt(ciphertext, associatedData));
+    assertEquals(
+        CryptoFormat.RAW_PREFIX_SIZE + plaintext.length + ivSize + tagSize, ciphertext.length);
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/aead/AesCtrHmacAeadKeyManagerTest.java b/java/src/test/java/com/google/crypto/tink/aead/AesCtrHmacAeadKeyManagerTest.java
index b18aef6..82ae96d 100644
--- a/java/src/test/java/com/google/crypto/tink/aead/AesCtrHmacAeadKeyManagerTest.java
+++ b/java/src/test/java/com/google/crypto/tink/aead/AesCtrHmacAeadKeyManagerTest.java
@@ -44,7 +44,7 @@
   public void testNewKeyMultipleTimes() throws Exception {
     KeyTemplate keyTemplate = AeadKeyTemplates.AES128_CTR_HMAC_SHA256;
     AesCtrHmacAeadKeyFormat aeadKeyFormat =
-        AesCtrHmacAeadKeyFormat.parseFrom(keyTemplate.getValue().toByteArray());
+        AesCtrHmacAeadKeyFormat.parseFrom(keyTemplate.getValue());
     ByteString serialized = ByteString.copyFrom(aeadKeyFormat.toByteArray());
     AesCtrHmacAeadKeyManager keyManager = new AesCtrHmacAeadKeyManager();
     Set<String> keys = new TreeSet<String>();
diff --git a/java/src/test/java/com/google/crypto/tink/aead/AesEaxKeyManagerTest.java b/java/src/test/java/com/google/crypto/tink/aead/AesEaxKeyManagerTest.java
index ae99c17..d7e1944 100644
--- a/java/src/test/java/com/google/crypto/tink/aead/AesEaxKeyManagerTest.java
+++ b/java/src/test/java/com/google/crypto/tink/aead/AesEaxKeyManagerTest.java
@@ -242,7 +242,7 @@
                 42,
                 KeyStatusType.ENABLED,
                 OutputPrefixType.RAW)));
-    return AeadFactory.getPrimitive(keysetHandle);
+    return keysetHandle.getPrimitive(Aead.class);
   }
 
   @Test
@@ -255,7 +255,7 @@
                 42,
                 KeyStatusType.ENABLED,
                 OutputPrefixType.TINK)));
-    TestUtil.runBasicAeadFactoryTests(keysetHandle);
+    TestUtil.runBasicAeadTests(keysetHandle.getPrimitive(Aead.class));
   }
 
   @Test
@@ -268,7 +268,7 @@
                 42,
                 KeyStatusType.ENABLED,
                 OutputPrefixType.TINK)));
-    Aead aead = AeadFactory.getPrimitive(keysetHandle);
+    Aead aead = keysetHandle.getPrimitive(Aead.class);
     byte[] plaintext = "plaintext".getBytes("UTF-8");
     byte[] associatedData = "associatedData".getBytes("UTF-8");
     byte[] ciphertext = aead.encrypt(plaintext, associatedData);
diff --git a/java/src/test/java/com/google/crypto/tink/aead/AesGcmKeyManagerTest.java b/java/src/test/java/com/google/crypto/tink/aead/AesGcmKeyManagerTest.java
index f304155..407a0d2 100644
--- a/java/src/test/java/com/google/crypto/tink/aead/AesGcmKeyManagerTest.java
+++ b/java/src/test/java/com/google/crypto/tink/aead/AesGcmKeyManagerTest.java
@@ -317,7 +317,7 @@
                     42,
                     KeyStatusType.ENABLED,
                     OutputPrefixType.RAW)));
-    return AeadFactory.getPrimitive(keysetHandle);
+    return keysetHandle.getPrimitive(Aead.class);
   }
 
   @Test
@@ -331,7 +331,7 @@
                     42,
                     KeyStatusType.ENABLED,
                     OutputPrefixType.TINK)));
-    TestUtil.runBasicAeadFactoryTests(keysetHandle);
+    TestUtil.runBasicAeadTests(keysetHandle.getPrimitive(Aead.class));
   }
 
   @Test
@@ -345,7 +345,7 @@
                     42,
                     KeyStatusType.ENABLED,
                     OutputPrefixType.TINK)));
-    Aead aead = AeadFactory.getPrimitive(keysetHandle);
+    Aead aead = keysetHandle.getPrimitive(Aead.class);
     byte[] plaintext = "plaintext".getBytes("UTF-8");
     byte[] associatedData = "associatedData".getBytes("UTF-8");
     byte[] ciphertext = aead.encrypt(plaintext, associatedData);
diff --git a/java/src/test/java/com/google/crypto/tink/aead/ChaCha20Poly1305KeyManagerTest.java b/java/src/test/java/com/google/crypto/tink/aead/ChaCha20Poly1305KeyManagerTest.java
index 325b23c..e43d976 100644
--- a/java/src/test/java/com/google/crypto/tink/aead/ChaCha20Poly1305KeyManagerTest.java
+++ b/java/src/test/java/com/google/crypto/tink/aead/ChaCha20Poly1305KeyManagerTest.java
@@ -44,13 +44,13 @@
   @Test
   public void testBasic() throws Exception {
     KeysetHandle keysetHandle = KeysetHandle.generateNew(AeadKeyTemplates.CHACHA20_POLY1305);
-    TestUtil.runBasicAeadFactoryTests(keysetHandle);
+    TestUtil.runBasicAeadTests(keysetHandle.getPrimitive(Aead.class));
   }
 
   @Test
   public void testCiphertextSize() throws Exception {
     KeysetHandle keysetHandle = KeysetHandle.generateNew(AeadKeyTemplates.CHACHA20_POLY1305);
-    Aead aead = AeadFactory.getPrimitive(keysetHandle);
+    Aead aead = keysetHandle.getPrimitive(Aead.class);
     byte[] plaintext = "plaintext".getBytes("UTF-8");
     byte[] associatedData = "associatedData".getBytes("UTF-8");
     byte[] ciphertext = aead.encrypt(plaintext, associatedData);
diff --git a/java/src/test/java/com/google/crypto/tink/aead/KmsAeadKeyManagerTest.java b/java/src/test/java/com/google/crypto/tink/aead/KmsAeadKeyManagerTest.java
index 36e2f0c..f080c37 100644
--- a/java/src/test/java/com/google/crypto/tink/aead/KmsAeadKeyManagerTest.java
+++ b/java/src/test/java/com/google/crypto/tink/aead/KmsAeadKeyManagerTest.java
@@ -16,6 +16,7 @@
 
 package com.google.crypto.tink.aead;
 
+import com.google.crypto.tink.Aead;
 import com.google.crypto.tink.KeysetHandle;
 import com.google.crypto.tink.KmsClient;
 import com.google.crypto.tink.KmsClients;
@@ -41,6 +42,6 @@
     KeysetHandle keysetHandle =
         KeysetHandle.generateNew(
             AeadKeyTemplates.createKmsAeadKeyTemplate(TestUtil.RESTRICTED_CRYPTO_KEY_URI));
-    TestUtil.runBasicAeadFactoryTests(keysetHandle);
+    TestUtil.runBasicAeadTests(keysetHandle.getPrimitive(Aead.class));
   }
 }
diff --git a/java/src/test/java/com/google/crypto/tink/aead/KmsEnvelopeAeadKeyManagerTest.java b/java/src/test/java/com/google/crypto/tink/aead/KmsEnvelopeAeadKeyManagerTest.java
index 620055c..4209750 100644
--- a/java/src/test/java/com/google/crypto/tink/aead/KmsEnvelopeAeadKeyManagerTest.java
+++ b/java/src/test/java/com/google/crypto/tink/aead/KmsEnvelopeAeadKeyManagerTest.java
@@ -53,7 +53,7 @@
     KeysetHandle keysetHandle = KeysetHandle.generateNew(
         AeadKeyTemplates.createKmsEnvelopeAeadKeyTemplate(
             TestUtil.RESTRICTED_CRYPTO_KEY_URI, dekTemplate));
-    TestUtil.runBasicAeadFactoryTests(keysetHandle);
+    TestUtil.runBasicAeadTests(keysetHandle.getPrimitive(Aead.class));
   }
 
   @Test
@@ -63,7 +63,7 @@
         AeadKeyTemplates.createKmsEnvelopeAeadKeyTemplate(
             TestUtil.RESTRICTED_CRYPTO_KEY_URI, dekTemplate));
 
-    Aead aead = AeadFactory.getPrimitive(keysetHandle);
+    Aead aead = keysetHandle.getPrimitive(Aead.class);
     byte[] plaintext = Random.randBytes(20);
     byte[] aad = Random.randBytes(20);
     byte[] ciphertext = aead.encrypt(plaintext, aad);
diff --git a/java/src/test/java/com/google/crypto/tink/aead/XChaCha20Poly1305KeyManagerTest.java b/java/src/test/java/com/google/crypto/tink/aead/XChaCha20Poly1305KeyManagerTest.java
new file mode 100644
index 0000000..37c5e1c
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/aead/XChaCha20Poly1305KeyManagerTest.java
@@ -0,0 +1,81 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.aead;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.crypto.tink.Aead;
+import com.google.crypto.tink.CryptoFormat;
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyTemplate;
+import com.google.crypto.tink.proto.XChaCha20Poly1305Key;
+import java.security.GeneralSecurityException;
+import java.util.Set;
+import java.util.TreeSet;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test for XChaCha20Poly1305KeyManager. */
+@RunWith(JUnit4.class)
+public class XChaCha20Poly1305KeyManagerTest {
+  @BeforeClass
+  public static void setUp() throws GeneralSecurityException {
+    AeadConfig.register();
+  }
+
+  @Test
+  public void testBasic() throws Exception {
+    KeysetHandle keysetHandle = KeysetHandle.generateNew(AeadKeyTemplates.XCHACHA20_POLY1305);
+    TestUtil.runBasicAeadTests(keysetHandle.getPrimitive(Aead.class));
+  }
+
+  @Test
+  public void testCiphertextSize() throws Exception {
+    KeysetHandle keysetHandle = KeysetHandle.generateNew(AeadKeyTemplates.XCHACHA20_POLY1305);
+    Aead aead = keysetHandle.getPrimitive(Aead.class);
+    byte[] plaintext = "plaintext".getBytes("UTF-8");
+    byte[] associatedData = "associatedData".getBytes("UTF-8");
+    byte[] ciphertext = aead.encrypt(plaintext, associatedData);
+    assertEquals(
+        CryptoFormat.NON_RAW_PREFIX_SIZE + 24 /* IV_SIZE */ + plaintext.length + 16 /* TAG_SIZE */,
+        ciphertext.length);
+  }
+
+  @Test
+  public void testNewKeyMultipleTimes() throws Exception {
+    KeyTemplate keyTemplate = AeadKeyTemplates.XCHACHA20_POLY1305;
+    XChaCha20Poly1305KeyManager keyManager = new XChaCha20Poly1305KeyManager();
+    Set<String> keys = new TreeSet<String>();
+    // Calls newKey multiple times and make sure that they generate different keys.
+    int numTests = 10;
+    for (int i = 0; i < numTests; i++) {
+      XChaCha20Poly1305Key key = (XChaCha20Poly1305Key) keyManager.newKey(keyTemplate.getValue());
+      keys.add(TestUtil.hexEncode(key.getKeyValue().toByteArray()));
+      assertEquals(32, key.getKeyValue().toByteArray().length);
+
+      KeyData keyData = keyManager.newKeyData(keyTemplate.getValue());
+      key = XChaCha20Poly1305Key.parseFrom(keyData.getValue());
+      keys.add(TestUtil.hexEncode(key.getKeyValue().toByteArray()));
+      assertEquals(32, key.getKeyValue().toByteArray().length);
+    }
+    assertEquals(numTests * 2, keys.size());
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/daead/AesSivKeyManagerTest.java b/java/src/test/java/com/google/crypto/tink/daead/AesSivKeyManagerTest.java
index 86930e6..4650439 100644
--- a/java/src/test/java/com/google/crypto/tink/daead/AesSivKeyManagerTest.java
+++ b/java/src/test/java/com/google/crypto/tink/daead/AesSivKeyManagerTest.java
@@ -67,7 +67,7 @@
   public void testCiphertextSize() throws Exception {
     for (KeyTemplate template : keyTemplates) {
       KeysetHandle keysetHandle = KeysetHandle.generateNew(template);
-      DeterministicAead daead = DeterministicAeadFactory.getPrimitive(keysetHandle);
+      DeterministicAead daead = keysetHandle.getPrimitive(DeterministicAead.class);
       byte[] plaintext = "plaintext".getBytes("UTF-8");
       byte[] associatedData = "associatedData".getBytes("UTF-8");
       byte[] ciphertext = daead.encryptDeterministically(plaintext, associatedData);
diff --git a/java/src/test/java/com/google/crypto/tink/daead/DeterministicAeadFactoryTest.java b/java/src/test/java/com/google/crypto/tink/daead/DeterministicAeadFactoryTest.java
index e319832..cc9ff55 100644
--- a/java/src/test/java/com/google/crypto/tink/daead/DeterministicAeadFactoryTest.java
+++ b/java/src/test/java/com/google/crypto/tink/daead/DeterministicAeadFactoryTest.java
@@ -216,7 +216,8 @@
       DeterministicAeadFactory.getPrimitive(keysetHandle);
       fail("Expected GeneralSecurityException");
     } catch (GeneralSecurityException e) {
-      assertExceptionContains(e, "invalid Deterministic AEAD key material");
+      assertExceptionContains(
+          e, "not match requested primitive type com.google.crypto.tink.DeterministicAead");
     }
 
     // invalid as the primary key.
@@ -225,7 +226,8 @@
       DeterministicAeadFactory.getPrimitive(keysetHandle);
       fail("Expected GeneralSecurityException");
     } catch (GeneralSecurityException e) {
-      assertExceptionContains(e, "invalid Deterministic AEAD key material");
+      assertExceptionContains(
+          e, "not match requested primitive type com.google.crypto.tink.DeterministicAead");
     }
   }
 }
diff --git a/java/src/test/java/com/google/crypto/tink/daead/DeterministicAeadIntegrationTest.java b/java/src/test/java/com/google/crypto/tink/daead/DeterministicAeadIntegrationTest.java
new file mode 100644
index 0000000..db36ac7
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/daead/DeterministicAeadIntegrationTest.java
@@ -0,0 +1,233 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.daead;
+
+import static com.google.crypto.tink.TestUtil.assertExceptionContains;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.crypto.tink.CryptoFormat;
+import com.google.crypto.tink.DeterministicAead;
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.aead.AeadConfig;
+import com.google.crypto.tink.proto.KeyStatusType;
+import com.google.crypto.tink.proto.Keyset.Key;
+import com.google.crypto.tink.proto.OutputPrefixType;
+import com.google.crypto.tink.subtle.Random;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import javax.crypto.Cipher;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests which run the everything for the DeterministicAead primitives. */
+@RunWith(JUnit4.class)
+public class DeterministicAeadIntegrationTest {
+  private Integer[] keySizeInBytes;
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    AeadConfig.register(); // need this for testInvalidKeyMaterial.
+    DeterministicAeadConfig.register();
+  }
+
+  @Before
+  public void setUp2() throws Exception {
+    if (Cipher.getMaxAllowedKeyLength("AES") < 256) {
+      System.out.println(
+          "Unlimited Strength Jurisdiction Policy Files are required"
+              + " but not installed. Skip all DeterministicAeadFactory tests");
+      keySizeInBytes = new Integer[] {};
+    } else {
+      keySizeInBytes = new Integer[] {64};
+    }
+  }
+
+  @Test
+  public void testEncrytDecrypt() throws Exception {
+    KeysetHandle keysetHandle = KeysetHandle.generateNew(DeterministicAeadKeyTemplates.AES256_SIV);
+    DeterministicAead aead = keysetHandle.getPrimitive(DeterministicAead.class);
+    byte[] plaintext = Random.randBytes(20);
+    byte[] associatedData = Random.randBytes(20);
+    byte[] ciphertext = aead.encryptDeterministically(plaintext, associatedData);
+    byte[] ciphertext2 = aead.encryptDeterministically(plaintext, associatedData);
+    byte[] decrypted = aead.decryptDeterministically(ciphertext, associatedData);
+    byte[] decrypted2 = aead.decryptDeterministically(ciphertext2, associatedData);
+
+    assertArrayEquals(ciphertext, ciphertext2);
+    assertArrayEquals(plaintext, decrypted);
+    assertArrayEquals(plaintext, decrypted2);
+  }
+
+  @Test
+  public void testMultipleKeys() throws Exception {
+    for (int keySize : keySizeInBytes) {
+      testMultipleKeys(keySize);
+    }
+  }
+
+  private static void testMultipleKeys(int keySize) throws Exception {
+    Key primary =
+        TestUtil.createKey(
+            TestUtil.createAesSivKeyData(keySize),
+            42,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.TINK);
+    Key raw =
+        TestUtil.createKey(
+            TestUtil.createAesSivKeyData(keySize), 43, KeyStatusType.ENABLED, OutputPrefixType.RAW);
+    Key legacy =
+        TestUtil.createKey(
+            TestUtil.createAesSivKeyData(keySize),
+            44,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.LEGACY);
+    Key tink =
+        TestUtil.createKey(
+            TestUtil.createAesSivKeyData(keySize),
+            45,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.TINK);
+    KeysetHandle keysetHandle =
+        TestUtil.createKeysetHandle(TestUtil.createKeyset(primary, raw, legacy, tink));
+
+    DeterministicAead daead = keysetHandle.getPrimitive(DeterministicAead.class);
+    byte[] plaintext = Random.randBytes(20);
+    byte[] associatedData = Random.randBytes(20);
+    byte[] ciphertext = daead.encryptDeterministically(plaintext, associatedData);
+    byte[] prefix = Arrays.copyOfRange(ciphertext, 0, CryptoFormat.NON_RAW_PREFIX_SIZE);
+    assertArrayEquals(prefix, CryptoFormat.getOutputPrefix(primary));
+    assertArrayEquals(plaintext, daead.decryptDeterministically(ciphertext, associatedData));
+    assertEquals(CryptoFormat.NON_RAW_PREFIX_SIZE + plaintext.length + 16, ciphertext.length);
+
+    // encrypt with a non-primary RAW key and decrypt with the keyset
+    KeysetHandle keysetHandle2 =
+        TestUtil.createKeysetHandle(TestUtil.createKeyset(raw, legacy, tink));
+    DeterministicAead daead2 = keysetHandle2.getPrimitive(DeterministicAead.class);
+    ciphertext = daead2.encryptDeterministically(plaintext, associatedData);
+    assertArrayEquals(plaintext, daead.decryptDeterministically(ciphertext, associatedData));
+
+    // encrypt with a random key not in the keyset, decrypt with the keyset should fail
+    Key random =
+        TestUtil.createKey(
+            TestUtil.createAesSivKeyData(keySize),
+            44,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.TINK);
+    keysetHandle2 = TestUtil.createKeysetHandle(TestUtil.createKeyset(random));
+    daead2 = keysetHandle2.getPrimitive(DeterministicAead.class);
+    ciphertext = daead2.encryptDeterministically(plaintext, associatedData);
+    try {
+      daead.decryptDeterministically(ciphertext, associatedData);
+      fail("Expected GeneralSecurityException");
+    } catch (GeneralSecurityException e) {
+      assertExceptionContains(e, "decryption failed");
+    }
+  }
+
+  @Test
+  public void testRawKeyAsPrimary() throws Exception {
+    for (int keySize : keySizeInBytes) {
+      testRawKeyAsPrimary(keySize);
+    }
+  }
+
+  private static void testRawKeyAsPrimary(int keySize) throws Exception {
+    Key primary =
+        TestUtil.createKey(
+            TestUtil.createAesSivKeyData(keySize), 42, KeyStatusType.ENABLED, OutputPrefixType.RAW);
+    Key raw =
+        TestUtil.createKey(
+            TestUtil.createAesSivKeyData(keySize), 43, KeyStatusType.ENABLED, OutputPrefixType.RAW);
+    Key legacy =
+        TestUtil.createKey(
+            TestUtil.createAesSivKeyData(keySize),
+            44,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.LEGACY);
+    KeysetHandle keysetHandle =
+        TestUtil.createKeysetHandle(TestUtil.createKeyset(primary, raw, legacy));
+
+    DeterministicAead daead = keysetHandle.getPrimitive(DeterministicAead.class);
+    byte[] plaintext = Random.randBytes(20);
+    byte[] associatedData = Random.randBytes(20);
+    byte[] ciphertext = daead.encryptDeterministically(plaintext, associatedData);
+
+    assertArrayEquals(plaintext, daead.decryptDeterministically(ciphertext, associatedData));
+    assertEquals(CryptoFormat.RAW_PREFIX_SIZE + plaintext.length + 16, ciphertext.length);
+  }
+
+  @Test
+  public void testSmallPlaintextWithRawKey() throws Exception {
+    for (int keySize : keySizeInBytes) {
+      testSmallPlaintextWithRawKey(keySize);
+    }
+  }
+
+  private static void testSmallPlaintextWithRawKey(int keySize) throws Exception {
+    Key primary =
+        TestUtil.createKey(
+            TestUtil.createAesSivKeyData(keySize), 42, KeyStatusType.ENABLED, OutputPrefixType.RAW);
+    KeysetHandle keysetHandle = TestUtil.createKeysetHandle(TestUtil.createKeyset(primary));
+
+    DeterministicAead daead = keysetHandle.getPrimitive(DeterministicAead.class);
+    byte[] plaintext = Random.randBytes(1);
+    byte[] associatedData = Random.randBytes(20);
+    byte[] ciphertext = daead.encryptDeterministically(plaintext, associatedData);
+
+    assertArrayEquals(plaintext, daead.decryptDeterministically(ciphertext, associatedData));
+    assertEquals(CryptoFormat.RAW_PREFIX_SIZE + plaintext.length + 16, ciphertext.length);
+  }
+
+  @Test
+  public void testInvalidKeyMaterial() throws Exception {
+    Key valid =
+        TestUtil.createKey(
+            TestUtil.createAesSivKeyData(64), 42, KeyStatusType.ENABLED, OutputPrefixType.TINK);
+    Key invalid =
+        TestUtil.createKey(
+            TestUtil.createAesCtrHmacAeadKeyData(
+                Random.randBytes(16), 12, Random.randBytes(16), 16),
+            43,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.RAW);
+
+    KeysetHandle keysetHandle = TestUtil.createKeysetHandle(TestUtil.createKeyset(valid, invalid));
+    try {
+      keysetHandle.getPrimitive(DeterministicAead.class);
+      fail("Expected GeneralSecurityException");
+    } catch (GeneralSecurityException e) {
+      assertExceptionContains(
+          e, "not match requested primitive type com.google.crypto.tink.DeterministicAead");
+    }
+
+    // invalid as the primary key.
+    keysetHandle = TestUtil.createKeysetHandle(TestUtil.createKeyset(invalid, valid));
+    try {
+      keysetHandle.getPrimitive(DeterministicAead.class);
+      fail("Expected GeneralSecurityException");
+    } catch (GeneralSecurityException e) {
+      assertExceptionContains(
+          e, "not match requested primitive type com.google.crypto.tink.DeterministicAead");
+    }
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/daead/DeterministicAeadWrapperTest.java b/java/src/test/java/com/google/crypto/tink/daead/DeterministicAeadWrapperTest.java
new file mode 100644
index 0000000..23d40b5
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/daead/DeterministicAeadWrapperTest.java
@@ -0,0 +1,204 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.daead;
+
+import static com.google.crypto.tink.TestUtil.assertExceptionContains;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.crypto.tink.CryptoFormat;
+import com.google.crypto.tink.DeterministicAead;
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.Registry;
+import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.proto.KeyStatusType;
+import com.google.crypto.tink.proto.Keyset.Key;
+import com.google.crypto.tink.proto.OutputPrefixType;
+import com.google.crypto.tink.subtle.Random;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import javax.crypto.Cipher;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for DeterministicAeadWrapper. */
+@RunWith(JUnit4.class)
+public class DeterministicAeadWrapperTest {
+  private Integer[] keySizeInBytes;
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    DeterministicAeadConfig.register();
+  }
+
+  @Before
+  public void setUp2() throws Exception {
+    if (Cipher.getMaxAllowedKeyLength("AES") < 256) {
+      System.out.println(
+          "Unlimited Strength Jurisdiction Policy Files are required"
+              + " but not installed. Skip all DeterministicAeadFactory tests");
+      keySizeInBytes = new Integer[] {};
+    } else {
+      keySizeInBytes = new Integer[] {64};
+    }
+  }
+
+  @Test
+  public void testEncrytDecrypt() throws Exception {
+    KeysetHandle keysetHandle = KeysetHandle.generateNew(DeterministicAeadKeyTemplates.AES256_SIV);
+    DeterministicAead aead =
+        new DeterministicAeadWrapper().wrap(Registry.getPrimitives(keysetHandle));
+    byte[] plaintext = Random.randBytes(20);
+    byte[] associatedData = Random.randBytes(20);
+    byte[] ciphertext = aead.encryptDeterministically(plaintext, associatedData);
+    byte[] ciphertext2 = aead.encryptDeterministically(plaintext, associatedData);
+    byte[] decrypted = aead.decryptDeterministically(ciphertext, associatedData);
+    byte[] decrypted2 = aead.decryptDeterministically(ciphertext2, associatedData);
+
+    assertArrayEquals(ciphertext, ciphertext2);
+    assertArrayEquals(plaintext, decrypted);
+    assertArrayEquals(plaintext, decrypted2);
+  }
+
+  @Test
+  public void testMultipleKeys() throws Exception {
+    for (int keySize : keySizeInBytes) {
+      testMultipleKeys(keySize);
+    }
+  }
+
+  private static void testMultipleKeys(int keySize) throws Exception {
+    Key primary =
+        TestUtil.createKey(
+            TestUtil.createAesSivKeyData(keySize),
+            42,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.TINK);
+    Key raw =
+        TestUtil.createKey(
+            TestUtil.createAesSivKeyData(keySize), 43, KeyStatusType.ENABLED, OutputPrefixType.RAW);
+    Key legacy =
+        TestUtil.createKey(
+            TestUtil.createAesSivKeyData(keySize),
+            44,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.LEGACY);
+    Key tink =
+        TestUtil.createKey(
+            TestUtil.createAesSivKeyData(keySize),
+            45,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.TINK);
+    KeysetHandle keysetHandle =
+        TestUtil.createKeysetHandle(TestUtil.createKeyset(primary, raw, legacy, tink));
+
+    DeterministicAead daead =
+        new DeterministicAeadWrapper().wrap(Registry.getPrimitives(keysetHandle));
+    byte[] plaintext = Random.randBytes(20);
+    byte[] associatedData = Random.randBytes(20);
+    byte[] ciphertext = daead.encryptDeterministically(plaintext, associatedData);
+    byte[] prefix = Arrays.copyOfRange(ciphertext, 0, CryptoFormat.NON_RAW_PREFIX_SIZE);
+    assertArrayEquals(prefix, CryptoFormat.getOutputPrefix(primary));
+    assertArrayEquals(plaintext, daead.decryptDeterministically(ciphertext, associatedData));
+    assertEquals(CryptoFormat.NON_RAW_PREFIX_SIZE + plaintext.length + 16, ciphertext.length);
+
+    // encrypt with a non-primary RAW key and decrypt with the keyset
+    KeysetHandle keysetHandle2 =
+        TestUtil.createKeysetHandle(TestUtil.createKeyset(raw, legacy, tink));
+    DeterministicAead daead2 =
+        new DeterministicAeadWrapper().wrap(Registry.getPrimitives(keysetHandle2));
+    ciphertext = daead2.encryptDeterministically(plaintext, associatedData);
+    assertArrayEquals(plaintext, daead.decryptDeterministically(ciphertext, associatedData));
+
+    // encrypt with a random key not in the keyset, decrypt with the keyset should fail
+    Key random =
+        TestUtil.createKey(
+            TestUtil.createAesSivKeyData(keySize),
+            44,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.TINK);
+    keysetHandle2 = TestUtil.createKeysetHandle(TestUtil.createKeyset(random));
+    daead2 = new DeterministicAeadWrapper().wrap(Registry.getPrimitives(keysetHandle2));
+    ciphertext = daead2.encryptDeterministically(plaintext, associatedData);
+    try {
+      daead.decryptDeterministically(ciphertext, associatedData);
+      fail("Expected GeneralSecurityException");
+    } catch (GeneralSecurityException e) {
+      assertExceptionContains(e, "decryption failed");
+    }
+  }
+
+  @Test
+  public void testRawKeyAsPrimary() throws Exception {
+    for (int keySize : keySizeInBytes) {
+      testRawKeyAsPrimary(keySize);
+    }
+  }
+
+  private static void testRawKeyAsPrimary(int keySize) throws Exception {
+    Key primary =
+        TestUtil.createKey(
+            TestUtil.createAesSivKeyData(keySize), 42, KeyStatusType.ENABLED, OutputPrefixType.RAW);
+    Key raw =
+        TestUtil.createKey(
+            TestUtil.createAesSivKeyData(keySize), 43, KeyStatusType.ENABLED, OutputPrefixType.RAW);
+    Key legacy =
+        TestUtil.createKey(
+            TestUtil.createAesSivKeyData(keySize),
+            44,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.LEGACY);
+    KeysetHandle keysetHandle =
+        TestUtil.createKeysetHandle(TestUtil.createKeyset(primary, raw, legacy));
+
+    DeterministicAead daead =
+        new DeterministicAeadWrapper().wrap(Registry.getPrimitives(keysetHandle));
+    byte[] plaintext = Random.randBytes(20);
+    byte[] associatedData = Random.randBytes(20);
+    byte[] ciphertext = daead.encryptDeterministically(plaintext, associatedData);
+
+    assertArrayEquals(plaintext, daead.decryptDeterministically(ciphertext, associatedData));
+    assertEquals(CryptoFormat.RAW_PREFIX_SIZE + plaintext.length + 16, ciphertext.length);
+  }
+
+  @Test
+  public void testSmallPlaintextWithRawKey() throws Exception {
+    for (int keySize : keySizeInBytes) {
+      testSmallPlaintextWithRawKey(keySize);
+    }
+  }
+
+  private static void testSmallPlaintextWithRawKey(int keySize) throws Exception {
+    Key primary =
+        TestUtil.createKey(
+            TestUtil.createAesSivKeyData(keySize), 42, KeyStatusType.ENABLED, OutputPrefixType.RAW);
+    KeysetHandle keysetHandle = TestUtil.createKeysetHandle(TestUtil.createKeyset(primary));
+
+    DeterministicAead daead =
+        new DeterministicAeadWrapper().wrap(Registry.getPrimitives(keysetHandle));
+    byte[] plaintext = Random.randBytes(1);
+    byte[] associatedData = Random.randBytes(20);
+    byte[] ciphertext = daead.encryptDeterministically(plaintext, associatedData);
+
+    assertArrayEquals(plaintext, daead.decryptDeterministically(ciphertext, associatedData));
+    assertEquals(CryptoFormat.RAW_PREFIX_SIZE + plaintext.length + 16, ciphertext.length);
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/hybrid/EciesAeadHkdfHybridDecryptTest.java b/java/src/test/java/com/google/crypto/tink/hybrid/EciesAeadHkdfHybridDecryptTest.java
index 8fdec9e..f913cf1 100644
--- a/java/src/test/java/com/google/crypto/tink/hybrid/EciesAeadHkdfHybridDecryptTest.java
+++ b/java/src/test/java/com/google/crypto/tink/hybrid/EciesAeadHkdfHybridDecryptTest.java
@@ -22,6 +22,8 @@
 import com.google.crypto.tink.Config;
 import com.google.crypto.tink.HybridDecrypt;
 import com.google.crypto.tink.HybridEncrypt;
+import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.TestUtil.BytesMutation;
 import com.google.crypto.tink.aead.AeadKeyTemplates;
 import com.google.crypto.tink.proto.HashType;
 import com.google.crypto.tink.proto.KeyTemplate;
@@ -29,6 +31,7 @@
 import com.google.crypto.tink.subtle.EciesAeadHkdfHybridEncrypt;
 import com.google.crypto.tink.subtle.EllipticCurves;
 import com.google.crypto.tink.subtle.EllipticCurves.CurveType;
+import com.google.crypto.tink.subtle.Hex;
 import com.google.crypto.tink.subtle.Random;
 import java.security.GeneralSecurityException;
 import java.security.KeyPair;
@@ -75,32 +78,32 @@
 
     assertArrayEquals(plaintext, decrypted);
 
-    // Changes each bit of ciphertext and makes sure that the decryption failed. This test
-    // implicitly checks the modification of public key and the raw ciphertext.
-    for (int bytes = 0; bytes < ciphertext.length; bytes++) {
-      for (int bit = 0; bit < 8; bit++) {
-        byte[] modifiedCiphertext = Arrays.copyOf(ciphertext, ciphertext.length);
-        modifiedCiphertext[bytes] ^= (byte) (1 << bit);
-        try {
-          hybridDecrypt.decrypt(modifiedCiphertext, context);
-          fail("Invalid ciphertext, should have thrown exception");
-        } catch (GeneralSecurityException expected) {
-          // Expected
-        }
+    // Modifies ciphertext and makes sure that the decryption failed. This test implicitly checks
+    // the modification of public key and the raw ciphertext.
+    for (BytesMutation mutation : TestUtil.generateMutations(ciphertext)) {
+      try {
+        hybridDecrypt.decrypt(mutation.value, context);
+        fail(
+            String.format(
+                "Invalid ciphertext, should have thrown exception: ciphertext = %s,context = %s,"
+                    + " description = %s",
+                Hex.encode(mutation.value), Hex.encode(context), mutation.description));
+      } catch (GeneralSecurityException expected) {
+        // Expected
       }
     }
 
     // Modify context.
-    for (int bytes = 0; bytes < context.length; bytes++) {
-      for (int bit = 0; bit < 8; bit++) {
-        byte[] modifiedContext = Arrays.copyOf(context, context.length);
-        modifiedContext[bytes] ^= (byte) (1 << bit);
-        try {
-          hybridDecrypt.decrypt(ciphertext, modifiedContext);
-          fail("Invalid context, should have thrown exception");
-        } catch (GeneralSecurityException expected) {
-          // Expected
-        }
+    for (BytesMutation mutation : TestUtil.generateMutations(context)) {
+      try {
+        hybridDecrypt.decrypt(ciphertext, mutation.value);
+        fail(
+            String.format(
+                "Invalid context, should have thrown exception: context = %s, ciphertext = %s,"
+                    + " description = %s",
+                Hex.encode(mutation.value), Hex.encode(ciphertext), mutation.description));
+      } catch (GeneralSecurityException expected) {
+        // Expected
       }
     }
 
diff --git a/java/src/test/java/com/google/crypto/tink/hybrid/EciesAeadHkdfHybridEncryptTest.java b/java/src/test/java/com/google/crypto/tink/hybrid/EciesAeadHkdfHybridEncryptTest.java
index 92c5613..53c101a 100644
--- a/java/src/test/java/com/google/crypto/tink/hybrid/EciesAeadHkdfHybridEncryptTest.java
+++ b/java/src/test/java/com/google/crypto/tink/hybrid/EciesAeadHkdfHybridEncryptTest.java
@@ -22,6 +22,7 @@
 import com.google.crypto.tink.Config;
 import com.google.crypto.tink.HybridDecrypt;
 import com.google.crypto.tink.HybridEncrypt;
+import com.google.crypto.tink.TestUtil;
 import com.google.crypto.tink.aead.AeadKeyTemplates;
 import com.google.crypto.tink.proto.HashType;
 import com.google.crypto.tink.proto.KeyTemplate;
@@ -97,8 +98,10 @@
     testBasicMultipleEncrypts(CurveType.NIST_P384, AeadKeyTemplates.AES128_CTR_HMAC_SHA256);
     testBasicMultipleEncrypts(CurveType.NIST_P521, AeadKeyTemplates.AES128_CTR_HMAC_SHA256);
 
-    testBasicMultipleEncrypts(CurveType.NIST_P256, AeadKeyTemplates.AES128_CTR_HMAC_SHA256);
-    testBasicMultipleEncrypts(CurveType.NIST_P384, AeadKeyTemplates.AES128_CTR_HMAC_SHA256);
-    testBasicMultipleEncrypts(CurveType.NIST_P521, AeadKeyTemplates.AES128_CTR_HMAC_SHA256);
+    if (!TestUtil.isAndroid()) {
+      testBasicMultipleEncrypts(CurveType.NIST_P256, AeadKeyTemplates.AES128_GCM);
+      testBasicMultipleEncrypts(CurveType.NIST_P384, AeadKeyTemplates.AES128_GCM);
+      testBasicMultipleEncrypts(CurveType.NIST_P521, AeadKeyTemplates.AES128_GCM);
+    }
   }
 }
diff --git a/java/src/test/java/com/google/crypto/tink/hybrid/HybridConfigTest.java b/java/src/test/java/com/google/crypto/tink/hybrid/HybridConfigTest.java
index c88629c..c98eaab 100644
--- a/java/src/test/java/com/google/crypto/tink/hybrid/HybridConfigTest.java
+++ b/java/src/test/java/com/google/crypto/tink/hybrid/HybridConfigTest.java
@@ -230,7 +230,7 @@
   @Test
   public void testConfigContents_LATEST() throws Exception {
     RegistryConfig config = HybridConfig.LATEST;
-    assertEquals(9, config.getEntryCount());
+    assertEquals(10, config.getEntryCount());
     assertEquals("TINK_HYBRID", config.getConfigName());
 
     TestUtil.verifyConfigEntry(
@@ -284,13 +284,20 @@
         0);
     TestUtil.verifyConfigEntry(
         config.getEntry(7),
+        "TinkAead",
+        "Aead",
+        "type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key",
+        true,
+        0);
+    TestUtil.verifyConfigEntry(
+        config.getEntry(8),
         "TinkHybridDecrypt",
         "HybridDecrypt",
         "type.googleapis.com/google.crypto.tink.EciesAeadHkdfPrivateKey",
         true,
         0);
     TestUtil.verifyConfigEntry(
-        config.getEntry(8),
+        config.getEntry(9),
         "TinkHybridEncrypt",
         "HybridEncrypt",
         "type.googleapis.com/google.crypto.tink.EciesAeadHkdfPublicKey",
diff --git a/java/src/test/java/com/google/crypto/tink/hybrid/HybridEncryptIntegrationTest.java b/java/src/test/java/com/google/crypto/tink/hybrid/HybridEncryptIntegrationTest.java
new file mode 100644
index 0000000..f71fb21
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/hybrid/HybridEncryptIntegrationTest.java
@@ -0,0 +1,117 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.hybrid;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import com.google.crypto.tink.HybridDecrypt;
+import com.google.crypto.tink.HybridEncrypt;
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.aead.AeadKeyTemplates;
+import com.google.crypto.tink.proto.EcPointFormat;
+import com.google.crypto.tink.proto.EciesAeadHkdfPrivateKey;
+import com.google.crypto.tink.proto.EllipticCurveType;
+import com.google.crypto.tink.proto.HashType;
+import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyStatusType;
+import com.google.crypto.tink.proto.KeyTemplate;
+import com.google.crypto.tink.proto.Keyset.Key;
+import com.google.crypto.tink.proto.OutputPrefixType;
+import com.google.crypto.tink.subtle.Random;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests which run the everything for the Hybrid primitives. */
+@RunWith(JUnit4.class)
+public class HybridEncryptIntegrationTest {
+  @BeforeClass
+  public static void setUp() throws Exception {
+    HybridConfig.register();
+  }
+
+  @Test
+  public void testBasicEncryption() throws Exception {
+    EllipticCurveType curve = EllipticCurveType.NIST_P384;
+    HashType hashType = HashType.SHA256;
+    EcPointFormat primaryPointFormat = EcPointFormat.UNCOMPRESSED;
+    EcPointFormat rawPointFormat = EcPointFormat.COMPRESSED;
+    KeyTemplate primaryDemKeyTemplate = AeadKeyTemplates.AES128_CTR_HMAC_SHA256;
+
+    KeyTemplate rawDemKeyTemplate = AeadKeyTemplates.AES128_CTR_HMAC_SHA256;
+    byte[] primarySalt = "some salt".getBytes("UTF-8");
+    byte[] rawSalt = "other salt".getBytes("UTF-8");
+
+    EciesAeadHkdfPrivateKey primaryPrivProto =
+        TestUtil.generateEciesAeadHkdfPrivKey(
+            curve, hashType, primaryPointFormat, primaryDemKeyTemplate, primarySalt);
+
+    Key primaryPriv =
+        TestUtil.createKey(
+            TestUtil.createKeyData(
+                primaryPrivProto,
+                EciesAeadHkdfPrivateKeyManager.TYPE_URL,
+                KeyData.KeyMaterialType.ASYMMETRIC_PRIVATE),
+            8,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.RAW);
+    Key primaryPub =
+        TestUtil.createKey(
+            TestUtil.createKeyData(
+                primaryPrivProto.getPublicKey(),
+                EciesAeadHkdfPublicKeyManager.TYPE_URL,
+                KeyData.KeyMaterialType.ASYMMETRIC_PUBLIC),
+            42,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.RAW);
+
+    EciesAeadHkdfPrivateKey rawPrivProto =
+        TestUtil.generateEciesAeadHkdfPrivKey(
+            curve, hashType, rawPointFormat, rawDemKeyTemplate, rawSalt);
+
+    Key rawPriv =
+        TestUtil.createKey(
+            TestUtil.createKeyData(
+                rawPrivProto,
+                EciesAeadHkdfPrivateKeyManager.TYPE_URL,
+                KeyData.KeyMaterialType.ASYMMETRIC_PRIVATE),
+            11,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.RAW);
+    Key rawPub =
+        TestUtil.createKey(
+            TestUtil.createKeyData(
+                rawPrivProto.getPublicKey(),
+                EciesAeadHkdfPublicKeyManager.TYPE_URL,
+                KeyData.KeyMaterialType.ASYMMETRIC_PUBLIC),
+            43,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.RAW);
+    KeysetHandle keysetHandlePub =
+        TestUtil.createKeysetHandle(TestUtil.createKeyset(primaryPub, rawPub));
+    KeysetHandle keysetHandlePriv =
+        TestUtil.createKeysetHandle(TestUtil.createKeyset(primaryPriv, rawPriv));
+    HybridEncrypt hybridEncrypt = keysetHandlePub.getPrimitive(HybridEncrypt.class);
+    HybridDecrypt hybridDecrypt = keysetHandlePriv.getPrimitive(HybridDecrypt.class);
+    byte[] plaintext = Random.randBytes(20);
+    byte[] contextInfo = Random.randBytes(20);
+    byte[] ciphertext = hybridEncrypt.encrypt(plaintext, contextInfo);
+    assertArrayEquals(plaintext, hybridDecrypt.decrypt(ciphertext, contextInfo));
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/hybrid/HybridEncryptWrapperTest.java b/java/src/test/java/com/google/crypto/tink/hybrid/HybridEncryptWrapperTest.java
new file mode 100644
index 0000000..908fe6d
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/hybrid/HybridEncryptWrapperTest.java
@@ -0,0 +1,122 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.hybrid;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import com.google.crypto.tink.HybridDecrypt;
+import com.google.crypto.tink.HybridEncrypt;
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.Registry;
+import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.aead.AeadKeyTemplates;
+import com.google.crypto.tink.daead.DeterministicAeadConfig;
+import com.google.crypto.tink.proto.EcPointFormat;
+import com.google.crypto.tink.proto.EciesAeadHkdfPrivateKey;
+import com.google.crypto.tink.proto.EllipticCurveType;
+import com.google.crypto.tink.proto.HashType;
+import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyStatusType;
+import com.google.crypto.tink.proto.KeyTemplate;
+import com.google.crypto.tink.proto.Keyset.Key;
+import com.google.crypto.tink.proto.OutputPrefixType;
+import com.google.crypto.tink.subtle.Random;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for HybridEncryptWrapper. */
+@RunWith(JUnit4.class)
+public class HybridEncryptWrapperTest {
+  @BeforeClass
+  public static void setUp() throws Exception {
+    HybridConfig.register();
+    DeterministicAeadConfig.register(); // need this for testInvalidKeyMaterial.
+  }
+
+  @Test
+  public void testBasicEncryption() throws Exception {
+    EllipticCurveType curve = EllipticCurveType.NIST_P384;
+    HashType hashType = HashType.SHA256;
+    EcPointFormat primaryPointFormat = EcPointFormat.UNCOMPRESSED;
+    EcPointFormat rawPointFormat = EcPointFormat.COMPRESSED;
+    KeyTemplate primaryDemKeyTemplate = AeadKeyTemplates.AES128_CTR_HMAC_SHA256;
+
+    KeyTemplate rawDemKeyTemplate = AeadKeyTemplates.AES128_CTR_HMAC_SHA256;
+    byte[] primarySalt = "some salt".getBytes("UTF-8");
+    byte[] rawSalt = "other salt".getBytes("UTF-8");
+
+    EciesAeadHkdfPrivateKey primaryPrivProto =
+        TestUtil.generateEciesAeadHkdfPrivKey(
+            curve, hashType, primaryPointFormat, primaryDemKeyTemplate, primarySalt);
+
+    Key primaryPriv =
+        TestUtil.createKey(
+            TestUtil.createKeyData(
+                primaryPrivProto,
+                EciesAeadHkdfPrivateKeyManager.TYPE_URL,
+                KeyData.KeyMaterialType.ASYMMETRIC_PRIVATE),
+            8,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.RAW);
+    Key primaryPub =
+        TestUtil.createKey(
+            TestUtil.createKeyData(
+                primaryPrivProto.getPublicKey(),
+                EciesAeadHkdfPublicKeyManager.TYPE_URL,
+                KeyData.KeyMaterialType.ASYMMETRIC_PUBLIC),
+            42,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.RAW);
+
+    EciesAeadHkdfPrivateKey rawPrivProto =
+        TestUtil.generateEciesAeadHkdfPrivKey(
+            curve, hashType, rawPointFormat, rawDemKeyTemplate, rawSalt);
+
+    Key rawPriv =
+        TestUtil.createKey(
+            TestUtil.createKeyData(
+                rawPrivProto,
+                EciesAeadHkdfPrivateKeyManager.TYPE_URL,
+                KeyData.KeyMaterialType.ASYMMETRIC_PRIVATE),
+            11,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.RAW);
+    Key rawPub =
+        TestUtil.createKey(
+            TestUtil.createKeyData(
+                rawPrivProto.getPublicKey(),
+                EciesAeadHkdfPublicKeyManager.TYPE_URL,
+                KeyData.KeyMaterialType.ASYMMETRIC_PUBLIC),
+            43,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.RAW);
+    KeysetHandle keysetHandlePub =
+        TestUtil.createKeysetHandle(TestUtil.createKeyset(primaryPub, rawPub));
+    KeysetHandle keysetHandlePriv =
+        TestUtil.createKeysetHandle(TestUtil.createKeyset(primaryPriv, rawPriv));
+    HybridEncrypt hybridEncrypt =
+        new HybridEncryptWrapper().wrap(Registry.getPrimitives(keysetHandlePub));
+    HybridDecrypt hybridDecrypt =
+        new HybridDecryptWrapper().wrap(Registry.getPrimitives(keysetHandlePriv));
+    byte[] plaintext = Random.randBytes(20);
+    byte[] contextInfo = Random.randBytes(20);
+    byte[] ciphertext = hybridEncrypt.encrypt(plaintext, contextInfo);
+    assertArrayEquals(plaintext, hybridDecrypt.decrypt(ciphertext, contextInfo));
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/mac/MacFactoryTest.java b/java/src/test/java/com/google/crypto/tink/mac/MacFactoryTest.java
index fedaf68..cae2de0 100644
--- a/java/src/test/java/com/google/crypto/tink/mac/MacFactoryTest.java
+++ b/java/src/test/java/com/google/crypto/tink/mac/MacFactoryTest.java
@@ -182,7 +182,7 @@
       MacFactory.getPrimitive(keysetHandle);
       fail("Expected GeneralSecurityException");
     } catch (GeneralSecurityException e) {
-      assertExceptionContains(e, "invalid MAC key material");
+      assertExceptionContains(e, "not match requested primitive type com.google.crypto.tink.Mac");
     }
 
     // invalid as the primary key.
@@ -191,7 +191,7 @@
       MacFactory.getPrimitive(keysetHandle);
       fail("Expected GeneralSecurityException");
     } catch (GeneralSecurityException e) {
-      assertExceptionContains(e, "invalid MAC key material");
+      assertExceptionContains(e, "not match requested primitive type com.google.crypto.tink.Mac");
     }
   }
 }
diff --git a/java/src/test/java/com/google/crypto/tink/mac/MacIntegrationTest.java b/java/src/test/java/com/google/crypto/tink/mac/MacIntegrationTest.java
new file mode 100644
index 0000000..ddfa9f8
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/mac/MacIntegrationTest.java
@@ -0,0 +1,162 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.mac;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.crypto.tink.CryptoFormat;
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.Mac;
+import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.proto.KeyStatusType;
+import com.google.crypto.tink.proto.Keyset.Key;
+import com.google.crypto.tink.proto.OutputPrefixType;
+import com.google.crypto.tink.subtle.Bytes;
+import com.google.crypto.tink.subtle.Random;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests which run the everything for Macs. */
+@RunWith(JUnit4.class)
+public class MacIntegrationTest {
+  private static final int HMAC_KEY_SIZE = 20;
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    MacConfig.register();
+  }
+
+  @Test
+  public void testMultipleKeys() throws Exception {
+    byte[] keyValue = Random.randBytes(HMAC_KEY_SIZE);
+    Key tink = TestUtil.createKey(
+          TestUtil.createHmacKeyData(keyValue, 16),
+          42,
+          KeyStatusType.ENABLED,
+          OutputPrefixType.TINK);
+    Key legacy = TestUtil.createKey(
+          TestUtil.createHmacKeyData(keyValue, 16),
+          43,
+          KeyStatusType.ENABLED,
+          OutputPrefixType.LEGACY);
+    Key raw = TestUtil.createKey(
+          TestUtil.createHmacKeyData(keyValue, 16),
+          44,
+          KeyStatusType.ENABLED,
+          OutputPrefixType.RAW);
+    Key crunchy = TestUtil.createKey(
+          TestUtil.createHmacKeyData(keyValue, 16),
+          45,
+          KeyStatusType.ENABLED,
+          OutputPrefixType.CRUNCHY);
+    Key[] keys = new Key[] {tink, legacy, raw, crunchy};
+    int j = keys.length;
+    for (int i = 0; i < j; i++) {
+      KeysetHandle keysetHandle = TestUtil.createKeysetHandle(
+        TestUtil.createKeyset(
+            keys[i],
+            keys[(i + 1) % j],
+            keys[(i + 2) % j],
+            keys[(i + 3) % j]));
+      Mac mac = keysetHandle.getPrimitive(Mac.class);
+      byte[] plaintext = "plaintext".getBytes("UTF-8");
+      byte[] tag = mac.computeMac(plaintext);
+      if (!keys[i].getOutputPrefixType().equals(OutputPrefixType.RAW)) {
+        byte[] prefix = Arrays.copyOfRange(tag, 0, CryptoFormat.NON_RAW_PREFIX_SIZE);
+        assertArrayEquals(prefix, CryptoFormat.getOutputPrefix(keys[i]));
+      }
+      try {
+        mac.verifyMac(tag, plaintext);
+      } catch (GeneralSecurityException e) {
+        fail("Valid MAC, should not throw exception: " + i);
+      }
+
+      // Modify plaintext or tag and make sure the verifyMac failed.
+      byte[] plaintextAndTag = Bytes.concat(plaintext, tag);
+      for (int b = 0; b < plaintextAndTag.length; b++) {
+        for (int bit = 0; bit < 8; bit++) {
+          byte[] modified = Arrays.copyOf(plaintextAndTag, plaintextAndTag.length);
+          modified[b] ^= (byte) (1 << bit);
+          try {
+            mac.verifyMac(Arrays.copyOfRange(modified, plaintext.length, modified.length),
+                Arrays.copyOfRange(modified, 0, plaintext.length));
+            fail("Invalid tag or plaintext, should have thrown exception");
+          } catch (GeneralSecurityException expected) {
+            // Expected
+          }
+        }
+      }
+
+      // mac with a non-primary RAW key, verify with the keyset
+      KeysetHandle keysetHandle2 = TestUtil.createKeysetHandle(
+          TestUtil.createKeyset(raw, legacy, tink, crunchy));
+      Mac mac2 = keysetHandle2.getPrimitive(Mac.class);
+      tag = mac2.computeMac(plaintext);
+      try {
+        mac.verifyMac(tag, plaintext);
+      } catch (GeneralSecurityException e) {
+        fail("Valid MAC, should not throw exception");
+      }
+
+      // mac with a random key not in the keyset, verify with the keyset should fail
+      byte[] keyValue2 = Random.randBytes(HMAC_KEY_SIZE);
+      Key random = TestUtil.createKey(
+          TestUtil.createHmacKeyData(keyValue2, 16),
+          44,
+          KeyStatusType.ENABLED,
+          OutputPrefixType.TINK);
+      keysetHandle2 = TestUtil.createKeysetHandle(
+          TestUtil.createKeyset(random));
+      mac2 = keysetHandle2.getPrimitive(Mac.class);
+      tag = mac2.computeMac(plaintext);
+      try {
+        mac.verifyMac(tag, plaintext);
+        fail("Invalid MAC MAC, should have thrown exception");
+      } catch (GeneralSecurityException expected) {
+        // Expected
+      }
+    }
+  }
+
+  @Test
+  public void testSmallPlaintextWithRawKey() throws Exception {
+    byte[] keyValue = Random.randBytes(HMAC_KEY_SIZE);
+    Key primary = TestUtil.createKey(
+        TestUtil.createHmacKeyData(keyValue, 16),
+        42,
+        KeyStatusType.ENABLED,
+        OutputPrefixType.RAW);
+    KeysetHandle keysetHandle = TestUtil.createKeysetHandle(
+        TestUtil.createKeyset(primary));
+    Mac mac = keysetHandle.getPrimitive(Mac.class);
+    byte[] plaintext = "blah".getBytes("UTF-8");
+    byte[] tag = mac.computeMac(plaintext);
+    // no prefix
+    assertEquals(16 /* TAG */, tag.length);
+    try {
+      mac.verifyMac(tag, plaintext);
+    } catch (GeneralSecurityException e) {
+      fail("Valid MAC, should not throw exception");
+    }
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/mac/MacWrapperTest.java b/java/src/test/java/com/google/crypto/tink/mac/MacWrapperTest.java
new file mode 100644
index 0000000..5838f5e
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/mac/MacWrapperTest.java
@@ -0,0 +1,165 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.mac;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.crypto.tink.CryptoFormat;
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.Mac;
+import com.google.crypto.tink.Registry;
+import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.daead.DeterministicAeadConfig;
+import com.google.crypto.tink.proto.KeyStatusType;
+import com.google.crypto.tink.proto.Keyset.Key;
+import com.google.crypto.tink.proto.OutputPrefixType;
+import com.google.crypto.tink.subtle.Bytes;
+import com.google.crypto.tink.subtle.Random;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for MacFactory. */
+@RunWith(JUnit4.class)
+public class MacWrapperTest {
+  private static final int HMAC_KEY_SIZE = 20;
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    MacConfig.register();
+    DeterministicAeadConfig.register(); // need this for testInvalidKeyMaterial.
+  }
+
+  @Test
+  public void testMultipleKeys() throws Exception {
+    byte[] keyValue = Random.randBytes(HMAC_KEY_SIZE);
+    Key tink = TestUtil.createKey(
+          TestUtil.createHmacKeyData(keyValue, 16),
+          42,
+          KeyStatusType.ENABLED,
+          OutputPrefixType.TINK);
+    Key legacy = TestUtil.createKey(
+          TestUtil.createHmacKeyData(keyValue, 16),
+          43,
+          KeyStatusType.ENABLED,
+          OutputPrefixType.LEGACY);
+    Key raw = TestUtil.createKey(
+          TestUtil.createHmacKeyData(keyValue, 16),
+          44,
+          KeyStatusType.ENABLED,
+          OutputPrefixType.RAW);
+    Key crunchy = TestUtil.createKey(
+          TestUtil.createHmacKeyData(keyValue, 16),
+          45,
+          KeyStatusType.ENABLED,
+          OutputPrefixType.CRUNCHY);
+    Key[] keys = new Key[] {tink, legacy, raw, crunchy};
+    int j = keys.length;
+    for (int i = 0; i < j; i++) {
+      KeysetHandle keysetHandle = TestUtil.createKeysetHandle(
+        TestUtil.createKeyset(
+            keys[i],
+            keys[(i + 1) % j],
+            keys[(i + 2) % j],
+            keys[(i + 3) % j]));
+      Mac mac = new MacWrapper().wrap(Registry.getPrimitives(keysetHandle));
+      byte[] plaintext = "plaintext".getBytes("UTF-8");
+      byte[] tag = mac.computeMac(plaintext);
+      if (!keys[i].getOutputPrefixType().equals(OutputPrefixType.RAW)) {
+        byte[] prefix = Arrays.copyOfRange(tag, 0, CryptoFormat.NON_RAW_PREFIX_SIZE);
+        assertArrayEquals(prefix, CryptoFormat.getOutputPrefix(keys[i]));
+      }
+      try {
+        mac.verifyMac(tag, plaintext);
+      } catch (GeneralSecurityException e) {
+        fail("Valid MAC, should not throw exception: " + i);
+      }
+
+      // Modify plaintext or tag and make sure the verifyMac failed.
+      byte[] plaintextAndTag = Bytes.concat(plaintext, tag);
+      for (int b = 0; b < plaintextAndTag.length; b++) {
+        for (int bit = 0; bit < 8; bit++) {
+          byte[] modified = Arrays.copyOf(plaintextAndTag, plaintextAndTag.length);
+          modified[b] ^= (byte) (1 << bit);
+          try {
+            mac.verifyMac(Arrays.copyOfRange(modified, plaintext.length, modified.length),
+                Arrays.copyOfRange(modified, 0, plaintext.length));
+            fail("Invalid tag or plaintext, should have thrown exception");
+          } catch (GeneralSecurityException expected) {
+            // Expected
+          }
+        }
+      }
+
+      // mac with a non-primary RAW key, verify with the keyset
+      KeysetHandle keysetHandle2 = TestUtil.createKeysetHandle(
+          TestUtil.createKeyset(raw, legacy, tink, crunchy));
+      Mac mac2 = new MacWrapper().wrap(Registry.getPrimitives(keysetHandle2));
+      tag = mac2.computeMac(plaintext);
+      try {
+        mac.verifyMac(tag, plaintext);
+      } catch (GeneralSecurityException e) {
+        fail("Valid MAC, should not throw exception");
+      }
+
+      // mac with a random key not in the keyset, verify with the keyset should fail
+      byte[] keyValue2 = Random.randBytes(HMAC_KEY_SIZE);
+      Key random = TestUtil.createKey(
+          TestUtil.createHmacKeyData(keyValue2, 16),
+          44,
+          KeyStatusType.ENABLED,
+          OutputPrefixType.TINK);
+      keysetHandle2 = TestUtil.createKeysetHandle(
+          TestUtil.createKeyset(random));
+      mac2 = new MacWrapper().wrap(Registry.getPrimitives(keysetHandle2));
+      tag = mac2.computeMac(plaintext);
+      try {
+        mac.verifyMac(tag, plaintext);
+        fail("Invalid MAC MAC, should have thrown exception");
+      } catch (GeneralSecurityException expected) {
+        // Expected
+      }
+    }
+  }
+
+  @Test
+  public void testSmallPlaintextWithRawKey() throws Exception {
+    byte[] keyValue = Random.randBytes(HMAC_KEY_SIZE);
+    Key primary = TestUtil.createKey(
+        TestUtil.createHmacKeyData(keyValue, 16),
+        42,
+        KeyStatusType.ENABLED,
+        OutputPrefixType.RAW);
+    KeysetHandle keysetHandle = TestUtil.createKeysetHandle(
+        TestUtil.createKeyset(primary));
+    Mac mac = new MacWrapper().wrap(Registry.getPrimitives(keysetHandle));
+    byte[] plaintext = "blah".getBytes("UTF-8");
+    byte[] tag = mac.computeMac(plaintext);
+    // no prefix
+    assertEquals(16 /* TAG */, tag.length);
+    try {
+      mac.verifyMac(tag, plaintext);
+    } catch (GeneralSecurityException e) {
+      fail("Valid MAC, should not throw exception");
+    }
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/signature/EcdsaVerifyKeyManagerTest.java b/java/src/test/java/com/google/crypto/tink/signature/EcdsaVerifyKeyManagerTest.java
index 22416e9..2bff0c5 100644
--- a/java/src/test/java/com/google/crypto/tink/signature/EcdsaVerifyKeyManagerTest.java
+++ b/java/src/test/java/com/google/crypto/tink/signature/EcdsaVerifyKeyManagerTest.java
@@ -20,11 +20,13 @@
 
 import com.google.crypto.tink.PublicKeyVerify;
 import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.TestUtil.BytesMutation;
 import com.google.crypto.tink.proto.EcdsaPublicKey;
 import com.google.crypto.tink.proto.EcdsaSignatureEncoding;
 import com.google.crypto.tink.proto.EllipticCurveType;
 import com.google.crypto.tink.proto.HashType;
 import com.google.crypto.tink.subtle.EllipticCurves;
+import com.google.crypto.tink.subtle.Hex;
 import com.google.crypto.tink.subtle.Random;
 import com.google.crypto.tink.subtle.SubtleUtil;
 import java.security.GeneralSecurityException;
@@ -135,6 +137,18 @@
       } catch (GeneralSecurityException e) {
         fail("Valid signature, should not throw exception");
       }
+      for (BytesMutation mutation : TestUtil.generateMutations(t.sig)) {
+        try {
+          verifier.verify(mutation.value, t.msg);
+          fail(
+              String.format(
+                  "Invalid signature, should have thrown exception : sig = %s, msg = %s,"
+                      + " description = %s",
+                  Hex.encode(mutation.value), Hex.encode(t.msg), mutation.description));
+        } catch (GeneralSecurityException expected) {
+          // Expected.
+        }
+      }
     }
   }
 
diff --git a/java/src/test/java/com/google/crypto/tink/signature/Ed25519PrivateKeyManagerTest.java b/java/src/test/java/com/google/crypto/tink/signature/Ed25519PrivateKeyManagerTest.java
index 9bdc60e..116e7a4 100644
--- a/java/src/test/java/com/google/crypto/tink/signature/Ed25519PrivateKeyManagerTest.java
+++ b/java/src/test/java/com/google/crypto/tink/signature/Ed25519PrivateKeyManagerTest.java
@@ -52,7 +52,7 @@
   public void testBasic() throws Exception {
     Ed25519PrivateKeyManager manager = new Ed25519PrivateKeyManager();
     KeyTemplate template = SignatureKeyTemplates.ED25519;
-    MessageLite key = manager.newKey(template);
+    MessageLite key = manager.newKey(template.getValue());
     assertTrue(key instanceof Ed25519PrivateKey);
 
     Ed25519PrivateKey keyProto = (Ed25519PrivateKey) key;
diff --git a/java/src/test/java/com/google/crypto/tink/signature/Ed25519PublicKeyManagerTest.java b/java/src/test/java/com/google/crypto/tink/signature/Ed25519PublicKeyManagerTest.java
index 1d2e618..0e0fdf0 100644
--- a/java/src/test/java/com/google/crypto/tink/signature/Ed25519PublicKeyManagerTest.java
+++ b/java/src/test/java/com/google/crypto/tink/signature/Ed25519PublicKeyManagerTest.java
@@ -16,18 +16,19 @@
 
 package com.google.crypto.tink.signature;
 
-import static com.google.crypto.tink.TestUtil.assertExceptionContains;
 import static org.junit.Assert.fail;
 
 import com.google.crypto.tink.Config;
 import com.google.crypto.tink.PublicKeySign;
 import com.google.crypto.tink.PublicKeyVerify;
+import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.TestUtil.BytesMutation;
 import com.google.crypto.tink.proto.Ed25519PrivateKey;
 import com.google.crypto.tink.proto.KeyTemplate;
+import com.google.crypto.tink.subtle.Hex;
 import com.google.crypto.tink.subtle.Random;
 import com.google.protobuf.MessageLite;
 import java.security.GeneralSecurityException;
-import java.util.Arrays;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -45,7 +46,7 @@
   public void testModifiedSignature() throws Exception {
     Ed25519PrivateKeyManager manager = new Ed25519PrivateKeyManager();
     KeyTemplate template = SignatureKeyTemplates.ED25519;
-    MessageLite key = manager.newKey(template);
+    MessageLite key = manager.newKey(template.getValue());
     Ed25519PrivateKey keyProto = (Ed25519PrivateKey) key;
 
     PublicKeySign signer = manager.getPrimitive(key);
@@ -59,37 +60,29 @@
       fail("Did not expect GeneralSecurityException: " + e);
     }
 
-    // Flip bits in message.
-    for (int i = 0; i < message.length; i++) {
-      byte[] copy = Arrays.copyOf(message, message.length);
-      copy[i] = (byte) (copy[i] ^ 0xff);
+    for (BytesMutation mutation : TestUtil.generateMutations(message)) {
       try {
-        verifier.verify(signature, copy);
-        fail("Expected GeneralSecurityException");
-      } catch (GeneralSecurityException e) {
-        assertExceptionContains(e, "Signature check failed.");
+        verifier.verify(signature, mutation.value);
+        fail(
+            String.format(
+                "Invalid message, should have thrown exception: sig = %s, msg = %s,"
+                    + " description = %s",
+                Hex.encode(signature), Hex.encode(mutation.value), mutation.description));
+      } catch (GeneralSecurityException expected) {
+        // Expected.
       }
     }
 
-    // Flip bits in signature.
-    // Flip the last byte.
-    byte[] copySig = Arrays.copyOf(signature, signature.length);
-    copySig[copySig.length - 1] = (byte) (copySig[copySig.length - 1] ^ 0xff);
-    try {
-      verifier.verify(copySig, message);
-      fail("Expected GeneralSecurityException");
-    } catch (GeneralSecurityException e) {
-      assertExceptionContains(e, "Signature check failed.");
-    }
-    // Flip other bytes.
-    for (int i = 0; i < signature.length - 1; i++) {
-      byte[] copy = Arrays.copyOf(signature, signature.length);
-      copy[i] = (byte) (copy[i] ^ 0xff);
+    for (BytesMutation mutation : TestUtil.generateMutations(signature)) {
       try {
-        verifier.verify(copy, message);
-        fail("Expected GeneralSecurityException");
-      } catch (GeneralSecurityException e) {
-        assertExceptionContains(e, "Signature check failed.");
+        verifier.verify(mutation.value, message);
+        fail(
+            String.format(
+                "Invalid signature, should have thrown exception: signature = %s, msg = %s,"
+                    + " description = %s",
+                Hex.encode(mutation.value), Hex.encode(message), mutation.description));
+      } catch (GeneralSecurityException expected) {
+        // Expected.
       }
     }
   }
diff --git a/java/src/test/java/com/google/crypto/tink/signature/PublicKeySignIntegrationTest.java b/java/src/test/java/com/google/crypto/tink/signature/PublicKeySignIntegrationTest.java
new file mode 100644
index 0000000..db52f46
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/signature/PublicKeySignIntegrationTest.java
@@ -0,0 +1,169 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.signature;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.fail;
+
+import com.google.crypto.tink.CryptoFormat;
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.PublicKeySign;
+import com.google.crypto.tink.PublicKeyVerify;
+import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.proto.EcdsaPrivateKey;
+import com.google.crypto.tink.proto.EcdsaSignatureEncoding;
+import com.google.crypto.tink.proto.EllipticCurveType;
+import com.google.crypto.tink.proto.HashType;
+import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyStatusType;
+import com.google.crypto.tink.proto.Keyset.Key;
+import com.google.crypto.tink.proto.OutputPrefixType;
+import com.google.crypto.tink.subtle.Random;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests which run the everything for signatures */
+@RunWith(JUnit4.class)
+// TODO(quannguyen): Add more tests.
+public class PublicKeySignIntegrationTest {
+
+  @Before
+  public void setUp() throws Exception {
+    SignatureConfig.register();
+  }
+
+  @Test
+  public void testMultipleKeys() throws Exception {
+    EcdsaPrivateKey tinkPrivateKey =
+        TestUtil.generateEcdsaPrivKey(
+            EllipticCurveType.NIST_P521, HashType.SHA512, EcdsaSignatureEncoding.DER);
+    Key tink =
+        TestUtil.createKey(
+            TestUtil.createKeyData(
+                tinkPrivateKey,
+                EcdsaSignKeyManager.TYPE_URL,
+                KeyData.KeyMaterialType.ASYMMETRIC_PRIVATE),
+            1,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.TINK);
+
+    EcdsaPrivateKey legacyPrivateKey =
+        TestUtil.generateEcdsaPrivKey(
+            EllipticCurveType.NIST_P256, HashType.SHA256, EcdsaSignatureEncoding.DER);
+    Key legacy =
+        TestUtil.createKey(
+            TestUtil.createKeyData(
+                legacyPrivateKey,
+                EcdsaSignKeyManager.TYPE_URL,
+                KeyData.KeyMaterialType.ASYMMETRIC_PRIVATE),
+            2,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.LEGACY);
+
+    EcdsaPrivateKey rawPrivateKey =
+        TestUtil.generateEcdsaPrivKey(
+            EllipticCurveType.NIST_P384, HashType.SHA512, EcdsaSignatureEncoding.DER);
+    Key raw =
+        TestUtil.createKey(
+            TestUtil.createKeyData(
+                rawPrivateKey,
+                EcdsaSignKeyManager.TYPE_URL,
+                KeyData.KeyMaterialType.ASYMMETRIC_PRIVATE),
+            3,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.RAW);
+
+    EcdsaPrivateKey crunchyPrivateKey =
+        TestUtil.generateEcdsaPrivKey(
+            EllipticCurveType.NIST_P384, HashType.SHA512, EcdsaSignatureEncoding.DER);
+    Key crunchy =
+        TestUtil.createKey(
+            TestUtil.createKeyData(
+                crunchyPrivateKey,
+                EcdsaSignKeyManager.TYPE_URL,
+                KeyData.KeyMaterialType.ASYMMETRIC_PRIVATE),
+            4,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.CRUNCHY);
+
+    Key[] keys = new Key[] {tink, legacy, raw, crunchy};
+    EcdsaPrivateKey[] privateKeys =
+        new EcdsaPrivateKey[] {tinkPrivateKey, legacyPrivateKey, rawPrivateKey, crunchyPrivateKey};
+
+    int j = keys.length;
+    for (int i = 0; i < j; i++) {
+      KeysetHandle keysetHandle =
+          TestUtil.createKeysetHandle(
+              TestUtil.createKeyset(
+                  keys[i], keys[(i + 1) % j], keys[(i + 2) % j], keys[(i + 3) % j]));
+      // Signs with the primary private key.
+      PublicKeySign signer = keysetHandle.getPrimitive(PublicKeySign.class);
+      byte[] plaintext = Random.randBytes(1211);
+      byte[] sig = signer.sign(plaintext);
+      if (keys[i].getOutputPrefixType() != OutputPrefixType.RAW) {
+        byte[] prefix = Arrays.copyOfRange(sig, 0, CryptoFormat.NON_RAW_PREFIX_SIZE);
+        assertArrayEquals(prefix, CryptoFormat.getOutputPrefix(keys[i]));
+      }
+
+      // Verifying with the primary public key should work.
+      PublicKeyVerify verifier =
+          TestUtil.createKeysetHandle(
+                  TestUtil.createKeyset(
+                      TestUtil.createKey(
+                          TestUtil.createKeyData(
+                              privateKeys[i].getPublicKey(),
+                              EcdsaVerifyKeyManager.TYPE_URL,
+                              KeyData.KeyMaterialType.ASYMMETRIC_PUBLIC),
+                          keys[i].getKeyId(),
+                          KeyStatusType.ENABLED,
+                          keys[i].getOutputPrefixType())))
+              .getPrimitive(PublicKeyVerify.class);
+      try {
+        verifier.verify(sig, plaintext);
+      } catch (GeneralSecurityException ex) {
+        fail("Valid signature, should not throw exception");
+      }
+
+      // Verifying with a random public key should fail.
+      EcdsaPrivateKey randomPrivKey =
+          TestUtil.generateEcdsaPrivKey(
+              EllipticCurveType.NIST_P521, HashType.SHA512, EcdsaSignatureEncoding.DER);
+      verifier =
+          TestUtil.createKeysetHandle(
+                  TestUtil.createKeyset(
+                      TestUtil.createKey(
+                          TestUtil.createKeyData(
+                              randomPrivKey.getPublicKey(),
+                              EcdsaVerifyKeyManager.TYPE_URL,
+                              KeyData.KeyMaterialType.ASYMMETRIC_PUBLIC),
+                          keys[i].getKeyId(),
+                          KeyStatusType.ENABLED,
+                          keys[i].getOutputPrefixType())))
+              .getPrimitive(PublicKeyVerify.class);
+      try {
+        verifier.verify(sig, plaintext);
+        fail("Invalid signature, should have thrown exception");
+      } catch (GeneralSecurityException expected) {
+        // Expected
+      }
+    }
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/signature/PublicKeySignWrapperTest.java b/java/src/test/java/com/google/crypto/tink/signature/PublicKeySignWrapperTest.java
new file mode 100644
index 0000000..3718402
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/signature/PublicKeySignWrapperTest.java
@@ -0,0 +1,170 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.signature;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.fail;
+
+import com.google.crypto.tink.CryptoFormat;
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.PublicKeySign;
+import com.google.crypto.tink.PublicKeyVerify;
+import com.google.crypto.tink.Registry;
+import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.proto.EcdsaPrivateKey;
+import com.google.crypto.tink.proto.EcdsaSignatureEncoding;
+import com.google.crypto.tink.proto.EllipticCurveType;
+import com.google.crypto.tink.proto.HashType;
+import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyStatusType;
+import com.google.crypto.tink.proto.Keyset.Key;
+import com.google.crypto.tink.proto.OutputPrefixType;
+import com.google.crypto.tink.subtle.Random;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link PublicKeySignWrapper}. */
+@RunWith(JUnit4.class)
+// TODO(quannguyen): Add more tests.
+public class PublicKeySignWrapperTest {
+
+  @Before
+  public void setUp() throws Exception {
+    SignatureConfig.register();
+  }
+
+  @Test
+  public void testMultipleKeys() throws Exception {
+    EcdsaPrivateKey tinkPrivateKey =
+        TestUtil.generateEcdsaPrivKey(
+            EllipticCurveType.NIST_P521, HashType.SHA512, EcdsaSignatureEncoding.DER);
+    Key tink =
+        TestUtil.createKey(
+            TestUtil.createKeyData(
+                tinkPrivateKey,
+                EcdsaSignKeyManager.TYPE_URL,
+                KeyData.KeyMaterialType.ASYMMETRIC_PRIVATE),
+            1,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.TINK);
+
+    EcdsaPrivateKey legacyPrivateKey =
+        TestUtil.generateEcdsaPrivKey(
+            EllipticCurveType.NIST_P256, HashType.SHA256, EcdsaSignatureEncoding.DER);
+    Key legacy =
+        TestUtil.createKey(
+            TestUtil.createKeyData(
+                legacyPrivateKey,
+                EcdsaSignKeyManager.TYPE_URL,
+                KeyData.KeyMaterialType.ASYMMETRIC_PRIVATE),
+            2,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.LEGACY);
+
+    EcdsaPrivateKey rawPrivateKey =
+        TestUtil.generateEcdsaPrivKey(
+            EllipticCurveType.NIST_P384, HashType.SHA512, EcdsaSignatureEncoding.DER);
+    Key raw =
+        TestUtil.createKey(
+            TestUtil.createKeyData(
+                rawPrivateKey,
+                EcdsaSignKeyManager.TYPE_URL,
+                KeyData.KeyMaterialType.ASYMMETRIC_PRIVATE),
+            3,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.RAW);
+
+    EcdsaPrivateKey crunchyPrivateKey =
+        TestUtil.generateEcdsaPrivKey(
+            EllipticCurveType.NIST_P384, HashType.SHA512, EcdsaSignatureEncoding.DER);
+    Key crunchy =
+        TestUtil.createKey(
+            TestUtil.createKeyData(
+                crunchyPrivateKey,
+                EcdsaSignKeyManager.TYPE_URL,
+                KeyData.KeyMaterialType.ASYMMETRIC_PRIVATE),
+            4,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.CRUNCHY);
+
+    Key[] keys = new Key[] {tink, legacy, raw, crunchy};
+    EcdsaPrivateKey[] privateKeys =
+        new EcdsaPrivateKey[] {tinkPrivateKey, legacyPrivateKey, rawPrivateKey, crunchyPrivateKey};
+
+    int j = keys.length;
+    for (int i = 0; i < j; i++) {
+      KeysetHandle keysetHandle =
+          TestUtil.createKeysetHandle(
+              TestUtil.createKeyset(
+                  keys[i], keys[(i + 1) % j], keys[(i + 2) % j], keys[(i + 3) % j]));
+      // Signs with the primary private key.
+      PublicKeySign signer = new PublicKeySignWrapper().wrap(Registry.getPrimitives(keysetHandle));
+      byte[] plaintext = Random.randBytes(1211);
+      byte[] sig = signer.sign(plaintext);
+      if (keys[i].getOutputPrefixType() != OutputPrefixType.RAW) {
+        byte[] prefix = Arrays.copyOfRange(sig, 0, CryptoFormat.NON_RAW_PREFIX_SIZE);
+        assertArrayEquals(prefix, CryptoFormat.getOutputPrefix(keys[i]));
+      }
+
+      // Verifying with the primary public key should work.
+      PublicKeyVerify verifier =
+          PublicKeyVerifyFactory.getPrimitive(
+              TestUtil.createKeysetHandle(
+                  TestUtil.createKeyset(
+                      TestUtil.createKey(
+                          TestUtil.createKeyData(
+                              privateKeys[i].getPublicKey(),
+                              EcdsaVerifyKeyManager.TYPE_URL,
+                              KeyData.KeyMaterialType.ASYMMETRIC_PUBLIC),
+                          keys[i].getKeyId(),
+                          KeyStatusType.ENABLED,
+                          keys[i].getOutputPrefixType()))));
+      try {
+        verifier.verify(sig, plaintext);
+      } catch (GeneralSecurityException ex) {
+        fail("Valid signature, should not throw exception");
+      }
+
+      // Verifying with a random public key should fail.
+      EcdsaPrivateKey randomPrivKey =
+          TestUtil.generateEcdsaPrivKey(
+              EllipticCurveType.NIST_P521, HashType.SHA512, EcdsaSignatureEncoding.DER);
+      verifier =
+          PublicKeyVerifyFactory.getPrimitive(
+              TestUtil.createKeysetHandle(
+                  TestUtil.createKeyset(
+                      TestUtil.createKey(
+                          TestUtil.createKeyData(
+                              randomPrivKey.getPublicKey(),
+                              EcdsaVerifyKeyManager.TYPE_URL,
+                              KeyData.KeyMaterialType.ASYMMETRIC_PUBLIC),
+                          keys[i].getKeyId(),
+                          KeyStatusType.ENABLED,
+                          keys[i].getOutputPrefixType()))));
+      try {
+        verifier.verify(sig, plaintext);
+        fail("Invalid signature, should have thrown exception");
+      } catch (GeneralSecurityException expected) {
+        // Expected
+      }
+    }
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/signature/PublicKeyVerifyIntegrationTest.java b/java/src/test/java/com/google/crypto/tink/signature/PublicKeyVerifyIntegrationTest.java
new file mode 100644
index 0000000..c0b40d9
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/signature/PublicKeyVerifyIntegrationTest.java
@@ -0,0 +1,164 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.signature;
+
+import static org.junit.Assert.fail;
+
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.PublicKeySign;
+import com.google.crypto.tink.PublicKeyVerify;
+import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.proto.EcdsaPrivateKey;
+import com.google.crypto.tink.proto.EcdsaSignatureEncoding;
+import com.google.crypto.tink.proto.EllipticCurveType;
+import com.google.crypto.tink.proto.HashType;
+import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyStatusType;
+import com.google.crypto.tink.proto.Keyset.Key;
+import com.google.crypto.tink.proto.OutputPrefixType;
+import com.google.crypto.tink.subtle.Random;
+import java.security.GeneralSecurityException;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests which run the everything for the Public Key signing primitives. */
+@RunWith(JUnit4.class)
+// TODO(quannguyen): Add more tests.
+public class PublicKeyVerifyIntegrationTest {
+
+  @Before
+  public void setUp() throws Exception {
+    SignatureConfig.register();
+  }
+
+  @Test
+  public void testMultipleKeys() throws Exception {
+    EcdsaPrivateKey tinkPrivateKey =
+        TestUtil.generateEcdsaPrivKey(
+            EllipticCurveType.NIST_P521, HashType.SHA512, EcdsaSignatureEncoding.DER);
+    Key tink =
+        TestUtil.createKey(
+            TestUtil.createKeyData(
+                tinkPrivateKey.getPublicKey(),
+                EcdsaVerifyKeyManager.TYPE_URL,
+                KeyData.KeyMaterialType.ASYMMETRIC_PUBLIC),
+            1,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.TINK);
+
+    EcdsaPrivateKey legacyPrivateKey =
+        TestUtil.generateEcdsaPrivKey(
+            EllipticCurveType.NIST_P256, HashType.SHA256, EcdsaSignatureEncoding.DER);
+    Key legacy =
+        TestUtil.createKey(
+            TestUtil.createKeyData(
+                legacyPrivateKey.getPublicKey(),
+                EcdsaVerifyKeyManager.TYPE_URL,
+                KeyData.KeyMaterialType.ASYMMETRIC_PUBLIC),
+            2,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.LEGACY);
+
+    EcdsaPrivateKey rawPrivateKey =
+        TestUtil.generateEcdsaPrivKey(
+            EllipticCurveType.NIST_P384, HashType.SHA512, EcdsaSignatureEncoding.DER);
+    Key raw =
+        TestUtil.createKey(
+            TestUtil.createKeyData(
+                rawPrivateKey.getPublicKey(),
+                EcdsaVerifyKeyManager.TYPE_URL,
+                KeyData.KeyMaterialType.ASYMMETRIC_PUBLIC),
+            3,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.RAW);
+
+    EcdsaPrivateKey crunchyPrivateKey =
+        TestUtil.generateEcdsaPrivKey(
+            EllipticCurveType.NIST_P384, HashType.SHA512, EcdsaSignatureEncoding.DER);
+    Key crunchy =
+        TestUtil.createKey(
+            TestUtil.createKeyData(
+                crunchyPrivateKey.getPublicKey(),
+                EcdsaVerifyKeyManager.TYPE_URL,
+                KeyData.KeyMaterialType.ASYMMETRIC_PUBLIC),
+            4,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.CRUNCHY);
+
+    Key[] keys = new Key[] {tink, legacy, raw, crunchy};
+    EcdsaPrivateKey[] privateKeys =
+        new EcdsaPrivateKey[] {tinkPrivateKey, legacyPrivateKey, rawPrivateKey, crunchyPrivateKey};
+
+    int j = keys.length;
+    for (int i = 0; i < j; i++) {
+      KeysetHandle keysetHandle =
+          TestUtil.createKeysetHandle(
+              TestUtil.createKeyset(
+                  keys[i], keys[(i + 1) % j], keys[(i + 2) % j], keys[(i + 3) % j]));
+      PublicKeyVerify verifier = keysetHandle.getPrimitive(PublicKeyVerify.class);
+      // Signature from any keys in the keyset should be valid.
+      for (int k = 0; k < j; k++) {
+        PublicKeySign signer =
+            TestUtil.createKeysetHandle(
+                    TestUtil.createKeyset(
+                        TestUtil.createKey(
+                            TestUtil.createKeyData(
+                                privateKeys[k],
+                                EcdsaSignKeyManager.TYPE_URL,
+                                KeyData.KeyMaterialType.ASYMMETRIC_PRIVATE),
+                            keys[k].getKeyId(),
+                            KeyStatusType.ENABLED,
+                            keys[k].getOutputPrefixType())))
+                .getPrimitive(PublicKeySign.class);
+        byte[] plaintext = Random.randBytes(1211);
+        byte[] sig = signer.sign(plaintext);
+        try {
+          verifier.verify(sig, plaintext);
+        } catch (GeneralSecurityException ex) {
+          fail("Valid signature, should not throw exception: " + k);
+        }
+      }
+
+      // Signature from a random key should be invalid.
+      EcdsaPrivateKey randomPrivKey =
+          TestUtil.generateEcdsaPrivKey(
+              EllipticCurveType.NIST_P521, HashType.SHA512, EcdsaSignatureEncoding.DER);
+      PublicKeySign signer =
+          TestUtil.createKeysetHandle(
+                  TestUtil.createKeyset(
+                      TestUtil.createKey(
+                          TestUtil.createKeyData(
+                              randomPrivKey,
+                              EcdsaSignKeyManager.TYPE_URL,
+                              KeyData.KeyMaterialType.ASYMMETRIC_PRIVATE),
+                          1,
+                          KeyStatusType.ENABLED,
+                          keys[0].getOutputPrefixType())))
+              .getPrimitive(PublicKeySign.class);
+      byte[] plaintext = Random.randBytes(1211);
+      byte[] sig = signer.sign(plaintext);
+      try {
+        verifier.verify(sig, plaintext);
+        fail("Invalid signature, should have thrown exception");
+      } catch (GeneralSecurityException expected) {
+        // Expected
+      }
+    }
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/signature/PublicKeyVerifyWrapperTest.java b/java/src/test/java/com/google/crypto/tink/signature/PublicKeyVerifyWrapperTest.java
new file mode 100644
index 0000000..1abdd02
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/signature/PublicKeyVerifyWrapperTest.java
@@ -0,0 +1,166 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.signature;
+
+import static org.junit.Assert.fail;
+
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.PublicKeySign;
+import com.google.crypto.tink.PublicKeyVerify;
+import com.google.crypto.tink.Registry;
+import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.proto.EcdsaPrivateKey;
+import com.google.crypto.tink.proto.EcdsaSignatureEncoding;
+import com.google.crypto.tink.proto.EllipticCurveType;
+import com.google.crypto.tink.proto.HashType;
+import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyStatusType;
+import com.google.crypto.tink.proto.Keyset.Key;
+import com.google.crypto.tink.proto.OutputPrefixType;
+import com.google.crypto.tink.subtle.Random;
+import java.security.GeneralSecurityException;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link PublicKeyVerifyWrapper}. */
+@RunWith(JUnit4.class)
+// TODO(quannguyen): Add more tests.
+public class PublicKeyVerifyWrapperTest {
+
+  @Before
+  public void setUp() throws Exception {
+    SignatureConfig.register();
+  }
+
+  @Test
+  public void testMultipleKeys() throws Exception {
+    EcdsaPrivateKey tinkPrivateKey =
+        TestUtil.generateEcdsaPrivKey(
+            EllipticCurveType.NIST_P521, HashType.SHA512, EcdsaSignatureEncoding.DER);
+    Key tink =
+        TestUtil.createKey(
+            TestUtil.createKeyData(
+                tinkPrivateKey.getPublicKey(),
+                EcdsaVerifyKeyManager.TYPE_URL,
+                KeyData.KeyMaterialType.ASYMMETRIC_PUBLIC),
+            1,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.TINK);
+
+    EcdsaPrivateKey legacyPrivateKey =
+        TestUtil.generateEcdsaPrivKey(
+            EllipticCurveType.NIST_P256, HashType.SHA256, EcdsaSignatureEncoding.DER);
+    Key legacy =
+        TestUtil.createKey(
+            TestUtil.createKeyData(
+                legacyPrivateKey.getPublicKey(),
+                EcdsaVerifyKeyManager.TYPE_URL,
+                KeyData.KeyMaterialType.ASYMMETRIC_PUBLIC),
+            2,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.LEGACY);
+
+    EcdsaPrivateKey rawPrivateKey =
+        TestUtil.generateEcdsaPrivKey(
+            EllipticCurveType.NIST_P384, HashType.SHA512, EcdsaSignatureEncoding.DER);
+    Key raw =
+        TestUtil.createKey(
+            TestUtil.createKeyData(
+                rawPrivateKey.getPublicKey(),
+                EcdsaVerifyKeyManager.TYPE_URL,
+                KeyData.KeyMaterialType.ASYMMETRIC_PUBLIC),
+            3,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.RAW);
+
+    EcdsaPrivateKey crunchyPrivateKey =
+        TestUtil.generateEcdsaPrivKey(
+            EllipticCurveType.NIST_P384, HashType.SHA512, EcdsaSignatureEncoding.DER);
+    Key crunchy =
+        TestUtil.createKey(
+            TestUtil.createKeyData(
+                crunchyPrivateKey.getPublicKey(),
+                EcdsaVerifyKeyManager.TYPE_URL,
+                KeyData.KeyMaterialType.ASYMMETRIC_PUBLIC),
+            4,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.CRUNCHY);
+
+    Key[] keys = new Key[] {tink, legacy, raw, crunchy};
+    EcdsaPrivateKey[] privateKeys =
+        new EcdsaPrivateKey[] {tinkPrivateKey, legacyPrivateKey, rawPrivateKey, crunchyPrivateKey};
+
+    int j = keys.length;
+    for (int i = 0; i < j; i++) {
+      KeysetHandle keysetHandle =
+          TestUtil.createKeysetHandle(
+              TestUtil.createKeyset(
+                  keys[i], keys[(i + 1) % j], keys[(i + 2) % j], keys[(i + 3) % j]));
+      PublicKeyVerify verifier =
+          new PublicKeyVerifyWrapper().wrap(Registry.getPrimitives(keysetHandle));
+      // Signature from any keys in the keyset should be valid.
+      for (int k = 0; k < j; k++) {
+        PublicKeySign signer =
+            PublicKeySignFactory.getPrimitive(
+                TestUtil.createKeysetHandle(
+                    TestUtil.createKeyset(
+                        TestUtil.createKey(
+                            TestUtil.createKeyData(
+                                privateKeys[k],
+                                EcdsaSignKeyManager.TYPE_URL,
+                                KeyData.KeyMaterialType.ASYMMETRIC_PRIVATE),
+                            keys[k].getKeyId(),
+                            KeyStatusType.ENABLED,
+                            keys[k].getOutputPrefixType()))));
+        byte[] plaintext = Random.randBytes(1211);
+        byte[] sig = signer.sign(plaintext);
+        try {
+          verifier.verify(sig, plaintext);
+        } catch (GeneralSecurityException ex) {
+          fail("Valid signature, should not throw exception: " + k);
+        }
+      }
+
+      // Signature from a random key should be invalid.
+      EcdsaPrivateKey randomPrivKey =
+          TestUtil.generateEcdsaPrivKey(
+              EllipticCurveType.NIST_P521, HashType.SHA512, EcdsaSignatureEncoding.DER);
+      PublicKeySign signer =
+          PublicKeySignFactory.getPrimitive(
+              TestUtil.createKeysetHandle(
+                  TestUtil.createKeyset(
+                      TestUtil.createKey(
+                          TestUtil.createKeyData(
+                              randomPrivKey,
+                              EcdsaSignKeyManager.TYPE_URL,
+                              KeyData.KeyMaterialType.ASYMMETRIC_PRIVATE),
+                          1,
+                          KeyStatusType.ENABLED,
+                          keys[0].getOutputPrefixType()))));
+      byte[] plaintext = Random.randBytes(1211);
+      byte[] sig = signer.sign(plaintext);
+      try {
+        verifier.verify(sig, plaintext);
+        fail("Invalid signature, should have thrown exception");
+      } catch (GeneralSecurityException expected) {
+        // Expected
+      }
+    }
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/signature/RsaSsaPkcs1SignKeyManagerTest.java b/java/src/test/java/com/google/crypto/tink/signature/RsaSsaPkcs1SignKeyManagerTest.java
new file mode 100644
index 0000000..7371320
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/signature/RsaSsaPkcs1SignKeyManagerTest.java
@@ -0,0 +1,176 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.signature;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.PublicKeySign;
+import com.google.crypto.tink.PublicKeyVerify;
+import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyTemplate;
+import com.google.crypto.tink.proto.RsaSsaPkcs1KeyFormat;
+import com.google.crypto.tink.proto.RsaSsaPkcs1PrivateKey;
+import com.google.crypto.tink.subtle.Random;
+import com.google.protobuf.ByteString;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.util.Set;
+import java.util.TreeSet;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for RsaSsaPkcs1SignKeyManager. */
+@RunWith(JUnit4.class)
+public class RsaSsaPkcs1SignKeyManagerTest {
+  @Before
+  public void setUp() throws Exception {
+    SignatureConfig.register();
+  }
+
+  final byte[] msg = Random.randBytes(20);
+
+  private void checkKey(RsaSsaPkcs1PrivateKey privateKey) throws Exception {
+    BigInteger p = new BigInteger(1, privateKey.getP().toByteArray());
+    BigInteger q = new BigInteger(1, privateKey.getQ().toByteArray());
+    BigInteger n = new BigInteger(1, privateKey.getPublicKey().getN().toByteArray());
+    BigInteger d = new BigInteger(1, privateKey.getD().toByteArray());
+    BigInteger dp = new BigInteger(1, privateKey.getDp().toByteArray());
+    BigInteger dq = new BigInteger(1, privateKey.getDq().toByteArray());
+    BigInteger crt = new BigInteger(1, privateKey.getCrt().toByteArray());
+    assertEquals(n, p.multiply(q));
+    assertEquals(dp, d.mod(p.subtract(BigInteger.ONE)));
+    assertEquals(dq, d.mod(q.subtract(BigInteger.ONE)));
+    assertEquals(crt, q.modInverse(p));
+  }
+
+  private void testNewKeyWithVerifier(KeyTemplate keyTemplate) throws Exception {
+    // Call newKey multiple times and make sure that it generates different keys.
+    int numTests = 3;
+    RsaSsaPkcs1PrivateKey[] privKeys = new RsaSsaPkcs1PrivateKey[numTests];
+    RsaSsaPkcs1SignKeyManager signManager = new RsaSsaPkcs1SignKeyManager();
+    Set<String> keys = new TreeSet<String>();
+
+    privKeys[0] =
+        (RsaSsaPkcs1PrivateKey)
+            signManager.newKey(RsaSsaPkcs1KeyFormat.parseFrom(keyTemplate.getValue()));
+    keys.add(TestUtil.hexEncode(privKeys[0].toByteArray()));
+
+    privKeys[1] = (RsaSsaPkcs1PrivateKey) signManager.newKey(keyTemplate.getValue());
+    keys.add(TestUtil.hexEncode(privKeys[1].toByteArray()));
+
+    privKeys[2] =
+        RsaSsaPkcs1PrivateKey.parseFrom(signManager.newKeyData(keyTemplate.getValue()).getValue());
+    keys.add(TestUtil.hexEncode(privKeys[2].toByteArray()));
+    assertEquals(numTests, keys.size());
+
+    // Check key.
+    for (int i = 0; i < numTests; i++) {
+      checkKey(privKeys[i]);
+    }
+
+    // Test whether signer works correctly with the corresponding verifier.
+    RsaSsaPkcs1VerifyKeyManager verifyManager = new RsaSsaPkcs1VerifyKeyManager();
+    for (int j = 0; j < numTests; j++) {
+      PublicKeySign signer = signManager.getPrimitive(privKeys[j]);
+      byte[] signature = signer.sign(msg);
+      for (int k = 0; k < numTests; k++) {
+        PublicKeyVerify verifier = verifyManager.getPrimitive(privKeys[k].getPublicKey());
+        if (j == k) { // The same key
+          try {
+            verifier.verify(signature, msg);
+          } catch (GeneralSecurityException ex) {
+            throw new AssertionError("Valid signature, should not throw exception", ex);
+          }
+        } else { // Different keys
+          try {
+            verifier.verify(signature, msg);
+            fail("Invalid signature, should have thrown exception");
+          } catch (GeneralSecurityException expected) {
+            // Expected
+          }
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testNewKeyWithVerifier() throws Exception {
+    if (TestUtil.isTsan()) {
+      // This test times out when running under thread sanitizer, so we just skip.
+      return;
+    }
+    testNewKeyWithVerifier(SignatureKeyTemplates.RSA_SSA_PKCS1_3072_SHA256_F4);
+    testNewKeyWithVerifier(SignatureKeyTemplates.RSA_SSA_PKCS1_4096_SHA512_F4);
+  }
+
+  @Test
+  public void testNewKeyWithCorruptedFormat() {
+    ByteString serialized = ByteString.copyFrom(new byte[128]);
+    KeyTemplate keyTemplate =
+        KeyTemplate.newBuilder()
+            .setTypeUrl(RsaSsaPkcs1SignKeyManager.TYPE_URL)
+            .setValue(serialized)
+            .build();
+    RsaSsaPkcs1SignKeyManager keyManager = new RsaSsaPkcs1SignKeyManager();
+    try {
+      keyManager.newKey(serialized);
+      fail("Corrupted format, should have thrown exception");
+    } catch (GeneralSecurityException expected) {
+      // Expected
+    }
+    try {
+      keyManager.newKeyData(keyTemplate.getValue());
+      fail("Corrupted format, should have thrown exception");
+    } catch (GeneralSecurityException expected) {
+      // Expected
+    }
+  }
+
+  /** Tests that a public key is extracted properly from a private key. */
+  @Test
+  public void testGetPublicKeyData() throws Exception {
+    if (TestUtil.isTsan()) {
+      // This test takes over a minute in successful tsan runs and sometimes times out.
+      return;
+    }
+    KeysetHandle privateHandle =
+        KeysetHandle.generateNew(SignatureKeyTemplates.RSA_SSA_PKCS1_3072_SHA256_F4);
+    KeyData privateKeyData = TestUtil.getKeyset(privateHandle).getKey(0).getKeyData();
+    RsaSsaPkcs1SignKeyManager privateManager = new RsaSsaPkcs1SignKeyManager();
+    KeyData publicKeyData = privateManager.getPublicKeyData(privateKeyData.getValue());
+    assertEquals(RsaSsaPkcs1VerifyKeyManager.TYPE_URL, publicKeyData.getTypeUrl());
+    assertEquals(KeyData.KeyMaterialType.ASYMMETRIC_PUBLIC, publicKeyData.getKeyMaterialType());
+    RsaSsaPkcs1PrivateKey privateKey = RsaSsaPkcs1PrivateKey.parseFrom(privateKeyData.getValue());
+    assertArrayEquals(
+        privateKey.getPublicKey().toByteArray(), publicKeyData.getValue().toByteArray());
+    RsaSsaPkcs1VerifyKeyManager publicManager = new RsaSsaPkcs1VerifyKeyManager();
+    PublicKeySign signer = privateManager.getPrimitive(privateKeyData.getValue());
+    PublicKeyVerify verifier = publicManager.getPrimitive(publicKeyData.getValue());
+    byte[] message = Random.randBytes(20);
+    try {
+      verifier.verify(signer.sign(message), message);
+    } catch (GeneralSecurityException e) {
+      fail("Should not fail: " + e);
+    }
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/signature/RsaSsaPkcs1VerifyKeyManagerTest.java b/java/src/test/java/com/google/crypto/tink/signature/RsaSsaPkcs1VerifyKeyManagerTest.java
new file mode 100644
index 0000000..d62a656
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/signature/RsaSsaPkcs1VerifyKeyManagerTest.java
@@ -0,0 +1,128 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+package com.google.crypto.tink.signature;
+
+import static com.google.crypto.tink.TestUtil.assertExceptionContains;
+import static org.junit.Assert.fail;
+
+import com.google.crypto.tink.PublicKeyVerify;
+import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.TestUtil.BytesMutation;
+import com.google.crypto.tink.proto.HashType;
+import com.google.crypto.tink.proto.RsaSsaPkcs1PublicKey;
+import com.google.crypto.tink.subtle.Hex;
+import java.security.GeneralSecurityException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for RsaSsaPkcs1VerifyKeyManager. */
+@RunWith(JUnit4.class)
+public final class RsaSsaPkcs1VerifyKeyManagerTest {
+
+  static class NistTestVector {
+    byte[] modulus;
+    byte[] exponent;
+    byte[] msg;
+    byte[] sig;
+    HashType hashType;
+
+    public NistTestVector(
+        String modulus, String exponent, String msg, String sig, HashType hashType) {
+      this.modulus = TestUtil.hexDecode(modulus);
+      this.exponent = TestUtil.hexDecode(exponent);
+      this.msg = TestUtil.hexDecode(msg);
+      this.sig = TestUtil.hexDecode(sig);
+      this.hashType = hashType;
+    }
+  }
+
+  // Test vector from
+  // https://csrc.nist.gov/Projects/Cryptographic-Algorithm-Validation-Program/Digital-Signatures
+  final NistTestVector[] nistTestVectors = {
+    new NistTestVector(
+        "c47abacc2a84d56f3614d92fd62ed36ddde459664b9301dcd1d61781cfcc026bcb2399bee7e75681a80b7bf500e2d08ceae1c42ec0b707927f2b2fe92ae852087d25f1d260cc74905ee5f9b254ed05494a9fe06732c3680992dd6f0dc634568d11542a705f83ae96d2a49763d5fbb24398edf3702bc94bc168190166492b8671de874bb9cecb058c6c8344aa8c93754d6effcd44a41ed7de0a9dcd9144437f212b18881d042d331a4618a9e630ef9bb66305e4fdf8f0391b3b2313fe549f0189ff968b92f33c266a4bc2cffc897d1937eeb9e406f5d0eaa7a14782e76af3fce98f54ed237b4a04a4159a5f6250a296a902880204e61d891c4da29f2d65f34cbb",
+        "49d2a1",
+        "95123c8d1b236540b86976a11cea31f8bd4e6c54c235147d20ce722b03a6ad756fbd918c27df8ea9ce3104444c0bbe877305bc02e35535a02a58dcda306e632ad30b3dc3ce0ba97fdf46ec192965dd9cd7f4a71b02b8cba3d442646eeec4af590824ca98d74fbca934d0b6867aa1991f3040b707e806de6e66b5934f05509bea",
+        "51265d96f11ab338762891cb29bf3f1d2b3305107063f5f3245af376dfcc7027d39365de70a31db05e9e10eb6148cb7f6425f0c93c4fb0e2291adbd22c77656afc196858a11e1c670d9eeb592613e69eb4f3aa501730743ac4464486c7ae68fd509e896f63884e9424f69c1c5397959f1e52a368667a598a1fc90125273d9341295d2f8e1cc4969bf228c860e07a3546be2eeda1cde48ee94d062801fe666e4a7ae8cb9cd79262c017b081af874ff00453ca43e34efdb43fffb0bb42a4e2d32a5e5cc9e8546a221fe930250e5f5333e0efe58ffebf19369a3b8ae5a67f6a048bc9ef915bda25160729b508667ada84a0c27e7e26cf2abca413e5e4693f4a9405",
+        HashType.SHA256),
+    new NistTestVector(
+        "9689eb163a617c0abbf01ddc0e6d88c37f8a6b0baec0f6cab8f8a683f372a53d028253a6ba502da462adaf4fd87c8dc2b03b6c07c2b6aacab1d8c8bd043d89f4effe72ea2547c73c6366a2efab9c916945820fb880890bc085564e57ee76f7107a008f71e941e9fd631aec78f82e410ea9c893faa3d553cd1ca628af1087ca1b0c6aef3b66edcee14d1d7dc48293ddd7deed1ccbe487c957585abb9509151038d53f46b068e3e139c7689bf8e8d38669896b8d082e65e458e1f82b8e8ec926e7aa0f97d08526e9636f2c00af4c2bd3d8bffc4bb93cd47b09af18883e11b639d47938d036f7cfeb77db74a2c09a6dee9df98b18eff2fda7d3f4135083bb3b59e2172244ec37bdbdcfe6e199d36dc949cda1cca123fb2be07803d003d76af3d7164453df77d44c7f2599636ca44d0b7a46218326b0c814ed322b9c4279b060f1b9e14b70f55a3751c4343763cdbf9c14637d2210c59fbd037be17ea6706846fdc7b9ab90278c01c458e64442f9256f3ad1cbceb22959d495063aaca1a3959eae03",
+        "fa3751",
+        "6459ea1d443df706907ffdd3ca2f193f93f5a349b50357d26748b767cde6ab5cbfe76b1acb2b9eb97da5c4d2ddc8d18e3a3b1a0326d475c1c2c49ca73c0fd3fc9540cbbba85ac52d6811fabd693a3b09a281d535715ab784df3ad7292606d15a70ccd1a7e2b1b48ad92a6a3f736f9fd5522d9a869c7b654446102e9493b3ed9f",
+        "2b72942573b825cd1f0172119c23440a2b384b7f2a3c5582bb02f764e2b159ea9ad880ca61b3df7ca249134f4bec285083c7ebf984b192808e916af687ef6c6a9a6722a4fa9189fac1521d03853f3dd5a95ff4b9dbdbf3c7077f720650ead01945ab5bfee582ac1643526fbf68efe1bb3b6f7d2b4b01f2155aaea38a2c7ed29add23ee791a703d11e3b1b7c500d9a6b647c1337bf537c071e5bada6faa025bcaf5e5d1196998909c3d64758826939ae7fe1466dc6efc10a2b25e21186c2d135ceace33cdf490b13a0d10c2527e04200aa70bc1d4f3cfb04b5d2bc17aee881d3a788401f45443470bc639232088a9553c8d792aa5707654f075476a66b86368d5a92b4c84a3b4baba1b0b98bdebb85b48b82b8409f2e9c1aa500670329ff3b6e83e25c561110d47b2fe93ea2946a74f9730da9b7d126f8d7c3fa4a51fc30144a827831c186390998d552a1b677afe5afee46e9d4a5774a56355a4d1967677e75d176aef71c3fa061644d7a9582385877de67f87724b0a6e868f3a2eeafb68c53b",
+        HashType.SHA512),
+  };
+
+  @Test
+  public void testNistTestVector() throws Exception {
+    if (TestUtil.isTsan()) {
+      // This test times out when running under thread sanitizer, so we just skip.
+      return;
+    }
+    for (NistTestVector t : nistTestVectors) {
+      RsaSsaPkcs1PublicKey pubKey =
+          TestUtil.createRsaSsaPkcs1PubKey(t.modulus, t.exponent, t.hashType);
+      RsaSsaPkcs1VerifyKeyManager keyManager = new RsaSsaPkcs1VerifyKeyManager();
+      PublicKeyVerify verifier = keyManager.getPrimitive(pubKey);
+      try {
+        verifier.verify(t.sig, t.msg);
+      } catch (GeneralSecurityException e) {
+        fail("Valid signature, should not throw exception" + e);
+      }
+      for (BytesMutation mutation : TestUtil.generateMutations(t.sig)) {
+        try {
+          verifier.verify(mutation.value, t.msg);
+          fail(
+              String.format(
+                  "Invalid signature, should have thrown exception : sig = %s, msg = %s,"
+                      + " description = %s",
+                  Hex.encode(mutation.value), Hex.encode(t.msg), mutation.description));
+        } catch (GeneralSecurityException expected) {
+          // Expected.
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testSmallModulus() throws Exception {
+    try {
+      RsaSsaPkcs1PublicKey pubKey =
+          TestUtil.createRsaSsaPkcs1PubKey(
+              TestUtil.hexDecode("23"), TestUtil.hexDecode("03"), HashType.SHA256);
+      RsaSsaPkcs1VerifyKeyManager keyManager = new RsaSsaPkcs1VerifyKeyManager();
+      keyManager.getPrimitive(pubKey);
+      fail("Invalid modulus, should have thrown exception");
+    } catch (GeneralSecurityException e) {
+      assertExceptionContains(e, "2048-bit");
+    }
+  }
+
+  @Test
+  public void testInvalidHash() throws Exception {
+    NistTestVector t = nistTestVectors[0];
+    try {
+      RsaSsaPkcs1PublicKey pubKey =
+          TestUtil.createRsaSsaPkcs1PubKey(t.modulus, t.exponent, HashType.SHA1);
+      RsaSsaPkcs1VerifyKeyManager keyManager = new RsaSsaPkcs1VerifyKeyManager();
+      keyManager.getPrimitive(pubKey);
+      fail("Invalid hash, should have thrown exception");
+    } catch (GeneralSecurityException e) {
+      assertExceptionContains(e, "SHA1");
+    }
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/signature/RsaSsaPssSignKeyManagerTest.java b/java/src/test/java/com/google/crypto/tink/signature/RsaSsaPssSignKeyManagerTest.java
new file mode 100644
index 0000000..853da66
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/signature/RsaSsaPssSignKeyManagerTest.java
@@ -0,0 +1,176 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.signature;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.PublicKeySign;
+import com.google.crypto.tink.PublicKeyVerify;
+import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyTemplate;
+import com.google.crypto.tink.proto.RsaSsaPssKeyFormat;
+import com.google.crypto.tink.proto.RsaSsaPssPrivateKey;
+import com.google.crypto.tink.subtle.Random;
+import com.google.protobuf.ByteString;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.util.Set;
+import java.util.TreeSet;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for RsaSsaPssSignKeyManager. */
+@RunWith(JUnit4.class)
+public class RsaSsaPssSignKeyManagerTest {
+  @Before
+  public void setUp() throws Exception {
+    SignatureConfig.register();
+  }
+
+  final byte[] msg = Random.randBytes(20);
+
+  private void checkKey(RsaSsaPssPrivateKey privateKey) throws Exception {
+    BigInteger p = new BigInteger(1, privateKey.getP().toByteArray());
+    BigInteger q = new BigInteger(1, privateKey.getQ().toByteArray());
+    BigInteger n = new BigInteger(1, privateKey.getPublicKey().getN().toByteArray());
+    BigInteger d = new BigInteger(1, privateKey.getD().toByteArray());
+    BigInteger dp = new BigInteger(1, privateKey.getDp().toByteArray());
+    BigInteger dq = new BigInteger(1, privateKey.getDq().toByteArray());
+    BigInteger crt = new BigInteger(1, privateKey.getCrt().toByteArray());
+    assertEquals(n, p.multiply(q));
+    assertEquals(dp, d.mod(p.subtract(BigInteger.ONE)));
+    assertEquals(dq, d.mod(q.subtract(BigInteger.ONE)));
+    assertEquals(crt, q.modInverse(p));
+  }
+
+  private void testNewKeyWithVerifier(KeyTemplate keyTemplate) throws Exception {
+    if (TestUtil.isTsan()) {
+      // This test times out in tsan mode.
+      return;
+    }
+    // Call newKey multiple times and make sure that it generates different keys.
+    int numTests = 3;
+    RsaSsaPssPrivateKey[] privKeys = new RsaSsaPssPrivateKey[numTests];
+    RsaSsaPssSignKeyManager signManager = new RsaSsaPssSignKeyManager();
+    Set<String> keys = new TreeSet<String>();
+
+    privKeys[0] =
+        (RsaSsaPssPrivateKey)
+            signManager.newKey(RsaSsaPssKeyFormat.parseFrom(keyTemplate.getValue()));
+    keys.add(TestUtil.hexEncode(privKeys[0].toByteArray()));
+
+    privKeys[1] = (RsaSsaPssPrivateKey) signManager.newKey(keyTemplate.getValue());
+    keys.add(TestUtil.hexEncode(privKeys[1].toByteArray()));
+
+    privKeys[2] =
+        RsaSsaPssPrivateKey.parseFrom(signManager.newKeyData(keyTemplate.getValue()).getValue());
+    keys.add(TestUtil.hexEncode(privKeys[2].toByteArray()));
+
+    assertEquals(numTests, keys.size());
+    // Check key.
+    for (int i = 0; i < numTests; i++) {
+      checkKey(privKeys[i]);
+    }
+
+    // Test whether signer works correctly with the corresponding verifier.
+    RsaSsaPssVerifyKeyManager verifyManager = new RsaSsaPssVerifyKeyManager();
+    for (int j = 0; j < numTests; j++) {
+      PublicKeySign signer = signManager.getPrimitive(privKeys[j]);
+      byte[] signature = signer.sign(msg);
+      for (int k = 0; k < numTests; k++) {
+        PublicKeyVerify verifier = verifyManager.getPrimitive(privKeys[k].getPublicKey());
+        if (j == k) { // The same key
+          try {
+            verifier.verify(signature, msg);
+          } catch (GeneralSecurityException ex) {
+            throw new AssertionError("Valid signature, should not throw exception", ex);
+          }
+        } else { // Different keys
+          try {
+            verifier.verify(signature, msg);
+            fail("Invalid signature, should have thrown exception");
+          } catch (GeneralSecurityException expected) {
+            // Expected
+          }
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testNewKeyWithVerifier() throws Exception {
+    testNewKeyWithVerifier(SignatureKeyTemplates.RSA_SSA_PSS_3072_SHA256_SHA256_32_F4);
+    testNewKeyWithVerifier(SignatureKeyTemplates.RSA_SSA_PSS_4096_SHA512_SHA512_64_F4);
+  }
+
+  @Test
+  public void testNewKeyWithCorruptedFormat() {
+    ByteString serialized = ByteString.copyFrom(new byte[128]);
+    KeyTemplate keyTemplate =
+        KeyTemplate.newBuilder()
+            .setTypeUrl(RsaSsaPssSignKeyManager.TYPE_URL)
+            .setValue(serialized)
+            .build();
+    RsaSsaPssSignKeyManager keyManager = new RsaSsaPssSignKeyManager();
+    try {
+      keyManager.newKey(serialized);
+      fail("Corrupted format, should have thrown exception");
+    } catch (GeneralSecurityException expected) {
+      // Expected
+    }
+    try {
+      keyManager.newKeyData(keyTemplate.getValue());
+      fail("Corrupted format, should have thrown exception");
+    } catch (GeneralSecurityException expected) {
+      // Expected
+    }
+  }
+
+  /** Tests that a public key is extracted properly from a private key. */
+  @Test
+  public void testGetPublicKeyData() throws Exception {
+    if (TestUtil.isTsan()) {
+      // This test times out in tsan mode.
+      return;
+    }
+    KeysetHandle privateHandle =
+        KeysetHandle.generateNew(SignatureKeyTemplates.RSA_SSA_PSS_4096_SHA512_SHA512_64_F4);
+    KeyData privateKeyData = TestUtil.getKeyset(privateHandle).getKey(0).getKeyData();
+    RsaSsaPssSignKeyManager privateManager = new RsaSsaPssSignKeyManager();
+    KeyData publicKeyData = privateManager.getPublicKeyData(privateKeyData.getValue());
+    assertEquals(RsaSsaPssVerifyKeyManager.TYPE_URL, publicKeyData.getTypeUrl());
+    assertEquals(KeyData.KeyMaterialType.ASYMMETRIC_PUBLIC, publicKeyData.getKeyMaterialType());
+    RsaSsaPssPrivateKey privateKey = RsaSsaPssPrivateKey.parseFrom(privateKeyData.getValue());
+    assertArrayEquals(
+        privateKey.getPublicKey().toByteArray(), publicKeyData.getValue().toByteArray());
+    RsaSsaPssVerifyKeyManager publicManager = new RsaSsaPssVerifyKeyManager();
+    PublicKeySign signer = privateManager.getPrimitive(privateKeyData.getValue());
+    PublicKeyVerify verifier = publicManager.getPrimitive(publicKeyData.getValue());
+    byte[] message = Random.randBytes(20);
+    try {
+      verifier.verify(signer.sign(message), message);
+    } catch (GeneralSecurityException e) {
+      fail("Should not fail: " + e);
+    }
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/signature/RsaSsaPssVerifyKeyManagerTest.java b/java/src/test/java/com/google/crypto/tink/signature/RsaSsaPssVerifyKeyManagerTest.java
new file mode 100644
index 0000000..91fa7ae
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/signature/RsaSsaPssVerifyKeyManagerTest.java
@@ -0,0 +1,129 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+package com.google.crypto.tink.signature;
+
+import static com.google.crypto.tink.TestUtil.assertExceptionContains;
+import static org.junit.Assert.fail;
+
+import com.google.crypto.tink.PublicKeyVerify;
+import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.proto.HashType;
+import com.google.crypto.tink.proto.RsaSsaPssPublicKey;
+import java.security.GeneralSecurityException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for RsaSsaPssVerifyKeyManager. */
+@RunWith(JUnit4.class)
+public final class RsaSsaPssVerifyKeyManagerTest {
+
+  static class NistTestVector {
+    byte[] modulus;
+    byte[] exponent;
+    byte[] msg;
+    byte[] sig;
+    HashType sigHash;
+    HashType mgf1Hash;
+    int saltLength;
+
+    public NistTestVector(
+        String modulus,
+        String exponent,
+        String msg,
+        String sig,
+        HashType sigHash,
+        HashType mgf1Hash,
+        int saltLength) {
+      this.modulus = TestUtil.hexDecode(modulus);
+      this.exponent = TestUtil.hexDecode(exponent);
+      this.msg = TestUtil.hexDecode(msg);
+      this.sig = TestUtil.hexDecode(sig);
+      this.sigHash = sigHash;
+      this.mgf1Hash = mgf1Hash;
+      this.saltLength = saltLength;
+    }
+  }
+
+  // Test vector from
+  // https://csrc.nist.gov/Projects/Cryptographic-Algorithm-Validation-Program/Digital-Signatures
+  final NistTestVector[] nistTestVectors = {
+    new NistTestVector(
+        "a47d04e7cacdba4ea26eca8a4c6e14563c2ce03b623b768c0d49868a57121301dbf783d82f4c055e73960e70550187d0af62ac3496f0a3d9103c2eb7919a72752fa7ce8c688d81e3aee99468887a15288afbb7acb845b7c522b5c64e678fcd3d22feb84b44272700be527d2b2025a3f83c2383bf6a39cf5b4e48b3cf2f56eef0dfff18555e31037b915248694876f3047814415164f2c660881e694b58c28038a032ad25634aad7b39171dee368e3d59bfb7299e4601d4587e68caaf8db457b75af42fc0cf1ae7caced286d77fac6cedb03ad94f1433d2c94d08e60bc1fdef0543cd2951e765b38230fdd18de5d2ca627ddc032fe05bbd2ff21e2db1c2f94d8b",
+        "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010e43f",
+        "e002377affb04f0fe4598de9d92d31d6c786040d5776976556a2cfc55e54a1dcb3cb1b126bd6a4bed2a184990ccea773fcc79d246553e6c64f686d21ad4152673cafec22aeb40f6a084e8a5b4991f4c64cf8a927effd0fd775e71e8329e41fdd4457b3911173187b4f09a817d79ea2397fc12dfe3d9c9a0290c8ead31b6690a6",
+        "4f9b425c2058460e4ab2f5c96384da2327fd29150f01955a76b4efe956af06dc08779a374ee4607eab61a93adc5608f4ec36e47f2a0f754e8ff839a8a19b1db1e884ea4cf348cd455069eb87afd53645b44e28a0a56808f5031da5ba9112768dfbfca44ebe63a0c0572b731d66122fb71609be1480faa4e4f75e43955159d70f081e2a32fbb19a48b9f162cf6b2fb445d2d6994bc58910a26b5943477803cdaaa1bd74b0da0a5d053d8b1dc593091db5388383c26079f344e2aea600d0e324164b450f7b9b465111b7265f3b1b063089ae7e2623fc0fda8052cf4bf3379102fbf71d7c98e8258664ceed637d20f95ff0111881e650ce61f251d9c3a629ef222d",
+        HashType.SHA256,
+        HashType.SHA256,
+        32),
+    new NistTestVector(
+        "99a5c8d094a5f917034667a0408b7ecfcaacc3f9784444e21773c3461ec355f0d0f52a5db0568a71d388696788ef66ae7340c6b28dbf925fe83557986575f79cca69217221397ed5808a26f7e7e714c93235f914d45c4a9af4619b20f511ad644bd3412dfdf0ff717f7aac746f310bfa9a141ac3dbf01c1fc74febd197938419c262293505c35f402f9053ad13c51a5960ecde55ec829e953f941af733e58705913767e7a7200d1d09e7e7e2d269fa29a558bb16304b059f13f4ca560a8101fe3720b4a779ec126427326caa132a3d3611d7dbc50336fac789ec406b397e1e36d7daf9b624bf639c82b859288747690c730c980b2f5a239dd95ad5389a2ec90c5778604713710383ae55d4d28c06d4ac26f0d1231f1d6762c8e0d918118156bc637760daea184746b8dcf6f61db274a7ddceaa074937ababad4549b97ab992494a807208abd789823f5d75c4b994089c8072cfc254e0d8202fd896476e96ad9d309a0e8e7301282f07eb2ae8edefb7dbbe13b96e8b4024c6b84de0a05e150285",
+        "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008a649",
+        "cc21593a6a0f737e2970b7c07984b070d761726296a07e24e056e68ff846b29cc1548179843d74dcee86479858b2c16e4cb84f2544b4ecdcb4dd43a04bb7183a768ae44a2712bf9ad47883acc2812f958306890ebea408c92eb4f001ed7dbf55f3a9c8d6d9f61e5fe32eb3253e59c18e863169478cd69b9155c335db66016f96",
+        "0aa572a6845b870b8909a683bb7e6e7616f77beff28746116d8bc4b7335546b51e8006ed0fc9a0d66f63ce0b9ebf792d7efd4305d7624d545400a5fd6a06b78f174b86803f7cd1cc93e3a97286f0ea590e40ff26195aa219fe1510a016785223606d9311a16c59a8fe4a6da6ecd0c1d7775039290c2aaa17ed1eb1b54374f7e572db13cca3a638575f8004aa54a2fa98422fc07e43ad3a20dd93001493442677d883914dc74ec1cbebbbd3d2b6bad4666d91457b69b46a1a61f21298f1a67942ec86c876322dd366ed167814e9c8fc9040c5b4b7a859bbd880cb6bc241b9e327ce779e0783b1cf445e0b2f5771b3f5822a1364391c154dc506fff1fb9d9a35f80199a6b30b4b92b92619a40e21aea19284015863c44866c61ed904a7ad19ee04d966c0aae390636243565581ff20bd6e3cfb6e31f5afba964b311dc2d023a21998c8dd50ca453699190bd467429e2f88ace29c4d1da4da61aac1eda2380230aa8dbb63c75a3c1ec04da3a1f880c9c747acdb74a8395af58f5f044015ccaf6e94",
+        HashType.SHA512,
+        HashType.SHA512,
+        0),
+  };
+
+  @Test
+  public void testNistTestVector() throws Exception {
+    for (NistTestVector t : nistTestVectors) {
+      RsaSsaPssPublicKey pubKey =
+          TestUtil.createRsaSsaPssPubKey(
+              t.modulus, t.exponent, t.sigHash, t.mgf1Hash, t.saltLength);
+      RsaSsaPssVerifyKeyManager keyManager = new RsaSsaPssVerifyKeyManager();
+      PublicKeyVerify verifier = keyManager.getPrimitive(pubKey);
+      try {
+        verifier.verify(t.sig, t.msg);
+      } catch (GeneralSecurityException e) {
+        fail("Valid signature, should not throw exception" + e);
+      }
+    }
+  }
+
+  @Test
+  public void testSmallModulus() throws Exception {
+    try {
+      RsaSsaPssPublicKey pubKey =
+          TestUtil.createRsaSsaPssPubKey(
+              TestUtil.hexDecode("23"),
+              TestUtil.hexDecode("03"),
+              HashType.SHA256,
+              HashType.SHA256,
+              32);
+      RsaSsaPssVerifyKeyManager keyManager = new RsaSsaPssVerifyKeyManager();
+      keyManager.getPrimitive(pubKey);
+      fail("Invalid modulus, should have thrown exception");
+    } catch (GeneralSecurityException e) {
+      assertExceptionContains(e, "2048-bit");
+    }
+  }
+
+  @Test
+  public void testInvalidHash() throws Exception {
+    NistTestVector t = nistTestVectors[0];
+    try {
+      RsaSsaPssPublicKey pubKey =
+          TestUtil.createRsaSsaPssPubKey(t.modulus, t.exponent, HashType.SHA1, HashType.SHA256, 20);
+      RsaSsaPssVerifyKeyManager keyManager = new RsaSsaPssVerifyKeyManager();
+      keyManager.getPrimitive(pubKey);
+      fail("Invalid hash, should have thrown exception");
+    } catch (GeneralSecurityException e) {
+      assertExceptionContains(e, "SHA1");
+    }
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/signature/SignatureConfigTest.java b/java/src/test/java/com/google/crypto/tink/signature/SignatureConfigTest.java
index fa7900b..0d3993f 100644
--- a/java/src/test/java/com/google/crypto/tink/signature/SignatureConfigTest.java
+++ b/java/src/test/java/com/google/crypto/tink/signature/SignatureConfigTest.java
@@ -152,7 +152,7 @@
   @Test
   public void testConfigContents_LATEST() throws Exception {
     RegistryConfig config = SignatureConfig.LATEST;
-    assertEquals(4, config.getEntryCount());
+    assertEquals(8, config.getEntryCount());
     assertEquals("TINK_SIGNATURE", config.getConfigName());
 
     TestUtil.verifyConfigEntry(
@@ -171,17 +171,45 @@
         0);
     TestUtil.verifyConfigEntry(
         config.getEntry(2),
+        "TinkPublicKeySign",
+        "PublicKeySign",
+        "type.googleapis.com/google.crypto.tink.RsaSsaPkcs1PrivateKey",
+        true,
+        0);
+    TestUtil.verifyConfigEntry(
+        config.getEntry(3),
+        "TinkPublicKeySign",
+        "PublicKeySign",
+        "type.googleapis.com/google.crypto.tink.RsaSsaPssPrivateKey",
+        true,
+        0);
+    TestUtil.verifyConfigEntry(
+        config.getEntry(4),
         "TinkPublicKeyVerify",
         "PublicKeyVerify",
         "type.googleapis.com/google.crypto.tink.EcdsaPublicKey",
         true,
         0);
     TestUtil.verifyConfigEntry(
-        config.getEntry(3),
+        config.getEntry(5),
         "TinkPublicKeyVerify",
         "PublicKeyVerify",
         "type.googleapis.com/google.crypto.tink.Ed25519PublicKey",
         true,
         0);
+    TestUtil.verifyConfigEntry(
+        config.getEntry(6),
+        "TinkPublicKeyVerify",
+        "PublicKeyVerify",
+        "type.googleapis.com/google.crypto.tink.RsaSsaPkcs1PublicKey",
+        true,
+        0);
+    TestUtil.verifyConfigEntry(
+        config.getEntry(7),
+        "TinkPublicKeyVerify",
+        "PublicKeyVerify",
+        "type.googleapis.com/google.crypto.tink.RsaSsaPssPublicKey",
+        true,
+        0);
   }
 }
diff --git a/java/src/test/java/com/google/crypto/tink/signature/SignatureKeyTemplatesTest.java b/java/src/test/java/com/google/crypto/tink/signature/SignatureKeyTemplatesTest.java
index fac7a15..3fc2750 100644
--- a/java/src/test/java/com/google/crypto/tink/signature/SignatureKeyTemplatesTest.java
+++ b/java/src/test/java/com/google/crypto/tink/signature/SignatureKeyTemplatesTest.java
@@ -25,6 +25,9 @@
 import com.google.crypto.tink.proto.HashType;
 import com.google.crypto.tink.proto.KeyTemplate;
 import com.google.crypto.tink.proto.OutputPrefixType;
+import com.google.crypto.tink.proto.RsaSsaPkcs1KeyFormat;
+import com.google.crypto.tink.proto.RsaSsaPssKeyFormat;
+import java.math.BigInteger;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -46,6 +49,19 @@
   }
 
   @Test
+  public void testECDSA_P256_IEEE_P1363() throws Exception {
+    KeyTemplate template = SignatureKeyTemplates.ECDSA_P256_IEEE_P1363;
+    assertEquals(EcdsaSignKeyManager.TYPE_URL, template.getTypeUrl());
+    assertEquals(OutputPrefixType.TINK, template.getOutputPrefixType());
+    EcdsaKeyFormat format = EcdsaKeyFormat.parseFrom(template.getValue());
+
+    assertTrue(format.hasParams());
+    assertEquals(HashType.SHA256, format.getParams().getHashType());
+    assertEquals(EllipticCurveType.NIST_P256, format.getParams().getCurve());
+    assertEquals(EcdsaSignatureEncoding.IEEE_P1363, format.getParams().getEncoding());
+  }
+
+  @Test
   public void testECDSA_P384() throws Exception {
     KeyTemplate template = SignatureKeyTemplates.ECDSA_P384;
     assertEquals(EcdsaSignKeyManager.TYPE_URL, template.getTypeUrl());
@@ -59,8 +75,21 @@
   }
 
   @Test
-  public void testECDSA_P521() throws Exception {
-    KeyTemplate template = SignatureKeyTemplates.ECDSA_P521;
+  public void testECDSA_P384_IEEE_P1363() throws Exception {
+    KeyTemplate template = SignatureKeyTemplates.ECDSA_P384_IEEE_P1363;
+    assertEquals(EcdsaSignKeyManager.TYPE_URL, template.getTypeUrl());
+    assertEquals(OutputPrefixType.TINK, template.getOutputPrefixType());
+    EcdsaKeyFormat format = EcdsaKeyFormat.parseFrom(template.getValue());
+
+    assertTrue(format.hasParams());
+    assertEquals(HashType.SHA512, format.getParams().getHashType());
+    assertEquals(EllipticCurveType.NIST_P384, format.getParams().getCurve());
+    assertEquals(EcdsaSignatureEncoding.IEEE_P1363, format.getParams().getEncoding());
+  }
+
+  @Test
+  public void testECDSA_P521_IEEE_P1363() throws Exception {
+    KeyTemplate template = SignatureKeyTemplates.ECDSA_P521_IEEE_P1363;
     assertEquals(EcdsaSignKeyManager.TYPE_URL, template.getTypeUrl());
     assertEquals(OutputPrefixType.TINK, template.getOutputPrefixType());
     EcdsaKeyFormat format = EcdsaKeyFormat.parseFrom(template.getValue());
@@ -68,7 +97,7 @@
     assertTrue(format.hasParams());
     assertEquals(HashType.SHA512, format.getParams().getHashType());
     assertEquals(EllipticCurveType.NIST_P521, format.getParams().getCurve());
-    assertEquals(EcdsaSignatureEncoding.DER, format.getParams().getEncoding());
+    assertEquals(EcdsaSignatureEncoding.IEEE_P1363, format.getParams().getEncoding());
   }
 
   @Test
@@ -93,6 +122,66 @@
     KeyTemplate template = SignatureKeyTemplates.ED25519;
     assertEquals(Ed25519PrivateKeyManager.TYPE_URL, template.getTypeUrl());
     assertEquals(OutputPrefixType.TINK, template.getOutputPrefixType());
-    assertTrue(template.getValue().isEmpty());  // Empty format.
+    assertTrue(template.getValue().isEmpty()); // Empty format.
+  }
+
+  @Test
+  public void testRSA_SSA_PKCS1_3072_SHA256_F4() throws Exception {
+    KeyTemplate template = SignatureKeyTemplates.RSA_SSA_PKCS1_3072_SHA256_F4;
+    assertEquals(RsaSsaPkcs1SignKeyManager.TYPE_URL, template.getTypeUrl());
+    assertEquals(OutputPrefixType.TINK, template.getOutputPrefixType());
+    RsaSsaPkcs1KeyFormat format = RsaSsaPkcs1KeyFormat.parseFrom(template.getValue());
+
+    assertTrue(format.hasParams());
+    assertEquals(HashType.SHA256, format.getParams().getHashType());
+    assertEquals(3072, format.getModulusSizeInBits());
+    assertEquals(
+        BigInteger.valueOf(65537), new BigInteger(1, format.getPublicExponent().toByteArray()));
+  }
+
+  @Test
+  public void testRSA_SSA_PKCS1_4096_SHA512_F4() throws Exception {
+    KeyTemplate template = SignatureKeyTemplates.RSA_SSA_PKCS1_4096_SHA512_F4;
+    assertEquals(RsaSsaPkcs1SignKeyManager.TYPE_URL, template.getTypeUrl());
+    assertEquals(OutputPrefixType.TINK, template.getOutputPrefixType());
+    RsaSsaPkcs1KeyFormat format = RsaSsaPkcs1KeyFormat.parseFrom(template.getValue());
+
+    assertTrue(format.hasParams());
+    assertEquals(HashType.SHA512, format.getParams().getHashType());
+    assertEquals(4096, format.getModulusSizeInBits());
+    assertEquals(
+        BigInteger.valueOf(65537), new BigInteger(1, format.getPublicExponent().toByteArray()));
+  }
+
+  @Test
+  public void testRSA_SSA_PSS_3072_SHA256_SHA256_32_F4() throws Exception {
+    KeyTemplate template = SignatureKeyTemplates.RSA_SSA_PSS_3072_SHA256_SHA256_32_F4;
+    assertEquals(RsaSsaPssSignKeyManager.TYPE_URL, template.getTypeUrl());
+    assertEquals(OutputPrefixType.TINK, template.getOutputPrefixType());
+    RsaSsaPssKeyFormat format = RsaSsaPssKeyFormat.parseFrom(template.getValue());
+
+    assertTrue(format.hasParams());
+    assertEquals(HashType.SHA256, format.getParams().getSigHash());
+    assertEquals(HashType.SHA256, format.getParams().getMgf1Hash());
+    assertEquals(32, format.getParams().getSaltLength());
+    assertEquals(3072, format.getModulusSizeInBits());
+    assertEquals(
+        BigInteger.valueOf(65537), new BigInteger(1, format.getPublicExponent().toByteArray()));
+  }
+
+  @Test
+  public void testRSA_SSA_PSS_4096_SHA512_SHA512_64_F4() throws Exception {
+    KeyTemplate template = SignatureKeyTemplates.RSA_SSA_PSS_4096_SHA512_SHA512_64_F4;
+    assertEquals(RsaSsaPssSignKeyManager.TYPE_URL, template.getTypeUrl());
+    assertEquals(OutputPrefixType.TINK, template.getOutputPrefixType());
+    RsaSsaPssKeyFormat format = RsaSsaPssKeyFormat.parseFrom(template.getValue());
+
+    assertTrue(format.hasParams());
+    assertEquals(HashType.SHA512, format.getParams().getSigHash());
+    assertEquals(HashType.SHA512, format.getParams().getMgf1Hash());
+    assertEquals(64, format.getParams().getSaltLength());
+    assertEquals(4096, format.getModulusSizeInBits());
+    assertEquals(
+        BigInteger.valueOf(65537), new BigInteger(1, format.getPublicExponent().toByteArray()));
   }
 }
diff --git a/java/src/test/java/com/google/crypto/tink/streamingaead/AesCtrHmacStreamingKeyManagerTest.java b/java/src/test/java/com/google/crypto/tink/streamingaead/AesCtrHmacStreamingKeyManagerTest.java
index 347a74d..bdb8ac5 100644
--- a/java/src/test/java/com/google/crypto/tink/streamingaead/AesCtrHmacStreamingKeyManagerTest.java
+++ b/java/src/test/java/com/google/crypto/tink/streamingaead/AesCtrHmacStreamingKeyManagerTest.java
@@ -81,6 +81,23 @@
   }
 
   @Test
+  public void testSkip() throws Exception {
+    AesCtrHmacStreamingKey key =
+        AesCtrHmacStreamingKey.newBuilder()
+            .setVersion(0)
+            .setKeyValue(ByteString.copyFrom(Random.randBytes(20)))
+            .setParams(keyParams)
+            .build();
+    StreamingAead streamingAead = keyManager.getPrimitive(key);
+    int offset = 0;
+    int plaintextSize = 1 << 16;
+    // Runs the test with different sizes for the chunks to skip.
+    StreamingTestUtil.testSkipWithStream(streamingAead, offset, plaintextSize, 1);
+    StreamingTestUtil.testSkipWithStream(streamingAead, offset, plaintextSize, 64);
+    StreamingTestUtil.testSkipWithStream(streamingAead, offset, plaintextSize, 300);
+  }
+
+  @Test
   public void testNewKeyMultipleTimes() throws Exception {
     AesCtrHmacStreamingKeyFormat keyFormat =
         AesCtrHmacStreamingKeyFormat.newBuilder().setParams(keyParams).setKeySize(16).build();
diff --git a/java/src/test/java/com/google/crypto/tink/streamingaead/AesGcmHkdfStreamingKeyManagerTest.java b/java/src/test/java/com/google/crypto/tink/streamingaead/AesGcmHkdfStreamingKeyManagerTest.java
index aa3154d..915ff97 100644
--- a/java/src/test/java/com/google/crypto/tink/streamingaead/AesGcmHkdfStreamingKeyManagerTest.java
+++ b/java/src/test/java/com/google/crypto/tink/streamingaead/AesGcmHkdfStreamingKeyManagerTest.java
@@ -77,6 +77,23 @@
   }
 
   @Test
+  public void testSkip() throws Exception {
+    AesGcmHkdfStreamingKey key =
+        AesGcmHkdfStreamingKey.newBuilder()
+            .setVersion(0)
+            .setKeyValue(ByteString.copyFrom(Random.randBytes(20)))
+            .setParams(keyParams)
+            .build();
+    StreamingAead streamingAead = keyManager.getPrimitive(key);
+    int offset = 0;
+    int plaintextSize = 1 << 16;
+    // Runs the test with different sizes for the chunks to skip.
+    StreamingTestUtil.testSkipWithStream(streamingAead, offset, plaintextSize, 1);
+    StreamingTestUtil.testSkipWithStream(streamingAead, offset, plaintextSize, 64);
+    StreamingTestUtil.testSkipWithStream(streamingAead, offset, plaintextSize, 300);
+  }
+
+  @Test
   public void testNewKeyMultipleTimes() throws Exception {
     AesGcmHkdfStreamingKeyFormat keyFormat =
         AesGcmHkdfStreamingKeyFormat.newBuilder().setParams(keyParams).setKeySize(16).build();
diff --git a/java/src/test/java/com/google/crypto/tink/streamingaead/StreamingAeadFactoryTest.java b/java/src/test/java/com/google/crypto/tink/streamingaead/StreamingAeadFactoryTest.java
index 0a1c1f2..658e0c6 100644
--- a/java/src/test/java/com/google/crypto/tink/streamingaead/StreamingAeadFactoryTest.java
+++ b/java/src/test/java/com/google/crypto/tink/streamingaead/StreamingAeadFactoryTest.java
@@ -163,7 +163,8 @@
       StreamingAeadFactory.getPrimitive(keysetHandle);
       fail("Expected GeneralSecurityException");
     } catch (GeneralSecurityException e) {
-      assertExceptionContains(e, "invalid StreamingAead key material");
+      assertExceptionContains(
+          e, "not match requested primitive type com.google.crypto.tink.StreamingAead");
     }
 
     // invalid as the primary key.
@@ -172,7 +173,8 @@
       StreamingAeadFactory.getPrimitive(keysetHandle);
       fail("Expected GeneralSecurityException");
     } catch (GeneralSecurityException e) {
-      assertExceptionContains(e, "invalid StreamingAead key material");
+      assertExceptionContains(
+          e, "not match requested primitive type com.google.crypto.tink.StreamingAead");
     }
   }
 }
diff --git a/java/src/test/java/com/google/crypto/tink/streamingaead/StreamingAeadIntegrationTest.java b/java/src/test/java/com/google/crypto/tink/streamingaead/StreamingAeadIntegrationTest.java
new file mode 100644
index 0000000..77be39b
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/streamingaead/StreamingAeadIntegrationTest.java
@@ -0,0 +1,147 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.streamingaead;
+
+import static com.google.crypto.tink.TestUtil.assertExceptionContains;
+import static org.junit.Assert.fail;
+
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.StreamingAead;
+import com.google.crypto.tink.StreamingTestUtil;
+import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.proto.KeyStatusType;
+import com.google.crypto.tink.proto.Keyset.Key;
+import com.google.crypto.tink.proto.OutputPrefixType;
+import com.google.crypto.tink.subtle.Random;
+import java.io.IOException;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests which run the everything for the Streaming Aeads. */
+@RunWith(JUnit4.class)
+public class StreamingAeadIntegrationTest {
+  private static final int KDF_KEY_SIZE = 16;
+  private static final int AES_KEY_SIZE = 16;
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    StreamingAeadConfig.register();
+  }
+
+  @Test
+  public void testBasicAesCtrHmacStreamingAead() throws Exception {
+    byte[] keyValue = Random.randBytes(KDF_KEY_SIZE);
+    int derivedKeySize = AES_KEY_SIZE;
+    int ciphertextSegmentSize = 128;
+    KeysetHandle keysetHandle =
+        TestUtil.createKeysetHandle(
+            TestUtil.createKeyset(
+                TestUtil.createKey(
+                    TestUtil.createAesCtrHmacStreamingKeyData(
+                        keyValue, derivedKeySize, ciphertextSegmentSize),
+                    42,
+                    KeyStatusType.ENABLED,
+                    OutputPrefixType.RAW)));
+    StreamingAead streamingAead = keysetHandle.getPrimitive(StreamingAead.class);
+    StreamingTestUtil.testEncryptionAndDecryption(streamingAead);
+  }
+
+  @Test
+  public void testBasicAesGcmHkdfStreamingAead() throws Exception {
+    byte[] keyValue = Random.randBytes(KDF_KEY_SIZE);
+    int derivedKeySize = AES_KEY_SIZE;
+    int ciphertextSegmentSize = 128;
+    KeysetHandle keysetHandle =
+        TestUtil.createKeysetHandle(
+            TestUtil.createKeyset(
+                TestUtil.createKey(
+                    TestUtil.createAesGcmHkdfStreamingKeyData(
+                        keyValue, derivedKeySize, ciphertextSegmentSize),
+                    42,
+                    KeyStatusType.ENABLED,
+                    OutputPrefixType.RAW)));
+    StreamingAead streamingAead = keysetHandle.getPrimitive(StreamingAead.class);
+    StreamingTestUtil.testEncryptionAndDecryption(streamingAead);
+  }
+
+
+  @Test
+  public void testMultipleKeys() throws Exception {
+    byte[] primaryKeyValue = Random.randBytes(KDF_KEY_SIZE);
+    byte[] otherKeyValue = Random.randBytes(KDF_KEY_SIZE);
+    byte[] anotherKeyValue = Random.randBytes(KDF_KEY_SIZE);
+    int derivedKeySize = AES_KEY_SIZE;
+
+    Key primaryKey = TestUtil.createKey(
+        TestUtil.createAesGcmHkdfStreamingKeyData(
+            primaryKeyValue, derivedKeySize, 512),
+        42,
+        KeyStatusType.ENABLED,
+        OutputPrefixType.RAW);
+    // Another key with a smaller segment size than the primary key
+    Key otherKey = TestUtil.createKey(
+        TestUtil.createAesCtrHmacStreamingKeyData(
+            otherKeyValue, derivedKeySize, 256),
+        43,
+        KeyStatusType.ENABLED,
+        OutputPrefixType.RAW);
+    // Another key with a larger segment size than the primary key
+    Key anotherKey = TestUtil.createKey(
+        TestUtil.createAesGcmHkdfStreamingKeyData(
+            anotherKeyValue, derivedKeySize, 1024),
+        72,
+        KeyStatusType.ENABLED,
+        OutputPrefixType.RAW);
+
+    KeysetHandle keysetHandle =
+        TestUtil.createKeysetHandle(TestUtil.createKeyset(primaryKey, otherKey, anotherKey));
+    StreamingAead streamingAead = keysetHandle.getPrimitive(StreamingAead.class);
+
+    StreamingAead primaryAead =
+        TestUtil.createKeysetHandle(TestUtil.createKeyset(primaryKey))
+            .getPrimitive(StreamingAead.class);
+    StreamingAead otherAead =
+        TestUtil.createKeysetHandle(TestUtil.createKeyset(otherKey))
+            .getPrimitive(StreamingAead.class);
+    StreamingAead anotherAead =
+        TestUtil.createKeysetHandle(TestUtil.createKeyset(anotherKey))
+            .getPrimitive(StreamingAead.class);
+
+    StreamingTestUtil.testEncryptionAndDecryption(streamingAead, streamingAead);
+    StreamingTestUtil.testEncryptionAndDecryption(streamingAead, primaryAead);
+    StreamingTestUtil.testEncryptionAndDecryption(primaryAead, streamingAead);
+    StreamingTestUtil.testEncryptionAndDecryption(otherAead, streamingAead);
+    StreamingTestUtil.testEncryptionAndDecryption(anotherAead, streamingAead);
+    StreamingTestUtil.testEncryptionAndDecryption(primaryAead, primaryAead);
+    StreamingTestUtil.testEncryptionAndDecryption(otherAead, otherAead);
+    StreamingTestUtil.testEncryptionAndDecryption(anotherAead, anotherAead);
+    try {
+      StreamingTestUtil.testEncryptionAndDecryption(otherAead, primaryAead);
+      fail("No matching key, should have thrown an exception");
+    } catch (IOException expected) {
+      assertExceptionContains(expected, "No matching key");
+    }
+    try {
+      StreamingTestUtil.testEncryptionAndDecryption(anotherAead, primaryAead);
+      fail("No matching key, should have thrown an exception");
+    } catch (IOException expected) {
+      assertExceptionContains(expected, "No matching key");
+    }
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/streamingaead/StreamingAeadWrapperTest.java b/java/src/test/java/com/google/crypto/tink/streamingaead/StreamingAeadWrapperTest.java
new file mode 100644
index 0000000..7fd657f
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/streamingaead/StreamingAeadWrapperTest.java
@@ -0,0 +1,158 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.streamingaead;
+
+import static com.google.crypto.tink.TestUtil.assertExceptionContains;
+import static org.junit.Assert.fail;
+
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.Registry;
+import com.google.crypto.tink.StreamingAead;
+import com.google.crypto.tink.StreamingTestUtil;
+import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.daead.DeterministicAeadConfig;
+import com.google.crypto.tink.proto.KeyStatusType;
+import com.google.crypto.tink.proto.Keyset.Key;
+import com.google.crypto.tink.proto.OutputPrefixType;
+import com.google.crypto.tink.subtle.Random;
+import java.io.IOException;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for StreamingAeadWrapper. */
+@RunWith(JUnit4.class)
+public class StreamingAeadWrapperTest {
+  private static final int KDF_KEY_SIZE = 16;
+  private static final int AES_KEY_SIZE = 16;
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    StreamingAeadConfig.register();
+    DeterministicAeadConfig.register(); // need this for testInvalidKeyMaterial.
+  }
+
+  @Test
+  public void testBasicAesCtrHmacStreamingAead() throws Exception {
+    byte[] keyValue = Random.randBytes(KDF_KEY_SIZE);
+    int derivedKeySize = AES_KEY_SIZE;
+    int ciphertextSegmentSize = 128;
+    KeysetHandle keysetHandle =
+        TestUtil.createKeysetHandle(
+            TestUtil.createKeyset(
+                TestUtil.createKey(
+                    TestUtil.createAesCtrHmacStreamingKeyData(
+                        keyValue, derivedKeySize, ciphertextSegmentSize),
+                    42,
+                    KeyStatusType.ENABLED,
+                    OutputPrefixType.RAW)));
+    StreamingAead streamingAead =
+        new StreamingAeadWrapper().wrap(Registry.getPrimitives(keysetHandle));
+    StreamingTestUtil.testEncryptionAndDecryption(streamingAead);
+  }
+
+  @Test
+  public void testBasicAesGcmHkdfStreamingAead() throws Exception {
+    byte[] keyValue = Random.randBytes(KDF_KEY_SIZE);
+    int derivedKeySize = AES_KEY_SIZE;
+    int ciphertextSegmentSize = 128;
+    KeysetHandle keysetHandle =
+        TestUtil.createKeysetHandle(
+            TestUtil.createKeyset(
+                TestUtil.createKey(
+                    TestUtil.createAesGcmHkdfStreamingKeyData(
+                        keyValue, derivedKeySize, ciphertextSegmentSize),
+                    42,
+                    KeyStatusType.ENABLED,
+                    OutputPrefixType.RAW)));
+    StreamingAead streamingAead =
+        new StreamingAeadWrapper().wrap(Registry.getPrimitives(keysetHandle));
+    StreamingTestUtil.testEncryptionAndDecryption(streamingAead);
+  }
+
+  @Test
+  public void testMultipleKeys() throws Exception {
+    byte[] primaryKeyValue = Random.randBytes(KDF_KEY_SIZE);
+    byte[] otherKeyValue = Random.randBytes(KDF_KEY_SIZE);
+    byte[] anotherKeyValue = Random.randBytes(KDF_KEY_SIZE);
+    int derivedKeySize = AES_KEY_SIZE;
+
+    Key primaryKey =
+        TestUtil.createKey(
+            TestUtil.createAesGcmHkdfStreamingKeyData(primaryKeyValue, derivedKeySize, 512),
+            42,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.RAW);
+    // Another key with a smaller segment size than the primary key
+    Key otherKey =
+        TestUtil.createKey(
+            TestUtil.createAesCtrHmacStreamingKeyData(otherKeyValue, derivedKeySize, 256),
+            43,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.RAW);
+    // Another key with a larger segment size than the primary key
+    Key anotherKey =
+        TestUtil.createKey(
+            TestUtil.createAesGcmHkdfStreamingKeyData(anotherKeyValue, derivedKeySize, 1024),
+            72,
+            KeyStatusType.ENABLED,
+            OutputPrefixType.RAW);
+
+    KeysetHandle keysetHandle =
+        TestUtil.createKeysetHandle(TestUtil.createKeyset(primaryKey, otherKey, anotherKey));
+    StreamingAead streamingAead =
+        new StreamingAeadWrapper().wrap(Registry.getPrimitives(keysetHandle));
+
+    StreamingAead primaryAead =
+        new StreamingAeadWrapper()
+            .wrap(
+                Registry.getPrimitives(
+                    TestUtil.createKeysetHandle(TestUtil.createKeyset(primaryKey))));
+    StreamingAead otherAead =
+        new StreamingAeadWrapper()
+            .wrap(
+                Registry.getPrimitives(
+                    TestUtil.createKeysetHandle(TestUtil.createKeyset(otherKey))));
+    StreamingAead anotherAead =
+        new StreamingAeadWrapper()
+            .wrap(
+                Registry.getPrimitives(
+                    TestUtil.createKeysetHandle(TestUtil.createKeyset(anotherKey))));
+
+    StreamingTestUtil.testEncryptionAndDecryption(streamingAead, streamingAead);
+    StreamingTestUtil.testEncryptionAndDecryption(streamingAead, primaryAead);
+    StreamingTestUtil.testEncryptionAndDecryption(primaryAead, streamingAead);
+    StreamingTestUtil.testEncryptionAndDecryption(otherAead, streamingAead);
+    StreamingTestUtil.testEncryptionAndDecryption(anotherAead, streamingAead);
+    StreamingTestUtil.testEncryptionAndDecryption(primaryAead, primaryAead);
+    StreamingTestUtil.testEncryptionAndDecryption(otherAead, otherAead);
+    StreamingTestUtil.testEncryptionAndDecryption(anotherAead, anotherAead);
+    try {
+      StreamingTestUtil.testEncryptionAndDecryption(otherAead, primaryAead);
+      fail("No matching key, should have thrown an exception");
+    } catch (IOException expected) {
+      assertExceptionContains(expected, "No matching key");
+    }
+    try {
+      StreamingTestUtil.testEncryptionAndDecryption(anotherAead, primaryAead);
+      fail("No matching key, should have thrown an exception");
+    } catch (IOException expected) {
+      assertExceptionContains(expected, "No matching key");
+    }
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/subtle/AeadThreadSafetyTest.java b/java/src/test/java/com/google/crypto/tink/subtle/AeadThreadSafetyTest.java
index 9411054..f679928 100644
--- a/java/src/test/java/com/google/crypto/tink/subtle/AeadThreadSafetyTest.java
+++ b/java/src/test/java/com/google/crypto/tink/subtle/AeadThreadSafetyTest.java
@@ -161,4 +161,11 @@
     Aead cipher = new ChaCha20Poly1305(key);
     testEncryptionDecryption(cipher, 5, 128, 20);
   }
+
+  @Test
+  public void testXChaChaPoly1305() throws Exception {
+    byte[] key = Random.randBytes(32);
+    Aead cipher = new XChaCha20Poly1305(key);
+    testEncryptionDecryption(cipher, 5, 128, 20);
+  }
 }
diff --git a/java/src/test/java/com/google/crypto/tink/subtle/AesCtrHmacStreamingTest.java b/java/src/test/java/com/google/crypto/tink/subtle/AesCtrHmacStreamingTest.java
index c58561a..1eb7f40 100644
--- a/java/src/test/java/com/google/crypto/tink/subtle/AesCtrHmacStreamingTest.java
+++ b/java/src/test/java/com/google/crypto/tink/subtle/AesCtrHmacStreamingTest.java
@@ -316,6 +316,27 @@
   }
 
   @Test
+  public void testSkipWithStream() throws Exception {
+    byte[] ikm = TestUtil.hexDecode("000102030405060708090a0b0c0d0e0f");
+    int keySize = 16;
+    int tagSize = 12;
+    int segmentSize = 256;
+    int offset = 8;
+    int plaintextSize = 1 << 16;
+    AesCtrHmacStreaming ags =
+        new AesCtrHmacStreaming(
+            ikm, "HmacSha256", keySize, "HmacSha256", tagSize, segmentSize, offset);
+    // Smallest possible chunk size
+    StreamingTestUtil.testSkipWithStream(ags, offset, plaintextSize, 1);
+    // Chunk size < segmentSize
+    StreamingTestUtil.testSkipWithStream(ags, offset, plaintextSize, 37);
+    // Chunk size > segmentSize
+    StreamingTestUtil.testSkipWithStream(ags, offset, plaintextSize, 384);
+    // Chunk size > 3*segmentSize
+    StreamingTestUtil.testSkipWithStream(ags, offset, plaintextSize, 800);
+  }
+
+  @Test
   public void testModifiedCiphertextWithSeekableByteChannel() throws Exception {
     byte[] ikm = TestUtil.hexDecode("000102030405060708090a0b0c0d0e0f");
     int keySize = 16;
@@ -369,6 +390,8 @@
   /** Encrypt some plaintext to a file, then decrypt from the file */
   @Test
   public void testFileEncryption() throws Exception {
-    StreamingTestUtil.testFileEncryption(createAesCtrHmacStreaming(), tmpFolder.newFile());
+    int plaintextSize = 1 << 20;
+    StreamingTestUtil.testFileEncryption(
+        createAesCtrHmacStreaming(), tmpFolder.newFile(), plaintextSize);
   }
 }
diff --git a/java/src/test/java/com/google/crypto/tink/subtle/AesEaxJceTest.java b/java/src/test/java/com/google/crypto/tink/subtle/AesEaxJceTest.java
index b165d85..56da23a 100644
--- a/java/src/test/java/com/google/crypto/tink/subtle/AesEaxJceTest.java
+++ b/java/src/test/java/com/google/crypto/tink/subtle/AesEaxJceTest.java
@@ -141,8 +141,7 @@
   public void testModifyCiphertext() throws Exception {
     testModifyCiphertext(16, 16);
     testModifyCiphertext(16, 12);
-    // TODO(bleichen): Skipping test with key sizes larger than 128 bits because of
-    //   https://buganizer.corp.google.com/issues/35928521
+    // TODO(bleichen): Skipping test with key sizes larger than 128 bits because of b/35928521.
     // testModifyCiphertext(24, 16);
     // testModifyCiphertext(32, 16);
   }
diff --git a/java/src/test/java/com/google/crypto/tink/subtle/AesGcmHkdfStreamingTest.java b/java/src/test/java/com/google/crypto/tink/subtle/AesGcmHkdfStreamingTest.java
index 02ee48e..ce96c8b 100644
--- a/java/src/test/java/com/google/crypto/tink/subtle/AesGcmHkdfStreamingTest.java
+++ b/java/src/test/java/com/google/crypto/tink/subtle/AesGcmHkdfStreamingTest.java
@@ -213,6 +213,27 @@
     testEncryptSingleBytes(16, 111111);
   }
 
+  @Test
+  public void testSkipWithStream() throws Exception {
+    byte[] ikm = TestUtil.hexDecode("000102030405060708090a0b0c0d0e0f");
+    int keySize = 16;
+    int tagSize = 12;
+    int segmentSize = 256;
+    int offset = 8;
+    int plaintextSize = 1 << 12;
+    AesCtrHmacStreaming ags =
+        new AesCtrHmacStreaming(
+            ikm, "HmacSha256", keySize, "HmacSha256", tagSize, segmentSize, offset);
+    // Smallest possible chunk size
+    StreamingTestUtil.testSkipWithStream(ags, offset, plaintextSize, 1);
+    // Chunk size < segmentSize
+    StreamingTestUtil.testSkipWithStream(ags, offset, plaintextSize, 37);
+    // Chunk size > segmentSize
+    StreamingTestUtil.testSkipWithStream(ags, offset, plaintextSize, 384);
+    // Chunk size > 3*segmentSize
+    StreamingTestUtil.testSkipWithStream(ags, offset, plaintextSize, 800);
+  }
+
   /**
    * Encrypts and decrypts a with non-ASCII characters using CharsetEncoders and CharsetDecoders.
    */
@@ -300,6 +321,8 @@
   /** Encrypt some plaintext to a file, then decrypt from the file */
   @Test
   public void testFileEncryption() throws Exception {
-    StreamingTestUtil.testFileEncryption(createAesGcmStreaming(), tmpFolder.newFile());
+    int plaintextSize = 1 << 20;
+    StreamingTestUtil.testFileEncryption(
+        createAesGcmStreaming(), tmpFolder.newFile(), plaintextSize);
   }
 }
diff --git a/java/src/test/java/com/google/crypto/tink/subtle/AesGcmJceTest.java b/java/src/test/java/com/google/crypto/tink/subtle/AesGcmJceTest.java
index b220261..4b6dfe4 100644
--- a/java/src/test/java/com/google/crypto/tink/subtle/AesGcmJceTest.java
+++ b/java/src/test/java/com/google/crypto/tink/subtle/AesGcmJceTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.fail;
 
 import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.TestUtil.BytesMutation;
 import com.google.crypto.tink.WycheproofTestUtil;
 import java.security.GeneralSecurityException;
 import java.util.Arrays;
@@ -97,29 +98,14 @@
     AesGcmJce gcm = new AesGcmJce(key);
     byte[] ciphertext = gcm.encrypt(message, aad);
 
-    // Flipping bits
-    for (int b = 0; b < ciphertext.length; b++) {
-      for (int bit = 0; bit < 8; bit++) {
-        byte[] modified = Arrays.copyOf(ciphertext, ciphertext.length);
-        modified[b] ^= (byte) (1 << bit);
-        try {
-          byte[] unused = gcm.decrypt(modified, aad);
-          fail("Decrypting modified ciphertext should fail");
-        } catch (GeneralSecurityException ex) {
-          // This is expected.
-          // This could be a AeadBadTagException when the tag verification
-          // fails or some not yet specified Exception when the ciphertext is too short.
-          // In all cases a GeneralSecurityException or a subclass of it must be thrown.
-        }
-      }
-    }
-
-    // Truncate the message.
-    for (int length = 0; length < ciphertext.length; length++) {
-      byte[] modified = Arrays.copyOf(ciphertext, length);
+    for (BytesMutation mutation : TestUtil.generateMutations(ciphertext)) {
       try {
-        byte[] unused = gcm.decrypt(modified, aad);
-        fail("Decrypting modified ciphertext should fail");
+        byte[] unused = gcm.decrypt(mutation.value, aad);
+        fail(
+            String.format(
+                "Decrypting modified ciphertext should fail : ciphertext = %s, aad = %s,"
+                    + " description = %s",
+                Hex.encode(mutation.value), Hex.encode(aad), mutation.description));
       } catch (GeneralSecurityException ex) {
         // This is expected.
         // This could be a AeadBadTagException when the tag verification
@@ -129,19 +115,19 @@
     }
 
     // Modify AAD
-    for (int b = 0; b < aad.length; b++) {
-      for (int bit = 0; bit < 8; bit++) {
-        byte[] modified = Arrays.copyOf(aad, aad.length);
-        modified[b] ^= (byte) (1 << bit);
-        try {
-          byte[] unused = gcm.decrypt(ciphertext, modified);
-          fail("Decrypting with modified aad should fail");
-        } catch (GeneralSecurityException ex) {
-          // This is expected.
-          // This could be a AeadBadTagException when the tag verification
-          // fails or some not yet specified Exception when the ciphertext is too short.
-          // In all cases a GeneralSecurityException or a subclass of it must be thrown.
-        }
+    for (BytesMutation mutation : TestUtil.generateMutations(aad)) {
+      try {
+        byte[] unused = gcm.decrypt(ciphertext, mutation.value);
+        fail(
+            String.format(
+                "Decrypting with modified aad should fail: ciphertext = %s, aad = %s,"
+                    + " description = %s",
+                ciphertext, mutation.value, mutation.description));
+      } catch (GeneralSecurityException ex) {
+        // This is expected.
+        // This could be a AeadBadTagException when the tag verification
+        // fails or some not yet specified Exception when the ciphertext is too short.
+        // In all cases a GeneralSecurityException or a subclass of it must be thrown.
       }
     }
   }
diff --git a/java/src/test/java/com/google/crypto/tink/subtle/AesUtilTest.java b/java/src/test/java/com/google/crypto/tink/subtle/AesUtilTest.java
index 6947b6b..129572b 100644
--- a/java/src/test/java/com/google/crypto/tink/subtle/AesUtilTest.java
+++ b/java/src/test/java/com/google/crypto/tink/subtle/AesUtilTest.java
@@ -52,16 +52,28 @@
   }
 
   @Test
-  public void dblWithLeadingZero() {
-    // from the SIV test vectors
-    byte[] r = AesUtil.dbl(Hex.decode("0e04dfafc1efbf040140582859bf073a"));
-    assertEquals("1c09bf5f83df7e080280b050b37e0e74", Hex.encode(r));
-  }
+  public void dblTestVectors() {
+    // Extracted from the SIV test vectors at https://tools.ietf.org/html/rfc5297#section-2.3
+    // (all the double() steps in Appendix A)
+    String[] testVectorInputs = {
+        "0e04dfafc1efbf040140582859bf073a",
+        "edf09de876c642ee4d78bce4ceedfc4f",
+        "c8b43b5974960e7ce6a5dd85231e591a",
+        "adf31e285d3d1e1d4ddefc1e5bec63e9",
+        "826aa75b5e568eed3125bfb266c61d4e",
+    };
+    String[] testVectorOutputs = {
+        "1c09bf5f83df7e080280b050b37e0e74",
+        "dbe13bd0ed8c85dc9af179c99ddbf819",
+        "916876b2e92c1cf9cd4bbb0a463cb2b3",
+        "5be63c50ba7a3c3a9bbdf83cb7d8c755",
+        "04d54eb6bcad1dda624b7f64cd8c3a1b",
+    };
 
-  @Test
-  public void dblWithLeadingOne() {
-    // from the SIV test vectors
-    byte[] r = AesUtil.dbl(Hex.decode("c8b43b5974960e7ce6a5dd85231e591a"));
-    assertEquals("916876b2e92c1cf9cd4bbb0a463cb2b3", Hex.encode(r));
+    byte[] r;
+    for (int i = 0; i < testVectorInputs.length; i++) {
+      r = AesUtil.dbl(Hex.decode(testVectorInputs[i]));
+      assertEquals(testVectorOutputs[i], Hex.encode(r));
+    }
   }
 }
diff --git a/java/src/test/java/com/google/crypto/tink/subtle/ChaCha20Poly1305Test.java b/java/src/test/java/com/google/crypto/tink/subtle/ChaCha20Poly1305Test.java
index 5f299ff..5c25f03 100644
--- a/java/src/test/java/com/google/crypto/tink/subtle/ChaCha20Poly1305Test.java
+++ b/java/src/test/java/com/google/crypto/tink/subtle/ChaCha20Poly1305Test.java
@@ -24,6 +24,7 @@
 
 import com.google.crypto.tink.Aead;
 import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.TestUtil.BytesMutation;
 import com.google.crypto.tink.WycheproofTestUtil;
 import java.security.GeneralSecurityException;
 import java.security.InvalidKeyException;
@@ -119,26 +120,14 @@
     byte[] message = Random.randBytes(32);
     byte[] ciphertext = aead.encrypt(message, aad);
 
-    // Flipping bits
-    for (int b = 0; b < ciphertext.length; b++) {
-      for (int bit = 0; bit < 8; bit++) {
-        byte[] modified = Arrays.copyOf(ciphertext, ciphertext.length);
-        modified[b] ^= (byte) (1 << bit);
-        try {
-          byte[] unused = aead.decrypt(modified, aad);
-          fail("Decrypting modified ciphertext should fail");
-        } catch (AEADBadTagException ex) {
-          // This is expected.
-        }
-      }
-    }
-
-    // Truncate the message.
-    for (int length = 0; length < ciphertext.length; length++) {
-      byte[] modified = Arrays.copyOf(ciphertext, length);
+    for (BytesMutation mutation : TestUtil.generateMutations(ciphertext)) {
       try {
-        byte[] unused = aead.decrypt(modified, aad);
-        fail("Decrypting modified ciphertext should fail");
+        byte[] unused = aead.decrypt(mutation.value, aad);
+        fail(
+            String.format(
+                "Decrypting modified ciphertext should fail : ciphertext = %s, aad = %s,"
+                    + " description = %s",
+                Hex.encode(mutation.value), aad, mutation.description));
       } catch (GeneralSecurityException ex) {
         // This is expected.
         // This could be a AeadBadTagException when the tag verification
diff --git a/java/src/test/java/com/google/crypto/tink/subtle/ChaCha20Test.java b/java/src/test/java/com/google/crypto/tink/subtle/ChaCha20Test.java
index 9c51cb4..9464dda 100644
--- a/java/src/test/java/com/google/crypto/tink/subtle/ChaCha20Test.java
+++ b/java/src/test/java/com/google/crypto/tink/subtle/ChaCha20Test.java
@@ -169,13 +169,25 @@
             + "6e6420656c656374726f6e696320636f6d6d756e69636174696f6e73206d61646520617420616e792074"
             + "696d65206f7220706c6163652c207768696368206172652061646472657373656420746f",
         "000000000000000000000002",
-        "a3fbf07df3fa2fde4f376ca23e82737041605d9f4f4f57bd8cff2c1d4b7955ec2a97948bd3722915c8f3d337f7d370050e9e96d647b7c39f56e031ca5eb6250d4042e02785ececfa4b4bb5e8ead0440e20b6e8db09d881a7c6132f420e52795042bdfa7773d8a9051447b3291ce1411c680465552aa6c405b7764d5e87bea85ad00f8449ed8f72d0d662ab052691ca66424bc86d2df80ea41f43abf937d3259dc4b2d0dfb48a6c9139ddd7f76966e928e635553ba76c5c879d7b35d49eb2e62b0871cdac638939e25e8a1e0ef9d5280fa8ca328b351c3c765989cbcf3daa8b6ccc3aaf9f3979c92b3720fc88dc95ed84a1be059c6499b9fda236e7e818b04b0bc39c1e876b193bfe5569753f88128cc08aaa9b63d1a16f80ef2554d7189c411f5869ca52c5b83fa36ff216b9c1d30062bebcfd2dc5bce0911934fda79a86f6e698ced759c3ff9b6477338f3da4f9cd8514ea9982ccafb341b2384dd902f3d1ab7ac61dd29c6f21ba5b862f3730e37cfdc4fd806c22f221",
+        "a3fbf07df3fa2fde4f376ca23e82737041605d9f4f4f57bd8cff2c1d4b7955ec2a97948bd3722915c8f3d337f7"
+            + "d370050e9e96d647b7c39f56e031ca5eb6250d4042e02785ececfa4b4bb5e8ead0440e20b6e8db09d881"
+            + "a7c6132f420e52795042bdfa7773d8a9051447b3291ce1411c680465552aa6c405b7764d5e87bea85ad0"
+            + "0f8449ed8f72d0d662ab052691ca66424bc86d2df80ea41f43abf937d3259dc4b2d0dfb48a6c9139ddd7"
+            + "f76966e928e635553ba76c5c879d7b35d49eb2e62b0871cdac638939e25e8a1e0ef9d5280fa8ca328b35"
+            + "1c3c765989cbcf3daa8b6ccc3aaf9f3979c92b3720fc88dc95ed84a1be059c6499b9fda236e7e818b04b"
+            + "0bc39c1e876b193bfe5569753f88128cc08aaa9b63d1a16f80ef2554d7189c411f5869ca52c5b83fa36f"
+            + "f216b9c1d30062bebcfd2dc5bce0911934fda79a86f6e698ced759c3ff9b6477338f3da4f9cd8514ea99"
+            + "82ccafb341b2384dd902f3d1ab7ac61dd29c6f21ba5b862f3730e37cfdc4fd806c22f221",
         1),
     new Rfc7539TestVector(
         "1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0",
-        "2754776173206272696c6c69672c20616e642074686520736c6974687920746f7665730a446964206779726520616e642067696d626c6520696e2074686520776162653a0a416c6c206d696d737920776572652074686520626f726f676f7665732c0a416e6420746865206d6f6d65207261746873206f757467726162652e",
+        "2754776173206272696c6c69672c20616e642074686520736c6974687920746f7665730a446964206779726520"
+            + "616e642067696d626c6520696e2074686520776162653a0a416c6c206d696d73792077657265207468652062"
+            + "6f726f676f7665732c0a416e6420746865206d6f6d65207261746873206f757467726162652e",
         "000000000000000000000002",
-        "62e6347f95ed87a45ffae7426f27a1df5fb69110044c0d73118effa95b01e5cf166d3df2d721caf9b21e5fb14c616871fd84c54f9d65b283196c7fe4f60553ebf39c6402c42234e32a356b3e764312a61a5532055716ead6962568f87d3f3f7704c6a8d1bcd1bf4d50d6154b6da731b187b58dfd728afa36757a797ac188d1",
+        "62e6347f95ed87a45ffae7426f27a1df5fb69110044c0d73118effa95b01e5cf166d3df2d721caf9b21e5fb14c"
+            + "616871fd84c54f9d65b283196c7fe4f60553ebf39c6402c42234e32a356b3e764312a61a5532055716ea"
+            + "d6962568f87d3f3f7704c6a8d1bcd1bf4d50d6154b6da731b187b58dfd728afa36757a797ac188d1",
         42),
     // Tests against the test vectors in Section 2.6.2 of RFC 7539.
     // https://tools.ietf.org/html/rfc7539#section-2.6.2
diff --git a/java/src/test/java/com/google/crypto/tink/subtle/EcdsaVerifyJceTest.java b/java/src/test/java/com/google/crypto/tink/subtle/EcdsaVerifyJceTest.java
index 6024100..87d781e 100644
--- a/java/src/test/java/com/google/crypto/tink/subtle/EcdsaVerifyJceTest.java
+++ b/java/src/test/java/com/google/crypto/tink/subtle/EcdsaVerifyJceTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.fail;
 
 import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.TestUtil.BytesMutation;
 import com.google.crypto.tink.WycheproofTestUtil;
 import com.google.crypto.tink.subtle.EllipticCurves.EcdsaEncoding;
 import com.google.crypto.tink.subtle.Enums.HashType;
@@ -32,7 +33,6 @@
 import java.security.interfaces.ECPublicKey;
 import java.security.spec.ECParameterSpec;
 import java.security.spec.X509EncodedKeySpec;
-import java.util.Arrays;
 import org.json.JSONArray;
 import org.json.JSONObject;
 import org.junit.Test;
@@ -215,26 +215,14 @@
       // Verify with EcdsaVerifyJce.
       EcdsaVerifyJce verifier = new EcdsaVerifyJce(pub, HashType.SHA256, encoding);
 
-      // Flip bits.
-      for (int i = 0; i < signature.length; i++) {
-        for (int j = 0; j < 8; j++) {
-          byte[] modifiedSignature = Arrays.copyOf(signature, signature.length);
-          modifiedSignature[i] = (byte) (modifiedSignature[i] ^ (1 << j));
-          try {
-            verifier.verify(modifiedSignature, message);
-            fail("Invalid signature, should have thrown exception");
-          } catch (GeneralSecurityException expected) {
-            // Expected.
-          }
-        }
-      }
-
-      // Truncate the signature.
-      for (int i = 0; i < signature.length; i++) {
-        byte[] modifiedSignature = Arrays.copyOf(signature, i);
+      for (BytesMutation mutation : TestUtil.generateMutations(signature)) {
         try {
-          verifier.verify(modifiedSignature, message);
-          fail("Invalid signature, should have thrown exception");
+          verifier.verify(mutation.value, message);
+          fail(
+              String.format(
+                  "Invalid signature, should have thrown exception : signature = %s, message = %s, "
+                      + " description = %s",
+                  Hex.encode(mutation.value), message, mutation.description));
         } catch (GeneralSecurityException expected) {
           // Expected.
         }
diff --git a/java/src/test/java/com/google/crypto/tink/subtle/KwpTest.java b/java/src/test/java/com/google/crypto/tink/subtle/KwpTest.java
new file mode 100644
index 0000000..6d830d4
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/subtle/KwpTest.java
@@ -0,0 +1,194 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.subtle;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.crypto.tink.KeyWrap;
+import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.WycheproofTestUtil;
+import java.security.GeneralSecurityException;
+import java.util.Set;
+import java.util.TreeSet;
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link Kwp}. */
+@RunWith(JUnit4.class)
+public class KwpTest {
+
+  @Test
+  public void testWrapUnwrapMsgSizes() throws Exception {
+    byte[] wrapKey = Random.randBytes(16);
+    KeyWrap wrapper = new Kwp(wrapKey);
+    for (int wrappedSize = 16; wrappedSize < 128; wrappedSize++) {
+      byte[] keyMaterialToWrap = Random.randBytes(wrappedSize);
+      byte[] wrapped = wrapper.wrap(keyMaterialToWrap);
+      byte[] unwrapped = wrapper.unwrap(wrapped);
+      assertArrayEquals(keyMaterialToWrap, unwrapped);
+    }
+  }
+
+  @Test
+  public void testInvalidKeySizes() throws Exception {
+    // Tests the wrapping key. Its key size is either 16 or 32.
+    for (int i = 0; i < 255; i++) {
+      if (i == 16 || i == 32) {
+        continue;
+      }
+      try {
+        KeyWrap unused = new Kwp(new byte[i]);
+        fail("Constructed wrapper with invalid key size");
+      } catch (GeneralSecurityException ex) {
+        // expected
+      }
+    }
+  }
+
+  @Test
+  public void testInvalidWrappingSizes() throws Exception {
+    byte[] wrapKey = Random.randBytes(16);
+    KeyWrap wrapper = new Kwp(wrapKey);
+    for (int wrappedSize = 0; wrappedSize < 16; wrappedSize++) {
+      try {
+        wrapper.wrap(new byte[wrappedSize]);
+        fail("Should not wrap short keys");
+      } catch (GeneralSecurityException ex) {
+        // expected
+      }
+    }
+  }
+
+  @Test
+  public void testWycheproof() throws Exception {
+    final String expectedVersion = "0.6";
+    JSONObject json =
+        WycheproofTestUtil.readJson("../wycheproof/testvectors/kwp_test.json");
+    Set<String> exceptions = new TreeSet<String>();
+    String generatorVersion = json.getString("generatorVersion");
+    if (!generatorVersion.equals(expectedVersion)) {
+      System.out.printf("Expecting test vectors with version %s found version %s.\n",
+                        expectedVersion, generatorVersion);
+    }
+    int errors = 0;
+    JSONArray testGroups = json.getJSONArray("testGroups");
+    for (int i = 0; i < testGroups.length(); i++) {
+      JSONObject group = testGroups.getJSONObject(i);
+      JSONArray tests = group.getJSONArray("tests");
+      for (int j = 0; j < tests.length(); j++) {
+        JSONObject testcase = tests.getJSONObject(j);
+        int tcid = testcase.getInt("tcId");
+        String tc = "tcId: " + tcid + " " + testcase.getString("comment");
+        byte[] key = Hex.decode(testcase.getString("key"));
+        byte[] data = Hex.decode(testcase.getString("msg"));
+        byte[] expected = Hex.decode(testcase.getString("ct"));
+        // Result is one of "valid", "invalid", "acceptable".
+        // "valid" are test vectors with matching plaintext, ciphertext and tag.
+        // "invalid" are test vectors with invalid parameters or invalid ciphertext and tag.
+        // "acceptable" are test vectors with weak parameters or legacy formats.
+        String result = testcase.getString("result");
+
+        // Test wrapping
+        KeyWrap wrapper;
+        try {
+          wrapper = new Kwp(key);
+        } catch (GeneralSecurityException ex) {
+          // tink restrict the key sizes to 128 or 256 bits.
+          if (key.length == 16 || key.length == 32) {
+            System.out.printf("Rejected valid key:%s\n", tc);
+            System.out.println(ex.toString());
+            errors++;
+          }
+          continue;
+        }
+        try {
+          byte[] wrapped = wrapper.wrap(data);
+          boolean eq = TestUtil.arrayEquals(expected, wrapped);
+          if (result.equals("invalid")) {
+            if (eq) {
+              // Some test vectors use invalid parameters that should be rejected.
+              System.out.printf("Wrapped test case:%s\n", tc);
+              errors++;
+            }
+          } else {
+            if (!eq) {
+              System.out.printf("Incorrect wrapping for test case:%s wrapped bytes:%s\n",
+                                tc, Hex.encode(wrapped));
+              errors++;
+            }
+          }
+        } catch (GeneralSecurityException ex) {
+          if (result.equals("valid")) {
+            System.out.printf("Failed to wrap test case:%s\n", tc);
+            errors++;
+          }
+        } catch (Exception ex) {
+          // Other exceptions are violating the interface.
+          System.out.printf("Test case %s throws %s.\n", tc, ex);
+          errors++;
+        }
+
+        // Test unwrapping
+        // The algorithms tested in this class are typically malleable. Hence, it is in possible
+        // that modifying ciphertext randomly results in some other valid ciphertext.
+        // However, all the test vectors in Wycheproof are constructed such that they have
+        // invalid padding. If this changes then the test below is too strict.
+        try {
+          byte[] unwrapped = wrapper.unwrap(expected);
+          boolean eq = TestUtil.arrayEquals(data, unwrapped);
+          if (result.equals("invalid")) {
+            System.out.printf("Unwrapped invalid test case:%s unwrapped:%s\n", tc,
+                              Hex.encode(unwrapped));
+            errors++;
+          } else {
+            if (!eq) {
+              System.out.printf("Incorrect unwrap. Excepted:%s actual:%s\n",
+                                Hex.encode(data), Hex.encode(unwrapped));
+              errors++;
+            }
+          }
+        } catch (GeneralSecurityException ex) {
+          // Trying to unwrap an invalid key should always result in a GeneralSecurityException
+          // or a subclass of it.
+          exceptions.add(ex.toString());
+          if (result.equals("valid")) {
+            System.out.printf("Failed to unwrap:%s\n", tc);
+            errors++;
+          }
+        } catch (Exception ex) {
+          // Other exceptions indicate a programming error.
+          System.out.printf("Test case:%s throws %s\n", tc, ex);
+          exceptions.add(ex.toString());
+          errors++;
+        }
+      }
+    }
+    // Even though strong pseudorandomness implies that information about incorrectly formatted
+    // ciphertexts is not helpful to an attacker, we still don't want to do this and expect
+    // exceptions that do not carry information about the unwrapped data.
+    System.out.printf("Number of distinct exceptions:%d\n", exceptions.size());
+    for (String ex : exceptions) {
+      System.out.println(ex);
+    }
+    assertEquals(0, errors);
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPkcs1SignJceTest.java b/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPkcs1SignJceTest.java
index b8b52ed..2c4de17 100644
--- a/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPkcs1SignJceTest.java
+++ b/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPkcs1SignJceTest.java
@@ -75,6 +75,10 @@
 
   @Test
   public void testSignWithTheSameMessage() throws Exception {
+    if (TestUtil.isTsan()) {
+      // This test times out when running under thread sanitizer, so we just skip.
+      return;
+    }
     int keySize = 4096;
     KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
     keyGen.initialize(keySize);
diff --git a/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPssSignJceTest.java b/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPssSignJceTest.java
new file mode 100644
index 0000000..6a9f1fa
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPssSignJceTest.java
@@ -0,0 +1,106 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.subtle;
+
+import static org.junit.Assert.fail;
+
+import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.subtle.Enums.HashType;
+import java.nio.charset.Charset;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.interfaces.RSAPublicKey;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for RsaSsaPssSignJce. */
+@RunWith(JUnit4.class)
+public class RsaSsaPssSignJceTest {
+  private static final Charset UTF_8 = Charset.forName("UTF-8");
+
+  @Test
+  public void testConstructorExceptions() throws Exception {
+    int keySize = 2048;
+    KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+    keyGen.initialize(keySize);
+
+    RSAPrivateCrtKey priv = (RSAPrivateCrtKey) keyGen.generateKeyPair().getPrivate();
+    try {
+      new RsaSsaPssSignJce(priv, HashType.SHA1, HashType.SHA1, 20);
+      fail("Unsafe hash, should have thrown exception.");
+    } catch (GeneralSecurityException e) {
+      // Expected.
+      TestUtil.assertExceptionContains(e, "SHA1 is not safe");
+    }
+  }
+
+  @Test
+  public void testBasicAgainstVerifier() throws Exception {
+    if (TestUtil.isTsan()) {
+      // This test times out when running under thread sanitizer, so we just skip.
+      return;
+    }
+    int keySize = 2048;
+    KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+    keyGen.initialize(keySize);
+    KeyPair keyPair = keyGen.generateKeyPair();
+    RSAPublicKey pub = (RSAPublicKey) keyPair.getPublic();
+    RSAPrivateCrtKey priv = (RSAPrivateCrtKey) keyPair.getPrivate();
+
+    // Sign with RsaSsaPssSignJce.
+    byte[] message = "Hello".getBytes(UTF_8);
+    RsaSsaPssSignJce signer = new RsaSsaPssSignJce(priv, HashType.SHA256, HashType.SHA256, 32);
+
+    for (int i = 0; i < 1024; i++) {
+      byte[] signature = signer.sign(message);
+      // Verify with JCE's Signature.
+      RsaSsaPssVerifyJce verifier =
+          new RsaSsaPssVerifyJce(pub, HashType.SHA256, HashType.SHA256, 32);
+      try {
+        verifier.verify(signature, message);
+      } catch (GeneralSecurityException e) {
+        throw new AssertionError("Valid signature, shouldn't throw exception", e);
+      }
+    }
+  }
+
+  @Test
+  public void testZeroSaltLength() throws Exception {
+    int keySize = 2048;
+    KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+    keyGen.initialize(keySize);
+    KeyPair keyPair = keyGen.generateKeyPair();
+    RSAPublicKey pub = (RSAPublicKey) keyPair.getPublic();
+    RSAPrivateCrtKey priv = (RSAPrivateCrtKey) keyPair.getPrivate();
+
+    // Sign with RsaSsaPssSignJce.
+    byte[] message = "Hello".getBytes(UTF_8);
+    RsaSsaPssSignJce signer = new RsaSsaPssSignJce(priv, HashType.SHA256, HashType.SHA256, 0);
+
+    byte[] signature = signer.sign(message);
+    // Verify with JCE's Signature.
+    RsaSsaPssVerifyJce verifier = new RsaSsaPssVerifyJce(pub, HashType.SHA256, HashType.SHA256, 0);
+    try {
+      verifier.verify(signature, message);
+    } catch (GeneralSecurityException e) {
+      throw new AssertionError("Valid signature, shouldn't throw exception", e);
+    }
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPssVerifyJceTest.java b/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPssVerifyJceTest.java
new file mode 100644
index 0000000..cfb6789
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/subtle/RsaSsaPssVerifyJceTest.java
@@ -0,0 +1,111 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.subtle;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.crypto.tink.TestUtil;
+import com.google.crypto.tink.WycheproofTestUtil;
+import com.google.crypto.tink.subtle.Enums.HashType;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPairGenerator;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.X509EncodedKeySpec;
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for RsaSsaPssVerifyJce. */
+@RunWith(JUnit4.class)
+public class RsaSsaPssVerifyJceTest {
+  @Test
+  public void testConstructorExceptions() throws Exception {
+    int keySize = 2048;
+    KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+    keyGen.initialize(keySize);
+    RSAPublicKey pub = (RSAPublicKey) keyGen.generateKeyPair().getPublic();
+    try {
+      new RsaSsaPssVerifyJce(pub, HashType.SHA1, HashType.SHA1, 20);
+      fail("Unsafe hash, should have thrown exception.");
+    } catch (GeneralSecurityException e) {
+      // Expected.
+      TestUtil.assertExceptionContains(e, "SHA1 is not safe");
+    }
+  }
+
+  @Test
+  public void testWycheproofVectors() throws Exception {
+    testWycheproofVectors(
+        "../wycheproof/testvectors/rsa_pss_2048_sha256_mgf1_0_test.json");
+    testWycheproofVectors(
+        "../wycheproof/testvectors/rsa_pss_2048_sha256_mgf1_32_test.json");
+    testWycheproofVectors(
+        "../wycheproof/testvectors/rsa_pss_3072_sha256_mgf1_32_test.json");
+    testWycheproofVectors(
+        "../wycheproof/testvectors/rsa_pss_4096_sha256_mgf1_32_test.json");
+    testWycheproofVectors(
+        "../wycheproof/testvectors/rsa_pss_4096_sha512_mgf1_32_test.json");
+  }
+
+  private static void testWycheproofVectors(String fileName) throws Exception {
+    JSONObject jsonObj = WycheproofTestUtil.readJson(fileName);
+
+    int errors = 0;
+    JSONArray testGroups = jsonObj.getJSONArray("testGroups");
+    for (int i = 0; i < testGroups.length(); i++) {
+      JSONObject group = testGroups.getJSONObject(i);
+
+      KeyFactory kf = KeyFactory.getInstance("RSA");
+      byte[] encodedPubKey = Hex.decode(group.getString("keyDer"));
+      X509EncodedKeySpec x509keySpec = new X509EncodedKeySpec(encodedPubKey);
+      HashType sigHash = WycheproofTestUtil.getHashType(group.getString("sha"));
+      HashType mgf1Hash = WycheproofTestUtil.getHashType(group.getString("mgfSha"));
+      int saltLength = group.getInt("sLen");
+
+      JSONArray tests = group.getJSONArray("tests");
+      for (int j = 0; j < tests.length(); j++) {
+        JSONObject testcase = tests.getJSONObject(j);
+        String tcId =
+            String.format(
+                "testcase %d (%s)", testcase.getInt("tcId"), testcase.getString("comment"));
+        RsaSsaPssVerifyJce verifier;
+        RSAPublicKey pubKey = (RSAPublicKey) kf.generatePublic(x509keySpec);
+        verifier = new RsaSsaPssVerifyJce(pubKey, sigHash, mgf1Hash, saltLength);
+        byte[] msg = Hex.decode(testcase.getString("msg"));
+        byte[] sig = Hex.decode(testcase.getString("sig"));
+        String result = testcase.getString("result");
+        try {
+          verifier.verify(sig, msg);
+          if (result.equals("invalid")) {
+            System.out.printf("FAIL %s: accepting invalid signature%n", tcId);
+            errors++;
+          }
+        } catch (GeneralSecurityException ex) {
+          if (result.equals("valid")) {
+            System.out.printf("FAIL %s: rejecting valid signature, exception: %s%n", tcId, ex);
+            errors++;
+          }
+        }
+      }
+    }
+    assertEquals(0, errors);
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/subtle/X25519Test.java b/java/src/test/java/com/google/crypto/tink/subtle/X25519Test.java
index 0689a63..7ab7f22 100644
--- a/java/src/test/java/com/google/crypto/tink/subtle/X25519Test.java
+++ b/java/src/test/java/com/google/crypto/tink/subtle/X25519Test.java
@@ -143,6 +143,7 @@
     for (int i = 0; i < testGroups.length(); i++) {
       JSONObject group = testGroups.getJSONObject(i);
       JSONArray tests = group.getJSONArray("tests");
+      String curve = group.getString("curve");
       for (int j = 0; j < tests.length(); j++) {
         JSONObject testcase = tests.getJSONObject(j);
         String tcId =
@@ -152,7 +153,6 @@
         String hexPubKey = testcase.getString("public");
         String hexPrivKey = testcase.getString("private");
         String expectedSharedSecret = testcase.getString("shared");
-        String curve = testcase.getString("curve");
         if (!curve.equals("curve25519")) {
           System.out.printf("Skipping %s, unknown curve name: %s", tcId, curve);
           cntSkippedTests++;
diff --git a/java/src/test/java/com/google/crypto/tink/subtle/XChaCha20Poly1305Test.java b/java/src/test/java/com/google/crypto/tink/subtle/XChaCha20Poly1305Test.java
new file mode 100644
index 0000000..0c09c25
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/subtle/XChaCha20Poly1305Test.java
@@ -0,0 +1,253 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.subtle;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+import com.google.crypto.tink.Aead;
+import com.google.crypto.tink.TestUtil;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.util.Arrays;
+import java.util.HashSet;
+import javax.crypto.AEADBadTagException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for XChaCha20Poly1305. */
+@RunWith(JUnit4.class)
+public class XChaCha20Poly1305Test {
+  private static final int KEY_SIZE = 32;
+
+  private static class XChaCha20Poly1305TestVector {
+    public byte[] key;
+    public byte[] nonce;
+    public byte[] plaintext;
+    public byte[] aad;
+    public byte[] ciphertext;
+    public byte[] tag;
+
+    public XChaCha20Poly1305TestVector(
+        String key, String nonce, String plaintext, String aad, String ciphertext, String tag) {
+      this.key = Hex.decode(key);
+      this.nonce = Hex.decode(nonce);
+      this.plaintext = Hex.decode(plaintext);
+      this.aad = Hex.decode(aad);
+      this.ciphertext = Hex.decode(ciphertext);
+      this.tag = Hex.decode(tag);
+    }
+  }
+
+  private static final XChaCha20Poly1305TestVector[] xChaCha20Poly1305TestVectors = {
+    // From libsodium's test/default/aead_xchacha20poly1305.c
+    // see test/default/aead_xchacha20poly1305.exp for ciphertext values.
+    new XChaCha20Poly1305TestVector(
+        "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
+        "07000000404142434445464748494a4b0000000000000000",
+        "4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a20496620"
+            + "4920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f722074686520667574"
+            + "7572652c2073756e73637265656e20776f756c642062652069742e",
+        "50515253c0c1c2c3c4c5c6c7",
+        "453c0693a7407f04ff4c56aedb17a3c0a1afff01174930fc22287c33dbcf0ac8b89ad929530a1bb3ab5e69f24c"
+            + "7f6070c8f840c9abb4f69fbfc8a7ff5126faeebbb55805ee9c1cf2ce5a57263287aec5780f04ec324c35"
+            + "14122cfc3231fc1a8b718a62863730a2702bb76366116bed09e0fd",
+        "5c6d84b6b0c1abaf249d5dd0f7f5a7ea"),
+    new XChaCha20Poly1305TestVector(
+        "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
+        "07000000404142434445464748494a4b0000000000000000",
+        "4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a20496620"
+            + "4920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f722074686520667574"
+            + "7572652c2073756e73637265656e20776f756c642062652069742e",
+        "" /* empty aad */,
+        "453c0693a7407f04ff4c56aedb17a3c0a1afff01174930fc22287c33dbcf0ac8b89ad929530a1bb3ab5e69f24c"
+            + "7f6070c8f840c9abb4f69fbfc8a7ff5126faeebbb55805ee9c1cf2ce5a57263287aec5780f04ec324c35"
+            + "14122cfc3231fc1a8b718a62863730a2702bb76366116bed09e0fd",
+        "d4c860b7074be894fac9697399be5cc1"),
+    // From  https://tools.ietf.org/html/draft-arciszewski-xchacha-01.
+    new XChaCha20Poly1305TestVector(
+        "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
+        "404142434445464748494a4b4c4d4e4f5051525354555657",
+        "4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a20496620"
+            + "4920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f722074686520667574"
+            + "7572652c2073756e73637265656e20776f756c642062652069742e",
+        "50515253c0c1c2c3c4c5c6c7",
+        "bd6d179d3e83d43b9576579493c0e939572a1700252bfaccbed2902c21396cbb731c7f1b0b4aa6440bf3a82f4e"
+            + "da7e39ae64c6708c54c216cb96b72e1213b4522f8c9ba40db5d945b11b69b982c1bb9e3f3fac2bc36948"
+            + "8f76b2383565d3fff921f9664c97637da9768812f615c68b13b52e",
+        "c0875924c1c7987947deafd8780acf49")
+  };
+
+  @Test
+  public void testXChaCha20Poly1305TestVectors() throws Exception {
+    for (XChaCha20Poly1305TestVector test : xChaCha20Poly1305TestVectors) {
+      Aead cipher = new XChaCha20Poly1305(test.key);
+      byte[] message =
+          cipher.decrypt(Bytes.concat(test.nonce, test.ciphertext, test.tag), test.aad);
+      assertThat(message).isEqualTo(test.plaintext);
+    }
+  }
+
+  public Aead createInstance(byte[] key) throws InvalidKeyException {
+    return new XChaCha20Poly1305(key);
+  }
+
+  @Test
+  public void testSnufflePoly1305ThrowsIllegalArgExpWhenKeyLenIsGreaterThan32()
+      throws InvalidKeyException {
+    try {
+      createInstance(new byte[KEY_SIZE + 1]);
+      fail("Expected InvalidKeyException.");
+    } catch (InvalidKeyException e) {
+      assertThat(e).hasMessageThat().containsMatch("The key length in bytes must be 32.");
+    }
+  }
+
+  @Test
+  public void testSnufflePoly1305ThrowsIllegalArgExpWhenKeyLenIsLessThan32()
+      throws InvalidKeyException {
+    try {
+      createInstance(new byte[KEY_SIZE - 1]);
+      fail("Expected InvalidKeyException.");
+    } catch (InvalidKeyException e) {
+      assertThat(e).hasMessageThat().containsMatch("The key length in bytes must be 32.");
+    }
+  }
+
+  @Test
+  public void testDecryptThrowsGeneralSecurityExpWhenCiphertextIsTooShort()
+      throws InvalidKeyException {
+    Aead cipher = createInstance(new byte[KEY_SIZE]);
+    try {
+      cipher.decrypt(new byte[27], new byte[1]);
+      fail("Expected GeneralSecurityException.");
+    } catch (GeneralSecurityException e) {
+      assertThat(e).hasMessageThat().containsMatch("ciphertext too short");
+    }
+  }
+
+  @Test
+  public void testEncryptDecrypt() throws Exception {
+    Aead aead = createInstance(Random.randBytes(KEY_SIZE));
+    for (int i = 0; i < 100; i++) {
+      byte[] message = Random.randBytes(i);
+      byte[] aad = Random.randBytes(i);
+      byte[] ciphertext = aead.encrypt(message, aad);
+      byte[] decrypted = aead.decrypt(ciphertext, aad);
+      assertArrayEquals(message, decrypted);
+    }
+  }
+
+  @Test
+  public void testLongMessages() throws Exception {
+    if (TestUtil.isAndroid() || TestUtil.isTsan()) {
+      System.out.println("testLongMessages doesn't work on Android and under tsan, skipping");
+      return;
+    }
+    int dataSize = 16;
+    while (dataSize <= (1 << 24)) {
+      byte[] plaintext = Random.randBytes(dataSize);
+      byte[] aad = Random.randBytes(dataSize / 3);
+      byte[] key = Random.randBytes(KEY_SIZE);
+      Aead aead = createInstance(key);
+      byte[] ciphertext = aead.encrypt(plaintext, aad);
+      byte[] decrypted = aead.decrypt(ciphertext, aad);
+      assertArrayEquals(plaintext, decrypted);
+      dataSize += 5 * dataSize / 11;
+    }
+  }
+
+  @Test
+  public void testModifyCiphertext() throws Exception {
+    byte[] key = Random.randBytes(KEY_SIZE);
+    Aead aead = createInstance(key);
+    byte[] aad = Random.randBytes(16);
+    byte[] message = Random.randBytes(32);
+    byte[] ciphertext = aead.encrypt(message, aad);
+
+    // Flipping bits
+    for (int b = 0; b < ciphertext.length; b++) {
+      for (int bit = 0; bit < 8; bit++) {
+        byte[] modified = Arrays.copyOf(ciphertext, ciphertext.length);
+        modified[b] ^= (byte) (1 << bit);
+        try {
+          byte[] unused = aead.decrypt(modified, aad);
+          fail("Decrypting modified ciphertext should fail");
+        } catch (AEADBadTagException ex) {
+          // This is expected.
+        }
+      }
+    }
+
+    // Truncate the message.
+    for (int length = 0; length < ciphertext.length; length++) {
+      byte[] modified = Arrays.copyOf(ciphertext, length);
+      try {
+        byte[] unused = aead.decrypt(modified, aad);
+        fail("Decrypting modified ciphertext should fail");
+      } catch (GeneralSecurityException ex) {
+        // This is expected.
+        // This could be a AeadBadTagException when the tag verification
+        // fails or some not yet specified Exception when the ciphertext is too short.
+        // In all cases a GeneralSecurityException or a subclass of it must be thrown.
+      }
+    }
+
+    // Modify AAD
+    for (int b = 0; b < aad.length; b++) {
+      for (int bit = 0; bit < 8; bit++) {
+        byte[] modified = Arrays.copyOf(aad, aad.length);
+        modified[b] ^= (byte) (1 << bit);
+        try {
+          byte[] unused = aead.decrypt(ciphertext, modified);
+          fail("Decrypting with modified aad should fail");
+        } catch (AEADBadTagException ex) {
+          // This is expected.
+        }
+      }
+    }
+  }
+
+  /**
+   * This is a very simple test for the randomness of the nonce. The test simply checks that the
+   * multiple ciphertexts of the same message are distinct.
+   */
+  @Test
+  public void testRandomNonce() throws Exception {
+    if (TestUtil.isTsan()) {
+      System.out.println("testRandomNonce takes too long under tsan, skipping");
+      return;
+    }
+    byte[] key = Random.randBytes(KEY_SIZE);
+    Aead aead = createInstance(key);
+    byte[] message = new byte[0];
+    byte[] aad = new byte[0];
+    HashSet<String> ciphertexts = new HashSet<>();
+    final int samples = 1 << 17;
+    for (int i = 0; i < samples; i++) {
+      byte[] ct = aead.encrypt(message, aad);
+      String ctHex = TestUtil.hexEncode(ct);
+      assertFalse(ciphertexts.contains(ctHex));
+      ciphertexts.add(ctHex);
+    }
+    assertEquals(samples, ciphertexts.size());
+  }
+}
diff --git a/java/src/test/java/com/google/crypto/tink/subtle/XChaCha20Test.java b/java/src/test/java/com/google/crypto/tink/subtle/XChaCha20Test.java
new file mode 100644
index 0000000..244d38c
--- /dev/null
+++ b/java/src/test/java/com/google/crypto/tink/subtle/XChaCha20Test.java
@@ -0,0 +1,264 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.subtle;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.fail;
+
+import com.google.crypto.tink.TestUtil;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link XChaCha20} */
+@RunWith(JUnit4.class)
+public class XChaCha20Test {
+  public IndCpaCipher createInstance(final byte[] key) throws InvalidKeyException {
+    return new XChaCha20(key, 0 /* initialCounter */);
+  }
+
+  @Test
+  public void testEncryptDecrypt() throws Exception {
+    for (int i = 0; i < 64; i++) {
+      byte[] key = Random.randBytes(32);
+      IndCpaCipher cipher = createInstance(key);
+      for (int j = 0; j < 64; j++) {
+        byte[] expectedInput = Random.randBytes(new java.util.Random().nextInt(300));
+        byte[] output = cipher.encrypt(expectedInput);
+        byte[] actualInput = cipher.decrypt(output);
+        assertArrayEquals(
+            String.format(
+                "\n\nMessage: %s\nKey: %s\nOutput: %s\nDecrypted Msg: %s\n",
+                TestUtil.hexEncode(expectedInput),
+                TestUtil.hexEncode(key),
+                TestUtil.hexEncode(output),
+                TestUtil.hexEncode(actualInput)),
+            expectedInput,
+            actualInput);
+      }
+    }
+  }
+
+  @Test
+  public void testNewCipherThrowsIllegalArgExpWhenKeyLenIsLessThan32() throws Exception {
+    try {
+      createInstance(new byte[1]);
+      fail("Expected InvalidKeyException.");
+    } catch (InvalidKeyException e) {
+      assertThat(e).hasMessageThat().containsMatch("The key length in bytes must be 32.");
+    }
+  }
+
+  @Test
+  public void testNewCipherThrowsIllegalArgExpWhenKeyLenIsGreaterThan32() throws Exception {
+    try {
+      createInstance(new byte[33]);
+      fail("Expected InvalidKeyException.");
+    } catch (InvalidKeyException e) {
+      assertThat(e).hasMessageThat().containsMatch("The key length in bytes must be 32.");
+    }
+  }
+
+  @Test
+  public void testDecryptThrowsGeneralSecurityExpWhenCiphertextIsTooShort() throws Exception {
+    try {
+      IndCpaCipher cipher = createInstance(Random.randBytes(32));
+      cipher.decrypt(new byte[2]);
+      fail("Expected GeneralSecurityException.");
+    } catch (GeneralSecurityException e) {
+      assertThat(e).hasMessageThat().containsMatch("ciphertext too short");
+    }
+  }
+
+  private static class HChaCha20TestVector {
+    public int[] key;
+    public int[] in;
+    public int[] out;
+
+    public HChaCha20TestVector(String key, String in, String out) {
+      this.key = ChaCha20.toIntArray(Hex.decode(key));
+      this.in = ChaCha20.toIntArray(Hex.decode(in));
+      this.out = ChaCha20.toIntArray(Hex.decode(out));
+    }
+  }
+
+  private static final HChaCha20TestVector[] hChaCha20TestVectors = {
+    // From libsodium's test/default/xchacha20.c (tv_hchacha20).
+    new HChaCha20TestVector(
+        "24f11cce8a1b3d61e441561a696c1c1b7e173d084fd4812425435a8896a013dc",
+        "d9660c5900ae19ddad28d6e06e45fe5e",
+        "5966b3eec3bff1189f831f06afe4d4e3be97fa9235ec8c20d08acfbbb4e851e3"),
+    new HChaCha20TestVector(
+        "80a5f6272031e18bb9bcd84f3385da65e7731b7039f13f5e3d475364cd4d42f7",
+        "c0eccc384b44c88e92c57eb2d5ca4dfa",
+        "6ed11741f724009a640a44fce7320954c46e18e0d7ae063bdbc8d7cf372709df"),
+    new HChaCha20TestVector(
+        "cb1fc686c0eec11a89438b6f4013bf110e7171dace3297f3a657a309b3199629",
+        "fcd49b93e5f8f299227e64d40dc864a3",
+        "84b7e96937a1a0a406bb7162eeaad34308d49de60fd2f7ec9dc6a79cbab2ca34"),
+    new HChaCha20TestVector(
+        "6640f4d80af5496ca1bc2cfff1fefbe99638dbceaabd7d0ade118999d45f053d",
+        "31f59ceeeafdbfe8cae7914caeba90d6",
+        "9af4697d2f5574a44834a2c2ae1a0505af9f5d869dbe381a994a18eb374c36a0"),
+    new HChaCha20TestVector(
+        "0693ff36d971225a44ac92c092c60b399e672e4cc5aafd5e31426f123787ac27",
+        "3a6293da061da405db45be1731d5fc4d",
+        "f87b38609142c01095bfc425573bb3c698f9ae866b7e4216840b9c4caf3b0865"),
+    new HChaCha20TestVector(
+        "809539bd2639a23bf83578700f055f313561c7785a4a19fc9114086915eee551",
+        "780c65d6a3318e479c02141d3f0b3918",
+        "902ea8ce4680c09395ce71874d242f84274243a156938aaa2dd37ac5be382b42"),
+    new HChaCha20TestVector(
+        "1a170ddf25a4fd69b648926e6d794e73408805835c64b2c70efddd8cd1c56ce0",
+        "05dbee10de87eb0c5acb2b66ebbe67d3",
+        "a4e20b634c77d7db908d387b48ec2b370059db916e8ea7716dc07238532d5981"),
+    new HChaCha20TestVector(
+        "3b354e4bb69b5b4a1126f509e84cad49f18c9f5f29f0be0c821316a6986e15a6",
+        "d8a89af02f4b8b2901d8321796388b6c",
+        "9816cb1a5b61993735a4b161b51ed2265b696e7ded5309c229a5a99f53534fbc"),
+    new HChaCha20TestVector(
+        "4b9a818892e15a530db50dd2832e95ee192e5ed6afffb408bd624a0c4e12a081",
+        "a9079c551de70501be0286d1bc78b045",
+        "ebc5224cf41ea97473683b6c2f38a084bf6e1feaaeff62676db59d5b719d999b"),
+    new HChaCha20TestVector(
+        "c49758f00003714c38f1d4972bde57ee8271f543b91e07ebce56b554eb7fa6a7",
+        "31f0204e10cf4f2035f9e62bb5ba7303",
+        "0dd8cc400f702d2c06ed920be52048a287076b86480ae273c6d568a2e9e7518c"),
+    // From https://tools.ietf.org/html/draft-arciszewski-xchacha-01#section-2.2.1.
+    new HChaCha20TestVector(
+        "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+        "000000090000004a0000000031415927",
+        "82413b4227b27bfed30e42508a877d73a0f9e4d58a74a853c12ec41326d3ecdc")
+  };
+
+  @Test
+  public void testHChaCha20TestVectors() {
+    for (HChaCha20TestVector test : hChaCha20TestVectors) {
+      int[] output = XChaCha20.hChaCha20(test.key, test.in);
+      assertThat(output).isEqualTo(test.out);
+    }
+  }
+
+  private static class XChaCha20TestVector {
+    public byte[] key;
+    public byte[] nonce;
+    public byte[] ciphertext;
+    public byte[] plaintext;
+
+    public XChaCha20TestVector(String key, String nonce, String ciphertext, String plaintext) {
+      this.key = Hex.decode(key);
+      this.nonce = Hex.decode(nonce);
+      this.ciphertext = Hex.decode(ciphertext);
+      this.plaintext = Hex.decode(plaintext);
+      if (plaintext.length() == 0) {
+        this.plaintext = new byte[this.ciphertext.length];
+      }
+    }
+  }
+
+  // From libsodium's test/default/xchacha20.c (tv_stream_xchacha20)
+  private static final XChaCha20TestVector[] xChaCha20TestVectors = {
+    new XChaCha20TestVector(
+        "79c99798ac67300bbb2704c95c341e3245f3dcb21761b98e52ff45b24f304fc4",
+        "b33ffd3096479bcfbc9aee49417688a0a2554f8d95389419",
+        "c6e9758160083ac604ef90e712ce6e75d7797590744e0cf060f013739c",
+        ""),
+    new XChaCha20TestVector(
+        "ddf7784fee099612c40700862189d0397fcc4cc4b3cc02b5456b3a97d1186173",
+        "a9a04491e7bf00c3ca91ac7c2d38a777d88993a7047dfcc4",
+        "2f289d371f6f0abc3cb60d11d9b7b29adf6bc5ad843e8493e928448d",
+        ""),
+    new XChaCha20TestVector(
+        "3d12800e7b014e88d68a73f0a95b04b435719936feba60473f02a9e61ae60682",
+        "56bed2599eac99fb27ebf4ffcb770a64772dec4d5849ea2d",
+        "a2c3c1406f33c054a92760a8e0666b84f84fa3a618f0",
+        ""),
+    new XChaCha20TestVector(
+        "5f5763ff9a30c95da5c9f2a8dfd7cc6efd9dfb431812c075aa3e4f32e04f53e4",
+        "a5fa890efa3b9a034d377926ce0e08ee6d7faccaee41b771",
+        "8a1a5ba898bdbcff602b1036e469a18a5e45789d0e8d9837d81a2388a52b0b6a0f51891528f424c4a7f492"
+            + "a8dd7bce8bac19fbdbe1fb379ac0",
+        ""),
+    new XChaCha20TestVector(
+        "eadc0e27f77113b5241f8ca9d6f9a5e7f09eee68d8a5cf30700563bf01060b4e",
+        "a171a4ef3fde7c4794c5b86170dc5a099b478f1b852f7b64",
+        "23839f61795c3cdbcee2c749a92543baeeea3cbb721402aa42e6cae140447575f2916c5d71108e3b13357e"
+            + "af86f060cb",
+        ""),
+    new XChaCha20TestVector(
+        "91319c9545c7c804ba6b712e22294c386fe31c4ff3d278827637b959d3dbaab2",
+        "410e854b2a911f174aaf1a56540fc3855851f41c65967a4e",
+        "cbe7d24177119b7fdfa8b06ee04dade4256ba7d35ffda6b89f014e479faef6",
+        ""),
+    new XChaCha20TestVector(
+        "6a6d3f412fc86c4450fc31f89f64ed46baa3256ffcf8616e8c23a06c422842b6",
+        "6b7773fce3c2546a5db4829f53a9165f41b08faae2fb72d5",
+        "8b23e35b3cdd5f3f75525fc37960ec2b68918e8c046d8a832b9838f1546be662e54feb1203e2",
+        ""),
+    new XChaCha20TestVector(
+        "d45e56368ebc7ba9be7c55cfd2da0feb633c1d86cab67cd5627514fd20c2b391",
+        "fd37da2db31e0c738754463edadc7dafb0833bd45da497fc",
+        "47950efa8217e3dec437454bd6b6a80a287e2570f0a48b3fa1ea3eb868be3d486f6516606d85e5643becc4"
+            + "73b370871ab9ef8e2a728f73b92bd98e6e26ea7c8ff96ec5a9e8de95e1eee9300c",
+        ""),
+    new XChaCha20TestVector(
+        "aface41a64a9a40cbc604d42bd363523bd762eb717f3e08fe2e0b4611eb4dcf3",
+        "6906e0383b895ab9f1cf3803f42f27c79ad47b681c552c63",
+        "a5fa7c0190792ee17675d52ad7570f1fb0892239c76d6e802c26b5b3544d13151e67513b8aaa1ac5af2d7f"
+            + "d0d5e4216964324838",
+        ""),
+    new XChaCha20TestVector(
+        "9d23bd4149cb979ccf3c5c94dd217e9808cb0e50cd0f67812235eaaf601d6232",
+        "c047548266b7c370d33566a2425cbf30d82d1eaf5294109e",
+        "a21209096594de8c5667b1d13ad93f744106d054df210e4782cd396fec692d3515a20bf351eec011a92c36"
+            + "7888bc464c32f0807acd6c203a247e0db854148468e9f96bee4cf718d68d5f637cbd5a376457788e"
+            + "6fae90fc31097cfc",
+        ""),
+    // https://tools.ietf.org/html/draft-arciszewski-xchacha-00.
+    new XChaCha20TestVector(
+        "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
+        "404142434445464748494a4b4c4d4e4f5051525354555658",
+        "4559abba4e48c16102e8bb2c05e6947f50a786de162f9b0b7e592a9b53d0d4e98d8d6410d540a1a6375b26"
+            + "d80dace4fab52384c731acbf16a5923c0c48d3575d4d0d2c673b666faa731061277701093a6bf7a15"
+            + "8a8864292a41c48e3a9b4c0daece0f8d98d0d7e05b37a307bbb66333164ec9e1b24ea0d6c3ffddcec"
+            + "4f68e7443056193a03c810e11344ca06d8ed8a2bfb1e8d48cfa6bc0eb4e2464b748142407c9f431ae"
+            + "e769960e15ba8b96890466ef2457599852385c661f752ce20f9da0c09ab6b19df74e76a95967446f8"
+            + "d0fd415e7bee2a12a114c20eb5292ae7a349ae577820d5520a1f3fb62a17ce6a7e68fa7c79111d886"
+            + "0920bc048ef43fe84486ccb87c25f0ae045f0cce1e7989a9aa220a28bdd4827e751a24a6d5c62d790"
+            + "a66393b93111c1a55dd7421a10184974c7c5",
+        "5468652064686f6c65202870726f6e6f756e6365642022646f6c65222920697320616c736f206b6e6f776e2"
+            + "061732074686520417369617469632077696c6420646f672c2072656420646f672c20616e64207768"
+            + "6973746c696e6720646f672e2049742069732061626f7574207468652073697a65206f66206120476"
+            + "5726d616e20736865706865726420627574206c6f6f6b73206d6f7265206c696b652061206c6f6e67"
+            + "2d6c656767656420666f782e205468697320686967686c7920656c757369766520616e6420736b696"
+            + "c6c6564206a756d70657220697320636c6173736966696564207769746820776f6c7665732c20636f"
+            + "796f7465732c206a61636b616c732c20616e6420666f78657320696e20746865207461786f6e6f6d6"
+            + "9632066616d696c792043616e696461652e")
+  };
+
+  @Test
+  public void testXChaCha20TestVectors() throws Exception {
+    for (XChaCha20TestVector test : xChaCha20TestVectors) {
+      IndCpaCipher cipher = new XChaCha20(test.key, 0 /* initialCounter */);
+      byte[] message = cipher.decrypt(Bytes.concat(test.nonce, test.ciphertext));
+      assertThat(message).isEqualTo(test.plaintext);
+    }
+  }
+}
diff --git a/kokoro/README.md b/kokoro/README.md
index 995c723..7fc46f0 100644
--- a/kokoro/README.md
+++ b/kokoro/README.md
@@ -3,17 +3,25 @@
 ![Kokoro Ubuntu](https://storage.googleapis.com/tink-kokoro-build-badges/tink-ubuntu.png)
 ![Kokoro macOS](https://storage.googleapis.com/tink-kokoro-build-badges/tink-macos.png)
 
-Tink is testing continuously with
-[Kokoro](https://www.cloudbees.com/sites/default/files/2016-jenkins-world-jenkins_inside_google.pdf)
+Tink is automatically tested using
+[Kokoro](https://www.cloudbees.com/sites/default/files/2016-jenkins-world-jenkins_inside_google.pdf),
 an internal deployment of Jenkins at Google.
 
 Continuous builds and presubmit tests are run on Ubuntu and macOS.
 
-Presubmit tests are triggered for the pull request if one of the following
+## Continuous builds
+
+Continuous builds are triggered for every new commit to the master branch and
+the current release branch.
+
+For the master branch, at the end of a successful
+continious build, a snapshot of Tink and the example apps are created and
+uploaded to Maven.
+
+## Presubmit testing
+
+Presubmit tests are triggered for a pull request if one of the following
 conditions is met:
 
  - The pull request is created by a Googler.
- - The pull request is attached with a kokoro:run label.
-
-Continuous builds are triggered for every new commit, and upload to Maven snapshot
-versions of Tink and apps.
+ - The pull request is attached with a `kokoro:run` label.
diff --git a/kokoro/continuous-release.sh b/kokoro/continuous-release.sh
new file mode 100755
index 0000000..b926dc3
--- /dev/null
+++ b/kokoro/continuous-release.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+# Copyright 2017 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+####################################################################################
+
+# Fail on any error.
+set -e
+
+# Display commands to stderr.
+set -x
+
+# Change to the root directory of the repository.
+cd git*/tink-release
+
+source ./kokoro/run_tests.sh
+
+# Run all manual tests.
+time bazel test \
+--strategy=TestRunner=standalone \
+--test_timeout 10000 \
+--test_output=all \
+//java:src/test/java/com/google/crypto/tink/subtle/AesGcmJceTest \
+//java:src/test/java/com/google/crypto/tink/subtle/AesGcmHkdfStreamingTest
diff --git a/kokoro/continuous.sh b/kokoro/continuous.sh
index 2c1d760..44c162e 100755
--- a/kokoro/continuous.sh
+++ b/kokoro/continuous.sh
@@ -26,6 +26,9 @@
 
 source ./kokoro/run_tests.sh
 
+# Test that Tink can be installed with the standard Go tooling.
+go get github.com/google/tink/go/...
+
 # Run all manual tests.
 time bazel test \
 --strategy=TestRunner=standalone \
diff --git a/kokoro/run_tests.sh b/kokoro/run_tests.sh
index acfa361..6517072 100755
--- a/kokoro/run_tests.sh
+++ b/kokoro/run_tests.sh
@@ -81,9 +81,6 @@
   --strategy=TestRunner=standalone --test_output=all \
   -- //... \
   -//objc/... || ( ls -l ; df -h / ; exit 1 )
-
-  # Test that Tink can be installed with the standard Go tooling.
-  go get github.com/google/tink/go/...
 }
 
 run_macos_tests() {
diff --git a/maven/tink.pom.xml b/maven/tink.pom.xml
index 07756ea..c89e6af 100644
--- a/maven/tink.pom.xml
+++ b/maven/tink.pom.xml
@@ -71,7 +71,7 @@
     <google-auto-service.version>1.0-rc4</google-auto-service.version>
     <google-guava.version>25.0-jre</google-guava.version>
     <json.version>20170516</json.version>
-    <protobuf.version>3.3.0</protobuf.version>
+    <protobuf.version>3.6.0</protobuf.version>
     <protobuf-lite.version>3.0.1</protobuf-lite.version>
   </properties>
 
diff --git a/objc/BUILD.bazel b/objc/BUILD.bazel
index 1fed6da..a04432a 100644
--- a/objc/BUILD.bazel
+++ b/objc/BUILD.bazel
@@ -3,6 +3,8 @@
 package(default_visibility = ["//tools/build_defs:internal_pkg"])
 
 load("@build_bazel_rules_apple//apple:ios.bzl", "ios_static_framework", "ios_unit_test")
+load("//:tink_version.bzl", "TINK_VERSION_LABEL")
+load("//tools:common.bzl", "template_rule")
 
 # public libraries
 
@@ -14,6 +16,10 @@
     "TINKAllConfig.h",
     "TINKBinaryKeysetReader.h",
     "TINKConfig.h",
+    "TINKDeterministicAead.h",
+    "TINKDeterministicAeadConfig.h",
+    "TINKDeterministicAeadFactory.h",
+    "TINKDeterministicAeadKeyTemplate.h",
     "TINKHybridConfig.h",
     "TINKHybridDecrypt.h",
     "TINKHybridDecryptFactory.h",
@@ -35,6 +41,7 @@
     "TINKRegistryConfig.h",
     "TINKSignatureConfig.h",
     "TINKSignatureKeyTemplate.h",
+    "TINKVersion.h",
 ]
 
 PUBLIC_API_DEPS = [
@@ -45,6 +52,10 @@
     ":all_config",
     ":binary_keyset_reader",
     ":config",
+    ":deterministic_aead",
+    ":deterministic_aead_config",
+    ":deterministic_aead_factory",
+    ":deterministic_aead_key_template",
     ":hybrid_config",
     ":hybrid_decrypt",
     ":hybrid_decrypt_factory",
@@ -66,6 +77,7 @@
     ":registry_config",
     ":signature_config",
     ":signature_key_template",
+    ":version",
     "//objc/util:errors",
     "//objc/util:strings",
 ]
@@ -243,6 +255,21 @@
     ],
 )
 
+template_rule(
+    name = "version_m",
+    src = "core/TINKVersion.m.templ",
+    out = "core/TINKVersion.m",
+    substitutions = {
+        "TINK_VERSION_LABEL": "%s" % TINK_VERSION_LABEL,
+    },
+)
+
+objc_library(
+    name = "version",
+    srcs = [":version_m"],
+    hdrs = ["TINKVersion.h"],
+)
+
 ############################
 #         Aead             #
 ############################
@@ -307,6 +334,70 @@
 )
 
 ############################
+#    Deterministic Aead    #
+############################
+
+objc_library(
+    name = "deterministic_aead",
+    hdrs = ["TINKDeterministicAead.h"],
+)
+
+objc_library(
+    name = "deterministic_aead_internal",
+    srcs = ["daead/TINKDeterministicAeadInternal.mm"],
+    hdrs = ["daead/TINKDeterministicAeadInternal.h"],
+    deps = [
+        ":deterministic_aead",
+        ":keyset_handle",
+        "//cc:deterministic_aead",
+        "//objc/util:errors",
+        "//objc/util:strings",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+objc_library(
+    name = "deterministic_aead_config",
+    srcs = ["daead/TINKDeterministicAeadConfig.mm"],
+    hdrs = ["TINKDeterministicAeadConfig.h"],
+    deps = [
+        ":registry_config",
+        "//cc/daead:deterministic_aead_config",
+        "//cc/util:errors",
+        "//objc/util:errors",
+        "//objc/util:strings",
+    ],
+)
+
+objc_library(
+    name = "deterministic_aead_factory",
+    srcs = ["daead/TINKDeterministicAeadFactory.mm"],
+    hdrs = ["TINKDeterministicAeadFactory.h"],
+    deps = [
+        ":deterministic_aead",
+        ":deterministic_aead_internal",
+        ":keyset_handle",
+        "//cc:keyset_handle",
+        "//cc/daead:deterministic_aead_factory",
+        "//cc/util:status",
+        "//objc/util:errors",
+    ],
+)
+
+objc_library(
+    name = "deterministic_aead_key_template",
+    srcs = ["daead/TINKDeterministicAeadKeyTemplate.mm"],
+    hdrs = ["TINKDeterministicAeadKeyTemplate.h"],
+    deps = [
+        ":key_template",
+        ":tink_cc_pb",
+        "//cc/daead:deterministic_aead_key_templates",
+        "//cc/util:status",
+        "//objc/util:errors",
+    ],
+)
+
+############################
 #         Hybrid           #
 ############################
 
@@ -629,6 +720,7 @@
         "//objc/util:proto_helpers",
         "//objc/util:test_helpers",
         "//proto:all_objc_proto",
+        "@com_google_absl//absl/memory",
         "@com_google_protobuf//:protobuf_lite",
     ],
 )
diff --git a/objc/CHANGELOG b/objc/CHANGELOG
index 37ccd5a..272aee2 100644
--- a/objc/CHANGELOG
+++ b/objc/CHANGELOG
@@ -1,3 +1,12 @@
+Version 1.2.2
+==================================
+Staticly linking BoringSSL in the CocoaPod build to prevent namespace clashes
+when Tink is used alongside pods that depend on OpenSSL.
+
+Version 1.2.1
+==================================
+No changes to the Obj-C implementation.
+
 Version 1.2.0
 ==================================
 Added support for IEEE P1363 signature encoding.
diff --git a/objc/TINKAeadKeyTemplate.h b/objc/TINKAeadKeyTemplate.h
index 3c10e67..d3b8c58 100644
--- a/objc/TINKAeadKeyTemplate.h
+++ b/objc/TINKAeadKeyTemplate.h
@@ -78,6 +78,14 @@
    *   OutputPrefixType: TINK
    */
   TINKAes256Eax = 6,
+
+  /**
+   * XChaCha20Poly1305Key with the following parameters:
+   *    XChacha20 key size: 32 bytes
+   *    IV size: 24 bytes
+   *    OutputPrefixType: TINK
+   */
+  TINKXChaCha20Poly1305 = 7,
 };
 
 NS_ASSUME_NONNULL_BEGIN
diff --git a/objc/TINKDeterministicAead.h b/objc/TINKDeterministicAead.h
new file mode 100644
index 0000000..ecc134c
--- /dev/null
+++ b/objc/TINKDeterministicAead.h
@@ -0,0 +1,83 @@
+/**
+ * Copyright 2018 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ **************************************************************************
+ */
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Protocol for Deterministic Authenticated Encryption with Associated Data (Deterministic AEAD).
+ *
+ * For why this protocol is desirable and some of its use cases, see for example
+ * RFC 5297 section 1.3 (https://tools.ietf.org/html/rfc5297#section-1.3).
+ *
+ * Warning
+ *
+ * Unlike Aead, implementations of this protocol are not semantically secure, because
+ * encrypting the same plaintex always yields the same ciphertext.
+ *
+ * Security guarantees
+ *
+ * Implementations of this protocol provide 128-bit security level against multi-user attacks
+ * with up to 2^32 keys. That means if an adversary obtains 2^32 ciphertexts of the same message
+ * encrypted under 2^32 keys, they need to do 2^128 computations to obtain a single key.
+ *
+ * Encryption with associated data ensures authenticity (who the sender is) and integrity (the
+ * data has not been tampered with) of that data, but not its secrecy. See RFC 5116 (
+ * https://tools.ietf.org/html/rfc5116)
+ */
+@protocol TINKDeterministicAead <NSObject>
+
+/**
+ * Deterministically encrypts @c plaintext with @c associatedData as associated
+ * authenticated data.
+ *
+ * Warning
+ *
+ * Encrypting the same @c plaintext multiple times protects the integrity of that plaintext, but
+ * confidentiality is compromised to the extent that an attacker can determine that the same
+ * plaintext was encrypted.
+ *
+ * The resulting ciphertext allows for checking authenticity and integrity of @c associatedData, but
+ * does not guarantee its secrecy.
+ *
+ * @param plaintext       The data to encrypt.
+ * @param associatedData  Additional associated data. (optional)
+ * @return                The encrypted data on success; nil in case of error.
+ */
+- (nullable NSData *)encryptDeterministically:(NSData *)plaintext
+                           withAssociatedData:(nullable NSData *)associatedData
+                                        error:(NSError **)error;
+
+/**
+ * Deterministically decrypts @c ciphertext with @c associatedData as associated authenticated data.
+ *
+ * The decryption verifies the authenticity and integrity of the associated data, but there are no
+ * guarantees with regards to secrecy of that data.
+ *
+ * @param ciphertext      The data to decrypt.
+ * @param associatedData  Associated authenticated data. (optional)
+ * @return                The decrypted data on success; nil in case of error.
+ */
+- (nullable NSData *)decryptDeterministically:(NSData *)ciphertext
+                           withAssociatedData:(nullable NSData *)associatedData
+                                        error:(NSError **)error;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/objc/TINKDeterministicAeadConfig.h b/objc/TINKDeterministicAeadConfig.h
new file mode 100644
index 0000000..42319e6
--- /dev/null
+++ b/objc/TINKDeterministicAeadConfig.h
@@ -0,0 +1,54 @@
+/**
+ * Copyright 2018 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ **************************************************************************
+ */
+
+#import <Foundation/Foundation.h>
+
+#import "TINKRegistryConfig.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * This class is used for registering with the Registry all instances of Deterministic Aead key
+ * types supported in a particular release of Tink.
+ *
+ * To register all Deterministic Aead key types provided in the latest release of Tink one can do:
+ *
+ * NSError *error = nil;
+ * TINKDeterministicAeadConfig *config =
+ *    [[TINKDeterministicAeadConfig alloc] initWithError:&error];
+ * if (!config || error) {
+ *   // handle error.
+ * }
+ *
+ * if (![TINKConfig registerConfig:aeadConfig error:&error]) {
+ *   // handle error.
+ * }
+ *
+ */
+@interface TINKDeterministicAeadConfig : TINKRegistryConfig
+
+/* Use -initWithError: to get an instance of TINKDeterministicAeadConfig. */
+- (nullable instancetype)init NS_UNAVAILABLE;
+
+/* Returns config of Deterministic Aead implementations supported in the latest version of Tink. */
+- (nullable instancetype)initWithError:(NSError **)error
+    NS_SWIFT_NAME(init())NS_DESIGNATED_INITIALIZER;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/objc/TINKDeterministicAeadFactory.h b/objc/TINKDeterministicAeadFactory.h
new file mode 100644
index 0000000..e4fa055
--- /dev/null
+++ b/objc/TINKDeterministicAeadFactory.h
@@ -0,0 +1,65 @@
+/**
+ * Copyright 2019 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ **************************************************************************
+ */
+
+#import <Foundation/Foundation.h>
+
+@class TINKKeysetHandle;
+@protocol TINKDeterministicAead;
+
+NS_ASSUME_NONNULL_BEGIN;
+
+/**
+ * TINKDeterministicAeadFactory allows for obtaining a TINKDeterministicAead primitive from a
+ * TINKKeysetHandle.
+ *
+ * TINKDeterministicAeadFactory gets primitives from the Registry, which can be initialized via
+ * convenience methods from TINKDeterministicAeadConfig. Here is an example how one can obtain and
+ * use a TINKDeterministicAead primitive:
+ *
+ * NSError *error = nil;
+ * TINKDeterministicAeadConfig *aeadConfig = [[TINKDeterministicAeadConfig alloc]
+ *     initWithError:&error];
+ * if (!aeadConfig || error) {
+ *   // handle error.
+ * }
+ *
+ * if (![TINKConfig registerConfig:aeadConfig error:&error]) {
+ *   // handle error.
+ * }
+ *
+ * TINKKeysetHandle keysetHandle = ...;
+ * id<TINKDeterministicAead> aead =
+ *    [TINKDeterministicAeadFactory primitiveWithKeysetHandle:keysetHandle error:&error];
+ * if (!aead || error) {
+ *   // handle error.
+ * }
+ *
+ * NSData *plaintext = ...;
+ * NSData *additionalData = ...;
+ * NSData *ciphertext = [aead encrypt:plaintext withAdditionalData:additionalData error:&error];
+ */
+@interface TINKDeterministicAeadFactory : NSObject
+/**
+ * Returns an object that conforms to the TINKDeterministicAead protocol. It uses key material from
+ * the keyset specified via @c keysetHandle.
+ */
++ (nullable id<TINKDeterministicAead>)primitiveWithKeysetHandle:(TINKKeysetHandle *)keysetHandle
+                                                          error:(NSError **)error;
+@end
+
+NS_ASSUME_NONNULL_END;
diff --git a/objc/TINKDeterministicAeadInternal.h b/objc/TINKDeterministicAeadInternal.h
new file mode 100644
index 0000000..07bc944
--- /dev/null
+++ b/objc/TINKDeterministicAeadInternal.h
@@ -0,0 +1,40 @@
+/**
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ **************************************************************************
+ */
+
+#import "objc/TINKDeterministicAead.h"
+
+#import <Foundation/Foundation.h>
+
+#include "tink/deterministic_aead.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * This interface is internal-only. Use initWithKeysetHandle:error: to get an instance of
+ * TINKDeterministicAead.
+ */
+@interface TINKDeterministicAead ()
+
+- (nullable instancetype)initWithCCDeterministicAead:
+    (std::unique_ptr<crypto::tink::DeterministicAead>)ccDeterministicAead NS_DESIGNATED_INITIALIZER;
+
+- (nullable crypto::tink::DeterministicAead *)ccDeterministicAead;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/objc/TINKDeterministicAeadKeyTemplate.h b/objc/TINKDeterministicAeadKeyTemplate.h
new file mode 100644
index 0000000..4cb2d83
--- /dev/null
+++ b/objc/TINKDeterministicAeadKeyTemplate.h
@@ -0,0 +1,82 @@
+/**
+ * Copyright 2018 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ **************************************************************************
+ */
+
+#import <Foundation/Foundation.h>
+
+#import "TINKKeyTemplate.h"
+
+typedef NS_ENUM(NSInteger, TINKDeterministicAeadKeyTemplates) {
+  /**
+   * Aes256SivKey with the following parameters:
+   *   Key size: 64 bytes
+   *   OutputPrefixType: TINK
+   */
+  TINKAes256Siv = 1,
+};
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Pre-generated key templates for TINKDeterministicAead key types.
+ * One can use these templates to generate new TINKKeysetHandle object with fresh keys.
+ *
+ * Example:
+ *
+ * NSError *error = nil;
+ * TINKDeterministicAeadConfig *aeadConfig =
+ *    [[TINKDeterministicAeadConfig alloc] initWithError:&error];
+ * if (!aeadConfig || error) {
+ *   // handle error.
+ * }
+ *
+ * if (![TINKConfig registerConfig:aeadConfig error:&error]) {
+ *   // handle error.
+ * }
+ *
+ * TINKDeterministicAeadKeyTemplate *tpl =
+ *    [[TINAeadKeyTemplate alloc] initWithKeyTemplate:TINKAes256Siv
+ *                                              error:&error];
+ * if (!tpl || error) {
+ *   // handle error.
+ * }
+ *
+ * TINKKeysetHandle *handle = [[TINKKeysetHandle alloc] initWithKeyTemplate:tpl error:&error];
+ * if (!handle || error) {
+ *   // handle error.
+ * }
+ *
+ */
+@interface TINKDeterministicAeadKeyTemplate : TINKKeyTemplate
+
+- (nullable instancetype)init
+    __attribute__((unavailable("Use -initWithKeyTemplate:error: instead.")));
+
+/**
+ * Creates a TINKDeterministicAeadKeyTemplate that can be used to generate aead keysets.
+ *
+ * @param keyTemplate The deterministic aead key template to use.
+ * @param error       If non-nil it will be populated with a descriptive error when the operation
+ *                    fails.
+ * @return            A TINKDeterministicAeadKeyTemplate or nil in case of error.
+ */
+- (nullable instancetype)initWithKeyTemplate:(TINKDeterministicAeadKeyTemplates)keyTemplate
+                                       error:(NSError **)error NS_DESIGNATED_INITIALIZER;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/objc/TINKKeysetHandle.h b/objc/TINKKeysetHandle.h
index e8b3f77..493afe4 100644
--- a/objc/TINKKeysetHandle.h
+++ b/objc/TINKKeysetHandle.h
@@ -50,6 +50,16 @@
                                         error:(NSError **)error;
 
 /**
+ * Creates a TINKKeysetHandle from a serialized keyset which contains no secret key material.
+ * This can be used to load public keysets or envelope encryption keysets.
+ *
+ * @param keyset  A serialized keyset.
+ * @param error   If non-nil it will be populated with a descriptive error message.
+ * @return        A TINKKeysetHandle, or nil in case of error.
+ */
+- (nullable instancetype)initWithNoSecretKeyset:(NSData *)keyset error:(NSError **)error;
+
+/**
  * Returns a new TINKKeysetHandle that contains a single fresh key generated according to
  * @c keyTemplate. @c keyTemplate can be obtained by using one of the subclasses such as
  * TINKAeadKeyTemplate, TINKHybridKeyTemplate etc.
diff --git a/objc/TINKSignatureKeyTemplate.h b/objc/TINKSignatureKeyTemplate.h
index 240f2a0..f847252 100644
--- a/objc/TINKSignatureKeyTemplate.h
+++ b/objc/TINKSignatureKeyTemplate.h
@@ -74,6 +74,51 @@
    *   - OutputPrefixType: TINK
    */
   TINKEcdsaP521Ieee = 6,
+
+  /**
+   * RsaSsaPkcs1PrivateKey with the following parameters:
+   *   - Modulus size in bits: 3072.
+   *   - Hash function: SHA256.
+   *   - Public Exponent: 65537 (aka F4).
+   *   - OutputPrefixType: TINK
+   */
+  TINKRsaSsaPkcs13072Sha256F4 = 7,
+
+  /**
+   * RsaSsaPkcs1PrivateKey with the following parameters:
+   *   - Modulus size in bits: 4096.
+   *   - Hash function: SHA512.
+   *   - Public Exponent: 65537 (aka F4).
+   *   - OutputPrefixType: TINK
+   */
+  TINKRsaSsaPkcs14096Sha512F4 = 8,
+
+  /**
+   * RsaSsaPssPrivateKey with the following parameters:
+   *   - Modulus size in bits: 3072.
+   *   - Signature hash: SHA256.
+   *   - MGF1 hash: SHA256.
+   *   - Salt length: 32 (i.e., SHA256's output length).
+   *   - Public Exponent: 65537 (aka F4).
+   *   - OutputPrefixType: TINK
+   */
+  TINKRsaSsaPss3072Sha256Sha256F4 = 9,
+
+  /**
+   * RsaSsaPssPrivateKey with the following parameters:
+   *   - Modulus size in bits: 4096.
+   *   - Signature hash: SHA512.
+   *   - MGF1 hash: SHA512.
+   *   - Salt length: 64 (i.e., SHA512's output length).
+   *   - Public Exponent: 65537 (aka F4).
+   *   - OutputPrefixType: TINK
+   */
+  TINKRsaSsaPss4096Sha512Sha512F4 = 10,
+
+  /**
+   * Ed25519PrivateKey.
+   */
+  TINKEd25519 = 11,
 };
 
 NS_ASSUME_NONNULL_BEGIN
diff --git a/objc/TINKVersion.h b/objc/TINKVersion.h
new file mode 100644
index 0000000..7d5cf5c
--- /dev/null
+++ b/objc/TINKVersion.h
@@ -0,0 +1,21 @@
+/**
+ * Copyright 2018 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ **************************************************************************
+ */
+
+#import <Foundation/Foundation.h>
+
+extern NSString *const TINKVersion;
diff --git a/objc/Tests/UnitTests/aead/TINKAeadConfigTest.mm b/objc/Tests/UnitTests/aead/TINKAeadConfigTest.mm
index cb7af77..064ecdd 100644
--- a/objc/Tests/UnitTests/aead/TINKAeadConfigTest.mm
+++ b/objc/Tests/UnitTests/aead/TINKAeadConfigTest.mm
@@ -37,6 +37,8 @@
       "type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey";
   std::string aes_gcm_key_type = "type.googleapis.com/google.crypto.tink.AesGcmKey";
   std::string aes_eax_key_type = "type.googleapis.com/google.crypto.tink.AesEaxKey";
+  std::string xchacha20_poly1305_key_type =
+      "type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key";
   std::string hmac_key_type = "type.googleapis.com/google.crypto.tink.HmacKey";
 
   NSError *error = nil;
@@ -45,7 +47,7 @@
   XCTAssertNil(error);
 
   google::crypto::tink::RegistryConfig config = aeadConfig.ccConfig;
-  XCTAssertTrue(config.entry_size() == 4);
+  XCTAssertTrue(config.entry_size() == 5);
 
   XCTAssertTrue("TinkMac" == config.entry(0).catalogue_name());
   XCTAssertTrue("Mac" == config.entry(0).primitive_name());
@@ -71,6 +73,12 @@
   XCTAssertTrue(config.entry(3).new_key_allowed());
   XCTAssertTrue(0 == config.entry(3).key_manager_version());
 
+  XCTAssertTrue("TinkAead" == config.entry(4).catalogue_name());
+  XCTAssertTrue("Aead" == config.entry(4).primitive_name());
+  XCTAssertTrue(xchacha20_poly1305_key_type == config.entry(4).type_url());
+  XCTAssertTrue(config.entry(4).new_key_allowed());
+  XCTAssertTrue(0 == config.entry(4).key_manager_version());
+
   // Registration of standard key types works.
   error = nil;
   XCTAssertTrue([TINKConfig registerConfig:aeadConfig error:&error]);
diff --git a/objc/Tests/UnitTests/aead/TINKAeadKeyTemplateTest.mm b/objc/Tests/UnitTests/aead/TINKAeadKeyTemplateTest.mm
index 01d65cb..2f979eb 100644
--- a/objc/Tests/UnitTests/aead/TINKAeadKeyTemplateTest.mm
+++ b/objc/Tests/UnitTests/aead/TINKAeadKeyTemplateTest.mm
@@ -20,6 +20,8 @@
 
 #import <XCTest/XCTest.h>
 
+#include "tink/aead/xchacha20_poly1305_key_manager.h"
+
 #import "objc/TINKKeyTemplate.h"
 #import "objc/core/TINKKeyTemplate_Internal.h"
 #import "objc/util/TINKProtoHelpers.h"
@@ -172,4 +174,29 @@
   XCTAssertEqual(keyFormat.keySize, 32);
 }
 
+- (void)testXChaCha20Poly1305KeyTemplates {
+  static NSString *const kTypeURL = @"type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key";
+
+  NSError *error = nil;
+  TINKAeadKeyTemplate *tpl = [[TINKAeadKeyTemplate alloc] initWithKeyTemplate:TINKXChaCha20Poly1305
+                                                                        error:&error];
+  XCTAssertNil(error);
+  XCTAssertNotNil(tpl);
+
+  error = nil;
+  TINKPBKeyTemplate *keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
+  XCTAssertNil(error);
+  XCTAssertNotNil(keyTemplate);
+
+  XCTAssertTrue([kTypeURL isEqualToString:keyTemplate.typeURL]);
+  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
+  error = nil;
+
+  // Check that the template works with the key manager.
+  crypto::tink::XChaCha20Poly1305KeyManager key_manager;
+  XCTAssertTrue(key_manager.get_key_type() == tpl.ccKeyTemplate->type_url());
+  auto new_key_result = key_manager.get_key_factory().NewKey(tpl.ccKeyTemplate->value());
+  XCTAssertTrue(new_key_result.ok());
+}
+
 @end
diff --git a/objc/Tests/UnitTests/core/TINKAllConfigTest.mm b/objc/Tests/UnitTests/core/TINKAllConfigTest.mm
index 7b06300..0ba4503 100644
--- a/objc/Tests/UnitTests/core/TINKAllConfigTest.mm
+++ b/objc/Tests/UnitTests/core/TINKAllConfigTest.mm
@@ -39,14 +39,14 @@
   XCTAssertNil(error);
   google::crypto::tink::RegistryConfig config = allConfig.ccConfig;
 
-  XCTAssertTrue(config.entry_size() == 8);
+  XCTAssertEqual(config.entry_size(), 16);
 
   std::string hmac_key_type = "type.googleapis.com/google.crypto.tink.HmacKey";
   XCTAssertTrue("TinkMac" == config.entry(0).catalogue_name());
   XCTAssertTrue("Mac" == config.entry(0).primitive_name());
   XCTAssertTrue(hmac_key_type == config.entry(0).type_url());
   XCTAssertTrue(config.entry(0).new_key_allowed());
-  XCTAssertTrue(0 == config.entry(0).key_manager_version());
+  XCTAssertEqual(config.entry(0).key_manager_version(), 0);
 
   std::string aes_ctr_hmac_aead_key_type =
       "type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey";
@@ -54,51 +54,112 @@
   XCTAssertTrue("Aead" == config.entry(1).primitive_name());
   XCTAssertTrue(aes_ctr_hmac_aead_key_type == config.entry(1).type_url());
   XCTAssertTrue(config.entry(1).new_key_allowed());
-  XCTAssertTrue(0 == config.entry(1).key_manager_version());
+  XCTAssertEqual(config.entry(1).key_manager_version(), 0);
 
   std::string aes_gcm_key_type = "type.googleapis.com/google.crypto.tink.AesGcmKey";
   XCTAssertTrue("TinkAead" == config.entry(2).catalogue_name());
   XCTAssertTrue("Aead" == config.entry(2).primitive_name());
   XCTAssertTrue(aes_gcm_key_type == config.entry(2).type_url());
   XCTAssertTrue(config.entry(2).new_key_allowed());
-  XCTAssertTrue(0 == config.entry(2).key_manager_version());
+  XCTAssertEqual(config.entry(2).key_manager_version(), 0);
 
   std::string aes_eax_key_type = "type.googleapis.com/google.crypto.tink.AesEaxKey";
   XCTAssertTrue("TinkAead" == config.entry(3).catalogue_name());
   XCTAssertTrue("Aead" == config.entry(3).primitive_name());
   XCTAssertTrue(aes_eax_key_type == config.entry(3).type_url());
   XCTAssertTrue(config.entry(3).new_key_allowed());
-  XCTAssertTrue(0 == config.entry(3).key_manager_version());
+  XCTAssertEqual(config.entry(3).key_manager_version(), 0);
+
+  std::string xchacha20_poly1305_key_type =
+      "type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key";
+  XCTAssertTrue("TinkAead" == config.entry(4).catalogue_name());
+  XCTAssertTrue("Aead" == config.entry(4).primitive_name());
+  XCTAssertTrue(xchacha20_poly1305_key_type == config.entry(4).type_url());
+  XCTAssertTrue(config.entry(4).new_key_allowed());
+  XCTAssertEqual(config.entry(4).key_manager_version(), 0);
 
   std::string ecies_hybrid_decrypt_key_type =
       "type.googleapis.com/google.crypto.tink.EciesAeadHkdfPrivateKey";
-  XCTAssertTrue("TinkHybridDecrypt" == config.entry(4).catalogue_name());
-  XCTAssertTrue("HybridDecrypt" == config.entry(4).primitive_name());
-  XCTAssertTrue(ecies_hybrid_decrypt_key_type == config.entry(4).type_url());
-  XCTAssertTrue(config.entry(4).new_key_allowed());
-  XCTAssertTrue(0 == config.entry(4).key_manager_version());
+  XCTAssertTrue("TinkHybridDecrypt" == config.entry(5).catalogue_name());
+  XCTAssertTrue("HybridDecrypt" == config.entry(5).primitive_name());
+  XCTAssertTrue(ecies_hybrid_decrypt_key_type == config.entry(5).type_url());
+  XCTAssertTrue(config.entry(5).new_key_allowed());
+  XCTAssertEqual(config.entry(5).key_manager_version(), 0);
 
   std::string ecies_hybrid_encrypt_key_type =
       "type.googleapis.com/google.crypto.tink.EciesAeadHkdfPublicKey";
-  XCTAssertTrue("TinkHybridEncrypt" == config.entry(5).catalogue_name());
-  XCTAssertTrue("HybridEncrypt" == config.entry(5).primitive_name());
-  XCTAssertTrue(ecies_hybrid_encrypt_key_type == config.entry(5).type_url());
-  XCTAssertTrue(config.entry(5).new_key_allowed());
-  XCTAssertTrue(0 == config.entry(5).key_manager_version());
+  XCTAssertTrue("TinkHybridEncrypt" == config.entry(6).catalogue_name());
+  XCTAssertTrue("HybridEncrypt" == config.entry(6).primitive_name());
+  XCTAssertTrue(ecies_hybrid_encrypt_key_type == config.entry(6).type_url());
+  XCTAssertTrue(config.entry(6).new_key_allowed());
+  XCTAssertEqual(config.entry(6).key_manager_version(), 0);
 
   std::string ecdsa_sign_key_type = "type.googleapis.com/google.crypto.tink.EcdsaPrivateKey";
-  XCTAssertTrue("TinkPublicKeySign" == config.entry(6).catalogue_name());
-  XCTAssertTrue("PublicKeySign" == config.entry(6).primitive_name());
-  XCTAssertTrue(ecdsa_sign_key_type == config.entry(6).type_url());
-  XCTAssertTrue(config.entry(6).new_key_allowed());
-  XCTAssertTrue(0 == config.entry(6).key_manager_version());
+  XCTAssertTrue("TinkPublicKeySign" == config.entry(7).catalogue_name());
+  XCTAssertTrue("PublicKeySign" == config.entry(7).primitive_name());
+  XCTAssertTrue(ecdsa_sign_key_type == config.entry(7).type_url());
+  XCTAssertTrue(config.entry(7).new_key_allowed());
+  XCTAssertEqual(config.entry(7).key_manager_version(), 0);
 
   std::string ecdsa_verify_key_type = "type.googleapis.com/google.crypto.tink.EcdsaPublicKey";
-  XCTAssertTrue("TinkPublicKeyVerify" == config.entry(7).catalogue_name());
-  XCTAssertTrue("PublicKeyVerify" == config.entry(7).primitive_name());
-  XCTAssertTrue(ecdsa_verify_key_type == config.entry(7).type_url());
-  XCTAssertTrue(config.entry(7).new_key_allowed());
-  XCTAssertTrue(0 == config.entry(7).key_manager_version());
+  XCTAssertTrue("TinkPublicKeyVerify" == config.entry(8).catalogue_name());
+  XCTAssertTrue("PublicKeyVerify" == config.entry(8).primitive_name());
+  XCTAssertTrue(ecdsa_verify_key_type == config.entry(8).type_url());
+  XCTAssertTrue(config.entry(8).new_key_allowed());
+  XCTAssertEqual(config.entry(8).key_manager_version(), 0);
+
+  std::string ed25519_sign_key_type = "type.googleapis.com/google.crypto.tink.Ed25519PrivateKey";
+  XCTAssertTrue("TinkPublicKeySign" == config.entry(9).catalogue_name());
+  XCTAssertTrue("PublicKeySign" == config.entry(9).primitive_name());
+  XCTAssertTrue(ed25519_sign_key_type == config.entry(9).type_url());
+  XCTAssertTrue(config.entry(9).new_key_allowed());
+  XCTAssertEqual(config.entry(9).key_manager_version(), 0);
+
+  std::string ed25519_verify_key_type = "type.googleapis.com/google.crypto.tink.Ed25519PublicKey";
+  XCTAssertTrue("TinkPublicKeyVerify" == config.entry(10).catalogue_name());
+  XCTAssertTrue("PublicKeyVerify" == config.entry(10).primitive_name());
+  XCTAssertTrue(ed25519_verify_key_type == config.entry(10).type_url());
+  XCTAssertTrue(config.entry(10).new_key_allowed());
+  XCTAssertEqual(config.entry(10).key_manager_version(), 0);
+
+  std::string rsa_ssa_pss_sign_key_type =
+      "type.googleapis.com/google.crypto.tink.RsaSsaPssPrivateKey";
+  XCTAssertTrue("TinkPublicKeySign" == config.entry(11).catalogue_name());
+  XCTAssertTrue("PublicKeySign" == config.entry(11).primitive_name());
+  XCTAssertTrue(rsa_ssa_pss_sign_key_type == config.entry(11).type_url());
+  XCTAssertTrue(config.entry(11).new_key_allowed());
+  XCTAssertEqual(config.entry(11).key_manager_version(), 0);
+
+  std::string rsa_ssa_pss_verify_key_type =
+      "type.googleapis.com/google.crypto.tink.RsaSsaPssPublicKey";
+  XCTAssertTrue("TinkPublicKeyVerify" == config.entry(12).catalogue_name());
+  XCTAssertTrue("PublicKeyVerify" == config.entry(12).primitive_name());
+  XCTAssertTrue(rsa_ssa_pss_verify_key_type == config.entry(12).type_url());
+  XCTAssertTrue(config.entry(12).new_key_allowed());
+  XCTAssertEqual(config.entry(12).key_manager_version(), 0);
+
+  std::string rsa_ssa_pkcs1_sign_key_type =
+      "type.googleapis.com/google.crypto.tink.RsaSsaPkcs1PrivateKey";
+  XCTAssertTrue("TinkPublicKeySign" == config.entry(13).catalogue_name());
+  XCTAssertTrue("PublicKeySign" == config.entry(13).primitive_name());
+  XCTAssertTrue(rsa_ssa_pkcs1_sign_key_type == config.entry(13).type_url());
+  XCTAssertTrue(config.entry(13).new_key_allowed());
+  XCTAssertEqual(config.entry(13).key_manager_version(), 0);
+
+  std::string rsa_ssa_pkcs1_verify_key_type =
+      "type.googleapis.com/google.crypto.tink.RsaSsaPkcs1PublicKey";
+  XCTAssertTrue("TinkPublicKeyVerify" == config.entry(14).catalogue_name());
+  XCTAssertTrue("PublicKeyVerify" == config.entry(14).primitive_name());
+  XCTAssertTrue(rsa_ssa_pkcs1_verify_key_type == config.entry(14).type_url());
+  XCTAssertTrue(config.entry(14).new_key_allowed());
+  XCTAssertEqual(config.entry(14).key_manager_version(), 0);
+
+  std::string aes_siv_key_type = "type.googleapis.com/google.crypto.tink.AesSivKey";
+  XCTAssertTrue("TinkDeterministicAead" == config.entry(15).catalogue_name());
+  XCTAssertTrue("DeterministicAead" == config.entry(15).primitive_name());
+  XCTAssertTrue(aes_siv_key_type == config.entry(15).type_url());
+  XCTAssertTrue(config.entry(15).new_key_allowed());
+  XCTAssertEqual(config.entry(15).key_manager_version(), 0);
 }
 
 - (void)testConfigRegistration {
diff --git a/objc/Tests/UnitTests/core/TINKKeysetHandleTest.mm b/objc/Tests/UnitTests/core/TINKKeysetHandleTest.mm
index 10615ca..54dc639 100644
--- a/objc/Tests/UnitTests/core/TINKKeysetHandleTest.mm
+++ b/objc/Tests/UnitTests/core/TINKKeysetHandleTest.mm
@@ -39,6 +39,12 @@
 #include "tink/util/test_util.h"
 #include "proto/tink.pb.h"
 
+using ::crypto::tink::test::AddRawKey;
+using ::crypto::tink::test::AddTinkKey;
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::Keyset;
+using ::google::crypto::tink::KeyStatusType;
+
 // Variables used to hold the serialized keyset data.
 static NSData *gBadSerializedKeyset;
 static NSData *gGoodSerializedKeyset;
@@ -406,5 +412,100 @@
       containsString:@"Key material is not of type KeyData::ASYMMETRIC_PRIVATE"]);
 }
 
+- (void)testReadNoSecret {
+  auto keyset = absl::make_unique<Keyset>();
+  Keyset::Key key;
+  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED, KeyData::ASYMMETRIC_PUBLIC,
+             keyset.get());
+  AddRawKey("some other key type", 711, key, KeyStatusType::ENABLED, KeyData::REMOTE, keyset.get());
+  keyset->set_primary_key_id(42);
+  NSData *serializedKeyset = TINKStringToNSData(keyset->SerializeAsString());
+  NSError *error = nil;
+  TINKKeysetHandle *handle = [[TINKKeysetHandle alloc] initWithNoSecretKeyset:serializedKeyset
+                                                                        error:&error];
+
+  XCTAssertNil(error);
+  XCTAssertNotNil(handle);
+  XCTAssertTrue(crypto::tink::KeysetUtil::GetKeyset(*handle.ccKeysetHandle).SerializeAsString() ==
+                keyset->SerializeAsString());
+}
+
+- (void)testReadNoSecretFailForTypeUnknown {
+  auto keyset = absl::make_unique<Keyset>();
+  Keyset::Key key;
+  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED, KeyData::UNKNOWN_KEYMATERIAL,
+             keyset.get());
+  keyset->set_primary_key_id(42);
+  NSData *serializedKeyset = TINKStringToNSData(keyset->SerializeAsString());
+  NSError *error = nil;
+  TINKKeysetHandle *handle = [[TINKKeysetHandle alloc] initWithNoSecretKeyset:serializedKeyset
+                                                                        error:&error];
+
+  XCTAssertNil(handle);
+  XCTAssertEqual(error.code, crypto::tink::util::error::FAILED_PRECONDITION);
+  XCTAssertTrue([error.localizedFailureReason
+      containsString:@"Cannot create KeysetHandle with secret key material"]);
+}
+
+- (void)testReadNoSecretFailForTypeSymmetric {
+  auto keyset = absl::make_unique<Keyset>();
+  Keyset::Key key;
+  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED, KeyData::SYMMETRIC, keyset.get());
+  keyset->set_primary_key_id(42);
+  NSData *serializedKeyset = TINKStringToNSData(keyset->SerializeAsString());
+  NSError *error = nil;
+  TINKKeysetHandle *handle = [[TINKKeysetHandle alloc] initWithNoSecretKeyset:serializedKeyset
+                                                                        error:&error];
+
+  XCTAssertNil(handle);
+  XCTAssertEqual(error.code, crypto::tink::util::error::FAILED_PRECONDITION);
+  XCTAssertTrue([error.localizedFailureReason
+      containsString:@"Cannot create KeysetHandle with secret key material"]);
+}
+
+- (void)testReadNoSecretFailForTypeAssymmetricPrivate {
+  auto keyset = absl::make_unique<Keyset>();
+  Keyset::Key key;
+  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED, KeyData::ASYMMETRIC_PRIVATE,
+             keyset.get());
+  keyset->set_primary_key_id(42);
+  NSData *serializedKeyset = TINKStringToNSData(keyset->SerializeAsString());
+  NSError *error = nil;
+  TINKKeysetHandle *handle = [[TINKKeysetHandle alloc] initWithNoSecretKeyset:serializedKeyset
+                                                                        error:&error];
+
+  XCTAssertNil(handle);
+  XCTAssertEqual(error.code, crypto::tink::util::error::FAILED_PRECONDITION);
+  XCTAssertTrue([error.localizedFailureReason
+      containsString:@"Cannot create KeysetHandle with secret key material"]);
+}
+
+- (void)testReadNoSecretFailForHidden {
+  auto keyset = absl::make_unique<Keyset>();
+  Keyset::Key key;
+  AddTinkKey("some key type", 42, key, KeyStatusType::ENABLED, KeyData::ASYMMETRIC_PUBLIC,
+             keyset.get());
+  for (int i = 0; i < 10; ++i) {
+    AddTinkKey(absl::StrCat("more key type", i), i, key, KeyStatusType::ENABLED,
+               KeyData::ASYMMETRIC_PUBLIC, keyset.get());
+  }
+  AddRawKey("some other key type", 10, key, KeyStatusType::ENABLED, KeyData::ASYMMETRIC_PRIVATE,
+            keyset.get());
+  for (int i = 0; i < 10; ++i) {
+    AddRawKey(absl::StrCat("more key type", i + 100), i + 100, key, KeyStatusType::ENABLED,
+              KeyData::ASYMMETRIC_PUBLIC, keyset.get());
+  }
+  keyset->set_primary_key_id(42);
+  NSData *serializedKeyset = TINKStringToNSData(keyset->SerializeAsString());
+  NSError *error = nil;
+  TINKKeysetHandle *handle = [[TINKKeysetHandle alloc] initWithNoSecretKeyset:serializedKeyset
+                                                                        error:&error];
+
+  XCTAssertNil(handle);
+  XCTAssertEqual(error.code, crypto::tink::util::error::FAILED_PRECONDITION);
+  XCTAssertTrue([error.localizedFailureReason
+      containsString:@"Cannot create KeysetHandle with secret key material"]);
+}
+
 @end
 
diff --git a/objc/Tests/UnitTests/core/TINKVersionTest.m b/objc/Tests/UnitTests/core/TINKVersionTest.m
new file mode 100644
index 0000000..15ae7b7
--- /dev/null
+++ b/objc/Tests/UnitTests/core/TINKVersionTest.m
@@ -0,0 +1,47 @@
+/**
+ * Copyright 2018 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ **************************************************************************
+ */
+
+#import "objc/TINKVersion.h"
+
+#import <XCTest/XCTest.h>
+
+@interface TINKVersionTest : XCTestCase
+@end
+
+@implementation TINKVersionTest
+
+- (void)testVersionFormat {
+  // The regex represents Semantic Versioning syntax (www.semver.org),
+  // i.e. three dot-separated numbers, with an optional suffix
+  // that starts with a hyphen, to cover alpha/beta releases and
+  // release candiates, for example:
+  //   1.2.3
+  //   1.2.3-beta
+  //   1.2.3-RC1
+  NSRegularExpression *expression =
+      [[NSRegularExpression alloc] initWithPattern:@"[0-9]+[.][0-9]+[.][0-9]+(-[A-Za-z0-9]+)?"
+                                           options:0
+                                             error:NULL];
+  NSRange range = NSMakeRange(0, [TINKVersion length]);
+  NSUInteger numberOfMatches = [expression numberOfMatchesInString:TINKVersion
+                                                           options:0
+                                                             range:range];
+  XCTAssertEqual(numberOfMatches, 1);
+}
+
+@end
diff --git a/objc/Tests/UnitTests/daead/TINKDeterministicAeadConfigTest.mm b/objc/Tests/UnitTests/daead/TINKDeterministicAeadConfigTest.mm
new file mode 100644
index 0000000..1a24097
--- /dev/null
+++ b/objc/Tests/UnitTests/daead/TINKDeterministicAeadConfigTest.mm
@@ -0,0 +1,59 @@
+/**
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ **************************************************************************
+ */
+
+#import "objc/TINKDeterministicAeadConfig.h"
+
+#import <XCTest/XCTest.h>
+
+#import "objc/TINKConfig.h"
+#import "objc/TINKRegistryConfig.h"
+#import "objc/core/TINKRegistryConfig_Internal.h"
+
+#include "tink/daead/deterministic_aead_config.h"
+#include "proto/config.pb.h"
+
+@interface TINKDeterministicAeadConfigTest : XCTestCase
+@end
+
+@implementation TINKDeterministicAeadConfigTest
+
+- (void)testConfigContents {
+  std::string aes_siv_key_type = "type.googleapis.com/google.crypto.tink.AesSivKey";
+
+  NSError *error = nil;
+  TINKDeterministicAeadConfig *aeadConfig =
+      [[TINKDeterministicAeadConfig alloc] initWithError:&error];
+  XCTAssertNotNil(aeadConfig);
+  XCTAssertNil(error);
+
+  google::crypto::tink::RegistryConfig config = aeadConfig.ccConfig;
+  XCTAssertEqual(config.entry_size(), 1);
+
+  XCTAssertTrue("TinkDeterministicAead" == config.entry(0).catalogue_name());
+  XCTAssertTrue("DeterministicAead" == config.entry(0).primitive_name());
+  XCTAssertTrue(aes_siv_key_type == config.entry(0).type_url());
+  XCTAssertTrue(config.entry(0).new_key_allowed());
+  XCTAssertEqual(config.entry(0).key_manager_version(), 0);
+
+  // Registration of standard key types works.
+  error = nil;
+  XCTAssertTrue([TINKConfig registerConfig:aeadConfig error:&error]);
+  XCTAssertNil(error);
+}
+
+@end
diff --git a/objc/Tests/UnitTests/daead/TINKDeterministicAeadFactoryTest.mm b/objc/Tests/UnitTests/daead/TINKDeterministicAeadFactoryTest.mm
new file mode 100644
index 0000000..2050392
--- /dev/null
+++ b/objc/Tests/UnitTests/daead/TINKDeterministicAeadFactoryTest.mm
@@ -0,0 +1,139 @@
+/**
+ * Copyright 2019 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ **************************************************************************
+ */
+
+#import "objc/TINKDeterministicAeadFactory.h"
+
+#import <XCTest/XCTest.h>
+
+#import "objc/TINKDeterministicAead.h"
+#import "objc/TINKDeterministicAeadConfig.h"
+#import "objc/TINKDeterministicAeadFactory.h"
+#import "objc/TINKKeysetHandle.h"
+#import "objc/core/TINKKeysetHandle_Internal.h"
+#import "objc/util/TINKStrings.h"
+
+#include "tink/crypto_format.h"
+#include "tink/daead/aes_siv_key_manager.h"
+#include "tink/daead/deterministic_aead_config.h"
+#include "tink/deterministic_aead.h"
+#include "tink/keyset_handle.h"
+#include "tink/util/keyset_util.h"
+#include "tink/util/status.h"
+#include "tink/util/test_util.h"
+#include "proto/aes_siv.pb.h"
+#include "proto/tink.pb.h"
+
+using crypto::tink::AesSivKeyManager;
+using crypto::tink::KeyFactory;
+using crypto::tink::KeysetUtil;
+using crypto::tink::test::AddRawKey;
+using crypto::tink::test::AddTinkKey;
+using google::crypto::tink::AesSivKeyFormat;
+using google::crypto::tink::KeyData;
+using google::crypto::tink::Keyset;
+using google::crypto::tink::KeyStatusType;
+
+@interface TINKDeterministicAeadFactoryTest : XCTestCase
+@end
+
+@implementation TINKDeterministicAeadFactoryTest
+
+- (void)testEmptyKeyset {
+  Keyset keyset;
+  TINKKeysetHandle *handle =
+      [[TINKKeysetHandle alloc] initWithCCKeysetHandle:KeysetUtil::GetKeysetHandle(keyset)];
+  XCTAssertNotNil(handle);
+
+  NSError *error = nil;
+  id<TINKDeterministicAead> aead = [TINKDeterministicAeadFactory primitiveWithKeysetHandle:handle
+                                                                                     error:&error];
+  XCTAssertNil(aead);
+  XCTAssertNotNil(error);
+  XCTAssertTrue(error.code == crypto::tink::util::error::INVALID_ARGUMENT);
+  XCTAssertTrue([error.localizedFailureReason containsString:@"at least one key"]);
+}
+
+- (void)testPrimitive {
+  // Prepare a template for generating keys for a Keyset.
+  AesSivKeyManager key_manager;
+  const KeyFactory &key_factory = key_manager.get_key_factory();
+  std::string key_type = key_manager.get_key_type();
+
+  AesSivKeyFormat key_format;
+  key_format.set_key_size(64);
+
+  // Prepare a Keyset.
+  Keyset keyset;
+  uint32_t key_id_1 = 1234543;
+  auto new_key = std::move(key_factory.NewKey(key_format).ValueOrDie());
+  AddTinkKey(key_type, key_id_1, *new_key, KeyStatusType::ENABLED, KeyData::SYMMETRIC, &keyset);
+
+  uint32_t key_id_2 = 726329;
+  new_key = std::move(key_factory.NewKey(key_format).ValueOrDie());
+  AddRawKey(key_type, key_id_2, *new_key, KeyStatusType::ENABLED, KeyData::SYMMETRIC, &keyset);
+
+  uint32_t key_id_3 = 7213743;
+  new_key = std::move(key_factory.NewKey(key_format).ValueOrDie());
+  AddTinkKey(key_type, key_id_3, *new_key, KeyStatusType::ENABLED, KeyData::SYMMETRIC, &keyset);
+
+  keyset.set_primary_key_id(key_id_3);
+
+  NSError *error = nil;
+  TINKDeterministicAeadConfig *aeadConfig =
+      [[TINKDeterministicAeadConfig alloc] initWithError:&error];
+  XCTAssertNotNil(aeadConfig);
+  XCTAssertNil(error);
+
+  TINKKeysetHandle *handle =
+      [[TINKKeysetHandle alloc] initWithCCKeysetHandle:KeysetUtil::GetKeysetHandle(keyset)];
+  XCTAssertNotNil(handle);
+
+  id<TINKDeterministicAead> aead = [TINKDeterministicAeadFactory primitiveWithKeysetHandle:handle
+                                                                                     error:&error];
+  XCTAssertNotNil(aead);
+  XCTAssertNil(error);
+
+  // Test the Aead primitive.
+  NSData *plaintext = [@"some_plaintext" dataUsingEncoding:NSUTF8StringEncoding];
+  NSData *aad = [@"some_aad" dataUsingEncoding:NSUTF8StringEncoding];
+  NSData *ciphertext = [aead encryptDeterministically:plaintext
+                                   withAssociatedData:aad
+                                                error:&error];
+  XCTAssertNil(error);
+  XCTAssertNotNil(ciphertext);
+
+  NSData *decrypted = [aead decryptDeterministically:ciphertext
+                                  withAssociatedData:aad
+                                               error:&error];
+  XCTAssertNil(error);
+  XCTAssertTrue([plaintext isEqual:decrypted]);
+
+  // Create raw ciphertext with 2nd key, and decrypt with Aead-instance.
+  auto raw_aead = std::move(key_manager.GetPrimitive(keyset.key(1).key_data()).ValueOrDie());
+  std::string raw_ciphertext = raw_aead
+                                   ->EncryptDeterministically(absl::string_view("some_plaintext"),
+                                                              absl::string_view("some_aad"))
+                                   .ValueOrDie();
+  ciphertext = TINKStringToNSData(raw_ciphertext);
+
+  decrypted = [aead decryptDeterministically:ciphertext withAssociatedData:aad error:&error];
+  XCTAssertNil(error);
+  XCTAssertTrue([plaintext isEqual:decrypted]);
+}
+
+@end
diff --git a/objc/Tests/UnitTests/daead/TINKDeterministicAeadKeyTemplateTest.mm b/objc/Tests/UnitTests/daead/TINKDeterministicAeadKeyTemplateTest.mm
new file mode 100644
index 0000000..07592b4
--- /dev/null
+++ b/objc/Tests/UnitTests/daead/TINKDeterministicAeadKeyTemplateTest.mm
@@ -0,0 +1,60 @@
+/**
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ **************************************************************************
+ */
+
+#import "objc/TINKDeterministicAeadKeyTemplate.h"
+
+#import <XCTest/XCTest.h>
+
+#import "objc/TINKKeyTemplate.h"
+#import "objc/core/TINKKeyTemplate_Internal.h"
+#import "objc/util/TINKProtoHelpers.h"
+#import "proto/AesSiv.pbobjc.h"
+#import "proto/Common.pbobjc.h"
+#import "proto/Tink.pbobjc.h"
+
+@interface TINKDeterministicAeadKeyTemplatesTest : XCTestCase
+@end
+
+@implementation TINKDeterministicAeadKeyTemplatesTest
+
+- (void)testAesSivKeyTemplates {
+  static NSString *const kTypeURL = @"type.googleapis.com/google.crypto.tink.AesSivKey";
+
+  NSError *error = nil;
+  // AES-256 SIV
+  TINKDeterministicAeadKeyTemplate *tpl =
+      [[TINKDeterministicAeadKeyTemplate alloc] initWithKeyTemplate:TINKAes256Siv error:&error];
+  XCTAssertNil(error);
+  XCTAssertNotNil(tpl);
+
+  error = nil;
+  TINKPBKeyTemplate *keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
+  XCTAssertNil(error);
+  XCTAssertNotNil(keyTemplate);
+
+  XCTAssertTrue([kTypeURL isEqualToString:keyTemplate.typeURL]);
+  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
+  error = nil;
+  TINKPBAesSivKeyFormat *keyFormat = [TINKPBAesSivKeyFormat parseFromData:keyTemplate.value
+                                                                    error:&error];
+  XCTAssertNil(error);
+  XCTAssertNotNil(keyFormat);
+  XCTAssertTrue(64 == keyFormat.keySize);
+}
+
+@end
diff --git a/objc/Tests/UnitTests/hybrid/TINKHybridConfigTest.mm b/objc/Tests/UnitTests/hybrid/TINKHybridConfigTest.mm
index 88b3cab..9b549fd 100644
--- a/objc/Tests/UnitTests/hybrid/TINKHybridConfigTest.mm
+++ b/objc/Tests/UnitTests/hybrid/TINKHybridConfigTest.mm
@@ -39,6 +39,8 @@
       "type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey";
   std::string aes_gcm_key_type = "type.googleapis.com/google.crypto.tink.AesGcmKey";
   std::string aes_eax_key_type = "type.googleapis.com/google.crypto.tink.AesEaxKey";
+  std::string xchacha20_poly1305_key_type =
+      "type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key";
   std::string hmac_key_type = "type.googleapis.com/google.crypto.tink.HmacKey";
 
   NSError *error = nil;
@@ -47,7 +49,7 @@
   XCTAssertNil(error);
 
   google::crypto::tink::RegistryConfig config = hybridConfig.ccConfig;
-  XCTAssertTrue(config.entry_size() == 6);
+  XCTAssertTrue(config.entry_size() == 7);
 
   XCTAssertTrue("TinkMac" == config.entry(0).catalogue_name());
   XCTAssertTrue("Mac" == config.entry(0).primitive_name());
@@ -73,18 +75,24 @@
   XCTAssertTrue(config.entry(3).new_key_allowed());
   XCTAssertTrue(0 == config.entry(3).key_manager_version());
 
-  XCTAssertTrue("TinkHybridDecrypt" == config.entry(4).catalogue_name());
-  XCTAssertTrue("HybridDecrypt" == config.entry(4).primitive_name());
-  XCTAssertTrue(decrypt_key_type == config.entry(4).type_url());
+  XCTAssertTrue("TinkAead" == config.entry(4).catalogue_name());
+  XCTAssertTrue("Aead" == config.entry(4).primitive_name());
+  XCTAssertTrue(xchacha20_poly1305_key_type == config.entry(4).type_url());
   XCTAssertTrue(config.entry(4).new_key_allowed());
   XCTAssertTrue(0 == config.entry(4).key_manager_version());
 
-  XCTAssertTrue("TinkHybridEncrypt" == config.entry(5).catalogue_name());
-  XCTAssertTrue("HybridEncrypt" == config.entry(5).primitive_name());
-  XCTAssertTrue(encrypt_key_type == config.entry(5).type_url());
+  XCTAssertTrue("TinkHybridDecrypt" == config.entry(5).catalogue_name());
+  XCTAssertTrue("HybridDecrypt" == config.entry(5).primitive_name());
+  XCTAssertTrue(decrypt_key_type == config.entry(5).type_url());
   XCTAssertTrue(config.entry(5).new_key_allowed());
   XCTAssertTrue(0 == config.entry(5).key_manager_version());
 
+  XCTAssertTrue("TinkHybridEncrypt" == config.entry(6).catalogue_name());
+  XCTAssertTrue("HybridEncrypt" == config.entry(6).primitive_name());
+  XCTAssertTrue(encrypt_key_type == config.entry(6).type_url());
+  XCTAssertTrue(config.entry(6).new_key_allowed());
+  XCTAssertTrue(0 == config.entry(6).key_manager_version());
+
   // Registration of standard key types works.
   error = nil;
   XCTAssertTrue([TINKConfig registerConfig:hybridConfig error:&error]);
diff --git a/objc/Tests/UnitTests/signature/TINKSignatureConfigTest.mm b/objc/Tests/UnitTests/signature/TINKSignatureConfigTest.mm
index 6e12f5c..c82bc98 100644
--- a/objc/Tests/UnitTests/signature/TINKSignatureConfigTest.mm
+++ b/objc/Tests/UnitTests/signature/TINKSignatureConfigTest.mm
@@ -33,17 +33,16 @@
 @implementation TINKSignatureConfigTest
 
 - (void)testConfigContents {
-  std::string sign_key_type = "type.googleapis.com/google.crypto.tink.EcdsaPrivateKey";
-  std::string verify_key_type = "type.googleapis.com/google.crypto.tink.EcdsaPublicKey";
-
   NSError *error = nil;
   TINKSignatureConfig *signatureConfig = [[TINKSignatureConfig alloc] initWithError:&error];
   XCTAssertNotNil(signatureConfig);
   XCTAssertNil(error);
 
   google::crypto::tink::RegistryConfig config = signatureConfig.ccConfig;
-  XCTAssertEqual(config.entry_size(), 2);
+  XCTAssertEqual(config.entry_size(), 8);
 
+  std::string sign_key_type = "type.googleapis.com/google.crypto.tink.EcdsaPrivateKey";
+  std::string verify_key_type = "type.googleapis.com/google.crypto.tink.EcdsaPublicKey";
   XCTAssertTrue("TinkPublicKeySign" == config.entry(0).catalogue_name());
   XCTAssertTrue("PublicKeySign" == config.entry(0).primitive_name());
   XCTAssertTrue(sign_key_type == config.entry(0).type_url());
@@ -56,6 +55,48 @@
   XCTAssertTrue(config.entry(1).new_key_allowed());
   XCTAssertEqual(config.entry(1).key_manager_version(), 0);
 
+  sign_key_type = "type.googleapis.com/google.crypto.tink.Ed25519PrivateKey";
+  verify_key_type = "type.googleapis.com/google.crypto.tink.Ed25519PublicKey";
+  XCTAssertTrue("TinkPublicKeySign" == config.entry(2).catalogue_name());
+  XCTAssertTrue("PublicKeySign" == config.entry(2).primitive_name());
+  XCTAssertTrue(sign_key_type == config.entry(2).type_url());
+  XCTAssertTrue(config.entry(2).new_key_allowed());
+  XCTAssertEqual(config.entry(2).key_manager_version(), 0);
+
+  XCTAssertTrue("TinkPublicKeyVerify" == config.entry(3).catalogue_name());
+  XCTAssertTrue("PublicKeyVerify" == config.entry(3).primitive_name());
+  XCTAssertTrue(verify_key_type == config.entry(3).type_url());
+  XCTAssertTrue(config.entry(3).new_key_allowed());
+  XCTAssertEqual(config.entry(3).key_manager_version(), 0);
+
+  sign_key_type = "type.googleapis.com/google.crypto.tink.RsaSsaPssPrivateKey";
+  verify_key_type = "type.googleapis.com/google.crypto.tink.RsaSsaPssPublicKey";
+  XCTAssertTrue("TinkPublicKeySign" == config.entry(4).catalogue_name());
+  XCTAssertTrue("PublicKeySign" == config.entry(4).primitive_name());
+  XCTAssertTrue(sign_key_type == config.entry(4).type_url());
+  XCTAssertTrue(config.entry(4).new_key_allowed());
+  XCTAssertEqual(config.entry(4).key_manager_version(), 0);
+
+  XCTAssertTrue("TinkPublicKeyVerify" == config.entry(5).catalogue_name());
+  XCTAssertTrue("PublicKeyVerify" == config.entry(5).primitive_name());
+  XCTAssertTrue(verify_key_type == config.entry(5).type_url());
+  XCTAssertTrue(config.entry(5).new_key_allowed());
+  XCTAssertEqual(config.entry(5).key_manager_version(), 0);
+
+  sign_key_type = "type.googleapis.com/google.crypto.tink.RsaSsaPkcs1PrivateKey";
+  verify_key_type = "type.googleapis.com/google.crypto.tink.RsaSsaPkcs1PublicKey";
+  XCTAssertTrue("TinkPublicKeySign" == config.entry(6).catalogue_name());
+  XCTAssertTrue("PublicKeySign" == config.entry(6).primitive_name());
+  XCTAssertTrue(sign_key_type == config.entry(6).type_url());
+  XCTAssertTrue(config.entry(6).new_key_allowed());
+  XCTAssertEqual(config.entry(6).key_manager_version(), 0);
+
+  XCTAssertTrue("TinkPublicKeyVerify" == config.entry(7).catalogue_name());
+  XCTAssertTrue("PublicKeyVerify" == config.entry(7).primitive_name());
+  XCTAssertTrue(verify_key_type == config.entry(7).type_url());
+  XCTAssertTrue(config.entry(7).new_key_allowed());
+  XCTAssertEqual(config.entry(7).key_manager_version(), 0);
+
   // Registration of standard key types works.
   error = nil;
   XCTAssertTrue([TINKConfig registerConfig:signatureConfig error:&error]);
diff --git a/objc/Tests/UnitTests/signature/TINKSignatureKeyTemplateTest.mm b/objc/Tests/UnitTests/signature/TINKSignatureKeyTemplateTest.mm
index 4ebf364..4716aa1 100644
--- a/objc/Tests/UnitTests/signature/TINKSignatureKeyTemplateTest.mm
+++ b/objc/Tests/UnitTests/signature/TINKSignatureKeyTemplateTest.mm
@@ -25,6 +25,9 @@
 #import "objc/util/TINKProtoHelpers.h"
 #import "proto/Common.pbobjc.h"
 #import "proto/Ecdsa.pbobjc.h"
+#import "proto/Empty.pbobjc.h"
+#import "proto/RsaSsaPkcs1.pbobjc.h"
+#import "proto/RsaSsaPss.pbobjc.h"
 #import "proto/Tink.pbobjc.h"
 
 #include "tink/util/status.h"
@@ -33,6 +36,12 @@
 @end
 
 static NSString *const kTypeURL = @"type.googleapis.com/google.crypto.tink.EcdsaPrivateKey";
+static NSString *const kTypeURLRsaPss =
+    @"type.googleapis.com/google.crypto.tink.RsaSsaPssPrivateKey";
+static NSString *const kTypeURLRsaPkcs1 =
+    @"type.googleapis.com/google.crypto.tink.RsaSsaPkcs1PrivateKey";
+static NSString *const kTypeURLEd25519 =
+    @"type.googleapis.com/google.crypto.tink.Ed25519PrivateKey";
 
 @implementation TINKSignatureKeyTemplatesTest
 
@@ -203,4 +212,133 @@
   XCTAssertTrue([error.localizedFailureReason containsString:@"Invalid TINKSignatureKeyTemplate"]);
 }
 
+- (void)testRsaSsaPkcs13072Sha256F4KeyTemplate {
+  NSError *error = nil;
+  TINKSignatureKeyTemplate *tpl =
+      [[TINKSignatureKeyTemplate alloc] initWithKeyTemplate:TINKRsaSsaPkcs13072Sha256F4
+                                                      error:&error];
+  XCTAssertNil(error);
+  XCTAssertNotNil(tpl);
+
+  error = nil;
+  TINKPBKeyTemplate *keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
+  XCTAssertNil(error);
+  XCTAssertNotNil(keyTemplate);
+
+  XCTAssertTrue([kTypeURLRsaPkcs1 isEqualToString:keyTemplate.typeURL]);
+  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
+
+  error = nil;
+  TINKPBRsaSsaPkcs1KeyFormat *keyFormat =
+      [TINKPBRsaSsaPkcs1KeyFormat parseFromData:keyTemplate.value error:&error];
+  XCTAssertNil(error);
+  XCTAssertNotNil(keyFormat);
+
+  XCTAssertEqual(keyFormat.params.hashType, TINKPBHashType_Sha256);
+  XCTAssertEqual(keyFormat.modulusSizeInBits, 3072);
+}
+
+- (void)testRsaSsaPkcs14096Sha512F4KeyTemplate {
+  NSError *error = nil;
+  TINKSignatureKeyTemplate *tpl =
+      [[TINKSignatureKeyTemplate alloc] initWithKeyTemplate:TINKRsaSsaPkcs14096Sha512F4
+                                                      error:&error];
+  XCTAssertNil(error);
+  XCTAssertNotNil(tpl);
+
+  error = nil;
+  TINKPBKeyTemplate *keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
+  XCTAssertNil(error);
+  XCTAssertNotNil(keyTemplate);
+
+  XCTAssertTrue([kTypeURLRsaPkcs1 isEqualToString:keyTemplate.typeURL]);
+  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
+
+  error = nil;
+  TINKPBRsaSsaPkcs1KeyFormat *keyFormat =
+      [TINKPBRsaSsaPkcs1KeyFormat parseFromData:keyTemplate.value error:&error];
+  XCTAssertNil(error);
+  XCTAssertNotNil(keyFormat);
+
+  XCTAssertEqual(keyFormat.params.hashType, TINKPBHashType_Sha512);
+  XCTAssertEqual(keyFormat.modulusSizeInBits, 4096);
+}
+
+- (void)testRsaSsaPss3072Sha256F4KeyTemplate {
+  NSError *error = nil;
+  TINKSignatureKeyTemplate *tpl =
+      [[TINKSignatureKeyTemplate alloc] initWithKeyTemplate:TINKRsaSsaPss3072Sha256Sha256F4
+                                                      error:&error];
+  XCTAssertNil(error);
+  XCTAssertNotNil(tpl);
+
+  error = nil;
+  TINKPBKeyTemplate *keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
+  XCTAssertNil(error);
+  XCTAssertNotNil(keyTemplate);
+
+  XCTAssertTrue([kTypeURLRsaPss isEqualToString:keyTemplate.typeURL]);
+  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
+
+  error = nil;
+  TINKPBRsaSsaPssKeyFormat *keyFormat = [TINKPBRsaSsaPssKeyFormat parseFromData:keyTemplate.value
+                                                                          error:&error];
+  XCTAssertNil(error);
+  XCTAssertNotNil(keyFormat);
+
+  XCTAssertEqual(keyFormat.params.sigHash, TINKPBHashType_Sha256);
+  XCTAssertEqual(keyFormat.params.mgf1Hash, TINKPBHashType_Sha256);
+  XCTAssertEqual(keyFormat.params.saltLength, 32);
+  XCTAssertEqual(keyFormat.modulusSizeInBits, 3072);
+}
+
+- (void)testRsaSsaPss4096Sha512F4KeyTemplate {
+  NSError *error = nil;
+  TINKSignatureKeyTemplate *tpl =
+      [[TINKSignatureKeyTemplate alloc] initWithKeyTemplate:TINKRsaSsaPss4096Sha512Sha512F4
+                                                      error:&error];
+  XCTAssertNil(error);
+  XCTAssertNotNil(tpl);
+
+  error = nil;
+  TINKPBKeyTemplate *keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
+  XCTAssertNil(error);
+  XCTAssertNotNil(keyTemplate);
+
+  XCTAssertTrue([kTypeURLRsaPss isEqualToString:keyTemplate.typeURL]);
+  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
+
+  error = nil;
+  TINKPBRsaSsaPssKeyFormat *keyFormat = [TINKPBRsaSsaPssKeyFormat parseFromData:keyTemplate.value
+                                                                          error:&error];
+  XCTAssertNil(error);
+  XCTAssertNotNil(keyFormat);
+
+  XCTAssertEqual(keyFormat.params.sigHash, TINKPBHashType_Sha512);
+  XCTAssertEqual(keyFormat.params.mgf1Hash, TINKPBHashType_Sha512);
+  XCTAssertEqual(keyFormat.params.saltLength, 64);
+  XCTAssertEqual(keyFormat.modulusSizeInBits, 4096);
+}
+
+- (void)testEd25519KeyTemplate {
+  NSError *error = nil;
+  TINKSignatureKeyTemplate *tpl = [[TINKSignatureKeyTemplate alloc] initWithKeyTemplate:TINKEd25519
+                                                                                  error:&error];
+  XCTAssertNil(error);
+  XCTAssertNotNil(tpl);
+
+  error = nil;
+  TINKPBKeyTemplate *keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
+  XCTAssertNil(error);
+  XCTAssertNotNil(keyTemplate);
+
+  XCTAssertTrue([kTypeURLEd25519 isEqualToString:keyTemplate.typeURL]);
+  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
+
+  error = nil;
+  TINKPBEmpty *keyFormat = [TINKPBEmpty parseFromData:keyTemplate.value error:&error];
+  XCTAssertNil(error);
+  XCTAssertNotNil(keyFormat);
+}
+
 @end
diff --git a/objc/aead/TINKAeadKeyTemplate.mm b/objc/aead/TINKAeadKeyTemplate.mm
index f761983..f2b6b1f 100644
--- a/objc/aead/TINKAeadKeyTemplate.mm
+++ b/objc/aead/TINKAeadKeyTemplate.mm
@@ -56,6 +56,10 @@
       ccKeyTemplate = const_cast<google::crypto::tink::KeyTemplate *>(
           &crypto::tink::AeadKeyTemplates::Aes256Eax());
       break;
+    case TINKXChaCha20Poly1305:
+      ccKeyTemplate = const_cast<google::crypto::tink::KeyTemplate *>(
+          &crypto::tink::AeadKeyTemplates::XChaCha20Poly1305());
+      break;
     default:
       if (error) {
         *error = TINKStatusToError(crypto::tink::util::Status(
diff --git a/objc/core/TINKKeysetHandle.mm b/objc/core/TINKKeysetHandle.mm
index 3cb82f0..354a16b 100644
--- a/objc/core/TINKKeysetHandle.mm
+++ b/objc/core/TINKKeysetHandle.mm
@@ -58,6 +58,27 @@
   _ccKeysetHandle.reset();
 }
 
+- (instancetype)initWithNoSecretKeyset:(NSData *)keyset error:(NSError **)error {
+  if (keyset == nil) {
+    if (error) {
+      *error = TINKStatusToError(crypto::tink::util::Status(
+          crypto::tink::util::error::INVALID_ARGUMENT, "keyset must be non-nil."));
+    }
+    return nil;
+  }
+
+  auto st = crypto::tink::KeysetHandle::ReadNoSecret(std::string(
+      reinterpret_cast<const char *>(keyset.bytes), static_cast<size_t>(keyset.length)));
+  if (!st.ok()) {
+    if (error) {
+      *error = TINKStatusToError(st.status());
+      return nil;
+    }
+  }
+
+  return [self initWithCCKeysetHandle:std::move(st.ValueOrDie())];
+}
+
 - (instancetype)initWithKeysetReader:(TINKKeysetReader *)reader
                               andKey:(id<TINKAead>)aeadKey
                                error:(NSError **)error {
diff --git a/objc/core/TINKVersion.m.templ b/objc/core/TINKVersion.m.templ
new file mode 100644
index 0000000..4a6488c
--- /dev/null
+++ b/objc/core/TINKVersion.m.templ
@@ -0,0 +1,21 @@
+/**
+ * Copyright 2018 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ **************************************************************************
+ */
+
+#import "objc/TINKVersion.h"
+
+NSString *const TINKVersion = @"TINK_VERSION_LABEL";
diff --git a/objc/daead/TINKDeterministicAeadConfig.mm b/objc/daead/TINKDeterministicAeadConfig.mm
new file mode 100644
index 0000000..1fb30e7
--- /dev/null
+++ b/objc/daead/TINKDeterministicAeadConfig.mm
@@ -0,0 +1,47 @@
+/**
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ **************************************************************************
+ */
+
+#import "objc/TINKDeterministicAeadConfig.h"
+
+#include "tink/daead/deterministic_aead_config.h"
+#include "tink/util/status.h"
+#include "proto/config.pb.h"
+
+#import "objc/TINKRegistryConfig.h"
+#import "objc/core/TINKRegistryConfig_Internal.h"
+#import "objc/util/TINKErrors.h"
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
+@implementation TINKDeterministicAeadConfig
+
+- (nullable instancetype)initWithError:(NSError **)error {
+  auto st = crypto::tink::DeterministicAeadConfig::Register();
+  if (!st.ok()) {
+    if (error) {
+      *error = TINKStatusToError(st);
+    }
+    return nil;
+  }
+
+  google::crypto::tink::RegistryConfig ccConfig = crypto::tink::DeterministicAeadConfig::Latest();
+  return (self = [super initWithCcConfig:ccConfig]);
+}
+
+@end
+#pragma clang diagnostic pop
diff --git a/objc/daead/TINKDeterministicAeadFactory.mm b/objc/daead/TINKDeterministicAeadFactory.mm
new file mode 100644
index 0000000..82423bf
--- /dev/null
+++ b/objc/daead/TINKDeterministicAeadFactory.mm
@@ -0,0 +1,60 @@
+/**
+ * Copyright 2019 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ **************************************************************************
+ */
+
+#import "objc/TINKDeterministicAeadFactory.h"
+
+#import <Foundation/Foundation.h>
+
+#include "tink/daead/deterministic_aead_factory.h"
+#include "tink/keyset_handle.h"
+#include "tink/util/status.h"
+
+#import "objc/TINKDeterministicAead.h"
+#import "objc/TINKKeysetHandle.h"
+#import "objc/core/TINKKeysetHandle_Internal.h"
+#import "objc/daead/TINKDeterministicAeadInternal.h"
+#import "objc/util/TINKErrors.h"
+
+@implementation TINKDeterministicAeadFactory
+
++ (id<TINKDeterministicAead>)primitiveWithKeysetHandle:(TINKKeysetHandle *)keysetHandle
+                                                 error:(NSError **)error {
+  crypto::tink::KeysetHandle *handle = [keysetHandle ccKeysetHandle];
+  auto st = handle->GetPrimitive<crypto::tink::DeterministicAead>();
+
+  if (!st.ok()) {
+    if (error) {
+      *error = TINKStatusToError(st.status());
+    }
+    return nil;
+  }
+  id<TINKDeterministicAead> aead = [[TINKDeterministicAeadInternal alloc]
+      initWithCCDeterministicAead:std::move(st.ValueOrDie())];
+  if (!aead) {
+    if (error) {
+      *error = TINKStatusToError(
+          crypto::tink::util::Status(crypto::tink::util::error::RESOURCE_EXHAUSTED,
+                                     "Cannot initialize TINKDeterministicAead"));
+    }
+    return nil;
+  }
+
+  return aead;
+}
+
+@end
diff --git a/objc/daead/TINKDeterministicAeadInternal.h b/objc/daead/TINKDeterministicAeadInternal.h
new file mode 100644
index 0000000..e9d65ca
--- /dev/null
+++ b/objc/daead/TINKDeterministicAeadInternal.h
@@ -0,0 +1,42 @@
+/**
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ **************************************************************************
+ */
+
+#import "objc/TINKDeterministicAead.h"
+
+#import <Foundation/Foundation.h>
+
+#include "tink/deterministic_aead.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * This interface is internal-only. Use initWithKeysetHandle:error: to get an instance of
+ * TINKDeterministicAead.
+ */
+@interface TINKDeterministicAeadInternal : NSObject <TINKDeterministicAead>
+
+- (nullable instancetype)init NS_UNAVAILABLE;
+
+- (nullable instancetype)initWithCCDeterministicAead:
+    (std::unique_ptr<crypto::tink::DeterministicAead>)ccDeterministicAead NS_DESIGNATED_INITIALIZER;
+
+- (nullable crypto::tink::DeterministicAead *)ccDeterministicAead;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/objc/daead/TINKDeterministicAeadInternal.mm b/objc/daead/TINKDeterministicAeadInternal.mm
new file mode 100644
index 0000000..85670d0
--- /dev/null
+++ b/objc/daead/TINKDeterministicAeadInternal.mm
@@ -0,0 +1,100 @@
+/**
+ * Copyright 2019 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ **************************************************************************
+ */
+
+#import "objc/daead/TINKDeterministicAeadInternal.h"
+#import "objc/TINKDeterministicAead.h"
+
+#include "absl/strings/string_view.h"
+#include "tink/deterministic_aead.h"
+#include "tink/keyset_handle.h"
+#include "tink/util/status.h"
+
+#import "objc/TINKKeysetHandle.h"
+#import "objc/core/TINKKeysetHandle_Internal.h"
+#import "objc/util/TINKErrors.h"
+#import "objc/util/TINKStrings.h"
+
+@implementation TINKDeterministicAeadInternal {
+  std::unique_ptr<crypto::tink::DeterministicAead> _ccDeterministicAead;
+}
+
+- (instancetype)initWithCCDeterministicAead:
+    (std::unique_ptr<crypto::tink::DeterministicAead>)ccDeterministicAead {
+  self = [super init];
+  if (self) {
+    _ccDeterministicAead = std::move(ccDeterministicAead);
+  }
+  return self;
+}
+
+- (void)dealloc {
+  _ccDeterministicAead.reset();
+}
+
+- (NSData *)encryptDeterministically:(NSData *)plaintext
+                  withAssociatedData:(NSData *)additionalData
+                               error:(NSError **)error {
+  absl::string_view ccAdditionalData;
+  if (additionalData && additionalData.length > 0) {
+    ccAdditionalData =
+        absl::string_view(static_cast<const char *>(additionalData.bytes), additionalData.length);
+  }
+
+  auto st = _ccDeterministicAead->EncryptDeterministically(
+      absl::string_view(static_cast<const char *>(plaintext.bytes), plaintext.length),
+      ccAdditionalData);
+  if (!st.ok()) {
+    if (error) {
+      *error = TINKStatusToError(st.status());
+    }
+    return nil;
+  }
+
+  return TINKStringToNSData(st.ValueOrDie());
+}
+
+- (NSData *)decryptDeterministically:(NSData *)ciphertext
+                  withAssociatedData:(NSData *)additionalData
+                               error:(NSError **)error {
+  absl::string_view ccAdditionalData;
+  if (additionalData && additionalData.length > 0) {
+    ccAdditionalData =
+        absl::string_view(static_cast<const char *>(additionalData.bytes), additionalData.length);
+  }
+
+  auto st = _ccDeterministicAead->DecryptDeterministically(
+      absl::string_view(static_cast<const char *>(ciphertext.bytes), ciphertext.length),
+      ccAdditionalData);
+  if (!st.ok()) {
+    if (error) {
+      *error = TINKStatusToError(st.status());
+    }
+    return nil;
+  }
+
+  return TINKStringToNSData(st.ValueOrDie());
+}
+
+- (nullable crypto::tink::DeterministicAead *)ccDeterministicAead {
+  if (!_ccDeterministicAead) {
+    return nil;
+  }
+  return _ccDeterministicAead.get();
+}
+
+@end
diff --git a/objc/daead/TINKDeterministicAeadKeyTemplate.mm b/objc/daead/TINKDeterministicAeadKeyTemplate.mm
new file mode 100644
index 0000000..d802801
--- /dev/null
+++ b/objc/daead/TINKDeterministicAeadKeyTemplate.mm
@@ -0,0 +1,50 @@
+/**
+ * Copyright 2018 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ **************************************************************************
+ */
+
+#import "objc/TINKDeterministicAeadKeyTemplate.h"
+
+#include "tink/daead/deterministic_aead_key_templates.h"
+#include "tink/util/status.h"
+#include "proto/tink.pb.h"
+
+#import "objc/TINKKeyTemplate.h"
+#import "objc/core/TINKKeyTemplate_Internal.h"
+#import "objc/util/TINKErrors.h"
+
+@implementation TINKDeterministicAeadKeyTemplate
+
+- (nullable instancetype)initWithKeyTemplate:(TINKDeterministicAeadKeyTemplates)keyTemplate
+                                       error:(NSError **)error {
+  google::crypto::tink::KeyTemplate *ccKeyTemplate = NULL;
+  switch (keyTemplate) {
+    case TINKAes256Siv:
+      ccKeyTemplate = const_cast<google::crypto::tink::KeyTemplate *>(
+          &crypto::tink::DeterministicAeadKeyTemplates::Aes256Siv());
+      break;
+    default:
+      if (error) {
+        *error = TINKStatusToError(
+            crypto::tink::util::Status(crypto::tink::util::error::INVALID_ARGUMENT,
+                                       "Invalid TINKDeterministicAeadKeyTemplate"));
+      }
+      return nil;
+  }
+  return (self = [super initWithCcKeyTemplate:ccKeyTemplate]);
+}
+
+@end
diff --git a/objc/signature/TINKSignatureKeyTemplate.mm b/objc/signature/TINKSignatureKeyTemplate.mm
index b8cd00f..d97c861 100644
--- a/objc/signature/TINKSignatureKeyTemplate.mm
+++ b/objc/signature/TINKSignatureKeyTemplate.mm
@@ -56,6 +56,26 @@
       ccKeyTemplate = const_cast<google::crypto::tink::KeyTemplate *>(
           &crypto::tink::SignatureKeyTemplates::EcdsaP521Ieee());
       break;
+    case TINKRsaSsaPkcs13072Sha256F4:
+      ccKeyTemplate = const_cast<google::crypto::tink::KeyTemplate *>(
+          &crypto::tink::SignatureKeyTemplates::RsaSsaPkcs13072Sha256F4());
+      break;
+    case TINKRsaSsaPkcs14096Sha512F4:
+      ccKeyTemplate = const_cast<google::crypto::tink::KeyTemplate *>(
+          &crypto::tink::SignatureKeyTemplates::RsaSsaPkcs14096Sha512F4());
+      break;
+    case TINKRsaSsaPss3072Sha256Sha256F4:
+      ccKeyTemplate = const_cast<google::crypto::tink::KeyTemplate *>(
+          &crypto::tink::SignatureKeyTemplates::RsaSsaPss3072Sha256Sha256F4());
+      break;
+    case TINKRsaSsaPss4096Sha512Sha512F4:
+      ccKeyTemplate = const_cast<google::crypto::tink::KeyTemplate *>(
+          &crypto::tink::SignatureKeyTemplates::RsaSsaPss4096Sha512Sha512F4());
+      break;
+    case TINKEd25519:
+      ccKeyTemplate = const_cast<google::crypto::tink::KeyTemplate *>(
+          &crypto::tink::SignatureKeyTemplates::Ed25519());
+      break;
     default:
       if (error) {
         *error = TINKStatusToError(crypto::tink::util::Status(
diff --git a/proto/BUILD.bazel b/proto/BUILD.bazel
index f3785d9..c227b96 100644
--- a/proto/BUILD.bazel
+++ b/proto/BUILD.bazel
@@ -158,7 +158,7 @@
 
 go_proto_library(
     name = "aes_siv_go_proto",
-    importpath = "github.com/google/tink/proto/aes_siv_proto",
+    importpath = "github.com/google/tink/proto/aes_siv_go_proto",
     proto = ":aes_siv_proto",
 )
 
@@ -321,7 +321,7 @@
 
 go_proto_library(
     name = "ed25519_go_proto",
-    importpath = "github.com/google/tink/proto/ed25519_proto",
+    importpath = "github.com/google/tink/proto/ed25519_go_proto",
     proto = ":ed25519_proto",
 )
 
@@ -398,7 +398,7 @@
 
 go_proto_library(
     name = "aes_ctr_go_proto",
-    importpath = "github.com/google/tink/proto/aes_ctr_proto",
+    importpath = "github.com/google/tink/proto/aes_ctr_go_proto",
     proto = ":aes_ctr_proto",
 )
 
@@ -439,7 +439,7 @@
 
 go_proto_library(
     name = "aes_ctr_hmac_aead_go_proto",
-    importpath = "github.com/google/tink/proto/aes_ctr_hmac_aead_proto",
+    importpath = "github.com/google/tink/proto/aes_ctr_hmac_aead_go_proto",
     proto = ":aes_ctr_hmac_aead_proto",
     deps = [
         ":aes_ctr_go_proto",
@@ -645,7 +645,7 @@
 
 go_proto_library(
     name = "chacha20_poly1305_go_proto",
-    importpath = "github.com/google/tink/proto/chacha20_poly1305_proto",
+    importpath = "github.com/google/tink/proto/chacha20_poly1305_go_proto",
     proto = ":chacha20_poly1305_proto",
 )
 
@@ -682,7 +682,7 @@
 
 go_proto_library(
     name = "kms_aead_go_proto",
-    importpath = "github.com/google/tink/proto/kms_aead_proto",
+    importpath = "github.com/google/tink/proto/kms_aead_go_proto",
     proto = ":kms_aead_proto",
 )
 
@@ -720,7 +720,7 @@
 
 go_proto_library(
     name = "kms_envelope_go_proto",
-    importpath = "github.com/google/tink/proto/kms_envelope_proto",
+    importpath = "github.com/google/tink/proto/kms_envelope_go_proto",
     proto = ":kms_envelope_proto",
     deps = [":tink_go_proto"],
 )
@@ -765,7 +765,7 @@
 
 go_proto_library(
     name = "ecies_aead_hkdf_go_proto",
-    importpath = "github.com/google/tink/proto/ecies_aead_hkdf_proto",
+    importpath = "github.com/google/tink/proto/ecies_aead_hkdf_go_proto",
     proto = ":ecies_aead_hkdf_proto",
     deps = [
         ":common_go_proto",
@@ -784,6 +784,80 @@
 )
 
 # -----------------------------------------------
+# XChacha20 with Poly1305
+# -----------------------------------------------
+proto_library(
+    name = "xchacha20_poly1305_proto",
+    srcs = [
+        "xchacha20_poly1305.proto",
+    ],
+)
+
+cc_proto_library(
+    name = "xchacha20_poly1305_cc_proto",
+    deps = [":xchacha20_poly1305_proto"],
+)
+
+java_proto_library(
+    name = "xchacha20_poly1305_java_proto",
+    deps = [":xchacha20_poly1305_proto"],
+)
+
+java_lite_proto_library(
+    name = "xchacha20_poly1305_java_proto_lite",
+    deps = [":xchacha20_poly1305_proto"],
+)
+
+go_proto_library(
+    name = "xchacha20_poly1305_go_proto",
+    importpath = "github.com/google/tink/proto/xchacha20_poly1305_go_proto",
+    proto = ":xchacha20_poly1305_proto",
+)
+
+objc_proto_compile(
+    name = "xchacha20_poly1305_objc_pb",
+    protos = ["xchacha20_poly1305.proto"],
+    tags = ["manual"],
+)
+
+# -----------------------------------------------
+# empty
+# -----------------------------------------------
+proto_library(
+    name = "empty_proto",
+    srcs = [
+        "empty.proto",
+    ],
+)
+
+cc_proto_library(
+    name = "empty_cc_proto",
+    deps = [":empty_proto"],
+)
+
+java_proto_library(
+    name = "empty_java_proto",
+    deps = [":empty_proto"],
+)
+
+java_lite_proto_library(
+    name = "empty_java_proto_lite",
+    deps = [":empty_proto"],
+)
+
+go_proto_library(
+    name = "empty_go_proto",
+    importpath = "github.com/google/tink/proto/empty_go_proto",
+    proto = ":empty_proto",
+)
+
+objc_proto_compile(
+    name = "empty_objc_pb",
+    protos = ["empty.proto"],
+    tags = ["manual"],
+)
+
+# -----------------------------------------------
 # objc library
 # -----------------------------------------------
 tink_objc_proto_library(
@@ -795,16 +869,21 @@
         ":aes_eax_objc_pb",
         ":aes_gcm_hkdf_streaming_objc_pb",
         ":aes_gcm_objc_pb",
+        ":aes_siv_objc_pb",
         ":chacha20_poly1305_objc_pb",
         ":common_objc_pb",
         ":config_objc_pb",
         ":ecdsa_objc_pb",
         ":ecies_aead_hkdf_objc_pb",
         ":ed25519_objc_pb",
+        ":empty_objc_pb",
         ":hmac_objc_pb",
         ":kms_aead_objc_pb",
         ":kms_envelope_objc_pb",
+        ":rsa_ssa_pkcs1_objc_pb",
+        ":rsa_ssa_pss_objc_pb",
         ":tink_objc_pb",
+        ":xchacha20_poly1305_objc_pb",
     ],
     tags = ["manual"],
 )
diff --git a/proto/CMakeLists.txt b/proto/CMakeLists.txt
index 2c1cb62..68cabdd 100644
--- a/proto/CMakeLists.txt
+++ b/proto/CMakeLists.txt
@@ -1,31 +1,115 @@
-macro(common_protobuf_cpp_lib PROTO)
-  set(_lib_name "tink_proto_${PROTO}_lib")
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
-  string(TOUPPER "${PROTO}" _proto_upper)
-  set(_hdrs_out "TINK_${_proto_upper}_PROTO_HDRS")
 
-  tink_make_protobuf_cpp_lib(${_lib_name} ${_hdrs_out} ${PROTO})
-  foreach(lib ${ARGN})
-    target_link_libraries(${_lib_name} tink_proto_${lib}_lib)
-  endforeach()
-endmacro()
+# Proto Library : common_proto
+tink_make_protobuf_cpp_lib(
+  tink_proto_common_lib
+  TINK_PROTO_COMMON_LIB_PROTO_HDRS
+  common
+)
 
-common_protobuf_cpp_lib(common)
-common_protobuf_cpp_lib(tink common)
-common_protobuf_cpp_lib(config)
-common_protobuf_cpp_lib(aes_siv)
-common_protobuf_cpp_lib(rsa_ssa_pkcs1 common)
-common_protobuf_cpp_lib(rsa_ssa_pss common)
-common_protobuf_cpp_lib(ecdsa common)
-common_protobuf_cpp_lib(ed25519)
-common_protobuf_cpp_lib(hmac common)
-common_protobuf_cpp_lib(aes_ctr)
-common_protobuf_cpp_lib(aes_ctr_hmac_aead aes_ctr hmac)
-common_protobuf_cpp_lib(aes_gcm)
-common_protobuf_cpp_lib(aes_ctr_hmac_streaming common hmac)
-common_protobuf_cpp_lib(aes_gcm_hkdf_streaming common)
-common_protobuf_cpp_lib(aes_eax)
-common_protobuf_cpp_lib(chacha20_poly1305)
-common_protobuf_cpp_lib(kms_aead)
-common_protobuf_cpp_lib(kms_envelope tink)
-common_protobuf_cpp_lib(ecies_aead_hkdf common tink)
+# Proto Library : tink_proto
+tink_make_protobuf_cpp_lib(
+  tink_proto_tink_lib
+  TINK_PROTO_TINK_LIB_PROTO_HDRS
+  tink
+)
+add_dependencies(tink_proto_tink_lib tink_proto_common_lib)
+target_link_libraries(tink_proto_tink_lib tink_proto_common_lib)
+
+# Proto Library : config_proto
+tink_make_protobuf_cpp_lib(
+  tink_proto_config_lib
+  TINK_PROTO_CONFIG_LIB_PROTO_HDRS
+  config
+)
+
+# Proto Library : ecdsa_proto
+tink_make_protobuf_cpp_lib(
+  tink_proto_ecdsa_lib
+  TINK_PROTO_ECDSA_LIB_PROTO_HDRS
+  ecdsa
+)
+add_dependencies(tink_proto_ecdsa_lib tink_proto_common_lib)
+target_link_libraries(tink_proto_ecdsa_lib tink_proto_common_lib)
+
+# Proto Library : hmac_proto
+tink_make_protobuf_cpp_lib(
+  tink_proto_hmac_lib
+  TINK_PROTO_HMAC_LIB_PROTO_HDRS
+  hmac
+)
+add_dependencies(tink_proto_hmac_lib tink_proto_common_lib)
+target_link_libraries(tink_proto_hmac_lib tink_proto_common_lib)
+
+# Proto Library : aes_ctr_proto
+tink_make_protobuf_cpp_lib(
+  tink_proto_aes_ctr_lib
+  TINK_PROTO_AES_CTR_LIB_PROTO_HDRS
+  aes_ctr
+)
+
+# Proto Library : aes_ctr_hmac_aead_proto
+tink_make_protobuf_cpp_lib(
+  tink_proto_aes_ctr_hmac_aead_lib
+  TINK_PROTO_AES_CTR_HMAC_AEAD_LIB_PROTO_HDRS
+  aes_ctr_hmac_aead
+)
+add_dependencies(
+  tink_proto_aes_ctr_hmac_aead_lib
+  tink_proto_aes_ctr_lib
+  tink_proto_hmac_lib
+)
+target_link_libraries(
+  tink_proto_aes_ctr_hmac_aead_lib
+  tink_proto_aes_ctr_lib
+  tink_proto_hmac_lib
+)
+
+# Proto Library : aes_gcm_proto
+tink_make_protobuf_cpp_lib(
+  tink_proto_aes_gcm_lib
+  TINK_PROTO_AES_GCM_LIB_PROTO_HDRS
+  aes_gcm
+)
+
+# Proto Library : aes_eax_proto
+tink_make_protobuf_cpp_lib(
+  tink_proto_aes_eax_lib
+  TINK_PROTO_AES_EAX_LIB_PROTO_HDRS
+  aes_eax
+)
+
+# Proto Library : ecies_aead_hkdf_proto
+tink_make_protobuf_cpp_lib(
+  tink_proto_ecies_aead_hkdf_lib
+  TINK_PROTO_ECIES_AEAD_HKDF_LIB_PROTO_HDRS
+  ecies_aead_hkdf
+)
+add_dependencies(
+  tink_proto_ecies_aead_hkdf_lib
+  tink_proto_common_lib
+  tink_proto_tink_lib
+)
+target_link_libraries(
+  tink_proto_ecies_aead_hkdf_lib
+  tink_proto_common_lib
+  tink_proto_tink_lib
+)
+
+# Proto Library : xchacha20_poly1305_proto
+tink_make_protobuf_cpp_lib(
+  tink_proto_xchacha20_poly1305_lib
+  TINK_PROTO_XCHACHA20_POLY1305_LIB_PROTO_HDRS
+  xchacha20_poly1305
+)
+
+# Proto Library : empty_proto
+tink_make_protobuf_cpp_lib(
+  tink_proto_empty_lib
+  TINK_PROTO_EMPTY_LIB_PROTO_HDRS
+  empty
+)
+
diff --git a/proto/README.md b/proto/README.md
new file mode 100644
index 0000000..17b6d08
--- /dev/null
+++ b/proto/README.md
@@ -0,0 +1,14 @@
+This folder contains protobuf definitions.
+
+The subfolders (e.g., aes_gcm_go_proto) contain Go auto-generated code of the
+protobuf definitions. These files facilitate using `go get` to install the Tink
+library. This is only applicable outside of google3. To update them, execute the
+following script:
+
+```shell
+$ g4d tink
+$ ./third_party/tink/tools/gen_pb_go.sh
+```
+
+The script performs a Blaze query for the current Go proto dependencies,
+generates the required files.
diff --git a/proto/aes_ctr_go_proto/aes_ctr.pb.go b/proto/aes_ctr_go_proto/aes_ctr.pb.go
new file mode 100644
index 0000000..30518d7
--- /dev/null
+++ b/proto/aes_ctr_go_proto/aes_ctr.pb.go
@@ -0,0 +1,210 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: third_party/tink/proto/aes_ctr.proto
+
+package aes_ctr_go_proto
+
+import (
+	fmt "fmt"
+	proto "github.com/golang/protobuf/proto"
+	math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+
+type AesCtrParams struct {
+	IvSize               uint32   `protobuf:"varint,1,opt,name=iv_size,json=ivSize,proto3" json:"iv_size,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *AesCtrParams) Reset()         { *m = AesCtrParams{} }
+func (m *AesCtrParams) String() string { return proto.CompactTextString(m) }
+func (*AesCtrParams) ProtoMessage()    {}
+func (*AesCtrParams) Descriptor() ([]byte, []int) {
+	return fileDescriptor_c4338643e2d19ef2, []int{0}
+}
+
+func (m *AesCtrParams) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_AesCtrParams.Unmarshal(m, b)
+}
+func (m *AesCtrParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_AesCtrParams.Marshal(b, m, deterministic)
+}
+func (m *AesCtrParams) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_AesCtrParams.Merge(m, src)
+}
+func (m *AesCtrParams) XXX_Size() int {
+	return xxx_messageInfo_AesCtrParams.Size(m)
+}
+func (m *AesCtrParams) XXX_DiscardUnknown() {
+	xxx_messageInfo_AesCtrParams.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_AesCtrParams proto.InternalMessageInfo
+
+func (m *AesCtrParams) GetIvSize() uint32 {
+	if m != nil {
+		return m.IvSize
+	}
+	return 0
+}
+
+type AesCtrKeyFormat struct {
+	Params               *AesCtrParams `protobuf:"bytes,1,opt,name=params,proto3" json:"params,omitempty"`
+	KeySize              uint32        `protobuf:"varint,2,opt,name=key_size,json=keySize,proto3" json:"key_size,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}      `json:"-"`
+	XXX_unrecognized     []byte        `json:"-"`
+	XXX_sizecache        int32         `json:"-"`
+}
+
+func (m *AesCtrKeyFormat) Reset()         { *m = AesCtrKeyFormat{} }
+func (m *AesCtrKeyFormat) String() string { return proto.CompactTextString(m) }
+func (*AesCtrKeyFormat) ProtoMessage()    {}
+func (*AesCtrKeyFormat) Descriptor() ([]byte, []int) {
+	return fileDescriptor_c4338643e2d19ef2, []int{1}
+}
+
+func (m *AesCtrKeyFormat) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_AesCtrKeyFormat.Unmarshal(m, b)
+}
+func (m *AesCtrKeyFormat) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_AesCtrKeyFormat.Marshal(b, m, deterministic)
+}
+func (m *AesCtrKeyFormat) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_AesCtrKeyFormat.Merge(m, src)
+}
+func (m *AesCtrKeyFormat) XXX_Size() int {
+	return xxx_messageInfo_AesCtrKeyFormat.Size(m)
+}
+func (m *AesCtrKeyFormat) XXX_DiscardUnknown() {
+	xxx_messageInfo_AesCtrKeyFormat.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_AesCtrKeyFormat proto.InternalMessageInfo
+
+func (m *AesCtrKeyFormat) GetParams() *AesCtrParams {
+	if m != nil {
+		return m.Params
+	}
+	return nil
+}
+
+func (m *AesCtrKeyFormat) GetKeySize() uint32 {
+	if m != nil {
+		return m.KeySize
+	}
+	return 0
+}
+
+// key_type: type.googleapis.com/google.crypto.tink.AesCtrKey
+type AesCtrKey struct {
+	Version              uint32        `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
+	Params               *AesCtrParams `protobuf:"bytes,2,opt,name=params,proto3" json:"params,omitempty"`
+	KeyValue             []byte        `protobuf:"bytes,3,opt,name=key_value,json=keyValue,proto3" json:"key_value,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}      `json:"-"`
+	XXX_unrecognized     []byte        `json:"-"`
+	XXX_sizecache        int32         `json:"-"`
+}
+
+func (m *AesCtrKey) Reset()         { *m = AesCtrKey{} }
+func (m *AesCtrKey) String() string { return proto.CompactTextString(m) }
+func (*AesCtrKey) ProtoMessage()    {}
+func (*AesCtrKey) Descriptor() ([]byte, []int) {
+	return fileDescriptor_c4338643e2d19ef2, []int{2}
+}
+
+func (m *AesCtrKey) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_AesCtrKey.Unmarshal(m, b)
+}
+func (m *AesCtrKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_AesCtrKey.Marshal(b, m, deterministic)
+}
+func (m *AesCtrKey) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_AesCtrKey.Merge(m, src)
+}
+func (m *AesCtrKey) XXX_Size() int {
+	return xxx_messageInfo_AesCtrKey.Size(m)
+}
+func (m *AesCtrKey) XXX_DiscardUnknown() {
+	xxx_messageInfo_AesCtrKey.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_AesCtrKey proto.InternalMessageInfo
+
+func (m *AesCtrKey) GetVersion() uint32 {
+	if m != nil {
+		return m.Version
+	}
+	return 0
+}
+
+func (m *AesCtrKey) GetParams() *AesCtrParams {
+	if m != nil {
+		return m.Params
+	}
+	return nil
+}
+
+func (m *AesCtrKey) GetKeyValue() []byte {
+	if m != nil {
+		return m.KeyValue
+	}
+	return nil
+}
+
+func init() {
+	proto.RegisterType((*AesCtrParams)(nil), "google.crypto.tink.AesCtrParams")
+	proto.RegisterType((*AesCtrKeyFormat)(nil), "google.crypto.tink.AesCtrKeyFormat")
+	proto.RegisterType((*AesCtrKey)(nil), "google.crypto.tink.AesCtrKey")
+}
+
+func init() {
+	proto.RegisterFile("proto/aes_ctr.proto", fileDescriptor_c4338643e2d19ef2)
+}
+
+var fileDescriptor_c4338643e2d19ef2 = []byte{
+	// 268 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x90, 0x41, 0x4b, 0xc3, 0x30,
+	0x14, 0x80, 0x69, 0x85, 0xd6, 0x3d, 0x27, 0x42, 0x2e, 0x56, 0xf4, 0x50, 0x86, 0xe0, 0x2e, 0xa6,
+	0xa0, 0x17, 0xaf, 0x4e, 0x10, 0x64, 0x20, 0xa5, 0x8a, 0x88, 0x97, 0x92, 0xd5, 0x67, 0x17, 0xba,
+	0xee, 0x95, 0x24, 0x2b, 0x64, 0xf8, 0x6b, 0xfc, 0xa5, 0xd2, 0x74, 0xca, 0xc0, 0x5d, 0x3c, 0x7e,
+	0xe1, 0xcb, 0xfb, 0x1e, 0x0f, 0xce, 0xcd, 0x5c, 0xaa, 0xf7, 0xbc, 0x11, 0xca, 0xd8, 0xc4, 0xc8,
+	0x65, 0x95, 0x34, 0x8a, 0x0c, 0x25, 0x02, 0x75, 0x5e, 0x18, 0xc5, 0x1d, 0x31, 0x56, 0x12, 0x95,
+	0x0b, 0xe4, 0x85, 0xb2, 0x8d, 0x21, 0xde, 0x79, 0xa3, 0x0b, 0x18, 0xde, 0xa2, 0xbe, 0x33, 0x2a,
+	0x15, 0x4a, 0xd4, 0x9a, 0x1d, 0x43, 0x28, 0xdb, 0x5c, 0xcb, 0x35, 0x46, 0x5e, 0xec, 0x8d, 0x0f,
+	0xb3, 0x40, 0xb6, 0x4f, 0x72, 0x8d, 0xa3, 0x0f, 0x38, 0xea, 0xc5, 0x29, 0xda, 0x7b, 0x52, 0xb5,
+	0x30, 0xec, 0x06, 0x82, 0xc6, 0xfd, 0x72, 0xea, 0xc1, 0x55, 0xcc, 0xff, 0x06, 0xf8, 0xf6, 0xf4,
+	0x6c, 0xe3, 0xb3, 0x13, 0xd8, 0xaf, 0xd0, 0xf6, 0x19, 0xdf, 0x65, 0xc2, 0x0a, 0xad, 0xeb, 0x7c,
+	0xc2, 0xe0, 0xb7, 0xc3, 0x22, 0x08, 0x5b, 0x54, 0x5a, 0xd2, 0x72, 0xb3, 0xcd, 0x0f, 0x6e, 0xb5,
+	0xfd, 0x7f, 0xb6, 0x4f, 0x61, 0xd0, 0xb5, 0x5b, 0xb1, 0x58, 0x61, 0xb4, 0x17, 0x7b, 0xe3, 0x61,
+	0xd6, 0x2d, 0xf3, 0xd2, 0xf1, 0xe4, 0x15, 0xce, 0x0a, 0xaa, 0x77, 0xcd, 0x72, 0x27, 0x4c, 0xbd,
+	0xb7, 0xcb, 0x52, 0x9a, 0xf9, 0x6a, 0xc6, 0x0b, 0xaa, 0x93, 0x5e, 0xdb, 0x71, 0xf0, 0xbc, 0xa4,
+	0xdc, 0x3d, 0x7c, 0xf9, 0xc1, 0xf3, 0xc3, 0xe3, 0x34, 0x9d, 0xcc, 0x02, 0xc7, 0xd7, 0xdf, 0x01,
+	0x00, 0x00, 0xff, 0xff, 0x94, 0xe7, 0xc9, 0x0e, 0xab, 0x01, 0x00, 0x00,
+}
diff --git a/proto/aes_ctr_hmac_aead_go_proto/aes_ctr_hmac_aead.pb.go b/proto/aes_ctr_hmac_aead_go_proto/aes_ctr_hmac_aead.pb.go
new file mode 100644
index 0000000..a8c74e7
--- /dev/null
+++ b/proto/aes_ctr_hmac_aead_go_proto/aes_ctr_hmac_aead.pb.go
@@ -0,0 +1,174 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: third_party/tink/proto/aes_ctr_hmac_aead.proto
+
+package aes_ctr_hmac_aead_go_proto
+
+import (
+	fmt "fmt"
+	proto "github.com/golang/protobuf/proto"
+	aes_ctr_go_proto "github.com/google/tink/proto/aes_ctr_go_proto"
+	hmac_go_proto "github.com/google/tink/proto/hmac_go_proto"
+	math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+
+type AesCtrHmacAeadKeyFormat struct {
+	AesCtrKeyFormat      *aes_ctr_go_proto.AesCtrKeyFormat `protobuf:"bytes,1,opt,name=aes_ctr_key_format,json=aesCtrKeyFormat,proto3" json:"aes_ctr_key_format,omitempty"`
+	HmacKeyFormat        *hmac_go_proto.HmacKeyFormat      `protobuf:"bytes,2,opt,name=hmac_key_format,json=hmacKeyFormat,proto3" json:"hmac_key_format,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}                          `json:"-"`
+	XXX_unrecognized     []byte                            `json:"-"`
+	XXX_sizecache        int32                             `json:"-"`
+}
+
+func (m *AesCtrHmacAeadKeyFormat) Reset()         { *m = AesCtrHmacAeadKeyFormat{} }
+func (m *AesCtrHmacAeadKeyFormat) String() string { return proto.CompactTextString(m) }
+func (*AesCtrHmacAeadKeyFormat) ProtoMessage()    {}
+func (*AesCtrHmacAeadKeyFormat) Descriptor() ([]byte, []int) {
+	return fileDescriptor_169302744b0080e1, []int{0}
+}
+
+func (m *AesCtrHmacAeadKeyFormat) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_AesCtrHmacAeadKeyFormat.Unmarshal(m, b)
+}
+func (m *AesCtrHmacAeadKeyFormat) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_AesCtrHmacAeadKeyFormat.Marshal(b, m, deterministic)
+}
+func (m *AesCtrHmacAeadKeyFormat) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_AesCtrHmacAeadKeyFormat.Merge(m, src)
+}
+func (m *AesCtrHmacAeadKeyFormat) XXX_Size() int {
+	return xxx_messageInfo_AesCtrHmacAeadKeyFormat.Size(m)
+}
+func (m *AesCtrHmacAeadKeyFormat) XXX_DiscardUnknown() {
+	xxx_messageInfo_AesCtrHmacAeadKeyFormat.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_AesCtrHmacAeadKeyFormat proto.InternalMessageInfo
+
+func (m *AesCtrHmacAeadKeyFormat) GetAesCtrKeyFormat() *aes_ctr_go_proto.AesCtrKeyFormat {
+	if m != nil {
+		return m.AesCtrKeyFormat
+	}
+	return nil
+}
+
+func (m *AesCtrHmacAeadKeyFormat) GetHmacKeyFormat() *hmac_go_proto.HmacKeyFormat {
+	if m != nil {
+		return m.HmacKeyFormat
+	}
+	return nil
+}
+
+// key_type: type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey
+type AesCtrHmacAeadKey struct {
+	Version              uint32                      `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
+	AesCtrKey            *aes_ctr_go_proto.AesCtrKey `protobuf:"bytes,2,opt,name=aes_ctr_key,json=aesCtrKey,proto3" json:"aes_ctr_key,omitempty"`
+	HmacKey              *hmac_go_proto.HmacKey      `protobuf:"bytes,3,opt,name=hmac_key,json=hmacKey,proto3" json:"hmac_key,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}                    `json:"-"`
+	XXX_unrecognized     []byte                      `json:"-"`
+	XXX_sizecache        int32                       `json:"-"`
+}
+
+func (m *AesCtrHmacAeadKey) Reset()         { *m = AesCtrHmacAeadKey{} }
+func (m *AesCtrHmacAeadKey) String() string { return proto.CompactTextString(m) }
+func (*AesCtrHmacAeadKey) ProtoMessage()    {}
+func (*AesCtrHmacAeadKey) Descriptor() ([]byte, []int) {
+	return fileDescriptor_169302744b0080e1, []int{1}
+}
+
+func (m *AesCtrHmacAeadKey) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_AesCtrHmacAeadKey.Unmarshal(m, b)
+}
+func (m *AesCtrHmacAeadKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_AesCtrHmacAeadKey.Marshal(b, m, deterministic)
+}
+func (m *AesCtrHmacAeadKey) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_AesCtrHmacAeadKey.Merge(m, src)
+}
+func (m *AesCtrHmacAeadKey) XXX_Size() int {
+	return xxx_messageInfo_AesCtrHmacAeadKey.Size(m)
+}
+func (m *AesCtrHmacAeadKey) XXX_DiscardUnknown() {
+	xxx_messageInfo_AesCtrHmacAeadKey.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_AesCtrHmacAeadKey proto.InternalMessageInfo
+
+func (m *AesCtrHmacAeadKey) GetVersion() uint32 {
+	if m != nil {
+		return m.Version
+	}
+	return 0
+}
+
+func (m *AesCtrHmacAeadKey) GetAesCtrKey() *aes_ctr_go_proto.AesCtrKey {
+	if m != nil {
+		return m.AesCtrKey
+	}
+	return nil
+}
+
+func (m *AesCtrHmacAeadKey) GetHmacKey() *hmac_go_proto.HmacKey {
+	if m != nil {
+		return m.HmacKey
+	}
+	return nil
+}
+
+func init() {
+	proto.RegisterType((*AesCtrHmacAeadKeyFormat)(nil), "google.crypto.tink.AesCtrHmacAeadKeyFormat")
+	proto.RegisterType((*AesCtrHmacAeadKey)(nil), "google.crypto.tink.AesCtrHmacAeadKey")
+}
+
+func init() {
+	proto.RegisterFile("proto/aes_ctr_hmac_aead.proto", fileDescriptor_169302744b0080e1)
+}
+
+var fileDescriptor_169302744b0080e1 = []byte{
+	// 296 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x92, 0xc1, 0x4a, 0xc3, 0x40,
+	0x10, 0x86, 0x49, 0x85, 0x56, 0xb7, 0x94, 0xe2, 0x5e, 0x0c, 0x55, 0xc1, 0x56, 0x0f, 0x9e, 0x36,
+	0xa0, 0xa0, 0x27, 0x0f, 0xad, 0x20, 0x96, 0x82, 0x84, 0xe0, 0xc9, 0xcb, 0xb2, 0xd9, 0xac, 0x49,
+	0xa8, 0xdb, 0x09, 0x9b, 0x55, 0xc8, 0xeb, 0x88, 0xcf, 0xe0, 0xf3, 0x49, 0x26, 0x4d, 0x8d, 0xb4,
+	0xe6, 0x38, 0x93, 0x7f, 0xbe, 0xf9, 0x26, 0x2c, 0x61, 0x36, 0x49, 0x4d, 0xc4, 0x33, 0x61, 0x6c,
+	0xe1, 0xd9, 0x74, 0xb5, 0xf4, 0x32, 0x03, 0x16, 0x3c, 0xa1, 0x72, 0x2e, 0xad, 0xe1, 0x89, 0x16,
+	0x92, 0x0b, 0x25, 0x22, 0x86, 0x7d, 0x4a, 0x63, 0x80, 0xf8, 0x4d, 0x31, 0x69, 0x8a, 0xcc, 0x02,
+	0x2b, 0x27, 0x46, 0x17, 0xed, 0x8c, 0x6a, 0x72, 0x34, 0xfe, 0x27, 0x55, 0x6e, 0xa8, 0x22, 0x93,
+	0x6f, 0x87, 0x1c, 0x4d, 0x55, 0x7e, 0x6f, 0xcd, 0xa3, 0x16, 0x72, 0xaa, 0x44, 0xb4, 0x50, 0xc5,
+	0x03, 0x18, 0x2d, 0x2c, 0xf5, 0x09, 0xad, 0x9d, 0x96, 0xaa, 0xe0, 0xaf, 0xd8, 0x75, 0x9d, 0x33,
+	0xe7, 0xb2, 0x7f, 0x75, 0xce, 0xb6, 0xad, 0x58, 0x05, 0xda, 0x00, 0x82, 0xa1, 0xf8, 0xdb, 0xa0,
+	0x73, 0x32, 0xc4, 0xeb, 0x1a, 0xb8, 0x0e, 0xe2, 0xc6, 0xbb, 0x70, 0xa5, 0xd1, 0x2f, 0x6c, 0x90,
+	0x34, 0xcb, 0xc9, 0x97, 0x43, 0x0e, 0xb7, 0xc4, 0xa9, 0x4b, 0x7a, 0x1f, 0xca, 0xe4, 0x29, 0xac,
+	0xd0, 0x73, 0x10, 0xd4, 0x25, 0xbd, 0x23, 0xfd, 0xc6, 0x31, 0xeb, 0xb5, 0xa7, 0xad, 0x57, 0x04,
+	0x07, 0x1b, 0x7f, 0x7a, 0x43, 0xf6, 0x6b, 0x73, 0x77, 0x0f, 0x67, 0x8f, 0x5b, 0x94, 0x83, 0xde,
+	0x5a, 0x76, 0x16, 0x92, 0x13, 0x09, 0x7a, 0x57, 0x14, 0xff, 0xbf, 0xef, 0xbc, 0xdc, 0xc6, 0xa9,
+	0x4d, 0xde, 0x43, 0x26, 0x41, 0x7b, 0x55, 0xac, 0xf5, 0x51, 0xf0, 0x18, 0x38, 0x7e, 0xfa, 0xec,
+	0x74, 0x9f, 0xe7, 0x4f, 0x0b, 0x7f, 0x16, 0x76, 0xb1, 0xbe, 0xfe, 0x09, 0x00, 0x00, 0xff, 0xff,
+	0x1a, 0xa5, 0xb9, 0x8c, 0x59, 0x02, 0x00, 0x00,
+}
diff --git a/proto/aes_gcm_go_proto/aes_gcm.pb.go b/proto/aes_gcm_go_proto/aes_gcm.pb.go
index 6aa926f..ec3c177 100644
--- a/proto/aes_gcm_go_proto/aes_gcm.pb.go
+++ b/proto/aes_gcm_go_proto/aes_gcm.pb.go
@@ -1,36 +1,29 @@
-// Copyright 2017 Google Inc.
-
+// Copyright 2018 Google Inc.
+//
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
-
+//
 //      http://www.apache.org/licenses/LICENSE-2.0
-
+//
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+//
 ////////////////////////////////////////////////////////////////////////////////
 
 // Code generated by protoc-gen-go. DO NOT EDIT.
-// source: proto/aes_gcm.proto
+// source: third_party/tink/proto/aes_gcm.proto
 
-/*
-Package aes_gcm_proto is a generated protocol buffer package.
-
-It is generated from these files:
-	proto/aes_gcm.proto
-
-It has these top-level messages:
-	AesGcmKeyFormat
-	AesGcmKey
-*/
 package aes_gcm_go_proto
 
-import proto "github.com/golang/protobuf/proto"
-import fmt "fmt"
-import math "math"
+import (
+	fmt "fmt"
+	proto "github.com/golang/protobuf/proto"
+	math "math"
+)
 
 // Reference imports to suppress errors if they are not otherwise used.
 var _ = proto.Marshal
@@ -41,18 +34,41 @@
 // is compatible with the proto package it is being compiled against.
 // A compilation error at this line likely means your copy of the
 // proto package needs to be updated.
-const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
 
 // only allowing IV size in bytes = 12 and tag size in bytes = 16
 // Thus, accept no params.
 type AesGcmKeyFormat struct {
-	KeySize uint32 `protobuf:"varint,2,opt,name=key_size,json=keySize" json:"key_size,omitempty"`
+	KeySize              uint32   `protobuf:"varint,2,opt,name=key_size,json=keySize,proto3" json:"key_size,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
 }
 
-func (m *AesGcmKeyFormat) Reset()                    { *m = AesGcmKeyFormat{} }
-func (m *AesGcmKeyFormat) String() string            { return proto.CompactTextString(m) }
-func (*AesGcmKeyFormat) ProtoMessage()               {}
-func (*AesGcmKeyFormat) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+func (m *AesGcmKeyFormat) Reset()         { *m = AesGcmKeyFormat{} }
+func (m *AesGcmKeyFormat) String() string { return proto.CompactTextString(m) }
+func (*AesGcmKeyFormat) ProtoMessage()    {}
+func (*AesGcmKeyFormat) Descriptor() ([]byte, []int) {
+	return fileDescriptor_916a2e6361d587ce, []int{0}
+}
+
+func (m *AesGcmKeyFormat) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_AesGcmKeyFormat.Unmarshal(m, b)
+}
+func (m *AesGcmKeyFormat) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_AesGcmKeyFormat.Marshal(b, m, deterministic)
+}
+func (m *AesGcmKeyFormat) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_AesGcmKeyFormat.Merge(m, src)
+}
+func (m *AesGcmKeyFormat) XXX_Size() int {
+	return xxx_messageInfo_AesGcmKeyFormat.Size(m)
+}
+func (m *AesGcmKeyFormat) XXX_DiscardUnknown() {
+	xxx_messageInfo_AesGcmKeyFormat.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_AesGcmKeyFormat proto.InternalMessageInfo
 
 func (m *AesGcmKeyFormat) GetKeySize() uint32 {
 	if m != nil {
@@ -63,14 +79,37 @@
 
 // key_type: type.googleapis.com/google.crypto.tink.AesGcmKey
 type AesGcmKey struct {
-	Version  uint32 `protobuf:"varint,1,opt,name=version" json:"version,omitempty"`
-	KeyValue []byte `protobuf:"bytes,3,opt,name=key_value,json=keyValue,proto3" json:"key_value,omitempty"`
+	Version              uint32   `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
+	KeyValue             []byte   `protobuf:"bytes,3,opt,name=key_value,json=keyValue,proto3" json:"key_value,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
 }
 
-func (m *AesGcmKey) Reset()                    { *m = AesGcmKey{} }
-func (m *AesGcmKey) String() string            { return proto.CompactTextString(m) }
-func (*AesGcmKey) ProtoMessage()               {}
-func (*AesGcmKey) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
+func (m *AesGcmKey) Reset()         { *m = AesGcmKey{} }
+func (m *AesGcmKey) String() string { return proto.CompactTextString(m) }
+func (*AesGcmKey) ProtoMessage()    {}
+func (*AesGcmKey) Descriptor() ([]byte, []int) {
+	return fileDescriptor_916a2e6361d587ce, []int{1}
+}
+
+func (m *AesGcmKey) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_AesGcmKey.Unmarshal(m, b)
+}
+func (m *AesGcmKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_AesGcmKey.Marshal(b, m, deterministic)
+}
+func (m *AesGcmKey) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_AesGcmKey.Merge(m, src)
+}
+func (m *AesGcmKey) XXX_Size() int {
+	return xxx_messageInfo_AesGcmKey.Size(m)
+}
+func (m *AesGcmKey) XXX_DiscardUnknown() {
+	xxx_messageInfo_AesGcmKey.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_AesGcmKey proto.InternalMessageInfo
 
 func (m *AesGcmKey) GetVersion() uint32 {
 	if m != nil {
@@ -91,21 +130,24 @@
 	proto.RegisterType((*AesGcmKey)(nil), "google.crypto.tink.AesGcmKey")
 }
 
-func init() { proto.RegisterFile("proto/aes_gcm.proto", fileDescriptor0) }
+func init() {
+	proto.RegisterFile("proto/aes_gcm.proto", fileDescriptor_916a2e6361d587ce)
+}
 
-var fileDescriptor0 = []byte{
-	// 206 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x2e, 0x28, 0xca, 0x2f,
-	0xc9, 0xd7, 0x4f, 0x4c, 0x2d, 0x8e, 0x4f, 0x4f, 0xce, 0xd5, 0x03, 0xf3, 0x84, 0x84, 0xd2, 0xf3,
-	0xf3, 0xd3, 0x73, 0x52, 0xf5, 0x92, 0x8b, 0x2a, 0x0b, 0x4a, 0xf2, 0xf5, 0x4a, 0x32, 0xf3, 0xb2,
-	0x95, 0x74, 0xb8, 0xf8, 0x1d, 0x53, 0x8b, 0xdd, 0x93, 0x73, 0xbd, 0x53, 0x2b, 0xdd, 0xf2, 0x8b,
-	0x72, 0x13, 0x4b, 0x84, 0x24, 0xb9, 0x38, 0xb2, 0x53, 0x2b, 0xe3, 0x8b, 0x33, 0xab, 0x52, 0x25,
-	0x98, 0x14, 0x18, 0x35, 0x78, 0x83, 0xd8, 0xb3, 0x53, 0x2b, 0x83, 0x33, 0xab, 0x52, 0x95, 0x9c,
-	0xb8, 0x38, 0xe1, 0xaa, 0x85, 0x24, 0xb8, 0xd8, 0xcb, 0x52, 0x8b, 0x8a, 0x33, 0xf3, 0xf3, 0x24,
-	0x18, 0x21, 0xca, 0xa0, 0x5c, 0x21, 0x69, 0x2e, 0x4e, 0x90, 0x09, 0x65, 0x89, 0x39, 0xa5, 0xa9,
-	0x12, 0xcc, 0x0a, 0x8c, 0x1a, 0x3c, 0x41, 0x20, 0x23, 0xc3, 0x40, 0x7c, 0xa7, 0x50, 0x2e, 0x99,
-	0xe4, 0xfc, 0x5c, 0x3d, 0x4c, 0xb7, 0x40, 0x5c, 0x19, 0xc0, 0x18, 0xa5, 0x95, 0x9e, 0x59, 0x92,
-	0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0x0f, 0x51, 0xa6, 0x0f, 0x92, 0xd7, 0x47, 0xf1, 0x53,
-	0x3c, 0x98, 0xb7, 0x88, 0x89, 0x2d, 0xc4, 0xd3, 0xcf, 0x3b, 0xc0, 0x29, 0x89, 0x0d, 0xcc, 0x37,
-	0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x56, 0xfa, 0x50, 0xa6, 0xfa, 0x00, 0x00, 0x00,
+var fileDescriptor_916a2e6361d587ce = []byte{
+	// 220 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x29, 0xc9, 0xc8, 0x2c,
+	0x4a, 0x89, 0x2f, 0x48, 0x2c, 0x2a, 0xa9, 0xd4, 0x2f, 0xc9, 0xcc, 0xcb, 0xd6, 0x2f, 0x28, 0xca,
+	0x2f, 0xc9, 0xd7, 0x4f, 0x4c, 0x2d, 0x8e, 0x4f, 0x4f, 0xce, 0xd5, 0x03, 0xf3, 0x84, 0x84, 0xd2,
+	0xf3, 0xf3, 0xd3, 0x73, 0x52, 0xf5, 0x92, 0x8b, 0x2a, 0x0b, 0x4a, 0xf2, 0xf5, 0x40, 0xea, 0x94,
+	0x74, 0xb8, 0xf8, 0x1d, 0x53, 0x8b, 0xdd, 0x93, 0x73, 0xbd, 0x53, 0x2b, 0xdd, 0xf2, 0x8b, 0x72,
+	0x13, 0x4b, 0x84, 0x24, 0xb9, 0x38, 0xb2, 0x53, 0x2b, 0xe3, 0x8b, 0x33, 0xab, 0x52, 0x25, 0x98,
+	0x14, 0x18, 0x35, 0x78, 0x83, 0xd8, 0xb3, 0x53, 0x2b, 0x83, 0x33, 0xab, 0x52, 0x95, 0x9c, 0xb8,
+	0x38, 0xe1, 0xaa, 0x85, 0x24, 0xb8, 0xd8, 0xcb, 0x52, 0x8b, 0x8a, 0x33, 0xf3, 0xf3, 0x24, 0x18,
+	0x21, 0xca, 0xa0, 0x5c, 0x21, 0x69, 0x2e, 0x4e, 0x90, 0x09, 0x65, 0x89, 0x39, 0xa5, 0xa9, 0x12,
+	0xcc, 0x0a, 0x8c, 0x1a, 0x3c, 0x41, 0x20, 0x23, 0xc3, 0x40, 0x7c, 0xa7, 0x08, 0x2e, 0x99, 0xe4,
+	0xfc, 0x5c, 0x3d, 0x4c, 0xb7, 0x40, 0x5c, 0x19, 0xc0, 0x18, 0xa5, 0x9b, 0x9e, 0x59, 0x92, 0x51,
+	0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0x0f, 0x51, 0x86, 0xc5, 0x4f, 0xf1, 0xe9, 0xf9, 0xf1, 0x60,
+	0x81, 0x45, 0x4c, 0x6c, 0x21, 0x9e, 0x7e, 0xde, 0x01, 0x4e, 0x49, 0x6c, 0x60, 0xbe, 0x31, 0x20,
+	0x00, 0x00, 0xff, 0xff, 0x04, 0x68, 0xaf, 0x57, 0x0e, 0x01, 0x00, 0x00,
 }
diff --git a/proto/aes_siv_go_proto/aes_siv.pb.go b/proto/aes_siv_go_proto/aes_siv.pb.go
new file mode 100644
index 0000000..2159fa9
--- /dev/null
+++ b/proto/aes_siv_go_proto/aes_siv.pb.go
@@ -0,0 +1,153 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: third_party/tink/proto/aes_siv.proto
+
+package aes_siv_go_proto
+
+import (
+	fmt "fmt"
+	proto "github.com/golang/protobuf/proto"
+	math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+
+type AesSivKeyFormat struct {
+	// Only valid value is: 64.
+	KeySize              uint32   `protobuf:"varint,1,opt,name=key_size,json=keySize,proto3" json:"key_size,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *AesSivKeyFormat) Reset()         { *m = AesSivKeyFormat{} }
+func (m *AesSivKeyFormat) String() string { return proto.CompactTextString(m) }
+func (*AesSivKeyFormat) ProtoMessage()    {}
+func (*AesSivKeyFormat) Descriptor() ([]byte, []int) {
+	return fileDescriptor_43b3629c0a9079e5, []int{0}
+}
+
+func (m *AesSivKeyFormat) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_AesSivKeyFormat.Unmarshal(m, b)
+}
+func (m *AesSivKeyFormat) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_AesSivKeyFormat.Marshal(b, m, deterministic)
+}
+func (m *AesSivKeyFormat) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_AesSivKeyFormat.Merge(m, src)
+}
+func (m *AesSivKeyFormat) XXX_Size() int {
+	return xxx_messageInfo_AesSivKeyFormat.Size(m)
+}
+func (m *AesSivKeyFormat) XXX_DiscardUnknown() {
+	xxx_messageInfo_AesSivKeyFormat.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_AesSivKeyFormat proto.InternalMessageInfo
+
+func (m *AesSivKeyFormat) GetKeySize() uint32 {
+	if m != nil {
+		return m.KeySize
+	}
+	return 0
+}
+
+// key_type: type.googleapis.com/google.crypto.tink.AesSivKey
+type AesSivKey struct {
+	Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
+	// First half is AES-CTR key, second is AES-SIV.
+	KeyValue             []byte   `protobuf:"bytes,2,opt,name=key_value,json=keyValue,proto3" json:"key_value,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *AesSivKey) Reset()         { *m = AesSivKey{} }
+func (m *AesSivKey) String() string { return proto.CompactTextString(m) }
+func (*AesSivKey) ProtoMessage()    {}
+func (*AesSivKey) Descriptor() ([]byte, []int) {
+	return fileDescriptor_43b3629c0a9079e5, []int{1}
+}
+
+func (m *AesSivKey) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_AesSivKey.Unmarshal(m, b)
+}
+func (m *AesSivKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_AesSivKey.Marshal(b, m, deterministic)
+}
+func (m *AesSivKey) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_AesSivKey.Merge(m, src)
+}
+func (m *AesSivKey) XXX_Size() int {
+	return xxx_messageInfo_AesSivKey.Size(m)
+}
+func (m *AesSivKey) XXX_DiscardUnknown() {
+	xxx_messageInfo_AesSivKey.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_AesSivKey proto.InternalMessageInfo
+
+func (m *AesSivKey) GetVersion() uint32 {
+	if m != nil {
+		return m.Version
+	}
+	return 0
+}
+
+func (m *AesSivKey) GetKeyValue() []byte {
+	if m != nil {
+		return m.KeyValue
+	}
+	return nil
+}
+
+func init() {
+	proto.RegisterType((*AesSivKeyFormat)(nil), "google.crypto.tink.AesSivKeyFormat")
+	proto.RegisterType((*AesSivKey)(nil), "google.crypto.tink.AesSivKey")
+}
+
+func init() {
+	proto.RegisterFile("proto/aes_siv.proto", fileDescriptor_43b3629c0a9079e5)
+}
+
+var fileDescriptor_43b3629c0a9079e5 = []byte{
+	// 218 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x29, 0xc9, 0xc8, 0x2c,
+	0x4a, 0x89, 0x2f, 0x48, 0x2c, 0x2a, 0xa9, 0xd4, 0x2f, 0xc9, 0xcc, 0xcb, 0xd6, 0x2f, 0x28, 0xca,
+	0x2f, 0xc9, 0xd7, 0x4f, 0x4c, 0x2d, 0x8e, 0x2f, 0xce, 0x2c, 0xd3, 0x03, 0xf3, 0x84, 0x84, 0xd2,
+	0xf3, 0xf3, 0xd3, 0x73, 0x52, 0xf5, 0x92, 0x8b, 0x2a, 0x0b, 0x4a, 0xf2, 0xf5, 0x40, 0xea, 0x94,
+	0x74, 0xb8, 0xf8, 0x1d, 0x53, 0x8b, 0x83, 0x33, 0xcb, 0xbc, 0x53, 0x2b, 0xdd, 0xf2, 0x8b, 0x72,
+	0x13, 0x4b, 0x84, 0x24, 0xb9, 0x38, 0xb2, 0x53, 0x2b, 0xe3, 0x8b, 0x33, 0xab, 0x52, 0x25, 0x18,
+	0x15, 0x18, 0x35, 0x78, 0x83, 0xd8, 0xb3, 0x53, 0x2b, 0x83, 0x33, 0xab, 0x52, 0x95, 0x9c, 0xb8,
+	0x38, 0xe1, 0xaa, 0x85, 0x24, 0xb8, 0xd8, 0xcb, 0x52, 0x8b, 0x8a, 0x33, 0xf3, 0xf3, 0x60, 0xca,
+	0xa0, 0x5c, 0x21, 0x69, 0x2e, 0x4e, 0x90, 0x09, 0x65, 0x89, 0x39, 0xa5, 0xa9, 0x12, 0x4c, 0x0a,
+	0x8c, 0x1a, 0x3c, 0x41, 0x20, 0x23, 0xc3, 0x40, 0x7c, 0xa7, 0x08, 0x2e, 0x99, 0xe4, 0xfc, 0x5c,
+	0x3d, 0x4c, 0xb7, 0x40, 0x5c, 0x19, 0xc0, 0x18, 0xa5, 0x9b, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4,
+	0x97, 0x9c, 0x9f, 0xab, 0x0f, 0x51, 0x86, 0xc5, 0x4f, 0xf1, 0xe9, 0xf9, 0xf1, 0x60, 0x81, 0x45,
+	0x4c, 0x6c, 0x21, 0x9e, 0x7e, 0xde, 0x01, 0x4e, 0x49, 0x6c, 0x60, 0xbe, 0x31, 0x20, 0x00, 0x00,
+	0xff, 0xff, 0x81, 0x45, 0x27, 0x3b, 0x0e, 0x01, 0x00, 0x00,
+}
diff --git a/proto/chacha20_poly1305_go_proto/chacha20_poly1305.pb.go b/proto/chacha20_poly1305_go_proto/chacha20_poly1305.pb.go
new file mode 100644
index 0000000..6fd5f21
--- /dev/null
+++ b/proto/chacha20_poly1305_go_proto/chacha20_poly1305.pb.go
@@ -0,0 +1,112 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: third_party/tink/proto/chacha20_poly1305.proto
+
+package chacha20_poly1305_go_proto
+
+import (
+	fmt "fmt"
+	proto "github.com/golang/protobuf/proto"
+	math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
+// key_type: type.googleapis.com/google.crypto.tink.ChaCha20Poly1305.
+// This key type actually implements ChaCha20Poly1305 as described
+// at https://tools.ietf.org/html/rfc7539#section-2.8.
+type ChaCha20Poly1305Key struct {
+	Version              uint32   `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
+	KeyValue             []byte   `protobuf:"bytes,2,opt,name=key_value,json=keyValue,proto3" json:"key_value,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *ChaCha20Poly1305Key) Reset()         { *m = ChaCha20Poly1305Key{} }
+func (m *ChaCha20Poly1305Key) String() string { return proto.CompactTextString(m) }
+func (*ChaCha20Poly1305Key) ProtoMessage()    {}
+func (*ChaCha20Poly1305Key) Descriptor() ([]byte, []int) {
+	return fileDescriptor_ddbcaacfeec7ca2e, []int{0}
+}
+
+func (m *ChaCha20Poly1305Key) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_ChaCha20Poly1305Key.Unmarshal(m, b)
+}
+func (m *ChaCha20Poly1305Key) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_ChaCha20Poly1305Key.Marshal(b, m, deterministic)
+}
+func (m *ChaCha20Poly1305Key) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ChaCha20Poly1305Key.Merge(m, src)
+}
+func (m *ChaCha20Poly1305Key) XXX_Size() int {
+	return xxx_messageInfo_ChaCha20Poly1305Key.Size(m)
+}
+func (m *ChaCha20Poly1305Key) XXX_DiscardUnknown() {
+	xxx_messageInfo_ChaCha20Poly1305Key.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ChaCha20Poly1305Key proto.InternalMessageInfo
+
+func (m *ChaCha20Poly1305Key) GetVersion() uint32 {
+	if m != nil {
+		return m.Version
+	}
+	return 0
+}
+
+func (m *ChaCha20Poly1305Key) GetKeyValue() []byte {
+	if m != nil {
+		return m.KeyValue
+	}
+	return nil
+}
+
+func init() {
+	proto.RegisterType((*ChaCha20Poly1305Key)(nil), "google.crypto.tink.ChaCha20Poly1305Key")
+}
+
+func init() {
+	proto.RegisterFile("proto/chacha20_poly1305.proto", fileDescriptor_ddbcaacfeec7ca2e)
+}
+
+var fileDescriptor_ddbcaacfeec7ca2e = []byte{
+	// 201 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x2b, 0xc9, 0xc8, 0x2c,
+	0x4a, 0x89, 0x2f, 0x48, 0x2c, 0x2a, 0xa9, 0xd4, 0x2f, 0xc9, 0xcc, 0xcb, 0xd6, 0x2f, 0x28, 0xca,
+	0x2f, 0xc9, 0xd7, 0x4f, 0xce, 0x48, 0x4c, 0xce, 0x48, 0x34, 0x32, 0x88, 0x2f, 0xc8, 0xcf, 0xa9,
+	0x34, 0x34, 0x36, 0x30, 0xd5, 0x03, 0x8b, 0x0b, 0x09, 0xa5, 0xe7, 0xe7, 0xa7, 0xe7, 0xa4, 0xea,
+	0x25, 0x17, 0x55, 0x16, 0x94, 0xe4, 0xeb, 0x81, 0x74, 0x28, 0xf9, 0x70, 0x09, 0x3b, 0x67, 0x24,
+	0x3a, 0x83, 0x94, 0x07, 0x40, 0x55, 0x7b, 0xa7, 0x56, 0x0a, 0x49, 0x70, 0xb1, 0x97, 0xa5, 0x16,
+	0x15, 0x67, 0xe6, 0xe7, 0x49, 0x30, 0x2a, 0x30, 0x6a, 0xf0, 0x06, 0xc1, 0xb8, 0x42, 0xd2, 0x5c,
+	0x9c, 0xd9, 0xa9, 0x95, 0xf1, 0x65, 0x89, 0x39, 0xa5, 0xa9, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0x3c,
+	0x41, 0x1c, 0xd9, 0xa9, 0x95, 0x61, 0x20, 0xbe, 0x53, 0x12, 0x97, 0x4c, 0x72, 0x7e, 0xae, 0x1e,
+	0xa6, 0x3d, 0x10, 0x17, 0x04, 0x30, 0x46, 0x99, 0xa7, 0x67, 0x96, 0x64, 0x94, 0x26, 0xe9, 0x25,
+	0xe7, 0xe7, 0xea, 0x43, 0x94, 0xe1, 0x75, 0x79, 0x7c, 0x7a, 0x7e, 0x3c, 0x58, 0x6a, 0x11, 0x13,
+	0x5b, 0x88, 0xa7, 0x9f, 0x77, 0x80, 0x53, 0x12, 0x1b, 0x98, 0x6f, 0x0c, 0x08, 0x00, 0x00, 0xff,
+	0xff, 0xdb, 0xa8, 0x37, 0x79, 0xfe, 0x00, 0x00, 0x00,
+}
diff --git a/proto/common_go_proto/common.pb.go b/proto/common_go_proto/common.pb.go
index dffc07d..cea8886 100644
--- a/proto/common_go_proto/common.pb.go
+++ b/proto/common_go_proto/common.pb.go
@@ -1,34 +1,29 @@
-// Copyright 2017 Google Inc.
-
+// Copyright 2018 Google Inc.
+//
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
-
+//
 //      http://www.apache.org/licenses/LICENSE-2.0
-
+//
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+//
 ////////////////////////////////////////////////////////////////////////////////
 
 // Code generated by protoc-gen-go. DO NOT EDIT.
-// source: proto/common.proto
+// source: third_party/tink/proto/common.proto
 
-/*
-Package common_proto is a generated protocol buffer package.
-
-It is generated from these files:
-	proto/common.proto
-
-It has these top-level messages:
-*/
 package common_go_proto
 
-import proto "github.com/golang/protobuf/proto"
-import fmt "fmt"
-import math "math"
+import (
+	fmt "fmt"
+	proto "github.com/golang/protobuf/proto"
+	math "math"
+)
 
 // Reference imports to suppress errors if they are not otherwise used.
 var _ = proto.Marshal
@@ -39,13 +34,12 @@
 // is compatible with the proto package it is being compiled against.
 // A compilation error at this line likely means your copy of the
 // proto package needs to be updated.
-const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
 
 type EllipticCurveType int32
 
 const (
 	EllipticCurveType_UNKNOWN_CURVE EllipticCurveType = 0
-	EllipticCurveType_NIST_P224     EllipticCurveType = 1
 	EllipticCurveType_NIST_P256     EllipticCurveType = 2
 	EllipticCurveType_NIST_P384     EllipticCurveType = 3
 	EllipticCurveType_NIST_P521     EllipticCurveType = 4
@@ -53,14 +47,13 @@
 
 var EllipticCurveType_name = map[int32]string{
 	0: "UNKNOWN_CURVE",
-	1: "NIST_P224",
 	2: "NIST_P256",
 	3: "NIST_P384",
 	4: "NIST_P521",
 }
+
 var EllipticCurveType_value = map[string]int32{
 	"UNKNOWN_CURVE": 0,
-	"NIST_P224":     1,
 	"NIST_P256":     2,
 	"NIST_P384":     3,
 	"NIST_P521":     4,
@@ -69,7 +62,10 @@
 func (x EllipticCurveType) String() string {
 	return proto.EnumName(EllipticCurveType_name, int32(x))
 }
-func (EllipticCurveType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+
+func (EllipticCurveType) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_51c37496ff2054f5, []int{0}
+}
 
 type EcPointFormat int32
 
@@ -77,30 +73,38 @@
 	EcPointFormat_UNKNOWN_FORMAT EcPointFormat = 0
 	EcPointFormat_UNCOMPRESSED   EcPointFormat = 1
 	EcPointFormat_COMPRESSED     EcPointFormat = 2
+	// Like UNCOMPRESSED but without the \x04 prefix. Crunchy uses this format.
+	// DO NOT USE unless you are a Crunchy user moving to Tink.
+	EcPointFormat_DO_NOT_USE_CRUNCHY_UNCOMPRESSED EcPointFormat = 3
 )
 
 var EcPointFormat_name = map[int32]string{
 	0: "UNKNOWN_FORMAT",
 	1: "UNCOMPRESSED",
 	2: "COMPRESSED",
+	3: "DO_NOT_USE_CRUNCHY_UNCOMPRESSED",
 }
+
 var EcPointFormat_value = map[string]int32{
-	"UNKNOWN_FORMAT": 0,
-	"UNCOMPRESSED":   1,
-	"COMPRESSED":     2,
+	"UNKNOWN_FORMAT":                  0,
+	"UNCOMPRESSED":                    1,
+	"COMPRESSED":                      2,
+	"DO_NOT_USE_CRUNCHY_UNCOMPRESSED": 3,
 }
 
 func (x EcPointFormat) String() string {
 	return proto.EnumName(EcPointFormat_name, int32(x))
 }
-func (EcPointFormat) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
+
+func (EcPointFormat) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_51c37496ff2054f5, []int{1}
+}
 
 type HashType int32
 
 const (
 	HashType_UNKNOWN_HASH HashType = 0
 	HashType_SHA1         HashType = 1
-	HashType_SHA224       HashType = 2
 	HashType_SHA256       HashType = 3
 	HashType_SHA512       HashType = 4
 )
@@ -108,14 +112,13 @@
 var HashType_name = map[int32]string{
 	0: "UNKNOWN_HASH",
 	1: "SHA1",
-	2: "SHA224",
 	3: "SHA256",
 	4: "SHA512",
 }
+
 var HashType_value = map[string]int32{
 	"UNKNOWN_HASH": 0,
 	"SHA1":         1,
-	"SHA224":       2,
 	"SHA256":       3,
 	"SHA512":       4,
 }
@@ -123,7 +126,10 @@
 func (x HashType) String() string {
 	return proto.EnumName(HashType_name, int32(x))
 }
-func (HashType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
+
+func (HashType) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_51c37496ff2054f5, []int{2}
+}
 
 func init() {
 	proto.RegisterEnum("google.crypto.tink.EllipticCurveType", EllipticCurveType_name, EllipticCurveType_value)
@@ -131,26 +137,30 @@
 	proto.RegisterEnum("google.crypto.tink.HashType", HashType_name, HashType_value)
 }
 
-func init() { proto.RegisterFile("proto/common.proto", fileDescriptor0) }
+func init() {
+	proto.RegisterFile("proto/common.proto", fileDescriptor_51c37496ff2054f5)
+}
 
-var fileDescriptor0 = []byte{
-	// 286 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0xd0, 0xc1, 0x6a, 0xf2, 0x40,
-	0x10, 0x07, 0x70, 0x13, 0x45, 0xfc, 0x86, 0x4f, 0x19, 0xf7, 0xdc, 0x17, 0xa8, 0x87, 0x04, 0xa3,
-	0x91, 0x5e, 0xa3, 0x5d, 0x89, 0x15, 0xd7, 0xe0, 0x26, 0x2d, 0xf4, 0x12, 0xcc, 0x12, 0x62, 0x68,
-	0x92, 0x0d, 0xe9, 0x5a, 0xf0, 0x75, 0xfa, 0xa4, 0xc5, 0xa4, 0x62, 0xa0, 0xb7, 0xf9, 0x31, 0xc3,
-	0xf0, 0xe7, 0x0f, 0xa4, 0xac, 0xa4, 0x92, 0xa6, 0x90, 0x79, 0x2e, 0x0b, 0xa3, 0x06, 0x21, 0x89,
-	0x94, 0x49, 0x16, 0x1b, 0xa2, 0xba, 0x94, 0x4a, 0x1a, 0x2a, 0x2d, 0x3e, 0x26, 0x11, 0x8c, 0x69,
-	0x96, 0xa5, 0xa5, 0x4a, 0xc5, 0xea, 0x5c, 0x7d, 0xc5, 0xfe, 0xa5, 0x8c, 0xc9, 0x18, 0x86, 0x01,
-	0xdb, 0xb2, 0xfd, 0x1b, 0x0b, 0x57, 0xc1, 0xe1, 0x95, 0x62, 0x87, 0x0c, 0xe1, 0x1f, 0xdb, 0x70,
-	0x3f, 0xf4, 0x2c, 0x6b, 0x8e, 0x5a, 0x8b, 0xf6, 0x02, 0xf5, 0x3b, 0x67, 0x4f, 0x73, 0xec, 0xde,
-	0x69, 0x5b, 0x53, 0xec, 0x4d, 0x28, 0x0c, 0xa9, 0xf0, 0x64, 0x5a, 0xa8, 0xb5, 0xac, 0xf2, 0xa3,
-	0x22, 0x04, 0x46, 0xb7, 0xff, 0xeb, 0xfd, 0x61, 0xe7, 0xf8, 0xd8, 0x21, 0x08, 0xff, 0x03, 0xb6,
-	0xda, 0xef, 0xbc, 0x03, 0xe5, 0x9c, 0x3e, 0xa3, 0x46, 0x46, 0x00, 0x2d, 0xeb, 0x93, 0x17, 0x18,
-	0xb8, 0xc7, 0xcf, 0x53, 0x9d, 0xb0, 0xbe, 0x6e, 0x3e, 0xb8, 0x0e, 0x77, 0xb1, 0x43, 0x06, 0xd0,
-	0xe3, 0xae, 0x33, 0x45, 0x8d, 0x00, 0xf4, 0xb9, 0xeb, 0x5c, 0x73, 0xea, 0xb7, 0xd9, 0x5e, 0x60,
-	0xf7, 0x77, 0xb6, 0xa7, 0x16, 0xf6, 0x96, 0x3e, 0x3c, 0x08, 0x99, 0x1b, 0x7f, 0x0b, 0x69, 0xaa,
-	0xf2, 0xb4, 0xf7, 0xc7, 0x24, 0x55, 0xa7, 0x73, 0x64, 0x08, 0x99, 0x9b, 0xcd, 0x99, 0x79, 0xdd,
-	0x9b, 0xed, 0x5e, 0xc3, 0x1a, 0xdf, 0x7a, 0xdf, 0xdf, 0xb0, 0xad, 0xb7, 0x8c, 0xfa, 0xb5, 0x67,
-	0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x2e, 0x21, 0x6b, 0xfe, 0x7d, 0x01, 0x00, 0x00,
+var fileDescriptor_51c37496ff2054f5 = []byte{
+	// 311 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0x4d, 0x6b, 0xf2, 0x40,
+	0x14, 0x85, 0xfd, 0x42, 0x7c, 0x2f, 0xaf, 0x32, 0xce, 0xba, 0xd0, 0x85, 0x3b, 0x29, 0x09, 0x6a,
+	0x2d, 0x5d, 0x15, 0x62, 0x8c, 0x44, 0xc4, 0x49, 0xc8, 0x24, 0x95, 0x76, 0x33, 0xe8, 0x54, 0xe2,
+	0xb4, 0xc6, 0x1b, 0xa6, 0x63, 0xc1, 0xbf, 0xd3, 0x5f, 0x5a, 0x8c, 0x2d, 0xb6, 0xb8, 0x9b, 0x07,
+	0xce, 0x9c, 0x7b, 0x78, 0xa0, 0x63, 0x36, 0x4a, 0xbf, 0x88, 0x7c, 0xa9, 0xcd, 0xc1, 0x36, 0x6a,
+	0xf7, 0x66, 0xe7, 0x1a, 0x0d, 0xda, 0x12, 0xb3, 0x0c, 0x77, 0x56, 0x01, 0x94, 0xa6, 0x88, 0xe9,
+	0x76, 0x6d, 0x49, 0x7d, 0xc8, 0x0d, 0x5a, 0xc7, 0x58, 0x97, 0x43, 0xdb, 0xdb, 0x6e, 0x55, 0x6e,
+	0x94, 0x74, 0xf7, 0xfa, 0x63, 0x1d, 0x1f, 0xf2, 0x35, 0x6d, 0x43, 0x33, 0x61, 0x33, 0x16, 0x2c,
+	0x98, 0x70, 0x93, 0xe8, 0xd1, 0x23, 0x25, 0xda, 0x84, 0x7f, 0x6c, 0xca, 0x63, 0x11, 0xf6, 0x87,
+	0x77, 0xa4, 0x72, 0xc6, 0xc1, 0xfd, 0x2d, 0xa9, 0x9e, 0x71, 0xd8, 0xef, 0x91, 0x5a, 0xf7, 0x15,
+	0x9a, 0x9e, 0x0c, 0x51, 0xed, 0xcc, 0x04, 0x75, 0xb6, 0x34, 0x94, 0x42, 0xeb, 0xa7, 0x70, 0x12,
+	0x44, 0x73, 0x27, 0x26, 0x25, 0x4a, 0xe0, 0x7f, 0xc2, 0xdc, 0x60, 0x1e, 0x46, 0x1e, 0xe7, 0xde,
+	0x98, 0x94, 0x69, 0x0b, 0xe0, 0x17, 0x57, 0x68, 0x07, 0xae, 0xc7, 0x81, 0x60, 0x41, 0x2c, 0x12,
+	0xee, 0x09, 0x37, 0x4a, 0x98, 0xeb, 0x3f, 0x89, 0x3f, 0x9f, 0xaa, 0xdd, 0x07, 0x68, 0xf8, 0xcb,
+	0xf7, 0x4d, 0xb1, 0xbb, 0xa8, 0x3c, 0x9d, 0xf1, 0x1d, 0xee, 0x93, 0x12, 0x6d, 0x40, 0x8d, 0xfb,
+	0x4e, 0x8f, 0x94, 0x29, 0x40, 0x9d, 0xfb, 0xce, 0x71, 0x7d, 0xf5, 0xfb, 0x3d, 0xec, 0xf5, 0x49,
+	0x6d, 0xb4, 0x80, 0x2b, 0x89, 0x99, 0x75, 0xa9, 0xe6, 0x24, 0x2d, 0x2c, 0x3f, 0xdf, 0xa4, 0xca,
+	0x6c, 0xf6, 0x2b, 0x4b, 0x62, 0x66, 0x9f, 0x62, 0x97, 0x86, 0x45, 0x8a, 0xa2, 0xe0, 0xcf, 0x4a,
+	0x3d, 0x9e, 0xb2, 0x59, 0x38, 0x5a, 0xd5, 0x0b, 0x1e, 0x7c, 0x05, 0x00, 0x00, 0xff, 0xff, 0xd6,
+	0x07, 0x9b, 0xdc, 0x9b, 0x01, 0x00, 0x00,
 }
diff --git a/proto/ecdsa_go_proto/ecdsa.pb.go b/proto/ecdsa_go_proto/ecdsa.pb.go
index c52ebf3..56190c3 100644
--- a/proto/ecdsa_go_proto/ecdsa.pb.go
+++ b/proto/ecdsa_go_proto/ecdsa.pb.go
@@ -1,39 +1,30 @@
-// Copyright 2017 Google Inc.
-
+// Copyright 2018 Google Inc.
+//
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
-
+//
 //      http://www.apache.org/licenses/LICENSE-2.0
-
+//
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+//
 ////////////////////////////////////////////////////////////////////////////////
 
 // Code generated by protoc-gen-go. DO NOT EDIT.
-// source: proto/ecdsa.proto
+// source: third_party/tink/proto/ecdsa.proto
 
-/*
-Package ecdsa_proto is a generated protocol buffer package.
-
-It is generated from these files:
-	proto/ecdsa.proto
-
-It has these top-level messages:
-	EcdsaParams
-	EcdsaPublicKey
-	EcdsaPrivateKey
-	EcdsaKeyFormat
-*/
 package ecdsa_go_proto
 
-import proto "github.com/golang/protobuf/proto"
-import fmt "fmt"
-import math "math"
-import google_crypto_tink "github.com/google/tink/proto/common_go_proto"
+import (
+	fmt "fmt"
+	proto "github.com/golang/protobuf/proto"
+	common_go_proto "github.com/google/tink/proto/common_go_proto"
+	math "math"
+)
 
 // Reference imports to suppress errors if they are not otherwise used.
 var _ = proto.Marshal
@@ -44,7 +35,7 @@
 // is compatible with the proto package it is being compiled against.
 // A compilation error at this line likely means your copy of the
 // proto package needs to be updated.
-const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
 
 type EcdsaSignatureEncoding int32
 
@@ -68,6 +59,7 @@
 	1: "IEEE_P1363",
 	2: "DER",
 }
+
 var EcdsaSignatureEncoding_value = map[string]int32{
 	"UNKNOWN_ENCODING": 0,
 	"IEEE_P1363":       1,
@@ -77,35 +69,61 @@
 func (x EcdsaSignatureEncoding) String() string {
 	return proto.EnumName(EcdsaSignatureEncoding_name, int32(x))
 }
-func (EcdsaSignatureEncoding) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+
+func (EcdsaSignatureEncoding) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_8eef580f8138be98, []int{0}
+}
 
 // Protos for Ecdsa.
 type EcdsaParams struct {
 	// Required.
-	HashType google_crypto_tink.HashType `protobuf:"varint,1,opt,name=hash_type,json=hashType,enum=google.crypto.tink.HashType" json:"hash_type,omitempty"`
+	HashType common_go_proto.HashType `protobuf:"varint,1,opt,name=hash_type,json=hashType,proto3,enum=google.crypto.tink.HashType" json:"hash_type,omitempty"`
 	// Required.
-	Curve google_crypto_tink.EllipticCurveType `protobuf:"varint,2,opt,name=curve,enum=google.crypto.tink.EllipticCurveType" json:"curve,omitempty"`
+	Curve common_go_proto.EllipticCurveType `protobuf:"varint,2,opt,name=curve,proto3,enum=google.crypto.tink.EllipticCurveType" json:"curve,omitempty"`
 	// Required.
-	Encoding EcdsaSignatureEncoding `protobuf:"varint,3,opt,name=encoding,enum=google.crypto.tink.EcdsaSignatureEncoding" json:"encoding,omitempty"`
+	Encoding             EcdsaSignatureEncoding `protobuf:"varint,3,opt,name=encoding,proto3,enum=google.crypto.tink.EcdsaSignatureEncoding" json:"encoding,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}               `json:"-"`
+	XXX_unrecognized     []byte                 `json:"-"`
+	XXX_sizecache        int32                  `json:"-"`
 }
 
-func (m *EcdsaParams) Reset()                    { *m = EcdsaParams{} }
-func (m *EcdsaParams) String() string            { return proto.CompactTextString(m) }
-func (*EcdsaParams) ProtoMessage()               {}
-func (*EcdsaParams) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+func (m *EcdsaParams) Reset()         { *m = EcdsaParams{} }
+func (m *EcdsaParams) String() string { return proto.CompactTextString(m) }
+func (*EcdsaParams) ProtoMessage()    {}
+func (*EcdsaParams) Descriptor() ([]byte, []int) {
+	return fileDescriptor_8eef580f8138be98, []int{0}
+}
 
-func (m *EcdsaParams) GetHashType() google_crypto_tink.HashType {
+func (m *EcdsaParams) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_EcdsaParams.Unmarshal(m, b)
+}
+func (m *EcdsaParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_EcdsaParams.Marshal(b, m, deterministic)
+}
+func (m *EcdsaParams) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_EcdsaParams.Merge(m, src)
+}
+func (m *EcdsaParams) XXX_Size() int {
+	return xxx_messageInfo_EcdsaParams.Size(m)
+}
+func (m *EcdsaParams) XXX_DiscardUnknown() {
+	xxx_messageInfo_EcdsaParams.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_EcdsaParams proto.InternalMessageInfo
+
+func (m *EcdsaParams) GetHashType() common_go_proto.HashType {
 	if m != nil {
 		return m.HashType
 	}
-	return google_crypto_tink.HashType_UNKNOWN_HASH
+	return common_go_proto.HashType_UNKNOWN_HASH
 }
 
-func (m *EcdsaParams) GetCurve() google_crypto_tink.EllipticCurveType {
+func (m *EcdsaParams) GetCurve() common_go_proto.EllipticCurveType {
 	if m != nil {
 		return m.Curve
 	}
-	return google_crypto_tink.EllipticCurveType_UNKNOWN_CURVE
+	return common_go_proto.EllipticCurveType_UNKNOWN_CURVE
 }
 
 func (m *EcdsaParams) GetEncoding() EcdsaSignatureEncoding {
@@ -118,9 +136,9 @@
 // key_type: type.googleapis.com/google.crypto.tink.EcdsaPublicKey
 type EcdsaPublicKey struct {
 	// Required.
-	Version uint32 `protobuf:"varint,1,opt,name=version" json:"version,omitempty"`
+	Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
 	// Required.
-	Params *EcdsaParams `protobuf:"bytes,2,opt,name=params" json:"params,omitempty"`
+	Params *EcdsaParams `protobuf:"bytes,2,opt,name=params,proto3" json:"params,omitempty"`
 	// Affine coordinates of the public key in bigendian representation. The
 	// public key is a point (x, y) on the curve defined by params.curve. For
 	// ECDH, it is crucial to verify whether the public key point (x, y) is on the
@@ -128,13 +146,36 @@
 	// Required.
 	X []byte `protobuf:"bytes,3,opt,name=x,proto3" json:"x,omitempty"`
 	// Required.
-	Y []byte `protobuf:"bytes,4,opt,name=y,proto3" json:"y,omitempty"`
+	Y                    []byte   `protobuf:"bytes,4,opt,name=y,proto3" json:"y,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
 }
 
-func (m *EcdsaPublicKey) Reset()                    { *m = EcdsaPublicKey{} }
-func (m *EcdsaPublicKey) String() string            { return proto.CompactTextString(m) }
-func (*EcdsaPublicKey) ProtoMessage()               {}
-func (*EcdsaPublicKey) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
+func (m *EcdsaPublicKey) Reset()         { *m = EcdsaPublicKey{} }
+func (m *EcdsaPublicKey) String() string { return proto.CompactTextString(m) }
+func (*EcdsaPublicKey) ProtoMessage()    {}
+func (*EcdsaPublicKey) Descriptor() ([]byte, []int) {
+	return fileDescriptor_8eef580f8138be98, []int{1}
+}
+
+func (m *EcdsaPublicKey) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_EcdsaPublicKey.Unmarshal(m, b)
+}
+func (m *EcdsaPublicKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_EcdsaPublicKey.Marshal(b, m, deterministic)
+}
+func (m *EcdsaPublicKey) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_EcdsaPublicKey.Merge(m, src)
+}
+func (m *EcdsaPublicKey) XXX_Size() int {
+	return xxx_messageInfo_EcdsaPublicKey.Size(m)
+}
+func (m *EcdsaPublicKey) XXX_DiscardUnknown() {
+	xxx_messageInfo_EcdsaPublicKey.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_EcdsaPublicKey proto.InternalMessageInfo
 
 func (m *EcdsaPublicKey) GetVersion() uint32 {
 	if m != nil {
@@ -167,18 +208,41 @@
 // key_type: type.googleapis.com/google.crypto.tink.EcdsaPrivateKey
 type EcdsaPrivateKey struct {
 	// Required.
-	Version uint32 `protobuf:"varint,1,opt,name=version" json:"version,omitempty"`
+	Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
 	// Required.
-	PublicKey *EcdsaPublicKey `protobuf:"bytes,2,opt,name=public_key,json=publicKey" json:"public_key,omitempty"`
+	PublicKey *EcdsaPublicKey `protobuf:"bytes,2,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"`
 	// Unsigned big integer in bigendian representation.
 	// Required.
-	KeyValue []byte `protobuf:"bytes,3,opt,name=key_value,json=keyValue,proto3" json:"key_value,omitempty"`
+	KeyValue             []byte   `protobuf:"bytes,3,opt,name=key_value,json=keyValue,proto3" json:"key_value,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
 }
 
-func (m *EcdsaPrivateKey) Reset()                    { *m = EcdsaPrivateKey{} }
-func (m *EcdsaPrivateKey) String() string            { return proto.CompactTextString(m) }
-func (*EcdsaPrivateKey) ProtoMessage()               {}
-func (*EcdsaPrivateKey) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
+func (m *EcdsaPrivateKey) Reset()         { *m = EcdsaPrivateKey{} }
+func (m *EcdsaPrivateKey) String() string { return proto.CompactTextString(m) }
+func (*EcdsaPrivateKey) ProtoMessage()    {}
+func (*EcdsaPrivateKey) Descriptor() ([]byte, []int) {
+	return fileDescriptor_8eef580f8138be98, []int{2}
+}
+
+func (m *EcdsaPrivateKey) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_EcdsaPrivateKey.Unmarshal(m, b)
+}
+func (m *EcdsaPrivateKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_EcdsaPrivateKey.Marshal(b, m, deterministic)
+}
+func (m *EcdsaPrivateKey) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_EcdsaPrivateKey.Merge(m, src)
+}
+func (m *EcdsaPrivateKey) XXX_Size() int {
+	return xxx_messageInfo_EcdsaPrivateKey.Size(m)
+}
+func (m *EcdsaPrivateKey) XXX_DiscardUnknown() {
+	xxx_messageInfo_EcdsaPrivateKey.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_EcdsaPrivateKey proto.InternalMessageInfo
 
 func (m *EcdsaPrivateKey) GetVersion() uint32 {
 	if m != nil {
@@ -203,13 +267,36 @@
 
 type EcdsaKeyFormat struct {
 	// Required.
-	Params *EcdsaParams `protobuf:"bytes,2,opt,name=params" json:"params,omitempty"`
+	Params               *EcdsaParams `protobuf:"bytes,2,opt,name=params,proto3" json:"params,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}     `json:"-"`
+	XXX_unrecognized     []byte       `json:"-"`
+	XXX_sizecache        int32        `json:"-"`
 }
 
-func (m *EcdsaKeyFormat) Reset()                    { *m = EcdsaKeyFormat{} }
-func (m *EcdsaKeyFormat) String() string            { return proto.CompactTextString(m) }
-func (*EcdsaKeyFormat) ProtoMessage()               {}
-func (*EcdsaKeyFormat) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
+func (m *EcdsaKeyFormat) Reset()         { *m = EcdsaKeyFormat{} }
+func (m *EcdsaKeyFormat) String() string { return proto.CompactTextString(m) }
+func (*EcdsaKeyFormat) ProtoMessage()    {}
+func (*EcdsaKeyFormat) Descriptor() ([]byte, []int) {
+	return fileDescriptor_8eef580f8138be98, []int{3}
+}
+
+func (m *EcdsaKeyFormat) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_EcdsaKeyFormat.Unmarshal(m, b)
+}
+func (m *EcdsaKeyFormat) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_EcdsaKeyFormat.Marshal(b, m, deterministic)
+}
+func (m *EcdsaKeyFormat) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_EcdsaKeyFormat.Merge(m, src)
+}
+func (m *EcdsaKeyFormat) XXX_Size() int {
+	return xxx_messageInfo_EcdsaKeyFormat.Size(m)
+}
+func (m *EcdsaKeyFormat) XXX_DiscardUnknown() {
+	xxx_messageInfo_EcdsaKeyFormat.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_EcdsaKeyFormat proto.InternalMessageInfo
 
 func (m *EcdsaKeyFormat) GetParams() *EcdsaParams {
 	if m != nil {
@@ -219,42 +306,43 @@
 }
 
 func init() {
+	proto.RegisterEnum("google.crypto.tink.EcdsaSignatureEncoding", EcdsaSignatureEncoding_name, EcdsaSignatureEncoding_value)
 	proto.RegisterType((*EcdsaParams)(nil), "google.crypto.tink.EcdsaParams")
 	proto.RegisterType((*EcdsaPublicKey)(nil), "google.crypto.tink.EcdsaPublicKey")
 	proto.RegisterType((*EcdsaPrivateKey)(nil), "google.crypto.tink.EcdsaPrivateKey")
 	proto.RegisterType((*EcdsaKeyFormat)(nil), "google.crypto.tink.EcdsaKeyFormat")
-	proto.RegisterEnum("google.crypto.tink.EcdsaSignatureEncoding", EcdsaSignatureEncoding_name, EcdsaSignatureEncoding_value)
 }
 
-func init() { proto.RegisterFile("proto/ecdsa.proto", fileDescriptor0) }
+func init() { proto.RegisterFile("proto/ecdsa.proto", fileDescriptor_8eef580f8138be98) }
 
-var fileDescriptor0 = []byte{
-	// 426 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x92, 0xd1, 0x6a, 0xd4, 0x40,
-	0x14, 0x86, 0x9d, 0xad, 0x6e, 0x77, 0x4f, 0xeb, 0xba, 0x0e, 0x22, 0x41, 0x0b, 0x4a, 0x40, 0x28,
-	0xbd, 0xc8, 0x62, 0x17, 0x14, 0xf1, 0xca, 0xb6, 0xd3, 0xba, 0x2c, 0xa4, 0x4b, 0x5a, 0x15, 0xbc,
-	0x09, 0xb3, 0xd3, 0x21, 0x19, 0x36, 0xc9, 0x0c, 0x93, 0xc9, 0xd2, 0xb9, 0xf2, 0x01, 0x7c, 0x0b,
-	0xdf, 0xc7, 0x77, 0x92, 0xcc, 0xa4, 0x45, 0x70, 0xeb, 0x45, 0xef, 0xce, 0x4f, 0xfe, 0x2f, 0xf3,
-	0xff, 0x87, 0x03, 0x4f, 0x95, 0x96, 0x46, 0x4e, 0x38, 0xbb, 0xaa, 0x69, 0xe4, 0x66, 0x8c, 0x33,
-	0x29, 0xb3, 0x82, 0x47, 0x4c, 0x5b, 0x65, 0x64, 0x64, 0x44, 0xb5, 0x7a, 0x81, 0xbd, 0x8d, 0xc9,
-	0xb2, 0x94, 0x95, 0xf7, 0x85, 0xbf, 0x11, 0xec, 0x90, 0x96, 0x5b, 0x50, 0x4d, 0xcb, 0x1a, 0x7f,
-	0x80, 0x61, 0x4e, 0xeb, 0x3c, 0x35, 0x56, 0xf1, 0x00, 0xbd, 0x46, 0xfb, 0xa3, 0xc3, 0xbd, 0xe8,
-	0xdf, 0x7f, 0x45, 0x9f, 0x69, 0x9d, 0x5f, 0x5a, 0xc5, 0x93, 0x41, 0xde, 0x4d, 0xf8, 0x23, 0x3c,
-	0x62, 0x8d, 0x5e, 0xf3, 0xa0, 0xe7, 0xb0, 0x37, 0x9b, 0x30, 0x52, 0x14, 0x42, 0x19, 0xc1, 0x8e,
-	0x5b, 0xa3, 0xe3, 0x3d, 0x83, 0x4f, 0x61, 0xc0, 0x2b, 0x26, 0xaf, 0x44, 0x95, 0x05, 0x5b, 0x8e,
-	0x3f, 0xd8, 0xc8, 0xb7, 0x51, 0x2f, 0x44, 0x56, 0x51, 0xd3, 0x68, 0x4e, 0x3a, 0x22, 0xb9, 0x65,
-	0xc3, 0x1f, 0x30, 0xf2, 0x75, 0x9a, 0x65, 0x21, 0xd8, 0x9c, 0x5b, 0x1c, 0xc0, 0xf6, 0x9a, 0xeb,
-	0x5a, 0xc8, 0xca, 0xf5, 0x79, 0x9c, 0xdc, 0x48, 0xfc, 0x1e, 0xfa, 0xca, 0xb5, 0x76, 0x89, 0x77,
-	0x0e, 0x5f, 0xdd, 0xf9, 0xa2, 0x5f, 0x4e, 0xd2, 0xd9, 0xf1, 0x2e, 0xa0, 0x6b, 0x97, 0x72, 0x37,
-	0x41, 0xd7, 0xad, 0xb2, 0xc1, 0x43, 0xaf, 0x6c, 0xf8, 0x13, 0xc1, 0x13, 0xcf, 0x68, 0xb1, 0xa6,
-	0x86, 0xff, 0x3f, 0xc2, 0x27, 0x00, 0xe5, 0x92, 0xa6, 0x2b, 0x6e, 0xbb, 0x18, 0xe1, 0xdd, 0x31,
-	0x6e, 0x4a, 0x25, 0x43, 0x75, 0xdb, 0xef, 0x25, 0x0c, 0x57, 0xdc, 0xa6, 0x6b, 0x5a, 0x34, 0xbc,
-	0x0b, 0x35, 0x58, 0x71, 0xfb, 0xb5, 0xd5, 0xe1, 0xac, 0x5b, 0xc7, 0x9c, 0xdb, 0x53, 0xa9, 0x4b,
-	0x6a, 0xee, 0x5d, 0xfa, 0xe0, 0x0c, 0x9e, 0x6f, 0xde, 0x3e, 0x7e, 0x06, 0xe3, 0x2f, 0xf1, 0x3c,
-	0x3e, 0xff, 0x16, 0xa7, 0x24, 0x3e, 0x3e, 0x3f, 0x99, 0xc5, 0x67, 0xe3, 0x07, 0x78, 0x04, 0x30,
-	0x23, 0x84, 0xa4, 0x8b, 0xb7, 0xd3, 0x77, 0xd3, 0x31, 0xc2, 0xdb, 0xb0, 0x75, 0x42, 0x92, 0x71,
-	0xef, 0xe8, 0x02, 0xf6, 0x98, 0x2c, 0x37, 0x3d, 0xeb, 0x4e, 0x72, 0x81, 0xbe, 0xef, 0x67, 0xc2,
-	0xe4, 0xcd, 0x32, 0x62, 0xb2, 0x9c, 0x78, 0xdb, 0xa4, 0xfd, 0x3e, 0xf9, 0xeb, 0xcc, 0x53, 0x37,
-	0xff, 0xea, 0xf5, 0x2f, 0x67, 0xf1, 0x7c, 0x71, 0xb4, 0xec, 0x3b, 0x3d, 0xfd, 0x13, 0x00, 0x00,
-	0xff, 0xff, 0xd2, 0x43, 0xbe, 0xe7, 0x0b, 0x03, 0x00, 0x00,
+var fileDescriptor_8eef580f8138be98 = []byte{
+	// 435 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x92, 0xd1, 0x6a, 0xdb, 0x30,
+	0x14, 0x86, 0xe7, 0x74, 0x4b, 0x93, 0xd3, 0x2e, 0x0b, 0x62, 0x0c, 0xb3, 0x15, 0x36, 0x3c, 0x06,
+	0xa3, 0x03, 0x87, 0x35, 0xb0, 0x31, 0x76, 0xb5, 0xb6, 0x6a, 0x17, 0x02, 0x6e, 0xf0, 0xba, 0x0e,
+	0x76, 0x23, 0x14, 0x45, 0xd8, 0x22, 0xb6, 0x25, 0x64, 0x39, 0x54, 0x57, 0x7b, 0x80, 0xbd, 0xc5,
+	0xde, 0x67, 0xef, 0x34, 0x2c, 0xbb, 0xbd, 0xa9, 0xb3, 0x8b, 0xde, 0xe9, 0x87, 0xf3, 0x49, 0xdf,
+	0x7f, 0x10, 0x04, 0x26, 0x15, 0x7a, 0x45, 0x14, 0xd5, 0xc6, 0x4e, 0x8c, 0x28, 0xd6, 0x13, 0xa5,
+	0xa5, 0x91, 0x13, 0xce, 0x56, 0x25, 0x0d, 0xdd, 0x19, 0xa1, 0x44, 0xca, 0x24, 0xe3, 0x21, 0xd3,
+	0x56, 0x19, 0x19, 0xd6, 0x53, 0xcf, 0x5f, 0x6f, 0xe1, 0x98, 0xcc, 0x73, 0x59, 0x34, 0x60, 0xf0,
+	0xd7, 0x83, 0x3d, 0x5c, 0x5f, 0xb4, 0xa0, 0x9a, 0xe6, 0x25, 0xfa, 0x04, 0xc3, 0x94, 0x96, 0x29,
+	0x31, 0x56, 0x71, 0xdf, 0x7b, 0xe5, 0xbd, 0x1d, 0x1d, 0x1d, 0x84, 0x77, 0x2f, 0x0f, 0xbf, 0xd2,
+	0x32, 0xbd, 0xb4, 0x8a, 0xc7, 0x83, 0xb4, 0x3d, 0xa1, 0xcf, 0xf0, 0x88, 0x55, 0x7a, 0xc3, 0xfd,
+	0x9e, 0xc3, 0xde, 0x74, 0x61, 0x38, 0xcb, 0x84, 0x32, 0x82, 0x9d, 0xd4, 0x83, 0x8e, 0x6f, 0x18,
+	0x74, 0x06, 0x03, 0x5e, 0x30, 0xb9, 0x12, 0x45, 0xe2, 0xef, 0x38, 0xfe, 0xb0, 0x93, 0xaf, 0x55,
+	0xbf, 0x89, 0xa4, 0xa0, 0xa6, 0xd2, 0x1c, 0xb7, 0x44, 0x7c, 0xcb, 0x06, 0xbf, 0x60, 0xd4, 0xd4,
+	0xa9, 0x96, 0x99, 0x60, 0x73, 0x6e, 0x91, 0x0f, 0xbb, 0x1b, 0xae, 0x4b, 0x21, 0x0b, 0xd7, 0xe7,
+	0x71, 0x7c, 0x13, 0xd1, 0x47, 0xe8, 0x2b, 0xd7, 0xda, 0x19, 0xef, 0x1d, 0xbd, 0xdc, 0xfa, 0x62,
+	0xb3, 0x9c, 0xb8, 0x1d, 0x47, 0xfb, 0xe0, 0x5d, 0x3b, 0xcb, 0xfd, 0xd8, 0xbb, 0xae, 0x93, 0xf5,
+	0x1f, 0x36, 0xc9, 0x06, 0xbf, 0x3d, 0x78, 0xd2, 0x30, 0x5a, 0x6c, 0xa8, 0xe1, 0xff, 0x57, 0xf8,
+	0x02, 0xa0, 0x9c, 0x29, 0x59, 0x73, 0xdb, 0x6a, 0x04, 0xdb, 0x35, 0x6e, 0x4a, 0xc5, 0x43, 0x75,
+	0xdb, 0xef, 0x05, 0x0c, 0xd7, 0xdc, 0x92, 0x0d, 0xcd, 0x2a, 0xde, 0x4a, 0x0d, 0xd6, 0xdc, 0x5e,
+	0xd5, 0x39, 0x98, 0xb5, 0xeb, 0x98, 0x73, 0x7b, 0x26, 0x75, 0x4e, 0xcd, 0xbd, 0x4b, 0x1f, 0x9e,
+	0xc3, 0xb3, 0xee, 0xed, 0xa3, 0xa7, 0x30, 0xfe, 0x1e, 0xcd, 0xa3, 0x8b, 0x1f, 0x11, 0xc1, 0xd1,
+	0xc9, 0xc5, 0xe9, 0x2c, 0x3a, 0x1f, 0x3f, 0x40, 0x23, 0x80, 0x19, 0xc6, 0x98, 0x2c, 0xde, 0x4f,
+	0x3f, 0x4c, 0xc7, 0x1e, 0xda, 0x85, 0x9d, 0x53, 0x1c, 0x8f, 0x7b, 0xc7, 0x57, 0x70, 0xc0, 0x64,
+	0xde, 0xf5, 0xac, 0xfb, 0x92, 0x0b, 0xef, 0xe7, 0xbb, 0x44, 0x98, 0xb4, 0x5a, 0x86, 0x4c, 0xe6,
+	0x93, 0x66, 0xec, 0xce, 0xbf, 0x27, 0x89, 0x24, 0x2e, 0xfe, 0xe9, 0xf5, 0x2f, 0x67, 0xd1, 0x7c,
+	0x71, 0xbc, 0xec, 0xbb, 0x3c, 0xfd, 0x17, 0x00, 0x00, 0xff, 0xff, 0x40, 0x46, 0x00, 0xdd, 0x30,
+	0x03, 0x00, 0x00,
 }
diff --git a/proto/ed25519_go_proto/ed25519.pb.go b/proto/ed25519_go_proto/ed25519.pb.go
new file mode 100644
index 0000000..94daef1
--- /dev/null
+++ b/proto/ed25519_go_proto/ed25519.pb.go
@@ -0,0 +1,178 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: third_party/tink/proto/ed25519.proto
+
+package ed25519_go_proto
+
+import (
+	fmt "fmt"
+	proto "github.com/golang/protobuf/proto"
+	math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
+// key_type: type.googleapis.com/google.crypto.tink.Ed25519PublicKey
+type Ed25519PublicKey struct {
+	// Required.
+	Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
+	// The public key is 32 bytes, encoded according to
+	// https://tools.ietf.org/html/rfc8032#section-5.1.2.
+	// Required.
+	KeyValue             []byte   `protobuf:"bytes,2,opt,name=key_value,json=keyValue,proto3" json:"key_value,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *Ed25519PublicKey) Reset()         { *m = Ed25519PublicKey{} }
+func (m *Ed25519PublicKey) String() string { return proto.CompactTextString(m) }
+func (*Ed25519PublicKey) ProtoMessage()    {}
+func (*Ed25519PublicKey) Descriptor() ([]byte, []int) {
+	return fileDescriptor_677c38422e9f421e, []int{0}
+}
+
+func (m *Ed25519PublicKey) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Ed25519PublicKey.Unmarshal(m, b)
+}
+func (m *Ed25519PublicKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Ed25519PublicKey.Marshal(b, m, deterministic)
+}
+func (m *Ed25519PublicKey) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Ed25519PublicKey.Merge(m, src)
+}
+func (m *Ed25519PublicKey) XXX_Size() int {
+	return xxx_messageInfo_Ed25519PublicKey.Size(m)
+}
+func (m *Ed25519PublicKey) XXX_DiscardUnknown() {
+	xxx_messageInfo_Ed25519PublicKey.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Ed25519PublicKey proto.InternalMessageInfo
+
+func (m *Ed25519PublicKey) GetVersion() uint32 {
+	if m != nil {
+		return m.Version
+	}
+	return 0
+}
+
+func (m *Ed25519PublicKey) GetKeyValue() []byte {
+	if m != nil {
+		return m.KeyValue
+	}
+	return nil
+}
+
+// key_type: type.googleapis.com/google.crypto.tink.Ed25519PrivateKey
+type Ed25519PrivateKey struct {
+	// Required.
+	Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
+	// The private key is 32 bytes of cryptographically secure random data.
+	// See https://tools.ietf.org/html/rfc8032#section-5.1.5.
+	// Required.
+	KeyValue []byte `protobuf:"bytes,2,opt,name=key_value,json=keyValue,proto3" json:"key_value,omitempty"`
+	// The corresponding public key.
+	PublicKey            *Ed25519PublicKey `protobuf:"bytes,3,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}          `json:"-"`
+	XXX_unrecognized     []byte            `json:"-"`
+	XXX_sizecache        int32             `json:"-"`
+}
+
+func (m *Ed25519PrivateKey) Reset()         { *m = Ed25519PrivateKey{} }
+func (m *Ed25519PrivateKey) String() string { return proto.CompactTextString(m) }
+func (*Ed25519PrivateKey) ProtoMessage()    {}
+func (*Ed25519PrivateKey) Descriptor() ([]byte, []int) {
+	return fileDescriptor_677c38422e9f421e, []int{1}
+}
+
+func (m *Ed25519PrivateKey) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Ed25519PrivateKey.Unmarshal(m, b)
+}
+func (m *Ed25519PrivateKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Ed25519PrivateKey.Marshal(b, m, deterministic)
+}
+func (m *Ed25519PrivateKey) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Ed25519PrivateKey.Merge(m, src)
+}
+func (m *Ed25519PrivateKey) XXX_Size() int {
+	return xxx_messageInfo_Ed25519PrivateKey.Size(m)
+}
+func (m *Ed25519PrivateKey) XXX_DiscardUnknown() {
+	xxx_messageInfo_Ed25519PrivateKey.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Ed25519PrivateKey proto.InternalMessageInfo
+
+func (m *Ed25519PrivateKey) GetVersion() uint32 {
+	if m != nil {
+		return m.Version
+	}
+	return 0
+}
+
+func (m *Ed25519PrivateKey) GetKeyValue() []byte {
+	if m != nil {
+		return m.KeyValue
+	}
+	return nil
+}
+
+func (m *Ed25519PrivateKey) GetPublicKey() *Ed25519PublicKey {
+	if m != nil {
+		return m.PublicKey
+	}
+	return nil
+}
+
+func init() {
+	proto.RegisterType((*Ed25519PublicKey)(nil), "google.crypto.tink.Ed25519PublicKey")
+	proto.RegisterType((*Ed25519PrivateKey)(nil), "google.crypto.tink.Ed25519PrivateKey")
+}
+
+func init() {
+	proto.RegisterFile("proto/ed25519.proto", fileDescriptor_677c38422e9f421e)
+}
+
+var fileDescriptor_677c38422e9f421e = []byte{
+	// 237 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x29, 0xc9, 0xc8, 0x2c,
+	0x4a, 0x89, 0x2f, 0x48, 0x2c, 0x2a, 0xa9, 0xd4, 0x2f, 0xc9, 0xcc, 0xcb, 0xd6, 0x2f, 0x28, 0xca,
+	0x2f, 0xc9, 0xd7, 0x4f, 0x4d, 0x31, 0x32, 0x35, 0x35, 0xb4, 0xd4, 0x03, 0xf3, 0x84, 0x84, 0xd2,
+	0xf3, 0xf3, 0xd3, 0x73, 0x52, 0xf5, 0x92, 0x8b, 0x2a, 0x0b, 0x4a, 0xf2, 0xf5, 0x40, 0xea, 0x94,
+	0x3c, 0xb9, 0x04, 0x5c, 0x21, 0x8a, 0x02, 0x4a, 0x93, 0x72, 0x32, 0x93, 0xbd, 0x53, 0x2b, 0x85,
+	0x24, 0xb8, 0xd8, 0xcb, 0x52, 0x8b, 0x8a, 0x33, 0xf3, 0xf3, 0x24, 0x18, 0x15, 0x18, 0x35, 0x78,
+	0x83, 0x60, 0x5c, 0x21, 0x69, 0x2e, 0xce, 0xec, 0xd4, 0xca, 0xf8, 0xb2, 0xc4, 0x9c, 0xd2, 0x54,
+	0x09, 0x26, 0x05, 0x46, 0x0d, 0x9e, 0x20, 0x8e, 0xec, 0xd4, 0xca, 0x30, 0x10, 0x5f, 0xa9, 0x9f,
+	0x91, 0x4b, 0x10, 0x66, 0x56, 0x51, 0x66, 0x59, 0x62, 0x49, 0x2a, 0xf9, 0x86, 0x09, 0x39, 0x73,
+	0x71, 0x15, 0x80, 0x1d, 0x14, 0x9f, 0x9d, 0x5a, 0x29, 0xc1, 0xac, 0xc0, 0xa8, 0xc1, 0x6d, 0xa4,
+	0xa2, 0x87, 0xe9, 0x01, 0x3d, 0x74, 0xd7, 0x07, 0x71, 0x16, 0xc0, 0x98, 0x4e, 0x11, 0x5c, 0x32,
+	0xc9, 0xf9, 0xb9, 0xd8, 0x74, 0x81, 0x03, 0x24, 0x80, 0x31, 0x4a, 0x37, 0x3d, 0xb3, 0x24, 0xa3,
+	0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x1f, 0xa2, 0x0c, 0x4b, 0xf0, 0xc5, 0xa7, 0xe7, 0xc7, 0x83,
+	0x05, 0x16, 0x31, 0xb1, 0x85, 0x78, 0xfa, 0x79, 0x07, 0x38, 0x25, 0xb1, 0x81, 0xf9, 0xc6, 0x80,
+	0x00, 0x00, 0x00, 0xff, 0xff, 0xbd, 0x23, 0x1d, 0x66, 0x79, 0x01, 0x00, 0x00,
+}
diff --git a/go/tink/private_key_manager.go b/proto/empty.proto
similarity index 64%
copy from go/tink/private_key_manager.go
copy to proto/empty.proto
index 7cf169a..757b6e2 100644
--- a/go/tink/private_key_manager.go
+++ b/proto/empty.proto
@@ -1,3 +1,5 @@
+// Copyright 2017 Google Inc.
+//
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
@@ -12,16 +14,13 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-package tink
+syntax = "proto3";
 
-import (
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
-)
+package google.crypto.tink;
 
-// PrivateKeyManager is a special type of KeyManager that understands private key types.
-type PrivateKeyManager interface {
-	KeyManager
+option java_package = "com.google.crypto.tink.proto";
+option java_multiple_files = true;
+option objc_class_prefix = "TINKPB";
+option go_package = "github.com/google/tink/proto/empty_go_proto";
 
-	// GetPublicKeyData extracts the public key data from the private key.
-	GetPublicKeyData(serializedKey []byte) (*tinkpb.KeyData, error)
-}
+message Empty {}
diff --git a/proto/hmac_go_proto/hmac.pb.go b/proto/hmac_go_proto/hmac.pb.go
index 6300d1c..f006653 100644
--- a/proto/hmac_go_proto/hmac.pb.go
+++ b/proto/hmac_go_proto/hmac.pb.go
@@ -1,38 +1,30 @@
-// Copyright 2017 Google Inc.
-
+// Copyright 2018 Google Inc.
+//
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
-
+//
 //      http://www.apache.org/licenses/LICENSE-2.0
-
+//
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+//
 ////////////////////////////////////////////////////////////////////////////////
 
 // Code generated by protoc-gen-go. DO NOT EDIT.
-// source: proto/hmac.proto
+// source: third_party/tink/proto/hmac.proto
 
-/*
-Package hmac_proto is a generated protocol buffer package.
-
-It is generated from these files:
-	proto/hmac.proto
-
-It has these top-level messages:
-	HmacParams
-	HmacKey
-	HmacKeyFormat
-*/
 package hmac_go_proto
 
-import proto "github.com/golang/protobuf/proto"
-import fmt "fmt"
-import math "math"
-import google_crypto_tink "github.com/google/tink/proto/common_go_proto"
+import (
+	fmt "fmt"
+	proto "github.com/golang/protobuf/proto"
+	common_go_proto "github.com/google/tink/proto/common_go_proto"
+	math "math"
+)
 
 // Reference imports to suppress errors if they are not otherwise used.
 var _ = proto.Marshal
@@ -43,23 +35,46 @@
 // is compatible with the proto package it is being compiled against.
 // A compilation error at this line likely means your copy of the
 // proto package needs to be updated.
-const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
 
 type HmacParams struct {
-	Hash    google_crypto_tink.HashType `protobuf:"varint,1,opt,name=hash,enum=google.crypto.tink.HashType" json:"hash,omitempty"`
-	TagSize uint32                      `protobuf:"varint,2,opt,name=tag_size,json=tagSize" json:"tag_size,omitempty"`
+	Hash                 common_go_proto.HashType `protobuf:"varint,1,opt,name=hash,proto3,enum=google.crypto.tink.HashType" json:"hash,omitempty"`
+	TagSize              uint32                   `protobuf:"varint,2,opt,name=tag_size,json=tagSize,proto3" json:"tag_size,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}                 `json:"-"`
+	XXX_unrecognized     []byte                   `json:"-"`
+	XXX_sizecache        int32                    `json:"-"`
 }
 
-func (m *HmacParams) Reset()                    { *m = HmacParams{} }
-func (m *HmacParams) String() string            { return proto.CompactTextString(m) }
-func (*HmacParams) ProtoMessage()               {}
-func (*HmacParams) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+func (m *HmacParams) Reset()         { *m = HmacParams{} }
+func (m *HmacParams) String() string { return proto.CompactTextString(m) }
+func (*HmacParams) ProtoMessage()    {}
+func (*HmacParams) Descriptor() ([]byte, []int) {
+	return fileDescriptor_310803c785e2f4dc, []int{0}
+}
 
-func (m *HmacParams) GetHash() google_crypto_tink.HashType {
+func (m *HmacParams) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_HmacParams.Unmarshal(m, b)
+}
+func (m *HmacParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_HmacParams.Marshal(b, m, deterministic)
+}
+func (m *HmacParams) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_HmacParams.Merge(m, src)
+}
+func (m *HmacParams) XXX_Size() int {
+	return xxx_messageInfo_HmacParams.Size(m)
+}
+func (m *HmacParams) XXX_DiscardUnknown() {
+	xxx_messageInfo_HmacParams.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_HmacParams proto.InternalMessageInfo
+
+func (m *HmacParams) GetHash() common_go_proto.HashType {
 	if m != nil {
 		return m.Hash
 	}
-	return google_crypto_tink.HashType_UNKNOWN_HASH
+	return common_go_proto.HashType_UNKNOWN_HASH
 }
 
 func (m *HmacParams) GetTagSize() uint32 {
@@ -71,15 +86,38 @@
 
 // key_type: type.googleapis.com/google.crypto.tink.HmacKey
 type HmacKey struct {
-	Version  uint32      `protobuf:"varint,1,opt,name=version" json:"version,omitempty"`
-	Params   *HmacParams `protobuf:"bytes,2,opt,name=params" json:"params,omitempty"`
-	KeyValue []byte      `protobuf:"bytes,3,opt,name=key_value,json=keyValue,proto3" json:"key_value,omitempty"`
+	Version              uint32      `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
+	Params               *HmacParams `protobuf:"bytes,2,opt,name=params,proto3" json:"params,omitempty"`
+	KeyValue             []byte      `protobuf:"bytes,3,opt,name=key_value,json=keyValue,proto3" json:"key_value,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
+	XXX_unrecognized     []byte      `json:"-"`
+	XXX_sizecache        int32       `json:"-"`
 }
 
-func (m *HmacKey) Reset()                    { *m = HmacKey{} }
-func (m *HmacKey) String() string            { return proto.CompactTextString(m) }
-func (*HmacKey) ProtoMessage()               {}
-func (*HmacKey) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
+func (m *HmacKey) Reset()         { *m = HmacKey{} }
+func (m *HmacKey) String() string { return proto.CompactTextString(m) }
+func (*HmacKey) ProtoMessage()    {}
+func (*HmacKey) Descriptor() ([]byte, []int) {
+	return fileDescriptor_310803c785e2f4dc, []int{1}
+}
+
+func (m *HmacKey) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_HmacKey.Unmarshal(m, b)
+}
+func (m *HmacKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_HmacKey.Marshal(b, m, deterministic)
+}
+func (m *HmacKey) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_HmacKey.Merge(m, src)
+}
+func (m *HmacKey) XXX_Size() int {
+	return xxx_messageInfo_HmacKey.Size(m)
+}
+func (m *HmacKey) XXX_DiscardUnknown() {
+	xxx_messageInfo_HmacKey.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_HmacKey proto.InternalMessageInfo
 
 func (m *HmacKey) GetVersion() uint32 {
 	if m != nil {
@@ -103,14 +141,37 @@
 }
 
 type HmacKeyFormat struct {
-	Params  *HmacParams `protobuf:"bytes,1,opt,name=params" json:"params,omitempty"`
-	KeySize uint32      `protobuf:"varint,2,opt,name=key_size,json=keySize" json:"key_size,omitempty"`
+	Params               *HmacParams `protobuf:"bytes,1,opt,name=params,proto3" json:"params,omitempty"`
+	KeySize              uint32      `protobuf:"varint,2,opt,name=key_size,json=keySize,proto3" json:"key_size,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
+	XXX_unrecognized     []byte      `json:"-"`
+	XXX_sizecache        int32       `json:"-"`
 }
 
-func (m *HmacKeyFormat) Reset()                    { *m = HmacKeyFormat{} }
-func (m *HmacKeyFormat) String() string            { return proto.CompactTextString(m) }
-func (*HmacKeyFormat) ProtoMessage()               {}
-func (*HmacKeyFormat) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
+func (m *HmacKeyFormat) Reset()         { *m = HmacKeyFormat{} }
+func (m *HmacKeyFormat) String() string { return proto.CompactTextString(m) }
+func (*HmacKeyFormat) ProtoMessage()    {}
+func (*HmacKeyFormat) Descriptor() ([]byte, []int) {
+	return fileDescriptor_310803c785e2f4dc, []int{2}
+}
+
+func (m *HmacKeyFormat) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_HmacKeyFormat.Unmarshal(m, b)
+}
+func (m *HmacKeyFormat) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_HmacKeyFormat.Marshal(b, m, deterministic)
+}
+func (m *HmacKeyFormat) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_HmacKeyFormat.Merge(m, src)
+}
+func (m *HmacKeyFormat) XXX_Size() int {
+	return xxx_messageInfo_HmacKeyFormat.Size(m)
+}
+func (m *HmacKeyFormat) XXX_DiscardUnknown() {
+	xxx_messageInfo_HmacKeyFormat.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_HmacKeyFormat proto.InternalMessageInfo
 
 func (m *HmacKeyFormat) GetParams() *HmacParams {
 	if m != nil {
@@ -132,27 +193,27 @@
 	proto.RegisterType((*HmacKeyFormat)(nil), "google.crypto.tink.HmacKeyFormat")
 }
 
-func init() { proto.RegisterFile("proto/hmac.proto", fileDescriptor0) }
+func init() { proto.RegisterFile("proto/hmac.proto", fileDescriptor_310803c785e2f4dc) }
 
-var fileDescriptor0 = []byte{
-	// 291 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x91, 0x4f, 0x4b, 0xfb, 0x30,
-	0x18, 0xc7, 0xc9, 0x7e, 0x3f, 0xda, 0xf9, 0x68, 0x45, 0x72, 0xea, 0x74, 0x48, 0xe9, 0xc5, 0x9e,
-	0x52, 0x99, 0xe0, 0x0b, 0xd8, 0x41, 0x26, 0x03, 0x29, 0x71, 0x08, 0x7a, 0x29, 0x69, 0x08, 0x6d,
-	0xe9, 0xd2, 0x94, 0x36, 0x1b, 0x66, 0x2f, 0xc7, 0x57, 0x2a, 0x4d, 0xe7, 0x3f, 0xdc, 0xc5, 0xdb,
-	0xf3, 0xa5, 0xdf, 0x7e, 0x3e, 0x79, 0x12, 0x38, 0x6b, 0x5a, 0xa5, 0x55, 0x5c, 0x48, 0xc6, 0x89,
-	0x1d, 0x31, 0xce, 0x95, 0xca, 0xd7, 0x82, 0xf0, 0xd6, 0x34, 0x5a, 0x11, 0x5d, 0xd6, 0xd5, 0x39,
-	0x1e, 0x5a, 0x5c, 0x49, 0xa9, 0xea, 0xa1, 0x17, 0x3e, 0x03, 0x2c, 0x24, 0xe3, 0x09, 0x6b, 0x99,
-	0xec, 0xf0, 0x35, 0xfc, 0x2f, 0x58, 0x57, 0xf8, 0x28, 0x40, 0xd1, 0xe9, 0x6c, 0x4a, 0x7e, 0x43,
-	0xc8, 0x82, 0x75, 0xc5, 0xca, 0x34, 0x82, 0xda, 0x26, 0x9e, 0xc0, 0x58, 0xb3, 0x3c, 0xed, 0xca,
-	0x9d, 0xf0, 0x47, 0x01, 0x8a, 0x3c, 0xea, 0x6a, 0x96, 0x3f, 0x96, 0x3b, 0x11, 0xbe, 0x82, 0xdb,
-	0xa3, 0x97, 0xc2, 0x60, 0x1f, 0xdc, 0xad, 0x68, 0xbb, 0x52, 0xd5, 0x16, 0xed, 0xd1, 0x8f, 0x88,
-	0x6f, 0xc1, 0x69, 0xac, 0xdb, 0xfe, 0x7d, 0x3c, 0xbb, 0x3c, 0xe8, 0xfc, 0x3c, 0x21, 0xdd, 0xb7,
-	0xf1, 0x05, 0x1c, 0x55, 0xc2, 0xa4, 0x5b, 0xb6, 0xde, 0x08, 0xff, 0x5f, 0x80, 0xa2, 0x13, 0x3a,
-	0xae, 0x84, 0x79, 0xea, 0x73, 0x98, 0x81, 0xb7, 0x37, 0xdf, 0xa9, 0x56, 0x32, 0xfd, 0xcd, 0x82,
-	0xfe, 0x64, 0x99, 0x40, 0x0f, 0xfd, 0xb1, 0x5d, 0x25, 0x4c, 0xbf, 0xdd, 0x9c, 0xc2, 0x94, 0x2b,
-	0x79, 0x88, 0x63, 0x2f, 0x36, 0x41, 0x2f, 0x57, 0x79, 0xa9, 0x8b, 0x4d, 0x46, 0xb8, 0x92, 0xf1,
-	0x50, 0x8b, 0xfb, 0xef, 0xf1, 0xd7, 0x5b, 0xa5, 0x76, 0x7c, 0x1b, 0x39, 0xab, 0xfb, 0x87, 0x65,
-	0x32, 0xcf, 0x1c, 0x9b, 0x6f, 0xde, 0x03, 0x00, 0x00, 0xff, 0xff, 0x3b, 0x7f, 0xac, 0x9f, 0xcf,
-	0x01, 0x00, 0x00,
+var fileDescriptor_310803c785e2f4dc = []byte{
+	// 303 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x91, 0xcd, 0x4e, 0x32, 0x31,
+	0x14, 0x86, 0x33, 0x7c, 0x5f, 0x00, 0x8f, 0xe2, 0xa2, 0xab, 0x41, 0x89, 0x41, 0xdc, 0x10, 0x17,
+	0x1d, 0x83, 0x89, 0x17, 0xc0, 0xc2, 0x60, 0x48, 0x0c, 0x19, 0xd1, 0x44, 0x37, 0x93, 0x52, 0x9b,
+	0xb6, 0x19, 0xca, 0x99, 0x74, 0x0a, 0xb1, 0x5c, 0x8e, 0x57, 0x6a, 0xa6, 0xa0, 0xf1, 0x07, 0x17,
+	0xee, 0xfa, 0x26, 0xef, 0x79, 0x9e, 0x9e, 0x16, 0x4e, 0x9d, 0xd2, 0xf6, 0x39, 0x2b, 0x98, 0x75,
+	0x3e, 0x71, 0x7a, 0x91, 0x27, 0x85, 0x45, 0x87, 0x89, 0x32, 0x8c, 0xd3, 0x70, 0x24, 0x44, 0x22,
+	0xca, 0xb9, 0xa0, 0xdc, 0xfa, 0xc2, 0x21, 0xad, 0x4a, 0x47, 0x67, 0xbf, 0x8c, 0x71, 0x34, 0x06,
+	0x17, 0x9b, 0xc1, 0xde, 0x23, 0xc0, 0xc8, 0x30, 0x3e, 0x61, 0x96, 0x99, 0x92, 0x5c, 0xc0, 0x7f,
+	0xc5, 0x4a, 0x15, 0x47, 0xdd, 0xa8, 0x7f, 0x38, 0xe8, 0xd0, 0x9f, 0x54, 0x3a, 0x62, 0xa5, 0x9a,
+	0xfa, 0x42, 0xa4, 0xa1, 0x49, 0xda, 0xd0, 0x74, 0x4c, 0x66, 0xa5, 0x5e, 0x8b, 0xb8, 0xd6, 0x8d,
+	0xfa, 0xad, 0xb4, 0xe1, 0x98, 0xbc, 0xd3, 0x6b, 0xd1, 0x7b, 0x81, 0x46, 0x85, 0x1e, 0x0b, 0x4f,
+	0x62, 0x68, 0xac, 0x84, 0x2d, 0x35, 0x2e, 0x02, 0xba, 0x95, 0xbe, 0x47, 0x72, 0x05, 0xf5, 0x22,
+	0xb8, 0xc3, 0xf4, 0xfe, 0xe0, 0x64, 0xa7, 0xf3, 0xe3, 0x86, 0xe9, 0xb6, 0x4d, 0x8e, 0x61, 0x2f,
+	0x17, 0x3e, 0x5b, 0xb1, 0xf9, 0x52, 0xc4, 0xff, 0xba, 0x51, 0xff, 0x20, 0x6d, 0xe6, 0xc2, 0x3f,
+	0x54, 0xb9, 0x37, 0x83, 0xd6, 0xd6, 0x7c, 0x8d, 0xd6, 0x30, 0xf7, 0xc9, 0x12, 0xfd, 0xc9, 0xd2,
+	0x86, 0x0a, 0xfa, 0x65, 0xbb, 0x5c, 0xf8, 0x6a, 0xbb, 0xe1, 0x3d, 0x74, 0x38, 0x9a, 0x5d, 0x9c,
+	0xf0, 0xb0, 0x93, 0xe8, 0xe9, 0x5c, 0x6a, 0xa7, 0x96, 0x33, 0xca, 0xd1, 0x24, 0x9b, 0xda, 0xf7,
+	0xcf, 0xcb, 0x24, 0x66, 0x21, 0xbd, 0xd6, 0xea, 0xd3, 0x9b, 0xdb, 0xf1, 0x64, 0x38, 0xab, 0x87,
+	0x7c, 0xf9, 0x16, 0x00, 0x00, 0xff, 0xff, 0x40, 0x5f, 0x69, 0xd2, 0xf4, 0x01, 0x00, 0x00,
 }
diff --git a/proto/kms_envelope_go_proto/kms_envelope.pb.go b/proto/kms_envelope_go_proto/kms_envelope.pb.go
new file mode 100644
index 0000000..7081464
--- /dev/null
+++ b/proto/kms_envelope_go_proto/kms_envelope.pb.go
@@ -0,0 +1,172 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: third_party/tink/proto/kms_envelope.proto
+
+package kms_envelope_go_proto
+
+import (
+	fmt "fmt"
+	proto "github.com/golang/protobuf/proto"
+	tink_go_proto "github.com/google/tink/proto/tink_go_proto"
+	math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
+type KmsEnvelopeAeadKeyFormat struct {
+	// Required.
+	// The location of the KEK in a remote KMS.
+	// With Google Cloud KMS, valid values have this format:
+	// gcp-kms://projects/*/locations/*/keyRings/*/cryptoKeys/*.
+	// With AWS KMS, valid values have this format:
+	// aws-kms://arn:aws:kms:<region>:<account-id>:key/<key-id>
+	KekUri string `protobuf:"bytes,1,opt,name=kek_uri,json=kekUri,proto3" json:"kek_uri,omitempty"`
+	// Key template of the Data Encryption Key, e.g., AesCtrHmacAeadKeyFormat.
+	// Required.
+	DekTemplate          *tink_go_proto.KeyTemplate `protobuf:"bytes,2,opt,name=dek_template,json=dekTemplate,proto3" json:"dek_template,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}                   `json:"-"`
+	XXX_unrecognized     []byte                     `json:"-"`
+	XXX_sizecache        int32                      `json:"-"`
+}
+
+func (m *KmsEnvelopeAeadKeyFormat) Reset()         { *m = KmsEnvelopeAeadKeyFormat{} }
+func (m *KmsEnvelopeAeadKeyFormat) String() string { return proto.CompactTextString(m) }
+func (*KmsEnvelopeAeadKeyFormat) ProtoMessage()    {}
+func (*KmsEnvelopeAeadKeyFormat) Descriptor() ([]byte, []int) {
+	return fileDescriptor_c02dbbaaedce3251, []int{0}
+}
+
+func (m *KmsEnvelopeAeadKeyFormat) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_KmsEnvelopeAeadKeyFormat.Unmarshal(m, b)
+}
+func (m *KmsEnvelopeAeadKeyFormat) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_KmsEnvelopeAeadKeyFormat.Marshal(b, m, deterministic)
+}
+func (m *KmsEnvelopeAeadKeyFormat) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_KmsEnvelopeAeadKeyFormat.Merge(m, src)
+}
+func (m *KmsEnvelopeAeadKeyFormat) XXX_Size() int {
+	return xxx_messageInfo_KmsEnvelopeAeadKeyFormat.Size(m)
+}
+func (m *KmsEnvelopeAeadKeyFormat) XXX_DiscardUnknown() {
+	xxx_messageInfo_KmsEnvelopeAeadKeyFormat.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_KmsEnvelopeAeadKeyFormat proto.InternalMessageInfo
+
+func (m *KmsEnvelopeAeadKeyFormat) GetKekUri() string {
+	if m != nil {
+		return m.KekUri
+	}
+	return ""
+}
+
+func (m *KmsEnvelopeAeadKeyFormat) GetDekTemplate() *tink_go_proto.KeyTemplate {
+	if m != nil {
+		return m.DekTemplate
+	}
+	return nil
+}
+
+// There is no actual key material in the key.
+type KmsEnvelopeAeadKey struct {
+	Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
+	// The key format also contains the params.
+	Params               *KmsEnvelopeAeadKeyFormat `protobuf:"bytes,2,opt,name=params,proto3" json:"params,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}                  `json:"-"`
+	XXX_unrecognized     []byte                    `json:"-"`
+	XXX_sizecache        int32                     `json:"-"`
+}
+
+func (m *KmsEnvelopeAeadKey) Reset()         { *m = KmsEnvelopeAeadKey{} }
+func (m *KmsEnvelopeAeadKey) String() string { return proto.CompactTextString(m) }
+func (*KmsEnvelopeAeadKey) ProtoMessage()    {}
+func (*KmsEnvelopeAeadKey) Descriptor() ([]byte, []int) {
+	return fileDescriptor_c02dbbaaedce3251, []int{1}
+}
+
+func (m *KmsEnvelopeAeadKey) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_KmsEnvelopeAeadKey.Unmarshal(m, b)
+}
+func (m *KmsEnvelopeAeadKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_KmsEnvelopeAeadKey.Marshal(b, m, deterministic)
+}
+func (m *KmsEnvelopeAeadKey) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_KmsEnvelopeAeadKey.Merge(m, src)
+}
+func (m *KmsEnvelopeAeadKey) XXX_Size() int {
+	return xxx_messageInfo_KmsEnvelopeAeadKey.Size(m)
+}
+func (m *KmsEnvelopeAeadKey) XXX_DiscardUnknown() {
+	xxx_messageInfo_KmsEnvelopeAeadKey.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_KmsEnvelopeAeadKey proto.InternalMessageInfo
+
+func (m *KmsEnvelopeAeadKey) GetVersion() uint32 {
+	if m != nil {
+		return m.Version
+	}
+	return 0
+}
+
+func (m *KmsEnvelopeAeadKey) GetParams() *KmsEnvelopeAeadKeyFormat {
+	if m != nil {
+		return m.Params
+	}
+	return nil
+}
+
+func init() {
+	proto.RegisterType((*KmsEnvelopeAeadKeyFormat)(nil), "google.crypto.tink.KmsEnvelopeAeadKeyFormat")
+	proto.RegisterType((*KmsEnvelopeAeadKey)(nil), "google.crypto.tink.KmsEnvelopeAeadKey")
+}
+
+func init() {
+	proto.RegisterFile("proto/kms_envelope.proto", fileDescriptor_c02dbbaaedce3251)
+}
+
+var fileDescriptor_c02dbbaaedce3251 = []byte{
+	// 265 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x90, 0x41, 0x4b, 0xc3, 0x30,
+	0x14, 0xc7, 0xe9, 0x0e, 0x1d, 0x66, 0x7a, 0xc9, 0xc5, 0x22, 0x82, 0x73, 0xa7, 0x09, 0x92, 0xc2,
+	0xfc, 0x04, 0x16, 0x15, 0xa4, 0x20, 0xa3, 0xcc, 0x8b, 0x20, 0x21, 0x6b, 0x1f, 0x5d, 0x48, 0xd3,
+	0x17, 0xd2, 0xb7, 0x49, 0xbf, 0x8e, 0x9f, 0x74, 0xac, 0xeb, 0x4e, 0x5b, 0x6f, 0xf9, 0x3f, 0xfe,
+	0xbf, 0xfc, 0x92, 0xc7, 0x9e, 0x68, 0xa3, 0x7d, 0x21, 0x9d, 0xf2, 0xd4, 0xc6, 0xa4, 0x6b, 0x13,
+	0x3b, 0x8f, 0x84, 0xb1, 0xb1, 0x8d, 0x84, 0x7a, 0x07, 0x15, 0x3a, 0x10, 0xdd, 0x88, 0xf3, 0x12,
+	0xb1, 0xac, 0x40, 0xe4, 0xbe, 0x75, 0x84, 0xe2, 0x50, 0xbe, 0x7b, 0x1c, 0xc0, 0x0f, 0xc7, 0x23,
+	0x36, 0xfb, 0x63, 0x51, 0x6a, 0x9b, 0xf7, 0xfe, 0xae, 0x57, 0x50, 0x45, 0x0a, 0xed, 0x07, 0x7a,
+	0xab, 0x88, 0xdf, 0xb2, 0xb1, 0x01, 0x23, 0xb7, 0x5e, 0x47, 0xc1, 0x34, 0x98, 0x5f, 0x65, 0xa1,
+	0x01, 0xf3, 0xed, 0x35, 0x4f, 0xd8, 0x75, 0x01, 0x46, 0x12, 0x58, 0x57, 0x29, 0x82, 0x68, 0x34,
+	0x0d, 0xe6, 0x93, 0xc5, 0x83, 0x38, 0x7f, 0x82, 0x48, 0xa1, 0x5d, 0xf5, 0xb5, 0x6c, 0x52, 0x80,
+	0x39, 0x85, 0x19, 0x31, 0x7e, 0x2e, 0xe6, 0x11, 0x1b, 0xef, 0xc0, 0x37, 0x1a, 0xeb, 0x4e, 0x79,
+	0x93, 0x9d, 0x22, 0x7f, 0x63, 0xa1, 0x53, 0x5e, 0xd9, 0xa6, 0xb7, 0x3d, 0x5f, 0xb4, 0x0d, 0x7c,
+	0x25, 0xeb, 0xd9, 0xe4, 0x97, 0xdd, 0xe7, 0x68, 0x2f, 0xa1, 0xdd, 0x3a, 0x96, 0xc1, 0xcf, 0xa2,
+	0xd4, 0xb4, 0xd9, 0xae, 0x45, 0x8e, 0x36, 0x3e, 0xd6, 0x86, 0x16, 0x2f, 0x4b, 0x94, 0xdd, 0xf4,
+	0x7f, 0x14, 0xae, 0x3e, 0xbf, 0xd2, 0x65, 0xb2, 0x0e, 0xbb, 0xfc, 0xb2, 0x0f, 0x00, 0x00, 0xff,
+	0xff, 0x46, 0x8b, 0x55, 0xf5, 0xb8, 0x01, 0x00, 0x00,
+}
diff --git a/proto/tink_go_proto/tink.pb.go b/proto/tink_go_proto/tink.pb.go
index d6bd6c7..ab7ec77 100644
--- a/proto/tink_go_proto/tink.pb.go
+++ b/proto/tink_go_proto/tink.pb.go
@@ -1,39 +1,29 @@
-// Copyright 2017 Google Inc.
-
+// Copyright 2018 Google Inc.
+//
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
-
+//
 //      http://www.apache.org/licenses/LICENSE-2.0
-
+//
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+//
 ////////////////////////////////////////////////////////////////////////////////
 
 // Code generated by protoc-gen-go. DO NOT EDIT.
-// source: tink.proto
+// source: third_party/tink/proto/tink.proto
 
-/*
-Package tink_proto is a generated protocol buffer package.
-
-It is generated from these files:
-	tink.proto
-
-It has these top-level messages:
-	KeyTemplate
-	KeyData
-	Keyset
-	KeysetInfo
-	EncryptedKeyset
-*/
 package tink_go_proto
 
-import proto "github.com/golang/protobuf/proto"
-import fmt "fmt"
-import math "math"
+import (
+	fmt "fmt"
+	proto "github.com/golang/protobuf/proto"
+	math "math"
+)
 
 // Reference imports to suppress errors if they are not otherwise used.
 var _ = proto.Marshal
@@ -44,7 +34,7 @@
 // is compatible with the proto package it is being compiled against.
 // A compilation error at this line likely means your copy of the
 // proto package needs to be updated.
-const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
 
 type KeyStatusType int32
 
@@ -61,6 +51,7 @@
 	2: "DISABLED",
 	3: "DESTROYED",
 }
+
 var KeyStatusType_value = map[string]int32{
 	"UNKNOWN_STATUS": 0,
 	"ENABLED":        1,
@@ -71,7 +62,10 @@
 func (x KeyStatusType) String() string {
 	return proto.EnumName(KeyStatusType_name, int32(x))
 }
-func (KeyStatusType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+
+func (KeyStatusType) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_a580d178bdd2ec8a, []int{0}
+}
 
 // Tink produces and accepts ciphertexts or signatures that consist
 // of a prefix and a payload. The payload and its format is determined
@@ -104,6 +98,7 @@
 	3: "RAW",
 	4: "CRUNCHY",
 }
+
 var OutputPrefixType_value = map[string]int32{
 	"UNKNOWN_PREFIX": 0,
 	"TINK":           1,
@@ -115,7 +110,10 @@
 func (x OutputPrefixType) String() string {
 	return proto.EnumName(OutputPrefixType_name, int32(x))
 }
-func (OutputPrefixType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
+
+func (OutputPrefixType) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_a580d178bdd2ec8a, []int{1}
+}
 
 type KeyData_KeyMaterialType int32
 
@@ -134,6 +132,7 @@
 	3: "ASYMMETRIC_PUBLIC",
 	4: "REMOTE",
 }
+
 var KeyData_KeyMaterialType_value = map[string]int32{
 	"UNKNOWN_KEYMATERIAL": 0,
 	"SYMMETRIC":           1,
@@ -145,23 +144,49 @@
 func (x KeyData_KeyMaterialType) String() string {
 	return proto.EnumName(KeyData_KeyMaterialType_name, int32(x))
 }
-func (KeyData_KeyMaterialType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{1, 0} }
+
+func (KeyData_KeyMaterialType) EnumDescriptor() ([]byte, []int) {
+	return fileDescriptor_a580d178bdd2ec8a, []int{1, 0}
+}
 
 type KeyTemplate struct {
 	// Required.
-	TypeUrl string `protobuf:"bytes,1,opt,name=type_url,json=typeUrl" json:"type_url,omitempty"`
+	TypeUrl string `protobuf:"bytes,1,opt,name=type_url,json=typeUrl,proto3" json:"type_url,omitempty"`
 	// Optional.
 	// If missing, it means the key type doesn't require a *KeyFormat proto.
 	Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
 	// Optional.
 	// If missing, uses OutputPrefixType.TINK.
-	OutputPrefixType OutputPrefixType `protobuf:"varint,3,opt,name=output_prefix_type,json=outputPrefixType,enum=google.crypto.tink.OutputPrefixType" json:"output_prefix_type,omitempty"`
+	OutputPrefixType     OutputPrefixType `protobuf:"varint,3,opt,name=output_prefix_type,json=outputPrefixType,proto3,enum=google.crypto.tink.OutputPrefixType" json:"output_prefix_type,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}         `json:"-"`
+	XXX_unrecognized     []byte           `json:"-"`
+	XXX_sizecache        int32            `json:"-"`
 }
 
-func (m *KeyTemplate) Reset()                    { *m = KeyTemplate{} }
-func (m *KeyTemplate) String() string            { return proto.CompactTextString(m) }
-func (*KeyTemplate) ProtoMessage()               {}
-func (*KeyTemplate) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+func (m *KeyTemplate) Reset()         { *m = KeyTemplate{} }
+func (m *KeyTemplate) String() string { return proto.CompactTextString(m) }
+func (*KeyTemplate) ProtoMessage()    {}
+func (*KeyTemplate) Descriptor() ([]byte, []int) {
+	return fileDescriptor_a580d178bdd2ec8a, []int{0}
+}
+
+func (m *KeyTemplate) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_KeyTemplate.Unmarshal(m, b)
+}
+func (m *KeyTemplate) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_KeyTemplate.Marshal(b, m, deterministic)
+}
+func (m *KeyTemplate) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_KeyTemplate.Merge(m, src)
+}
+func (m *KeyTemplate) XXX_Size() int {
+	return xxx_messageInfo_KeyTemplate.Size(m)
+}
+func (m *KeyTemplate) XXX_DiscardUnknown() {
+	xxx_messageInfo_KeyTemplate.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_KeyTemplate proto.InternalMessageInfo
 
 func (m *KeyTemplate) GetTypeUrl() string {
 	if m != nil {
@@ -190,17 +215,40 @@
 // about the type key material.
 type KeyData struct {
 	// Required.
-	TypeUrl string `protobuf:"bytes,1,opt,name=type_url,json=typeUrl" json:"type_url,omitempty"`
+	TypeUrl string `protobuf:"bytes,1,opt,name=type_url,json=typeUrl,proto3" json:"type_url,omitempty"`
 	// Required.
 	Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
 	// Required.
-	KeyMaterialType KeyData_KeyMaterialType `protobuf:"varint,3,opt,name=key_material_type,json=keyMaterialType,enum=google.crypto.tink.KeyData_KeyMaterialType" json:"key_material_type,omitempty"`
+	KeyMaterialType      KeyData_KeyMaterialType `protobuf:"varint,3,opt,name=key_material_type,json=keyMaterialType,proto3,enum=google.crypto.tink.KeyData_KeyMaterialType" json:"key_material_type,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}                `json:"-"`
+	XXX_unrecognized     []byte                  `json:"-"`
+	XXX_sizecache        int32                   `json:"-"`
 }
 
-func (m *KeyData) Reset()                    { *m = KeyData{} }
-func (m *KeyData) String() string            { return proto.CompactTextString(m) }
-func (*KeyData) ProtoMessage()               {}
-func (*KeyData) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
+func (m *KeyData) Reset()         { *m = KeyData{} }
+func (m *KeyData) String() string { return proto.CompactTextString(m) }
+func (*KeyData) ProtoMessage()    {}
+func (*KeyData) Descriptor() ([]byte, []int) {
+	return fileDescriptor_a580d178bdd2ec8a, []int{1}
+}
+
+func (m *KeyData) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_KeyData.Unmarshal(m, b)
+}
+func (m *KeyData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_KeyData.Marshal(b, m, deterministic)
+}
+func (m *KeyData) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_KeyData.Merge(m, src)
+}
+func (m *KeyData) XXX_Size() int {
+	return xxx_messageInfo_KeyData.Size(m)
+}
+func (m *KeyData) XXX_DiscardUnknown() {
+	xxx_messageInfo_KeyData.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_KeyData proto.InternalMessageInfo
 
 func (m *KeyData) GetTypeUrl() string {
 	if m != nil {
@@ -230,16 +278,39 @@
 type Keyset struct {
 	// Identifies key used to generate new crypto data (encrypt, sign).
 	// Required.
-	PrimaryKeyId uint32 `protobuf:"varint,1,opt,name=primary_key_id,json=primaryKeyId" json:"primary_key_id,omitempty"`
+	PrimaryKeyId uint32 `protobuf:"varint,1,opt,name=primary_key_id,json=primaryKeyId,proto3" json:"primary_key_id,omitempty"`
 	// Actual keys in the Keyset.
 	// Required.
-	Key []*Keyset_Key `protobuf:"bytes,2,rep,name=key" json:"key,omitempty"`
+	Key                  []*Keyset_Key `protobuf:"bytes,2,rep,name=key,proto3" json:"key,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}      `json:"-"`
+	XXX_unrecognized     []byte        `json:"-"`
+	XXX_sizecache        int32         `json:"-"`
 }
 
-func (m *Keyset) Reset()                    { *m = Keyset{} }
-func (m *Keyset) String() string            { return proto.CompactTextString(m) }
-func (*Keyset) ProtoMessage()               {}
-func (*Keyset) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
+func (m *Keyset) Reset()         { *m = Keyset{} }
+func (m *Keyset) String() string { return proto.CompactTextString(m) }
+func (*Keyset) ProtoMessage()    {}
+func (*Keyset) Descriptor() ([]byte, []int) {
+	return fileDescriptor_a580d178bdd2ec8a, []int{2}
+}
+
+func (m *Keyset) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Keyset.Unmarshal(m, b)
+}
+func (m *Keyset) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Keyset.Marshal(b, m, deterministic)
+}
+func (m *Keyset) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Keyset.Merge(m, src)
+}
+func (m *Keyset) XXX_Size() int {
+	return xxx_messageInfo_Keyset.Size(m)
+}
+func (m *Keyset) XXX_DiscardUnknown() {
+	xxx_messageInfo_Keyset.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Keyset proto.InternalMessageInfo
 
 func (m *Keyset) GetPrimaryKeyId() uint32 {
 	if m != nil {
@@ -258,20 +329,43 @@
 type Keyset_Key struct {
 	// Contains the actual, instantiation specific key proto.
 	// By convention, each key proto contains a version field.
-	KeyData *KeyData      `protobuf:"bytes,1,opt,name=key_data,json=keyData" json:"key_data,omitempty"`
-	Status  KeyStatusType `protobuf:"varint,2,opt,name=status,enum=google.crypto.tink.KeyStatusType" json:"status,omitempty"`
+	KeyData *KeyData      `protobuf:"bytes,1,opt,name=key_data,json=keyData,proto3" json:"key_data,omitempty"`
+	Status  KeyStatusType `protobuf:"varint,2,opt,name=status,proto3,enum=google.crypto.tink.KeyStatusType" json:"status,omitempty"`
 	// Identifies a key within a keyset, is a part of metadata
 	// of a ciphertext/signature.
-	KeyId uint32 `protobuf:"varint,3,opt,name=key_id,json=keyId" json:"key_id,omitempty"`
+	KeyId uint32 `protobuf:"varint,3,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty"`
 	// Determines the prefix of the ciphertexts/signatures produced by this key.
 	// This value is copied verbatim from the key template.
-	OutputPrefixType OutputPrefixType `protobuf:"varint,4,opt,name=output_prefix_type,json=outputPrefixType,enum=google.crypto.tink.OutputPrefixType" json:"output_prefix_type,omitempty"`
+	OutputPrefixType     OutputPrefixType `protobuf:"varint,4,opt,name=output_prefix_type,json=outputPrefixType,proto3,enum=google.crypto.tink.OutputPrefixType" json:"output_prefix_type,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}         `json:"-"`
+	XXX_unrecognized     []byte           `json:"-"`
+	XXX_sizecache        int32            `json:"-"`
 }
 
-func (m *Keyset_Key) Reset()                    { *m = Keyset_Key{} }
-func (m *Keyset_Key) String() string            { return proto.CompactTextString(m) }
-func (*Keyset_Key) ProtoMessage()               {}
-func (*Keyset_Key) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2, 0} }
+func (m *Keyset_Key) Reset()         { *m = Keyset_Key{} }
+func (m *Keyset_Key) String() string { return proto.CompactTextString(m) }
+func (*Keyset_Key) ProtoMessage()    {}
+func (*Keyset_Key) Descriptor() ([]byte, []int) {
+	return fileDescriptor_a580d178bdd2ec8a, []int{2, 0}
+}
+
+func (m *Keyset_Key) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Keyset_Key.Unmarshal(m, b)
+}
+func (m *Keyset_Key) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Keyset_Key.Marshal(b, m, deterministic)
+}
+func (m *Keyset_Key) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Keyset_Key.Merge(m, src)
+}
+func (m *Keyset_Key) XXX_Size() int {
+	return xxx_messageInfo_Keyset_Key.Size(m)
+}
+func (m *Keyset_Key) XXX_DiscardUnknown() {
+	xxx_messageInfo_Keyset_Key.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Keyset_Key proto.InternalMessageInfo
 
 func (m *Keyset_Key) GetKeyData() *KeyData {
 	if m != nil {
@@ -306,16 +400,39 @@
 // Keyset.
 type KeysetInfo struct {
 	// See Keyset.primary_key_id.
-	PrimaryKeyId uint32 `protobuf:"varint,1,opt,name=primary_key_id,json=primaryKeyId" json:"primary_key_id,omitempty"`
+	PrimaryKeyId uint32 `protobuf:"varint,1,opt,name=primary_key_id,json=primaryKeyId,proto3" json:"primary_key_id,omitempty"`
 	// KeyInfos in the KeysetInfo.
 	// Each KeyInfo is corresponding to a Key in the corresponding Keyset.
-	KeyInfo []*KeysetInfo_KeyInfo `protobuf:"bytes,2,rep,name=key_info,json=keyInfo" json:"key_info,omitempty"`
+	KeyInfo              []*KeysetInfo_KeyInfo `protobuf:"bytes,2,rep,name=key_info,json=keyInfo,proto3" json:"key_info,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}              `json:"-"`
+	XXX_unrecognized     []byte                `json:"-"`
+	XXX_sizecache        int32                 `json:"-"`
 }
 
-func (m *KeysetInfo) Reset()                    { *m = KeysetInfo{} }
-func (m *KeysetInfo) String() string            { return proto.CompactTextString(m) }
-func (*KeysetInfo) ProtoMessage()               {}
-func (*KeysetInfo) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
+func (m *KeysetInfo) Reset()         { *m = KeysetInfo{} }
+func (m *KeysetInfo) String() string { return proto.CompactTextString(m) }
+func (*KeysetInfo) ProtoMessage()    {}
+func (*KeysetInfo) Descriptor() ([]byte, []int) {
+	return fileDescriptor_a580d178bdd2ec8a, []int{3}
+}
+
+func (m *KeysetInfo) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_KeysetInfo.Unmarshal(m, b)
+}
+func (m *KeysetInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_KeysetInfo.Marshal(b, m, deterministic)
+}
+func (m *KeysetInfo) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_KeysetInfo.Merge(m, src)
+}
+func (m *KeysetInfo) XXX_Size() int {
+	return xxx_messageInfo_KeysetInfo.Size(m)
+}
+func (m *KeysetInfo) XXX_DiscardUnknown() {
+	xxx_messageInfo_KeysetInfo.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_KeysetInfo proto.InternalMessageInfo
 
 func (m *KeysetInfo) GetPrimaryKeyId() uint32 {
 	if m != nil {
@@ -334,19 +451,42 @@
 type KeysetInfo_KeyInfo struct {
 	// the type url of this key,
 	// e.g., type.googleapis.com/google.crypto.tink.HmacKey.
-	TypeUrl string `protobuf:"bytes,1,opt,name=type_url,json=typeUrl" json:"type_url,omitempty"`
+	TypeUrl string `protobuf:"bytes,1,opt,name=type_url,json=typeUrl,proto3" json:"type_url,omitempty"`
 	// See Keyset.Key.status.
-	Status KeyStatusType `protobuf:"varint,2,opt,name=status,enum=google.crypto.tink.KeyStatusType" json:"status,omitempty"`
+	Status KeyStatusType `protobuf:"varint,2,opt,name=status,proto3,enum=google.crypto.tink.KeyStatusType" json:"status,omitempty"`
 	// See Keyset.Key.key_id.
-	KeyId uint32 `protobuf:"varint,3,opt,name=key_id,json=keyId" json:"key_id,omitempty"`
+	KeyId uint32 `protobuf:"varint,3,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty"`
 	// See Keyset.Key.output_prefix_type.
-	OutputPrefixType OutputPrefixType `protobuf:"varint,4,opt,name=output_prefix_type,json=outputPrefixType,enum=google.crypto.tink.OutputPrefixType" json:"output_prefix_type,omitempty"`
+	OutputPrefixType     OutputPrefixType `protobuf:"varint,4,opt,name=output_prefix_type,json=outputPrefixType,proto3,enum=google.crypto.tink.OutputPrefixType" json:"output_prefix_type,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}         `json:"-"`
+	XXX_unrecognized     []byte           `json:"-"`
+	XXX_sizecache        int32            `json:"-"`
 }
 
-func (m *KeysetInfo_KeyInfo) Reset()                    { *m = KeysetInfo_KeyInfo{} }
-func (m *KeysetInfo_KeyInfo) String() string            { return proto.CompactTextString(m) }
-func (*KeysetInfo_KeyInfo) ProtoMessage()               {}
-func (*KeysetInfo_KeyInfo) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3, 0} }
+func (m *KeysetInfo_KeyInfo) Reset()         { *m = KeysetInfo_KeyInfo{} }
+func (m *KeysetInfo_KeyInfo) String() string { return proto.CompactTextString(m) }
+func (*KeysetInfo_KeyInfo) ProtoMessage()    {}
+func (*KeysetInfo_KeyInfo) Descriptor() ([]byte, []int) {
+	return fileDescriptor_a580d178bdd2ec8a, []int{3, 0}
+}
+
+func (m *KeysetInfo_KeyInfo) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_KeysetInfo_KeyInfo.Unmarshal(m, b)
+}
+func (m *KeysetInfo_KeyInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_KeysetInfo_KeyInfo.Marshal(b, m, deterministic)
+}
+func (m *KeysetInfo_KeyInfo) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_KeysetInfo_KeyInfo.Merge(m, src)
+}
+func (m *KeysetInfo_KeyInfo) XXX_Size() int {
+	return xxx_messageInfo_KeysetInfo_KeyInfo.Size(m)
+}
+func (m *KeysetInfo_KeyInfo) XXX_DiscardUnknown() {
+	xxx_messageInfo_KeysetInfo_KeyInfo.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_KeysetInfo_KeyInfo proto.InternalMessageInfo
 
 func (m *KeysetInfo_KeyInfo) GetTypeUrl() string {
 	if m != nil {
@@ -381,13 +521,36 @@
 	// Required.
 	EncryptedKeyset []byte `protobuf:"bytes,2,opt,name=encrypted_keyset,json=encryptedKeyset,proto3" json:"encrypted_keyset,omitempty"`
 	// Optional.
-	KeysetInfo *KeysetInfo `protobuf:"bytes,3,opt,name=keyset_info,json=keysetInfo" json:"keyset_info,omitempty"`
+	KeysetInfo           *KeysetInfo `protobuf:"bytes,3,opt,name=keyset_info,json=keysetInfo,proto3" json:"keyset_info,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
+	XXX_unrecognized     []byte      `json:"-"`
+	XXX_sizecache        int32       `json:"-"`
 }
 
-func (m *EncryptedKeyset) Reset()                    { *m = EncryptedKeyset{} }
-func (m *EncryptedKeyset) String() string            { return proto.CompactTextString(m) }
-func (*EncryptedKeyset) ProtoMessage()               {}
-func (*EncryptedKeyset) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
+func (m *EncryptedKeyset) Reset()         { *m = EncryptedKeyset{} }
+func (m *EncryptedKeyset) String() string { return proto.CompactTextString(m) }
+func (*EncryptedKeyset) ProtoMessage()    {}
+func (*EncryptedKeyset) Descriptor() ([]byte, []int) {
+	return fileDescriptor_a580d178bdd2ec8a, []int{4}
+}
+
+func (m *EncryptedKeyset) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_EncryptedKeyset.Unmarshal(m, b)
+}
+func (m *EncryptedKeyset) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_EncryptedKeyset.Marshal(b, m, deterministic)
+}
+func (m *EncryptedKeyset) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_EncryptedKeyset.Merge(m, src)
+}
+func (m *EncryptedKeyset) XXX_Size() int {
+	return xxx_messageInfo_EncryptedKeyset.Size(m)
+}
+func (m *EncryptedKeyset) XXX_DiscardUnknown() {
+	xxx_messageInfo_EncryptedKeyset.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_EncryptedKeyset proto.InternalMessageInfo
 
 func (m *EncryptedKeyset) GetEncryptedKeyset() []byte {
 	if m != nil {
@@ -404,6 +567,9 @@
 }
 
 func init() {
+	proto.RegisterEnum("google.crypto.tink.KeyStatusType", KeyStatusType_name, KeyStatusType_value)
+	proto.RegisterEnum("google.crypto.tink.OutputPrefixType", OutputPrefixType_name, OutputPrefixType_value)
+	proto.RegisterEnum("google.crypto.tink.KeyData_KeyMaterialType", KeyData_KeyMaterialType_name, KeyData_KeyMaterialType_value)
 	proto.RegisterType((*KeyTemplate)(nil), "google.crypto.tink.KeyTemplate")
 	proto.RegisterType((*KeyData)(nil), "google.crypto.tink.KeyData")
 	proto.RegisterType((*Keyset)(nil), "google.crypto.tink.Keyset")
@@ -411,54 +577,52 @@
 	proto.RegisterType((*KeysetInfo)(nil), "google.crypto.tink.KeysetInfo")
 	proto.RegisterType((*KeysetInfo_KeyInfo)(nil), "google.crypto.tink.KeysetInfo.KeyInfo")
 	proto.RegisterType((*EncryptedKeyset)(nil), "google.crypto.tink.EncryptedKeyset")
-	proto.RegisterEnum("google.crypto.tink.KeyStatusType", KeyStatusType_name, KeyStatusType_value)
-	proto.RegisterEnum("google.crypto.tink.OutputPrefixType", OutputPrefixType_name, OutputPrefixType_value)
-	proto.RegisterEnum("google.crypto.tink.KeyData_KeyMaterialType", KeyData_KeyMaterialType_name, KeyData_KeyMaterialType_value)
 }
 
-func init() { proto.RegisterFile("tink.proto", fileDescriptor0) }
+func init() { proto.RegisterFile("proto/tink.proto", fileDescriptor_a580d178bdd2ec8a) }
 
-var fileDescriptor0 = []byte{
-	// 653 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x54, 0xcd, 0x6e, 0xd3, 0x40,
-	0x10, 0xae, 0xed, 0x34, 0x49, 0x27, 0x6d, 0xb2, 0x5d, 0x28, 0x84, 0x82, 0xaa, 0x10, 0x55, 0x10,
-	0x8a, 0x94, 0xa2, 0x20, 0x21, 0x71, 0x42, 0x4e, 0xb2, 0x80, 0xe5, 0xfc, 0x69, 0xe3, 0x50, 0xc2,
-	0xc5, 0x72, 0x9b, 0x6d, 0xb1, 0x9c, 0xc4, 0x96, 0xb3, 0x46, 0xf8, 0xc0, 0x03, 0x70, 0xe5, 0x11,
-	0x78, 0x14, 0x0e, 0x1c, 0x78, 0x0d, 0x5e, 0x04, 0xed, 0xda, 0xad, 0xda, 0xd0, 0x56, 0x20, 0x4e,
-	0x9c, 0x76, 0x66, 0xf6, 0xdb, 0x99, 0xf9, 0x3e, 0xcf, 0x18, 0x80, 0xbb, 0x73, 0xaf, 0x1e, 0x84,
-	0x3e, 0xf7, 0x31, 0x3e, 0xf1, 0xfd, 0x93, 0x29, 0xab, 0x1f, 0x85, 0x71, 0xc0, 0xfd, 0xba, 0xb8,
-	0xa9, 0x7e, 0x51, 0xa0, 0x60, 0xb2, 0xd8, 0x62, 0xb3, 0x60, 0xea, 0x70, 0x86, 0xef, 0x40, 0x9e,
-	0xc7, 0x01, 0xb3, 0xa3, 0x70, 0x5a, 0x56, 0x2a, 0x4a, 0x6d, 0x8d, 0xe6, 0x84, 0x3f, 0x0a, 0xa7,
-	0xf8, 0x26, 0xac, 0x7e, 0x70, 0xa6, 0x11, 0x2b, 0xab, 0x15, 0xa5, 0xb6, 0x4e, 0x13, 0x07, 0x53,
-	0xc0, 0x7e, 0xc4, 0x83, 0x88, 0xdb, 0x41, 0xc8, 0x8e, 0xdd, 0x8f, 0xb6, 0x80, 0x97, 0xb5, 0x8a,
-	0x52, 0x2b, 0x36, 0x76, 0xeb, 0xbf, 0x57, 0xac, 0xf7, 0x25, 0x7a, 0x20, 0xc1, 0x56, 0x1c, 0x30,
-	0x8a, 0xfc, 0xa5, 0x48, 0xf5, 0xb3, 0x0a, 0x39, 0x93, 0xc5, 0x6d, 0x87, 0x3b, 0x7f, 0xdf, 0xd0,
-	0x01, 0x6c, 0x7a, 0x2c, 0xb6, 0x67, 0x0e, 0x67, 0xa1, 0xeb, 0x4c, 0xcf, 0xf7, 0xf3, 0xf8, 0xb2,
-	0x7e, 0xd2, 0x42, 0xe2, 0xec, 0xa6, 0x6f, 0x64, 0x5b, 0x25, 0xef, 0x62, 0xa0, 0xca, 0xa1, 0xb4,
-	0x84, 0xc1, 0xb7, 0xe1, 0xc6, 0xa8, 0x67, 0xf6, 0xfa, 0x07, 0x3d, 0xdb, 0x24, 0xe3, 0xae, 0x6e,
-	0x11, 0x6a, 0xe8, 0x1d, 0xb4, 0x82, 0x37, 0x60, 0x6d, 0x38, 0xee, 0x76, 0x89, 0x45, 0x8d, 0x16,
-	0x52, 0xf0, 0x2d, 0xc0, 0xfa, 0x99, 0x6f, 0x0f, 0xa8, 0xf1, 0x46, 0xb7, 0x08, 0x52, 0xf1, 0x16,
-	0x6c, 0x9e, 0x8f, 0x8f, 0x9a, 0x1d, 0xa3, 0x85, 0x34, 0x0c, 0x90, 0xa5, 0xa4, 0xdb, 0xb7, 0x08,
-	0xca, 0x54, 0xbf, 0xab, 0x90, 0x35, 0x59, 0xbc, 0x60, 0x1c, 0xef, 0x42, 0x31, 0x08, 0xdd, 0x99,
-	0x13, 0xc6, 0xb6, 0x60, 0xe8, 0x4e, 0xa4, 0x20, 0x1b, 0x74, 0x3d, 0x8d, 0x9a, 0x2c, 0x36, 0x26,
-	0xf8, 0x09, 0x68, 0x1e, 0x8b, 0xcb, 0x6a, 0x45, 0xab, 0x15, 0x1a, 0x3b, 0x57, 0x30, 0x5e, 0x30,
-	0x2e, 0x0e, 0x2a, 0xa0, 0xdb, 0x3f, 0x15, 0xd0, 0x4c, 0x16, 0xe3, 0x67, 0x90, 0x17, 0x79, 0x27,
-	0x0e, 0x77, 0x64, 0xe6, 0x42, 0xe3, 0xee, 0x35, 0x82, 0xd1, 0x9c, 0x97, 0x7e, 0xa2, 0xe7, 0x90,
-	0x5d, 0x70, 0x87, 0x47, 0x0b, 0xf9, 0x21, 0x8a, 0x8d, 0xfb, 0x57, 0xbc, 0x1a, 0x4a, 0x90, 0x14,
-	0x37, 0x7d, 0x80, 0xb7, 0x20, 0x9b, 0x52, 0xd1, 0x24, 0x95, 0x55, 0x4f, 0x72, 0xb8, 0x7c, 0xa8,
-	0x32, 0xff, 0x34, 0x54, 0xdf, 0x54, 0x80, 0x84, 0xb9, 0x31, 0x3f, 0xf6, 0xff, 0x50, 0x4c, 0x3d,
-	0x91, 0xc4, 0x9d, 0x1f, 0xfb, 0xa9, 0xa2, 0x0f, 0xae, 0x56, 0x54, 0xe4, 0x15, 0xa6, 0x38, 0xa5,
-	0x3a, 0xc2, 0xd8, 0xfe, 0xa1, 0xc8, 0x61, 0x96, 0x45, 0xaf, 0x19, 0xe6, 0xff, 0x43, 0xc4, 0x4f,
-	0x50, 0x22, 0x73, 0xf9, 0x86, 0x4d, 0xd2, 0xa9, 0x7c, 0x04, 0x88, 0x9d, 0x86, 0x84, 0x94, 0x0b,
-	0xc6, 0xd3, 0x85, 0x2c, 0xb1, 0x25, 0xe8, 0x0b, 0x28, 0x24, 0x80, 0x44, 0x50, 0x4d, 0xce, 0xd8,
-	0xce, 0xf5, 0x82, 0x52, 0xf0, 0xce, 0xec, 0xbd, 0x2e, 0x6c, 0x5c, 0x90, 0x00, 0x63, 0x28, 0x9e,
-	0x2e, 0xe0, 0xd0, 0xd2, 0xad, 0xd1, 0x10, 0xad, 0xe0, 0x02, 0xe4, 0x48, 0x4f, 0x6f, 0x76, 0x48,
-	0x1b, 0x29, 0x78, 0x1d, 0xf2, 0x6d, 0x63, 0x98, 0x78, 0xaa, 0x58, 0xcb, 0x36, 0x19, 0x5a, 0xb4,
-	0x3f, 0x26, 0x6d, 0xa4, 0xed, 0x51, 0x40, 0xcb, 0x9c, 0xcf, 0x67, 0x1c, 0x50, 0xf2, 0xd2, 0x78,
-	0x8b, 0x56, 0x70, 0x1e, 0x32, 0x96, 0xd1, 0x33, 0x91, 0x22, 0x36, 0xb3, 0x43, 0x5e, 0xe9, 0xad,
-	0x31, 0x52, 0x71, 0x0e, 0x34, 0xaa, 0x1f, 0x20, 0x4d, 0x14, 0x6c, 0xd1, 0x51, 0xaf, 0xf5, 0x7a,
-	0x8c, 0x32, 0x4d, 0x0a, 0xf7, 0x8e, 0xfc, 0xd9, 0x65, 0x9c, 0xe4, 0x4f, 0x78, 0xa0, 0xbc, 0x7b,
-	0x78, 0xe2, 0xf2, 0xf7, 0xd1, 0x61, 0xfd, 0xc8, 0x9f, 0xed, 0x27, 0xb0, 0x7d, 0x71, 0xbf, 0x2f,
-	0xef, 0xa5, 0x69, 0x4b, 0xf3, 0xab, 0x9a, 0x15, 0x55, 0x07, 0xcd, 0xc3, 0xac, 0xf4, 0x9f, 0xfe,
-	0x0a, 0x00, 0x00, 0xff, 0xff, 0x56, 0xaa, 0x4f, 0x00, 0xcd, 0x05, 0x00, 0x00,
+var fileDescriptor_a580d178bdd2ec8a = []byte{
+	// 667 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x54, 0xcf, 0x6e, 0xd3, 0x4e,
+	0x10, 0xae, 0xed, 0x34, 0x49, 0x27, 0x6d, 0xb2, 0xdd, 0xdf, 0xaf, 0x10, 0x0a, 0xaa, 0xd2, 0xa8,
+	0x42, 0xa1, 0x48, 0x29, 0x0a, 0x12, 0x12, 0x27, 0xe4, 0x24, 0x0b, 0x58, 0xce, 0x3f, 0x6d, 0x1c,
+	0x4a, 0xb8, 0x58, 0x6e, 0xb3, 0x4d, 0x2d, 0x27, 0xb1, 0xe5, 0x6c, 0x10, 0x3e, 0xf0, 0x00, 0x5c,
+	0x79, 0x04, 0x1e, 0x85, 0x03, 0x07, 0x5e, 0x83, 0x17, 0x41, 0xbb, 0x76, 0xab, 0x36, 0xb4, 0x11,
+	0x88, 0x13, 0xa7, 0x9d, 0x19, 0x7f, 0x3b, 0x33, 0xdf, 0xe7, 0x99, 0x85, 0x7d, 0x7e, 0xee, 0x86,
+	0x23, 0x3b, 0x70, 0x42, 0x1e, 0x1d, 0x71, 0x77, 0xe6, 0x1d, 0x05, 0xa1, 0xcf, 0x7d, 0x69, 0x56,
+	0xa5, 0x89, 0xf1, 0xd8, 0xf7, 0xc7, 0x13, 0x56, 0x3d, 0x0d, 0xa3, 0x80, 0xfb, 0x55, 0xf1, 0xa5,
+	0xfc, 0x59, 0x81, 0x9c, 0xc9, 0x22, 0x8b, 0x4d, 0x83, 0x89, 0xc3, 0x19, 0xbe, 0x07, 0x59, 0x1e,
+	0x05, 0xcc, 0x5e, 0x84, 0x93, 0xa2, 0x52, 0x52, 0x2a, 0x1b, 0x34, 0x23, 0xfc, 0x41, 0x38, 0xc1,
+	0xff, 0xc3, 0xfa, 0x7b, 0x67, 0xb2, 0x60, 0x45, 0xb5, 0xa4, 0x54, 0x36, 0x69, 0xec, 0x60, 0x0a,
+	0xd8, 0x5f, 0xf0, 0x60, 0xc1, 0xed, 0x20, 0x64, 0x67, 0xee, 0x07, 0x5b, 0xc0, 0x8b, 0x5a, 0x49,
+	0xa9, 0xe4, 0x6b, 0x07, 0xd5, 0x5f, 0x2b, 0x56, 0xbb, 0x12, 0xdd, 0x93, 0x60, 0x2b, 0x0a, 0x18,
+	0x45, 0xfe, 0x52, 0xa4, 0xfc, 0x49, 0x85, 0x8c, 0xc9, 0xa2, 0xa6, 0xc3, 0x9d, 0x3f, 0x6f, 0xe8,
+	0x18, 0xb6, 0x3d, 0x16, 0xd9, 0x53, 0x87, 0xb3, 0xd0, 0x75, 0x26, 0x57, 0xfb, 0x79, 0x7c, 0x53,
+	0x3f, 0x49, 0x21, 0x71, 0xb6, 0x93, 0x3b, 0xb2, 0xad, 0x82, 0x77, 0x3d, 0x50, 0xe6, 0x50, 0x58,
+	0xc2, 0xe0, 0xbb, 0xf0, 0xdf, 0xa0, 0x63, 0x76, 0xba, 0xc7, 0x1d, 0xdb, 0x24, 0xc3, 0xb6, 0x6e,
+	0x11, 0x6a, 0xe8, 0x2d, 0xb4, 0x86, 0xb7, 0x60, 0xa3, 0x3f, 0x6c, 0xb7, 0x89, 0x45, 0x8d, 0x06,
+	0x52, 0xf0, 0x1d, 0xc0, 0xfa, 0xa5, 0x6f, 0xf7, 0xa8, 0xf1, 0x46, 0xb7, 0x08, 0x52, 0xf1, 0x0e,
+	0x6c, 0x5f, 0x8d, 0x0f, 0xea, 0x2d, 0xa3, 0x81, 0x34, 0x0c, 0x90, 0xa6, 0xa4, 0xdd, 0xb5, 0x08,
+	0x4a, 0x95, 0xbf, 0xa9, 0x90, 0x36, 0x59, 0x34, 0x67, 0x1c, 0x1f, 0x40, 0x3e, 0x08, 0xdd, 0xa9,
+	0x13, 0x46, 0xb6, 0x60, 0xe8, 0x8e, 0xa4, 0x20, 0x5b, 0x74, 0x33, 0x89, 0x9a, 0x2c, 0x32, 0x46,
+	0xf8, 0x09, 0x68, 0x1e, 0x8b, 0x8a, 0x6a, 0x49, 0xab, 0xe4, 0x6a, 0x7b, 0xb7, 0x30, 0x9e, 0x33,
+	0x2e, 0x0e, 0x2a, 0xa0, 0xbb, 0x3f, 0x14, 0xd0, 0x4c, 0x16, 0xe1, 0x67, 0x90, 0x15, 0x79, 0x47,
+	0x0e, 0x77, 0x64, 0xe6, 0x5c, 0xed, 0xfe, 0x0a, 0xc1, 0x68, 0xc6, 0x4b, 0x7e, 0xd1, 0x73, 0x48,
+	0xcf, 0xb9, 0xc3, 0x17, 0x73, 0xf9, 0x23, 0xf2, 0xb5, 0xfd, 0x5b, 0x6e, 0xf5, 0x25, 0x48, 0x8a,
+	0x9b, 0x5c, 0xc0, 0x3b, 0x90, 0x4e, 0xa8, 0x68, 0x92, 0xca, 0xba, 0x27, 0x39, 0xdc, 0x3c, 0x54,
+	0xa9, 0xbf, 0x1a, 0xaa, 0xaf, 0x2a, 0x40, 0xcc, 0xdc, 0x98, 0x9d, 0xf9, 0xbf, 0x29, 0xa6, 0x1e,
+	0x4b, 0xe2, 0xce, 0xce, 0xfc, 0x44, 0xd1, 0x87, 0xb7, 0x2b, 0x2a, 0xf2, 0x0a, 0x53, 0x9c, 0x52,
+	0x1d, 0x61, 0xec, 0x7e, 0x57, 0xe4, 0x30, 0xcb, 0xa2, 0x2b, 0x86, 0xf9, 0xdf, 0x10, 0xf1, 0x23,
+	0x14, 0xc8, 0x4c, 0xde, 0x61, 0xa3, 0x64, 0x2a, 0x1f, 0x01, 0x62, 0x17, 0x21, 0x21, 0xe5, 0x9c,
+	0xf1, 0x64, 0x21, 0x0b, 0x6c, 0x09, 0xfa, 0x02, 0x72, 0x31, 0x20, 0x16, 0x54, 0x93, 0x33, 0xb6,
+	0xb7, 0x5a, 0x50, 0x0a, 0xde, 0xa5, 0x7d, 0xd8, 0x86, 0xad, 0x6b, 0x12, 0x60, 0x0c, 0xf9, 0x8b,
+	0x05, 0xec, 0x5b, 0xba, 0x35, 0xe8, 0xa3, 0x35, 0x9c, 0x83, 0x0c, 0xe9, 0xe8, 0xf5, 0x16, 0x69,
+	0x22, 0x05, 0x6f, 0x42, 0xb6, 0x69, 0xf4, 0x63, 0x4f, 0x15, 0x6b, 0xd9, 0x24, 0x7d, 0x8b, 0x76,
+	0x87, 0xa4, 0x89, 0xb4, 0x43, 0x0a, 0x68, 0x99, 0xf3, 0xd5, 0x8c, 0x3d, 0x4a, 0x5e, 0x1a, 0x6f,
+	0xd1, 0x1a, 0xce, 0x42, 0xca, 0x32, 0x3a, 0x26, 0x52, 0xc4, 0x66, 0xb6, 0xc8, 0x2b, 0xbd, 0x31,
+	0x44, 0x2a, 0xce, 0x80, 0x46, 0xf5, 0x63, 0xa4, 0x89, 0x82, 0x0d, 0x3a, 0xe8, 0x34, 0x5e, 0x0f,
+	0x51, 0xaa, 0x3e, 0x80, 0x07, 0xa7, 0xfe, 0xf4, 0x26, 0x4e, 0xf2, 0x11, 0xee, 0x29, 0xef, 0x0e,
+	0xc7, 0x2e, 0x3f, 0x5f, 0x9c, 0x54, 0x4f, 0xfd, 0xe9, 0x51, 0x0c, 0x5b, 0x7e, 0xaf, 0xed, 0xb1,
+	0x6f, 0x4b, 0xef, 0x8b, 0x9a, 0x16, 0x85, 0x7b, 0xf5, 0x93, 0xb4, 0xf4, 0x9f, 0xfe, 0x0c, 0x00,
+	0x00, 0xff, 0xff, 0x8a, 0x74, 0x7a, 0x5d, 0xe7, 0x05, 0x00, 0x00,
 }
diff --git a/proto/xchacha20_poly1305.proto b/proto/xchacha20_poly1305.proto
new file mode 100644
index 0000000..89d66ed
--- /dev/null
+++ b/proto/xchacha20_poly1305.proto
@@ -0,0 +1,30 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+syntax = "proto3";
+
+package google.crypto.tink;
+
+option java_package = "com.google.crypto.tink.proto";
+option java_multiple_files = true;
+option objc_class_prefix = "TINKPB";
+option go_package = "github.com/google/tink/proto/xchacha20_poly1305_go_proto";
+
+// key_type: type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key
+message XChaCha20Poly1305Key {
+  uint32 version = 1;
+  bytes key_value = 3;
+}
diff --git a/proto/xchacha20_poly1305_go_proto/xchacha20_poly1305.pb.go b/proto/xchacha20_poly1305_go_proto/xchacha20_poly1305.pb.go
new file mode 100644
index 0000000..7459331
--- /dev/null
+++ b/proto/xchacha20_poly1305_go_proto/xchacha20_poly1305.pb.go
@@ -0,0 +1,110 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: third_party/tink/proto/xchacha20_poly1305.proto
+
+package xchacha20_poly1305_go_proto
+
+import (
+	fmt "fmt"
+	proto "github.com/golang/protobuf/proto"
+	math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
+// key_type: type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key
+type XChaCha20Poly1305Key struct {
+	Version              uint32   `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
+	KeyValue             []byte   `protobuf:"bytes,3,opt,name=key_value,json=keyValue,proto3" json:"key_value,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *XChaCha20Poly1305Key) Reset()         { *m = XChaCha20Poly1305Key{} }
+func (m *XChaCha20Poly1305Key) String() string { return proto.CompactTextString(m) }
+func (*XChaCha20Poly1305Key) ProtoMessage()    {}
+func (*XChaCha20Poly1305Key) Descriptor() ([]byte, []int) {
+	return fileDescriptor_d05c005514adb1c5, []int{0}
+}
+
+func (m *XChaCha20Poly1305Key) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_XChaCha20Poly1305Key.Unmarshal(m, b)
+}
+func (m *XChaCha20Poly1305Key) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_XChaCha20Poly1305Key.Marshal(b, m, deterministic)
+}
+func (m *XChaCha20Poly1305Key) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_XChaCha20Poly1305Key.Merge(m, src)
+}
+func (m *XChaCha20Poly1305Key) XXX_Size() int {
+	return xxx_messageInfo_XChaCha20Poly1305Key.Size(m)
+}
+func (m *XChaCha20Poly1305Key) XXX_DiscardUnknown() {
+	xxx_messageInfo_XChaCha20Poly1305Key.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_XChaCha20Poly1305Key proto.InternalMessageInfo
+
+func (m *XChaCha20Poly1305Key) GetVersion() uint32 {
+	if m != nil {
+		return m.Version
+	}
+	return 0
+}
+
+func (m *XChaCha20Poly1305Key) GetKeyValue() []byte {
+	if m != nil {
+		return m.KeyValue
+	}
+	return nil
+}
+
+func init() {
+	proto.RegisterType((*XChaCha20Poly1305Key)(nil), "google.crypto.tink.XChaCha20Poly1305Key")
+}
+
+func init() {
+	proto.RegisterFile("proto/xchacha20_poly1305.proto", fileDescriptor_d05c005514adb1c5)
+}
+
+var fileDescriptor_d05c005514adb1c5 = []byte{
+	// 203 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x2f, 0xc9, 0xc8, 0x2c,
+	0x4a, 0x89, 0x2f, 0x48, 0x2c, 0x2a, 0xa9, 0xd4, 0x2f, 0xc9, 0xcc, 0xcb, 0xd6, 0x2f, 0x28, 0xca,
+	0x2f, 0xc9, 0xd7, 0xaf, 0x48, 0xce, 0x48, 0x4c, 0xce, 0x48, 0x34, 0x32, 0x88, 0x2f, 0xc8, 0xcf,
+	0xa9, 0x34, 0x34, 0x36, 0x30, 0xd5, 0x03, 0x4b, 0x08, 0x09, 0xa5, 0xe7, 0xe7, 0xa7, 0xe7, 0xa4,
+	0xea, 0x25, 0x17, 0x55, 0x16, 0x94, 0xe4, 0xeb, 0x81, 0xb4, 0x28, 0xf9, 0x72, 0x89, 0x44, 0x38,
+	0x67, 0x24, 0x3a, 0x83, 0xd4, 0x07, 0x40, 0x95, 0x7b, 0xa7, 0x56, 0x0a, 0x49, 0x70, 0xb1, 0x97,
+	0xa5, 0x16, 0x15, 0x67, 0xe6, 0xe7, 0x49, 0x30, 0x2a, 0x30, 0x6a, 0xf0, 0x06, 0xc1, 0xb8, 0x42,
+	0xd2, 0x5c, 0x9c, 0xd9, 0xa9, 0x95, 0xf1, 0x65, 0x89, 0x39, 0xa5, 0xa9, 0x12, 0xcc, 0x0a, 0x8c,
+	0x1a, 0x3c, 0x41, 0x1c, 0xd9, 0xa9, 0x95, 0x61, 0x20, 0xbe, 0x53, 0x32, 0x97, 0x4c, 0x72, 0x7e,
+	0xae, 0x1e, 0xa6, 0x45, 0x10, 0x27, 0x04, 0x30, 0x46, 0x59, 0xa4, 0x67, 0x96, 0x64, 0x94, 0x26,
+	0xe9, 0x25, 0xe7, 0xe7, 0xea, 0x43, 0x94, 0xe1, 0x77, 0x7b, 0x7c, 0x7a, 0x7e, 0x3c, 0x58, 0x6e,
+	0x11, 0x13, 0x5b, 0x88, 0xa7, 0x9f, 0x77, 0x80, 0x53, 0x12, 0x1b, 0x98, 0x6f, 0x0c, 0x08, 0x00,
+	0x00, 0xff, 0xff, 0xe9, 0x5f, 0x24, 0x80, 0x01, 0x01, 0x00, 0x00,
+}
diff --git a/third_party/aws_sdk_cpp.BUILD.bazel b/third_party/aws_sdk_cpp.BUILD.bazel
new file mode 100644
index 0000000..8cf8aa9
--- /dev/null
+++ b/third_party/aws_sdk_cpp.BUILD.bazel
@@ -0,0 +1,63 @@
+licenses(["notice"])  # Apache 2.0
+
+load("@tink//tools:common.bzl", "template_rule")
+
+cc_library(
+    name = "aws_sdk_core",
+    srcs = glob([
+        "aws-cpp-sdk-core/include/**/*.h",
+        "aws-cpp-sdk-core/source/*.cpp",
+        "aws-cpp-sdk-core/source/auth/**/*.cpp",
+        "aws-cpp-sdk-core/source/config/**/*.cpp",
+        "aws-cpp-sdk-core/source/client/**/*.cpp",
+        "aws-cpp-sdk-core/source/external/**/*.cpp",
+        "aws-cpp-sdk-core/source/internal/**/*.cpp",
+        "aws-cpp-sdk-core/source/http/*.cpp",
+        "aws-cpp-sdk-core/source/http/curl/**/*.cpp",
+        "aws-cpp-sdk-core/source/http/standard/**/*.cpp",
+        "aws-cpp-sdk-core/source/platform/linux-shared/*.cpp",
+        "aws-cpp-sdk-core/source/utils/*.cpp",
+        "aws-cpp-sdk-core/source/utils/*.h",
+        "aws-cpp-sdk-core/source/utils/base64/**/*.cpp",
+        "aws-cpp-sdk-core/source/utils/json/**/*.cpp",
+        "aws-cpp-sdk-core/source/utils/logging/**/*.cpp",
+        "aws-cpp-sdk-core/source/utils/memory/**/*.cpp",
+        "aws-cpp-sdk-core/source/utils/stream/**/*.cpp",
+        "aws-cpp-sdk-core/source/utils/threading/**/*.cpp",
+        "aws-cpp-sdk-core/source/utils/xml/**/*.cpp",
+        "aws-cpp-sdk-core/source/utils/crypto/*.cpp",
+        "aws-cpp-sdk-core/source/utils/crypto/factory/**/*.cpp",
+        "aws-cpp-sdk-kms/include/**/*.h",
+        "aws-cpp-sdk-kms/source/**/*.cpp",
+        ]),
+    hdrs = [
+        "aws-cpp-sdk-core/include/aws/core/SDKConfig.h",
+    ],
+    includes = [
+        "aws-cpp-sdk-core/include/",
+        "aws-cpp-sdk-kms/include/",
+    ],
+    # These must be in sync with version of aws_cpp_sdk in WORKSPACE.
+    defines = [
+        "AWS_SDK_VERSION_MAJOR=1",
+        "AWS_SDK_VERSION_MINOR=4",
+        "AWS_SDK_VERSION_PATCH=80",
+        "ENABLE_CURL_CLIENT",
+        "ENABLE_NO_ENCRYPTION",
+        "PLATFORM_LINUX",
+    ],
+    visibility = ["//visibility:public"],
+    strip_include_prefix = "aws-cpp-sdk-core/include",
+    deps = [
+        "@curl",
+    ],
+)
+
+template_rule(
+    name = "SDKConfig_h",
+    src = "aws-cpp-sdk-core/include/aws/core/SDKConfig.h.in",
+    out = "aws-cpp-sdk-core/include/aws/core/SDKConfig.h",
+    substitutions = {
+        "cmakedefine": "define",
+    },
+)
diff --git a/third_party/curl.BUILD.bazel b/third_party/curl.BUILD.bazel
new file mode 100644
index 0000000..55a1f45
--- /dev/null
+++ b/third_party/curl.BUILD.bazel
@@ -0,0 +1,634 @@
+# Description:
+#   curl is a tool for talking to web servers.
+
+licenses(["notice"])  # MIT/X derivative license
+
+# Settings for building in various environments.
+config_setting(
+    name = "linux_x86_64",
+    values = {"cpu": "k8"},
+)
+
+config_setting(
+    name = "mac_x86_64",
+    values = {"cpu": "darwin"},
+)
+
+cc_library(
+    name = "curl",
+    srcs = [
+        "include/curl_config.h",
+        "lib/amigaos.h",
+        "lib/arpa_telnet.h",
+        "lib/asyn.h",
+        "lib/asyn-ares.c",
+        "lib/base64.c",
+        "lib/config-win32.h",
+        "lib/conncache.c",
+        "lib/conncache.h",
+        "lib/connect.c",
+        "lib/connect.h",
+        "lib/content_encoding.c",
+        "lib/content_encoding.h",
+        "lib/cookie.c",
+        "lib/cookie.h",
+        "lib/curl_addrinfo.c",
+        "lib/curl_addrinfo.h",
+        "lib/curl_base64.h",
+        "lib/curl_des.h",
+        "lib/curl_endian.h",
+        "lib/curl_fnmatch.c",
+        "lib/curl_fnmatch.h",
+        "lib/curl_gethostname.c",
+        "lib/curl_gethostname.h",
+        "lib/curl_gssapi.h",
+        "lib/curl_hmac.h",
+        "lib/curl_ldap.h",
+        "lib/curl_md4.h",
+        "lib/curl_md5.h",
+        "lib/curl_memory.h",
+        "lib/curl_memrchr.c",
+        "lib/curl_memrchr.h",
+        "lib/curl_multibyte.c",
+        "lib/curl_multibyte.h",
+        "lib/curl_ntlm_core.h",
+        "lib/curl_ntlm_wb.h",
+        "lib/curl_printf.h",
+        "lib/curl_rtmp.c",
+        "lib/curl_rtmp.h",
+        "lib/curl_sasl.c",
+        "lib/curl_sasl.h",
+        "lib/curl_sec.h",
+        "lib/curl_setup.h",
+        "lib/curl_setup_once.h",
+        "lib/curl_sspi.c",
+        "lib/curl_sspi.h",
+        "lib/curl_threads.c",
+        "lib/curl_threads.h",
+        "lib/curlx.h",
+        "lib/dict.h",
+        "lib/dotdot.c",
+        "lib/dotdot.h",
+        "lib/easy.c",
+        "lib/easyif.h",
+        "lib/escape.c",
+        "lib/escape.h",
+        "lib/file.h",
+        "lib/fileinfo.c",
+        "lib/fileinfo.h",
+        "lib/formdata.c",
+        "lib/formdata.h",
+        "lib/ftp.h",
+        "lib/ftplistparser.h",
+        "lib/getenv.c",
+        "lib/getinfo.c",
+        "lib/getinfo.h",
+        "lib/gopher.h",
+        "lib/hash.c",
+        "lib/hash.h",
+        "lib/hmac.c",
+        "lib/hostasyn.c",
+        "lib/hostcheck.c",
+        "lib/hostcheck.h",
+        "lib/hostip.c",
+        "lib/hostip.h",
+        "lib/hostip4.c",
+        "lib/hostip6.c",
+        "lib/hostsyn.c",
+        "lib/http.c",
+        "lib/http.h",
+        "lib/http2.c",
+        "lib/http2.h",
+        "lib/http_chunks.c",
+        "lib/http_chunks.h",
+        "lib/http_digest.c",
+        "lib/http_digest.h",
+        "lib/http_negotiate.h",
+        "lib/http_ntlm.h",
+        "lib/http_proxy.c",
+        "lib/http_proxy.h",
+        "lib/if2ip.c",
+        "lib/if2ip.h",
+        "lib/imap.h",
+        "lib/inet_ntop.h",
+        "lib/inet_pton.c",
+        "lib/inet_pton.h",
+        "lib/krb5.c",
+        "lib/llist.c",
+        "lib/llist.h",
+        "lib/md4.c",
+        "lib/md5.c",
+        "lib/memdebug.c",
+        "lib/memdebug.h",
+        "lib/mprintf.c",
+        "lib/multi.c",
+        "lib/multihandle.h",
+        "lib/multiif.h",
+        "lib/netrc.c",
+        "lib/netrc.h",
+        "lib/non-ascii.h",
+        "lib/nonblock.c",
+        "lib/nonblock.h",
+        "lib/nwlib.c",
+        "lib/nwos.c",
+        "lib/parsedate.c",
+        "lib/parsedate.h",
+        "lib/pingpong.h",
+        "lib/pipeline.c",
+        "lib/pipeline.h",
+        "lib/pop3.h",
+        "lib/progress.c",
+        "lib/progress.h",
+        "lib/rawstr.c",
+        "lib/rawstr.h",
+        "lib/rtsp.c",
+        "lib/rtsp.h",
+        "lib/security.c",
+        "lib/select.c",
+        "lib/select.h",
+        "lib/sendf.c",
+        "lib/sendf.h",
+        "lib/setup-os400.h",
+        "lib/setup-vms.h",
+        "lib/share.c",
+        "lib/share.h",
+        "lib/sigpipe.h",
+        "lib/slist.c",
+        "lib/slist.h",
+        "lib/smb.h",
+        "lib/smtp.h",
+        "lib/sockaddr.h",
+        "lib/socks.c",
+        "lib/socks.h",
+        "lib/speedcheck.c",
+        "lib/speedcheck.h",
+        "lib/splay.c",
+        "lib/splay.h",
+        "lib/ssh.h",
+        "lib/strdup.c",
+        "lib/strdup.h",
+        "lib/strequal.c",
+        "lib/strequal.h",
+        "lib/strerror.c",
+        "lib/strerror.h",
+        "lib/strtok.c",
+        "lib/strtok.h",
+        "lib/strtoofft.c",
+        "lib/strtoofft.h",
+        "lib/system_win32.h",
+        "lib/telnet.h",
+        "lib/tftp.h",
+        "lib/timeval.c",
+        "lib/timeval.h",
+        "lib/transfer.c",
+        "lib/transfer.h",
+        "lib/url.c",
+        "lib/url.h",
+        "lib/urldata.h",
+        "lib/vauth/cleartext.c",
+        "lib/vauth/cram.c",
+        "lib/vauth/digest.c",
+        "lib/vauth/digest.h",
+        "lib/vauth/ntlm.h",
+        "lib/vauth/oauth2.c",
+        "lib/vauth/vauth.c",
+        "lib/vauth/vauth.h",
+        "lib/version.c",
+        "lib/vtls/axtls.h",
+        "lib/vtls/cyassl.h",
+        "lib/vtls/darwinssl.h",
+        "lib/vtls/gskit.h",
+        "lib/vtls/gtls.h",
+        "lib/vtls/mbedtls.h",
+        "lib/vtls/nssg.h",
+        "lib/vtls/openssl.h",
+        "lib/vtls/polarssl.h",
+        "lib/vtls/polarssl_threadlock.h",
+        "lib/vtls/schannel.h",
+        "lib/vtls/vtls.c",
+        "lib/vtls/vtls.h",
+        "lib/warnless.c",
+        "lib/warnless.h",
+        "lib/wildcard.c",
+        "lib/wildcard.h",
+        "lib/x509asn1.h",
+    ] + select({
+        ":mac_x86_64": [
+            "lib/vtls/darwinssl.c",
+        ],
+        ":linux_x86_64": [
+            "lib/vtls/openssl.c",
+        ],
+    }),
+
+    hdrs = [
+        ":configure",
+        "include/curl/curl.h",
+        "include/curl/curlbuild.h",
+        "include/curl/curlrules.h",
+        "include/curl/curlver.h",
+        "include/curl/easy.h",
+        "include/curl/mprintf.h",
+        "include/curl/multi.h",
+        "include/curl/stdcheaders.h",
+        "include/curl/typecheck-gcc.h",
+    ],
+    copts = [
+        "-Iexternal/curl/lib",
+        "-D_GNU_SOURCE",
+        "-DHAVE_CONFIG_H",
+        "-DCURL_DISABLE_FTP",
+        "-DCURL_DISABLE_NTLM",  # turning it off in configure is not enough
+        "-DHAVE_LIBZ",
+        "-DHAVE_ZLIB_H",
+        "-Wno-string-plus-int",
+    ] + select({
+        ":mac_x86_64": [
+            "-fno-constant-cfstrings",
+        ],
+        ":linux_x86_64": [
+            "-DCURL_MAX_WRITE_SIZE=65536",
+        ],
+    }),
+    defines = ["CURL_STATICLIB"],
+    includes = ["include"],
+    linkopts = select({
+        ":mac_x86_64": [
+            "-Wl,-framework",
+            "-Wl,CoreFoundation",
+            "-Wl,-framework",
+            "-Wl,Security",
+        ],
+        ":linux_x86_64": [
+            "-lrt",
+        ],
+    }),
+    visibility = ["//visibility:public"],
+    deps = [
+        "@boringssl//:ssl",
+        "@zlib_archive//:zlib",
+    ],
+)
+
+cc_binary(
+    name = "curl_bin",
+    srcs = [
+        "lib/config-win32.h",
+        "src/slist_wc.c",
+        "src/slist_wc.h",
+        "src/tool_binmode.c",
+        "src/tool_binmode.h",
+        "src/tool_bname.c",
+        "src/tool_bname.h",
+        "src/tool_cb_dbg.c",
+        "src/tool_cb_dbg.h",
+        "src/tool_cb_hdr.c",
+        "src/tool_cb_hdr.h",
+        "src/tool_cb_prg.c",
+        "src/tool_cb_prg.h",
+        "src/tool_cb_rea.c",
+        "src/tool_cb_rea.h",
+        "src/tool_cb_see.c",
+        "src/tool_cb_see.h",
+        "src/tool_cb_wrt.c",
+        "src/tool_cb_wrt.h",
+        "src/tool_cfgable.c",
+        "src/tool_cfgable.h",
+        "src/tool_convert.c",
+        "src/tool_convert.h",
+        "src/tool_dirhie.c",
+        "src/tool_dirhie.h",
+        "src/tool_doswin.c",
+        "src/tool_doswin.h",
+        "src/tool_easysrc.c",
+        "src/tool_easysrc.h",
+        "src/tool_formparse.c",
+        "src/tool_formparse.h",
+        "src/tool_getparam.c",
+        "src/tool_getparam.h",
+        "src/tool_getpass.c",
+        "src/tool_getpass.h",
+        "src/tool_help.c",
+        "src/tool_help.h",
+        "src/tool_helpers.c",
+        "src/tool_helpers.h",
+        "src/tool_homedir.c",
+        "src/tool_homedir.h",
+        "src/tool_hugehelp.c",
+        "src/tool_hugehelp.h",
+        "src/tool_libinfo.c",
+        "src/tool_libinfo.h",
+        "src/tool_main.c",
+        "src/tool_main.h",
+        "src/tool_metalink.c",
+        "src/tool_metalink.h",
+        "src/tool_mfiles.c",
+        "src/tool_mfiles.h",
+        "src/tool_msgs.c",
+        "src/tool_msgs.h",
+        "src/tool_operate.c",
+        "src/tool_operate.h",
+        "src/tool_operhlp.c",
+        "src/tool_operhlp.h",
+        "src/tool_panykey.c",
+        "src/tool_panykey.h",
+        "src/tool_paramhlp.c",
+        "src/tool_paramhlp.h",
+        "src/tool_parsecfg.c",
+        "src/tool_parsecfg.h",
+        "src/tool_sdecls.h",
+        "src/tool_setopt.c",
+        "src/tool_setopt.h",
+        "src/tool_setup.h",
+        "src/tool_sleep.c",
+        "src/tool_sleep.h",
+        "src/tool_strdup.c",
+        "src/tool_strdup.h",
+        "src/tool_urlglob.c",
+        "src/tool_urlglob.h",
+        "src/tool_util.c",
+        "src/tool_util.h",
+        "src/tool_version.h",
+        "src/tool_vms.c",
+        "src/tool_vms.h",
+        "src/tool_writeenv.c",
+        "src/tool_writeenv.h",
+        "src/tool_writeout.c",
+        "src/tool_writeout.h",
+        "src/tool_xattr.c",
+        "src/tool_xattr.h",
+    ],
+    copts = [
+        "-Iexternal/curl/lib",
+        "-D_GNU_SOURCE",
+        "-DHAVE_CONFIG_H",
+        "-DCURL_DISABLE_LIBCURL_OPTION",
+        "-Wno-string-plus-int",
+    ],
+    deps = [":curl"],
+)
+
+genrule(
+    name = "configure",
+    outs = ["include/curl_config.h"],
+    cmd = "\n".join([
+        "cat <<'EOF' >$@",
+        "#ifndef EXTERNAL_CURL_INCLUDE_CURL_CONFIG_H_",
+        "#define EXTERNAL_CURL_INCLUDE_CURL_CONFIG_H_",
+        "",
+        "#if !defined(_WIN32) && !defined(__APPLE__)",
+        "#  include <openssl/opensslv.h>",
+        "#  if defined(OPENSSL_IS_BORINGSSL)",
+        "#    define HAVE_BORINGSSL 1",
+        "#  endif",
+        "#endif",
+        "",
+        "#if defined(_WIN32)",
+        "#  include \"lib/config-win32.h\"",
+        "#  define BUILDING_LIBCURL 1",
+        "#  define CURL_DISABLE_CRYPTO_AUTH 1",
+        "#  define CURL_DISABLE_DICT 1",
+        "#  define CURL_DISABLE_FILE 1",
+        "#  define CURL_DISABLE_GOPHER 1",
+        "#  define CURL_DISABLE_IMAP 1",
+        "#  define CURL_DISABLE_LDAP 1",
+        "#  define CURL_DISABLE_LDAPS 1",
+        "#  define CURL_DISABLE_POP3 1",
+        "#  define CURL_PULL_WS2TCPIP_H 1",
+        "#  define CURL_DISABLE_SMTP 1",
+        "#  define CURL_DISABLE_TELNET 1",
+        "#  define CURL_DISABLE_TFTP 1",
+        "#  define CURL_PULL_WS2TCPIP_H 1",
+        "#  define USE_WINDOWS_SSPI 1",
+        "#  define USE_WIN32_IDN 1",
+        "#  define USE_SCHANNEL 1",
+        "#  define WANT_IDN_PROTOTYPES 1",
+        "#elif defined(__APPLE__)",
+        "#  define HAVE_FSETXATTR_6 1",
+        "#  define HAVE_SETMODE 1",
+        "#  define HAVE_SYS_FILIO_H 1",
+        "#  define HAVE_SYS_SOCKIO_H 1",
+        "#  define OS \"x86_64-apple-darwin15.5.0\"",
+        "#  define USE_DARWINSSL 1",
+        "#else",
+        "#  define CURL_CA_BUNDLE \"/etc/ssl/certs/ca-certificates.crt\"",
+        "#  define GETSERVBYPORT_R_ARGS 6",
+        "#  define GETSERVBYPORT_R_BUFSIZE 4096",
+        "#  define HAVE_BORINGSSL 1",
+        "#  define HAVE_CLOCK_GETTIME_MONOTONIC 1",
+        "#  define HAVE_CRYPTO_CLEANUP_ALL_EX_DATA 1",
+        "#  define HAVE_FSETXATTR_5 1",
+        "#  define HAVE_GETHOSTBYADDR_R 1",
+        "#  define HAVE_GETHOSTBYADDR_R_8 1",
+        "#  define HAVE_GETHOSTBYNAME_R 1",
+        "#  define HAVE_GETHOSTBYNAME_R_6 1",
+        "#  define HAVE_GETSERVBYPORT_R 1",
+        "#  define HAVE_LIBSSL 1",
+        "#  define HAVE_MALLOC_H 1",
+        "#  define HAVE_MSG_NOSIGNAL 1",
+        "#  define HAVE_OPENSSL_CRYPTO_H 1",
+        "#  define HAVE_OPENSSL_ERR_H 1",
+        "#  define HAVE_OPENSSL_PEM_H 1",
+        "#  define HAVE_OPENSSL_PKCS12_H 1",
+        "#  define HAVE_OPENSSL_RSA_H 1",
+        "#  define HAVE_OPENSSL_SSL_H 1",
+        "#  define HAVE_OPENSSL_X509_H 1",
+        "#  define HAVE_RAND_EGD 1",
+        "#  define HAVE_RAND_STATUS 1",
+        "#  define HAVE_SSL_GET_SHUTDOWN 1",
+        "#  define HAVE_TERMIOS_H 1",
+        "#  define OS \"x86_64-pc-linux-gnu\"",
+        "#  define RANDOM_FILE \"/dev/urandom\"",
+        "#  define USE_OPENSSL 1",
+        "#endif",
+        "",
+        "#if !defined(_WIN32)",
+        "#  define CURL_DISABLE_DICT 1",
+        "#  define CURL_DISABLE_FILE 1",
+        "#  define CURL_DISABLE_GOPHER 1",
+        "#  define CURL_DISABLE_IMAP 1",
+        "#  define CURL_DISABLE_LDAP 1",
+        "#  define CURL_DISABLE_LDAPS 1",
+        "#  define CURL_DISABLE_POP3 1",
+        "#  define CURL_DISABLE_SMTP 1",
+        "#  define CURL_DISABLE_TELNET 1",
+        "#  define CURL_DISABLE_TFTP 1",
+        "#  define CURL_EXTERN_SYMBOL __attribute__ ((__visibility__ (\"default\")))",
+        "#  define ENABLE_IPV6 1",
+        "#  define GETHOSTNAME_TYPE_ARG2 size_t",
+        "#  define GETNAMEINFO_QUAL_ARG1 const",
+        "#  define GETNAMEINFO_TYPE_ARG1 struct sockaddr *",
+        "#  define GETNAMEINFO_TYPE_ARG2 socklen_t",
+        "#  define GETNAMEINFO_TYPE_ARG46 socklen_t",
+        "#  define GETNAMEINFO_TYPE_ARG7 int",
+        "#  define HAVE_ALARM 1",
+        "#  define HAVE_ALLOCA_H 1",
+        "#  define HAVE_ARPA_INET_H 1",
+        "#  define HAVE_ARPA_TFTP_H 1",
+        "#  define HAVE_ASSERT_H 1",
+        "#  define HAVE_BASENAME 1",
+        "#  define HAVE_BOOL_T 1",
+        "#  define HAVE_CONNECT 1",
+        "#  define HAVE_DLFCN_H 1",
+        "#  define HAVE_ERRNO_H 1",
+        "#  define HAVE_FCNTL 1",
+        "#  define HAVE_FCNTL_H 1",
+        "#  define HAVE_FCNTL_O_NONBLOCK 1",
+        "#  define HAVE_FDOPEN 1",
+        "#  define HAVE_FORK 1",
+        "#  define HAVE_FREEADDRINFO 1",
+        "#  define HAVE_FREEIFADDRS 1",
+        "#  if !defined(__ANDROID__)",
+        "#    define HAVE_FSETXATTR 1",
+        "#  endif",
+        "#  define HAVE_FTRUNCATE 1",
+        "#  define HAVE_GAI_STRERROR 1",
+        "#  define HAVE_GETADDRINFO 1",
+        "#  define HAVE_GETADDRINFO_THREADSAFE 1",
+        "#  define HAVE_GETEUID 1",
+        "#  define HAVE_GETHOSTBYADDR 1",
+        "#  define HAVE_GETHOSTBYNAME 1",
+        "#  define HAVE_GETHOSTNAME 1",
+        "#  if !defined(__ANDROID__)",
+        "#    define HAVE_GETIFADDRS 1",
+        "#  endif",
+        "#  define HAVE_GETNAMEINFO 1",
+        "#  define HAVE_GETPPID 1",
+        "#  define HAVE_GETPROTOBYNAME 1",
+        "#  define HAVE_GETPWUID 1",
+        "#  if !defined(__ANDROID__)",
+        "#    define HAVE_GETPWUID_R 1",
+        "#  endif",
+        "#  define HAVE_GETRLIMIT 1",
+        "#  define HAVE_GETTIMEOFDAY 1",
+        "#  define HAVE_GMTIME_R 1",
+        "#  if !defined(__ANDROID__)",
+        "#    define HAVE_IFADDRS_H 1",
+        "#  endif",
+        "#  define HAVE_IF_NAMETOINDEX 1",
+        "#  define HAVE_INET_ADDR 1",
+        "#  define HAVE_INET_NTOP 1",
+        "#  define HAVE_INET_PTON 1",
+        "#  define HAVE_INTTYPES_H 1",
+        "#  define HAVE_IOCTL 1",
+        "#  define HAVE_IOCTL_FIONBIO 1",
+        "#  define HAVE_IOCTL_SIOCGIFADDR 1",
+        "#  define HAVE_LIBGEN_H 1",
+        "#  define HAVE_LIBZ 1",
+        "#  define HAVE_LIMITS_H 1",
+        "#  define HAVE_LL 1",
+        "#  define HAVE_LOCALE_H 1",
+        "#  define HAVE_LOCALTIME_R 1",
+        "#  define HAVE_LONGLONG 1",
+        "#  define HAVE_MEMORY_H 1",
+        "#  define HAVE_NETDB_H 1",
+        "#  define HAVE_NETINET_IN_H 1",
+        "#  define HAVE_NETINET_TCP_H 1",
+        "#  define HAVE_NET_IF_H 1",
+        "#  define HAVE_PERROR 1",
+        "#  define HAVE_PIPE 1",
+        "#  define HAVE_POLL 1",
+        "#  define HAVE_POLL_FINE 1",
+        "#  define HAVE_POLL_H 1",
+        "#  define HAVE_POSIX_STRERROR_R 1",
+        "#  define HAVE_PWD_H 1",
+        "#  define HAVE_RECV 1",
+        "#  define HAVE_SELECT 1",
+        "#  define HAVE_SEND 1",
+        "#  define HAVE_SETJMP_H 1",
+        "#  define HAVE_SETLOCALE 1",
+        "#  define HAVE_SETRLIMIT 1",
+        "#  define HAVE_SETSOCKOPT 1",
+        "#  define HAVE_SGTTY_H 1",
+        "#  define HAVE_SIGACTION 1",
+        "#  define HAVE_SIGINTERRUPT 1",
+        "#  define HAVE_SIGNAL 1",
+        "#  define HAVE_SIGNAL_H 1",
+        "#  define HAVE_SIGSETJMP 1",
+        "#  define HAVE_SIG_ATOMIC_T 1",
+        "#  define HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1",
+        "#  define HAVE_SOCKET 1",
+        "#  define HAVE_SOCKETPAIR 1",
+        "#  define HAVE_STDBOOL_H 1",
+        "#  define HAVE_STDINT_H 1",
+        "#  define HAVE_STDIO_H 1",
+        "#  define HAVE_STDLIB_H 1",
+        "#  define HAVE_STRCASECMP 1",
+        "#  define HAVE_STRDUP 1",
+        "#  define HAVE_STRERROR_R 1",
+        "#  define HAVE_STRINGS_H 1",
+        "#  define HAVE_STRING_H 1",
+        "#  define HAVE_STRNCASECMP 1",
+        "#  define HAVE_STRSTR 1",
+        "#  define HAVE_STRTOK_R 1",
+        "#  define HAVE_STRTOLL 1",
+        "#  define HAVE_STRUCT_SOCKADDR_STORAGE 1",
+        "#  define HAVE_STRUCT_TIMEVAL 1",
+        "#  define HAVE_SYS_IOCTL_H 1",
+        "#  define HAVE_SYS_PARAM_H 1",
+        "#  define HAVE_SYS_POLL_H 1",
+        "#  define HAVE_SYS_RESOURCE_H 1",
+        "#  define HAVE_SYS_SELECT_H 1",
+        "#  define HAVE_SYS_SOCKET_H 1",
+        "#  define HAVE_SYS_STAT_H 1",
+        "#  define HAVE_SYS_TIME_H 1",
+        "#  define HAVE_SYS_TYPES_H 1",
+        "#  define HAVE_SYS_UIO_H 1",
+        "#  define HAVE_SYS_UN_H 1",
+        "#  define HAVE_SYS_WAIT_H 1",
+        "#  define HAVE_SYS_XATTR_H 1",
+        "#  define HAVE_TIME_H 1",
+        "#  define HAVE_UNAME 1",
+        "#  define HAVE_UNISTD_H 1",
+        "#  define HAVE_UTIME 1",
+        "#  define HAVE_UTIME_H 1",
+        "#  define HAVE_VARIADIC_MACROS_C99 1",
+        "#  define HAVE_VARIADIC_MACROS_GCC 1",
+        "#  define HAVE_WRITABLE_ARGV 1",
+        "#  define HAVE_WRITEV 1",
+        "#  define HAVE_ZLIB_H 1",
+        "#  define LT_OBJDIR \".libs/\"",
+        "#  define PACKAGE \"curl\"",
+        "#  define PACKAGE_BUGREPORT \"a suitable curl mailing list: https://curl.haxx.se/mail/\"",
+        "#  define PACKAGE_NAME \"curl\"",
+        "#  define PACKAGE_STRING \"curl -\"",
+        "#  define PACKAGE_TARNAME \"curl\"",
+        "#  define PACKAGE_URL \"\"",
+        "#  define PACKAGE_VERSION \"-\"",
+        "#  define RECV_TYPE_ARG1 int",
+        "#  define RECV_TYPE_ARG2 void *",
+        "#  define RECV_TYPE_ARG3 size_t",
+        "#  define RECV_TYPE_ARG4 int",
+        "#  define RECV_TYPE_RETV ssize_t",
+        "#  define RETSIGTYPE void",
+        "#  define SELECT_QUAL_ARG5",
+        "#  define SELECT_TYPE_ARG1 int",
+        "#  define SELECT_TYPE_ARG234 fd_set *",
+        "#  define SELECT_TYPE_ARG5 struct timeval *",
+        "#  define SELECT_TYPE_RETV int",
+        "#  define SEND_QUAL_ARG2 const",
+        "#  define SEND_TYPE_ARG1 int",
+        "#  define SEND_TYPE_ARG2 void *",
+        "#  define SEND_TYPE_ARG3 size_t",
+        "#  define SEND_TYPE_ARG4 int",
+        "#  define SEND_TYPE_RETV ssize_t",
+        "#  define SIZEOF_INT 4",
+        "#  define SIZEOF_LONG 8",
+        "#  define SIZEOF_OFF_T 8",
+        "#  define SIZEOF_SHORT 2",
+        "#  define SIZEOF_SIZE_T 8",
+        "#  define SIZEOF_TIME_T 8",
+        "#  define SIZEOF_VOIDP 8",
+        "#  define STDC_HEADERS 1",
+        "#  define STRERROR_R_TYPE_ARG3 size_t",
+        "#  define TIME_WITH_SYS_TIME 1",
+        "#  define VERSION \"-\"",
+        "#  ifndef _DARWIN_USE_64_BIT_INODE",
+        "#    define _DARWIN_USE_64_BIT_INODE 1",
+        "#  endif",
+        "#endif",
+        "",
+        "#endif  // EXTERNAL_CURL_INCLUDE_CURL_CONFIG_H_",
+        "EOF",
+    ]),
+)
diff --git a/third_party/rules_protobuf/protobuf/internal/proto_compile.bzl b/third_party/rules_protobuf/protobuf/internal/proto_compile.bzl
index 331fdcb..975667a 100644
--- a/third_party/rules_protobuf/protobuf/internal/proto_compile.bzl
+++ b/third_party/rules_protobuf/protobuf/internal/proto_compile.bzl
@@ -289,7 +289,7 @@
     # This set size must be 0 or 1. (all source files must exist in this
     # workspace or the same external workspace).
     roots = depset(external_roots)
-    n = len(roots)
+    n = len(roots.to_list())
     if n:
         if n > 1:
             fail(
@@ -316,7 +316,7 @@
     transitive_units = depset()
     for u in unit.data.transitive_units:
         transitive_units = transitive_units | u.inputs
-    inputs = list(unit.inputs | transitive_units) + [unit.compiler]
+    inputs = depset(transitive = [unit.inputs, transitive_units]).to_list() + [unit.compiler]
     outputs = list(unit.outputs)
 
     cmds = [" ".join(protoc_cmd)]
@@ -493,7 +493,7 @@
             mandatory = False,
         ),
         "protos": attr.label_list(
-            allow_files = FileType([".proto"]),
+            allow_files = [".proto"],
         ),
         "deps": attr.label_list(
             providers = ["proto_compile_result"],
diff --git a/third_party/zlib.BUILD.bazel b/third_party/zlib.BUILD.bazel
new file mode 100644
index 0000000..e35d028
--- /dev/null
+++ b/third_party/zlib.BUILD.bazel
@@ -0,0 +1,40 @@
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # BSD/MIT-like license (for zlib)
+
+cc_library(
+    name = "zlib",
+    srcs = [
+        "adler32.c",
+        "compress.c",
+        "crc32.c",
+        "crc32.h",
+        "deflate.c",
+        "deflate.h",
+        "gzclose.c",
+        "gzguts.h",
+        "gzlib.c",
+        "gzread.c",
+        "gzwrite.c",
+        "infback.c",
+        "inffast.c",
+        "inffast.h",
+        "inffixed.h",
+        "inflate.c",
+        "inflate.h",
+        "inftrees.c",
+        "inftrees.h",
+        "trees.c",
+        "trees.h",
+        "uncompr.c",
+        "zconf.h",
+        "zutil.c",
+        "zutil.h",
+    ],
+    hdrs = ["zlib.h"],
+    copts = [
+        "-Wno-shift-negative-value",
+        "-DZ_HAVE_UNISTD_H",
+    ],
+    includes = ["."],
+)
diff --git a/tink_version.bzl b/tink_version.bzl
new file mode 100644
index 0000000..b6e0c97
--- /dev/null
+++ b/tink_version.bzl
@@ -0,0 +1,2 @@
+""" Version of the current release of Tink """
+TINK_VERSION_LABEL = "1.3.0-SNAPSHOT"
diff --git a/tools/build_defs/javac.bzl b/tools/build_defs/javac.bzl
index 83432e8..e706ab4 100644
--- a/tools/build_defs/javac.bzl
+++ b/tools/build_defs/javac.bzl
@@ -1,12 +1,5 @@
 """Build definitions for javac related operations in tink."""
 
-XLINT_OPTS = [
-    "-Werror",
-    "-Xlint:all",
-    "-Xlint:-serial",
-    "-Xlint:-classfile",
-]
-
 # errorprone checks primarily related to coding style, enabled to
 # improve uniformity.
 EP_STYLE_CHECKS = [
@@ -106,6 +99,6 @@
     "-target 1.7",
 ]
 
-JAVACOPTS = XLINT_OPTS + EP_OPTS
+JAVACOPTS = EP_OPTS
 
-JAVACOPTS_OSS = XLINT_OPTS + EP_OPTS + SOURCE_7_TARGET_7
+JAVACOPTS_OSS = EP_OPTS + SOURCE_7_TARGET_7
diff --git a/tools/common.bzl b/tools/common.bzl
new file mode 100644
index 0000000..0d1dfd7
--- /dev/null
+++ b/tools/common.bzl
@@ -0,0 +1,59 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+""" Rule for simple expansion of template files. """
+
+# Rule for simple expansion of template files. This performs a simple
+# search over the template file for the keys in substitutions,
+# and replaces them with the corresponding values.
+#
+# Borrowed from TensorFlow (https://github.com/tensorflow/tensorflow)
+#
+# Typical usage:
+#   load("/tools/build_rules/template_rule", "expand_header_template")
+#   template_rule(
+#       name = "ExpandMyTemplate",
+#       src = "my.template",
+#       out = "my.txt",
+#       substitutions = {
+#         "$VAR1": "foo",
+#         "$VAR2": "bar",
+#       }
+#   )
+#
+# Args:
+#   name: The name of the rule.
+#   template: The template file to expand
+#   out: The destination of the expanded file
+#   substitutions: A dictionary mapping strings to their substitutions
+
+def template_rule_impl(ctx):
+    ctx.actions.expand_template(
+        template = ctx.file.src,
+        output = ctx.outputs.out,
+        substitutions = ctx.attr.substitutions,
+    )
+
+template_rule = rule(
+    attrs = {
+        "src": attr.label(
+            mandatory = True,
+            allow_files = True,
+            single_file = True,
+        ),
+        "substitutions": attr.string_dict(mandatory = True),
+        "out": attr.output(mandatory = True),
+    },
+    # output_to_genfiles is required for header files.
+    output_to_genfiles = True,
+    implementation = template_rule_impl,
+)
diff --git a/tools/convert_for_cobalt b/tools/convert_for_cobalt
new file mode 100755
index 0000000..4bd4235
--- /dev/null
+++ b/tools/convert_for_cobalt
@@ -0,0 +1,723 @@
+#! /usr/bin/env python3
+
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os.path
+import sys
+import datetime
+
+
+def TargetNameFromInTreeSpec(spec):
+  dirname, name = ParseTargetSpec(spec)
+  head = dirname
+  path_list = [name]
+  while head != '':
+    head, tail = os.path.split(head)
+    path_list.insert(0, tail)
+  return '_'.join(path_list)
+
+
+def IsSubdir(parent, child):
+  if parent == child:
+    return False
+  return (os.path.commonpath([parent, child]) == parent)
+
+
+class BazelGlobals(object):
+  IGNORABLES = frozenset([
+      'load',
+      'package',
+      'licenses',
+      'exports_files',
+      'select',
+  ])
+
+  def __init__(self, build_graph, dirpath):
+    self._build_graph = build_graph
+    self._dirpath = dirpath
+    self._set_objects = {}
+
+  def __getitem__(self, key):
+    if key in self._set_objects:
+      return self._set_objects[key]
+
+    if key == '__builtins__':
+      raise KeyError(key)
+
+    if key in self.IGNORABLES:
+      return lambda *args, **kwargs: None
+
+    return self._build_graph.GetTargetAdder(self._dirpath, key)
+
+  def __setitem__(self, key, value):
+    self._set_objects[key] = value
+
+  def __contains__(self, key):
+    if key == '__builtins__':
+      raise KeyError(key)
+
+    return True
+
+  def __len__(self):
+    return 1
+
+
+class BuildGraph(object):
+
+  def __init__(self, root_path, ignored_targets):
+    self._targets = {}
+    if not os.path.isabs(root_path):
+      raise Exception('{} is not an absolute path.'.format(root_path))
+    self._root_path = root_path
+    self._ignored_targets = ignored_targets
+
+    # These are specs that will be remapped.
+    self._spec_map_old_to_new = {}
+
+  def AddTarget(self, target_cls, dirpath, name, deps=None, **kwargs):
+    if deps:
+      deps = [MakeTargetRef(dep, dirpath) for dep in deps]
+      deps = [dep for dep in deps if dep.spec() not in self._ignored_targets]
+      for i in range(len(deps)):
+        if deps[i].spec() not in self._spec_map_old_to_new:
+          continue
+        deps[i] = MakeTargetRef(self._spec_map_old_to_new[deps[i].spec()], dirpath)
+
+    target = target_cls(
+        dirpath=dirpath,
+        name=name,
+        sort_id=len(self._targets),
+        deps=deps,
+        **kwargs)
+    assert(isinstance(target, Target))
+    target = self.RemapTarget(target)
+    assert(isinstance(target, Target))
+    self._targets[target.spec()] = target
+
+  def RemapTarget(self, target):
+    "CMake does not like it if a cc_library has headers in a subdir. So we move the target."
+    if not isinstance(target, CcLibTarget):
+      return target
+
+    dirnames = [os.path.dirname(hdr) for hdr in target.hdrs]
+    if not any(d != '' for d in dirnames):
+      return target
+
+    if not all(d != '' for d in dirnames):
+      raise Exception('I do not know how to deal with targets that have some headers and sources in a subdirectory and some not: ' + target.spec)
+
+    if not all(d == dirnames[0] for d in dirnames):
+      raise Exception('I do not know how to deal with targets that have headers and sources in different subdirectories: ' + target.spec)
+
+    subdir = dirnames[0]
+
+    old_spec = target.spec()
+    dirpath = os.path.join(target.dirpath(), subdir)
+    name = target.name()
+    sort_id = target.sort_id()
+    deps = target.deps()
+    srcs = [os.path.relpath(src, subdir) for src in target.srcs]
+    hdrs = [os.path.relpath(hdr, subdir) for hdr in target.hdrs]
+    target = CcLibTarget(dirpath, name, sort_id, deps, srcs=srcs, hdrs=hdrs)
+
+    new_spec = target.spec()
+
+    self._spec_map_old_to_new[old_spec] = new_spec
+
+    for t in self._targets.values():
+      t.RemapDep(old_spec, new_spec)
+
+    return target
+
+
+  def GetTargetAdder(self, absdirpath, target_type):
+    if not IsSubdir(self._root_path, absdirpath):
+      Exception(
+          'GetTargetAdder expected a path under {} which {} is not.'.format(
+              self._root_path, absdirpath))
+    dirpath = os.path.relpath(absdirpath, self._root_path)
+    if target_type == 'proto_library':
+      return self.GetProtoLibraryAdder(dirpath)
+    elif target_type == 'cc_library':
+      return self.GetCcLibraryAdder(dirpath)
+    elif target_type == 'cc_proto_library':
+      return self.GetCcProtoLibraryAdder(dirpath)
+    return self.GetUnknownTargetAdder(dirpath, target_type)
+
+  def GetUnknownTargetAdder(self, dirpath, target_type):
+
+    def Add(*args, **kwargs):
+      if 'name' not in kwargs or args:
+        raise Exception(
+            '"{}" is not a target type. Please add to IGNORABLES.'.format(
+                target_type))
+      name = kwargs['name']
+      deps = kwargs.get('deps', [])
+      self.AddTarget(
+          UnknownTarget, dirpath, name, deps=deps, target_type=target_type)
+
+    return Add
+
+  def GetCcProtoLibraryAdder(self, dirpath):
+
+    def Add(name, deps=None, **kwargs):
+      self.AddTarget(CcProtoLibTarget, dirpath, name, deps=deps)
+
+    return Add
+
+  def GetProtoLibraryAdder(self, dirpath):
+
+    def Add(name, srcs, deps=None, **kwargs):
+      self.AddTarget(ProtoLibTarget, dirpath, name, srcs=srcs, deps=deps)
+
+    return Add
+
+  def GetCcLibraryAdder(self, dirpath):
+
+    def Add(name, srcs=None, hdrs=None, deps=None, **kwargs):
+      self.AddTarget(
+          CcLibTarget, dirpath, name, hdrs=hdrs, srcs=srcs, deps=deps)
+
+    return Add
+
+  def AddToGraph(self, dirpath):
+    filepath = os.path.join(dirpath, 'BUILD.bazel')
+    fp = open(filepath, 'r')
+    content = fp.read()
+    gs = BazelGlobals(self, dirpath)
+    exec(content, {}, gs)
+
+  def GetTarget(self, target_spec):
+    target_spec = self._spec_map_old_to_new.get(target_spec, target_spec)
+    if target_spec in self._ignored_targets:
+      raise Exception('{} is being ignored'.format(target_spec))
+    ref = MakeTargetRef(target_spec, '')
+    if not ref.InTree():
+      raise Exception('{} is not in the tree.'.format(target_spec))
+    if target_spec not in self._targets:
+      dirpath, _ = ParseTargetSpec(target_spec)
+      absdirpath = os.path.join(self._root_path, dirpath)
+      self.AddToGraph(absdirpath)
+      if target_spec not in self._targets:
+        print(self._targets.keys())
+        raise Exception('{} could not be found in {}'.format(
+            target_spec, absdirpath))
+
+    return self._targets[target_spec]
+
+
+##############################################################
+# The Targets themselves.
+##############################################################
+class Target(object):
+
+  def __init__(self, dirpath, name, sort_id, deps):
+    assert (isinstance(sort_id, int))
+    self._sort_id = sort_id
+    assert (isinstance(name, str))
+    self._name = name
+    assert (isinstance(dirpath, str))
+    self._dirpath = dirpath
+    self._deps = deps or []
+    assert (all(isinstance(dep, TargetRef) for dep in self._deps))
+
+  def __repr__(self):
+    raise NotImplementedError
+
+  def name(self):
+    return self._name
+
+  def spec(self):
+    return '//{}:{}'.format(self._dirpath, self._name)
+
+  def deps(self):
+    return self._deps
+
+  def dirpath(self):
+    return self._dirpath
+
+  def sort_id(self):
+    return self._sort_id
+
+  def target_type(self):
+    raise NotImplementedError
+
+  def IsUnknown(self):
+    return False
+
+  def IsProto(self):
+    return False
+
+  def RemapDep(self, old, new):
+    for i in range(len(self._deps)):
+      if self._deps[i].spec() == old:
+        self._deps[i] = MakeTargetRef(new, self._dirpath)
+
+
+class UnknownTarget(Target):
+
+  def __init__(self, dirpath, name, sort_id, target_type, deps):
+    super(UnknownTarget, self).__init__(dirpath, name, sort_id, deps=deps)
+    assert (isinstance(target_type, str))
+    self._target_type = target_type
+
+  def __repr__(self):
+    return 'UnknownTarget<{}: {}>'.format(self._target_type, self.spec())
+
+  def IsUnknown(self):
+    return True
+
+
+class LibTarget(Target):
+
+  def __init__(self, dirpath, name, sort_id, deps, srcs=None):
+    super(LibTarget, self).__init__(dirpath, name, sort_id, deps)
+    self.srcs = srcs or []
+
+  def __repr__(self):
+    return 'LibTarget<{}>'.format(self.spec())
+
+
+class ProtoLibTarget(LibTarget):
+
+  def IsProto(self):
+    return True
+
+  def __repr__(self):
+    return 'ProtoLibTarget<{}>'.format(self.spec())
+
+  def target_type(self):
+    return 'Proto Library'
+
+  def GetCMakeDep(self):
+    spec = self.spec()
+    suffix = '_proto'
+    if not spec.endswith(suffix):
+      raise Exception('Unexpected name for proto_library: ' + self.spec())
+    spec = spec[:-len(suffix)] + '_lib'
+
+    return TargetNameFromInTreeSpec(spec)
+
+
+class CcProtoLibTarget(Target):
+
+  def IsProto(self):
+    return True
+
+  def __repr__(self):
+    return 'CcProtoLibTarget<{}>'.format(self.spec())
+
+  def target_type(self):
+    return 'CC Proto Library'
+
+  def GetCMakeDep(self):
+    spec = self.spec()
+    suffix = '_cc_proto'
+    if not spec.endswith(suffix):
+      raise Exception('Unexpected name for cc_proto_library: ' + self.spec())
+    spec = spec[:-len(suffix)] + '_lib'
+
+    return TargetNameFromInTreeSpec(spec)
+
+
+class CcLibTarget(LibTarget):
+
+  def __init__(self, dirpath, name, sort_id, deps, srcs=None, hdrs=None):
+    super(CcLibTarget, self).__init__(
+        dirpath, name, sort_id, srcs=srcs, deps=deps)
+    self.hdrs = hdrs or []
+
+  def __repr__(self):
+    return 'CcLibTarget<{}>'.format(self.spec())
+
+  def target_type(self):
+    return 'CC Library'
+
+  def GetCMakeDep(self):
+    return TargetNameFromInTreeSpec(self.spec())
+
+
+##############################################################
+# Target references. (Used mostly for dependencies)
+##############################################################
+def ParseTargetSpec(dep):
+  parts = dep.split(':')
+  dirpath = parts[0][2:]
+  if len(parts) == 1:
+    name = os.path.basename(dep)
+  elif len(parts) == 2:
+    name = parts[1]
+  else:
+    raise Exception('I do not know how to deal with this dep: "{}"'.format(dep))
+
+  return dirpath, name
+
+
+def MakeTargetRef(dep, dirpath):
+  if dep[0] == ':':
+    return InTreeTargetRef(dirpath, dep[1:])
+  if dep[:2] == '//':
+    dirpath, name = ParseTargetSpec(dep)
+    return InTreeTargetRef(dirpath, name)
+
+  absl_prefix = '@com_google_absl'
+  if dep.startswith(absl_prefix):
+    dep = dep[len(absl_prefix):]
+    dirpath, name = ParseTargetSpec(dep)
+    return AbslTargetRef(dirpath, name)
+
+  if dep == '@com_google_protobuf//:protobuf_lite':
+    return ProtobufLiteTargetRef()
+
+  if dep == '@rapidjson':
+    return RapidJsonTargetRef()
+
+  googletest_prefix = '@com_google_googletest'
+  if dep.startswith(googletest_prefix):
+    dep = dep[len(googletest_prefix):]
+    dirpath, name = ParseTargetSpec(dep)
+    return GoogleTestTargetRef(dirpath, name)
+
+  boringssl_prefix = '@boringssl'
+  if dep.startswith(boringssl_prefix):
+    dep = dep[len(boringssl_prefix):]
+    dirpath, name = ParseTargetSpec(dep)
+    return BoringSslTargetRef(dirpath, name)
+
+  raise Exception('I do not know how to deal with this dep: "{}"'.format(dep))
+
+
+class TargetRef(object):
+
+  def InTree(self):
+    return False
+
+
+class PathTargetRef(TargetRef):
+
+  def __init__(self, dirpath, name):
+    self._dirpath = dirpath
+    self._name = name
+
+  def spec(self):
+    return '//{}:{}'.format(self._dirpath, self._name)
+
+  def dirpath(self):
+    return self._dirpath
+
+  def __repr__(self):
+    return '{}<{}>'.format(type(self).__name__, self.spec())
+
+
+class InTreeTargetRef(PathTargetRef):
+
+  def InTree(self):
+    return True
+
+
+class AbslTargetRef(PathTargetRef):
+
+  def GetCMakeDep(self):
+    return '::'.join(self._dirpath.split('/'))
+
+
+class GoogleTestTargetRef(PathTargetRef):
+  pass
+
+
+class ProtobufLiteTargetRef(TargetRef):
+
+  def GetCMakeDep(self):
+    return 'protobuf_lite'
+
+  def spec(self):
+    return '@protobuf_lite'
+
+
+class RapidJsonTargetRef(TargetRef):
+
+  def spec(self):
+    return '@rapidjson'
+
+
+class BoringSslTargetRef(PathTargetRef):
+
+  def GetCMakeDep(self):
+    return self._name
+
+  pass
+
+
+##############################################################
+# TargetFetcher grabs all the targets we need.
+##############################################################
+class TargetFetcher(object):
+
+  def __init__(self, build_graph, initial_targets, error_on_unknown):
+    # _to_fetch is a list of target specs.
+    self._to_fetch = initial_targets
+    self._targets = []
+    self._fetched = set()
+    self._build_graph = build_graph
+    self._error_on_unknown = error_on_unknown
+
+  def FetchAll(self):
+    while self._to_fetch:
+      self.FetchNext()
+    return self._targets
+
+  def FetchNext(self):
+    spec = self._to_fetch.pop(0)
+    if spec in self._fetched:
+      return
+
+    target = self._build_graph.GetTarget(spec)
+    if target.IsUnknown() and self._error_on_unknown:
+      raise Exception('Found unknown target: {}'.format(target))
+    self._targets.append(target)
+
+    # We add both the actual spec and the spec we tried to fetch just in
+    # case there was a remapping.
+    self._fetched.add(target.spec())
+    self._fetched.add(spec)
+    for dep in target.deps():
+      if not dep.InTree():
+        continue
+      if dep.spec() not in self._fetched:
+        self._to_fetch.append(dep.spec())
+
+
+##############################################################
+# BuildFile holds the information necessary to create a build file.
+##############################################################
+class BuildFile(object):
+
+  def __init__(self, dirpath, targets, subdirs):
+    self.dirpath = dirpath
+    self.targets = targets
+    self.subdirs = sorted(subdirs)
+
+  def HasProto(self):
+    return any(t.IsProto() for t in self.targets.values())
+
+
+def CreateBuildFiles(targets):
+  build_file_targets = {}
+  for t in targets:
+    if t.dirpath() not in build_file_targets:
+      build_file_targets[t.dirpath()] = {}
+    build_file_targets[t.dirpath()][t.spec()] = t
+
+  dirpaths = [k for k in build_file_targets.keys()]
+  build_files = []
+
+  for dirpath, targets in build_file_targets.items():
+    subdirs = []
+    for d in dirpaths:
+      # If d is an immediate subdirectory of dirpath, add it to subdirs
+      if not IsSubdir(dirpath, d):
+        continue
+      rel = os.path.relpath(d, dirpath)
+      if os.path.basename(rel) == rel:
+        subdirs.append(rel)
+
+    build_files.append(BuildFile(dirpath, targets, subdirs))
+
+  return build_files
+
+
+class BuildFileWriter(object):
+
+  def __init__(self, rootdir, build_graph, max_line):
+    self._rootdir = rootdir
+    self._max_line = max_line
+    self._build_graph = build_graph
+
+  def GetBuildFilePath(self, dirpath):
+    raise NotImplementedError
+
+  def WriteBuildFile(self, build_file):
+    self._indent = 0
+    self._filepath = self.GetBuildFilePath(build_file)
+    self._fp = open(self._filepath, 'w+')
+    self._build_file = build_file
+    self._nl = False
+
+    print('Writing to {},'.format(self._filepath))
+    self.WriteHeader()
+    targets = sorted(
+        build_file.targets.values(), key=lambda target: target.sort_id())
+
+    for target in targets:
+      self.WriteTarget(target)
+
+    assert (self._indent == 0)
+
+  def WriteHeader(self):
+    year = datetime.datetime.today().year
+    self.emitlines([
+        '# Copyright {} The Fuchsia Authors. All rights reserved.'.format(year),
+        '# Use of this source code is governed by a BSD-style license that can be',
+        '# found in the LICENSE file.'
+    ])
+
+  def WriteTarget(self, target):
+    self.emitnl('# {} : {}'.format(target.target_type(), target.name()))
+
+  def emit(self, s):
+    if self._nl:
+      self._fp.write('  ' * self._indent)
+      self._nl = False
+    if not isinstance(s, str):
+      raise Exception('emit expected a string, not {}'.format(s))
+    self._fp.write(s)
+
+  def emitnl(self, s):
+    self.emit(s)
+    self.nl()
+
+  def emitlines(self, lines):
+    for line in lines:
+      self.emitnl(line)
+
+  def nl(self):
+    self._fp.write('\n')
+    self._nl = True
+
+  def indent(self):
+    self._indent += 1
+
+  def deindent(self):
+    self._indent -= 1
+
+
+class CMakeWriter(BuildFileWriter):
+
+  def __init__(self, rootdir, target_prefix, build_graph, max_line):
+    super(CMakeWriter, self).__init__(
+        rootdir=rootdir, build_graph=build_graph, max_line=max_line)
+    self._target_prefix = target_prefix
+
+  def GetBuildFilePath(self, build_file):
+    return os.path.join(self._rootdir, build_file.dirpath, 'CMakeLists.txt')
+
+  def SingleLineFunction(self, func, *args):
+    return '{}({})'.format(func, ' '.join(args))
+
+  def WriteFunction(self, func, *args):
+    single = self.SingleLineFunction(func, *args)
+    if len(single) <= self._max_line:
+      self.emitnl(single)
+      return
+
+    self.emitnl('{}('.format(func))
+    self.indent()
+
+    for arg in args:
+      self.emitnl(arg)
+
+    self.deindent()
+    self.emitnl(')')
+
+  def WriteHeader(self):
+    super(CMakeWriter, self).WriteHeader()
+    self.nl()
+    for subdir in self._build_file.subdirs:
+      self.WriteFunction('add_subdirectory', subdir)
+    self.nl()
+
+    if not self._build_file.HasProto():
+      return
+
+  def WriteTarget(self, target):
+    # Skip cc_proto_lib targets.
+    if isinstance(target, CcProtoLibTarget):
+      return
+    super(CMakeWriter, self).WriteTarget(target)
+    if isinstance(target, CcLibTarget):
+      self.WriteCcLibTarget(target)
+      return
+    if isinstance(target, ProtoLibTarget):
+      self.WriteProtoLibTarget(target)
+      return
+    raise NotImplementedError('I do not know how to write {}'.format(
+        str(target)))
+
+  def WriteLibTargetDeps(self, target):
+    name = self.TargetName(target)
+    in_tree_deps = [self.DepName(dep) for dep in target.deps() if dep.InTree()]
+    deps = [self.DepName(dep) for dep in target.deps()]
+
+    if in_tree_deps:
+      self.WriteFunction('add_dependencies', name, *in_tree_deps)
+    if deps:
+      self.WriteFunction('target_link_libraries', name, *deps)
+
+  def WriteCcLibTarget(self, target):
+    name = self.TargetName(target)
+    self.WriteFunction('add_library', name, *target.hdrs, *target.srcs)
+    if not target.srcs:
+      self.WriteFunction('set_target_properties', name, 'PROPERTIES',
+                         'LINKER_LANGUAGE', 'CXX')
+    if target.hdrs:
+      self.WriteFunction('tink_export_hdrs', *target.hdrs)
+    self.WriteLibTargetDeps(target)
+    self.nl()
+
+  def WriteProtoLibTarget(self, target):
+    name = self.TargetName(target)
+    srcs = []
+    for src in target.srcs:
+      suffix = '.proto'
+      if not src.endswith(suffix):
+        raise Exception('Unexpected name {} for proto in {}'.format(
+            src, target.spec()))
+      srcs.append(src[:-len(suffix)])
+    hdrs_out = name.upper() + '_PROTO_HDRS'
+    self.WriteFunction('tink_make_protobuf_cpp_lib', name, hdrs_out, *srcs)
+    self.WriteLibTargetDeps(target)
+    self.nl()
+
+  def TargetName(self, target):
+    return self._target_prefix + '_' + target.GetCMakeDep()
+
+  def DepName(self, dep):
+    if dep.InTree():
+      target = self._build_graph.GetTarget(dep.spec())
+      return self._target_prefix + '_' + target.GetCMakeDep()
+    return dep.GetCMakeDep()
+
+
+def main(args):
+  script_path = os.path.abspath(args[0])
+  tools_path = os.path.dirname(script_path)
+  tink_path = os.path.dirname(tools_path)
+  cc_path = os.path.join(tink_path, 'cc')
+
+  # Targets listed her are the start of the fetch. Put all the targets you plan
+  # on directly depending upon here.
+  targets = [
+      '//cc/hybrid:hybrid_config',
+      '//cc/hybrid:hybrid_encrypt_factory',
+      '//cc/hybrid:hybrid_decrypt_factory',
+      '//cc:hybrid_decrypt',
+      '//cc:hybrid_encrypt',
+      '//cc:keyset_handle',
+  ]
+  # Targets listed here will be ignored.
+  ignored_targets = frozenset([])
+  graph = BuildGraph(tink_path, ignored_targets)
+  fetcher = TargetFetcher(graph, targets, error_on_unknown=True)
+  targets = fetcher.FetchAll()
+  print('Found {} targets.'.format(len(targets)))
+  build_files = CreateBuildFiles(targets)
+  cmake_writer = CMakeWriter(
+      rootdir=tink_path, target_prefix='tink', build_graph=graph, max_line=80)
+  for f in build_files:
+    cmake_writer.WriteBuildFile(f)
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv))
diff --git a/tools/coverage.sh b/tools/coverage.sh
index a5fce75..f977d8f 100755
--- a/tools/coverage.sh
+++ b/tools/coverage.sh
@@ -1,4 +1,18 @@
-#!/bin/sh
+#!/bin/bash
+#
+###############################################################################
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+################################################################################
 #
 # Usage
 #
@@ -6,39 +20,54 @@
 #
 # COVERAGE_CPUS defaults to 2, and the default destination is a temp
 # dir.
-set -e
-genhtml=$(which genhtml)
+
+set -eu
+
+genhtml=$(command -v genhtml)
 if [[ -z "${genhtml}" ]]; then
     echo "Install 'genhtml' (contained in the 'lcov' package)"
     exit 1
 fi
+
 destdir="$1"
 if [[ -z "${destdir}" ]]; then
     destdir=$(mktemp -d /tmp/gerritcov.XXXXXX)
 fi
+
 targets="$2"
 if [[ -z "${targets}" ]]; then
     targets="apps/... java/..."
 fi
+
 echo "Running 'bazel coverage'; this may take a while"
 # coverage is expensive to run; use --jobs=2 to avoid overloading the
 # machine.
-bazel coverage -k --jobs=${COVERAGE_CPUS:-2} -- $targets
+bazel coverage -k --jobs="${COVERAGE_CPUS:-2}" -- "$targets"
+
 # The coverage data contains filenames relative to the Java root, and
 # genhtml has no logic to search these elsewhere. Workaround this
 # limitation by running genhtml in a directory with the files in the
 # right place. Also -inexplicably- genhtml wants to have the source
 # files relative to the output directory.
-rm -rf ${destdir}/* || true
-mkdir -p ${destdir}/
+rm -rf "${destdir}" || true
+mkdir -p "${destdir}"
+
 for ROOT in java apps/paymentmethodtoken; do
-  rsync -a $ROOT/src/{main,test}/java/ ${destdir}/
+  rsync -a "${ROOT}/src/main/java/" "${ROOT}/src/test/java/" "${destdir}/"
 done
+
 base=$(bazel info bazel-testlogs)
-for f in $(find ${base}  -name 'coverage.dat') ; do
-  cp $f ${destdir}/$(echo $f| sed "s|${base}/||" | sed "s|/|_|g")
-done
-cd ${destdir}
-find -name '*coverage.dat' -size 0 -delete
-genhtml -o . --ignore-errors source *coverage.dat
-echo "coverage report at file://${destdir}/index.html"
+
+find "${base}" -name 'coverage.dat' -exec sh -c '
+  for ff do
+    f=$(printf '%s' "${ff#"$base"/}" | sed "s|/|_|g")
+    cp "$ff" "${destdir}/$f"
+  done
+' find-sh {} +
+
+cd "${destdir}"
+
+find -name '*coverage.dat' -size 0 -exec rm -f {} +
+
+genhtml -o . --ignore-errors source ./*coverage.dat
+printf "coverage report at file://%s/index.html" "${destdir}"
diff --git a/tools/java_single_jar.bzl b/tools/java_single_jar.bzl
index 51227c6..ce3dd20 100644
--- a/tools/java_single_jar.bzl
+++ b/tools/java_single_jar.bzl
@@ -44,7 +44,7 @@
 
     args = ctx.actions.args()
     args.add("--sources")
-    args.add(inputs)
+    args.add_all(inputs)
     args.use_param_file(
         "@%s",
         use_always = True,
diff --git a/tools/javadoc.bzl b/tools/javadoc.bzl
index 9ed1781..e7d1146 100644
--- a/tools/javadoc.bzl
+++ b/tools/javadoc.bzl
@@ -32,7 +32,7 @@
 
     include_packages = ":".join(ctx.attr.root_packages)
     javadoc_command = [
-        ctx.file._javadoc_binary.path,
+        "%s/bin/javadoc" % ctx.attr._jdk[java_common.JavaRuntimeInfo].java_home,
         "-sourcepath srcs",
         "-use",
         "-subpackages",
@@ -72,7 +72,8 @@
                 if path_prefix in src.path:
                     prepare_srcs_command += "cp %s srcs/%s && " % (src.path, path_prefix)
 
-    jar_command = "%s cf %s -C tmp ." % (ctx.file._jar_binary.path, ctx.outputs.jar.path)
+    jar_binary = "%s/bin/jar" % ctx.attr._jdk[java_common.JavaRuntimeInfo].java_home
+    jar_command = "%s cf %s -C tmp ." % (jar_binary, ctx.outputs.jar.path)
 
     ctx.actions.run_shell(
         inputs = srcs + classpath + ctx.files._jdk,
@@ -94,17 +95,10 @@
             default = _android_jar,
             allow_single_file = True,
         ),
-        "_javadoc_binary": attr.label(
-            default = Label("@local_jdk//:bin/javadoc"),
-            allow_single_file = True,
-        ),
-        "_jar_binary": attr.label(
-            default = Label("@local_jdk//:bin/jar"),
-            allow_single_file = True,
-        ),
         "_jdk": attr.label(
-            default = Label("@local_jdk//:jdk-default"),
+            default = Label("@bazel_tools//tools/jdk:current_java_runtime"),
             allow_files = True,
+            providers = [java_common.JavaRuntimeInfo],
         ),
     },
     outputs = {"jar": "%{name}.jar"},
diff --git a/tools/testing/BUILD.bazel b/tools/testing/BUILD.bazel
index 2c1b878..c1606cb 100644
--- a/tools/testing/BUILD.bazel
+++ b/tools/testing/BUILD.bazel
@@ -1,4 +1,4 @@
-package(default_visibility = ["//tools/build_defs:internal_pkg"])
+package(default_visibility = ["//tools/testing:__subpackages__"])
 
 licenses(["notice"])  # Apache 2.0
 
@@ -11,7 +11,19 @@
         "java/com/google/crypto/tink/testing/CliUtil.java",
     ],
     javacopts = JAVACOPTS_OSS,
-    visibility = ["//tools/testing:__subpackages__"],
+    deps = [
+        "//java:testonly",
+    ],
+)
+
+java_binary(
+    name = "version_cli_java",
+    testonly = 1,
+    srcs = [
+        "java/com/google/crypto/tink/testing/VersionCli.java",
+    ],
+    javacopts = JAVACOPTS_OSS,
+    main_class = "com.google.crypto.tink.testing.VersionCli",
     deps = [
         "//java:testonly",
     ],
@@ -25,7 +37,48 @@
     ],
     javacopts = JAVACOPTS_OSS,
     main_class = "com.google.crypto.tink.testing.AeadCli",
-    visibility = ["//tools/testing:__subpackages__"],
+    deps = [
+        ":cli_util",
+        "//java:testonly",
+    ],
+)
+
+java_binary(
+    name = "deterministic_aead_cli_java",
+    testonly = 1,
+    srcs = [
+        "java/com/google/crypto/tink/testing/DeterministicAeadCli.java",
+    ],
+    javacopts = JAVACOPTS_OSS,
+    main_class = "com.google.crypto.tink.testing.DeterministicAeadCli",
+    deps = [
+        ":cli_util",
+        "//java:testonly",
+    ],
+)
+
+java_binary(
+    name = "streaming_aead_cli_java",
+    testonly = 1,
+    srcs = [
+        "java/com/google/crypto/tink/testing/StreamingAeadCli.java",
+    ],
+    javacopts = JAVACOPTS_OSS,
+    main_class = "com.google.crypto.tink.testing.StreamingAeadCli",
+    deps = [
+        ":cli_util",
+        "//java:testonly",
+    ],
+)
+
+java_binary(
+    name = "mac_cli_java",
+    testonly = 1,
+    srcs = [
+        "java/com/google/crypto/tink/testing/MacCli.java",
+    ],
+    javacopts = JAVACOPTS_OSS,
+    main_class = "com.google.crypto.tink.testing.MacCli",
     deps = [
         ":cli_util",
         "//java:testonly",
@@ -40,7 +93,6 @@
     ],
     javacopts = JAVACOPTS_OSS,
     main_class = "com.google.crypto.tink.testing.HybridEncryptCli",
-    visibility = ["//tools/testing:__subpackages__"],
     deps = [
         ":cli_util",
         "//java:testonly",
@@ -55,7 +107,6 @@
     ],
     javacopts = JAVACOPTS_OSS,
     main_class = "com.google.crypto.tink.testing.HybridDecryptCli",
-    visibility = ["//tools/testing:__subpackages__"],
     deps = [
         ":cli_util",
         "//java:testonly",
@@ -70,7 +121,6 @@
     ],
     javacopts = JAVACOPTS_OSS,
     main_class = "com.google.crypto.tink.testing.PublicKeySignCli",
-    visibility = ["//tools/testing:__subpackages__"],
     deps = [
         ":cli_util",
         "//java:testonly",
@@ -85,7 +135,6 @@
     ],
     javacopts = JAVACOPTS_OSS,
     main_class = "com.google.crypto.tink.testing.PublicKeyVerifyCli",
-    visibility = ["//tools/testing:__subpackages__"],
     deps = [
         ":cli_util",
         "//java:testonly",
diff --git a/tools/testing/cc/BUILD.bazel b/tools/testing/cc/BUILD.bazel
index 63112e8..b31863c 100644
--- a/tools/testing/cc/BUILD.bazel
+++ b/tools/testing/cc/BUILD.bazel
@@ -1,4 +1,4 @@
-package(default_visibility = ["//tools/build_defs:internal_pkg"])
+package(default_visibility = ["//tools/testing:__subpackages__"])
 
 licenses(["notice"])  # Apache 2.0
 
@@ -6,7 +6,6 @@
     name = "cli_util",
     srcs = ["cli_util.cc"],
     hdrs = ["cli_util.h"],
-    visibility = ["//tools/testing:__subpackages__"],
     deps = [
         "//cc",
         "//cc:binary_keyset_reader",
@@ -19,10 +18,29 @@
     ],
 )
 
+filegroup(
+    name = "aws_kms_access_files",
+    srcs = glob(["*access_key.csv"]) + [
+        "aws_key_arn.txt",
+        "bad_aws_key_arn.txt",
+    ],
+)
+
+cc_binary(
+    name = "aws_kms_aead_cli",
+    srcs = ["aws_kms_aead_cli.cc"],
+    deps = [
+        ":cli_util",
+        "//cc",
+        "//cc/integration/awskms:aws_crypto",
+        "//cc/integration/awskms:aws_kms_aead",
+        "@aws_cpp_sdk//:aws_sdk_core",
+    ],
+)
+
 cc_binary(
     name = "keyset_reader_writer_cli",
     srcs = ["keyset_reader_writer_cli.cc"],
-    visibility = ["//tools/testing:__subpackages__"],
     deps = [
         ":cli_util",
         "//cc",
@@ -30,9 +48,34 @@
 )
 
 cc_binary(
+    name = "version_cli_cc",
+    srcs = ["version_cli.cc"],
+    deps = [
+        "//cc",
+    ],
+)
+
+cc_binary(
     name = "aead_cli_cc",
     srcs = ["aead_cli.cc"],
-    visibility = ["//tools/testing:__subpackages__"],
+    deps = [
+        ":cli_util",
+        "//cc",
+    ],
+)
+
+cc_binary(
+    name = "deterministic_aead_cli_cc",
+    srcs = ["deterministic_aead_cli.cc"],
+    deps = [
+        ":cli_util",
+        "//cc",
+    ],
+)
+
+cc_binary(
+    name = "mac_cli_cc",
+    srcs = ["mac_cli.cc"],
     deps = [
         ":cli_util",
         "//cc",
@@ -42,7 +85,6 @@
 cc_binary(
     name = "hybrid_encrypt_cli_cc",
     srcs = ["hybrid_encrypt_cli.cc"],
-    visibility = ["//tools/testing:__subpackages__"],
     deps = [
         ":cli_util",
         "//cc",
@@ -52,7 +94,6 @@
 cc_binary(
     name = "hybrid_decrypt_cli_cc",
     srcs = ["hybrid_decrypt_cli.cc"],
-    visibility = ["//tools/testing:__subpackages__"],
     deps = [
         ":cli_util",
         "//cc",
@@ -62,7 +103,6 @@
 cc_binary(
     name = "public_key_sign_cli_cc",
     srcs = ["public_key_sign_cli.cc"],
-    visibility = ["//tools/testing:__subpackages__"],
     deps = [
         ":cli_util",
         "//cc",
@@ -72,9 +112,21 @@
 cc_binary(
     name = "public_key_verify_cli_cc",
     srcs = ["public_key_verify_cli.cc"],
-    visibility = ["//tools/testing:__subpackages__"],
     deps = [
         ":cli_util",
         "//cc",
     ],
 )
+
+sh_test(
+    name = "aws_kms_aead_test",
+    size = "medium",
+    srcs = [
+        "aws_kms_aead_test.sh",
+    ],
+    data = [
+        ":aws_kms_access_files",
+        ":aws_kms_aead_cli",
+        "//tools/testing/cross_language:test_lib",
+    ],
+)
diff --git a/tools/testing/cc/aead_cli.cc b/tools/testing/cc/aead_cli.cc
index e58e6aa..4c435e3 100644
--- a/tools/testing/cc/aead_cli.cc
+++ b/tools/testing/cc/aead_cli.cc
@@ -19,11 +19,9 @@
 
 #include "tink/aead.h"
 #include "tink/keyset_handle.h"
-#include "tink/aead/aead_factory.h"
 #include "tink/util/status.h"
 #include "tools/testing/cc/cli_util.h"
 
-using crypto::tink::AeadFactory;
 using crypto::tink::KeysetHandle;
 
 // A command-line utility for testing AEAD-primitives.
@@ -32,18 +30,19 @@
 //   operation: the actual AEAD-operation, i.e. "encrypt" or "decrypt"
 //   input-file:  name of the file with input (plaintext for encryption, or
 //                or ciphertext for decryption)
-//   associated-data:  a std::string to be used as assciated data
+//   associated-data-file:  name of the file containing associated data
 //   output-file:  name of the file for the resulting output
 int main(int argc, char** argv) {
   if (argc != 6) {
     std::clog << "Usage: " << argv[0]
-         << " keyset-file operation input-file associated-data output-file\n";
+         << " keyset-file operation input-file associated-data-file "
+         << "output-file\n";
     exit(1);
   }
   std::string keyset_filename(argv[1]);
   std::string operation(argv[2]);
   std::string input_filename(argv[3]);
-  std::string associated_data(argv[4]);
+  std::string associated_data_file(argv[4]);
   std::string output_filename(argv[5]);
   if (!(operation == "encrypt" || operation == "decrypt")) {
     std::clog << "Unknown operation '" << operation << "'.\n"
@@ -53,8 +52,8 @@
   std::clog << "Using keyset from file " << keyset_filename
             << " to AEAD-" << operation
             << " file "<< input_filename
-            << " with associated data '" << associated_data << "'.\n"
-            << "The resulting output will be written to file "
+            << " with associated data from from file " << associated_data_file
+            << ".\n" << "The resulting output will be written to file "
             << output_filename << std::endl;
 
   // Init Tink;
@@ -65,7 +64,7 @@
       CliUtil::ReadKeyset(keyset_filename);
 
   // Get the primitive.
-  auto primitive_result = AeadFactory::GetPrimitive(*keyset_handle);
+  auto primitive_result = keyset_handle->GetPrimitive<crypto::tink::Aead>();
   if (!primitive_result.ok()) {
     std::clog << "Getting AEAD-primitive from the factory failed: "
               << primitive_result.status().error_message() << std::endl;
@@ -76,6 +75,7 @@
 
   // Read the input.
   std::string input = CliUtil::Read(input_filename);
+  std::string associated_data = CliUtil::Read(associated_data_file);
 
   // Compute the output.
   std::clog << operation << "ing...\n";
diff --git a/tools/testing/cc/aws_key_arn.txt b/tools/testing/cc/aws_key_arn.txt
new file mode 100644
index 0000000..de21741
--- /dev/null
+++ b/tools/testing/cc/aws_key_arn.txt
@@ -0,0 +1 @@
+arn:aws:kms:us-east-1:889465572106:key/465a8817-deb2-4c2e-8466-2acb3643f568
diff --git a/tools/testing/cc/aws_kms_aead_cli.cc b/tools/testing/cc/aws_kms_aead_cli.cc
new file mode 100644
index 0000000..5c9aa43
--- /dev/null
+++ b/tools/testing/cc/aws_kms_aead_cli.cc
@@ -0,0 +1,151 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+
+#include <string>
+#include <vector>
+
+#include "absl/strings/ascii.h"
+#include "absl/strings/str_split.h"
+#include "aws/core/Aws.h"
+#include "aws/core/auth/AWSCredentialsProvider.h"
+#include "aws/core/client/ClientConfiguration.h"
+#include "aws/core/utils/crypto/Factories.h"
+#include "aws/core/utils/memory/AWSMemory.h"
+#include "aws/kms/KMSClient.h"
+#include "tink/aead.h"
+#include "tink/integration/awskms/aws_crypto.h"
+#include "tink/integration/awskms/aws_kms_aead.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "tools/testing/cc/cli_util.h"
+
+using crypto::tink::Aead;
+using crypto::tink::integration::awskms::AwsKmsAead;
+using crypto::tink::integration::awskms::AwsSha256Factory;
+using crypto::tink::integration::awskms::AwsSha256HmacFactory;
+using crypto::tink::integration::awskms::kAwsCryptoAllocationTag;
+
+// A command-line utility for testing AwsKmsAead.
+// It requires 6 arguments:
+//   key-arn-file:  Amazon Resource Name of AWS KMS key for encryption
+//   access-key-csv-file: credentials file containing AWS access key
+//   operation: the actual AEAD-operation, i.e. "encrypt" or "decrypt"
+//   input-file:  name of the file with input (plaintext for encryption, or
+//                or ciphertext for decryption)
+//   associated-data:  a std::string to be used as assciated data
+//   output-file:  name of the file for the resulting output
+int main(int argc, char** argv) {
+  if (argc != 7) {
+    std::clog << "Usage: " << argv[0]
+              << " key-arn-file access-key-csv-file"
+              << " operation input-file associated-data output-file\n";
+    exit(1);
+  }
+  std::string key_arn_filename(argv[1]);
+  std::string access_key_filename(argv[2]);
+  std::string operation(argv[3]);
+  std::string input_filename(argv[4]);
+  std::string associated_data(argv[5]);
+  std::string output_filename(argv[6]);
+  if (!(operation == "encrypt" || operation == "decrypt")) {
+    std::clog << "Unknown operation '" << operation << "'.\n"
+              << "Expected 'encrypt' or 'decrypt'.\n";
+    exit(1);
+  }
+  std::clog << "Using key_arn from file " << key_arn_filename
+            << " and AWS access key from file " << access_key_filename
+            << " to AEAD-" << operation
+            << " file "<< input_filename
+            << " with associated data '" << associated_data << "'.\n"
+            << "The resulting output will be written to file "
+            << output_filename << std::endl;
+
+  // Init AWS API.
+  Aws::SDKOptions options;
+  options.cryptoOptions.sha256Factory_create_fn = []() {
+      return Aws::MakeShared<AwsSha256Factory>(kAwsCryptoAllocationTag);
+  };
+  options.cryptoOptions.sha256HMACFactory_create_fn = []() {
+      return Aws::MakeShared<AwsSha256HmacFactory>(kAwsCryptoAllocationTag);
+  };
+  Aws::InitAPI(options);
+
+  // Prepare AWS credentials and params.
+  // access-key-cvs-file contains two lines, where  the first line
+  // describes the actual comma-separated values present in the second line.
+  std::vector<std::string> access_key =
+      absl::StrSplit(CliUtil::Read(access_key_filename), '\n');
+  std::vector<std::string> access_key_values =
+      absl::StrSplit(access_key[1], ',');
+  std::string access_key_id = access_key_values[0];
+  absl::StripAsciiWhitespace(&access_key_id);
+  std::string secret_access_key = access_key_values[1];
+  absl::StripAsciiWhitespace(&secret_access_key);
+  Aws::Auth::AWSCredentials credentials(access_key_id.c_str(),
+                                        secret_access_key.c_str());
+  std::string key_arn = CliUtil::Read(key_arn_filename);
+  absl::StripAsciiWhitespace(&key_arn);
+
+  std::clog << "Will use key ARN " << key_arn << std::endl
+            << "with access key ID [" << access_key_id << "]" << std::endl;
+
+  // Create AWS KMSClient.
+  Aws::Client::ClientConfiguration configuration;
+  configuration.region = "us-east-1";
+  configuration.scheme = Aws::Http::Scheme::HTTPS;
+  configuration.connectTimeoutMs = 30000;
+  configuration.requestTimeoutMs = 60000;
+  auto aws_client = Aws::MakeShared<Aws::KMS::KMSClient>(
+      kAwsCryptoAllocationTag, credentials, configuration);
+
+  // Create Aead-primitive.
+  auto aead_result = AwsKmsAead::New(key_arn, aws_client);
+  if (!aead_result.ok()) {
+    std::clog << "Aead creation failed: "
+              << aead_result.status().error_message()
+              << "\n";
+    exit(1);
+  }
+  std::unique_ptr<Aead> aead(std::move(aead_result.ValueOrDie()));
+
+  // Read the input.
+  std::string input = CliUtil::Read(input_filename);
+
+  // Compute the output.
+  std::clog << operation << "ing...\n";
+  std::string output;
+  if (operation == "encrypt") {
+    auto encrypt_result = aead->Encrypt(input, associated_data);
+    if (!encrypt_result.ok()) {
+      std::clog << "Error while encrypting the input:"
+                << encrypt_result.status().error_message() << std::endl;
+      exit(1);
+    }
+    output = encrypt_result.ValueOrDie();
+  } else {  // operation == "decrypt"
+    auto decrypt_result = aead->Decrypt(input, associated_data);
+    if (!decrypt_result.ok()) {
+      std::clog << "Error while decrypting the input:"
+                << decrypt_result.status().error_message() << std::endl;
+      exit(1);
+    }
+    output = decrypt_result.ValueOrDie();
+  }
+
+  // Write the output to the output file.
+  CliUtil::Write(output, output_filename);
+
+  std::clog << "All done.\n";
+}
diff --git a/tools/testing/cc/aws_kms_aead_test.sh b/tools/testing/cc/aws_kms_aead_test.sh
new file mode 100755
index 0000000..9aaf4b8
--- /dev/null
+++ b/tools/testing/cc/aws_kms_aead_test.sh
@@ -0,0 +1,76 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+################################################################################
+
+#!/bin/bash
+
+ROOT_DIR="$TEST_SRCDIR/tink"
+AWS_KMS_AEAD_CLI="$ROOT_DIR/tools/testing/cc/aws_kms_aead_cli"
+AWS_KEY_ARN_FILE="$ROOT_DIR/tools/testing/cc/aws_key_arn.txt"
+AWS_ACCESS_KEY_CSV_FILE="$ROOT_DIR/tools/testing/cc/aws_access_key.csv"
+BAD_AWS_KEY_ARN_FILE="$ROOT_DIR/tools/testing/cc/bad_aws_key_arn.txt"
+BAD_AWS_ACCESS_KEY_CSV_FILE="$ROOT_DIR/tools/testing/cc/bad_aws_access_key.csv"
+TEST_UTIL="$ROOT_DIR/tools/testing/cross_language/test_util.sh"
+associated_data="some associated data"
+
+source $TEST_UTIL || exit 1
+
+#############################################################################
+# Bad access key test.
+test_name="bad_aws_access_key"
+echo "+++ starting test $test_name ..."
+generate_plaintext $test_name
+encrypted_file="$TEST_TMPDIR/${test_name}_encrypted.bin"
+log_file="$TEST_TMPDIR/${test_name}.log"
+$AWS_KMS_AEAD_CLI $AWS_KEY_ARN_FILE $BAD_AWS_ACCESS_KEY_CSV_FILE\
+  encrypt $plaintext_file "$associated_data" $encrypted_file 2> $log_file
+
+assert_file_contains $log_file "UnrecognizedClientException"
+
+if [ ! -e $AWS_ACCESS_KEY_CSV_FILE ]; then
+  echo "WARNING: no AWS access key found, skiping the subsequent tests"
+  exit 0
+fi
+
+#############################################################################
+# Bad key arn test.
+test_name="bad_key_arn"
+echo "+++ starting test $test_name ..."
+generate_plaintext $test_name
+encrypted_file="$TEST_TMPDIR/${test_name}_encrypted.bin"
+log_file="$TEST_TMPDIR/${test_name}.log"
+$AWS_KMS_AEAD_CLI $BAD_AWS_KEY_ARN_FILE $AWS_ACCESS_KEY_CSV_FILE\
+  encrypt $plaintext_file "$associated_data" $encrypted_file 2> $log_file
+
+assert_file_contains $log_file "AccessDeniedException"
+
+#############################################################################
+# All good, encryption and decryption should work.
+test_name="good_key_arn_and_access_key"
+echo "+++ starting test $test_name ..."
+generate_plaintext $test_name
+encrypted_file="$TEST_TMPDIR/${test_name}_encrypted.bin"
+decrypted_file="$TEST_TMPDIR/${test_name}_decrypted.bin"
+log_file="$TEST_TMPDIR/${test_name}.log"
+echo "    encrypting..."
+$AWS_KMS_AEAD_CLI $AWS_KEY_ARN_FILE $AWS_ACCESS_KEY_CSV_FILE\
+  encrypt $plaintext_file "$associated_data" $encrypted_file 2> $log_file
+assert_file_contains $log_file "All done"
+assert_files_different $plaintext_file $encrypted_file
+
+echo "    decrypting..."
+$AWS_KMS_AEAD_CLI $AWS_KEY_ARN_FILE $AWS_ACCESS_KEY_CSV_FILE\
+  decrypt $encrypted_file "$associated_data" $decrypted_file 2> $log_file
+assert_file_contains $log_file "All done"
+
+echo "    checking decryption result..."
+assert_files_equal $plaintext_file $decrypted_file
diff --git a/tools/testing/cc/bad_aws_access_key.csv b/tools/testing/cc/bad_aws_access_key.csv
new file mode 100644
index 0000000..a9ec78f
--- /dev/null
+++ b/tools/testing/cc/bad_aws_access_key.csv
@@ -0,0 +1,2 @@
+Access key ID,Secret access key
+AKIAIOSFODNN7EXAMPLE,wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
diff --git a/tools/testing/cc/bad_aws_key_arn.txt b/tools/testing/cc/bad_aws_key_arn.txt
new file mode 100644
index 0000000..0b730f3
--- /dev/null
+++ b/tools/testing/cc/bad_aws_key_arn.txt
@@ -0,0 +1 @@
+arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012
diff --git a/tools/testing/cc/cli_util.h b/tools/testing/cc/cli_util.h
index f23bdbe..cd45599 100644
--- a/tools/testing/cc/cli_util.h
+++ b/tools/testing/cc/cli_util.h
@@ -14,6 +14,9 @@
 //
 ///////////////////////////////////////////////////////////////////////////////
 
+#ifndef TOOLS_TESTING_CC_CLI_UTIL_H_
+#define TOOLS_TESTING_CC_CLI_UTIL_H_
+
 #include <iostream>
 #include <fstream>
 
@@ -59,3 +62,5 @@
   // In case of errors writes a log message and aborts.
   static void Write(const std::string& output, const std::string& filename);
 };
+
+#endif  // TOOLS_TESTING_CC_CLI_UTIL_H_
diff --git a/tools/testing/cc/deterministic_aead_cli.cc b/tools/testing/cc/deterministic_aead_cli.cc
new file mode 100644
index 0000000..e4f4c54
--- /dev/null
+++ b/tools/testing/cc/deterministic_aead_cli.cc
@@ -0,0 +1,114 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include <iostream>
+#include <fstream>
+
+#include "tink/deterministic_aead.h"
+#include "tink/keyset_handle.h"
+#include "tink/daead/deterministic_aead_factory.h"
+#include "tink/util/status.h"
+#include "tools/testing/cc/cli_util.h"
+
+using crypto::tink::DeterministicAeadFactory;
+using crypto::tink::KeysetHandle;
+
+// A command-line utility for testing DeterministicAead-primitives.
+// It requires 5 arguments:
+//   keyset-file:  name of the file with the keyset to be used for encryption
+//   operation: the actual DeterminisiticAead-operation, i.e.
+//       "encrypt_deterministically" or "decrypt_deterministically"
+//   input-file:  name of the file with input (plaintext for encryption, or
+//       or ciphertext for decryption)
+//   associated-data-file:  name of the file containing associated data
+//   output-file:  name of the file for the resulting output
+int main(int argc, char** argv) {
+  if (argc != 6) {
+    std::clog << "Usage: " << argv[0]
+         << " keyset-file operation input-file associated-data-file "
+         << "output-file\n";
+    exit(1);
+  }
+  std::string keyset_filename(argv[1]);
+  std::string operation(argv[2]);
+  std::string input_filename(argv[3]);
+  std::string associated_data_file(argv[4]);
+  std::string output_filename(argv[5]);
+  if (!(operation == "encryptdeterministically" ||
+        operation == "decryptdeterministically")) {
+    std::clog << "Unknown operation '" << operation << "'.\n"
+              << "Expected 'encryptdeterministically' "
+              << "or 'decryptdeterministically'.\n";
+    exit(1);
+  }
+  std::clog << "Using keyset from file " << keyset_filename
+            << " to AEAD-" << operation
+            << " file "<< input_filename
+            << " with associated data from from file " << associated_data_file
+            << ".\n" << "The resulting output will be written to file "
+            << output_filename << std::endl;
+
+  // Init Tink;
+  CliUtil::InitTink();
+
+  // Read the keyset.
+  std::unique_ptr<KeysetHandle> keyset_handle =
+      CliUtil::ReadKeyset(keyset_filename);
+
+  // Get the primitive.
+  auto primitive_result =
+      DeterministicAeadFactory::GetPrimitive(*keyset_handle);
+  if (!primitive_result.ok()) {
+    std::clog << "Getting DeterministicAead-primitive from the factory failed: "
+              << primitive_result.status().error_message() << std::endl;
+    exit(1);
+  }
+  std::unique_ptr<crypto::tink::DeterministicAead> daead =
+      std::move(primitive_result.ValueOrDie());
+
+  // Read the input.
+  std::string input = CliUtil::Read(input_filename);
+  std::string associated_data = CliUtil::Read(associated_data_file);
+
+  // Compute the output.
+  std::clog << "performing operation " << operation << "...\n";
+  std::string output;
+  if (operation == "encryptdeterministically") {
+    auto encrypt_result = daead->EncryptDeterministically(input,
+                                                          associated_data);
+    if (!encrypt_result.ok()) {
+      std::clog << "Error while encrypting the input:"
+                << encrypt_result.status().error_message() << std::endl;
+      exit(1);
+    }
+    output = encrypt_result.ValueOrDie();
+  } else {  // operation == "decryptdeterministically"
+    auto decrypt_result = daead->DecryptDeterministically(input,
+                                                         associated_data);
+    if (!decrypt_result.ok()) {
+      std::clog << "Error while decrypting the input:"
+                << decrypt_result.status().error_message() << std::endl;
+      exit(1);
+    }
+    output = decrypt_result.ValueOrDie();
+  }
+
+  // Write the output to the output file.
+  CliUtil::Write(output, output_filename);
+
+  std::clog << "All done.\n";
+  return 0;
+}
diff --git a/tools/testing/cc/hybrid_decrypt_cli.cc b/tools/testing/cc/hybrid_decrypt_cli.cc
index 774b7bc..808b041 100644
--- a/tools/testing/cc/hybrid_decrypt_cli.cc
+++ b/tools/testing/cc/hybrid_decrypt_cli.cc
@@ -19,34 +19,33 @@
 
 #include "tink/hybrid_decrypt.h"
 #include "tink/keyset_handle.h"
-#include "tink/hybrid/hybrid_decrypt_factory.h"
 #include "tink/util/status.h"
 #include "tools/testing/cc/cli_util.h"
 
-using crypto::tink::HybridDecryptFactory;
 using crypto::tink::KeysetHandle;
 
 // A command-line utility for testing HybridDecrypt-primitives.
 // It requires 4 arguments:
 //   keyset-file:  name of the file with the keyset to be used for decryption
 //   ciphertext-file:  name of the file that contains ciphertext to be decrypted
-//   context-info:  a std::string to be used as "context info" during the decryption
+//   context-info-file:  name of the file that contains "context info" which
+//       will be used during the decryption
 //   output-file:  name of the output file for the resulting plaintext
 int main(int argc, char** argv) {
   if (argc != 5) {
     std::clog << "Usage: "
               << argv[0]
-              << " keyset-file ciphertext-file context-info output-file\n";
+              << " keyset-file ciphertext-file context-info-file output-file\n";
     exit(1);
   }
   std::string keyset_filename(argv[1]);
   std::string ciphertext_filename(argv[2]);
-  std::string context_info(argv[3]);
+  std::string context_info_filename(argv[3]);
   std::string output_filename(argv[4]);
   std::clog << "Using keyset from file " << keyset_filename
             << " to decrypt file " << ciphertext_filename
-            << " with context info '" << context_info << "'.\n"
-            << "The resulting ciphertext will be written to file "
+            << " with context info from file " << context_info_filename
+            << ".\n" << "The resulting ciphertext will be written to file "
             << output_filename << std::endl;
 
   // Init Tink;
@@ -57,7 +56,8 @@
       CliUtil::ReadKeyset(keyset_filename);
 
   // Get the primitive.
-  auto primitive_result = HybridDecryptFactory::GetPrimitive(*keyset_handle);
+  auto primitive_result =
+      keyset_handle->GetPrimitive<crypto::tink::HybridDecrypt>();
   if (!primitive_result.ok()) {
     std::clog << "Getting HybridDecrypt-primitive from the factory failed: "
               << primitive_result.status().error_message() << std::endl;
@@ -68,6 +68,7 @@
 
   // Read the ciphertext.
   std::string ciphertext = CliUtil::Read(ciphertext_filename);
+  std::string context_info = CliUtil::Read(context_info_filename);
 
   // Compute the plaintext.
   std::clog << "Decrypting...\n";
diff --git a/tools/testing/cc/hybrid_encrypt_cli.cc b/tools/testing/cc/hybrid_encrypt_cli.cc
index 93db7d2..585598b 100644
--- a/tools/testing/cc/hybrid_encrypt_cli.cc
+++ b/tools/testing/cc/hybrid_encrypt_cli.cc
@@ -27,23 +27,25 @@
 // It requires 4 arguments:
 //   keyset-file:  name of the file with the keyset to be used for encryption
 //   plaintext-file:  name of the file that contains plaintext to be encrypted
-//   context-info:  a std::string to be used as "context info" during the encryption
+//   context-info-file:  name of the file that contains "context info" which
+//       will be used during the decryption
 //   output-file:  name of the output file for the resulting ciphertext
 int main(int argc, char** argv) {
   if (argc != 5) {
     std::clog << "Usage: "
               << argv[0]
-              << " keyset-file plaintext-file context-info output-file\n";
+              << " keyset-file plaintext-file context-info-file "
+              << "output-file\n";
     exit(1);
   }
   std::string keyset_filename(argv[1]);
   std::string plaintext_filename(argv[2]);
-  std::string context_info(argv[3]);
+  std::string context_info_filename(argv[3]);
   std::string output_filename(argv[4]);
   std::clog << "Using keyset from file " << keyset_filename
             << " to encrypt file " << plaintext_filename
-            << " with context info '" << context_info << "'.\n"
-            << "The resulting ciphertext will be written to file "
+            << " with context info from file " << context_info_filename
+            << ".\n" << "The resulting ciphertext will be written to file "
             << output_filename << std::endl;
 
   // Init Tink;
@@ -54,7 +56,8 @@
       CliUtil::ReadKeyset(keyset_filename);
 
   // Get the primitive.
-  auto primitive_result = HybridEncryptFactory::GetPrimitive(*keyset_handle);
+  auto primitive_result =
+      keyset_handle->GetPrimitive<crypto::tink::HybridEncrypt>();
   if (!primitive_result.ok()) {
     std::clog << "Getting HybridEncrypt-primitive from the factory failed: "
               << primitive_result.status().error_message() << std::endl;
@@ -65,6 +68,7 @@
 
   // Read the plaintext.
   std::string plaintext = CliUtil::Read(plaintext_filename);
+  std::string context_info = CliUtil::Read(context_info_filename);
 
   // Compute the ciphertext.
   std::clog << "Encrypting...\n";
diff --git a/tools/testing/cc/mac_cli.cc b/tools/testing/cc/mac_cli.cc
new file mode 100644
index 0000000..83da27d
--- /dev/null
+++ b/tools/testing/cc/mac_cli.cc
@@ -0,0 +1,115 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include <iostream>
+#include <fstream>
+
+#include "tink/keyset_handle.h"
+#include "tink/mac.h"
+#include "tink/mac/mac_factory.h"
+#include "tink/util/status.h"
+#include "tools/testing/cc/cli_util.h"
+
+using crypto::tink::MacFactory;
+using crypto::tink::KeysetHandle;
+
+// A command-line utility for testing Mac-primitives.
+// It requires 4 for MAC computation and 5 for MAC verification:
+//   keyset-file:  name of the file with the keyset to be used for MAC
+//   operation: the actual MAC-operation, i.e. "compute" or "verify"
+//   data-file:  name of the file with data for MAC computation/verification
+//   mac-file:  name of the file for MAC value (when computing the MAC),
+//              or with MAC value (when verifying the MAC)
+//   result-file: name of the file for MAC verification result (valid/invalid)
+//                (only for MAC verification operation)
+int main(int argc, char** argv) {
+  if (argc != 5 && argc != 6) {
+    std::clog << "Usage: " << argv[0]
+         << " keyset-file operation data-file mac-file [result-file]\n";
+    exit(1);
+  }
+  std::string keyset_filename(argv[1]);
+  std::string operation(argv[2]);
+  std::string data_filename(argv[3]);
+  std::string mac_filename(argv[4]);
+  std::string result_filename = "";
+  if (!(operation == "compute" || operation == "verify")) {
+    std::clog << "Unknown operation '" << operation << "'.\n"
+              << "Expected 'compute' or 'verify'.\n";
+    exit(1);
+  }
+  if (operation == "compute") {
+    std::clog << "Using keyset from file " << keyset_filename
+              << " to compute MAC of data from file "<< data_filename
+              << std::endl
+              << "The resulting MAC will be written to file "
+              << mac_filename << std::endl;
+  } else {  // operation == "verify"
+    result_filename = std::string(argv[5]);
+    std::clog << "Using keyset from file " << keyset_filename
+              << " to verify MAC value from file "<< mac_filename
+              << " computed for data from file " << data_filename
+              << std::endl
+              << "The verification result will be written to file "
+              << result_filename << std::endl;
+  }
+
+  // Init Tink;
+  CliUtil::InitTink();
+
+  // Read the keyset.
+  std::unique_ptr<KeysetHandle> keyset_handle =
+      CliUtil::ReadKeyset(keyset_filename);
+
+  // Get the primitive.
+  auto primitive_result = MacFactory::GetPrimitive(*keyset_handle);
+  if (!primitive_result.ok()) {
+    std::clog << "Getting MAC-primitive from the factory failed: "
+              << primitive_result.status().error_message() << std::endl;
+    exit(1);
+  }
+  std::unique_ptr<crypto::tink::Mac> mac =
+      std::move(primitive_result.ValueOrDie());
+
+  // Read the data.
+  std::string data = CliUtil::Read(data_filename);
+
+  // Compute and write the output.
+  if (operation == "compute") {
+    std::clog << "computing MAC...\n";
+    auto mac_result = mac->ComputeMac(data);
+    if (!mac_result.ok()) {
+      std::clog << "Error while computing the MAC:"
+                << mac_result.status().error_message() << std::endl;
+      exit(1);
+    }
+    CliUtil::Write(mac_result.ValueOrDie(), mac_filename);
+  } else {  // operation == "verify"
+    std::clog << "verifying MAC...\n";
+    std::string mac_value = CliUtil::Read(mac_filename);
+    std::string result = "valid";
+    auto status = mac->VerifyMac(mac_value, data);
+    if (!status.ok()) {
+      std::clog << "Error while verifying MAC:"
+                << status.error_message() << std::endl;
+      result = "invalid";
+    }
+    CliUtil::Write(result, result_filename);
+  }
+
+  std::clog << "All done.\n";
+  return 0;
+}
diff --git a/tools/testing/cc/public_key_sign_cli.cc b/tools/testing/cc/public_key_sign_cli.cc
index c5b8bb2..cd2a69b 100644
--- a/tools/testing/cc/public_key_sign_cli.cc
+++ b/tools/testing/cc/public_key_sign_cli.cc
@@ -19,11 +19,9 @@
 
 #include "tink/public_key_sign.h"
 #include "tink/keyset_handle.h"
-#include "tink/signature/public_key_sign_factory.h"
 #include "tink/util/status.h"
 #include "tools/testing/cc/cli_util.h"
 
-using crypto::tink::PublicKeySignFactory;
 using crypto::tink::KeysetHandle;
 
 // A command-line utility for testing PublicKeySign-primitives.
@@ -54,7 +52,8 @@
       CliUtil::ReadKeyset(keyset_filename);
 
   // Get the primitive.
-  auto primitive_result = PublicKeySignFactory::GetPrimitive(*keyset_handle);
+  auto primitive_result =
+      keyset_handle->GetPrimitive<crypto::tink::PublicKeySign>();
   if (!primitive_result.ok()) {
     std::clog << "Getting PublicKeySign-primitive from the factory failed: "
               << primitive_result.status().error_message() << std::endl;
diff --git a/tools/testing/cc/public_key_verify_cli.cc b/tools/testing/cc/public_key_verify_cli.cc
index 631f8e6..45bd95f 100644
--- a/tools/testing/cc/public_key_verify_cli.cc
+++ b/tools/testing/cc/public_key_verify_cli.cc
@@ -55,7 +55,8 @@
       CliUtil::ReadKeyset(keyset_filename);
 
   // Get the primitive.
-  auto primitive_result = PublicKeyVerifyFactory::GetPrimitive(*keyset_handle);
+  auto primitive_result =
+      keyset_handle->GetPrimitive<crypto::tink::PublicKeyVerify>();
   if (!primitive_result.ok()) {
     std::clog << "Getting PublicKeyVerify-primitive from the factory failed: "
               << primitive_result.status().error_message() << std::endl;
diff --git a/tools/testing/cc/version_cli.cc b/tools/testing/cc/version_cli.cc
new file mode 100644
index 0000000..58f320f
--- /dev/null
+++ b/tools/testing/cc/version_cli.cc
@@ -0,0 +1,25 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include <iostream>
+
+#include "tink/version.h"
+
+// A command-line utility for testing Tink version setting.
+// It takes no arguments and just prints the version of Tink library.
+int main(int argc, char** argv) {
+  std::cout << crypto::tink::Version::kTinkVersion;
+}
diff --git a/tools/testing/cross_language/BUILD.bazel b/tools/testing/cross_language/BUILD.bazel
index 9b27c62..af5806d 100644
--- a/tools/testing/cross_language/BUILD.bazel
+++ b/tools/testing/cross_language/BUILD.bazel
@@ -23,6 +23,20 @@
 )
 
 sh_test(
+    name = "version_test",
+    size = "medium",
+    srcs = [
+        "version_test.sh",
+    ],
+    data = [
+        ":test_lib",
+        "//:tink_version",
+        "//tools/testing:version_cli_java",
+        "//tools/testing/cc:version_cli_cc",
+    ],
+)
+
+sh_test(
     name = "aead_test",
     size = "medium",
     srcs = [
@@ -37,6 +51,47 @@
 )
 
 sh_test(
+    name = "deterministic_aead_test",
+    size = "medium",
+    srcs = [
+        "deterministic_aead_test.sh",
+    ],
+    data = [
+        ":test_lib",
+        "//tools/testing:deterministic_aead_cli_java",
+        "//tools/testing/cc:deterministic_aead_cli_cc",
+        "//tools/tinkey",
+    ],
+)
+
+sh_test(
+    name = "streaming_aead_test",
+    size = "medium",
+    srcs = [
+        "streaming_aead_test.sh",
+    ],
+    data = [
+        ":test_lib",
+        "//tools/testing:streaming_aead_cli_java",
+        "//tools/tinkey",
+    ],
+)
+
+sh_test(
+    name = "mac_test",
+    size = "medium",
+    srcs = [
+        "mac_test.sh",
+    ],
+    data = [
+        ":test_lib",
+        "//tools/testing:mac_cli_java",
+        "//tools/testing/cc:mac_cli_cc",
+        "//tools/tinkey",
+    ],
+)
+
+sh_test(
     name = "hybrid_encryption_test",
     size = "medium",
     srcs = [
diff --git a/tools/testing/cross_language/aead_test.sh b/tools/testing/cross_language/aead_test.sh
index 6266331..919a55a 100755
--- a/tools/testing/cross_language/aead_test.sh
+++ b/tools/testing/cross_language/aead_test.sh
@@ -1,6 +1,19 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+################################################################################
+
 #!/bin/bash
 
-ROOT_DIR="$TEST_SRCDIR/__main__"
+ROOT_DIR="$TEST_SRCDIR/tink"
 CC_AEAD_CLI="$ROOT_DIR/tools/testing/cc/aead_cli_cc"
 JAVA_AEAD_CLI="$ROOT_DIR/tools/testing/aead_cli_java"
 TEST_UTIL="$ROOT_DIR/tools/testing/cross_language/test_util.sh"
@@ -24,17 +37,18 @@
   for key_template in ${key_templates[*]}
   do
     local test_instance="${test_name}_${key_template}"
-    generate_symmetric_key "aead" $test_instance $key_template
+    generate_symmetric_key $test_instance $key_template
     generate_plaintext $test_instance
 
     local encrypted_file="$TEST_TMPDIR/${test_instance}_encrypted.bin"
     local decrypted_file="$TEST_TMPDIR/${test_instance}_decrypted.bin"
-    local associated_data="some associated data for $test_instance"
+    local associated_data_file="$TEST_TMPDIR/${test_instance}_aad.bin"
+    echo "some associated data for $test_instance" > $associated_data_file
     $encrypt_cli $symmetric_key_file "encrypt" $plaintext_file\
-        "$associated_data" $encrypted_file || exit 1
+        $associated_data_file $encrypted_file || exit 1
     assert_files_different $plaintext_file $encrypted_file
     $decrypt_cli $symmetric_key_file "decrypt" $encrypted_file\
-        "$associated_data" $decrypted_file || exit 1
+        $associated_data_file $decrypted_file || exit 1
     assert_files_equal $plaintext_file $decrypted_file
   done
 }
diff --git a/tools/testing/cross_language/deterministic_aead_test.sh b/tools/testing/cross_language/deterministic_aead_test.sh
new file mode 100755
index 0000000..8be5e58
--- /dev/null
+++ b/tools/testing/cross_language/deterministic_aead_test.sh
@@ -0,0 +1,66 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+################################################################################
+
+#!/bin/bash
+
+ROOT_DIR="$TEST_SRCDIR/tink"
+CC_DAEAD_CLI="$ROOT_DIR/tools/testing/cc/deterministic_aead_cli_cc"
+JAVA_DAEAD_CLI="$ROOT_DIR/tools/testing/deterministic_aead_cli_java"
+TEST_UTIL="$ROOT_DIR/tools/testing/cross_language/test_util.sh"
+
+KEY_TEMPLATES=(AES256_SIV)
+
+source $TEST_UTIL || exit 1
+
+#############################################################################
+### Helpers for DeterministicAead-tests.
+
+# Basic tests of DeterministicAead-implementations.
+deterministic_aead_basic_test() {
+  local test_name="$1-aead-basic-test"
+  local encrypt_cli="$2"
+  local decrypt_cli="$3"
+  local key_templates=$4
+
+  echo "############ starting test $test_name for the following templates:"
+  echo $key_templates
+  for key_template in ${key_templates[*]}
+  do
+    local test_instance="${test_name}_${key_template}"
+    generate_symmetric_key $test_instance $key_template
+    generate_plaintext $test_instance
+
+    local encrypted_file="$TEST_TMPDIR/${test_instance}_encrypted.bin"
+    local decrypted_file="$TEST_TMPDIR/${test_instance}_decrypted.bin"
+    local associated_data_file="$TEST_TMPDIR/${test_instance}_aad.bin"
+    echo "some associated data for $test_instance" > $associated_data_file
+    $encrypt_cli $symmetric_key_file "encryptdeterministically" $plaintext_file\
+        $associated_data_file $encrypted_file || exit 1
+    assert_files_different $plaintext_file $encrypted_file
+    $decrypt_cli $symmetric_key_file "decryptdeterministically" $encrypted_file\
+        $associated_data_file $decrypted_file || exit 1
+    assert_files_equal $plaintext_file $decrypted_file
+  done
+}
+
+#############################################################################
+##### Run the actual tests.
+deterministic_aead_basic_test "CC-CC"\
+    $CC_DAEAD_CLI   $CC_DAEAD_CLI   "${KEY_TEMPLATES[*]}"
+deterministic_aead_basic_test "CC-JAVA"\
+    $CC_DAEAD_CLI   $JAVA_DAEAD_CLI "${KEY_TEMPLATES[*]}"
+deterministic_aead_basic_test "JAVA-CC"\
+    $JAVA_DAEAD_CLI $CC_DAEAD_CLI   "${KEY_TEMPLATES[*]}"
+deterministic_aead_basic_test "JAVA-JAVA"\
+    $JAVA_DAEAD_CLI $JAVA_DAEAD_CLI "${KEY_TEMPLATES[*]}"
+
diff --git a/tools/testing/cross_language/hybrid_encryption_test.sh b/tools/testing/cross_language/hybrid_encryption_test.sh
index 6c7fb05..0ab9da1 100755
--- a/tools/testing/cross_language/hybrid_encryption_test.sh
+++ b/tools/testing/cross_language/hybrid_encryption_test.sh
@@ -1,6 +1,19 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+################################################################################
+
 #!/bin/bash
 
-ROOT_DIR="$TEST_SRCDIR/__main__"
+ROOT_DIR="$TEST_SRCDIR/tink"
 CC_ENCRYPT_CLI="$ROOT_DIR/tools/testing/cc/hybrid_encrypt_cli_cc"
 CC_DECRYPT_CLI="$ROOT_DIR/tools/testing/cc/hybrid_decrypt_cli_cc"
 JAVA_ENCRYPT_CLI="$ROOT_DIR/tools/testing/hybrid_encrypt_cli_java"
@@ -27,16 +40,17 @@
   for key_template in ${key_templates[*]}
   do
     local test_instance="${test_name}_${key_template}"
-    generate_asymmetric_keys "hybrid" $test_instance $key_template
+    generate_asymmetric_keys $test_instance $key_template
     generate_plaintext $test_instance
 
     local encrypted_file="$TEST_TMPDIR/${test_instance}_encrypted.bin"
     local decrypted_file="$TEST_TMPDIR/${test_instance}_decrypted.bin"
-    local context_info="some context info for $test_instance"
-    $encrypt_cli $pub_key_file $plaintext_file "$context_info" \
+    local context_info_file="$TEST_TMPDIR/${test_instance}_context_info.bin"
+    echo "some context info for $test_instance" > $context_info_file
+    $encrypt_cli $pub_key_file $plaintext_file $context_info_file \
         $encrypted_file || exit 1
     assert_files_different $plaintext_file $encrypted_file
-    $decrypt_cli $priv_key_file $encrypted_file "$context_info" \
+    $decrypt_cli $priv_key_file $encrypted_file $context_info_file \
         $decrypted_file || exit 1
     assert_files_equal $plaintext_file $decrypted_file
   done
diff --git a/tools/testing/cross_language/keyset_reader_writer_test.sh b/tools/testing/cross_language/keyset_reader_writer_test.sh
index a6120a0..cd8d8ee 100755
--- a/tools/testing/cross_language/keyset_reader_writer_test.sh
+++ b/tools/testing/cross_language/keyset_reader_writer_test.sh
@@ -1,6 +1,19 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+################################################################################
+
 #!/bin/bash
 
-ROOT_DIR="$TEST_SRCDIR/__main__"
+ROOT_DIR="$TEST_SRCDIR/tink"
 CC_KEYSET_RW_CLI="$ROOT_DIR/tools/testing/cc/keyset_reader_writer_cli"
 JAVA_KEYSET_RW_CLI="$ROOT_DIR/tools/tinkey/tinkey"
 TEST_UTIL="$ROOT_DIR/tools/testing/cross_language/test_util.sh"
@@ -47,17 +60,17 @@
 ##### Run the actual tests.
 
 ### Java BINARY, C++ BINARY
-generate_symmetric_key "aead" "AEAD-test-1" "AES128_CTR_HMAC_SHA256" "BINARY"
+generate_symmetric_key "AEAD-test-1" "AES128_CTR_HMAC_SHA256" "BINARY"
 keyset_reader_writer_basic_test "BINARY" $symmetric_key_file "BINARY"
 
 ### Java BINARY, C++ JSON
-generate_symmetric_key "aead" "AEAD-test-2" "AES128_CTR_HMAC_SHA256" "BINARY"
+generate_symmetric_key "AEAD-test-2" "AES128_CTR_HMAC_SHA256" "BINARY"
 keyset_reader_writer_basic_test "BINARY" $symmetric_key_file "JSON"
 
 ### Java JSON, C++ BINARY
-generate_symmetric_key "aead" "AEAD-test-3" "AES128_CTR_HMAC_SHA256" "JSON"
+generate_symmetric_key "AEAD-test-3" "AES128_CTR_HMAC_SHA256" "JSON"
 keyset_reader_writer_basic_test "JSON" $symmetric_key_file "BINARY"
 
 ### Java JSON, C++ JSON
-generate_symmetric_key "aead" "AEAD-test-4" "AES256_CTR_HMAC_SHA256" "JSON"
+generate_symmetric_key "AEAD-test-4" "AES256_CTR_HMAC_SHA256" "JSON"
 keyset_reader_writer_basic_test "JSON" $symmetric_key_file "JSON"
diff --git a/tools/testing/cross_language/mac_test.sh b/tools/testing/cross_language/mac_test.sh
new file mode 100755
index 0000000..a141ebb
--- /dev/null
+++ b/tools/testing/cross_language/mac_test.sh
@@ -0,0 +1,76 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+################################################################################
+
+#!/bin/bash
+
+ROOT_DIR="$TEST_SRCDIR/tink"
+CC_MAC_CLI="$ROOT_DIR/tools/testing/cc/mac_cli_cc"
+JAVA_MAC_CLI="$ROOT_DIR/tools/testing/mac_cli_java"
+TEST_UTIL="$ROOT_DIR/tools/testing/cross_language/test_util.sh"
+
+KEY_TEMPLATES=(HMAC_SHA256_128BITTAG HMAC_SHA256_256BITTAG)
+
+source $TEST_UTIL || exit 1
+
+#############################################################################
+### Helpers for MAC-tests.
+
+# Basic tests of MAC-implementations.
+mac_basic_test() {
+  local test_name="$1-mac-basic-test-$5"
+  local compute_mac_cli="$2"
+  local verify_mac_cli="$3"
+  local key_templates=$4
+  local output_prefix="$5"
+
+  echo "############ starting test $test_name for the following templates:"
+  echo $key_templates
+  for key_template in ${key_templates[*]}
+  do
+    local test_instance="${test_name}_${key_template}"
+    generate_symmetric_key $test_instance $key_template
+    generate_plaintext $test_instance
+
+    local mac_file="$TEST_TMPDIR/${test_instance}_mac.bin"
+    local result_file="$TEST_TMPDIR/${test_instance}_result.txt"
+    $compute_mac_cli $symmetric_key_file "compute" $plaintext_file\
+        $mac_file || exit 1
+    assert_files_different $plaintext_file $mac_file
+    $verify_mac_cli $symmetric_key_file "verify" $plaintext_file\
+        $mac_file $result_file || exit 1
+    assert_file_equals "valid" $result_file
+  done
+}
+
+#############################################################################
+##### Run the actual tests.
+
+### Tests with OutputPrefixType=="TINK"
+mac_basic_test "CC-CC"     $CC_MAC_CLI   $CC_MAC_CLI   \
+  "${KEY_TEMPLATES[*]}" "TINK"
+mac_basic_test "CC-JAVA"   $CC_MAC_CLI   $JAVA_MAC_CLI \
+  "${KEY_TEMPLATES[*]}" "TINK"
+mac_basic_test "JAVA-CC"   $JAVA_MAC_CLI $CC_MAC_CLI   \
+  "${KEY_TEMPLATES[*]}" "TINK"
+mac_basic_test "JAVA-JAVA" $JAVA_MAC_CLI $JAVA_MAC_CLI \
+  "${KEY_TEMPLATES[*]}" "TINK"
+
+### Tests with OutputPrefixType=="LEGACY"
+mac_basic_test "CC-CC"     $CC_MAC_CLI   $CC_MAC_CLI   \
+  "${KEY_TEMPLATES[*]}" "LEGACY"
+mac_basic_test "CC-JAVA"   $CC_MAC_CLI   $JAVA_MAC_CLI \
+  "${KEY_TEMPLATES[*]}" "LEGACY"
+mac_basic_test "JAVA-CC"   $JAVA_MAC_CLI $CC_MAC_CLI   \
+  "${KEY_TEMPLATES[*]}" "LEGACY"
+mac_basic_test "JAVA-JAVA" $JAVA_MAC_CLI $JAVA_MAC_CLI \
+  "${KEY_TEMPLATES[*]}" "LEGACY"
diff --git a/tools/testing/cross_language/signature_test.sh b/tools/testing/cross_language/signature_test.sh
index 205d7c6..52e5485 100755
--- a/tools/testing/cross_language/signature_test.sh
+++ b/tools/testing/cross_language/signature_test.sh
@@ -1,6 +1,19 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+################################################################################
+
 #!/bin/bash
 
-ROOT_DIR="$TEST_SRCDIR/__main__"
+ROOT_DIR="$TEST_SRCDIR/tink"
 CC_SIGN_CLI="$ROOT_DIR/tools/testing/cc/public_key_sign_cli_cc"
 CC_VERIFY_CLI="$ROOT_DIR/tools/testing/cc/public_key_verify_cli_cc"
 JAVA_SIGN_CLI="$ROOT_DIR/tools/testing/public_key_sign_cli_java"
@@ -17,17 +30,18 @@
 
 # Basic tests of PublicKeySign and PublicKeyVerify.
 signature_basic_test() {
-  local test_name="$1-signature-basic-test"
+  local test_name="$1-signature-basic-test-$5"
   local sign_cli="$2"
   local verify_cli="$3"
-  local key_templates=$4
+  local key_templates="$4"
+  local output_prefix="$5"
 
   echo "############ starting test $test_name for the following templates:"
   echo $key_templates
   for key_template in ${key_templates[*]}
   do
     local test_instance="${test_name}_${key_template}"
-    generate_asymmetric_keys "signature" $test_instance $key_template
+    generate_asymmetric_keys $test_instance $key_template $output_prefix
     generate_plaintext $test_instance
 
     local signature_file="$TEST_TMPDIR/${test_instance}_signature.bin"
@@ -42,9 +56,25 @@
 
 #############################################################################
 ##### Run the actual tests.
-signature_basic_test "CC-CC"     $CC_SIGN_CLI   $CC_VERIFY_CLI   "${KEY_TEMPLATES[*]}"
-signature_basic_test "CC-JAVA"   $CC_SIGN_CLI   $JAVA_VERIFY_CLI "${KEY_TEMPLATES[*]}"
-signature_basic_test "JAVA-CC"   $JAVA_SIGN_CLI $CC_VERIFY_CLI   "${KEY_TEMPLATES[*]}"
-signature_basic_test "JAVA-JAVA" $JAVA_SIGN_CLI $JAVA_VERIFY_CLI "${KEY_TEMPLATES[*]}"
+
+### Tests with OutputPrefixType=="TINK"
+signature_basic_test "CC-CC"     $CC_SIGN_CLI   $CC_VERIFY_CLI   \
+  "${KEY_TEMPLATES[*]}" "TINK"
+signature_basic_test "CC-JAVA"   $CC_SIGN_CLI   $JAVA_VERIFY_CLI \
+  "${KEY_TEMPLATES[*]}" "TINK"
+signature_basic_test "JAVA-CC"   $JAVA_SIGN_CLI $CC_VERIFY_CLI   \
+  "${KEY_TEMPLATES[*]}" "TINK"
+signature_basic_test "JAVA-JAVA" $JAVA_SIGN_CLI $JAVA_VERIFY_CLI \
+  "${KEY_TEMPLATES[*]}" "TINK"
+
+### Tests with OutputPrefixType=="LEGACY"
+signature_basic_test "CC-CC"     $CC_SIGN_CLI   $CC_VERIFY_CLI   \
+  "${KEY_TEMPLATES[*]}" "LEGACY"
+signature_basic_test "CC-JAVA"   $CC_SIGN_CLI   $JAVA_VERIFY_CLI \
+  "${KEY_TEMPLATES[*]}" "LEGACY"
+signature_basic_test "JAVA-CC"   $JAVA_SIGN_CLI $CC_VERIFY_CLI   \
+  "${KEY_TEMPLATES[*]}" "LEGACY"
+signature_basic_test "JAVA-JAVA" $JAVA_SIGN_CLI $JAVA_VERIFY_CLI \
+  "${KEY_TEMPLATES[*]}" "LEGACY"
 
 
diff --git a/tools/testing/cross_language/streaming_aead_test.sh b/tools/testing/cross_language/streaming_aead_test.sh
new file mode 100755
index 0000000..2ec77b1
--- /dev/null
+++ b/tools/testing/cross_language/streaming_aead_test.sh
@@ -0,0 +1,60 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+################################################################################
+
+#!/bin/bash
+
+ROOT_DIR="$TEST_SRCDIR/tink"
+JAVA_AEAD_CLI="$ROOT_DIR/tools/testing/streaming_aead_cli_java"
+TEST_UTIL="$ROOT_DIR/tools/testing/cross_language/test_util.sh"
+
+KEY_TEMPLATES=(AES128_CTR_HMAC_SHA256_4KB AES256_CTR_HMAC_SHA256_4KB AES128_GCM_HKDF_4KB AES256_GCM_HKDF_4KB)
+
+source $TEST_UTIL || exit 1
+
+#############################################################################
+### Helpers for streaming AEAD-tests.
+
+# Basic tests of streaming AEAD-implementations.
+streaming_aead_basic_test() {
+  local test_name="$1-streaming-aead-basic-test"
+  local encrypt_cli="$2"
+  local decrypt_cli="$3"
+  local key_templates=$4
+
+  echo "############ starting test $test_name for the following templates:"
+  echo $key_templates
+  for key_template in ${key_templates[*]}
+  do
+    local test_instance="${test_name}_${key_template}"
+    local test_file_size_mb=5
+    generate_symmetric_key $test_instance $key_template
+    generate_long_plaintext $test_instance $test_file_size_mb
+
+    local encrypted_file="$TEST_TMPDIR/${test_instance}_encrypted.bin"
+    local decrypted_file="$TEST_TMPDIR/${test_instance}_decrypted.bin"
+    local associated_data_file="$TEST_TMPDIR/${test_instance}_aad.bin"
+    echo "some associated data for $test_instance" > $associated_data_file
+    $encrypt_cli $symmetric_key_file "encrypt" $plaintext_file\
+        $associated_data_file $encrypted_file || exit 1
+    assert_files_different $plaintext_file $encrypted_file
+    $decrypt_cli $symmetric_key_file "decrypt" $encrypted_file\
+        $associated_data_file $decrypted_file || exit 1
+    assert_files_equal $plaintext_file $decrypted_file
+  done
+}
+
+#############################################################################
+##### Run the actual tests.
+streaming_aead_basic_test "JAVA-JAVA" $JAVA_AEAD_CLI $JAVA_AEAD_CLI "${KEY_TEMPLATES[*]}"
+
+
diff --git a/tools/testing/cross_language/test_util.sh b/tools/testing/cross_language/test_util.sh
index 7e43d64..c859a3f 100755
--- a/tools/testing/cross_language/test_util.sh
+++ b/tools/testing/cross_language/test_util.sh
@@ -1,39 +1,59 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+################################################################################
+
 #!/bin/bash
 
-ROOT_DIR="$TEST_SRCDIR/__main__"
+ROOT_DIR="$TEST_SRCDIR/tink"
 TINKEY_CLI="$ROOT_DIR/tools/tinkey/tinkey"
 
 #############################################################################
 ##### Helper functions.
 
 # Generates private and public keys according to $key_template,
-# which should be present in a subdirectory $templates_subdir.
+# which should be supported by Tinkey.
+# If $output_prefix is specified, the generated keyset will use it
+# instead of default value "TINK".
 # Stores the keys in files $priv_key_file and $pub_key_file, respectively.
 generate_asymmetric_keys() {
-  local templates_subdir="$1"
-  local key_name="$2"
-  local key_template="$3"
+  local key_name="$1"
+  local key_template="$2"
+  local output_prefix="$3"
+  if [ "$output_prefix" == "" ]; then
+    output_prefix="TINK"
+  fi
 
+  local json_priv_key_file="$TEST_TMPDIR/${key_name}_private_key.json"
   priv_key_file="$TEST_TMPDIR/${key_name}_private_key.bin"
   pub_key_file="$TEST_TMPDIR/${key_name}_public_key.bin"
   echo "--- Using template $key_template to generate keysets"\
       "to files $priv_key_file and $pub_key_file ..."
 
-  $TINKEY_CLI create-keyset --key-template  $key_template\
-      --out-format BINARY --out $priv_key_file  || exit 1
+  $TINKEY_CLI create-keyset --key-template  $key_template --out-format JSON\
+    | sed -e "s/\"TINK\"/\"$output_prefix\"/" > $json_priv_key_file  || exit 1
+  $TINKEY_CLI convert-keyset --in-format JSON --in $json_priv_key_file\
+    --out-format BINARY --out $priv_key_file  || exit 1
   $TINKEY_CLI create-public-keyset --in-format BINARY --in $priv_key_file\
       --out-format BINARY --out $pub_key_file  || exit 1
   echo "Done generating keysets."
 }
 
 # Generates a symmetric key according to $key_template,
-# which should be present in a subdirectory $templates_subdir.
+# which should be supported by Tinkey.
 # Stores the key in file $symmetric_key_file.
 generate_symmetric_key() {
-  local templates_subdir="$1"
-  local key_name="$2"
-  local key_template="$3"
-  local output_format="$4"
+  local key_name="$1"
+  local key_template="$2"
+  local output_format="$3"
   if [ "$output_format" == "" ]; then
     output_format="BINARY"
   fi
@@ -52,10 +72,31 @@
   local plaintext_name="$1"
 
   plaintext_file="$TEST_TMPDIR/${plaintext_name}_plaintext.bin"
-  echo "This is some plaintext message to be encrypted"\
-      " named $plaintext_name just like that." > $plaintext_file
+  echo "This is some plaintext message to be encrypted and/or signed" \
+       " named $plaintext_name just like that." > $plaintext_file
 }
 
+# Generates some example plaintext data, and stores it in $plaintext_file.
+generate_long_plaintext() {
+  local plaintext_name="$1"
+  local size_mb="$2"
+  local bytes_in_mb=1048576
+
+  plaintext_file="$TEST_TMPDIR/${plaintext_name}_plaintext.bin"
+  dd if=/dev/urandom of="$plaintext_file" bs=$bytes_in_mb count="$2"
+}
+
+
+# Checks that two values are equal.
+assert_equals() {
+  local expected="$1"
+  local actual="$2"
+  if [ "$expected" != "$actual" ]; then
+    echo "--- Failure: expected value: [$expected], actual value: [$actual]"
+    exit 1
+  fi
+  echo "    Success: got [$actual], as expected."
+}
 
 # Checks that two files are equal.
 assert_files_equal() {
@@ -101,3 +142,27 @@
   fi
   echo "+++ Success: the files are different."
 }
+
+# Checks that a given file contains specified substrings.
+assert_file_contains() {
+  local file_to_test="$1"
+  echo "*** Checking that file $file_to_test contains substrings:"
+  cat $file_to_test
+  # Shift the first argument and iterate through the remaining ones.
+  shift
+  for s do
+  echo "... checking for string [$s]"
+  if grep -q "$s" "$file_to_test"; then
+    echo "   found"
+  else
+    echo "--- Failure: file does not contain string [$s]"
+    exit 1
+  fi
+  done
+  echo "+++ Success: file contains all expected substrings."
+}
+
+# Keeps name of file while removing the path part.
+get_file_name() {
+  echo $1 | sed -e "s/.*\///"
+}
diff --git a/tools/testing/cross_language/version_test.sh b/tools/testing/cross_language/version_test.sh
new file mode 100755
index 0000000..6ecddeb
--- /dev/null
+++ b/tools/testing/cross_language/version_test.sh
@@ -0,0 +1,33 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+################################################################################
+
+#!/bin/bash
+
+ROOT_DIR="$TEST_SRCDIR/tink"
+VERSION_FILE="$ROOT_DIR/tink_version.bzl"
+CC_VERSION_CLI="$ROOT_DIR/tools/testing/cc/version_cli_cc"
+JAVA_VERSION_CLI="$ROOT_DIR/tools/testing/version_cli_java"
+TEST_UTIL="$ROOT_DIR/tools/testing/cross_language/test_util.sh"
+
+source $TEST_UTIL || exit 1
+
+#############################################################################
+##### Run the actual tests.
+TINK_VERSION=$(cat $VERSION_FILE | grep "TINK_VERSION_LABEL" | cut -d \" -f 2)
+echo "CONFIG: $TINK_VERSION"
+CC_TINK_VERSION=$($CC_VERSION_CLI)
+echo "CC: $CC_TINK_VERSION"
+JAVA_TINK_VERSION=$($JAVA_VERSION_CLI)
+echo "JAVA: $JAVA_TINK_VERSION"
+assert_equals "$TINK_VERSION" "$CC_TINK_VERSION"
+assert_equals "$TINK_VERSION" "$JAVA_TINK_VERSION"
diff --git a/tools/testing/java/com/google/crypto/tink/testing/AeadCli.java b/tools/testing/java/com/google/crypto/tink/testing/AeadCli.java
index c1414cb..8c0d7bd 100644
--- a/tools/testing/java/com/google/crypto/tink/testing/AeadCli.java
+++ b/tools/testing/java/com/google/crypto/tink/testing/AeadCli.java
@@ -18,7 +18,6 @@
 
 import com.google.crypto.tink.Aead;
 import com.google.crypto.tink.KeysetHandle;
-import com.google.crypto.tink.aead.AeadFactory;
 
 /**
  * A command-line utility for testing Aead-primitives.
@@ -27,20 +26,20 @@
  *   operation: the actual AEAD-operation, i.e. "encrypt" or "decrypt"
  *   input-file:  name of the file with input (plaintext for encryption, or
  *                or ciphertext for decryption)
- *   associated-data:  a string to be used as assciated data
+ *   associated-data-file:  name of the file containing associated data
  *   output-file:  name of the file for the resulting output
  */
 public class AeadCli {
   public static void main(String[] args) throws Exception {
     if (args.length != 5) {
       System.out.println(
-          "Usage: AeadCli keyset-file operation input-file associated-data output-file");
+          "Usage: AeadCli keyset-file operation input-file associated-data-file output-file");
       System.exit(1);
     }
     String keysetFilename = args[0];
     String operation = args[1];
     String inputFilename = args[2];
-    String associatedData = args[3];
+    String associatedDataFile = args[3];
     String outputFilename = args[4];
     if (!(operation.equals("encrypt") || operation.equals("decrypt"))) {
       System.out.println(
@@ -48,7 +47,7 @@
       System.exit(1);
     }
     System.out.println("Using keyset from file " + keysetFilename + " to AEAD-" + operation
-        + " file " + inputFilename + " with associated data '" + associatedData + "'.");
+        + " file " + inputFilename + " with associated data from file " + associatedDataFile + ".");
     System.out.println("The resulting output will be written to file " + outputFilename);
 
     // Init Tink.
@@ -60,18 +59,19 @@
 
     // Get the primitive.
     System.out.println("Getting the primitive...");
-    Aead aead = AeadFactory.getPrimitive(keysetHandle);
+    Aead aead = keysetHandle.getPrimitive(Aead.class);
 
     // Read the input.
     byte[] input = CliUtil.read(inputFilename);
+    byte[] aad = CliUtil.read(associatedDataFile);
 
     // Compute the output.
     System.out.println(operation + "ing...");
     byte[] output;
     if (operation.equals("encrypt")) {
-      output = aead.encrypt(input, associatedData.getBytes(CliUtil.UTF_8));
+      output = aead.encrypt(input, aad);
     } else { // operation.equals("decrypt")
-      output = aead.decrypt(input, associatedData.getBytes(CliUtil.UTF_8));
+      output = aead.decrypt(input, aad);
     }
 
     // Write the output to the output file.
diff --git a/tools/testing/java/com/google/crypto/tink/testing/CliUtil.java b/tools/testing/java/com/google/crypto/tink/testing/CliUtil.java
index d267498..325ae96 100644
--- a/tools/testing/java/com/google/crypto/tink/testing/CliUtil.java
+++ b/tools/testing/java/com/google/crypto/tink/testing/CliUtil.java
@@ -56,14 +56,22 @@
   }
 
   /**
-   * Reads the specified file and returns the contents as a string.
+   * Reads the specified file and returns the contents as a byte array.
    * In case of errors throws an exception.
    */
   public static byte[] read(String filename) throws GeneralSecurityException, IOException {
-    System.out.println("Reading the input...");
+    System.out.println("Reading file " + filename);
     InputStream inputStream = new FileInputStream(Paths.get(filename).toFile());
+    return read(inputStream);
+  }
+
+  /**
+   * Reads the specified InputStream and returns the contents as a byte array.
+   * In case of errors throws an exception.
+   */
+  public static byte[] read(InputStream inputStream) throws GeneralSecurityException, IOException {
     ByteArrayOutputStream result = new ByteArrayOutputStream();
-    byte[] buffer = new byte[1024];
+    byte[] buffer = new byte[512];
     int length;
     while ((length = inputStream.read(buffer)) != -1) {
       result.write(buffer, 0, length);
@@ -77,7 +85,7 @@
    * In case of errors throws an exception.
    */
   public static void write(byte[] output, String filename) throws IOException {
-    System.out.println("Writing the output...");
+    System.out.println("Writing to file " + filename);
     OutputStream outputStream = new FileOutputStream(Paths.get(filename).toFile());
     outputStream.write(output);
     outputStream.close();
diff --git a/tools/testing/java/com/google/crypto/tink/testing/DeterministicAeadCli.java b/tools/testing/java/com/google/crypto/tink/testing/DeterministicAeadCli.java
new file mode 100644
index 0000000..96cb022
--- /dev/null
+++ b/tools/testing/java/com/google/crypto/tink/testing/DeterministicAeadCli.java
@@ -0,0 +1,84 @@
+// Copyright 2017 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.testing;
+
+import com.google.crypto.tink.DeterministicAead;
+import com.google.crypto.tink.KeysetHandle;
+
+/**
+ * A command-line utility for testing DeterministicAead-primitives.
+ * It requires 5 arguments:
+ *   keyset-file:  name of the file with the keyset to be used for encryption
+ *   operation: the actual DeterministicAead-operation, i.e.
+ *   "encryptdeterministically" or "decryptdeterministically"
+ *   input-file:  name of the file with input (plaintext for encryption, or
+ *                or ciphertext for decryption)
+ *   associated-data-file:  name of the file containing associated data
+ *   output-file:  name of the file for the resulting output
+ */
+public class DeterministicAeadCli {
+  public static void main(String[] args) throws Exception {
+    if (args.length != 5) {
+      System.out.println("Usage: DeterministicAeadCli keyset-file operation "
+          + "input-file associated-data-file output-file");
+      System.exit(1);
+    }
+    String keysetFilename = args[0];
+    String operation = args[1];
+    String inputFilename = args[2];
+    String associatedDataFile = args[3];
+    String outputFilename = args[4];
+    if (!(operation.equals("encryptdeterministically")
+        || operation.equals("decryptdeterministically"))) {
+      System.out.println("Unknown operation '" + operation + "'.\nExpected "
+          + "'encryptdeterministically' or 'decryptdeterministically'.");
+      System.exit(1);
+    }
+    System.out.println("Using keyset from file " + keysetFilename + " to " + operation
+        + " file " + inputFilename + " with associated data from file " + associatedDataFile + ".");
+    System.out.println("The resulting output will be written to file " + outputFilename);
+
+    // Init Tink.
+    CliUtil.initTink();
+
+    // Read the keyset.
+    System.out.println("Reading the keyset...");
+    KeysetHandle keysetHandle = CliUtil.readKeyset(keysetFilename);
+
+    // Get the primitive.
+    System.out.println("Getting the primitive...");
+    DeterministicAead daead = keysetHandle.getPrimitive(DeterministicAead.class);
+
+    // Read the input.
+    byte[] input = CliUtil.read(inputFilename);
+    byte[] aad = CliUtil.read(associatedDataFile);
+
+    // Compute the output.
+    System.out.println("performing operation " + operation + "...");
+    byte[] output;
+    if (operation.equals("encryptdeterministically")) {
+      output = daead.encryptDeterministically(input, aad);
+    } else { // operation.equals("decryptdeterministically")
+      output = daead.decryptDeterministically(input, aad);
+    }
+
+    // Write the output to the output file.
+    CliUtil.write(output, outputFilename);
+
+    System.out.println("All done.");
+  }
+}
diff --git a/tools/testing/java/com/google/crypto/tink/testing/HybridDecryptCli.java b/tools/testing/java/com/google/crypto/tink/testing/HybridDecryptCli.java
index 2fc0a15..ae17824 100644
--- a/tools/testing/java/com/google/crypto/tink/testing/HybridDecryptCli.java
+++ b/tools/testing/java/com/google/crypto/tink/testing/HybridDecryptCli.java
@@ -18,29 +18,29 @@
 
 import com.google.crypto.tink.HybridDecrypt;
 import com.google.crypto.tink.KeysetHandle;
-import com.google.crypto.tink.hybrid.HybridDecryptFactory;
 
 /**
  * A command-line utility for testing HybridDecrypt-primitives.
  * It requires 4 arguments:
- *  keyset-file:  name of the file with the keyset to be used for decryption
- *  ciphertext-file:  name of the file that contains ciphertext to be decrypted
- *  context-info:  a string to be used as "context info" during the decryption
- *  output-file:  name of the output file for the resulting ciphertext
+ * keyset-file:  name of the file with the keyset to be used for decryption
+ * ciphertext-file:  name of the file that contains ciphertext to be decrypted
+ * context-info-file:  name of the file that contains "context info" which will
+ *     be used during the decryption
+ * output-file:  name of the output file for the resulting ciphertext
  */
 public class HybridDecryptCli {
   public static void main(String[] args) throws Exception {
     if (args.length != 4) {
       System.out.println(
-          "Usage: HybridDecryptCli keyset-file ciphertext-file context-info output-file");
+          "Usage: HybridDecryptCli keyset-file ciphertext-file context-info-file output-file");
       System.exit(1);
     }
     String keysetFilename = args[0];
     String ciphertextFilename = args[1];
-    String contextInfo = args[2];
+    String contextInfoFilename = args[2];
     String outputFilename = args[3];
     System.out.println("Using keyset from file " + keysetFilename + " to decrypt file "
-        + ciphertextFilename + " with context info '" + contextInfo + "'.");
+        + ciphertextFilename + " with context info from file " + contextInfoFilename + ".");
     System.out.println("The resulting plaintext will be written to file " + outputFilename);
 
     // Init Tink.
@@ -52,14 +52,15 @@
 
     // Get the primitive.
     System.out.println("Getting the primitive...");
-    HybridDecrypt hybridDecrypt = HybridDecryptFactory.getPrimitive(keysetHandle);
+    HybridDecrypt hybridDecrypt = keysetHandle.getPrimitive(HybridDecrypt.class);
 
     // Read the ciphertext.
     byte[] ciphertext = CliUtil.read(ciphertextFilename);
+    byte[] contextInfo = CliUtil.read(contextInfoFilename);
 
     // Compute the plaintext.
     System.out.println("Decrypting...");
-    byte[] plaintext = hybridDecrypt.decrypt(ciphertext, contextInfo.getBytes(CliUtil.UTF_8));
+    byte[] plaintext = hybridDecrypt.decrypt(ciphertext, contextInfo);
 
     // Write the plaintext to the output file.
     CliUtil.write(plaintext, outputFilename);
diff --git a/tools/testing/java/com/google/crypto/tink/testing/HybridEncryptCli.java b/tools/testing/java/com/google/crypto/tink/testing/HybridEncryptCli.java
index 893c90f..3a1b924 100644
--- a/tools/testing/java/com/google/crypto/tink/testing/HybridEncryptCli.java
+++ b/tools/testing/java/com/google/crypto/tink/testing/HybridEncryptCli.java
@@ -18,29 +18,29 @@
 
 import com.google.crypto.tink.HybridEncrypt;
 import com.google.crypto.tink.KeysetHandle;
-import com.google.crypto.tink.hybrid.HybridEncryptFactory;
 
 /**
  * A command-line utility for testing HybridEncrypt-primitives.
  * It requires 4 arguments:
- *  keyset-file:  name of the file with the keyset to be used for encryption
- *  plaintext-file:  name of the file that contains plaintext to be encrypted
- *  context-info:  a string to be used as "context info" during the encryption
- *  output-file:  name of the output file for the resulting ciphertext
+ * keyset-file:  name of the file with the keyset to be used for encryption
+ * plaintext-file:  name of the file that contains plaintext to be encrypted
+ * context-info-file:  name of the file that contains "context info" which will
+ *     be used during the decryption
+ * output-file:  name of the output file for the resulting ciphertext
  */
 public class HybridEncryptCli {
   public static void main(String[] args) throws Exception {
     if (args.length != 4) {
       System.out.println(
-          "Usage: HybridEncryptCli keyset-file plaintext-file context-info output-file");
+          "Usage: HybridEncryptCli keyset-file plaintext-file context-info-file output-file");
       System.exit(1);
     }
     String keysetFilename = args[0];
     String plaintextFilename = args[1];
-    String contextInfo = args[2];
+    String contextInfoFilename = args[2];
     String outputFilename = args[3];
     System.out.println("Using keyset from file " + keysetFilename + " to encrypt file "
-        + plaintextFilename + " with context info '" + contextInfo + "'.");
+        + plaintextFilename + " with context info from file " + contextInfoFilename + ".");
     System.out.println("The resulting ciphertext will be written to file " + outputFilename);
 
     // Init Tink.
@@ -52,14 +52,15 @@
 
     // Get the primitive.
     System.out.println("Getting the primitive...");
-    HybridEncrypt hybridEncrypt = HybridEncryptFactory.getPrimitive(keysetHandle);
+    HybridEncrypt hybridEncrypt = keysetHandle.getPrimitive(HybridEncrypt.class);
 
     // Read the plaintext.
     byte[] plaintext = CliUtil.read(plaintextFilename);
+    byte[] contextInfo = CliUtil.read(contextInfoFilename);
 
     // Compute the ciphertext.
     System.out.println("Encrypting...");
-    byte[] ciphertext = hybridEncrypt.encrypt(plaintext, contextInfo.getBytes(CliUtil.UTF_8));
+    byte[] ciphertext = hybridEncrypt.encrypt(plaintext, contextInfo);
 
     // Write the ciphertext to the output file.
     CliUtil.write(ciphertext, outputFilename);
diff --git a/tools/testing/java/com/google/crypto/tink/testing/MacCli.java b/tools/testing/java/com/google/crypto/tink/testing/MacCli.java
new file mode 100644
index 0000000..b883a9d
--- /dev/null
+++ b/tools/testing/java/com/google/crypto/tink/testing/MacCli.java
@@ -0,0 +1,98 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.testing;
+
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.Mac;
+import java.security.GeneralSecurityException;
+
+/**
+ * A command-line utility for testing Mac-primitives.
+ * It requires 4 for MAC computation and 5 for MAC verification:
+ *   keyset-file:  name of the file with the keyset to be used for MAC
+ *   operation: the actual MAC-operation, i.e. "compute" or "verify"
+ *   data-file:  name of the file with data for MAC computation/verification
+ *   mac-file:  name of the file for MAC value (when computing the MAC),
+ *              or with MAC value (when verifying the MAC)
+ *   result-file: name of the file for MAC verification result (valid/invalid)
+ *                (only for MAC verification operation)
+ */
+public class MacCli {
+  public static void main(String[] args) throws Exception {
+    if (args.length != 4 && args.length != 5) {
+      System.out.println(
+          "Usage: MacCli keyset-file operation data-file mac-file [result-file]");
+      System.exit(1);
+    }
+    String keysetFilename = args[0];
+    String operation = args[1];
+    String dataFilename = args[2];
+    String macFilename = args[3];
+    String resultFilename = "";
+    if (!(operation.equals("compute") || operation.equals("verify"))) {
+      System.out.println(
+          "Unknown operation '" + operation + "'.\nExpected 'compute' or 'verify'.");
+      System.exit(1);
+    }
+    if (operation.equals("compute")) {
+      System.out.println("Using keyset from file " + keysetFilename
+          + " to compute MAC of data from file " + dataFilename);
+      System.out.println("The resulting MAC will be written to file " + macFilename);
+    } else {  // operation.equals("verify")
+      resultFilename = args[4];
+      System.out.println("Using keyset from file " + keysetFilename
+          + " to verify MAC value from file " + macFilename
+          + " computed for data from file " + dataFilename);
+      System.out.println("The verification result will be written to file " + resultFilename);
+    }
+
+    // Init Tink.
+    CliUtil.initTink();
+
+    // Read the keyset.
+    System.out.println("Reading the keyset...");
+    KeysetHandle keysetHandle = CliUtil.readKeyset(keysetFilename);
+
+    // Get the primitive.
+    System.out.println("Getting the primitive...");
+    Mac mac = keysetHandle.getPrimitive(Mac.class);
+
+    // Read the data.
+    byte[] data = CliUtil.read(dataFilename);
+
+    // Compute and write the output.
+    if (operation.equals("compute")) {
+      System.out.println("computing MAC...");
+      byte[] macValue;
+      macValue = mac.computeMac(data);
+      CliUtil.write(macValue, macFilename);
+    } else { // operation.equals("verify")
+      System.out.println("verifying MAC...");
+      byte[] macValue = CliUtil.read(macFilename);
+      String result = "valid";
+      try {
+        mac.verifyMac(macValue, data);
+      } catch (GeneralSecurityException e) {
+        System.out.println("Verification failed: " + e);
+        result = "invalid";
+      }
+      CliUtil.write(result.getBytes(CliUtil.UTF_8), resultFilename);
+    }
+
+    System.out.println("All done.");
+  }
+}
diff --git a/tools/testing/java/com/google/crypto/tink/testing/PublicKeySignCli.java b/tools/testing/java/com/google/crypto/tink/testing/PublicKeySignCli.java
index c1d190c..3723d6f 100644
--- a/tools/testing/java/com/google/crypto/tink/testing/PublicKeySignCli.java
+++ b/tools/testing/java/com/google/crypto/tink/testing/PublicKeySignCli.java
@@ -18,7 +18,6 @@
 
 import com.google.crypto.tink.KeysetHandle;
 import com.google.crypto.tink.PublicKeySign;
-import com.google.crypto.tink.signature.PublicKeySignFactory;
 
 /**
  * A command-line utility for testing PublicKeySign-primitives.
@@ -50,7 +49,7 @@
 
     // Get the primitive.
     System.out.println("Getting the primitive...");
-    PublicKeySign pkSign = PublicKeySignFactory.getPrimitive(keysetHandle);
+    PublicKeySign pkSign = keysetHandle.getPrimitive(PublicKeySign.class);
 
     // Read the message.
     byte[] message = CliUtil.read(messageFilename);
diff --git a/tools/testing/java/com/google/crypto/tink/testing/PublicKeyVerifyCli.java b/tools/testing/java/com/google/crypto/tink/testing/PublicKeyVerifyCli.java
index 70f6004..69c167a 100644
--- a/tools/testing/java/com/google/crypto/tink/testing/PublicKeyVerifyCli.java
+++ b/tools/testing/java/com/google/crypto/tink/testing/PublicKeyVerifyCli.java
@@ -18,7 +18,6 @@
 
 import com.google.crypto.tink.KeysetHandle;
 import com.google.crypto.tink.PublicKeyVerify;
-import com.google.crypto.tink.signature.PublicKeyVerifyFactory;
 import java.security.GeneralSecurityException;
 
 /**
@@ -54,7 +53,7 @@
 
     // Get the primitive.
     System.out.println("Getting the primitive...");
-    PublicKeyVerify pkVerify = PublicKeyVerifyFactory.getPrimitive(keysetHandle);
+    PublicKeyVerify pkVerify = keysetHandle.getPrimitive(PublicKeyVerify.class);
 
     // Read the signature.
     byte[] signature = CliUtil.read(signatureFilename);
diff --git a/tools/testing/java/com/google/crypto/tink/testing/StreamingAeadCli.java b/tools/testing/java/com/google/crypto/tink/testing/StreamingAeadCli.java
new file mode 100644
index 0000000..c135de1
--- /dev/null
+++ b/tools/testing/java/com/google/crypto/tink/testing/StreamingAeadCli.java
@@ -0,0 +1,143 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package com.google.crypto.tink.testing;
+
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.StreamingAead;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.nio.file.Paths;
+import java.security.GeneralSecurityException;
+
+/**
+ * A command-line utility for testing StreamingAead-primitives.
+ * It requires 5 arguments:
+ *   keyset-file:  name of the file with the keyset to be used for encryption
+ *   operation: the actual Streaming AEAD-operation, i.e. "encrypt" or "decrypt"
+ *   input-file:  name of the file with input (plaintext for encryption, or
+ *                or ciphertext for decryption)
+ *   associated-data-file:  name of the file containing associated data
+ *   output-file:  name of the file for the resulting output
+ */
+public class StreamingAeadCli {
+
+
+  /**
+   * Returns an InputStream that provides ciphertext resulting from encryption
+   * of 'plaintextStream' with 'associatedData' via 'streamingAead'.
+   *
+   * This method demonstrates how to "invert" the direction of the encrypting
+   * stream, which might be required in some use cases:
+   *
+   * {@code StreamingAead.newEncryptingStream()} expects as parameter an
+   * OutputStream for the resulting ciphertext ({@code ciphertextDestination}),
+   * and returns an OutputStream to which the plaintext can be written.
+   * The plaintext to this OutputStream is automatically encrypted and
+   * the corresponding ciphertext is written to the ciphertext destination.
+   *
+   * Sometimes however the plaintext for encryption might be given
+   * in an InputStream and the desired encrypting stream should be also
+   * an InputStream (ciphertextSource), such that each read from this
+   * stream automatically reads from the plaintext stream, encrypts the
+   * the plaintext bytes, and returns the corresponding ciphertext bytes.
+   *
+   * NOTE: this method is for demonstration only, and should be adjusted
+   *       to specific needs. In particular, the handling of potential
+   *       exceptions via a RuntimeException might be not suitable.
+   */
+  public static InputStream getCiphertextStream(final StreamingAead streamingAead,
+      final InputStream plaintextStream, final byte[] associatedData)
+      throws IOException {
+
+    PipedInputStream ciphertextStream = new PipedInputStream();
+    final PipedOutputStream outputStream = new PipedOutputStream(ciphertextStream);
+    new Thread(new Runnable() {
+        @Override
+        public void run(){
+          try (OutputStream encryptingStream =
+              streamingAead.newEncryptingStream(outputStream, associatedData)) {
+            byte[] buffer = new byte[1024];
+            int length;
+            while ((length = plaintextStream.read(buffer)) != -1) {
+              encryptingStream.write(buffer, 0, length);
+            }
+            plaintextStream.close();
+          } catch (GeneralSecurityException | IOException e) {
+            throw new RuntimeException("Stream encryption failure.", e);
+          }
+        }
+      }
+      ).start();
+    return ciphertextStream;
+  }
+
+  public static void main(String[] args) throws Exception {
+    if (args.length != 5) {
+      System.out.println("Usage: StreamingAeadCli"
+          + " keyset-file operation input-file associated-data-file output-file");
+      System.exit(1);
+    }
+    String keysetFilename = args[0];
+    String operation = args[1];
+    String inputFilename = args[2];
+    String associatedDataFile = args[3];
+    String outputFilename = args[4];
+    if (!(operation.equals("encrypt") || operation.equals("decrypt"))) {
+      System.out.println(
+          "Unknown operation '" + operation + "'.\nExpected 'encrypt' or 'decrypt'.");
+      System.exit(1);
+    }
+    System.out.println("Using keyset from file " + keysetFilename + " to AEAD-" + operation
+        + " file " + inputFilename + " with associated data from file " + associatedDataFile + ".");
+    System.out.println("The resulting output will be written to file " + outputFilename);
+
+    // Init Tink.
+    CliUtil.initTink();
+
+    // Read the keyset.
+    System.out.println("Reading the keyset...");
+    KeysetHandle keysetHandle = CliUtil.readKeyset(keysetFilename);
+
+    // Get the primitive.
+    System.out.println("Getting the primitive...");
+    StreamingAead streamingAead = keysetHandle.getPrimitive(StreamingAead.class);
+
+    // Read the input.
+    InputStream inputStream = new FileInputStream(Paths.get(inputFilename).toFile());
+    byte[] aad = CliUtil.read(associatedDataFile);
+
+    // Compute the output.
+    System.out.println(operation + "ing...");
+    byte[] output;
+    if (operation.equals("encrypt")) {
+      InputStream ciphertextStream = getCiphertextStream(streamingAead, inputStream, aad);
+      output = CliUtil.read(ciphertextStream);
+    } else { // operation.equals("decrypt")
+      InputStream plaintextStream = streamingAead.newDecryptingStream(inputStream, aad);
+      output = CliUtil.read(plaintextStream);
+    }
+
+    // Write the output to the output file.
+    CliUtil.write(output, outputFilename);
+
+    System.out.println("All done.");
+  }
+}
diff --git a/go/tink/private_key_manager.go b/tools/testing/java/com/google/crypto/tink/testing/VersionCli.java
similarity index 62%
copy from go/tink/private_key_manager.go
copy to tools/testing/java/com/google/crypto/tink/testing/VersionCli.java
index 7cf169a..e479f8a 100644
--- a/go/tink/private_key_manager.go
+++ b/tools/testing/java/com/google/crypto/tink/testing/VersionCli.java
@@ -1,3 +1,5 @@
+// Copyright 2018 Google Inc.
+//
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
@@ -12,16 +14,16 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-package tink
+package com.google.crypto.tink.testing;
 
-import (
-	tinkpb "github.com/google/tink/proto/tink_go_proto"
-)
+import com.google.crypto.tink.Version;
 
-// PrivateKeyManager is a special type of KeyManager that understands private key types.
-type PrivateKeyManager interface {
-	KeyManager
-
-	// GetPublicKeyData extracts the public key data from the private key.
-	GetPublicKeyData(serializedKey []byte) (*tinkpb.KeyData, error)
+/**
+ * A command-line utility for testing Tink version setting.
+ * It takes no arguments and just prints the version of Tink library.
+ */
+public class VersionCli {
+  public static void main(String[] args) throws Exception {
+    System.out.print(Version.TINK_VERSION);
+  }
 }