diff --git a/python/signature/__init__.py b/python/signature/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..75424f704d4cf473c0a68d53ce90602c43321d9d
--- /dev/null
+++ b/python/signature/__init__.py
@@ -0,0 +1,33 @@
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Signature package."""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import google_type_annotations
+from __future__ import print_function
+
+from tink.python.signature import public_key_sign
+from tink.python.signature import public_key_sign_key_manager
+from tink.python.signature import public_key_sign_wrapper
+from tink.python.signature import public_key_verify
+from tink.python.signature import public_key_verify_key_manager
+from tink.python.signature import public_key_verify_wrapper
+from tink.python.signature import signature_key_templates
+
+
+PublicKeySign = public_key_sign.PublicKeySign
+PublicKeyVerify = public_key_verify.PublicKeyVerify
+PublicKeySignWrapper = public_key_sign_wrapper.PublicKeySignWrapper
+PublicKeyVerifyWrapper = public_key_verify_wrapper.PublicKeyVerifyWrapper
diff --git a/python/signature/public_key_sign.py b/python/signature/public_key_sign.py
new file mode 100644
index 0000000000000000000000000000000000000000..b7ce1126245f191f9797a1298686b5ce205d25f4
--- /dev/null
+++ b/python/signature/public_key_sign.py
@@ -0,0 +1,47 @@
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Interface for PublicKeySign."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import google_type_annotations
+from __future__ import print_function
+
+import abc
+
+
+class PublicKeySign(object):
+  """Interface for public key signing.
+
+  Digital Signatures provide functionality of signing data and verification of
+  the signatures. They are represented by a pair of primitives (interfaces)
+  'PublicKeySign' for signing of data, and 'PublicKeyVerify' for verification
+  of signatures. Implementations of these interfaces are secure against
+  adaptive chosen-message attacks. Signing data ensures the authenticity and
+  the integrity of that data, but not its secrecy.
+  """
+
+  __metaclass__ = abc.ABCMeta
+
+  @abc.abstractmethod
+  def sign(self, data: bytes) -> bytes:
+    """Computes the signature for data.
+
+    Args:
+      data: bytes, the input data.
+    Returns:
+      The signature as bytes.
+    """
+    raise NotImplementedError()
diff --git a/python/signature/public_key_sign_key_manager.py b/python/signature/public_key_sign_key_manager.py
new file mode 100644
index 0000000000000000000000000000000000000000..befd6216442a457556577567710f44d3100f234d
--- /dev/null
+++ b/python/signature/public_key_sign_key_manager.py
@@ -0,0 +1,47 @@
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Python wrapper of the CLIF-wrapped C++ Public Key Signature key manager."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import google_type_annotations
+from __future__ import print_function
+
+from typing import Text
+
+from tink.cc.python import public_key_sign as cc_public_key_sign
+from tink.python.cc.clif import cc_key_manager
+from tink.python.core import key_manager
+from tink.python.core import tink_error
+from tink.python.signature import public_key_sign
+
+
+class _PublicKeySignCcToPyWrapper(public_key_sign.PublicKeySign):
+  """Transforms cliffed C++ PublicKeySign into a Python primitive."""
+
+  def __init__(self, cc_primitive: cc_public_key_sign.PublicKeySign):
+    self._public_key_sign = cc_primitive
+
+  @tink_error.use_tink_errors
+  def sign(self, data: bytes) -> bytes:
+    return self._public_key_sign.sign(data)
+
+
+def from_cc_registry(
+    type_url: Text
+) -> key_manager.PrivateKeyManager[public_key_sign.PublicKeySign]:
+  return key_manager.PrivateKeyManagerCcToPyWrapper(
+      cc_key_manager.PublicKeySignKeyManager.from_cc_registry(type_url),
+      public_key_sign.PublicKeySign, _PublicKeySignCcToPyWrapper)
diff --git a/python/signature/public_key_sign_key_manager_test.py b/python/signature/public_key_sign_key_manager_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..80ba54f87fa20643a48d614b8b278516ecd9ffe1
--- /dev/null
+++ b/python/signature/public_key_sign_key_manager_test.py
@@ -0,0 +1,100 @@
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Tests for tink.python.public_key_sign_key_manager."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import unittest
+
+from tink.proto import common_pb2
+from tink.proto import ecdsa_pb2
+from tink.proto import tink_pb2
+from tink.python.core import tink_config
+from tink.python.signature import public_key_sign
+from tink.python.signature import public_key_sign_key_manager
+from tink.python.signature import public_key_verify_key_manager
+
+
+def setUpModule():
+  tink_config.register()
+
+
+def new_ecdsa_key_template(hash_type, curve_type, encoding):
+  key_format = ecdsa_pb2.EcdsaKeyFormat()
+  key_format.params.hash_type = hash_type
+  key_format.params.curve = curve_type
+  key_format.params.encoding = encoding
+  key_template = tink_pb2.KeyTemplate()
+  key_template.type_url = (
+      'type.googleapis.com/google.crypto.tink.EcdsaPrivateKey')
+  key_template.value = key_format.SerializeToString()
+  return key_template
+
+
+class PublicKeySignKeyManagerTest(googletest.TestCase):
+
+  def setUp(self):
+    super(PublicKeySignKeyManagerTest, self).setUp()
+    self.key_manager = public_key_sign_key_manager.from_cc_registry(
+        'type.googleapis.com/google.crypto.tink.EcdsaPrivateKey')
+    self.key_manager_verify = public_key_verify_key_manager.from_cc_registry(
+        'type.googleapis.com/google.crypto.tink.EcdsaPublicKey')
+
+  def test_primitive_class(self):
+    self.assertEqual(self.key_manager.primitive_class(),
+                     public_key_sign.PublicKeySign)
+
+  def test_key_type(self):
+    self.assertEqual(self.key_manager.key_type(),
+                     'type.googleapis.com/google.crypto.tink.EcdsaPrivateKey')
+
+  def test_new_key_data(self):
+    key_template = new_ecdsa_key_template(common_pb2.SHA256,
+                                          common_pb2.NIST_P256, ecdsa_pb2.DER)
+    key_data = self.key_manager.new_key_data(key_template)
+    self.assertEqual(key_data.type_url, self.key_manager.key_type())
+    key = ecdsa_pb2.EcdsaPrivateKey()
+    key.ParseFromString(key_data.value)
+    public_key = key.public_key
+    self.assertEqual(key.version, 0)
+    self.assertEqual(public_key.version, 0)
+    self.assertEqual(public_key.params.hash_type, common_pb2.SHA256)
+    self.assertEqual(public_key.params.curve, common_pb2.NIST_P256)
+    self.assertEqual(public_key.params.encoding, ecdsa_pb2.DER)
+    self.assertLen(key.key_value, 32)
+
+  def test_signature_success(self):
+
+    priv_key = self.key_manager.new_key_data(
+        new_ecdsa_key_template(common_pb2.SHA256, common_pb2.NIST_P256,
+                               ecdsa_pb2.DER))
+    pub_key = self.key_manager.public_key_data(priv_key)
+
+    verifier = self.key_manager_verify.primitive(pub_key)
+    signer = self.key_manager.primitive(priv_key)
+
+    data = b'data'
+    signature = signer.sign(data)
+
+    # Starts with a DER sequence
+    self.assertEqual(bytearray(signature)[0], 0x30)
+
+    verifier.verify(signature, data)
+
+
+if __name__ == '__main__':
+  googletest.main()
diff --git a/python/signature/public_key_sign_wrapper.py b/python/signature/public_key_sign_wrapper.py
new file mode 100644
index 0000000000000000000000000000000000000000..b90d802c3c33639cc5a4a447115e23f3428a8933
--- /dev/null
+++ b/python/signature/public_key_sign_wrapper.py
@@ -0,0 +1,73 @@
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Public Key Sign wrapper."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import google_type_annotations
+from __future__ import print_function
+
+from typing import Type
+
+from tink.proto import tink_pb2
+from tink.python.core import crypto_format
+from tink.python.core import primitive_set
+from tink.python.core import primitive_wrapper
+from tink.python.core import tink_error
+from tink.python.signature import public_key_sign
+
+
+class _WrappedPublicKeySign(public_key_sign.PublicKeySign):
+  """Implements PublicKeySign for a set of PublicKeySign primitives."""
+
+  def __init__(self, primitives_set: primitive_set.PrimitiveSet):
+    self._primitive_set = primitives_set
+
+  def sign(self, data: bytes) -> bytes:
+    """Computes the signature for data using the primary primitive.
+
+    Args:
+      data: The input data.
+
+    Returns:
+      The signature.
+    """
+    primary = self._primitive_set.primary()
+
+    if not primary:
+      raise tink_error.TinkError('primary primitive not set')
+
+    sign_data = data
+    if primary.output_prefix_type == tink_pb2.LEGACY:
+      sign_data = sign_data + crypto_format.LEGACY_START_BYTE
+
+    return primary.identifier + primary.primitive.sign(sign_data)
+
+
+class PublicKeySignWrapper(
+    primitive_wrapper.PrimitiveWrapper[public_key_sign.PublicKeySign]):
+  """A PrimitiveWrapper for the PublicKeySign primitive.
+
+  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.
+  """
+
+  def wrap(self, primitives_set: primitive_set.PrimitiveSet
+          ) -> _WrappedPublicKeySign:
+    return _WrappedPublicKeySign(primitives_set)
+
+  def primitive_class(self) -> Type[public_key_sign.PublicKeySign]:
+    return public_key_sign.PublicKeySign
diff --git a/python/signature/public_key_sign_wrapper_test.py b/python/signature/public_key_sign_wrapper_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..a0a0d77516d65d29fffca99f7e7c2eedf4902d1e
--- /dev/null
+++ b/python/signature/public_key_sign_wrapper_test.py
@@ -0,0 +1,79 @@
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Tests for tink.python.public_key_sign_wrapper."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import unittest
+from google3.testing.pybase import parameterized
+
+from tink.proto import tink_pb2
+from tink.python.core import primitive_set
+from tink.python.core import tink_error
+from tink.python.signature import public_key_sign
+from tink.python.signature import public_key_sign_wrapper
+from tink.python.signature import public_key_verify
+from tink.python.signature import public_key_verify_wrapper
+from tink.python.testing import helper
+
+
+def new_primitive_key_pair(key_id, output_prefix_type):
+  fake_key = helper.fake_key(
+      key_id=key_id,
+      key_material_type=tink_pb2.KeyData.ASYMMETRIC_PRIVATE,
+      output_prefix_type=output_prefix_type)
+  fake_sign = helper.FakePublicKeySign('fakePublicKeySign {}'.format(key_id))
+  return fake_sign, fake_key,
+
+
+def to_verify_key_pair(key):
+  fake_verify = helper.FakePublicKeyVerify('fakePublicKeySign {}'.format(
+      key.key_id))
+  return fake_verify, key,
+
+
+class PublicKeySignWrapperTest(parameterized.TestCase):
+
+  @parameterized.named_parameters(('tink', tink_pb2.TINK),
+                                  ('legacy', tink_pb2.LEGACY))
+  def test_signature(self, output_prefix_type):
+    pair0 = new_primitive_key_pair(1234, output_prefix_type)
+    pair1 = new_primitive_key_pair(5678, output_prefix_type)
+    pset = primitive_set.new_primitive_set(public_key_sign.PublicKeySign)
+    pset_verify = primitive_set.new_primitive_set(
+        public_key_verify.PublicKeyVerify)
+
+    pset.add_primitive(*pair0)
+    pset.set_primary(pset.add_primitive(*pair1))
+
+    pset_verify.add_primitive(*to_verify_key_pair(pair0[1]))
+    entry = pset_verify.add_primitive(*to_verify_key_pair(pair1[1]))
+    pset_verify.set_primary(entry)
+
+    wrapped_pk_sign = public_key_sign_wrapper.PublicKeySignWrapper().wrap(pset)
+    wrapped_pk_verify = public_key_verify_wrapper.PublicKeyVerifyWrapper().wrap(
+        pset_verify)
+    signature = wrapped_pk_sign.sign(b'data')
+
+    wrapped_pk_verify.verify(signature, b'data')
+
+    with self.assertRaisesRegex(tink_error.TinkError, 'invalid signature'):
+      wrapped_pk_verify.verify(signature, b'invalid')
+
+
+if __name__ == '__main__':
+  googletest.main()
diff --git a/python/signature/public_key_verify.py b/python/signature/public_key_verify.py
new file mode 100644
index 0000000000000000000000000000000000000000..5de96996ea13629ffc2ad26e01faa3780beec7f7
--- /dev/null
+++ b/python/signature/public_key_verify.py
@@ -0,0 +1,50 @@
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Interface for PublicKeyVerify."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import google_type_annotations
+from __future__ import print_function
+
+import abc
+
+
+class PublicKeyVerify(object):
+  """Interface for public key verifying.
+
+  Digital Signatures provide functionality of signing data and verification of
+  the signatures. They are represented by a pair of primitives (interfaces)
+  'PublicKeySign' for signing of data, and 'PublicKeyVerify' for verification
+  of signatures. Implementations of these interfaces are secure against
+  adaptive chosen-message attacks. Signing data ensures the authenticity and
+  the integrity of that data, but not its secrecy.
+  """
+
+  __metaclass__ = abc.ABCMeta
+
+  @abc.abstractmethod
+  def verify(self, signature: bytes, data: bytes) -> bytes:
+    """Verifies that signature is a digital signature for data.
+
+    Args:
+      signature: The signature bytes to be checked.
+      data: The data bytes to be checked.
+
+    Raises:
+      google3.third_party.tink.python.tink_error.TinkError if the verification
+      fails.
+    """
+    raise NotImplementedError()
diff --git a/python/signature/public_key_verify_key_manager.py b/python/signature/public_key_verify_key_manager.py
new file mode 100644
index 0000000000000000000000000000000000000000..39e100ea723d98600a200ddd126c0dd0db5f5e69
--- /dev/null
+++ b/python/signature/public_key_verify_key_manager.py
@@ -0,0 +1,47 @@
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Python wrapper of the CLIF-wrapped C++ Public Key Verify key manager."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import google_type_annotations
+from __future__ import print_function
+
+from typing import Text
+
+from tink.cc.python import public_key_verify as cc_public_key_verify
+from tink.python.cc.clif import cc_key_manager
+from tink.python.core import key_manager
+from tink.python.core import tink_error
+from tink.python.signature import public_key_verify
+
+
+class _PublicKeyVerifyCcToPyWrapper(public_key_verify.PublicKeyVerify):
+  """Transforms cliffed C++ PublicKeyVerify into a Python primitive."""
+
+  def __init__(self, cc_primitive: cc_public_key_verify.PublicKeyVerify):
+    self._public_key_verify = cc_primitive
+
+  @tink_error.use_tink_errors
+  def verify(self, signature: bytes, data: bytes) -> None:
+    self._public_key_verify.verify(signature, data)
+
+
+def from_cc_registry(
+    type_url: Text
+) -> key_manager.KeyManager[public_key_verify.PublicKeyVerify]:
+  return key_manager.KeyManagerCcToPyWrapper(
+      cc_key_manager.PublicKeyVerifyKeyManager.from_cc_registry(type_url),
+      public_key_verify.PublicKeyVerify, _PublicKeyVerifyCcToPyWrapper)
diff --git a/python/signature/public_key_verify_key_manager_test.py b/python/signature/public_key_verify_key_manager_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..f808fcd6bdb200b4d419dfb9900346c389b20ced
--- /dev/null
+++ b/python/signature/public_key_verify_key_manager_test.py
@@ -0,0 +1,105 @@
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Tests for tink.python.public_key_verify_key_manager."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import unittest
+
+from tink.proto import common_pb2
+from tink.proto import ecdsa_pb2
+from tink.proto import tink_pb2
+from tink.python.core import tink_config
+from tink.python.core import tink_error
+from tink.python.signature import public_key_sign_key_manager
+from tink.python.signature import public_key_verify_key_manager
+
+
+def setUpModule():
+  tink_config.register()
+
+
+def new_ecdsa_key_template(hash_type, curve_type, encoding, public=True):
+  params = ecdsa_pb2.EcdsaParams(
+      hash_type=hash_type, curve=curve_type, encoding=encoding)
+  key_format = ecdsa_pb2.EcdsaKeyFormat(params=params)
+  key_template = tink_pb2.KeyTemplate()
+  if public:
+    append = 'EcdsaPublicKey'
+  else:
+    append = 'EcdsaPrivateKey'
+  key_template.type_url = 'type.googleapis.com/google.crypto.tink.' + append
+  key_template.value = key_format.SerializeToString()
+
+  return key_template
+
+
+class PublicKeyVerifyKeyManagerTest(googletest.TestCase):
+
+  def setUp(self):
+    super(PublicKeyVerifyKeyManagerTest, self).setUp()
+    self.key_manager = public_key_verify_key_manager.from_cc_registry(
+        'type.googleapis.com/google.crypto.tink.EcdsaPublicKey')
+    self.key_manager_sign = public_key_sign_key_manager.from_cc_registry(
+        'type.googleapis.com/google.crypto.tink.EcdsaPrivateKey')
+
+  def test_key_type(self):
+    self.assertEqual(self.key_manager.key_type(),
+                     'type.googleapis.com/google.crypto.tink.EcdsaPublicKey')
+
+  def test_new_key_data(self):
+    key_template = new_ecdsa_key_template(
+        common_pb2.SHA256, common_pb2.NIST_P256, ecdsa_pb2.DER, True)
+    with self.assertRaisesRegex(tink_error.TinkError,
+                                'Operation not supported'):
+      self.key_manager.new_key_data(key_template)
+
+  def test_verify_success(self):
+    key_template = new_ecdsa_key_template(
+        common_pb2.SHA256, common_pb2.NIST_P256, ecdsa_pb2.DER, False)
+    priv_key = self.key_manager_sign.new_key_data(key_template)
+    pub_key = self.key_manager_sign.public_key_data(priv_key)
+
+    signer = self.key_manager_sign.primitive(priv_key)
+    verifier = self.key_manager.primitive(pub_key)
+
+    data = b'data'
+    signature = signer.sign(data)
+
+    verifier.verify(signature, data)
+
+  def test_verify_wrong(self):
+    key_template = new_ecdsa_key_template(
+        common_pb2.SHA256, common_pb2.NIST_P256, ecdsa_pb2.DER, False)
+    priv_key = self.key_manager_sign.new_key_data(key_template)
+    pub_key = self.key_manager_sign.public_key_data(priv_key)
+
+    signer = self.key_manager_sign.primitive(priv_key)
+    verifier = self.key_manager.primitive(pub_key)
+
+    data = b'data'
+    signature = signer.sign(data)
+
+    with self.assertRaisesRegex(tink_error.TinkError, 'Signature is not valid'):
+      verifier.verify(signature, 'wrongdata')
+
+    with self.assertRaisesRegex(tink_error.TinkError, 'Signature is not valid'):
+      verifier.verify('wrongsignature', data)
+
+
+if __name__ == '__main__':
+  googletest.main()
diff --git a/python/signature/public_key_verify_wrapper.py b/python/signature/public_key_verify_wrapper.py
new file mode 100644
index 0000000000000000000000000000000000000000..22b5e13d7455e22228a1cdac90c8bcc522f5a177
--- /dev/null
+++ b/python/signature/public_key_verify_wrapper.py
@@ -0,0 +1,102 @@
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Public Key Verify wrapper."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import google_type_annotations
+from __future__ import print_function
+
+import logging
+from typing import Type
+
+from tink.proto import tink_pb2
+from tink.python.core import crypto_format
+from tink.python.core import primitive_set
+from tink.python.core import primitive_wrapper
+from tink.python.core import tink_error
+from tink.python.signature import public_key_verify
+
+
+class _WrappedPublicKeyVerify(public_key_verify.PublicKeyVerify):
+  """Implements PublicKeyVerify for a set of PublicKeyVerify primitives."""
+
+  def __init__(self, primitives_set: primitive_set.PrimitiveSet):
+    self._primitive_set = primitives_set
+
+  def verify(self, signature: bytes, data: bytes):
+    """Verifies that signature is a digital signature for data.
+
+    Args:
+      signature: The signature bytes to be checked.
+      data: The data bytes to be checked.
+
+    Raises:
+      tink_error.TinkError if the verification fails.
+    """
+    if len(signature) <= crypto_format.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.
+      raise tink_error.TinkError('signature too short')
+
+    key_id = signature[:crypto_format.NON_RAW_PREFIX_SIZE]
+    raw_sig = signature[crypto_format.NON_RAW_PREFIX_SIZE:]
+
+    for entry in self._primitive_set.primitive_from_identifier(key_id):
+      try:
+        if entry.output_prefix_type == tink_pb2.LEGACY:
+          entry.primitive.verify(raw_sig,
+                                 data + crypto_format.LEGACY_START_BYTE)
+        else:
+          entry.primitive.verify(raw_sig, data)
+        # Signature is valid, we can return
+        return
+      except tink_error.TinkError as err:
+        logging.info('signature prefix matches a key, but cannot verify: %s',
+                     err)
+
+    # No matching key succeeded with verification, try all RAW keys
+    for entry in self._primitive_set.raw_primitives():
+      try:
+        entry.primitive.verify(signature, data)
+        # Signature is valid, we can return
+        return
+      except tink_error.TinkError:
+        pass
+
+    raise tink_error.TinkError('invalid signature')
+
+
+class PublicKeyVerifyWrapper(
+    primitive_wrapper.PrimitiveWrapper[public_key_verify.PublicKeyVerify]):
+  """WrappedPublicKeyVerify is the PrimitiveWrapper for PublicKeyVerify.
+
+  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.
+
+  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 tink_pb2.OutputPrefixType = tink_pb2.RAW.
+  """
+
+  def wrap(self, primitives_set: primitive_set.PrimitiveSet
+          ) -> _WrappedPublicKeyVerify:
+    return _WrappedPublicKeyVerify(primitives_set)
+
+  def primitive_class(self) -> Type[public_key_verify.PublicKeyVerify]:
+    return public_key_verify.PublicKeyVerify
diff --git a/python/signature/public_key_verify_wrapper_test.py b/python/signature/public_key_verify_wrapper_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..a744a7f33851ff4eae5dcbdecaf66e71e84d1893
--- /dev/null
+++ b/python/signature/public_key_verify_wrapper_test.py
@@ -0,0 +1,70 @@
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Tests for tink.python.public_key_verify_wrapper."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import unittest
+
+from tink.proto import tink_pb2
+from tink.python.core import primitive_set
+from tink.python.signature import public_key_sign
+from tink.python.signature import public_key_sign_wrapper
+from tink.python.signature import public_key_verify
+from tink.python.signature import public_key_verify_wrapper
+from tink.python.testing import helper
+
+
+def new_primitive_key_pair(key_id, output_prefix_type):
+  fake_key = helper.fake_key(
+      key_id=key_id,
+      key_material_type=tink_pb2.KeyData.ASYMMETRIC_PRIVATE,
+      output_prefix_type=output_prefix_type)
+  fake_sign = helper.FakePublicKeyVerify('fakePublicKeySign {}'.format(key_id))
+  return fake_sign, fake_key,
+
+
+class PublicKeyVerifyWrapperTest(googletest.TestCase):
+
+  def test_verify_signature(self):
+    pair0 = new_primitive_key_pair(1234, tink_pb2.RAW)
+    pair1 = new_primitive_key_pair(5678, tink_pb2.TINK)
+    pair2 = new_primitive_key_pair(9012, tink_pb2.LEGACY)
+    pset = primitive_set.new_primitive_set(public_key_verify.PublicKeyVerify)
+
+    pset.add_primitive(*pair0)
+    pset.add_primitive(*pair1)
+    pset.set_primary(pset.add_primitive(*pair2))
+
+    # Check all keys work
+    for unused_primitive, key in (pair0, pair1, pair2):
+      pset_sign = primitive_set.new_primitive_set(public_key_sign.PublicKeySign)
+      pset_sign.set_primary(
+          pset_sign.add_primitive(
+              helper.FakePublicKeySign('fakePublicKeySign {}'.format(
+                  key.key_id)), key))
+
+      wrapped_pk_verify = public_key_verify_wrapper.PublicKeyVerifyWrapper(
+      ).wrap(pset)
+      wrapped_pk_sign = public_key_sign_wrapper.PublicKeySignWrapper().wrap(
+          pset_sign)
+
+      wrapped_pk_verify.verify(wrapped_pk_sign.sign(b'data'), b'data')
+
+
+if __name__ == '__main__':
+  googletest.main()
diff --git a/python/signature/signature_key_templates.py b/python/signature/signature_key_templates.py
new file mode 100644
index 0000000000000000000000000000000000000000..10915025ed1b2ff9336113f2ce3630eb2b5e44da
--- /dev/null
+++ b/python/signature/signature_key_templates.py
@@ -0,0 +1,141 @@
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Pre-generated KeyTemplate for PublicKeySign and PublicKeyVerify.
+
+One can use these templates to generate new tink_pb2.Keyset with
+tink_pb2.KeysetHandle. To generate a new keyset that contains a single
+EcdsaPrivateKey, one can do:
+
+handle = keyset_handle.KeysetHandle(signature_key_templates.ECDSA_P256);
+"""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import google_type_annotations
+from __future__ import print_function
+
+from tink.proto import common_pb2
+from tink.proto import ecdsa_pb2
+from tink.proto import rsa_ssa_pkcs1_pb2
+from tink.proto import rsa_ssa_pss_pb2
+from tink.proto import tink_pb2
+
+_prefix = 'type.googleapis.com/google.crypto.tink.'
+_ECDSA_KEY_TYPE_URL = _prefix + 'EcdsaPrivateKey'
+_ED25519_KEY_TYPE_URL = _prefix + 'Ed25519PrivateKey'
+_RSA_PKCS1_KEY_TYPE_URL = _prefix + 'RsaSsaPkcs1PrivateKey'
+_RSA_PSS_KEY_TYPE_URL = _prefix + 'RsaSsaPssPrivateKey'
+_RSA_F4 = 65537
+
+
+def _num_to_bytes(n: int) -> bytes:
+  """Converts a number to bytes."""
+  if n < 0:
+    raise OverflowError("number can't be negative")
+
+  if n == 0:
+    return b'\x00'
+
+  octets = bytearray()
+  while n:
+    octets.append(n % 256)
+    n //= 256
+
+  return bytes(octets[::-1])
+
+
+def create_ecdsa_key_template(hash_type: common_pb2.HashType,
+                              curve: common_pb2.EllipticCurveType,
+                              encoding: ecdsa_pb2.EcdsaSignatureEncoding
+                             ) -> tink_pb2.KeyTemplate:
+  """Creates a KeyTemplate containing an EcdsaKeyFormat."""
+  params = ecdsa_pb2.EcdsaParams(
+      hash_type=hash_type, curve=curve, encoding=encoding)
+  key_format = ecdsa_pb2.EcdsaKeyFormat(params=params)
+  key_template = tink_pb2.KeyTemplate(
+      value=key_format.SerializeToString(),
+      type_url=_ECDSA_KEY_TYPE_URL,
+      output_prefix_type=tink_pb2.TINK)
+
+  return key_template
+
+
+def create_rsa_ssa_pkcs1_key_template(hash_type: common_pb2.HashType,
+                                      modulus_size: int, public_exponent: int
+                                     ) -> tink_pb2.KeyTemplate:
+  """Creates a KeyTemplate containing an RsaSsaPkcs1KeyFormat."""
+
+  params = rsa_ssa_pkcs1_pb2.RsaSsaPkcs1Params(hash_type=hash_type)
+  key_format = rsa_ssa_pkcs1_pb2.RsaSsaPkcs1KeyFormat(
+      params=params,
+      modulus_size_in_bits=modulus_size,
+      public_exponent=_num_to_bytes(public_exponent))
+  key_template = tink_pb2.KeyTemplate(
+      value=key_format.SerializeToString(),
+      type_url=_RSA_PKCS1_KEY_TYPE_URL,
+      output_prefix_type=tink_pb2.TINK)
+
+  return key_template
+
+
+def create_rsa_ssa_pss_key_template(sig_hash: common_pb2.HashType,
+                                    mgf1_hash: common_pb2.HashType,
+                                    salt_length: int, modulus_size: int,
+                                    public_exponent: int
+                                   ) -> tink_pb2.KeyTemplate:
+  """Creates a KeyTemplate containing an RsaSsaPssKeyFormat."""
+  params = rsa_ssa_pss_pb2.RsaSsaPssParams(
+      sig_hash=sig_hash, mgf1_hash=mgf1_hash, salt_length=salt_length)
+  key_format = rsa_ssa_pss_pb2.RsaSsaPssKeyFormat(
+      params=params,
+      modulus_size_in_bits=modulus_size,
+      public_exponent=_num_to_bytes(public_exponent))
+  key_template = tink_pb2.KeyTemplate(
+      value=key_format.SerializeToString(),
+      type_url=_RSA_PSS_KEY_TYPE_URL,
+      output_prefix_type=tink_pb2.TINK)
+
+  return key_template
+
+
+ECDSA_P256 = create_ecdsa_key_template(common_pb2.SHA256, common_pb2.NIST_P256,
+                                       ecdsa_pb2.DER)
+ECDSA_P384 = create_ecdsa_key_template(common_pb2.SHA512, common_pb2.NIST_P384,
+                                       ecdsa_pb2.DER)
+ECDSA_P521 = create_ecdsa_key_template(common_pb2.SHA512, common_pb2.NIST_P521,
+                                       ecdsa_pb2.DER)
+
+ECDSA_P256_IEEE_P1363 = create_ecdsa_key_template(common_pb2.SHA256,
+                                                  common_pb2.NIST_P256,
+                                                  ecdsa_pb2.IEEE_P1363)
+ECDSA_P384_IEEE_P1363 = create_ecdsa_key_template(common_pb2.SHA512,
+                                                  common_pb2.NIST_P384,
+                                                  ecdsa_pb2.IEEE_P1363)
+ECDSA_P521_IEEE_P1363 = create_ecdsa_key_template(common_pb2.SHA512,
+                                                  common_pb2.NIST_P521,
+                                                  ecdsa_pb2.IEEE_P1363)
+
+ED25519 = tink_pb2.KeyTemplate(
+    type_url=_ED25519_KEY_TYPE_URL, output_prefix_type=tink_pb2.TINK)
+
+RSA_SSA_PKCS1_3072_SHA256_F4 = create_rsa_ssa_pkcs1_key_template(
+    common_pb2.SHA256, 3072, _RSA_F4)
+RSA_SSA_PKCS1_4096_SHA512_F4 = create_rsa_ssa_pkcs1_key_template(
+    common_pb2.SHA512, 4096, _RSA_F4)
+
+RSA_SSA_PSS_3072_SHA256_SHA256_32_F4 = create_rsa_ssa_pss_key_template(
+    common_pb2.SHA256, common_pb2.SHA256, 32, 3072, _RSA_F4)
+RSA_SSA_PSS_4096_SHA512_SHA512_64_F4 = create_rsa_ssa_pss_key_template(
+    common_pb2.SHA512, common_pb2.SHA512, 64, 4096, _RSA_F4)
diff --git a/python/signature/signature_key_templates_test.py b/python/signature/signature_key_templates_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..bac00150556cb7aba2816e16c6e59fd211e9ae6b
--- /dev/null
+++ b/python/signature/signature_key_templates_test.py
@@ -0,0 +1,204 @@
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Tests for tink.python.signature_key_templates."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import unittest
+from google3.testing.pybase import parameterized
+
+from tink.proto import common_pb2
+from tink.proto import ecdsa_pb2
+from tink.proto import rsa_ssa_pkcs1_pb2
+from tink.proto import rsa_ssa_pss_pb2
+from tink.proto import tink_pb2
+from tink.python.core import tink_config
+from tink.python.signature import public_key_sign_key_manager
+from tink.python.signature import signature_key_templates
+
+
+ECDSA_DER_PARAMS_P256 = [
+    signature_key_templates.ECDSA_P256, common_pb2.SHA256, common_pb2.NIST_P256
+]
+ECDSA_DER_PARAMS_P384 = [
+    signature_key_templates.ECDSA_P384, common_pb2.SHA512, common_pb2.NIST_P384
+]
+ECDSA_DER_PARAMS_P521 = [
+    signature_key_templates.ECDSA_P521, common_pb2.SHA512, common_pb2.NIST_P521
+]
+
+ECDSA_IEEE_PARAMS_P256 = [
+    signature_key_templates.ECDSA_P256_IEEE_P1363, common_pb2.SHA256,
+    common_pb2.NIST_P256
+]
+ECDSA_IEEE_PARAMS_P384 = [
+    signature_key_templates.ECDSA_P384_IEEE_P1363, common_pb2.SHA512,
+    common_pb2.NIST_P384
+]
+ECDSA_IEEE_PARAMS_P521 = [
+    signature_key_templates.ECDSA_P521_IEEE_P1363, common_pb2.SHA512,
+    common_pb2.NIST_P521
+]
+
+RSA_PKCS1_PARAMS_3072 = [
+    signature_key_templates.RSA_SSA_PKCS1_3072_SHA256_F4, common_pb2.SHA256,
+    3072, 65537
+]
+RSA_PKCS1_PARAMS_4096 = [
+    signature_key_templates.RSA_SSA_PKCS1_4096_SHA512_F4, common_pb2.SHA512,
+    4096, 65537
+]
+
+RSA_PSS_PARAMS_3072 = [
+    signature_key_templates.RSA_SSA_PSS_3072_SHA256_SHA256_32_F4,
+    common_pb2.SHA256, 3072, 65537
+]
+RSA_PSS_PARAMS_4096 = [
+    signature_key_templates.RSA_SSA_PSS_4096_SHA512_SHA512_64_F4,
+    common_pb2.SHA512, 4096, 65537
+]
+
+
+def bytes_to_num(data):
+  res = 0
+
+  for b in bytearray(data):
+    res <<= 8
+    res |= b
+
+  return res
+
+
+def setUpModule():
+  tink_config.register()
+
+
+class SignatureKeyTemplatesTest(parameterized.TestCase):
+
+  def test_bytes_to_num(self):
+    for i in range(100000):
+      res = bytes_to_num(signature_key_templates._num_to_bytes(i))
+      self.assertEqual(res, i)
+
+  @parameterized.named_parameters(('0', 0, b'\x00'), ('256', 256, b'\x01\x00'),
+                                  ('65537', 65537, b'\x01\x00\x01'))
+  def test_num_to_bytes(self, number, expected):
+    self.assertEqual(signature_key_templates._num_to_bytes(number), expected)
+
+    with self.assertRaises(OverflowError):
+      signature_key_templates._num_to_bytes(-1)
+
+  @parameterized.named_parameters(
+      ['ecdsa_p256'] + ECDSA_DER_PARAMS_P256,
+      ['ecdsa_p384'] + ECDSA_DER_PARAMS_P384,
+      ['ecdsa_p521'] + ECDSA_DER_PARAMS_P521,
+  )
+  def test_ecdsa_der(self, key_template, hash_type, curve):
+    self.assertEqual(key_template.type_url,
+                     'type.googleapis.com/google.crypto.tink.EcdsaPrivateKey')
+    self.assertEqual(key_template.output_prefix_type, tink_pb2.TINK)
+
+    key_format = ecdsa_pb2.EcdsaKeyFormat()
+    key_format.ParseFromString(key_template.value)
+    self.assertEqual(key_format.params.hash_type, hash_type)
+    self.assertEqual(key_format.params.curve, curve)
+    self.assertEqual(key_format.params.encoding, ecdsa_pb2.DER)
+
+    # Check that the template works with the key manager
+    key_manager = public_key_sign_key_manager.from_cc_registry(
+        key_template.type_url)
+    key_manager.new_key_data(key_template)
+
+  @parameterized.named_parameters(
+      ['ecdsa_p256'] + ECDSA_IEEE_PARAMS_P256,
+      ['ecdsa_p384'] + ECDSA_IEEE_PARAMS_P384,
+      ['ecdsa_p521'] + ECDSA_IEEE_PARAMS_P521,
+  )
+  def test_ecdsa_ieee(self, key_template, hash_type, curve):
+    self.assertEqual(key_template.type_url,
+                     'type.googleapis.com/google.crypto.tink.EcdsaPrivateKey')
+    self.assertEqual(key_template.output_prefix_type, tink_pb2.TINK)
+
+    key_format = ecdsa_pb2.EcdsaKeyFormat()
+    key_format.ParseFromString(key_template.value)
+    self.assertEqual(key_format.params.hash_type, hash_type)
+    self.assertEqual(key_format.params.curve, curve)
+    self.assertEqual(key_format.params.encoding, ecdsa_pb2.IEEE_P1363)
+
+    # Check that the template works with the key manager
+    key_manager = public_key_sign_key_manager.from_cc_registry(
+        key_template.type_url)
+    key_manager.new_key_data(key_template)
+
+  def test_ed25519(self):
+    key_template = signature_key_templates.ED25519
+    self.assertEqual(
+        key_template.type_url,
+        'type.googleapis.com/google.crypto.tink.Ed25519PrivateKey')
+    self.assertEqual(key_template.output_prefix_type, tink_pb2.TINK)
+
+    # Check that the template works with the key manager
+    key_manager = public_key_sign_key_manager.from_cc_registry(
+        key_template.type_url)
+    key_manager.new_key_data(key_template)
+
+  @parameterized.named_parameters(
+      ['rsa_pkcs1_3072'] + RSA_PKCS1_PARAMS_3072,
+      ['rsa_pkcs1_4096'] + RSA_PKCS1_PARAMS_4096,
+  )
+  def test_rsa_pkcs1(self, key_template, hash_algo, modulus_size, exponent):
+    self.assertEqual(
+        key_template.type_url,
+        'type.googleapis.com/google.crypto.tink.RsaSsaPkcs1PrivateKey')
+    self.assertEqual(key_template.output_prefix_type, tink_pb2.TINK)
+
+    key_format = rsa_ssa_pkcs1_pb2.RsaSsaPkcs1KeyFormat()
+    key_format.ParseFromString(key_template.value)
+    self.assertEqual(key_format.modulus_size_in_bits, modulus_size)
+    self.assertEqual(key_format.params.hash_type, hash_algo)
+    self.assertEqual(bytes_to_num(key_format.public_exponent), exponent)
+
+    # Check that the template works with the key manager
+    key_manager = public_key_sign_key_manager.from_cc_registry(
+        key_template.type_url)
+    key_manager.new_key_data(key_template)
+
+  @parameterized.named_parameters(
+      ['rsa_pss_3072'] + RSA_PSS_PARAMS_3072,
+      ['rsa_pss_4096'] + RSA_PSS_PARAMS_4096,
+  )
+  def test_rsa_pss(self, key_template, hash_algo, modulus_size, exponent):
+    self.assertEqual(
+        key_template.type_url,
+        'type.googleapis.com/google.crypto.tink.RsaSsaPssPrivateKey')
+    self.assertEqual(key_template.output_prefix_type, tink_pb2.TINK)
+
+    key_format = rsa_ssa_pss_pb2.RsaSsaPssKeyFormat()
+    key_format.ParseFromString(key_template.value)
+    self.assertEqual(key_format.modulus_size_in_bits, modulus_size)
+    self.assertEqual(key_format.params.sig_hash, hash_algo)
+    self.assertEqual(key_format.params.mgf1_hash, hash_algo)
+    self.assertEqual(bytes_to_num(key_format.public_exponent), exponent)
+
+    # Check that the template works with the key manager
+    key_manager = public_key_sign_key_manager.from_cc_registry(
+        key_template.type_url)
+    key_manager.new_key_data(key_template)
+
+
+if __name__ == '__main__':
+  googletest.main()