diff --git a/src/_zkapauthorizer/model.py b/src/_zkapauthorizer/model.py
index 8691fface49804e256e2a003930cb9035c155da7..3cb8204f300cc894b3d40ff1d80aed4b0d0e2d0b 100644
--- a/src/_zkapauthorizer/model.py
+++ b/src/_zkapauthorizer/model.py
@@ -94,6 +94,13 @@ class StoreOpenError(Exception):
         self.reason = reason
 
 
+class NotEnoughTokens(Exception):
+    """
+    An attempt to extract tokens failed because the store does not contain as
+    many tokens as were requested.
+    """
+
+
 CONFIG_DB_NAME = u"privatestorageio-zkapauthz-v1.sqlite3"
 
 def open_and_initialize(path, connect=None):
@@ -396,6 +403,16 @@ class VoucherStore(object):
 
         :return list[UnblindedTokens]: The removed unblinded tokens.
         """
+        cursor.execute(
+            """
+            SELECT COUNT(token)
+            FROM [unblinded-tokens]
+            """,
+        )
+        [(existing_tokens,)] = cursor.fetchall()
+        if existing_tokens < count:
+            raise NotEnoughTokens()
+
         cursor.execute(
             """
             CREATE TEMPORARY TABLE [extracting]
diff --git a/src/_zkapauthorizer/tests/test_model.py b/src/_zkapauthorizer/tests/test_model.py
index c4885ed96367e4227a46dc080e2f66df1895d780..5975f597da9be24bcbd9d9f8fcf153daa464cfb1 100644
--- a/src/_zkapauthorizer/tests/test_model.py
+++ b/src/_zkapauthorizer/tests/test_model.py
@@ -75,6 +75,7 @@ from ..storage_common import (
 
 from ..model import (
     StoreOpenError,
+    NotEnoughTokens,
     VoucherStore,
     Voucher,
     Pending,
@@ -395,8 +396,14 @@ class UnblindedTokenStoreTests(TestCase):
         self.expectThat(unblinded_tokens, AfterPreprocessing(sorted, Equals(retrieved_tokens)))
 
         # After extraction, the unblinded tokens are no longer available.
-        more_unblinded_tokens = store.extract_unblinded_tokens(1)
-        self.expectThat([], Equals(more_unblinded_tokens))
+        try:
+            result = store.extract_unblinded_tokens(1)
+        except NotEnoughTokens:
+            pass
+        except Exception as e:
+            self.fail("extract_unblinded_tokens raised wrong exception: {}".format(e))
+        else:
+            self.fail("extract_unblinded_tokens didn't raise, returned: {}".format(result))
 
     @given(
         tahoe_configs(),
@@ -526,6 +533,49 @@ class UnblindedTokenStoreTests(TestCase):
         else:
             self.fail("mark_voucher_double_spent didn't raise, returned: {}".format(result))
 
+    @given(
+        tahoe_configs(),
+        datetimes(),
+        vouchers(),
+        dummy_ristretto_keys(),
+        integers(min_value=1, max_value=100),
+        integers(min_value=1),
+        data(),
+    )
+    def test_not_enough_unblinded_tokens(self, get_config, now, voucher_value, public_key, num_tokens, extra, data):
+        """
+        ``extract_unblinded_tokens`` raises ``NotEnoughTokens`` if ``count`` is
+        greater than the number of unblinded tokens in the store.
+        """
+        random = data.draw(
+            lists(
+                random_tokens(),
+                min_size=num_tokens,
+                max_size=num_tokens,
+                unique=True,
+            ),
+        )
+        unblinded = data.draw(
+            lists(
+                unblinded_tokens(),
+                min_size=num_tokens,
+                max_size=num_tokens,
+                unique=True,
+            ),
+        )
+        store = self.useFixture(TemporaryVoucherStore(get_config, lambda: now)).store
+        store.add(voucher_value, lambda: random)
+        store.insert_unblinded_tokens_for_voucher(voucher_value, public_key, unblinded)
+
+        try:
+            result = store.extract_unblinded_tokens(num_tokens + extra)
+        except NotEnoughTokens:
+            pass
+        except Exception as e:
+            self.fail("extract_unblinded_tokens raised wrong exception: {}".format(e))
+        else:
+            self.fail("extract_unblinded_tokens didn't raise, returned: {}".format(result))
+
 
     # TODO: Other error states and transient states
 
diff --git a/src/_zkapauthorizer/tests/test_plugin.py b/src/_zkapauthorizer/tests/test_plugin.py
index 6fb6a13963c7f4ca92ae36f4e0d91266da5f91ab..d98408cd622c40a927d36ad7e18ddd5a6e7750e5 100644
--- a/src/_zkapauthorizer/tests/test_plugin.py
+++ b/src/_zkapauthorizer/tests/test_plugin.py
@@ -42,7 +42,6 @@ from testtools.matchers import (
     Always,
     Contains,
     AfterPreprocessing,
-    Equals,
 )
 from testtools.twistedsupport import (
     succeeded,
@@ -100,6 +99,7 @@ from ..foolscap import (
     RIPrivacyPassAuthorizedStorageServer,
 )
 from ..model import (
+    NotEnoughTokens,
     VoucherStore,
 )
 from ..controller import (
@@ -107,6 +107,10 @@ from ..controller import (
     PaymentController,
     DummyRedeemer,
 )
+from ..storage_common import (
+    BYTES_PER_PASS,
+    required_passes,
+)
 from .._storage_client import (
     IncorrectStorageServerReference,
 )
@@ -418,7 +422,8 @@ class ClientPluginTests(TestCase):
         controller = PaymentController(
             store,
             DummyRedeemer(),
-            1,
+            # Give it enough for the allocate_buckets call below.
+            required_passes(BYTES_PER_PASS, [size] * len(sharenums)),
         )
         # Get a token inserted into the store.
         redeeming = controller.redeem(voucher)
@@ -445,11 +450,14 @@ class ClientPluginTests(TestCase):
         )
 
         # There should be no unblinded tokens left to extract.
-        remaining = store.extract_unblinded_tokens(1)
-        self.assertThat(
-            remaining,
-            Equals([]),
-        )
+        try:
+            result = store.extract_unblinded_tokens(1)
+        except NotEnoughTokens:
+            pass
+        except Exception as e:
+            self.fail("extract_unblinded_tokens raised wrong exception: {}".format(e))
+        else:
+            self.fail("extract_unblinded_tokens didn't raise, returned: {}".format(result))
 
 
 class ClientResourceTests(TestCase):