diff --git a/src/_zkapauthorizer/controller.py b/src/_zkapauthorizer/controller.py
index 429805b5bb222830b5b3cd0b65bebe508712b74a..2b005112013de69ae1bbc6b47d9c44e53812d447 100644
--- a/src/_zkapauthorizer/controller.py
+++ b/src/_zkapauthorizer/controller.py
@@ -33,7 +33,9 @@ from json import (
 from datetime import (
     timedelta,
 )
-
+from base64 import (
+    b64encode,
+)
 import attr
 
 from zope.interface import (
@@ -72,6 +74,10 @@ from treq.client import (
 
 import privacypass
 
+from ._base64 import (
+    urlsafe_b64decode,
+)
+
 from .model import (
     RandomToken,
     UnblindedToken,
@@ -177,12 +183,7 @@ class NonRedeemer(object):
         return cls()
 
     def random_tokens_for_voucher(self, voucher, count):
-        # It doesn't matter because we're never going to try to redeem them.
-        return list(
-            RandomToken(u"{}-{}".format(voucher.number, n))
-            for n
-            in range(count)
-        )
+        return dummy_random_tokens(voucher, count)
 
     def redeem(self, voucher, random_tokens):
         # Don't try to redeem them.
@@ -260,8 +261,17 @@ class UnpaidRedeemer(object):
 
 
 def dummy_random_tokens(voucher, count):
+    v = urlsafe_b64decode(voucher.number.encode("ascii"))
+    def dummy_random_token(n):
+        return RandomToken(
+            # Padding is 96 (random token length) - 32 (decoded voucher
+            # length)
+            b64encode(
+                v + u"{:0>64}".format(n).encode("ascii"),
+            ).decode("ascii"),
+        )
     return list(
-        RandomToken(u"{}-{}".format(voucher.number, n))
+        dummy_random_token(n)
         for n
         in range(count)
     )
diff --git a/src/_zkapauthorizer/model.py b/src/_zkapauthorizer/model.py
index 50c84cdbf702a08bae984ae8402e215a2113c58a..79ffe1c01e10622eb8e295e9eb3567a9a0cc7592 100644
--- a/src/_zkapauthorizer/model.py
+++ b/src/_zkapauthorizer/model.py
@@ -667,7 +667,17 @@ class Pass(object):
 
 @attr.s(frozen=True)
 class RandomToken(object):
-    token_value = attr.ib(validator=attr.validators.instance_of(unicode))
+    """
+    :ivar unicode token_value: The base64-encoded representation of the random
+        token.
+    """
+    token_value = attr.ib(
+        validator=attr.validators.and_(
+            attr.validators.instance_of(unicode),
+            is_base64_encoded(),
+            has_length(128),
+        ),
+    )
 
 
 @attr.s(frozen=True)
diff --git a/src/_zkapauthorizer/tests/strategies.py b/src/_zkapauthorizer/tests/strategies.py
index 4ba150cd8589c160bf895fc4dc6277100f93ca8e..c0953e40ac1a319b5852d80cd46a7344306d41f0 100644
--- a/src/_zkapauthorizer/tests/strategies.py
+++ b/src/_zkapauthorizer/tests/strategies.py
@@ -17,6 +17,7 @@ Hypothesis strategies for property testing.
 """
 
 from base64 import (
+    b64encode,
     urlsafe_b64encode,
 )
 from datetime import (
@@ -77,6 +78,11 @@ from ..model import (
     Redeemed,
 )
 
+# Sizes informed by
+# https://github.com/brave-intl/challenge-bypass-ristretto/blob/2f98b057d7f353c12b2b12d0f5ae9ad115f1d0ba/src/oprf.rs#L18-L33
+
+# The length of a `Token`, in bytes.
+_TOKEN_LENGTH = 96
 
 def _merge_dictionaries(dictionaries):
     result = {}
@@ -296,15 +302,36 @@ def voucher_objects():
     )
 
 
-def random_tokens():
+def byte_strings(label, length, entropy):
     """
-    Build random tokens as unicode strings.
+    Build byte strings of the given length with at most the given amount of
+    entropy.
+
+    These are cheaper for Hypothesis to construct than byte strings where
+    potentially the entire length is random.
     """
+    if len(label) + entropy > length:
+        raise ValueError("Entropy and label don't fit into {} bytes".format(
+            length,
+        ))
     return binary(
-        min_size=32,
-        max_size=32,
+        min_size=entropy,
+        max_size=entropy,
     ).map(
-        urlsafe_b64encode,
+        lambda bs: label + b"x" * (length - entropy - len(label)) + bs,
+    )
+
+
+def random_tokens():
+    """
+    Build ``RandomToken`` instances.
+    """
+    return byte_strings(
+        label=b"random-tokens",
+        length=_TOKEN_LENGTH,
+        entropy=4,
+    ).map(
+        b64encode,
     ).map(
         lambda token: RandomToken(token.decode("ascii")),
     )