From f57c4ba5a4f7648cdda4cefcf11e229ccc73cad1 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Tue, 1 Jun 2021 23:55:48 -0700 Subject: [PATCH 01/10] Add VerifiedKeyFrag.from_verified_bytes() for the purposes of Nucypher's datastore --- docs/source/api.rst | 1 + tests/test_key_frag.py | 8 +++++++- umbral/key_frag.py | 12 ++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/source/api.rst b/docs/source/api.rst index 5e84a7a..931ffaa 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -40,6 +40,7 @@ Intermediate objects :show-inheritance: .. autoclass:: VerifiedKeyFrag() + :members: :special-members: __eq__, __hash__ .. autoclass:: CapsuleFrag() diff --git a/tests/test_key_frag.py b/tests/test_key_frag.py index f2a5dc8..4b12162 100644 --- a/tests/test_key_frag.py +++ b/tests/test_key_frag.py @@ -1,7 +1,7 @@ import pytest from umbral import KeyFrag, PublicKey, Signer, VerificationError -from umbral.key_frag import KeyFragID, KeyFragBase +from umbral.key_frag import KeyFragID, KeyFragBase, VerifiedKeyFrag from umbral.curve_scalar import CurveScalar @@ -124,3 +124,9 @@ def test_kfrag_str(kfrags): s = str(KeyFrag.from_bytes(bytes(kfrags[0]))) assert "VerifiedKeyFrag" not in s assert "KeyFrag" in s + + +def test_from_verified_bytes(kfrags): + kfrag_bytes = bytes(kfrags[0]) + verified_kfrag = VerifiedKeyFrag.from_verified_bytes(kfrag_bytes) + assert verified_kfrag == kfrags[0] diff --git a/umbral/key_frag.py b/umbral/key_frag.py index 34fbe60..27f945e 100644 --- a/umbral/key_frag.py +++ b/umbral/key_frag.py @@ -253,6 +253,18 @@ class VerifiedKeyFrag: def __bytes__(self): return bytes(self.kfrag) + @classmethod + def from_verified_bytes(cls, data) -> 'VerifiedKeyFrag': + """ + Restores a verified keyfrag directly from serialized bytes, + skipping :py:meth:`KeyFrag.verify` call. + + Intended for internal storage; + make sure that the bytes come from a trusted source. + """ + kfrag = KeyFrag.from_bytes(data) + return cls(kfrag) + def __eq__(self, other): return self.kfrag == other.kfrag From c7292fe92c76ae777502831c4bbd61da9534f63c Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Tue, 1 Jun 2021 23:25:34 -0700 Subject: [PATCH 02/10] Remove unused dev dependencies --- Pipfile | 8 +- Pipfile.lock | 501 +++++++++++++++++++++------------------------------ setup.py | 4 - 3 files changed, 209 insertions(+), 304 deletions(-) diff --git a/Pipfile b/Pipfile index a6e4579..93f16f9 100644 --- a/Pipfile +++ b/Pipfile @@ -13,23 +13,19 @@ bumpversion = "*" # Pytest Plugins pytest = "*" pytest-mypy = "*" -pytest-mock = "*" pytest-cov = "*" pytest-benchmark = {version = "*",extras = ["histogram"]} # Pytest Plugin Subdeps -mock = "*" mypy = "*" -monkeytype = "*" coverage = "*" codecov = "*" -# Testing libraries -hypothesis = "*" +# Testing libraries nbval = "*" # Docs sphinx = "*" sphinx-autobuild = "*" sphinx_rtd_theme = "*" -# Overrides vulnerable versions allowed by codecov and sphinx: +# Overrides vulnerable versions allowed by codecov and sphinx: requests = ">=2.20.0" [pipenv] diff --git a/Pipfile.lock b/Pipfile.lock index 6e03671..c20b578 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "55db9d5f9de5a65ef3a43b7931e19d8a1ee2cb9cbfa9e616fa2c512f8576116b" + "sha256": "5046c797bf7733818d480d0c9df2934393dbd080d847a85d03221cff74ec7907" }, "pipfile-spec": 6, "requires": {}, @@ -17,14 +17,23 @@ "cffi": { "hashes": [ "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813", + "sha256:04c468b622ed31d408fea2346bec5bbffba2cc44226302a0de1ade9f5ea3d373", + "sha256:06d7cd1abac2ffd92e65c0609661866709b4b2d82dd15f611e602b9b188b0b69", + "sha256:06db6321b7a68b2bd6df96d08a5adadc1fa0e8f419226e25b2a5fbf6ccc7350f", "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06", + "sha256:0f861a89e0043afec2a51fd177a567005847973be86f709bbb044d7f42fc4e05", "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea", "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee", + "sha256:1bf1ac1984eaa7675ca8d5745a8cb87ef7abecb5592178406e55858d411eadc0", "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396", + "sha256:24a570cd11895b60829e941f2613a4f79df1a27344cbbb82164ef2e0116f09c7", + "sha256:24ec4ff2c5c0c8f9c6b87d5bb53555bf267e1e6f70e52e5a9740d32861d36b6f", "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73", "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315", + "sha256:293e7ea41280cb28c6fcaaa0b1aa1f533b8ce060b9e701d78511e1e6c4a1de76", "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1", "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49", + "sha256:3c3f39fa737542161d8b0d680df2ec249334cd70a8f420f71c9304bd83c3cbed", "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892", "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482", "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058", @@ -32,6 +41,7 @@ "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53", "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045", "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3", + "sha256:681d07b0d1e3c462dd15585ef5e33cb021321588bebd910124ef4f4fb71aef55", "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5", "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e", "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c", @@ -49,8 +59,10 @@ "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e", "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991", "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6", + "sha256:cc5a8e069b9ebfa22e26d0e6b97d6f9781302fe7f4f2b8776c3e1daea35f1adc", "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1", "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406", + "sha256:df5052c5d867c1ea0b311fb7c3cd28b19df469c056f7fdcfe88c7473aa63e333", "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d", "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c" ], @@ -58,21 +70,21 @@ }, "cryptography": { "hashes": [ - "sha256:066bc53f052dfeda2f2d7c195cf16fb3e5ff13e1b6b7415b468514b40b381a5b", - "sha256:0923ba600d00718d63a3976f23cab19aef10c1765038945628cd9be047ad0336", - "sha256:2d32223e5b0ee02943f32b19245b61a62db83a882f0e76cc564e1cec60d48f87", - "sha256:4169a27b818de4a1860720108b55a2801f32b6ae79e7f99c00d79f2a2822eeb7", - "sha256:57ad77d32917bc55299b16d3b996ffa42a1c73c6cfa829b14043c561288d2799", - "sha256:5ecf2bcb34d17415e89b546dbb44e73080f747e504273e4d4987630493cded1b", - "sha256:600cf9bfe75e96d965509a4c0b2b183f74a4fa6f5331dcb40fb7b77b7c2484df", - "sha256:66b57a9ca4b3221d51b237094b0303843b914b7d5afd4349970bb26518e350b0", - "sha256:93cfe5b7ff006de13e1e89830810ecbd014791b042cbe5eec253be11ac2b28f3", - "sha256:9e98b452132963678e3ac6c73f7010fe53adf72209a32854d55690acac3f6724", - "sha256:df186fcbf86dc1ce56305becb8434e4b6b7504bc724b71ad7a3239e0c9d14ef2", - "sha256:fec7fb46b10da10d9e1d078d1ff8ed9e05ae14f431fdbd11145edd0550b9a964" + "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d", + "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959", + "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6", + "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873", + "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2", + "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713", + "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1", + "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177", + "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250", + "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca", + "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d", + "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9" ], "index": "pypi", - "version": "==3.4.6" + "version": "==3.4.7" }, "pycparser": { "hashes": [ @@ -108,11 +120,11 @@ }, "six": { "hashes": [ - "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", - "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.15.0" + "version": "==1.16.0" } }, "develop": { @@ -133,19 +145,19 @@ }, "attrs": { "hashes": [ - "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", - "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" + "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", + "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.3.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.2.0" }, "babel": { "hashes": [ - "sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5", - "sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05" + "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9", + "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.9.0" + "version": "==2.9.1" }, "backcall": { "hashes": [ @@ -172,10 +184,10 @@ }, "certifi": { "hashes": [ - "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c", - "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" + "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee", + "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8" ], - "version": "==2020.12.5" + "version": "==2021.5.30" }, "chardet": { "hashes": [ @@ -262,10 +274,11 @@ }, "decorator": { "hashes": [ - "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760", - "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7" + "sha256:6e5c199c16f7a9f0e3a61a4a54b3d27e7dad0dbdde92b944426cb20914376323", + "sha256:72ecfba4320a893c53f9706bebb2d55c270c1e51a28789361aa93e4a21319ed5" ], - "version": "==4.4.2" + "markers": "python_version >= '3.5'", + "version": "==5.0.9" }, "docutils": { "hashes": [ @@ -282,14 +295,6 @@ ], "version": "==3.0.12" }, - "hypothesis": { - "hashes": [ - "sha256:2dd38676402d1c218225210cde0cf19f286352279f32631ac5c801f5d767bc94", - "sha256:3b7d9f7e40e406b550d4fd26fef0ce3fad216f163a3400ab701329b865e25876" - ], - "index": "pypi", - "version": "==6.8.1" - }, "idna": { "hashes": [ "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", @@ -315,19 +320,19 @@ }, "ipykernel": { "hashes": [ - "sha256:98321abefdf0505fb3dc7601f60fc4087364d394bd8fad53107eb1adee9ff475", - "sha256:efd07253b54d84d26e0878d268c8c3a41582a18750da633c2febfd2ece0d467d" + "sha256:29eee66548ee7c2edb7941de60c0ccf0a7a8dd957341db0a49c5e8e6a0fcb712", + "sha256:e976751336b51082a89fc2099fb7f96ef20f535837c398df6eab1283c2070884" ], "markers": "python_version >= '3.5'", - "version": "==5.5.0" + "version": "==5.5.5" }, "ipython": { "hashes": [ - "sha256:04323f72d5b85b606330b6d7e2dc8d2683ad46c3905e955aa96ecc7a99388e70", - "sha256:34207ffb2f653bced2bc8e3756c1db86e7d93e44ed049daae9814fed66d408ec" + "sha256:a171caa3d3d4c819a1c0742e3abecfd5a2b8ab525ca1c9f114b40b76b0679ab1", + "sha256:f86788eef439891438af3498525094cc2acbdbea4f2aa2f8895782d4ff471341" ], "markers": "python_version >= '3.7'", - "version": "==7.21.0" + "version": "==7.24.0" }, "ipython-genutils": { "hashes": [ @@ -346,11 +351,11 @@ }, "jinja2": { "hashes": [ - "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419", - "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6" + "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4", + "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==2.11.3" + "markers": "python_version >= '3.6'", + "version": "==3.0.1" }, "jsonschema": { "hashes": [ @@ -375,14 +380,6 @@ "markers": "python_version >= '3.6'", "version": "==4.7.1" }, - "libcst": { - "hashes": [ - "sha256:2766671c107263daa3fc34e39d55134a6fe253701564d7670586f30eee2c201c", - "sha256:4638e4e8f166f4c74df399222d347ce3e1d316e206b550d8a6254d51b4cf7275" - ], - "markers": "python_version >= '3.6'", - "version": "==0.3.17" - }, "livereload": { "hashes": [ "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869" @@ -391,77 +388,51 @@ }, "markupsafe": { "hashes": [ - "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" + "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298", + "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64", + "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b", + "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567", + "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff", + "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74", + "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35", + "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26", + "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7", + "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75", + "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f", + "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135", + "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8", + "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a", + "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914", + "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18", + "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8", + "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2", + "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d", + "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b", + "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f", + "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb", + "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833", + "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415", + "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902", + "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9", + "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d", + "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066", + "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f", + "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5", + "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94", + "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509", + "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51", + "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.1.1" + "markers": "python_version >= '3.6'", + "version": "==2.0.1" }, - "mock": { + "matplotlib-inline": { "hashes": [ - "sha256:122fcb64ee37cfad5b3f48d7a7d51875d7031aaf3d8be7c42e2bee25044eee62", - "sha256:7d3fbbde18228f4ff2f1f119a45cdffa458b4c0dee32eb4d2bb2f82554bac7bc" + "sha256:5cf1176f554abb4fa98cb362aa2b55c500147e4bdbb07e3fda359143e1da0811", + "sha256:f41d5ff73c9f5385775d5c0bc13b424535c8402fe70ea8210f93e11f3683993e" ], - "index": "pypi", - "version": "==4.0.3" - }, - "monkeytype": { - "hashes": [ - "sha256:b8ed88485d2ffb05fb1597a6e5eacb05ba5420de682054403c06fac84fdc4038", - "sha256:fe596bebc5e1b6a64eae71a40b880688de433e4f70507a31ada48510195251dd" - ], - "index": "pypi", - "version": "==20.5.0" + "markers": "python_version >= '3.5'", + "version": "==0.1.2" }, "mypy": { "hashes": [ @@ -500,11 +471,11 @@ }, "nbformat": { "hashes": [ - "sha256:1d223e64a18bfa7cdf2db2e9ba8a818312fc2a0701d2e910b58df66809385a56", - "sha256:3949fdc8f5fa0b1afca16fb307546e78494fa7a7bceff880df8168eafda0e7ac" + "sha256:b516788ad70771c6250977c1374fcca6edebe6126fd2adb5a69aa5c2356fd1c8", + "sha256:eb8447edd7127d043361bc17f2f5a807626bc8e878c7709a1c647abda28a9171" ], "markers": "python_version >= '3.5'", - "version": "==5.1.2" + "version": "==5.1.3" }, "nbval": { "hashes": [ @@ -524,11 +495,11 @@ }, "parso": { "hashes": [ - "sha256:15b00182f472319383252c18d5913b69269590616c947747bc50bf4ac768f410", - "sha256:8519430ad07087d4c997fda3a7918f7cfa27cb58972a8c89c2a0295a1c940e9e" + "sha256:12b83492c6239ce32ff5eed6d3639d6a536170723c6f3f1506869f1ace413398", + "sha256:a8c4922db71e4fdb90e0d0bc6e50f9b273d3397925e5e60a717e719201778d22" ], "markers": "python_version >= '3.6'", - "version": "==0.8.1" + "version": "==0.8.2" }, "pexpect": { "hashes": [ @@ -555,11 +526,11 @@ }, "prompt-toolkit": { "hashes": [ - "sha256:4cea7d09e46723885cb8bc54678175453e5071e9449821dce6f017b1d1fbfc1a", - "sha256:9397a7162cf45449147ad6042fa37983a081b8a73363a5253dd4072666333137" + "sha256:bf00f22079f5fadc949f42ae8ff7f05702826a97059ffcc6281036ad40ac6f04", + "sha256:e1b4f11b9336a28fa11810bc623c357420f69dfdb6d2dac41ca2c21a55c033bc" ], "markers": "python_full_version >= '3.6.1'", - "version": "==3.0.17" + "version": "==3.0.18" }, "ptyprocess": { "hashes": [ @@ -578,9 +549,9 @@ }, "py-cpuinfo": { "hashes": [ - "sha256:9aa2e49675114959697d25cf57fec41c29b55887bff3bc4809b44ac6f5730097" + "sha256:5f269be0e08e33fd959de96b34cd4aeeeacac014dd8305f70eb28d06de2345c5" ], - "version": "==7.0.0" + "version": "==8.0.0" }, "pygal": { "hashes": [ @@ -598,11 +569,11 @@ }, "pygments": { "hashes": [ - "sha256:2656e1a6edcdabf4275f9a3640db59fd5de107d88e8663c5d4e9a0fa62f77f94", - "sha256:534ef71d539ae97d4c3a4cf7d6f110f214b0e687e92f9cb9d2a3b0d3101289c8" + "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f", + "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e" ], "markers": "python_version >= '3.5'", - "version": "==2.8.1" + "version": "==2.9.0" }, "pyparsing": { "hashes": [ @@ -621,46 +592,38 @@ }, "pytest": { "hashes": [ - "sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9", - "sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839" + "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b", + "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890" ], "index": "pypi", - "version": "==6.2.2" + "version": "==6.2.4" }, "pytest-benchmark": { "extras": [ "histogram" ], "hashes": [ - "sha256:01f79d38d506f5a3a0a9ada22ded714537bbdfc8147a881a35c1655db07289d9", - "sha256:ad4314d093a3089701b24c80a05121994c7765ce373478c8f4ba8d23c9ba9528" + "sha256:36d2b08c4882f6f997fd3126a3d6dfd70f3249cde178ed8bbc0b73db7c20f809", + "sha256:40e263f912de5a81d891619032983557d62a3d85843f9a9f30b98baea0cd7b47" ], "index": "pypi", - "version": "==3.2.3" + "version": "==3.4.1" }, "pytest-cov": { "hashes": [ - "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7", - "sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da" + "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a", + "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7" ], "index": "pypi", - "version": "==2.11.1" - }, - "pytest-mock": { - "hashes": [ - "sha256:379b391cfad22422ea2e252bdfc008edd08509029bcde3c25b2c0bd741e0424e", - "sha256:a1e2aba6af9560d313c642dae7e00a2a12b022b80301d9d7fc8ec6858e1dd9fc" - ], - "index": "pypi", - "version": "==3.5.1" + "version": "==2.12.1" }, "pytest-mypy": { "hashes": [ - "sha256:63d418a4fea7d598ac40b659723c00804d16a251d90a5cfbca213eeba5aaf01c", - "sha256:8d2112972c1debf087943f48963a0daf04f3424840aea0cf437cc97053b1b0ef" + "sha256:1fa55723a4bf1d054fcba1c3bd694215a2a65cc95ab10164f5808afd893f3b11", + "sha256:6e68e8eb7ceeb7d1c83a1590912f784879f037b51adfb9c17b95c6b2fc57466b" ], "index": "pypi", - "version": "==0.8.0" + "version": "==0.8.1" }, "python-dateutil": { "hashes": [ @@ -677,78 +640,43 @@ ], "version": "==2021.1" }, - "pyyaml": { - "hashes": [ - "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: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" + "sha256:089b974ec04d663b8685ac90e86bfe0e4da9d911ff3cf52cb765ff22408b102d", + "sha256:0ea7f4237991b0f745a4432c63e888450840bf8cb6c48b93fb7d62864f455529", + "sha256:0f0f27eaab9ba7b92d73d71c51d1a04464a1da6097a252d007922103253d2313", + "sha256:12ffcf33db6ba7c0e5aaf901e65517f5e2b719367b80bcbfad692f546a297c7a", + "sha256:1389b615917d4196962a9b469e947ba862a8ec6f5094a47da5e7a8d404bc07a4", + "sha256:18dd2ca4540c476558099891c129e6f94109971d110b549db2a9775c817cedbd", + "sha256:24fb5bb641f0b2aa25fc3832f4b6fc62430f14a7d328229fe994b2bcdc07c93a", + "sha256:285514956c08c7830da9d94e01f5414661a987831bd9f95e4d89cc8aaae8da10", + "sha256:41049cff5265e9cd75606aa2c90a76b9c80b98d8fe70ee08cf4af3cedb113358", + "sha256:461ed80d741692d9457ab820b1cc057ba9c37c394e67b647b639f623c8b321f6", + "sha256:4b8fb1b3174b56fd020e4b10232b1764e52cf7f3babcfb460c5253bdc48adad0", + "sha256:4c4fe69c7dc0d13d4ae180ad650bb900854367f3349d3c16f0569f6c6447f698", + "sha256:4e9b9a2f6944acdaf57316436c1acdcb30b8df76726bcf570ad9342bc5001654", + "sha256:6355f81947e1fe6e7bb9e123aeb3067264391d3ebe8402709f824ef8673fa6f3", + "sha256:68be16107f41563b9f67d93dff1c9f5587e0f76aa8fd91dc04c83d813bcdab1f", + "sha256:68e2c4505992ab5b89f976f89a9135742b18d60068f761bef994a6805f1cae0c", + "sha256:7040d6dd85ea65703904d023d7f57fab793d7ffee9ba9e14f3b897f34ff2415d", + "sha256:734ea6565c71fc2d03d5b8c7d0d7519c96bb5567e0396da1b563c24a4ac66f0c", + "sha256:9ee48413a2d3cd867fd836737b4c89c24cea1150a37f4856d82d20293fa7519f", + "sha256:a1c77796f395804d6002ff56a6a8168c1f98579896897ad7e35665a9b4a9eec5", + "sha256:b2f707b52e09098a7770503e39294ca6e22ae5138ffa1dd36248b6436d23d78e", + "sha256:bf80b2cec42d96117248b99d3c86e263a00469c840a778e6cb52d916f4fdf82c", + "sha256:c4674004ed64685a38bee222cd75afa769424ec603f9329f0dd4777138337f48", + "sha256:c6a81c9e6754465d09a87e3acd74d9bb1f0039b2d785c6899622f0afdb41d760", + "sha256:c6d0c32532a0519997e1ded767e184ebb8543bdb351f8eff8570bd461e874efc", + "sha256:c8fff75af4c7af92dce9f81fa2a83ed009c3e1f33ee8b5222db2ef80b94e242e", + "sha256:cb9f9fe1305ef69b65794655fd89b2209b11bff3e837de981820a8aa051ef914", + "sha256:d3ecfee2ee8d91ab2e08d2d8e89302c729b244e302bbc39c5b5dde42306ff003", + "sha256:d5e5be93e1714a59a535bbbc086b9e4fd2448c7547c5288548f6fd86353cad9e", + "sha256:de5806be66c9108e4dcdaced084e8ceae14100aa559e2d57b4f0cceb98c462de", + "sha256:f49755684a963731479ff3035d45a8185545b4c9f662d368bd349c419839886d", + "sha256:fc712a90401bcbf3fa25747f189d6dcfccbecc32712701cad25c6355589dac57" ], "markers": "python_version >= '3.6'", - "version": "==22.0.3" + "version": "==22.1.0" }, "requests": { "hashes": [ @@ -760,11 +688,11 @@ }, "six": { "hashes": [ - "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", - "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.15.0" + "version": "==1.16.0" }, "snowballstemmer": { "hashes": [ @@ -773,20 +701,13 @@ ], "version": "==2.1.0" }, - "sortedcontainers": { - "hashes": [ - "sha256:37257a32add0a3ee490bb170b599e93095eed89a55da91fa9f48753ea12fd73f", - "sha256:59cc937650cf60d677c16775597c89a960658a09cf7c1a668f86e1e4464b10a1" - ], - "version": "==2.3.0" - }, "sphinx": { "hashes": [ - "sha256:672cfcc24b6b69235c97c750cb190a44ecd72696b4452acaf75c2d9cc78ca5ff", - "sha256:ef64a814576f46ec7de06adf11b433a0d6049be007fefe7fd0d183d28b581fac" + "sha256:b5c2ae4120bf00c799ba9b3699bc895816d272d120080fbc967292f29b52b48c", + "sha256:d1cb10bee9c4231f1700ec2e24a91be3f3a3aba066ea4ca9f3bbe47e59d5a1d4" ], "index": "pypi", - "version": "==3.5.2" + "version": "==4.0.2" }, "sphinx-autobuild": { "hashes": [ @@ -798,11 +719,11 @@ }, "sphinx-rtd-theme": { "hashes": [ - "sha256:eda689eda0c7301a80cf122dad28b1861e5605cbf455558f3775e1e8200e83a5", - "sha256:fa6bebd5ab9a73da8e102509a86f3fcc36dec04a0b52ea80e5a033b2aba00113" + "sha256:32bd3b5d13dc8186d7a42fc816a23d32e83a4827d7d9882948e7b837c232da5a", + "sha256:4a05bdbe8b1446d77a01e20a23ebc6777c74f43237035e76be89699308987d6f" ], "index": "pypi", - "version": "==0.5.1" + "version": "==0.5.2" }, "sphinxcontrib-applehelp": { "hashes": [ @@ -822,11 +743,11 @@ }, "sphinxcontrib-htmlhelp": { "hashes": [ - "sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f", - "sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b" + "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07", + "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2" ], - "markers": "python_version >= '3.5'", - "version": "==1.0.3" + "markers": "python_version >= '3.6'", + "version": "==2.0.0" }, "sphinxcontrib-jsmath": { "hashes": [ @@ -846,11 +767,11 @@ }, "sphinxcontrib-serializinghtml": { "hashes": [ - "sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc", - "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a" + "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd", + "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952" ], "markers": "python_version >= '3.5'", - "version": "==1.1.4" + "version": "==1.1.5" }, "toml": { "hashes": [ @@ -917,62 +838,54 @@ }, "typed-ast": { "hashes": [ - "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" + "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace", + "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff", + "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266", + "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528", + "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6", + "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808", + "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4", + "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363", + "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341", + "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04", + "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41", + "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e", + "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3", + "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899", + "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805", + "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c", + "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c", + "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39", + "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a", + "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3", + "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7", + "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f", + "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075", + "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0", + "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40", + "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428", + "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927", + "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3", + "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f", + "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65" ], - "version": "==1.4.2" + "version": "==1.4.3" }, "typing-extensions": { "hashes": [ - "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", - "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", - "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" + "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", + "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", + "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" ], - "version": "==3.7.4.3" - }, - "typing-inspect": { - "hashes": [ - "sha256:3b98390df4d999a28cf5b35d8b333425af5da2ece8a4ea9e98f71e7591347b4f", - "sha256:8f1b1dd25908dbfd81d3bebc218011531e7ab614ba6e5bf7826d887c834afab7", - "sha256:de08f50a22955ddec353876df7b2545994d6df08a2f45d54ac8c05e530372ca0" - ], - "version": "==0.6.0" + "version": "==3.10.0.0" }, "urllib3": { "hashes": [ - "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df", - "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937" + "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c", + "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098" ], "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" + "version": "==1.26.5" }, "wcwidth": { "hashes": [ diff --git a/setup.py b/setup.py index 992124d..7b6ed7f 100644 --- a/setup.py +++ b/setup.py @@ -65,13 +65,9 @@ INSTALL_REQUIRES = [ DEV_INSTALL_REQUIRES = [ 'pytest', 'pytest-mypy', - 'pytest-mock', 'pytest-cov', - 'mock', - 'hypothesis', 'coverage', 'codecov', - 'monkeytype', 'nbval', 'mypy', 'bumpversion', From 6a1bc0ce1400ee9fc0efa85165c4bf17fda3e50a Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Tue, 1 Jun 2021 23:53:29 -0700 Subject: [PATCH 03/10] Remove metadata support from reencrypt() --- tests/test_capsule_frag.py | 69 ++++---------------------------- tests/test_compatibility.py | 13 +++--- tests/test_vectors.py | 6 +-- umbral/capsule_frag.py | 16 ++------ umbral/hashing.py | 6 +-- umbral/pre.py | 10 +---- vectors/generate_test_vectors.py | 6 +-- vectors/vectors_cfrags.json | 5 +-- 8 files changed, 27 insertions(+), 104 deletions(-) diff --git a/tests/test_capsule_frag.py b/tests/test_capsule_frag.py index e05a484..c1b4e46 100644 --- a/tests/test_capsule_frag.py +++ b/tests/test_capsule_frag.py @@ -1,6 +1,6 @@ import pytest -from umbral import reencrypt, CapsuleFrag, PublicKey, Capsule, VerificationError +from umbral import encrypt, reencrypt, CapsuleFrag, PublicKey, Capsule, VerificationError from umbral.curve_point import CurvePoint @@ -8,9 +8,8 @@ def test_cfrag_serialization(verification_keys, capsule, kfrags): verifying_pk, delegating_pk, receiving_pk = verification_keys - metadata = b'This is an example of metadata for re-encryption request' for kfrag in kfrags: - cfrag = reencrypt(capsule, kfrag, metadata=metadata) + cfrag = reencrypt(capsule, kfrag) cfrag_bytes = bytes(cfrag) new_cfrag = CapsuleFrag.from_bytes(cfrag_bytes) @@ -19,34 +18,15 @@ def test_cfrag_serialization(verification_keys, capsule, kfrags): verifying_pk=verifying_pk, delegating_pk=delegating_pk, receiving_pk=receiving_pk, - metadata=metadata, ) assert verified_cfrag == cfrag - # No metadata - with pytest.raises(VerificationError): - new_cfrag.verify(capsule, - verifying_pk=verifying_pk, - delegating_pk=delegating_pk, - receiving_pk=receiving_pk, - ) - - # Wrong metadata - with pytest.raises(VerificationError): - new_cfrag.verify(capsule, - verifying_pk=verifying_pk, - delegating_pk=delegating_pk, - receiving_pk=receiving_pk, - metadata=b'Not the same metadata', - ) - # Wrong delegating key with pytest.raises(VerificationError): new_cfrag.verify(capsule, verifying_pk=verifying_pk, delegating_pk=receiving_pk, receiving_pk=receiving_pk, - metadata=metadata, ) # Wrong receiving key @@ -55,7 +35,6 @@ def test_cfrag_serialization(verification_keys, capsule, kfrags): verifying_pk=verifying_pk, delegating_pk=delegating_pk, receiving_pk=delegating_pk, - metadata=metadata, ) # Wrong signing key @@ -64,34 +43,6 @@ def test_cfrag_serialization(verification_keys, capsule, kfrags): verifying_pk=receiving_pk, delegating_pk=delegating_pk, receiving_pk=receiving_pk, - metadata=metadata, - ) - - -def test_cfrag_serialization_no_metadata(verification_keys, capsule, kfrags): - - verifying_pk, delegating_pk, receiving_pk = verification_keys - - for kfrag in kfrags: - - # Create with no metadata - cfrag = reencrypt(capsule, kfrag) - cfrag_bytes = bytes(cfrag) - new_cfrag = CapsuleFrag.from_bytes(cfrag_bytes) - - verified_cfrag = new_cfrag.verify(capsule, - verifying_pk=verifying_pk, - delegating_pk=delegating_pk, - receiving_pk=receiving_pk, - ) - assert verified_cfrag == cfrag - - with pytest.raises(VerificationError): - new_cfrag.verify(capsule, - verifying_pk=verifying_pk, - delegating_pk=delegating_pk, - receiving_pk=receiving_pk, - metadata=b'some metadata', ) @@ -103,8 +54,7 @@ def test_cfrag_with_wrong_capsule(verification_keys, kfrags, capsule_and_ciphert 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) + cfrag = reencrypt(capsule_alice2, kfrags[0]) cfrag = CapsuleFrag.from_bytes(bytes(cfrag)) # de-verify with pytest.raises(VerificationError): @@ -112,7 +62,6 @@ def test_cfrag_with_wrong_capsule(verification_keys, kfrags, capsule_and_ciphert verifying_pk=verifying_pk, delegating_pk=delegating_pk, receiving_pk=receiving_pk, - metadata=metadata, ) @@ -121,8 +70,7 @@ def test_cfrag_with_wrong_data(verification_keys, kfrags, capsule_and_ciphertext capsule, ciphertext = capsule_and_ciphertext verifying_pk, delegating_pk, receiving_pk = verification_keys - metadata = b"some metadata" - cfrag = reencrypt(capsule, kfrags[0], metadata=metadata) + cfrag = reencrypt(capsule, kfrags[0]) # Let's put random garbage in one of the cfrags cfrag = CapsuleFrag.from_bytes(bytes(cfrag)) # de-verify @@ -134,7 +82,6 @@ def test_cfrag_with_wrong_data(verification_keys, kfrags, capsule_and_ciphertext verifying_pk=verifying_pk, delegating_pk=delegating_pk, receiving_pk=receiving_pk, - metadata=metadata, ) @@ -142,8 +89,8 @@ def test_cfrag_is_hashable(verification_keys, capsule, kfrags): verifying_pk, delegating_pk, receiving_pk = verification_keys - cfrag0 = reencrypt(capsule, kfrags[0], metadata=b'abcdef') - cfrag1 = reencrypt(capsule, kfrags[1], metadata=b'abcdef') + cfrag0 = reencrypt(capsule, kfrags[0]) + cfrag1 = reencrypt(capsule, kfrags[1]) assert hash(cfrag0) != hash(cfrag1) @@ -154,13 +101,13 @@ def test_cfrag_is_hashable(verification_keys, capsule, kfrags): verifying_pk=verifying_pk, delegating_pk=delegating_pk, receiving_pk=receiving_pk, - metadata=b'abcdef') + ) assert hash(verified_cfrag) == hash(cfrag0) def test_cfrag_str(capsule, kfrags): - cfrag0 = reencrypt(capsule, kfrags[0], metadata=b'abcdef') + cfrag0 = reencrypt(capsule, kfrags[0]) s = str(cfrag0) assert 'VerifiedCapsuleFrag' in s diff --git a/tests/test_compatibility.py b/tests/test_compatibility.py index 99bd763..87efcb0 100644 --- a/tests/test_compatibility.py +++ b/tests/test_compatibility.py @@ -143,16 +143,16 @@ def test_kfrags(implementations): def _reencrypt(umbral, verifying_pk_bytes, delegating_pk_bytes, receiving_pk_bytes, - capsule_bytes, kfrags_bytes, threshold, metadata): + capsule_bytes, kfrags_bytes, threshold): capsule = umbral.Capsule.from_bytes(bytes(capsule_bytes)) verified_kfrags = _verify_kfrags(umbral, kfrags_bytes, verifying_pk_bytes, delegating_pk_bytes, receiving_pk_bytes) - cfrags = [umbral.reencrypt(capsule, kfrag, metadata=metadata) for kfrag in verified_kfrags[:threshold]] + cfrags = [umbral.reencrypt(capsule, kfrag) for kfrag in verified_kfrags[:threshold]] return [bytes(cfrag) for cfrag in cfrags] def _decrypt_reencrypted(umbral, receiving_sk_bytes, delegating_pk_bytes, verifying_pk_bytes, - capsule_bytes, cfrags_bytes, ciphertext, metadata): + capsule_bytes, cfrags_bytes, ciphertext): receiving_sk = umbral.SecretKey.from_bytes(receiving_sk_bytes) receiving_pk = umbral.PublicKey.from_secret_key(receiving_sk) @@ -166,7 +166,7 @@ def _decrypt_reencrypted(umbral, receiving_sk_bytes, delegating_pk_bytes, verify verifying_pk=verifying_pk, delegating_pk=delegating_pk, receiving_pk=receiving_pk, - metadata=metadata) + ) for cfrag in cfrags] # Decryption by Bob @@ -184,7 +184,6 @@ def test_reencrypt(implementations): umbral1, umbral2 = implementations - metadata = b'metadata' threshold = 2 num_kfrags = 3 plaintext = b'peace at dawn' @@ -203,13 +202,13 @@ def test_reencrypt(implementations): # On client 2 cfrags_bytes = _reencrypt(umbral2, verifying_pk_bytes, delegating_pk_bytes, receiving_pk_bytes, - capsule_bytes, kfrags_bytes, threshold, metadata) + capsule_bytes, kfrags_bytes, threshold) # On client 1 plaintext_reencrypted = _decrypt_reencrypted(umbral1, receiving_sk_bytes, delegating_pk_bytes, verifying_pk_bytes, - capsule_bytes, cfrags_bytes, ciphertext, metadata) + capsule_bytes, cfrags_bytes, ciphertext) assert plaintext_reencrypted == plaintext diff --git a/tests/test_vectors.py b/tests/test_vectors.py index 9b9c17a..8b71fdc 100644 --- a/tests/test_vectors.py +++ b/tests/test_vectors.py @@ -147,14 +147,12 @@ def test_cfrags(): 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: verified_kfrag = kfrag.verify(verifying_pk=verifying_pk, delegating_pk=delegating_pk, receiving_pk=receiving_pk) - new_cfrag = reencrypt(capsule, verified_kfrag, metadata=metadata).cfrag + new_cfrag = reencrypt(capsule, verified_kfrag).cfrag assert new_cfrag.point_e1 == cfrag.point_e1 assert new_cfrag.point_v1 == cfrag.point_v1 assert new_cfrag.kfrag_id == cfrag.kfrag_id @@ -163,4 +161,4 @@ def test_cfrags(): verifying_pk=verifying_pk, delegating_pk=delegating_pk, receiving_pk=receiving_pk, - metadata=metadata) + ) diff --git a/umbral/capsule_frag.py b/umbral/capsule_frag.py index 0a639ef..2b48998 100644 --- a/umbral/capsule_frag.py +++ b/umbral/capsule_frag.py @@ -58,7 +58,6 @@ class CapsuleFragProof(Serializable): kfrag: KeyFrag, cfrag_e1: CurvePoint, cfrag_v1: CurvePoint, - metadata: Optional[bytes], ) -> 'CapsuleFragProof': params = PARAMETERS @@ -81,7 +80,7 @@ class CapsuleFragProof(Serializable): v2 = v * t u2 = u * t - h = hash_to_cfrag_verification([e, e1, e2, v, v1, v2, u, u1, u2], metadata) + h = hash_to_cfrag_verification([e, e1, e2, v, v1, v2, u, u1, u2]) ### @@ -141,15 +140,11 @@ class CapsuleFrag(Serializable): bytes(self.proof)) @classmethod - def reencrypted(cls, - capsule: Capsule, - kfrag: KeyFrag, - metadata: Optional[bytes] = None, - ) -> 'CapsuleFrag': + def reencrypted(cls, capsule: Capsule, kfrag: KeyFrag) -> '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) + proof = CapsuleFragProof.from_kfrag_and_cfrag(capsule, kfrag, e1, v1) return cls(point_e1=e1, point_v1=v1, @@ -163,12 +158,9 @@ class CapsuleFrag(Serializable): verifying_pk: PublicKey, delegating_pk: PublicKey, receiving_pk: PublicKey, - metadata: Optional[bytes] = None, ) -> 'VerifiedCapsuleFrag': """ Verifies the validity of this fragment. - - ``metadata`` should coincide with the one given to :py:func:`reencrypt`. """ params = PARAMETERS @@ -189,7 +181,7 @@ class CapsuleFrag(Serializable): 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) + h = hash_to_cfrag_verification([e, e1, e2, v, v1, v2, u, u1, u2]) ### diff --git a/umbral/hashing.py b/umbral/hashing.py index ca62f11..2f088cc 100644 --- a/umbral/hashing.py +++ b/umbral/hashing.py @@ -63,14 +63,10 @@ def hash_to_shared_secret(precursor: CurvePoint, return CurveScalar.from_digest(digest) -def hash_to_cfrag_verification(points: Iterable[CurvePoint], - metadata: Optional[bytes] = None - ) -> CurveScalar: +def hash_to_cfrag_verification(points: Iterable[CurvePoint]) -> 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) diff --git a/umbral/pre.py b/umbral/pre.py index 906f4ca..4c25717 100644 --- a/umbral/pre.py +++ b/umbral/pre.py @@ -60,23 +60,17 @@ def generate_kfrags(delegating_sk: SecretKey, return [VerifiedKeyFrag(kfrag) for kfrag in kfrags] -def reencrypt(capsule: Capsule, - kfrag: VerifiedKeyFrag, - metadata: Optional[bytes] = None - ) -> VerifiedCapsuleFrag: +def reencrypt(capsule: Capsule, kfrag: VerifiedKeyFrag) -> VerifiedCapsuleFrag: """ Creates a capsule fragment using the given key fragment. Capsule fragments can later be used to decrypt the ciphertext. - - If `metadata` is provided, it will have to be used for verification in - :py:meth:`CapsuleFrag.verify`. """ # We could let duck typing do its work, # but it's better to make a common error more understandable. if isinstance(kfrag, KeyFrag) and not isinstance(kfrag, VerifiedKeyFrag): raise TypeError("KeyFrag must be verified before reencryption") - return VerifiedCapsuleFrag(CapsuleFrag.reencrypted(capsule, kfrag.kfrag, metadata)) + return VerifiedCapsuleFrag(CapsuleFrag.reencrypted(capsule, kfrag.kfrag)) def decrypt_reencrypted(decrypting_sk: SecretKey, diff --git a/vectors/generate_test_vectors.py b/vectors/generate_test_vectors.py index e62b532..d53f07e 100644 --- a/vectors/generate_test_vectors.py +++ b/vectors/generate_test_vectors.py @@ -237,9 +237,8 @@ create_test_vector_file(vector_suite, 'vectors_kfrags.json', generate_again=gene vectors = list() -metadata = b'kfrag_metadata' for kfrag in kfrags: - cfrag = reencrypt(capsule, kfrag, metadata) + cfrag = reencrypt(capsule, kfrag) json_input = {'kfrag': hexlify(kfrag), 'cfrag': hexlify(cfrag)} vectors.append(json_input) @@ -249,10 +248,9 @@ vector_suite = { '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\')`'), + '`reencrypt(kfrag, capsule)`'), 'params': 'default', 'capsule': hexlify(capsule), - 'metadata': hexlify(metadata), 'verifying_pk': hexlify(verifying_pk), 'delegating_pk': hexlify(delegating_pk), 'receiving_pk': hexlify(receiving_pk), diff --git a/vectors/vectors_cfrags.json b/vectors/vectors_cfrags.json index e17dd4a..600fc79 100644 --- a/vectors/vectors_cfrags.json +++ b/vectors/vectors_cfrags.json @@ -1,9 +1,8 @@ { "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 `reencrypt(kfrag, capsule, , b'kfrag_metadata')`", + "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)`", "params": "default", "capsule": "02558f1de19a58e73a94e8fbbc6d3b1de2d312d90746ea74cb29f046943bf5787102906780e9484aec2102a01a157f10ced5aec952cd00631d94d5ea2edfa9b6808361b109353b0827b7e4013ab92a70eb3337a37f6fe34b3ccb058592caa246c974", - "metadata": "6b667261675f6d65746164617461", "verifying_pk": "030b95b3f249297824b32d3391392d62a9aff32e8698fa78c7e8ce4a9d17071f56", "delegating_pk": "02d67029bb92522059225d190038230c23466e28d132d48f714f9098168a562b8a", "receiving_pk": "03b0d0243e8954b408047eee3b09b5ed132ccc25ec70e99fc74b6e9f54e5ecf9c7", @@ -49,4 +48,4 @@ "cfrag": "03a3e0b6b2fa9f634acac1d4ed682dbf0c08ba27889087854059d80d021797d7b403a64ced5bc32ad0cf5746eb5ab1ebdc12a1d1bbbd0f45d0fc90c3e4b56ba70546df55683a69e4136205a61c895959d86eb64840bb2175d5d15e4491b27e6a38bc036523005d1234abcd44d36704620cfcff444edac7a64dfc256281966351a7803b02ef471b06988f07ce34c7bfa5825603eedbde4138c05296055cff54f6204d245a02119417e7aa9c207a71540abefb8de8e04c977db7d11a82e2f98a1ea3fff5519e03ab6ffe8768ecd246956362065ca48488183f96c3ee5957be7a7ce02f3ef9cb1202e8febd066a999b83b4441662636c6c978cb8789553914b5ed94c02d4784432d74e9fffcb5ce10b65d95624b23940415f0dbe29e174a6b73c78cb3d5d380d8882ae3cc293a58554a55dbc50c0e111c6e9bb0208631833da5e66b1adb2aa1ecdd615c09098fba7af4b03763b846e1f5358a6bac27c36eadd6cd2c98fd58c277e0b" } ] -} \ No newline at end of file +} From 16def465647041f93513552b023f976eae88c555 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Wed, 2 Jun 2021 18:50:54 -0700 Subject: [PATCH 04/10] Fix key names in encrypt/decrypt_original/decrypt_reencrypted --- README.rst | 2 +- docs/examples/umbral_simple_api.py | 2 +- docs/notebooks/pyUmbral Simple API.ipynb | 6 +++--- docs/source/using_pyumbral.rst | 4 ++-- tests/test_compatibility.py | 2 +- tests/test_pre.py | 4 ++-- umbral/capsule.py | 8 ++++---- umbral/pre.py | 14 +++++++------- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/README.rst b/README.rst index c655980..d0a3f41 100644 --- a/README.rst +++ b/README.rst @@ -147,7 +147,7 @@ and then decrypts the re-encrypted ciphertext. from umbral import decrypt_reencrypted - bob_cleartext = pre.decrypt_reencrypted(decrypting_sk=bobs_secret_key, + bob_cleartext = pre.decrypt_reencrypted(receiving_sk=bobs_secret_key, delegating_pk=alices_public_key, capsule=capsule, cfrags=cfrags, diff --git a/docs/examples/umbral_simple_api.py b/docs/examples/umbral_simple_api.py index bb31b49..7e813f5 100644 --- a/docs/examples/umbral_simple_api.py +++ b/docs/examples/umbral_simple_api.py @@ -103,7 +103,7 @@ cfrags = [cfrag.verify(capsule, # ------------------------------------ # Finally, Bob decrypts the re-encrypted ciphertext using his key. -bob_cleartext = decrypt_reencrypted(decrypting_sk=bobs_secret_key, +bob_cleartext = decrypt_reencrypted(receiving_sk=bobs_secret_key, delegating_pk=alices_public_key, capsule=bob_capsule, verified_cfrags=cfrags, diff --git a/docs/notebooks/pyUmbral Simple API.ipynb b/docs/notebooks/pyUmbral Simple API.ipynb index fbd2055..eea199f 100644 --- a/docs/notebooks/pyUmbral Simple API.ipynb +++ b/docs/notebooks/pyUmbral Simple API.ipynb @@ -94,7 +94,7 @@ "from umbral import decrypt_original\n", "\n", "\n", - "cleartext = decrypt_original(sk=alices_private_key,\n", + "cleartext = decrypt_original(delegating_sk=alices_private_key,\n", " capsule=capsule,\n", " ciphertext=ciphertext)\n", "print(cleartext)" @@ -144,7 +144,7 @@ "from umbral import GenericError\n", "\n", "try:\n", - " fail_decrypted_data = decrypt_original(sk=bobs_private_key,\n", + " fail_decrypted_data = decrypt_original(delegating_sk=bobs_private_key,\n", " capsule=capsule,\n", " ciphertext=ciphertext)\n", "except GenericError:\n", @@ -266,7 +266,7 @@ "source": [ "from umbral import decrypt_reencrypted\n", "\n", - "bob_cleartext = decrypt_reencrypted(decrypting_sk=bobs_private_key,\n", + "bob_cleartext = decrypt_reencrypted(receiving_sk=bobs_private_key,\n", " delegating_pk=alices_public_key,\n", " capsule=capsule,\n", " verified_cfrags=cfrags,\n", diff --git a/docs/source/using_pyumbral.rst b/docs/source/using_pyumbral.rst index 858795b..70b233b 100644 --- a/docs/source/using_pyumbral.rst +++ b/docs/source/using_pyumbral.rst @@ -130,7 +130,7 @@ or re-encrypted for him by Ursula, he will not be able to open it. .. doctest:: capsule_story - >>> fail = decrypt_original(sk=bobs_secret_key, + >>> fail = decrypt_original(delegating_sk=bobs_secret_key, ... capsule=capsule, ... ciphertext=ciphertext) Traceback (most recent call last): @@ -196,7 +196,7 @@ Finally, Bob decrypts the re-encrypted ciphertext using his key. .. doctest:: capsule_story >>> from umbral import decrypt_reencrypted - >>> cleartext = decrypt_reencrypted(decrypting_sk=bobs_secret_key, + >>> cleartext = decrypt_reencrypted(receiving_sk=bobs_secret_key, ... delegating_pk=alices_public_key, ... capsule=capsule, ... verified_cfrags=cfrags, diff --git a/tests/test_compatibility.py b/tests/test_compatibility.py index 87efcb0..9404b34 100644 --- a/tests/test_compatibility.py +++ b/tests/test_compatibility.py @@ -170,7 +170,7 @@ def _decrypt_reencrypted(umbral, receiving_sk_bytes, delegating_pk_bytes, verify for cfrag in cfrags] # Decryption by Bob - plaintext = umbral.decrypt_reencrypted(decrypting_sk=receiving_sk, + plaintext = umbral.decrypt_reencrypted(receiving_sk=receiving_sk, delegating_pk=delegating_pk, capsule=capsule, verified_cfrags=verified_cfrags, diff --git a/tests/test_pre.py b/tests/test_pre.py index be282ac..863a7ac 100644 --- a/tests/test_pre.py +++ b/tests/test_pre.py @@ -81,7 +81,7 @@ def test_simple_api(num_kfrags, threshold): cfrags = [reencrypt(capsule, kfrag) for kfrag in kfrags] # Decryption by Bob - plaintext_reenc = decrypt_reencrypted(decrypting_sk=receiving_sk, + plaintext_reenc = decrypt_reencrypted(receiving_sk=receiving_sk, delegating_pk=delegating_pk, capsule=capsule, verified_cfrags=cfrags[:threshold], @@ -105,7 +105,7 @@ def test_decrypt_unverified_cfrag(verification_keys, bobs_keys, capsule_and_ciph cfrags = [reencrypt(capsule, kfrag) for kfrag in kfrags] cfrags[0] = CapsuleFrag.from_bytes(bytes(cfrags[0])) with pytest.raises(TypeError): - plaintext_reenc = decrypt_reencrypted(decrypting_sk=receiving_sk, + plaintext_reenc = decrypt_reencrypted(receiving_sk=receiving_sk, delegating_pk=delegating_pk, capsule=capsule, verified_cfrags=cfrags, diff --git a/umbral/capsule.py b/umbral/capsule.py index d75c6b7..d1318f3 100644 --- a/umbral/capsule.py +++ b/umbral/capsule.py @@ -43,7 +43,7 @@ class Capsule(Serializable): return bytes(self.point_e) + bytes(self.point_v) + bytes(self.signature) @classmethod - def from_public_key(cls, pk: PublicKey) -> Tuple['Capsule', CurvePoint]: + def from_public_key(cls, delegating_pk: PublicKey) -> Tuple['Capsule', CurvePoint]: g = CurvePoint.generator() priv_r = CurveScalar.random_nonzero() @@ -55,12 +55,12 @@ class Capsule(Serializable): h = hash_capsule_points(pub_r, pub_u) s = priv_u + (priv_r * h) - shared_key = pk._point_key * (priv_r + priv_u) + shared_key = delegating_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_original(self, delegating_sk: SecretKey) -> CurvePoint: + return (self.point_e + self.point_v) * delegating_sk.secret_scalar() def open_reencrypted(self, receiving_sk: SecretKey, diff --git a/umbral/pre.py b/umbral/pre.py index 4c25717..676acbb 100644 --- a/umbral/pre.py +++ b/umbral/pre.py @@ -8,25 +8,25 @@ from .key_frag import VerifiedKeyFrag, KeyFrag, KeyFragBase from .signing import Signer -def encrypt(pk: PublicKey, plaintext: bytes) -> Tuple[Capsule, bytes]: +def encrypt(delegating_pk: PublicKey, plaintext: bytes) -> Tuple[Capsule, bytes]: """ Generates and encapsulates a symmetric key and uses it to encrypt the given plaintext. Returns the KEM Capsule and the ciphertext. """ - capsule, key_seed = Capsule.from_public_key(pk) + capsule, key_seed = Capsule.from_public_key(delegating_pk) dem = DEM(bytes(key_seed)) ciphertext = dem.encrypt(plaintext, authenticated_data=bytes(capsule)) return capsule, ciphertext -def decrypt_original(sk: SecretKey, capsule: Capsule, ciphertext: bytes) -> bytes: +def decrypt_original(delegating_sk: SecretKey, capsule: Capsule, ciphertext: bytes) -> bytes: """ - Opens the capsule using the original (Alice's) key used for encryption and gets what's inside. + Opens the capsule using the delegator'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_seed = capsule.open_original(sk) + key_seed = capsule.open_original(delegating_sk) dem = DEM(bytes(key_seed)) return dem.decrypt(ciphertext, authenticated_data=bytes(capsule)) @@ -73,7 +73,7 @@ def reencrypt(capsule: Capsule, kfrag: VerifiedKeyFrag) -> VerifiedCapsuleFrag: return VerifiedCapsuleFrag(CapsuleFrag.reencrypted(capsule, kfrag.kfrag)) -def decrypt_reencrypted(decrypting_sk: SecretKey, +def decrypt_reencrypted(receiving_sk: SecretKey, delegating_pk: PublicKey, capsule: Capsule, verified_cfrags: Sequence[VerifiedCapsuleFrag], @@ -89,6 +89,6 @@ def decrypt_reencrypted(decrypting_sk: SecretKey, raise TypeError("All CapsuleFrags must be verified before decryption") cfrags = [vcfrag.cfrag for vcfrag in verified_cfrags] - key_seed = capsule.open_reencrypted(decrypting_sk, delegating_pk, cfrags) + key_seed = capsule.open_reencrypted(receiving_sk, delegating_pk, cfrags) dem = DEM(bytes(key_seed)) return dem.decrypt(ciphertext, authenticated_data=bytes(capsule)) From b8175a3247e587a66de0fa12442320c4f802402a Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Wed, 2 Jun 2021 23:46:26 -0700 Subject: [PATCH 05/10] Expose serialized sizes of objects and overhaul Serializable accordingly We assume here that all our objects have constant serialized size. --- tests/test_serializable.py | 55 ++++++++++++++++++---------- umbral/capsule.py | 18 ++++++---- umbral/capsule_frag.py | 44 ++++++++++++----------- umbral/curve_point.py | 11 +++--- umbral/curve_scalar.py | 10 +++--- umbral/hashing.py | 10 +++--- umbral/key_frag.py | 60 ++++++++++++++++++------------- umbral/keys.py | 27 +++++++++----- umbral/serializable.py | 74 +++++++++++++++++++++++--------------- umbral/signing.py | 9 +++-- 10 files changed, 193 insertions(+), 125 deletions(-) diff --git a/tests/test_serializable.py b/tests/test_serializable.py index 62d78e9..6fa38ac 100644 --- a/tests/test_serializable.py +++ b/tests/test_serializable.py @@ -2,7 +2,7 @@ import re import pytest -from umbral.serializable import Serializable, serialize_bool, take_bool +from umbral.serializable import Serializable, bool_bytes, bool_from_exact_bytes class A(Serializable): @@ -12,12 +12,15 @@ class A(Serializable): 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 serialized_size(cls): + return 4 + + @classmethod + def _from_exact_bytes(cls, data): + return cls(int.from_bytes(data, byteorder='big')) def __bytes__(self): - return self.val.to_bytes(4, byteorder='big') + return self.val.to_bytes(self.serialized_size(), byteorder='big') def __eq__(self, other): return isinstance(other, A) and self.val == other.val @@ -30,12 +33,15 @@ class B(Serializable): 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 serialized_size(cls): + return 2 + + @classmethod + def _from_exact_bytes(cls, data): + return cls(int.from_bytes(data, byteorder='big')) def __bytes__(self): - return self.val.to_bytes(2, byteorder='big') + return self.val.to_bytes(self.serialized_size(), byteorder='big') def __eq__(self, other): return isinstance(other, B) and self.val == other.val @@ -48,9 +54,13 @@ class C(Serializable): self.b = b @classmethod - def __take__(cls, data): - components, data = cls.__take_types__(data, A, B) - return cls(*components), data + def serialized_size(cls): + return A.serialized_size() + B.serialized_size() + + @classmethod + def _from_exact_bytes(cls, data): + components = cls._split(data, A, B) + return cls(*components) def __bytes__(self): return bytes(self.a) + bytes(self.b) @@ -71,7 +81,7 @@ 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"): + with pytest.raises(ValueError, match="Expected 6 bytes, got 7"): C.from_bytes(bytes(c) + b'\x00') @@ -80,13 +90,22 @@ def test_not_enough_bytes(): 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"): + with pytest.raises(ValueError, match="Expected 6 bytes, got 5"): 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') +def test_bool_bytes(): + assert bool_from_exact_bytes(bool_bytes(True)) == True + assert bool_from_exact_bytes(bool_bytes(False)) == False 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') + bool_from_exact_bytes(b'z') + + +def test_split_bool(): + a = A(2**32 - 123) + b = True + data = bytes(a) + bool_bytes(b) + a_back, b_back = Serializable._split(data, A, bool) + assert a_back == a + assert b_back == b diff --git a/umbral/capsule.py b/umbral/capsule.py index d1318f3..631c1cd 100644 --- a/umbral/capsule.py +++ b/umbral/capsule.py @@ -29,17 +29,21 @@ class Capsule(Serializable): 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) + _COMPONENT_TYPES = CurvePoint, CurvePoint, CurveScalar + _SERIALIZED_SIZE = sum(tp.serialized_size() for tp in _COMPONENT_TYPES) - capsule = cls(e, v, sig) + @classmethod + def serialized_size(cls): + return cls._SERIALIZED_SIZE + + @classmethod + def _from_exact_bytes(cls, data: bytes): + capsule = cls(*cls._split(data, *cls._COMPONENT_TYPES)) if not capsule._verify(): raise GenericError("Capsule self-verification failed. Serialized data may be damaged.") + return capsule - return capsule, data - - def __bytes__(self) -> bytes: + def __bytes__(self): return bytes(self.point_e) + bytes(self.point_v) + bytes(self.signature) @classmethod diff --git a/umbral/capsule_frag.py b/umbral/capsule_frag.py index 2b48998..22824b9 100644 --- a/umbral/capsule_frag.py +++ b/umbral/capsule_frag.py @@ -1,4 +1,4 @@ -from typing import Optional, Tuple +from typing import Optional, Tuple, Type from .capsule import Capsule from .curve_point import CurvePoint @@ -34,23 +34,23 @@ class CapsuleFragProof(Serializable): return (self.point_e2, self.point_v2, self.kfrag_commitment, self.kfrag_pok, self.signature, self.kfrag_signature) + _COMPONENT_TYPES: Tuple[Type[Serializable], ...] = ( + CurvePoint, CurvePoint, CurvePoint, CurvePoint, CurveScalar, Signature) + _SERIALIZED_SIZE = sum(tp.serialized_size() for tp in _COMPONENT_TYPES) + 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 serialized_size(cls): + return cls._SERIALIZED_SIZE + + @classmethod + def _from_exact_bytes(cls, data): + return cls(*cls._split(data, *cls._COMPONENT_TYPES)) 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) - ) + return b''.join(bytes(comp) for comp in self._components()) @classmethod def from_kfrag_and_cfrag(cls, @@ -117,6 +117,10 @@ class CapsuleFrag(Serializable): def _components(self): return (self.point_e1, self.point_v1, self.kfrag_id, self.precursor, self.proof) + _COMPONENT_TYPES: Tuple[Type[Serializable], ...] = ( + CurvePoint, CurvePoint, KeyFragID, CurvePoint, CapsuleFragProof) + _SERIALIZED_SIZE = sum(tp.serialized_size() for tp in _COMPONENT_TYPES) + def __eq__(self, other): return self._components() == other._components() @@ -127,17 +131,15 @@ class CapsuleFrag(Serializable): 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 serialized_size(cls): + return cls._SERIALIZED_SIZE + + @classmethod + def _from_exact_bytes(cls, data): + return cls(*cls._split(data, *cls._COMPONENT_TYPES)) def __bytes__(self): - return (bytes(self.point_e1) + - bytes(self.point_v1) + - bytes(self.kfrag_id) + - bytes(self.precursor) + - bytes(self.proof)) + return b''.join(bytes(comp) for comp in self._components()) @classmethod def reencrypted(cls, capsule: Capsule, kfrag: KeyFrag) -> 'CapsuleFrag': diff --git a/umbral/curve_point.py b/umbral/curve_point.py index 165820f..785a8ac 100644 --- a/umbral/curve_point.py +++ b/umbral/curve_point.py @@ -34,14 +34,15 @@ class CurvePoint(Serializable): return openssl.point_to_affine_coords(CURVE, self._backend_point) @classmethod - def __take__(cls, data: bytes) -> Tuple['CurvePoint', bytes]: + def serialized_size(cls): + return CURVE.field_element_size + 1 # compressed point size + + @classmethod + def _from_exact_bytes(cls, data: 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 + return cls(openssl.point_from_bytes(CURVE, data)) def __bytes__(self) -> bytes: """ diff --git a/umbral/curve_scalar.py b/umbral/curve_scalar.py index 3a7d8c1..9fc3c74 100644 --- a/umbral/curve_scalar.py +++ b/umbral/curve_scalar.py @@ -45,10 +45,12 @@ class CurveScalar(Serializable): return cls(openssl.bn_from_bytes(digest.finalize(), apply_modulus=CURVE.bn_order)) @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 serialized_size(cls): + return CURVE.scalar_size + + @classmethod + def _from_exact_bytes(cls, data: bytes): + return cls(openssl.bn_from_bytes(data, check_modulus=CURVE.bn_order)) def __bytes__(self) -> bytes: """ diff --git a/umbral/hashing.py b/umbral/hashing.py index 2f088cc..d826f30 100644 --- a/umbral/hashing.py +++ b/umbral/hashing.py @@ -6,7 +6,7 @@ from .openssl import backend, ErrorInvalidCompressedPoint from .curve import CURVE from .curve_scalar import CurveScalar from .curve_point import CurvePoint -from .serializable import Serializable, serialize_bool +from .serializable import Serializable, bool_bytes if TYPE_CHECKING: # pragma: no cover from .key_frag import KeyFragID @@ -79,14 +79,14 @@ def kfrag_signature_message(kfrag_id: 'KeyFragID', # Have to convert to bytes manually because `mypy` is not smart enough to resolve types. - delegating_part = ([serialize_bool(True), bytes(maybe_delegating_pk)] + delegating_part = ([bool_bytes(True), bytes(maybe_delegating_pk)] if maybe_delegating_pk - else [serialize_bool(False)]) + else [bool_bytes(False)]) cast(List[Serializable], delegating_part) - receiving_part = ([serialize_bool(True), bytes(maybe_receiving_pk)] + receiving_part = ([bool_bytes(True), bytes(maybe_receiving_pk)] if maybe_receiving_pk - else [serialize_bool(False)]) + else [bool_bytes(False)]) components = ([bytes(kfrag_id), bytes(commitment), bytes(precursor)] + delegating_part + diff --git a/umbral/key_frag.py b/umbral/key_frag.py index 27f945e..b56c9dc 100644 --- a/umbral/key_frag.py +++ b/umbral/key_frag.py @@ -1,5 +1,5 @@ import os -from typing import List, Optional +from typing import List, Optional, Tuple, Type from .curve_point import CurvePoint from .curve_scalar import CurveScalar @@ -7,7 +7,7 @@ from .errors import VerificationError from .hashing import hash_to_shared_secret, kfrag_signature_message, hash_to_polynomial_arg from .keys import PublicKey, SecretKey from .params import PARAMETERS -from .serializable import Serializable, serialize_bool, take_bool +from .serializable import Serializable, bool_bytes, bool_serialized_size from .signing import Signature, Signer @@ -26,9 +26,12 @@ class KeyFragID(Serializable): return cls(os.urandom(cls.__SIZE)) @classmethod - def __take__(cls, data): - id_, data = cls.__take_bytes__(data, cls.__SIZE) - return cls(id_), data + def serialized_size(cls): + return cls.__SIZE + + @classmethod + def _from_exact_bytes(cls, data): + return cls(data) def __bytes__(self): return self._id @@ -102,22 +105,25 @@ class KeyFragProof(Serializable): 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) + _SERIALIZED_SIZE = (CurvePoint.serialized_size() + + Signature.serialized_size() * 2 + + bool_serialized_size() * 2) - obj = cls(commitment, sig_proxy, sig_bob, delegating_key_signed, receiving_key_signed) - return obj, data + @classmethod + def serialized_size(cls): + return cls._SERIALIZED_SIZE + + @classmethod + def _from_exact_bytes(cls, data): + types = [CurvePoint, Signature, Signature, bool, bool] + return cls(*cls._split(data, *types)) 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) + bool_bytes(self.delegating_key_signed) + + bool_bytes(self.receiving_key_signed) ) @@ -144,18 +150,24 @@ class KeyFrag(Serializable): 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 + _COMPONENT_TYPES: Tuple[Type[Serializable], ...] = ( + KeyFragID, CurveScalar, CurvePoint, KeyFragProof) + _SERIALIZED_SIZE = sum(tp.serialized_size() for tp in _COMPONENT_TYPES) + + @classmethod + def serialized_size(cls): + return cls._SERIALIZED_SIZE + + @classmethod + def _from_exact_bytes(cls, data): + return cls(*cls._split(data, *cls._COMPONENT_TYPES)) + + def __bytes__(self): + return b''.join(bytes(comp) for comp in self._components()) + def __eq__(self, other): return self._components() == other._components() diff --git a/umbral/keys.py b/umbral/keys.py index 24bfd32..ad3f73a 100644 --- a/umbral/keys.py +++ b/umbral/keys.py @@ -43,9 +43,12 @@ class SecretKey(Serializable): return self._scalar_key @classmethod - def __take__(cls, data: bytes) -> Tuple['SecretKey', bytes]: - (scalar_key,), data = cls.__take_types__(data, CurveScalar) - return cls(scalar_key), data + def serialized_size(cls): + return CurveScalar.serialized_size() + + @classmethod + def _from_exact_bytes(cls, data: bytes): + return cls(CurveScalar._from_exact_bytes(data)) def __bytes__(self) -> bytes: return bytes(self._scalar_key) @@ -70,9 +73,12 @@ class PublicKey(Serializable): return cls(sk._public_key_point) @classmethod - def __take__(cls, data: bytes) -> Tuple['PublicKey', bytes]: - (point_key,), data = cls.__take_types__(data, CurvePoint) - return cls(point_key), data + def serialized_size(cls): + return CurvePoint.serialized_size() + + @classmethod + def _from_exact_bytes(cls, data: bytes): + return cls(CurvePoint._from_exact_bytes(data)) def __bytes__(self) -> bytes: return bytes(self._point_key) @@ -122,9 +128,12 @@ class SecretKeyFactory(Serializable): 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 serialized_size(cls): + return cls._KEY_SEED_SIZE + + @classmethod + def _from_exact_bytes(cls, data: bytes): + return cls(data) def __bytes__(self) -> bytes: return bytes(self.__key_seed) diff --git a/umbral/serializable.py b/umbral/serializable.py index 773030c..2cdaf4e 100644 --- a/umbral/serializable.py +++ b/umbral/serializable.py @@ -14,41 +14,54 @@ class Serializable(ABC): """ 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 + expected_size = cls.serialized_size() + if len(data) != expected_size: + raise ValueError(f"Expected {expected_size} bytes, got {len(data)}") + return cls._from_exact_bytes(data) - @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 " - f"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]: + @staticmethod + def _split(data: bytes, *types: Type) -> List[Any]: """ 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 = [] + pos = 0 + for tp in types: - obj, data = tp.__take__(data) + + if issubclass(tp, bool): + size = bool_serialized_size() + else: + size = tp.serialized_size() + + chunk = data[pos:pos+size] + + if issubclass(tp, bool): + obj = bool_from_exact_bytes(chunk) + else: + obj = tp._from_exact_bytes(chunk) + objs.append(obj) - return objs, data + pos += size + + return objs @classmethod @abstractmethod - def __take__(cls: Type[_T], data: bytes) -> Tuple[_T, bytes]: + def serialized_size(cls) -> int: """ - Take however much is necessary from ``data`` and instantiate the object, - returning it and the remaining bytestring. + Returns the size in bytes of the serialized representation of this object + (obtained with ``bytes()``). + """ + raise NotImplementedError - Must be implemented by the derived class. + @classmethod + @abstractmethod + def _from_exact_bytes(cls: Type[_T], data: bytes) -> _T: + """ + Deserializes the object from a bytestring of exactly the expected length + (defined by ``serialized_size()``). """ raise NotImplementedError @@ -60,17 +73,20 @@ class Serializable(ABC): raise NotImplementedError -def serialize_bool(b: bool) -> bytes: +def bool_serialized_size() -> int: + return 1 + + +def bool_bytes(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': +def bool_from_exact_bytes(data: bytes) -> bool: + if data == b'\x01': b = True - elif bool_bytes == b'\x00': + elif data == b'\x00': b = False else: raise ValueError("Incorrectly serialized boolean; " - f"expected b'\\x00' or b'\\x01', got {repr(bool_bytes)}") - return b, data + f"expected b'\\x00' or b'\\x01', got {repr(data)}") + return b diff --git a/umbral/signing.py b/umbral/signing.py index 38422c7..50302ec 100644 --- a/umbral/signing.py +++ b/umbral/signing.py @@ -92,9 +92,12 @@ class Signature(Serializable): return self.verify_digest(verifying_key, digest) @classmethod - def __take__(cls, data): - (r, s), data = cls.__take_types__(data, CurveScalar, CurveScalar) - return cls(r, s), data + def serialized_size(cls): + return CurveScalar.serialized_size() * 2 + + @classmethod + def _from_exact_bytes(cls, data: bytes): + return cls(*cls._split(data, CurveScalar, CurveScalar)) def __bytes__(self): return bytes(self.r) + bytes(self.s) From 32a612c99d8d9b1986d21e4c9b7dfcf783283c2d Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Thu, 3 Jun 2021 14:34:03 -0700 Subject: [PATCH 06/10] Split Serializable into (De)Serializable --- docs/source/api.rst | 11 ++++++++++- tests/test_capsule_frag.py | 6 ++++++ tests/test_key_frag.py | 6 ++++++ tests/test_serializable.py | 10 +++++----- umbral/capsule.py | 4 ++-- umbral/capsule_frag.py | 12 ++++++++---- umbral/curve_point.py | 4 ++-- umbral/curve_scalar.py | 4 ++-- umbral/key_frag.py | 14 +++++++++----- umbral/keys.py | 8 ++++---- umbral/serializable.py | 30 ++++++++++++++++++------------ umbral/signing.py | 4 ++-- 12 files changed, 74 insertions(+), 39 deletions(-) diff --git a/docs/source/api.rst b/docs/source/api.rst index 931ffaa..13bc51d 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -42,6 +42,7 @@ Intermediate objects .. autoclass:: VerifiedKeyFrag() :members: :special-members: __eq__, __hash__ + :show-inheritance: .. autoclass:: CapsuleFrag() :members: @@ -50,6 +51,7 @@ Intermediate objects .. autoclass:: VerifiedCapsuleFrag() :special-members: __eq__, __hash__ + :show-inheritance: Encryption, re-encryption and decryption ---------------------------------------- @@ -73,6 +75,13 @@ Utilities .. autoclass:: umbral.VerificationError :show-inheritance: +.. autoclass:: umbral.serializable.HasSerializedSize + :members: serialized_size + .. autoclass:: umbral.serializable.Serializable - :members: from_bytes :special-members: __bytes__ + :show-inheritance: + +.. autoclass:: umbral.serializable.Deserializable + :members: from_bytes + :show-inheritance: diff --git a/tests/test_capsule_frag.py b/tests/test_capsule_frag.py index c1b4e46..1998a5e 100644 --- a/tests/test_capsule_frag.py +++ b/tests/test_capsule_frag.py @@ -114,3 +114,9 @@ def test_cfrag_str(capsule, kfrags): s = str(CapsuleFrag.from_bytes(bytes(cfrag0))) assert "VerifiedCapsuleFrag" not in s assert "CapsuleFrag" in s + + +def test_serialized_size(capsule, kfrags): + verified_cfrag = reencrypt(capsule, kfrags[0]) + cfrag = CapsuleFrag.from_bytes(bytes(verified_cfrag)) + assert verified_cfrag.serialized_size() == cfrag.serialized_size() diff --git a/tests/test_key_frag.py b/tests/test_key_frag.py index 4b12162..4978271 100644 --- a/tests/test_key_frag.py +++ b/tests/test_key_frag.py @@ -130,3 +130,9 @@ def test_from_verified_bytes(kfrags): kfrag_bytes = bytes(kfrags[0]) verified_kfrag = VerifiedKeyFrag.from_verified_bytes(kfrag_bytes) assert verified_kfrag == kfrags[0] + + +def test_serialized_size(kfrags): + verified_kfrag = kfrags[0] + kfrag = KeyFrag.from_bytes(bytes(verified_kfrag)) + assert verified_kfrag.serialized_size() == kfrag.serialized_size() diff --git a/tests/test_serializable.py b/tests/test_serializable.py index 6fa38ac..4a7519b 100644 --- a/tests/test_serializable.py +++ b/tests/test_serializable.py @@ -2,10 +2,10 @@ import re import pytest -from umbral.serializable import Serializable, bool_bytes, bool_from_exact_bytes +from umbral.serializable import Serializable, Deserializable, bool_bytes, bool_from_exact_bytes -class A(Serializable): +class A(Serializable, Deserializable): def __init__(self, val: int): assert 0 <= val < 2**32 @@ -26,7 +26,7 @@ class A(Serializable): return isinstance(other, A) and self.val == other.val -class B(Serializable): +class B(Serializable, Deserializable): def __init__(self, val: int): assert 0 <= val < 2**16 @@ -47,7 +47,7 @@ class B(Serializable): return isinstance(other, B) and self.val == other.val -class C(Serializable): +class C(Serializable, Deserializable): def __init__(self, a: A, b: B): self.a = a @@ -106,6 +106,6 @@ def test_split_bool(): a = A(2**32 - 123) b = True data = bytes(a) + bool_bytes(b) - a_back, b_back = Serializable._split(data, A, bool) + a_back, b_back = Deserializable._split(data, A, bool) assert a_back == a assert b_back == b diff --git a/umbral/capsule.py b/umbral/capsule.py index 631c1cd..19bf121 100644 --- a/umbral/capsule.py +++ b/umbral/capsule.py @@ -5,7 +5,7 @@ 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 .serializable import Serializable +from .serializable import Serializable, Deserializable if TYPE_CHECKING: # pragma: no cover from .capsule_frag import CapsuleFrag @@ -19,7 +19,7 @@ def lambda_coeff(xs: Sequence[CurveScalar], i: int) -> CurveScalar: return res -class Capsule(Serializable): +class Capsule(Serializable, Deserializable): """ Encapsulated symmetric key. """ diff --git a/umbral/capsule_frag.py b/umbral/capsule_frag.py index 22824b9..cc485ea 100644 --- a/umbral/capsule_frag.py +++ b/umbral/capsule_frag.py @@ -8,11 +8,11 @@ from .hashing import hash_to_cfrag_verification, kfrag_signature_message from .keys import PublicKey from .key_frag import KeyFrag, KeyFragID from .params import PARAMETERS -from .serializable import Serializable +from .serializable import Serializable, Deserializable from .signing import Signature -class CapsuleFragProof(Serializable): +class CapsuleFragProof(Serializable, Deserializable): def __init__(self, point_e2: CurvePoint, @@ -95,7 +95,7 @@ class CapsuleFragProof(Serializable): ) -class CapsuleFrag(Serializable): +class CapsuleFrag(Serializable, Deserializable): """ Re-encrypted fragment of :py:class:`Capsule`. """ @@ -215,7 +215,7 @@ class CapsuleFrag(Serializable): return VerifiedCapsuleFrag(self) -class VerifiedCapsuleFrag: +class VerifiedCapsuleFrag(Serializable): """ Verified capsule frag, good for decryption. Can be cast to ``bytes``, but cannot be deserialized from bytes directly. @@ -228,6 +228,10 @@ class VerifiedCapsuleFrag: def __bytes__(self): return bytes(self.cfrag) + @classmethod + def serialized_size(cls): + return CapsuleFrag.serialized_size() + def __eq__(self, other): return self.cfrag == other.cfrag diff --git a/umbral/curve_point.py b/umbral/curve_point.py index 785a8ac..156a8d9 100644 --- a/umbral/curve_point.py +++ b/umbral/curve_point.py @@ -3,10 +3,10 @@ from typing import Tuple from . import openssl from .curve import CURVE from .curve_scalar import CurveScalar -from .serializable import Serializable +from .serializable import Serializable, Deserializable -class CurvePoint(Serializable): +class CurvePoint(Serializable, Deserializable): """ Represents an OpenSSL EC_POINT except more Pythonic. """ diff --git a/umbral/curve_scalar.py b/umbral/curve_scalar.py index 9fc3c74..7926405 100644 --- a/umbral/curve_scalar.py +++ b/umbral/curve_scalar.py @@ -2,12 +2,12 @@ from typing import TYPE_CHECKING, Union, Tuple from . import openssl from .curve import CURVE -from .serializable import Serializable +from .serializable import Serializable, Deserializable if TYPE_CHECKING: # pragma: no cover from .hashing import Hash -class CurveScalar(Serializable): +class CurveScalar(Serializable, Deserializable): """ Represents an OpenSSL Bignum modulo the order of a curve. Some of these operations will only work with prime numbers. diff --git a/umbral/key_frag.py b/umbral/key_frag.py index b56c9dc..684a154 100644 --- a/umbral/key_frag.py +++ b/umbral/key_frag.py @@ -7,11 +7,11 @@ from .errors import VerificationError from .hashing import hash_to_shared_secret, kfrag_signature_message, hash_to_polynomial_arg from .keys import PublicKey, SecretKey from .params import PARAMETERS -from .serializable import Serializable, bool_bytes, bool_serialized_size +from .serializable import Serializable, Deserializable, bool_bytes, bool_serialized_size from .signing import Signature, Signer -class KeyFragID(Serializable): +class KeyFragID(Serializable, Deserializable): __SIZE = 32 @@ -37,7 +37,7 @@ class KeyFragID(Serializable): return self._id -class KeyFragProof(Serializable): +class KeyFragProof(Serializable, Deserializable): @classmethod def from_base(cls, @@ -135,7 +135,7 @@ def poly_eval(coeffs: List[CurveScalar], x: CurveScalar) -> CurveScalar: return result -class KeyFrag(Serializable): +class KeyFrag(Serializable, Deserializable): """ A signed fragment of the delegating key. """ @@ -252,7 +252,7 @@ class KeyFrag(Serializable): return VerifiedKeyFrag(self) -class VerifiedKeyFrag: +class VerifiedKeyFrag(Serializable): """ Verified kfrag, good for reencryption. Can be cast to ``bytes``, but cannot be deserialized from bytes directly. @@ -265,6 +265,10 @@ class VerifiedKeyFrag: def __bytes__(self): return bytes(self.kfrag) + @classmethod + def serialized_size(cls): + return KeyFrag.serialized_size() + @classmethod def from_verified_bytes(cls, data) -> 'VerifiedKeyFrag': """ diff --git a/umbral/keys.py b/umbral/keys.py index ad3f73a..b628c94 100644 --- a/umbral/keys.py +++ b/umbral/keys.py @@ -5,10 +5,10 @@ from .curve_scalar import CurveScalar from .curve_point import CurvePoint from .dem import kdf from .hashing import Hash -from .serializable import Serializable +from .serializable import Serializable, Deserializable -class SecretKey(Serializable): +class SecretKey(Serializable, Deserializable): """ Umbral secret (private) key. """ @@ -54,7 +54,7 @@ class SecretKey(Serializable): return bytes(self._scalar_key) -class PublicKey(Serializable): +class PublicKey(Serializable, Deserializable): """ Umbral public key. """ @@ -93,7 +93,7 @@ class PublicKey(Serializable): return hash((self.__class__, bytes(self))) -class SecretKeyFactory(Serializable): +class SecretKeyFactory(Serializable, Deserializable): """ This class handles keyring material for Umbral, by allowing deterministic derivation of :py:class:`SecretKey` objects based on labels. diff --git a/umbral/serializable.py b/umbral/serializable.py index 2cdaf4e..3b06143 100644 --- a/umbral/serializable.py +++ b/umbral/serializable.py @@ -2,12 +2,24 @@ from abc import abstractmethod, ABC from typing import Tuple, Type, List, Any, TypeVar -class Serializable(ABC): +class HasSerializedSize(ABC): + + @classmethod + @abstractmethod + def serialized_size(cls) -> int: + """ + Returns the size in bytes of the serialized representation of this object + (obtained with ``bytes()``). + """ + raise NotImplementedError + + +class Deserializable(HasSerializedSize): """ A mixin for composable serialization. """ - _T = TypeVar('_T', bound='Serializable') + _T = TypeVar('_T', bound='Deserializable') @classmethod def from_bytes(cls: Type[_T], data: bytes) -> _T: @@ -22,7 +34,7 @@ class Serializable(ABC): @staticmethod def _split(data: bytes, *types: Type) -> List[Any]: """ - Given a list of ``Serializable`` types, attempts to deserialize them from the bytestring + Given a list of ``Deserializable`` types, attempts to deserialize them from the bytestring one by one and returns the list of the resulting objects and the remaining bytestring. """ objs = [] @@ -47,15 +59,6 @@ class Serializable(ABC): return objs - @classmethod - @abstractmethod - def serialized_size(cls) -> int: - """ - Returns the size in bytes of the serialized representation of this object - (obtained with ``bytes()``). - """ - raise NotImplementedError - @classmethod @abstractmethod def _from_exact_bytes(cls: Type[_T], data: bytes) -> _T: @@ -65,6 +68,9 @@ class Serializable(ABC): """ raise NotImplementedError + +class Serializable(HasSerializedSize): + @abstractmethod def __bytes__(self): """ diff --git a/umbral/signing.py b/umbral/signing.py index 50302ec..a119568 100644 --- a/umbral/signing.py +++ b/umbral/signing.py @@ -3,7 +3,7 @@ from .curve import CURVE from .curve_scalar import CurveScalar from .hashing import Hash from .keys import SecretKey, PublicKey -from .serializable import Serializable +from .serializable import Serializable, Deserializable def digest_for_signing(message: bytes) -> Hash: @@ -66,7 +66,7 @@ class Signer: raise RuntimeError(f"{self.__class__.__name__} objects do not support serialization") -class Signature(Serializable): +class Signature(Serializable, Deserializable): """ Wrapper for ECDSA signatures. """ From 76e6b85f1afc44e0c09b9a3b4037063b6f62e277 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Thu, 3 Jun 2021 13:59:57 -0700 Subject: [PATCH 07/10] Add a compatibility test for serialized sizes --- tests/test_compatibility.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/test_compatibility.py b/tests/test_compatibility.py index 9404b34..551d11c 100644 --- a/tests/test_compatibility.py +++ b/tests/test_compatibility.py @@ -242,3 +242,30 @@ def test_signer(implementations): assert _verify_message(umbral1, pk_bytes, signature2_bytes, message) assert _verify_message(umbral2, pk_bytes, signature1_bytes, message) + + +def _measure_sizes(umbral): + + sized_types = [ + umbral.SecretKey, + umbral.SecretKeyFactory, + umbral.PublicKey, + umbral.Signature, + umbral.Capsule, + umbral.KeyFrag, + umbral.VerifiedKeyFrag, + umbral.CapsuleFrag, + umbral.VerifiedCapsuleFrag, + ] + + return {tp.__name__: tp.serialized_size() for tp in sized_types} + + +def test_serialization_size(implementations): + + umbral1, umbral2 = implementations + + sizes1 = _measure_sizes(umbral1) + sizes2 = _measure_sizes(umbral1) + + assert sizes1 == sizes2 From 29b9f85fb784bea578dfe2d2a8364067cd7c5844 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Thu, 3 Jun 2021 15:28:40 -0700 Subject: [PATCH 08/10] Relax type bounds a little --- umbral/capsule_frag.py | 6 +++--- umbral/key_frag.py | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/umbral/capsule_frag.py b/umbral/capsule_frag.py index cc485ea..44a2e70 100644 --- a/umbral/capsule_frag.py +++ b/umbral/capsule_frag.py @@ -8,7 +8,7 @@ from .hashing import hash_to_cfrag_verification, kfrag_signature_message from .keys import PublicKey from .key_frag import KeyFrag, KeyFragID from .params import PARAMETERS -from .serializable import Serializable, Deserializable +from .serializable import Serializable, Deserializable, HasSerializedSize from .signing import Signature @@ -34,7 +34,7 @@ class CapsuleFragProof(Serializable, Deserializable): return (self.point_e2, self.point_v2, self.kfrag_commitment, self.kfrag_pok, self.signature, self.kfrag_signature) - _COMPONENT_TYPES: Tuple[Type[Serializable], ...] = ( + _COMPONENT_TYPES: Tuple[Type[HasSerializedSize], ...] = ( CurvePoint, CurvePoint, CurvePoint, CurvePoint, CurveScalar, Signature) _SERIALIZED_SIZE = sum(tp.serialized_size() for tp in _COMPONENT_TYPES) @@ -117,7 +117,7 @@ class CapsuleFrag(Serializable, Deserializable): def _components(self): return (self.point_e1, self.point_v1, self.kfrag_id, self.precursor, self.proof) - _COMPONENT_TYPES: Tuple[Type[Serializable], ...] = ( + _COMPONENT_TYPES: Tuple[Type[HasSerializedSize], ...] = ( CurvePoint, CurvePoint, KeyFragID, CurvePoint, CapsuleFragProof) _SERIALIZED_SIZE = sum(tp.serialized_size() for tp in _COMPONENT_TYPES) diff --git a/umbral/key_frag.py b/umbral/key_frag.py index 684a154..da64345 100644 --- a/umbral/key_frag.py +++ b/umbral/key_frag.py @@ -7,7 +7,9 @@ from .errors import VerificationError from .hashing import hash_to_shared_secret, kfrag_signature_message, hash_to_polynomial_arg from .keys import PublicKey, SecretKey from .params import PARAMETERS -from .serializable import Serializable, Deserializable, bool_bytes, bool_serialized_size +from .serializable import ( + Serializable, Deserializable, HasSerializedSize, + bool_bytes, bool_serialized_size) from .signing import Signature, Signer @@ -153,7 +155,7 @@ class KeyFrag(Serializable, Deserializable): def _components(self): return self.id, self.key, self.precursor, self.proof - _COMPONENT_TYPES: Tuple[Type[Serializable], ...] = ( + _COMPONENT_TYPES: Tuple[Type[HasSerializedSize], ...] = ( KeyFragID, CurveScalar, CurvePoint, KeyFragProof) _SERIALIZED_SIZE = sum(tp.serialized_size() for tp in _COMPONENT_TYPES) From 7334ed24644ef8401f67a8af43803321abe7874c Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Thu, 3 Jun 2021 15:29:22 -0700 Subject: [PATCH 09/10] Add docstrings to serialization mixins --- umbral/serializable.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/umbral/serializable.py b/umbral/serializable.py index 3b06143..b993f80 100644 --- a/umbral/serializable.py +++ b/umbral/serializable.py @@ -3,6 +3,9 @@ from typing import Tuple, Type, List, Any, TypeVar class HasSerializedSize(ABC): + """ + A base serialization mixin, denoting a type with a constant-size serialized representation. + """ @classmethod @abstractmethod @@ -16,7 +19,7 @@ class HasSerializedSize(ABC): class Deserializable(HasSerializedSize): """ - A mixin for composable serialization. + A mixin for composable deserialization. """ _T = TypeVar('_T', bound='Deserializable') @@ -70,6 +73,9 @@ class Deserializable(HasSerializedSize): class Serializable(HasSerializedSize): + """ + A mixin for composable serialization. + """ @abstractmethod def __bytes__(self): From 42b287705d113f7fb47fb2ffeb3d88c148cc1731 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sun, 6 Jun 2021 22:51:07 -0700 Subject: [PATCH 10/10] Get rid of GenericError In all cases where it is used it is just ValueError --- docs/examples/umbral_simple_api.py | 4 ++-- docs/notebooks/pyUmbral Simple API.ipynb | 4 +--- docs/source/api.rst | 3 --- docs/source/using_pyumbral.rst | 2 +- tests/test_capsule.py | 5 ++--- tests/test_dem.py | 5 ++--- tests/test_pre.py | 3 +-- umbral/__init__.py | 3 +-- umbral/capsule.py | 5 ++--- umbral/dem.py | 7 +++---- umbral/errors.py | 8 +------- 11 files changed, 16 insertions(+), 33 deletions(-) diff --git a/docs/examples/umbral_simple_api.py b/docs/examples/umbral_simple_api.py index 7e813f5..0b9a3b7 100644 --- a/docs/examples/umbral_simple_api.py +++ b/docs/examples/umbral_simple_api.py @@ -1,6 +1,6 @@ import random from umbral import ( - SecretKey, PublicKey, Signer, GenericError, CapsuleFrag, + SecretKey, PublicKey, Signer, CapsuleFrag, encrypt, generate_kfrags, reencrypt, decrypt_original, decrypt_reencrypted) # Generate an Umbral key pair @@ -46,7 +46,7 @@ bob_capsule = capsule # Attempt Bob's decryption (fail) try: fail_decrypted_data = decrypt_original(bobs_secret_key, bob_capsule, ciphertext) -except GenericError: +except ValueError: print("Decryption failed! Bob doesn't has access granted yet.") # Alice grants access to Bob by generating kfrags diff --git a/docs/notebooks/pyUmbral Simple API.ipynb b/docs/notebooks/pyUmbral Simple API.ipynb index eea199f..dae16d6 100644 --- a/docs/notebooks/pyUmbral Simple API.ipynb +++ b/docs/notebooks/pyUmbral Simple API.ipynb @@ -141,13 +141,11 @@ } ], "source": [ - "from umbral import GenericError\n", - "\n", "try:\n", " fail_decrypted_data = decrypt_original(delegating_sk=bobs_private_key,\n", " capsule=capsule,\n", " ciphertext=ciphertext)\n", - "except GenericError:\n", + "except ValueError:\n", " print(\"Decryption failed! Bob doesn't has access granted yet.\")\n" ] }, diff --git a/docs/source/api.rst b/docs/source/api.rst index 13bc51d..ee4349d 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -69,9 +69,6 @@ Encryption, re-encryption and decryption Utilities --------- -.. autoclass:: umbral.GenericError - :show-inheritance: - .. autoclass:: umbral.VerificationError :show-inheritance: diff --git a/docs/source/using_pyumbral.rst b/docs/source/using_pyumbral.rst index 70b233b..bc2aac1 100644 --- a/docs/source/using_pyumbral.rst +++ b/docs/source/using_pyumbral.rst @@ -135,7 +135,7 @@ or re-encrypted for him by Ursula, he will not be able to open it. ... ciphertext=ciphertext) Traceback (most recent call last): ... - umbral.GenericError + ValueError Ursulas perform re-encryption diff --git a/tests/test_capsule.py b/tests/test_capsule.py index 5ee03e6..367fb15 100644 --- a/tests/test_capsule.py +++ b/tests/test_capsule.py @@ -5,7 +5,6 @@ from umbral import ( SecretKey, PublicKey, Signer, - GenericError, encrypt, decrypt_original, reencrypt, @@ -29,7 +28,7 @@ def test_capsule_serialization(alices_keys): capsule.point_e = CurvePoint.random() capsule_bytes = bytes(capsule) - with pytest.raises(GenericError): + with pytest.raises(ValueError): Capsule.from_bytes(capsule_bytes) @@ -86,7 +85,7 @@ def test_open_reencrypted(alices_keys, bobs_keys): capsule.open_reencrypted(receiving_sk, delegating_pk, []) # Not enough cfrags - with pytest.raises(GenericError, match="Internal validation failed"): + with pytest.raises(ValueError, match="Internal validation failed"): capsule.open_reencrypted(receiving_sk, delegating_pk, cfrags[:threshold-1]) # Repeating cfrags diff --git a/tests/test_dem.py b/tests/test_dem.py index 7d5a5b1..8045c9b 100644 --- a/tests/test_dem.py +++ b/tests/test_dem.py @@ -1,7 +1,6 @@ import pytest import os -from umbral import GenericError from umbral.dem import DEM @@ -48,7 +47,7 @@ def test_malformed_ciphertext(): dem.decrypt(ciphertext[:DEM.NONCE_SIZE + DEM.TAG_SIZE - 1]) # Too long - with pytest.raises(GenericError): + with pytest.raises(ValueError): dem.decrypt(ciphertext + b'abcd') @@ -77,5 +76,5 @@ def test_encrypt_decrypt_associated_data(): assert cleartext1 == plaintext # Attempt decryption with invalid associated data - with pytest.raises(GenericError): + with pytest.raises(ValueError): cleartext2 = dem.decrypt(ciphertext0, authenticated_data=b'wrong data') diff --git a/tests/test_pre.py b/tests/test_pre.py index 863a7ac..ce90f34 100644 --- a/tests/test_pre.py +++ b/tests/test_pre.py @@ -4,7 +4,6 @@ from umbral import ( SecretKey, PublicKey, Signer, - GenericError, KeyFrag, CapsuleFrag, encrypt, @@ -25,7 +24,7 @@ def test_public_key_encryption(alices_keys): # Wrong secret key sk = SecretKey.random() - with pytest.raises(GenericError): + with pytest.raises(ValueError): decrypt_original(sk, capsule, ciphertext) diff --git a/umbral/__init__.py b/umbral/__init__.py index 1b5ca86..dbe509a 100644 --- a/umbral/__init__.py +++ b/umbral/__init__.py @@ -4,7 +4,7 @@ from .__about__ import ( from .capsule import Capsule from .capsule_frag import CapsuleFrag, VerifiedCapsuleFrag -from .errors import GenericError, VerificationError +from .errors import VerificationError from .key_frag import KeyFrag, VerifiedKeyFrag from .keys import SecretKey, PublicKey, SecretKeyFactory from .pre import encrypt, decrypt_original, decrypt_reencrypted, reencrypt, generate_kfrags @@ -21,7 +21,6 @@ __all__ = [ "VerifiedKeyFrag", "CapsuleFrag", "VerifiedCapsuleFrag", - "GenericError", "VerificationError", "encrypt", "decrypt_original", diff --git a/umbral/capsule.py b/umbral/capsule.py index 19bf121..f6275c8 100644 --- a/umbral/capsule.py +++ b/umbral/capsule.py @@ -2,7 +2,6 @@ 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 .serializable import Serializable, Deserializable @@ -40,7 +39,7 @@ class Capsule(Serializable, Deserializable): def _from_exact_bytes(cls, data: bytes): capsule = cls(*cls._split(data, *cls._COMPONENT_TYPES)) if not capsule._verify(): - raise GenericError("Capsule self-verification failed. Serialized data may be damaged.") + raise ValueError("Capsule self-verification failed. Serialized data may be damaged.") return capsule def __bytes__(self): @@ -110,7 +109,7 @@ class Capsule(Serializable, Deserializable): # 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") + raise ValueError("Internal validation failed") return (e_prime + v_prime) * d diff --git a/umbral/dem.py b/umbral/dem.py index a4b2fec..8989d5d 100644 --- a/umbral/dem.py +++ b/umbral/dem.py @@ -14,7 +14,6 @@ from nacl.bindings.crypto_aead import ( ) from . import openssl -from .errors import GenericError def kdf(data: bytes, @@ -64,6 +63,6 @@ class DEM: try: return xchacha_decrypt(ciphertext, authenticated_data, nonce, self._key) except nacl.exceptions.CryptoError as e: - raise GenericError("Decryption of ciphertext failed: " - "either someone tampered with the ciphertext or " - "you are using an incorrect decryption key.") from e + raise ValueError("Decryption of ciphertext failed: " + "either someone tampered with the ciphertext or " + "you are using an incorrect decryption key.") from e diff --git a/umbral/errors.py b/umbral/errors.py index 5ca58fe..48bc179 100644 --- a/umbral/errors.py +++ b/umbral/errors.py @@ -1,10 +1,4 @@ -class GenericError(Exception): - """ - An interal Umbral error, see the message for details. - """ - - -class VerificationError(GenericError): +class VerificationError(Exception): """ Integrity of the data cannot be verified, see the message for details. """