diff --git a/src/_zkapauthorizer/_plugin.py b/src/_zkapauthorizer/_plugin.py
index 4ba84ddd840e6a56e7cf54fd0ed6ae8bc3449f40..a1935a562ad674d8895951d19d2881977357c192 100644
--- a/src/_zkapauthorizer/_plugin.py
+++ b/src/_zkapauthorizer/_plugin.py
@@ -175,12 +175,24 @@ class ZKAPAuthorizer(object):
         )
 
 
-    def get_client_resource(self, node_config):
+    def get_client_resource(self, node_config, default_token_count=None):
+        """
+        Get an ``IZKAPRoot`` for the given node configuration.
+
+        :param allmydata.node._Config node_config: The configuration object
+            for the relevant node.
+
+        :param int default_token_count: Configure the payment controller with
+            a default number of tokens to request during voucher redemption.
+            This is only used if a number of tokens isn't specified at the
+            point of redemption.
+        """
         from twisted.internet import reactor
         return resource_from_configuration(
             node_config,
             store=self._get_store(node_config),
             redeemer=self._get_redeemer(node_config, None, reactor),
+            default_token_count=default_token_count,
         )
 
 
diff --git a/src/_zkapauthorizer/_stack.py b/src/_zkapauthorizer/_stack.py
new file mode 100644
index 0000000000000000000000000000000000000000..6fffe2cd73a6d241322269101f32afef7dfc0d73
--- /dev/null
+++ b/src/_zkapauthorizer/_stack.py
@@ -0,0 +1,47 @@
+# Copyright 2019 PrivateStorage.io, 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.
+
+from contextlib import (
+    contextmanager,
+)
+
+try:
+    from resource import (
+        RLIMIT_STACK,
+        getrlimit,
+        setrlimit,
+    )
+except ImportError:
+    # Not available on Windows, unfortunately.
+    RLIMIT_STACK = object()
+    def getrlimit(which):
+        return (-1, -1)
+    def setrlimit(which, what):
+        pass
+
+
+
+@contextmanager
+def less_limited_stack():
+    """
+    A context manager which removes the resource limit on stack size, to the
+    extent possible, for execution of the context.
+
+    More precisely, the soft stack limit is raised to the hard limit.
+    """
+    soft, hard = getrlimit(RLIMIT_STACK)
+    # We can raise the soft limit to the hard limit and no higher.
+    setrlimit(RLIMIT_STACK, (hard, hard))
+    yield
+    setrlimit(RLIMIT_STACK, (soft, hard))
diff --git a/src/_zkapauthorizer/controller.py b/src/_zkapauthorizer/controller.py
index 5539cea4e03f306f07a388a57a57a07315d05e85..755397108ee0f10ef7cc8931a5818ccfba1da480 100644
--- a/src/_zkapauthorizer/controller.py
+++ b/src/_zkapauthorizer/controller.py
@@ -17,6 +17,10 @@ This module implements controllers (in the MVC sense) for the web interface
 for the client side of the storage plugin.
 """
 
+from __future__ import (
+    absolute_import,
+)
+
 from sys import (
     exc_info,
 )
@@ -78,6 +82,9 @@ import privacypass
 from ._base64 import (
     urlsafe_b64decode,
 )
+from ._stack import (
+    less_limited_stack,
+)
 
 from .model import (
     RandomToken,
@@ -90,9 +97,6 @@ from .model import (
     Error as model_Error,
 )
 
-# The number of tokens to submit with a voucher redemption.
-NUM_TOKENS = 100
-
 RETRY_INTERVAL = timedelta(minutes=3)
 
 class AlreadySpent(Exception):
@@ -442,7 +446,12 @@ class RistrettoRedeemer(object):
             elif reason == u"unpaid":
                 raise Unpaid(voucher)
 
-        self._log.info("Redeemed: {public-key} {proof} {signatures}", **result)
+        self._log.info(
+            "Redeemed: {public_key} {proof} {count}",
+            public_key=result[u"public-key"],
+            proof=result[u"proof"],
+            count=len(result[u"signatures"]),
+        )
 
         marshaled_signed_tokens = result[u"signatures"]
         marshaled_proof = result[u"proof"]
@@ -451,6 +460,7 @@ class RistrettoRedeemer(object):
         public_key = privacypass.PublicKey.decode_base64(
             marshaled_public_key.encode("ascii"),
         )
+        self._log.info("Decoded public key")
         clients_signed_tokens = list(
             privacypass.SignedToken.decode_base64(
                 marshaled_signed_token.encode("ascii"),
@@ -458,15 +468,19 @@ class RistrettoRedeemer(object):
             for marshaled_signed_token
             in marshaled_signed_tokens
         )
+        self._log.info("Decoded signed tokens")
         clients_proof = privacypass.BatchDLEQProof.decode_base64(
             marshaled_proof.encode("ascii"),
         )
-        clients_unblinded_tokens = clients_proof.invalid_or_unblind(
-            random_tokens,
-            blinded_tokens,
-            clients_signed_tokens,
-            public_key,
-        )
+        with less_limited_stack():
+            self._log.info("Decoded batch proof")
+            clients_unblinded_tokens = clients_proof.invalid_or_unblind(
+                random_tokens,
+                blinded_tokens,
+                clients_signed_tokens,
+                public_key,
+            )
+        self._log.info("Validated proof")
         returnValue(list(
             UnblindedToken(token.encode_base64().decode("ascii"))
             for token
@@ -530,6 +544,10 @@ class PaymentController(object):
          the voucher.  The data store marks the voucher as redeemed and stores
          the unblinded tokens for use by the storage client.
 
+    :ivar int default_token_count: The number of tokens to request when
+        redeeming a voucher, if no other count is given when the redemption is
+        started.
+
     :ivar dict[unicode, datetime] _active: A mapping from voucher identifiers
         which currently have redemption attempts in progress to timestamps
         when the attempt began.
@@ -546,6 +564,7 @@ class PaymentController(object):
 
     store = attr.ib()
     redeemer = attr.ib()
+    default_token_count = attr.ib()
 
     _clock = attr.ib(
         default=attr.Factory(partial(namedAny, "twisted.internet.reactor")),
@@ -595,13 +614,13 @@ class PaymentController(object):
         for voucher in vouchers:
             if voucher.state.should_start_redemption():
                 self._log.info(
-                    "Controller found voucher ({}) at startup that needs redemption.",
+                    "Controller found voucher ({voucher}) at startup that needs redemption.",
                     voucher=voucher.number,
                 )
                 self.redeem(voucher.number)
             else:
                 self._log.info(
-                    "Controller found voucher ({}) at startup that does not need redemption.",
+                    "Controller found voucher ({voucher}) at startup that does not need redemption.",
                     voucher=voucher.number,
                 )
 
@@ -633,7 +652,7 @@ class PaymentController(object):
         self._log.info("Generating random tokens for a voucher ({voucher}).", voucher=voucher)
         tokens = self.redeemer.random_tokens_for_voucher(Voucher(voucher), num_tokens)
 
-        self._log.info("Persistenting random tokens for a voucher ({voucher}).", voucher=voucher)
+        self._log.info("Persisting random tokens for a voucher ({voucher}).", voucher=voucher)
         self.store.add(voucher, tokens)
 
         # XXX If the voucher is already in the store then the tokens passed to
@@ -659,7 +678,7 @@ class PaymentController(object):
         # number of passes that can be constructed is still only the size of
         # the set of random tokens.
         if num_tokens is None:
-            num_tokens = NUM_TOKENS
+            num_tokens = self.default_token_count
         tokens = self._get_random_tokens_for_voucher(voucher, num_tokens)
         return self._perform_redeem(voucher, tokens)
 
diff --git a/src/_zkapauthorizer/resource.py b/src/_zkapauthorizer/resource.py
index 6c4931ca2cd08d64856f21b43bbbf37b6b35765d..79c8d3146e82c3d42b5c5a9f404ee8496eaafd85 100644
--- a/src/_zkapauthorizer/resource.py
+++ b/src/_zkapauthorizer/resource.py
@@ -58,6 +58,10 @@ from .controller import (
     get_redeemer,
 )
 
+# The number of tokens to submit with a voucher redemption.
+NUM_TOKENS = 512000
+
+
 class IZKAPRoot(IResource):
     """
     The root of the resource tree of this plugin's client web presence.
@@ -66,13 +70,12 @@ class IZKAPRoot(IResource):
     controller = Attribute("The ``PaymentController`` used by this resource tree.")
 
 
-def from_configuration(node_config, store, redeemer=None):
+def from_configuration(node_config, store, redeemer=None, default_token_count=None):
     """
     Instantiate the plugin root resource using data from its configuration
-    section in the Tahoe-LAFS configuration file::
-
-        [storageclient.plugins.privatestorageio-zkapauthz-v1]
-        # nothing yet
+    section, **storageclient.plugins.privatestorageio-zkapauthz-v1**, in the
+    Tahoe-LAFS configuration file.  See the configuration documentation for
+    details of the configuration section.
 
     :param _Config node_config: An object representing the overall node
         configuration.  The plugin configuration can be extracted from this.
@@ -94,7 +97,9 @@ def from_configuration(node_config, store, redeemer=None):
             None,
             None,
         )
-    controller = PaymentController(store, redeemer)
+    if default_token_count is None:
+        default_token_count = NUM_TOKENS
+    controller = PaymentController(store, redeemer, default_token_count)
     root = Resource()
     root.store = store
     root.controller = controller
diff --git a/src/_zkapauthorizer/tests/test_client_resource.py b/src/_zkapauthorizer/tests/test_client_resource.py
index 8a24a2c84df5b8225b0772540801bd1f5a1c59cd..f901d020fa1e23d9b26c141369996560d00a0d44 100644
--- a/src/_zkapauthorizer/tests/test_client_resource.py
+++ b/src/_zkapauthorizer/tests/test_client_resource.py
@@ -153,6 +153,9 @@ from .matchers import (
     Provides,
 )
 
+# A small number of tokens to work with in the tests.
+NUM_TOKENS = 10
+
 TRANSIENT_ERROR = u"something went wrong, who knows what"
 
 # Helper to work-around https://github.com/twisted/treq/issues/161
@@ -248,6 +251,7 @@ def root_from_config(config, now):
             now,
             memory_connect,
         ),
+        default_token_count=NUM_TOKENS,
     )
 
 
@@ -763,9 +767,7 @@ class VoucherTests(TestCase):
                 created=Equals(now),
                 state=Equals(Redeemed(
                     finished=now,
-                    # Value duplicated from PaymentController.redeem default.
-                    # Should do this better.
-                    token_count=100,
+                    token_count=NUM_TOKENS,
                 )),
             ),
         )
@@ -910,10 +912,7 @@ class VoucherTests(TestCase):
                         created=now,
                         state=Redeemed(
                             finished=now,
-                            # Value duplicated from
-                            # PaymentController.redeem
-                            # default.  Should do this better.
-                            token_count=100,
+                            token_count=NUM_TOKENS,
                         ),
                     ).marshal()
                     for voucher
diff --git a/src/_zkapauthorizer/tests/test_controller.py b/src/_zkapauthorizer/tests/test_controller.py
index dec0176991e75bc905b725fd6a08ba165c4b1d3c..0dd8bdd7d982f14eea28abfbe27adb34cc81bd7a 100644
--- a/src/_zkapauthorizer/tests/test_controller.py
+++ b/src/_zkapauthorizer/tests/test_controller.py
@@ -139,6 +139,7 @@ class PaymentControllerTests(TestCase):
         controller = PaymentController(
             store,
             NonRedeemer(),
+            default_token_count=10,
         )
         controller.redeem(voucher)
 
@@ -157,6 +158,7 @@ class PaymentControllerTests(TestCase):
         controller = PaymentController(
             store,
             DummyRedeemer(),
+            default_token_count=10,
         )
         controller.redeem(voucher)
 
@@ -165,7 +167,7 @@ class PaymentControllerTests(TestCase):
             persisted_voucher.state,
             Equals(model_Redeemed(
                 finished=now,
-                token_count=100,
+                token_count=10,
             )),
         )
 
@@ -179,6 +181,7 @@ class PaymentControllerTests(TestCase):
         controller = PaymentController(
             store,
             DoubleSpendRedeemer(),
+            default_token_count=10,
         )
         controller.redeem(voucher)
 
@@ -204,6 +207,7 @@ class PaymentControllerTests(TestCase):
         unpaid_controller = PaymentController(
             store,
             UnpaidRedeemer(),
+            default_token_count=10,
         )
         unpaid_controller.redeem(voucher)
 
@@ -219,6 +223,7 @@ class PaymentControllerTests(TestCase):
         success_controller = PaymentController(
             store,
             DummyRedeemer(),
+            default_token_count=10,
         )
 
         self.assertThat(
@@ -247,7 +252,8 @@ class PaymentControllerTests(TestCase):
         controller = PaymentController(
             store,
             UnpaidRedeemer(),
-            clock,
+            default_token_count=10,
+            clock=clock,
         )
         controller.redeem(voucher)
         # It fails this time.
diff --git a/src/_zkapauthorizer/tests/test_plugin.py b/src/_zkapauthorizer/tests/test_plugin.py
index e9c8eecc3ce3290ca8bafb2f286b600442f47c6a..41c2dd43f4b3bd240ec9bbf852fe19e93c328de0 100644
--- a/src/_zkapauthorizer/tests/test_plugin.py
+++ b/src/_zkapauthorizer/tests/test_plugin.py
@@ -461,7 +461,7 @@ class ClientResourceTests(TestCase):
         nodedir = tempdir.join(b"node")
         config = get_config(nodedir, b"tub.port")
         self.assertThat(
-            storage_server.get_client_resource(config),
+            storage_server.get_client_resource(config, default_token_count=10),
             Provides([IResource]),
         )