diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst
index b403363d6d25b7e8c6d4c84c871c3c8b11865ecf..cf88d3c48de1e9306be711f9260465db41508e44 100644
--- a/docs/source/configuration.rst
+++ b/docs/source/configuration.rst
@@ -48,3 +48,13 @@ Then also configure the Ristretto-flavored PrivacyPass issuer the server will an
 
   [storageserver.plugins.privatestorageio-zkapauthz-v1]
   ristretto-issuer-root-url = https://issuer.example.invalid/
+
+The storage server must also be configured with the path to the Ristretto-flavored PrivacyPass signing key.
+To avoid placing secret material in tahoe.cfg,
+this configuration is done using a path::
+
+  [storageserver.plugins.privatestorageio-zkapauthz-v1]
+  ristretto-signing-key-path = /path/to/signing.key
+
+The signing key is the keystone secret to the entire system and must be managed with extreme care to prevent unintended disclosure.
+If things go well a future version of ZKAPAuthorizer will remove the requirement that the signing key be distributed to storage servers.
diff --git a/setup.py b/setup.py
index 606849326a4002007fd42060b51e69a19c18675c..f61c0015abc4af74c07c67a1f6127d032867aa66 100644
--- a/setup.py
+++ b/setup.py
@@ -1,3 +1,7 @@
 from setuptools import setup
 
-setup()
+setup(
+    package_data={
+        "": ["testing-signing.key"],
+    },
+)
diff --git a/src/_zkapauthorizer/_plugin.py b/src/_zkapauthorizer/_plugin.py
index e5c59e23456605c95c36ed24839aa1a439957520..355642dbf1ae232099ae802fba3520b573b555a1 100644
--- a/src/_zkapauthorizer/_plugin.py
+++ b/src/_zkapauthorizer/_plugin.py
@@ -27,6 +27,9 @@ from zope.interface import (
     implementer,
 )
 
+from twisted.python.filepath import (
+    FilePath,
+)
 from twisted.internet.defer import (
     succeed,
 )
@@ -35,6 +38,9 @@ from allmydata.interfaces import (
     IFoolscapStoragePlugin,
     IAnnounceableStorageServer,
 )
+from privacypass import (
+    SigningKey,
+)
 
 from .api import (
     ZKAPAuthorizerStorageServer,
@@ -104,11 +110,17 @@ class ZKAPAuthorizer(object):
     def get_storage_server(self, configuration, get_anonymous_storage_server):
         kwargs = configuration.copy()
         root_url = kwargs.pop(u"ristretto-issuer-root-url")
+        signing_key = SigningKey.decode_base64(
+            FilePath(
+                kwargs.pop(u"ristretto-signing-key-path"),
+            ).getContent(),
+        )
         announcement = {
             u"ristretto-issuer-root-url": root_url,
         }
         storage_server = ZKAPAuthorizerStorageServer(
             get_anonymous_storage_server(),
+            signing_key,
             **kwargs
         )
         return succeed(
diff --git a/src/_zkapauthorizer/_storage_client.py b/src/_zkapauthorizer/_storage_client.py
index 6b1c1acf3babf0d303802f75320dc0734e6cdde1..7eebdc56a6e567715b2909ed0e9c9a2c03660486 100644
--- a/src/_zkapauthorizer/_storage_client.py
+++ b/src/_zkapauthorizer/_storage_client.py
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 """
-A Tahoe-LAFS ``IStorageServer`` implementation which presents tokens
+A Tahoe-LAFS ``IStorageServer`` implementation which presents passes
 per-call to prove authorization for writes and lease updates.
 
 This is the client part of a storage access protocol.  The server part is
@@ -30,19 +30,28 @@ from allmydata.interfaces import (
     IStorageServer,
 )
 
+from .storage_common import (
+    BYTES_PER_PASS,
+    required_passes,
+    allocate_buckets_message,
+    add_lease_message,
+    renew_lease_message,
+    slot_testv_and_readv_and_writev_message,
+)
+
 @implementer(IStorageServer)
 @attr.s
 class ZKAPAuthorizerStorageClient(object):
     """
-    An implementation of the client portion of an access-token-based
+    An implementation of the client portion of an access-pass-based
     authorization scheme on top of the basic Tahoe-LAFS storage protocol.
 
     This ``IStorageServer`` implementation aims to offer the same storage
     functionality as Tahoe-LAFS' built-in storage server but with an added
-    layer of token-based authorization for some operations.  The Python
+    layer of pass-based authorization for some operations.  The Python
     interface exposed to application code is the same but the network protocol
-    is augmented with tokens which are automatically inserted by this class.
-    The tokens are interpreted by the corresponding server-side implementation
+    is augmented with passes which are automatically inserted by this class.
+    The passes are interpreted by the corresponding server-side implementation
     of this scheme.
 
     :ivar _get_rref: A no-argument callable which retrieves the most recently
@@ -64,13 +73,16 @@ class ZKAPAuthorizerStorageClient(object):
 
     def _get_encoded_passes(self, message, count):
         """
+        :param unicode message: The message to which to bind the passes.
+
         :return: A list of passes from ``_get_passes`` encoded into their
             ``bytes`` representation.
         """
+        assert isinstance(message, unicode)
         return list(
             t.text.encode("ascii")
             for t
-            in self._get_passes(message.encode("hex"), count)
+            in self._get_passes(message.encode("utf-8"), count)
         )
 
     def get_version(self):
@@ -89,7 +101,10 @@ class ZKAPAuthorizerStorageClient(object):
     ):
         return self._rref.callRemote(
             "allocate_buckets",
-            self._get_encoded_passes(storage_index, 1),
+            self._get_encoded_passes(
+                allocate_buckets_message(storage_index),
+                required_passes(BYTES_PER_PASS, sharenums, allocated_size),
+            ),
             storage_index,
             renew_secret,
             cancel_secret,
@@ -115,7 +130,7 @@ class ZKAPAuthorizerStorageClient(object):
     ):
         return self._rref.callRemote(
             "add_lease",
-            self._get_encoded_passes(storage_index, 1),
+            self._get_encoded_passes(add_lease_message(storage_index), 1),
             storage_index,
             renew_secret,
             cancel_secret,
@@ -128,7 +143,7 @@ class ZKAPAuthorizerStorageClient(object):
     ):
         return self._rref.callRemote(
             "renew_lease",
-            self._get_encoded_passes(storage_index, 1),
+            self._get_encoded_passes(renew_lease_message(storage_index), 1),
             storage_index,
             renew_secret,
         )
@@ -157,7 +172,7 @@ class ZKAPAuthorizerStorageClient(object):
     ):
         return self._rref.callRemote(
             "slot_testv_and_readv_and_writev",
-            self._get_encoded_passes(storage_index, 1),
+            self._get_encoded_passes(slot_testv_and_readv_and_writev_message(storage_index), 1),
             storage_index,
             secrets,
             tw_vectors,
diff --git a/src/_zkapauthorizer/_storage_server.py b/src/_zkapauthorizer/_storage_server.py
index 3f9aec7c698a18913c45c86a1b8924111a3ab110..6d03290e7a3dc719a1c2c5fee00df03cdaae179d 100644
--- a/src/_zkapauthorizer/_storage_server.py
+++ b/src/_zkapauthorizer/_storage_server.py
@@ -14,7 +14,7 @@
 
 """
 A Tahoe-LAFS RIStorageServer-alike which authorizes writes and lease
-updates using a per-call token.
+updates using per-call passes.
 
 This is the server part of a storage access protocol.  The client part is
 implemented in ``_storage_client.py``.
@@ -27,12 +27,12 @@ from __future__ import (
 import attr
 from attr.validators import (
     provides,
+    instance_of,
 )
 
 from zope.interface import (
     implementer_only,
 )
-
 from foolscap.api import (
     Referenceable,
 )
@@ -43,10 +43,37 @@ from foolscap.ipb import (
 from allmydata.interfaces import (
     RIStorageServer,
 )
-
+from privacypass import (
+    TokenPreimage,
+    VerificationSignature,
+    SigningKey,
+)
 from .foolscap import (
     RITokenAuthorizedStorageServer,
 )
+from .storage_common import (
+    BYTES_PER_PASS,
+    required_passes,
+    allocate_buckets_message,
+    add_lease_message,
+    renew_lease_message,
+    slot_testv_and_readv_and_writev_message,
+)
+
+class MorePassesRequired(Exception):
+    def __init__(self, valid_count, required_count):
+        self.valid_count = valid_count
+        self.required_count = required_count
+
+    def __repr__(self):
+        return "MorePassedRequired(valid_count={}, required_count={})".format(
+            self.valid_count,
+            self.required_count,
+        )
+
+    def __str__(self):
+        return repr(self)
+
 
 @implementer_only(RITokenAuthorizedStorageServer, IReferenceable, IRemotelyCallable)
 # It would be great to use `frozen=True` (value-based hashing) instead of
@@ -55,85 +82,130 @@ from .foolscap import (
 @attr.s(cmp=False)
 class ZKAPAuthorizerStorageServer(Referenceable):
     """
-    A class which wraps an ``RIStorageServer`` to insert token validity checks
+    A class which wraps an ``RIStorageServer`` to insert pass validity checks
     before allowing certain functionality.
     """
     _original = attr.ib(validator=provides(RIStorageServer))
+    _signing_key = attr.ib(validator=instance_of(SigningKey))
 
-    def _validate_tokens(self, tokens):
+    def _is_invalid_pass(self, message, pass_):
         """
-        Check that all of the given tokens are valid.
+        Check the validity of a single pass.
 
-        :raise InvalidToken: If any token in ``tokens`` is not valid.
+        :param unicode message: The shared message for pass validation.
+        :param bytes pass_: The encoded pass to validate.
 
-        :return NoneType: If all of the tokens in ``tokens`` are valid.
+        :return bool: ``True`` if the pass is invalid, ``False`` otherwise.
+        """
+        assert isinstance(message, unicode), "message %r not unicode" % (message,)
+        assert isinstance(pass_, bytes), "pass %r not bytes" % (pass_,)
+        try:
+            preimage_base64, signature_base64 = pass_.split(b" ")
+            preimage = TokenPreimage.decode_base64(preimage_base64)
+            proposed_signature = VerificationSignature.decode_base64(signature_base64)
+            unblinded_token = self._signing_key.rederive_unblinded_token(preimage)
+            verification_key = unblinded_token.derive_verification_key_sha512()
+            invalid_pass = verification_key.invalid_sha512(proposed_signature, message.encode("utf-8"))
+            return invalid_pass
+        except Exception:
+            # It would be pretty nice to log something here, sometimes, I guess?
+            return True
+
+    def _validate_passes(self, message, passes):
+        """
+        Check all of the given passes for validity.
+
+        :param unicode message: The shared message for pass validation.
+        :param list[bytes] passes: The encoded passes to validate.
 
-        :note: This is yet to be implemented so it always returns ``None``.
+        :return list[bytes]: The passes which are found to be valid.
         """
-        return None
+        return list(
+            pass_
+            for pass_
+            in passes
+            if not self._is_invalid_pass(message, pass_)
+        )
 
     def remote_get_version(self):
         """
-        Pass through without token check to allow clients to learn about our
+        Pass-through without pass check to allow clients to learn about our
         version and configuration in case it helps them decide how to behave.
         """
         return self._original.remote_get_version()
 
-    def remote_allocate_buckets(self, tokens, *a, **kw):
+    def remote_allocate_buckets(self, passes, storage_index, renew_secret, cancel_secret, sharenums, allocated_size, canary):
         """
-        Pass through after a token check to ensure that clients can only allocate
-        storage for immutable shares if they present valid tokens.
+        Pass-through after a pass check to ensure that clients can only allocate
+        storage for immutable shares if they present valid passes.
         """
-        self._validate_tokens(tokens)
-        return self._original.remote_allocate_buckets(*a, **kw)
+        valid_passes = self._validate_passes(
+            allocate_buckets_message(storage_index),
+            passes,
+        )
+        required_pass_count = required_passes(BYTES_PER_PASS, sharenums, allocated_size)
+        if len(valid_passes) < required_pass_count:
+            raise MorePassesRequired(
+                len(valid_passes),
+                required_pass_count,
+            )
+
+        return self._original.remote_allocate_buckets(
+            storage_index,
+            renew_secret,
+            cancel_secret,
+            sharenums,
+            allocated_size,
+            canary,
+        )
 
     def remote_get_buckets(self, storage_index):
         """
-        Pass through without token check to let clients read immutable shares as
+        Pass-through without pass check to let clients read immutable shares as
         long as those shares exist.
         """
         return self._original.remote_get_buckets(storage_index)
 
-    def remote_add_lease(self, tokens, *a, **kw):
+    def remote_add_lease(self, passes, storage_index, *a, **kw):
         """
-        Pass through after a token check to ensure clients can only extend the
-        duration of share storage if they present valid tokens.
+        Pass-through after a pass check to ensure clients can only extend the
+        duration of share storage if they present valid passes.
         """
-        self._validate_tokens(tokens)
-        return self._original.remote_add_lease(*a, **kw)
+        self._validate_passes(add_lease_message(storage_index), passes)
+        return self._original.remote_add_lease(storage_index, *a, **kw)
 
-    def remote_renew_lease(self, tokens, *a, **kw):
+    def remote_renew_lease(self, passes, storage_index, *a, **kw):
         """
-        Pass through after a token check to ensure clients can only extend the
-        duration of share storage if they present valid tokens.
+        Pass-through after a pass check to ensure clients can only extend the
+        duration of share storage if they present valid passes.
         """
-        self._validate_tokens(tokens)
-        return self._original.remote_renew_lease(*a, **kw)
+        self._validate_passes(renew_lease_message(storage_index), passes)
+        return self._original.remote_renew_lease(storage_index, *a, **kw)
 
     def remote_advise_corrupt_share(self, *a, **kw):
         """
-        Pass through without a token check to let clients inform us of possible
+        Pass-through without a pass check to let clients inform us of possible
         issues with the system without incurring any cost to themselves.
         """
         return self._original.remote_advise_corrupt_share(*a, **kw)
 
     def remote_slot_testv_and_readv_and_writev(
             self,
-            tokens,
+            passes,
             storage_index,
             secrets,
             tw_vectors,
             r_vector,
     ):
         """
-        Pass through after a token check to ensure clients can only allocate
-        storage for mutable shares if they present valid tokens.
+        Pass-through after a pass check to ensure clients can only allocate
+        storage for mutable shares if they present valid passes.
 
         :note: This method can be used both to allocate storage and to rewrite
             data in already-allocated storage.  These cases may not be the
-            same from the perspective of token validation.
+            same from the perspective of pass validation.
         """
-        self._validate_tokens(tokens)
+        self._validate_passes(slot_testv_and_readv_and_writev_message(storage_index), passes)
         # Skip over the remotely exposed method and jump to the underlying
         # implementation which accepts one additional parameter that we know
         # about (and don't expose over the network): renew_leases.  We always
@@ -149,7 +221,7 @@ class ZKAPAuthorizerStorageServer(Referenceable):
 
     def remote_slot_readv(self, *a, **kw):
         """
-        Pass through without a token check to let clients read mutable shares as
+        Pass-through without a pass check to let clients read mutable shares as
         long as those shares exist.
         """
         return self._original.remote_slot_readv(*a, **kw)
diff --git a/src/_zkapauthorizer/api.py b/src/_zkapauthorizer/api.py
index 81f47520ce66ffadb55a41fb3885d1cd50a7947c..8b89611ba10e3ee3833f2d5c7c45d1d8365ee320 100644
--- a/src/_zkapauthorizer/api.py
+++ b/src/_zkapauthorizer/api.py
@@ -13,12 +13,14 @@
 # limitations under the License.
 
 __all__ = [
+    "MorePassesRequired",
     "ZKAPAuthorizerStorageServer",
     "ZKAPAuthorizerStorageClient",
     "ZKAPAuthorizer",
 ]
 
 from ._storage_server import (
+    MorePassesRequired,
     ZKAPAuthorizerStorageServer,
 )
 from ._storage_client import (
diff --git a/src/_zkapauthorizer/storage_common.py b/src/_zkapauthorizer/storage_common.py
new file mode 100644
index 0000000000000000000000000000000000000000..955eb59928c337a4ada4881cf23f92977904dc57
--- /dev/null
+++ b/src/_zkapauthorizer/storage_common.py
@@ -0,0 +1,41 @@
+
+from base64 import (
+    b64encode,
+)
+
+from math import (
+    ceil,
+)
+
+def _message_maker(label):
+    def make_message(storage_index):
+        return u"{label} {storage_index}".format(
+            label=label,
+            storage_index=b64encode(storage_index),
+        )
+    return make_message
+
+allocate_buckets_message = _message_maker(u"allocate_buckets")
+add_lease_message = _message_maker(u"add_lease")
+renew_lease_message = _message_maker(u"renew_lease")
+slot_testv_and_readv_and_writev_message = _message_maker(u"slot_testv_and_readv_and_writev")
+
+# The number of bytes we're willing to store for a lease period for each pass
+# submitted.
+BYTES_PER_PASS = 128 * 1024
+
+def required_passes(bytes_per_pass, share_nums, share_size):
+    """
+    Calculate the number of passes that are required to store ``stored_bytes``
+    for one lease period.
+
+    :param int stored_bytes: A number of bytes of storage for which to
+        calculate a price in passes.
+
+    :return int: The number of passes.
+    """
+    return int(
+        ceil(
+            (len(share_nums) * share_size) / bytes_per_pass,
+        ),
+    )
diff --git a/src/_zkapauthorizer/tests/fixtures.py b/src/_zkapauthorizer/tests/fixtures.py
new file mode 100644
index 0000000000000000000000000000000000000000..20b05922495aefce85e12a69941276836840950a
--- /dev/null
+++ b/src/_zkapauthorizer/tests/fixtures.py
@@ -0,0 +1,34 @@
+from __future__ import (
+    absolute_import,
+)
+
+from fixtures import (
+    Fixture,
+    TempDir,
+)
+
+from twisted.python.filepath import (
+    FilePath,
+)
+
+from allmydata.storage.server import (
+    StorageServer,
+)
+
+class AnonymousStorageServer(Fixture):
+    """
+    Supply an instance of allmydata.storage.server.StorageServer which
+    implements anonymous access to Tahoe-LAFS storage server functionality.
+
+    :ivar FilePath tempdir: The path to the server's storage on the
+        filesystem.
+
+    :ivar allmydata.storage.server.StorageServer storage_server: The storage
+        server.
+    """
+    def _setUp(self):
+        self.tempdir = FilePath(self.useFixture(TempDir()).join(b"storage"))
+        self.storage_server = StorageServer(
+            self.tempdir.asBytesMode().path,
+            b"x" * 20,
+        )
diff --git a/src/_zkapauthorizer/tests/privacypass.py b/src/_zkapauthorizer/tests/privacypass.py
new file mode 100644
index 0000000000000000000000000000000000000000..9b46fe3832bf1ce57a6852037e9bca5c3fb1fb37
--- /dev/null
+++ b/src/_zkapauthorizer/tests/privacypass.py
@@ -0,0 +1,55 @@
+from __future__ import (
+    absolute_import,
+)
+
+from privacypass import (
+    BatchDLEQProof,
+    PublicKey,
+)
+
+def make_passes(signing_key, for_message, random_tokens):
+    blinded_tokens = list(
+        token.blind()
+        for token
+        in random_tokens
+    )
+    signatures = list(
+        signing_key.sign(blinded_token)
+        for blinded_token
+        in blinded_tokens
+    )
+    proof = BatchDLEQProof.create(
+        signing_key,
+        blinded_tokens,
+        signatures,
+    )
+    unblinded_signatures = proof.invalid_or_unblind(
+        random_tokens,
+        blinded_tokens,
+        signatures,
+        PublicKey.from_signing_key(signing_key),
+    )
+    preimages = list(
+        unblinded_signature.preimage()
+        for unblinded_signature
+        in unblinded_signatures
+    )
+    verification_keys = list(
+        unblinded_signature.derive_verification_key_sha512()
+        for unblinded_signature
+        in unblinded_signatures
+    )
+    message_signatures = list(
+        verification_key.sign_sha512(for_message.encode("utf-8"))
+        for verification_key
+        in verification_keys
+    )
+    passes = list(
+        u"{} {}".format(
+            preimage.encode_base64().decode("ascii"),
+            signature.encode_base64().decode("ascii"),
+        ).encode("ascii")
+        for (preimage, signature)
+        in zip(preimages, message_signatures)
+    )
+    return passes
diff --git a/src/_zkapauthorizer/tests/strategies.py b/src/_zkapauthorizer/tests/strategies.py
index 0af3d1f86dc76db4b2e2627eedde8972e85a2338..5a0c531a6448bda2c59d40deeb9aa9e3523be77e 100644
--- a/src/_zkapauthorizer/tests/strategies.py
+++ b/src/_zkapauthorizer/tests/strategies.py
@@ -164,12 +164,16 @@ def node_nicknames():
     )
 
 
-def server_configurations():
+def server_configurations(signing_key_path):
     """
     Build configuration values for the server-side plugin.
+
+    :param unicode signing_key_path: A value to insert for the
+        **ristretto-signing-key-path** item.
     """
     return just({
         u"ristretto-issuer-root-url": u"https://issuer.example.invalid/",
+        u"ristretto-signing-key-path": signing_key_path.path,
     })
 
 
@@ -214,13 +218,10 @@ def zkaps():
     """
     Build random ZKAPs as ``Pass` instances.
     """
-    return binary(
-        min_size=32,
-        max_size=32,
-    ).map(
-        urlsafe_b64encode,
-    ).map(
-        lambda zkap: Pass(zkap.decode("ascii")),
+    return builds(
+        lambda preimage, signature: Pass(u"{} {}".format(preimage, signature)),
+        preimage=binary(min_size=66, max_size=66).map(urlsafe_b64encode),
+        signature=binary(min_size=66, max_size=66).map(urlsafe_b64encode),
     )
 
 
diff --git a/src/_zkapauthorizer/tests/test_base64.py b/src/_zkapauthorizer/tests/test_base64.py
index 05501c6caae6d185a413a9d5876d78c0c5df9322..4a988b6beae887138a8379210f2e76cae0aaab53 100644
--- a/src/_zkapauthorizer/tests/test_base64.py
+++ b/src/_zkapauthorizer/tests/test_base64.py
@@ -16,6 +16,10 @@
 Tests for ``_zkapauthorizer._base64``.
 """
 
+from __future__ import (
+    absolute_import,
+)
+
 from base64 import (
     urlsafe_b64encode,
 )
diff --git a/src/_zkapauthorizer/tests/test_client_resource.py b/src/_zkapauthorizer/tests/test_client_resource.py
index 77b5bb38228a73044f7c08f98d460a024138c8b2..20047d3cf2b986112c386dd99e1cf782d87cb573 100644
--- a/src/_zkapauthorizer/tests/test_client_resource.py
+++ b/src/_zkapauthorizer/tests/test_client_resource.py
@@ -17,6 +17,10 @@ Tests for the web resource provided by the client part of the Tahoe-LAFS
 plugin.
 """
 
+from __future__ import (
+    absolute_import,
+)
+
 import attr
 
 from .._base64 import (
diff --git a/src/_zkapauthorizer/tests/test_controller.py b/src/_zkapauthorizer/tests/test_controller.py
index 4e3f264ce22a05d32535c87888d19d2af93d0147..6001b29a5d0f824d655c6f24d9fa039e70645577 100644
--- a/src/_zkapauthorizer/tests/test_controller.py
+++ b/src/_zkapauthorizer/tests/test_controller.py
@@ -16,6 +16,10 @@
 Tests for ``_zkapauthorizer.controller``.
 """
 
+from __future__ import (
+    absolute_import,
+)
+
 from json import (
     loads,
     dumps,
diff --git a/src/_zkapauthorizer/tests/test_matchers.py b/src/_zkapauthorizer/tests/test_matchers.py
index 7ff71c0fd360daefab6a0485dd62abbf4bc4d187..868a1e998bec4a54c093f24637a5a3f8ecee6261 100644
--- a/src/_zkapauthorizer/tests/test_matchers.py
+++ b/src/_zkapauthorizer/tests/test_matchers.py
@@ -16,6 +16,10 @@
 Tests for ``_zkapauthorizer.tests.matchers``.
 """
 
+from __future__ import (
+    absolute_import,
+)
+
 from zope.interface import (
     Interface,
     implementer,
diff --git a/src/_zkapauthorizer/tests/test_model.py b/src/_zkapauthorizer/tests/test_model.py
index f614c1c912823cec16cf9db27a546c46d27a7b9b..14fd2f0a7339fd264ff6ba5e492f55adad6a8b20 100644
--- a/src/_zkapauthorizer/tests/test_model.py
+++ b/src/_zkapauthorizer/tests/test_model.py
@@ -17,6 +17,10 @@
 Tests for ``_zkapauthorizer.model``.
 """
 
+from __future__ import (
+    absolute_import,
+)
+
 from os import (
     mkdir,
 )
diff --git a/src/_zkapauthorizer/tests/test_plugin.py b/src/_zkapauthorizer/tests/test_plugin.py
index d497c724877505caf6ae95cbc451e429fbb2d384..0257683da1813e697407d9336ffdd4880ad62147 100644
--- a/src/_zkapauthorizer/tests/test_plugin.py
+++ b/src/_zkapauthorizer/tests/test_plugin.py
@@ -16,6 +16,10 @@
 Tests for the Tahoe-LAFS plugin.
 """
 
+from __future__ import (
+    absolute_import,
+)
+
 from io import (
     BytesIO,
 )
@@ -67,6 +71,9 @@ from allmydata.interfaces import (
     RIStorageServer,
 )
 
+from twisted.python.filepath import (
+    FilePath,
+)
 from twisted.plugin import (
     getPlugins,
 )
@@ -103,6 +110,9 @@ from .matchers import (
 )
 
 
+SIGNING_KEY_PATH = FilePath(__file__).sibling(u"testing-signing.key")
+
+
 @implementer(RIStorageServer)
 class StubStorageServer(object):
     pass
@@ -146,7 +156,7 @@ class ServerPluginTests(TestCase):
     Tests for the plugin's implementation of
     ``IFoolscapStoragePlugin.get_storage_server``.
     """
-    @given(server_configurations())
+    @given(server_configurations(SIGNING_KEY_PATH))
     def test_returns_announceable(self, configuration):
         """
         ``storage_server.get_storage_server`` returns an instance which provides
@@ -162,7 +172,7 @@ class ServerPluginTests(TestCase):
         )
 
 
-    @given(server_configurations())
+    @given(server_configurations(SIGNING_KEY_PATH))
     def test_returns_referenceable(self, configuration):
         """
         The storage server attached to the result of
@@ -183,7 +193,7 @@ class ServerPluginTests(TestCase):
             ),
         )
 
-    @given(server_configurations())
+    @given(server_configurations(SIGNING_KEY_PATH))
     def test_returns_serializable(self, configuration):
         """
         The storage server attached to the result of
@@ -207,7 +217,7 @@ class ServerPluginTests(TestCase):
         )
 
 
-    @given(server_configurations())
+    @given(server_configurations(SIGNING_KEY_PATH))
     def test_returns_hashable(self, configuration):
         """
         The storage server attached to the result of
diff --git a/src/_zkapauthorizer/tests/test_storage_protocol.py b/src/_zkapauthorizer/tests/test_storage_protocol.py
index 0bd6cb80835986f16bee3f4eacfa72f635608bec..98320fdd1581c2a537bc505492c597ddceb0c3e8 100644
--- a/src/_zkapauthorizer/tests/test_storage_protocol.py
+++ b/src/_zkapauthorizer/tests/test_storage_protocol.py
@@ -16,11 +16,13 @@
 Tests for communication between the client and server components.
 """
 
+from __future__ import (
+    absolute_import,
+)
+
 import attr
 
 from fixtures import (
-    Fixture,
-    TempDir,
     MonkeyPatch,
 )
 from testtools import (
@@ -62,10 +64,14 @@ from foolscap.referenceable import (
     LocalReferenceable,
 )
 
-from allmydata.storage.server import (
-    StorageServer,
+from privacypass import (
+    RandomToken,
+    random_signing_key,
 )
 
+from .privacypass import (
+    make_passes,
+)
 from .strategies import (
     storage_indexes,
     lease_renew_secrets,
@@ -81,36 +87,20 @@ from .strategies import (
 from .matchers import (
     matches_version_dictionary,
 )
+from .fixtures import (
+    AnonymousStorageServer,
+)
 from ..api import (
     ZKAPAuthorizerStorageServer,
     ZKAPAuthorizerStorageClient,
 )
-from ..foolscap import (
-    TOKEN_LENGTH,
+from ..storage_common import (
+    slot_testv_and_readv_and_writev_message,
 )
 from ..model import (
     Pass,
 )
 
-class AnonymousStorageServer(Fixture):
-    """
-    Supply an instance of allmydata.storage.server.StorageServer which
-    implements anonymous access to Tahoe-LAFS storage server functionality.
-
-    :ivar FilePath tempdir: The path to the server's storage on the
-        filesystem.
-
-    :ivar allmydata.storage.server.StorageServer storage_server: The storage
-        server.
-    """
-    def _setUp(self):
-        self.tempdir = FilePath(self.useFixture(TempDir()).join(b"storage"))
-        self.storage_server = StorageServer(
-            self.tempdir.asBytesMode().path,
-            b"x" * 20,
-        )
-
-
 @attr.s
 class LocalRemote(object):
     """
@@ -159,19 +149,21 @@ class ShareTests(TestCase):
         super(ShareTests, self).setUp()
         self.canary = LocalReferenceable(None)
         self.anonymous_storage_server = self.useFixture(AnonymousStorageServer()).storage_server
+        self.signing_key = random_signing_key()
 
         def get_passes(message, count):
-            if not isinstance(message, bytes):
-                raise TypeError("message must be bytes")
-            try:
-                message.decode("utf-8")
-            except UnicodeDecodeError:
-                raise TypeError("message must be valid utf-8")
-
-            return [Pass(u"x" * TOKEN_LENGTH)] * count
-
+            return list(
+                Pass(pass_.decode("ascii"))
+                for pass_
+                in make_passes(
+                    self.signing_key,
+                    message,
+                    list(RandomToken.create() for n in range(count)),
+                )
+            )
         self.server = ZKAPAuthorizerStorageServer(
             self.anonymous_storage_server,
+            self.signing_key,
         )
         self.local_remote_server = LocalRemote(self.server)
         self.client = ZKAPAuthorizerStorageClient(
@@ -520,7 +512,10 @@ class ShareTests(TestCase):
         d = self.client._rref.callRemote(
             "slot_testv_and_readv_and_writev",
             # passes
-            self.client._get_encoded_passes(storage_index, 1),
+            self.client._get_encoded_passes(
+                slot_testv_and_readv_and_writev_message(storage_index),
+                1,
+            ),
             # storage_index
             storage_index,
             # secrets
diff --git a/src/_zkapauthorizer/tests/test_storage_server.py b/src/_zkapauthorizer/tests/test_storage_server.py
new file mode 100644
index 0000000000000000000000000000000000000000..32ebcc42e78056a39926ee5650b99d74503eeb79
--- /dev/null
+++ b/src/_zkapauthorizer/tests/test_storage_server.py
@@ -0,0 +1,121 @@
+from __future__ import (
+    absolute_import,
+    division,
+)
+
+from random import (
+    shuffle,
+)
+from testtools import (
+    TestCase,
+)
+from testtools.matchers import (
+    Equals,
+    AfterPreprocessing,
+    raises,
+)
+from hypothesis import (
+    given,
+)
+from hypothesis.strategies import (
+    integers,
+    lists,
+)
+from privacypass import (
+    RandomToken,
+    random_signing_key,
+)
+from foolscap.referenceable import (
+    LocalReferenceable,
+)
+
+from .privacypass import (
+    make_passes,
+)
+from .strategies import (
+    zkaps,
+)
+from .fixtures import (
+    AnonymousStorageServer,
+)
+from ..api import (
+    ZKAPAuthorizerStorageServer,
+    MorePassesRequired,
+)
+from ..storage_common import (
+    BYTES_PER_PASS,
+    allocate_buckets_message,
+)
+
+
+class PassValidationTests(TestCase):
+    """
+    Tests for pass validation performed by ``ZKAPAuthorizerStorageServer``.
+    """
+    def setUp(self):
+        super(PassValidationTests, self).setUp()
+        self.anonymous_storage_server = self.useFixture(AnonymousStorageServer()).storage_server
+        self.signing_key = random_signing_key()
+        self.storage_server = ZKAPAuthorizerStorageServer(
+            self.anonymous_storage_server,
+            self.signing_key,
+        )
+
+    @given(integers(min_value=0, max_value=64), lists(zkaps(), max_size=64))
+    def test_validation_result(self, valid_count, invalid_passes):
+        """
+        ``_get_valid_passes`` returns the number of cryptographically valid passes
+        in the list passed to it.
+        """
+        message = u"hello world"
+        valid_passes = make_passes(
+            self.signing_key,
+            message,
+            list(RandomToken.create() for i in range(valid_count)),
+        )
+        all_passes = valid_passes + list(pass_.text.encode("ascii") for pass_ in invalid_passes)
+        shuffle(all_passes)
+
+        self.assertThat(
+            self.storage_server._validate_passes(message, all_passes),
+            AfterPreprocessing(
+                set,
+                Equals(set(valid_passes)),
+            ),
+        )
+
+
+    def test_allocate_buckets_fails_without_enough_passes(self):
+        """
+        ``remote_allocate_buckets`` fails with ``MorePassesRequired`` if it is
+        passed fewer passes than it requires for the amount of data to be
+        stored.
+        """
+        required_passes = 2
+        share_nums = {3, 7}
+        allocated_size = int((required_passes * BYTES_PER_PASS) / len(share_nums))
+        storage_index = b"0123456789"
+        renew_secret = b"x" * 32
+        cancel_secret = b"y" * 32
+        valid_passes = make_passes(
+            self.signing_key,
+            allocate_buckets_message(storage_index),
+            list(RandomToken.create() for i in range(required_passes - 1)),
+        )
+
+        allocate_buckets = lambda: self.storage_server.doRemoteCall(
+            "allocate_buckets",
+            (valid_passes,
+             storage_index,
+             renew_secret,
+             cancel_secret,
+             share_nums,
+             allocated_size,
+             LocalReferenceable(None),
+            ),
+            {},
+        )
+        self.assertThat(
+            allocate_buckets,
+            raises(MorePassesRequired),
+        )
diff --git a/src/_zkapauthorizer/tests/test_strategies.py b/src/_zkapauthorizer/tests/test_strategies.py
index 22f5aacfa67f0a06f886122f4d30856b718b8eac..6a0307ea4b799204dba2c4f297473a0d52619215 100644
--- a/src/_zkapauthorizer/tests/test_strategies.py
+++ b/src/_zkapauthorizer/tests/test_strategies.py
@@ -16,6 +16,10 @@
 Tests for our custom Hypothesis strategies.
 """
 
+from __future__ import (
+    absolute_import,
+)
+
 from testtools import (
     TestCase,
 )
diff --git a/src/_zkapauthorizer/tests/testing-signing.key b/src/_zkapauthorizer/tests/testing-signing.key
new file mode 100644
index 0000000000000000000000000000000000000000..078e2134bd719832938b965b0288e4246c364d2d
--- /dev/null
+++ b/src/_zkapauthorizer/tests/testing-signing.key
@@ -0,0 +1 @@
+mkQf85V2vyLQRUYuqRb+Ke6K+M9pOtXm4MslsuCdBgg=
\ No newline at end of file