diff --git a/src/_zkapauthorizer/_plugin.py b/src/_zkapauthorizer/_plugin.py
index 2ed966d8af44e357e789feb8bfc38bb2b92ef4c4..450e25f356083a12b1c79e1be6d5c6e3dc81049e 100644
--- a/src/_zkapauthorizer/_plugin.py
+++ b/src/_zkapauthorizer/_plugin.py
@@ -71,7 +71,10 @@ from .model import (
 from .resource import (
     from_configuration as resource_from_configuration,
 )
-
+from .storage_common import (
+    BYTES_PER_PASS,
+    get_configured_pass_value,
+)
 from .controller import (
     get_redeemer,
 )
@@ -134,6 +137,7 @@ class ZKAPAuthorizer(object):
     def get_storage_server(self, configuration, get_anonymous_storage_server):
         kwargs = configuration.copy()
         root_url = kwargs.pop(u"ristretto-issuer-root-url")
+        pass_value = kwargs.pop(u"pass-value", BYTES_PER_PASS)
         signing_key = SigningKey.decode_base64(
             FilePath(
                 kwargs.pop(u"ristretto-signing-key-path"),
@@ -144,7 +148,8 @@ class ZKAPAuthorizer(object):
         }
         storage_server = ZKAPAuthorizerStorageServer(
             get_anonymous_storage_server(),
-            signing_key,
+            pass_value=pass_value,
+            signing_key=signing_key,
             **kwargs
         )
         return succeed(
@@ -170,6 +175,7 @@ class ZKAPAuthorizer(object):
             return redeemer.tokens_to_passes(message, unblinded_tokens)
 
         return ZKAPAuthorizerStorageClient(
+            get_configured_pass_value(node_config),
             get_rref,
             get_passes,
         )
diff --git a/src/_zkapauthorizer/_storage_client.py b/src/_zkapauthorizer/_storage_client.py
index a51fbfd1402c74e0f95ebddbd325e99f7ea076f4..6559b732e6a1bcd67396dea3d561162f2ce31c5c 100644
--- a/src/_zkapauthorizer/_storage_client.py
+++ b/src/_zkapauthorizer/_storage_client.py
@@ -34,7 +34,7 @@ from allmydata.interfaces import (
 )
 
 from .storage_common import (
-    BYTES_PER_PASS,
+    pass_value_attribute,
     required_passes,
     allocate_buckets_message,
     add_lease_message,
@@ -92,7 +92,7 @@ class ZKAPAuthorizerStorageClient(object):
     _expected_remote_interface_name = (
         "RIPrivacyPassAuthorizedStorageServer.tahoe.privatestorage.io"
     )
-
+    _pass_value = pass_value_attribute()
     _get_rref = attr.ib()
     _get_passes = attr.ib()
 
@@ -148,7 +148,7 @@ class ZKAPAuthorizerStorageClient(object):
             "allocate_buckets",
             self._get_encoded_passes(
                 allocate_buckets_message(storage_index),
-                required_passes(BYTES_PER_PASS, [allocated_size] * len(sharenums)),
+                required_passes(self._pass_value, [allocated_size] * len(sharenums)),
             ),
             storage_index,
             renew_secret,
@@ -179,7 +179,7 @@ class ZKAPAuthorizerStorageClient(object):
             storage_index,
             None,
         )).values()
-        num_passes = required_passes(BYTES_PER_PASS, share_sizes)
+        num_passes = required_passes(self._pass_value, share_sizes)
         # print("Adding lease to {!r} with sizes {} with {} passes".format(
         #     storage_index,
         #     share_sizes,
@@ -206,7 +206,7 @@ class ZKAPAuthorizerStorageClient(object):
             storage_index,
             None,
         )).values()
-        num_passes = required_passes(BYTES_PER_PASS, share_sizes)
+        num_passes = required_passes(self._pass_value, share_sizes)
         returnValue((
             yield self._rref.callRemote(
                 "renew_lease",
@@ -265,6 +265,7 @@ class ZKAPAuthorizerStorageClient(object):
             )
             # Determine the cost of the new storage for the operation.
             required_new_passes = get_required_new_passes_for_mutable_write(
+                self._pass_value,
                 current_sizes,
                 tw_vectors,
             )
diff --git a/src/_zkapauthorizer/_storage_server.py b/src/_zkapauthorizer/_storage_server.py
index d8c747b451bd1b95ed841ace69094b92e6b2df53..a2d4b9f2c5cce038eff98184ae4859cbde8f8b81 100644
--- a/src/_zkapauthorizer/_storage_server.py
+++ b/src/_zkapauthorizer/_storage_server.py
@@ -86,7 +86,7 @@ from .foolscap import (
     RIPrivacyPassAuthorizedStorageServer,
 )
 from .storage_common import (
-    BYTES_PER_PASS,
+    pass_value_attribute,
     required_passes,
     allocate_buckets_message,
     add_lease_message,
@@ -153,6 +153,7 @@ class ZKAPAuthorizerStorageServer(Referenceable):
     LEASE_PERIOD = timedelta(days=31)
 
     _original = attr.ib(validator=provides(RIStorageServer))
+    _pass_value = pass_value_attribute()
     _signing_key = attr.ib(validator=instance_of(SigningKey))
     _clock = attr.ib(
         validator=provides(IReactorTime),
@@ -217,7 +218,12 @@ class ZKAPAuthorizerStorageServer(Referenceable):
             allocate_buckets_message(storage_index),
             passes,
         )
-        check_pass_quantity_for_write(len(valid_passes), sharenums, allocated_size)
+        check_pass_quantity_for_write(
+            self._pass_value,
+            len(valid_passes),
+            sharenums,
+            allocated_size,
+        )
 
         return self._original.remote_allocate_buckets(
             storage_index,
@@ -243,6 +249,7 @@ class ZKAPAuthorizerStorageServer(Referenceable):
         # print("server add_lease({}, {!r})".format(len(passes), storage_index))
         valid_passes = self._validate_passes(add_lease_message(storage_index), passes)
         check_pass_quantity_for_lease(
+            self._pass_value,
             storage_index,
             valid_passes,
             self._original,
@@ -256,6 +263,7 @@ class ZKAPAuthorizerStorageServer(Referenceable):
         """
         valid_passes = self._validate_passes(renew_lease_message(storage_index), passes)
         check_pass_quantity_for_lease(
+            self._pass_value,
             storage_index,
             valid_passes,
             self._original,
@@ -324,6 +332,7 @@ class ZKAPAuthorizerStorageServer(Referenceable):
                 renew_leases = True
 
             required_new_passes = get_required_new_passes_for_mutable_write(
+                self._pass_value,
                 current_sizes,
                 tw_vectors,
             )
@@ -372,23 +381,7 @@ def has_active_lease(storage_server, storage_index, now):
     )
 
 
-def check_pass_quantity_for_lease(storage_index, valid_passes, storage_server):
-    """
-    Check that the given number of passes is sufficient to add or renew a
-    lease for one period for the given storage index.
-    """
-    allocated_sizes = dict(
-        get_share_sizes(
-            storage_server,
-            storage_index,
-            list(get_all_share_numbers(storage_server, storage_index)),
-        ),
-    ).values()
-    # print("allocated_sizes: {}".format(allocated_sizes))
-    check_pass_quantity(len(valid_passes), allocated_sizes)
-    # print("Checked out")
-
-def check_pass_quantity(valid_count, share_sizes):
+def check_pass_quantity(pass_value, valid_count, share_sizes):
     """
     Check that the given number of passes is sufficient to cover leases for
     one period for shares of the given sizes.
@@ -402,14 +395,30 @@ def check_pass_quantity(valid_count, share_sizes):
 
     :return: ``None`` if the given number of passes is sufficient.
     """
-    required_pass_count = required_passes(BYTES_PER_PASS, share_sizes)
+    required_pass_count = required_passes(pass_value, share_sizes)
     if valid_count < required_pass_count:
         raise MorePassesRequired(
             valid_count,
             required_pass_count,
         )
 
-def check_pass_quantity_for_write(valid_count, sharenums, allocated_size):
+
+def check_pass_quantity_for_lease(pass_value, storage_index, valid_passes, storage_server):
+    """
+    Check that the given number of passes is sufficient to add or renew a
+    lease for one period for the given storage index.
+    """
+    allocated_sizes = dict(
+        get_share_sizes(
+            storage_server,
+            storage_index,
+            list(get_all_share_numbers(storage_server, storage_index)),
+        ),
+    ).values()
+    check_pass_quantity(pass_value, len(valid_passes), allocated_sizes)
+
+
+def check_pass_quantity_for_write(pass_value, valid_count, sharenums, allocated_size):
     """
     Determine if the given number of valid passes is sufficient for an
     attempted write.
@@ -423,7 +432,7 @@ def check_pass_quantity_for_write(valid_count, sharenums, allocated_size):
 
     :return: ``None`` if the number of valid passes given is sufficient.
     """
-    check_pass_quantity(valid_count, [allocated_size] * len(sharenums))
+    check_pass_quantity(pass_value, valid_count, [allocated_size] * len(sharenums))
 
 
 def get_all_share_paths(storage_server, storage_index):
diff --git a/src/_zkapauthorizer/model.py b/src/_zkapauthorizer/model.py
index fed05e1d489b12864512628bb60e01b0173b25b9..5e391c52c8575f4ecac9a7af0828cba811a22eef 100644
--- a/src/_zkapauthorizer/model.py
+++ b/src/_zkapauthorizer/model.py
@@ -27,9 +27,6 @@ from json import (
 from datetime import (
     datetime,
 )
-from base64 import (
-    b64decode,
-)
 from zope.interface import (
     Interface,
     implementer,
@@ -56,8 +53,15 @@ from ._base64 import (
     urlsafe_b64decode,
 )
 
+from .validators import (
+    is_base64_encoded,
+    has_length,
+    greater_than,
+)
+
 from .storage_common import (
-    BYTES_PER_PASS,
+    pass_value_attribute,
+    get_configured_pass_value,
     required_passes,
 )
 
@@ -171,6 +175,8 @@ class VoucherStore(object):
     """
     _log = Logger()
 
+    pass_value = pass_value_attribute()
+
     database_path = attr.ib(validator=attr.validators.instance_of(FilePath))
     now = attr.ib()
 
@@ -196,6 +202,7 @@ class VoucherStore(object):
             connect=connect,
         )
         return cls(
+            get_configured_pass_value(node_config),
             db_path,
             now,
             conn,
@@ -489,7 +496,7 @@ class VoucherStore(object):
 
         :return LeaseMaintenance: A new, started lease maintenance object.
         """
-        m = LeaseMaintenance(self.now, self._connection)
+        m = LeaseMaintenance(self.pass_value, self.now, self._connection)
         m.start()
         return m
 
@@ -533,6 +540,8 @@ class LeaseMaintenance(object):
     the ``observe`` and ``finish`` methods to persist state about a lease
     maintenance run.
 
+    :ivar int _pass_value: The value of a single ZKAP in byte-months.
+
     :ivar _now: A no-argument callable which returns a datetime giving a time
         to use as current.
 
@@ -543,6 +552,7 @@ class LeaseMaintenance(object):
         objects, the database row id that corresponds to the started run.
         This is used to make sure future updates go to the right row.
     """
+    _pass_value = pass_value_attribute()
     _now = attr.ib()
     _connection = attr.ib()
     _rowid = attr.ib(default=None)
@@ -569,7 +579,7 @@ class LeaseMaintenance(object):
         """
         Record a storage shares of the given sizes.
         """
-        count = required_passes(BYTES_PER_PASS, sizes)
+        count = required_passes(self._pass_value, sizes)
         cursor.execute(
             """
             UPDATE [lease-maintenance-spending]
@@ -612,46 +622,6 @@ class LeaseMaintenanceActivity(object):
 # x = store.get_latest_lease_maintenance_activity()
 # xs.started, xs.passes_required, xs.finished
 
-def is_base64_encoded(b64decode=b64decode):
-    def validate_is_base64_encoded(inst, attr, value):
-        try:
-            b64decode(value.encode("ascii"))
-        except (TypeError, Error):
-            raise TypeError(
-                "{name!r} must be base64 encoded unicode, (got {value!r})".format(
-                    name=attr.name,
-                    value=value,
-                ),
-            )
-    return validate_is_base64_encoded
-
-def has_length(expected):
-    def validate_has_length(inst, attr, value):
-        if len(value) != expected:
-            raise ValueError(
-                "{name!r} must have length {expected}, instead has length {actual}".format(
-                    name=attr.name,
-                    expected=expected,
-                    actual=len(value),
-                ),
-            )
-    return validate_has_length
-
-def greater_than(expected):
-    def validate_relation(inst, attr, value):
-        if value > expected:
-            return None
-
-        raise ValueError(
-            "{name!r} must be greater than {expected}, instead it was {actual}".format(
-                name=attr.name,
-                expected=expected,
-                actual=value,
-            ),
-        )
-    return validate_relation
-
-
 @attr.s(frozen=True)
 class UnblindedToken(object):
     """
diff --git a/src/_zkapauthorizer/storage_common.py b/src/_zkapauthorizer/storage_common.py
index 9bf9435e69e5429cf7bdf596d7e1b18fe0472da1..800f7f0aba9f736e6935bd38e80e6558ae7eb0eb 100644
--- a/src/_zkapauthorizer/storage_common.py
+++ b/src/_zkapauthorizer/storage_common.py
@@ -24,6 +24,12 @@ from base64 import (
     b64encode,
 )
 
+import attr
+
+from .validators import (
+    greater_than,
+)
+
 def _message_maker(label):
     def make_message(storage_index):
         return u"{label} {storage_index}".format(
@@ -43,6 +49,21 @@ slot_testv_and_readv_and_writev_message = _message_maker(u"slot_testv_and_readv_
 # submitted.
 BYTES_PER_PASS = 128 * 1024
 
+def get_configured_pass_value(node_config):
+    """
+    Determine the configuration-specified value of a single ZKAP.
+
+    If no value is explicitly configured, a default value is returned.  The
+    value is read from the **pass-value** option of the ZKAPAuthorizer plugin
+    client section.
+    """
+    section_name = u"storageclient.plugins.privatestorageio-zkapauthz-v1"
+    return int(node_config.get_config(
+        section=section_name,
+        option=u"pass-value",
+        default=BYTES_PER_PASS,
+    ))
+
 def required_passes(bytes_per_pass, share_sizes):
     """
     Calculate the number of passes that are required to store ``stored_bytes``
@@ -136,10 +157,13 @@ def get_implied_data_length(data_vector, new_length):
     return min(new_length, data_based_size)
 
 
-def get_required_new_passes_for_mutable_write(current_sizes, tw_vectors):
+def get_required_new_passes_for_mutable_write(pass_value, current_sizes, tw_vectors):
+    """
+    :param int pass_value: The value of a single pass in byte-months.
+    """
     # print("get_required_new_passes_for_mutable_write({}, {})".format(current_sizes, summarize(tw_vectors)))
     current_passes = required_passes(
-        BYTES_PER_PASS,
+        pass_value,
         current_sizes.values(),
     )
 
@@ -155,7 +179,7 @@ def get_required_new_passes_for_mutable_write(current_sizes, tw_vectors):
 
     new_sizes.update()
     new_passes = required_passes(
-        BYTES_PER_PASS,
+        pass_value,
         new_sizes.values(),
     )
     required_new_passes = new_passes - current_passes
@@ -180,3 +204,14 @@ def summarize(tw_vectors):
         for (sharenum, (test_vector, data_vectors, new_length))
         in tw_vectors.items()
     }
+
+def pass_value_attribute():
+    """
+    Define an attribute for an attrs-based object which can hold a pass value.
+    """
+    return attr.ib(
+        validator=attr.validators.and_(
+            attr.validators.instance_of((int, long)),
+            greater_than(0),
+        ),
+    )
diff --git a/src/_zkapauthorizer/tests/test_client_resource.py b/src/_zkapauthorizer/tests/test_client_resource.py
index 6c2089a999b53ae1a655b824de86424144c28752..e2f8934e2ca7a551302cd7f751f12e6589a804c6 100644
--- a/src/_zkapauthorizer/tests/test_client_resource.py
+++ b/src/_zkapauthorizer/tests/test_client_resource.py
@@ -135,7 +135,6 @@ from ..resource import (
 )
 
 from ..storage_common import (
-    BYTES_PER_PASS,
     required_passes,
 )
 
@@ -546,7 +545,7 @@ class UnblindedTokenTests(TestCase):
         total = 0
         activity = root.store.start_lease_maintenance()
         for sizes in size_observations:
-            total += required_passes(BYTES_PER_PASS, sizes)
+            total += required_passes(root.store.pass_value, sizes)
             activity.observe(sizes)
         activity.finish()
 
diff --git a/src/_zkapauthorizer/tests/test_model.py b/src/_zkapauthorizer/tests/test_model.py
index e6df76c1ad9921552af6abb0667f341d76f7c57c..13d3dcf85e4192b1b2417f6a0a67117166f980af 100644
--- a/src/_zkapauthorizer/tests/test_model.py
+++ b/src/_zkapauthorizer/tests/test_model.py
@@ -68,10 +68,6 @@ from twisted.python.runtime import (
     platform,
 )
 
-from ..storage_common import (
-    BYTES_PER_PASS,
-)
-
 from ..model import (
     StoreOpenError,
     NotEnoughTokens,
@@ -355,8 +351,11 @@ class LeaseMaintenanceTests(TestCase):
                     tuples(
                         # The activity itself, in pass count
                         integers(min_value=1, max_value=2 ** 16 - 1),
-                        # Amount by which to trim back the share sizes
-                        integers(min_value=0, max_value=BYTES_PER_PASS - 1),
+                        # Amount by which to trim back the share sizes.  This
+                        # might exceed the value of a single pass but we don't
+                        # know that value yet.  We'll map it into a coherent
+                        # range with mod inside the test.
+                        integers(min_value=0),
                     ),
                 ),
                 # How much time passes before this activity finishes
@@ -382,8 +381,9 @@ class LeaseMaintenanceTests(TestCase):
             passes_required = 0
             for (num_passes, trim_size) in sizes:
                 passes_required += num_passes
+                trim_size %= store.pass_value
                 x.observe([
-                    num_passes * BYTES_PER_PASS - trim_size,
+                    num_passes * store.pass_value - trim_size,
                 ])
             now += finish_delay
             x.finish()
diff --git a/src/_zkapauthorizer/tests/test_plugin.py b/src/_zkapauthorizer/tests/test_plugin.py
index 48f8cfcee9ceed36b72dc9df7dd42953c1eee11f..aafecc3df38d45bab480147763b4d3319f1d64bd 100644
--- a/src/_zkapauthorizer/tests/test_plugin.py
+++ b/src/_zkapauthorizer/tests/test_plugin.py
@@ -108,7 +108,6 @@ from ..controller import (
     DummyRedeemer,
 )
 from ..storage_common import (
-    BYTES_PER_PASS,
     required_passes,
 )
 from .._storage_client import (
@@ -423,7 +422,7 @@ class ClientPluginTests(TestCase):
             store,
             DummyRedeemer(),
             # Give it enough for the allocate_buckets call below.
-            required_passes(BYTES_PER_PASS, [size] * len(sharenums)),
+            required_passes(store.pass_value, [size] * len(sharenums)),
         )
         # Get a token inserted into the store.
         redeeming = controller.redeem(voucher)
diff --git a/src/_zkapauthorizer/tests/test_storage_protocol.py b/src/_zkapauthorizer/tests/test_storage_protocol.py
index 713a0c3e862dddfbf803e409898043f0cd562532..f2b9b6895246583018d498422e84ad2265e4eb4c 100644
--- a/src/_zkapauthorizer/tests/test_storage_protocol.py
+++ b/src/_zkapauthorizer/tests/test_storage_protocol.py
@@ -167,6 +167,8 @@ class ShareTests(TestCase):
         iteration of the test so far, probably; so make relative comparisons
         instead of absolute ones).
     """
+    pass_value = 128 * 1024
+
     def setUp(self):
         super(ShareTests, self).setUp()
         self.canary = LocalReferenceable(None)
@@ -187,10 +189,12 @@ class ShareTests(TestCase):
             )
         self.server = ZKAPAuthorizerStorageServer(
             self.anonymous_storage_server,
+            self.pass_value,
             self.signing_key,
         )
         self.local_remote_server = LocalRemote(self.server)
         self.client = ZKAPAuthorizerStorageClient(
+            self.pass_value,
             get_rref=lambda: self.local_remote_server,
             get_passes=get_passes,
         )
diff --git a/src/_zkapauthorizer/tests/test_storage_server.py b/src/_zkapauthorizer/tests/test_storage_server.py
index 55f4402da118c6eac3bf7717a6a690c742c3d835..88ae5a1f1294bc0679787942f7432aa7e08d2291 100644
--- a/src/_zkapauthorizer/tests/test_storage_server.py
+++ b/src/_zkapauthorizer/tests/test_storage_server.py
@@ -92,7 +92,6 @@ from ..api import (
     MorePassesRequired,
 )
 from ..storage_common import (
-    BYTES_PER_PASS,
     required_passes,
     allocate_buckets_message,
     add_lease_message,
@@ -107,6 +106,8 @@ class PassValidationTests(TestCase):
     """
     Tests for pass validation performed by ``ZKAPAuthorizerStorageServer``.
     """
+    pass_value = 128 * 1024
+
     @skipIf(platform.isWindows(), "Storage server is not supported on Windows")
     def setUp(self):
         super(PassValidationTests, self).setUp()
@@ -119,6 +120,7 @@ class PassValidationTests(TestCase):
         self.signing_key = random_signing_key()
         self.storage_server = ZKAPAuthorizerStorageServer(
             self.anonymous_storage_server,
+            self.pass_value,
             self.signing_key,
             self.clock,
         )
@@ -162,7 +164,7 @@ class PassValidationTests(TestCase):
 
         required_passes = 2
         share_nums = {3, 7}
-        allocated_size = int((required_passes * BYTES_PER_PASS) / len(share_nums))
+        allocated_size = int((required_passes * self.pass_value) / len(share_nums))
         storage_index = b"0123456789"
         renew_secret = b"x" * 32
         cancel_secret = b"y" * 32
@@ -250,7 +252,7 @@ class PassValidationTests(TestCase):
         :param make_data_vector: A one-argument callable.  It will be called
             with the current length of a slot share.  It should return a write
             vector which will increase the storage requirements of that slot
-            share by at least BYTES_PER_PASS.
+            share by at least ``self.pass_value``.
         """
         # hypothesis causes our storage server to be used many times.  Clean
         # up between iterations.
@@ -266,6 +268,7 @@ class PassValidationTests(TestCase):
 
         # print("test suite")
         required_pass_count = get_required_new_passes_for_mutable_write(
+            self.pass_value,
             dict.fromkeys(tw_vectors.keys(), 0),
             tw_vectors,
         )
@@ -355,7 +358,7 @@ class PassValidationTests(TestCase):
             test_and_write_vectors_for_shares,
             lambda current_length: (
                 [],
-                [(current_length, "x" * BYTES_PER_PASS)],
+                [(current_length, "x" * self.pass_value)],
                 None,
             ),
         )
@@ -387,7 +390,7 @@ class PassValidationTests(TestCase):
 
         renew_secret, cancel_secret = secrets
 
-        required_count = required_passes(BYTES_PER_PASS, [allocated_size] * len(sharenums))
+        required_count = required_passes(self.pass_value, [allocated_size] * len(sharenums))
         # Create some shares at a slot which will require lease renewal.
         write_toy_shares(
             self.anonymous_storage_server,
@@ -524,6 +527,7 @@ class PassValidationTests(TestCase):
 
         # Create an initial share to toy with.
         required_pass_count = get_required_new_passes_for_mutable_write(
+            self.pass_value,
             dict.fromkeys(tw_vectors.keys(), 0),
             tw_vectors,
         )
diff --git a/src/_zkapauthorizer/validators.py b/src/_zkapauthorizer/validators.py
new file mode 100644
index 0000000000000000000000000000000000000000..bd1545144b3a9ed39d10c656ccd9ebbbde549804
--- /dev/null
+++ b/src/_zkapauthorizer/validators.py
@@ -0,0 +1,60 @@
+# 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.
+
+"""
+This module implements validators for ``attrs``-defined attributes.
+"""
+
+from base64 import (
+    b64decode,
+)
+
+def is_base64_encoded(b64decode=b64decode):
+    def validate_is_base64_encoded(inst, attr, value):
+        try:
+            b64decode(value.encode("ascii"))
+        except TypeError:
+            raise TypeError(
+                "{name!r} must be base64 encoded unicode, (got {value!r})".format(
+                    name=attr.name,
+                    value=value,
+                ),
+            )
+    return validate_is_base64_encoded
+
+def has_length(expected):
+    def validate_has_length(inst, attr, value):
+        if len(value) != expected:
+            raise ValueError(
+                "{name!r} must have length {expected}, instead has length {actual}".format(
+                    name=attr.name,
+                    expected=expected,
+                    actual=len(value),
+                ),
+            )
+    return validate_has_length
+
+def greater_than(expected):
+    def validate_relation(inst, attr, value):
+        if value > expected:
+            return None
+
+        raise ValueError(
+            "{name!r} must be greater than {expected}, instead it was {actual}".format(
+                name=attr.name,
+                expected=expected,
+                actual=value,
+            ),
+        )
+    return validate_relation