diff --git a/src/_zkapauthorizer/tests/storage_common.py b/src/_zkapauthorizer/tests/storage_common.py
index d00a580c29adf51f1d39583012fbe09b11555678..c882a4917a92f6954b00bfb4d64b1c7acfcf3f56 100644
--- a/src/_zkapauthorizer/tests/storage_common.py
+++ b/src/_zkapauthorizer/tests/storage_common.py
@@ -16,6 +16,10 @@
 ``allmydata.storage``-related helpers shared across the test suite.
 """
 
+from functools import (
+    partial,
+)
+
 from os import (
     SEEK_CUR,
 )
@@ -23,15 +27,34 @@ from struct import (
     pack,
 )
 
+from itertools import (
+    count,
+    islice,
+)
+
+import attr
+
 from twisted.python.filepath import (
     FilePath,
 )
 
+from challenge_bypass_ristretto import (
+    RandomToken,
+)
+
 from .strategies import (
     # Not really a strategy...
     bytes_for_share,
 )
 
+from .privacypass import (
+    make_passes,
+)
+
+from ..model import (
+    Pass,
+)
+
 # Hard-coded in Tahoe-LAFS
 LEASE_INTERVAL = 60 * 60 * 24 * 31
 
@@ -133,3 +156,85 @@ def whitebox_write_sparse_share(sharepath, version, size, leases, now):
                 in leases
             ),
         )
+
+
+def integer_passes():
+    """
+    :return: Return a function which can be used to get a number of passes.
+        The function accepts a unicode request-binding message and an integer
+        number of passes.  It returns a list of integers which serve as passes.
+        Successive calls to the function return unique pass values.
+    """
+    counter = count(0)
+    def get_passes(message, num_passes):
+        return list(islice(counter, num_passes))
+    return get_passes
+
+
+def get_passes(message, count, signing_key):
+    """
+    :param unicode message: Request-binding message for PrivacyPass.
+
+    :param int count: The number of passes to get.
+
+    :param SigningKEy signing_key: The key to use to sign the passes.
+
+    :return list[Pass]: ``count`` new random passes signed with the given key
+        and bound to the given message.
+    """
+    return list(
+        Pass(*pass_.split(u" "))
+        for pass_
+        in make_passes(
+            signing_key,
+            message,
+            list(RandomToken.create() for n in range(count)),
+        )
+    )
+
+
+def privacypass_passes(signing_key):
+    """
+    Get a PrivacyPass issuing function.
+
+    :param SigningKey signing_key: The key to use to issue passes.
+
+    :return: Return a function which can be used to get a number of passes.
+        The function accepts a unicode request-binding message and an integer
+        number of passes.  It returns a list of real pass values signed by the
+        given key.  Successive calls to the function return unique passes.
+    """
+    return partial(get_passes, signing_key=signing_key)
+
+
+def pass_factory(get_passes=None):
+    """
+    Get a new factory for passes.
+
+    :param (unicode -> int -> [pass]) get_passes: A function the factory can
+        use to get new passes.
+    """
+    if get_passes is None:
+        get_passes = integer_passes()
+    return _PassFactory(get_passes=get_passes)
+
+
+@attr.s
+class _PassFactory(object):
+    """
+    A stateful pass issuer.
+
+    :ivar (unicode -> int -> [bytes]) _get_passes: A function for getting
+        passes.
+
+    :ivar set[int] issued: All of the passes ever given out.
+
+    """
+    _get_passes = attr.ib()
+    issued = attr.ib(default=attr.Factory(set), init=False)
+
+    def get(self, message, num_passes):
+        passes = []
+        passes.extend(self._get_passes(message, num_passes))
+        self.issued.update(passes)
+        return passes
diff --git a/src/_zkapauthorizer/tests/test_storage_protocol.py b/src/_zkapauthorizer/tests/test_storage_protocol.py
index bb79fb25e919110d0454595fe55cb81203dfbac9..4ddb289408f50e042cdb67bb40e8c8b064cc3ce2 100644
--- a/src/_zkapauthorizer/tests/test_storage_protocol.py
+++ b/src/_zkapauthorizer/tests/test_storage_protocol.py
@@ -67,7 +67,6 @@ from foolscap.referenceable import (
 )
 
 from challenge_bypass_ristretto import (
-    RandomToken,
     random_signing_key,
 )
 
@@ -79,9 +78,6 @@ from .common import (
     skipIf,
 )
 
-from .privacypass import (
-    make_passes,
-)
 from .strategies import (
     storage_indexes,
     lease_renew_secrets,
@@ -107,6 +103,9 @@ from .storage_common import (
     cleanup_storage_server,
     write_toy_shares,
     whitebox_write_sparse_share,
+    get_passes,
+    privacypass_passes,
+    pass_factory,
 )
 from .foolscap import (
     LocalRemote,
@@ -122,9 +121,6 @@ from ..storage_common import (
     get_implied_data_length,
     required_passes,
 )
-from ..model import (
-    Pass,
-)
 from ..foolscap import (
     ShareStat,
 )
@@ -167,36 +163,12 @@ class RequiredPassesTests(TestCase):
         )
 
 
-def get_passes(message, count, signing_key):
-    """
-    :param unicode message: Request-binding message for PrivacyPass.
-
-    :param int count: The number of passes to get.
-
-    :param SigningKEy signing_key: The key to use to sign the passes.
-
-    :return list[Pass]: ``count`` new random passes signed with the given key
-        and bound to the given message.
-    """
-    return list(
-        Pass(*pass_.split(u" "))
-        for pass_
-        in make_passes(
-            signing_key,
-            message,
-            list(RandomToken.create() for n in range(count)),
-        )
-    )
-
-
 class ShareTests(TestCase):
     """
     Tests for interaction with shares.
 
-    :ivar int spent_passes: The number of passes which have been spent so far
-        in the course of a single test (in the case of Hypothesis, every
-        iteration of the test so far, probably; so make relative comparisons
-        instead of absolute ones).
+    :ivar pass_factory: An object which is responsible for creating passes
+        which are used by these tests.
     """
     pass_value = 128 * 1024
 
@@ -205,11 +177,8 @@ class ShareTests(TestCase):
         self.canary = LocalReferenceable(None)
         self.anonymous_storage_server = self.useFixture(AnonymousStorageServer()).storage_server
         self.signing_key = random_signing_key()
-        self.spent_passes = 0
 
-        def counting_get_passes(message, count):
-            self.spent_passes += count
-            return get_passes(message, count, self.signing_key)
+        self.pass_factory = pass_factory(get_passes=privacypass_passes(self.signing_key))
 
         self.server = ZKAPAuthorizerStorageServer(
             self.anonymous_storage_server,
@@ -220,7 +189,7 @@ class ShareTests(TestCase):
         self.client = ZKAPAuthorizerStorageClient(
             self.pass_value,
             get_rref=lambda: self.local_remote_server,
-            get_passes=counting_get_passes,
+            get_passes=self.pass_factory.get,
         )
 
     def test_get_version(self):
@@ -826,12 +795,12 @@ class ShareTests(TestCase):
             u"Server gave back read results when we asked for none.",
         )
         # Now we can read it back without spending any more passes.
-        before_spent_passes = self.spent_passes
+        before_passes = len(self.pass_factory.issued)
         assert_read_back_data(self, storage_index, secrets, test_and_write_vectors_for_shares)
-        after_spent_passes = self.spent_passes
+        after_passes = len(self.pass_factory.issued)
         self.assertThat(
-            before_spent_passes,
-            Equals(after_spent_passes),
+            before_passes,
+            Equals(after_passes),
         )
 
     @given(