diff --git a/src/_zkapauthorizer/_plugin.py b/src/_zkapauthorizer/_plugin.py
index 8b16a7751eec14dc61e1751101839724440ea346..1584a19dc169c70a2ed4a53f8b2d09aafa58ad53 100644
--- a/src/_zkapauthorizer/_plugin.py
+++ b/src/_zkapauthorizer/_plugin.py
@@ -119,9 +119,8 @@ class ZKAPAuthorizer(object):
         """
         return ZKAPAuthorizerStorageClient(
             get_rref,
-            # TODO: Make the caller figure out the correct number of
-            # passes to extract.
-            partial(self._get_store(node_config).extract_passes, 1),
+            # TODO: Make the caller figure out the correct number to extract.
+            partial(self._get_store(node_config).extract_unblinded_tokens, 1),
         )
 
 
diff --git a/src/_zkapauthorizer/controller.py b/src/_zkapauthorizer/controller.py
index 285e721b2542180eeeb64432b6b9220e59a26975..844a06ed8ec681514de688be710fc08c0a801978 100644
--- a/src/_zkapauthorizer/controller.py
+++ b/src/_zkapauthorizer/controller.py
@@ -45,13 +45,10 @@ from treq import (
 
 import privacypass
 
-from .foolscap import (
-    TOKEN_LENGTH,
-)
 from .model import (
     RandomToken,
+    UnblindedToken,
     Voucher,
-    Pass,
 )
 
 
@@ -86,7 +83,8 @@ class IRedeemer(Interface):
 
     def redeem(voucher, random_tokens):
         """
-        Redeem a voucher for passes.
+        Redeem a voucher for unblinded tokens which can be used to construct
+        passes.
 
         Implementations of this method do not need to be fault tolerant.  If a
         redemption attempt is interrupted before it completes, it is the
@@ -98,8 +96,8 @@ class IRedeemer(Interface):
         :param list[RandomToken] random_tokens: The random tokens to use in
             the redemption process.
 
-        :return: A ``Deferred`` which fires with a list of ``Pass`` instances
-            on successful redemption or which fails with
+        :return: A ``Deferred`` which fires with a list of ``UnblindedToken``
+            instances on successful redemption or which fails with
             ``TransientRedemptionError`` on any error which may be resolved by
             simply trying again later or which fails with
             ``PermanentRedemptionError`` on any error which is definitive and
@@ -147,12 +145,12 @@ class DummyRedeemer(object):
 
     def redeem(self, voucher, random_tokens):
         """
-        :return: An already-fired ``Deferred`` that has a list of ``Pass``
-            instances wrapping meaningless values.
+        :return: An already-fired ``Deferred`` that has a list of
+          ``UnblindedToken`` instances wrapping meaningless values.
         """
         return succeed(
             list(
-                Pass((u"pass-" + token.token_value).zfill(TOKEN_LENGTH))
+                UnblindedToken(token.token_value)
                 for token
                 in random_tokens
             ),
@@ -216,8 +214,8 @@ class RistrettoRedeemer(object):
             public_key,
         )
         returnValue(list(
-            Pass(text=unblinded_token.encode_base64().decode("ascii"))
-            for unblinded_token
+            UnblindedToken(token.encode_base64().decode("ascii"))
+            for token
             in clients_unblinded_tokens
         ))
 
@@ -267,8 +265,10 @@ class PaymentController(object):
       3. The controller hands the voucher and some random tokens to a redeemer.
          In the future, this step will need to be retried in the case of failures.
 
-      4. When the voucher has been redeemed for passes, the controller hands them to the data store with the voucher.
-        The data store marks the voucher as redeemed and stores the passes for use by the storage client.
+      4. When the voucher has been redeemed for unblinded tokens (inputs to
+         pass construction), the controller hands them to the data store with
+         the voucher.  The data store marks the voucher as redeemed and stores
+         the unblinded tokens for use by the storage client.
     """
     store = attr.ib()
     redeemer = attr.ib()
@@ -297,9 +297,10 @@ class PaymentController(object):
             partial(self._redeemSuccess, voucher),
         )
 
-    def _redeemSuccess(self, voucher, passes):
+    def _redeemSuccess(self, voucher, unblinded_tokens):
         """
         Update the database state to reflect that a voucher was redeemed and to
-        store the resulting passes.
+        store the resulting unblinded tokens (which can be used to construct
+        passes later).
         """
-        self.store.insert_passes_for_voucher(voucher, passes)
+        self.store.insert_unblinded_tokens_for_voucher(voucher, unblinded_tokens)
diff --git a/src/_zkapauthorizer/model.py b/src/_zkapauthorizer/model.py
index c2e56063489167c5e41ace8f3526751213e0a4ef..76a659da860617fd26dc39afaebe3b25be8cad3a 100644
--- a/src/_zkapauthorizer/model.py
+++ b/src/_zkapauthorizer/model.py
@@ -136,10 +136,10 @@ def open_and_initialize(path, required_schema_version, connect=None):
         )
         cursor.execute(
             """
-            CREATE TABLE IF NOT EXISTS [passes] (
-                [text] text, -- The string that defines the pass.
+            CREATE TABLE IF NOT EXISTS [unblinded-tokens] (
+                [token] text, -- The base64 encoded unblinded token.
 
-                PRIMARY KEY([text])
+                PRIMARY KEY([token])
             )
             """,
         )
@@ -271,21 +271,26 @@ class VoucherStore(object):
         )
 
     @with_cursor
-    def insert_passes_for_voucher(self, cursor, voucher, passes):
+    def insert_unblinded_tokens_for_voucher(self, cursor, voucher, unblinded_tokens):
         """
-        Store some passes.
+        Store some unblinded tokens.
 
-        :param unicode voucher: The voucher associated with the passes.  This
-            voucher will be marked as redeemed to indicate it has fulfilled
-            its purpose and has no further use for us.
+        :param unicode voucher: The voucher associated with the unblinded
+            tokens.  This voucher will be marked as redeemed to indicate it
+            has fulfilled its purpose and has no further use for us.
 
-        :param list[Pass] passes: The passes to store.
+        :param list[UnblindedToken] unblinded_tokens: The unblinded tokens to
+            store.
         """
         cursor.executemany(
             """
-            INSERT INTO [passes] VALUES (?)
+            INSERT INTO [unblinded-tokens] VALUES (?)
             """,
-            list((p.text,) for p in passes),
+            list(
+                (t.text,)
+                for t
+                in unblinded_tokens
+            ),
         )
         cursor.execute(
             """
@@ -295,47 +300,63 @@ class VoucherStore(object):
         )
 
     @with_cursor
-    def extract_passes(self, cursor, count):
+    def extract_unblinded_tokens(self, cursor, count):
         """
-        Remove and return some passes.
+        Remove and return some unblinded tokens.
 
-        :param int count: The maximum number of passes to remove and return.
-            If fewer passes than this are available, only as many as are
+        :param int count: The maximum number of unblinded tokens to remove and
+            return.  If fewer than this are available, only as many as are
             available are returned.
 
-        :return list[Pass]: The removed passes.
+        :return list[UnblindedTokens]: The removed unblinded tokens.
         """
         cursor.execute(
             """
-            CREATE TEMPORARY TABLE [extracting-passes]
+            CREATE TEMPORARY TABLE [extracting]
             AS
-            SELECT [text] FROM [passes] LIMIT ?
+            SELECT [token] FROM [unblinded-tokens] LIMIT ?
             """,
             (count,),
         )
         cursor.execute(
             """
-            DELETE FROM [passes] WHERE [text] IN [extracting-passes]
+            DELETE FROM [unblinded-tokens] WHERE [token] IN [extracting]
             """,
         )
         cursor.execute(
             """
-            SELECT [text] FROM [extracting-passes]
+            SELECT [token] FROM [extracting]
             """,
         )
         texts = cursor.fetchall()
         cursor.execute(
             """
-            DROP TABLE [extracting-passes]
+            DROP TABLE [extracting]
             """,
         )
         return list(
-            Pass(t)
+            UnblindedToken(t)
             for (t,)
             in texts
         )
 
 
+@attr.s(frozen=True)
+class UnblindedToken(object):
+    """
+    An ``UnblindedToken`` instance represents cryptographic proof of a voucher
+    redemption.  It is an intermediate artifact in the PrivacyPass protocol
+    and can be used to construct a privacy-preserving pass which can be
+    exchanged for service.
+
+    :ivar unicode text: The base64 encoded serialized form of the unblinded
+        token.  This can be used to reconstruct a
+        ``privacypass.UnblindedToken`` using that class's ``decode_base64``
+        method.
+    """
+    text = attr.ib(validator=attr.validators.instance_of(unicode))
+
+
 @attr.s(frozen=True)
 class Pass(object):
     """
diff --git a/src/_zkapauthorizer/tests/strategies.py b/src/_zkapauthorizer/tests/strategies.py
index 8f6e74ed6c4a71cff35f75630b7b26fb3232c543..f35371cc1a4f904ddaeed4bfc71293a3930e8688 100644
--- a/src/_zkapauthorizer/tests/strategies.py
+++ b/src/_zkapauthorizer/tests/strategies.py
@@ -55,6 +55,7 @@ from allmydata.client import (
 from ..model import (
     Pass,
     RandomToken,
+    UnblindedToken
 )
 
 
@@ -208,6 +209,22 @@ def zkaps():
     )
 
 
+def unblinded_tokens():
+    """
+    Builds random ``_zkapauthorizer.model.UnblindedToken`` wrapping invalid
+    base64 encode data.  You cannot use these in the PrivacyPass cryptographic
+    protocol but you can put them into the database and take them out again.
+    """
+    return binary(
+        min_size=32,
+        max_size=32,
+    ).map(
+        urlsafe_b64encode,
+    ).map(
+        lambda zkap: UnblindedToken(zkap.decode("ascii")),
+    )
+
+
 def request_paths():
     """
     Build lists of unicode strings that represent the path component of an
diff --git a/src/_zkapauthorizer/tests/test_controller.py b/src/_zkapauthorizer/tests/test_controller.py
index 636a7e33cb9ddf77e660e4f89004d4845bfdc96a..f30eeafe409232efa7da74433b49466e4870a3b0 100644
--- a/src/_zkapauthorizer/tests/test_controller.py
+++ b/src/_zkapauthorizer/tests/test_controller.py
@@ -23,7 +23,6 @@ from json import (
 from zope.interface import (
     implementer,
 )
-import attr
 from testtools import (
     TestCase,
 )
@@ -58,11 +57,9 @@ from twisted.python.url import (
 )
 from twisted.internet.defer import (
     fail,
-    succeed,
 )
 from twisted.web.iweb import (
     IAgent,
-    IBodyProducer,
 )
 from twisted.web.resource import (
     Resource,
@@ -91,7 +88,7 @@ from ..model import (
     memory_connect,
     VoucherStore,
     Voucher,
-    Pass,
+    UnblindedToken,
 )
 
 from .strategies import (
@@ -176,7 +173,7 @@ class RistrettoRedeemerTests(TestCase):
         """
         If the issuer returns a successful result then
         ``RistrettoRedeemer.redeem`` returns a ``Deferred`` that fires with a
-        list of ``Pass`` instances.
+        list of ``UnblindedToken`` instances.
         """
         signing_key = random_signing_key()
         issuer = RistrettoRedemption(signing_key)
@@ -192,7 +189,7 @@ class RistrettoRedeemerTests(TestCase):
             succeeded(
                 MatchesAll(
                     AllMatch(
-                        IsInstance(Pass),
+                        IsInstance(UnblindedToken),
                     ),
                     HasLength(num_tokens),
                 ),
@@ -202,9 +199,9 @@ class RistrettoRedeemerTests(TestCase):
     @given(vouchers().map(Voucher), integers(min_value=1, max_value=100))
     def test_bad_ristretto_redemption(self, voucher, num_tokens):
         """
-        If the issuer returns a successful result then
+        If the issuer returns a successful result with an invalid proof then
         ``RistrettoRedeemer.redeem`` returns a ``Deferred`` that fires with a
-        list of ``Pass`` instances.
+        ``Failure`` wrapping ``SecurityException``.
         """
         signing_key = random_signing_key()
         issuer = RistrettoRedemption(signing_key)
diff --git a/src/_zkapauthorizer/tests/test_model.py b/src/_zkapauthorizer/tests/test_model.py
index 1a6ac3cf944dc53b3b6ad554ea85addd170b9aaa..f614c1c912823cec16cf9db27a546c46d27a7b9b 100644
--- a/src/_zkapauthorizer/tests/test_model.py
+++ b/src/_zkapauthorizer/tests/test_model.py
@@ -65,7 +65,7 @@ from .strategies import (
     tahoe_configs,
     vouchers,
     random_tokens,
-    zkaps,
+    unblinded_tokens,
 )
 
 
@@ -255,14 +255,14 @@ class VoucherTests(TestCase):
         )
 
 
-class ZKAPStoreTests(TestCase):
+class UnblindedTokenStoreTests(TestCase):
     """
-    Tests for ZKAP-related functionality of ``VoucherStore``.
+    Tests for ``UnblindedToken``-related functionality of ``VoucherStore``.
     """
-    @given(tahoe_configs(), vouchers(), lists(zkaps(), unique=True))
-    def test_zkaps_round_trip(self, get_config, voucher_value, passes):
+    @given(tahoe_configs(), vouchers(), lists(unblinded_tokens(), unique=True))
+    def test_unblinded_tokens_round_trip(self, get_config, voucher_value, tokens):
         """
-        ZKAPs that are added to the store can later be retrieved.
+        Unblinded tokens that are added to the store can later be retrieved.
         """
         tempdir = self.useFixture(TempDir())
         config = get_config(tempdir.join(b"node"), b"tub.port")
@@ -270,18 +270,19 @@ class ZKAPStoreTests(TestCase):
             config,
             memory_connect,
         )
-        store.insert_passes_for_voucher(voucher_value, passes)
-        retrieved_passes = store.extract_passes(len(passes))
-        self.expectThat(passes, Equals(retrieved_passes))
+        store.insert_unblinded_tokens_for_voucher(voucher_value, tokens)
+        retrieved_tokens = store.extract_unblinded_tokens(len(tokens))
+        self.expectThat(tokens, Equals(retrieved_tokens))
 
-        # After extraction, the passes are no longer available.
-        more_passes = store.extract_passes(1)
-        self.expectThat([], Equals(more_passes))
+        # After extraction, the unblinded tokens are no longer available.
+        more_unblinded_tokens = store.extract_unblinded_tokens(1)
+        self.expectThat([], Equals(more_unblinded_tokens))
 
-    @given(tahoe_configs(), vouchers(), random_tokens(), zkaps())
-    def test_mark_vouchers_redeemed(self, get_config, voucher_value, token, one_pass):
+    @given(tahoe_configs(), vouchers(), random_tokens(), unblinded_tokens())
+    def test_mark_vouchers_redeemed(self, get_config, voucher_value, token, one_token):
         """
-        The voucher for ZKAPs that are added to the store are marked as redeemed.
+        The voucher for unblinded tokens that are added to the store is marked as
+        redeemed.
         """
         tempdir = self.useFixture(TempDir())
         config = get_config(tempdir.join(b"node"), b"tub.port")
@@ -290,6 +291,6 @@ class ZKAPStoreTests(TestCase):
             memory_connect,
         )
         store.add(voucher_value, [token])
-        store.insert_passes_for_voucher(voucher_value, [one_pass])
+        store.insert_unblinded_tokens_for_voucher(voucher_value, [one_token])
         loaded_voucher = store.get(voucher_value)
         self.assertThat(loaded_voucher.redeemed, Equals(True))
diff --git a/src/_zkapauthorizer/tests/test_plugin.py b/src/_zkapauthorizer/tests/test_plugin.py
index 2587904e68bf78928d8b4344a653de7db4d4a4cd..8c0f648d77f7bf80c5fab8e5fe72d498faf9a1dd 100644
--- a/src/_zkapauthorizer/tests/test_plugin.py
+++ b/src/_zkapauthorizer/tests/test_plugin.py
@@ -82,7 +82,7 @@ from .strategies import (
     announcements,
     vouchers,
     random_tokens,
-    zkaps,
+    unblinded_tokens,
     storage_indexes,
     lease_renew_secrets,
 )
@@ -256,23 +256,23 @@ class ClientPluginTests(TestCase):
         announcements(),
         vouchers(),
         random_tokens(),
-        zkaps(),
+        unblinded_tokens(),
         storage_indexes(),
         lease_renew_secrets(),
     )
-    def test_passes_extracted(
+    def test_unblinded_tokens_extracted(
             self,
             get_config,
             announcement,
             voucher,
             token,
-            zkap,
+            unblinded_token,
             storage_index,
             renew_secret,
     ):
         """
         The ``ZKAPAuthorizerStorageServer`` returned by ``get_storage_client``
-        extracts passes from the plugin database.
+        extracts unblinded tokens from the plugin database.
         """
         tempdir = self.useFixture(TempDir())
         node_config = get_config(
@@ -282,7 +282,7 @@ class ClientPluginTests(TestCase):
 
         store = VoucherStore.from_node_config(node_config)
         store.add(voucher, [token])
-        store.insert_passes_for_voucher(voucher, [zkap])
+        store.insert_unblinded_tokens_for_voucher(voucher, [unblinded_token])
 
         storage_client = storage_server.get_storage_client(
             node_config,
@@ -298,8 +298,8 @@ class ClientPluginTests(TestCase):
         )
         d.addBoth(lambda ignored: None)
 
-        # There should be no passes left to extract.
-        remaining = store.extract_passes(1)
+        # There should be no unblinded tokens left to extract.
+        remaining = store.extract_unblinded_tokens(1)
         self.assertThat(
             remaining,
             Equals([]),