diff --git a/requirements/base-1.16.in b/requirements/base-1.16.in
new file mode 100644
index 0000000000000000000000000000000000000000..0c5e30c1d6c0f5de527dc552c550976d988aa9c6
--- /dev/null
+++ b/requirements/base-1.16.in
@@ -0,0 +1,7 @@
+attrs
+zope.interface
+eliot
+aniso8601
+python-challenge-bypass-ristretto
+https://github.com/fenn-cs/tahoe-lafs/archive/f6a96ae3976ee21ad0376f7b6a22fc3d12110dce.tar.gz
+treq
diff --git a/requirements/base-1.16.txt b/requirements/base-1.16.txt
new file mode 100644
index 0000000000000000000000000000000000000000..5cc65c150025978042c2b51dd2cd793055a5aadf
--- /dev/null
+++ b/requirements/base-1.16.txt
@@ -0,0 +1,187 @@
+#
+# This file is autogenerated by pip-compile
+# To update, run:
+#
+#    pip-compile --allow-unsafe --output-file=requirements/base-1.16.txt requirements/base-1.16.in
+#
+aniso8601==9.0.1
+    # via -r requirements/base-1.16.in
+appdirs==1.4.4
+    # via twisted
+argparse==1.4.0
+    # via zfec
+attrs==21.2.0
+    # via
+    #   -r requirements/base-1.16.in
+    #   automat
+    #   magic-wormhole
+    #   python-challenge-bypass-ristretto
+    #   service-identity
+    #   tahoe-lafs
+    #   treq
+    #   twisted
+autobahn[twisted]==19.11.2
+    # via
+    #   magic-wormhole
+    #   tahoe-lafs
+automat==20.2.0
+    # via
+    #   magic-wormhole
+    #   twisted
+    #   txtorcon
+bcrypt==3.1.7
+    # via twisted
+boltons==21.0.0
+    # via eliot
+certifi==2021.5.30
+    # via requests
+cffi==1.14.6
+    # via
+    #   bcrypt
+    #   cryptography
+    #   pynacl
+    #   python-challenge-bypass-ristretto
+chardet==4.0.0
+    # via requests
+click==7.1.2
+    # via magic-wormhole
+configparser==4.0.2
+    # via tahoe-lafs
+constantly==15.1.0
+    # via twisted
+cryptography==3.3.2
+    # via
+    #   autobahn
+    #   pyopenssl
+    #   service-identity
+    #   tahoe-lafs
+    #   twisted
+    #   txtorcon
+distro==1.6.0
+    # via tahoe-lafs
+eliot==1.7.0
+    # via
+    #   -r requirements/base-1.16.in
+    #   tahoe-lafs
+enum34==1.1.10
+    # via cryptography
+foolscap==0.13.1
+    # via tahoe-lafs
+future==0.18.2
+    # via tahoe-lafs
+hkdf==0.0.3
+    # via
+    #   magic-wormhole
+    #   spake2
+humanize==1.0.0
+    # via magic-wormhole
+hyperlink==21.0.0
+    # via
+    #   treq
+    #   twisted
+idna==2.10
+    # via
+    #   hyperlink
+    #   requests
+    #   twisted
+incremental==21.3.0
+    # via
+    #   treq
+    #   twisted
+    #   txtorcon
+ipaddress==1.0.23
+    # via
+    #   cryptography
+    #   service-identity
+    #   txtorcon
+magic-wormhole==0.12.0
+    # via tahoe-lafs
+netifaces==0.11.0
+    # via tahoe-lafs
+pyasn1-modules==0.2.8
+    # via service-identity
+pyasn1==0.4.8
+    # via
+    #   pyasn1-modules
+    #   service-identity
+    #   twisted
+pycparser==2.20
+    # via cffi
+pyhamcrest==1.10.1
+    # via twisted
+pynacl==1.4.0
+    # via magic-wormhole
+pyopenssl==21.0.0
+    # via
+    #   foolscap
+    #   twisted
+pyrsistent==0.16.1
+    # via
+    #   eliot
+    #   tahoe-lafs
+python-challenge-bypass-ristretto==2021.7.12
+    # via -r requirements/base-1.16.in
+pyutil==3.3.0
+    # via tahoe-lafs
+pyyaml==5.4.1
+    # via tahoe-lafs
+requests==2.26.0
+    # via treq
+service-identity==21.1.0
+    # via twisted
+six==1.16.0
+    # via
+    #   autobahn
+    #   automat
+    #   bcrypt
+    #   cryptography
+    #   eliot
+    #   magic-wormhole
+    #   pyhamcrest
+    #   pynacl
+    #   pyopenssl
+    #   pyrsistent
+    #   service-identity
+    #   tahoe-lafs
+    #   treq
+    #   txaio
+spake2==0.8
+    # via magic-wormhole
+https://github.com/fenn-cs/tahoe-lafs/archive/f6a96ae3976ee21ad0376f7b6a22fc3d12110dce.tar.gz
+    # via -r requirements/base-1.16.in
+tqdm==4.62.3
+    # via magic-wormhole
+treq==21.1.0
+    # via -r requirements/base-1.16.in
+twisted[conch,tls]==20.3.0
+    # via
+    #   autobahn
+    #   foolscap
+    #   magic-wormhole
+    #   tahoe-lafs
+    #   treq
+    #   txtorcon
+txaio==18.8.1
+    # via autobahn
+txtorcon==21.1.0
+    # via magic-wormhole
+typing==3.10.0.0
+    # via hyperlink
+urllib3==1.26.7
+    # via requests
+zfec==1.5.5
+    # via tahoe-lafs
+zope.interface==5.4.0
+    # via
+    #   -r requirements/base-1.16.in
+    #   autobahn
+    #   eliot
+    #   tahoe-lafs
+    #   twisted
+    #   txtorcon
+
+# The following packages are considered to be unsafe in a requirements file:
+setuptools==44.1.1
+    # via
+    #   tahoe-lafs
+    #   zope.interface
diff --git a/requirements/base.in b/requirements/base.in
new file mode 100644
index 0000000000000000000000000000000000000000..1793016025136d40827452225a6f91bf1c805b40
--- /dev/null
+++ b/requirements/base.in
@@ -0,0 +1,9 @@
+attrs
+zope.interface
+eliot
+aniso8601
+python-challenge-bypass-ristretto
+tahoe-lafs >=1.14, <1.15
+treq
+pyutil
+
diff --git a/requirements/base.txt b/requirements/base.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9a97c820a1161a1aee451b2fb50ea9312b542d72
--- /dev/null
+++ b/requirements/base.txt
@@ -0,0 +1,177 @@
+#
+# This file is autogenerated by pip-compile
+# To update, run:
+#
+#    pip-compile --output-file=requirements/base.txt requirements/base.in
+#
+aniso8601==9.0.1
+    # via -r requirements/base.in
+appdirs==1.4.4
+    # via twisted
+argparse==1.4.0
+    # via zfec
+attrs==21.2.0
+    # via
+    #   -r requirements/base.in
+    #   automat
+    #   magic-wormhole
+    #   python-challenge-bypass-ristretto
+    #   service-identity
+    #   tahoe-lafs
+    #   treq
+    #   twisted
+autobahn[twisted]==19.11.2
+    # via
+    #   magic-wormhole
+    #   tahoe-lafs
+automat==20.2.0
+    # via
+    #   magic-wormhole
+    #   twisted
+    #   txtorcon
+bcrypt==3.1.7
+    # via twisted
+boltons==21.0.0
+    # via eliot
+certifi==2021.5.30
+    # via requests
+cffi==1.14.6
+    # via
+    #   bcrypt
+    #   cryptography
+    #   pynacl
+    #   python-challenge-bypass-ristretto
+chardet==4.0.0
+    # via requests
+click==7.1.2
+    # via magic-wormhole
+constantly==15.1.0
+    # via twisted
+cryptography==3.3.2
+    # via
+    #   autobahn
+    #   pyopenssl
+    #   service-identity
+    #   tahoe-lafs
+    #   twisted
+    #   txtorcon
+eliot==1.7.0
+    # via
+    #   -r requirements/base.in
+    #   tahoe-lafs
+enum34==1.1.10
+    # via cryptography
+foolscap==0.13.1
+    # via tahoe-lafs
+hkdf==0.0.3
+    # via
+    #   magic-wormhole
+    #   spake2
+humanize==1.0.0
+    # via magic-wormhole
+hyperlink==21.0.0
+    # via
+    #   treq
+    #   twisted
+idna==2.10
+    # via
+    #   hyperlink
+    #   requests
+    #   twisted
+incremental==21.3.0
+    # via
+    #   treq
+    #   twisted
+    #   txtorcon
+ipaddress==1.0.23
+    # via
+    #   cryptography
+    #   service-identity
+    #   txtorcon
+magic-wormhole==0.12.0
+    # via tahoe-lafs
+nevow==0.14.5
+    # via tahoe-lafs
+pyasn1-modules==0.2.8
+    # via service-identity
+pyasn1==0.4.8
+    # via
+    #   pyasn1-modules
+    #   service-identity
+    #   twisted
+pycparser==2.20
+    # via cffi
+pyhamcrest==1.10.1
+    # via twisted
+pynacl==1.4.0
+    # via magic-wormhole
+pyopenssl==21.0.0
+    # via
+    #   foolscap
+    #   twisted
+pyrsistent==0.16.1
+    # via eliot
+python-challenge-bypass-ristretto==2021.7.12
+    # via -r requirements/base.in
+pyutil==3.3.0
+    # via -r requirements/base.in
+pyyaml==5.4.1
+    # via tahoe-lafs
+requests==2.26.0
+    # via treq
+service-identity==21.1.0
+    # via twisted
+six==1.16.0
+    # via
+    #   autobahn
+    #   automat
+    #   bcrypt
+    #   cryptography
+    #   eliot
+    #   magic-wormhole
+    #   pyhamcrest
+    #   pynacl
+    #   pyopenssl
+    #   pyrsistent
+    #   service-identity
+    #   tahoe-lafs
+    #   treq
+    #   txaio
+spake2==0.8
+    # via magic-wormhole
+tahoe-lafs==1.14.0
+    # via -r requirements/base.in
+tqdm==4.62.3
+    # via magic-wormhole
+treq==21.1.0
+    # via -r requirements/base.in
+twisted[conch,tls]==20.3.0
+    # via
+    #   autobahn
+    #   foolscap
+    #   magic-wormhole
+    #   nevow
+    #   tahoe-lafs
+    #   treq
+    #   txtorcon
+txaio==18.8.1
+    # via autobahn
+txtorcon==21.1.0
+    # via magic-wormhole
+typing==3.10.0.0
+    # via hyperlink
+urllib3==1.26.7
+    # via requests
+zfec==1.5.5
+    # via tahoe-lafs
+zope.interface==5.4.0
+    # via
+    #   -r requirements/base.in
+    #   autobahn
+    #   eliot
+    #   tahoe-lafs
+    #   twisted
+    #   txtorcon
+
+# The following packages are considered to be unsafe in a requirements file:
+# setuptools
diff --git a/requirements/test.in b/requirements/test.in
new file mode 100644
index 0000000000000000000000000000000000000000..a3b94c25433c8adcb4b30a9a3a9e4ba47b54cb16
--- /dev/null
+++ b/requirements/test.in
@@ -0,0 +1,4 @@
+coverage
+fixtures
+testtools
+hypothesis
diff --git a/requirements/test.txt b/requirements/test.txt
new file mode 100644
index 0000000000000000000000000000000000000000..32088bf3897da5a031ecbd241d84a2af3a163ecd
--- /dev/null
+++ b/requirements/test.txt
@@ -0,0 +1,47 @@
+#
+# This file is autogenerated by pip-compile
+# To update, run:
+#
+#    pip-compile --allow-unsafe --output-file=requirements/test.txt --pip-args='-c requirements/base-1.16.txt -c requirements/base.txt' requirements/test.in
+#
+argparse==1.4.0
+    # via unittest2
+attrs==21.2.0
+    # via hypothesis
+coverage==5.5
+    # via -r requirements/test.in
+enum34==1.1.10
+    # via hypothesis
+extras==1.0.0
+    # via testtools
+fixtures==3.0.0
+    # via
+    #   -r requirements/test.in
+    #   testtools
+hypothesis==4.57.1
+    # via -r requirements/test.in
+linecache2==1.0.0
+    # via traceback2
+pbr==5.6.0
+    # via
+    #   fixtures
+    #   testtools
+python-mimeparse==1.6.0
+    # via testtools
+six==1.16.0
+    # via
+    #   fixtures
+    #   testtools
+    #   unittest2
+sortedcontainers==2.4.0
+    # via hypothesis
+testtools==2.4.0
+    # via
+    #   -r requirements/test.in
+    #   fixtures
+traceback2==1.4.0
+    # via
+    #   testtools
+    #   unittest2
+unittest2==1.1.0
+    # via testtools
diff --git a/scripts/pin-requirements b/scripts/pin-requirements
new file mode 100755
index 0000000000000000000000000000000000000000..c8db2cc331a1423be048705f4da6699f0867a12d
--- /dev/null
+++ b/scripts/pin-requirements
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+export PYTHONPATH=.
+pip-compile -q --allow-unsafe requirements/base.in -o requirements/base.txt
+pip-compile -q --allow-unsafe requirements/base-1.16.in -o requirements/base-1.16.txt
+pip-compile -q --allow-unsafe requirements/test.in -o requirements/test.txt --pip-args "-c requirements/base-1.16.txt -c requirements/base.txt"