diff --git a/.circleci/config.yml b/.circleci/config.yml
index 04f80ac8..312e6f66 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -4,10 +4,6 @@ workflows:
   version: 2
   build_test_deploy:
     jobs:
-      - pipenv_install_35:
-          filters:
-            tags:
-              only: /.*/
       - pipenv_install_36:
           filters:
             tags:
@@ -16,10 +12,6 @@ workflows:
           filters:
             tags:
               only: /.*/
-      - pip_install_35:
-          filters:
-            tags:
-              only: /.*/
       - pip_install_36:
           filters:
             tags:
@@ -28,13 +20,6 @@ workflows:
           filters:
             tags:
               only: /.*/
-      - run_tests_35:
-          filters:
-            tags:
-              only: /.*/
-          requires:
-            - pipenv_install_35
-            - pip_install_35
       - run_tests_36:
           filters:
             tags:
@@ -54,7 +39,6 @@ workflows:
             tags:
               only: /.*/
           requires:
-            - run_tests_35
             - run_tests_36
             - run_tests_37
       - doctests_36:
@@ -62,7 +46,6 @@ workflows:
             tags:
               only: /.*/
           requires:
-            - run_tests_35
             - run_tests_36
             - run_tests_37
       - notebook_tests_36:
@@ -70,7 +53,6 @@ workflows:
             tags:
               only: /.*/
           requires:
-            - run_tests_35
             - run_tests_36
             - run_tests_37
       - reencryption_memory_profile_36:
@@ -78,7 +60,6 @@ workflows:
             tags:
               only: /.*/
           requires:
-            - run_tests_35
             - run_tests_36
             - run_tests_37
       - reencryption_benchmark_36:
@@ -86,7 +67,6 @@ workflows:
             tags:
               only: /.*/
 #          requires:  # Commented this section to speed up the build
-#            - run_tests_35
 #            - run_tests_36
 #            - run_tests_37
       - test_deploy:
@@ -119,11 +99,6 @@ workflows:
             branches:
               ignore: /.*/
 
-python_35_base: &python_35_base
-    working_directory: ~/pyUmbral-35
-    docker:
-      - image: circleci/python:3.5
-
 python_36_base: &python_36_base
     working_directory: ~/pyUmbral-36
     docker:
@@ -190,12 +165,6 @@ commands:
 
 jobs:
 
-  pipenv_install_35:
-    <<: *python_35_base
-    steps:
-      - pipenv_install:
-          python_version: "3.5"
-
   pipenv_install_36:
     <<: *python_36_base
     steps:
@@ -208,11 +177,6 @@ jobs:
       - pipenv_install:
           python_version: "3.7"
 
-  pip_install_35:
-    <<: *python_35_base
-    steps:
-    - pip_install
-
   pip_install_36:
     <<: *python_36_base
     steps:
@@ -223,13 +187,6 @@ jobs:
     steps:
     - pip_install
 
-  run_tests_35:
-    <<: *python_35_base
-    parallelism: 4
-    steps:
-    - run_tests:
-        python_version: "3.5"
-
   run_tests_36:
     <<: *python_36_base
     parallelism: 4
diff --git a/.coveragerc b/.coveragerc
index 7ea406ea..cb912888 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -3,3 +3,11 @@ omit =
     setup.py,
     *__init__.py
 parallel=True
+
+[report]
+exclude_lines =
+    # Have to re-enable the standard pragma
+    pragma: no cover
+
+    # Exclude abstract methods
+    @abstractmethod
diff --git a/Pipfile b/Pipfile
index d9c3c7f6..a6e4579a 100644
--- a/Pipfile
+++ b/Pipfile
@@ -5,13 +5,8 @@ name = "pypi"
 
 [packages]
 setuptools = "*"
-# Third Party
 cryptography = ">=2.3"
 pynacl = "*"
-pysha3 = "*"
-# NuCypher
-bytestring-splitter = "*"
-constant-sorrow = ">=0.1.0a7"
 
 [dev-packages]
 bumpversion = "*"
diff --git a/Pipfile.lock b/Pipfile.lock
index b4d4b566..6e03671b 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "60c034332445dc29a4f621268c67609c5efb4b0d76ee7fa586ea8436cf70866a"
+            "sha256": "55db9d5f9de5a65ef3a43b7931e19d8a1ee2cb9cbfa9e616fa2c512f8576116b"
         },
         "pipfile-spec": 6,
         "requires": {},
@@ -14,157 +14,105 @@
         ]
     },
     "default": {
-        "asn1crypto": {
-            "hashes": [
-                "sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87",
-                "sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49"
-            ],
-            "version": "==0.24.0"
-        },
-        "bytestring-splitter": {
-            "hashes": [
-                "sha256:708f70a171fbd5d28f8374e18b8fe00d931bdc908069611cb866397a8fbb34e5",
-                "sha256:a681e2fc5a6bd8b083ce740fc4353b34492af63d0891e59c05cd0300f79f91d8"
-            ],
-            "index": "pypi",
-            "version": "==1.0.0a4"
-        },
         "cffi": {
             "hashes": [
-                "sha256:0b5f895714a7a9905148fc51978c62e8a6cbcace30904d39dcd0d9e2265bb2f6",
-                "sha256:27cdc7ba35ee6aa443271d11583b50815c4bb52be89a909d0028e86c21961709",
-                "sha256:2d4a38049ea93d5ce3c7659210393524c1efc3efafa151bd85d196fa98fce50a",
-                "sha256:3262573d0d60fc6b9d0e0e6e666db0e5045cbe8a531779aa0deb3b425ec5a282",
-                "sha256:358e96cfffc185ab8f6e7e425c7bb028931ed08d65402fbcf3f4e1bff6e66556",
-                "sha256:37c7db824b5687fbd7ea5519acfd054c905951acc53503547c86be3db0580134",
-                "sha256:39b9554dfe60f878e0c6ff8a460708db6e1b1c9cc6da2c74df2955adf83e355d",
-                "sha256:42b96a77acf8b2d06821600fa87c208046decc13bd22a4a0e65c5c973443e0da",
-                "sha256:5b37dde5035d3c219324cac0e69d96495970977f310b306fa2df5910e1f329a1",
-                "sha256:5d35819f5566d0dd254f273d60cf4a2dcdd3ae3003dfd412d40b3fe8ffd87509",
-                "sha256:5df73aa465e53549bd03c819c1bc69fb85529a5e1a693b7b6cb64408dd3970d1",
-                "sha256:7075b361f7a4d0d4165439992d0b8a3cdfad1f302bf246ed9308a2e33b046bd3",
-                "sha256:7678b5a667b0381c173abe530d7bdb0e6e3b98e062490618f04b80ca62686d96",
-                "sha256:7dfd996192ff8a535458c17f22ff5eb78b83504c34d10eefac0c77b1322609e2",
-                "sha256:8a3be5d31d02c60f84c4fd4c98c5e3a97b49f32e16861367f67c49425f955b28",
-                "sha256:9812e53369c469506b123aee9dcb56d50c82fad60c5df87feb5ff59af5b5f55c",
-                "sha256:9b6f7ba4e78c52c1a291d0c0c0bd745d19adde1a9e1c03cb899f0c6efd6f8033",
-                "sha256:a85bc1d7c3bba89b3d8c892bc0458de504f8b3bcca18892e6ed15b5f7a52ad9d",
-                "sha256:aa6b9c843ad645ebb12616de848cc4e25a40f633ccc293c3c9fe34107c02c2ea",
-                "sha256:bae1aa56ee00746798beafe486daa7cfb586cd395c6ce822ba3068e48d761bc0",
-                "sha256:bae96e26510e4825d5910a196bf6b5a11a18b87d9278db6d08413be8ea799469",
-                "sha256:bd78df3b594013b227bf31d0301566dc50ba6f40df38a70ded731d5a8f2cb071",
-                "sha256:c2711197154f46d06f73542c539a0ff5411f1951fab391e0a4ac8359badef719",
-                "sha256:d998c20e3deed234fca993fd6c8314cb7cbfda05fd170f1bd75bb5d7421c3c5a",
-                "sha256:df4f840d77d9e37136f8e6b432fecc9d6b8730f18f896e90628712c793466ce6",
-                "sha256:f5653c2581acb038319e6705d4e3593677676df14b112f13e0b5b44b6a18df1a",
-                "sha256:f7c7aa485a2e2250d455148470ffd0195eecc3d845122635202d7467d6f7b4cf",
-                "sha256:f9e2c66a6493147de835f207f198540a56b26745ce4f272fbc7c2f2cfebeb729"
-            ],
-            "version": "==1.12.1"
-        },
-        "constant-sorrow": {
-            "hashes": [
-                "sha256:0ab5ddacde6484e986703a523b721517b7230e395d2bfddea5eced7153acbf9c",
-                "sha256:8ff487bbc15d4ddcae7f0f745c56494f4267e90b20b19102d81b390803e14ded"
-            ],
-            "index": "pypi",
-            "version": "==0.1.0a8"
+                "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813",
+                "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06",
+                "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea",
+                "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee",
+                "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396",
+                "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73",
+                "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315",
+                "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1",
+                "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49",
+                "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892",
+                "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482",
+                "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058",
+                "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5",
+                "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53",
+                "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045",
+                "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3",
+                "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5",
+                "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e",
+                "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c",
+                "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369",
+                "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827",
+                "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053",
+                "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa",
+                "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4",
+                "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322",
+                "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132",
+                "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62",
+                "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa",
+                "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0",
+                "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396",
+                "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e",
+                "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991",
+                "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6",
+                "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1",
+                "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406",
+                "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d",
+                "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"
+            ],
+            "version": "==1.14.5"
         },
         "cryptography": {
             "hashes": [
-                "sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af",
-                "sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e",
-                "sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2",
-                "sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7",
-                "sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079",
-                "sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063",
-                "sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401",
-                "sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695",
-                "sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85",
-                "sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3",
-                "sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad",
-                "sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca",
-                "sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd",
-                "sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f",
-                "sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159",
-                "sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0",
-                "sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e",
-                "sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3",
-                "sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00"
+                "sha256:066bc53f052dfeda2f2d7c195cf16fb3e5ff13e1b6b7415b468514b40b381a5b",
+                "sha256:0923ba600d00718d63a3976f23cab19aef10c1765038945628cd9be047ad0336",
+                "sha256:2d32223e5b0ee02943f32b19245b61a62db83a882f0e76cc564e1cec60d48f87",
+                "sha256:4169a27b818de4a1860720108b55a2801f32b6ae79e7f99c00d79f2a2822eeb7",
+                "sha256:57ad77d32917bc55299b16d3b996ffa42a1c73c6cfa829b14043c561288d2799",
+                "sha256:5ecf2bcb34d17415e89b546dbb44e73080f747e504273e4d4987630493cded1b",
+                "sha256:600cf9bfe75e96d965509a4c0b2b183f74a4fa6f5331dcb40fb7b77b7c2484df",
+                "sha256:66b57a9ca4b3221d51b237094b0303843b914b7d5afd4349970bb26518e350b0",
+                "sha256:93cfe5b7ff006de13e1e89830810ecbd014791b042cbe5eec253be11ac2b28f3",
+                "sha256:9e98b452132963678e3ac6c73f7010fe53adf72209a32854d55690acac3f6724",
+                "sha256:df186fcbf86dc1ce56305becb8434e4b6b7504bc724b71ad7a3239e0c9d14ef2",
+                "sha256:fec7fb46b10da10d9e1d078d1ff8ed9e05ae14f431fdbd11145edd0550b9a964"
             ],
             "index": "pypi",
-            "version": "==2.5"
-        },
-        "msgpack-python": {
-            "hashes": [
-                "sha256:378cc8a6d3545b532dfd149da715abae4fda2a3adb6d74e525d0d5e51f46909b"
-            ],
-            "version": "==0.5.6"
+            "version": "==3.4.6"
         },
         "pycparser": {
             "hashes": [
-                "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"
+                "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
+                "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
             ],
-            "version": "==2.19"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==2.20"
         },
         "pynacl": {
             "hashes": [
-                "sha256:05c26f93964373fc0abe332676cb6735f0ecad27711035b9472751faa8521255",
-                "sha256:0c6100edd16fefd1557da078c7a31e7b7d7a52ce39fdca2bec29d4f7b6e7600c",
-                "sha256:0d0a8171a68edf51add1e73d2159c4bc19fc0718e79dec51166e940856c2f28e",
-                "sha256:1c780712b206317a746ace34c209b8c29dbfd841dfbc02aa27f2084dd3db77ae",
-                "sha256:2424c8b9f41aa65bbdbd7a64e73a7450ebb4aa9ddedc6a081e7afcc4c97f7621",
-                "sha256:2d23c04e8d709444220557ae48ed01f3f1086439f12dbf11976e849a4926db56",
-                "sha256:30f36a9c70450c7878053fa1344aca0145fd47d845270b43a7ee9192a051bf39",
-                "sha256:37aa336a317209f1bb099ad177fef0da45be36a2aa664507c5d72015f956c310",
-                "sha256:4943decfc5b905748f0756fdd99d4f9498d7064815c4cf3643820c9028b711d1",
-                "sha256:57ef38a65056e7800859e5ba9e6091053cd06e1038983016effaffe0efcd594a",
-                "sha256:5bd61e9b44c543016ce1f6aef48606280e45f892a928ca7068fba30021e9b786",
-                "sha256:6482d3017a0c0327a49dddc8bd1074cc730d45db2ccb09c3bac1f8f32d1eb61b",
-                "sha256:7d3ce02c0784b7cbcc771a2da6ea51f87e8716004512493a2b69016326301c3b",
-                "sha256:a14e499c0f5955dcc3991f785f3f8e2130ed504fa3a7f44009ff458ad6bdd17f",
-                "sha256:a39f54ccbcd2757d1d63b0ec00a00980c0b382c62865b61a505163943624ab20",
-                "sha256:aabb0c5232910a20eec8563503c153a8e78bbf5459490c49ab31f6adf3f3a415",
-                "sha256:bd4ecb473a96ad0f90c20acba4f0bf0df91a4e03a1f4dd6a4bdc9ca75aa3a715",
-                "sha256:e2da3c13307eac601f3de04887624939aca8ee3c9488a0bb0eca4fb9401fc6b1",
-                "sha256:f67814c38162f4deb31f68d590771a29d5ae3b1bd64b75cf232308e5c74777e0"
+                "sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4",
+                "sha256:11335f09060af52c97137d4ac54285bcb7df0cef29014a1a4efe64ac065434c4",
+                "sha256:2fe0fc5a2480361dcaf4e6e7cea00e078fcda07ba45f811b167e3f99e8cff574",
+                "sha256:30f9b96db44e09b3304f9ea95079b1b7316b2b4f3744fe3aaecccd95d547063d",
+                "sha256:4e10569f8cbed81cb7526ae137049759d2a8d57726d52c1a000a3ce366779634",
+                "sha256:511d269ee845037b95c9781aa702f90ccc36036f95d0f31373a6a79bd8242e25",
+                "sha256:537a7ccbea22905a0ab36ea58577b39d1fa9b1884869d173b5cf111f006f689f",
+                "sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505",
+                "sha256:757250ddb3bff1eecd7e41e65f7f833a8405fede0194319f87899690624f2122",
+                "sha256:7757ae33dae81c300487591c68790dfb5145c7d03324000433d9a2c141f82af7",
+                "sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420",
+                "sha256:8122ba5f2a2169ca5da936b2e5a511740ffb73979381b4229d9188f6dcb22f1f",
+                "sha256:9c4a7ea4fb81536c1b1f5cc44d54a296f96ae78c1ebd2311bd0b60be45a48d96",
+                "sha256:c914f78da4953b33d4685e3cdc7ce63401247a21425c16a39760e282075ac4a6",
+                "sha256:cd401ccbc2a249a47a3a1724c2918fcd04be1f7b54eb2a5a71ff915db0ac51c6",
+                "sha256:d452a6746f0a7e11121e64625109bc4468fc3100452817001dbe018bb8b08514",
+                "sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff",
+                "sha256:f8851ab9041756003119368c1e6cd0b9c631f46d686b3904b18c0139f4419f80"
             ],
             "index": "pypi",
-            "version": "==1.3.0"
-        },
-        "pysha3": {
-            "hashes": [
-                "sha256:0060a66be16665d90c432f55a0ba1f6480590cfb7d2ad389e688a399183474f0",
-                "sha256:11a2ba7a2e1d9669d0052fc8fb30f5661caed5512586ecbeeaf6bf9478ab5c48",
-                "sha256:386998ee83e313b6911327174e088021f9f2061cbfa1651b97629b761e9ef5c4",
-                "sha256:41be70b06c8775a9e4d4eeb52f2f6a3f356f17539a54eac61f43a29e42fd453d",
-                "sha256:4416f16b0f1605c25f627966f76873e432971824778b369bd9ce1bb63d6566d9",
-                "sha256:571a246308a7b63f15f5aa9651f99cf30f2a6acba18eddf28f1510935968b603",
-                "sha256:59111c08b8f34495575d12e5f2ce3bafb98bea470bc81e70c8b6df99aef0dd2f",
-                "sha256:5ec8da7c5c70a53b5fa99094af3ba8d343955b212bc346a0d25f6ff75853999f",
-                "sha256:684cb01d87ed6ff466c135f1c83e7e4042d0fc668fa20619f581e6add1d38d77",
-                "sha256:68c3a60a39f9179b263d29e221c1bd6e01353178b14323c39cc70593c30f21c5",
-                "sha256:6e6a84efb7856f5d760ee55cd2b446972cb7b835676065f6c4f694913ea8f8d9",
-                "sha256:827b308dc025efe9b6b7bae36c2e09ed0118a81f792d888548188e97b9bf9a3d",
-                "sha256:93abd775dac570cb9951c4e423bcb2bc6303a9d1dc0dc2b7afa2dd401d195b24",
-                "sha256:9c778fa8b161dc9348dc5cc361e94d54aa5ff18413788f4641f6600d4893a608",
-                "sha256:9fdd28884c5d0b4edfed269b12badfa07f1c89dbc5c9c66dd279833894a9896b",
-                "sha256:c7c2adcc43836223680ebdf91f1d3373543dc32747c182c8ca2e02d1b69ce030",
-                "sha256:c93a2676e6588abcfaecb73eb14485c81c63b94fca2000a811a7b4fb5937b8e8",
-                "sha256:cd5c961b603bd2e6c2b5ef9976f3238a561c58569945d4165efb9b9383b050ef",
-                "sha256:f9046d59b3e72aa84f6dae83a040bd1184ebd7fef4e822d38186a8158c89e3cf",
-                "sha256:fd7e66999060d079e9c0e8893e78d8017dad4f59721f6fe0be6307cd32127a07",
-                "sha256:fe988e73f2ce6d947220624f04d467faf05f1bbdbc64b0a201296bb3af92739e"
-            ],
-            "index": "pypi",
-            "version": "==1.0.2"
+            "version": "==1.4.0"
         },
         "six": {
             "hashes": [
-                "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
-                "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
+                "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
+                "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
             ],
-            "version": "==1.12.0"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==1.15.0"
         }
     },
     "develop": {
@@ -177,170 +125,209 @@
         },
         "appnope": {
             "hashes": [
-                "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0",
-                "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"
-            ],
-            "markers": "sys_platform == 'darwin'",
-            "version": "==0.1.0"
-        },
-        "argh": {
-            "hashes": [
-                "sha256:a9b3aaa1904eeb78e32394cd46c6f37ac0fb4af6dc488daa58971bdc7d7fcaf3",
-                "sha256:e9535b8c84dc9571a48999094fda7f33e63c3f1b74f3e5f3ac0105a58405bb65"
+                "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442",
+                "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"
             ],
-            "version": "==0.26.2"
-        },
-        "atomicwrites": {
-            "hashes": [
-                "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4",
-                "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"
-            ],
-            "version": "==1.3.0"
+            "markers": "sys_platform == 'darwin' and platform_system == 'Darwin'",
+            "version": "==0.1.2"
         },
         "attrs": {
             "hashes": [
-                "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69",
-                "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb"
+                "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6",
+                "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"
             ],
-            "version": "==18.2.0"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==20.3.0"
         },
         "babel": {
             "hashes": [
-                "sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669",
-                "sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23"
+                "sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5",
+                "sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05"
             ],
-            "version": "==2.6.0"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==2.9.0"
         },
         "backcall": {
             "hashes": [
-                "sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4",
-                "sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2"
+                "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e",
+                "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"
             ],
-            "version": "==0.1.0"
+            "version": "==0.2.0"
+        },
+        "bump2version": {
+            "hashes": [
+                "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410",
+                "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6"
+            ],
+            "markers": "python_version >= '3.5'",
+            "version": "==1.0.1"
         },
         "bumpversion": {
             "hashes": [
-                "sha256:6744c873dd7aafc24453d8b6a1a0d6d109faf63cd0cd19cb78fd46e74932c77e",
-                "sha256:6753d9ff3552013e2130f7bc03c1007e24473b4835952679653fb132367bdd57"
+                "sha256:4ba55e4080d373f80177b4dabef146c07ce73c7d1377aabf9d3c3ae1f94584a6",
+                "sha256:4eb3267a38194d09f048a2179980bb4803701969bff2c85fa8f6d1ce050be15e"
             ],
             "index": "pypi",
-            "version": "==0.5.3"
+            "version": "==0.6.0"
         },
         "certifi": {
             "hashes": [
-                "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7",
-                "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033"
+                "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
+                "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
             ],
-            "version": "==2018.11.29"
+            "version": "==2020.12.5"
         },
         "chardet": {
             "hashes": [
-                "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
-                "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
+                "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
+                "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
             ],
-            "version": "==3.0.4"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+            "version": "==4.0.0"
         },
-        "click": {
+        "codecov": {
             "hashes": [
-                "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
-                "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
+                "sha256:6cde272454009d27355f9434f4e49f238c0273b216beda8472a65dc4957f473b",
+                "sha256:ba8553a82942ce37d4da92b70ffd6d54cf635fc1793ab0a7dc3fecd6ebfb3df8",
+                "sha256:e95901d4350e99fc39c8353efa450050d2446c55bac91d90fcfd2354e19a6aef"
             ],
-            "version": "==7.0"
+            "index": "pypi",
+            "version": "==2.1.11"
         },
-        "codecov": {
+        "colorama": {
             "hashes": [
-                "sha256:8ed8b7c6791010d359baed66f84f061bba5bd41174bf324c31311e8737602788",
-                "sha256:ae00d68e18d8a20e9c3288ba3875ae03db3a8e892115bf9b83ef20507732bed4"
+                "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
+                "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
             ],
-            "index": "pypi",
-            "version": "==2.0.15"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+            "version": "==0.4.4"
         },
         "coverage": {
             "hashes": [
-                "sha256:029c69deaeeeae1b15bc6c59f0ffa28aa8473721c614a23f2c2976dec245cd12",
-                "sha256:02abbbebc6e9d5abe13cd28b5e963dedb6ffb51c146c916d17b18f141acd9947",
-                "sha256:1bbfe5b82a3921d285e999c6d256c1e16b31c554c29da62d326f86c173d30337",
-                "sha256:210c02f923df33a8d0e461c86fdcbbb17228ff4f6d92609fc06370a98d283c2d",
-                "sha256:2d0807ba935f540d20b49d5bf1c0237b90ce81e133402feda906e540003f2f7a",
-                "sha256:35d7a013874a7c927ce997350d314144ffc5465faf787bb4e46e6c4f381ef562",
-                "sha256:3636f9d0dcb01aed4180ef2e57a4e34bb4cac3ecd203c2a23db8526d86ab2fb4",
-                "sha256:42f4be770af2455a75e4640f033a82c62f3fb0d7a074123266e143269d7010ef",
-                "sha256:48440b25ba6cda72d4c638f3a9efa827b5b87b489c96ab5f4ff597d976413156",
-                "sha256:4dac8dfd1acf6a3ac657475dfdc66c621f291b1b7422a939cc33c13ac5356473",
-                "sha256:4e8474771c69c2991d5eab65764289a7dd450bbea050bc0ebb42b678d8222b42",
-                "sha256:551f10ddfeff56a1325e5a34eff304c5892aa981fd810babb98bfee77ee2fb17",
-                "sha256:5b104982f1809c1577912519eb249f17d9d7e66304ad026666cb60a5ef73309c",
-                "sha256:5c62aef73dfc87bfcca32cee149a1a7a602bc74bac72223236b0023543511c88",
-                "sha256:633151f8d1ad9467b9f7e90854a7f46ed8f2919e8bc7d98d737833e8938fc081",
-                "sha256:772207b9e2d5bf3f9d283b88915723e4e92d9a62c83f44ec92b9bd0cd685541b",
-                "sha256:7d5e02f647cd727afc2659ec14d4d1cc0508c47e6cfb07aea33d7aa9ca94d288",
-                "sha256:a9798a4111abb0f94584000ba2a2c74841f2cfe5f9254709756367aabbae0541",
-                "sha256:b38ea741ab9e35bfa7015c93c93bbd6a1623428f97a67083fc8ebd366238b91f",
-                "sha256:b6a5478c904236543c0347db8a05fac6fc0bd574c870e7970faa88e1d9890044",
-                "sha256:c6248bfc1de36a3844685a2e10ba17c18119ba6252547f921062a323fb31bff1",
-                "sha256:c705ab445936457359b1424ef25ccc0098b0491b26064677c39f1d14a539f056",
-                "sha256:d95a363d663ceee647291131dbd213af258df24f41350246842481ec3709bd33",
-                "sha256:e27265eb80cdc5dab55a40ef6f890e04ecc618649ad3da5265f128b141f93f78",
-                "sha256:ebc276c9cb5d917bd2ae959f84ffc279acafa9c9b50b0fa436ebb70bbe2166ea",
-                "sha256:f4d229866d030863d0fe3bf297d6d11e6133ca15bbb41ed2534a8b9a3d6bd061",
-                "sha256:f95675bd88b51474d4fe5165f3266f419ce754ffadfb97f10323931fa9ac95e5",
-                "sha256:f95bc54fb6d61b9f9ff09c4ae8ff6a3f5edc937cda3ca36fc937302a7c152bf1",
-                "sha256:fd0f6be53de40683584e5331c341e65a679dbe5ec489a0697cec7c2ef1a48cda"
+                "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c",
+                "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6",
+                "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45",
+                "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a",
+                "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03",
+                "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529",
+                "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a",
+                "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a",
+                "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2",
+                "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6",
+                "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759",
+                "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53",
+                "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a",
+                "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4",
+                "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff",
+                "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502",
+                "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793",
+                "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb",
+                "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905",
+                "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821",
+                "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b",
+                "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81",
+                "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0",
+                "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b",
+                "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3",
+                "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184",
+                "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701",
+                "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a",
+                "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82",
+                "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638",
+                "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5",
+                "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083",
+                "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6",
+                "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90",
+                "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465",
+                "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a",
+                "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3",
+                "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e",
+                "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066",
+                "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf",
+                "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b",
+                "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae",
+                "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669",
+                "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873",
+                "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b",
+                "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6",
+                "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb",
+                "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160",
+                "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c",
+                "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079",
+                "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d",
+                "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"
             ],
             "index": "pypi",
-            "version": "==5.0a4"
+            "version": "==5.5"
         },
         "decorator": {
             "hashes": [
-                "sha256:33cd704aea07b4c28b3eb2c97d288a06918275dac0ecebdaf1bc8a48d98adb9e",
-                "sha256:cabb249f4710888a2fc0e13e9a16c343d932033718ff62e1e9bc93a9d3a9122b"
+                "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760",
+                "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"
             ],
-            "version": "==4.3.2"
+            "version": "==4.4.2"
         },
         "docutils": {
             "hashes": [
-                "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
-                "sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274",
-                "sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6"
+                "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af",
+                "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"
+            ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+            "version": "==0.16"
+        },
+        "filelock": {
+            "hashes": [
+                "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59",
+                "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"
             ],
-            "version": "==0.14"
+            "version": "==3.0.12"
         },
         "hypothesis": {
             "hashes": [
-                "sha256:7f97f984d45f22b8895ee4d98c7f48e7ebc2c2fbf9d5ce07dad77989997821f6",
-                "sha256:cb8c89295c36db0014409c8d7c897094f538cb880a25e3dc3e86fb768787a03d",
-                "sha256:e83442d2f74ceb1d166e351308810b394802fd9758c3b89ae36ca8daadac9fd4"
+                "sha256:2dd38676402d1c218225210cde0cf19f286352279f32631ac5c801f5d767bc94",
+                "sha256:3b7d9f7e40e406b550d4fd26fef0ce3fad216f163a3400ab701329b865e25876"
             ],
             "index": "pypi",
-            "version": "==4.6.1"
+            "version": "==6.8.1"
         },
         "idna": {
             "hashes": [
-                "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
-                "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
+                "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
+                "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
             ],
-            "version": "==2.8"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==2.10"
         },
         "imagesize": {
             "hashes": [
-                "sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8",
-                "sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5"
+                "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1",
+                "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"
             ],
-            "version": "==1.1.0"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==1.2.0"
+        },
+        "iniconfig": {
+            "hashes": [
+                "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
+                "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
+            ],
+            "version": "==1.1.1"
         },
         "ipykernel": {
             "hashes": [
-                "sha256:0aeb7ec277ac42cc2b59ae3d08b10909b2ec161dc6908096210527162b53675d",
-                "sha256:0fc0bf97920d454102168ec2008620066878848fcfca06c22b669696212e292f"
+                "sha256:98321abefdf0505fb3dc7601f60fc4087364d394bd8fad53107eb1adee9ff475",
+                "sha256:efd07253b54d84d26e0878d268c8c3a41582a18750da633c2febfd2ece0d467d"
             ],
-            "version": "==5.1.0"
+            "markers": "python_version >= '3.5'",
+            "version": "==5.5.0"
         },
         "ipython": {
             "hashes": [
-                "sha256:06de667a9e406924f97781bda22d5d76bfb39762b678762d86a466e63f65dc39",
-                "sha256:5d3e020a6b5f29df037555e5c45ab1088d6a7cf3bd84f47e0ba501eeb0c3ec82"
+                "sha256:04323f72d5b85b606330b6d7e2dc8d2683ad46c3905e955aa96ecc7a99388e70",
+                "sha256:34207ffb2f653bced2bc8e3756c1db86e7d93e44ed049daae9814fed66d408ec"
             ],
-            "version": "==7.3.0"
+            "markers": "python_version >= '3.7'",
+            "version": "==7.21.0"
         },
         "ipython-genutils": {
             "hashes": [
@@ -351,167 +338,205 @@
         },
         "jedi": {
             "hashes": [
-                "sha256:571702b5bd167911fe9036e5039ba67f820d6502832285cde8c881ab2b2149fd",
-                "sha256:c8481b5e59d34a5c7c42e98f6625e633f6ef59353abea6437472c7ec2093f191"
+                "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93",
+                "sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707"
             ],
-            "version": "==0.13.2"
+            "markers": "python_version >= '3.6'",
+            "version": "==0.18.0"
         },
         "jinja2": {
             "hashes": [
-                "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
-                "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
+                "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419",
+                "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"
             ],
-            "version": "==2.10"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+            "version": "==2.11.3"
         },
         "jsonschema": {
             "hashes": [
-                "sha256:683fe7ed58763ea0be572de5aad47cd3cc1297640916f9a8ccd222b287da7d2f",
-                "sha256:b42d7a292addb57370e6260bcbadb77e00a899fe6ec998c453f45893c41c658b"
+                "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163",
+                "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"
             ],
-            "version": "==3.0.0b3"
+            "version": "==3.2.0"
         },
         "jupyter-client": {
             "hashes": [
-                "sha256:b5f9cb06105c1d2d30719db5ffb3ea67da60919fb68deaefa583deccd8813551",
-                "sha256:c44411eb1463ed77548bc2d5ec0d744c9b81c4a542d9637c7a52824e2121b987"
+                "sha256:c4bca1d0846186ca8be97f4d2fa6d2bae889cce4892a167ffa1ba6bd1f73e782",
+                "sha256:e053a2c44b6fa597feebe2b3ecb5eea3e03d1d91cc94351a52931ee1426aecfc"
             ],
-            "version": "==5.2.4"
+            "markers": "python_version >= '3.5'",
+            "version": "==6.1.12"
         },
         "jupyter-core": {
             "hashes": [
-                "sha256:927d713ffa616ea11972534411544589976b2493fc7e09ad946e010aa7eb9970",
-                "sha256:ba70754aa680300306c699790128f6fbd8c306ee5927976cbe48adacf240c0b7"
+                "sha256:79025cb3225efcd36847d0840f3fc672c0abd7afd0de83ba8a1d3837619122b4",
+                "sha256:8c6c0cac5c1b563622ad49321d5ec47017bd18b94facb381c6973a0486395f8e"
+            ],
+            "markers": "python_version >= '3.6'",
+            "version": "==4.7.1"
+        },
+        "libcst": {
+            "hashes": [
+                "sha256:2766671c107263daa3fc34e39d55134a6fe253701564d7670586f30eee2c201c",
+                "sha256:4638e4e8f166f4c74df399222d347ce3e1d316e206b550d8a6254d51b4cf7275"
             ],
-            "version": "==4.4.0"
+            "markers": "python_version >= '3.6'",
+            "version": "==0.3.17"
         },
         "livereload": {
             "hashes": [
-                "sha256:29cadfabcedd12eed792e0131991235b9d4764d4474bed75cf525f57109ec0a2",
-                "sha256:e632a6cd1d349155c1d7f13a65be873b38f43ef02961804a1bba8d817fa649a7"
+                "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"
             ],
-            "version": "==2.6.0"
+            "version": "==2.6.3"
         },
         "markupsafe": {
             "hashes": [
-                "sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432",
-                "sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b",
-                "sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9",
-                "sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af",
-                "sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834",
-                "sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd",
-                "sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d",
-                "sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7",
-                "sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b",
-                "sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3",
-                "sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c",
-                "sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2",
-                "sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7",
-                "sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36",
-                "sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1",
-                "sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e",
-                "sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1",
-                "sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c",
-                "sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856",
-                "sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550",
-                "sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492",
-                "sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672",
-                "sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401",
-                "sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6",
-                "sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6",
-                "sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c",
-                "sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd",
-                "sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1"
-            ],
-            "version": "==1.1.0"
+                "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
+                "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
+                "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
+                "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
+                "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
+                "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f",
+                "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39",
+                "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
+                "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
+                "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014",
+                "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f",
+                "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
+                "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
+                "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
+                "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
+                "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
+                "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
+                "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
+                "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
+                "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85",
+                "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1",
+                "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
+                "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
+                "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
+                "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850",
+                "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0",
+                "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
+                "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
+                "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb",
+                "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
+                "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
+                "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
+                "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1",
+                "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2",
+                "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
+                "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
+                "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
+                "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7",
+                "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
+                "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8",
+                "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
+                "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193",
+                "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
+                "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b",
+                "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
+                "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
+                "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5",
+                "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c",
+                "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032",
+                "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
+                "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be",
+                "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"
+            ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==1.1.1"
         },
         "mock": {
             "hashes": [
-                "sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1",
-                "sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba"
+                "sha256:122fcb64ee37cfad5b3f48d7a7d51875d7031aaf3d8be7c42e2bee25044eee62",
+                "sha256:7d3fbbde18228f4ff2f1f119a45cdffa458b4c0dee32eb4d2bb2f82554bac7bc"
             ],
             "index": "pypi",
-            "version": "==2.0.0"
+            "version": "==4.0.3"
         },
         "monkeytype": {
             "hashes": [
-                "sha256:baeeee422c17202038ccf17ca73eb97eddb65a4178a215c1ff212cfb7373eb65",
-                "sha256:ecee4162a153c8a0d2151dfc66f06ebb82e4582b0d46281798d908888bb0c9b9"
+                "sha256:b8ed88485d2ffb05fb1597a6e5eacb05ba5420de682054403c06fac84fdc4038",
+                "sha256:fe596bebc5e1b6a64eae71a40b880688de433e4f70507a31ada48510195251dd"
             ],
             "index": "pypi",
-            "version": "==19.1.1"
-        },
-        "more-itertools": {
-            "hashes": [
-                "sha256:0125e8f60e9e031347105eb1682cef932f5e97d7b9a1a28d9bf00c22a5daef40",
-                "sha256:590044e3942351a1bdb1de960b739ff4ce277960f2425ad4509446dbace8d9d1"
-            ],
-            "markers": "python_version > '2.7'",
-            "version": "==6.0.0"
+            "version": "==20.5.0"
         },
         "mypy": {
             "hashes": [
-                "sha256:308c274eb8482fbf16006f549137ddc0d69e5a589465e37b99c4564414363ca7",
-                "sha256:e80fd6af34614a0e898a57f14296d0dacb584648f0339c2e000ddbf0f4cc2f8d"
+                "sha256:0d0a87c0e7e3a9becdfbe936c981d32e5ee0ccda3e0f07e1ef2c3d1a817cf73e",
+                "sha256:25adde9b862f8f9aac9d2d11971f226bd4c8fbaa89fb76bdadb267ef22d10064",
+                "sha256:28fb5479c494b1bab244620685e2eb3c3f988d71fd5d64cc753195e8ed53df7c",
+                "sha256:2f9b3407c58347a452fc0736861593e105139b905cca7d097e413453a1d650b4",
+                "sha256:33f159443db0829d16f0a8d83d94df3109bb6dd801975fe86bacb9bf71628e97",
+                "sha256:3f2aca7f68580dc2508289c729bd49ee929a436208d2b2b6aab15745a70a57df",
+                "sha256:499c798053cdebcaa916eef8cd733e5584b5909f789de856b482cd7d069bdad8",
+                "sha256:4eec37370483331d13514c3f55f446fc5248d6373e7029a29ecb7b7494851e7a",
+                "sha256:552a815579aa1e995f39fd05dde6cd378e191b063f031f2acfe73ce9fb7f9e56",
+                "sha256:5873888fff1c7cf5b71efbe80e0e73153fe9212fafdf8e44adfe4c20ec9f82d7",
+                "sha256:61a3d5b97955422964be6b3baf05ff2ce7f26f52c85dd88db11d5e03e146a3a6",
+                "sha256:674e822aa665b9fd75130c6c5f5ed9564a38c6cea6a6432ce47eafb68ee578c5",
+                "sha256:7ce3175801d0ae5fdfa79b4f0cfed08807af4d075b402b7e294e6aa72af9aa2a",
+                "sha256:9743c91088d396c1a5a3c9978354b61b0382b4e3c440ce83cf77994a43e8c521",
+                "sha256:9f94aac67a2045ec719ffe6111df543bac7874cee01f41928f6969756e030564",
+                "sha256:a26f8ec704e5a7423c8824d425086705e381b4f1dfdef6e3a1edab7ba174ec49",
+                "sha256:abf7e0c3cf117c44d9285cc6128856106183938c68fd4944763003decdcfeb66",
+                "sha256:b09669bcda124e83708f34a94606e01b614fa71931d356c1f1a5297ba11f110a",
+                "sha256:cd07039aa5df222037005b08fbbfd69b3ab0b0bd7a07d7906de75ae52c4e3119",
+                "sha256:d23e0ea196702d918b60c8288561e722bf437d82cb7ef2edcd98cfa38905d506",
+                "sha256:d65cc1df038ef55a99e617431f0553cd77763869eebdf9042403e16089fe746c",
+                "sha256:d7da2e1d5f558c37d6e8c1246f1aec1e7349e4913d8fb3cb289a35de573fe2eb"
             ],
             "index": "pypi",
-            "version": "==0.670"
+            "version": "==0.812"
         },
         "mypy-extensions": {
             "hashes": [
-                "sha256:37e0e956f41369209a3d5f34580150bcacfabaa57b33a15c0b25f4b5725e0812",
-                "sha256:b16cabe759f55e3409a7d231ebd2841378fb0c27a5d1994719e340e4f429ac3e"
+                "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
+                "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
             ],
-            "version": "==0.4.1"
+            "version": "==0.4.3"
         },
         "nbformat": {
             "hashes": [
-                "sha256:b9a0dbdbd45bb034f4f8893cafd6f652ea08c8c1674ba83f2dc55d3955743b0b",
-                "sha256:f7494ef0df60766b7cabe0a3651556345a963b74dbc16bc7c18479041170d402"
+                "sha256:1d223e64a18bfa7cdf2db2e9ba8a818312fc2a0701d2e910b58df66809385a56",
+                "sha256:3949fdc8f5fa0b1afca16fb307546e78494fa7a7bceff880df8168eafda0e7ac"
             ],
-            "version": "==4.4.0"
+            "markers": "python_version >= '3.5'",
+            "version": "==5.1.2"
         },
         "nbval": {
             "hashes": [
-                "sha256:3f18b87af4e94ccd073263dd58cd3eebabe9f5e4d6ab535b39d3af64811c7eda",
-                "sha256:74ff5e2c90a50b1ddf7edd02978c4e43221b1ee252dc14fcaa4230aae4492eda"
+                "sha256:4f9b780997d8942408853513f2c5ee6c1863de193559fc3f95e1c1cde8110439",
+                "sha256:cfefcd2ef66ee2d337d0b252c6bcec4023384eb32e8b9e5fcc3ac80ab8cd7d40"
             ],
             "index": "pypi",
-            "version": "==0.9.1"
+            "version": "==0.9.6"
         },
         "packaging": {
             "hashes": [
-                "sha256:0c98a5d0be38ed775798ece1b9727178c4469d9c3b4ada66e8e6b7849f8732af",
-                "sha256:9e1cbf8c12b1f1ce0bb5344b8d7ecf66a6f8a6e91bcb0c84593ed6d3ab5c4ab3"
+                "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5",
+                "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"
             ],
-            "version": "==19.0"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==20.9"
         },
         "parso": {
             "hashes": [
-                "sha256:4580328ae3f548b358f4901e38c0578229186835f0fa0846e47369796dd5bcc9",
-                "sha256:68406ebd7eafe17f8e40e15a84b56848eccbf27d7c1feb89e93d8fca395706db"
+                "sha256:15b00182f472319383252c18d5913b69269590616c947747bc50bf4ac768f410",
+                "sha256:8519430ad07087d4c997fda3a7918f7cfa27cb58972a8c89c2a0295a1c940e9e"
             ],
-            "version": "==0.3.4"
-        },
-        "pathtools": {
-            "hashes": [
-                "sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0"
-            ],
-            "version": "==0.1.2"
-        },
-        "pbr": {
-            "hashes": [
-                "sha256:a7953f66e1f82e4b061f43096a4bcc058f7d3d41de9b94ac871770e8bdd831a2",
-                "sha256:d717573351cfe09f49df61906cd272abaa759b3e91744396b804965ff7bff38b"
-            ],
-            "version": "==5.1.2"
+            "markers": "python_version >= '3.6'",
+            "version": "==0.8.1"
         },
         "pexpect": {
             "hashes": [
-                "sha256:2a8e88259839571d1251d278476f3eec5db26deb73a70be5ed5dc5435e418aba",
-                "sha256:3fbd41d4caf27fa4a377bfd16fef87271099463e6fa73e92a52f92dfee5d425b"
+                "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937",
+                "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"
             ],
             "markers": "sys_platform != 'win32'",
-            "version": "==4.6.0"
+            "version": "==4.8.0"
         },
         "pickleshare": {
             "hashes": [
@@ -522,44 +547,40 @@
         },
         "pluggy": {
             "hashes": [
-                "sha256:8ddc32f03971bfdf900a81961a48ccf2fb677cf7715108f85295c67405798616",
-                "sha256:980710797ff6a041e9a73a5787804f848996ecaa6f8a1b1e08224a5894f2074a"
+                "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
+                "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
             ],
-            "version": "==0.8.1"
-        },
-        "port-for": {
-            "hashes": [
-                "sha256:b16a84bb29c2954db44c29be38b17c659c9c27e33918dec16b90d375cc596f1c"
-            ],
-            "version": "==0.3.1"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==0.13.1"
         },
         "prompt-toolkit": {
             "hashes": [
-                "sha256:11adf3389a996a6d45cc277580d0d53e8a5afd281d0c9ec71b28e6f121463780",
-                "sha256:2519ad1d8038fd5fc8e770362237ad0364d16a7650fb5724af6997ed5515e3c1",
-                "sha256:977c6583ae813a37dc1c2e1b715892461fcbdaa57f6fc62f33a528c4886c8f55"
+                "sha256:4cea7d09e46723885cb8bc54678175453e5071e9449821dce6f017b1d1fbfc1a",
+                "sha256:9397a7162cf45449147ad6042fa37983a081b8a73363a5253dd4072666333137"
             ],
-            "version": "==2.0.9"
+            "markers": "python_full_version >= '3.6.1'",
+            "version": "==3.0.17"
         },
         "ptyprocess": {
             "hashes": [
-                "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0",
-                "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"
+                "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35",
+                "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"
             ],
-            "version": "==0.6.0"
+            "version": "==0.7.0"
         },
         "py": {
             "hashes": [
-                "sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694",
-                "sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6"
+                "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3",
+                "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"
             ],
-            "version": "==1.7.0"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==1.10.0"
         },
         "py-cpuinfo": {
             "hashes": [
-                "sha256:6615d4527118d4ea1db4d86dac4340725b3906aa04bf36b7902f7af4425fb25f"
+                "sha256:9aa2e49675114959697d25cf57fec41c29b55887bff3bc4809b44ac6f5730097"
             ],
-            "version": "==4.0.0"
+            "version": "==7.0.0"
         },
         "pygal": {
             "hashes": [
@@ -570,279 +591,395 @@
         },
         "pygaljs": {
             "hashes": [
-                "sha256:bd778749bc387a71520dde99f36a75ea776b3dcde5f3b59b4bd079c5723b1eb8",
-                "sha256:be757bc75f52b46669472f6cc7eac95935092495bef153b8810a46c0c59f88a1"
+                "sha256:0b71ee32495dcba5fbb4a0476ddbba07658ad65f5675e4ad409baf154dec5111",
+                "sha256:d75e18cb21cc2cda40c45c3ee690771e5e3d4652bf57206f20137cf475c0dbe8"
             ],
-            "version": "==1.0.1"
+            "version": "==1.0.2"
         },
         "pygments": {
             "hashes": [
-                "sha256:5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a",
-                "sha256:e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d"
+                "sha256:2656e1a6edcdabf4275f9a3640db59fd5de107d88e8663c5d4e9a0fa62f77f94",
+                "sha256:534ef71d539ae97d4c3a4cf7d6f110f214b0e687e92f9cb9d2a3b0d3101289c8"
             ],
-            "version": "==2.3.1"
+            "markers": "python_version >= '3.5'",
+            "version": "==2.8.1"
         },
         "pyparsing": {
             "hashes": [
-                "sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a",
-                "sha256:f6c5ef0d7480ad048c054c37632c67fca55299990fff127850181659eea33fc3"
+                "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
+                "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
             ],
-            "version": "==2.3.1"
+            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==2.4.7"
         },
         "pyrsistent": {
             "hashes": [
-                "sha256:07f7ae71291af8b0dbad8c2ab630d8223e4a8c4e10fc37badda158c02e753acf"
+                "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"
             ],
-            "version": "==0.14.10"
+            "markers": "python_version >= '3.5'",
+            "version": "==0.17.3"
         },
         "pytest": {
             "hashes": [
-                "sha256:067a1d4bf827ffdd56ad21bd46674703fce77c5957f6c1eef731f6146bfcef1c",
-                "sha256:9687049d53695ad45cf5fdc7bbd51f0c49f1ea3ecfc4b7f3fde7501b541f17f4"
+                "sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9",
+                "sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"
             ],
             "index": "pypi",
-            "version": "==4.3.0"
+            "version": "==6.2.2"
         },
         "pytest-benchmark": {
             "extras": [
                 "histogram"
             ],
             "hashes": [
-                "sha256:4512c6805318d07926efcb3b39f7b98a10d035305a93edfd5329c86cbf9cfbf7",
-                "sha256:ab851115ce022639173b9497d4a4183a1d8fe9cdcf8fab9d8a57607008aedd3d"
+                "sha256:01f79d38d506f5a3a0a9ada22ded714537bbdfc8147a881a35c1655db07289d9",
+                "sha256:ad4314d093a3089701b24c80a05121994c7765ce373478c8f4ba8d23c9ba9528"
             ],
             "index": "pypi",
-            "version": "==3.2.2"
+            "version": "==3.2.3"
         },
         "pytest-cov": {
             "hashes": [
-                "sha256:0ab664b25c6aa9716cbf203b17ddb301932383046082c081b9848a0edf5add33",
-                "sha256:230ef817450ab0699c6cc3c9c8f7a829c34674456f2ed8df1fe1d39780f7c87f"
+                "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7",
+                "sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da"
             ],
             "index": "pypi",
-            "version": "==2.6.1"
+            "version": "==2.11.1"
         },
         "pytest-mock": {
             "hashes": [
-                "sha256:4d0d06d173eecf172703219a71dbd4ade0e13904e6bbce1ce660e2e0dc78b5c4",
-                "sha256:bfdf02789e3d197bd682a758cae0a4a18706566395fbe2803badcd1335e0173e"
+                "sha256:379b391cfad22422ea2e252bdfc008edd08509029bcde3c25b2c0bd741e0424e",
+                "sha256:a1e2aba6af9560d313c642dae7e00a2a12b022b80301d9d7fc8ec6858e1dd9fc"
             ],
             "index": "pypi",
-            "version": "==1.10.1"
+            "version": "==3.5.1"
         },
         "pytest-mypy": {
             "hashes": [
-                "sha256:8f6436eed8118afd6c10a82b3b60fb537336736b0fd7a29262a656ac42ce01ac",
-                "sha256:acc653210e7d8d5c72845a5248f00fd33f4f3379ca13fe56cfc7b749b5655c3e"
+                "sha256:63d418a4fea7d598ac40b659723c00804d16a251d90a5cfbca213eeba5aaf01c",
+                "sha256:8d2112972c1debf087943f48963a0daf04f3424840aea0cf437cc97053b1b0ef"
             ],
             "index": "pypi",
-            "version": "==0.3.2"
+            "version": "==0.8.0"
         },
         "python-dateutil": {
             "hashes": [
-                "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb",
-                "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"
+                "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
+                "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
             ],
-            "version": "==2.8.0"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==2.8.1"
         },
         "pytz": {
             "hashes": [
-                "sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9",
-                "sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c"
+                "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da",
+                "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"
             ],
-            "version": "==2018.9"
+            "version": "==2021.1"
         },
         "pyyaml": {
             "hashes": [
-                "sha256:254bf6fda2b7c651837acb2c718e213df29d531eebf00edb54743d10bcb694eb",
-                "sha256:3108529b78577327d15eec243f0ff348a0640b0c3478d67ad7f5648f93bac3e2",
-                "sha256:3c17fb92c8ba2f525e4b5f7941d850e7a48c3a59b32d331e2502a3cdc6648e76",
-                "sha256:8d6d96001aa7f0a6a4a95e8143225b5d06e41b1131044913fecb8f85a125714b",
-                "sha256:c8a88edd93ee29ede719080b2be6cb2333dfee1dccba213b422a9c8e97f2967b"
-            ],
-            "version": "==4.2b4"
+                "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf",
+                "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696",
+                "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393",
+                "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77",
+                "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922",
+                "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5",
+                "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8",
+                "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10",
+                "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc",
+                "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018",
+                "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e",
+                "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253",
+                "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347",
+                "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183",
+                "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541",
+                "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb",
+                "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185",
+                "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc",
+                "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db",
+                "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa",
+                "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46",
+                "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122",
+                "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b",
+                "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63",
+                "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df",
+                "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc",
+                "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247",
+                "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6",
+                "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"
+            ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
+            "version": "==5.4.1"
         },
         "pyzmq": {
             "hashes": [
-                "sha256:07a03450418694fb07e76a0191b6bc9f411afc8e364ca2062edcf28bb0e51c63",
-                "sha256:15f0bf7cd80020f165635595e197603aedb37fddf4164ad5ae226afc43242f7b",
-                "sha256:1756dc72e192c670490e38c788c3a35f901adc74ee436e5131d5a3e85fdd7dc6",
-                "sha256:1d1eb490da54679d724b08ef3ee04530849023670c4ba7e400ed2cdf906720c4",
-                "sha256:228402625796821f08706f58cc42a3c51c9897d723550babaefe4feec2b6dacc",
-                "sha256:264ac9dcee6a7af2bce4b61f2d19e5926118a5caa629b50f107ef6318670a364",
-                "sha256:2b5a43da65f5dec857184d5c2ce13b80071019e96358f146bdecff7238765bc9",
-                "sha256:3928534fa00a2aabfcfdb439c08ba37fbe99ab0cf57776c8db8d2b73a51693ba",
-                "sha256:3d2a295b1086d450981f73d3561ac204a0cc9c8ded386a4a34327d918f3b1d0a",
-                "sha256:411def5b4cbe6111856040a55c8048df113882e90c57ce88de4a48f0189441ac",
-                "sha256:4b77e96a7ffc1c5e08eaf274db554f227b31717d086adca1bb42b12ef35a7194",
-                "sha256:4c87fa3e449e1f4ab9170cdfe8213dc0ba34a11b160e6adecafa892e451a29b6",
-                "sha256:4fd8621a309db6ec23ef1369f43cdf7a9b0dc217d8ff9ca4095a6e932b379bda",
-                "sha256:54fe55a1694ffe608c8e4c5183e83cab7a91f3e5c84bd6f188868d6676c12aba",
-                "sha256:60acabd86808a16a895a247fd2bf7a127284a33562d79687bb5df163cff068b2",
-                "sha256:618887be4ad754228c0cbba7631f6574608b4430fe93974e6322324f1304fdac",
-                "sha256:69130efb6efa936de601cb135a8a4eec1caccd4ea2b784237145ff4075c2d3ae",
-                "sha256:6e7f78eeac82140bde7e60e975c6e6b1b678a4dd377782ab63319c1c78bf3aa1",
-                "sha256:6ee760cdb84e43574da6b3f2f1fc1251e8acf87253900d28a06451c5f5de39e9",
-                "sha256:75c87f1dc1e65cea4b709f2ebc78fa18d4b475e41463502aec9cd26208b88e0f",
-                "sha256:97cb1b7cd2c46e87b0a26651eccd2bbb8c758035efd1635ebb81ac36aa76a88c",
-                "sha256:abfa774dbadacc849121ed92eae05189d226daab583388b499472e1bbb17ef69",
-                "sha256:ae3d2627d74195ddc95675f2f814aca998381b73dc4341b9e10e3e191e1bdb0b",
-                "sha256:b30c339eb58355f51f4f54dd61d785f1ff58c86bca1c3a5916977631d121867b",
-                "sha256:cbabdced5b137cd56aa22633f13ac5690029a0ad43ab6c05f53206e489178362"
-            ],
-            "version": "==18.0.0"
+                "sha256:13465c1ff969cab328bc92f7015ce3843f6e35f8871ad79d236e4fbc85dbe4cb",
+                "sha256:23a74de4b43c05c3044aeba0d1f3970def8f916151a712a3ac1e5cd9c0bc2902",
+                "sha256:26380487eae4034d6c2a3fb8d0f2dff6dd0d9dd711894e8d25aa2d1938950a33",
+                "sha256:279cc9b51db48bec2db146f38e336049ac5a59e5f12fb3a8ad864e238c1c62e3",
+                "sha256:2f971431aaebe0a8b54ac018e041c2f0b949a43745444e4dadcc80d0f0ef8457",
+                "sha256:30df70f81fe210506aa354d7fd486a39b87d9f7f24c3d3f4f698ec5d96b8c084",
+                "sha256:33acd2b9790818b9d00526135acf12790649d8d34b2b04d64558b469c9d86820",
+                "sha256:38e3dca75d81bec4f2defa14b0a65b74545812bb519a8e89c8df96bbf4639356",
+                "sha256:3e29f9cf85a40d521d048b55c63f59d6c772ac1c4bf51cdfc23b62a62e377c33",
+                "sha256:3ef50d74469b03725d781a2a03c57537d86847ccde587130fe35caafea8f75c6",
+                "sha256:4231943514812dfb74f44eadcf85e8dd8cf302b4d0bce450ce1357cac88dbfdc",
+                "sha256:4f34a173f813b38b83f058e267e30465ed64b22cd0cf6bad21148d3fa718f9bb",
+                "sha256:532af3e6dddea62d9c49062ece5add998c9823c2419da943cf95589f56737de0",
+                "sha256:581787c62eaa0e0db6c5413cedc393ebbadac6ddfd22e1cf9a60da23c4f1a4b2",
+                "sha256:60e63577b85055e4cc43892fecd877b86695ee3ef12d5d10a3c5d6e77a7cc1a3",
+                "sha256:61e4bb6cd60caf1abcd796c3f48395e22c5b486eeca6f3a8797975c57d94b03e",
+                "sha256:6d4163704201fff0f3ab0cd5d7a0ea1514ecfffd3926d62ec7e740a04d2012c7",
+                "sha256:7026f0353977431fc884abd4ac28268894bd1a780ba84bb266d470b0ec26d2ed",
+                "sha256:763c175294d861869f18eb42901d500eda7d3fa4565f160b3b2fd2678ea0ebab",
+                "sha256:81e7df0da456206201e226491aa1fc449da85328bf33bbeec2c03bb3a9f18324",
+                "sha256:9221783dacb419604d5345d0e097bddef4459a9a95322de6c306bf1d9896559f",
+                "sha256:a558c5bc89d56d7253187dccc4e81b5bb0eac5ae9511eb4951910a1245d04622",
+                "sha256:b25e5d339550a850f7e919fe8cb4c8eabe4c917613db48dab3df19bfb9a28969",
+                "sha256:b62ea18c0458a65ccd5be90f276f7a5a3f26a6dea0066d948ce2fa896051420f",
+                "sha256:c0cde362075ee8f3d2b0353b283e203c2200243b5a15d5c5c03b78112a17e7d4",
+                "sha256:c5e29fe4678f97ce429f076a2a049a3d0b2660ada8f2c621e5dc9939426056dd",
+                "sha256:d18ddc6741b51f3985978f2fda57ddcdae359662d7a6b395bc8ff2292fca14bd",
+                "sha256:da7d4d4c778c86b60949d17531e60c54ed3726878de8a7f8a6d6e7f8cc8c3205",
+                "sha256:f52070871a0fd90a99130babf21f8af192304ec1e995bec2a9533efc21ea4452",
+                "sha256:f5831eff6b125992ec65d973f5151c48003b6754030094723ac4c6e80a97c8c4",
+                "sha256:f7f63ce127980d40f3e6a5fdb87abf17ce1a7c2bd8bf2c7560e1bbce8ab1f92d",
+                "sha256:ff1ea14075bbddd6f29bf6beb8a46d0db779bcec6b9820909584081ec119f8fd"
+            ],
+            "markers": "python_version >= '3.6'",
+            "version": "==22.0.3"
         },
         "requests": {
             "hashes": [
-                "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e",
-                "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b"
+                "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
+                "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
             ],
             "index": "pypi",
-            "version": "==2.21.0"
+            "version": "==2.25.1"
         },
-        "retype": {
+        "six": {
             "hashes": [
-                "sha256:33cfb36601bfeb355924731d8db78fa82f3f12eb37e87236e9179d81aba97740",
-                "sha256:b64b767befbe6f5fd918603ab7d6bbff07fc4c431bae2f471e195677a0c9b327"
+                "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
+                "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
             ],
-            "version": "==17.12.0"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==1.15.0"
         },
-        "six": {
+        "snowballstemmer": {
             "hashes": [
-                "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
-                "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
+                "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2",
+                "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"
             ],
-            "version": "==1.12.0"
+            "version": "==2.1.0"
         },
-        "snowballstemmer": {
+        "sortedcontainers": {
             "hashes": [
-                "sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128",
-                "sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89"
+                "sha256:37257a32add0a3ee490bb170b599e93095eed89a55da91fa9f48753ea12fd73f",
+                "sha256:59cc937650cf60d677c16775597c89a960658a09cf7c1a668f86e1e4464b10a1"
             ],
-            "version": "==1.2.1"
+            "version": "==2.3.0"
         },
         "sphinx": {
             "hashes": [
-                "sha256:230af939a2f678ab4f2a0a948c3b24a822a0d280821859caaefb750ef7413003",
-                "sha256:835c701420102a0a71ba2ed54a5bada2da6fd01263bf6dc8c5c80c798e27709c"
+                "sha256:672cfcc24b6b69235c97c750cb190a44ecd72696b4452acaf75c2d9cc78ca5ff",
+                "sha256:ef64a814576f46ec7de06adf11b433a0d6049be007fefe7fd0d183d28b581fac"
             ],
             "index": "pypi",
-            "version": "==2.0.0b1"
+            "version": "==3.5.2"
         },
         "sphinx-autobuild": {
             "hashes": [
-                "sha256:66388f81884666e3821edbe05dd53a0cfb68093873d17320d0610de8db28c74e",
-                "sha256:e60aea0789cab02fa32ee63c7acae5ef41c06f1434d9fd0a74250a61f5994692"
+                "sha256:8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac",
+                "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05"
             ],
             "index": "pypi",
-            "version": "==0.7.1"
+            "version": "==2021.3.14"
         },
         "sphinx-rtd-theme": {
             "hashes": [
-                "sha256:00cf895504a7895ee433807c62094cf1e95f065843bf3acd17037c3e9a2becd4",
-                "sha256:728607e34d60456d736cc7991fd236afb828b21b82f956c5ea75f94c8414040a"
+                "sha256:eda689eda0c7301a80cf122dad28b1861e5605cbf455558f3775e1e8200e83a5",
+                "sha256:fa6bebd5ab9a73da8e102509a86f3fcc36dec04a0b52ea80e5a033b2aba00113"
             ],
             "index": "pypi",
-            "version": "==0.4.3"
+            "version": "==0.5.1"
         },
         "sphinxcontrib-applehelp": {
             "hashes": [
-                "sha256:edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897",
-                "sha256:fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"
+                "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a",
+                "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"
             ],
-            "version": "==1.0.1"
+            "markers": "python_version >= '3.5'",
+            "version": "==1.0.2"
         },
         "sphinxcontrib-devhelp": {
             "hashes": [
-                "sha256:6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34",
-                "sha256:9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"
+                "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e",
+                "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"
             ],
-            "version": "==1.0.1"
+            "markers": "python_version >= '3.5'",
+            "version": "==1.0.2"
         },
         "sphinxcontrib-htmlhelp": {
             "hashes": [
-                "sha256:0d691ca8edf5995fbacfe69b191914256071a94cbad03c3688dca47385c9206c",
-                "sha256:e31c8271f5a8f04b620a500c0442a7d5cfc1a732fa5c10ec363f90fe72af0cb8"
+                "sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f",
+                "sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b"
             ],
-            "version": "==1.0.1"
+            "markers": "python_version >= '3.5'",
+            "version": "==1.0.3"
         },
         "sphinxcontrib-jsmath": {
             "hashes": [
                 "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178",
                 "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"
             ],
+            "markers": "python_version >= '3.5'",
             "version": "==1.0.1"
         },
         "sphinxcontrib-qthelp": {
             "hashes": [
-                "sha256:18ec9f74ea2c92fd512d5f3b532d6ab4ac2be76b12cc2490f7729842ba2a60c9",
-                "sha256:f39159b45de6d3d86c30874a3220be4f8e75ed12c71aff50cb8b2cac46e240f0"
+                "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72",
+                "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"
             ],
-            "version": "==1.0.1"
+            "markers": "python_version >= '3.5'",
+            "version": "==1.0.3"
         },
         "sphinxcontrib-serializinghtml": {
             "hashes": [
-                "sha256:01d9b2617d7e8ddf7a00cae091f08f9fa4db587cc160b493141ee56710810932",
-                "sha256:392187ac558863b8aff0d76dc78e0731fed58f3b06e2b00e22995dcdb630f213"
+                "sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc",
+                "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"
             ],
-            "version": "==1.1.1"
+            "markers": "python_version >= '3.5'",
+            "version": "==1.1.4"
         },
-        "tornado": {
+        "toml": {
             "hashes": [
-                "sha256:d3b719a0cb7094e2b1ca94b31f4b601639fa7ad01a548a1a2ccdd6cbdfd56671"
+                "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
+                "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
             ],
-            "version": "==6.0b1"
+            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==0.10.2"
+        },
+        "tornado": {
+            "hashes": [
+                "sha256:0a00ff4561e2929a2c37ce706cb8233b7907e0cdc22eab98888aca5dd3775feb",
+                "sha256:0d321a39c36e5f2c4ff12b4ed58d41390460f798422c4504e09eb5678e09998c",
+                "sha256:1e8225a1070cd8eec59a996c43229fe8f95689cb16e552d130b9793cb570a288",
+                "sha256:20241b3cb4f425e971cb0a8e4ffc9b0a861530ae3c52f2b0434e6c1b57e9fd95",
+                "sha256:25ad220258349a12ae87ede08a7b04aca51237721f63b1808d39bdb4b2164558",
+                "sha256:33892118b165401f291070100d6d09359ca74addda679b60390b09f8ef325ffe",
+                "sha256:33c6e81d7bd55b468d2e793517c909b139960b6c790a60b7991b9b6b76fb9791",
+                "sha256:3447475585bae2e77ecb832fc0300c3695516a47d46cefa0528181a34c5b9d3d",
+                "sha256:34ca2dac9e4d7afb0bed4677512e36a52f09caa6fded70b4e3e1c89dbd92c326",
+                "sha256:3e63498f680547ed24d2c71e6497f24bca791aca2fe116dbc2bd0ac7f191691b",
+                "sha256:548430be2740e327b3fe0201abe471f314741efcb0067ec4f2d7dcfb4825f3e4",
+                "sha256:6196a5c39286cc37c024cd78834fb9345e464525d8991c21e908cc046d1cc02c",
+                "sha256:61b32d06ae8a036a6607805e6720ef00a3c98207038444ba7fd3d169cd998910",
+                "sha256:6286efab1ed6e74b7028327365cf7346b1d777d63ab30e21a0f4d5b275fc17d5",
+                "sha256:65d98939f1a2e74b58839f8c4dab3b6b3c1ce84972ae712be02845e65391ac7c",
+                "sha256:66324e4e1beede9ac79e60f88de548da58b1f8ab4b2f1354d8375774f997e6c0",
+                "sha256:6c77c9937962577a6a76917845d06af6ab9197702a42e1346d8ae2e76b5e3675",
+                "sha256:70dec29e8ac485dbf57481baee40781c63e381bebea080991893cd297742b8fd",
+                "sha256:7250a3fa399f08ec9cb3f7b1b987955d17e044f1ade821b32e5f435130250d7f",
+                "sha256:748290bf9112b581c525e6e6d3820621ff020ed95af6f17fedef416b27ed564c",
+                "sha256:7da13da6f985aab7f6f28debab00c67ff9cbacd588e8477034c0652ac141feea",
+                "sha256:8f959b26f2634a091bb42241c3ed8d3cedb506e7c27b8dd5c7b9f745318ddbb6",
+                "sha256:9de9e5188a782be6b1ce866e8a51bc76a0fbaa0e16613823fc38e4fc2556ad05",
+                "sha256:a48900ecea1cbb71b8c71c620dee15b62f85f7c14189bdeee54966fbd9a0c5bd",
+                "sha256:b87936fd2c317b6ee08a5741ea06b9d11a6074ef4cc42e031bc6403f82a32575",
+                "sha256:c77da1263aa361938476f04c4b6c8916001b90b2c2fdd92d8d535e1af48fba5a",
+                "sha256:cb5ec8eead331e3bb4ce8066cf06d2dfef1bfb1b2a73082dfe8a161301b76e37",
+                "sha256:cc0ee35043162abbf717b7df924597ade8e5395e7b66d18270116f8745ceb795",
+                "sha256:d14d30e7f46a0476efb0deb5b61343b1526f73ebb5ed84f23dc794bdb88f9d9f",
+                "sha256:d371e811d6b156d82aa5f9a4e08b58debf97c302a35714f6f45e35139c332e32",
+                "sha256:d3d20ea5782ba63ed13bc2b8c291a053c8d807a8fa927d941bd718468f7b950c",
+                "sha256:d3f7594930c423fd9f5d1a76bee85a2c36fd8b4b16921cae7e965f22575e9c01",
+                "sha256:dcef026f608f678c118779cd6591c8af6e9b4155c44e0d1bc0c87c036fb8c8c4",
+                "sha256:e0791ac58d91ac58f694d8d2957884df8e4e2f6687cdf367ef7eb7497f79eaa2",
+                "sha256:e385b637ac3acaae8022e7e47dfa7b83d3620e432e3ecb9a3f7f58f150e50921",
+                "sha256:e519d64089b0876c7b467274468709dadf11e41d65f63bba207e04217f47c085",
+                "sha256:e7229e60ac41a1202444497ddde70a48d33909e484f96eb0da9baf8dc68541df",
+                "sha256:ed3ad863b1b40cd1d4bd21e7498329ccaece75db5a5bf58cd3c9f130843e7102",
+                "sha256:f0ba29bafd8e7e22920567ce0d232c26d4d47c8b5cf4ed7b562b5db39fa199c5",
+                "sha256:fa2ba70284fa42c2a5ecb35e322e68823288a4251f9ba9cc77be04ae15eada68",
+                "sha256:fba85b6cd9c39be262fcd23865652920832b61583de2a2ca907dbd8e8a8c81e5"
+            ],
+            "markers": "python_version >= '3.5'",
+            "version": "==6.1"
         },
         "traitlets": {
             "hashes": [
-                "sha256:9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835",
-                "sha256:c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9"
+                "sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396",
+                "sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426"
             ],
-            "version": "==4.3.2"
+            "markers": "python_version >= '3.7'",
+            "version": "==5.0.5"
         },
         "typed-ast": {
             "hashes": [
-                "sha256:035a54ede6ce1380599b2ce57844c6554666522e376bd111eb940fbc7c3dad23",
-                "sha256:037c35f2741ce3a9ac0d55abfcd119133cbd821fffa4461397718287092d9d15",
-                "sha256:049feae7e9f180b64efacbdc36b3af64a00393a47be22fa9cb6794e68d4e73d3",
-                "sha256:19228f7940beafc1ba21a6e8e070e0b0bfd1457902a3a81709762b8b9039b88d",
-                "sha256:2ea681e91e3550a30c2265d2916f40a5f5d89b59469a20f3bad7d07adee0f7a6",
-                "sha256:3a6b0a78af298d82323660df5497bcea0f0a4a25a0b003afd0ce5af049bd1f60",
-                "sha256:5385da8f3b801014504df0852bf83524599df890387a3c2b17b7caa3d78b1773",
-                "sha256:606d8afa07eef77280c2bf84335e24390055b478392e1975f96286d99d0cb424",
-                "sha256:69245b5b23bbf7fb242c9f8f08493e9ecd7711f063259aefffaeb90595d62287",
-                "sha256:6f6d839ab09830d59b7fa8fb6917023d8cb5498ee1f1dbd82d37db78eb76bc99",
-                "sha256:730888475f5ac0e37c1de4bd05eeb799fdb742697867f524dc8a4cd74bcecc23",
-                "sha256:9819b5162ffc121b9e334923c685b0d0826154e41dfe70b2ede2ce29034c71d8",
-                "sha256:9e60ef9426efab601dd9aa120e4ff560f4461cf8442e9c0a2b92548d52800699",
-                "sha256:af5fbdde0690c7da68e841d7fc2632345d570768ea7406a9434446d7b33b0ee1",
-                "sha256:b64efdbdf3bbb1377562c179f167f3bf301251411eb5ac77dec6b7d32bcda463",
-                "sha256:bac5f444c118aeb456fac1b0b5d14c6a71ea2a42069b09c176f75e9bd4c186f6",
-                "sha256:bda9068aafb73859491e13b99b682bd299c1b5fd50644d697533775828a28ee0",
-                "sha256:d659517ca116e6750101a1326107d3479028c5191f0ecee3c7203c50f5b915b0",
-                "sha256:eddd3fb1f3e0f82e5915a899285a39ee34ce18fd25d89582bc89fc9fb16cd2c6"
-            ],
-            "version": "==1.3.1"
-        },
-        "urllib3": {
-            "hashes": [
-                "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39",
-                "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22"
+                "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1",
+                "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d",
+                "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6",
+                "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd",
+                "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37",
+                "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151",
+                "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07",
+                "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440",
+                "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70",
+                "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496",
+                "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea",
+                "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400",
+                "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc",
+                "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606",
+                "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc",
+                "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581",
+                "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412",
+                "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a",
+                "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2",
+                "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787",
+                "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f",
+                "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937",
+                "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64",
+                "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487",
+                "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b",
+                "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41",
+                "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a",
+                "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3",
+                "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166",
+                "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10"
+            ],
+            "version": "==1.4.2"
+        },
+        "typing-extensions": {
+            "hashes": [
+                "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918",
+                "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c",
+                "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"
+            ],
+            "version": "==3.7.4.3"
+        },
+        "typing-inspect": {
+            "hashes": [
+                "sha256:3b98390df4d999a28cf5b35d8b333425af5da2ece8a4ea9e98f71e7591347b4f",
+                "sha256:8f1b1dd25908dbfd81d3bebc218011531e7ab614ba6e5bf7826d887c834afab7",
+                "sha256:de08f50a22955ddec353876df7b2545994d6df08a2f45d54ac8c05e530372ca0"
             ],
-            "version": "==1.24.1"
+            "version": "==0.6.0"
         },
-        "watchdog": {
+        "urllib3": {
             "hashes": [
-                "sha256:965f658d0732de3188211932aeb0bb457587f04f63ab4c1e33eab878e9de961d"
+                "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df",
+                "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"
             ],
-            "version": "==0.9.0"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
+            "version": "==1.26.4"
         },
         "wcwidth": {
             "hashes": [
-                "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e",
-                "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"
+                "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784",
+                "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"
             ],
-            "version": "==0.1.7"
+            "version": "==0.2.5"
         }
     }
 }
diff --git a/README.rst b/README.rst
index e914bec4..3fc364dd 100644
--- a/README.rst
+++ b/README.rst
@@ -62,19 +62,18 @@ Additionally, users that delegate access to their data (like Alice, in this exam
 
 .. code-block:: python
 
-    from umbral import pre, keys, signing
+    from umbral import SecretKey, PublicKey
 
     # Generate Umbral keys for Alice.
-    alices_private_key = keys.UmbralPrivateKey.gen_key()
-    alices_public_key = alices_private_key.get_pubkey()
+    alices_secret_key = SecretKey.random()
+    alices_public_key = PublicKey.from_secret_key(alices_secret_key)
 
-    alices_signing_key = keys.UmbralPrivateKey.gen_key()
-    alices_verifying_key = alices_signing_key.get_pubkey()
-    alices_signer = signing.Signer(private_key=alices_signing_key)
+    alices_signing_key = SecretKey.random()
+    alices_verifying_key = PublicKey.from_secret_key(alices_signing_key)
 
     # Generate Umbral keys for Bob.
-    bobs_private_key = keys.UmbralPrivateKey.gen_key()
-    bobs_public_key = bobs_private_key.get_pubkey()
+    bobs_secret_key = SecretKey.random()
+    bobs_public_key = PublicKey.from_secret_key(bobs_secret_key)
 
 
 **Encryption**
@@ -89,14 +88,14 @@ Alice can open the capsule and decrypt the ciphertext with her private key.
 
 .. code-block:: python
 
+    from umbral import encrypt, decrypt_original
+
     # Encrypt data with Alice's public key.
     plaintext = b'Proxy Re-Encryption is cool!'
-    ciphertext, capsule = pre.encrypt(alices_public_key, plaintext)
+    capsule, ciphertext = encrypt(alices_public_key, plaintext)
 
     # Decrypt data with Alice's private key.
-    cleartext = pre.decrypt(ciphertext=ciphertext, 
-                            capsule=capsule, 
-                            decrypting_key=alices_private_key)
+    cleartext = decrypt_original(alices_secret_key, capsule, ciphertext)
 
 
 **Re-Encryption Key Fragments**
@@ -107,13 +106,15 @@ which are next sent to N proxies or *Ursulas*.
 
 .. code-block:: python
 
+    from umbral import generate_kfrags
+
     # Alice generates "M of N" re-encryption key fragments (or "KFrags") for Bob.
     # In this example, 10 out of 20.
-    kfrags = pre.generate_kfrags(delegating_privkey=alices_private_key,
-                                 signer=alices_signer,
-                                 receiving_pubkey=bobs_public_key,
-                                 threshold=10,
-                                 N=20)
+    kfrags = generate_kfrags(delegating_sk=alices_secret_key,
+                             receiving_pk=bobs_public_key,
+                             signing_sk=alices_signing_key,
+                             threshold=10,
+                             num_kfrags=20)
 
 
 **Re-Encryption**
@@ -127,17 +128,13 @@ Bob must gather at least ``threshold`` cfrags in order to activate the capsule.
 
 .. code-block:: python
 
-  # Several Ursulas perform re-encryption, and Bob collects the resulting `cfrags`.
-  # He must gather at least `threshold` `cfrags` in order to activate the capsule.
-
-  capsule.set_correctness_keys(delegating=alices_public_key,
-                               receiving=bobs_public_key,
-                               verifying=alices_verifying_key)
+    from umbral import reencrypt
 
-  cfrags = list()           # Bob's cfrag collection
-  for kfrag in kfrags[:10]:
-    cfrag = pre.reencrypt(kfrag=kfrag, capsule=capsule)
-    cfrags.append(cfrag)    # Bob collects a cfrag
+    # Several Ursulas perform re-encryption, and Bob collects the resulting `cfrags`.
+    cfrags = list()           # Bob's cfrag collection
+    for kfrag in kfrags[:10]:
+        cfrag = pre.reencrypt(capsule=capsule, kfrag=kfrag)
+        cfrags.append(cfrag)    # Bob collects a cfrag
 
 
 **Decryption by Bob**
@@ -147,14 +144,14 @@ and then decrypts the re-encrypted ciphertext.
 
 .. code-block:: python
 
-  # Bob activates and opens the capsule
-  for cfrag in cfrags:
-    capsule.attach_cfrag(cfrag)
+    from umbral import decrypt_reencrypted
 
-  bob_cleartext = pre.decrypt(ciphertext=ciphertext, 
-                              capsule=capsule, 
-                              decrypting_key=bobs_private_key)
-  assert bob_cleartext == plaintext
+    bob_cleartext = pre.decrypt_reencrypted(decrypting_sk=bobs_secret_key,
+                                            delegating_pk=alices_public_key,
+                                            capsule=capsule,
+                                            cfrags=cfrags,
+                                            ciphertext=ciphertext)
+    assert bob_cleartext == plaintext
 
 See more detailed usage examples in the docs_ directory.
 
@@ -171,7 +168,7 @@ To install pyUmbral, simply use ``pip``:
   $ pip3 install umbral
 
 
-Alternatively, you can checkout the repo and install it from there. 
+Alternatively, you can checkout the repo and install it from there.
 The NuCypher team uses ``pipenv`` for managing pyUmbral's dependencies.
 The recommended installation procedure is as follows:
 
diff --git a/docs/examples/umbral_simple_api.py b/docs/examples/umbral_simple_api.py
index f87f7719..e28d3802 100644
--- a/docs/examples/umbral_simple_api.py
+++ b/docs/examples/umbral_simple_api.py
@@ -1,41 +1,19 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-#1
-# Sets a default curve (secp256k1)
 import random
-from umbral import pre, keys, config, signing
+from umbral import (
+    SecretKey, PublicKey, GenericError,
+    encrypt, generate_kfrags, reencrypt, decrypt_original, decrypt_reencrypted)
 
-config.set_default_curve()
-
-#2
 # Generate an Umbral key pair
 # ---------------------------
 # First, Let's generate two asymmetric key pairs for Alice:
 # A delegating key pair and a Signing key pair.
 
-alices_private_key = keys.UmbralPrivateKey.gen_key()
-alices_public_key = alices_private_key.get_pubkey()
+alices_secret_key = SecretKey.random()
+alices_public_key = PublicKey.from_secret_key(alices_secret_key)
 
-alices_signing_key = keys.UmbralPrivateKey.gen_key()
-alices_verifying_key = alices_signing_key.get_pubkey()
-alices_signer = signing.Signer(private_key=alices_signing_key)
+alices_signing_key = SecretKey.random()
+alices_verifying_key = PublicKey.from_secret_key(alices_signing_key)
 
-#3
 # Encrypt some data for Alice
 # ---------------------------
 # Now let's encrypt data with Alice's public key.
@@ -44,98 +22,86 @@
 # this operation.
 
 plaintext = b'Proxy Re-encryption is cool!'
-ciphertext, capsule = pre.encrypt(alices_public_key, plaintext)
+capsule, ciphertext = encrypt(alices_public_key, plaintext)
 print(ciphertext)
 
-#4
 # Decrypt data for Alice
 # ----------------------
 # Since data was encrypted with Alice's public key,
 # Alice can open the capsule and decrypt the ciphertext with her private key.
 
-cleartext = pre.decrypt(ciphertext=ciphertext,
-                        capsule=capsule,
-                        decrypting_key=alices_private_key)
+cleartext = decrypt_original(alices_secret_key, capsule, ciphertext)
 print(cleartext)
 
-#5
 # Bob Exists
 # -----------
 
-bobs_private_key = keys.UmbralPrivateKey.gen_key()
-bobs_public_key = bobs_private_key.get_pubkey()
+bobs_secret_key = SecretKey.random()
+bobs_public_key = PublicKey.from_secret_key(bobs_secret_key)
 
-#6
 # Bob receives a capsule through a side channel (s3, ipfs, Google cloud, etc)
 bob_capsule = capsule
 
-#7
 # Attempt Bob's decryption (fail)
 try:
-    fail_decrypted_data = pre.decrypt(ciphertext=ciphertext,
-                                      capsule=bob_capsule,
-                                      decrypting_key=bobs_private_key)
-except pre.UmbralDecryptionError:
+    fail_decrypted_data = decrypt_original(bobs_secret_key, bob_capsule, ciphertext)
+except GenericError:
     print("Decryption failed! Bob doesn't has access granted yet.")
 
-#8
-# Alice grants access to Bob by generating kfrags 
+# Alice grants access to Bob by generating kfrags
 # -----------------------------------------------
-# When Alice wants to grant Bob access to open her encrypted messages, 
-# she creates *threshold split re-encryption keys*, or *"kfrags"*, 
-# which are next sent to N proxies or *Ursulas*. 
-# She uses her private key, and Bob's public key, and she sets a minimum 
+# When Alice wants to grant Bob access to open her encrypted messages,
+# she creates *threshold split re-encryption keys*, or *"kfrags"*,
+# which are next sent to N proxies or *Ursulas*.
+# She uses her private key, and Bob's public key, and she sets a minimum
 # threshold of 10, for 20 total shares
 
-kfrags = pre.generate_kfrags(delegating_privkey=alices_private_key,
-                             signer=alices_signer,
-                             receiving_pubkey=bobs_public_key,
-                             threshold=10,
-                             N=20)
+kfrags = generate_kfrags(delegating_sk=alices_secret_key,
+                         receiving_pk=bobs_public_key,
+                         signing_sk=alices_signing_key,
+                         threshold=10,
+                         num_kfrags=20)
 
-#9
 # Ursulas perform re-encryption
 # ------------------------------
-# Bob asks several Ursulas to re-encrypt the capsule so he can open it. 
-# Each Ursula performs re-encryption on the capsule using the `kfrag` 
+# Bob asks several Ursulas to re-encrypt the capsule so he can open it.
+# Each Ursula performs re-encryption on the capsule using the `kfrag`
 # provided by Alice, obtaining this way a "capsule fragment", or `cfrag`.
 # Let's mock a network or transport layer by sampling `threshold` random `kfrags`,
 # one for each required Ursula.
 
-import random
-
 kfrags = random.sample(kfrags,  # All kfrags from above
                        10)      # M - Threshold
 
-# Bob collects the resulting `cfrags` from several Ursulas. 
-# Bob must gather at least `threshold` `cfrags` in order to activate the capsule.
-
-bob_capsule.set_correctness_keys(delegating=alices_public_key,
-                                 receiving=bobs_public_key,
-                                 verifying=alices_verifying_key)
+# Bob collects the resulting `cfrags` from several Ursulas.
+# Bob must gather at least `threshold` `cfrags` in order to open the capsule.
 
 cfrags = list()  # Bob's cfrag collection
 for kfrag in kfrags:
-    cfrag = pre.reencrypt(kfrag=kfrag, capsule=bob_capsule)
+    cfrag = reencrypt(capsule=capsule, kfrag=kfrag)
     cfrags.append(cfrag)  # Bob collects a cfrag
 
 assert len(cfrags) == 10
 
-#10
-# Bob attaches cfrags to the capsule
-# ----------------------------------
-# Bob attaches at least `threshold` `cfrags` to the capsule;
-# then it can become *activated*.
+# Bob checks the capsule fragments
+# --------------------------------
+# Bob can verify that the capsule fragments are valid and really originate from Alice,
+# using Alice's public keys.
 
-for cfrag in cfrags:
-    bob_capsule.attach_cfrag(cfrag)
+assert all(cfrag.verify(capsule,
+                        delegating_pk=alices_public_key,
+                        receiving_pk=bobs_public_key,
+                        signing_pk=alices_verifying_key)
+           for cfrag in cfrags)
 
-#11
-# Bob activates and opens the capsule
+# Bob opens the capsule
 # ------------------------------------
-# Finally, Bob activates and opens the capsule,
-# then decrypts the re-encrypted ciphertext.
+# Finally, Bob decrypts the re-encrypted ciphertext using his key.
 
-bob_cleartext = pre.decrypt(ciphertext=ciphertext, capsule=bob_capsule, decrypting_key=bobs_private_key)
+bob_cleartext = decrypt_reencrypted(decrypting_sk=bobs_secret_key,
+                                    delegating_pk=alices_public_key,
+                                    capsule=bob_capsule,
+                                    cfrags=cfrags,
+                                    ciphertext=ciphertext)
 print(bob_cleartext)
 assert bob_cleartext == plaintext
diff --git a/docs/notebooks/pyUmbral Simple API.ipynb b/docs/notebooks/pyUmbral Simple API.ipynb
index 7bdd010d..6962723c 100644
--- a/docs/notebooks/pyUmbral Simple API.ipynb	
+++ b/docs/notebooks/pyUmbral Simple API.ipynb	
@@ -7,30 +7,6 @@
     "# pyUmbral Python API"
    ]
   },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "## Setting the default curve\n",
-    "\n",
-    "The first time you use umbral, you may want to specify an elliptic curve to use.  If you do not specify a curve, secp256k1 will be used for all operations, with a slight performace hit for the lookup.\n",
-    "\n",
-    "To set the default curve use `umbral.config.set_default_curve()`\n",
-    "\n",
-    "Note: you can only set the dafault once, or `UmbralConfigurationError` will be raised."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 1,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "from umbral.config import set_default_curve\n",
-    "\n",
-    "set_default_curve()"
-   ]
-  },
   {
    "cell_type": "markdown",
    "metadata": {},
@@ -46,16 +22,15 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "from umbral import keys, signing\n",
+    "from umbral import SecretKey, PublicKey\n",
     "\n",
     "\n",
     "# Alice's Keys\n",
-    "alices_private_key = keys.UmbralPrivateKey.gen_key()\n",
-    "alices_public_key = alices_private_key.get_pubkey()\n",
+    "alices_private_key = SecretKey.random()\n",
+    "alices_public_key = PublicKey.from_secret_key(alices_private_key)\n",
     "\n",
-    "alices_signing_key = keys.UmbralPrivateKey.gen_key()\n",
-    "alices_verifying_key = alices_signing_key.get_pubkey()\n",
-    "alices_signer = signing.Signer(private_key=alices_signing_key)"
+    "alices_signing_key = SecretKey.random()\n",
+    "alices_verifying_key = PublicKey.from_secret_key(alices_signing_key)"
    ]
   },
   {
@@ -80,16 +55,16 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "b'#\\xebQ\\xd4\\xad\\x8ah,9\\x8f\\xc9\\x18\\x84[\\x95M\\x8e\\xb1\\x85\\xf9\\xbe\\x97\\x07\\xf3\\x80@\\x11\\xab\\x82\\xac\\xa1\\xbf\\xc0\\x00e\\xecpTq\\xef\\x94\\xd94\\x94\\x1a\\xdf\\xf0\\x04)\\xf5\\r\\xc4\\xbd/:\\x8c'\n"
+      "b'\\x1c\\xa0\\xa83\\x0cv\\x97\\x02d\\xe9\\xe9\\xc5_\\x9d5NRGRx\\xd4\\xc9\\x17%\\x9b\\xb4\\x05\\xd1\\xc2\\x1e\\x9d\\x0b\\xbf\\xb4g\\xf0n\\xfe\\x9eM\\x93\\xe0\\xbf#l\\xf9\\x033\\xb00\\xf5\\r\\xff\\xc9\\x133C\\xf0\\xa3\\xc0\\xd1e\\xdb~.E$%'\n"
      ]
     }
    ],
    "source": [
-    "from umbral import pre\n",
+    "from umbral import encrypt\n",
     "\n",
     "\n",
     "plaintext = b'Proxy Re-encryption is cool!'\n",
-    "ciphertext, capsule = pre.encrypt(alices_public_key, plaintext)\n",
+    "capsule, ciphertext = encrypt(alices_public_key, plaintext)\n",
     "print(ciphertext)"
    ]
   },
@@ -115,10 +90,13 @@
     }
    ],
    "source": [
-    "cleartext = pre.decrypt(ciphertext=ciphertext, \n",
-    "                        capsule=capsule, \n",
-    "                        decrypting_key=alices_private_key)\n",
-    "print(cleartext)\n"
+    "from umbral import decrypt_original\n",
+    "\n",
+    "\n",
+    "cleartext = decrypt_original(sk=alices_private_key,\n",
+    "                             capsule=capsule,\n",
+    "                             ciphertext=ciphertext)\n",
+    "print(cleartext)"
    ]
   },
   {
@@ -135,8 +113,8 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "bobs_private_key = keys.UmbralPrivateKey.gen_key()\n",
-    "bobs_public_key = bobs_private_key.get_pubkey()\n",
+    "bobs_private_key = SecretKey.random()\n",
+    "bobs_public_key = PublicKey.from_secret_key(bobs_private_key)\n",
     "\n",
     "bob_capsule = capsule"
    ]
@@ -162,11 +140,13 @@
     }
    ],
    "source": [
+    "from umbral import GenericError\n",
+    "\n",
     "try:\n",
-    "    fail_decrypted_data = pre.decrypt(ciphertext=ciphertext, \n",
-    "                                      capsule=capsule, \n",
-    "                                      decrypting_key=bobs_private_key)\n",
-    "except pre.UmbralDecryptionError:\n",
+    "    fail_decrypted_data = decrypt_original(sk=bobs_private_key,\n",
+    "                                           capsule=capsule,\n",
+    "                                           ciphertext=ciphertext)\n",
+    "except GenericError:\n",
     "    print(\"Decryption failed! Bob doesn't has access granted yet.\")\n"
    ]
   },
@@ -184,21 +164,24 @@
    "metadata": {},
    "source": [
     "## Alice grants access to Bob by generating KFrags \n",
-    "When Alice wants to grant Bob access to open her encrypted messages, she creates *re-encryption key fragments*, or *\"kfrags\"*, which are next sent to N proxies or *Ursulas*. She uses her private key, and Bob's public key, and she sets a minimum threshold of 10, for 20 total shares\n"
+    "When Alice wants to grant Bob access to open her encrypted messages, she creates *re-encryption key fragments*, or \"kfrags\", which are next sent to N proxies or *Ursulas*. She uses her private key, and Bob's public key, and she sets a minimum threshold of 10, for 20 total shares\n"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": 8,
    "metadata": {},
    "outputs": [],
    "source": [
-    "M, N = 10, 20\n",
-    "kfrags = pre.generate_kfrags(delegating_privkey=alices_private_key, \n",
-    "                             receiving_pubkey=bobs_public_key, \n",
-    "                             signer=alices_signer,\n",
-    "                             threshold=M, \n",
-    "                             N=N)\n"
+    "from umbral import generate_kfrags\n",
+    "\n",
+    "\n",
+    "M, N = 10, 20 # the threshold and the total number of fragments\n",
+    "kfrags = generate_kfrags(delegating_sk=alices_private_key,\n",
+    "                         receiving_pk=bobs_public_key,\n",
+    "                         signing_sk=alices_signing_key,\n",
+    "                         threshold=M,\n",
+    "                         num_kfrags=N)"
    ]
   },
   {
@@ -207,44 +190,63 @@
    "source": [
     "\n",
     "## Ursulas Re-encrypt; Bob attaches fragments to `capsule`\n",
-    "Bob asks several Ursulas to re-encrypt the capsule so he can open it. Each Ursula performs re-encryption on the capsule using the `kfrag` provided by Alice, obtaining this way a \"capsule fragment\", or `cfrag`. Let's mock a network or transport layer by sampling `M` random `kfrags`, one for each required Ursula. Note that each Ursula must prepare the received capsule before re-encryption by setting the proper correctness keys. Bob collects the resulting `cfrags` from several Ursulas. He must gather at least `M` `cfrags` in order to activate the capsule.\n"
+    "Bob asks several Ursulas to re-encrypt the capsule so he can open it. Each Ursula performs re-encryption on the capsule using the `kfrag` provided by Alice, obtaining this way a \"capsule fragment\", or `cfrag`. Let's mock a network or transport layer by sampling `M` random `kfrags`, one for each required Ursula. Bob collects the resulting `cfrags` from several Ursulas. He must gather at least `M` `cfrags` in order to activate the capsule.\n"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 8,
+   "execution_count": 9,
    "metadata": {},
    "outputs": [],
    "source": [
     "import random\n",
     "kfrags = random.sample(kfrags,  # All kfrags from above\n",
-    "                       10)      # M - Threshold\n",
+    "                       M)       # Threshold\n",
+    "\n",
+    "\n",
+    "from umbral import reencrypt\n",
     "\n",
-    "bob_capsule.set_correctness_keys(delegating=alices_public_key,\n",
-    "                                 receiving=bobs_public_key,\n",
-    "                                 verifying=alices_verifying_key)\n",
     "\n",
     "cfrags = list()                 # Bob's cfrag collection\n",
     "for kfrag in kfrags:\n",
-    "    cfrag = pre.reencrypt(kfrag=kfrag, capsule=bob_capsule)\n",
-    "    cfrags.append(cfrag)        # Bob collects a cfrag\n",
-    "\n",
-    "assert len(cfrags) == 10\n"
+    "    cfrag = reencrypt(capsule=capsule, kfrag=kfrag)\n",
+    "    cfrags.append(cfrag)        # Bob collects a cfrag"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "## Bob activates and opens the capsule; Decrypts data from Alice.\n",
-    "The `capsule` can become *activated* once Bob attaches at least `M` `cfrags` to it. Note that it has to be prepared in advance with the necessary `correctness_keys` (specifically, Alice's public key, Alice's signature verification key and his own public key). \n",
-    "\n",
-    "Finally, Bob activates and opens the capsule, then decrypts the re-encrypted ciphertext."
+    "## Bob checks the capsule fragments\n",
+    "Bob can verify that the capsule fragments are valid and really originate from Alice, using Alice's public keys."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 9,
+   "execution_count": 11,
+   "metadata": {
+    "scrolled": true
+   },
+   "outputs": [],
+   "source": [
+    "assert all(cfrag.verify(capsule,\n",
+    "                        delegating_pk=alices_public_key,\n",
+    "                        receiving_pk=bobs_public_key,\n",
+    "                        signing_pk=alices_verifying_key)\n",
+    "           for cfrag in cfrags)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Bob opens the capsule; Decrypts data from Alice.\n",
+    "Finally, Bob decrypts the re-encrypted ciphertext using his secret key."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
    "metadata": {},
    "outputs": [
     {
@@ -256,17 +258,16 @@
     }
    ],
    "source": [
-    "bob_capsule.set_correctness_keys(delegating=alices_public_key,\n",
-    "                                 receiving=bobs_public_key,\n",
-    "                                 verifying=alices_verifying_key)\n",
+    "from umbral import decrypt_reencrypted\n",
+    "\n",
+    "bob_cleartext = decrypt_reencrypted(decrypting_sk=bobs_private_key,\n",
+    "                                    delegating_pk=alices_public_key,\n",
+    "                                    capsule=capsule,\n",
+    "                                    cfrags=cfrags,\n",
+    "                                    ciphertext=ciphertext)\n",
     "\n",
-    "for cfrag in cfrags:\n",
-    "    bob_capsule.attach_cfrag(cfrag)\n",
-    "    \n",
-    "bob_cleartext = pre.decrypt(ciphertext=ciphertext, capsule=capsule, decrypting_key=bobs_private_key)\n",
     "print(bob_cleartext)\n",
-    "assert bob_cleartext == plaintext\n",
-    "\n"
+    "assert bob_cleartext == plaintext"
    ]
   },
   {
@@ -293,7 +294,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.7.0"
+   "version": "3.8.5"
   }
  },
  "nbformat": 4,
diff --git a/docs/source/api.rst b/docs/source/api.rst
new file mode 100644
index 00000000..de61bfdd
--- /dev/null
+++ b/docs/source/api.rst
@@ -0,0 +1,60 @@
+Public API
+==========
+
+.. automodule:: umbral
+
+Keys
+----
+
+.. autoclass:: SecretKey()
+    :members:
+    :show-inheritance:
+
+.. autoclass:: PublicKey()
+    :members:
+    :special-members: __eq__, __hash__
+    :show-inheritance:
+
+.. autoclass:: SecretKeyFactory()
+    :members:
+    :show-inheritance:
+
+Intermediate objects
+--------------------
+
+.. autoclass:: Capsule()
+    :special-members: __eq__, __hash__
+    :show-inheritance:
+
+.. autoclass:: KeyFrag()
+    :members: verify
+    :special-members: __eq__, __hash__
+    :show-inheritance:
+
+.. autoclass:: CapsuleFrag()
+    :members: verify
+    :special-members: __eq__, __hash__
+    :show-inheritance:
+
+Encryption, re-encryption and decryption
+----------------------------------------
+
+.. autofunction:: encrypt
+
+.. autofunction:: decrypt_original
+
+.. autofunction:: generate_kfrags
+
+.. autofunction:: reencrypt
+
+.. autofunction:: decrypt_reencrypted
+
+Utilities
+---------
+
+.. autoclass:: umbral.GenericError
+    :show-inheritance:
+
+.. autoclass:: umbral.serializable.Serializable
+    :members: from_bytes
+    :special-members: __bytes__
diff --git a/docs/source/choosing_and_using_curves.rst b/docs/source/choosing_and_using_curves.rst
deleted file mode 100644
index fa1357b0..00000000
--- a/docs/source/choosing_and_using_curves.rst
+++ /dev/null
@@ -1,63 +0,0 @@
-=========================
-Choosing and Using Curves
-=========================
-
-
-The matter of which curve to use is the subject of some debate.  If you aren't sure, you might start here:
-https://safecurves.cr.yp.to/
-
-A number of curves are available in the Cryptography.io_ library, on which pyUmbral depends.
-You can find them in the ``cryptography.hazmat.primitives.asymmetric.ec`` module.
-
-.. _Cryptography.io: https://cryptography.io/en/latest/
-
-Be careful when choosing a curve - the security of your application depends on it.
-
-We provide curve ``SECP256K1`` as a default because it is the basis for a number of crypto-blockchain projects;
-we don't otherwise endorse its security.
-We additionally support curves ``SECP256R1`` (also known as "NIST P-256") and ``SECP384R1`` ("NIST P-384").
-
-
-Setting a default curve
---------------------------
-
-Before you perform any ECC operations, you can set a default curve.
-
-.. code-block:: python
-
-    >>> from umbral.curve import SECP256K1
-    >>> config.set_default_curve(SECP256K1)
-
-If you don't set a default curve, then SECP256K1 will be set for you when you perform the first ECC
-operation.  This causes a small one-time performance penalty.
-
-
-.. code-block:: python
-
-    >>> from umbral import keys
-    >>> private_key = keys.UmbralPrivateKey.gen_key()
-
-    RuntimeWarning: No default curve has been set.  Using SECP256K1.
-    A slight performance penalty has been incurred for only this call.
-    Set a default curve with umbral.config.set_default_curve().
-
-
-To use SECP256K1 and avoid this penalty, you can simply call ``set_default_curve()`` with no argument:
-
-
-.. code-block:: python
-
-    >>> config.set_default_curve()
-
-Attempting to set the default curve twice in the same runtime will raise
-a ``UmbralConfigurationError``.
-
-
-.. code-block:: python
-
-    >>> from umbral import config
-    >>> config.set_default_curve()
-    >>> config.set_default_curve()
-    Traceback (most recent call last):
-        ...
-    umbral.config._CONFIG.UmbralConfigurationError
diff --git a/docs/source/index.rst b/docs/source/index.rst
index ba8074de..801a629e 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -31,7 +31,7 @@ pyUmbral
 .. end-badges
 
 pyUmbral is the reference implementation of the Umbral_ threshold proxy re-encryption scheme.
-It is open-source, built with Python, and uses OpenSSL_ and Cryptography.io_.
+It is open-source, built with Python, and uses OpenSSL_ via Cryptography.io_, and libsodium_ via PyNaCl_.
 
 Using Umbral, Alice (the data owner) can *delegate decryption rights* to Bob for
 any ciphertext intended to her, through a re-encryption process performed by a
@@ -50,6 +50,8 @@ a proxy re-encryption network to empower privacy in decentralized systems.
 .. _Cryptography.io: https://cryptography.io/en/latest/
 .. _OpenSSL: https://www.openssl.org/
 .. _nucypher: https://github.com/nucypher/nucypher
+.. _libsodium: https://github.com/jedisct1/libsodium
+.. _PyNaCl: https://pynacl.readthedocs.io/en/latest/
 
 .. toctree::
    :maxdepth: 3
@@ -57,6 +59,7 @@ a proxy re-encryption network to empower privacy in decentralized systems.
 
    installation
    using_pyumbral
+   api
 
 
 Academic Whitepaper
diff --git a/docs/source/installation.rst b/docs/source/installation.rst
index 90b99cdc..b6e8239f 100644
--- a/docs/source/installation.rst
+++ b/docs/source/installation.rst
@@ -1,7 +1,6 @@
-
 Installing pyUmbral
 ====================
-v0.1.3-alpha.2
+
 
 Using pip
 -------------------------
diff --git a/docs/source/using_pyumbral.rst b/docs/source/using_pyumbral.rst
index b7fe0f80..396f9f15 100644
--- a/docs/source/using_pyumbral.rst
+++ b/docs/source/using_pyumbral.rst
@@ -12,30 +12,24 @@ Using pyUmbral
     sys.path.append(os.path.abspath(os.getcwd()))
 
 
-.. testcleanup:: capsule_story
+Elliptic Curves
+===============
 
-    from umbral import config
-    config._CONFIG.___CONFIG__curve = None
-    config._CONFIG.___CONFIG__params = None
+The matter of which curve to use is the subject of some debate.  If you aren't sure, you might start here:
+https://safecurves.cr.yp.to/
 
+A number of curves are available in the Cryptography.io_ library, on which pyUmbral depends.
+You can find them in the ``cryptography.hazmat.primitives.asymmetric.ec`` module.
 
-Configuration
-==============
-
-
-Setting the default curve
---------------------------
+.. _Cryptography.io: https://cryptography.io/en/latest/
 
-The best way to start using pyUmbral is to decide on an elliptic curve to use and set it as your default.
-
-
-.. doctest:: capsule_story
+.. important::
 
-    >>> from umbral import config
-    >>> from umbral.curve import SECP256K1
-    >>> config.set_default_curve(SECP256K1)
+    Be careful when choosing a curve - the security of your application depends on it.
 
-For more information on curves, see :doc:`choosing_and_using_curves`.
+We provide curve ``SECP256K1`` as a default because it is the basis for a number of crypto-blockchain projects;
+we don't otherwise endorse its security.
+We additionally support curves ``SECP256R1`` (also known as "NIST P-256") and ``SECP384R1`` ("NIST P-384"), but they cannot currently be selected via the public API.
 
 
 Encryption
@@ -49,28 +43,27 @@ A delegating key pair and a signing key pair.
 
 .. doctest:: capsule_story
 
-    >>> from umbral import keys, signing
+    >>> from umbral import SecretKey, PublicKey
 
-    >>> alices_private_key = keys.UmbralPrivateKey.gen_key()
-    >>> alices_public_key = alices_private_key.get_pubkey()
+    >>> alices_secret_key = SecretKey.random()
+    >>> alices_public_key = PublicKey.from_secret_key(alices_secret_key)
 
-    >>> alices_signing_key = keys.UmbralPrivateKey.gen_key()
-    >>> alices_verifying_key = alices_signing_key.get_pubkey()
-    >>> alices_signer = signing.Signer(private_key=alices_signing_key)
+    >>> alices_signing_key = SecretKey.random()
+    >>> alices_verifying_key = PublicKey.from_secret_key(alices_signing_key)
 
 
 Encrypt with a public key
 --------------------------
 Now let's encrypt data with Alice's public key.
-Invocation of ``pre.encrypt`` returns both the ``ciphertext`` and a ``capsule``.
+Invocation of :py:func:`encrypt` returns both a ``capsule`` and a ``ciphertext``.
 Note that anyone with Alice's public key can perform this operation.
 
 
 .. doctest:: capsule_story
 
-    >>> from umbral import pre
+    >>> from umbral import encrypt
     >>> plaintext = b'Proxy Re-encryption is cool!'
-    >>> ciphertext, capsule = pre.encrypt(alices_public_key, plaintext)
+    >>> capsule, ciphertext = encrypt(alices_public_key, plaintext)
 
 
 Decrypt with a private key
@@ -80,9 +73,8 @@ Alice can open the capsule and decrypt the ciphertext with her private key.
 
 .. doctest:: capsule_story
 
-    >>> cleartext = pre.decrypt(ciphertext=ciphertext,
-    ...                         capsule=capsule,
-    ...                         decrypting_key=alices_private_key)
+    >>> from umbral import decrypt_original
+    >>> cleartext = decrypt_original(alices_secret_key, capsule, ciphertext)
 
 
 Threshold Re-Encryption
@@ -93,29 +85,29 @@ Bob Exists
 
 .. doctest:: capsule_story
 
-    >>> from umbral import keys
-    >>> bobs_private_key = keys.UmbralPrivateKey.gen_key()
-    >>> bobs_public_key = bobs_private_key.get_pubkey()
+    >>> bobs_secret_key = SecretKey.random()
+    >>> bobs_public_key = PublicKey.from_secret_key(bobs_secret_key)
 
 
-Alice grants access to Bob by generating kfrags 
+Alice grants access to Bob by generating kfrags
 -----------------------------------------------
-When Alice wants to grant Bob access to open her encrypted messages, 
+When Alice wants to grant Bob access to view her encrypted data,
 she creates *re-encryption key fragments*, or *"kfrags"*,
 which are next sent to N proxies or *Ursulas*.
 
-Alice must specify ``N`` (the total number of kfrags),
+Alice must specify ``num_kfrags`` (the total number of kfrags),
 and a ``threshold`` (the minimum number of kfrags needed to activate a capsule).
 In the following example, Alice creates 20 kfrags,
 but Bob needs to get only 10 re-encryptions to activate the capsule.
 
 .. doctest:: capsule_story
 
-    >>> kfrags = pre.generate_kfrags(delegating_privkey=alices_private_key,
-    ...                              signer=alices_signer,
-    ...                              receiving_pubkey=bobs_public_key,
-    ...                              threshold=10,
-    ...                              N=20)
+    >>> from umbral import generate_kfrags
+    >>> kfrags = generate_kfrags(delegating_sk=alices_secret_key,
+    ...                          receiving_pk=bobs_public_key,
+    ...                          signing_sk=alices_signing_key,
+    ...                          threshold=10,
+    ...                          num_kfrags=20)
 
 
 Bob receives a capsule
@@ -137,25 +129,24 @@ or re-encrypted for him by Ursula, he will not be able to open it.
 
 .. doctest:: capsule_story
 
-    >>> fail = pre.decrypt(ciphertext=ciphertext,
-    ...                    capsule=capsule,
-    ...                    decrypting_key=bobs_private_key)
+    >>> fail = decrypt_original(sk=bobs_secret_key,
+    ...                         capsule=capsule,
+    ...                         ciphertext=ciphertext)
     Traceback (most recent call last):
         ...
-    umbral.pre.UmbralDecryptionError
+    umbral.GenericError
 
 
 Ursulas perform re-encryption
 ------------------------------
-Bob asks several Ursulas to re-encrypt the capsule so he can open it. 
+Bob asks several Ursulas to re-encrypt the capsule so he can open it.
 Each Ursula performs re-encryption on the capsule using the ``kfrag``
 provided by Alice, obtaining this way a "capsule fragment", or ``cfrag``.
 Let's mock a network or transport layer by sampling ``threshold`` random kfrags,
-one for each required Ursula. Note that each Ursula must prepare the received 
-capsule before re-encryption by setting the proper correctness keys.
+one for each required Ursula.
 
 Bob collects the resulting cfrags from several Ursulas.
-Bob must gather at least ``threshold`` cfrags in order to activate the capsule.
+Bob must gather at least ``threshold`` cfrags in order to open the capsule.
 
 
 .. doctest:: capsule_story
@@ -164,14 +155,10 @@ Bob must gather at least ``threshold`` cfrags in order to activate the capsule.
     >>> kfrags = random.sample(kfrags,  # All kfrags from above
     ...                        10)      # M - Threshold
 
-    >>> capsule.set_correctness_keys(delegating=alices_public_key,
-    ...                              receiving=bobs_public_key,
-    ...                              verifying=alices_verifying_key)
-    (True, True, True)
-
+    >>> from umbral import reencrypt
     >>> cfrags = list()                 # Bob's cfrag collection
     >>> for kfrag in kfrags:
-    ...     cfrag = pre.reencrypt(kfrag=kfrag, capsule=capsule)
+    ...     cfrag = reencrypt(capsule=capsule, kfrag=kfrag)
     ...     cfrags.append(cfrag)        # Bob collects a cfrag
 
 .. doctest:: capsule_story
@@ -183,32 +170,34 @@ Bob must gather at least ``threshold`` cfrags in order to activate the capsule.
 Decryption
 ==================================
 
-Bob attaches cfrags to the capsule
-----------------------------------
-Bob attaches at least ``threshold`` cfrags to the capsule,
-which has to be prepared in advance with the necessary correctness keys. 
-Only then it can become *activated*.
+Bob checks the capsule fragments
+--------------------------------
+Bob can verify that the capsule fragments are valid and really originate from Alice,
+using Alice's public keys.
 
 .. doctest:: capsule_story
 
-    >>> capsule.set_correctness_keys(delegating=alices_public_key,
-    ...                              receiving=bobs_public_key,
-    ...                              verifying=alices_verifying_key)
-    (False, False, False)
+    >>> all(cfrag.verify(capsule,
+    ...                  delegating_pk=alices_public_key,
+    ...                  receiving_pk=bobs_public_key,
+    ...                  signing_pk=alices_verifying_key)
+    ...     for cfrag in cfrags)
+    True
 
-    >>> for cfrag in cfrags:
-    ...     capsule.attach_cfrag(cfrag)
 
-
-Bob activates and opens the capsule
-------------------------------------
-Finally, Bob decrypts the re-encrypted ciphertext using the activated capsule.
+Bob opens the capsule
+---------------------
+Finally, Bob decrypts the re-encrypted ciphertext using his key.
 
 .. doctest:: capsule_story
 
-    >>> cleartext = pre.decrypt(ciphertext=ciphertext,
-    ...                         capsule=capsule,
-    ...                         decrypting_key=bobs_private_key)
+    >>> from umbral import decrypt_reencrypted
+    >>> cleartext = decrypt_reencrypted(decrypting_sk=bobs_secret_key,
+    ...                                 delegating_pk=alices_public_key,
+    ...                                 capsule=capsule,
+    ...                                 cfrags=cfrags,
+    ...                                 ciphertext=ciphertext)
+
 
 .. doctest:: capsule_story
    :hide:
diff --git a/setup.py b/setup.py
index e7b2b8ee..992124d4 100644
--- a/setup.py
+++ b/setup.py
@@ -60,9 +60,6 @@ def run(self):
     'setuptools',
     'cryptography>=2.3',
     'pynacl',
-    'pysha3',
-    'constant-sorrow>=0.1.0a7',
-    'bytestring-splitter',
 ]
 
 DEV_INSTALL_REQUIRES = [
@@ -82,7 +79,7 @@ def run(self):
 
 EXTRAS_REQUIRE = {
     'testing': DEV_INSTALL_REQUIRES,
-    'docs': ['sphinx', 'sphinx-autobuild'],
+    'docs': ['sphinx', 'sphinx-autobuild', 'sphinx_rtd_theme'],
     'benchmarks': ['pytest-benchmark'],
 }
 
@@ -105,7 +102,6 @@ def run(self):
           "Natural Language :: English",
           "Programming Language :: Python :: Implementation",
           "Programming Language :: Python :: 3 :: Only",
-          "Programming Language :: Python :: 3.5",
           "Programming Language :: Python :: 3.6",
           "Programming Language :: Python :: 3.7",
           "Topic :: Scientific/Engineering",
diff --git a/tests/__init__.py b/tests/__init__.py
index b1419391..e69de29b 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,16 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
diff --git a/tests/conftest.py b/tests/conftest.py
index ed4fca93..954b727e 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,139 +1,51 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
 import pytest
-from collections import namedtuple
-
-from umbral import keys
-from umbral.curve import SECP256K1, SECP384R1, SECP256R1
-from umbral.curvebn import CurveBN
-from umbral.config import set_default_curve
-from umbral.point import Point
-from umbral.signing import Signer
-from umbral import pre
-
-set_default_curve(SECP256K1)
-
-parameters = (
-    # (N, M)
-    (1, 1),
-    (6, 1),
-    (6, 4),
-    (6, 6),
-    (50, 30)
-)
 
-wrong_parameters = (
-    # (N, M)
-    (-1, -1),   (-1, 0),    (-1, 5),
-    (0, -1),    (0, 0),     (0, 5),
-    (1, -1),    (1, 0),     (1, 5),
-    (5, -1),    (5, 0),     (5, 10)
-)
-
-other_supported_curves = (
-    SECP384R1,
-    SECP256R1
-)
-
-kfrag_signing_modes = (
-    (True, True), (True, False), (False, True), (False, False)
-)
+from umbral import SecretKey, PublicKey, generate_kfrags, encrypt
 
 
 @pytest.fixture
 def alices_keys():
-    delegating_priv = keys.UmbralPrivateKey.gen_key()
-    signing_priv = keys.UmbralPrivateKey.gen_key()
-    return delegating_priv, signing_priv
+    delegating_sk = SecretKey.random()
+    signing_sk = SecretKey.random()
+    return delegating_sk, signing_sk
 
 
 @pytest.fixture
 def bobs_keys():
-    priv = keys.UmbralPrivateKey.gen_key()
-    pub = priv.get_pubkey()
-    return priv, pub
-
-
-@pytest.fixture()
-def random_ec_point1():
-    yield Point.gen_rand()
-
-
-@pytest.fixture()
-def random_ec_point2():
-    yield Point.gen_rand()
-
-
-@pytest.fixture()
-def random_ec_curvebn1():
-    yield CurveBN.gen_rand()
-
-
-@pytest.fixture()
-def random_ec_curvebn2():
-    yield CurveBN.gen_rand()
+    sk = SecretKey.random()
+    pk = PublicKey.from_secret_key(sk)
+    return sk, pk
 
 
+@pytest.fixture
+def kfrags(alices_keys, bobs_keys):
+    delegating_sk, signing_sk = alices_keys
+    receiving_sk, receiving_pk = bobs_keys
+    yield generate_kfrags(delegating_sk=delegating_sk,
+                          signing_sk=signing_sk,
+                          receiving_pk=receiving_pk,
+                          threshold=6, num_kfrags=10)
 
 
 @pytest.fixture(scope='session')
 def message():
-    message = b"dnunez [9:30 AM]" \
-              b"@Tux we had this super fruitful discussion last night with @jMyles @michwill @KPrasch" \
-              b"to sum up: the symmetric ciphertext is now called the 'Chimney'." \
-              b"the chimney of the capsule, of course" \
-              b"tux [9:32 AM]" \
-              b"wat"
+    message = (b"dnunez [9:30 AM]"
+               b"@Tux we had this super fruitful discussion last night with @jMyles @michwill @KPrasch"
+               b"to sum up: the symmetric ciphertext is now called the 'Chimney'."
+               b"the chimney of the capsule, of course"
+               b"tux [9:32 AM]"
+               b"wat")
     return message
 
 
 @pytest.fixture
-def ciphertext_and_capsule(alices_keys, message):
-    delegating_privkey, _signing_privkey = alices_keys
-    # See nucypher's issue #183
-    chimney, capsule = pre.encrypt(delegating_privkey.get_pubkey(), message)
-    return chimney, capsule
+def capsule_and_ciphertext(alices_keys, message):
+    delegating_sk, _signing_sk = alices_keys
+    capsule, ciphertext = encrypt(PublicKey.from_secret_key(delegating_sk), message)
+    return capsule, ciphertext
 
 
 @pytest.fixture
-def capsule(ciphertext_and_capsule):
-    ciphertext, capsule = ciphertext_and_capsule
+def capsule(capsule_and_ciphertext):
+    capsule, ciphertext = capsule_and_ciphertext
     return capsule
-
-
-@pytest.fixture
-def prepared_capsule(alices_keys, bobs_keys, capsule):
-    delegating_privkey, signing_privkey = alices_keys
-    _receiving_privkey, receiving_pubkey = bobs_keys
-    capsule.set_correctness_keys(delegating=delegating_privkey.get_pubkey(),
-                                 receiving=receiving_pubkey,
-                                 verifying=signing_privkey.get_pubkey())
-    return capsule    
-
-
-@pytest.fixture
-def kfrags(alices_keys, bobs_keys):
-    delegating_privkey, signing_privkey = alices_keys
-    signer_alice = Signer(signing_privkey)
-
-    receiving_privkey, receiving_pubkey = bobs_keys
-
-    yield pre.generate_kfrags(delegating_privkey=delegating_privkey,
-                              signer=signer_alice,
-                              receiving_pubkey=receiving_pubkey,
-                              threshold=6, N=10)
diff --git a/tests/functional/__init__.py b/tests/functional/__init__.py
deleted file mode 100644
index b1419391..00000000
--- a/tests/functional/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
diff --git a/tests/functional/test_correctness.py b/tests/functional/test_correctness.py
deleted file mode 100644
index eea0a063..00000000
--- a/tests/functional/test_correctness.py
+++ /dev/null
@@ -1,156 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-import pytest
-
-from umbral import pre
-from umbral.point import Point
-from umbral.signing import Signer
-from umbral.cfrags import CapsuleFrag
-
-
-def test_cheating_ursula_replays_old_reencryption(alices_keys, bobs_keys,
-                                                  kfrags, prepared_capsule):
-    delegating_privkey, signing_privkey = alices_keys
-    delegating_pubkey = delegating_privkey.get_pubkey()
-
-    receiving_privkey, receiving_pubkey = bobs_keys
-
-    capsule_alice1 = prepared_capsule
-
-    _unused_key2, capsule_alice2 = pre._encapsulate(delegating_pubkey)
-
-    capsule_alice2.set_correctness_keys(delegating=delegating_pubkey,
-                                        receiving=receiving_pubkey,
-                                        verifying=signing_privkey.get_pubkey())
-
-    cfrags = []
-    for i, kfrag in enumerate(kfrags):
-
-        # Example of potential metadata to describe the re-encryption request
-        metadata_i = "This is an example of metadata for re-encryption request #{}"
-        metadata_i = metadata_i.format(i).encode()
-
-        if i == 0:
-            # Let's put the re-encryption of a different Alice ciphertext
-            cfrag = pre.reencrypt(kfrag, capsule_alice2, metadata=metadata_i)
-        else:
-            cfrag = pre.reencrypt(kfrag, capsule_alice1, metadata=metadata_i)
-
-        cfrags.append(cfrag)
-
-    #  CFrag 0 is not valid ...
-    assert not cfrags[0].verify_correctness(capsule_alice1)
-
-    # ... and trying to attach it raises an error.
-    with pytest.raises(pre.UmbralCorrectnessError) as exception_info:
-        capsule_alice1.attach_cfrag(cfrags[0])
-
-    correctness_error = exception_info.value
-    assert cfrags[0] in correctness_error.offending_cfrags
-    assert len(correctness_error.offending_cfrags) == 1
-
-    # The rest of CFrags should be correct:
-    correct_cases = 0
-    for cfrag_i in cfrags[1:]:
-        assert cfrag_i.verify_correctness(capsule_alice1)
-        capsule_alice1.attach_cfrag(cfrag_i)
-        correct_cases += 1
-
-    assert correct_cases == len(cfrags[1:])
-
-
-def test_cheating_ursula_sends_garbage(kfrags, prepared_capsule):
-    capsule_alice = prepared_capsule
-
-    cfrags = []
-    for i, kfrag in enumerate(kfrags):
-        # Example of potential metadata to describe the re-encryption request
-        metadata_i = "This is an example of metadata for re-encryption request #{}"
-        metadata_i = metadata_i.format(i).encode()
-
-        cfrag = pre.reencrypt(kfrag, capsule_alice, metadata=metadata_i)
-        cfrags.append(cfrag)
-
-    # Let's put random garbage in one of the cfrags
-    cfrags[0].point_e1 = Point.gen_rand()
-    cfrags[0].point_v1 = Point.gen_rand()
-
-    #  Of course, this CFrag is not valid ...
-    assert not cfrags[0].verify_correctness(capsule_alice)
-
-    # ... and trying to attach it raises an error.
-    with pytest.raises(pre.UmbralCorrectnessError) as exception_info:
-        capsule_alice.attach_cfrag(cfrags[0])
-
-    correctness_error = exception_info.value
-    assert cfrags[0] in correctness_error.offending_cfrags
-    assert len(correctness_error.offending_cfrags) == 1
-
-    # The response of cheating Ursula is in cfrags[0],
-    # so the rest of CFrags should be correct:
-    for cfrag_i in cfrags[1:]:
-        assert cfrag_i.verify_correctness(capsule_alice)
-        capsule_alice.attach_cfrag(cfrag_i)
-
-
-def test_cfrag_with_missing_proof_cannot_be_attached(kfrags, prepared_capsule):
-    capsule = prepared_capsule
-
-    cfrags = []
-    for kfrag in kfrags:
-        cfrag = pre.reencrypt(kfrag, capsule)
-        cfrags.append(cfrag)
-
-    # If the proof is lost (e.g., it is chopped off a serialized CFrag or similar), 
-    #  then the CFrag cannot be attached.
-    cfrags[0].proof = None
-    with pytest.raises(CapsuleFrag.NoProofProvided):
-        capsule.attach_cfrag(cfrags[0])
-
-    # The remaining CFrags are fine, so they can be attached correctly 
-    for cfrag in cfrags[1:]:
-        capsule.attach_cfrag(cfrag)
-
-
-def test_kfrags_signed_without_correctness_keys(alices_keys, bobs_keys, capsule):
-    delegating_privkey, signing_privkey = alices_keys
-    delegating_pubkey = delegating_privkey.get_pubkey()
-    verifying_key = signing_privkey.get_pubkey()
-
-    receiving_privkey, receiving_pubkey = bobs_keys
-
-    kfrags = pre.generate_kfrags(delegating_privkey=delegating_privkey,
-                                 signer=Signer(signing_privkey),
-                                 receiving_pubkey=receiving_pubkey,
-                                 threshold=6,
-                                 N=10,
-                                 sign_delegating_key=False,
-                                 sign_receiving_key=False)
-
-    for kfrag in kfrags:
-        # You can verify the KFrag specifying only the verifying key
-        assert kfrag.verify(signing_pubkey=verifying_key)
-
-        # ... or if it is set in the capsule, using the capsule
-        capsule.set_correctness_keys(verifying=verifying_key)
-        assert kfrag.verify_for_capsule(capsule)
-
-        # It should even work when other keys are set in the capsule
-        assert kfrag.verify(signing_pubkey=verifying_key,
-                            delegating_pubkey=delegating_pubkey,
-                            receiving_pubkey=receiving_pubkey)
diff --git a/tests/functional/test_correctness_keys.py b/tests/functional/test_correctness_keys.py
deleted file mode 100644
index aca88251..00000000
--- a/tests/functional/test_correctness_keys.py
+++ /dev/null
@@ -1,81 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-import pytest
-
-from umbral import pre
-from umbral.keys import UmbralPrivateKey
-from umbral.kfrags import KFrag
-
-
-def test_set_correctness_keys(alices_keys, bobs_keys, capsule, kfrags):
-    """
-    If the three keys do appear together, along with the capsule,
-    we can attach them all at once.
-    """
-
-    delegating_privkey, signing_privkey = alices_keys
-    _receiving_privkey, receiving_pubkey = bobs_keys
-
-    capsule.set_correctness_keys(delegating_privkey.get_pubkey(),
-                                 receiving_pubkey,
-                                 signing_privkey.get_pubkey()
-                                 )
-
-    for kfrag in kfrags:
-        cfrag = pre.reencrypt(kfrag, capsule)
-        capsule.attach_cfrag(cfrag)
-
-
-def test_setting_one_correctness_keys(alices_keys, capsule):
-    # The capsule doesn't have any correctness keys set initially
-    assert capsule.get_correctness_keys()['delegating'] is None
-    assert capsule.get_correctness_keys()['receiving'] is None
-    assert capsule.get_correctness_keys()['verifying'] is None
-
-    # Let's set only one of them, e.g., the delegating key
-    delegating_privkey, _signing_privkey = alices_keys
-    delegating_pubkey = delegating_privkey.get_pubkey()
-
-    details = capsule.set_correctness_keys(delegating=delegating_pubkey)
-
-    # Since we are only setting the first key ("delegating"),
-    # the other keys are not set
-    assert details == (True, False, False)
-
-    assert capsule.get_correctness_keys()['delegating'] == delegating_pubkey
-    assert capsule.get_correctness_keys()['receiving'] is None
-    assert capsule.get_correctness_keys()['verifying'] is None
-
-
-def test_set_invalid_correctness_keys(alices_keys, capsule, kfrags):
-    """
-    If the three keys do appear together, along with the capsule,
-    we can attach them all at once.
-    """
-
-    delegating_privkey, signing_privkey = alices_keys
-    unrelated_receiving_pubkey = UmbralPrivateKey.gen_key().get_pubkey()
-
-    capsule.set_correctness_keys(delegating_privkey.get_pubkey(),
-                                 unrelated_receiving_pubkey,
-                                 signing_privkey.get_pubkey()
-                                 )
-
-    for kfrag in kfrags:
-        with pytest.raises(KFrag.NotValid):
-            cfrag = pre.reencrypt(kfrag, capsule)
diff --git a/tests/functional/test_pre_api.py b/tests/functional/test_pre_api.py
deleted file mode 100644
index 1928a0ad..00000000
--- a/tests/functional/test_pre_api.py
+++ /dev/null
@@ -1,56 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-import pytest
-
-from umbral import pre
-from umbral.signing import Signer
-from ..conftest import wrong_parameters
-
-
-def test_public_key_encryption(alices_keys):
-    delegating_privkey, _ = alices_keys
-    plain_data = b'peace at dawn'
-    ciphertext, capsule = pre.encrypt(delegating_privkey.get_pubkey(), plain_data)
-    cleartext = pre.decrypt(ciphertext, capsule, delegating_privkey)
-    assert cleartext == plain_data
-
-
-@pytest.mark.parametrize("N, M", wrong_parameters)
-def test_wrong_N_M_in_split_rekey(N, M, alices_keys, bobs_keys):
-    delegating_privkey, signing_privkey = alices_keys
-    signer = Signer(signing_privkey)
-    _receiving_privkey, receiving_pubkey = bobs_keys
-
-    with pytest.raises(ValueError):
-        _kfrags = pre.generate_kfrags(delegating_privkey=delegating_privkey,
-                                      signer=signer,
-                                      receiving_pubkey=receiving_pubkey,
-                                      threshold=M,
-                                      N=N)
-
-
-def test_decryption_error(alices_keys, bobs_keys, ciphertext_and_capsule, message):
-    delegating_privkey, _signing_privkey = alices_keys
-    receiving_privkey, _receiving_pubkey = bobs_keys
-    ciphertext, capsule = ciphertext_and_capsule
-
-    cleartext = pre.decrypt(ciphertext, capsule, delegating_privkey)
-    assert message == cleartext
-
-    with pytest.raises(pre.UmbralDecryptionError) as e:
-        _cleartext = pre.decrypt(ciphertext, capsule, receiving_privkey)
diff --git a/tests/functional/test_vectors.py b/tests/functional/test_vectors.py
deleted file mode 100644
index b3e4717c..00000000
--- a/tests/functional/test_vectors.py
+++ /dev/null
@@ -1,190 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-import json
-import os
-
-from umbral.curvebn import CurveBN
-from umbral.point import Point
-from umbral.keys import UmbralPublicKey
-from umbral.config import default_params
-from umbral.kfrags import KFrag
-from umbral.cfrags import CapsuleFrag
-from umbral.random_oracles import hash_to_curvebn, unsafe_hash_to_point, kdf
-from umbral import pre
-
-def test_curvebn_operations():
-
-    vector_file = os.path.join('vectors', 'vectors_curvebn_operations.json')
-    try:
-        with open(vector_file) as f:
-            vector_suite = json.load(f)
-    except OSError:
-        raise
-
-    bn1 = CurveBN.from_bytes(bytes.fromhex(vector_suite['first operand']))
-    bn2 = CurveBN.from_bytes(bytes.fromhex(vector_suite['second operand']))
-
-    expected = dict()
-    for op_result in vector_suite['vectors']:
-        result = bytes.fromhex(op_result['result'])
-        expected[op_result['operation']] = CurveBN.from_bytes(result)
-
-    test = [('Addition', bn1 + bn2),
-            ('Subtraction', bn1 - bn2),
-            ('Multiplication', bn1 * bn2),
-            ('Division', bn1 / bn2),
-            ('Pow', bn1 ** bn2),
-            ('Mod', bn1 % bn2),
-            ('Inverse', ~bn1),
-            ('Neg', -bn1),
-            ]
-
-    for (operation, result) in test:
-        assert result == expected[operation], 'Error in {}'.format(operation)
-
-def test_curvebn_hash():
-
-    vector_file = os.path.join('vectors', 'vectors_curvebn_hash.json')
-    try:
-        with open(vector_file) as f:
-            vector_suite = json.load(f)
-    except OSError:
-        raise
-
-    params = default_params()
-
-    for vector in vector_suite['vectors']:
-        hash_input = [bytes.fromhex(item['bytes']) for item in vector['input']]
-        expected = CurveBN.from_bytes(bytes.fromhex(vector['output']))
-        assert hash_to_curvebn(*hash_input, params=params) == expected
-
-
-def test_point_operations():
-
-    vector_file = os.path.join('vectors', 'vectors_point_operations.json')
-    try:
-        with open(vector_file) as f:
-            vector_suite = json.load(f)
-    except OSError:
-        raise
-
-    point1 = Point.from_bytes(bytes.fromhex(vector_suite['first Point operand']))
-    point2 = Point.from_bytes(bytes.fromhex(vector_suite['second Point operand']))
-    bn1 = CurveBN.from_bytes(bytes.fromhex(vector_suite['CurveBN operand']))
-
-    expected = dict()
-    for op_result in vector_suite['vectors']:
-        expected[op_result['operation']] = bytes.fromhex(op_result['result'])
-
-    test = [('Addition', point1 + point2),
-            ('Subtraction', point1 - point2),
-            ('Multiplication', bn1 * point1),
-            ('Inversion', -point1),
-            ]
-
-    for (operation, result) in test:
-        assert result == Point.from_bytes(expected[operation]), 'Error in {}'.format(operation)
-
-    test = [('To_affine.X', point1.to_affine()[0]),
-            ('To_affine.Y', point1.to_affine()[1]),
-            ]
-
-    for (operation, result) in test:
-        assert result == int.from_bytes(expected[operation], 'big'), 'Error in {}'.format(operation)
-
-    assert kdf(point1, pre.DEM_KEYSIZE) == expected['kdf']
-
-
-def test_unsafe_hash_to_point():
-
-    vector_file = os.path.join('vectors', 'vectors_unsafe_hash_to_point.json')
-    try:
-        with open(vector_file) as f:
-            vector_suite = json.load(f)
-    except OSError:
-        raise
-
-    params = default_params()
-
-    for item in vector_suite['vectors']:
-        data = bytes.fromhex(item['data'])
-        label = bytes.fromhex(item['label'])
-        expected = Point.from_bytes(bytes.fromhex(item['point']))
-        assert expected == unsafe_hash_to_point(label=label, data=data, params=params)
-
-
-def test_kfrags():
-
-    vector_file = os.path.join('vectors', 'vectors_kfrags.json')
-    try:
-        with open(vector_file) as f:
-            vector_suite = json.load(f)
-    except OSError:
-        raise
-
-    verifying_key = UmbralPublicKey.from_bytes(bytes.fromhex(vector_suite['verifying_key']))
-    delegating_key = UmbralPublicKey.from_bytes(bytes.fromhex(vector_suite['delegating_key']))
-    receiving_key = UmbralPublicKey.from_bytes(bytes.fromhex(vector_suite['receiving_key']))
-
-    for json_kfrag in vector_suite['vectors']:
-        kfrag = KFrag.from_bytes(bytes.fromhex(json_kfrag['kfrag']))
-        assert kfrag.verify(signing_pubkey=verifying_key,
-                            delegating_pubkey=delegating_key,
-                            receiving_pubkey=receiving_key), \
-            'Invalid KFrag {}'.format(kfrag.to_bytes().hex())
-
-
-def test_cfrags():
-
-    vector_file = os.path.join('vectors', 'vectors_cfrags.json')
-    try:
-        with open(vector_file) as f:
-            vector_suite = json.load(f)
-    except OSError:
-        raise
-
-    params = default_params()
-
-    capsule = pre.Capsule.from_bytes(bytes.fromhex(vector_suite['capsule']),
-                                     params=params)
-
-    verifying_key = UmbralPublicKey.from_bytes(bytes.fromhex(vector_suite['verifying_key']))
-    delegating_key = UmbralPublicKey.from_bytes(bytes.fromhex(vector_suite['delegating_key']))
-    receiving_key = UmbralPublicKey.from_bytes(bytes.fromhex(vector_suite['receiving_key']))
-
-    kfrags_n_cfrags = [(KFrag.from_bytes(bytes.fromhex(json_kfrag['kfrag'])),
-                        CapsuleFrag.from_bytes(bytes.fromhex(json_kfrag['cfrag'])))
-                       for json_kfrag in vector_suite['vectors']]
-
-    capsule.set_correctness_keys(delegating=delegating_key,
-                                 receiving=receiving_key,
-                                 verifying=verifying_key)
-
-    for kfrag, cfrag in kfrags_n_cfrags:
-        assert kfrag.verify(signing_pubkey=verifying_key,
-                            delegating_pubkey=delegating_key,
-                            receiving_pubkey=receiving_key), \
-            'Invalid KFrag {}'.format(kfrag.to_bytes().hex())
-
-        new_cfrag = pre.reencrypt(kfrag, capsule, provide_proof=False)
-        assert new_cfrag.point_e1 == cfrag.point_e1
-        assert new_cfrag.point_v1 == cfrag.point_v1
-        assert new_cfrag.kfrag_id == cfrag.kfrag_id
-        assert new_cfrag.point_precursor == cfrag.point_precursor
-        assert new_cfrag.proof is None
-        assert cfrag.to_bytes() == new_cfrag.to_bytes()
diff --git a/tests/metrics/reencryption_benchmark.py b/tests/metrics/reencryption_benchmark.py
index dce7eaa0..ee1ca91c 100644
--- a/tests/metrics/reencryption_benchmark.py
+++ b/tests/metrics/reencryption_benchmark.py
@@ -1,41 +1,25 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-import sys
 import os
 import time
 
-sys.path.append(os.path.abspath(os.getcwd()))
-
-
 import pytest
-from umbral import keys, pre
-from umbral.config import default_curve
-from umbral.params import UmbralParameters
-from umbral.signing import Signer
 
+import umbral as umbral_py
 
-#
-# Setup
-#
+try:
+    import umbral_pre as umbral_rs
+except ImportError:
+    umbral_rs = None
 
 
-CURVE = default_curve()
-PARAMS = UmbralParameters(curve=CURVE)
+def pytest_generate_tests(metafunc):
+    if 'umbral' in metafunc.fixturenames:
+        implementations = [umbral_py]
+        ids = ['python']
+        if umbral_rs is not None:
+            implementations.append(umbral_rs)
+            ids.append('rust')
+        metafunc.parametrize('umbral', implementations, ids=ids)
+
 
 #                              Faster
 #              (M, N)        # |
@@ -51,25 +35,20 @@
 #                              Slower
 
 
-def __standard_encryption_api() -> tuple:
+def __standard_encryption_api(umbral) -> tuple:
 
-    delegating_privkey = keys.UmbralPrivateKey.gen_key(params=PARAMS)
-    delegating_pubkey = delegating_privkey.get_pubkey()
+    delegating_sk = umbral.SecretKey.random()
+    delegating_pk = umbral.PublicKey.from_secret_key(delegating_sk)
 
-    signing_privkey = keys.UmbralPrivateKey.gen_key(params=PARAMS)
-    signer = Signer(signing_privkey)
+    signing_sk = umbral.SecretKey.random()
 
-    receiving_privkey = keys.UmbralPrivateKey.gen_key(params=PARAMS)
-    receiving_pubkey = receiving_privkey.get_pubkey()
+    receiving_sk = umbral.SecretKey.random()
+    receiving_pk = umbral.PublicKey.from_secret_key(receiving_sk)
 
     plain_data = os.urandom(32)
-    ciphertext, capsule = pre.encrypt(delegating_pubkey, plain_data)
-
-    capsule.set_correctness_keys(delegating=delegating_pubkey,
-                                 receiving=receiving_pubkey,
-                                 verifying=signing_privkey.get_pubkey())
+    capsule, ciphertext = umbral.encrypt(delegating_pk, plain_data)
 
-    return delegating_privkey, signer, receiving_pubkey, ciphertext, capsule
+    return delegating_sk, receiving_pk, signing_sk, ciphertext, capsule
 
 
 #
@@ -82,16 +61,13 @@ def __standard_encryption_api() -> tuple:
                        warmup=True,
                        warmup_iterations=10)
 @pytest.mark.parametrize("m, n", FRAG_VALUES)
-def test_generate_kfrags_performance(benchmark, m: int, n: int) -> None:
+def test_generate_kfrags_performance(benchmark, m: int, n: int, umbral) -> None:
 
     def __setup():
-        delegating_privkey, signer, receiving_pubkey, ciphertext, capsule = __standard_encryption_api()
-        args = (delegating_privkey, receiving_pubkey)
-        kwargs = {"threshold": m, "N": n, "signer": signer}
-        return args, kwargs
+        delegating_sk, receiving_pk, signing_sk, ciphertext, capsule = __standard_encryption_api(umbral)
+        return (delegating_sk, receiving_pk, signing_sk, m, n, True, True), {}
 
-    print("\nBenchmarking {function} with M:{M} of N:{N}...".format(function="pre.generate_kfrags", M=m, N=n))
-    benchmark.pedantic(pre.generate_kfrags, setup=__setup, rounds=1000)
+    benchmark.pedantic(umbral.generate_kfrags, setup=__setup, rounds=1000)
     assert True  # ensure function finishes and succeeds.
 
 
@@ -105,17 +81,15 @@ def __setup():
                        warmup=True,
                        warmup_iterations=10)
 @pytest.mark.parametrize("m, n", ((6, 10), ))
-def test_random_frag_reencryption_performance(benchmark, m: int, n: int) -> None:
+def test_random_frag_reencryption_performance(benchmark, m: int, n: int, umbral) -> None:
 
     def __setup():
-        delegating_privkey, signer, receiving_pubkey, ciphertext, capsule = __standard_encryption_api()
-        kfrags = pre.generate_kfrags(delegating_privkey, receiving_pubkey, m, n, signer)
+        delegating_sk, receiving_pk, signing_sk, ciphertext, capsule = __standard_encryption_api(umbral)
+        kfrags = umbral.generate_kfrags(delegating_sk, receiving_pk, signing_sk, m, n, True, True)
         one_kfrag, *remaining_kfrags = kfrags
-        args, kwargs = tuple(), {"kfrag": one_kfrag, "capsule": capsule},
-        return args, kwargs
+        return (capsule, one_kfrag), {}
 
-    print("\nBenchmarking {} with randomly created fragments...".format("pre.reencrypt"))
-    benchmark.pedantic(pre.reencrypt, setup=__setup, rounds=1000)
+    benchmark.pedantic(umbral.reencrypt, setup=__setup, rounds=1000)
     assert True  # ensure function finishes and succeeds.
 
 
@@ -128,13 +102,12 @@ def __setup():
                        warmup=True,
                        warmup_iterations=10)
 @pytest.mark.parametrize("m, n", ((6, 10), ))
-def test_single_frag_reencryption_performance(benchmark, m: int, n: int) -> None:
+def test_single_frag_reencryption_performance(benchmark, m: int, n: int, umbral) -> None:
 
-    delegating_privkey, signer, receiving_pubkey, ciphertext, capsule = __standard_encryption_api()
-    kfrags = pre.generate_kfrags(delegating_privkey, receiving_pubkey, m, n, signer)
+    delegating_sk, receiving_pk, signing_sk, ciphertext, capsule = __standard_encryption_api(umbral)
+    kfrags = umbral.generate_kfrags(delegating_sk, receiving_pk, signing_sk, m, n, True, True)
     one_kfrag, *remaining_kfrags = kfrags
-    args, kwargs = tuple(), {"kfrag": one_kfrag, "capsule": capsule},
+    args, kwargs = (capsule, one_kfrag), {}
 
-    print("\nBenchmarking {} with the same fragment({M} of {N}) repeatedly...".format("pre.reencrypt", M=m, N=n))
-    benchmark.pedantic(pre.reencrypt, args=args, kwargs=kwargs, iterations=20, rounds=100)
+    benchmark.pedantic(umbral.reencrypt, args=args, kwargs=kwargs, iterations=20, rounds=100)
     assert True  # ensure function finishes and succeeds.
diff --git a/tests/metrics/reencryption_firehose.py b/tests/metrics/reencryption_firehose.py
index 17039563..defe6be6 100644
--- a/tests/metrics/reencryption_firehose.py
+++ b/tests/metrics/reencryption_firehose.py
@@ -1,57 +1,30 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
 import os
 import sys
-from typing import Tuple, List
 
 sys.path.append(os.path.abspath(os.getcwd()))
 
-from umbral.kfrags import KFrag
-from umbral.pre import Capsule
-from umbral import keys, pre
-from umbral.config import default_curve
-from umbral.params import UmbralParameters
-from umbral.signing import Signer
+from typing import Tuple, List
+
+import umbral
+
 
-CURVE = default_curve()
-PARAMS = UmbralParameters(curve=CURVE)
 REENCRYPTIONS = 1000
 
 
-def __produce_kfrags_and_capsule(m: int, n: int) -> Tuple[List[KFrag], Capsule]:
+def __produce_kfrags_and_capsule(m: int, n: int) -> Tuple[List[umbral.KeyFrag], umbral.Capsule]:
 
-    delegating_privkey = keys.UmbralPrivateKey.gen_key(params=PARAMS)
-    delegating_pubkey = delegating_privkey.get_pubkey()
+    delegating_sk = umbral.SecretKey.random()
+    delegating_pk = umbral.PublicKey.from_secret_key(delegating_sk)
 
-    signing_privkey = keys.UmbralPrivateKey.gen_key(params=PARAMS)
-    signer = Signer(signing_privkey)
+    signing_sk = umbral.SecretKey.random()
 
-    receiving_privkey = keys.UmbralPrivateKey.gen_key(params=PARAMS)
-    receiving_pubkey = receiving_privkey.get_pubkey()
+    receiving_sk = umbral.SecretKey.random()
+    receiving_pk = umbral.PublicKey.from_secret_key(receiving_sk)
 
     plain_data = os.urandom(32)
-    ciphertext, capsule = pre.encrypt(delegating_pubkey, plain_data)
-
-    kfrags = pre.generate_kfrags(delegating_privkey, receiving_pubkey, m, n, signer)
+    capsule, ciphertext = umbral.encrypt(delegating_pk, plain_data)
 
-    capsule.set_correctness_keys(delegating=delegating_pubkey,
-                                 receiving=receiving_pubkey,
-                                 verifying=signing_privkey.get_pubkey())
+    kfrags = umbral.generate_kfrags(delegating_sk, receiving_pk, signing_sk, m, n)
 
     return kfrags, capsule
 
@@ -66,7 +39,7 @@ def firehose(m: int=6, n: int=10) -> None:
     successful_reencryptions = 0
     for iteration in range(int(REENCRYPTIONS)):
 
-        _cfrag = pre.reencrypt(one_kfrag, capsule)    # <<< REENCRYPTION HAPPENS HERE
+        _cfrag = umbral.reencrypt(capsule, one_kfrag)    # <<< REENCRYPTION HAPPENS HERE
 
         successful_reencryptions += 1
         if iteration % 20 == 0:
diff --git a/tests/scenario/__init__.py b/tests/scenario/__init__.py
deleted file mode 100644
index b1419391..00000000
--- a/tests/scenario/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
diff --git a/tests/scenario/test_lifecycle_multidomain.py b/tests/scenario/test_lifecycle_multidomain.py
deleted file mode 100644
index 1649f804..00000000
--- a/tests/scenario/test_lifecycle_multidomain.py
+++ /dev/null
@@ -1,167 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-import pytest
-
-from umbral import pre
-from umbral.cfrags import CapsuleFrag
-from umbral.kfrags import KFrag
-from umbral.config import default_curve
-from umbral.params import UmbralParameters
-from umbral.signing import Signer
-from umbral.keys import UmbralPrivateKey, UmbralPublicKey
-from ..conftest import parameters, other_supported_curves, kfrag_signing_modes
-
-
-@pytest.mark.parametrize("N, M", parameters)
-@pytest.mark.parametrize("signing_mode", kfrag_signing_modes)
-def test_lifecycle_with_serialization(N, M, signing_mode, curve=default_curve()):
-    """
-    This test is a variant of test_simple_api, but with intermediate 
-    serialization/deserialization steps, modeling how pyUmbral artifacts 
-    (such as keys, ciphertexts, etc) will actually be used. 
-    These intermediate steps are in between the different 'usage domains' 
-    in NuCypher, namely, key generation, delegation, encryption, decryption by 
-    Alice, re-encryption by Ursula, and decryption by Bob. 
-
-    Manually injects UmbralParameters for multi-curve testing.
-    """
-
-    # Convenience method to avoid replicating key generation code
-    def new_keypair_bytes():
-        privkey = UmbralPrivateKey.gen_key(params=params)
-        return privkey.to_bytes(), privkey.get_pubkey().to_bytes()
-
-    ## SETUP
-    params = UmbralParameters(curve=curve)
-
-    delegating_privkey_bytes, delegating_pubkey_bytes = new_keypair_bytes()
-    signing_privkey_bytes, signing_pubkey_bytes = new_keypair_bytes()
-    receiving_privkey_bytes, receiving_pubkey_bytes = new_keypair_bytes()
-
-    ## DELEGATION DOMAIN:
-    ## Alice delegates decryption rights to some Bob by generating a set of 
-    ## KFrags, using her delegating private key and Bob's receiving public key
-
-    delegating_privkey = UmbralPrivateKey.from_bytes(delegating_privkey_bytes, params=params)
-    signing_privkey = UmbralPrivateKey.from_bytes(signing_privkey_bytes, params=params)
-    receiving_pubkey = UmbralPublicKey.from_bytes(receiving_pubkey_bytes, params=params)
-
-    signer = Signer(signing_privkey)
-
-    sign_delegating_key, sign_receiving_key = signing_mode
-
-    kfrags = pre.generate_kfrags(delegating_privkey=delegating_privkey,
-                                 receiving_pubkey=receiving_pubkey,
-                                 threshold=M,
-                                 N=N,
-                                 signer=signer,
-                                 sign_delegating_key=sign_delegating_key,
-                                 sign_receiving_key=sign_receiving_key)
-
-    kfrags_bytes = tuple(map(bytes, kfrags))
-
-    del kfrags
-    del signer
-    del delegating_privkey
-    del signing_privkey
-    del receiving_pubkey
-    del params
-
-    ## ENCRYPTION DOMAIN ##
-
-    params = UmbralParameters(curve=curve)
-
-    delegating_pubkey = UmbralPublicKey.from_bytes(delegating_pubkey_bytes, params)
-
-    plain_data = b'peace at dawn'
-    ciphertext, capsule = pre.encrypt(delegating_pubkey, plain_data)
-    capsule_bytes = bytes(capsule)
-    
-    del capsule
-    del delegating_pubkey
-    del params
-
-    ## DECRYPTION BY ALICE ##
-
-    params = UmbralParameters(curve=curve)
-
-    delegating_privkey = UmbralPrivateKey.from_bytes(delegating_privkey_bytes, params=params)
-    capsule = pre.Capsule.from_bytes(capsule_bytes, params)
-    cleartext = pre.decrypt(ciphertext, capsule, delegating_privkey)
-    assert cleartext == plain_data
-
-    del delegating_privkey
-    del capsule
-    del params
-
-    ## RE-ENCRYPTION DOMAIN (i.e., Ursula's side)
-
-    cfrags_bytes = list()
-    for kfrag_bytes in kfrags_bytes:
-        params = UmbralParameters(curve=curve)
-        delegating_pubkey = UmbralPublicKey.from_bytes(delegating_pubkey_bytes, params)
-        signing_pubkey = UmbralPublicKey.from_bytes(signing_pubkey_bytes, params)
-        receiving_pubkey = UmbralPublicKey.from_bytes(receiving_pubkey_bytes, params)
-
-        capsule = pre.Capsule.from_bytes(capsule_bytes, params)
-        capsule.set_correctness_keys(delegating=delegating_pubkey,
-                                     receiving=receiving_pubkey,
-                                     verifying=signing_pubkey)
-
-        # TODO: use params instead of curve?
-        kfrag = KFrag.from_bytes(kfrag_bytes, params.curve)
-
-        assert kfrag.verify(signing_pubkey, delegating_pubkey, receiving_pubkey, params)
-
-        cfrag_bytes = bytes(pre.reencrypt(kfrag, capsule))
-        cfrags_bytes.append(cfrag_bytes)
-
-        del capsule
-        del kfrag
-        del params
-        del delegating_pubkey
-        del signing_pubkey
-        del receiving_pubkey
-
-    ## DECRYPTION DOMAIN (i.e., Bob's side)
-    params = UmbralParameters(curve=curve)
-
-    capsule = pre.Capsule.from_bytes(capsule_bytes, params)
-    delegating_pubkey = UmbralPublicKey.from_bytes(delegating_pubkey_bytes, params)
-    signing_pubkey = UmbralPublicKey.from_bytes(signing_pubkey_bytes, params)
-    receiving_privkey = UmbralPrivateKey.from_bytes(receiving_privkey_bytes, params=params)
-    receiving_pubkey = receiving_privkey.get_pubkey()
-
-    capsule.set_correctness_keys(delegating=delegating_pubkey,
-                                 receiving=receiving_pubkey,
-                                 verifying=signing_pubkey)
-
-    for cfrag_bytes in cfrags_bytes:
-        # TODO: use params instead of curve?
-        cfrag = CapsuleFrag.from_bytes(cfrag_bytes, params.curve)
-        capsule.attach_cfrag(cfrag)
-
-    reenc_cleartext = pre.decrypt(ciphertext, capsule, receiving_privkey)
-    assert reenc_cleartext == plain_data
-
-
-@pytest.mark.parametrize("curve", other_supported_curves)
-@pytest.mark.parametrize("N, M", parameters)
-@pytest.mark.parametrize("signing_mode", kfrag_signing_modes)
-def test_lifecycle_with_serialization_on_multiple_curves(N, M, signing_mode, curve):
-    test_lifecycle_with_serialization(N, M, signing_mode, curve)
diff --git a/tests/scenario/test_simple_api.py b/tests/scenario/test_simple_api.py
deleted file mode 100644
index afd3ce75..00000000
--- a/tests/scenario/test_simple_api.py
+++ /dev/null
@@ -1,98 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-import pytest
-
-from umbral import pre
-from umbral.config import default_curve
-from umbral.params import UmbralParameters
-from umbral.signing import Signer
-from umbral.keys import UmbralPrivateKey
-from ..conftest import parameters, other_supported_curves
-
-
-@pytest.mark.parametrize("N, M", parameters)
-def test_simple_api(N, M, curve=default_curve()):
-    """
-    This test models the main interactions between NuCypher actors (i.e., Alice, 
-    Bob, Data Source, and Ursulas) and artifacts (i.e., public and private keys,
-    ciphertexts, capsules, KFrags, CFrags, etc). 
-
-    The test covers all the main stages of data sharing with NuCypher:
-    key generation, delegation, encryption, decryption by 
-    Alice, re-encryption by Ursula, and decryption by Bob. 
-
-    Manually injects umbralparameters for multi-curve testing."""
-
-    # Generation of global parameters
-    params = UmbralParameters(curve=curve)
-
-    # Key Generation (Alice)
-    delegating_privkey = UmbralPrivateKey.gen_key(params=params)
-    delegating_pubkey = delegating_privkey.get_pubkey()
-
-    signing_privkey = UmbralPrivateKey.gen_key(params=params)
-    signing_pubkey = signing_privkey.get_pubkey()
-    signer = Signer(signing_privkey)
-
-    # Key Generation (Bob)
-    receiving_privkey = UmbralPrivateKey.gen_key(params=params)
-    receiving_pubkey = receiving_privkey.get_pubkey()
-
-    # Encryption by an unnamed data source
-    plain_data = b'peace at dawn'
-    ciphertext, capsule = pre.encrypt(delegating_pubkey, plain_data)
-
-    # Decryption by Alice
-    cleartext = pre.decrypt(ciphertext, capsule, delegating_privkey)
-    assert cleartext == plain_data
-
-    # Split Re-Encryption Key Generation (aka Delegation)
-    kfrags = pre.generate_kfrags(delegating_privkey, receiving_pubkey, M, N, signer)
-
-
-    # Capsule preparation (necessary before re-encryotion and activation)
-    capsule.set_correctness_keys(delegating=delegating_pubkey,
-                                 receiving=receiving_pubkey,
-                                 verifying=signing_pubkey)
-
-    # Bob requests re-encryption to some set of M ursulas
-    cfrags = list()
-    for kfrag in kfrags[:M]:
-        # Ursula checks that the received kfrag is valid
-        assert kfrag.verify(signing_pubkey, delegating_pubkey, receiving_pubkey, params)
-
-        # Re-encryption by an Ursula
-        cfrag = pre.reencrypt(kfrag, capsule)
-
-        # Bob collects the result
-        cfrags.append(cfrag)
-
-    # Capsule activation (by Bob)
-    for cfrag in cfrags:
-        capsule.attach_cfrag(cfrag)
-
-    # Decryption by Bob
-    reenc_cleartext = pre.decrypt(ciphertext, capsule, receiving_privkey)
-    assert reenc_cleartext == plain_data
-
-
-@pytest.mark.parametrize("curve", other_supported_curves)
-@pytest.mark.parametrize("N, M", parameters)
-def test_simple_api_on_multiple_curves(N, M, curve):
-    test_simple_api(N, M, curve)
-
diff --git a/tests/test_capsule.py b/tests/test_capsule.py
new file mode 100644
index 00000000..9f6566cf
--- /dev/null
+++ b/tests/test_capsule.py
@@ -0,0 +1,108 @@
+import pytest
+
+from umbral import (
+    Capsule,
+    SecretKey,
+    PublicKey,
+    GenericError,
+    encrypt,
+    decrypt_original,
+    reencrypt,
+    decrypt_reencrypted,
+    generate_kfrags
+    )
+from umbral.curve_point import CurvePoint
+
+
+def test_capsule_serialization(alices_keys):
+
+    delegating_sk, _signing_sk = alices_keys
+    delegating_pk = PublicKey.from_secret_key(delegating_sk)
+
+    capsule, _key = Capsule.from_public_key(delegating_pk)
+    new_capsule = Capsule.from_bytes(bytes(capsule))
+
+    assert capsule == new_capsule
+
+    # Deserializing a bad capsule triggers verification error
+    capsule.point_e = CurvePoint.random()
+    capsule_bytes = bytes(capsule)
+
+    with pytest.raises(GenericError):
+        Capsule.from_bytes(capsule_bytes)
+
+
+def test_capsule_is_hashable(alices_keys):
+
+    delegating_sk, _signing_sk = alices_keys
+    delegating_pk = PublicKey.from_secret_key(delegating_sk)
+
+    capsule1, key1 = Capsule.from_public_key(delegating_pk)
+    capsule2, key2 = Capsule.from_public_key(delegating_pk)
+
+    assert capsule1 != capsule2
+    assert key1 != key2
+    assert hash(capsule1) != hash(capsule2)
+
+    new_capsule = Capsule.from_bytes(bytes(capsule1))
+    assert hash(new_capsule) == hash(capsule1)
+
+
+def test_open_original(alices_keys):
+
+    delegating_sk, _signing_sk = alices_keys
+    delegating_pk = PublicKey.from_secret_key(delegating_sk)
+
+    capsule, key = Capsule.from_public_key(delegating_pk)
+    key_back = capsule.open_original(delegating_sk)
+    assert key == key_back
+
+
+def test_open_reencrypted(alices_keys, bobs_keys):
+
+    threshold = 6
+    num_kfrags = 10
+
+    delegating_sk, signing_sk = alices_keys
+    receiving_sk, receiving_pk = bobs_keys
+
+    signing_pk = PublicKey.from_secret_key(signing_sk)
+    delegating_pk = PublicKey.from_secret_key(delegating_sk)
+
+    capsule, key = Capsule.from_public_key(delegating_pk)
+    kfrags = generate_kfrags(delegating_sk=delegating_sk,
+                             signing_sk=signing_sk,
+                             receiving_pk=receiving_pk,
+                             threshold=threshold,
+                             num_kfrags=num_kfrags)
+
+    cfrags = [reencrypt(capsule, kfrag) for kfrag in kfrags]
+    key_back = capsule.open_reencrypted(receiving_sk, delegating_pk, cfrags[:threshold])
+    assert key_back == key
+
+    # No cfrags at all
+    with pytest.raises(ValueError, match="Empty CapsuleFrag sequence"):
+        capsule.open_reencrypted(receiving_sk, delegating_pk, [])
+
+    # Not enough cfrags
+    with pytest.raises(GenericError, match="Internal validation failed"):
+        capsule.open_reencrypted(receiving_sk, delegating_pk, cfrags[:threshold-1])
+
+    # Repeating cfrags
+    with pytest.raises(ValueError, match="Some of the CapsuleFrags are repeated"):
+        capsule.open_reencrypted(receiving_sk, delegating_pk, [cfrags[0]] + cfrags[:threshold-1])
+
+    # Mismatched cfrags
+    kfrags2 = generate_kfrags(delegating_sk=delegating_sk,
+                              signing_sk=signing_sk,
+                              receiving_pk=receiving_pk,
+                              threshold=threshold,
+                              num_kfrags=num_kfrags)
+    cfrags2 = [reencrypt(capsule, kfrag) for kfrag in kfrags2]
+    with pytest.raises(ValueError, match="CapsuleFrags are not pairwise consistent"):
+        capsule.open_reencrypted(receiving_sk, delegating_pk, [cfrags2[0]] + cfrags[:threshold-1])
+
+
+def test_capsule_str(capsule):
+    s = str(capsule)
+    assert 'Capsule' in s
diff --git a/tests/test_capsule_frag.py b/tests/test_capsule_frag.py
new file mode 100644
index 00000000..ff81e724
--- /dev/null
+++ b/tests/test_capsule_frag.py
@@ -0,0 +1,149 @@
+from umbral import reencrypt, CapsuleFrag, PublicKey, Capsule
+from umbral.curve_point import CurvePoint
+
+
+def test_cfrag_serialization(alices_keys, bobs_keys, capsule, kfrags):
+
+    delegating_sk, signing_sk = alices_keys
+    _receiving_sk, receiving_pk = bobs_keys
+
+    signing_pk = PublicKey.from_secret_key(signing_sk)
+    delegating_pk = PublicKey.from_secret_key(delegating_sk)
+
+    metadata = b'This is an example of metadata for re-encryption request'
+    for kfrag in kfrags:
+        cfrag = reencrypt(capsule, kfrag, metadata=metadata)
+        cfrag_bytes = bytes(cfrag)
+
+        new_cfrag = CapsuleFrag.from_bytes(cfrag_bytes)
+        assert new_cfrag == cfrag
+
+        assert new_cfrag.verify(capsule,
+                                delegating_pk=delegating_pk,
+                                receiving_pk=receiving_pk,
+                                signing_pk=signing_pk,
+                                metadata=metadata)
+
+        # No metadata
+        assert not new_cfrag.verify(capsule,
+                                    delegating_pk=delegating_pk,
+                                    receiving_pk=receiving_pk,
+                                    signing_pk=signing_pk)
+
+        # Wrong metadata
+        assert not new_cfrag.verify(capsule,
+                                    delegating_pk=delegating_pk,
+                                    receiving_pk=receiving_pk,
+                                    signing_pk=signing_pk,
+                                    metadata=b'Not the same metadata')
+
+        # Wrong delegating key
+        assert not new_cfrag.verify(capsule,
+                                    delegating_pk=receiving_pk,
+                                    receiving_pk=receiving_pk,
+                                    signing_pk=signing_pk,
+                                    metadata=metadata)
+
+        # Wrong receiving key
+        assert not new_cfrag.verify(capsule,
+                                    delegating_pk=delegating_pk,
+                                    receiving_pk=delegating_pk,
+                                    signing_pk=signing_pk,
+                                    metadata=metadata)
+
+        # Wrong signing key
+        assert not new_cfrag.verify(capsule,
+                                    delegating_pk=delegating_pk,
+                                    receiving_pk=receiving_pk,
+                                    signing_pk=receiving_pk,
+                                    metadata=metadata)
+
+
+def test_cfrag_serialization_no_metadata(alices_keys, bobs_keys, capsule, kfrags):
+
+    delegating_sk, signing_sk = alices_keys
+    _receiving_sk, receiving_pk = bobs_keys
+
+    signing_pk = PublicKey.from_secret_key(signing_sk)
+    delegating_pk = PublicKey.from_secret_key(delegating_sk)
+
+    for kfrag in kfrags:
+
+        # Create with no metadata
+        cfrag = reencrypt(capsule, kfrag)
+        cfrag_bytes = bytes(cfrag)
+        new_cfrag = CapsuleFrag.from_bytes(cfrag_bytes)
+
+        assert new_cfrag.verify(capsule,
+                                delegating_pk=delegating_pk,
+                                receiving_pk=receiving_pk,
+                                signing_pk=signing_pk)
+
+        assert not new_cfrag.verify(capsule,
+                                    delegating_pk=delegating_pk,
+                                    receiving_pk=receiving_pk,
+                                    signing_pk=signing_pk,
+                                    metadata=b'some metadata')
+
+
+def test_cfrag_with_wrong_capsule(alices_keys, bobs_keys,
+                                  kfrags, capsule_and_ciphertext, message):
+
+    capsule, ciphertext = capsule_and_ciphertext
+
+    delegating_sk, signing_sk = alices_keys
+    delegating_pk = PublicKey.from_secret_key(delegating_sk)
+
+    _receiving_sk, receiving_pk = bobs_keys
+
+    capsule_alice1 = capsule
+    capsule_alice2, _unused_key2 = Capsule.from_public_key(delegating_pk)
+
+    metadata = b"some metadata"
+    cfrag = reencrypt(capsule_alice2, kfrags[0], metadata=metadata)
+
+    assert not cfrag.verify(capsule_alice1,
+                            delegating_pk=delegating_pk,
+                            receiving_pk=receiving_pk,
+                            signing_pk=PublicKey.from_secret_key(signing_sk),
+                            metadata=metadata)
+
+
+def test_cfrag_with_wrong_data(kfrags, alices_keys, bobs_keys, capsule_and_ciphertext, message):
+
+    capsule, ciphertext = capsule_and_ciphertext
+
+    delegating_sk, signing_sk = alices_keys
+    delegating_pk = PublicKey.from_secret_key(delegating_sk)
+
+    _receiving_sk, receiving_pk = bobs_keys
+
+    metadata = b"some metadata"
+    cfrag = reencrypt(capsule, kfrags[0], metadata=metadata)
+
+    # Let's put random garbage in one of the cfrags
+    cfrag.point_e1 = CurvePoint.random()
+    cfrag.point_v1 = CurvePoint.random()
+
+    assert not cfrag.verify(capsule,
+                            delegating_pk=delegating_pk,
+                            receiving_pk=receiving_pk,
+                            signing_pk=PublicKey.from_secret_key(signing_sk),
+                            metadata=metadata)
+
+
+def test_cfrag_is_hashable(capsule, kfrags):
+
+    cfrag0 = reencrypt(capsule, kfrags[0], metadata=b'abcdef')
+    cfrag1 = reencrypt(capsule, kfrags[1], metadata=b'abcdef')
+
+    assert hash(cfrag0) != hash(cfrag1)
+
+    new_cfrag = CapsuleFrag.from_bytes(bytes(cfrag0))
+    assert hash(new_cfrag) == hash(cfrag0)
+
+
+def test_cfrag_str(capsule, kfrags):
+    cfrag0 = reencrypt(capsule, kfrags[0], metadata=b'abcdef')
+    s = str(cfrag0)
+    assert 'CapsuleFrag' in s
diff --git a/tests/test_compatibility.py b/tests/test_compatibility.py
new file mode 100644
index 00000000..bba1bee4
--- /dev/null
+++ b/tests/test_compatibility.py
@@ -0,0 +1,205 @@
+import pytest
+
+try:
+    import umbral_pre as umbral_rs
+except ImportError:
+    umbral_rs = None
+
+import umbral as umbral_py
+
+
+def pytest_generate_tests(metafunc):
+    if 'implementations' in metafunc.fixturenames:
+        implementations = [(umbral_py, umbral_py)]
+        ids = ['python -> python']
+        if umbral_rs is not None:
+            implementations.extend([(umbral_py, umbral_rs), (umbral_rs, umbral_py)])
+            ids.extend(['python -> rust', 'rust -> python'])
+
+        metafunc.parametrize('implementations', implementations, ids=ids)
+
+
+def _create_keypair(umbral):
+    sk = umbral.SecretKey.random()
+    pk = umbral.PublicKey.from_secret_key(sk)
+    return bytes(sk), bytes(pk)
+
+
+def _restore_keys(umbral, sk_bytes, pk_bytes):
+    sk = umbral.SecretKey.from_bytes(sk_bytes)
+    pk_from_sk = umbral.PublicKey.from_secret_key(sk)
+    pk_from_bytes = umbral.PublicKey.from_bytes(pk_bytes)
+    assert pk_from_sk == pk_from_bytes
+
+
+def test_keys(implementations):
+    umbral1, umbral2 = implementations
+
+    # On client 1
+    sk_bytes, pk_bytes = _create_keypair(umbral1)
+
+    # On client 2
+    _restore_keys(umbral2, sk_bytes, pk_bytes)
+
+
+def _create_sk_factory_and_sk(umbral, label):
+    skf = umbral.SecretKeyFactory.random()
+    sk = skf.secret_key_by_label(label)
+    return bytes(skf), bytes(sk)
+
+
+def _check_sk_is_same(umbral, label, skf_bytes, sk_bytes):
+    skf = umbral.SecretKeyFactory.from_bytes(skf_bytes)
+    sk_restored = umbral.SecretKey.from_bytes(sk_bytes)
+    sk_generated = skf.secret_key_by_label(label)
+    assert sk_restored == sk_generated
+
+
+def test_secret_key_factory(implementations):
+    umbral1, umbral2 = implementations
+    label = b'label'
+
+    skf_bytes, sk_bytes = _create_sk_factory_and_sk(umbral1, label)
+    _check_sk_is_same(umbral2, label, skf_bytes, sk_bytes)
+
+
+def _encrypt(umbral, plaintext, pk_bytes):
+    pk = umbral.PublicKey.from_bytes(pk_bytes)
+    capsule, ciphertext = umbral.encrypt(pk, plaintext)
+    return bytes(capsule), ciphertext
+
+
+def _decrypt_original(umbral, sk_bytes, capsule_bytes, ciphertext):
+    capsule = umbral.Capsule.from_bytes(bytes(capsule_bytes))
+    sk = umbral.SecretKey.from_bytes(sk_bytes)
+    return umbral.decrypt_original(sk, capsule, ciphertext)
+
+
+def test_encrypt_decrypt(implementations):
+
+    umbral1, umbral2 = implementations
+    plaintext = b'peace at dawn'
+
+    # On client 1
+    sk_bytes, pk_bytes = _create_keypair(umbral1)
+
+    # On client 2
+    capsule_bytes, ciphertext = _encrypt(umbral2, plaintext, pk_bytes)
+
+    # On client 1
+    plaintext_decrypted = _decrypt_original(umbral1, sk_bytes, capsule_bytes, ciphertext)
+
+    assert plaintext_decrypted == plaintext
+
+
+def _generate_kfrags(umbral, delegating_sk_bytes, receiving_pk_bytes,
+                     signing_sk_bytes, threshold, num_frags):
+
+    delegating_sk = umbral.SecretKey.from_bytes(delegating_sk_bytes)
+    receiving_pk = umbral.PublicKey.from_bytes(receiving_pk_bytes)
+    signing_sk = umbral.SecretKey.from_bytes(signing_sk_bytes)
+
+    kfrags = umbral.generate_kfrags(delegating_sk,
+                                    receiving_pk,
+                                    signing_sk,
+                                    threshold,
+                                    num_frags,
+                                    True,
+                                    True,
+                                    )
+
+    return [bytes(kfrag) for kfrag in kfrags]
+
+
+def _verify_kfrags(umbral, kfrags_bytes, signing_pk_bytes, delegating_pk_bytes, receiving_pk_bytes):
+    kfrags = [umbral.KeyFrag.from_bytes(kfrag_bytes) for kfrag_bytes in kfrags_bytes]
+    signing_pk = umbral.PublicKey.from_bytes(signing_pk_bytes)
+    delegating_pk = umbral.PublicKey.from_bytes(delegating_pk_bytes)
+    receiving_pk = umbral.PublicKey.from_bytes(receiving_pk_bytes)
+    assert all(kfrag.verify(signing_pk, delegating_pk, receiving_pk) for kfrag in kfrags)
+
+
+def test_kfrags(implementations):
+
+    umbral1, umbral2 = implementations
+
+    threshold = 2
+    num_frags = 3
+    plaintext = b'peace at dawn'
+
+    # On client 1
+
+    receiving_sk_bytes, receiving_pk_bytes = _create_keypair(umbral1)
+    delegating_sk_bytes, delegating_pk_bytes = _create_keypair(umbral1)
+    signing_sk_bytes, signing_pk_bytes = _create_keypair(umbral1)
+    kfrags_bytes = _generate_kfrags(umbral1, delegating_sk_bytes, receiving_pk_bytes,
+                                    signing_sk_bytes, threshold, num_frags)
+
+    # On client 2
+
+    _verify_kfrags(umbral2, kfrags_bytes, signing_pk_bytes, delegating_pk_bytes, receiving_pk_bytes)
+
+
+def _reencrypt(umbral, capsule_bytes, kfrags_bytes, threshold, metadata):
+    capsule = umbral.Capsule.from_bytes(bytes(capsule_bytes))
+    kfrags = [umbral.KeyFrag.from_bytes(kfrag_bytes) for kfrag_bytes in kfrags_bytes]
+    cfrags = [umbral.reencrypt(capsule, kfrag, metadata=metadata) for kfrag in kfrags[:threshold]]
+    return [bytes(cfrag) for cfrag in cfrags]
+
+
+def _decrypt_reencrypted(umbral, receiving_sk_bytes, delegating_pk_bytes, signing_pk_bytes,
+                         capsule_bytes, cfrags_bytes, ciphertext, metadata):
+
+    receiving_sk = umbral.SecretKey.from_bytes(receiving_sk_bytes)
+    receiving_pk = umbral.PublicKey.from_secret_key(receiving_sk)
+    delegating_pk = umbral.PublicKey.from_bytes(delegating_pk_bytes)
+    signing_pk = umbral.PublicKey.from_bytes(signing_pk_bytes)
+
+    capsule = umbral.Capsule.from_bytes(bytes(capsule_bytes))
+    cfrags = [umbral.CapsuleFrag.from_bytes(cfrag_bytes) for cfrag_bytes in cfrags_bytes]
+
+    assert all(cfrag.verify(capsule, delegating_pk, receiving_pk, signing_pk, metadata=metadata)
+               for cfrag in cfrags)
+
+    # Decryption by Bob
+    plaintext = umbral.decrypt_reencrypted(receiving_sk,
+                                           delegating_pk,
+                                           capsule,
+                                           cfrags,
+                                           ciphertext,
+                                           )
+
+    return plaintext
+
+
+def test_reencrypt(implementations):
+
+    umbral1, umbral2 = implementations
+
+    metadata = b'metadata'
+    threshold = 2
+    num_frags = 3
+    plaintext = b'peace at dawn'
+
+    # On client 1
+
+    receiving_sk_bytes, receiving_pk_bytes = _create_keypair(umbral1)
+    delegating_sk_bytes, delegating_pk_bytes = _create_keypair(umbral1)
+    signing_sk_bytes, signing_pk_bytes = _create_keypair(umbral1)
+
+    capsule_bytes, ciphertext = _encrypt(umbral1, plaintext, delegating_pk_bytes)
+
+    kfrags_bytes = _generate_kfrags(umbral1, delegating_sk_bytes, receiving_pk_bytes,
+                                    signing_sk_bytes, threshold, num_frags)
+
+    # On client 2
+
+    cfrags_bytes = _reencrypt(umbral2, capsule_bytes, kfrags_bytes, threshold, metadata)
+
+    # On client 1
+
+    plaintext_reencrypted = _decrypt_reencrypted(umbral1,
+                                                 receiving_sk_bytes, delegating_pk_bytes, signing_pk_bytes,
+                                                 capsule_bytes, cfrags_bytes, ciphertext, metadata)
+
+    assert plaintext_reencrypted == plaintext
diff --git a/tests/test_curve.py b/tests/test_curve.py
new file mode 100644
index 00000000..a8c220b0
--- /dev/null
+++ b/tests/test_curve.py
@@ -0,0 +1,113 @@
+import pytest
+
+from umbral.openssl import Curve, bn_to_int, point_to_affine_coords
+from umbral.curve import CURVE, CURVES, SECP256R1, SECP256K1, SECP384R1
+
+
+def test_supported_curves():
+
+    # Ensure we have the correct number of supported curves hardcoded
+    number_of_supported_curves = 3
+    assert len(Curve._supported_curves) == number_of_supported_curves
+
+    # Manually ensure the `_supported curves` dict contains only valid supported curves
+    assert Curve._supported_curves[415] == 'secp256r1'
+    assert Curve._supported_curves[714] == 'secp256k1'
+    assert Curve._supported_curves[715] == 'secp384r1'
+
+
+def test_create_by_nid():
+
+    nid, name = 714, 'secp256k1'
+
+    # supported
+    _curve_714 = Curve(nid=nid)
+    assert _curve_714.nid == nid
+    assert _curve_714.name == name
+
+    # unsuported
+    with pytest.raises(NotImplementedError):
+        Curve(711)
+
+
+def test_create_by_name():
+
+    nid, name = 714, 'secp256k1'
+
+    # Supported
+    _curve_secp256k1 = Curve.from_name(name)
+    assert _curve_secp256k1.name == name
+    assert _curve_secp256k1.nid == nid
+
+    # Unsupported
+    with pytest.raises(NotImplementedError):
+        Curve.from_name('abcd123e4')
+
+
+def test_curve_constants():
+
+    test_p256 = SECP256R1
+    test_secp256k1 = SECP256K1
+    test_p384 = SECP384R1
+
+    assert CURVE == SECP256K1
+
+    # Test the hardcoded curve NIDs are correct:
+    assert test_p256.nid == 415
+    assert test_secp256k1.nid == 714
+    assert test_p384.nid == 715
+
+    # Ensure every curve constant is in the CURVES collection
+    number_of_supported_curves = 3
+    assert len(CURVES) == number_of_supported_curves
+
+    # Ensure all supported curves can be initialized
+    for nid, name in Curve._supported_curves.items():
+        by_nid, by_name = Curve(nid=nid), Curve.from_name(name)
+        assert by_nid.name == name
+        assert by_name.nid == nid
+
+
+def test_curve_str():
+    for curve in CURVES:
+        s = str(curve)
+        assert str(curve.nid) in s
+        assert str(curve.name) in s
+
+
+def _curve_info(curve: Curve):
+    assert bn_to_int(curve.bn_order) == curve.order
+    return dict(order=curve.order,
+                field_element_size=curve.field_element_size,
+                scalar_size=curve.scalar_size,
+                generator=point_to_affine_coords(curve, curve.point_generator))
+
+
+def test_secp256k1():
+    info = _curve_info(SECP256K1)
+    assert info['order'] == 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_BAAEDCE6_AF48A03B_BFD25E8C_D0364141
+    assert info['field_element_size'] == 32
+    assert info['scalar_size'] == 32
+    assert info['generator'] == (
+        0x79BE667E_F9DCBBAC_55A06295_CE870B07_029BFCDB_2DCE28D9_59F2815B_16F81798,
+        0x483ADA77_26A3C465_5DA4FBFC_0E1108A8_FD17B448_A6855419_9C47D08F_FB10D4B8)
+
+
+def test_p256():
+    info = _curve_info(SECP256R1)
+    assert info['order'] == 0xFFFFFFFF_00000000_FFFFFFFF_FFFFFFFF_BCE6FAAD_A7179E84_F3B9CAC2_FC632551
+    assert info['field_element_size'] == 32
+    assert info['scalar_size'] == 32
+    assert info['generator'] == (
+        0x6B17D1F2_E12C4247_F8BCE6E5_63A440F2_77037D81_2DEB33A0_F4A13945_D898C296,
+        0x4FE342E2_FE1A7F9B_8EE7EB4A_7C0F9E16_2BCE3357_6B315ECE_CBB64068_37BF51F5)
+
+
+def test_p384():
+    info = _curve_info(SECP384R1)
+    assert info['order'] == 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_C7634D81_F4372DDF_581A0DB2_48B0A77A_ECEC196A_CCC52973
+    assert info['field_element_size'] == 48
+    assert info['scalar_size'] == 48
+    assert info['generator'] == (
+         0xAA87CA22_BE8B0537_8EB1C71E_F320AD74_6E1D3B62_8BA79B98_59F741E0_82542A38_5502F25D_BF55296C_3A545E38_72760AB7,
+         0x3617DE4A_96262C6F_5D9E98BF_9292DC29_F8F41DBD_289A147C_E9DA3113_B5F0B8C0_0A60B1CE_1D7E819D_7A431D7C_90EA0E5F)
diff --git a/tests/test_curve_point.py b/tests/test_curve_point.py
new file mode 100644
index 00000000..38d3866f
--- /dev/null
+++ b/tests/test_curve_point.py
@@ -0,0 +1,90 @@
+import pytest
+
+from umbral.openssl import ErrorInvalidCompressedPoint, ErrorInvalidPointEncoding
+from umbral.curve_point import CurvePoint
+from umbral.curve import CURVE
+
+
+def test_random():
+    p1 = CurvePoint.random()
+    p2 = CurvePoint.random()
+    assert isinstance(p1, CurvePoint)
+    assert isinstance(p2, CurvePoint)
+    assert p1 != p2
+
+
+def test_generator_point():
+    """http://www.secg.org/SEC2-Ver-1.0.pdf Section 2.7.1"""
+    g1 = CurvePoint.generator()
+
+    g_compressed = 0x0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
+    g_compressed_bytes = g_compressed.to_bytes(CURVE.field_element_size + 1, byteorder='big')
+    g2 = CurvePoint.from_bytes(g_compressed_bytes)
+
+    assert g1 == g2
+
+
+def test_to_and_from_affine():
+
+    x = 17004608369308732328368332205668001941491834793934321461466076545247324070015
+    y = 69725941631324401609944843130171147910924748427773762412028916504484868631573
+
+    p = CurvePoint.from_affine(x, y)
+
+    assert p.to_affine() == (x, y)
+
+
+def test_invalid_serialized_points():
+
+    field_order = 2**256 - 0x1000003D1
+
+    # A point on secp256k1
+    x = 17004608369308732328368332205668001941491834793934321461466076545247324070015
+    y = 69725941631324401609944843130171147910924748427773762412028916504484868631573
+
+    # Check it
+    assert (y**2 - x**3 - 7) % field_order == 0
+
+    # Should load
+    point_data = b'\x03' + x.to_bytes(CURVE.field_element_size, 'big')
+    p = CurvePoint.from_bytes(point_data)
+
+    # Make it invalid
+    bad_x = x - 1
+    assert (y**2 - bad_x**3 - 7) % field_order != 0
+
+    bad_x_data = b'\x03' + bad_x.to_bytes(CURVE.field_element_size, 'big')
+    with pytest.raises(ErrorInvalidCompressedPoint):
+        CurvePoint.from_bytes(bad_x_data)
+
+    # Valid x, invalid prefix
+    bad_format = b'\xff' + x.to_bytes(CURVE.field_element_size, 'big')
+    with pytest.raises(ErrorInvalidPointEncoding):
+        CurvePoint.from_bytes(bad_format)
+
+
+def test_serialize_point_at_infinity():
+
+    p = CurvePoint.random()
+    point_at_infinity = p - p
+
+    bytes_point_at_infinity = bytes(point_at_infinity)
+    assert bytes_point_at_infinity == b'\x00'
+
+
+def test_coords_with_special_characteristics():
+
+    # Testing that a point with x coordinate greater than the curve order is still valid.
+    # In particular, we will test the last valid point from the default curve (secp256k1)
+    # whose x coordinate is `field_order - 3` and is greater than the order of the curve
+
+    field_order = 2**256 - 0x1000003D1
+    compressed = b'\x02' + (field_order-3).to_bytes(32, 'big')
+
+    last_point = CurvePoint.from_bytes(compressed)
+
+    # The same point, but obtained through the from_affine method
+    x = 115792089237316195423570985008687907853269984665640564039457584007908834671660
+    y = 109188863561374057667848968960504138135859662956057034999983532397866404169138
+
+    assert last_point == CurvePoint.from_affine(x, y)
diff --git a/tests/test_curve_scalar.py b/tests/test_curve_scalar.py
new file mode 100644
index 00000000..a9f0fd80
--- /dev/null
+++ b/tests/test_curve_scalar.py
@@ -0,0 +1,127 @@
+import pytest
+
+from umbral.curve import CURVE
+from umbral.curve_scalar import CurveScalar
+from umbral.hashing import Hash
+
+
+def test_random():
+    r1 = CurveScalar.random_nonzero()
+    r2 = CurveScalar.random_nonzero()
+    assert r1 != r2
+    assert not r1.is_zero()
+    assert not r2.is_zero()
+
+
+def test_from_and_to_int():
+    zero = CurveScalar.from_int(0)
+    assert zero.is_zero()
+    assert int(zero) == 0
+
+    one = CurveScalar.one()
+    assert not one.is_zero()
+    assert int(one) == 1
+
+    big_int = CURVE.order - 2
+    big_scalar = CurveScalar.from_int(big_int)
+    assert int(big_scalar) == big_int
+
+    # normalization check
+    with pytest.raises(ValueError):
+        CurveScalar.from_int(CURVE.order)
+
+    # disable normalization check
+    too_big = CurveScalar.from_int(CURVE.order, check_normalization=False)
+
+
+def test_from_digest():
+    digest = Hash(b'asdf')
+    digest.update(b'some info')
+    s1 = CurveScalar.from_digest(digest)
+
+    digest = Hash(b'asdf')
+    digest.update(b'some info')
+    s2 = CurveScalar.from_digest(digest)
+
+    assert s1 == s2
+    assert int(s1) == int(s2)
+
+
+def test_eq():
+    random = CurveScalar.random_nonzero()
+    same = CurveScalar.from_int(int(random))
+    different = CurveScalar.random_nonzero()
+    assert random == same
+    assert random == int(same)
+    assert random != different
+    assert random != int(different)
+
+
+def test_serialization_rotations_of_1():
+
+    size_in_bytes = CURVE.scalar_size
+    for i in range(size_in_bytes):
+        lonely_one = 1 << i
+        bn = CurveScalar.from_int(lonely_one)
+        lonely_one_in_bytes = lonely_one.to_bytes(size_in_bytes, 'big')
+
+        # Check serialization
+        assert bytes(bn) == lonely_one_in_bytes
+
+        # Check deserialization
+        assert CurveScalar.from_bytes(lonely_one_in_bytes) == bn
+
+
+def test_invalid_deserialization():
+    size_in_bytes = CURVE.scalar_size
+
+    # All-ones bytestring is invalid (since it's greater than the order)
+    lots_of_ones = b'\xFF' * size_in_bytes
+    with pytest.raises(ValueError):
+        CurveScalar.from_bytes(lots_of_ones)
+
+    # Serialization of `order` is invalid since it's not strictly lower than
+    # the order of the curve
+    order = CURVE.order
+    with pytest.raises(ValueError):
+        CurveScalar.from_bytes(order.to_bytes(size_in_bytes, 'big'))
+
+    # On the other hand, serialization of `order - 1` is valid
+    order -= 1
+    CurveScalar.from_bytes(order.to_bytes(size_in_bytes, 'big'))
+
+
+def test_add():
+    r1 = CurveScalar.random_nonzero()
+    r2 = CurveScalar.random_nonzero()
+    r1i = int(r1)
+    r2i = int(r2)
+    assert r1 + r2 == (r1i + r2i) % CURVE.order
+    assert r1 + r2i == (r1i + r2i) % CURVE.order
+
+
+def test_sub():
+    r1 = CurveScalar.random_nonzero()
+    r2 = CurveScalar.random_nonzero()
+    r1i = int(r1)
+    r2i = int(r2)
+    assert r1 - r2 == (r1i - r2i) % CURVE.order
+    assert r1 - r2i == (r1i - r2i) % CURVE.order
+
+
+def test_mul():
+    r1 = CurveScalar.random_nonzero()
+    r2 = CurveScalar.random_nonzero()
+    r1i = int(r1)
+    r2i = int(r2)
+    assert r1 * r2 == (r1i * r2i) % CURVE.order
+    assert r1 * r2i == (r1i * r2i) % CURVE.order
+
+
+def test_invert():
+    r1 = CurveScalar.random_nonzero()
+    r1i = int(r1)
+    r1inv = r1.invert()
+    assert r1 * r1inv == CurveScalar.one()
+    assert (r1i * int(r1inv)) % CURVE.order == 1
+
diff --git a/tests/unit/test_dem.py b/tests/test_dem.py
similarity index 56%
rename from tests/unit/test_dem.py
rename to tests/test_dem.py
index 55b4327a..7d5a5b12 100644
--- a/tests/unit/test_dem.py
+++ b/tests/test_dem.py
@@ -1,31 +1,14 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
 import pytest
 import os
 
-from umbral.dem import UmbralDEM, DEM_KEYSIZE, DEM_NONCE_SIZE
-from cryptography.exceptions import InvalidTag
+from umbral import GenericError
+from umbral.dem import DEM
 
 
 def test_encrypt_decrypt():
-    key = os.urandom(32)
 
-    dem = UmbralDEM(key)
+    key = os.urandom(DEM.KEY_SIZE)
+    dem = DEM(key)
 
     plaintext = b'peace at dawn'
 
@@ -39,7 +22,7 @@ def test_encrypt_decrypt():
     assert ciphertext0 != ciphertext1
 
     # Nonce should be different
-    assert ciphertext0[:DEM_NONCE_SIZE] != ciphertext1[:DEM_NONCE_SIZE]
+    assert ciphertext0[:DEM.NONCE_SIZE] != ciphertext1[:DEM.NONCE_SIZE]
 
     cleartext0 = dem.decrypt(ciphertext0)
     cleartext1 = dem.decrypt(ciphertext1)
@@ -48,11 +31,32 @@ def test_encrypt_decrypt():
     assert cleartext1 == plaintext
 
 
+def test_malformed_ciphertext():
+
+    key = os.urandom(DEM.KEY_SIZE)
+    dem = DEM(key)
+
+    plaintext = b'peace at dawn'
+    ciphertext = dem.encrypt(plaintext)
+
+    # So short it we can tell right away it doesn't even contain a nonce
+    with pytest.raises(ValueError, match="The ciphertext must include the nonce"):
+        dem.decrypt(ciphertext[:DEM.NONCE_SIZE-1])
+
+    # Too short to contain a tag
+    with pytest.raises(ValueError, match="The authentication tag is missing or malformed"):
+        dem.decrypt(ciphertext[:DEM.NONCE_SIZE + DEM.TAG_SIZE - 1])
+
+    # Too long
+    with pytest.raises(GenericError):
+        dem.decrypt(ciphertext + b'abcd')
+
+
 def test_encrypt_decrypt_associated_data():
     key = os.urandom(32)
     aad = b'secret code 1234'
 
-    dem = UmbralDEM(key)
+    dem = DEM(key)
 
     plaintext = b'peace at dawn'
 
@@ -64,7 +68,7 @@ def test_encrypt_decrypt_associated_data():
 
     assert ciphertext0 != ciphertext1
 
-    assert ciphertext0[:DEM_NONCE_SIZE] != ciphertext1[:DEM_NONCE_SIZE]
+    assert ciphertext0[:DEM.NONCE_SIZE] != ciphertext1[:DEM.NONCE_SIZE]
 
     cleartext0 = dem.decrypt(ciphertext0, authenticated_data=aad)
     cleartext1 = dem.decrypt(ciphertext1, authenticated_data=aad)
@@ -73,5 +77,5 @@ def test_encrypt_decrypt_associated_data():
     assert cleartext1 == plaintext
 
     # Attempt decryption with invalid associated data
-    with pytest.raises(InvalidTag):
+    with pytest.raises(GenericError):
         cleartext2 = dem.decrypt(ciphertext0, authenticated_data=b'wrong data')
diff --git a/tests/test_key_frag.py b/tests/test_key_frag.py
new file mode 100644
index 00000000..72de83cf
--- /dev/null
+++ b/tests/test_key_frag.py
@@ -0,0 +1,126 @@
+import pytest
+
+from umbral import KeyFrag, PublicKey, generate_kfrags
+from umbral.key_frag import KeyFragID
+from umbral.curve_scalar import CurveScalar
+
+
+def test_kfrag_serialization(alices_keys, bobs_keys, kfrags):
+
+    delegating_sk, signing_sk = alices_keys
+    _receiving_sk, receiving_pk = bobs_keys
+
+    signing_pk = PublicKey.from_secret_key(signing_sk)
+    delegating_pk = PublicKey.from_secret_key(delegating_sk)
+
+    for kfrag in kfrags:
+        kfrag_bytes = bytes(kfrag)
+        new_kfrag = KeyFrag.from_bytes(kfrag_bytes)
+
+        assert new_kfrag.verify(signing_pk=signing_pk,
+                                delegating_pk=delegating_pk,
+                                receiving_pk=receiving_pk)
+
+        assert new_kfrag == kfrag
+
+
+def test_kfrag_verification(alices_keys, bobs_keys, kfrags):
+
+    delegating_sk, signing_sk = alices_keys
+    _receiving_sk, receiving_pk = bobs_keys
+
+    signing_pk = PublicKey.from_secret_key(signing_sk)
+    delegating_pk = PublicKey.from_secret_key(delegating_sk)
+
+    # Wrong signature
+    kfrag = kfrags[0]
+    kfrag.id = KeyFragID.random()
+    kfrag_bytes = bytes(kfrag)
+    new_kfrag = KeyFrag.from_bytes(kfrag_bytes)
+    assert not new_kfrag.verify(signing_pk=signing_pk,
+                                delegating_pk=delegating_pk,
+                                receiving_pk=receiving_pk)
+
+    # Wrong key
+    kfrag = kfrags[1]
+    kfrag.key = CurveScalar.random_nonzero()
+    kfrag_bytes = bytes(kfrag)
+    new_kfrag = KeyFrag.from_bytes(kfrag_bytes)
+    assert not new_kfrag.verify(signing_pk=signing_pk,
+                                delegating_pk=delegating_pk,
+                                receiving_pk=receiving_pk)
+
+
+@pytest.mark.parametrize('sign_delegating_key',
+                         [False, True],
+                         ids=['sign_delegating_key', 'dont_sign_delegating_key'])
+@pytest.mark.parametrize('sign_receiving_key',
+                         [False, True],
+                         ids=['sign_receiving_key', 'dont_sign_receiving_key'])
+def test_kfrag_signing(alices_keys, bobs_keys, sign_delegating_key, sign_receiving_key):
+
+    delegating_sk, signing_sk = alices_keys
+    _receiving_sk, receiving_pk = bobs_keys
+
+    signing_pk = PublicKey.from_secret_key(signing_sk)
+    delegating_pk = PublicKey.from_secret_key(delegating_sk)
+
+    kfrags = generate_kfrags(delegating_sk=delegating_sk,
+                             signing_sk=signing_sk,
+                             receiving_pk=receiving_pk,
+                             threshold=6,
+                             num_kfrags=10,
+                             sign_delegating_key=sign_delegating_key,
+                             sign_receiving_key=sign_receiving_key)
+
+    kfrag = kfrags[0]
+
+    # serialize/deserialize to make sure sign_* fields are serialized correctly
+    kfrag = KeyFrag.from_bytes(bytes(kfrag))
+
+    for pass_delegating_key, pass_receiving_key in zip([False, True], [False, True]):
+
+        delegating_key_ok = (not sign_delegating_key) or pass_delegating_key
+        receiving_key_ok = (not sign_receiving_key) or pass_receiving_key
+        should_verify = delegating_key_ok and receiving_key_ok
+
+        result = kfrag.verify(signing_pk=signing_pk,
+                              delegating_pk=delegating_pk if pass_delegating_key else None,
+                              receiving_pk=receiving_pk if pass_receiving_key else None)
+
+        assert result == should_verify
+
+
+def test_kfrag_is_hashable(kfrags):
+
+    assert hash(kfrags[0]) != hash(kfrags[1])
+
+    new_kfrag = KeyFrag.from_bytes(bytes(kfrags[0]))
+    assert hash(new_kfrag) == hash(kfrags[0])
+
+
+def test_kfrag_str(kfrags):
+    s = str(kfrags[0])
+    assert "KeyFrag" in s
+
+
+WRONG_PARAMETERS = (
+    # (num_kfrags, threshold)
+    (-1, -1),   (-1, 0),    (-1, 5),
+    (0, -1),    (0, 0),     (0, 5),
+    (1, -1),    (1, 0),     (1, 5),
+    (5, -1),    (5, 0),     (5, 10)
+)
+
+@pytest.mark.parametrize("num_kfrags, threshold", WRONG_PARAMETERS)
+def test_wrong_threshold_and_num_kfrags(num_kfrags, threshold, alices_keys, bobs_keys):
+
+    delegating_sk, signing_sk = alices_keys
+    _receiving_sk, receiving_pk = bobs_keys
+
+    with pytest.raises(ValueError):
+        generate_kfrags(delegating_sk=delegating_sk,
+                        signing_sk=signing_sk,
+                        receiving_pk=receiving_pk,
+                        threshold=threshold,
+                        num_kfrags=num_kfrags)
diff --git a/tests/test_keys.py b/tests/test_keys.py
new file mode 100644
index 00000000..2b0f698f
--- /dev/null
+++ b/tests/test_keys.py
@@ -0,0 +1,203 @@
+import os
+import string
+
+import pytest
+
+from umbral.keys import PublicKey, SecretKey, SecretKeyFactory, Signature
+from umbral.hashing import Hash
+
+
+def test_gen_key():
+    sk = SecretKey.random()
+    assert type(sk) == SecretKey
+
+    pk = PublicKey.from_secret_key(sk)
+    assert type(pk) == PublicKey
+
+    pk2 = PublicKey.from_secret_key(sk)
+    assert pk == pk2
+
+
+def test_derive_key_from_label():
+    factory = SecretKeyFactory.random()
+
+    label = b"my_healthcare_information"
+
+    sk1 = factory.secret_key_by_label(label)
+    assert type(sk1) == SecretKey
+
+    pk1 = PublicKey.from_secret_key(sk1)
+    assert type(pk1) == PublicKey
+
+    # Check that key derivation is reproducible
+    sk2 = factory.secret_key_by_label(label)
+    pk2 = PublicKey.from_secret_key(sk2)
+    assert sk1 == sk2
+    assert pk1 == pk2
+
+    # Different labels on the same master secret create different keys
+    label = b"my_tax_information"
+    sk3 = factory.secret_key_by_label(label)
+    pk3 = PublicKey.from_secret_key(sk3)
+    assert sk1 != sk3
+
+
+def test_secret_key_serialization():
+    sk = SecretKey.random()
+    encoded_key = bytes(sk)
+    decoded_key = SecretKey.from_bytes(encoded_key)
+    assert sk == decoded_key
+
+
+def test_secret_key_str():
+    sk = SecretKey.random()
+    s = str(sk)
+    assert s == "SecretKey:..."
+
+
+def test_secret_key_hash():
+    sk = SecretKey.random()
+    # Insecure Python hash, shouldn't be available.
+    with pytest.raises(NotImplementedError):
+        hash(sk)
+
+
+def test_secret_key_factory_str():
+    skf = SecretKeyFactory.random()
+    s = str(skf)
+    assert s == "SecretKeyFactory:..."
+
+
+def test_secret_key_factory_hash():
+    skf = SecretKeyFactory.random()
+    # Insecure Python hash, shouldn't be available.
+    with pytest.raises(NotImplementedError):
+        hash(skf)
+
+
+def test_public_key_serialization():
+    sk = SecretKey.random()
+    pk = PublicKey.from_secret_key(sk)
+
+    encoded_key = bytes(pk)
+    decoded_key = PublicKey.from_bytes(encoded_key)
+    assert pk == decoded_key
+
+
+def test_public_key_point():
+    pk = PublicKey.from_secret_key(SecretKey.random())
+    assert bytes(pk) == bytes(pk.point())
+
+
+def test_public_key_str():
+    pk = PublicKey.from_secret_key(SecretKey.random())
+    s = str(pk)
+    assert 'PublicKey' in s
+
+
+def test_keying_material_serialization():
+    factory = SecretKeyFactory.random()
+
+    encoded_factory = bytes(factory)
+    decoded_factory = SecretKeyFactory.from_bytes(encoded_factory)
+
+    label = os.urandom(32)
+    sk1 = factory.secret_key_by_label(label)
+    sk2 = decoded_factory.secret_key_by_label(label)
+    assert sk1 == sk2
+
+
+def test_public_key_is_hashable():
+    sk = SecretKey.random()
+    pk = PublicKey.from_secret_key(sk)
+
+    sk2 = SecretKey.random()
+    pk2 = PublicKey.from_secret_key(sk2)
+    assert hash(pk) != hash(pk2)
+
+    pk3 = PublicKey.from_bytes(bytes(pk))
+    assert hash(pk) == hash(pk3)
+
+
+@pytest.mark.parametrize('execution_number', range(20))  # Run this test 20 times.
+def test_sign_and_verify(execution_number):
+    sk = SecretKey.random()
+    pk = PublicKey.from_secret_key(sk)
+
+    message = b"peace at dawn"
+    dst = b"dst"
+
+    digest = Hash(dst)
+    digest.update(message)
+    signature = sk.sign_digest(digest)
+
+    digest = Hash(dst)
+    digest.update(message)
+    assert signature.verify_digest(pk, digest)
+
+
+@pytest.mark.parametrize('execution_number', range(20))  # Run this test 20 times.
+def test_sign_serialize_and_verify(execution_number):
+    sk = SecretKey.random()
+    pk = PublicKey.from_secret_key(sk)
+
+    message = b"peace at dawn"
+    dst = b"dst"
+
+    digest = Hash(dst)
+    digest.update(message)
+    signature = sk.sign_digest(digest)
+
+    signature_bytes = bytes(signature)
+    signature_restored = Signature.from_bytes(signature_bytes)
+
+    digest = Hash(dst)
+    digest.update(message)
+    assert signature_restored.verify_digest(pk, digest)
+
+
+def test_verification_fail():
+    sk = SecretKey.random()
+    pk = PublicKey.from_secret_key(sk)
+
+    message = b"peace at dawn"
+    dst = b"dst"
+
+    digest = Hash(dst)
+    digest.update(message)
+    signature = sk.sign_digest(digest)
+
+    # wrong DST
+    digest = Hash(b"other dst")
+    digest.update(message)
+    assert not signature.verify_digest(pk, digest)
+
+    # wrong message
+    digest = Hash(dst)
+    digest.update(b"no peace at dawn")
+    assert not signature.verify_digest(pk, digest)
+
+    # bad signature
+    signature_bytes = bytes(signature)
+    signature_bytes = b'\x00' + signature_bytes[1:]
+    signature_restored = Signature.from_bytes(signature_bytes)
+
+    digest = Hash(dst)
+    digest.update(message)
+    assert not signature_restored.verify_digest(pk, digest)
+
+
+def test_signature_repr():
+
+    sk = SecretKey.random()
+    pk = PublicKey.from_secret_key(sk)
+
+    message = b"peace at dawn"
+    dst = b"dst"
+
+    digest = Hash(dst)
+    digest.update(message)
+    signature = sk.sign_digest(digest)
+
+    s = repr(signature)
+    assert 'Signature' in s
diff --git a/tests/test_pre.py b/tests/test_pre.py
new file mode 100644
index 00000000..94617ebe
--- /dev/null
+++ b/tests/test_pre.py
@@ -0,0 +1,95 @@
+import pytest
+
+from umbral import (
+    SecretKey,
+    PublicKey,
+    GenericError,
+    encrypt,
+    generate_kfrags,
+    decrypt_original,
+    reencrypt,
+    decrypt_reencrypted,
+    )
+
+
+def test_public_key_encryption(alices_keys):
+    delegating_sk, _ = alices_keys
+    delegating_pk = PublicKey.from_secret_key(delegating_sk)
+    plaintext = b'peace at dawn'
+    capsule, ciphertext = encrypt(delegating_pk, plaintext)
+    plaintext_decrypted = decrypt_original(delegating_sk, capsule, ciphertext)
+    assert plaintext == plaintext_decrypted
+
+    # Wrong secret key
+    sk = SecretKey.random()
+    with pytest.raises(GenericError):
+        decrypt_original(sk, capsule, ciphertext)
+
+
+SIMPLE_API_PARAMETERS = (
+    # (num_kfrags, threshold)
+    (1, 1),
+    (6, 1),
+    (6, 4),
+    (6, 6),
+    (50, 30)
+)
+
+@pytest.mark.parametrize("num_kfrags, threshold", SIMPLE_API_PARAMETERS)
+def test_simple_api(num_kfrags, threshold):
+    """
+    This test models the main interactions between actors (i.e., Alice,
+    Bob, Data Source, and Ursulas) and artifacts (i.e., public and private keys,
+    ciphertexts, capsules, KFrags, CFrags, etc).
+
+    The test covers all the main stages of data sharing:
+    key generation, delegation, encryption, decryption by
+    Alice, re-encryption by Ursula, and decryption by Bob.
+    """
+
+    # Key Generation (Alice)
+    delegating_sk = SecretKey.random()
+    delegating_pk = PublicKey.from_secret_key(delegating_sk)
+
+    signing_sk = SecretKey.random()
+    signing_pk = PublicKey.from_secret_key(signing_sk)
+
+    # Key Generation (Bob)
+    receiving_sk = SecretKey.random()
+    receiving_pk = PublicKey.from_secret_key(receiving_sk)
+
+    # Encryption by an unnamed data source
+    plaintext = b'peace at dawn'
+    capsule, ciphertext = encrypt(delegating_pk, plaintext)
+
+    # Decryption by Alice
+    plaintext_decrypted = decrypt_original(delegating_sk, capsule, ciphertext)
+    assert plaintext_decrypted == plaintext
+
+    # Split Re-Encryption Key Generation (aka Delegation)
+    kfrags = generate_kfrags(delegating_sk, receiving_pk, signing_sk, threshold, num_kfrags)
+
+    # Bob requests re-encryption to some set of M ursulas
+    cfrags = list()
+    for kfrag in kfrags[:threshold]:
+        # Ursula checks that the received kfrag is valid
+        assert kfrag.verify(signing_pk, delegating_pk, receiving_pk)
+
+        # Re-encryption by an Ursula
+        cfrag = reencrypt(capsule, kfrag)
+
+        # Bob collects the result
+        cfrags.append(cfrag)
+
+    # Bob checks that the received cfrags are valid
+    assert all(cfrag.verify(capsule, delegating_pk, receiving_pk, signing_pk) for cfrag in cfrags)
+
+    # Decryption by Bob
+    plaintext_reenc = decrypt_reencrypted(receiving_sk,
+                                          delegating_pk,
+                                          capsule,
+                                          cfrags[:threshold],
+                                          ciphertext,
+                                          )
+
+    assert plaintext_reenc == plaintext
diff --git a/tests/test_serializable.py b/tests/test_serializable.py
new file mode 100644
index 00000000..62d78e9b
--- /dev/null
+++ b/tests/test_serializable.py
@@ -0,0 +1,92 @@
+import re
+
+import pytest
+
+from umbral.serializable import Serializable, serialize_bool, take_bool
+
+
+class A(Serializable):
+
+    def __init__(self, val: int):
+        assert 0 <= val < 2**32
+        self.val = val
+
+    @classmethod
+    def __take__(cls, data):
+        val_bytes, data = cls.__take_bytes__(data, 4)
+        return cls(int.from_bytes(val_bytes, byteorder='big')), data
+
+    def __bytes__(self):
+        return self.val.to_bytes(4, byteorder='big')
+
+    def __eq__(self, other):
+        return isinstance(other, A) and self.val == other.val
+
+
+class B(Serializable):
+
+    def __init__(self, val: int):
+        assert 0 <= val < 2**16
+        self.val = val
+
+    @classmethod
+    def __take__(cls, data):
+        val_bytes, data = cls.__take_bytes__(data, 2)
+        return cls(int.from_bytes(val_bytes, byteorder='big')), data
+
+    def __bytes__(self):
+        return self.val.to_bytes(2, byteorder='big')
+
+    def __eq__(self, other):
+        return isinstance(other, B) and self.val == other.val
+
+
+class C(Serializable):
+
+    def __init__(self, a: A, b: B):
+        self.a = a
+        self.b = b
+
+    @classmethod
+    def __take__(cls, data):
+        components, data = cls.__take_types__(data, A, B)
+        return cls(*components), data
+
+    def __bytes__(self):
+        return bytes(self.a) + bytes(self.b)
+
+    def __eq__(self, other):
+        return isinstance(other, C) and self.a == other.a and self.b == other.b
+
+
+def test_normal_operation():
+    a = A(2**32 - 123)
+    b = B(2**16 - 456)
+    c = C(a, b)
+    c_back = C.from_bytes(bytes(c))
+    assert c_back == c
+
+
+def test_too_many_bytes():
+    a = A(2**32 - 123)
+    b = B(2**16 - 456)
+    c = C(a, b)
+    with pytest.raises(ValueError, match="1 bytes remaining after deserializing"):
+        C.from_bytes(bytes(c) + b'\x00')
+
+
+def test_not_enough_bytes():
+    a = A(2**32 - 123)
+    b = B(2**16 - 456)
+    c = C(a, b)
+    # Will happen on deserialization of B - 1 byte missing
+    with pytest.raises(ValueError, match="cannot take 2 bytes from a bytestring of size 1"):
+        C.from_bytes(bytes(c)[:-1])
+
+
+def test_serialize_bool():
+    assert take_bool(serialize_bool(True) + b'1234') == (True, b'1234')
+    assert take_bool(serialize_bool(False) + b'12') == (False, b'12')
+    error_msg = re.escape("Incorrectly serialized boolean; expected b'\\x00' or b'\\x01', got b'z'")
+    with pytest.raises(ValueError, match=error_msg):
+        take_bool(b'z1234')
diff --git a/tests/test_vectors.py b/tests/test_vectors.py
new file mode 100644
index 00000000..967c9359
--- /dev/null
+++ b/tests/test_vectors.py
@@ -0,0 +1,167 @@
+import json
+import os
+
+from umbral import (
+    Capsule, KeyFrag, CapsuleFrag, SecretKey, PublicKey, encrypt, generate_kfrags, reencrypt)
+from umbral.curve_scalar import CurveScalar
+from umbral.curve_point import  CurvePoint
+from umbral.hashing import Hash, unsafe_hash_to_point
+from umbral.dem import DEM, kdf
+
+
+def test_scalar_operations():
+
+    vector_file = os.path.join('vectors', 'vectors_scalar_operations.json')
+    try:
+        with open(vector_file) as f:
+            vector_suite = json.load(f)
+    except OSError:
+        raise
+
+    bn1 = CurveScalar.from_bytes(bytes.fromhex(vector_suite['first operand']))
+    bn2 = CurveScalar.from_bytes(bytes.fromhex(vector_suite['second operand']))
+
+    expected = dict()
+    for op_result in vector_suite['vectors']:
+        result = bytes.fromhex(op_result['result'])
+        expected[op_result['operation']] = CurveScalar.from_bytes(result)
+
+    test = [('Addition', bn1 + bn2),
+            ('Subtraction', bn1 - bn2),
+            ('Multiplication', bn1 * bn2),
+            ('Inverse', bn1.invert()),
+            ]
+
+    for (operation, result) in test:
+        assert result == expected[operation], 'Error in {}'.format(operation)
+
+def test_scalar_hash():
+
+    vector_file = os.path.join('vectors', 'vectors_scalar_from_digest.json')
+    try:
+        with open(vector_file) as f:
+            vector_suite = json.load(f)
+    except OSError:
+        raise
+
+    for vector in vector_suite['vectors']:
+        hash_input = [bytes.fromhex(item['bytes']) for item in vector['input']]
+        expected = CurveScalar.from_bytes(bytes.fromhex(vector['output']))
+
+        digest = Hash(b'some_dst')
+        for input_ in hash_input:
+            digest.update(input_)
+        scalar = CurveScalar.from_digest(digest)
+        assert scalar == expected
+
+
+def test_point_operations():
+
+    vector_file = os.path.join('vectors', 'vectors_point_operations.json')
+    try:
+        with open(vector_file) as f:
+            vector_suite = json.load(f)
+    except OSError:
+        raise
+
+    point1 = CurvePoint.from_bytes(bytes.fromhex(vector_suite['first CurvePoint operand']))
+    point2 = CurvePoint.from_bytes(bytes.fromhex(vector_suite['second CurvePoint operand']))
+    bn1 = CurveScalar.from_bytes(bytes.fromhex(vector_suite['CurveScalar operand']))
+
+    expected = dict()
+    for op_result in vector_suite['vectors']:
+        expected[op_result['operation']] = bytes.fromhex(op_result['result'])
+
+    test = [('Addition', point1 + point2),
+            ('Subtraction', point1 - point2),
+            ('Multiplication', point1 * bn1),
+            ('Inversion', -point1),
+            ]
+
+    for (operation, result) in test:
+        assert result == CurvePoint.from_bytes(expected[operation]), 'Error in {}'.format(operation)
+
+    test = [('To_affine.X', point1.to_affine()[0]),
+            ('To_affine.Y', point1.to_affine()[1]),
+            ]
+
+    for (operation, result) in test:
+        assert result == int.from_bytes(expected[operation], 'big'), 'Error in {}'.format(operation)
+
+    assert kdf(bytes(point1), DEM.KEY_SIZE) == expected['kdf']
+
+
+def test_unsafe_hash_to_point():
+
+    vector_file = os.path.join('vectors', 'vectors_unsafe_hash_to_point.json')
+    try:
+        with open(vector_file) as f:
+            vector_suite = json.load(f)
+    except OSError:
+        raise
+
+    for item in vector_suite['vectors']:
+        data = bytes.fromhex(item['data'])
+        dst = bytes.fromhex(item['dst'])
+        expected = CurvePoint.from_bytes(bytes.fromhex(item['point']))
+        assert expected == unsafe_hash_to_point(dst=dst, data=data)
+
+
+def test_kfrags():
+
+    vector_file = os.path.join('vectors', 'vectors_kfrags.json')
+    try:
+        with open(vector_file) as f:
+            vector_suite = json.load(f)
+    except OSError:
+        raise
+
+    verifying_pk = PublicKey.from_bytes(bytes.fromhex(vector_suite['verifying_pk']))
+    delegating_pk = PublicKey.from_bytes(bytes.fromhex(vector_suite['delegating_pk']))
+    receiving_pk = PublicKey.from_bytes(bytes.fromhex(vector_suite['receiving_pk']))
+
+    for json_kfrag in vector_suite['vectors']:
+        kfrag = KeyFrag.from_bytes(bytes.fromhex(json_kfrag['kfrag']))
+        assert kfrag.verify(signing_pk=verifying_pk,
+                            delegating_pk=delegating_pk,
+                            receiving_pk=receiving_pk), \
+            'Invalid KeyFrag {}'.format(bytes(kfrag).hex())
+
+
+def test_cfrags():
+
+    vector_file = os.path.join('vectors', 'vectors_cfrags.json')
+    try:
+        with open(vector_file) as f:
+            vector_suite = json.load(f)
+    except OSError:
+        raise
+
+    capsule = Capsule.from_bytes(bytes.fromhex(vector_suite['capsule']))
+
+    verifying_pk = PublicKey.from_bytes(bytes.fromhex(vector_suite['verifying_pk']))
+    delegating_pk = PublicKey.from_bytes(bytes.fromhex(vector_suite['delegating_pk']))
+    receiving_pk = PublicKey.from_bytes(bytes.fromhex(vector_suite['receiving_pk']))
+
+    kfrags_n_cfrags = [(KeyFrag.from_bytes(bytes.fromhex(json_kfrag['kfrag'])),
+                        CapsuleFrag.from_bytes(bytes.fromhex(json_kfrag['cfrag'])))
+                       for json_kfrag in vector_suite['vectors']]
+
+    metadata = bytes.fromhex(vector_suite['metadata'])
+
+    for kfrag, cfrag in kfrags_n_cfrags:
+        assert kfrag.verify(signing_pk=verifying_pk,
+                            delegating_pk=delegating_pk,
+                            receiving_pk=receiving_pk), \
+            'Invalid KeyFrag {}'.format(bytes(kfrag.to_bytes).hex())
+
+        new_cfrag = reencrypt(capsule, kfrag, metadata=metadata)
+        assert new_cfrag.point_e1 == cfrag.point_e1
+        assert new_cfrag.point_v1 == cfrag.point_v1
+        assert new_cfrag.kfrag_id == cfrag.kfrag_id
+        assert new_cfrag.precursor == cfrag.precursor
+        assert new_cfrag.verify(capsule,
+                                signing_pk=verifying_pk,
+                                delegating_pk=delegating_pk,
+                                receiving_pk=receiving_pk,
+                                metadata=metadata)
diff --git a/tests/unit/test_capsule_correctness_checks.py b/tests/unit/test_capsule_correctness_checks.py
deleted file mode 100644
index 6b218791..00000000
--- a/tests/unit/test_capsule_correctness_checks.py
+++ /dev/null
@@ -1,104 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-import os
-
-import pytest
-
-from umbral.curvebn import CurveBN
-from umbral.cfrags import CapsuleFrag
-from umbral.keys import UmbralPrivateKey
-from umbral.point import Point
-from umbral.pre import Capsule
-from umbral.config import default_params
-
-
-def test_cannot_attach_cfrag_without_keys():
-    """
-    We need the proper keys to verify the correctness of CFrags
-    in order to attach them to a Capsule.
-    """
-    params = default_params()
-
-    capsule = Capsule(params,
-                      point_e=Point.gen_rand(),
-                      point_v=Point.gen_rand(),
-                      bn_sig=CurveBN.gen_rand())
-
-    cfrag = CapsuleFrag(point_e1=Point.gen_rand(),
-                        point_v1=Point.gen_rand(),
-                        kfrag_id=os.urandom(10),
-                        point_precursor=Point.gen_rand(),
-                        )
-
-    with pytest.raises(TypeError):
-        capsule.attach_cfrag(cfrag)
-
-
-def test_cannot_attach_cfrag_without_proof():
-    """
-    However, even when properly attaching keys, we can't attach the CFrag
-    if it is unproven.
-    """
-    params = default_params()
-
-    capsule = Capsule(params,
-                      point_e=Point.gen_rand(),
-                      point_v=Point.gen_rand(),
-                      bn_sig=CurveBN.gen_rand())
-
-    cfrag = CapsuleFrag(point_e1=Point.gen_rand(),
-                        point_v1=Point.gen_rand(),
-                        kfrag_id=os.urandom(10),
-                        point_precursor=Point.gen_rand(),
-                        )
-    key_details = capsule.set_correctness_keys(
-        UmbralPrivateKey.gen_key().get_pubkey(),
-        UmbralPrivateKey.gen_key().get_pubkey(),
-        UmbralPrivateKey.gen_key().get_pubkey())
-
-    delegating_details, receiving_details, verifying_details = key_details
-
-    assert all((delegating_details, receiving_details, verifying_details))
-
-    with pytest.raises(cfrag.NoProofProvided):
-        capsule.attach_cfrag(cfrag)
-
-
-def test_cannot_set_different_keys():
-    """
-    Once a key is set on a Capsule, it can't be changed to a different key.
-    """
-    params = default_params()
-
-    capsule = Capsule(params,
-                      point_e=Point.gen_rand(),
-                      point_v=Point.gen_rand(),
-                      bn_sig=CurveBN.gen_rand())
-
-    capsule.set_correctness_keys(delegating=UmbralPrivateKey.gen_key().get_pubkey(),
-                                 receiving=UmbralPrivateKey.gen_key().get_pubkey(),
-                                 verifying=UmbralPrivateKey.gen_key().get_pubkey())
-
-    with pytest.raises(ValueError):
-        capsule.set_correctness_keys(delegating=UmbralPrivateKey.gen_key().get_pubkey())
-
-    with pytest.raises(ValueError):
-        capsule.set_correctness_keys(receiving=UmbralPrivateKey.gen_key().get_pubkey())
-
-    with pytest.raises(ValueError):
-        capsule.set_correctness_keys(verifying=UmbralPrivateKey.gen_key().get_pubkey())
diff --git a/tests/unit/test_capsule_operations.py b/tests/unit/test_capsule_operations.py
deleted file mode 100644
index 4f4588bd..00000000
--- a/tests/unit/test_capsule_operations.py
+++ /dev/null
@@ -1,135 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-import pytest
-
-from umbral import pre
-from umbral.curvebn import CurveBN
-from umbral.point import Point
-from umbral.pre import Capsule
-from umbral.signing import Signer
-from umbral.keys import UmbralPrivateKey
-from umbral.config import default_params
-
-
-def test_capsule_creation(alices_keys):
-
-    params = default_params()
-
-    with pytest.raises(TypeError):
-        rare_capsule = Capsule(params)  # Alice cannot make a capsule this way.
-
-
-
-    # Some users may create capsules their own way.
-    custom_capsule = Capsule(params,
-                             point_e=Point.gen_rand(),
-                             point_v=Point.gen_rand(),
-                             bn_sig=CurveBN.gen_rand())
-
-    assert isinstance(custom_capsule, Capsule)
-
-    # Typical Alice, constructing a typical capsule
-    delegating_privkey, _signing_key = alices_keys
-    plaintext = b'peace at dawn'
-    ciphertext, typical_capsule = pre.encrypt(delegating_privkey.get_pubkey(), plaintext)
-
-    assert isinstance(typical_capsule, Capsule)
-
-
-def test_capsule_equality():
-    params = default_params()
-
-    one_capsule = Capsule(params,
-                          point_e=Point.gen_rand(),
-                          point_v=Point.gen_rand(),
-                          bn_sig=CurveBN.gen_rand())
-
-    another_capsule = Capsule(params,
-                              point_e=Point.gen_rand(),
-                              point_v=Point.gen_rand(),
-                              bn_sig=CurveBN.gen_rand())
-
-    assert one_capsule != another_capsule
-
-
-def test_decapsulation_by_alice(alices_keys):
-    params = default_params()
-
-    delegating_privkey, _signing_privkey = alices_keys
-
-    sym_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey())
-    assert len(sym_key) == 32
-
-    # The symmetric key sym_key is perhaps used for block cipher here in a real-world scenario.
-    sym_key_2 = pre._decapsulate_original(delegating_privkey, capsule)
-    assert sym_key_2 == sym_key
-
-
-def test_bad_capsule_fails_reencryption(kfrags):
-    params = default_params()
-
-    bollocks_capsule = Capsule(params,
-                               point_e=Point.gen_rand(),
-                               point_v=Point.gen_rand(),
-                               bn_sig=CurveBN.gen_rand())
-
-    for kfrag in kfrags:
-        with pytest.raises(Capsule.NotValid):
-            pre.reencrypt(kfrag, bollocks_capsule)
-
-
-def test_capsule_as_dict_key(alices_keys, bobs_keys):
-    delegating_privkey, signing_privkey = alices_keys
-    signer_alice = Signer(signing_privkey)
-    delegating_pubkey = delegating_privkey.get_pubkey()
-    signing_pubkey = signing_privkey.get_pubkey()
-
-    receiving_privkey, receiving_pubkey = bobs_keys
-
-    plain_data = b'peace at dawn'
-    ciphertext, capsule = pre.encrypt(delegating_pubkey, plain_data)
-
-    # We can use the capsule as a key, and successfully lookup using it.
-    some_dict = {capsule: "Thing that Bob wants to try per-Capsule"}
-    assert some_dict[capsule] == "Thing that Bob wants to try per-Capsule"
-
-    # And if we change the value for this key, all is still well.
-    some_dict[capsule] = "Bob has changed his mind."
-    assert some_dict[capsule] == "Bob has changed his mind."
-    assert len(some_dict.keys()) == 1
-
-
-def test_capsule_length(prepared_capsule, kfrags):
-    capsule = prepared_capsule
-
-    for counter, kfrag in enumerate(kfrags):
-        assert len(capsule) == counter
-        cfrag = pre.reencrypt(kfrag, capsule)
-        capsule.attach_cfrag(cfrag)
-
-
-def test_capsule_clear(prepared_capsule, kfrags):
-    capsule = prepared_capsule
-
-    for counter, kfrag in enumerate(kfrags):
-        assert len(capsule) == counter
-        cfrag = pre.reencrypt(kfrag, capsule)
-        capsule.attach_cfrag(cfrag)
-
-    capsule.clear_cfrags()
-    assert len(capsule) == 0
\ No newline at end of file
diff --git a/tests/unit/test_capsule_serializers.py b/tests/unit/test_capsule_serializers.py
deleted file mode 100644
index 84e42df2..00000000
--- a/tests/unit/test_capsule_serializers.py
+++ /dev/null
@@ -1,63 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-import pytest
-
-from umbral.pre import Capsule
-from umbral.curvebn import CurveBN
-from umbral.point import Point
-
-
-def test_capsule_serialization(capsule: Capsule):
-    params = capsule.params
-    capsule_bytes = capsule.to_bytes()
-    capsule_bytes_casted = bytes(capsule)
-    assert capsule_bytes == capsule_bytes_casted
-
-    # A Capsule can be represented as the 98 total bytes of two Points (33 each) and a CurveBN (32).
-    assert len(capsule_bytes) == Capsule.expected_bytes_length()
-
-    new_capsule = Capsule.from_bytes(capsule_bytes, params)
-
-    # Three ways to think about equality.
-    # First, the public approach for the Capsule.  Simply:
-    assert new_capsule == capsule
-
-    # Second, we show that the original components (which is all we have here since we haven't activated) are the same:
-    assert new_capsule.components() == capsule.components()
-
-    # Third, we can directly compare the private original component attributes
-    # (though this is not a supported approach):
-    assert new_capsule.point_e == capsule.point_e
-    assert new_capsule.point_v == capsule.point_v
-    assert new_capsule.bn_sig == capsule.bn_sig
-
-
-def test_cannot_create_capsule_from_bogus_material(alices_keys):
-    params = alices_keys[0].params
-    
-    with pytest.raises(TypeError):
-        _capsule_of_questionable_parentage = Capsule(params,
-                                                     point_e=Point.gen_rand(),
-                                                     point_v=42,
-                                                     bn_sig=CurveBN.gen_rand())
-
-    with pytest.raises(TypeError):
-        _capsule_of_questionable_parentage = Capsule(params,
-                                                     point_e=Point.gen_rand(),
-                                                     point_v=Point.gen_rand(),
-                                                     bn_sig=42)
diff --git a/tests/unit/test_cfrags.py b/tests/unit/test_cfrags.py
deleted file mode 100644
index d707157c..00000000
--- a/tests/unit/test_cfrags.py
+++ /dev/null
@@ -1,126 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-from umbral import pre
-from umbral.cfrags import CapsuleFrag, CorrectnessProof
-
-
-def test_cfrag_serialization_with_proof_and_metadata(prepared_capsule, kfrags):
-
-    # Example of potential metadata to describe the re-encryption request
-    metadata = b'This is an example of metadata for re-encryption request'
-    for kfrag in kfrags:
-        cfrag = pre.reencrypt(kfrag, prepared_capsule, provide_proof=True, metadata=metadata)
-        cfrag_bytes = cfrag.to_bytes()
-
-        proof = cfrag.proof
-        assert proof is not None
-        assert proof.metadata is not None
-
-        new_cfrag = CapsuleFrag.from_bytes(cfrag_bytes)
-        assert new_cfrag.point_e1 == cfrag.point_e1
-        assert new_cfrag.point_v1 == cfrag.point_v1
-        assert new_cfrag.kfrag_id == cfrag.kfrag_id
-        assert new_cfrag.point_precursor == cfrag.point_precursor
-
-        new_proof = new_cfrag.proof
-        assert new_proof is not None
-        assert new_proof.point_e2 == proof.point_e2
-        assert new_proof.point_v2 == proof.point_v2
-        assert new_proof.point_kfrag_commitment == proof.point_kfrag_commitment
-        assert new_proof.point_kfrag_pok == proof.point_kfrag_pok
-        assert new_proof.bn_sig == proof.bn_sig
-        assert new_proof.metadata == metadata
-        assert new_proof.metadata == proof.metadata
-
-
-def test_cfrag_serialization_with_proof_but_no_metadata(prepared_capsule, kfrags):
-
-    for kfrag in kfrags:
-        cfrag = pre.reencrypt(kfrag, prepared_capsule, provide_proof=True)
-        cfrag_bytes = cfrag.to_bytes()
-
-        proof = cfrag.proof
-        assert proof is not None
-        assert proof.metadata is None
-
-        # A CFrag can be represented as the 131 total bytes of three Points (33 each) and a CurveBN (32).
-        # TODO: Figure out final size for CFrags with proofs
-        # assert len(cfrag_bytes) == 33 + 33 + 33 + 32 == 131
-
-        new_cfrag = CapsuleFrag.from_bytes(cfrag_bytes)
-        assert new_cfrag.point_e1 == cfrag.point_e1
-        assert new_cfrag.point_v1 == cfrag.point_v1
-        assert new_cfrag.kfrag_id == cfrag.kfrag_id
-        assert new_cfrag.point_precursor == cfrag.point_precursor
-
-        new_proof = new_cfrag.proof
-        assert new_proof is not None
-        assert new_proof.point_e2 == proof.point_e2
-        assert new_proof.point_v2 == proof.point_v2
-        assert new_proof.point_kfrag_commitment == proof.point_kfrag_commitment
-        assert new_proof.point_kfrag_pok == proof.point_kfrag_pok
-        assert new_proof.bn_sig == proof.bn_sig
-        assert new_proof.metadata is None
-
-
-def test_cfrag_serialization_no_proof_no_metadata(prepared_capsule, kfrags):
-    
-    for kfrag in kfrags:
-        cfrag = pre.reencrypt(kfrag, prepared_capsule, provide_proof=False)
-        cfrag_bytes = cfrag.to_bytes()
-
-        proof = cfrag.proof
-        assert proof is None
-
-        assert len(cfrag_bytes) == CapsuleFrag.expected_bytes_length()
-
-        new_cfrag = CapsuleFrag.from_bytes(cfrag_bytes)
-        assert new_cfrag.point_e1 == cfrag.point_e1
-        assert new_cfrag.point_v1 == cfrag.point_v1
-        assert new_cfrag.kfrag_id == cfrag.kfrag_id
-        assert new_cfrag.point_precursor == cfrag.point_precursor
-
-        new_proof = new_cfrag.proof
-        assert new_proof is None
-
-
-def test_correctness_proof_serialization(prepared_capsule, kfrags):
-    
-    # Example of potential metadata to describe the re-encryption request
-    metadata = b"This is an example of metadata for re-encryption request"
-
-    for kfrag in kfrags:
-        cfrag = pre.reencrypt(kfrag, prepared_capsule, metadata=metadata)
-        proof = cfrag.proof
-        proof_bytes = proof.to_bytes()
-
-        # A CorrectnessProof can be represented as
-        # the 228 total bytes of four Points (33 each) and three BigNums (32 each).
-        # TODO: Figure out final size for CorrectnessProofs
-        # assert len(proof_bytes) == (33 * 4) + (32 * 3) == 228
-
-        new_proof = CorrectnessProof.from_bytes(proof_bytes)
-        assert new_proof.point_e2 == proof.point_e2
-        assert new_proof.point_v2 == proof.point_v2
-        assert new_proof.point_kfrag_commitment == proof.point_kfrag_commitment
-        assert new_proof.point_kfrag_pok == proof.point_kfrag_pok
-        assert new_proof.bn_sig == proof.bn_sig
-        assert new_proof.kfrag_signature == proof.kfrag_signature
-        assert new_proof.metadata == proof.metadata
-
-        
\ No newline at end of file
diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py
deleted file mode 100644
index cf130519..00000000
--- a/tests/unit/test_config.py
+++ /dev/null
@@ -1,86 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-import importlib
-import pytest
-import warnings
-
-from umbral.config import _CONFIG
-from umbral.curve import SECP256K1, SECP256R1
-
-
-def _copy_config_for_testing():
-    """
-    NEVER do this.  This is for testing only.
-    This is absolutely not a thing to actually do in production code.  At all.  Ever.
-    """
-    config_module_spec = importlib.util.find_spec("umbral.config")
-    config_copy = importlib.util.module_from_spec(config_module_spec)
-    config_module_spec.loader.exec_module(config_copy)
-    assert hasattr(config_copy, "default_curve")
-    assert config_copy is not _CONFIG
-    return config_copy
-
-
-def test_try_to_use_curve_with_no_default_curve():
-    config = _copy_config_for_testing()
-
-    # No curve is set.
-    assert config._CONFIG._CONFIG__curve is None
-
-    # Getting the default curve if we haven't set one yet sets one and gives us a warning.
-    with warnings.catch_warnings(record=True) as caught_warnings:
-        assert len(caught_warnings) == 0
-        config.default_curve()
-        assert len(caught_warnings) == 1
-        assert caught_warnings[0].message.args[0] == config._CONFIG._CONFIG__WARNING_IF_NO_DEFAULT_SET
-        assert caught_warnings[0].category == RuntimeWarning
-
-    # Now, a default curve has been set.
-    assert config._CONFIG._CONFIG__curve == SECP256K1
-
-
-def test_try_to_use_default_params_with_no_default_curve():
-    config = _copy_config_for_testing()
-
-    # Again, no curve is set.
-    assert config._CONFIG._CONFIG__curve is None
-
-    # This time, we'll try to use default_params() and get the same warning as above.
-    with warnings.catch_warnings(record=True) as caught_warnings:
-        assert len(caught_warnings) == 0
-        config.default_params()
-        assert len(caught_warnings) == 1
-        assert caught_warnings[0].message.args[0] == config._CONFIG._CONFIG__WARNING_IF_NO_DEFAULT_SET
-        assert caught_warnings[0].category == RuntimeWarning
-
-    # Now, a default curve has been set.
-    assert config._CONFIG._CONFIG__curve == SECP256K1
-
-
-def test_cannot_set_default_curve_twice():
-    config = _copy_config_for_testing()
-
-    # pyumbral even supports NIST curves!
-    config.set_default_curve(SECP256R1)
-
-    # Our default curve has been set...
-    assert config.default_curve() == SECP256R1
-
-    # ...but once set, you can't set the default curve again, even if you've found a better one.
-    with pytest.raises(config._CONFIG.UmbralConfigurationError):
-        config.set_default_curve(SECP256K1)
diff --git a/tests/unit/test_curve.py b/tests/unit/test_curve.py
deleted file mode 100644
index 16b14ec2..00000000
--- a/tests/unit/test_curve.py
+++ /dev/null
@@ -1,82 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-import pytest
-
-from umbral.curve import Curve
-
-
-def test_supported_curves():
-
-    # Ensure we have the correct number opf supported curves hardcoded
-    number_of_supported_curves = 3
-    assert len(Curve._supported_curves) == number_of_supported_curves
-
-    # Manually ensure the `_supported curves` dict contains only valid supported curves
-    assert Curve._supported_curves[415] == 'secp256r1'
-    assert Curve._supported_curves[714] == 'secp256k1'
-    assert Curve._supported_curves[715] == 'secp384r1'
-
-    nid, name = 714, 'secp256k1'
-
-    #
-    # Create by NID
-    #
-
-    # supported
-    _curve_714 = Curve(nid=nid)
-    assert _curve_714.curve_nid == nid
-    assert _curve_714.name == name
-
-    # unsuported
-    with pytest.raises(NotImplementedError):
-        _ = Curve(711)
-
-
-    #
-    # Create by Name
-    #
-
-    # Supported
-    _curve_secp256k1 = Curve.from_name(name)
-    assert _curve_secp256k1.name == name
-    assert _curve_secp256k1.curve_nid == nid
-
-    # Unsupported
-    with pytest.raises(NotImplementedError):
-        _ = Curve.from_name('abcd123e4')
-
-    # Import curve constants
-    from umbral.curve import SECP256R1, SECP256K1, SECP384R1
-    test_p256 = SECP256R1
-    test_secp256k1 = SECP256K1
-    test_p384 = SECP384R1
-
-    # Test the hardcoded curve NIDs are correct:
-    assert test_p256.curve_nid == 415
-    assert test_secp256k1.curve_nid == 714
-    assert test_p384.curve_nid == 715
-
-    # Ensure every curve constant is in the CURVES collection
-    from umbral.curve import CURVES
-    assert len(CURVES) == number_of_supported_curves
-
-    # Ensure all supported curves can be initialized
-    for nid, name in Curve._supported_curves.items():
-        _curve_nid, _curve_name = Curve(nid=nid), Curve.from_name(name)
-        assert _curve_nid.name == name
-        assert _curve_name.curve_nid == nid
diff --git a/tests/unit/test_kfrags.py b/tests/unit/test_kfrags.py
deleted file mode 100644
index 1d3297f2..00000000
--- a/tests/unit/test_kfrags.py
+++ /dev/null
@@ -1,67 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-from umbral.kfrags import KFrag
-
-
-def test_kfrag_serialization(alices_keys, bobs_keys, kfrags):
-    
-    delegating_privkey, signing_privkey = alices_keys
-    _receiving_privkey, receiving_pubkey = bobs_keys
-
-    for kfrag in kfrags:
-        kfrag_bytes = kfrag.to_bytes()
-        assert len(kfrag_bytes) == KFrag.expected_bytes_length()
-
-        new_kfrag = KFrag.from_bytes(kfrag_bytes)
-        assert new_kfrag.id == kfrag.id
-        assert new_kfrag.bn_key == kfrag.bn_key
-        assert new_kfrag.point_precursor == kfrag.point_precursor
-        assert new_kfrag.point_commitment == kfrag.point_commitment
-        assert new_kfrag.keys_in_signature == kfrag.keys_in_signature
-        assert new_kfrag.signature_for_proxy == kfrag.signature_for_proxy
-        assert new_kfrag.signature_for_bob == kfrag.signature_for_bob
-
-        assert new_kfrag.verify(signing_pubkey=signing_privkey.get_pubkey(),
-                                delegating_pubkey=delegating_privkey.get_pubkey(),
-                                receiving_pubkey=receiving_pubkey)
-
-        assert new_kfrag == kfrag
-
-
-def test_kfrag_verify_for_capsule(prepared_capsule, kfrags):
-    for kfrag in kfrags:
-        assert kfrag.verify_for_capsule(prepared_capsule)
-
-        # If we alter some element, the verification fails
-        previous_id, kfrag.id = kfrag.id, bytes(32)
-        assert not kfrag.verify_for_capsule(prepared_capsule)    
-
-        # Let's restore the KFrag, and alter the re-encryption key instead
-        kfrag.id = previous_id
-        kfrag.bn_key += kfrag.bn_key
-        assert not kfrag.verify_for_capsule(prepared_capsule)    
-
-
-def test_kfrag_as_dict_key(kfrags):
-    dict_with_kfrags_as_keys = dict()
-    dict_with_kfrags_as_keys[kfrags[0]] = "Some llamas.  Definitely some llamas."
-    dict_with_kfrags_as_keys[kfrags[1]] = "No llamas here.  Definitely not."
-
-    assert dict_with_kfrags_as_keys[kfrags[0]] != dict_with_kfrags_as_keys[kfrags[1]]
-
-
diff --git a/tests/unit/test_primitives/conftest.py b/tests/unit/test_primitives/conftest.py
deleted file mode 100644
index cc118005..00000000
--- a/tests/unit/test_primitives/conftest.py
+++ /dev/null
@@ -1,167 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-import contextlib
-
-import pytest
-from cryptography.hazmat.backends.openssl import backend
-
-from umbral.curvebn import CurveBN
-from umbral.point import Point
-
-
-@pytest.fixture()
-def mock_openssl(mocker, random_ec_point1: Point, random_ec_curvebn1: CurveBN, random_ec_curvebn2: CurveBN):
-    """
-    Patches openssl backend methods for testing.
-    For all functions, 1 is returned for success, 0 on error.
-
-    """
-
-    actual_backend = {
-        # Point
-        'EC_POINT_mul': backend._lib.EC_POINT_mul,
-        'EC_POINT_cmp': backend._lib.EC_POINT_cmp,
-        'EC_POINT_add': backend._lib.EC_POINT_add,
-        'EC_POINT_invert': backend._lib.EC_POINT_invert,
-
-        # Bignum
-        'BN_cmp': backend._lib.BN_cmp,
-        'BN_mod_exp': backend._lib.BN_mod_exp,
-        'BN_mod_mul': backend._lib.BN_mod_mul,
-        'BN_mod_inverse': backend._lib.BN_mod_inverse,
-        'BN_mod_add': backend._lib.BN_mod_add,
-        'BN_mod_sub': backend._lib.BN_mod_sub,
-        'BN_nnmod': backend._lib.BN_nnmod,
-    }
-
-    def check_curvebn_ctypes(*curvebns):
-        for bn in curvebns:
-            assert 'BIGNUM' in str(bn)
-            assert bn.__class__.__name__ == 'CDataGCP'
-
-    def check_point_ctypes(*ec_points):
-        for point in ec_points:
-            assert 'EC_POINT' in str(point)
-            assert point.__class__.__name__ == 'CDataGCP'
-
-    @contextlib.contextmanager
-    def mocked_openssl_backend():
-        def mocked_ec_point_equality(group, ec_point, other_point, context):
-            check_point_ctypes(ec_point, other_point)
-            assert 'BN_CTX' in str(context)
-            assert 'EC_GROUP' in str(group)
-            assert random_ec_point1.curve.ec_group == group
-            assert not bool(actual_backend['EC_POINT_cmp'](group, random_ec_point1.ec_point, ec_point, context))
-            result = actual_backend['EC_POINT_cmp'](group, random_ec_point1.ec_point, other_point, context)
-            assert not bool(result)
-            return result
-
-        def mocked_ec_point_addition(group, sum, ec_point, other_point, context):
-            check_point_ctypes(sum, other_point)
-            assert 'BN_CTX' in str(context)
-            assert random_ec_point1.group == group
-            assert not bool(actual_backend['EC_POINT_cmp'](group, random_ec_point1.ec_point, ec_point, context))
-            return actual_backend['EC_POINT_add'](group, sum, ec_point, other_point, context)
-
-        def mocked_ec_point_multiplication(group, product, null, ec_point, curvebn, context):
-            check_point_ctypes(ec_point, product)
-            assert 'BN_CTX' in str(context)
-            assert 'EC_GROUP' in str(group)
-            assert 'NULL' in str(null)
-            assert random_ec_point1.group == group
-            assert random_ec_curvebn1.curve_nid == random_ec_point1.curve_nid
-            assert not bool(actual_backend['EC_POINT_cmp'](group, random_ec_point1.ec_point, ec_point, context))
-            assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
-            return actual_backend['EC_POINT_mul'](group, product, null, ec_point, curvebn, context)
-
-        def mocked_ec_point_inversion(group, inverse, context):
-            check_point_ctypes(inverse)
-            assert 'BN_CTX' in str(context)
-            assert random_ec_point1.group == group
-            return actual_backend['EC_POINT_invert'](group, inverse, context)
-
-        def mocked_bn_compare(curvebn, other):
-            check_curvebn_ctypes(curvebn, other)
-            assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
-            return actual_backend['BN_cmp'](curvebn, other)
-
-        def mocked_bn_mod_exponent(power, curvebn, other, order, context):
-            check_curvebn_ctypes(curvebn, other, power, order)
-            assert 'BN_CTX' in str(context)
-            assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
-            assert not bool(actual_backend['BN_cmp'](random_ec_curvebn2.bignum, other))
-            return actual_backend['BN_mod_exp'](power, curvebn, other, order, context)
-
-        def mocked_bn_mod_multiplication(product, curvebn, other, order, context):
-            check_curvebn_ctypes(curvebn, other, product, order)
-            assert 'BN_CTX' in str(context)
-            assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
-            assert not bool(actual_backend['BN_cmp'](random_ec_curvebn2.bignum, other))
-            return actual_backend['BN_mod_mul'](product, curvebn, other, order, context)
-
-        def mocked_bn_inverse(null, curvebn, order, context):
-            check_curvebn_ctypes(curvebn, order)
-            assert 'BN_CTX' in str(context)
-            assert 'NULL' in str(null)
-            assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
-            return actual_backend['BN_mod_inverse'](null, curvebn, order, context)
-
-        def mocked_bn_addition(sum, curvebn, other, order, context):
-            check_curvebn_ctypes(curvebn, other, sum, order)
-            assert 'BN_CTX' in str(context)
-            assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
-            assert not bool(actual_backend['BN_cmp'](random_ec_curvebn2.bignum, other))
-            return actual_backend['BN_mod_add'](sum, curvebn, other, order, context)
-
-        def mocked_bn_subtraction(diff, curvebn, other, order, context):
-            check_curvebn_ctypes(curvebn, other, diff, order)
-            assert 'BN_CTX' in str(context)
-            assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
-            assert not bool(actual_backend['BN_cmp'](random_ec_curvebn2.bignum, other))
-            return actual_backend['BN_mod_sub'](diff, curvebn, other, order, context)
-
-        def mocked_bn_nmodulus(rem, curvebn, other, context):
-            check_curvebn_ctypes(curvebn, other, rem)
-            assert 'BN_CTX' in str(context)
-            assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
-            assert not bool(actual_backend['BN_cmp'](random_ec_curvebn2.bignum, other))
-            return actual_backend['BN_nnmod'](rem, curvebn, other, context)
-
-        mock_load = {
-                     # Point
-                     'EC_POINT_mul': mocked_ec_point_multiplication,
-                     'EC_POINT_cmp': mocked_ec_point_equality,
-                     'EC_POINT_add': mocked_ec_point_addition,
-                     'EC_POINT_invert': mocked_ec_point_inversion,
-
-                     # Bignum
-                     'BN_cmp': mocked_bn_compare,
-                     'BN_mod_exp': mocked_bn_mod_exponent,
-                     'BN_mod_mul': mocked_bn_mod_multiplication,
-                     'BN_mod_inverse': mocked_bn_inverse,
-                     'BN_mod_add': mocked_bn_addition,
-                     'BN_mod_sub': mocked_bn_subtraction,
-                     'BN_nnmod': mocked_bn_nmodulus,
-                    }
-
-        with contextlib.ExitStack() as stack:
-            for method, patch in mock_load.items():
-                stack.enter_context(mocker.mock_module.patch.object(backend._lib, method, patch))
-            yield
-
-    return mocked_openssl_backend
diff --git a/tests/unit/test_primitives/test_curvebn_arithmetic.py b/tests/unit/test_primitives/test_curvebn_arithmetic.py
deleted file mode 100644
index 4f7e9474..00000000
--- a/tests/unit/test_primitives/test_curvebn_arithmetic.py
+++ /dev/null
@@ -1,64 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-from cryptography.hazmat.backends.openssl import backend
-from umbral.curvebn import CurveBN
-
-
-def test_mocked_openssl_curvebn_arithmetic(mock_openssl, random_ec_curvebn1, random_ec_curvebn2):
-
-    operations_that_construct = (
-        random_ec_curvebn1 * random_ec_curvebn2,           # __mul__
-        random_ec_curvebn1 ** random_ec_curvebn2,          # __pow__
-        random_ec_curvebn1 ** int(random_ec_curvebn2),     # __pow__ (as int)
-        random_ec_curvebn1 + random_ec_curvebn2,           # __add__
-        random_ec_curvebn1 - random_ec_curvebn2,           # __sub__
-        -random_ec_curvebn1,                               # __neg__
-        random_ec_curvebn1 % random_ec_curvebn2,           # __mod__
-        random_ec_curvebn1 % int(random_ec_curvebn2),      # __mod__ (as int)
-        ~random_ec_curvebn1,                               # __invert__
-        random_ec_curvebn1 / random_ec_curvebn2            # __truediv__
-    )
-
-    with mock_openssl():
-        assert random_ec_curvebn1 == random_ec_curvebn1    # __eq__
-        for operator_result in operations_that_construct:
-            assert operator_result
-            assert isinstance(operator_result, CurveBN)
-
-    order = backend._bn_to_int(random_ec_curvebn1.curve.order)
-    random_ec_curvebn1 = int(random_ec_curvebn1)
-    random_ec_curvebn2 = int(random_ec_curvebn2)
-
-    # For simplicity, we test these two cases separately 
-    assert (int(operations_that_construct[-2]) * random_ec_curvebn1) % order == 1
-    assert (int(operations_that_construct[-1]) * random_ec_curvebn2) % order == random_ec_curvebn1
-
-    # The remaining cases can be tested in bulk
-    expected_results = (
-        (random_ec_curvebn1 * random_ec_curvebn2) % order,     # __mul__
-        pow(random_ec_curvebn1, random_ec_curvebn2, order),    # __pow__
-        pow(random_ec_curvebn1, random_ec_curvebn2, order),    # __pow__ (as int)
-        (random_ec_curvebn1 + random_ec_curvebn2) % order,     # __add__
-        (random_ec_curvebn1 - random_ec_curvebn2) % order,     # __sub__
-        (-random_ec_curvebn1) % order,                         # __neg__
-        random_ec_curvebn1 % random_ec_curvebn2,               # __mod__
-        random_ec_curvebn1 % int(random_ec_curvebn2),          # __mod__ (as int)
-    )
-
-    for (result, expected) in zip(operations_that_construct[:-2], expected_results):
-        assert result == expected
diff --git a/tests/unit/test_primitives/test_curvebn_methods.py b/tests/unit/test_primitives/test_curvebn_methods.py
deleted file mode 100644
index a714dada..00000000
--- a/tests/unit/test_primitives/test_curvebn_methods.py
+++ /dev/null
@@ -1,39 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-from umbral.curvebn import CurveBN
-from umbral.random_oracles import hash_to_curvebn
-import pytest
-
-
-def test_cast_curvebn_to_int():
-    x = CurveBN.gen_rand()
-
-    x_as_int_from_dunder = x.__int__()
-    x_as_int_type_caster = int(x)
-    assert x_as_int_from_dunder == x_as_int_type_caster
-    x = x_as_int_type_caster
-
-    y = CurveBN.from_int(x)
-    assert x == y
-
-
-def test_cant_hash_arbitrary_object_into_bignum():
-    whatever = object()
-    with pytest.raises(TypeError):
-        hash_to_curvebn(whatever)
-
diff --git a/tests/unit/test_primitives/test_curvebn_serializers.py b/tests/unit/test_primitives/test_curvebn_serializers.py
deleted file mode 100644
index 9782846f..00000000
--- a/tests/unit/test_primitives/test_curvebn_serializers.py
+++ /dev/null
@@ -1,62 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-import pytest
-
-from umbral.curvebn import CurveBN
-from umbral.curve import CURVES
-from cryptography.hazmat.backends import default_backend
-
-@pytest.mark.parametrize("curve", CURVES)
-def test_serialization_rotations_of_1(curve):
-
-    size_in_bytes = CurveBN.expected_bytes_length(curve)
-    for i in range(size_in_bytes):
-        lonely_one = 1 << i
-        bn = CurveBN.from_int(lonely_one, curve)
-        lonely_one_in_bytes = lonely_one.to_bytes(size_in_bytes, 'big')
-
-        # Check serialization
-        assert bn.to_bytes() == lonely_one_in_bytes
-
-        # Check deserialization
-        assert CurveBN.from_bytes(lonely_one_in_bytes, curve) == bn
-
-@pytest.mark.parametrize("curve", CURVES)
-def test_invalid_deserialization(curve):
-	size_in_bytes = CurveBN.expected_bytes_length(curve)
-	
-	# All-zeros bytestring are invalid (i.e., 0 < bn < order of the curve)
-	zero_bytes = bytes(size_in_bytes)
-	with pytest.raises(ValueError):
-		_bn = CurveBN.from_bytes(zero_bytes, curve)
-
-	# All-ones bytestring is invalid too (since it's greater than order)
-	lots_of_ones = 2**(8*size_in_bytes) - 1
-	lots_of_ones = lots_of_ones.to_bytes(size_in_bytes, 'big')
-	with pytest.raises(ValueError):
-		_bn = CurveBN.from_bytes(lots_of_ones, curve)
-
-	# Serialization of `order` is invalid since it's not strictly lower than 
-	# the order of the curve
-	order = default_backend()._bn_to_int(curve.order)
-	with pytest.raises(ValueError):
-		_bn = CurveBN.from_bytes(order.to_bytes(size_in_bytes, 'big'), curve)
-
-	# On the other hand, serialization of `order - 1` is valid
-	order -= 1
-	_bn = CurveBN.from_bytes(order.to_bytes(size_in_bytes, 'big'), curve)
diff --git a/tests/unit/test_primitives/test_point_arithmetic.py b/tests/unit/test_primitives/test_point_arithmetic.py
deleted file mode 100644
index a8d02c81..00000000
--- a/tests/unit/test_primitives/test_point_arithmetic.py
+++ /dev/null
@@ -1,59 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-from umbral.curvebn import CurveBN
-from umbral.point import Point
-
-
-def test_mocked_openssl_point_arithmetic(mock_openssl, random_ec_point1, random_ec_point2, random_ec_curvebn1):
-
-    operations_that_construct = (
-        random_ec_point1 * random_ec_curvebn1, # __mul__
-        random_ec_point1 + random_ec_point2,   # __add__
-        random_ec_point1 - random_ec_point2,   # __sub__
-        -random_ec_point1                      # __neg__
-    )
-
-    with mock_openssl():
-        assert random_ec_point1 == random_ec_point1   # __eq__
-        for operator_result in operations_that_construct:
-            assert operator_result
-            assert isinstance(operator_result, Point)
-
-
-def test_point_curve_multiplication_regression():
-    k256_point_bytes = b'\x03\xe0{\x1bQ\xbf@\x1f\x95\x8d\xe1\x17\xa7\xbe\x9e-G`T\xbf\xd7\x9e\xa7\x10\xc8uA\xc0z$\xc0\x92\x8a'
-    k256_bn_bytes = b'4u\xd70-\xa0h\xdeG\xf0\x143\x06!\x91\x05{\xe4jC\n\xf1h\xed7a\xf8\x9d\xec^\x19\x8c'
-
-    k256_point = Point.from_bytes(k256_point_bytes)
-    k256_bn = CurveBN.from_bytes(k256_bn_bytes)
-
-    product_with_star_operator = k256_point * k256_bn
-
-    # Make sure we have instantiated a new, unequal point in the same curve and group
-    assert isinstance(product_with_star_operator, Point), "Point.__mul__ did not return a point instance"
-    assert k256_point != product_with_star_operator
-    assert k256_point.curve == product_with_star_operator.curve
-
-    product_bytes = b'\x03\xc9\xda\xa2\x88\xe2\xa0+\xb1N\xb6\xe6\x1c\xa5(\xe6\xe0p\xf6\xf4\xa9\xfc\xb1\xfaUV\xd3\xb3\x0e4\x94\xbe\x12'
-    product_point = Point.from_bytes(product_bytes)
-    assert product_with_star_operator.to_bytes() == product_bytes
-    assert product_point == product_with_star_operator
-
-    # Repeating the operation, should return the same result.
-    product_with_star_operator_again = k256_point * k256_bn
-    assert product_with_star_operator == product_with_star_operator_again
diff --git a/tests/unit/test_primitives/test_point_serializers.py b/tests/unit/test_primitives/test_point_serializers.py
deleted file mode 100644
index ec9e881e..00000000
--- a/tests/unit/test_primitives/test_point_serializers.py
+++ /dev/null
@@ -1,165 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-import pytest
-from cryptography.exceptions import InternalError
-
-from umbral.curve import SECP256K1, SECP256R1
-from umbral.point import Point
-
-
-def generate_test_points_bytes(quantity=2):
-    points_bytes = [
-        (SECP256K1, 714, b'\x02x{DR\x94\x8f\x17\xb8\xa2\x14t\x11\xdb\xb1VK\xdb\xc2\xa0T\x97iCK\x8cz~\xea\xa3\xb7AJ'),
-    ]
-    for _ in range(quantity):
-        args = (SECP256K1, 714, Point.gen_rand(curve=SECP256K1).to_bytes())
-        points_bytes.append(args)
-    return points_bytes
-
-
-def generate_test_points_affine(quantity=2):
-    points_affine = [
-        (SECP256K1, 714, (54495335564072000415434275044935054036617226655045445809732056033758606213450,
-                            26274482902044210718566767736429706729731617411738990314884135712590488065008)),
-    ]
-    for _ in range(quantity):
-        args = (SECP256K1, 714, Point.gen_rand(curve=SECP256K1).to_affine())
-        points_affine.append(args)
-    return points_affine
-
-
-def test_generate_random_points():
-    for _ in range(10):
-        point = Point.gen_rand()
-        another_point = Point.gen_rand()
-        assert isinstance(point, Point)
-        assert isinstance(another_point, Point)
-        assert point != another_point
-
-
-@pytest.mark.parametrize("curve, nid, point_bytes", generate_test_points_bytes())
-def test_bytes_serializers(point_bytes, nid, curve):
-    point_with_curve = Point.from_bytes(point_bytes, curve=curve) # from curve
-    assert isinstance(point_with_curve, Point)
-
-    the_same_point_bytes = point_with_curve.to_bytes()
-    assert point_bytes == the_same_point_bytes
-
-    representations = (point_bytes, # Compressed representation
-                       point_with_curve.to_bytes(is_compressed=False)) # Uncompressed
-
-    for point_representation in representations:
-
-        malformed_point_bytes = point_representation + b'0x'
-        with pytest.raises(InternalError):
-            _ = Point.from_bytes(malformed_point_bytes)
-
-        malformed_point_bytes = point_representation[1:]
-        with pytest.raises(InternalError):
-            _ = Point.from_bytes(malformed_point_bytes)
-
-        malformed_point_bytes = point_representation[:-1]
-        with pytest.raises(InternalError):
-            _ = Point.from_bytes(malformed_point_bytes)
-
-
-@pytest.mark.parametrize("curve, nid, point_affine", generate_test_points_affine())
-def test_affine(point_affine, nid, curve):
-    point = Point.from_affine(point_affine, curve=curve)  # from curve
-    assert isinstance(point, Point)
-    point_affine2 = point.to_affine()
-    assert point_affine == point_affine2
-
-
-def test_invalid_points(random_ec_point2):
-
-    point_bytes = bytearray(random_ec_point2.to_bytes(is_compressed=False))
-    point_bytes[-1] = point_bytes[-1] ^ 0x01        # Flips last bit
-    point_bytes = bytes(point_bytes)
-
-    with pytest.raises(InternalError) as e:
-        _point = Point.from_bytes(point_bytes)
-
-    # We want to catch specific InternalExceptions:
-    # - Point not in the curve (code 107)
-    # - Invalid compressed point (code 110)
-    # https://github.com/openssl/openssl/blob/master/include/openssl/ecerr.h#L228
-    assert e.value.err_code[0].reason in (107, 110)
-
-
-def test_generator_point():
-    """http://www.secg.org/SEC2-Ver-1.0.pdf Section 2.7.1"""
-    g1 = Point.get_generator_from_curve()
-
-    g_compressed = 0x0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
-    g_uncompressed = 0x0479BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
-
-    g_compressed = g_compressed.to_bytes(32+1, byteorder='big')
-    g_uncompressed = g_uncompressed.to_bytes(64+1, byteorder='big')
-
-    g2 = Point.from_bytes(g_compressed)
-    assert g1 == g2
-
-    g3 = Point.from_bytes(g_uncompressed)
-    assert g1 == g3
-    assert g2 == g3
-
-
-def test_point_not_on_curve():
-    """
-    We want to be unable to create a Point that's not on the curve.
-
-    When we try, we get cryptography.exceptions.InternalError - is that specifically because it isn't
-    on the curve?  It seems to be reliably raised in the event of the Point being off the curve.
-
-    The OpenSSL docs don't explicitly say that they raise an error for this reason:
-    https://www.openssl.org/docs/man1.1.0/crypto/EC_GFp_simple_method.html
-    """
-    point_on_koblitz256_but_not_P256 = Point.from_bytes(b'\x03%\x98Dk\x88\xe2\x97\xab?\xabZ\xef\xd4' \
-    b'\x9e\xaa\xc6\xb3\xa4\xa3\x89\xb2\xd7b.\x8f\x16Ci_&\xe0\x7f', curve=SECP256K1)
-
-    from cryptography.exceptions import InternalError
-    with pytest.raises(InternalError):
-        Point.from_bytes(point_on_koblitz256_but_not_P256.to_bytes(), curve=SECP256R1)
-
-
-def test_serialize_point_at_infinity():
-
-    p = Point.gen_rand()
-    point_at_infinity = p - p
-    
-    bytes_point_at_infinity = point_at_infinity.to_bytes()
-    assert bytes_point_at_infinity == b'\x00'
-
-
-def test_coords_with_special_characteristics():
-
-    # Testing that a point with x coordinate greater than the curve order is still valid.
-    # In particular, we will test the last valid point from the default curve (secp256k1)
-    # whose x coordinate is `field_order - 3` and is greater than the order of the curve
-
-    field_order = 2**256 - 0x1000003D1
-    compressed = b'\x02' + (field_order-3).to_bytes(32, 'big')
-
-    last_point = Point.from_bytes(compressed)
-
-    # The same point, but obtained through the from_affine method
-    coords = (115792089237316195423570985008687907853269984665640564039457584007908834671660, 
-        109188863561374057667848968960504138135859662956057034999983532397866404169138)
-
-    assert last_point == Point.from_affine(coords)
diff --git a/tests/unit/test_serialization_property_based.py b/tests/unit/test_serialization_property_based.py
deleted file mode 100644
index b203d82d..00000000
--- a/tests/unit/test_serialization_property_based.py
+++ /dev/null
@@ -1,115 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-from cryptography.hazmat.backends.openssl import backend
-from hypothesis import HealthCheck, given, settings, unlimited
-from hypothesis.strategies import binary, booleans, integers, tuples
-from umbral.config import default_curve
-from umbral.curvebn import CurveBN
-from umbral.cfrags import CorrectnessProof
-from umbral.kfrags import KFrag
-from umbral.keys import UmbralPrivateKey, UmbralPublicKey
-from umbral.params import UmbralParameters
-from umbral.point import Point
-from umbral.random_oracles import unsafe_hash_to_point
-from umbral.pre import Capsule
-
-# test parameters
-max_examples = 1000
-
-# crypto constants
-curve = default_curve()
-params = UmbralParameters(curve)
-bn_size = curve.group_order_size_in_bytes
-
-# generators
-bns = integers(min_value=1, max_value=backend._bn_to_int(curve.order)).map(
-    lambda x: CurveBN.from_int(x))
-
-points = binary(min_size=1).map(
-    lambda x: unsafe_hash_to_point(x, label=b'hypothesis', params=params))
-
-signatures = tuples(integers(min_value=1, max_value=backend._bn_to_int(curve.order)),
-                 integers(min_value=1, max_value=backend._bn_to_int(curve.order))).map(
-                    lambda tup: tup[0].to_bytes(bn_size, 'big') + tup[1].to_bytes(bn_size, 'big'))
-
-# # utility
-def assert_kfrag_eq(k0, k1):
-    assert(all([ k0.id                  == k1.id
-               , k0.bn_key              == k1.bn_key
-               , k0.point_precursor     == k1.point_precursor
-               , k0.point_commitment    == k1.point_commitment
-               , k0.signature_for_bob   == k1.signature_for_bob
-               , k0.signature_for_proxy == k1.signature_for_proxy
-               ]))
-
-def assert_cp_eq(c0, c1):
-    assert(all([ c0.point_e2               == c1.point_e2
-               , c0.point_v2               == c1.point_v2
-               , c0.point_kfrag_commitment == c1.point_kfrag_commitment
-               , c0.point_kfrag_pok        == c1.point_kfrag_pok
-               , c0.kfrag_signature        == c1.kfrag_signature
-               , c0.bn_sig                 == c1.bn_sig
-               , c0.metadata               == c1.metadata
-               ]))
-  
-# tests
-
-@given(bns)
-@settings(max_examples=max_examples, timeout=unlimited)
-def test_bn_roundtrip(bn):
-    assert(bn == CurveBN.from_bytes(bn.to_bytes()))
-
-@given(points, booleans())
-@settings(max_examples=max_examples, timeout=unlimited)
-def test_point_roundtrip(p, c):
-    assert(p == Point.from_bytes(p.to_bytes(is_compressed=c)))
-
-@given(binary(min_size=bn_size, max_size=bn_size), bns, points, points, signatures, signatures)
-@settings(max_examples=max_examples, timeout=unlimited)
-def test_kfrag_roundtrip(d, b0, p0, p1, sig_proxy, sig_bob):
-    k = KFrag(identifier=d, bn_key=b0, point_commitment=p0, point_precursor=p1,
-              signature_for_proxy=sig_proxy, signature_for_bob=sig_bob)
-    assert_kfrag_eq(k, KFrag.from_bytes(k.to_bytes()))
-
-@given(points, points, bns)
-@settings(max_examples=max_examples, timeout=unlimited)
-def test_capsule_roundtrip_0(p0, p1, b):
-    c = Capsule(params=params, point_e=p0, point_v=p1, bn_sig=b)
-    assert(c == Capsule.from_bytes(c.to_bytes(), params=params))
-
-@given(points, points, points, points, bns, signatures)
-@settings(max_examples=max_examples, timeout=unlimited)
-def test_cp_roundtrip(p0, p1, p2, p3, b0, sig):
-    c = CorrectnessProof(p0, p1, p2, p3, b0, sig)
-    assert_cp_eq(c, CorrectnessProof.from_bytes(c.to_bytes()))
-
-@given(points)
-@settings(max_examples=max_examples, timeout=unlimited)
-def test_pubkey_roundtrip(p):
-    k = UmbralPublicKey(p, params)
-    assert(k == UmbralPublicKey.from_bytes(k.to_bytes(), params=params))
-
-@given(binary(min_size=1))
-@settings(max_examples=20, timeout=unlimited, suppress_health_check=[HealthCheck.hung_test])
-def test_privkey_roundtrip(p):
-    insecure_scrypt_cost = 5   # This is deliberately insecure, just to make it faster
-    k = UmbralPrivateKey.gen_key()
-    rt = UmbralPrivateKey.from_bytes(k.to_bytes(password=p, _scrypt_cost=insecure_scrypt_cost), 
-                                     password=p, 
-                                     _scrypt_cost=insecure_scrypt_cost)
-    assert(k.get_pubkey() == rt.get_pubkey())
\ No newline at end of file
diff --git a/tests/unit/test_signing.py b/tests/unit/test_signing.py
deleted file mode 100644
index 3de31465..00000000
--- a/tests/unit/test_signing.py
+++ /dev/null
@@ -1,62 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-import pytest
-from cryptography.hazmat.backends.openssl import backend
-from cryptography.hazmat.primitives import hashes
-from umbral.keys import UmbralPrivateKey
-from umbral.signing import Signer, Signature, DEFAULT_HASH_ALGORITHM
-
-
-@pytest.mark.parametrize('execution_number', range(20))  # Run this test 20 times.
-def test_sign_and_verify(execution_number):
-    privkey = UmbralPrivateKey.gen_key()
-    pubkey = privkey.get_pubkey()
-    signer = Signer(private_key=privkey)
-    message = b"peace at dawn"
-    signature = signer(message=message)
-
-    # Basic signature verification
-    assert signature.verify(message, pubkey)
-    assert not signature.verify(b"another message", pubkey)
-    another_pubkey = UmbralPrivateKey.gen_key().pubkey
-    assert not signature.verify(message, another_pubkey)
-
-    # Signature serialization
-    sig_bytes = bytes(signature)
-    assert len(sig_bytes) == Signature.expected_bytes_length()
-    restored_signature = Signature.from_bytes(sig_bytes)
-    assert restored_signature == signature
-    assert restored_signature.verify(message, pubkey)
-
-
-@pytest.mark.parametrize('execution_number', range(20))  # Run this test 20 times.
-def test_prehashed_message(execution_number):
-    privkey = UmbralPrivateKey.gen_key()
-    pubkey = privkey.get_pubkey()
-    signer = Signer(private_key=privkey)
-
-    message = b"peace at dawn"
-    hash_function = hashes.Hash(DEFAULT_HASH_ALGORITHM(), backend=backend)
-    hash_function.update(message)
-    prehashed_message = hash_function.finalize()
-
-    signature = signer(message=prehashed_message, is_prehashed=True)
-
-    assert signature.verify(message=prehashed_message,
-                            verifying_key=pubkey,
-                            is_prehashed=True)
diff --git a/tests/unit/test_umbral_keys.py b/tests/unit/test_umbral_keys.py
deleted file mode 100644
index 46b092b5..00000000
--- a/tests/unit/test_umbral_keys.py
+++ /dev/null
@@ -1,223 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-import base64
-import os
-import pytest
-import string
-
-from umbral.config import default_params
-from umbral.keys import UmbralPublicKey, UmbralPrivateKey, UmbralKeyingMaterial
-from umbral.point import Point
-
-
-def test_gen_key():
-    # Pass in the parameters to test that manual param selection works
-    umbral_priv_key = UmbralPrivateKey.gen_key()
-    assert type(umbral_priv_key) == UmbralPrivateKey
-
-    umbral_pub_key = umbral_priv_key.get_pubkey()
-    assert type(umbral_pub_key) == UmbralPublicKey
-
-
-def test_derive_key_from_label():
-    umbral_keying_material = UmbralKeyingMaterial()
-
-    label = b"my_healthcare_information"
-
-    priv_key1 = umbral_keying_material.derive_privkey_by_label(label)
-    assert type(priv_key1) == UmbralPrivateKey
-
-    pub_key1 = priv_key1.get_pubkey()
-    assert type(pub_key1) == UmbralPublicKey
-
-    # Check that key derivation is reproducible
-    priv_key2 = umbral_keying_material.derive_privkey_by_label(label)
-    pub_key2 = priv_key2.get_pubkey()
-    assert priv_key1.bn_key == priv_key2.bn_key
-    assert pub_key1 == pub_key2
-
-    # A salt can be used too, but of course it affects the derived key
-    salt = b"optional, randomly generated salt"
-    priv_key3 = umbral_keying_material.derive_privkey_by_label(label, salt=salt)
-    assert priv_key3.bn_key != priv_key1.bn_key
-
-    # Different labels on the same master secret create different keys
-    label = b"my_tax_information"
-    priv_key4 = umbral_keying_material.derive_privkey_by_label(label)
-    pub_key4 = priv_key4.get_pubkey()
-    assert priv_key1.bn_key != priv_key4.bn_key
-
-
-def test_private_key_serialization(random_ec_curvebn1):
-    priv_key = random_ec_curvebn1
-    umbral_key = UmbralPrivateKey(priv_key, default_params())
-
-    encoded_key = umbral_key.to_bytes()
-
-    decoded_key = UmbralPrivateKey.from_bytes(encoded_key)
-    assert priv_key == decoded_key.bn_key
-
-
-def test_private_key_serialization_with_encryption(random_ec_curvebn1):
-    priv_key = random_ec_curvebn1
-    umbral_key = UmbralPrivateKey(priv_key, default_params())
-
-    insecure_cost = 15  # This is deliberately insecure, just to make the tests faster
-    encoded_key = umbral_key.to_bytes(password=b'test', 
-                                      _scrypt_cost=insecure_cost)
-
-    decoded_key = UmbralPrivateKey.from_bytes(encoded_key, 
-                                              password=b'test', 
-                                              _scrypt_cost=insecure_cost)
-    assert priv_key == decoded_key.bn_key
-
-
-def test_public_key_serialization(random_ec_curvebn1):
-    umbral_key = UmbralPrivateKey(random_ec_curvebn1, default_params()).get_pubkey()
-    pub_point = umbral_key.point_key
-
-    encoded_key = umbral_key.to_bytes()
-
-    decoded_key = UmbralPublicKey.from_bytes(encoded_key)
-    assert pub_point == decoded_key.point_key
-
-
-def test_public_key_to_compressed_bytes(random_ec_curvebn1):
-    umbral_key = UmbralPrivateKey(random_ec_curvebn1, default_params()).get_pubkey()
-    key_bytes = bytes(umbral_key)
-    assert len(key_bytes) == Point.expected_bytes_length(is_compressed=True)
-
-
-def test_public_key_to_uncompressed_bytes(random_ec_curvebn1):
-    umbral_key = UmbralPrivateKey(random_ec_curvebn1, default_params()).get_pubkey()
-    key_bytes = umbral_key.to_bytes(is_compressed=False)
-    assert len(key_bytes) == Point.expected_bytes_length(is_compressed=False)
-
-
-def test_key_encoder_decoder(random_ec_curvebn1):
-    priv_key = random_ec_curvebn1
-    umbral_key = UmbralPrivateKey(priv_key, default_params())
-
-    encoded_key = umbral_key.to_bytes(encoder=base64.urlsafe_b64encode)
-
-    decoded_key = UmbralPrivateKey.from_bytes(encoded_key,
-                                              decoder=base64.urlsafe_b64decode)
-    assert decoded_key.to_bytes() == umbral_key.to_bytes()
-
-
-def test_public_key_as_hex(random_ec_curvebn1):
-    pubkey = UmbralPrivateKey(random_ec_curvebn1, default_params()).get_pubkey()
-    hex_string = pubkey.hex()
-
-    assert set(hex_string).issubset(set(string.hexdigits))
-    assert len(hex_string) == 2 * UmbralPublicKey.expected_bytes_length()
-
-    decoded_pubkey = UmbralPublicKey.from_hex(hex_string)
-
-    assert pubkey == decoded_pubkey
-
-    hex_string = pubkey.hex(is_compressed=False)
-
-    assert set(hex_string).issubset(set(string.hexdigits))
-    assert len(hex_string) == 2 * UmbralPublicKey.expected_bytes_length(is_compressed=False)
-
-    decoded_pubkey = UmbralPublicKey.from_hex(hex_string)
-    assert pubkey == decoded_pubkey
-
-
-def test_umbral_key_to_cryptography_keys():
-    umbral_priv_key = UmbralPrivateKey.gen_key()
-    umbral_pub_key = umbral_priv_key.get_pubkey()
-
-    crypto_privkey = umbral_priv_key.to_cryptography_privkey()
-    assert int(umbral_priv_key.bn_key) == crypto_privkey.private_numbers().private_value
-
-    crypto_pubkey = umbral_pub_key.to_cryptography_pubkey()
-    umbral_affine = umbral_pub_key.point_key.to_affine()
-    x, y = crypto_pubkey.public_numbers().x, crypto_pubkey.public_numbers().y
-    assert umbral_affine == (x, y)
-
-
-def test_keying_material_serialization():
-    umbral_keying_material = UmbralKeyingMaterial()
-
-    encoded_keying_material = umbral_keying_material.to_bytes()
-
-    decoded_keying_material = UmbralKeyingMaterial.from_bytes(encoded_keying_material)
-
-    label = os.urandom(32)
-    privkey_bytes = umbral_keying_material.derive_privkey_by_label(label).to_bytes()
-    assert privkey_bytes == decoded_keying_material.derive_privkey_by_label(label).to_bytes()
-
-
-def test_keying_material_serialization_with_encryption():
-    umbral_keying_material = UmbralKeyingMaterial()
-
-    insecure_cost = 15  # This is deliberately insecure, just to make the tests faster
-    encoded_keying_material = umbral_keying_material.to_bytes(password=b'test',
-                                                              _scrypt_cost=insecure_cost)
-
-    decoded_keying_material = UmbralKeyingMaterial.from_bytes(encoded_keying_material,
-                                                              password=b'test',
-                                                              _scrypt_cost=insecure_cost)
-
-    label = os.urandom(32)
-    privkey_bytes = umbral_keying_material.derive_privkey_by_label(label).to_bytes()
-    assert privkey_bytes == decoded_keying_material.derive_privkey_by_label(label).to_bytes()
-
-
-def test_umbral_public_key_equality():
-    umbral_priv_key = UmbralPrivateKey.gen_key()
-    umbral_pub_key = umbral_priv_key.get_pubkey()
-
-    as_bytes = bytes(umbral_pub_key)
-    assert umbral_pub_key == as_bytes
-
-    reconstructed = UmbralPublicKey.from_bytes(as_bytes)
-    assert reconstructed == umbral_pub_key
-
-    assert not umbral_pub_key == b"some whatever bytes"
-
-    another_umbral_priv_key = UmbralPrivateKey.gen_key()
-    another_umbral_pub_key = another_umbral_priv_key.get_pubkey()
-
-    assert not umbral_pub_key == another_umbral_pub_key
-
-    # Also not equal to a totally disparate type.
-    assert not umbral_pub_key == 107
-
-
-def test_umbral_public_key_as_dict_key():
-    umbral_priv_key = UmbralPrivateKey.gen_key()
-    umbral_pub_key = umbral_priv_key.get_pubkey()
-
-    d = {umbral_pub_key: 19}
-    assert d[umbral_pub_key] == 19
-
-    another_umbral_priv_key = UmbralPrivateKey.gen_key()
-    another_umbral_pub_key = another_umbral_priv_key.get_pubkey()
-
-    with pytest.raises(KeyError):
-        _ = d[another_umbral_pub_key]
-
-    d[another_umbral_pub_key] = False
-
-    assert d[umbral_pub_key] == 19
-    d[umbral_pub_key] = 20
-    assert d[umbral_pub_key] == 20
-    assert d[another_umbral_pub_key] is False
diff --git a/umbral/__about__.py b/umbral/__about__.py
index 66448cd9..7615ffe1 100644
--- a/umbral/__about__.py
+++ b/umbral/__about__.py
@@ -1,20 +1,3 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published b
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
 from __future__ import absolute_import, division, print_function
 
 __all__ = [
diff --git a/umbral/__init__.py b/umbral/__init__.py
index 97d7c652..38c55f4c 100644
--- a/umbral/__init__.py
+++ b/umbral/__init__.py
@@ -1,25 +1,33 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published b
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-from umbral.__about__ import (
+from .__about__ import (
     __author__,  __license__, __summary__, __title__, __version__, __copyright__, __email__, __url__
 )
 
+from .capsule import Capsule
+from .capsule_frag import CapsuleFrag
+from .errors import GenericError
+from .key_frag import KeyFrag, generate_kfrags
+from .keys import SecretKey, PublicKey, SecretKeyFactory
+from .pre import encrypt, decrypt_original, decrypt_reencrypted, reencrypt
 
 __all__ = [
-    "__title__", "__summary__", "__version__", "__author__", "__license__", "__copyright__", "__email__", "__url__"
+    "__title__",
+    "__summary__",
+    "__version__",
+    "__author__",
+    "__license__",
+    "__copyright__",
+    "__email__",
+    "__url__",
+    "SecretKey",
+    "PublicKey",
+    "SecretKeyFactory",
+    "Capsule",
+    "KeyFrag",
+    "CapsuleFrag",
+    "GenericError",
+    "encrypt",
+    "decrypt_original",
+    "generate_kfrags",
+    "reencrypt",
+    "decrypt_reencrypted",
 ]
diff --git a/umbral/capsule.py b/umbral/capsule.py
new file mode 100644
index 00000000..8af488b1
--- /dev/null
+++ b/umbral/capsule.py
@@ -0,0 +1,130 @@
+from typing import TYPE_CHECKING, Tuple, Sequence
+
+from .curve_point import CurvePoint
+from .curve_scalar import CurveScalar
+from .errors import GenericError
+from .hashing import hash_capsule_points, hash_to_polynomial_arg, hash_to_shared_secret
+from .keys import PublicKey, SecretKey
+from .params import PARAMETERS
+from .serializable import Serializable
+if TYPE_CHECKING: # pragma: no cover
+    from .capsule_frag import CapsuleFrag
+
+
+def lambda_coeff(xs: Sequence[CurveScalar], i: int) -> CurveScalar:
+    res = CurveScalar.one()
+    for j in range(len(xs)):
+        if j != i:
+            inv_diff = (xs[j] - xs[i]).invert()
+            res = (res * xs[j]) * inv_diff
+    return res
+
+
+class Capsule(Serializable):
+    """
+    Encapsulated symmetric key.
+    """
+
+    def __init__(self, point_e: CurvePoint, point_v: CurvePoint, signature: CurveScalar):
+        self.point_e = point_e
+        self.point_v = point_v
+        self.signature = signature
+
+    @classmethod
+    def __take__(cls, data: bytes) -> Tuple['Capsule', bytes]:
+        (e, v, sig), data = cls.__take_types__(data, CurvePoint, CurvePoint, CurveScalar)
+
+        capsule = cls(e, v, sig)
+        if not capsule._verify():
+            raise GenericError("Capsule self-verification failed. Serialized data may be damaged.")
+
+        return capsule, data
+
+    def __bytes__(self) -> bytes:
+        return bytes(self.point_e) + bytes(self.point_v) + bytes(self.signature)
+
+    @classmethod
+    def from_public_key(cls, pk: PublicKey) -> Tuple['Capsule', CurvePoint]:
+        g = CurvePoint.generator()
+
+        priv_r = CurveScalar.random_nonzero()
+        pub_r = g * priv_r
+
+        priv_u = CurveScalar.random_nonzero()
+        pub_u = g * priv_u
+
+        h = hash_capsule_points(pub_r, pub_u)
+        s = priv_u + (priv_r * h)
+
+        shared_key = pk._point_key * (priv_r + priv_u)
+
+        return cls(point_e=pub_r, point_v=pub_u, signature=s), shared_key
+
+    def open_original(self, sk: SecretKey) -> CurvePoint:
+        return (self.point_e + self.point_v) * sk.secret_scalar()
+
+    def open_reencrypted(self,
+                         receiving_sk: SecretKey,
+                         delegating_pk: PublicKey,
+                         cfrags: Sequence['CapsuleFrag'],
+                         ) -> CurvePoint:
+
+        if len(cfrags) == 0:
+            raise ValueError("Empty CapsuleFrag sequence")
+
+        precursor = cfrags[0].precursor
+
+        if len(set(cfrags)) != len(cfrags):
+            raise ValueError("Some of the CapsuleFrags are repeated")
+
+        if not all(cfrag.precursor == precursor for cfrag in cfrags[1:]):
+            raise ValueError("CapsuleFrags are not pairwise consistent")
+
+        pub_key = PublicKey.from_secret_key(receiving_sk).point()
+        dh_point = precursor * receiving_sk.secret_scalar()
+
+        # Combination of CFrags via Shamir's Secret Sharing reconstruction
+        lc = [hash_to_polynomial_arg(precursor, pub_key, dh_point, cfrag.kfrag_id)
+              for cfrag in cfrags]
+
+        e_primes = []
+        v_primes = []
+        for i, cfrag in enumerate(cfrags):
+            lambda_i = lambda_coeff(lc, i)
+            e_primes.append(cfrag.point_e1 * lambda_i)
+            v_primes.append(cfrag.point_v1 * lambda_i)
+        e_prime = sum(e_primes[1:], e_primes[0])
+        v_prime = sum(v_primes[1:], v_primes[0])
+
+        # Secret value 'd' allows to make Umbral non-interactive
+        d = hash_to_shared_secret(precursor, pub_key, dh_point)
+
+        s = self.signature
+        h = hash_capsule_points(self.point_e, self.point_v)
+
+        orig_pub_key = delegating_pk.point()
+
+        # TODO: check for d == 0? Or just let if fail?
+        inv_d = d.invert()
+        if orig_pub_key * (s * inv_d) != (e_prime * h) + v_prime:
+            raise GenericError("Internal validation failed")
+
+        return (e_prime + v_prime) * d
+
+    def _components(self):
+        return (self.point_e, self.point_v, self.signature)
+
+    def _verify(self) -> bool:
+        g = CurvePoint.generator()
+        e, v, s = self._components()
+        h = hash_capsule_points(e, v)
+        return g * s == v + (e * h)
+
+    def __eq__(self, other):
+        return self._components() == other._components()
+
+    def __hash__(self):
+        return hash((self.__class__, bytes(self)))
+
+    def __str__(self):
+        return f"{self.__class__.__name__}:{bytes(self).hex()[:16]}"
diff --git a/umbral/capsule_frag.py b/umbral/capsule_frag.py
new file mode 100644
index 00000000..b1d48f47
--- /dev/null
+++ b/umbral/capsule_frag.py
@@ -0,0 +1,208 @@
+from typing import Sequence, Optional
+
+from .capsule import Capsule
+from .curve_point import CurvePoint
+from .curve_scalar import CurveScalar
+from .hashing import Hash, hash_to_cfrag_verification, hash_to_cfrag_signature
+from .keys import PublicKey, SecretKey, Signature
+from .key_frag import KeyFrag, KeyFragID
+from .params import PARAMETERS
+from .serializable import Serializable
+
+
+class CapsuleFragProof(Serializable):
+
+    def __init__(self,
+                 point_e2: CurvePoint,
+                 point_v2: CurvePoint,
+                 kfrag_commitment: CurvePoint,
+                 kfrag_pok: CurvePoint,
+                 signature: CurveScalar,
+                 kfrag_signature: Signature,
+                 ):
+
+        self.point_e2 = point_e2
+        self.point_v2 = point_v2
+        self.kfrag_commitment = kfrag_commitment
+        self.kfrag_pok = kfrag_pok
+        self.signature = signature
+        self.kfrag_signature = kfrag_signature
+
+    def _components(self):
+        return (self.point_e2, self.point_v2, self.kfrag_commitment,
+                self.kfrag_pok, self.signature, self.kfrag_signature)
+
+    def __eq__(self, other):
+        return self._components() == other._components()
+
+    @classmethod
+    def __take__(cls, data):
+        types = [CurvePoint, CurvePoint, CurvePoint, CurvePoint, CurveScalar, Signature]
+        components, data = cls.__take_types__(data, *types)
+        return cls(*components), data
+
+    def __bytes__(self):
+        return (bytes(self.point_e2) +
+                bytes(self.point_v2) +
+                bytes(self.kfrag_commitment) +
+                bytes(self.kfrag_pok) +
+                bytes(self.signature) +
+                bytes(self.kfrag_signature)
+                )
+
+    @classmethod
+    def from_kfrag_and_cfrag(cls,
+                             capsule: Capsule,
+                             kfrag: KeyFrag,
+                             cfrag_e1: CurvePoint,
+                             cfrag_v1: CurvePoint,
+                             metadata: Optional[bytes],
+                             ) -> 'CapsuleFragProof':
+
+        params = PARAMETERS
+
+        rk = kfrag.key
+        t = CurveScalar.random_nonzero()
+
+        # Here are the formulaic constituents shared with `CapsuleFrag.verify()`.
+
+        e = capsule.point_e
+        v = capsule.point_v
+
+        e1 = cfrag_e1
+        v1 = cfrag_v1
+
+        u = params.u
+        u1 = kfrag.proof.commitment
+
+        e2 = e * t
+        v2 = v * t
+        u2 = u * t
+
+        h = hash_to_cfrag_verification([e, e1, e2, v, v1, v2, u, u1, u2], metadata)
+
+        ###
+
+        z3 = t + rk * h
+
+        return cls(point_e2=e2,
+                   point_v2=v2,
+                   kfrag_commitment=u1,
+                   kfrag_pok=u2,
+                   signature=z3,
+                   kfrag_signature=kfrag.proof.signature_for_receiver,
+                   )
+
+
+class CapsuleFrag(Serializable):
+    """
+    Re-encrypted fragment of :py:class:`Capsule`.
+    """
+
+    def __init__(self,
+                 point_e1: CurvePoint,
+                 point_v1: CurvePoint,
+                 kfrag_id: KeyFragID,
+                 precursor: CurvePoint,
+                 proof: CapsuleFragProof,
+                 ):
+
+        self.point_e1 = point_e1
+        self.point_v1 = point_v1
+        self.kfrag_id = kfrag_id
+        self.precursor = precursor
+        self.proof = proof
+
+    def _components(self):
+        return (self.point_e1, self.point_v1, self.kfrag_id, self.precursor, self.proof)
+
+    def __eq__(self, other):
+        return self._components() == other._components()
+
+    def __hash__(self):
+        return hash((self.__class__, bytes(self)))
+
+    def __str__(self):
+        return f"{self.__class__.__name__}:{bytes(self).hex()[:16]}"
+
+    @classmethod
+    def __take__(cls, data):
+        types = CurvePoint, CurvePoint, KeyFragID, CurvePoint, CapsuleFragProof
+        components, data = cls.__take_types__(data, *types)
+        return cls(*components), data
+
+    def __bytes__(self):
+        return (bytes(self.point_e1) +
+                bytes(self.point_v1) +
+                bytes(self.kfrag_id) +
+                bytes(self.precursor) +
+                bytes(self.proof))
+
+    @classmethod
+    def reencrypted(cls,
+                    capsule: Capsule,
+                    kfrag: KeyFrag,
+                    metadata: Optional[bytes] = None,
+                    ) -> 'CapsuleFrag':
+        rk = kfrag.key
+        e1 = capsule.point_e * rk
+        v1 = capsule.point_v * rk
+        proof = CapsuleFragProof.from_kfrag_and_cfrag(capsule, kfrag, e1, v1, metadata)
+
+        return cls(point_e1=e1,
+                   point_v1=v1,
+                   kfrag_id=kfrag.id,
+                   precursor=kfrag.precursor,
+                   proof=proof,
+                   )
+
+    def verify(self,
+               capsule: Capsule,
+               delegating_pk: PublicKey,
+               receiving_pk: PublicKey,
+               signing_pk: PublicKey,
+               metadata: Optional[bytes] = None,
+               ) -> bool:
+        """
+        Verifies the validity of this fragment.
+
+        ``metadata`` should coincide with the one given to :py:func:`reencrypt`.
+        """
+
+        params = PARAMETERS
+
+        # Here are the formulaic constituents shared with
+        # `CapsuleFragProof.from_kfrag_and_cfrag`.
+
+        e = capsule.point_e
+        v = capsule.point_v
+
+        e1 = self.point_e1
+        v1 = self.point_v1
+
+        u = params.u
+        u1 = self.proof.kfrag_commitment
+
+        e2 = self.proof.point_e2
+        v2 = self.proof.point_v2
+        u2 = self.proof.kfrag_pok
+
+        h = hash_to_cfrag_verification([e, e1, e2, v, v1, v2, u, u1, u2], metadata)
+
+        ###
+
+        precursor = self.precursor
+        kfrag_id = self.kfrag_id
+
+        kfrag_signature = hash_to_cfrag_signature(kfrag_id, u1, precursor, delegating_pk, receiving_pk)
+        valid_kfrag_signature = kfrag_signature.verify(signing_pk, self.proof.kfrag_signature)
+
+        z3 = self.proof.signature
+        correct_reencryption_of_e = e * z3 == e2 + e1 * h
+        correct_reencryption_of_v = v * z3 == v2 + v1 * h
+        correct_rk_commitment = u * z3 == u2 + u1 * h
+
+        return (valid_kfrag_signature
+                and correct_reencryption_of_e
+                and correct_reencryption_of_v
+                and correct_rk_commitment)
diff --git a/umbral/cfrags.py b/umbral/cfrags.py
deleted file mode 100644
index 9679bdb5..00000000
--- a/umbral/cfrags.py
+++ /dev/null
@@ -1,287 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-from typing import Optional, Any
-
-from bytestring_splitter import BytestringSplitter
-
-from umbral.config import default_curve
-from umbral.curvebn import CurveBN
-from umbral.point import Point
-from umbral.signing import Signature
-from umbral.curve import Curve
-from umbral.random_oracles import hash_to_curvebn, ExtendedKeccak
-
-
-class CorrectnessProof:
-    def __init__(self, point_e2: Point, point_v2: Point, point_kfrag_commitment: Point,
-                 point_kfrag_pok: Point, bn_sig: CurveBN, kfrag_signature: Signature,
-                 metadata: Optional[bytes] = None) -> None:
-        self.point_e2 = point_e2
-        self.point_v2 = point_v2
-        self.point_kfrag_commitment = point_kfrag_commitment
-        self.point_kfrag_pok = point_kfrag_pok
-        self.bn_sig = bn_sig
-        self.metadata = metadata
-        self.kfrag_signature = kfrag_signature
-
-    @classmethod
-    def expected_bytes_length(cls, curve: Optional[Curve] = None):
-        """
-        Returns the size (in bytes) of a CorrectnessProof without the metadata.
-        If no curve is given, it will use the default curve.
-        """
-        curve = curve if curve is not None else default_curve()
-        bn_size = CurveBN.expected_bytes_length(curve=curve)
-        point_size = Point.expected_bytes_length(curve=curve)
-
-        return (bn_size * 3) + (point_size * 4)
-
-    @classmethod
-    def from_bytes(cls, data: bytes, curve: Optional[Curve] = None) -> 'CorrectnessProof':
-        """
-        Instantiate CorrectnessProof from serialized data.
-        """
-        curve = curve if curve is not None else default_curve()
-        bn_size = CurveBN.expected_bytes_length(curve)
-        point_size = Point.expected_bytes_length(curve)
-        arguments = {'curve': curve}
-        splitter = BytestringSplitter(
-            (Point, point_size, arguments),  # point_e2
-            (Point, point_size, arguments),  # point_v2
-            (Point, point_size, arguments),  # point_kfrag_commitment
-            (Point, point_size, arguments),  # point_kfrag_pok
-            (CurveBN, bn_size, arguments),  # bn_sig
-            (Signature, Signature.expected_bytes_length(curve), arguments),  # kfrag_signature
-        )
-        components = splitter(data, return_remainder=True)
-        components.append(components.pop() or None)
-
-        return cls(*components)
-
-    def to_bytes(self) -> bytes:
-        """
-        Serialize the CorrectnessProof to a bytestring.
-        """
-        e2 = self.point_e2.to_bytes()
-        v2 = self.point_v2.to_bytes()
-        kfrag_commitment = self.point_kfrag_commitment.to_bytes()
-        kfrag_pok = self.point_kfrag_pok.to_bytes()
-
-        result = e2 \
-                 + v2 \
-                 + kfrag_commitment \
-                 + kfrag_pok \
-                 + self.bn_sig.to_bytes() \
-                 + self.kfrag_signature
-
-        result += self.metadata or b''
-
-        return result
-
-    def __bytes__(self):
-        return self.to_bytes()
-
-
-class CapsuleFrag:
-    def __init__(self,
-                 point_e1: Point,
-                 point_v1: Point,
-                 kfrag_id: bytes,
-                 point_precursor: Point,
-                 proof: Optional[CorrectnessProof] = None) -> None:
-        self.point_e1 = point_e1
-        self.point_v1 = point_v1
-        self.kfrag_id = kfrag_id
-        self.point_precursor = point_precursor
-        self.proof = proof
-
-    class NoProofProvided(TypeError):
-        """
-        Raised when a cfrag is assessed for correctness, but no proof is attached.
-        """
-
-    @classmethod
-    def expected_bytes_length(cls, curve: Optional[Curve] = None) -> int:
-        """
-        Returns the size (in bytes) of a CapsuleFrag given the curve without
-        the CorrectnessProof.
-        If no curve is provided, it will use the default curve.
-        """
-        curve = curve if curve is not None else default_curve()
-        bn_size = CurveBN.expected_bytes_length(curve)
-        point_size = Point.expected_bytes_length(curve)
-
-        return (bn_size * 1) + (point_size * 3)
-
-    @classmethod
-    def from_bytes(cls, data: bytes, curve: Optional[Curve] = None) -> 'CapsuleFrag':
-        """
-        Instantiates a CapsuleFrag object from the serialized data.
-        """
-        curve = curve if curve is not None else default_curve()
-
-        bn_size = CurveBN.expected_bytes_length(curve)
-        point_size = Point.expected_bytes_length(curve)
-        arguments = {'curve': curve}
-
-        splitter = BytestringSplitter(
-            (Point, point_size, arguments),  # point_e1
-            (Point, point_size, arguments),  # point_v1
-            bn_size,  # kfrag_id
-            (Point, point_size, arguments),  # point_precursor
-        )
-        components = splitter(data, return_remainder=True)
-
-        proof = components.pop() or None
-        components.append(CorrectnessProof.from_bytes(proof, curve) if proof else None)
-
-        return cls(*components)
-
-    def to_bytes(self) -> bytes:
-        """
-        Serialize the CapsuleFrag into a bytestring.
-        """
-        e1 = self.point_e1.to_bytes()
-        v1 = self.point_v1.to_bytes()
-        precursor = self.point_precursor.to_bytes()
-
-        serialized_cfrag = e1 + v1 + self.kfrag_id + precursor
-
-        if self.proof is not None:
-            serialized_cfrag += self.proof.to_bytes()
-
-        return serialized_cfrag
-
-    def prove_correctness(self,
-                          capsule,
-                          kfrag,
-                          metadata: Optional[bytes] = None):
-
-        params = capsule.params
-
-        # Check correctness of original ciphertext
-        if not capsule.verify():
-            raise capsule.NotValid("Capsule verification failed.")
-
-        rk = kfrag.bn_key
-        t = CurveBN.gen_rand(params.curve)
-        ####
-        # Here are the formulaic constituents shared with `verify_correctness`.
-        ####
-        e = capsule.point_e
-        v = capsule.point_v
-
-        e1 = self.point_e1
-        v1 = self.point_v1
-
-        u = params.u
-        u1 = kfrag.point_commitment
-
-        e2 = t * e  # type: Any
-        v2 = t * v  # type: Any
-        u2 = t * u  # type: Any
-
-        hash_input = [e, e1, e2, v, v1, v2, u, u1, u2]
-        if metadata is not None:
-            hash_input.append(metadata)
-
-        h = hash_to_curvebn(*hash_input, params=params, hash_class=ExtendedKeccak)
-        ########
-
-        z3 = t + h * rk
-
-        self.attach_proof(e2, v2, u1, u2, metadata=metadata, z3=z3, kfrag_signature=kfrag.signature_for_bob)
-
-    def verify_correctness(self, capsule) -> bool:
-        if self.proof is None:
-            raise CapsuleFrag.NoProofProvided
-
-        correctness_keys = capsule.get_correctness_keys()
-
-        delegating_pubkey = correctness_keys['delegating']
-        signing_pubkey = correctness_keys['verifying']
-        receiving_pubkey = correctness_keys['receiving']
-
-        params = capsule.params
-
-        ####
-        # Here are the formulaic constituents shared with `prove_correctness`.
-        ####
-        e = capsule.point_e
-        v = capsule.point_v
-
-        e1 = self.point_e1
-        v1 = self.point_v1
-
-        u = params.u
-        u1 = self.proof.point_kfrag_commitment
-
-        e2 = self.proof.point_e2
-        v2 = self.proof.point_v2
-        u2 = self.proof.point_kfrag_pok
-
-        hash_input = [e, e1, e2, v, v1, v2, u, u1, u2]
-        if self.proof.metadata is not None:
-            hash_input.append(self.proof.metadata)
-
-        h = hash_to_curvebn(*hash_input, params=params, hash_class=ExtendedKeccak)
-        ########
-
-        precursor = self.point_precursor
-        kfrag_id = self.kfrag_id
-
-        validity_input = (kfrag_id, delegating_pubkey, receiving_pubkey, u1, precursor)
-
-        kfrag_validity_message = bytes().join(bytes(item) for item in validity_input)
-        valid_kfrag_signature = self.proof.kfrag_signature.verify(kfrag_validity_message, signing_pubkey)
-
-        z3 = self.proof.bn_sig
-        correct_reencryption_of_e = z3 * e == e2 + (h * e1)
-
-        correct_reencryption_of_v = z3 * v == v2 + (h * v1)
-
-        correct_rk_commitment = z3 * u == u2 + (h * u1)
-
-        return valid_kfrag_signature \
-               & correct_reencryption_of_e \
-               & correct_reencryption_of_v \
-               & correct_rk_commitment
-
-    def attach_proof(self,
-                     e2: Point,
-                     v2: Point,
-                     u1: Point,
-                     u2: Point,
-                     z3: CurveBN,
-                     kfrag_signature: Signature,
-                     metadata: Optional[bytes]) -> None:
-
-        self.proof = CorrectnessProof(point_e2=e2,
-                                      point_v2=v2,
-                                      point_kfrag_commitment=u1,
-                                      point_kfrag_pok=u2,
-                                      bn_sig=z3,
-                                      kfrag_signature=kfrag_signature,
-                                      metadata=metadata,
-                                      )
-
-    def __bytes__(self) -> bytes:
-        return self.to_bytes()
-
-    def __repr__(self):
-        return "CFrag:{}".format(self.point_e1.to_bytes().hex()[2:17])
diff --git a/umbral/config.py b/umbral/config.py
deleted file mode 100644
index 9e4a17c8..00000000
--- a/umbral/config.py
+++ /dev/null
@@ -1,77 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-from typing import Optional, Type
-from warnings import warn
-
-from umbral.curve import Curve, SECP256K1
-from umbral.params import UmbralParameters
-
-
-class _CONFIG:
-    __curve = None
-    __params = None
-    __CURVE_TO_USE_IF_NO_DEFAULT_IS_SET_BY_USER = SECP256K1
-    __WARNING_IF_NO_DEFAULT_SET = "No default curve has been set.  " \
-                                  "Using SECP256K1.  " \
-                                  "A slight performance penalty has been " \
-                                  "incurred for only this call.  Set a default " \
-                                  "curve with umbral.config.set_default_curve()."
-
-    class UmbralConfigurationError(RuntimeError):
-        """Raised when somebody does something dumb re: configuration."""
-
-    @classmethod
-    def __set_curve_by_default(cls):
-        warn(cls.__WARNING_IF_NO_DEFAULT_SET, RuntimeWarning)
-        cls.set_curve(cls.__CURVE_TO_USE_IF_NO_DEFAULT_IS_SET_BY_USER)
-
-    @classmethod
-    def params(cls) -> UmbralParameters:
-        if not cls.__params:
-            cls.__set_curve_by_default()
-        return cls.__params  # type: ignore
-
-    @classmethod
-    def curve(cls) -> Curve:
-        if not cls.__curve:
-            cls.__set_curve_by_default()
-        return cls.__curve  # type: ignore
-
-    @classmethod
-    def set_curve(cls, curve: Optional[Curve] = None) -> None:
-        if cls.__curve:
-            raise cls.UmbralConfigurationError(
-                "You can only set the default curve once.  Do it once and then leave it alone.")
-        else:
-            from umbral.params import UmbralParameters
-            if curve is None:
-                curve = _CONFIG.__CURVE_TO_USE_IF_NO_DEFAULT_IS_SET_BY_USER
-            cls.__curve = curve
-            cls.__params = UmbralParameters(curve)
-
-
-def set_default_curve(curve: Optional[Curve] = None) -> None:
-    return _CONFIG.set_curve(curve)
-
-
-def default_curve() -> Curve:
-    return _CONFIG.curve()
-
-
-def default_params() -> UmbralParameters:
-    return _CONFIG.params()
diff --git a/umbral/curve.py b/umbral/curve.py
index 7185e166..dee0cc23 100644
--- a/umbral/curve.py
+++ b/umbral/curve.py
@@ -1,135 +1,11 @@
-"""
-This file is part of pyUmbral.
+from . import openssl
 
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-from cryptography.hazmat.backends import default_backend
-
-from umbral import openssl
-
-
-class Curve:
-    """
-    Acts as a container to store constant variables such as the OpenSSL
-    curve_nid, the EC_GROUP struct, and the order of the curve.
-
-    Contains a whitelist of supported elliptic curves used in pyUmbral.
-
-    """
-
-    _supported_curves = {
-        415: 'secp256r1',
-        714: 'secp256k1',
-        715: 'secp384r1'
-    }
-
-    def __init__(self, nid: int) -> None:
-        """
-        Instantiates an OpenSSL curve with the provided curve_nid and derives
-        the proper EC_GROUP struct and order. You can _only_ instantiate curves
-        with supported nids (see `Curve.supported_curves`).
-        """
-
-        try:
-            self.__curve_name = self._supported_curves[nid]
-        except KeyError:
-            raise NotImplementedError("Curve NID {} is not supported.".format(nid))
-
-        # set only once
-        self.__curve_nid = nid
-        self.__ec_group = openssl._get_ec_group_by_curve_nid(self.__curve_nid)
-        self.__order = openssl._get_ec_order_by_group(self.ec_group)
-        self.__generator = openssl._get_ec_generator_by_group(self.ec_group)
-
-        # Init cache
-        self.__field_order_size_in_bytes = 0
-        self.__group_order_size_in_bytes = 0
-
-    @classmethod
-    def from_name(cls, name: str) -> 'Curve':
-        """
-        Alternate constructor to generate a curve instance by its name.
-
-        Raises NotImplementedError if the name cannot be mapped to a known
-        supported curve NID.
-
-        """
-
-        name = name.casefold()  # normalize
-
-        for supported_nid, supported_name in cls._supported_curves.items():
-            if name == supported_name:
-                instance = cls(nid=supported_nid)
-                break
-        else:
-            message = "{} is not supported curve name.".format(name)
-            raise NotImplementedError(message)
-
-        return instance
-
-    def __eq__(self, other):
-        return self.__curve_nid == other.curve_nid
-
-    def __repr__(self):
-        return "<OpenSSL Curve(nid={}, name={})>".format(self.__curve_nid, self.__curve_name)
-
-    #
-    # Immutable Curve Data
-    #
-
-    @property
-    def field_order_size_in_bytes(self) -> int:
-        if not self.__field_order_size_in_bytes:
-            size_in_bits = openssl._get_ec_group_degree(self.__ec_group)
-            self.__field_order_size_in_bytes = (size_in_bits + 7) // 8
-        return self.__field_order_size_in_bytes
-
-    @property
-    def group_order_size_in_bytes(self) -> int:
-        if not self.__group_order_size_in_bytes:
-            BN_num_bytes = default_backend()._lib.BN_num_bytes
-            self.__group_order_size_in_bytes = BN_num_bytes(self.order)
-        return self.__group_order_size_in_bytes
-
-    @property
-    def curve_nid(self) -> int:
-        return self.__curve_nid
-
-    @property
-    def name(self) -> str:
-        return self.__curve_name
-
-    @property
-    def ec_group(self):
-        return self.__ec_group
-
-    @property
-    def order(self):
-        return self.__order
-
-    @property
-    def generator(self):
-        return self.__generator
-
-
-#
 # Global Curve Instances
-#
 
-SECP256R1 = Curve.from_name('secp256r1')
-SECP256K1 = Curve.from_name('secp256k1')
-SECP384R1 = Curve.from_name('secp384r1')
+SECP256R1 = openssl.Curve.from_name('secp256r1')
+SECP256K1 = openssl.Curve.from_name('secp256k1')
+SECP384R1 = openssl.Curve.from_name('secp384r1')
 
 CURVES = (SECP256K1, SECP256R1, SECP384R1)
+
+CURVE = SECP256K1
diff --git a/umbral/curve_point.py b/umbral/curve_point.py
new file mode 100644
index 00000000..0067fba8
--- /dev/null
+++ b/umbral/curve_point.py
@@ -0,0 +1,90 @@
+from typing import Optional, Tuple
+
+from . import openssl
+from .curve import CURVE
+from .curve_scalar import CurveScalar
+from .serializable import Serializable
+
+
+class CurvePoint(Serializable):
+    """
+    Represents an OpenSSL EC_POINT except more Pythonic.
+    """
+
+    def __init__(self, backend_point) -> None:
+        self._backend_point = backend_point
+
+    @classmethod
+    def generator(cls) -> 'CurvePoint':
+        return cls(CURVE.point_generator)
+
+    @classmethod
+    def random(cls) -> 'CurvePoint':
+        """
+        Returns a CurvePoint object with a cryptographically secure EC_POINT based
+        on the provided curve.
+        """
+        return cls.generator() * CurveScalar.random_nonzero()
+
+    @classmethod
+    def from_affine(cls, affine_x: int, affine_y: int) -> 'CurvePoint':
+        """
+        Returns a CurvePoint object from the given affine coordinates in a tuple in
+        the format of (x, y) and a given curve.
+        """
+        backend_point = openssl.point_from_affine_coords(CURVE, affine_x, affine_y)
+        return cls(backend_point)
+
+    def to_affine(self) -> Tuple[int, int]:
+        """
+        Returns a tuple of Python ints in the format of (x, y) that represents
+        the point in the curve.
+        """
+        return openssl.point_to_affine_coords(CURVE, self._backend_point)
+
+    @classmethod
+    def __take__(cls, data: bytes) -> Tuple['CurvePoint', bytes]:
+        """
+        Returns a CurvePoint object from the given byte data on the curve provided.
+        """
+        size = CURVE.field_element_size + 1 # compressed point size
+        point_data, data = cls.__take_bytes__(data, size)
+        point = openssl.point_from_bytes(CURVE, point_data)
+        return cls(point), data
+
+    def __bytes__(self) -> bytes:
+        """
+        Returns the CurvePoint serialized as bytes in the compressed form.
+        """
+        return openssl.point_to_bytes_compressed(CURVE, self._backend_point)
+
+    def __eq__(self, other):
+        """
+        Compares two EC_POINTS for equality.
+        """
+        return openssl.point_eq(CURVE, self._backend_point, other._backend_point)
+
+    def __mul__(self, other: CurveScalar) -> 'CurvePoint':
+        """
+        Performs an EC_POINT_mul on an EC_POINT and a BIGNUM.
+        """
+        return CurvePoint(openssl.point_mul_bn(CURVE, self._backend_point, other._backend_bignum))
+
+    def __add__(self, other: 'CurvePoint') -> 'CurvePoint':
+        """
+        Performs an EC_POINT_add on two EC_POINTS.
+        """
+        return CurvePoint(openssl.point_add(CURVE, self._backend_point, other._backend_point))
+
+    def __sub__(self, other: 'CurvePoint') -> 'CurvePoint':
+        """
+        Performs subtraction by adding the inverse of the `other` to the point.
+        """
+        return (self + (-other))
+
+    def __neg__(self) -> 'CurvePoint':
+        """
+        Computes the additive inverse of a CurvePoint, by performing an
+        EC_POINT_invert on itself.
+        """
+        return CurvePoint(openssl.point_neg(CURVE, self._backend_point))
diff --git a/umbral/curve_scalar.py b/umbral/curve_scalar.py
new file mode 100644
index 00000000..6fa9925d
--- /dev/null
+++ b/umbral/curve_scalar.py
@@ -0,0 +1,109 @@
+from typing import TYPE_CHECKING, Optional, Union, Tuple
+
+from . import openssl
+from .curve import CURVE
+from .serializable import Serializable
+if TYPE_CHECKING: # pragma: no cover
+    from .hashing import Hash
+
+
+class CurveScalar(Serializable):
+    """
+    Represents an OpenSSL Bignum modulo the order of a curve. Some of these
+    operations will only work with prime numbers.
+
+    By default, the underlying OpenSSL BIGNUM has BN_FLG_CONSTTIME set for
+    constant time operations.
+    """
+
+    def __init__(self, backend_bignum):
+        self._backend_bignum = backend_bignum
+
+    @classmethod
+    def random_nonzero(cls) -> 'CurveScalar':
+        """
+        Returns a CurveScalar object with a cryptographically secure OpenSSL BIGNUM.
+        """
+        return cls(openssl.bn_random_nonzero(CURVE.bn_order))
+
+    @classmethod
+    def from_int(cls, num: int, check_normalization: bool = True) -> 'CurveScalar':
+        """
+        Returns a CurveScalar object from a given integer on a curve.
+        """
+        modulus = CURVE.bn_order if check_normalization else None
+        conv_bn = openssl.bn_from_int(num, check_modulus=modulus)
+        return cls(conv_bn)
+
+    @classmethod
+    def from_digest(cls, digest: 'Hash') -> 'CurveScalar':
+        # TODO (#39): to be replaced by the standard algroithm.
+        # Currently just matching what we have in RustCrypto stack
+        # (taking bytes modulo curve order).
+        # Can produce zeros!
+        bn = openssl.bn_from_bytes(digest.finalize(), apply_modulus=CURVE.bn_order)
+        return cls(bn)
+
+    @classmethod
+    def __take__(cls, data: bytes) -> Tuple['CurveScalar', bytes]:
+        scalar_data, data = cls.__take_bytes__(data, CURVE.scalar_size)
+        bignum = openssl.bn_from_bytes(scalar_data, check_modulus=CURVE.bn_order)
+        return cls(bignum), data
+
+    def __bytes__(self) -> bytes:
+        """
+        Returns the CurveScalar as bytes.
+        """
+        return openssl.bn_to_bytes(self._backend_bignum, CURVE.scalar_size)
+
+    def __int__(self) -> int:
+        """
+        Converts the CurveScalar to a Python int.
+        """
+        return openssl.bn_to_int(self._backend_bignum)
+
+    def __eq__(self, other) -> bool:
+        """
+        Compares the two BIGNUMS or int.
+        """
+        if type(other) == int:
+            other = CurveScalar.from_int(other)
+        return openssl.bn_cmp(self._backend_bignum, other._backend_bignum) == 0
+
+    @classmethod
+    def one(cls):
+        return cls(openssl.bn_one())
+
+    def is_zero(self):
+        return openssl.bn_is_zero(self._backend_bignum)
+
+    def __mul__(self, other: Union[int, 'CurveScalar']) -> 'CurveScalar':
+        """
+        Performs a BN_mod_mul between two BIGNUMS.
+        """
+        if isinstance(other, int):
+            other = CurveScalar.from_int(other)
+        return CurveScalar(openssl.bn_mul(self._backend_bignum, other._backend_bignum, CURVE.bn_order))
+
+    def __add__(self, other : Union[int, 'CurveScalar']) -> 'CurveScalar':
+        """
+        Performs a BN_mod_add on two BIGNUMs.
+        """
+        if isinstance(other, int):
+            other = CurveScalar.from_int(other)
+        return CurveScalar(openssl.bn_add(self._backend_bignum, other._backend_bignum, CURVE.bn_order))
+
+    def __sub__(self, other : Union[int, 'CurveScalar']) -> 'CurveScalar':
+        """
+        Performs a BN_mod_sub on two BIGNUMS.
+        """
+        if isinstance(other, int):
+            other = CurveScalar.from_int(other)
+        return CurveScalar(openssl.bn_sub(self._backend_bignum, other._backend_bignum, CURVE.bn_order))
+
+    def invert(self) -> 'CurveScalar':
+        """
+        Performs a BN_mod_inverse.
+        WARNING: Only in constant time if BN_FLG_CONSTTIME is set on the BN.
+        """
+        return CurveScalar(openssl.bn_invert(self._backend_bignum, CURVE.bn_order))
diff --git a/umbral/curvebn.py b/umbral/curvebn.py
deleted file mode 100644
index 1ab85670..00000000
--- a/umbral/curvebn.py
+++ /dev/null
@@ -1,272 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-from typing import Optional, Union, cast
-
-from cryptography.hazmat.backends.openssl import backend
-
-from umbral import openssl
-from umbral.config import default_curve
-from umbral.curve import Curve
-
-
-class CurveBN:
-    """
-    Represents an OpenSSL Bignum modulo the order of a curve. Some of these
-    operations will only work with prime numbers
-    By default, the underlying OpenSSL BIGNUM has BN_FLG_CONSTTIME set for
-    constant time operations.
-    """
-
-    def __init__(self, bignum, curve: Curve) -> None:
-        on_curve = openssl._bn_is_on_curve(bignum, curve)
-        if not on_curve:
-            raise ValueError("The provided BIGNUM is not on the provided curve.")
-
-        self.bignum = bignum
-        self.curve = curve
-
-    @classmethod
-    def expected_bytes_length(cls, curve: Optional[Curve] = None) -> int:
-        """
-        Returns the size (in bytes) of a CurveBN given the curve,
-        which comes from the size of the order of the generated group.
-        If no curve is provided, it uses the default.
-        """
-        curve = curve if curve is not None else default_curve()
-        return curve.group_order_size_in_bytes
-
-    @classmethod
-    def gen_rand(cls, curve: Optional[Curve] = None) -> 'CurveBN':
-        """
-        Returns a CurveBN object with a cryptographically secure OpenSSL BIGNUM
-        based on the given curve.
-        By default, the underlying OpenSSL BIGNUM has BN_FLG_CONSTTIME set for
-        constant time operations.
-        """
-        curve = curve if curve is not None else default_curve()
-
-        new_rand_bn = openssl._get_new_BN()
-        rand_res = backend._lib.BN_rand_range(new_rand_bn, curve.order)
-        backend.openssl_assert(rand_res == 1)
-
-        if not openssl._bn_is_on_curve(new_rand_bn, curve):
-            new_rand_bn = cls.gen_rand(curve=curve)
-            return new_rand_bn
-
-        return cls(new_rand_bn, curve)
-
-    @classmethod
-    def from_int(cls, num: int, curve: Optional[Curve] = None) -> 'CurveBN':
-        """
-        Returns a CurveBN object from a given integer on a curve.
-        By default, the underlying OpenSSL BIGNUM has BN_FLG_CONSTTIME set for
-        constant time operations.
-        """
-        curve = curve if curve is not None else default_curve()
-        conv_bn = openssl._int_to_bn(num, curve)
-        return cls(conv_bn, curve)
-
-    @classmethod
-    def from_bytes(cls, data: bytes, curve: Optional[Curve] = None) -> 'CurveBN':
-        """
-        Returns a CurveBN object from the given byte data that's within the size
-        of the provided curve's order.
-        By default, the underlying OpenSSL BIGNUM has BN_FLG_CONSTTIME set for
-        constant time operations.
-        """
-        curve = curve if curve is not None else default_curve()
-
-        size = backend._lib.BN_num_bytes(curve.order)
-        if len(data) != size:
-            raise ValueError("Expected {} B for CurveBNs".format(size))
-        bignum = openssl._bytes_to_bn(data)
-        return cls(bignum, curve)
-
-    def to_bytes(self) -> bytes:
-        """
-        Returns the CurveBN as bytes.
-        """
-        size = backend._lib.BN_num_bytes(self.curve.order)
-        return openssl._bn_to_bytes(self.bignum, size)
-
-    def __int__(self) -> int:
-        """
-        Converts the CurveBN to a Python int.
-        """
-        return backend._bn_to_int(self.bignum)
-
-    def __eq__(self, other) -> bool:
-        """
-        Compares the two BIGNUMS or int.
-        """
-        # TODO: Should this stay in or not?
-        if type(other) == int:
-            other = openssl._int_to_bn(other)
-            other = CurveBN(other, self.curve)
-
-        # -1 less than, 0 is equal to, 1 is greater than
-        return not bool(backend._lib.BN_cmp(self.bignum, other.bignum))
-
-    def __pow__(self, other: Union[int, 'CurveBN']) -> 'CurveBN':
-        """
-        Performs a BN_mod_exp on two BIGNUMS.
-
-        WARNING: Only in constant time if BN_FLG_CONSTTIME is set on the BN.
-        """
-        # TODO: Should this stay in or not?
-        if type(other) == int:
-            other = openssl._int_to_bn(other)
-            other = CurveBN(other, self.curve)
-
-        other = cast('CurveBN', other)  # This is just for mypy
-
-        power = openssl._get_new_BN()
-        with backend._tmp_bn_ctx() as bn_ctx, openssl._tmp_bn_mont_ctx(self.curve.order) as bn_mont_ctx:
-            res = backend._lib.BN_mod_exp_mont(
-                power, self.bignum, other.bignum, self.curve.order, bn_ctx, bn_mont_ctx
-            )
-            backend.openssl_assert(res == 1)
-
-        return CurveBN(power, self.curve)
-
-    def __mul__(self, other) -> 'CurveBN':
-        """
-        Performs a BN_mod_mul between two BIGNUMS.
-        """
-        if type(other) != CurveBN:
-            return NotImplemented
-
-        product = openssl._get_new_BN()
-        with backend._tmp_bn_ctx() as bn_ctx:
-            res = backend._lib.BN_mod_mul(
-                product, self.bignum, other.bignum, self.curve.order, bn_ctx
-            )
-            backend.openssl_assert(res == 1)
-
-        return CurveBN(product, self.curve)
-
-    def __truediv__(self, other: 'CurveBN') -> 'CurveBN':
-        """
-        Performs a BN_div on two BIGNUMs (modulo the order of the curve).
-
-        WARNING: Only in constant time if BN_FLG_CONSTTIME is set on the BN.
-        """
-        product = openssl._get_new_BN()
-        with backend._tmp_bn_ctx() as bn_ctx:
-            inv_other = backend._lib.BN_mod_inverse(
-                backend._ffi.NULL, other.bignum, self.curve.order, bn_ctx
-            )
-            backend.openssl_assert(inv_other != backend._ffi.NULL)
-            inv_other = backend._ffi.gc(inv_other, backend._lib.BN_clear_free)
-
-            res = backend._lib.BN_mod_mul(
-                product, self.bignum, inv_other, self.curve.order, bn_ctx
-            )
-            backend.openssl_assert(res == 1)
-
-        return CurveBN(product, self.curve)
-
-    def __add__(self, other : Union[int, 'CurveBN']) -> 'CurveBN':
-        """
-        Performs a BN_mod_add on two BIGNUMs.
-        """
-        if type(other) == int:
-            other = openssl._int_to_bn(other)
-            other = CurveBN(other, self.curve)
-
-        other = cast('CurveBN', other)  # This is just for mypy
-            
-        op_sum = openssl._get_new_BN()
-        with backend._tmp_bn_ctx() as bn_ctx:
-            res = backend._lib.BN_mod_add(
-                op_sum, self.bignum, other.bignum, self.curve.order, bn_ctx
-            )
-            backend.openssl_assert(res == 1)
-
-        return CurveBN(op_sum, self.curve)
-
-    def __sub__(self, other : Union[int, 'CurveBN']) -> 'CurveBN':
-        """
-        Performs a BN_mod_sub on two BIGNUMS.
-        """
-        if type(other) == int:
-            other = openssl._int_to_bn(other)
-            other = CurveBN(other, self.curve)
-
-        other = cast('CurveBN', other)  # This is just for mypy
-
-        diff = openssl._get_new_BN()
-        with backend._tmp_bn_ctx() as bn_ctx:
-            res = backend._lib.BN_mod_sub(
-                diff, self.bignum, other.bignum, self.curve.order, bn_ctx
-            )
-            backend.openssl_assert(res == 1)
-
-        return CurveBN(diff, self.curve)
-
-    def __invert__(self) -> 'CurveBN':
-        """
-        Performs a BN_mod_inverse.
-
-        WARNING: Only in constant time if BN_FLG_CONSTTIME is set on the BN.
-
-        """
-        with backend._tmp_bn_ctx() as bn_ctx:
-            inv = backend._lib.BN_mod_inverse(
-                backend._ffi.NULL, self.bignum, self.curve.order, bn_ctx
-            )
-            backend.openssl_assert(inv != backend._ffi.NULL)
-            inv = backend._ffi.gc(inv, backend._lib.BN_clear_free)
-
-        return CurveBN(inv, self.curve)
-
-    def __neg__(self) -> 'CurveBN':
-        """
-        Computes the modular opposite (i.e., additive inverse) of a BIGNUM
-
-        """
-        zero = backend._int_to_bn(0)
-        zero = backend._ffi.gc(zero, backend._lib.BN_clear_free)
-
-        the_opposite = openssl._get_new_BN()
-        with backend._tmp_bn_ctx() as bn_ctx:
-            res = backend._lib.BN_mod_sub(
-                the_opposite, zero, self.bignum, self.curve.order, bn_ctx
-            )
-            backend.openssl_assert(res == 1)
-
-        return CurveBN(the_opposite, self.curve)
-
-    def __mod__(self, other: Union[int, 'CurveBN']) -> 'CurveBN':
-        """
-        Performs a BN_nnmod on two BIGNUMS.
-        """
-        if type(other) == int:
-            other = openssl._int_to_bn(other)
-            other = CurveBN(other, self.curve)
-
-        other = cast('CurveBN', other)  # This is just for mypy
-
-        rem = openssl._get_new_BN()
-        with backend._tmp_bn_ctx() as bn_ctx:
-            res = backend._lib.BN_nnmod(
-                rem, self.bignum, other.bignum, bn_ctx
-            )
-            backend.openssl_assert(res == 1)
-
-        return CurveBN(rem, self.curve)
diff --git a/umbral/dem.py b/umbral/dem.py
index b372a82d..042a1ee7 100644
--- a/umbral/dem.py
+++ b/umbral/dem.py
@@ -1,58 +1,69 @@
-"""
-This file is part of pyUmbral.
+import os
+from typing import Optional
 
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
+from cryptography.hazmat.primitives.kdf.hkdf import HKDF
+from cryptography.hazmat.primitives import hashes
 
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
+import nacl
+from nacl.bindings.crypto_aead import (
+    crypto_aead_xchacha20poly1305_ietf_encrypt as xchacha_encrypt,
+    crypto_aead_xchacha20poly1305_ietf_decrypt as xchacha_decrypt,
+    crypto_aead_xchacha20poly1305_ietf_KEYBYTES as XCHACHA_KEY_SIZE,
+    crypto_aead_xchacha20poly1305_ietf_NPUBBYTES as XCHACHA_NONCE_SIZE,
+    crypto_aead_xchacha20poly1305_ietf_ABYTES as XCHACHA_TAG_SIZE,
+    )
 
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
+from . import openssl
+from .errors import GenericError
 
-import os
-from typing import Optional
 
-from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
-
-
-DEM_KEYSIZE = 32
-DEM_NONCE_SIZE = 12
-
-
-class UmbralDEM:
-    def __init__(self, symm_key: bytes) -> None:
-        """
-        Initializes an UmbralDEM object. Requires a key to perform
-        ChaCha20-Poly1305.
-        """
-        if len(symm_key) != DEM_KEYSIZE:
-            raise ValueError(
-                "Invalid key size, must be {} bytes".format(DEM_KEYSIZE)
-            )
-
-        self.cipher = ChaCha20Poly1305(symm_key)
-
-    def encrypt(self, data: bytes, authenticated_data: Optional[bytes] = None) -> bytes:
-        """
-        Encrypts data using ChaCha20-Poly1305 with optional authenticated data.
-        """
-        nonce = os.urandom(DEM_NONCE_SIZE)
-        enc_data = self.cipher.encrypt(nonce, data, authenticated_data)
-        # Ciphertext will be a 12 byte nonce, the ciphertext, and a 16 byte tag.
-        return nonce + enc_data
-
-    def decrypt(self, ciphertext: bytes, authenticated_data: Optional[bytes] = None) -> bytes:
-        """
-        Decrypts data using ChaCha20-Poly1305 and validates the provided
-        authenticated data.
-        """
-        nonce = ciphertext[:DEM_NONCE_SIZE]
-        ciphertext = ciphertext[DEM_NONCE_SIZE:]
-        cleartext = self.cipher.decrypt(nonce, ciphertext, authenticated_data)
-        return cleartext
+def kdf(data: bytes,
+        key_length: int,
+        salt: Optional[bytes] = None,
+        info: Optional[bytes] = None,
+        ) -> bytes:
+
+    hkdf = HKDF(algorithm=hashes.SHA256(),
+                length=key_length,
+                salt=salt,
+                info=info,
+                backend=openssl.backend)
+    return hkdf.derive(data)
+
+
+class DEM:
+
+    KEY_SIZE = XCHACHA_KEY_SIZE
+    NONCE_SIZE = XCHACHA_NONCE_SIZE
+    TAG_SIZE = XCHACHA_TAG_SIZE
+
+    def __init__(self,
+                 key_material: bytes,
+                 salt: Optional[bytes] = None,
+                 info: Optional[bytes] = None,
+                 ):
+        self._key = kdf(key_material, self.KEY_SIZE, salt, info)
+
+    def encrypt(self, plaintext: bytes, authenticated_data: bytes = b"") -> bytes:
+        nonce = os.urandom(self.NONCE_SIZE)
+        ciphertext = xchacha_encrypt(plaintext, authenticated_data, nonce, self._key)
+        return nonce + ciphertext
+
+    def decrypt(self, nonce_and_ciphertext: bytes, authenticated_data: bytes = b"") -> bytes:
+
+        if len(nonce_and_ciphertext) < self.NONCE_SIZE:
+            raise ValueError(f"The ciphertext must include the nonce")
+
+        nonce = nonce_and_ciphertext[:self.NONCE_SIZE]
+        ciphertext = nonce_and_ciphertext[self.NONCE_SIZE:]
+
+        # Prevent an out of bounds error deep in NaCl
+        if len(ciphertext) < self.TAG_SIZE:
+            raise ValueError(f"The authentication tag is missing or malformed")
+
+        try:
+            return xchacha_decrypt(ciphertext, authenticated_data, nonce, self._key)
+        except nacl.exceptions.CryptoError:
+            raise GenericError("Decryption of ciphertext failed: "
+                               "either someone tampered with the ciphertext or "
+                               "you are using an incorrect decryption key.")
diff --git a/umbral/errors.py b/umbral/errors.py
new file mode 100644
index 00000000..328c3b9f
--- /dev/null
+++ b/umbral/errors.py
@@ -0,0 +1,5 @@
+class GenericError(Exception):
+    """
+    An interal Umbral error, see the message for details.
+    """
+    pass
diff --git a/umbral/hashing.py b/umbral/hashing.py
new file mode 100644
index 00000000..0eba516d
--- /dev/null
+++ b/umbral/hashing.py
@@ -0,0 +1,146 @@
+from typing import TYPE_CHECKING, Optional, Type, Iterable, Union
+
+from cryptography.hazmat.primitives import hashes
+
+from .openssl import backend, ErrorInvalidCompressedPoint
+from .curve import CURVE
+from .curve_scalar import CurveScalar
+from .curve_point import CurvePoint
+from .keys import PublicKey, SecretKey, Signature
+from .serializable import Serializable, serialize_bool
+
+if TYPE_CHECKING: # pragma: no cover
+    from .key_frag import KeyFragID
+
+
+class Hash:
+
+    OUTPUT_SIZE = 32
+
+    def __init__(self, dst: bytes):
+        self._backend_hash_algorithm = hashes.SHA256()
+        self._hash = hashes.Hash(self._backend_hash_algorithm, backend=backend)
+
+        len_dst = len(dst).to_bytes(4, byteorder='big')
+        self.update(len_dst + dst)
+
+    def update(self, data: Union[bytes, Serializable]) -> None:
+        self._hash.update(bytes(data))
+
+    def finalize(self) -> bytes:
+        return self._hash.finalize()
+
+
+def hash_to_polynomial_arg(precursor: CurvePoint,
+                           pubkey: CurvePoint,
+                           dh_point: CurvePoint,
+                           kfrag_id: 'KeyFragID',
+                           ) -> CurveScalar:
+    digest = Hash(b"POLYNOMIAL_ARG")
+    digest.update(precursor)
+    digest.update(pubkey)
+    digest.update(dh_point)
+    digest.update(kfrag_id)
+    return CurveScalar.from_digest(digest)
+
+
+def hash_capsule_points(e: CurvePoint, v: CurvePoint) -> CurveScalar:
+    digest = Hash(b"CAPSULE_POINTS")
+    digest.update(e)
+    digest.update(v)
+    return CurveScalar.from_digest(digest)
+
+
+def hash_to_shared_secret(precursor: CurvePoint,
+                          pubkey: CurvePoint,
+                          dh_point: CurvePoint
+                          ) -> CurveScalar:
+    digest = Hash(b"SHARED_SECRET")
+    digest.update(precursor)
+    digest.update(pubkey)
+    digest.update(dh_point)
+    return CurveScalar.from_digest(digest)
+
+
+
+def hash_to_cfrag_verification(points: Iterable[CurvePoint], metadata: Optional[bytes] = None) -> CurveScalar:
+    digest = Hash(b"CFRAG_VERIFICATION")
+    for point in points:
+        digest.update(point)
+    if metadata is not None:
+        digest.update(metadata)
+    return CurveScalar.from_digest(digest)
+
+
+def hash_to_cfrag_signature(kfrag_id: 'KeyFragID',
+                            commitment: CurvePoint,
+                            precursor: CurvePoint,
+                            maybe_delegating_pk: Optional[PublicKey],
+                            maybe_receiving_pk: Optional[PublicKey],
+                            ) -> 'SignatureDigest':
+
+    digest = SignatureDigest(b"CFRAG_SIGNATURE")
+    digest.update(kfrag_id)
+    digest.update(commitment)
+    digest.update(precursor)
+
+    if maybe_delegating_pk:
+        digest.update(serialize_bool(True))
+        digest.update(maybe_delegating_pk)
+    else:
+        digest.update(serialize_bool(False))
+
+    if maybe_receiving_pk:
+        digest.update(serialize_bool(True))
+        digest.update(maybe_receiving_pk)
+    else:
+        digest.update(serialize_bool(False))
+
+    return digest
+
+
+class SignatureDigest:
+
+    def __init__(self, dst: bytes):
+        self._digest = Hash(dst)
+
+    def update(self, value):
+        self._digest.update(value)
+
+    def sign(self, sk: SecretKey) -> Signature:
+        return sk.sign_digest(self._digest)
+
+    def verify(self, pk: PublicKey, sig: Signature):
+        return sig.verify_digest(pk, self._digest)
+
+
+def unsafe_hash_to_point(dst: bytes, data: bytes) -> CurvePoint:
+    """
+    Hashes arbitrary data into a valid EC point of the specified curve,
+    using the try-and-increment method.
+
+    WARNING: Do not use when the input data is secret, as this implementation is not
+    in constant time, and hence, it is not safe with respect to timing attacks.
+    """
+
+    len_data = len(data).to_bytes(4, byteorder='big')
+    data_with_len = len_data + data
+    sign = b'\x02'
+
+    # We use an internal 32-bit counter as additional input
+    for i in range(2**32):
+        ibytes = i.to_bytes(4, byteorder='big')
+        digest = Hash(dst)
+        digest.update(data_with_len + ibytes)
+        point_data = digest.finalize()[:CURVE.field_element_size]
+
+        compressed_point = sign + point_data
+
+        try:
+            return CurvePoint.from_bytes(compressed_point)
+        except ErrorInvalidCompressedPoint:
+            # If it is not a valid point, continue on
+            pass
+
+    # Only happens with probability 2^(-32)
+    raise ValueError('Could not hash input into the curve') # pragma: no cover
diff --git a/umbral/key_frag.py b/umbral/key_frag.py
new file mode 100644
index 00000000..de8fbf39
--- /dev/null
+++ b/umbral/key_frag.py
@@ -0,0 +1,305 @@
+import os
+from typing import Tuple, List, Optional
+
+from .curve_point import CurvePoint
+from .curve_scalar import CurveScalar
+from .hashing import hash_to_shared_secret, hash_to_cfrag_signature, hash_to_polynomial_arg
+from .keys import PublicKey, SecretKey, Signature
+from .params import PARAMETERS
+from .serializable import Serializable, serialize_bool, take_bool
+
+
+class KeyFragID(Serializable):
+
+    __SIZE = 32
+
+    def __init__(self, id_: bytes):
+        self._id = id_
+
+    def __eq__(self, other):
+        return self._id == other._id
+
+    @classmethod
+    def random(cls) -> 'KeyFragID':
+        return cls(os.urandom(cls.__SIZE))
+
+    @classmethod
+    def __take__(cls, data):
+        id_, data = cls.__take_bytes__(data, cls.__SIZE)
+        return cls(id_), data
+
+    def __bytes__(self):
+        return self._id
+
+
+class KeyFragProof(Serializable):
+
+    @classmethod
+    def from_base(cls,
+                  base: 'KeyFragBase',
+                  kfrag_id: KeyFragID,
+                  kfrag_key: CurveScalar,
+                  sign_delegating_key: bool,
+                  sign_receiving_key: bool,
+                  ) -> 'KeyFragProof':
+
+        params = PARAMETERS
+
+        kfrag_precursor = base.precursor
+        signing_sk = base.signing_sk
+        delegating_pk = base.delegating_pk
+        receiving_pk = base.receiving_pk
+
+        commitment = params.u * kfrag_key
+
+        signature_for_receiver = hash_to_cfrag_signature(kfrag_id,
+                                                         commitment,
+                                                         kfrag_precursor,
+                                                         delegating_pk,
+                                                         receiving_pk,
+                                                         ).sign(signing_sk)
+
+        maybe_delegating_pk = delegating_pk if sign_delegating_key else None
+        maybe_receiving_pk = receiving_pk if sign_receiving_key else None
+        signature_for_proxy = hash_to_cfrag_signature(kfrag_id,
+                                                      commitment,
+                                                      kfrag_precursor,
+                                                      maybe_delegating_pk,
+                                                      maybe_receiving_pk
+                                                      ).sign(signing_sk)
+
+        return cls(commitment,
+                   signature_for_proxy,
+                   signature_for_receiver,
+                   sign_delegating_key,
+                   sign_receiving_key)
+
+    def __init__(self,
+                 commitment: CurvePoint,
+                 signature_for_proxy: Signature,
+                 signature_for_receiver: Signature,
+                 delegating_key_signed: bool,
+                 receiving_key_signed: bool
+                 ):
+
+        self.commitment = commitment
+        self.signature_for_proxy = signature_for_proxy
+        self.signature_for_receiver = signature_for_receiver
+        self.delegating_key_signed = delegating_key_signed
+        self.receiving_key_signed = receiving_key_signed
+
+    def _components(self):
+        return (self.commitment,
+                self.signature_for_proxy,
+                self.signature_for_receiver,
+                self.delegating_key_signed,
+                self.receiving_key_signed)
+
+    def __eq__(self, other):
+        return self._components() == other._components()
+
+    @classmethod
+    def __take__(cls, data):
+        types = [CurvePoint, Signature, Signature]
+        (commitment, sig_proxy, sig_bob), data = cls.__take_types__(data, *types)
+        delegating_key_signed, data = take_bool(data)
+        receiving_key_signed, data = take_bool(data)
+
+        obj = cls(commitment, sig_proxy, sig_bob, delegating_key_signed, receiving_key_signed)
+        return obj, data
+
+    def __bytes__(self):
+        return (bytes(self.commitment) +
+                bytes(self.signature_for_proxy) +
+                bytes(self.signature_for_receiver) +
+                serialize_bool(self.delegating_key_signed) +
+                serialize_bool(self.receiving_key_signed)
+                )
+
+
+# Coefficients of the generating polynomial
+def poly_eval(coeffs: List[CurveScalar], x: CurveScalar) -> CurveScalar:
+    result = coeffs[-1]
+    for coeff in reversed(coeffs[:-1]):
+        result = (result * x) + coeff
+    return result
+
+
+class KeyFrag(Serializable):
+    """
+    A signed fragment of the delegating key.
+    """
+
+    def __init__(self,
+                 id_: KeyFragID,
+                 key: CurveScalar,
+                 precursor: CurvePoint,
+                 proof: KeyFragProof):
+        self.id = id_
+        self.key = key
+        self.precursor = precursor
+        self.proof = proof
+
+    @classmethod
+    def __take__(cls, data):
+        types = [KeyFragID, CurveScalar, CurvePoint, KeyFragProof]
+        components, data = cls.__take_types__(data, *types)
+        return cls(*components), data
+
+    def __bytes__(self):
+        return bytes(self.id) + bytes(self.key) + bytes(self.precursor) + bytes(self.proof)
+
+    def _components(self):
+        return self.id, self.key, self.precursor, self.proof
+
+    def __eq__(self, other):
+        return self._components() == other._components()
+
+    def __hash__(self):
+        return hash((self.__class__, bytes(self)))
+
+    def __str__(self):
+        return f"{self.__class__.__name__}:{bytes(self).hex()[:16]}"
+
+    @classmethod
+    def from_base(cls,
+                  base: 'KeyFragBase',
+                  sign_delegating_key: bool,
+                  sign_receiving_key: bool,
+                  ) -> 'KeyFrag':
+
+        kfrag_id = KeyFragID.random()
+
+        # The index of the re-encryption key share (which in Shamir's Secret
+        # Sharing corresponds to x in the tuple (x, f(x)), with f being the
+        # generating polynomial), is used to prevent reconstruction of the
+        # re-encryption key without Bob's intervention
+        share_index = hash_to_polynomial_arg(base.precursor,
+                                             base.receiving_pk.point(),
+                                             base.dh_point,
+                                             kfrag_id,
+                                             )
+
+        # The re-encryption key share is the result of evaluating the generating
+        # polynomial for the index value
+        rk = poly_eval(base.coefficients, share_index)
+
+        proof = KeyFragProof.from_base(base,
+                                       kfrag_id,
+                                       rk,
+                                       sign_delegating_key,
+                                       sign_receiving_key,
+                                       )
+
+        return cls(kfrag_id, rk, base.precursor, proof)
+
+    def verify(self,
+               signing_pk: PublicKey,
+               delegating_pk: Optional[PublicKey] = None,
+               receiving_pk: Optional[PublicKey] = None,
+               ) -> bool:
+        """
+        Verifies the validity of this fragment.
+
+        If the delegating and/or receiving key were not signed in :py:func:`generate_kfrags`,
+        but are given to this function, they are ignored.
+        """
+
+        u = PARAMETERS.u
+
+        kfrag_id = self.id
+        key = self.key
+        commitment = self.proof.commitment
+        precursor = self.precursor
+
+        # We check that the commitment is well-formed
+        if commitment != u * key:
+            return False
+
+        # A shortcut, perhaps not necessary
+        delegating_key_missing = self.proof.delegating_key_signed and not bool(delegating_pk)
+        receiving_key_missing = self.proof.receiving_key_signed and not bool(receiving_pk)
+
+        if delegating_key_missing or receiving_key_missing:
+            return False
+
+        delegating_pk = delegating_pk if self.proof.delegating_key_signed else None
+        receiving_pk = receiving_pk if self.proof.receiving_key_signed else None
+        sig = hash_to_cfrag_signature(kfrag_id,
+                                      commitment,
+                                      precursor,
+                                      delegating_pk,
+                                      receiving_pk)
+        return sig.verify(signing_pk, self.proof.signature_for_proxy)
+
+
+class KeyFragBase:
+
+    def __init__(self,
+                 delegating_sk: SecretKey,
+                 receiving_pk: PublicKey,
+                 signing_sk: SecretKey,
+                 threshold: int,
+                 ):
+
+        if threshold <= 0:
+            raise ValueError(f"`threshold` must be larger than 0 (given: {threshold})")
+
+        g = CurvePoint.generator()
+
+        delegating_pk = PublicKey.from_secret_key(delegating_sk)
+
+        receiving_pk_point = receiving_pk.point()
+
+        while True:
+            # The precursor point is used as an ephemeral public key in a DH key exchange,
+            # and the resulting shared secret 'dh_point' is used to derive other secret values
+            private_precursor = CurveScalar.random_nonzero()
+            precursor = g * private_precursor
+
+            dh_point = receiving_pk_point * private_precursor
+
+            # Secret value 'd' allows to make Umbral non-interactive
+            d = hash_to_shared_secret(precursor, receiving_pk_point, dh_point)
+
+            # At the moment we cannot statically ensure `d` is not zero,
+            # but we need it to be non-zero for the algorithm to work.
+            if not d.is_zero():
+                break
+
+        # Coefficients of the generating polynomial
+        # `invert()` is guaranteed to work because `d` is nonzero.
+        coefficients = [
+            delegating_sk.secret_scalar() * d.invert(),
+            *[CurveScalar.random_nonzero() for _ in range(threshold-1)]]
+
+        self.signing_sk = signing_sk
+        self.precursor = precursor
+        self.dh_point = dh_point
+        self.delegating_pk = delegating_pk
+        self.receiving_pk = receiving_pk
+        self.coefficients = coefficients
+
+
+def generate_kfrags(delegating_sk: SecretKey,
+                    receiving_pk: PublicKey,
+                    signing_sk: SecretKey,
+                    threshold: int,
+                    num_kfrags: int,
+                    sign_delegating_key: bool = True,
+                    sign_receiving_key: bool = True,
+                    ) -> List[KeyFrag]:
+    """
+    Generates ``num_kfrags`` key fragments to pass to proxies for re-encryption.
+    At least ``threshold`` of them will be needed for decryption.
+    If ``sign_delegating_key`` or ``sign_receiving_key`` are ``True``,
+    the corresponding keys will have to be provided to :py:meth:`KeyFrag.verify`.
+    """
+
+    base = KeyFragBase(delegating_sk, receiving_pk, signing_sk, threshold)
+
+    # Technically we could allow it, but what would be the use of these kfrags?
+    if num_kfrags < threshold:
+        raise ValueError(f"Creating less kfrags ({num_kfrags}) than threshold ({threshold}) makes them useless")
+
+    return [KeyFrag.from_base(base, sign_delegating_key, sign_receiving_key) for _ in range(num_kfrags)]
diff --git a/umbral/keys.py b/umbral/keys.py
index b012e166..53fe0e1b 100644
--- a/umbral/keys.py
+++ b/umbral/keys.py
@@ -1,472 +1,205 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
 import os
-from typing import Callable, Optional, Any
-
-from cryptography.hazmat.backends import default_backend
-from cryptography.hazmat.backends.openssl.ec import _EllipticCurvePrivateKey, _EllipticCurvePublicKey
-from cryptography.exceptions import InternalError
-from cryptography.hazmat.primitives import hashes
-from cryptography.hazmat.primitives.kdf.hkdf import HKDF
-from cryptography.hazmat.primitives.kdf.scrypt import Scrypt as CryptographyScrypt
-from nacl.secret import SecretBox
+from typing import TYPE_CHECKING, Tuple
 
-from umbral import openssl
-from umbral.config import default_params
-from umbral.curvebn import CurveBN
-from umbral.params import UmbralParameters
-from umbral.point import Point
-from umbral.curve import Curve
-from umbral.random_oracles import hash_to_curvebn
+from cryptography.exceptions import InvalidSignature
+from cryptography.hazmat.primitives.asymmetric import utils
+from cryptography.hazmat.primitives.asymmetric.ec import ECDSA
 
+from . import openssl
+from .curve import CURVE
+from .curve_scalar import CurveScalar
+from .curve_point import CurvePoint
+from .dem import kdf
+from .serializable import Serializable
 
-__SALT_SIZE = 32
+if TYPE_CHECKING: # pragma: no cover
+    from .hashing import Hash
 
 
-class Scrypt:
-    __DEFAULT_SCRYPT_COST = 20
-
-    def __call__(self,
-                 password: bytes,
-                 salt: bytes,
-                 **kwargs) -> bytes:
-        """
-        Derives a symmetric encryption key from a pair of password and salt.
-        It also accepts an additional _scrypt_cost argument.
-        WARNING: RFC7914 recommends that you use a 2^20 cost value for sensitive
-        files. It is NOT recommended to change the `_scrypt_cost` value unless
-        you know what you are doing.
-        :param password: byte-encoded password used to derive a symmetric key
-        :param salt: cryptographic salt added during key derivation
-        :return:
-        """
-
-        _scrypt_cost = kwargs.get('_scrypt_cost', Scrypt.__DEFAULT_SCRYPT_COST)
-        try:
-            derived_key = CryptographyScrypt(
-                salt=salt,
-                length=SecretBox.KEY_SIZE,
-                n=2 ** _scrypt_cost,
-                r=8,
-                p=1,
-                backend=default_backend()
-            ).derive(password)
-        except InternalError as e:
-            required_memory = 128 * 2**_scrypt_cost * 8 // (10**6)
-            if e.err_code[0].reason == 65:
-                raise MemoryError(
-                    "Scrypt key derivation requires at least {} MB of memory. "
-                    "Please free up some memory and try again.".format(required_memory)
-                )
-            else:
-                raise e
-        else:
-            return derived_key
-
-
-def derive_key_from_password(password: bytes,
-                             salt: bytes,
-                             **kwargs) -> bytes:
+class SecretKey(Serializable):
     """
-    Derives a symmetric encryption key from a pair of password and salt.
-    It uses Scrypt by default.
+    Umbral secret (private) key.
     """
-    kdf = kwargs.get('kdf', Scrypt)()
-    derived_key = kdf(password, salt, **kwargs)
-    return derived_key
 
+    __SERIALIZATION_INFO = b"SECRET_KEY"
 
-def wrap_key(key_to_wrap: bytes,
-             wrapping_key: Optional[bytes] = None,
-             password: Optional[bytes] = None,
-             **kwargs) -> bytes:
-    """
-    Wraps a key using a provided wrapping key. Alternatively, it can derive
-    the wrapping key from a password.
-    :param key_to_wrap:
-    :param wrapping_key:
-    :param password:
-    :return:
-    """
-    if not(bool(password) ^ bool(wrapping_key)):
-        raise ValueError("Either password or wrapping_key must be passed")
-
-    wrapped_key = b''
-    if password:
-        salt = os.urandom(__SALT_SIZE)
-        wrapping_key = derive_key_from_password(password=password,
-                                                salt=salt,
-                                                **kwargs)
-        wrapped_key = salt
-
-    wrapped_key += SecretBox(wrapping_key).encrypt(key_to_wrap)
-    return wrapped_key
-
-
-def unwrap_key(wrapped_key: bytes,
-               wrapping_key: Optional[bytes] = None,
-               password: Optional[bytes] = None,
-               **kwargs) -> bytes:
-    """
-    Unwraps a key using a provided wrapping key. Alternatively, it can derive
-    the wrapping key from a password.
-    :param wrapped_key:
-    :param wrapping_key:
-    :param password:
-    :return:
-    """
-    if all((password, wrapping_key)) or not any((password, wrapping_key)):
-        raise ValueError("Either password or wrapping_key must be passed")
-
-    if password:
-        salt = wrapped_key[:__SALT_SIZE]
-        wrapped_key = wrapped_key[__SALT_SIZE:]
-        wrapping_key = derive_key_from_password(password=password,
-                                                salt=salt,
-                                                **kwargs)
-
-    key = SecretBox(wrapping_key).decrypt(wrapped_key)
-    return key
-
-
-class UmbralPrivateKey:
-    def __init__(self, bn_key: CurveBN, params: UmbralParameters) -> None:
-        """
-        Initializes an Umbral private key.
-        """
-        self.params = params
-        self.bn_key = bn_key
-        self.pubkey = UmbralPublicKey(self.bn_key * params.g, params=params)  # type: ignore
+    def __init__(self, scalar_key: CurveScalar):
+        self._scalar_key = scalar_key
+        # Cached public key. Access it via `PublicKey.from_secret_key()` -
+        # it may be removed later.
+        # We are assuming here that there will be on average more calls to
+        # `PublicKey.from_secret_key()` than secret key instantiations.
+        self._public_key_point = CurvePoint.generator() * self._scalar_key
 
     @classmethod
-    def gen_key(cls, params: Optional[UmbralParameters] = None) -> 'UmbralPrivateKey':
+    def random(cls) -> 'SecretKey':
         """
-        Generates a private key and returns it.
+        Generates a random secret key and returns it.
         """
-        if params is None:
-            params = default_params()
+        return cls(CurveScalar.random_nonzero())
 
-        bn_key = CurveBN.gen_rand(params.curve)
-        return cls(bn_key, params)
+    def __eq__(self, other):
+        return self._scalar_key == other._scalar_key
 
-    @classmethod
-    def from_bytes(cls,
-                   key_bytes: bytes,
-                   wrapping_key: Optional[bytes] = None,
-                   password: Optional[bytes] = None,
-                   params: Optional[UmbralParameters] = None,
-                   decoder: Optional[Callable] = None,
-                   **kwargs) -> 'UmbralPrivateKey':
-        """
-        Loads an Umbral private key from bytes.
-        Optionally, allows a decoder function to be passed as a param to decode
-        the data provided before converting to an Umbral key.
-        Optionally, uses a wrapping key to unwrap an encrypted Umbral private key.
-        Alternatively, if a password is provided it will derive the wrapping key
-        from it.
-        """
-        if params is None:
-            params = default_params()
-
-        if decoder:
-            key_bytes = decoder(key_bytes)
-
-        if any((wrapping_key, password)):
-            key_bytes = unwrap_key(wrapped_key=key_bytes,
-                                   wrapping_key=wrapping_key,
-                                   password=password,
-                                   **kwargs)
-
-        bn_key = CurveBN.from_bytes(key_bytes, params.curve)
-        return cls(bn_key, params)
-
-    def to_bytes(self,
-                 wrapping_key: Optional[bytes] = None,
-                 password: Optional[bytes] = None,
-                 encoder: Optional[Callable] = None,
-                 **kwargs) -> bytes:
-        """
-        Returns an UmbralPrivateKey as bytes with optional symmetric
-        encryption via nacl's Salsa20-Poly1305.
-        If a password is provided instead of a wrapping key, it will use
-        Scrypt for key derivation.
-        Optionally, allows an encoder to be passed in as a param to encode the
-        data before returning it.
-        """
+    def __str__(self):
+        return f"{self.__class__.__name__}:..."
 
-        key_bytes = self.bn_key.to_bytes()
+    def __hash__(self):
+        raise NotImplementedError("Hashing secret objects is insecure")
 
-        if wrapping_key or password:
-            key_bytes = wrap_key(key_to_wrap=key_bytes,
-                                 wrapping_key=wrapping_key,
-                                 password=password,
-                                 **kwargs)
+    def secret_scalar(self):
+        return self._scalar_key
 
-        if encoder:
-            key_bytes = encoder(key_bytes)
+    @classmethod
+    def __take__(cls, data: bytes) -> Tuple['SecretKey', bytes]:
+        (scalar_key,), data = cls.__take_types__(data, CurveScalar)
+        return cls(scalar_key), data
 
-        return key_bytes
+    def __bytes__(self) -> bytes:
+        return bytes(self._scalar_key)
 
-    def get_pubkey(self) -> 'UmbralPublicKey':
-        """
-        Calculates and returns the public key of the private key.
-        """
-        return self.pubkey
+    def sign_digest(self, digest: 'Hash') -> 'Signature':
 
-    def to_cryptography_privkey(self) -> _EllipticCurvePrivateKey:
-        """
-        Returns a cryptography.io EllipticCurvePrivateKey from the Umbral key.
-        """
-        backend = default_backend()
+        signature_algorithm = ECDSA(utils.Prehashed(digest._backend_hash_algorithm))
+        message = digest.finalize()
 
-        backend.openssl_assert(self.bn_key.curve.ec_group != backend._ffi.NULL)
-        backend.openssl_assert(self.bn_key.bignum != backend._ffi.NULL)
+        backend_sk = openssl.bn_to_privkey(CURVE, self._scalar_key._backend_bignum)
+        signature_der_bytes = backend_sk.sign(message, signature_algorithm)
+        r_int, s_int = utils.decode_dss_signature(signature_der_bytes)
 
-        ec_key = backend._lib.EC_KEY_new()
-        backend.openssl_assert(ec_key != backend._ffi.NULL)
-        ec_key = backend._ffi.gc(ec_key, backend._lib.EC_KEY_free)
+        # Normalize s
+        # s is public, so no constant-timeness required here
+        if s_int > (CURVE.order >> 1):
+            s_int = CURVE.order - s_int
 
-        set_group_result = backend._lib.EC_KEY_set_group(
-            ec_key, self.bn_key.curve.ec_group
-        )
-        backend.openssl_assert(set_group_result == 1)
+        # Already normalized, don't waste time
+        r = CurveScalar.from_int(r_int, check_normalization=False)
+        s = CurveScalar.from_int(s_int, check_normalization=False)
 
-        set_privkey_result = backend._lib.EC_KEY_set_private_key(
-            ec_key, self.bn_key.bignum
-        )
-        backend.openssl_assert(set_privkey_result == 1)
+        return Signature(r, s)
 
-        # Get public key
-        point = openssl._get_new_EC_POINT(self.params.curve)
-        with backend._tmp_bn_ctx() as bn_ctx:
-            mult_result = backend._lib.EC_POINT_mul(
-                self.bn_key.curve.ec_group, point, self.bn_key.bignum,
-                backend._ffi.NULL, backend._ffi.NULL, bn_ctx
-            )
-            backend.openssl_assert(mult_result == 1)
 
-        set_pubkey_result = backend._lib.EC_KEY_set_public_key(ec_key, point)
-        backend.openssl_assert(set_pubkey_result == 1)
+class Signature(Serializable):
+    """
+    Wrapper for ECDSA signatures.
+    """
 
-        evp_pkey = backend._ec_cdata_to_evp_pkey(ec_key)
-        return _EllipticCurvePrivateKey(backend, ec_key, evp_pkey)
+    def __init__(self, r: CurveScalar, s: CurveScalar):
+        self.r = r
+        self.s = s
 
+    def __repr__(self):
+        return f"ECDSA Signature: {bytes(self).hex()[:15]}"
 
-class UmbralPublicKey:
-    def __init__(self, point_key: Point, params: UmbralParameters) -> None:
-        """
-        Initializes an Umbral public key.
-        """
-        self.params = params
+    def verify_digest(self, verifying_key: 'PublicKey', digest: 'Hash') -> bool:
+        backend_pk = openssl.point_to_pubkey(CURVE, verifying_key.point()._backend_point)
+        signature_algorithm = ECDSA(utils.Prehashed(digest._backend_hash_algorithm))
 
-        if not isinstance(point_key, Point):
-            raise TypeError("point_key can only be a Point.  Don't pass anything else.")
+        message = digest.finalize()
+        signature_der_bytes = utils.encode_dss_signature(int(self.r), int(self.s))
 
-        self.point_key = point_key
+        # TODO: Raise error instead of returning boolean
+        try:
+            backend_pk.verify(signature=signature_der_bytes,
+                              data=message,
+                              signature_algorithm=signature_algorithm)
+        except InvalidSignature:
+            return False
+        return True
 
     @classmethod
-    def from_bytes(cls,
-                   key_bytes: bytes,
-                   params: Optional[UmbralParameters] = None,
-                   decoder: Optional[Callable] = None) -> 'UmbralPublicKey':
-        """
-        Loads an Umbral public key from bytes.
-        Optionally, if an decoder function is provided it will be used to decode
-        the data before returning it as an Umbral key.
-        """
-        if params is None:
-            params = default_params()
-
-        if decoder:
-            key_bytes = decoder(key_bytes)
+    def __take__(cls, data):
+        (r, s), data = cls.__take_types__(data, CurveScalar, CurveScalar)
+        return cls(r, s), data
 
-        point_key = Point.from_bytes(key_bytes, params.curve)
-        return cls(point_key, params)
+    def __bytes__(self):
+        return bytes(self.r) + bytes(self.s)
 
-    @classmethod
-    def expected_bytes_length(cls, curve: Optional[Curve] = None,
-                              is_compressed: bool = True) -> int:
-        """
-        Returns the size (in bytes) of an UmbralPublicKey given a curve.
-        If no curve is provided, it uses the default curve.
-        By default, it assumes compressed representation (is_compressed = True).
-        """
-        return Point.expected_bytes_length(curve=curve, is_compressed=is_compressed)
+    def __eq__(self, other):
+        return self.r == other.r and self.s == other.s
 
-    def to_bytes(self, encoder: Callable = None, is_compressed: bool = True) -> bytes:
-        """
-        Returns an Umbral public key as bytes.
-        Optionally, if an encoder function is provided it will be used to encode
-        the data before returning it.
-        """
-        umbral_pubkey = self.point_key.to_bytes(is_compressed=is_compressed)
 
-        if encoder:
-            umbral_pubkey = encoder(umbral_pubkey)
+class PublicKey(Serializable):
+    """
+    Umbral public key.
+    """
 
-        return umbral_pubkey
+    def __init__(self, point_key: CurvePoint):
+        self._point_key = point_key
 
-    def hex(self, is_compressed: bool = True) -> str:
-        """
-        Returns an Umbral public key as hex string.
-        """
-        return self.to_bytes(is_compressed=is_compressed).hex()
+    def point(self):
+        return self._point_key
 
     @classmethod
-    def from_hex(cls, hex_string) -> 'UmbralPublicKey':
-        return cls.from_bytes(key_bytes=hex_string, decoder=bytes.fromhex)
-
-    def to_cryptography_pubkey(self) -> _EllipticCurvePublicKey:
+    def from_secret_key(cls, sk: SecretKey) -> 'PublicKey':
         """
-        Returns a cryptography.io EllipticCurvePublicKey from the Umbral key.
+        Creates the public key corresponding to the given secret key.
         """
-        backend = default_backend()
-
-        backend.openssl_assert(self.point_key.curve.ec_group != backend._ffi.NULL)
-        backend.openssl_assert(self.point_key.ec_point != backend._ffi.NULL)
+        return cls(sk._public_key_point)
 
-        ec_key = backend._lib.EC_KEY_new()
-        backend.openssl_assert(ec_key != backend._ffi.NULL)
-        ec_key = backend._ffi.gc(ec_key, backend._lib.EC_KEY_free)
-
-        set_group_result = backend._lib.EC_KEY_set_group(
-            ec_key, self.point_key.curve.ec_group
-        )
-        backend.openssl_assert(set_group_result == 1)
-
-        set_pubkey_result = backend._lib.EC_KEY_set_public_key(
-            ec_key, self.point_key.ec_point
-        )
-        backend.openssl_assert(set_pubkey_result == 1)
-
-        evp_pkey = backend._ec_cdata_to_evp_pkey(ec_key)
-        return _EllipticCurvePublicKey(backend, ec_key, evp_pkey)
+    @classmethod
+    def __take__(cls, data: bytes) -> Tuple['PublicKey', bytes]:
+        (point_key,), data = cls.__take_types__(data, CurvePoint)
+        return cls(point_key), data
 
     def __bytes__(self) -> bytes:
-        """
-        Returns an Umbral Public key as a bytestring.
-        """
-        return self.point_key.to_bytes()
+        return bytes(self._point_key)
 
-    def __repr__(self):
-        return "{}:{}".format(self.__class__.__name__, self.point_key.to_bytes().hex()[:15])
+    def __str__(self):
+        return f"{self.__class__.__name__}:{bytes(self).hex()[:16]}"
 
-    def __eq__(self, other: Any) -> bool:
-        if type(other) == bytes:
-            is_eq = bytes(other) == bytes(self)
-        elif hasattr(other, "point_key") and hasattr(other, "params"):
-            is_eq = (self.point_key, self.params) == (other.point_key, other.params)
-        else:
-            is_eq = False
-        return is_eq
+    def __eq__(self, other):
+        return self._point_key == other._point_key
 
     def __hash__(self) -> int:
-        return int.from_bytes(self.to_bytes(), byteorder="big")
+        return hash((self.__class__, bytes(self)))
 
 
-class UmbralKeyingMaterial:
+class SecretKeyFactory(Serializable):
     """
-    This class handles keying material for Umbral, by allowing deterministic
-    derivation of UmbralPrivateKeys based on labels. 
+    This class handles keyring material for Umbral, by allowing deterministic
+    derivation of :py:class:`SecretKey` objects based on labels.
+
     Don't use this key material directly as a key.
     """
 
-    def __init__(self, keying_material: Optional[bytes] = None) -> None:
-        """
-        Initializes an UmbralKeyingMaterial.
-        """
-        if keying_material:
-            if len(keying_material) < 32:
-                raise ValueError("UmbralKeyingMaterial must have size at least 32 bytes.")
-            self.__keying_material = keying_material
-        else:
-            self.__keying_material = os.urandom(64)
-
-    def derive_privkey_by_label(self,
-                                label: bytes,
-                                salt: Optional[bytes] = None,
-                                params: Optional[UmbralParameters] = None) -> UmbralPrivateKey:
-        """
-        Derives an UmbralPrivateKey using a KDF from this instance of 
-        UmbralKeyingMaterial, a label, and an optional salt.
-        """
-        params = params if params is not None else default_params()
-
-        key_material = HKDF(
-            algorithm=hashes.BLAKE2b(64),
-            length=64,
-            salt=salt,
-            info=b"NuCypher/KeyDerivation/"+label,
-            backend=default_backend()
-        ).derive(self.__keying_material)
+    _KEY_SEED_SIZE = 64
+    _DERIVED_KEY_SIZE = 64
 
-        bn_key = hash_to_curvebn(key_material, params=params)
-        return UmbralPrivateKey(bn_key, params)
+    def __init__(self, key_seed: bytes):
+        self.__key_seed = key_seed
 
     @classmethod
-    def from_bytes(cls,
-                   key_bytes: bytes,
-                   wrapping_key: Optional[bytes] = None,
-                   password: Optional[bytes] = None,
-                   decoder: Optional[Callable] = None,
-                   **kwargs) -> 'UmbralKeyingMaterial':
+    def random(cls) -> 'SecretKeyFactory':
         """
-        Loads an UmbralKeyingMaterial from bytes.
-        Optionally, allows a decoder function to be passed as a param to decode
-        the data provided before converting to an Umbral key.
-        Optionally, uses a wrapping key to unwrap an encrypted UmbralKeyingMaterial.
-        Alternatively, if a password is provided it will derive the wrapping key
-        from it.
+        Creates a random factory.
         """
-        if decoder:
-            key_bytes = decoder(key_bytes)
-
-        if any((password, wrapping_key)):
-            key_bytes = unwrap_key(wrapped_key=key_bytes,
-                                   wrapping_key=wrapping_key,
-                                   password=password,
-                                   **kwargs)
-
-        return cls(keying_material=key_bytes)
-
-    def to_bytes(self,
-                 wrapping_key: Optional[bytes] = None,
-                 password: Optional[bytes] = None,
-                 encoder: Optional[Callable] = None,
-                 **kwargs) -> bytes:
+        return cls(os.urandom(cls._KEY_SEED_SIZE))
+
+    def secret_key_by_label(self, label: bytes) -> SecretKey:
         """
-        Returns an UmbralKeyingMaterial as bytes with optional symmetric
-        encryption via nacl's Salsa20-Poly1305.
-        If a password is provided instead of a wrapping key, it will use
-        Scrypt for key derivation.
-        Optionally, allows an encoder to be passed in as a param to encode the
-        data before returning it.
+        Creates a :py:class:`SecretKey` from the given label.
         """
+        tag = b"KEY_DERIVATION/" + label
+        key = kdf(self.__key_seed, self._DERIVED_KEY_SIZE, info=tag)
 
-        key_bytes = self.__keying_material
+        from .hashing import Hash
+        digest = Hash(tag)
+        digest.update(key)
+        scalar_key = CurveScalar.from_digest(digest)
 
-        if any((password, wrapping_key)):
-            key_bytes = wrap_key(key_to_wrap=key_bytes,
-                                 wrapping_key=wrapping_key,
-                                 password=password,
-                                 **kwargs)
+        return SecretKey(scalar_key)
+
+    @classmethod
+    def __take__(cls, data: bytes) -> Tuple['SecretKeyFactory', bytes]:
+        key_seed, data = cls.__take_bytes__(data, cls._KEY_SEED_SIZE)
+        return cls(key_seed), data
+
+    def __bytes__(self) -> bytes:
+        return bytes(self.__key_seed)
 
-        if encoder:
-            key_bytes = encoder(key_bytes)
+    def __str__(self):
+        return f"{self.__class__.__name__}:..."
 
-        return key_bytes
+    def __hash__(self):
+        raise NotImplementedError("Hashing secret objects is insecure")
diff --git a/umbral/kfrags.py b/umbral/kfrags.py
deleted file mode 100644
index f468978a..00000000
--- a/umbral/kfrags.py
+++ /dev/null
@@ -1,199 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-import hmac
-from typing import Optional
-
-from bytestring_splitter import BytestringSplitter
-
-from umbral.config import default_curve, default_params
-from umbral.curvebn import CurveBN
-from umbral.keys import UmbralPublicKey
-from umbral.point import Point
-from umbral.signing import Signature
-from umbral.params import UmbralParameters
-from umbral.curve import Curve
-
-NO_KEY = b'\x00'
-DELEGATING_ONLY = b'\x01'
-RECEIVING_ONLY = b'\x02'
-DELEGATING_AND_RECEIVING = b'\x03'
-
-
-class KFrag:
-
-    def __init__(self,
-                 identifier: bytes,
-                 bn_key: CurveBN,
-                 point_commitment: Point,
-                 point_precursor: Point,
-                 signature_for_proxy: Signature,
-                 signature_for_bob: Signature,
-                 keys_in_signature=DELEGATING_AND_RECEIVING,
-                 ) -> None:
-        self.id = identifier
-        self.bn_key = bn_key
-        self.point_commitment = point_commitment
-        self.point_precursor = point_precursor
-        self.signature_for_proxy = signature_for_proxy
-        self.signature_for_bob = signature_for_bob
-        self.keys_in_signature = keys_in_signature
-
-    class NotValid(ValueError):
-        """
-        raised if the KFrag does not pass verification.
-        """
-
-    @classmethod
-    def expected_bytes_length(cls, curve: Optional[Curve] = None) -> int:
-        """
-        Returns the size (in bytes) of a KFrag given the curve.
-        If no curve is provided, it will use the default curve.
-        """
-        curve = curve if curve is not None else default_curve()
-        bn_size = CurveBN.expected_bytes_length(curve)
-        point_size = Point.expected_bytes_length(curve)
-
-        # self.id --> 1 bn_size
-        # self.bn_key --> 1 bn_size
-        # self.point_commitment --> 1 point_size
-        # self.point_precursor --> 1 point_size
-        # self.signature_for_proxy --> 2 bn_size
-        # self.signature_for_bob --> 2 bn_size
-        # self.keys_in_signature --> 1
-
-        return bn_size * 6 + point_size * 2 + 1
-
-    @classmethod
-    def from_bytes(cls, data: bytes, curve: Optional[Curve] = None) -> 'KFrag':
-        """
-        Instantiate a KFrag object from the serialized data.
-        """
-        curve = curve if curve is not None else default_curve()
-
-        bn_size = CurveBN.expected_bytes_length(curve)
-        point_size = Point.expected_bytes_length(curve)
-        signature_size = Signature.expected_bytes_length(curve)
-        arguments = {'curve': curve}
-
-        splitter = BytestringSplitter(
-            bn_size,  # id
-            (CurveBN, bn_size, arguments),  # bn_key
-            (Point, point_size, arguments),  # point_commitment
-            (Point, point_size, arguments),  # point_precursor
-            1,  # keys_in_signature
-            (Signature, signature_size, arguments),  # signature_for_proxy
-            (Signature, signature_size, arguments),  # signature_for_bob
-        )
-        components = splitter(data)
-
-        return cls(identifier=components[0],
-                   bn_key=components[1],
-                   point_commitment=components[2],
-                   point_precursor=components[3],
-                   keys_in_signature=components[4],
-                   signature_for_proxy=components[5],
-                   signature_for_bob=components[6])
-
-    def to_bytes(self) -> bytes:
-        """
-        Serialize the KFrag into a bytestring.
-        """
-        key = self.bn_key.to_bytes()
-        commitment = self.point_commitment.to_bytes()
-        precursor = self.point_precursor.to_bytes()
-        signature_for_proxy = bytes(self.signature_for_proxy)
-        signature_for_bob = bytes(self.signature_for_bob)
-        mode = bytes(self.keys_in_signature)
-
-        return self.id + key + commitment + precursor \
-             + mode + signature_for_proxy + signature_for_bob
-
-    def verify(self,
-               signing_pubkey: UmbralPublicKey,
-               delegating_pubkey: Optional[UmbralPublicKey] = None,
-               receiving_pubkey: Optional[UmbralPublicKey] = None,
-               params: Optional[UmbralParameters] = None,
-               ) -> bool:
-        if params is None:
-            params = default_params()
-
-        if signing_pubkey is None:
-            raise ValueError("The verifying pubkey is required to verify this KFrag.")
-
-        if self.delegating_key_in_signature():
-            if delegating_pubkey is None:
-                raise ValueError("The delegating pubkey is required to verify this KFrag.")
-            elif delegating_pubkey.params != params:
-                raise ValueError("The delegating pubkey has different UmbralParameters.")
-
-        if self.receiving_key_in_signature():
-            if receiving_pubkey is None:
-                raise ValueError("The receiving pubkey is required to verify this KFrag.")
-            elif receiving_pubkey.params != params:
-                raise ValueError("The receiving pubkey has different UmbralParameters.")
-
-        u = params.u
-
-        kfrag_id = self.id
-        key = self.bn_key
-        commitment = self.point_commitment
-        precursor = self.point_precursor
-
-        #  We check that the commitment is well-formed
-        correct_commitment = commitment == key * u
-
-        validity_input = [kfrag_id, commitment, precursor, self.keys_in_signature]
-
-        if self.delegating_key_in_signature():
-            validity_input.append(delegating_pubkey)
-
-        if self.receiving_key_in_signature():
-            validity_input.append(receiving_pubkey)
-
-        kfrag_validity_message = bytes().join(bytes(item) for item in validity_input)
-        valid_kfrag_signature = self.signature_for_proxy.verify(kfrag_validity_message, signing_pubkey)
-
-        return correct_commitment & valid_kfrag_signature
-
-    def verify_for_capsule(self, capsule) -> bool:
-        correctness_keys = capsule.get_correctness_keys()
-
-        return self.verify(params=capsule.params,
-                           signing_pubkey=correctness_keys["verifying"],
-                           delegating_pubkey=correctness_keys["delegating"],
-                           receiving_pubkey=correctness_keys["receiving"])
-
-    def delegating_key_in_signature(self):
-        return self.keys_in_signature == DELEGATING_ONLY or \
-               self.keys_in_signature == DELEGATING_AND_RECEIVING
-
-    def receiving_key_in_signature(self):
-        return self.keys_in_signature == RECEIVING_ONLY or \
-               self.keys_in_signature == DELEGATING_AND_RECEIVING
-
-    def __bytes__(self) -> bytes:
-        return self.to_bytes()
-
-    def __eq__(self, other):
-        return hmac.compare_digest(bytes(self), bytes(other))
-
-    def __hash__(self):
-        return hash(bytes(self.id))
-
-    def __repr__(self):
-        return "{}:{}".format(self.__class__.__name__, self.id.hex()[:15])
diff --git a/umbral/openssl.py b/umbral/openssl.py
index cc52d7fc..927be3b3 100644
--- a/umbral/openssl.py
+++ b/umbral/openssl.py
@@ -1,218 +1,440 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-import typing
 from contextlib import contextmanager
+from typing import Tuple
+
+from cryptography.exceptions import InternalError
 from cryptography.hazmat.backends.openssl import backend
+from cryptography.hazmat.backends.openssl.ec import _EllipticCurvePrivateKey, _EllipticCurvePublicKey
 
 
-@typing.no_type_check
-def _get_new_BN(set_consttime_flag=True):
+class Curve:
     """
-    Returns a new and initialized OpenSSL BIGNUM.
-    The set_consttime_flag is set to True by default. When this instance of a
-    CurveBN object has BN_FLG_CONSTTIME set, OpenSSL will use constant time
-    operations whenever this CurveBN is passed.
+    Acts as a container to store constant variables such as the OpenSSL
+    curve_nid, the EC_GROUP struct, and the order of the curve.
+
+    Contains a whitelist of supported elliptic curves used in pyUmbral.
     """
-    new_bn = backend._lib.BN_new()
-    backend.openssl_assert(new_bn != backend._ffi.NULL)
-    new_bn = backend._ffi.gc(new_bn, backend._lib.BN_clear_free)
 
-    if set_consttime_flag:
-        backend._lib.BN_set_flags(new_bn, backend._lib.BN_FLG_CONSTTIME)
-    return new_bn
+    _supported_curves = {
+        415: 'secp256r1',
+        714: 'secp256k1',
+        715: 'secp384r1'
+    }
+
+    @staticmethod
+    def _get_ec_group_by_curve_nid(nid: int):
+        """
+        Returns the group of a given curve via its OpenSSL nid. This must be freed
+        after each use otherwise it leaks memory.
+        """
+        group = backend._lib.EC_GROUP_new_by_curve_name(nid)
+        backend.openssl_assert(group != backend._ffi.NULL)
+        return group
+
+    @staticmethod
+    def _get_ec_order_by_group(ec_group):
+        """
+        Returns the order of a given curve via its OpenSSL EC_GROUP.
+        """
+        ec_order = _bn_new()
+        with backend._tmp_bn_ctx() as bn_ctx:
+            res = backend._lib.EC_GROUP_get_order(ec_group, ec_order, bn_ctx)
+            backend.openssl_assert(res == 1)
+        return ec_order
 
+    @staticmethod
+    def _get_ec_generator_by_group(ec_group):
+        """
+        Returns the generator point of a given curve via its OpenSSL EC_GROUP.
+        """
+        generator = backend._lib.EC_GROUP_get0_generator(ec_group)
+        backend.openssl_assert(generator != backend._ffi.NULL)
+        generator = backend._ffi.gc(generator, backend._lib.EC_POINT_clear_free)
 
-@typing.no_type_check
-def _get_ec_group_by_curve_nid(curve_nid: int):
-    """
-    Returns the group of a given curve via its OpenSSL nid. This must be freed
-    after each use otherwise it leaks memory.
-    """
-    group = backend._lib.EC_GROUP_new_by_curve_name(curve_nid)
-    backend.openssl_assert(group != backend._ffi.NULL)
+        return generator
 
-    return group
+    @staticmethod
+    def _get_ec_group_degree(ec_group):
+        """
+        Returns the number of bits needed to represent the order of the finite
+        field upon the curve is based.
+        """
+        return backend._lib.EC_GROUP_get_degree(ec_group)
 
+    def __init__(self, nid: int):
+        """
+        Instantiates an OpenSSL curve with the provided curve_nid and derives
+        the proper EC_GROUP struct and order. You can _only_ instantiate curves
+        with supported nids (see `Curve.supported_curves`).
+        """
 
-@typing.no_type_check
-def _get_ec_order_by_group(ec_group):
-    """
-    Returns the order of a given curve via its OpenSSL EC_GROUP.
-    """
-    ec_order = _get_new_BN()
-    with backend._tmp_bn_ctx() as bn_ctx:
-        res = backend._lib.EC_GROUP_get_order(ec_group, ec_order, bn_ctx)
-        backend.openssl_assert(res == 1)
-    return ec_order
+        try:
+            self.name = self._supported_curves[nid]
+        except KeyError:
+            raise NotImplementedError("Curve NID {} is not supported.".format(nid))
 
+        self.nid = nid
+
+        self.ec_group = self._get_ec_group_by_curve_nid(self.nid)
+        self.bn_order = self._get_ec_order_by_group(self.ec_group)
+        self.point_generator = self._get_ec_generator_by_group(self.ec_group)
+
+        size_in_bits = self._get_ec_group_degree(self.ec_group)
+        self.field_element_size = (size_in_bits + 7) // 8
+
+        self.scalar_size = _bn_size(self.bn_order)
+        self.order = bn_to_int(self.bn_order)
+
+    @classmethod
+    def from_name(cls, name: str) -> 'Curve':
+        """
+        Alternate constructor to generate a curve instance by its name.
+
+        Raises NotImplementedError if the name cannot be mapped to a known
+        supported curve NID.
+        """
+
+        name = name.casefold()  # normalize
+
+        for supported_nid, supported_name in cls._supported_curves.items():
+            if name == supported_name:
+                instance = cls(nid=supported_nid)
+                break
+        else:
+            raise NotImplementedError(f"{name} is not supported curve name.")
+
+        return instance
+
+    def __eq__(self, other):
+        return self.nid == other.nid
+
+    def __str__(self):
+        return "<OpenSSL Curve(nid={}, name={})>".format(self.nid, self.name)
 
-@typing.no_type_check
-def _get_ec_generator_by_group(ec_group):
-    """
-    Returns the generator point of a given curve via its OpenSSL EC_GROUP.
-    """
-    generator = backend._lib.EC_GROUP_get0_generator(ec_group)
-    backend.openssl_assert(generator != backend._ffi.NULL)
-    generator = backend._ffi.gc(generator, backend._lib.EC_POINT_clear_free)
 
-    return generator
+#
+# OpenSSL bignums
+#
 
 
-@typing.no_type_check
-def _get_ec_group_degree(ec_group):
+def _bn_new():
     """
-    Returns the number of bits needed to represent the order of the finite 
-    field upon the curve is based.
+    Returns a new and initialized OpenSSL BIGNUM.
     """
-    return backend._lib.EC_GROUP_get_degree(ec_group) 
+    new_bn = backend._lib.BN_new()
+    backend.openssl_assert(new_bn != backend._ffi.NULL)
+    new_bn = backend._ffi.gc(new_bn, backend._lib.BN_clear_free)
+
+    # Always use constant time operations.
+    backend._lib.BN_set_flags(new_bn, backend._lib.BN_FLG_CONSTTIME)
+    return new_bn
 
 
-@typing.no_type_check
-def _bn_is_on_curve(check_bn, curve: 'Curve'):
+def bn_is_normalized(check_bn, modulus):
     """
-    Checks if a given OpenSSL BIGNUM is within the provided curve's order.
-    Returns True if the provided BN is on the curve, or False if the BN is zero
-    or not on the curve.
+    Returns ``True`` if ``check_bn`` is in ``[0, modulus)``, ``False`` otherwise.
     """
     zero = backend._int_to_bn(0)
     zero = backend._ffi.gc(zero, backend._lib.BN_clear_free)
 
     check_sign = backend._lib.BN_cmp(check_bn, zero)
-    range_check = backend._lib.BN_cmp(check_bn, curve.order)
-    return check_sign == 1 and range_check == -1
+    range_check = backend._lib.BN_cmp(check_bn, modulus)
+    return (check_sign == 1 or check_sign == 0) and range_check == -1
 
 
-@typing.no_type_check
-def _int_to_bn(py_int: int, curve: 'Curve'=None, set_consttime_flag=True):
+def bn_from_int(py_int: int, check_modulus=None):
     """
-    Converts the given Python int to an OpenSSL BIGNUM. If a curve is
-    provided, it will check if the Python integer is within the order of that
-    curve. If it's not within the order, it will raise a ValueError.
-
-    If set_consttime_flag is set to True, OpenSSL will use constant time
-    operations when using this CurveBN.
+    Converts the given Python int to an OpenSSL BIGNUM. If ``modulus`` is
+    provided, it will check if the Python integer is within ``[0, modulus)``.
     """
     conv_bn = backend._int_to_bn(py_int)
     conv_bn = backend._ffi.gc(conv_bn, backend._lib.BN_clear_free)
-    
-    if curve:
-        on_curve = _bn_is_on_curve(conv_bn, curve)
-        if not on_curve:
-            raise ValueError("The Python integer given is not on the provided curve.")
-
-    if set_consttime_flag:
-        backend._lib.BN_set_flags(conv_bn, backend._lib.BN_FLG_CONSTTIME)
+
+    if check_modulus and not bn_is_normalized(conv_bn, check_modulus):
+        raise ValueError(f"The Python integer given ({py_int}) is not under the provided modulus.")
+
+    backend._lib.BN_set_flags(conv_bn, backend._lib.BN_FLG_CONSTTIME)
     return conv_bn
 
-@typing.no_type_check
-def _bytes_to_bn(bytes_seq: bytes, set_consttime_flag=True):
+
+def bn_from_bytes(bytes_seq: bytes, check_modulus=None, apply_modulus=None):
     """
-    Converts the given byte sequence to an OpenSSL BIGNUM. 
-    If set_consttime_flag is set to True, OpenSSL will use constant time
-    operations when using this BIGNUM.
+    Converts the given byte sequence to an OpenSSL BIGNUM.
     """
-    bn = _get_new_BN(set_consttime_flag)
+    bn = _bn_new()
     backend._lib.BN_bin2bn(bytes_seq, len(bytes_seq), bn)
-    backend.openssl_assert(bn != backend._ffi.NULL)    
-    return bn
+    backend.openssl_assert(bn != backend._ffi.NULL)
+
+    if check_modulus and not bn_is_normalized(bn, check_modulus):
+        raise ValueError(f"The integer encoded with given bytes ({repr(bytes_seq)}) "
+                          "is not under the provided modulus.")
+
+    if apply_modulus:
+        bignum =_bn_new()
+        with backend._tmp_bn_ctx() as bn_ctx:
+            res = backend._lib.BN_mod(bignum, bn, apply_modulus, bn_ctx)
+            backend.openssl_assert(res == 1)
+        return bignum
+    else:
+        return bn
+
 
-@typing.no_type_check
-def _bn_to_bytes(bignum, length : int = None):
+def bn_to_bytes(bn, length: int):
     """
     Converts the given OpenSSL BIGNUM into a Python bytes sequence.
     If length is given, the return bytes will have such length.
     If the BIGNUM doesn't fit, it raises a ValueError.
     """
 
-    if bignum is None or bignum == backend._ffi.NULL:
-        raise ValueError("Input BIGNUM must have a value")
-
-    bn_num_bytes = backend._lib.BN_num_bytes(bignum)
-    if length is None:
-        length = bn_num_bytes
-    elif bn_num_bytes > length:
-        raise ValueError("Input BIGNUM doesn't fit in {} B".format(length))
+    # Sanity check, CurveScalar ensures it won't happen.
+    bn_num_bytes = backend._lib.BN_num_bytes(bn)
+    assert bn_num_bytes <= length, f"Input BIGNUM doesn't fit in {length} B"
 
     bin_ptr = backend._ffi.new("unsigned char []", length)
-    bin_len = backend._lib.BN_bn2bin(bignum, bin_ptr)
+    bin_len = backend._lib.BN_bn2bin(bn, bin_ptr)
     return bytes.rjust(backend._ffi.buffer(bin_ptr, bin_len)[:], length, b'\0')
 
 
-@typing.no_type_check
-def _get_new_EC_POINT(curve: 'Curve'):
+def bn_random_nonzero(modulus):
+
+    one = backend._lib.BN_value_one()
+
+    # TODO: in most cases, we want this number to be secret.
+    # OpenSSL 1.1.1 has `BN_priv_rand_range()`, but it is not
+    # currently exported by `cryptography`.
+    # Use when available.
+
+    # Calculate `modulus - 1`
+    modulus_minus_1 = _bn_new()
+    res = backend._lib.BN_sub(modulus_minus_1, modulus, one)
+    backend.openssl_assert(res == 1)
+
+    # Get a random in range `[0, modulus - 1)`
+    new_rand_bn = _bn_new()
+    res = backend._lib.BN_rand_range(new_rand_bn, modulus_minus_1)
+    backend.openssl_assert(res == 1)
+
+    # Turn it into a random in range `[1, modulus)`
+    op_sum = _bn_new()
+    res = backend._lib.BN_add(op_sum, new_rand_bn, one)
+    backend.openssl_assert(res == 1)
+
+    return op_sum
+
+
+def _bn_size(bn):
+    return backend._lib.BN_num_bytes(bn)
+
+
+def bn_to_int(bn):
+    return backend._bn_to_int(bn)
+
+
+def bn_cmp(bn1, bn2):
+    # -1 less than, 0 is equal to, 1 is greater than
+    return backend._lib.BN_cmp(bn1, bn2)
+
+
+def bn_one():
+    return backend._lib.BN_value_one()
+
+
+def bn_is_zero(bn):
+    # No special function exported in the current backend, so this will have to do
+    return bn_cmp(bn, bn_from_int(0)) == 0
+
+
+def bn_invert(bn, modulus):
+    with backend._tmp_bn_ctx() as bn_ctx:
+        inv = backend._lib.BN_mod_inverse(backend._ffi.NULL, bn, modulus, bn_ctx)
+        backend.openssl_assert(inv != backend._ffi.NULL)
+        inv = backend._ffi.gc(inv, backend._lib.BN_clear_free)
+    return inv
+
+
+def bn_sub(bn1, bn2, modulus):
+    diff = _bn_new()
+    with backend._tmp_bn_ctx() as bn_ctx:
+        res = backend._lib.BN_mod_sub(diff, bn1, bn2, modulus, bn_ctx)
+        backend.openssl_assert(res == 1)
+    return diff
+
+
+def bn_add(bn1, bn2, modulus):
+    op_sum = _bn_new()
+    with backend._tmp_bn_ctx() as bn_ctx:
+        res = backend._lib.BN_mod_add(op_sum, bn1, bn2, modulus, bn_ctx)
+        backend.openssl_assert(res == 1)
+    return op_sum
+
+
+def bn_mul(bn1, bn2, modulus):
+    product = _bn_new()
+    with backend._tmp_bn_ctx() as bn_ctx:
+        res = backend._lib.BN_mod_mul(product, bn1, bn2, modulus, bn_ctx)
+        backend.openssl_assert(res == 1)
+    return product
+
+
+def bn_to_privkey(curve: Curve, bn):
+
+    ec_key = backend._lib.EC_KEY_new()
+    backend.openssl_assert(ec_key != backend._ffi.NULL)
+    ec_key = backend._ffi.gc(ec_key, backend._lib.EC_KEY_free)
+
+    set_group_result = backend._lib.EC_KEY_set_group(ec_key, curve.ec_group)
+    backend.openssl_assert(set_group_result == 1)
+
+    set_privkey_result = backend._lib.EC_KEY_set_private_key(ec_key, bn)
+    backend.openssl_assert(set_privkey_result == 1)
+
+    evp_pkey = backend._ec_cdata_to_evp_pkey(ec_key)
+    return _EllipticCurvePrivateKey(backend, ec_key, evp_pkey)
+
+
+#
+# OpenSSL EC points
+#
+
+
+def _point_new(ec_group):
     """
     Returns a new and initialized OpenSSL EC_POINT given the group of a curve.
     If __curve_nid is provided, it retrieves the group from the curve provided.
     """
-    new_point = backend._lib.EC_POINT_new(curve.ec_group)
+    new_point = backend._lib.EC_POINT_new(ec_group)
     backend.openssl_assert(new_point != backend._ffi.NULL)
     new_point = backend._ffi.gc(new_point, backend._lib.EC_POINT_clear_free)
 
     return new_point
 
 
-@typing.no_type_check
-def _get_EC_POINT_via_affine(affine_x, affine_y, curve: 'Curve'):
+def point_from_affine_coords(curve: Curve, affine_x: int, affine_y: int):
     """
     Returns an EC_POINT given the group of a curve and the affine coordinates
     provided.
     """
-    new_point = _get_new_EC_POINT(curve)
+    bn_affine_x = bn_from_int(affine_x)
+    bn_affine_y = bn_from_int(affine_y)
+
+    new_point = _point_new(curve.ec_group)
     with backend._tmp_bn_ctx() as bn_ctx:
         res = backend._lib.EC_POINT_set_affine_coordinates_GFp(
-            curve.ec_group, new_point, affine_x, affine_y, bn_ctx
+            curve.ec_group, new_point, bn_affine_x, bn_affine_y, bn_ctx
         )
         backend.openssl_assert(res == 1)
     return new_point
 
 
-@typing.no_type_check
-def _get_affine_coords_via_EC_POINT(ec_point, curve: 'Curve'):
+def point_to_affine_coords(curve: Curve, point) -> Tuple[int, int]:
     """
     Returns the affine coordinates of a given point on the provided ec_group.
     """
-    affine_x = _get_new_BN()
-    affine_y = _get_new_BN()
+    affine_x = _bn_new()
+    affine_y = _bn_new()
 
     with backend._tmp_bn_ctx() as bn_ctx:
         res = backend._lib.EC_POINT_get_affine_coordinates_GFp(
-            curve.ec_group, ec_point, affine_x, affine_y, bn_ctx
+            curve.ec_group, point, affine_x, affine_y, bn_ctx
         )
         backend.openssl_assert(res == 1)
-    return (affine_x, affine_y)
 
+    return bn_to_int(affine_x), bn_to_int(affine_y)
 
-@typing.no_type_check
-@contextmanager
-def _tmp_bn_mont_ctx(modulus):
-    """
-    Initializes and returns a BN_MONT_CTX for Montgomery ops.
-    Requires a modulus to place in the Montgomery structure.
-    """
-    bn_mont_ctx = backend._lib.BN_MONT_CTX_new()
-    backend.openssl_assert(bn_mont_ctx != backend._ffi.NULL)
-    # Don't set the garbage collector. Only free it when the context is done
-    # or else you'll get a null pointer error.
 
+class ErrorInvalidCompressedPoint(Exception):
+    pass
+
+
+class ErrorInvalidPointEncoding(Exception):
+    pass
+
+
+def point_from_bytes(curve: Curve, data):
+    point = _point_new(curve.ec_group)
     try:
         with backend._tmp_bn_ctx() as bn_ctx:
-            res = backend._lib.BN_MONT_CTX_set(bn_mont_ctx, modulus, bn_ctx)
+            res = backend._lib.EC_POINT_oct2point(curve.ec_group, point, data, len(data), bn_ctx);
             backend.openssl_assert(res == 1)
-            yield bn_mont_ctx
-    finally:
-        backend._lib.BN_MONT_CTX_free(bn_mont_ctx)
+    except InternalError as e:
+        # We want to catch specific InternalExceptions.
+        # https://github.com/openssl/openssl/blob/master/include/openssl/ecerr.h
+        # There is also EC_R_POINT_IS_NOT_ON_CURVE (code 107),
+        # but somehow it is never triggered during deserialization.
+        if e.err_code[0].reason == 110: # EC_R_INVALID_COMPRESSED_POINT
+            raise ErrorInvalidCompressedPoint
+        elif e.err_code[0].reason == 102: # EC_R_INVALID_ENCODING
+            raise ErrorInvalidPointEncoding
+        else:
+            # Any other exception, we raise it.
+            # (although at the moment I'm not sure what should one do to cause it)
+            raise e # pragma: no cover
+    return point
+
+
+def point_to_bytes_compressed(curve: Curve, point):
+    point_conversion_form = backend._lib.POINT_CONVERSION_COMPRESSED
+
+    size = curve.field_element_size + 1 # compressed point size
+
+    bin_ptr = backend._ffi.new("unsigned char[]", size)
+    with backend._tmp_bn_ctx() as bn_ctx:
+        bin_len = backend._lib.EC_POINT_point2oct(
+            curve.ec_group, point, point_conversion_form,
+            bin_ptr, size, bn_ctx
+        )
+        backend.openssl_assert(bin_len != 0)
+
+    return bytes(backend._ffi.buffer(bin_ptr, bin_len)[:])
+
+
+def point_eq(curve: Curve, point1, point2):
+    with backend._tmp_bn_ctx() as bn_ctx:
+        is_equal = backend._lib.EC_POINT_cmp(curve.ec_group, point1, point2, bn_ctx)
+        backend.openssl_assert(is_equal != -1)
+
+    # 1 is not-equal, 0 is equal, -1 is error
+    return is_equal == 0
+
+
+def point_mul_bn(curve: Curve, point, bn):
+    prod = _point_new(curve.ec_group)
+    with backend._tmp_bn_ctx() as bn_ctx:
+        res = backend._lib.EC_POINT_mul(curve.ec_group, prod, backend._ffi.NULL, point, bn, bn_ctx)
+        backend.openssl_assert(res == 1)
+    return prod
+
+
+def point_add(curve: Curve, point1, point2):
+    op_sum = _point_new(curve.ec_group)
+    with backend._tmp_bn_ctx() as bn_ctx:
+        res = backend._lib.EC_POINT_add(curve.ec_group, op_sum, point1, point2, bn_ctx)
+        backend.openssl_assert(res == 1)
+    return op_sum
+
+
+def point_neg(curve: Curve, point):
+    inv = backend._lib.EC_POINT_dup(point, curve.ec_group)
+    backend.openssl_assert(inv != backend._ffi.NULL)
+    inv = backend._ffi.gc(inv, backend._lib.EC_POINT_clear_free)
+
+    with backend._tmp_bn_ctx() as bn_ctx:
+        res = backend._lib.EC_POINT_invert(curve.ec_group, inv, bn_ctx)
+        backend.openssl_assert(res == 1)
+
+    return inv
+
+
+def point_to_pubkey(curve: Curve, point):
+
+    ec_key = backend._lib.EC_KEY_new()
+    backend.openssl_assert(ec_key != backend._ffi.NULL)
+    ec_key = backend._ffi.gc(ec_key, backend._lib.EC_KEY_free)
+
+    set_group_result = backend._lib.EC_KEY_set_group(ec_key, curve.ec_group)
+    backend.openssl_assert(set_group_result == 1)
+
+    set_pubkey_result = backend._lib.EC_KEY_set_public_key(ec_key, point)
+    backend.openssl_assert(set_pubkey_result == 1)
+
+    evp_pkey = backend._ec_cdata_to_evp_pkey(ec_key)
+    return _EllipticCurvePublicKey(backend, ec_key, evp_pkey)
diff --git a/umbral/params.py b/umbral/params.py
index c0dfdb2a..88c53344 100644
--- a/umbral/params.py
+++ b/umbral/params.py
@@ -1,41 +1,10 @@
-"""
-This file is part of pyUmbral.
+from .hashing import unsafe_hash_to_point
 
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
 
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
+class Parameters:
 
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
+    def __init__(self):
+        self.u = unsafe_hash_to_point(b'PARAMETERS', b'POINT_U')
 
-from umbral.curve import Curve
 
-
-class UmbralParameters:
-    def __init__(self, curve: Curve) -> None:
-        from umbral.point import Point
-        from umbral.random_oracles import unsafe_hash_to_point
-
-        self.curve = curve
-        self.CURVE_KEY_SIZE_BYTES = self.curve.field_order_size_in_bytes
-
-        self.g = Point.get_generator_from_curve(curve=curve)
-        g_bytes = self.g.to_bytes()
-
-        parameters_seed = b'NuCypher/UmbralParameters/'
-        self.u = unsafe_hash_to_point(g_bytes, self, parameters_seed + b'u')
-
-    def __eq__(self, other) -> bool:
-
-        # TODO: This is not comparing the order, which currently is an OpenSSL pointer
-        self_attributes = self.curve, self.g, self.CURVE_KEY_SIZE_BYTES, self.u
-        others_attributes = other.curve, other.g, other.CURVE_KEY_SIZE_BYTES, other.u
-
-        return self_attributes == others_attributes
+PARAMETERS = Parameters()
diff --git a/umbral/point.py b/umbral/point.py
deleted file mode 100644
index 26b4a2e3..00000000
--- a/umbral/point.py
+++ /dev/null
@@ -1,211 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-from typing import Optional, Tuple
-
-from cryptography.hazmat.backends.openssl import backend
-
-from umbral import openssl
-from umbral.config import default_curve
-from umbral.curve import Curve
-from umbral.curvebn import CurveBN
-
-
-class Point:
-    """
-    Represents an OpenSSL EC_POINT except more Pythonic
-    """
-
-    def __init__(self, ec_point, curve: Curve) -> None:
-        self.ec_point = ec_point
-        self.curve = curve
-
-    @classmethod
-    def expected_bytes_length(cls, curve: Optional[Curve] = None,
-                              is_compressed: bool = True):
-        """
-        Returns the size (in bytes) of a Point given a curve.
-        If no curve is provided, it uses the default curve.
-        By default, it assumes compressed representation (is_compressed = True).
-        """
-        curve = curve if curve is not None else default_curve()
-
-        coord_size = curve.field_order_size_in_bytes
-
-        if is_compressed:
-            return 1 + coord_size
-        else:
-            return 1 + 2 * coord_size
-
-    @classmethod
-    def gen_rand(cls, curve: Optional[Curve] = None) -> 'Point':
-        """
-        Returns a Point object with a cryptographically secure EC_POINT based
-        on the provided curve.
-        """
-        curve = curve if curve is not None else default_curve()
-
-        rand_point = openssl._get_new_EC_POINT(curve)
-        rand_bn = CurveBN.gen_rand(curve).bignum
-
-        with backend._tmp_bn_ctx() as bn_ctx:
-            res = backend._lib.EC_POINT_mul(
-                curve.ec_group, rand_point, backend._ffi.NULL, curve.generator,
-                rand_bn, bn_ctx
-            )
-            backend.openssl_assert(res == 1)
-
-        return cls(rand_point, curve)
-
-    @classmethod
-    def from_affine(cls, coords: Tuple[int, int], curve: Optional[Curve] = None) -> 'Point':
-        """
-        Returns a Point object from the given affine coordinates in a tuple in
-        the format of (x, y) and a given curve.
-        """
-        curve = curve if curve is not None else default_curve()
-
-        affine_x, affine_y = coords
-        if type(affine_x) == int:
-            affine_x = openssl._int_to_bn(affine_x, curve=None)
-
-        if type(affine_y) == int:
-            affine_y = openssl._int_to_bn(affine_y, curve=None)
-
-        ec_point = openssl._get_EC_POINT_via_affine(affine_x, affine_y, curve)
-        return cls(ec_point, curve)
-
-    def to_affine(self):
-        """
-        Returns a tuple of Python ints in the format of (x, y) that represents
-        the point in the curve.
-        """
-        affine_x, affine_y = openssl._get_affine_coords_via_EC_POINT(
-                                self.ec_point, self.curve)
-        return (backend._bn_to_int(affine_x), backend._bn_to_int(affine_y))
-
-    @classmethod
-    def from_bytes(cls, data: bytes, curve: Optional[Curve] = None) -> 'Point':
-        """
-        Returns a Point object from the given byte data on the curve provided.
-        """
-        curve = curve if curve is not None else default_curve()
-
-        point = openssl._get_new_EC_POINT(curve)
-        with backend._tmp_bn_ctx() as bn_ctx:
-            res = backend._lib.EC_POINT_oct2point(
-                curve.ec_group, point, data, len(data), bn_ctx);
-            backend.openssl_assert(res == 1)
-
-        return cls(point, curve)
-
-    def to_bytes(self, is_compressed: bool=True) -> bytes:
-        """
-        Returns the Point serialized as bytes. It will return a compressed form
-        if is_compressed is set to True.
-        """
-        length = self.expected_bytes_length(self.curve, is_compressed)
-
-        if is_compressed:
-            point_conversion_form = backend._lib.POINT_CONVERSION_COMPRESSED
-        else:
-            point_conversion_form = backend._lib.POINT_CONVERSION_UNCOMPRESSED
-
-        bin_ptr = backend._ffi.new("unsigned char[]", length)
-        with backend._tmp_bn_ctx() as bn_ctx:
-            bin_len = backend._lib.EC_POINT_point2oct(
-                self.curve.ec_group, self.ec_point, point_conversion_form, 
-                bin_ptr, length, bn_ctx
-            )
-            backend.openssl_assert(bin_len != 0)
-
-        return bytes(backend._ffi.buffer(bin_ptr, bin_len)[:])
-
-    @classmethod
-    def get_generator_from_curve(cls, curve: Optional[Curve] = None) -> 'Point':
-        """
-        Returns the generator Point from the given curve as a Point object.
-        """
-        curve = curve if curve is not None else default_curve()
-        return cls(curve.generator, curve)
-
-    def __eq__(self, other):
-        """
-        Compares two EC_POINTS for equality.
-        """
-        with backend._tmp_bn_ctx() as bn_ctx:
-            is_equal = backend._lib.EC_POINT_cmp(
-                self.curve.ec_group, self.ec_point, other.ec_point, bn_ctx
-            )
-            backend.openssl_assert(is_equal != -1)
-
-        # 1 is not-equal, 0 is equal, -1 is error
-        return not bool(is_equal)
-
-    def __mul__(self, other: CurveBN) -> 'Point':
-        """
-        Performs an EC_POINT_mul on an EC_POINT and a BIGNUM.
-        """
-        # TODO: Check that both points use the same curve.
-        prod = openssl._get_new_EC_POINT(self.curve)
-        with backend._tmp_bn_ctx() as bn_ctx:
-            res = backend._lib.EC_POINT_mul(
-                self.curve.ec_group, prod, backend._ffi.NULL,
-                self.ec_point, other.bignum, bn_ctx
-            )
-            backend.openssl_assert(res == 1)
-
-        return Point(prod, self.curve)
-
-    __rmul__ = __mul__
-
-    def __add__(self, other) -> 'Point':
-        """
-        Performs an EC_POINT_add on two EC_POINTS.
-        """
-        op_sum = openssl._get_new_EC_POINT(self.curve)
-        with backend._tmp_bn_ctx() as bn_ctx:
-            res = backend._lib.EC_POINT_add(
-                self.curve.ec_group, op_sum, self.ec_point, other.ec_point, bn_ctx
-            )
-            backend.openssl_assert(res == 1)
-        return Point(op_sum, self.curve)
-
-    def __sub__(self, other):
-        """
-        Performs subtraction by adding the inverse of the `other` to the point.
-        """
-        return (self + (-other))
-
-    def __neg__(self) -> 'Point':
-        """
-        Computes the additive inverse of a Point, by performing an 
-        EC_POINT_invert on itself.
-        """
-        inv = backend._lib.EC_POINT_dup(self.ec_point, self.curve.ec_group)
-        backend.openssl_assert(inv != backend._ffi.NULL)
-        inv = backend._ffi.gc(inv, backend._lib.EC_POINT_clear_free)
-
-        with backend._tmp_bn_ctx() as bn_ctx:
-            res = backend._lib.EC_POINT_invert(
-                self.curve.ec_group, inv, bn_ctx
-            )
-            backend.openssl_assert(res == 1)
-        return Point(inv, self.curve)
-
-    def __bytes__(self) -> bytes:
-        return self.to_bytes()
diff --git a/umbral/pre.py b/umbral/pre.py
index 60fbacef..df02ef31 100644
--- a/umbral/pre.py
+++ b/umbral/pre.py
@@ -1,518 +1,57 @@
-"""
-This file is part of pyUmbral.
+from typing import Tuple, Optional, Sequence
 
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
+from .capsule import Capsule
+from .capsule_frag import CapsuleFrag
+from .dem import DEM
+from .keys import PublicKey, SecretKey
+from .key_frag import KeyFrag
 
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
 
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-import os
-import typing
-from typing import Dict, List, Optional, Tuple, Union, Any
-
-from bytestring_splitter import BytestringSplitter
-from cryptography.exceptions import InvalidTag
-from constant_sorrow import constants
-
-from umbral.cfrags import CapsuleFrag
-from umbral.config import default_curve
-from umbral.curve import Curve
-from umbral.curvebn import CurveBN
-from umbral.dem import UmbralDEM, DEM_KEYSIZE, DEM_NONCE_SIZE
-from umbral.keys import UmbralPrivateKey, UmbralPublicKey
-from umbral.kfrags import KFrag, NO_KEY, DELEGATING_ONLY, RECEIVING_ONLY, DELEGATING_AND_RECEIVING
-from umbral.params import UmbralParameters
-from umbral.point import Point
-from umbral.random_oracles import kdf, hash_to_curvebn
-from umbral.signing import Signer
-from umbral.utils import poly_eval, lambda_coeff
-
-
-class GenericUmbralError(Exception):
-    pass
-
-
-class UmbralCorrectnessError(GenericUmbralError):
-    def __init__(self, message: str, offending_cfrags: List[CapsuleFrag]) -> None:
-        super().__init__(message)
-        self.offending_cfrags = offending_cfrags
-
-
-class UmbralDecryptionError(GenericUmbralError):
-    def __init__(self) -> None:
-        super().__init__("Decryption of ciphertext failed: "
-                         "either someone tampered with the ciphertext or "
-                         "you are using an incorrect decryption key.")
-
-
-class Capsule:
-
-    def __init__(self,
-                 params: UmbralParameters,
-                 point_e: Point,
-                 point_v: Point,
-                 bn_sig: CurveBN,
-                 ) -> None:
-
-        self.params = params
-
-        if not all((isinstance(point_e, Point),
-                    isinstance(point_v, Point),
-                    isinstance(bn_sig, CurveBN))):
-            raise TypeError("Need valid point_e, point_v, and bn_sig to make a Capsule.")
-
-        self.point_e = point_e
-        self.point_v = point_v
-        self.bn_sig = bn_sig
-
-        self._attached_cfrags = set()    # type: set
-        self._cfrag_correctness_keys = {
-            'delegating': None, 'receiving': None, 'verifying': None
-        }   # type: dict
-
-    class NotValid(ValueError):
-        """
-        raised if the capsule does not pass verification.
-        """
-
-    @classmethod
-    def expected_bytes_length(cls, curve: Optional[Curve] = None) -> int:
-        """
-        Returns the size (in bytes) of a Capsule given the curve.
-        If no curve is provided, it will use the default curve.
-        """
-        curve = curve if curve is not None else default_curve()
-        bn_size = CurveBN.expected_bytes_length(curve)
-        point_size = Point.expected_bytes_length(curve)
-
-        return (bn_size * 1) + (point_size * 2)
-
-    @classmethod
-    def from_bytes(cls, capsule_bytes: bytes, params: UmbralParameters) -> 'Capsule':
-        """
-        Instantiates a Capsule object from the serialized data.
-        """
-        curve = params.curve
-
-        bn_size = CurveBN.expected_bytes_length(curve)
-        point_size = Point.expected_bytes_length(curve)
-        arguments = {'curve': curve}
-
-        if len(capsule_bytes) == cls.expected_bytes_length(curve):
-            splitter = BytestringSplitter(
-                (Point, point_size, arguments),  # point_e
-                (Point, point_size, arguments),  # point_v
-                (CurveBN, bn_size, arguments)  # bn_sig
-            )
-        else:
-            raise ValueError("Byte string does not have a valid length for a Capsule")
-
-        components = splitter(capsule_bytes)
-        return cls(params, *components)
-
-    def _set_cfrag_correctness_key(self, key_type: str, key: Optional[UmbralPublicKey]) -> bool:
-        if key_type not in ("delegating", "receiving", "verifying"): 
-            raise ValueError("You can only set 'delegating', 'receiving' or 'verifying' keys.") 
-
-        current_key = self._cfrag_correctness_keys[key_type]
-
-        if current_key is None:
-            if key is None:
-                return False
-            elif self.params != key.params:
-                raise TypeError("You are trying to set a key with different UmbralParameters.")
-            else:
-                self._cfrag_correctness_keys[key_type] = key
-                return True
-        elif key in (None, current_key):
-            return False
-        else:
-            raise ValueError("The {} key is already set; you can't set it again.".format(key_type))
-
-    def get_correctness_keys(self) -> Dict[str, Union[UmbralPublicKey, None]]:
-        return dict(self._cfrag_correctness_keys)
-
-    def set_correctness_keys(self,
-                             delegating: Optional[UmbralPublicKey] = None,
-                             receiving: Optional[UmbralPublicKey] = None,
-                             verifying: Optional[UmbralPublicKey] = None,
-                             ) -> Tuple[bool, bool, bool]:
-
-        delegating_key_details = self._set_cfrag_correctness_key(key_type="delegating", key=delegating)
-        receiving_key_details = self._set_cfrag_correctness_key(key_type="receiving", key=receiving)
-        verifying_key_details = self._set_cfrag_correctness_key(key_type="verifying", key=verifying)
-
-        return delegating_key_details, receiving_key_details, verifying_key_details
-
-    def to_bytes(self) -> bytes:
-        """
-        Serialize the Capsule into a bytestring.
-        """
-        e, v, s = self.components()
-        return e.to_bytes() + v.to_bytes() + s.to_bytes()
-
-    def verify(self) -> bool:
-
-        g = self.params.g
-        e, v, s = self.components()
-        h = hash_to_curvebn(e, v, params=self.params)
-
-        result = s * g == v + (h * e)      # type: bool
-        return result
-
-    def attach_cfrag(self, cfrag: CapsuleFrag) -> None:
-        if cfrag.verify_correctness(self):
-            self._attached_cfrags.add(cfrag)
-        else:
-            error_msg = "CFrag is not correct and cannot be attached to the Capsule"
-            raise UmbralCorrectnessError(error_msg, [cfrag])
-
-    def clear_cfrags(self):
-        self._attached_cfrags = set()
-
-    def first_cfrag(self):
-        try:
-            return list(self._attached_cfrags)[0]
-        except IndexError:
-            raise TypeError("This Capsule doesn't have any CFrags attached.  Ergo, you can't get the first one.")
-
-    def components(self) -> Tuple[Point, Point, CurveBN]:
-        return self.point_e, self.point_v, self.bn_sig
-
-    def __bytes__(self) -> bytes:
-        return self.to_bytes()
-
-    def __contains__(self, cfrag):
-        return cfrag in self._attached_cfrags
-
-    def __eq__(self, other) -> bool:
-        """
-        Each component is compared to its counterpart in constant time per the __eq__ of Point and CurveBN.
-        """
-        return hasattr(other, "components") and self.components() == other.components() and all(self.components())
-
-    @typing.no_type_check
-    def __hash__(self) -> int:
-        # In case this isn't obvious, don't use this as a secure hash.  Use BLAKE2b or something.
-        component_bytes = tuple(component.to_bytes() for component in self.components())
-        return hash(component_bytes)
-
-    def __len__(self) -> int:
-        return len(self._attached_cfrags)
-
-    def __repr__(self):
-        return "{}:{}".format(self.__class__.__name__, hex(int(self.bn_sig))[2:17])
-
-
-def generate_kfrags(delegating_privkey: UmbralPrivateKey,
-                    receiving_pubkey: UmbralPublicKey,
-                    threshold: int,
-                    N: int,
-                    signer: Signer,
-                    sign_delegating_key: Optional[bool] = True,
-                    sign_receiving_key: Optional[bool] = True,
-                    ) -> List[KFrag]:
+def encrypt(pk: PublicKey, plaintext: bytes) -> Tuple[Capsule, bytes]:
     """
-    Creates a re-encryption key from Alice's delegating public key to Bob's
-    receiving public key, and splits it in KFrags, using Shamir's Secret Sharing.
-    Requires a threshold number of KFrags out of N.
+    Generates and encapsulates a symmetric key and uses it to encrypt the given plaintext.
 
-    Returns a list of N KFrags
+    Returns the KEM Capsule and the ciphertext.
     """
+    capsule, key_seed = Capsule.from_public_key(pk)
+    dem = DEM(bytes(key_seed))
+    ciphertext = dem.encrypt(plaintext, authenticated_data=bytes(capsule))
+    return capsule, ciphertext
 
-    if threshold <= 0 or threshold > N:
-        raise ValueError('Arguments threshold and N must satisfy 0 < threshold <= N')
-
-    if delegating_privkey.params != receiving_pubkey.params:
-        raise ValueError("Keys must have the same parameter set.")
-
-    params = delegating_privkey.params
-
-    g = params.g
-
-    delegating_pubkey = delegating_privkey.get_pubkey()
-
-    bob_pubkey_point = receiving_pubkey.point_key
-
-    # The precursor point is used as an ephemeral public key in a DH key exchange,
-    # and the resulting shared secret 'dh_point' is used to derive other secret values
-    private_precursor = CurveBN.gen_rand(params.curve)
-    precursor = private_precursor * g   # type: Any
-
-    dh_point = private_precursor * bob_pubkey_point
-
-    # Secret value 'd' allows to make Umbral non-interactive
-    d = hash_to_curvebn(precursor,
-                        bob_pubkey_point,
-                        dh_point,
-                        bytes(constants.NON_INTERACTIVE),
-                        params=params)
-
-    # Coefficients of the generating polynomial
-    coefficients = [delegating_privkey.bn_key * (~d)]
-    coefficients += [CurveBN.gen_rand(params.curve) for _ in range(threshold - 1)]
-
-    bn_size = CurveBN.expected_bytes_length(params.curve)
-
-    kfrags = list()
-    for _ in range(N):
-        kfrag_id = os.urandom(bn_size)
-
-        # The index of the re-encryption key share (which in Shamir's Secret
-        # Sharing corresponds to x in the tuple (x, f(x)), with f being the
-        # generating polynomial), is used to prevent reconstruction of the
-        # re-encryption key without Bob's intervention
-        share_index = hash_to_curvebn(precursor,
-                                      bob_pubkey_point,
-                                      dh_point,
-                                      bytes(constants.X_COORDINATE),
-                                      kfrag_id,
-                                      params=params)
-
-        # The re-encryption key share is the result of evaluating the generating
-        # polynomial for the index value
-        rk = poly_eval(coefficients, share_index)
 
-        commitment = rk * params.u  # type: Any
-
-        validity_message_for_bob = (kfrag_id,
-                                    delegating_pubkey,
-                                    receiving_pubkey,
-                                    commitment,
-                                    precursor,
-                                    )  # type: Any
-        validity_message_for_bob = bytes().join(bytes(item) for item in validity_message_for_bob)
-        signature_for_bob = signer(validity_message_for_bob)
-
-        if sign_delegating_key and sign_receiving_key:
-            mode = DELEGATING_AND_RECEIVING
-        elif sign_delegating_key:
-            mode = DELEGATING_ONLY
-        elif sign_receiving_key:
-            mode = RECEIVING_ONLY
-        else:
-            mode = NO_KEY
-
-        validity_message_for_proxy = [kfrag_id, commitment, precursor, mode]  # type: Any
-
-        if sign_delegating_key:
-            validity_message_for_proxy.append(delegating_pubkey)
-        if sign_receiving_key:
-            validity_message_for_proxy.append(receiving_pubkey)
-
-        validity_message_for_proxy = bytes().join(bytes(item) for item in validity_message_for_proxy)
-        signature_for_proxy = signer(validity_message_for_proxy)
-
-        kfrag = KFrag(identifier=kfrag_id,
-                      bn_key=rk,
-                      point_commitment=commitment,
-                      point_precursor=precursor,
-                      signature_for_proxy=signature_for_proxy,
-                      signature_for_bob=signature_for_bob,
-                      keys_in_signature=mode,
-                      )
-
-        kfrags.append(kfrag)
-
-    return kfrags
-
-
-def reencrypt(kfrag: KFrag,
-              capsule: Capsule,
-              provide_proof: bool = True,
-              metadata: Optional[bytes] = None,
-              verify_kfrag: bool = True) -> CapsuleFrag:
-
-    if not isinstance(capsule, Capsule) or not capsule.verify():
-        raise Capsule.NotValid
-
-    if verify_kfrag:
-        if not isinstance(kfrag, KFrag) or not kfrag.verify_for_capsule(capsule):
-            raise KFrag.NotValid
-
-    rk = kfrag.bn_key
-    e1 = rk * capsule.point_e  # type: Any
-    v1 = rk * capsule.point_v  # type: Any
-
-    cfrag = CapsuleFrag(point_e1=e1, point_v1=v1, kfrag_id=kfrag.id,
-                        point_precursor=kfrag.point_precursor)
-
-    if provide_proof:
-        cfrag.prove_correctness(capsule, kfrag, metadata)
-
-    return cfrag
-
-
-def _encapsulate(alice_pubkey: UmbralPublicKey, 
-                 key_length: int = DEM_KEYSIZE) -> Tuple[bytes, Capsule]:
-    """Generates a symmetric key and its associated KEM ciphertext"""
-
-    params = alice_pubkey.params
-    g = params.g
-
-    priv_r = CurveBN.gen_rand(params.curve)
-    pub_r = priv_r * g  # type: Any
-
-    priv_u = CurveBN.gen_rand(params.curve)
-    pub_u = priv_u * g  # type: Any
-
-    h = hash_to_curvebn(pub_r, pub_u, params=params)
-    s = priv_u + (priv_r * h)
-
-    shared_key = (priv_r + priv_u) * alice_pubkey.point_key  # type: Any
-
-    # Key to be used for symmetric encryption
-    key = kdf(shared_key, key_length)
-
-    return key, Capsule(point_e=pub_r, point_v=pub_u, bn_sig=s, params=params)
-
-
-def _decapsulate_original(private_key: UmbralPrivateKey,
-                          capsule: Capsule,
-                          key_length: int = DEM_KEYSIZE) -> bytes:
-    """Derive the same symmetric key"""
-
-    if not capsule.verify():
-        # Check correctness of original ciphertext
-        raise capsule.NotValid("Capsule verification failed.")
-
-    shared_key = private_key.bn_key * (capsule.point_e + capsule.point_v)  # type: Any
-    key = kdf(shared_key, key_length)
-    return key
-
-
-def _decapsulate_reencrypted(receiving_privkey: UmbralPrivateKey, capsule: Capsule,
-                             key_length: int = DEM_KEYSIZE) -> bytes:
-    """Derive the same symmetric encapsulated_key"""
-
-    params = capsule.params
-
-    pub_key = receiving_privkey.get_pubkey().point_key
-    priv_key = receiving_privkey.bn_key
-
-    precursor = capsule.first_cfrag().point_precursor
-    dh_point = priv_key * precursor
-
-    # Combination of CFrags via Shamir's Secret Sharing reconstruction
-    xs = list()
-    for cfrag in capsule._attached_cfrags:
-        x = hash_to_curvebn(precursor,
-                            pub_key,
-                            dh_point,
-                            bytes(constants.X_COORDINATE),
-                            cfrag.kfrag_id,
-                            params=params)
-        xs.append(x)
-
-    e_summands, v_summands = list(), list()
-    for cfrag, x in zip(capsule._attached_cfrags, xs):
-        if precursor != cfrag.point_precursor:
-            raise ValueError("Attached CFrags are not pairwise consistent")
-        lambda_i = lambda_coeff(x, xs)
-        e_summands.append(lambda_i * cfrag.point_e1)
-        v_summands.append(lambda_i * cfrag.point_v1)
-
-    e_prime = sum(e_summands[1:], e_summands[0])
-    v_prime = sum(v_summands[1:], v_summands[0])
-
-    # Secret value 'd' allows to make Umbral non-interactive
-    d = hash_to_curvebn(precursor,
-                        pub_key,
-                        dh_point,
-                        bytes(constants.NON_INTERACTIVE),
-                        params=params)
-
-    e, v, s = capsule.components()
-    h = hash_to_curvebn(e, v, params=params)
-
-    orig_pub_key = capsule.get_correctness_keys()['delegating'].point_key  # type: ignore
-
-    if not (s / d) * orig_pub_key == (h * e_prime) + v_prime:
-        raise GenericUmbralError()
-
-    shared_key = d * (e_prime + v_prime)
-    encapsulated_key = kdf(shared_key, key_length)
-    return encapsulated_key
-
-
-def encrypt(alice_pubkey: UmbralPublicKey, plaintext: bytes) -> Tuple[bytes, Capsule]:
+def decrypt_original(sk: SecretKey, capsule: Capsule, ciphertext: bytes) -> bytes:
     """
-    Performs an encryption using the UmbralDEM object and encapsulates a key
-    for the sender using the public key provided.
-
-    Returns the ciphertext and the KEM Capsule.
+    Opens the capsule using the original (Alice's) key used for encryption and gets what's inside.
+    We hope that's a symmetric key, which we use to decrypt the ciphertext
+    and return the resulting cleartext.
     """
-    key, capsule = _encapsulate(alice_pubkey, DEM_KEYSIZE)
-
-    capsule_bytes = bytes(capsule)
+    key_seed = capsule.open_original(sk)
+    dem = DEM(bytes(key_seed))
+    return dem.decrypt(ciphertext, authenticated_data=bytes(capsule))
 
-    dem = UmbralDEM(key)
-    ciphertext = dem.encrypt(plaintext, authenticated_data=capsule_bytes)
 
-    return ciphertext, capsule
-
-
-def _open_capsule(capsule: Capsule, receiving_privkey: UmbralPrivateKey,
-                  check_proof: bool = True) -> bytes:
+def reencrypt(capsule: Capsule, kfrag: KeyFrag, metadata: Optional[bytes] = None) -> CapsuleFrag:
     """
-    Activates the Capsule from the attached CFrags,
-    opens the Capsule and returns what is inside.
+    Creates a capsule fragment using the given key fragment.
+    Capsule fragments can later be used to decrypt the ciphertext.
 
-    This will often be a symmetric key.
+    If `metadata` is provided, it will have to be used for verification in
+    :py:meth:`CapsuleFrag.verify`.
     """
+    return CapsuleFrag.reencrypted(capsule, kfrag, metadata)
 
-    if check_proof:
-        offending_cfrags = []
-        for cfrag in capsule._attached_cfrags:
-            if not cfrag.verify_correctness(capsule):
-                offending_cfrags.append(cfrag)
-
-        if offending_cfrags:
-            error_msg = "Decryption error: Some CFrags are not correct"
-            raise UmbralCorrectnessError(error_msg, offending_cfrags)
 
-    key = _decapsulate_reencrypted(receiving_privkey, capsule)
-    return key
-
-
-def decrypt(ciphertext: bytes, capsule: Capsule, decrypting_key: UmbralPrivateKey,
-            check_proof: bool = True) -> bytes:
+def decrypt_reencrypted(decrypting_sk: SecretKey,
+                        delegating_pk: PublicKey,
+                        capsule: Capsule,
+                        cfrags: Sequence[CapsuleFrag],
+                        ciphertext: bytes,
+                        ) -> bytes:
     """
-    Opens the capsule and gets what's inside.
-
-    We hope that's a symmetric key, which we use to decrypt the ciphertext
-    and return the resulting cleartext.
+    Decrypts the ciphertext using the original capsule and the reencrypted capsule fragments.
     """
 
-    if not isinstance(ciphertext, bytes) or len(ciphertext) < DEM_NONCE_SIZE:
-        raise ValueError("Input ciphertext must be a bytes object of length >= {}".format(DEM_NONCE_SIZE))
-    elif not isinstance(capsule, Capsule) or not capsule.verify():
-        raise Capsule.NotValid
-    elif not isinstance(decrypting_key, UmbralPrivateKey):
-        raise TypeError("The decrypting key is not an UmbralPrivateKey")
-
-    if capsule._attached_cfrags:
-        # Since there are cfrags attached, we assume this is Bob opening the Capsule.
-        # (i.e., this is a re-encrypted capsule)
-        encapsulated_key = _open_capsule(capsule, decrypting_key, check_proof=check_proof)
-    else:
-        # Since there aren't cfrags attached, we assume this is Alice opening the Capsule.
-        # (i.e., this is an original capsule)
-        encapsulated_key = _decapsulate_original(decrypting_key, capsule)
-
-    dem = UmbralDEM(encapsulated_key)
-    try:
-        cleartext = dem.decrypt(ciphertext, authenticated_data=bytes(capsule))
-    except InvalidTag as e:
-        raise UmbralDecryptionError() from e
+    key_seed = capsule.open_reencrypted(decrypting_sk, delegating_pk, cfrags)
+    dem = DEM(bytes(key_seed))
+    return dem.decrypt(ciphertext, authenticated_data=bytes(capsule))
 
-    return cleartext
diff --git a/umbral/random_oracles.py b/umbral/random_oracles.py
deleted file mode 100644
index 427e0a28..00000000
--- a/umbral/random_oracles.py
+++ /dev/null
@@ -1,214 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-from abc import abstractmethod, ABC
-from typing import Optional, Type
-
-from cryptography.hazmat.backends.openssl import backend
-from cryptography.hazmat.backends import default_backend
-from cryptography.hazmat.primitives import hashes
-from cryptography.hazmat.primitives.kdf.hkdf import HKDF
-from cryptography.exceptions import InternalError
-
-import sha3
-
-from umbral import openssl
-from umbral.curvebn import CurveBN
-from umbral.point import Point
-from umbral.params import UmbralParameters
-from umbral.config import default_params
-
-
-class Hash(ABC):
-
-    CUSTOMIZATION_STRING_LENGTH = 64
-    CUSTOMIZATION_STRING_PAD = b'\x00'
-
-    @abstractmethod
-    def __init__(self, customization_string: bytes = b''):
-
-        if len(customization_string) > Hash.CUSTOMIZATION_STRING_LENGTH:
-            raise ValueError("The maximum length of the customization string is "
-                             "{} bytes".format(Hash.CUSTOMIZATION_STRING_LENGTH))
-
-        self.customization_string = customization_string.ljust(
-            Hash.CUSTOMIZATION_STRING_LENGTH,
-            Hash.CUSTOMIZATION_STRING_PAD
-        )
-        self.update(self.customization_string)
-
-    @abstractmethod
-    def update(self, data: bytes) -> None:
-        raise NotImplementedError
-
-    @abstractmethod
-    def copy(self) -> 'Hash':
-        raise NotImplementedError
-
-    @abstractmethod
-    def finalize(self) -> bytes:
-        raise NotImplementedError
-
-
-class Blake2b(Hash):
-    def __init__(self, customization_string: bytes = b''):
-        # TODO: use a Blake2b implementation that supports personalization (see #155)
-        self._blake2b = hashes.Hash(hashes.BLAKE2b(64), backend=backend)
-        super().__init__(customization_string)
-
-    def update(self, data: bytes) -> None:
-        self._blake2b.update(data)
-
-    def copy(self) -> 'Blake2b':
-        replica = type(self)()
-        replica._blake2b = self._blake2b.copy()
-        return replica
-
-    def finalize(self) -> bytes:
-        return self._blake2b.finalize()
-
-
-class ExtendedKeccak(Hash):
-
-    _UPPER_PREFIX = b'\x00'
-    _LOWER_PREFIX = b'\x01'
-
-    def __init__(self, customization_string: bytes = b''):
-        self._upper = sha3.keccak_256()
-        self._lower = sha3.keccak_256()
-
-        self._upper.update(self._UPPER_PREFIX)
-        self._lower.update(self._LOWER_PREFIX)
-
-        super().__init__(customization_string)
-
-    def update(self, data: bytes) -> None:
-        self._upper.update(data)
-        self._lower.update(data)
-
-    def copy(self) -> 'ExtendedKeccak':
-        replica = type(self)()
-        replica._upper = self._upper.copy()
-        replica._lower = self._lower.copy()
-        return replica
-
-    def finalize(self) -> bytes:
-        return self._upper.digest() + self._lower.digest()
-
-
-def kdf(ecpoint: Point,
-        key_length: int,
-        salt: Optional[bytes] = None,
-        info: Optional[bytes] = None,
-        ) -> bytes:
-
-    data = ecpoint.to_bytes(is_compressed=True)
-    hkdf = HKDF(algorithm=hashes.BLAKE2b(64),
-                length=key_length,
-                salt=salt,
-                info=info,
-                backend=default_backend())
-    return hkdf.derive(data)
-
-
-# TODO: Common API for all hash_to_curvebn functions.
-# TODO: ^ It should check the correct number and type args, instead of current approach.
-def hash_to_curvebn(*crypto_items,
-                    params: UmbralParameters,
-                    customization_string: bytes = b'',
-                    hash_class: Type[Hash] = Blake2b) -> CurveBN:
-
-    customization_string = b'hash_to_curvebn' + customization_string
-    hash_function = hash_class(customization_string=customization_string)
-
-    for item in crypto_items:
-        try:
-            item_bytes = item.to_bytes()
-        except AttributeError:
-            if isinstance(item, bytes):
-                item_bytes = item
-            else:
-                raise TypeError("Input with type {} not accepted".format(type(item)))
-        hash_function.update(item_bytes)
-
-    hash_digest = openssl._bytes_to_bn(hash_function.finalize())
-
-    one = backend._lib.BN_value_one()
-
-    order_minus_1 = openssl._get_new_BN()
-    res = backend._lib.BN_sub(order_minus_1, params.curve.order, one)
-    backend.openssl_assert(res == 1)
-
-    bignum = openssl._get_new_BN()
-    with backend._tmp_bn_ctx() as bn_ctx:
-        res = backend._lib.BN_mod(bignum, hash_digest, order_minus_1, bn_ctx)
-        backend.openssl_assert(res == 1)
-
-    res = backend._lib.BN_add(bignum, bignum, one)
-    backend.openssl_assert(res == 1)
-
-    return CurveBN(bignum, params.curve)
-
-
-def unsafe_hash_to_point(data: bytes = b'',
-                         params: UmbralParameters = None,
-                         label: bytes = b'',
-                         hash_class = Blake2b,
-                         ) -> 'Point':
-    """
-    Hashes arbitrary data into a valid EC point of the specified curve,
-    using the try-and-increment method.
-    It admits an optional label as an additional input to the hash function.
-    It uses BLAKE2b (with a digest size of 64 bytes) as the internal hash function.
-
-    WARNING: Do not use when the input data is secret, as this implementation is not
-    in constant time, and hence, it is not safe with respect to timing attacks.
-    """
-
-    params = params if params is not None else default_params()
-
-    len_data = len(data).to_bytes(4, byteorder='big')
-    len_label = len(label).to_bytes(4, byteorder='big')
-
-    label_data = len_label + label + len_data + data
-
-    # We use an internal 32-bit counter as additional input
-    i = 0
-    while i < 2**32:
-        ibytes = i.to_bytes(4, byteorder='big')
-        hash_function = hash_class()
-        hash_function.update(label_data + ibytes)
-        hash_digest = hash_function.finalize()[:1 + params.CURVE_KEY_SIZE_BYTES]
-
-        sign = b'\x02' if hash_digest[0] & 1 == 0 else b'\x03'
-        compressed_point = sign + hash_digest[1:]
-
-        try:
-            return Point.from_bytes(compressed_point, params.curve)
-        except InternalError as e:
-            # We want to catch specific InternalExceptions:
-            # - Point not in the curve (code 107)
-            # - Invalid compressed point (code 110)
-            # https://github.com/openssl/openssl/blob/master/include/openssl/ecerr.h#L228
-            if e.err_code[0].reason in (107, 110):
-                pass
-            else:
-                # Any other exception, we raise it
-                raise e
-        i += 1
-
-    # Only happens with probability 2^(-32)
-    raise ValueError('Could not hash input into the curve')
diff --git a/umbral/serializable.py b/umbral/serializable.py
new file mode 100644
index 00000000..27968fde
--- /dev/null
+++ b/umbral/serializable.py
@@ -0,0 +1,74 @@
+from abc import abstractmethod, ABC
+from typing import Tuple, Type, List, Any, TypeVar
+
+
+class Serializable(ABC):
+    """
+    A mixin for composable serialization.
+    """
+
+    _T = TypeVar('_T', bound='Serializable')
+
+    @classmethod
+    def from_bytes(cls: Type[_T], data: bytes) -> _T:
+        """
+        Restores the object from serialized bytes.
+        """
+        obj, remainder = cls.__take__(data)
+        if len(remainder) != 0:
+            raise ValueError(f"{len(remainder)} bytes remaining after deserializing {cls}")
+        return obj
+
+    @classmethod
+    def __take_bytes__(cls, data: bytes, size: int) -> Tuple[bytes, bytes]:
+        """
+        Takes ``size`` bytes from the bytestring and returns them along with the remainder.
+        """
+        if len(data) < size:
+            raise ValueError(f"{cls} cannot take {size} bytes from a bytestring of size {len(data)}")
+        return data[:size], data[size:]
+
+    @classmethod
+    def __take_types__(cls, data: bytes, *types: Type) -> Tuple[List[Any], bytes]:
+        """
+        Given a list of ``Serializable`` types, attempts to deserialize them from the bytestring
+        one by one and returns the list of the resulting objects and the remaining bytestring.
+        """
+        objs = []
+        for tp in types:
+            obj, data = tp.__take__(data)
+            objs.append(obj)
+        return objs, data
+
+    @classmethod
+    @abstractmethod
+    def __take__(cls: Type[_T], data: bytes) -> Tuple[_T, bytes]:
+        """
+        Take however much is necessary from ``data`` and instantiate the object,
+        returning it and the remaining bytestring.
+
+        Must be implemented by the derived class.
+        """
+        raise NotImplementedError
+
+    @abstractmethod
+    def __bytes__(self):
+        """
+        Serializes the object into bytes.
+        """
+        raise NotImplementedError
+
+
+def serialize_bool(b: bool) -> bytes:
+    return b'\x01' if b else b'\x00'
+
+
+def take_bool(data: bytes) -> Tuple[bool, bytes]:
+    bool_bytes, data = Serializable.__take_bytes__(data, 1)
+    if bool_bytes == b'\x01':
+        b = True
+    elif bool_bytes == b'\x00':
+        b = False
+    else:
+        raise ValueError(f"Incorrectly serialized boolean; expected b'\\x00' or b'\\x01', got {repr(bool_bytes)}")
+    return b, data
diff --git a/umbral/signing.py b/umbral/signing.py
deleted file mode 100644
index d473d1fd..00000000
--- a/umbral/signing.py
+++ /dev/null
@@ -1,151 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published b
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-import hmac
-from typing import Optional, Type
-
-from cryptography.exceptions import InvalidSignature
-from cryptography.hazmat.primitives.hashes import HashAlgorithm, SHA256
-from cryptography.hazmat.primitives.asymmetric import utils
-from cryptography.hazmat.primitives.asymmetric.ec import ECDSA
-
-
-from umbral.config import default_curve
-from umbral.curve import Curve
-from umbral.curvebn import CurveBN
-from umbral.keys import UmbralPublicKey, UmbralPrivateKey
-
-
-DEFAULT_HASH_ALGORITHM = SHA256
-
-
-class Signature:
-    """
-    Wrapper for ECDSA signatures.
-    We store signatures as r and s; this class allows interoperation
-    between (r, s) and DER formatting.
-    """
-
-    def __init__(self,
-                 r: CurveBN,
-                 s: CurveBN,
-                 hash_algorithm: Type[HashAlgorithm] = DEFAULT_HASH_ALGORITHM) -> None:
-        self.r = r
-        self.s = s
-        self.hash_algorithm = hash_algorithm
-
-    def __repr__(self):
-        return "ECDSA Signature: {}".format(bytes(self).hex()[:15])
-
-    @classmethod
-    def expected_bytes_length(cls, curve: Optional[Curve] = None) -> int:
-        curve = curve if curve is not None else default_curve()
-        return 2 * curve.group_order_size_in_bytes
-
-    def verify(self, message: bytes,
-               verifying_key: UmbralPublicKey,
-               is_prehashed: bool = False) -> bool:
-        """
-        Verifies that a message's signature was valid.
-
-        :param message: The message to verify
-        :param verifying_key: UmbralPublicKey of the signer
-        :param is_prehashed: True if the message has been prehashed previously
-        :return: True if valid, False if invalid
-        """
-        cryptography_pub_key = verifying_key.to_cryptography_pubkey()
-        if is_prehashed:
-            signature_algorithm = ECDSA(utils.Prehashed(self.hash_algorithm()))
-        else:
-            signature_algorithm = ECDSA(self.hash_algorithm())
-
-        # TODO: Raise error instead of returning boolean
-        try:
-            cryptography_pub_key.verify(
-                signature=self._der_encoded_bytes(),
-                data=message,
-                signature_algorithm=signature_algorithm
-            )
-        except InvalidSignature:
-            return False
-        return True
-
-    @classmethod
-    def from_bytes(cls,
-                   signature_as_bytes: bytes,
-                   der_encoded: bool = False,
-                   curve: Optional[Curve] = None) -> 'Signature':
-        curve = curve if curve is not None else default_curve()
-        if der_encoded:
-            r, s = utils.decode_dss_signature(signature_as_bytes)
-        else:
-            expected_len = cls.expected_bytes_length(curve)
-            if not len(signature_as_bytes) == expected_len:
-                raise ValueError("Looking for exactly {} bytes if you call from_bytes \
-                    with der_encoded=False and curve={}.".format(expected_len, curve))
-            else:
-                r = int.from_bytes(signature_as_bytes[:(expected_len//2)], "big")
-                s = int.from_bytes(signature_as_bytes[(expected_len//2):], "big")
-
-        return cls(CurveBN.from_int(r, curve), CurveBN.from_int(s, curve))
-
-    def _der_encoded_bytes(self) -> bytes:
-        return utils.encode_dss_signature(int(self.r), int(self.s))
-
-    def __bytes__(self) -> bytes:
-        return self.r.to_bytes() + self.s.to_bytes()
-
-    def __len__(self):
-        return len(bytes(self))
-
-    def __add__(self, other):
-        return bytes(self) + other
-
-    def __radd__(self, other: bytes) -> bytes:
-        return other + bytes(self)
-
-    def __eq__(self, other) -> bool:
-        simple_bytes_match = hmac.compare_digest(bytes(self), bytes(other))
-        der_encoded_match = hmac.compare_digest(self._der_encoded_bytes(), bytes(other))
-        return simple_bytes_match or der_encoded_match
-
-
-class Signer:
-    """Callable wrapping ECDSA signing with UmbralPrivateKeys"""
-
-    def __init__(self,
-                 private_key: UmbralPrivateKey,
-                 hash_algorithm: Type[HashAlgorithm] = DEFAULT_HASH_ALGORITHM) -> None:
-        self.__cryptography_private_key = private_key.to_cryptography_privkey()
-        self.curve = private_key.params.curve
-        self.hash_algorithm = hash_algorithm
-
-    def __call__(self, message: bytes, is_prehashed: bool = False) -> Signature:
-        """
-         Signs the message with this instance's private key.
-
-         :param message: Message to hash and sign
-         :param is_prehashed: True if the message has been prehashed previously
-         :return: signature
-         """
-        if is_prehashed:
-            signature_algorithm = ECDSA(utils.Prehashed(self.hash_algorithm()))
-        else:
-            signature_algorithm = ECDSA(self.hash_algorithm())
-
-        signature_der_bytes = self.__cryptography_private_key.sign(message, signature_algorithm)
-        return Signature.from_bytes(signature_der_bytes, der_encoded=True, curve=self.curve)
diff --git a/umbral/utils.py b/umbral/utils.py
deleted file mode 100644
index 0b06b572..00000000
--- a/umbral/utils.py
+++ /dev/null
@@ -1,41 +0,0 @@
-"""
-This file is part of pyUmbral.
-
-pyUmbral is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published b
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-pyUmbral is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
-"""
-
-from typing import List
-
-from umbral.curvebn import CurveBN
-
-
-def lambda_coeff(id_i: CurveBN, selected_ids: List[CurveBN]) -> CurveBN:
-    ids = [x for x in selected_ids if x != id_i]
-
-    if not ids:
-        return CurveBN.from_int(1, id_i.curve)
-
-    result = ids[0] / (ids[0] - id_i)
-    for id_j in ids[1:]:
-        result = result * id_j / (id_j - id_i)
-
-    return result
-
-
-def poly_eval(coeff: List[CurveBN], x: CurveBN) -> CurveBN:
-    result = coeff[-1]
-    for i in range(-2, -len(coeff) - 1, -1):
-        result = (result * x) + coeff[i]
-
-    return result
diff --git a/vectors/generate_test_vectors.py b/vectors/generate_test_vectors.py
index eb22071e..434cfb04 100644
--- a/vectors/generate_test_vectors.py
+++ b/vectors/generate_test_vectors.py
@@ -1,13 +1,11 @@
 import json
 import os
 
-from umbral import pre
-from umbral.keys import UmbralPrivateKey
-from umbral.signing import Signer
-from umbral.curvebn import CurveBN
-from umbral.point import Point
-from umbral.random_oracles import hash_to_curvebn, unsafe_hash_to_point, kdf
-from umbral.config import set_default_curve, default_params
+from umbral import SecretKey, PublicKey, encrypt, generate_kfrags, reencrypt
+from umbral.curve_scalar import CurveScalar
+from umbral.curve_point import  CurvePoint
+from umbral.hashing import Hash, unsafe_hash_to_point
+from umbral.dem import DEM, kdf
 
 
 #######################
@@ -35,90 +33,77 @@ def create_test_vector_file(vector, filename, generate_again=False):
 
 
 # If True, this will overwrite existing test vector files with new randomly generated instances
-generate_again = False
+generate_again = True
 
 #########
 # SETUP #
 #########
-set_default_curve()
-params = default_params()
-curve = params.curve
 
 # We create also some Umbral objects for later
-delegating_privkey = UmbralPrivateKey.gen_key(params=params)
-receiving_privkey = UmbralPrivateKey.gen_key(params=params)
-signing_privkey = UmbralPrivateKey.gen_key(params=params)
+delegating_sk = SecretKey.random()
+receiving_sk = SecretKey.random()
+signing_sk = SecretKey.random()
 
-verifying_key = signing_privkey.get_pubkey()
-delegating_key = delegating_privkey.get_pubkey()
-receiving_key = receiving_privkey.get_pubkey()
+verifying_pk = PublicKey.from_secret_key(signing_sk)
+delegating_pk = PublicKey.from_secret_key(delegating_sk)
+receiving_pk = PublicKey.from_secret_key(receiving_sk)
 
-signer = Signer(signing_privkey)
-
-kfrags = pre.generate_kfrags(delegating_privkey=delegating_privkey,
-                             receiving_pubkey=receiving_key,
-                             threshold=6,
-                             N=10,
-                             signer=signer,
-                             )
+kfrags = generate_kfrags(delegating_sk=delegating_sk,
+                         receiving_pk=receiving_pk,
+                         signing_sk=signing_sk,
+                         threshold=6,
+                         num_kfrags=10,
+                         )
 
 plain_data = b'peace at dawn'
 
-ciphertext, capsule = pre.encrypt(delegating_key, plain_data)
-
-capsule.set_correctness_keys(delegating=delegating_key,
-                             receiving=receiving_key,
-                             verifying=verifying_key)
+capsule, ciphertext = encrypt(delegating_pk, plain_data)
 
-cfrag = pre.reencrypt(kfrags[0], capsule)
+cfrag = reencrypt(capsule, kfrags[0])
 points = [capsule.point_e, cfrag.point_e1, cfrag.proof.point_e2,
           capsule.point_v, cfrag.point_v1, cfrag.proof.point_v2,
-          capsule.params.u, cfrag.proof.point_kfrag_commitment, cfrag.proof.point_kfrag_pok]
+          cfrag.proof.kfrag_commitment, cfrag.proof.kfrag_pok]
 
-z = cfrag.proof.bn_sig
+z = cfrag.proof.signature
 
 
-#######################
-# CurveBN arithmetics #
-#######################
+###########################
+# CurveScalar arithmetics #
+###########################
 
-# Let's generate two random CurveBNs
-bn1 = CurveBN.gen_rand(curve)
-bn2 = CurveBN.gen_rand(curve)
+# Let's generate two random CurveScalars
+bn1 = CurveScalar.random_nonzero()
+bn2 = CurveScalar.random_nonzero()
 
 # Expected results for some binary operations
 expected = [('Addition', bn1 + bn2),
             ('Subtraction', bn1 - bn2),
             ('Multiplication', bn1 * bn2),
-            ('Division', bn1 / bn2),
-            ('Pow', bn1 ** bn2),
-            ('Mod', bn1 % bn2),
-            ('Inverse', ~bn1),
-            ('Neg', -bn1),
+            ('Inverse', bn1.invert()),
             ]
 
 expected = [{'operation': op, 'result': hexlify(result)} for (op, result) in expected]
 
 # Definition of test vector
 vector_suite = {
-    'name': 'Test vectors for CurveBN operations',
+    'name': 'Test vectors for CurveScalar operations',
     'params': 'default',
     'first operand': hexlify(bn1),
     'second operand': hexlify(bn2),
     'vectors': expected
 }
 
-json_file = 'vectors_curvebn_operations.json'
+json_file = 'vectors_scalar_operations.json'
 
 create_test_vector_file(vector_suite, json_file, generate_again=generate_again)
 
 
 
-###################
-# hash_to_curvebn #
-###################
+###############################
+# CurveScalar.from_digest()   #
+###############################
 
-# Test vectors for different kinds of inputs (bytes, Points, CurveBNs, etc.)
+# Test vectors for different kinds of inputs (bytes, CurvePoints, CurveScalars, etc.)
 inputs = ([b''],
           [b'abc'],
           [capsule.point_e],
@@ -129,51 +114,54 @@ def create_test_vector_file(vector, filename, generate_again=False):
 
 vectors = list()
 for input_to_hash in inputs:
-    bn_output = hash_to_curvebn(*input_to_hash, params=params)
+    digest = Hash(b'some_dst')
+    for input_ in input_to_hash:
+        digest.update(input_)
+    scalar = CurveScalar.from_digest(digest)
     json_input = [{'class': data.__class__.__name__,
                    'bytes': hexlify(data),
                    } for data in input_to_hash]
 
-    json_input = {'input': json_input, 'output': hexlify(bn_output) }
+    json_input = {'input': json_input, 'output': hexlify(scalar) }
 
     vectors.append(json_input)
 
 vector_suite = {
-    'name' : 'Test vectors for umbral.curvebn.CurveBN.hash()',
+    'name' : 'Test vectors for umbral.curvebn.CurveScalar.from_digest()',
     'params' : 'default',
     'vectors' : vectors
 }
 
-create_test_vector_file(vector_suite, 'vectors_curvebn_hash.json', generate_again=generate_again)
+create_test_vector_file(vector_suite, 'vectors_scalar_from_digest.json', generate_again=generate_again)
 #print(json.dumps(vector_suite, indent=2))
 
 
-##########
-# Points #
-##########
+###############
+# CurvePoints #
+###############
 
-point1 = Point.gen_rand(curve)
-point2 = Point.gen_rand(curve)
+point1 = CurvePoint.random()
+point2 = CurvePoint.random()
 
-# Expected results for some Point operations
+# Expected results for some CurvePoint operations
 expected = [('Addition', point1 + point2),
             ('Subtraction', point1 - point2),
-            ('Multiplication', bn1 * point1),
+            ('Multiplication', point1 * bn1),
             ('Inversion', -point1),
             ('To_affine.X', point1.to_affine()[0]),
             ('To_affine.Y', point1.to_affine()[1]),
-            ('kdf', kdf(point1, pre.DEM_KEYSIZE)),
+            ('kdf', kdf(bytes(point1), DEM.KEY_SIZE)),
             ]
 
 expected = [{'operation': op, 'result': hexlify(result)} for (op, result) in expected]
 
 # Definition of test vector
 vector_suite = {
-    'name': 'Test vectors for Point operations',
+    'name': 'Test vectors for CurvePoint operations',
     'params': 'default',
-    'first Point operand': hexlify(point1),
-    'second Point operand': hexlify(point2),
-    'CurveBN operand': hexlify(bn1),
+    'first CurvePoint operand': hexlify(point1),
+    'second CurvePoint operand': hexlify(point2),
+    'CurveScalar operand': hexlify(bn1),
     'vectors': expected
 }
 
@@ -194,17 +182,17 @@ def create_test_vector_file(vector, filename, generate_again=False):
 
 vectors = list()
 for data in inputs:
-    for label in inputs:
-        point = unsafe_hash_to_point(label=label, data=data, params=params)
+    for dst in inputs:
+        point = unsafe_hash_to_point(dst=dst, data=data)
         json_input = {'data': hexlify(data),
-                      'label': hexlify(label),
+                      'dst': hexlify(dst),
                       'point': hexlify(point),
                       }
 
         vectors.append(json_input)
 
 vector_suite = {
-    'name': 'Test vectors for umbral.point.Point.unsafe_hash_to_point',
+    'name': 'Test vectors for unsafe_hash_to_point()',
     'params': 'default',
     'vectors': vectors
 }
@@ -219,7 +207,7 @@ def create_test_vector_file(vector, filename, generate_again=False):
 
 vectors = list()
 for kfrag in kfrags:
-    assert kfrag.verify(verifying_key, delegating_key, receiving_key)
+    assert kfrag.verify(verifying_pk, delegating_pk, receiving_pk)
 
     json_input = {'kfrag': hexlify(kfrag)}
 
@@ -232,9 +220,9 @@ def create_test_vector_file(vector, filename, generate_again=False):
                     'Each of them must deserialize correctly and the '
                     'call to verify() must succeed.'),
     'params': 'default',
-    'verifying_key': hexlify(verifying_key),
-    'delegating_key': hexlify(delegating_key),
-    'receiving_key': hexlify(receiving_key),
+    'verifying_pk': hexlify(verifying_pk),
+    'delegating_pk': hexlify(delegating_pk),
+    'receiving_pk': hexlify(receiving_pk),
     'vectors': vectors
 }
 
@@ -246,14 +234,11 @@ def create_test_vector_file(vector, filename, generate_again=False):
 # CFrags #
 ##########
 
-capsule.set_correctness_keys(delegating=delegating_key,
-                             receiving=receiving_key,
-                             verifying=verifying_key)
-
 vectors = list()
 
+metadata = b'kfrag_metadata'
 for kfrag in kfrags:
-    cfrag = pre.reencrypt(kfrag, capsule, provide_proof=False)
+    cfrag = reencrypt(capsule, kfrag, metadata)
     json_input = {'kfrag': hexlify(kfrag), 'cfrag': hexlify(cfrag)}
     vectors.append(json_input)
 
@@ -263,18 +248,15 @@ def create_test_vector_file(vector, filename, generate_again=False):
                     'enclosed Capsule, under the enclosed delegating, '
                     'verifying and receiving keys. Each CFrag must deserialize '
                     'correctly and can be replicated with a call to '
-                    '`pre.reencrypt(kfrag, capsule, provide_proof=False)`'),
+                    '`reencrypt(kfrag, capsule, , b\'kfrag_metadata\')`'),
     'params': 'default',
     'capsule': hexlify(capsule),
-    'verifying_key': hexlify(verifying_key),
-    'delegating_key': hexlify(delegating_key),
-    'receiving_key': hexlify(receiving_key),
+    'metadata': hexlify(metadata),
+    'verifying_pk': hexlify(verifying_pk),
+    'delegating_pk': hexlify(delegating_pk),
+    'receiving_pk': hexlify(receiving_pk),
     'vectors': vectors
 }
 
 #print(json.dumps(vector_suite, indent=2))
 create_test_vector_file(vector_suite, 'vectors_cfrags.json', generate_again=generate_again)
-
-
-
-
diff --git a/vectors/vectors_cfrags.json b/vectors/vectors_cfrags.json
index 5da3fc6b..996d045d 100644
--- a/vectors/vectors_cfrags.json
+++ b/vectors/vectors_cfrags.json
@@ -1,51 +1,52 @@
 {
   "name": "Test vectors for CFrags",
-  "description": "This is a collection of CFrags, originated from the enclosed Capsule, under the enclosed delegating, verifying and receiving keys. Each CFrag must deserialize correctly and can be replicated with a call to `pre.reencrypt(kfrag, capsule, provide_proof=False)`",
+  "description": "This is a collection of CFrags, originated from the enclosed Capsule, under the enclosed delegating, verifying and receiving keys. Each CFrag must deserialize correctly and can be replicated with a call to `reencrypt(kfrag, capsule, , b'kfrag_metadata')`",
   "params": "default",
-  "capsule": "03cdaadd5e0493c8426fd65004ea1e10ddcd3fcb37ac87ce84d0ffdda36d8d60720278adf9ca33ea46b2c0fb7b5a14a2820b171a97f8c69846e9866194ed1315438a1a525f615e246d744b82f7283aeba923f38cff0181e9013cfefe4747e32dc076",
-  "verifying_key": "03714109aee5bded1ea98ced8660ad1d49697e14f2a1bbefd5944e4ce443493d08",
-  "delegating_key": "021c0e2868064f20441df0050bf501137ec84db6ca3596b88a967308e14bea2caa",
-  "receiving_key": "036285c48fbb7c38bccceb6e15959a33ed3dd5ab5c96daaff67636b8557d6885d4",
+  "capsule": "02a03893438c0502dd13818f65c039b2ef4fce33bfed150c6ad166554b4e8a51c2028da9ebf8cc0966bc010152fd3917a8f12dfff0af3b06e34e17f300d622893159367ce07602310c78fc65a8b6115d75f65d362abdf2fd799bebc55aa0cda44dbb",
+  "metadata": "6b667261675f6d65746164617461",
+  "verifying_pk": "03f24761ac8b02de08ad1622d023f669d6214c3bab81a33087ed3ec5505e4d43db",
+  "delegating_pk": "03a73623a2e72fd52b2d313214c7495580c14fe6cd8de7ad0d63bbfbfd6fb6bd4d",
+  "receiving_pk": "02952a1903b9c929f0d93d935b34b272ea25a84833a04e22d887f27bc3bf0cc409",
   "vectors": [
     {
-      "kfrag": "417225b396fb91a82f0ed97b4dfb235200742a58d9632d46e352e507c5c716863b349c46905501861f950d11aefd2c69a1f4fe31ae91b4c8624c7556ec7d01ef026fa5450221297328a37043220b0f82a7fdf2a4ab01c9b30cd572181736390ad303b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f4103428244169c9d2d3cfefa6b88251e7600c4a6f9a4edb5e371a0af7bc5134bf68ce9ad3b8a1acbd66926687e2cf7a30762527a85d41b1eaee133866df9dc4fe49b43601bb4d9b496f59e44e2e172141f9480bad0cfda18e5dd6c8dd7a5e72b4bc59281ed11ba75b87f7f1919c30b56d7f0c522b77fd15d37f893fc784eb77d1493",
-      "cfrag": "02aa6a41b809ba8d2816444788d597e6af5b81c56f7645f67e8dde417fca052828027adba799a3cf3935abb5366b5ec3f132f05a9baa0647734e7be50952e3309bd2417225b396fb91a82f0ed97b4dfb235200742a58d9632d46e352e507c5c7168603b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41"
+      "kfrag": "3f3453856117dedb3d7518d0435c04b10734700c8aca48fa5a8b85eded515cdffefbefddcd3cb728dcc6d05061b5e201aa1cef55ce89e94c34b6887b307d5ab2025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb7490374d2e59ea274c8011f6bf26ca8fe8eb8e7837cafed8547485e3fde6ffe0368e98d26b0d7e12095941daffae8ca429147d9686dc3285ea182c0d7b15db41c7afd3fae3c88100707ef28b53d7cf93961dc509864eb319f0cce274544306c96099b5792af0b820bf758f5c8f2a69cfc1e0a5f91fab96ab82c10c7740602f90efdd315ff5dbd07323526ca1c9a93132b4d008fdf1796a55b92e2fe75c544652256c80101",
+      "cfrag": "02ecbc4dc0aed60efa211c7bf0e238593d292a042373b891928bcee459151c2f440281171b7e330ebd097575dadb210e8a405bc162e293881457a301da03f7571c7a3f3453856117dedb3d7518d0435c04b10734700c8aca48fa5a8b85eded515cdf025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74902889960dbc86c87e5a1275f2c1df31e43ed0b3e4616126845a7394b27a83f6d1e03f0b7c60a76c7c9f590c9ac2d1f0ebf6aecaa9d371e2dcd04e19caa134e9c1b530374d2e59ea274c8011f6bf26ca8fe8eb8e7837cafed8547485e3fde6ffe0368e902660d6c64d749050f027983ebd4631838373a47c887537bea95aa139d53bfe38ea532a6b7464b0b5f45522c495a2b8a772b98c4950aa2bb830f6602e7636a1bd95792af0b820bf758f5c8f2a69cfc1e0a5f91fab96ab82c10c7740602f90efdd315ff5dbd07323526ca1c9a93132b4d008fdf1796a55b92e2fe75c544652256c8"
     },
     {
-      "kfrag": "fe11bce6ea1d451fbe396c6c7d00747264324eebc93bd6ca9e949c2970be214e56571ecd1c02064be2058043cbe5d7ddde90771c5709c1cb48f2a4866bef246b03cb7014d5978aba65b7849bc5756d0e94ce515cc31725ed86550e4e38424b63d903b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f4103adbaf6ef97a68ad61ca39b1ab244b5d1881b00fbf7e2f0eec06907a0078ec4fcb573d101c2d1976985fa7d6d7c96b327030333ac3c66fc0223b456a70e996e19a31822dea4781891b054e86ff34abbfcce05f7c613b7296133ad5b10455061368a102dff42a913edb8e5d248a8d979879a3cfca1f8d3fbf6b8a24ca5357ae057",
-      "cfrag": "03bfcdf5dafbc9482fab6da942ed7849869d92333a815e9e81116eeff31a97ac680243d1d352ea857eef383300d41f1e1df14d00beb54abd43bc112c2d8af98e4be7fe11bce6ea1d451fbe396c6c7d00747264324eebc93bd6ca9e949c2970be214e03b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41"
+      "kfrag": "d15ac028be9938b5e2d7a9fb9957b416db538a11d141d320a5f656ab4e3cb0cab3a2ddd2672f3514e7da2f29cbe3815c83b6d704e09d595a68fdd5aeff52ef8a025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb749033d6984555d41614a2ba08c2182e27a7105cb60ed414c9732a156c3e44d196dd3507335e3781d6c9a4543e3ae81a4c7538cf280292c9c0d92e4f513e4e073721f2f72a1ea0e69912e21754e32edf9cefd3a96183f5501266bedb301f291709ffb25ed5213660db4c8bfc086e794650455bfde1251f92e1fe49f1feb016ad44fe859b342964db14dddfa6a33eba53021149d8663d306850b8907f9dabf4a5d3ecd0101",
+      "cfrag": "03a98cc0d807a4f2b6183dfb9b7ff589f376183b33f88ae07a50323edaac946d9b02fc04f97b7afa5c8664082e4433a24184d54724e9e18afd44922bd052bea87e49d15ac028be9938b5e2d7a9fb9957b416db538a11d141d320a5f656ab4e3cb0ca025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74903bf5c856a6b918df7a70a10db9854d43009ee517e7fc4403185c99eb05f42638c0213c86d5b3f3816a4807bf797731b8b23ef2563e6698e9380a0ee4044755504a9033d6984555d41614a2ba08c2182e27a7105cb60ed414c9732a156c3e44d196dd303de157a0d01a46d89e57d5586bee352b17bb123ea52dea60ef283456fa50cb5a98eca7141ac4bd117511c10922216c69e548c5005e49571087fefb69d50cecc8225ed5213660db4c8bfc086e794650455bfde1251f92e1fe49f1feb016ad44fe859b342964db14dddfa6a33eba53021149d8663d306850b8907f9dabf4a5d3ecd"
     },
     {
-      "kfrag": "4edffcacb49f5620c1cebd67f43366aa22dc380dd9fd0f4853ffbbd44e31986f4a99f953e5c553f08a83cb16c0b5c793a4f691d4d3d239e29f232d2b058970d903041b77b8891d845c240a8a12b71dfe9651de67ceb817b54eb578636fe8a9605103b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41035ba90e2a06117e205dd807c780b5687ff6dadf19454fffb367e7af6b3f9cadf03572dc3311c0e1706354ba3340d1b1f915afc82007f9e09ddd34ce07a038a7bb7ecc7343b1532b175e5b8c7d3c712e3eecb18ff69c22237ba9ab6b47b4572529a35b29c48874f396def9971c0230a51e26975a953ff87af8fdbfd53bf42aae07",
-      "cfrag": "02c6870bd1336ec336752ce7d6b99f36d3ae69983d7a06cb5a743f20c562c0b2ba037fb7d9db1faf763c49e257f906e3345e33df144eeba8b9794462a31a305459c44edffcacb49f5620c1cebd67f43366aa22dc380dd9fd0f4853ffbbd44e31986f03b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41"
+      "kfrag": "f780e25b78581e70d28f102264e1f04b482eb9ecd45c188f2f9cd90026627f904b903c6da7529805e1d91fbaebc384588083e2c5f235c31e7f768b8d0df43513025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb749032f4d57c068c255281703ba6d449345e2f6240a4b8b3047209ff2aca5edec0ad2394020e796b7b9f9130c0ab02b4c8151d050f38f3d0ff5f38dc164e4d254ad2737758c4b67c0b54267b113692f11a410ffc5fe3e41c2f8ec9246854baa63dbfaebb58f0cc89c528e119788c521bbfa8dd33c69129c1a05ed454d7534224c9df35007ef11b38769ee8d2788c72745e50e14e32b0c2b9d988e2550dd3f89cbd6b50101",
+      "cfrag": "03ec5772868ec577fb8e054a7f0717dc56b7eded542a06c593ac7166093ae196c10394fc79d3d700a9536b10233ad1cf145a533a43d35c48028c7b2a6137a8d6097ff780e25b78581e70d28f102264e1f04b482eb9ecd45c188f2f9cd90026627f90025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74903ad08b8333f7a44acf71a2738e7e6213d715d7cee759a825abce04b72ca8d56d0033eba87004940ec899c4eb593265e55138f2d17e8b4c712027009388c18724bd0032f4d57c068c255281703ba6d449345e2f6240a4b8b3047209ff2aca5edec0ad2037403376f5789c4d7c755e4a0b45541a88cd045c77fe61a615e9b6ab6af9e97bbd868593f2b973cd492439c4fdbc6351739e8e9e7f5ba501fb9bbc96605dcf144ebb58f0cc89c528e119788c521bbfa8dd33c69129c1a05ed454d7534224c9df35007ef11b38769ee8d2788c72745e50e14e32b0c2b9d988e2550dd3f89cbd6b5"
     },
     {
-      "kfrag": "762a4ae9fdbb74de327a29d25c577c46521ee44a951c6e33739e5ceb2a340aefd2a93d8cbe37b9149dd26ce59b0b7ffcd9adf1ca822c5b477595ecba1933291503409c76b74f0f415f5b6a8421514c250f71a5c5b6c5b0d89148e03ba2a624a9a503b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41034f04b1666bc36ac5d3b7ba66420b660d19caf9a995b6cc7cc0aa7c9d0e739f25b1048e754ffd0703e16b453ae8279e5410eb16d2d53683f2d80c30c39918834d70970a09ae17c014fc939979c1e3659b92a40d8688ed77861407316a22d4a8e0b5628c5eec0a2123129ebdb627eae78f8c404f8e129dd407feb2e970b0ba5e39",
-      "cfrag": "038bbe8c32677553121c7b708c4266e85930ae38541fec43df9b4eb131fcda4b06036ab69b2d77584016302c8391a13bc821811fe8837bd28523ad04081faa6fc498762a4ae9fdbb74de327a29d25c577c46521ee44a951c6e33739e5ceb2a340aef03b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41"
+      "kfrag": "afdbd2183491cec86259c4e6785c9048f17e1f0c86df597983e78f50eb9e88ebfc725b6addb07ce67e5e33e404237dfd9baf15825329d780bbcf07952d899709025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74902fbba98644ea41f7abbef7f0a80d645d428844eb6a08c848b73122057f02cc264f27b1b019d667b1a3c89e176b9203870c42dcf3bb6af9191a89db98b2cd1a1b82c61d188f2d2a5d8e1c92a64d3402e98ca09e4594dd67ad74a9583e47b5ee2fd0d38772f8dee198e6e7fa2f7acca079a90b280f98e880106bc337c314bdea20b1e8d1d43ed0912a4a55475c2587ba81a16b9775681774a8bc9d1af2395875e000101",
+      "cfrag": "02b6b320eb5ef6bb58adc6b0379b3052d7fe3ce290d62d8a9edc5d9560c7472f75036dd5f864310b2f9f7239bca239877edf63b42ac00637fb6cdcdcf944fb0ef2f2afdbd2183491cec86259c4e6785c9048f17e1f0c86df597983e78f50eb9e88eb025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb749037b55bdc05a83cf07d672c695d0c57a961be2a7289d9386b6e86953d6abe90f17036e5e32b140cf076873542eb4551c328608c8f2d951a3d52083b7a2f6612c21d902fbba98644ea41f7abbef7f0a80d645d428844eb6a08c848b73122057f02cc264027ddbf0d8eec3118c1765eb0c636184375b5ce593c1fd83bfbf501d94687695954edccdff64dd78e9ae94aa137754c502c03cacbaeeb09b1dcb798bb8442476ba0d38772f8dee198e6e7fa2f7acca079a90b280f98e880106bc337c314bdea20b1e8d1d43ed0912a4a55475c2587ba81a16b9775681774a8bc9d1af2395875e00"
     },
     {
-      "kfrag": "b40778198920434d132c4d3724a7b7422428c9921bd627710c3764a6c54376f6a03be8af10cb0f1c3b74d132dda5feeb4340a9f6213da859c05b6b57249e54e903e9198cc0e86b880251931ab162130f93242164a5b242015195290ba75b5a6bea03b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f4103f27829a4798466ed68c2c53137e4e658e86faebfabf2556837b60b502a1721de3e073a98974686d1b847c27167f118eeea6a27029775800cb1618b393bca9cc35b341b546c8851395b3b68945b73b01fdbe94a9f6c400b22bfb5e3bebeee95887d888c49ccf99553a2941de1cbd3f845c5725d1b6b26017dea5cf161fadb5fda",
-      "cfrag": "020604cacae76ef0f2a81a582b2c642accc4bc9d29ea9b172211a3d8225cfc4975026ac1a78becd4be2f09b939b3bdac3901bbeec488ab1013965c40565034586824b40778198920434d132c4d3724a7b7422428c9921bd627710c3764a6c54376f603b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41"
+      "kfrag": "831f7b7b14181746fcfd7e26e03e3fb63e7a8903acff3dd3c7ad0c2c94550d9abcced3aacb62fa59fd2847a2b18cc6a6f0a6f646e39ec94f05ef186186dfbe10025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb749020db8647602d7401b47c556f25540bd45da99e69462ee8eef1b9c26a26a3757c77f90b015143d8516a5bf2ada51a24b8192e1326d38bf47c0f54591619db42c7c122fd0d955f788dfdeee8d762022887216a3de332632d44092f1f06793eb351fd6acaf0f2060ace737a11e1c1cec72ba361ce754c913765113e1695de323225f24c3f0bbf832c9062d004b69e40090930b40d5986eb9ede462e5d044083127450101",
+      "cfrag": "038a54e78c4dd408df7b242610d5c21bc6a34f7b16af21a059ee8e44eea24337c702d4584beebc883104f375e922aba70ad1449d54f6a835ba6716b36271bfae8e53831f7b7b14181746fcfd7e26e03e3fb63e7a8903acff3dd3c7ad0c2c94550d9a025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74903c1d11486753d8d4b60a4e8a890007d021ea00aaed7d608b25475e69364e5f3120304ef459543d9482aa3b48c5605176e87bf55c0bef3f03244bac83446dd84fe3d020db8647602d7401b47c556f25540bd45da99e69462ee8eef1b9c26a26a3757c702182da47b2bef272ed9840eb2cff17f8f62d3e9544cfc699905e40e8ab061d4d17dc4e840d39308e81fae434b30ed124ce08f846a1a9ab73c926a6a222d15657ad6acaf0f2060ace737a11e1c1cec72ba361ce754c913765113e1695de323225f24c3f0bbf832c9062d004b69e40090930b40d5986eb9ede462e5d04408312745"
     },
     {
-      "kfrag": "bb063b1ae2a2e2c804671a40571b5d9a7746a7b11b98fd1794207daf68e8d408e4ad0acf429b3e97d7d098766cf304fc43454a79d541a8e85cd95ee93b7bfb460242325b9a16de25bca5ce9225091b2d9eec4b98fa627f6c2fec591cfba3561bd303b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41034c2742c62c69c571623a751dc842998cc47a6d33c7e2d30e099bc10d2675fa8bed049ff1474d604eb97e6c1b997dc506bfcefd6aed3838db23fd08f0cebe176fad3a0c5734d986b506145f0770e6689eb1425eb2817d699be1c993d64b26f2dd59d3acf01c5372adabebaa04336ff03219eadf1fd085a6957bd5e90b83919774",
-      "cfrag": "022edd320a1560725a60c8116810ac0debf2595bdf274510477181e37e1ce9684c02b56d7468d8a29c5034602dd1960d4a09cc19edf11fd7b90b536a0463f465854bbb063b1ae2a2e2c804671a40571b5d9a7746a7b11b98fd1794207daf68e8d40803b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41"
+      "kfrag": "c63acdf5fb6820df50354e74e65eefacaf75e7e22c780b2b5e2c49a0db9d353e7217ca6a942fce879bd4b596aabd489e0c5af75bd94b72aed37999799e9c8dc9025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74902fcaa0a22aabe3b4564972a9368d6a752bc5e359b306cfaff1626b49911158cda599e662db88909f430575f44b3677a8161a5003519e48cf0da635cb20b15f18d632a1bf41eb525b9ee9fd47f196fdd15059d989f9166e5b437cbbd66459e03fad404248f1da8048a5bf9dd9650fbdd50d1afcbadabbcd7a5aca1882b5eeb803443cd4caff8c4b090bc263f56f496aca086bb6f1fedfd5a99e1715ebfe2c6b5010101",
+      "cfrag": "038caa0566b556fb81617617ee8d4a4808cfaac1fa65b1e1999b58bbefafb30623022a43ae7cd2595e155f3cc7cf5e716168f2b959d574e07f7037ba30329dc384dec63acdf5fb6820df50354e74e65eefacaf75e7e22c780b2b5e2c49a0db9d353e025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74903361957fd737be52d9423a400e677264d8e57ffc1ab35f19aff29ac7d13275ece03a1b57ec36f35089242d2586be843853ce830f1fff20cea9e6a354598ec10c1e002fcaa0a22aabe3b4564972a9368d6a752bc5e359b306cfaff1626b49911158cda0300a120a88ee5994de9a4a5287dbb2e63bd1cd3fbca77740ddad0789dc4a40547a792377ec089eadb1771196797c6835cf01bd4307c6c33be4e61acdb6e05f583d404248f1da8048a5bf9dd9650fbdd50d1afcbadabbcd7a5aca1882b5eeb803443cd4caff8c4b090bc263f56f496aca086bb6f1fedfd5a99e1715ebfe2c6b501"
     },
     {
-      "kfrag": "9a2d97e3bba40307b7c60f6f22c5965f6587206cef4d5d7980ad8d75f0f4ae69de90b3ac071a58a06c4d2d0895db9e71a4f36b86fd078dfce6e436514f70797d03d959ebb3d7ee2fd90f2a390da0d7377f7549877f7720739e7e84e0c488a6d16203b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f410394ba445cfe40a9ebf1bc6a6f9588b704b11b129a2a102e8c5bf892fbb695e2d37d396c1b34f8eed4f348ecb11a29fa2b68c3790ef2ddc169e6fa7dc86e03b1a7fd8faf67773903168ee5f9c2ac60468cd81c7ee389af0f03f3fed356c65e7f182faa22867aa4544554d235f78d28afe7d90fff1cf60184bb1ea947a343c13d07",
-      "cfrag": "035f87f2fecce4d4734a01d444e3614e2f32be3f9277373f1338ddb0044a4b79ae03a604f360df7cb23097d7b49826088bd3628d82f601f2b4ea9262931c3b4bc5809a2d97e3bba40307b7c60f6f22c5965f6587206cef4d5d7980ad8d75f0f4ae6903b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41"
+      "kfrag": "1365f76b3cecb0d27cd839263d6e91e8a3e703cb10590fc4ee8c533caed2fcb4e390938ab2eb832d51d7436058182fb46fb4288c9409a2465c25e447919e61eb025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74903ffe0345c282a9890aa6759dadd14afdd50a82c19123a110d37f4582a1549ce5d4308de0dbbc76897e910253bd1fdb10e65bbbe45cede7dcbf494eba5dee18f9c141cc095ff275208485abe81b229b730c11a1fd87de00756e3d705faff9e6f508cff3f81aec66079b4737c5d55ada03f7a749da2605e6b1bb57324528c373cac773526c02a0cf876744a5e7cd7dee1bad8ccf1e81a46d4c7049743cabca94dad0101",
+      "cfrag": "03e39b8d144411b2db704a4394b9291bc8897ce619df95e2a8355066a9da8fe91b02037fe5e553f4e7e711502abe59bff33fae5ed0eee63747252744701ad1c550f31365f76b3cecb0d27cd839263d6e91e8a3e703cb10590fc4ee8c533caed2fcb4025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74903a0bf927bc8b75413f899b06ec3bb3ebba8b73f2a5d22c007e157440c2ec6496703be800d325be2e0d10d3cf81d9fe65e035a31d9f4b857b81af38d52d1a815a40b03ffe0345c282a9890aa6759dadd14afdd50a82c19123a110d37f4582a1549ce5d02518eea3159155d5ec2437907ae06a3bf0ece2e8747e13c1528ccffc62eedeebeb50690a4f2f1e5ebcec48789e7d08039ae4853de86a2a2fd991e70ba37e7dc1c8cff3f81aec66079b4737c5d55ada03f7a749da2605e6b1bb57324528c373cac773526c02a0cf876744a5e7cd7dee1bad8ccf1e81a46d4c7049743cabca94dad"
     },
     {
-      "kfrag": "3d09cbb50ab9ff970c93a03976efaf5f13aef88fc4e1b8840cebad96d0469605b343d7f3d4297ada1fcc08009260250f29dbb64bfb6dd64c2dd72174be3d87a202f665886dabd4fd94513aa1082a403efcb9722115e7c44f8ce5394799e10c2ca203b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41037ec97969ef20834c753f460c1c29360ce535cb19e0dd0da3bc9c568157137c3f741dadb514586112bfa22c4aa64c28ba854f7393a04ce77a3c4b731e6eaa41e29f2d240b3adf5a973688c7691e8bde140647f4d2c4d0c06a0d690415a82f18f095fb69a084607734579a590c6d1e477d93716919c67d6627a890fa5a2250109a",
-      "cfrag": "0321674813fe3ba72c9164b7016299862ce1efdbf57c7eef83bd4d3f6535bc3eae03940fdcd41dfaeb7667a0d2a3e929944ea6a3c2c6d7e9f2fff4265ffb011169b53d09cbb50ab9ff970c93a03976efaf5f13aef88fc4e1b8840cebad96d046960503b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41"
+      "kfrag": "d961d60a31b197ee26437b1ff6b484c63bcfd208f509af1225a8db8f65240e366f7924ae66d6cd64c25c74761067bcb3a388cf2d4d2e90738666252868e790b0025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb749037c88f9bf80bb6a406843e339dcc8548b3f8bf16e525be9c94d5e3b8c243f466fbb19cf3eec376f9c46d9c52040555927c7eb506af29718ed9b728ac210d43b2b4958eae22852a1e3761a71cedff210a52f5d312e5cc1b12bdbb2edbe3f955f5535b8b1ce9e3e8b04a010a2dd227c9d906d7806fdf9df07d8ce379597bf7513c23db95d84e7774a3fe0de392cd7390c6ec7cab60ad22317d8c00de0865bd0ceb80101",
+      "cfrag": "03367a497f07271e9b505f58d762527cfd8b0bd30f4b925219b495c8ff825c8cef023b856c54ee517300280f9b9109ee6f8e260341c4013a0b74911fda9eac562b71d961d60a31b197ee26437b1ff6b484c63bcfd208f509af1225a8db8f65240e36025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74902d5dd9d1d29e06800648c0b2b45a2755f5a36ed4e7179d17c5ed9506d1d455daf02fe8deea5266b7059e6bdf53cb4f23c3f27814e14f955eb6236ca911639c3d6f8037c88f9bf80bb6a406843e339dcc8548b3f8bf16e525be9c94d5e3b8c243f466f03bdd9ce48dbeafe54277c44c4cf3cb5d5eb10e036640180c0d0e197594f2bebe029f79a6eafcac19331d83eab043df3aa6486a70409c46c2509a4b03ead49666b35b8b1ce9e3e8b04a010a2dd227c9d906d7806fdf9df07d8ce379597bf7513c23db95d84e7774a3fe0de392cd7390c6ec7cab60ad22317d8c00de0865bd0ceb8"
     },
     {
-      "kfrag": "71c39f563bdda903d8397370b077ba1348e07cf644eaffdfbca251f63d8e0c8377d0f143019bca10090a07c15ab9d6d71857451443e0e77ca6768b45b93892b802b54fd786330796eebd5df0a7d5dc26422177d5637028aaea293dfa223a72a56203b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f4103aaaf48203b37dc6152c0735493517a31284be4c46b57a21f01359572d1e1e3d1b0fd64fc82efb401f25dab88e48474450b274625c28e16cb51f31259206aba6fe4886083237bdb5f7da67c8e1e2da7fdef57789c919ccceae6b5453eaa22fb6dcb58a2f9f1f20f05d03326031d4817ec52cf3dc9eb37f2d3b014ba315f5b8c16",
-      "cfrag": "02b37b1fc91e5d99f8249e6c70d19ac9af80cfeab42d2c8b5dbb43b791e5818c480345339e46528e5f7f2d48ab5dd6ba50dc7a44ed7a99a9ef0cee902839076f13a671c39f563bdda903d8397370b077ba1348e07cf644eaffdfbca251f63d8e0c8303b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41"
+      "kfrag": "8618ade86dd8b8f15c206274caba6955c30171e466e220ef2344af25f3ca5976ea2eb761d4e8d083e4bf30052c90f6fde525716bd19b5abe98d66bdd48745f85025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74902bb441051315ea81c8d9d9cc4e63d3f66e6fe3ef52217e8639e0af421d5dbba1be604f5168408cc4bf118346126b9a105b57524b741e141d5cebca15c8964669858bed28994196a23e4f99706cdb8d912d9c53b7c58f98e6d5eb5a4ef3aa2b7b9e0a270af089c667647613c6abe1b7aba8103ee386a04df80341e51e132d6d35d28753fa65e7f8d8c8f3a10d8adc8499a174bcb8041cf6078520ef5e9e00853290101",
+      "cfrag": "03ae6db34ac0125757de78dc64cb563d32ef5ce3983b67f8053cbb9e3539f5240c03855d0862b9930d7fa36e5c60785352de497b84e6ca6d1712627a35fa49df79688618ade86dd8b8f15c206274caba6955c30171e466e220ef2344af25f3ca5976025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb749021594688234f9a0ba6331cf6d341dcea4992d0354492247c528a011620fe7271a03ebb8652e960aa91511db011d16dc3f15df71d81b6e5555a41dc29480158fd8e302bb441051315ea81c8d9d9cc4e63d3f66e6fe3ef52217e8639e0af421d5dbba1b0398e26bb2caa42e1cc1f55f3688bfcaaadc7714a9f79f1910f5c158faa0d53b370668c081a5622181957a2af1fed562695ec390b01b8855411518f401638e185de0a270af089c667647613c6abe1b7aba8103ee386a04df80341e51e132d6d35d28753fa65e7f8d8c8f3a10d8adc8499a174bcb8041cf6078520ef5e9e0085329"
     },
     {
-      "kfrag": "55faf9c9be6d59da90b2c2e9f09d85e3965e00629b31c3854367f86039725ecb5c560659b6dc7d16a5f3202da0489aa02cbbabb65d8e1107c3645ca7e5bf36c3020e06cdf7d03bafc42e77c86d07b76840ffa7db6a5c511f3530d44b34edb69aba03b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f4103f5674cd594e92cace034b8b443a7aae0ae48ed57ca9eeca5ad6e2855334a989c0ea976c30e4f5eb2527b01caa468f150db4a19cae08ba6015df41fc07d4ea6bb33433a543608fd59cacba037daecf6e069f0954ce9a5f1651297c28cf31fdf76b655ed10284f5bb436c93934bb89b2434b7ff9f7bcb414b3e879a5571c88a0b1",
-      "cfrag": "03a48d0c6578a259efdebdf0b12ab6e1d29b472a520a1f14ecf628eb161a386ce2021ab6cafd3c7778bef9c199911306939ed679e7b4c09fbbd02c897f994417a81f55faf9c9be6d59da90b2c2e9f09d85e3965e00629b31c3854367f86039725ecb03b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41"
+      "kfrag": "939cfdfc368ae2c011ade7b7d18543f66f6b2f5aea0ed6732830d8ab281ec492cd90cd1756a44414e51d741e99f9a7d46cbedbfc7bdb60f6b8d33ffb1d4d6b31025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb749020d6a191832eed71367b495a514ee4ac442ce19040c00c4e6ab331f844b2fff11d1db98034e5ff8f86a5b2d015cfa439774dd83644adb80bcba1881f09bdb1b105e37329495d03775e99da0f1c99e753e6868fbe04766572a086ce8e1c575e0d253698578672fbfdcdf2607513d0d0c03f346cb8183506d179e9b999988c7ca317c10e8606e3f7a18ac9146dcc2f484dc6c46a149c371c7a22b17c14f0e3e84220101",
+      "cfrag": "03b45ef82c8e3439a698e3cc4383cd223d6ae84a6c1c7e6ea6b1e89fb501273ef1024e2b1a83a2e675e7f3e4f1d803f456dc83d6536bc33c07b01fc521380cec39a4939cfdfc368ae2c011ade7b7d18543f66f6b2f5aea0ed6732830d8ab281ec492025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74903893bc5023c9da3e0acf0028256ae37fe1015e09b8fcaea89b3aff10a49b1ac5d03c295e648f0dc3df65314ce141ff21b04fcf53432300ff8994ba765d8205bd90d020d6a191832eed71367b495a514ee4ac442ce19040c00c4e6ab331f844b2fff110390d4511e87f7dee7091cbffb952da38c7efbf1ee5a6890ea8afcfa22ee425c0cecd183c8351af7f917ccaa9bd7771de0ef9cf240bc02a7dc32c10e0bd4ed725653698578672fbfdcdf2607513d0d0c03f346cb8183506d179e9b999988c7ca317c10e8606e3f7a18ac9146dcc2f484dc6c46a149c371c7a22b17c14f0e3e8422"
     }
   ]
 }
\ No newline at end of file
diff --git a/vectors/vectors_curvebn_hash.json b/vectors/vectors_curvebn_hash.json
deleted file mode 100644
index 2a8b5f4b..00000000
--- a/vectors/vectors_curvebn_hash.json
+++ /dev/null
@@ -1,96 +0,0 @@
-{
-  "name": "Test vectors for umbral.curvebn.CurveBN.hash()",
-  "params": "default",
-  "vectors": [
-    {
-      "input": [
-        {
-          "class": "bytes",
-          "bytes": ""
-        }
-      ],
-      "output": "fd307d78e9f94e1b76762b8efd1284067c4ddd49f64f95441d57784d05a6323d"
-    },
-    {
-      "input": [
-        {
-          "class": "bytes",
-          "bytes": "616263"
-        }
-      ],
-      "output": "26249427a90ff70e82597a97a80f62fd133038a1ddae58b446eddf1a08098da3"
-    },
-    {
-      "input": [
-        {
-          "class": "Point",
-          "bytes": "03cdaadd5e0493c8426fd65004ea1e10ddcd3fcb37ac87ce84d0ffdda36d8d6072"
-        }
-      ],
-      "output": "b8070774566dbc192f9aeb4e34eb94b39bacdee67f7c373ca1371dd4b8b1ab41"
-    },
-    {
-      "input": [
-        {
-          "class": "CurveBN",
-          "bytes": "b32e9abad43a3724ff37dd9d15253fe66339f978e7581dff5b34b4db970b7bf3"
-        }
-      ],
-      "output": "7d1978b2203d57abb5fe3383d7929ff8aec7b2ea08bc7f4120d12843792ceadb"
-    },
-    {
-      "input": [
-        {
-          "class": "Point",
-          "bytes": "03cdaadd5e0493c8426fd65004ea1e10ddcd3fcb37ac87ce84d0ffdda36d8d6072"
-        },
-        {
-          "class": "CurveBN",
-          "bytes": "b32e9abad43a3724ff37dd9d15253fe66339f978e7581dff5b34b4db970b7bf3"
-        }
-      ],
-      "output": "57d604fa36e3acbe841c5367f29393868c57290fa7f411b1b9fbdbfe1c320c4f"
-    },
-    {
-      "input": [
-        {
-          "class": "Point",
-          "bytes": "03cdaadd5e0493c8426fd65004ea1e10ddcd3fcb37ac87ce84d0ffdda36d8d6072"
-        },
-        {
-          "class": "Point",
-          "bytes": "02aa6a41b809ba8d2816444788d597e6af5b81c56f7645f67e8dde417fca052828"
-        },
-        {
-          "class": "Point",
-          "bytes": "0336877b38451dd8baaa4830794091a38b7abb2c8187e1062cc2512e202739d5eb"
-        },
-        {
-          "class": "Point",
-          "bytes": "0278adf9ca33ea46b2c0fb7b5a14a2820b171a97f8c69846e9866194ed1315438a"
-        },
-        {
-          "class": "Point",
-          "bytes": "027adba799a3cf3935abb5366b5ec3f132f05a9baa0647734e7be50952e3309bd2"
-        },
-        {
-          "class": "Point",
-          "bytes": "03ee94ece91a0091e3f2c132f348716b0ab01d4ec320ad408ff9308e092dfe1e1b"
-        },
-        {
-          "class": "Point",
-          "bytes": "0203c98795773ff1c241fc0b1cced85e80f8366581dda5c9452175ebd41385fa1f"
-        },
-        {
-          "class": "Point",
-          "bytes": "026fa5450221297328a37043220b0f82a7fdf2a4ab01c9b30cd572181736390ad3"
-        },
-        {
-          "class": "Point",
-          "bytes": "038a295338304923b70a233778a1b93f869062a5d1663127a6e5a5a5b07739f21d"
-        }
-      ],
-      "output": "c384b9566bc17b5ae424862116c89f882374c5bb2ec2c5c54bbf7b4e2cc8e179"
-    }
-  ]
-}
\ No newline at end of file
diff --git a/vectors/vectors_curvebn_operations.json b/vectors/vectors_curvebn_operations.json
deleted file mode 100644
index f0e1660a..00000000
--- a/vectors/vectors_curvebn_operations.json
+++ /dev/null
@@ -1,40 +0,0 @@
-{
-  "name": "Test vectors for CurveBN operations",
-  "params": "default",
-  "first operand": "42bd8598f460a2334e921feaa58a64700bdd5eeb4a6d5f59f7363f5d167a2830",
-  "second operand": "cead0bb7e8b078069664f7a4ec81945b9c3b2233a39097f0310670c29e1e34fc",
-  "vectors": [
-    {
-      "operation": "Addition",
-      "result": "116a9150dd111a39e4f7178f920bf8cced69a4383eb5570e686a5192e4621beb"
-    },
-    {
-      "operation": "Subtraction",
-      "result": "741079e10bb02a2cb82d2845b908d0132a51199e562567a586022d2748923475"
-    },
-    {
-      "operation": "Multiplication",
-      "result": "14ae3ce9b69ed69bbd9fe81e50c9482afc72651b5a5f69b40e8b9235d6fd459b"
-    },
-    {
-      "operation": "Division",
-      "result": "1ef7189cfb9918d4fdde0298e811631ccb147036fdc2557bcc6b81db7e3e7222"
-    },
-    {
-      "operation": "Pow",
-      "result": "f558c003bc71493f8ebbe39b42316c514ccc610437f1b4f37c73f81fe903dad2"
-    },
-    {
-      "operation": "Mod",
-      "result": "42bd8598f460a2334e921feaa58a64700bdd5eeb4a6d5f59f7363f5d167a2830"
-    },
-    {
-      "operation": "Inverse",
-      "result": "6c6640d846d233a6c705c1c2c06bb1372d95bca81ea8cdbb6840ccb9a415dc3d"
-    },
-    {
-      "operation": "Neg",
-      "result": "bd427a670b9f5dccb16de0155a759b8eaed17dfb64db40e1c89c1f2fb9bc1911"
-    }
-  ]
-}
\ No newline at end of file
diff --git a/vectors/vectors_kfrags.json b/vectors/vectors_kfrags.json
index 33b0eae4..e1f3a959 100644
--- a/vectors/vectors_kfrags.json
+++ b/vectors/vectors_kfrags.json
@@ -2,39 +2,39 @@
   "name": "Test vectors for KFrags",
   "description": "This is a collection of KFrags generated under the enclosed delegating, verifying and receiving keys. Each of them must deserialize correctly and the call to verify() must succeed.",
   "params": "default",
-  "verifying_key": "03714109aee5bded1ea98ced8660ad1d49697e14f2a1bbefd5944e4ce443493d08",
-  "delegating_key": "021c0e2868064f20441df0050bf501137ec84db6ca3596b88a967308e14bea2caa",
-  "receiving_key": "036285c48fbb7c38bccceb6e15959a33ed3dd5ab5c96daaff67636b8557d6885d4",
+  "verifying_pk": "03f24761ac8b02de08ad1622d023f669d6214c3bab81a33087ed3ec5505e4d43db",
+  "delegating_pk": "03a73623a2e72fd52b2d313214c7495580c14fe6cd8de7ad0d63bbfbfd6fb6bd4d",
+  "receiving_pk": "02952a1903b9c929f0d93d935b34b272ea25a84833a04e22d887f27bc3bf0cc409",
   "vectors": [
     {
-      "kfrag": "417225b396fb91a82f0ed97b4dfb235200742a58d9632d46e352e507c5c716863b349c46905501861f950d11aefd2c69a1f4fe31ae91b4c8624c7556ec7d01ef026fa5450221297328a37043220b0f82a7fdf2a4ab01c9b30cd572181736390ad303b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f4103428244169c9d2d3cfefa6b88251e7600c4a6f9a4edb5e371a0af7bc5134bf68ce9ad3b8a1acbd66926687e2cf7a30762527a85d41b1eaee133866df9dc4fe49b43601bb4d9b496f59e44e2e172141f9480bad0cfda18e5dd6c8dd7a5e72b4bc59281ed11ba75b87f7f1919c30b56d7f0c522b77fd15d37f893fc784eb77d1493"
+      "kfrag": "3f3453856117dedb3d7518d0435c04b10734700c8aca48fa5a8b85eded515cdffefbefddcd3cb728dcc6d05061b5e201aa1cef55ce89e94c34b6887b307d5ab2025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb7490374d2e59ea274c8011f6bf26ca8fe8eb8e7837cafed8547485e3fde6ffe0368e98d26b0d7e12095941daffae8ca429147d9686dc3285ea182c0d7b15db41c7afd3fae3c88100707ef28b53d7cf93961dc509864eb319f0cce274544306c96099b5792af0b820bf758f5c8f2a69cfc1e0a5f91fab96ab82c10c7740602f90efdd315ff5dbd07323526ca1c9a93132b4d008fdf1796a55b92e2fe75c544652256c80101"
     },
     {
-      "kfrag": "fe11bce6ea1d451fbe396c6c7d00747264324eebc93bd6ca9e949c2970be214e56571ecd1c02064be2058043cbe5d7ddde90771c5709c1cb48f2a4866bef246b03cb7014d5978aba65b7849bc5756d0e94ce515cc31725ed86550e4e38424b63d903b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f4103adbaf6ef97a68ad61ca39b1ab244b5d1881b00fbf7e2f0eec06907a0078ec4fcb573d101c2d1976985fa7d6d7c96b327030333ac3c66fc0223b456a70e996e19a31822dea4781891b054e86ff34abbfcce05f7c613b7296133ad5b10455061368a102dff42a913edb8e5d248a8d979879a3cfca1f8d3fbf6b8a24ca5357ae057"
+      "kfrag": "d15ac028be9938b5e2d7a9fb9957b416db538a11d141d320a5f656ab4e3cb0cab3a2ddd2672f3514e7da2f29cbe3815c83b6d704e09d595a68fdd5aeff52ef8a025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb749033d6984555d41614a2ba08c2182e27a7105cb60ed414c9732a156c3e44d196dd3507335e3781d6c9a4543e3ae81a4c7538cf280292c9c0d92e4f513e4e073721f2f72a1ea0e69912e21754e32edf9cefd3a96183f5501266bedb301f291709ffb25ed5213660db4c8bfc086e794650455bfde1251f92e1fe49f1feb016ad44fe859b342964db14dddfa6a33eba53021149d8663d306850b8907f9dabf4a5d3ecd0101"
     },
     {
-      "kfrag": "4edffcacb49f5620c1cebd67f43366aa22dc380dd9fd0f4853ffbbd44e31986f4a99f953e5c553f08a83cb16c0b5c793a4f691d4d3d239e29f232d2b058970d903041b77b8891d845c240a8a12b71dfe9651de67ceb817b54eb578636fe8a9605103b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41035ba90e2a06117e205dd807c780b5687ff6dadf19454fffb367e7af6b3f9cadf03572dc3311c0e1706354ba3340d1b1f915afc82007f9e09ddd34ce07a038a7bb7ecc7343b1532b175e5b8c7d3c712e3eecb18ff69c22237ba9ab6b47b4572529a35b29c48874f396def9971c0230a51e26975a953ff87af8fdbfd53bf42aae07"
+      "kfrag": "f780e25b78581e70d28f102264e1f04b482eb9ecd45c188f2f9cd90026627f904b903c6da7529805e1d91fbaebc384588083e2c5f235c31e7f768b8d0df43513025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb749032f4d57c068c255281703ba6d449345e2f6240a4b8b3047209ff2aca5edec0ad2394020e796b7b9f9130c0ab02b4c8151d050f38f3d0ff5f38dc164e4d254ad2737758c4b67c0b54267b113692f11a410ffc5fe3e41c2f8ec9246854baa63dbfaebb58f0cc89c528e119788c521bbfa8dd33c69129c1a05ed454d7534224c9df35007ef11b38769ee8d2788c72745e50e14e32b0c2b9d988e2550dd3f89cbd6b50101"
     },
     {
-      "kfrag": "762a4ae9fdbb74de327a29d25c577c46521ee44a951c6e33739e5ceb2a340aefd2a93d8cbe37b9149dd26ce59b0b7ffcd9adf1ca822c5b477595ecba1933291503409c76b74f0f415f5b6a8421514c250f71a5c5b6c5b0d89148e03ba2a624a9a503b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41034f04b1666bc36ac5d3b7ba66420b660d19caf9a995b6cc7cc0aa7c9d0e739f25b1048e754ffd0703e16b453ae8279e5410eb16d2d53683f2d80c30c39918834d70970a09ae17c014fc939979c1e3659b92a40d8688ed77861407316a22d4a8e0b5628c5eec0a2123129ebdb627eae78f8c404f8e129dd407feb2e970b0ba5e39"
+      "kfrag": "afdbd2183491cec86259c4e6785c9048f17e1f0c86df597983e78f50eb9e88ebfc725b6addb07ce67e5e33e404237dfd9baf15825329d780bbcf07952d899709025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74902fbba98644ea41f7abbef7f0a80d645d428844eb6a08c848b73122057f02cc264f27b1b019d667b1a3c89e176b9203870c42dcf3bb6af9191a89db98b2cd1a1b82c61d188f2d2a5d8e1c92a64d3402e98ca09e4594dd67ad74a9583e47b5ee2fd0d38772f8dee198e6e7fa2f7acca079a90b280f98e880106bc337c314bdea20b1e8d1d43ed0912a4a55475c2587ba81a16b9775681774a8bc9d1af2395875e000101"
     },
     {
-      "kfrag": "b40778198920434d132c4d3724a7b7422428c9921bd627710c3764a6c54376f6a03be8af10cb0f1c3b74d132dda5feeb4340a9f6213da859c05b6b57249e54e903e9198cc0e86b880251931ab162130f93242164a5b242015195290ba75b5a6bea03b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f4103f27829a4798466ed68c2c53137e4e658e86faebfabf2556837b60b502a1721de3e073a98974686d1b847c27167f118eeea6a27029775800cb1618b393bca9cc35b341b546c8851395b3b68945b73b01fdbe94a9f6c400b22bfb5e3bebeee95887d888c49ccf99553a2941de1cbd3f845c5725d1b6b26017dea5cf161fadb5fda"
+      "kfrag": "831f7b7b14181746fcfd7e26e03e3fb63e7a8903acff3dd3c7ad0c2c94550d9abcced3aacb62fa59fd2847a2b18cc6a6f0a6f646e39ec94f05ef186186dfbe10025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb749020db8647602d7401b47c556f25540bd45da99e69462ee8eef1b9c26a26a3757c77f90b015143d8516a5bf2ada51a24b8192e1326d38bf47c0f54591619db42c7c122fd0d955f788dfdeee8d762022887216a3de332632d44092f1f06793eb351fd6acaf0f2060ace737a11e1c1cec72ba361ce754c913765113e1695de323225f24c3f0bbf832c9062d004b69e40090930b40d5986eb9ede462e5d044083127450101"
     },
     {
-      "kfrag": "bb063b1ae2a2e2c804671a40571b5d9a7746a7b11b98fd1794207daf68e8d408e4ad0acf429b3e97d7d098766cf304fc43454a79d541a8e85cd95ee93b7bfb460242325b9a16de25bca5ce9225091b2d9eec4b98fa627f6c2fec591cfba3561bd303b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41034c2742c62c69c571623a751dc842998cc47a6d33c7e2d30e099bc10d2675fa8bed049ff1474d604eb97e6c1b997dc506bfcefd6aed3838db23fd08f0cebe176fad3a0c5734d986b506145f0770e6689eb1425eb2817d699be1c993d64b26f2dd59d3acf01c5372adabebaa04336ff03219eadf1fd085a6957bd5e90b83919774"
+      "kfrag": "c63acdf5fb6820df50354e74e65eefacaf75e7e22c780b2b5e2c49a0db9d353e7217ca6a942fce879bd4b596aabd489e0c5af75bd94b72aed37999799e9c8dc9025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74902fcaa0a22aabe3b4564972a9368d6a752bc5e359b306cfaff1626b49911158cda599e662db88909f430575f44b3677a8161a5003519e48cf0da635cb20b15f18d632a1bf41eb525b9ee9fd47f196fdd15059d989f9166e5b437cbbd66459e03fad404248f1da8048a5bf9dd9650fbdd50d1afcbadabbcd7a5aca1882b5eeb803443cd4caff8c4b090bc263f56f496aca086bb6f1fedfd5a99e1715ebfe2c6b5010101"
     },
     {
-      "kfrag": "9a2d97e3bba40307b7c60f6f22c5965f6587206cef4d5d7980ad8d75f0f4ae69de90b3ac071a58a06c4d2d0895db9e71a4f36b86fd078dfce6e436514f70797d03d959ebb3d7ee2fd90f2a390da0d7377f7549877f7720739e7e84e0c488a6d16203b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f410394ba445cfe40a9ebf1bc6a6f9588b704b11b129a2a102e8c5bf892fbb695e2d37d396c1b34f8eed4f348ecb11a29fa2b68c3790ef2ddc169e6fa7dc86e03b1a7fd8faf67773903168ee5f9c2ac60468cd81c7ee389af0f03f3fed356c65e7f182faa22867aa4544554d235f78d28afe7d90fff1cf60184bb1ea947a343c13d07"
+      "kfrag": "1365f76b3cecb0d27cd839263d6e91e8a3e703cb10590fc4ee8c533caed2fcb4e390938ab2eb832d51d7436058182fb46fb4288c9409a2465c25e447919e61eb025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74903ffe0345c282a9890aa6759dadd14afdd50a82c19123a110d37f4582a1549ce5d4308de0dbbc76897e910253bd1fdb10e65bbbe45cede7dcbf494eba5dee18f9c141cc095ff275208485abe81b229b730c11a1fd87de00756e3d705faff9e6f508cff3f81aec66079b4737c5d55ada03f7a749da2605e6b1bb57324528c373cac773526c02a0cf876744a5e7cd7dee1bad8ccf1e81a46d4c7049743cabca94dad0101"
     },
     {
-      "kfrag": "3d09cbb50ab9ff970c93a03976efaf5f13aef88fc4e1b8840cebad96d0469605b343d7f3d4297ada1fcc08009260250f29dbb64bfb6dd64c2dd72174be3d87a202f665886dabd4fd94513aa1082a403efcb9722115e7c44f8ce5394799e10c2ca203b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41037ec97969ef20834c753f460c1c29360ce535cb19e0dd0da3bc9c568157137c3f741dadb514586112bfa22c4aa64c28ba854f7393a04ce77a3c4b731e6eaa41e29f2d240b3adf5a973688c7691e8bde140647f4d2c4d0c06a0d690415a82f18f095fb69a084607734579a590c6d1e477d93716919c67d6627a890fa5a2250109a"
+      "kfrag": "d961d60a31b197ee26437b1ff6b484c63bcfd208f509af1225a8db8f65240e366f7924ae66d6cd64c25c74761067bcb3a388cf2d4d2e90738666252868e790b0025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb749037c88f9bf80bb6a406843e339dcc8548b3f8bf16e525be9c94d5e3b8c243f466fbb19cf3eec376f9c46d9c52040555927c7eb506af29718ed9b728ac210d43b2b4958eae22852a1e3761a71cedff210a52f5d312e5cc1b12bdbb2edbe3f955f5535b8b1ce9e3e8b04a010a2dd227c9d906d7806fdf9df07d8ce379597bf7513c23db95d84e7774a3fe0de392cd7390c6ec7cab60ad22317d8c00de0865bd0ceb80101"
     },
     {
-      "kfrag": "71c39f563bdda903d8397370b077ba1348e07cf644eaffdfbca251f63d8e0c8377d0f143019bca10090a07c15ab9d6d71857451443e0e77ca6768b45b93892b802b54fd786330796eebd5df0a7d5dc26422177d5637028aaea293dfa223a72a56203b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f4103aaaf48203b37dc6152c0735493517a31284be4c46b57a21f01359572d1e1e3d1b0fd64fc82efb401f25dab88e48474450b274625c28e16cb51f31259206aba6fe4886083237bdb5f7da67c8e1e2da7fdef57789c919ccceae6b5453eaa22fb6dcb58a2f9f1f20f05d03326031d4817ec52cf3dc9eb37f2d3b014ba315f5b8c16"
+      "kfrag": "8618ade86dd8b8f15c206274caba6955c30171e466e220ef2344af25f3ca5976ea2eb761d4e8d083e4bf30052c90f6fde525716bd19b5abe98d66bdd48745f85025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74902bb441051315ea81c8d9d9cc4e63d3f66e6fe3ef52217e8639e0af421d5dbba1be604f5168408cc4bf118346126b9a105b57524b741e141d5cebca15c8964669858bed28994196a23e4f99706cdb8d912d9c53b7c58f98e6d5eb5a4ef3aa2b7b9e0a270af089c667647613c6abe1b7aba8103ee386a04df80341e51e132d6d35d28753fa65e7f8d8c8f3a10d8adc8499a174bcb8041cf6078520ef5e9e00853290101"
     },
     {
-      "kfrag": "55faf9c9be6d59da90b2c2e9f09d85e3965e00629b31c3854367f86039725ecb5c560659b6dc7d16a5f3202da0489aa02cbbabb65d8e1107c3645ca7e5bf36c3020e06cdf7d03bafc42e77c86d07b76840ffa7db6a5c511f3530d44b34edb69aba03b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f4103f5674cd594e92cace034b8b443a7aae0ae48ed57ca9eeca5ad6e2855334a989c0ea976c30e4f5eb2527b01caa468f150db4a19cae08ba6015df41fc07d4ea6bb33433a543608fd59cacba037daecf6e069f0954ce9a5f1651297c28cf31fdf76b655ed10284f5bb436c93934bb89b2434b7ff9f7bcb414b3e879a5571c88a0b1"
+      "kfrag": "939cfdfc368ae2c011ade7b7d18543f66f6b2f5aea0ed6732830d8ab281ec492cd90cd1756a44414e51d741e99f9a7d46cbedbfc7bdb60f6b8d33ffb1d4d6b31025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb749020d6a191832eed71367b495a514ee4ac442ce19040c00c4e6ab331f844b2fff11d1db98034e5ff8f86a5b2d015cfa439774dd83644adb80bcba1881f09bdb1b105e37329495d03775e99da0f1c99e753e6868fbe04766572a086ce8e1c575e0d253698578672fbfdcdf2607513d0d0c03f346cb8183506d179e9b999988c7ca317c10e8606e3f7a18ac9146dcc2f484dc6c46a149c371c7a22b17c14f0e3e84220101"
     }
   ]
 }
\ No newline at end of file
diff --git a/vectors/vectors_point_operations.json b/vectors/vectors_point_operations.json
index 520d4d43..2e39456c 100644
--- a/vectors/vectors_point_operations.json
+++ b/vectors/vectors_point_operations.json
@@ -1,37 +1,37 @@
 {
-  "name": "Test vectors for Point operations",
+  "name": "Test vectors for CurvePoint operations",
   "params": "default",
-  "first Point operand": "036893b75f97f4200270c07d043c86d4b9a48e0f0cf4a649b5311cb557cd49f65d",
-  "second Point operand": "022a7baeacffd711253616cc83da6c17451e5511b92fbef51da2a74f17db1989c6",
-  "CurveBN operand": "42bd8598f460a2334e921feaa58a64700bdd5eeb4a6d5f59f7363f5d167a2830",
+  "first CurvePoint operand": "02eb0184eedd9d14e10ad0714afd9915c58b2b40b582283e3e741d0141189246c0",
+  "second CurvePoint operand": "02b925f594ea60040f470195c72fc7aa8caeac7161af3abe65dceb2908bc866754",
+  "CurveScalar operand": "d63d8806eba7bfc2f5fd77d21aa5d7cc7cffcee26ac096f7c5904629c0db1c12",
   "vectors": [
     {
       "operation": "Addition",
-      "result": "024e520b33f362bf072339db00c591ed9082914fa3c4f3e32b0e614019bf4961de"
+      "result": "02de2d20749111f538575c81e0abc406c71cf6d4c7c1b3555476d6f8778f271d5a"
     },
     {
       "operation": "Subtraction",
-      "result": "021e4e230982f00e23b7b95eb4fcd36881f478b90928a35f5fb0a46fe36d7eb8ed"
+      "result": "031bd3e6f5b33b3f94601d3a243efaf2c359c3b097aff1aa3fa8fbf5210f110b8d"
     },
     {
       "operation": "Multiplication",
-      "result": "03f72a2a0bfef906e5ae98931f156db679bfbb3e72aed461b217d9a035cb2882ad"
+      "result": "020fd46d21ec56d94a787c6d717222489b7a070a4f57065f442f588789d1feed87"
     },
     {
       "operation": "Inversion",
-      "result": "026893b75f97f4200270c07d043c86d4b9a48e0f0cf4a649b5311cb557cd49f65d"
+      "result": "03eb0184eedd9d14e10ad0714afd9915c58b2b40b582283e3e741d0141189246c0"
     },
     {
       "operation": "To_affine.X",
-      "result": "6893b75f97f4200270c07d043c86d4b9a48e0f0cf4a649b5311cb557cd49f65d"
+      "result": "eb0184eedd9d14e10ad0714afd9915c58b2b40b582283e3e741d0141189246c0"
     },
     {
       "operation": "To_affine.Y",
-      "result": "f940cab3f294e54a5ccd5643a212350be822b145e6dab7cae5ad1d98ffb9bbad"
+      "result": "d9bed51d198e8ccd919b54a6eabfed2032cc737d410e0364643716986de89afc"
     },
     {
       "operation": "kdf",
-      "result": "1bbe934cc018de9b123002acdf658b80d63456206c948279ea832832349328f3"
+      "result": "40b49492ba7924c421dd61ea39bf94ac6566feff43a1ef14e7adc2b9af3f6664"
     }
   ]
 }
\ No newline at end of file
diff --git a/vectors/vectors_scalar_from_digest.json b/vectors/vectors_scalar_from_digest.json
new file mode 100644
index 00000000..b3ad32e2
--- /dev/null
+++ b/vectors/vectors_scalar_from_digest.json
@@ -0,0 +1,92 @@
+{
+  "name": "Test vectors for umbral.curvebn.CurveScalar.from_digest()",
+  "params": "default",
+  "vectors": [
+    {
+      "input": [
+        {
+          "class": "bytes",
+          "bytes": ""
+        }
+      ],
+      "output": "42184a0ea1e39037cad1ed7f3bb0cd8b7fe978e6d8b94f965e47d582cbdb8208"
+    },
+    {
+      "input": [
+        {
+          "class": "bytes",
+          "bytes": "616263"
+        }
+      ],
+      "output": "02e2c58350c30e80f9deea1ae19e21a0baa7761f4448c792f205b8e9b7ac1ab3"
+    },
+    {
+      "input": [
+        {
+          "class": "CurvePoint",
+          "bytes": "02a03893438c0502dd13818f65c039b2ef4fce33bfed150c6ad166554b4e8a51c2"
+        }
+      ],
+      "output": "cd175898869252f3d6c6e77eaed94a72e74410d99534b349f27df2362c35745a"
+    },
+    {
+      "input": [
+        {
+          "class": "CurveScalar",
+          "bytes": "5e8601ee29241f263bf49b9999594413f863193fa1b8a985fff981cda7cc9087"
+        }
+      ],
+      "output": "2b83903fedca70169024365a4a5b387536a9ba38bd7b9fa0462f5f932f41a493"
+    },
+    {
+      "input": [
+        {
+          "class": "CurvePoint",
+          "bytes": "02a03893438c0502dd13818f65c039b2ef4fce33bfed150c6ad166554b4e8a51c2"
+        },
+        {
+          "class": "CurveScalar",
+          "bytes": "5e8601ee29241f263bf49b9999594413f863193fa1b8a985fff981cda7cc9087"
+        }
+      ],
+      "output": "9f7cea094a5ed29ab2ec83391527db31850f915781c07c0b13853869e9885968"
+    },
+    {
+      "input": [
+        {
+          "class": "CurvePoint",
+          "bytes": "02a03893438c0502dd13818f65c039b2ef4fce33bfed150c6ad166554b4e8a51c2"
+        },
+        {
+          "class": "CurvePoint",
+          "bytes": "02ecbc4dc0aed60efa211c7bf0e238593d292a042373b891928bcee459151c2f44"
+        },
+        {
+          "class": "CurvePoint",
+          "bytes": "02aa6a09c61286e36d82f6371038f1c33b2095b3b6dc8d09de7489f516c2dfe49a"
+        },
+        {
+          "class": "CurvePoint",
+          "bytes": "028da9ebf8cc0966bc010152fd3917a8f12dfff0af3b06e34e17f300d622893159"
+        },
+        {
+          "class": "CurvePoint",
+          "bytes": "0281171b7e330ebd097575dadb210e8a405bc162e293881457a301da03f7571c7a"
+        },
+        {
+          "class": "CurvePoint",
+          "bytes": "03e2869b26bbf46a2e1f46116d8d9eeeb45f18acf4a808defee52040221dd08af9"
+        },
+        {
+          "class": "CurvePoint",
+          "bytes": "0374d2e59ea274c8011f6bf26ca8fe8eb8e7837cafed8547485e3fde6ffe0368e9"
+        },
+        {
+          "class": "CurvePoint",
+          "bytes": "024526ff9ab3c9c4cf619166ff897b8c023a6ff01f54c42a921ec1ab564d5a65eb"
+        }
+      ],
+      "output": "072c408c4631491eb12b00b38b8f7f20080b802e3e7c5c421f89887055248b1a"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/vectors/vectors_scalar_operations.json b/vectors/vectors_scalar_operations.json
new file mode 100644
index 00000000..d7e342c4
--- /dev/null
+++ b/vectors/vectors_scalar_operations.json
@@ -0,0 +1,24 @@
+{
+  "name": "Test vectors for CurveScalar operations",
+  "params": "default",
+  "first operand": "d63d8806eba7bfc2f5fd77d21aa5d7cc7cffcee26ac096f7c5904629c0db1c12",
+  "second operand": "c2cc9d2f0b39201a5d4d4aa755c0506eab19c1abc89068d216f23f4965427ac4",
+  "vectors": [
+    {
+      "operation": "Addition",
+      "result": "990a2535f6e0dfdd534ac2797066283c6d6ab3a784085f8e1cb026e655e75595"
+    },
+    {
+      "operation": "Subtraction",
+      "result": "1370ead7e06e9fa898b02d2ac4e5875dd1e60d36a2302e25ae9e06e05b98a14e"
+    },
+    {
+      "operation": "Multiplication",
+      "result": "88cdbd2959262c74f26d4315e65b7e8c4fb5d1326fb9f1c6dbfd7c951d43485f"
+    },
+    {
+      "operation": "Inverse",
+      "result": "663c74e198bd4dcd2db7b78895fe8994a727d8bcba073818475a22483bb0103a"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/vectors/vectors_unsafe_hash_to_point.json b/vectors/vectors_unsafe_hash_to_point.json
index 920ed742..fd2a4394 100644
--- a/vectors/vectors_unsafe_hash_to_point.json
+++ b/vectors/vectors_unsafe_hash_to_point.json
@@ -1,86 +1,86 @@
 {
-  "name": "Test vectors for umbral.point.Point.unsafe_hash_to_point",
+  "name": "Test vectors for unsafe_hash_to_point()",
   "params": "default",
   "vectors": [
     {
       "data": "",
-      "label": "",
-      "point": "03bb27bd2a7b7ff7500284bc44285d80ce5527448c79e7ef25843d1209c5df40c3"
+      "dst": "",
+      "point": "0215ec7bf0b50732b49f8228e07d24365338f9e3ab994b00af08e5a3bffe55fd8b"
     },
     {
       "data": "",
-      "label": "616263",
-      "point": "03eec257593bbefd7f617350bd2aea9b14cc14a93528bad07b6e39c5db433adbd8"
+      "dst": "616263",
+      "point": "0297427e8f434c897d66d7ad40b51e0a11f8bdfed31e724ca4ac86c14fb5e2668f"
     },
     {
       "data": "",
-      "label": "4e75437970686572",
-      "point": "0352184687070d943946f5da637b8e2aa00640b0da2231e7598ceea0286657a7cb"
+      "dst": "4e75437970686572",
+      "point": "025aa967c88c73854f4f4d9286a7ce4292898dfa42c7593b75cf823cd1b6dada96"
     },
     {
       "data": "",
-      "label": "4e75637970686572",
-      "point": "0290c2fff33ad722da7cc813f57153afbcf33fc2acd1607f188770f9dcc7c00a0d"
+      "dst": "4e75637970686572",
+      "point": "02f3fcae4fb3596fbb34feaf3e2fff938b177b55d89c66f728e51fef220d9b702f"
     },
     {
       "data": "616263",
-      "label": "",
-      "point": "02a79e7fe8a6559b5de0614702a4e13d97a672a899d5943e5c6b1a0c45fca933b0"
+      "dst": "",
+      "point": "0204c19746f60b6c4abbce9dbe31f2e0df9b22d8130cc0844cbf67db154d944db3"
     },
     {
       "data": "616263",
-      "label": "616263",
-      "point": "0234cb950681a734bc29feaa5c1dd029a9062352488bae035acd56c0ebe99f0b7f"
+      "dst": "616263",
+      "point": "024c70ca862edba77a8265ee46e0137729826a79721855888bf7791feea42b9990"
     },
     {
       "data": "616263",
-      "label": "4e75437970686572",
-      "point": "032106a37075e17b6f01b4ab63d5c84622efe5111843155c7d53e34548f287115d"
+      "dst": "4e75437970686572",
+      "point": "02357334755ceedaef03cb81b6dbbebd8399e0cf40a122a586069ae241e34fc869"
     },
     {
       "data": "616263",
-      "label": "4e75637970686572",
-      "point": "034b4db429ccf36858718cb864c0c27520fb16218992a3290d1fc4758756ee0bef"
+      "dst": "4e75637970686572",
+      "point": "0256ec5dbf81d55fbdad3c2095177982a068bb0043dd2cf2834cc6a53e538157bf"
     },
     {
       "data": "4e75437970686572",
-      "label": "",
-      "point": "03828335c1f3ccac7ec40d6dd4771e9ba527b039d7104fa0477a96cdccbf16c7cf"
+      "dst": "",
+      "point": "02b0cd14ef08638d57804c768d3b0a171461268f6faede586751f2919bdd7490b6"
     },
     {
       "data": "4e75437970686572",
-      "label": "616263",
-      "point": "03193846a6ba4d9dab59990e53317050fe301477d3ac699f8cc260dd33cbc2c9c5"
+      "dst": "616263",
+      "point": "02a1c3e1c00f45a059fcf7749e31c5206388aa72bcc7c10195907e9c70c2a0a700"
     },
     {
       "data": "4e75437970686572",
-      "label": "4e75437970686572",
-      "point": "0324215e7df37205a23c0b7f0a7e168c7984d9109ef31ebe7b37edc7d81ede4b55"
+      "dst": "4e75437970686572",
+      "point": "02100c656eed3ed2e175e5430bbd644ac86f24fa69fc1c5b3fd65ece562b480764"
     },
     {
       "data": "4e75437970686572",
-      "label": "4e75637970686572",
-      "point": "03d5744667f3f2ff36217380a5d1701edf939d70b79d17d78a3969b533acb7c326"
+      "dst": "4e75637970686572",
+      "point": "0227a46bc66817fcaa803535a1c109674d300de5df0d8d11f6588325cf6cedf2b1"
     },
     {
       "data": "4e75637970686572",
-      "label": "",
-      "point": "03f31c20a8264446d224c4ae6368193ac8b97247cf386bafe2cc27ffb6783ace19"
+      "dst": "",
+      "point": "02483315691815818fa1f1804406fc4246940cc8cb39405401e2aa5fd8d94bfa64"
     },
     {
       "data": "4e75637970686572",
-      "label": "616263",
-      "point": "0347b4a8559bf9eb00cdc4c183ee4ff0a5c3a12692988d88927b07157c3fdd50a1"
+      "dst": "616263",
+      "point": "02dc01829e4725f8cacf6990c12ab0a5f837770b21e41bdd9964bb0f1ad52fcc31"
     },
     {
       "data": "4e75637970686572",
-      "label": "4e75437970686572",
-      "point": "02b158ccb0b3384c2a8af92ac3e0e59ed9d0647fb62715fefdc9c0796c0b28bebb"
+      "dst": "4e75437970686572",
+      "point": "02b6653e2ed79579380104598cf83fc2b119dd8b91afae2a2a8077ffdca0b212ad"
     },
     {
       "data": "4e75637970686572",
-      "label": "4e75637970686572",
-      "point": "0356b5aa5ef6be8229f3611095a47842aad8a3e54b3aa1f6caea696fa4bfdfe95e"
+      "dst": "4e75637970686572",
+      "point": "02192de02d9c15a52d90ef7192794a2fc925c09f7dcdb4b584b8c7fab33bbda1df"
     }
   ]
 }
\ No newline at end of file