mirror of https://github.com/nucypher/pyUmbral.git
commit
7b7fdfa285
|
@ -4,10 +4,6 @@ workflows:
|
|||
version: 2
|
||||
build_test_deploy:
|
||||
jobs:
|
||||
- pipenv_install_35:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- pipenv_install_36:
|
||||
filters:
|
||||
tags:
|
||||
|
@ -16,10 +12,6 @@ workflows:
|
|||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- pip_install_35:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- pip_install_36:
|
||||
filters:
|
||||
tags:
|
||||
|
@ -28,13 +20,6 @@ workflows:
|
|||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- run_tests_35:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
requires:
|
||||
- pipenv_install_35
|
||||
- pip_install_35
|
||||
- run_tests_36:
|
||||
filters:
|
||||
tags:
|
||||
|
@ -54,7 +39,6 @@ workflows:
|
|||
tags:
|
||||
only: /.*/
|
||||
requires:
|
||||
- run_tests_35
|
||||
- run_tests_36
|
||||
- run_tests_37
|
||||
- doctests_36:
|
||||
|
@ -62,7 +46,6 @@ workflows:
|
|||
tags:
|
||||
only: /.*/
|
||||
requires:
|
||||
- run_tests_35
|
||||
- run_tests_36
|
||||
- run_tests_37
|
||||
- notebook_tests_36:
|
||||
|
@ -70,7 +53,6 @@ workflows:
|
|||
tags:
|
||||
only: /.*/
|
||||
requires:
|
||||
- run_tests_35
|
||||
- run_tests_36
|
||||
- run_tests_37
|
||||
- reencryption_memory_profile_36:
|
||||
|
@ -78,7 +60,6 @@ workflows:
|
|||
tags:
|
||||
only: /.*/
|
||||
requires:
|
||||
- run_tests_35
|
||||
- run_tests_36
|
||||
- run_tests_37
|
||||
- reencryption_benchmark_36:
|
||||
|
@ -86,7 +67,6 @@ workflows:
|
|||
tags:
|
||||
only: /.*/
|
||||
# requires: # Commented this section to speed up the build
|
||||
# - run_tests_35
|
||||
# - run_tests_36
|
||||
# - run_tests_37
|
||||
- test_deploy:
|
||||
|
@ -119,11 +99,6 @@ workflows:
|
|||
branches:
|
||||
ignore: /.*/
|
||||
|
||||
python_35_base: &python_35_base
|
||||
working_directory: ~/pyUmbral-35
|
||||
docker:
|
||||
- image: circleci/python:3.5
|
||||
|
||||
python_36_base: &python_36_base
|
||||
working_directory: ~/pyUmbral-36
|
||||
docker:
|
||||
|
@ -190,12 +165,6 @@ commands:
|
|||
|
||||
jobs:
|
||||
|
||||
pipenv_install_35:
|
||||
<<: *python_35_base
|
||||
steps:
|
||||
- pipenv_install:
|
||||
python_version: "3.5"
|
||||
|
||||
pipenv_install_36:
|
||||
<<: *python_36_base
|
||||
steps:
|
||||
|
@ -208,11 +177,6 @@ jobs:
|
|||
- pipenv_install:
|
||||
python_version: "3.7"
|
||||
|
||||
pip_install_35:
|
||||
<<: *python_35_base
|
||||
steps:
|
||||
- pip_install
|
||||
|
||||
pip_install_36:
|
||||
<<: *python_36_base
|
||||
steps:
|
||||
|
@ -223,13 +187,6 @@ jobs:
|
|||
steps:
|
||||
- pip_install
|
||||
|
||||
run_tests_35:
|
||||
<<: *python_35_base
|
||||
parallelism: 4
|
||||
steps:
|
||||
- run_tests:
|
||||
python_version: "3.5"
|
||||
|
||||
run_tests_36:
|
||||
<<: *python_36_base
|
||||
parallelism: 4
|
||||
|
|
|
@ -3,3 +3,11 @@ omit =
|
|||
setup.py,
|
||||
*__init__.py
|
||||
parallel=True
|
||||
|
||||
[report]
|
||||
exclude_lines =
|
||||
# Have to re-enable the standard pragma
|
||||
pragma: no cover
|
||||
|
||||
# Exclude abstract methods
|
||||
@abstractmethod
|
||||
|
|
5
Pipfile
5
Pipfile
|
@ -5,13 +5,8 @@ name = "pypi"
|
|||
|
||||
[packages]
|
||||
setuptools = "*"
|
||||
# Third Party
|
||||
cryptography = ">=2.3"
|
||||
pynacl = "*"
|
||||
pysha3 = "*"
|
||||
# NuCypher
|
||||
bytestring-splitter = "*"
|
||||
constant-sorrow = ">=0.1.0a7"
|
||||
|
||||
[dev-packages]
|
||||
bumpversion = "*"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
67
README.rst
67
README.rst
|
@ -62,19 +62,18 @@ Additionally, users that delegate access to their data (like Alice, in this exam
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
from umbral import pre, keys, signing
|
||||
from umbral import SecretKey, PublicKey
|
||||
|
||||
# Generate Umbral keys for Alice.
|
||||
alices_private_key = keys.UmbralPrivateKey.gen_key()
|
||||
alices_public_key = alices_private_key.get_pubkey()
|
||||
alices_secret_key = SecretKey.random()
|
||||
alices_public_key = PublicKey.from_secret_key(alices_secret_key)
|
||||
|
||||
alices_signing_key = keys.UmbralPrivateKey.gen_key()
|
||||
alices_verifying_key = alices_signing_key.get_pubkey()
|
||||
alices_signer = signing.Signer(private_key=alices_signing_key)
|
||||
alices_signing_key = SecretKey.random()
|
||||
alices_verifying_key = PublicKey.from_secret_key(alices_signing_key)
|
||||
|
||||
# Generate Umbral keys for Bob.
|
||||
bobs_private_key = keys.UmbralPrivateKey.gen_key()
|
||||
bobs_public_key = bobs_private_key.get_pubkey()
|
||||
bobs_secret_key = SecretKey.random()
|
||||
bobs_public_key = PublicKey.from_secret_key(bobs_secret_key)
|
||||
|
||||
|
||||
**Encryption**
|
||||
|
@ -89,14 +88,14 @@ Alice can open the capsule and decrypt the ciphertext with her private key.
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
from umbral import encrypt, decrypt_original
|
||||
|
||||
# Encrypt data with Alice's public key.
|
||||
plaintext = b'Proxy Re-Encryption is cool!'
|
||||
ciphertext, capsule = pre.encrypt(alices_public_key, plaintext)
|
||||
capsule, ciphertext = encrypt(alices_public_key, plaintext)
|
||||
|
||||
# Decrypt data with Alice's private key.
|
||||
cleartext = pre.decrypt(ciphertext=ciphertext,
|
||||
capsule=capsule,
|
||||
decrypting_key=alices_private_key)
|
||||
cleartext = decrypt_original(alices_secret_key, capsule, ciphertext)
|
||||
|
||||
|
||||
**Re-Encryption Key Fragments**
|
||||
|
@ -107,13 +106,15 @@ which are next sent to N proxies or *Ursulas*.
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
from umbral import generate_kfrags
|
||||
|
||||
# Alice generates "M of N" re-encryption key fragments (or "KFrags") for Bob.
|
||||
# In this example, 10 out of 20.
|
||||
kfrags = pre.generate_kfrags(delegating_privkey=alices_private_key,
|
||||
signer=alices_signer,
|
||||
receiving_pubkey=bobs_public_key,
|
||||
threshold=10,
|
||||
N=20)
|
||||
kfrags = generate_kfrags(delegating_sk=alices_secret_key,
|
||||
receiving_pk=bobs_public_key,
|
||||
signing_sk=alices_signing_key,
|
||||
threshold=10,
|
||||
num_kfrags=20)
|
||||
|
||||
|
||||
**Re-Encryption**
|
||||
|
@ -127,17 +128,13 @@ Bob must gather at least ``threshold`` cfrags in order to activate the capsule.
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
# Several Ursulas perform re-encryption, and Bob collects the resulting `cfrags`.
|
||||
# He must gather at least `threshold` `cfrags` in order to activate the capsule.
|
||||
from umbral import reencrypt
|
||||
|
||||
capsule.set_correctness_keys(delegating=alices_public_key,
|
||||
receiving=bobs_public_key,
|
||||
verifying=alices_verifying_key)
|
||||
|
||||
cfrags = list() # Bob's cfrag collection
|
||||
for kfrag in kfrags[:10]:
|
||||
cfrag = pre.reencrypt(kfrag=kfrag, capsule=capsule)
|
||||
cfrags.append(cfrag) # Bob collects a cfrag
|
||||
# Several Ursulas perform re-encryption, and Bob collects the resulting `cfrags`.
|
||||
cfrags = list() # Bob's cfrag collection
|
||||
for kfrag in kfrags[:10]:
|
||||
cfrag = pre.reencrypt(capsule=capsule, kfrag=kfrag)
|
||||
cfrags.append(cfrag) # Bob collects a cfrag
|
||||
|
||||
|
||||
**Decryption by Bob**
|
||||
|
@ -147,14 +144,14 @@ and then decrypts the re-encrypted ciphertext.
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
# Bob activates and opens the capsule
|
||||
for cfrag in cfrags:
|
||||
capsule.attach_cfrag(cfrag)
|
||||
from umbral import decrypt_reencrypted
|
||||
|
||||
bob_cleartext = pre.decrypt(ciphertext=ciphertext,
|
||||
capsule=capsule,
|
||||
decrypting_key=bobs_private_key)
|
||||
assert bob_cleartext == plaintext
|
||||
bob_cleartext = pre.decrypt_reencrypted(decrypting_sk=bobs_secret_key,
|
||||
delegating_pk=alices_public_key,
|
||||
capsule=capsule,
|
||||
cfrags=cfrags,
|
||||
ciphertext=ciphertext)
|
||||
assert bob_cleartext == plaintext
|
||||
|
||||
See more detailed usage examples in the docs_ directory.
|
||||
|
||||
|
@ -171,7 +168,7 @@ To install pyUmbral, simply use ``pip``:
|
|||
$ pip3 install umbral
|
||||
|
||||
|
||||
Alternatively, you can checkout the repo and install it from there.
|
||||
Alternatively, you can checkout the repo and install it from there.
|
||||
The NuCypher team uses ``pipenv`` for managing pyUmbral's dependencies.
|
||||
The recommended installation procedure is as follows:
|
||||
|
||||
|
|
|
@ -1,41 +1,19 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
#1
|
||||
# Sets a default curve (secp256k1)
|
||||
import random
|
||||
from umbral import pre, keys, config, signing
|
||||
from umbral import (
|
||||
SecretKey, PublicKey, GenericError,
|
||||
encrypt, generate_kfrags, reencrypt, decrypt_original, decrypt_reencrypted)
|
||||
|
||||
config.set_default_curve()
|
||||
|
||||
#2
|
||||
# Generate an Umbral key pair
|
||||
# ---------------------------
|
||||
# First, Let's generate two asymmetric key pairs for Alice:
|
||||
# A delegating key pair and a Signing key pair.
|
||||
|
||||
alices_private_key = keys.UmbralPrivateKey.gen_key()
|
||||
alices_public_key = alices_private_key.get_pubkey()
|
||||
alices_secret_key = SecretKey.random()
|
||||
alices_public_key = PublicKey.from_secret_key(alices_secret_key)
|
||||
|
||||
alices_signing_key = keys.UmbralPrivateKey.gen_key()
|
||||
alices_verifying_key = alices_signing_key.get_pubkey()
|
||||
alices_signer = signing.Signer(private_key=alices_signing_key)
|
||||
alices_signing_key = SecretKey.random()
|
||||
alices_verifying_key = PublicKey.from_secret_key(alices_signing_key)
|
||||
|
||||
#3
|
||||
# Encrypt some data for Alice
|
||||
# ---------------------------
|
||||
# Now let's encrypt data with Alice's public key.
|
||||
|
@ -44,98 +22,86 @@ alices_signer = signing.Signer(private_key=alices_signing_key)
|
|||
# this operation.
|
||||
|
||||
plaintext = b'Proxy Re-encryption is cool!'
|
||||
ciphertext, capsule = pre.encrypt(alices_public_key, plaintext)
|
||||
capsule, ciphertext = encrypt(alices_public_key, plaintext)
|
||||
print(ciphertext)
|
||||
|
||||
#4
|
||||
# Decrypt data for Alice
|
||||
# ----------------------
|
||||
# Since data was encrypted with Alice's public key,
|
||||
# Alice can open the capsule and decrypt the ciphertext with her private key.
|
||||
|
||||
cleartext = pre.decrypt(ciphertext=ciphertext,
|
||||
capsule=capsule,
|
||||
decrypting_key=alices_private_key)
|
||||
cleartext = decrypt_original(alices_secret_key, capsule, ciphertext)
|
||||
print(cleartext)
|
||||
|
||||
#5
|
||||
# Bob Exists
|
||||
# -----------
|
||||
|
||||
bobs_private_key = keys.UmbralPrivateKey.gen_key()
|
||||
bobs_public_key = bobs_private_key.get_pubkey()
|
||||
bobs_secret_key = SecretKey.random()
|
||||
bobs_public_key = PublicKey.from_secret_key(bobs_secret_key)
|
||||
|
||||
#6
|
||||
# Bob receives a capsule through a side channel (s3, ipfs, Google cloud, etc)
|
||||
bob_capsule = capsule
|
||||
|
||||
#7
|
||||
# Attempt Bob's decryption (fail)
|
||||
try:
|
||||
fail_decrypted_data = pre.decrypt(ciphertext=ciphertext,
|
||||
capsule=bob_capsule,
|
||||
decrypting_key=bobs_private_key)
|
||||
except pre.UmbralDecryptionError:
|
||||
fail_decrypted_data = decrypt_original(bobs_secret_key, bob_capsule, ciphertext)
|
||||
except GenericError:
|
||||
print("Decryption failed! Bob doesn't has access granted yet.")
|
||||
|
||||
#8
|
||||
# Alice grants access to Bob by generating kfrags
|
||||
# Alice grants access to Bob by generating kfrags
|
||||
# -----------------------------------------------
|
||||
# When Alice wants to grant Bob access to open her encrypted messages,
|
||||
# she creates *threshold split re-encryption keys*, or *"kfrags"*,
|
||||
# which are next sent to N proxies or *Ursulas*.
|
||||
# She uses her private key, and Bob's public key, and she sets a minimum
|
||||
# When Alice wants to grant Bob access to open her encrypted messages,
|
||||
# she creates *threshold split re-encryption keys*, or *"kfrags"*,
|
||||
# which are next sent to N proxies or *Ursulas*.
|
||||
# She uses her private key, and Bob's public key, and she sets a minimum
|
||||
# threshold of 10, for 20 total shares
|
||||
|
||||
kfrags = pre.generate_kfrags(delegating_privkey=alices_private_key,
|
||||
signer=alices_signer,
|
||||
receiving_pubkey=bobs_public_key,
|
||||
threshold=10,
|
||||
N=20)
|
||||
kfrags = generate_kfrags(delegating_sk=alices_secret_key,
|
||||
receiving_pk=bobs_public_key,
|
||||
signing_sk=alices_signing_key,
|
||||
threshold=10,
|
||||
num_kfrags=20)
|
||||
|
||||
#9
|
||||
# Ursulas perform re-encryption
|
||||
# ------------------------------
|
||||
# Bob asks several Ursulas to re-encrypt the capsule so he can open it.
|
||||
# Each Ursula performs re-encryption on the capsule using the `kfrag`
|
||||
# Bob asks several Ursulas to re-encrypt the capsule so he can open it.
|
||||
# Each Ursula performs re-encryption on the capsule using the `kfrag`
|
||||
# provided by Alice, obtaining this way a "capsule fragment", or `cfrag`.
|
||||
# Let's mock a network or transport layer by sampling `threshold` random `kfrags`,
|
||||
# one for each required Ursula.
|
||||
|
||||
import random
|
||||
|
||||
kfrags = random.sample(kfrags, # All kfrags from above
|
||||
10) # M - Threshold
|
||||
|
||||
# Bob collects the resulting `cfrags` from several Ursulas.
|
||||
# Bob must gather at least `threshold` `cfrags` in order to activate the capsule.
|
||||
|
||||
bob_capsule.set_correctness_keys(delegating=alices_public_key,
|
||||
receiving=bobs_public_key,
|
||||
verifying=alices_verifying_key)
|
||||
# Bob collects the resulting `cfrags` from several Ursulas.
|
||||
# Bob must gather at least `threshold` `cfrags` in order to open the capsule.
|
||||
|
||||
cfrags = list() # Bob's cfrag collection
|
||||
for kfrag in kfrags:
|
||||
cfrag = pre.reencrypt(kfrag=kfrag, capsule=bob_capsule)
|
||||
cfrag = reencrypt(capsule=capsule, kfrag=kfrag)
|
||||
cfrags.append(cfrag) # Bob collects a cfrag
|
||||
|
||||
assert len(cfrags) == 10
|
||||
|
||||
#10
|
||||
# Bob attaches cfrags to the capsule
|
||||
# ----------------------------------
|
||||
# Bob attaches at least `threshold` `cfrags` to the capsule;
|
||||
# then it can become *activated*.
|
||||
# Bob checks the capsule fragments
|
||||
# --------------------------------
|
||||
# Bob can verify that the capsule fragments are valid and really originate from Alice,
|
||||
# using Alice's public keys.
|
||||
|
||||
for cfrag in cfrags:
|
||||
bob_capsule.attach_cfrag(cfrag)
|
||||
assert all(cfrag.verify(capsule,
|
||||
delegating_pk=alices_public_key,
|
||||
receiving_pk=bobs_public_key,
|
||||
signing_pk=alices_verifying_key)
|
||||
for cfrag in cfrags)
|
||||
|
||||
#11
|
||||
# Bob activates and opens the capsule
|
||||
# Bob opens the capsule
|
||||
# ------------------------------------
|
||||
# Finally, Bob activates and opens the capsule,
|
||||
# then decrypts the re-encrypted ciphertext.
|
||||
# Finally, Bob decrypts the re-encrypted ciphertext using his key.
|
||||
|
||||
bob_cleartext = pre.decrypt(ciphertext=ciphertext, capsule=bob_capsule, decrypting_key=bobs_private_key)
|
||||
bob_cleartext = decrypt_reencrypted(decrypting_sk=bobs_secret_key,
|
||||
delegating_pk=alices_public_key,
|
||||
capsule=bob_capsule,
|
||||
cfrags=cfrags,
|
||||
ciphertext=ciphertext)
|
||||
print(bob_cleartext)
|
||||
assert bob_cleartext == plaintext
|
||||
|
|
|
@ -7,30 +7,6 @@
|
|||
"# pyUmbral Python API"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Setting the default curve\n",
|
||||
"\n",
|
||||
"The first time you use umbral, you may want to specify an elliptic curve to use. If you do not specify a curve, secp256k1 will be used for all operations, with a slight performace hit for the lookup.\n",
|
||||
"\n",
|
||||
"To set the default curve use `umbral.config.set_default_curve()`\n",
|
||||
"\n",
|
||||
"Note: you can only set the dafault once, or `UmbralConfigurationError` will be raised."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from umbral.config import set_default_curve\n",
|
||||
"\n",
|
||||
"set_default_curve()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
|
@ -46,16 +22,15 @@
|
|||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from umbral import keys, signing\n",
|
||||
"from umbral import SecretKey, PublicKey\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Alice's Keys\n",
|
||||
"alices_private_key = keys.UmbralPrivateKey.gen_key()\n",
|
||||
"alices_public_key = alices_private_key.get_pubkey()\n",
|
||||
"alices_private_key = SecretKey.random()\n",
|
||||
"alices_public_key = PublicKey.from_secret_key(alices_private_key)\n",
|
||||
"\n",
|
||||
"alices_signing_key = keys.UmbralPrivateKey.gen_key()\n",
|
||||
"alices_verifying_key = alices_signing_key.get_pubkey()\n",
|
||||
"alices_signer = signing.Signer(private_key=alices_signing_key)"
|
||||
"alices_signing_key = SecretKey.random()\n",
|
||||
"alices_verifying_key = PublicKey.from_secret_key(alices_signing_key)"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -80,16 +55,16 @@
|
|||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"b'#\\xebQ\\xd4\\xad\\x8ah,9\\x8f\\xc9\\x18\\x84[\\x95M\\x8e\\xb1\\x85\\xf9\\xbe\\x97\\x07\\xf3\\x80@\\x11\\xab\\x82\\xac\\xa1\\xbf\\xc0\\x00e\\xecpTq\\xef\\x94\\xd94\\x94\\x1a\\xdf\\xf0\\x04)\\xf5\\r\\xc4\\xbd/:\\x8c'\n"
|
||||
"b'\\x1c\\xa0\\xa83\\x0cv\\x97\\x02d\\xe9\\xe9\\xc5_\\x9d5NRGRx\\xd4\\xc9\\x17%\\x9b\\xb4\\x05\\xd1\\xc2\\x1e\\x9d\\x0b\\xbf\\xb4g\\xf0n\\xfe\\x9eM\\x93\\xe0\\xbf#l\\xf9\\x033\\xb00\\xf5\\r\\xff\\xc9\\x133C\\xf0\\xa3\\xc0\\xd1e\\xdb~.E$%'\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from umbral import pre\n",
|
||||
"from umbral import encrypt\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"plaintext = b'Proxy Re-encryption is cool!'\n",
|
||||
"ciphertext, capsule = pre.encrypt(alices_public_key, plaintext)\n",
|
||||
"capsule, ciphertext = encrypt(alices_public_key, plaintext)\n",
|
||||
"print(ciphertext)"
|
||||
]
|
||||
},
|
||||
|
@ -115,10 +90,13 @@
|
|||
}
|
||||
],
|
||||
"source": [
|
||||
"cleartext = pre.decrypt(ciphertext=ciphertext, \n",
|
||||
" capsule=capsule, \n",
|
||||
" decrypting_key=alices_private_key)\n",
|
||||
"print(cleartext)\n"
|
||||
"from umbral import decrypt_original\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"cleartext = decrypt_original(sk=alices_private_key,\n",
|
||||
" capsule=capsule,\n",
|
||||
" ciphertext=ciphertext)\n",
|
||||
"print(cleartext)"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -135,8 +113,8 @@
|
|||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"bobs_private_key = keys.UmbralPrivateKey.gen_key()\n",
|
||||
"bobs_public_key = bobs_private_key.get_pubkey()\n",
|
||||
"bobs_private_key = SecretKey.random()\n",
|
||||
"bobs_public_key = PublicKey.from_secret_key(bobs_private_key)\n",
|
||||
"\n",
|
||||
"bob_capsule = capsule"
|
||||
]
|
||||
|
@ -162,11 +140,13 @@
|
|||
}
|
||||
],
|
||||
"source": [
|
||||
"from umbral import GenericError\n",
|
||||
"\n",
|
||||
"try:\n",
|
||||
" fail_decrypted_data = pre.decrypt(ciphertext=ciphertext, \n",
|
||||
" capsule=capsule, \n",
|
||||
" decrypting_key=bobs_private_key)\n",
|
||||
"except pre.UmbralDecryptionError:\n",
|
||||
" fail_decrypted_data = decrypt_original(sk=bobs_private_key,\n",
|
||||
" capsule=capsule,\n",
|
||||
" ciphertext=ciphertext)\n",
|
||||
"except GenericError:\n",
|
||||
" print(\"Decryption failed! Bob doesn't has access granted yet.\")\n"
|
||||
]
|
||||
},
|
||||
|
@ -184,30 +164,7 @@
|
|||
"metadata": {},
|
||||
"source": [
|
||||
"## Alice grants access to Bob by generating KFrags \n",
|
||||
"When Alice wants to grant Bob access to open her encrypted messages, she creates *re-encryption key fragments*, or *\"kfrags\"*, which are next sent to N proxies or *Ursulas*. She uses her private key, and Bob's public key, and she sets a minimum threshold of 10, for 20 total shares\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"M, N = 10, 20\n",
|
||||
"kfrags = pre.generate_kfrags(delegating_privkey=alices_private_key, \n",
|
||||
" receiving_pubkey=bobs_public_key, \n",
|
||||
" signer=alices_signer,\n",
|
||||
" threshold=M, \n",
|
||||
" N=N)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"\n",
|
||||
"## Ursulas Re-encrypt; Bob attaches fragments to `capsule`\n",
|
||||
"Bob asks several Ursulas to re-encrypt the capsule so he can open it. Each Ursula performs re-encryption on the capsule using the `kfrag` provided by Alice, obtaining this way a \"capsule fragment\", or `cfrag`. Let's mock a network or transport layer by sampling `M` random `kfrags`, one for each required Ursula. Note that each Ursula must prepare the received capsule before re-encryption by setting the proper correctness keys. Bob collects the resulting `cfrags` from several Ursulas. He must gather at least `M` `cfrags` in order to activate the capsule.\n"
|
||||
"When Alice wants to grant Bob access to open her encrypted messages, she creates *re-encryption key fragments*, or \"kfrags\", which are next sent to N proxies or *Ursulas*. She uses her private key, and Bob's public key, and she sets a minimum threshold of 10, for 20 total shares\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -216,36 +173,81 @@
|
|||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import random\n",
|
||||
"kfrags = random.sample(kfrags, # All kfrags from above\n",
|
||||
" 10) # M - Threshold\n",
|
||||
"from umbral import generate_kfrags\n",
|
||||
"\n",
|
||||
"bob_capsule.set_correctness_keys(delegating=alices_public_key,\n",
|
||||
" receiving=bobs_public_key,\n",
|
||||
" verifying=alices_verifying_key)\n",
|
||||
"\n",
|
||||
"cfrags = list() # Bob's cfrag collection\n",
|
||||
"for kfrag in kfrags:\n",
|
||||
" cfrag = pre.reencrypt(kfrag=kfrag, capsule=bob_capsule)\n",
|
||||
" cfrags.append(cfrag) # Bob collects a cfrag\n",
|
||||
"\n",
|
||||
"assert len(cfrags) == 10\n"
|
||||
"M, N = 10, 20 # the threshold and the total number of fragments\n",
|
||||
"kfrags = generate_kfrags(delegating_sk=alices_private_key,\n",
|
||||
" receiving_pk=bobs_public_key,\n",
|
||||
" signing_sk=alices_signing_key,\n",
|
||||
" threshold=M,\n",
|
||||
" num_kfrags=N)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Bob activates and opens the capsule; Decrypts data from Alice.\n",
|
||||
"The `capsule` can become *activated* once Bob attaches at least `M` `cfrags` to it. Note that it has to be prepared in advance with the necessary `correctness_keys` (specifically, Alice's public key, Alice's signature verification key and his own public key). \n",
|
||||
"\n",
|
||||
"Finally, Bob activates and opens the capsule, then decrypts the re-encrypted ciphertext."
|
||||
"## Ursulas Re-encrypt; Bob attaches fragments to `capsule`\n",
|
||||
"Bob asks several Ursulas to re-encrypt the capsule so he can open it. Each Ursula performs re-encryption on the capsule using the `kfrag` provided by Alice, obtaining this way a \"capsule fragment\", or `cfrag`. Let's mock a network or transport layer by sampling `M` random `kfrags`, one for each required Ursula. Bob collects the resulting `cfrags` from several Ursulas. He must gather at least `M` `cfrags` in order to activate the capsule.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import random\n",
|
||||
"kfrags = random.sample(kfrags, # All kfrags from above\n",
|
||||
" M) # Threshold\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"from umbral import reencrypt\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"cfrags = list() # Bob's cfrag collection\n",
|
||||
"for kfrag in kfrags:\n",
|
||||
" cfrag = reencrypt(capsule=capsule, kfrag=kfrag)\n",
|
||||
" cfrags.append(cfrag) # Bob collects a cfrag"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Bob checks the capsule fragments\n",
|
||||
"Bob can verify that the capsule fragments are valid and really originate from Alice, using Alice's public keys."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"assert all(cfrag.verify(capsule,\n",
|
||||
" delegating_pk=alices_public_key,\n",
|
||||
" receiving_pk=bobs_public_key,\n",
|
||||
" signing_pk=alices_verifying_key)\n",
|
||||
" for cfrag in cfrags)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Bob opens the capsule; Decrypts data from Alice.\n",
|
||||
"Finally, Bob decrypts the re-encrypted ciphertext using his secret key."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
|
@ -256,17 +258,16 @@
|
|||
}
|
||||
],
|
||||
"source": [
|
||||
"bob_capsule.set_correctness_keys(delegating=alices_public_key,\n",
|
||||
" receiving=bobs_public_key,\n",
|
||||
" verifying=alices_verifying_key)\n",
|
||||
"from umbral import decrypt_reencrypted\n",
|
||||
"\n",
|
||||
"bob_cleartext = decrypt_reencrypted(decrypting_sk=bobs_private_key,\n",
|
||||
" delegating_pk=alices_public_key,\n",
|
||||
" capsule=capsule,\n",
|
||||
" cfrags=cfrags,\n",
|
||||
" ciphertext=ciphertext)\n",
|
||||
"\n",
|
||||
"for cfrag in cfrags:\n",
|
||||
" bob_capsule.attach_cfrag(cfrag)\n",
|
||||
" \n",
|
||||
"bob_cleartext = pre.decrypt(ciphertext=ciphertext, capsule=capsule, decrypting_key=bobs_private_key)\n",
|
||||
"print(bob_cleartext)\n",
|
||||
"assert bob_cleartext == plaintext\n",
|
||||
"\n"
|
||||
"assert bob_cleartext == plaintext"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -293,7 +294,7 @@
|
|||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.7.0"
|
||||
"version": "3.8.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
Public API
|
||||
==========
|
||||
|
||||
.. automodule:: umbral
|
||||
|
||||
Keys
|
||||
----
|
||||
|
||||
.. autoclass:: SecretKey()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: PublicKey()
|
||||
:members:
|
||||
:special-members: __eq__, __hash__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: SecretKeyFactory()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
Intermediate objects
|
||||
--------------------
|
||||
|
||||
.. autoclass:: Capsule()
|
||||
:special-members: __eq__, __hash__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: KeyFrag()
|
||||
:members: verify
|
||||
:special-members: __eq__, __hash__
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: CapsuleFrag()
|
||||
:members: verify
|
||||
:special-members: __eq__, __hash__
|
||||
:show-inheritance:
|
||||
|
||||
Encryption, re-encryption and decryption
|
||||
----------------------------------------
|
||||
|
||||
.. autofunction:: encrypt
|
||||
|
||||
.. autofunction:: decrypt_original
|
||||
|
||||
.. autofunction:: generate_kfrags
|
||||
|
||||
.. autofunction:: reencrypt
|
||||
|
||||
.. autofunction:: decrypt_reencrypted
|
||||
|
||||
Utilities
|
||||
---------
|
||||
|
||||
.. autoclass:: umbral.GenericError
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: umbral.serializable.Serializable
|
||||
:members: from_bytes
|
||||
:special-members: __bytes__
|
|
@ -1,63 +0,0 @@
|
|||
=========================
|
||||
Choosing and Using Curves
|
||||
=========================
|
||||
|
||||
|
||||
The matter of which curve to use is the subject of some debate. If you aren't sure, you might start here:
|
||||
https://safecurves.cr.yp.to/
|
||||
|
||||
A number of curves are available in the Cryptography.io_ library, on which pyUmbral depends.
|
||||
You can find them in the ``cryptography.hazmat.primitives.asymmetric.ec`` module.
|
||||
|
||||
.. _Cryptography.io: https://cryptography.io/en/latest/
|
||||
|
||||
Be careful when choosing a curve - the security of your application depends on it.
|
||||
|
||||
We provide curve ``SECP256K1`` as a default because it is the basis for a number of crypto-blockchain projects;
|
||||
we don't otherwise endorse its security.
|
||||
We additionally support curves ``SECP256R1`` (also known as "NIST P-256") and ``SECP384R1`` ("NIST P-384").
|
||||
|
||||
|
||||
Setting a default curve
|
||||
--------------------------
|
||||
|
||||
Before you perform any ECC operations, you can set a default curve.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> from umbral.curve import SECP256K1
|
||||
>>> config.set_default_curve(SECP256K1)
|
||||
|
||||
If you don't set a default curve, then SECP256K1 will be set for you when you perform the first ECC
|
||||
operation. This causes a small one-time performance penalty.
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> from umbral import keys
|
||||
>>> private_key = keys.UmbralPrivateKey.gen_key()
|
||||
|
||||
RuntimeWarning: No default curve has been set. Using SECP256K1.
|
||||
A slight performance penalty has been incurred for only this call.
|
||||
Set a default curve with umbral.config.set_default_curve().
|
||||
|
||||
|
||||
To use SECP256K1 and avoid this penalty, you can simply call ``set_default_curve()`` with no argument:
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> config.set_default_curve()
|
||||
|
||||
Attempting to set the default curve twice in the same runtime will raise
|
||||
a ``UmbralConfigurationError``.
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> from umbral import config
|
||||
>>> config.set_default_curve()
|
||||
>>> config.set_default_curve()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
umbral.config._CONFIG.UmbralConfigurationError
|
|
@ -31,7 +31,7 @@ pyUmbral
|
|||
.. end-badges
|
||||
|
||||
pyUmbral is the reference implementation of the Umbral_ threshold proxy re-encryption scheme.
|
||||
It is open-source, built with Python, and uses OpenSSL_ and Cryptography.io_.
|
||||
It is open-source, built with Python, and uses OpenSSL_ via Cryptography.io_, and libsodium_ via PyNaCl_.
|
||||
|
||||
Using Umbral, Alice (the data owner) can *delegate decryption rights* to Bob for
|
||||
any ciphertext intended to her, through a re-encryption process performed by a
|
||||
|
@ -50,6 +50,8 @@ a proxy re-encryption network to empower privacy in decentralized systems.
|
|||
.. _Cryptography.io: https://cryptography.io/en/latest/
|
||||
.. _OpenSSL: https://www.openssl.org/
|
||||
.. _nucypher: https://github.com/nucypher/nucypher
|
||||
.. _libsodium: https://github.com/jedisct1/libsodium
|
||||
.. _PyNaCl: https://pynacl.readthedocs.io/en/latest/
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
|
@ -57,6 +59,7 @@ a proxy re-encryption network to empower privacy in decentralized systems.
|
|||
|
||||
installation
|
||||
using_pyumbral
|
||||
api
|
||||
|
||||
|
||||
Academic Whitepaper
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
Installing pyUmbral
|
||||
====================
|
||||
v0.1.3-alpha.2
|
||||
|
||||
|
||||
Using pip
|
||||
-------------------------
|
||||
|
|
|
@ -12,30 +12,24 @@ Using pyUmbral
|
|||
sys.path.append(os.path.abspath(os.getcwd()))
|
||||
|
||||
|
||||
.. testcleanup:: capsule_story
|
||||
Elliptic Curves
|
||||
===============
|
||||
|
||||
from umbral import config
|
||||
config._CONFIG.___CONFIG__curve = None
|
||||
config._CONFIG.___CONFIG__params = None
|
||||
The matter of which curve to use is the subject of some debate. If you aren't sure, you might start here:
|
||||
https://safecurves.cr.yp.to/
|
||||
|
||||
A number of curves are available in the Cryptography.io_ library, on which pyUmbral depends.
|
||||
You can find them in the ``cryptography.hazmat.primitives.asymmetric.ec`` module.
|
||||
|
||||
Configuration
|
||||
==============
|
||||
.. _Cryptography.io: https://cryptography.io/en/latest/
|
||||
|
||||
.. important::
|
||||
|
||||
Setting the default curve
|
||||
--------------------------
|
||||
Be careful when choosing a curve - the security of your application depends on it.
|
||||
|
||||
The best way to start using pyUmbral is to decide on an elliptic curve to use and set it as your default.
|
||||
|
||||
|
||||
.. doctest:: capsule_story
|
||||
|
||||
>>> from umbral import config
|
||||
>>> from umbral.curve import SECP256K1
|
||||
>>> config.set_default_curve(SECP256K1)
|
||||
|
||||
For more information on curves, see :doc:`choosing_and_using_curves`.
|
||||
We provide curve ``SECP256K1`` as a default because it is the basis for a number of crypto-blockchain projects;
|
||||
we don't otherwise endorse its security.
|
||||
We additionally support curves ``SECP256R1`` (also known as "NIST P-256") and ``SECP384R1`` ("NIST P-384"), but they cannot currently be selected via the public API.
|
||||
|
||||
|
||||
Encryption
|
||||
|
@ -49,28 +43,27 @@ A delegating key pair and a signing key pair.
|
|||
|
||||
.. doctest:: capsule_story
|
||||
|
||||
>>> from umbral import keys, signing
|
||||
>>> from umbral import SecretKey, PublicKey
|
||||
|
||||
>>> alices_private_key = keys.UmbralPrivateKey.gen_key()
|
||||
>>> alices_public_key = alices_private_key.get_pubkey()
|
||||
>>> alices_secret_key = SecretKey.random()
|
||||
>>> alices_public_key = PublicKey.from_secret_key(alices_secret_key)
|
||||
|
||||
>>> alices_signing_key = keys.UmbralPrivateKey.gen_key()
|
||||
>>> alices_verifying_key = alices_signing_key.get_pubkey()
|
||||
>>> alices_signer = signing.Signer(private_key=alices_signing_key)
|
||||
>>> alices_signing_key = SecretKey.random()
|
||||
>>> alices_verifying_key = PublicKey.from_secret_key(alices_signing_key)
|
||||
|
||||
|
||||
Encrypt with a public key
|
||||
--------------------------
|
||||
Now let's encrypt data with Alice's public key.
|
||||
Invocation of ``pre.encrypt`` returns both the ``ciphertext`` and a ``capsule``.
|
||||
Invocation of :py:func:`encrypt` returns both a ``capsule`` and a ``ciphertext``.
|
||||
Note that anyone with Alice's public key can perform this operation.
|
||||
|
||||
|
||||
.. doctest:: capsule_story
|
||||
|
||||
>>> from umbral import pre
|
||||
>>> from umbral import encrypt
|
||||
>>> plaintext = b'Proxy Re-encryption is cool!'
|
||||
>>> ciphertext, capsule = pre.encrypt(alices_public_key, plaintext)
|
||||
>>> capsule, ciphertext = encrypt(alices_public_key, plaintext)
|
||||
|
||||
|
||||
Decrypt with a private key
|
||||
|
@ -80,9 +73,8 @@ Alice can open the capsule and decrypt the ciphertext with her private key.
|
|||
|
||||
.. doctest:: capsule_story
|
||||
|
||||
>>> cleartext = pre.decrypt(ciphertext=ciphertext,
|
||||
... capsule=capsule,
|
||||
... decrypting_key=alices_private_key)
|
||||
>>> from umbral import decrypt_original
|
||||
>>> cleartext = decrypt_original(alices_secret_key, capsule, ciphertext)
|
||||
|
||||
|
||||
Threshold Re-Encryption
|
||||
|
@ -93,29 +85,29 @@ Bob Exists
|
|||
|
||||
.. doctest:: capsule_story
|
||||
|
||||
>>> from umbral import keys
|
||||
>>> bobs_private_key = keys.UmbralPrivateKey.gen_key()
|
||||
>>> bobs_public_key = bobs_private_key.get_pubkey()
|
||||
>>> bobs_secret_key = SecretKey.random()
|
||||
>>> bobs_public_key = PublicKey.from_secret_key(bobs_secret_key)
|
||||
|
||||
|
||||
Alice grants access to Bob by generating kfrags
|
||||
Alice grants access to Bob by generating kfrags
|
||||
-----------------------------------------------
|
||||
When Alice wants to grant Bob access to open her encrypted messages,
|
||||
When Alice wants to grant Bob access to view her encrypted data,
|
||||
she creates *re-encryption key fragments*, or *"kfrags"*,
|
||||
which are next sent to N proxies or *Ursulas*.
|
||||
|
||||
Alice must specify ``N`` (the total number of kfrags),
|
||||
Alice must specify ``num_kfrags`` (the total number of kfrags),
|
||||
and a ``threshold`` (the minimum number of kfrags needed to activate a capsule).
|
||||
In the following example, Alice creates 20 kfrags,
|
||||
but Bob needs to get only 10 re-encryptions to activate the capsule.
|
||||
|
||||
.. doctest:: capsule_story
|
||||
|
||||
>>> kfrags = pre.generate_kfrags(delegating_privkey=alices_private_key,
|
||||
... signer=alices_signer,
|
||||
... receiving_pubkey=bobs_public_key,
|
||||
... threshold=10,
|
||||
... N=20)
|
||||
>>> from umbral import generate_kfrags
|
||||
>>> kfrags = generate_kfrags(delegating_sk=alices_secret_key,
|
||||
... receiving_pk=bobs_public_key,
|
||||
... signing_sk=alices_signing_key,
|
||||
... threshold=10,
|
||||
... num_kfrags=20)
|
||||
|
||||
|
||||
Bob receives a capsule
|
||||
|
@ -137,25 +129,24 @@ or re-encrypted for him by Ursula, he will not be able to open it.
|
|||
|
||||
.. doctest:: capsule_story
|
||||
|
||||
>>> fail = pre.decrypt(ciphertext=ciphertext,
|
||||
... capsule=capsule,
|
||||
... decrypting_key=bobs_private_key)
|
||||
>>> fail = decrypt_original(sk=bobs_secret_key,
|
||||
... capsule=capsule,
|
||||
... ciphertext=ciphertext)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
umbral.pre.UmbralDecryptionError
|
||||
umbral.GenericError
|
||||
|
||||
|
||||
Ursulas perform re-encryption
|
||||
------------------------------
|
||||
Bob asks several Ursulas to re-encrypt the capsule so he can open it.
|
||||
Bob asks several Ursulas to re-encrypt the capsule so he can open it.
|
||||
Each Ursula performs re-encryption on the capsule using the ``kfrag``
|
||||
provided by Alice, obtaining this way a "capsule fragment", or ``cfrag``.
|
||||
Let's mock a network or transport layer by sampling ``threshold`` random kfrags,
|
||||
one for each required Ursula. Note that each Ursula must prepare the received
|
||||
capsule before re-encryption by setting the proper correctness keys.
|
||||
one for each required Ursula.
|
||||
|
||||
Bob collects the resulting cfrags from several Ursulas.
|
||||
Bob must gather at least ``threshold`` cfrags in order to activate the capsule.
|
||||
Bob must gather at least ``threshold`` cfrags in order to open the capsule.
|
||||
|
||||
|
||||
.. doctest:: capsule_story
|
||||
|
@ -164,14 +155,10 @@ Bob must gather at least ``threshold`` cfrags in order to activate the capsule.
|
|||
>>> kfrags = random.sample(kfrags, # All kfrags from above
|
||||
... 10) # M - Threshold
|
||||
|
||||
>>> capsule.set_correctness_keys(delegating=alices_public_key,
|
||||
... receiving=bobs_public_key,
|
||||
... verifying=alices_verifying_key)
|
||||
(True, True, True)
|
||||
|
||||
>>> from umbral import reencrypt
|
||||
>>> cfrags = list() # Bob's cfrag collection
|
||||
>>> for kfrag in kfrags:
|
||||
... cfrag = pre.reencrypt(kfrag=kfrag, capsule=capsule)
|
||||
... cfrag = reencrypt(capsule=capsule, kfrag=kfrag)
|
||||
... cfrags.append(cfrag) # Bob collects a cfrag
|
||||
|
||||
.. doctest:: capsule_story
|
||||
|
@ -183,32 +170,34 @@ Bob must gather at least ``threshold`` cfrags in order to activate the capsule.
|
|||
Decryption
|
||||
==================================
|
||||
|
||||
Bob attaches cfrags to the capsule
|
||||
----------------------------------
|
||||
Bob attaches at least ``threshold`` cfrags to the capsule,
|
||||
which has to be prepared in advance with the necessary correctness keys.
|
||||
Only then it can become *activated*.
|
||||
Bob checks the capsule fragments
|
||||
--------------------------------
|
||||
Bob can verify that the capsule fragments are valid and really originate from Alice,
|
||||
using Alice's public keys.
|
||||
|
||||
.. doctest:: capsule_story
|
||||
|
||||
>>> capsule.set_correctness_keys(delegating=alices_public_key,
|
||||
... receiving=bobs_public_key,
|
||||
... verifying=alices_verifying_key)
|
||||
(False, False, False)
|
||||
|
||||
>>> for cfrag in cfrags:
|
||||
... capsule.attach_cfrag(cfrag)
|
||||
>>> all(cfrag.verify(capsule,
|
||||
... delegating_pk=alices_public_key,
|
||||
... receiving_pk=bobs_public_key,
|
||||
... signing_pk=alices_verifying_key)
|
||||
... for cfrag in cfrags)
|
||||
True
|
||||
|
||||
|
||||
Bob activates and opens the capsule
|
||||
------------------------------------
|
||||
Finally, Bob decrypts the re-encrypted ciphertext using the activated capsule.
|
||||
Bob opens the capsule
|
||||
---------------------
|
||||
Finally, Bob decrypts the re-encrypted ciphertext using his key.
|
||||
|
||||
.. doctest:: capsule_story
|
||||
|
||||
>>> cleartext = pre.decrypt(ciphertext=ciphertext,
|
||||
... capsule=capsule,
|
||||
... decrypting_key=bobs_private_key)
|
||||
>>> from umbral import decrypt_reencrypted
|
||||
>>> cleartext = decrypt_reencrypted(decrypting_sk=bobs_secret_key,
|
||||
... delegating_pk=alices_public_key,
|
||||
... capsule=capsule,
|
||||
... cfrags=cfrags,
|
||||
... ciphertext=ciphertext)
|
||||
|
||||
|
||||
.. doctest:: capsule_story
|
||||
:hide:
|
||||
|
|
6
setup.py
6
setup.py
|
@ -60,9 +60,6 @@ INSTALL_REQUIRES = [
|
|||
'setuptools',
|
||||
'cryptography>=2.3',
|
||||
'pynacl',
|
||||
'pysha3',
|
||||
'constant-sorrow>=0.1.0a7',
|
||||
'bytestring-splitter',
|
||||
]
|
||||
|
||||
DEV_INSTALL_REQUIRES = [
|
||||
|
@ -82,7 +79,7 @@ DEV_INSTALL_REQUIRES = [
|
|||
|
||||
EXTRAS_REQUIRE = {
|
||||
'testing': DEV_INSTALL_REQUIRES,
|
||||
'docs': ['sphinx', 'sphinx-autobuild'],
|
||||
'docs': ['sphinx', 'sphinx-autobuild', 'sphinx_rtd_theme'],
|
||||
'benchmarks': ['pytest-benchmark'],
|
||||
}
|
||||
|
||||
|
@ -105,7 +102,6 @@ setup(name=ABOUT['__title__'],
|
|||
"Natural Language :: English",
|
||||
"Programming Language :: Python :: Implementation",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Programming Language :: Python :: 3.5",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Topic :: Scientific/Engineering",
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
|
@ -1,139 +1,51 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from collections import namedtuple
|
||||
|
||||
from umbral import keys
|
||||
from umbral.curve import SECP256K1, SECP384R1, SECP256R1
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.config import set_default_curve
|
||||
from umbral.point import Point
|
||||
from umbral.signing import Signer
|
||||
from umbral import pre
|
||||
|
||||
set_default_curve(SECP256K1)
|
||||
|
||||
parameters = (
|
||||
# (N, M)
|
||||
(1, 1),
|
||||
(6, 1),
|
||||
(6, 4),
|
||||
(6, 6),
|
||||
(50, 30)
|
||||
)
|
||||
|
||||
wrong_parameters = (
|
||||
# (N, M)
|
||||
(-1, -1), (-1, 0), (-1, 5),
|
||||
(0, -1), (0, 0), (0, 5),
|
||||
(1, -1), (1, 0), (1, 5),
|
||||
(5, -1), (5, 0), (5, 10)
|
||||
)
|
||||
|
||||
other_supported_curves = (
|
||||
SECP384R1,
|
||||
SECP256R1
|
||||
)
|
||||
|
||||
kfrag_signing_modes = (
|
||||
(True, True), (True, False), (False, True), (False, False)
|
||||
)
|
||||
from umbral import SecretKey, PublicKey, generate_kfrags, encrypt
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def alices_keys():
|
||||
delegating_priv = keys.UmbralPrivateKey.gen_key()
|
||||
signing_priv = keys.UmbralPrivateKey.gen_key()
|
||||
return delegating_priv, signing_priv
|
||||
delegating_sk = SecretKey.random()
|
||||
signing_sk = SecretKey.random()
|
||||
return delegating_sk, signing_sk
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def bobs_keys():
|
||||
priv = keys.UmbralPrivateKey.gen_key()
|
||||
pub = priv.get_pubkey()
|
||||
return priv, pub
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def random_ec_point1():
|
||||
yield Point.gen_rand()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def random_ec_point2():
|
||||
yield Point.gen_rand()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def random_ec_curvebn1():
|
||||
yield CurveBN.gen_rand()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def random_ec_curvebn2():
|
||||
yield CurveBN.gen_rand()
|
||||
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def message():
|
||||
message = b"dnunez [9:30 AM]" \
|
||||
b"@Tux we had this super fruitful discussion last night with @jMyles @michwill @KPrasch" \
|
||||
b"to sum up: the symmetric ciphertext is now called the 'Chimney'." \
|
||||
b"the chimney of the capsule, of course" \
|
||||
b"tux [9:32 AM]" \
|
||||
b"wat"
|
||||
return message
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ciphertext_and_capsule(alices_keys, message):
|
||||
delegating_privkey, _signing_privkey = alices_keys
|
||||
# See nucypher's issue #183
|
||||
chimney, capsule = pre.encrypt(delegating_privkey.get_pubkey(), message)
|
||||
return chimney, capsule
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def capsule(ciphertext_and_capsule):
|
||||
ciphertext, capsule = ciphertext_and_capsule
|
||||
return capsule
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def prepared_capsule(alices_keys, bobs_keys, capsule):
|
||||
delegating_privkey, signing_privkey = alices_keys
|
||||
_receiving_privkey, receiving_pubkey = bobs_keys
|
||||
capsule.set_correctness_keys(delegating=delegating_privkey.get_pubkey(),
|
||||
receiving=receiving_pubkey,
|
||||
verifying=signing_privkey.get_pubkey())
|
||||
return capsule
|
||||
sk = SecretKey.random()
|
||||
pk = PublicKey.from_secret_key(sk)
|
||||
return sk, pk
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def kfrags(alices_keys, bobs_keys):
|
||||
delegating_privkey, signing_privkey = alices_keys
|
||||
signer_alice = Signer(signing_privkey)
|
||||
delegating_sk, signing_sk = alices_keys
|
||||
receiving_sk, receiving_pk = bobs_keys
|
||||
yield generate_kfrags(delegating_sk=delegating_sk,
|
||||
signing_sk=signing_sk,
|
||||
receiving_pk=receiving_pk,
|
||||
threshold=6, num_kfrags=10)
|
||||
|
||||
receiving_privkey, receiving_pubkey = bobs_keys
|
||||
|
||||
yield pre.generate_kfrags(delegating_privkey=delegating_privkey,
|
||||
signer=signer_alice,
|
||||
receiving_pubkey=receiving_pubkey,
|
||||
threshold=6, N=10)
|
||||
@pytest.fixture(scope='session')
|
||||
def message():
|
||||
message = (b"dnunez [9:30 AM]"
|
||||
b"@Tux we had this super fruitful discussion last night with @jMyles @michwill @KPrasch"
|
||||
b"to sum up: the symmetric ciphertext is now called the 'Chimney'."
|
||||
b"the chimney of the capsule, of course"
|
||||
b"tux [9:32 AM]"
|
||||
b"wat")
|
||||
return message
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def capsule_and_ciphertext(alices_keys, message):
|
||||
delegating_sk, _signing_sk = alices_keys
|
||||
capsule, ciphertext = encrypt(PublicKey.from_secret_key(delegating_sk), message)
|
||||
return capsule, ciphertext
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def capsule(capsule_and_ciphertext):
|
||||
capsule, ciphertext = capsule_and_ciphertext
|
||||
return capsule
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
|
@ -1,156 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from umbral import pre
|
||||
from umbral.point import Point
|
||||
from umbral.signing import Signer
|
||||
from umbral.cfrags import CapsuleFrag
|
||||
|
||||
|
||||
def test_cheating_ursula_replays_old_reencryption(alices_keys, bobs_keys,
|
||||
kfrags, prepared_capsule):
|
||||
delegating_privkey, signing_privkey = alices_keys
|
||||
delegating_pubkey = delegating_privkey.get_pubkey()
|
||||
|
||||
receiving_privkey, receiving_pubkey = bobs_keys
|
||||
|
||||
capsule_alice1 = prepared_capsule
|
||||
|
||||
_unused_key2, capsule_alice2 = pre._encapsulate(delegating_pubkey)
|
||||
|
||||
capsule_alice2.set_correctness_keys(delegating=delegating_pubkey,
|
||||
receiving=receiving_pubkey,
|
||||
verifying=signing_privkey.get_pubkey())
|
||||
|
||||
cfrags = []
|
||||
for i, kfrag in enumerate(kfrags):
|
||||
|
||||
# Example of potential metadata to describe the re-encryption request
|
||||
metadata_i = "This is an example of metadata for re-encryption request #{}"
|
||||
metadata_i = metadata_i.format(i).encode()
|
||||
|
||||
if i == 0:
|
||||
# Let's put the re-encryption of a different Alice ciphertext
|
||||
cfrag = pre.reencrypt(kfrag, capsule_alice2, metadata=metadata_i)
|
||||
else:
|
||||
cfrag = pre.reencrypt(kfrag, capsule_alice1, metadata=metadata_i)
|
||||
|
||||
cfrags.append(cfrag)
|
||||
|
||||
# CFrag 0 is not valid ...
|
||||
assert not cfrags[0].verify_correctness(capsule_alice1)
|
||||
|
||||
# ... and trying to attach it raises an error.
|
||||
with pytest.raises(pre.UmbralCorrectnessError) as exception_info:
|
||||
capsule_alice1.attach_cfrag(cfrags[0])
|
||||
|
||||
correctness_error = exception_info.value
|
||||
assert cfrags[0] in correctness_error.offending_cfrags
|
||||
assert len(correctness_error.offending_cfrags) == 1
|
||||
|
||||
# The rest of CFrags should be correct:
|
||||
correct_cases = 0
|
||||
for cfrag_i in cfrags[1:]:
|
||||
assert cfrag_i.verify_correctness(capsule_alice1)
|
||||
capsule_alice1.attach_cfrag(cfrag_i)
|
||||
correct_cases += 1
|
||||
|
||||
assert correct_cases == len(cfrags[1:])
|
||||
|
||||
|
||||
def test_cheating_ursula_sends_garbage(kfrags, prepared_capsule):
|
||||
capsule_alice = prepared_capsule
|
||||
|
||||
cfrags = []
|
||||
for i, kfrag in enumerate(kfrags):
|
||||
# Example of potential metadata to describe the re-encryption request
|
||||
metadata_i = "This is an example of metadata for re-encryption request #{}"
|
||||
metadata_i = metadata_i.format(i).encode()
|
||||
|
||||
cfrag = pre.reencrypt(kfrag, capsule_alice, metadata=metadata_i)
|
||||
cfrags.append(cfrag)
|
||||
|
||||
# Let's put random garbage in one of the cfrags
|
||||
cfrags[0].point_e1 = Point.gen_rand()
|
||||
cfrags[0].point_v1 = Point.gen_rand()
|
||||
|
||||
# Of course, this CFrag is not valid ...
|
||||
assert not cfrags[0].verify_correctness(capsule_alice)
|
||||
|
||||
# ... and trying to attach it raises an error.
|
||||
with pytest.raises(pre.UmbralCorrectnessError) as exception_info:
|
||||
capsule_alice.attach_cfrag(cfrags[0])
|
||||
|
||||
correctness_error = exception_info.value
|
||||
assert cfrags[0] in correctness_error.offending_cfrags
|
||||
assert len(correctness_error.offending_cfrags) == 1
|
||||
|
||||
# The response of cheating Ursula is in cfrags[0],
|
||||
# so the rest of CFrags should be correct:
|
||||
for cfrag_i in cfrags[1:]:
|
||||
assert cfrag_i.verify_correctness(capsule_alice)
|
||||
capsule_alice.attach_cfrag(cfrag_i)
|
||||
|
||||
|
||||
def test_cfrag_with_missing_proof_cannot_be_attached(kfrags, prepared_capsule):
|
||||
capsule = prepared_capsule
|
||||
|
||||
cfrags = []
|
||||
for kfrag in kfrags:
|
||||
cfrag = pre.reencrypt(kfrag, capsule)
|
||||
cfrags.append(cfrag)
|
||||
|
||||
# If the proof is lost (e.g., it is chopped off a serialized CFrag or similar),
|
||||
# then the CFrag cannot be attached.
|
||||
cfrags[0].proof = None
|
||||
with pytest.raises(CapsuleFrag.NoProofProvided):
|
||||
capsule.attach_cfrag(cfrags[0])
|
||||
|
||||
# The remaining CFrags are fine, so they can be attached correctly
|
||||
for cfrag in cfrags[1:]:
|
||||
capsule.attach_cfrag(cfrag)
|
||||
|
||||
|
||||
def test_kfrags_signed_without_correctness_keys(alices_keys, bobs_keys, capsule):
|
||||
delegating_privkey, signing_privkey = alices_keys
|
||||
delegating_pubkey = delegating_privkey.get_pubkey()
|
||||
verifying_key = signing_privkey.get_pubkey()
|
||||
|
||||
receiving_privkey, receiving_pubkey = bobs_keys
|
||||
|
||||
kfrags = pre.generate_kfrags(delegating_privkey=delegating_privkey,
|
||||
signer=Signer(signing_privkey),
|
||||
receiving_pubkey=receiving_pubkey,
|
||||
threshold=6,
|
||||
N=10,
|
||||
sign_delegating_key=False,
|
||||
sign_receiving_key=False)
|
||||
|
||||
for kfrag in kfrags:
|
||||
# You can verify the KFrag specifying only the verifying key
|
||||
assert kfrag.verify(signing_pubkey=verifying_key)
|
||||
|
||||
# ... or if it is set in the capsule, using the capsule
|
||||
capsule.set_correctness_keys(verifying=verifying_key)
|
||||
assert kfrag.verify_for_capsule(capsule)
|
||||
|
||||
# It should even work when other keys are set in the capsule
|
||||
assert kfrag.verify(signing_pubkey=verifying_key,
|
||||
delegating_pubkey=delegating_pubkey,
|
||||
receiving_pubkey=receiving_pubkey)
|
|
@ -1,81 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from umbral import pre
|
||||
from umbral.keys import UmbralPrivateKey
|
||||
from umbral.kfrags import KFrag
|
||||
|
||||
|
||||
def test_set_correctness_keys(alices_keys, bobs_keys, capsule, kfrags):
|
||||
"""
|
||||
If the three keys do appear together, along with the capsule,
|
||||
we can attach them all at once.
|
||||
"""
|
||||
|
||||
delegating_privkey, signing_privkey = alices_keys
|
||||
_receiving_privkey, receiving_pubkey = bobs_keys
|
||||
|
||||
capsule.set_correctness_keys(delegating_privkey.get_pubkey(),
|
||||
receiving_pubkey,
|
||||
signing_privkey.get_pubkey()
|
||||
)
|
||||
|
||||
for kfrag in kfrags:
|
||||
cfrag = pre.reencrypt(kfrag, capsule)
|
||||
capsule.attach_cfrag(cfrag)
|
||||
|
||||
|
||||
def test_setting_one_correctness_keys(alices_keys, capsule):
|
||||
# The capsule doesn't have any correctness keys set initially
|
||||
assert capsule.get_correctness_keys()['delegating'] is None
|
||||
assert capsule.get_correctness_keys()['receiving'] is None
|
||||
assert capsule.get_correctness_keys()['verifying'] is None
|
||||
|
||||
# Let's set only one of them, e.g., the delegating key
|
||||
delegating_privkey, _signing_privkey = alices_keys
|
||||
delegating_pubkey = delegating_privkey.get_pubkey()
|
||||
|
||||
details = capsule.set_correctness_keys(delegating=delegating_pubkey)
|
||||
|
||||
# Since we are only setting the first key ("delegating"),
|
||||
# the other keys are not set
|
||||
assert details == (True, False, False)
|
||||
|
||||
assert capsule.get_correctness_keys()['delegating'] == delegating_pubkey
|
||||
assert capsule.get_correctness_keys()['receiving'] is None
|
||||
assert capsule.get_correctness_keys()['verifying'] is None
|
||||
|
||||
|
||||
def test_set_invalid_correctness_keys(alices_keys, capsule, kfrags):
|
||||
"""
|
||||
If the three keys do appear together, along with the capsule,
|
||||
we can attach them all at once.
|
||||
"""
|
||||
|
||||
delegating_privkey, signing_privkey = alices_keys
|
||||
unrelated_receiving_pubkey = UmbralPrivateKey.gen_key().get_pubkey()
|
||||
|
||||
capsule.set_correctness_keys(delegating_privkey.get_pubkey(),
|
||||
unrelated_receiving_pubkey,
|
||||
signing_privkey.get_pubkey()
|
||||
)
|
||||
|
||||
for kfrag in kfrags:
|
||||
with pytest.raises(KFrag.NotValid):
|
||||
cfrag = pre.reencrypt(kfrag, capsule)
|
|
@ -1,56 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from umbral import pre
|
||||
from umbral.signing import Signer
|
||||
from ..conftest import wrong_parameters
|
||||
|
||||
|
||||
def test_public_key_encryption(alices_keys):
|
||||
delegating_privkey, _ = alices_keys
|
||||
plain_data = b'peace at dawn'
|
||||
ciphertext, capsule = pre.encrypt(delegating_privkey.get_pubkey(), plain_data)
|
||||
cleartext = pre.decrypt(ciphertext, capsule, delegating_privkey)
|
||||
assert cleartext == plain_data
|
||||
|
||||
|
||||
@pytest.mark.parametrize("N, M", wrong_parameters)
|
||||
def test_wrong_N_M_in_split_rekey(N, M, alices_keys, bobs_keys):
|
||||
delegating_privkey, signing_privkey = alices_keys
|
||||
signer = Signer(signing_privkey)
|
||||
_receiving_privkey, receiving_pubkey = bobs_keys
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
_kfrags = pre.generate_kfrags(delegating_privkey=delegating_privkey,
|
||||
signer=signer,
|
||||
receiving_pubkey=receiving_pubkey,
|
||||
threshold=M,
|
||||
N=N)
|
||||
|
||||
|
||||
def test_decryption_error(alices_keys, bobs_keys, ciphertext_and_capsule, message):
|
||||
delegating_privkey, _signing_privkey = alices_keys
|
||||
receiving_privkey, _receiving_pubkey = bobs_keys
|
||||
ciphertext, capsule = ciphertext_and_capsule
|
||||
|
||||
cleartext = pre.decrypt(ciphertext, capsule, delegating_privkey)
|
||||
assert message == cleartext
|
||||
|
||||
with pytest.raises(pre.UmbralDecryptionError) as e:
|
||||
_cleartext = pre.decrypt(ciphertext, capsule, receiving_privkey)
|
|
@ -1,190 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.point import Point
|
||||
from umbral.keys import UmbralPublicKey
|
||||
from umbral.config import default_params
|
||||
from umbral.kfrags import KFrag
|
||||
from umbral.cfrags import CapsuleFrag
|
||||
from umbral.random_oracles import hash_to_curvebn, unsafe_hash_to_point, kdf
|
||||
from umbral import pre
|
||||
|
||||
def test_curvebn_operations():
|
||||
|
||||
vector_file = os.path.join('vectors', 'vectors_curvebn_operations.json')
|
||||
try:
|
||||
with open(vector_file) as f:
|
||||
vector_suite = json.load(f)
|
||||
except OSError:
|
||||
raise
|
||||
|
||||
bn1 = CurveBN.from_bytes(bytes.fromhex(vector_suite['first operand']))
|
||||
bn2 = CurveBN.from_bytes(bytes.fromhex(vector_suite['second operand']))
|
||||
|
||||
expected = dict()
|
||||
for op_result in vector_suite['vectors']:
|
||||
result = bytes.fromhex(op_result['result'])
|
||||
expected[op_result['operation']] = CurveBN.from_bytes(result)
|
||||
|
||||
test = [('Addition', bn1 + bn2),
|
||||
('Subtraction', bn1 - bn2),
|
||||
('Multiplication', bn1 * bn2),
|
||||
('Division', bn1 / bn2),
|
||||
('Pow', bn1 ** bn2),
|
||||
('Mod', bn1 % bn2),
|
||||
('Inverse', ~bn1),
|
||||
('Neg', -bn1),
|
||||
]
|
||||
|
||||
for (operation, result) in test:
|
||||
assert result == expected[operation], 'Error in {}'.format(operation)
|
||||
|
||||
def test_curvebn_hash():
|
||||
|
||||
vector_file = os.path.join('vectors', 'vectors_curvebn_hash.json')
|
||||
try:
|
||||
with open(vector_file) as f:
|
||||
vector_suite = json.load(f)
|
||||
except OSError:
|
||||
raise
|
||||
|
||||
params = default_params()
|
||||
|
||||
for vector in vector_suite['vectors']:
|
||||
hash_input = [bytes.fromhex(item['bytes']) for item in vector['input']]
|
||||
expected = CurveBN.from_bytes(bytes.fromhex(vector['output']))
|
||||
assert hash_to_curvebn(*hash_input, params=params) == expected
|
||||
|
||||
|
||||
def test_point_operations():
|
||||
|
||||
vector_file = os.path.join('vectors', 'vectors_point_operations.json')
|
||||
try:
|
||||
with open(vector_file) as f:
|
||||
vector_suite = json.load(f)
|
||||
except OSError:
|
||||
raise
|
||||
|
||||
point1 = Point.from_bytes(bytes.fromhex(vector_suite['first Point operand']))
|
||||
point2 = Point.from_bytes(bytes.fromhex(vector_suite['second Point operand']))
|
||||
bn1 = CurveBN.from_bytes(bytes.fromhex(vector_suite['CurveBN operand']))
|
||||
|
||||
expected = dict()
|
||||
for op_result in vector_suite['vectors']:
|
||||
expected[op_result['operation']] = bytes.fromhex(op_result['result'])
|
||||
|
||||
test = [('Addition', point1 + point2),
|
||||
('Subtraction', point1 - point2),
|
||||
('Multiplication', bn1 * point1),
|
||||
('Inversion', -point1),
|
||||
]
|
||||
|
||||
for (operation, result) in test:
|
||||
assert result == Point.from_bytes(expected[operation]), 'Error in {}'.format(operation)
|
||||
|
||||
test = [('To_affine.X', point1.to_affine()[0]),
|
||||
('To_affine.Y', point1.to_affine()[1]),
|
||||
]
|
||||
|
||||
for (operation, result) in test:
|
||||
assert result == int.from_bytes(expected[operation], 'big'), 'Error in {}'.format(operation)
|
||||
|
||||
assert kdf(point1, pre.DEM_KEYSIZE) == expected['kdf']
|
||||
|
||||
|
||||
def test_unsafe_hash_to_point():
|
||||
|
||||
vector_file = os.path.join('vectors', 'vectors_unsafe_hash_to_point.json')
|
||||
try:
|
||||
with open(vector_file) as f:
|
||||
vector_suite = json.load(f)
|
||||
except OSError:
|
||||
raise
|
||||
|
||||
params = default_params()
|
||||
|
||||
for item in vector_suite['vectors']:
|
||||
data = bytes.fromhex(item['data'])
|
||||
label = bytes.fromhex(item['label'])
|
||||
expected = Point.from_bytes(bytes.fromhex(item['point']))
|
||||
assert expected == unsafe_hash_to_point(label=label, data=data, params=params)
|
||||
|
||||
|
||||
def test_kfrags():
|
||||
|
||||
vector_file = os.path.join('vectors', 'vectors_kfrags.json')
|
||||
try:
|
||||
with open(vector_file) as f:
|
||||
vector_suite = json.load(f)
|
||||
except OSError:
|
||||
raise
|
||||
|
||||
verifying_key = UmbralPublicKey.from_bytes(bytes.fromhex(vector_suite['verifying_key']))
|
||||
delegating_key = UmbralPublicKey.from_bytes(bytes.fromhex(vector_suite['delegating_key']))
|
||||
receiving_key = UmbralPublicKey.from_bytes(bytes.fromhex(vector_suite['receiving_key']))
|
||||
|
||||
for json_kfrag in vector_suite['vectors']:
|
||||
kfrag = KFrag.from_bytes(bytes.fromhex(json_kfrag['kfrag']))
|
||||
assert kfrag.verify(signing_pubkey=verifying_key,
|
||||
delegating_pubkey=delegating_key,
|
||||
receiving_pubkey=receiving_key), \
|
||||
'Invalid KFrag {}'.format(kfrag.to_bytes().hex())
|
||||
|
||||
|
||||
def test_cfrags():
|
||||
|
||||
vector_file = os.path.join('vectors', 'vectors_cfrags.json')
|
||||
try:
|
||||
with open(vector_file) as f:
|
||||
vector_suite = json.load(f)
|
||||
except OSError:
|
||||
raise
|
||||
|
||||
params = default_params()
|
||||
|
||||
capsule = pre.Capsule.from_bytes(bytes.fromhex(vector_suite['capsule']),
|
||||
params=params)
|
||||
|
||||
verifying_key = UmbralPublicKey.from_bytes(bytes.fromhex(vector_suite['verifying_key']))
|
||||
delegating_key = UmbralPublicKey.from_bytes(bytes.fromhex(vector_suite['delegating_key']))
|
||||
receiving_key = UmbralPublicKey.from_bytes(bytes.fromhex(vector_suite['receiving_key']))
|
||||
|
||||
kfrags_n_cfrags = [(KFrag.from_bytes(bytes.fromhex(json_kfrag['kfrag'])),
|
||||
CapsuleFrag.from_bytes(bytes.fromhex(json_kfrag['cfrag'])))
|
||||
for json_kfrag in vector_suite['vectors']]
|
||||
|
||||
capsule.set_correctness_keys(delegating=delegating_key,
|
||||
receiving=receiving_key,
|
||||
verifying=verifying_key)
|
||||
|
||||
for kfrag, cfrag in kfrags_n_cfrags:
|
||||
assert kfrag.verify(signing_pubkey=verifying_key,
|
||||
delegating_pubkey=delegating_key,
|
||||
receiving_pubkey=receiving_key), \
|
||||
'Invalid KFrag {}'.format(kfrag.to_bytes().hex())
|
||||
|
||||
new_cfrag = pre.reencrypt(kfrag, capsule, provide_proof=False)
|
||||
assert new_cfrag.point_e1 == cfrag.point_e1
|
||||
assert new_cfrag.point_v1 == cfrag.point_v1
|
||||
assert new_cfrag.kfrag_id == cfrag.kfrag_id
|
||||
assert new_cfrag.point_precursor == cfrag.point_precursor
|
||||
assert new_cfrag.proof is None
|
||||
assert cfrag.to_bytes() == new_cfrag.to_bytes()
|
|
@ -1,42 +1,26 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
sys.path.append(os.path.abspath(os.getcwd()))
|
||||
|
||||
|
||||
import pytest
|
||||
from umbral import keys, pre
|
||||
from umbral.config import default_curve
|
||||
from umbral.params import UmbralParameters
|
||||
from umbral.signing import Signer
|
||||
|
||||
import umbral as umbral_py
|
||||
|
||||
try:
|
||||
import umbral_pre as umbral_rs
|
||||
except ImportError:
|
||||
umbral_rs = None
|
||||
|
||||
|
||||
#
|
||||
# Setup
|
||||
#
|
||||
def pytest_generate_tests(metafunc):
|
||||
if 'umbral' in metafunc.fixturenames:
|
||||
implementations = [umbral_py]
|
||||
ids = ['python']
|
||||
if umbral_rs is not None:
|
||||
implementations.append(umbral_rs)
|
||||
ids.append('rust')
|
||||
metafunc.parametrize('umbral', implementations, ids=ids)
|
||||
|
||||
|
||||
CURVE = default_curve()
|
||||
PARAMS = UmbralParameters(curve=CURVE)
|
||||
|
||||
# Faster
|
||||
# (M, N) # |
|
||||
FRAG_VALUES = ((1, 1), # |
|
||||
|
@ -51,25 +35,20 @@ FRAG_VALUES = ((1, 1), # |
|
|||
# Slower
|
||||
|
||||
|
||||
def __standard_encryption_api() -> tuple:
|
||||
def __standard_encryption_api(umbral) -> tuple:
|
||||
|
||||
delegating_privkey = keys.UmbralPrivateKey.gen_key(params=PARAMS)
|
||||
delegating_pubkey = delegating_privkey.get_pubkey()
|
||||
delegating_sk = umbral.SecretKey.random()
|
||||
delegating_pk = umbral.PublicKey.from_secret_key(delegating_sk)
|
||||
|
||||
signing_privkey = keys.UmbralPrivateKey.gen_key(params=PARAMS)
|
||||
signer = Signer(signing_privkey)
|
||||
signing_sk = umbral.SecretKey.random()
|
||||
|
||||
receiving_privkey = keys.UmbralPrivateKey.gen_key(params=PARAMS)
|
||||
receiving_pubkey = receiving_privkey.get_pubkey()
|
||||
receiving_sk = umbral.SecretKey.random()
|
||||
receiving_pk = umbral.PublicKey.from_secret_key(receiving_sk)
|
||||
|
||||
plain_data = os.urandom(32)
|
||||
ciphertext, capsule = pre.encrypt(delegating_pubkey, plain_data)
|
||||
capsule, ciphertext = umbral.encrypt(delegating_pk, plain_data)
|
||||
|
||||
capsule.set_correctness_keys(delegating=delegating_pubkey,
|
||||
receiving=receiving_pubkey,
|
||||
verifying=signing_privkey.get_pubkey())
|
||||
|
||||
return delegating_privkey, signer, receiving_pubkey, ciphertext, capsule
|
||||
return delegating_sk, receiving_pk, signing_sk, ciphertext, capsule
|
||||
|
||||
|
||||
#
|
||||
|
@ -82,16 +61,13 @@ def __standard_encryption_api() -> tuple:
|
|||
warmup=True,
|
||||
warmup_iterations=10)
|
||||
@pytest.mark.parametrize("m, n", FRAG_VALUES)
|
||||
def test_generate_kfrags_performance(benchmark, m: int, n: int) -> None:
|
||||
def test_generate_kfrags_performance(benchmark, m: int, n: int, umbral) -> None:
|
||||
|
||||
def __setup():
|
||||
delegating_privkey, signer, receiving_pubkey, ciphertext, capsule = __standard_encryption_api()
|
||||
args = (delegating_privkey, receiving_pubkey)
|
||||
kwargs = {"threshold": m, "N": n, "signer": signer}
|
||||
return args, kwargs
|
||||
delegating_sk, receiving_pk, signing_sk, ciphertext, capsule = __standard_encryption_api(umbral)
|
||||
return (delegating_sk, receiving_pk, signing_sk, m, n, True, True), {}
|
||||
|
||||
print("\nBenchmarking {function} with M:{M} of N:{N}...".format(function="pre.generate_kfrags", M=m, N=n))
|
||||
benchmark.pedantic(pre.generate_kfrags, setup=__setup, rounds=1000)
|
||||
benchmark.pedantic(umbral.generate_kfrags, setup=__setup, rounds=1000)
|
||||
assert True # ensure function finishes and succeeds.
|
||||
|
||||
|
||||
|
@ -105,17 +81,15 @@ def test_generate_kfrags_performance(benchmark, m: int, n: int) -> None:
|
|||
warmup=True,
|
||||
warmup_iterations=10)
|
||||
@pytest.mark.parametrize("m, n", ((6, 10), ))
|
||||
def test_random_frag_reencryption_performance(benchmark, m: int, n: int) -> None:
|
||||
def test_random_frag_reencryption_performance(benchmark, m: int, n: int, umbral) -> None:
|
||||
|
||||
def __setup():
|
||||
delegating_privkey, signer, receiving_pubkey, ciphertext, capsule = __standard_encryption_api()
|
||||
kfrags = pre.generate_kfrags(delegating_privkey, receiving_pubkey, m, n, signer)
|
||||
delegating_sk, receiving_pk, signing_sk, ciphertext, capsule = __standard_encryption_api(umbral)
|
||||
kfrags = umbral.generate_kfrags(delegating_sk, receiving_pk, signing_sk, m, n, True, True)
|
||||
one_kfrag, *remaining_kfrags = kfrags
|
||||
args, kwargs = tuple(), {"kfrag": one_kfrag, "capsule": capsule},
|
||||
return args, kwargs
|
||||
return (capsule, one_kfrag), {}
|
||||
|
||||
print("\nBenchmarking {} with randomly created fragments...".format("pre.reencrypt"))
|
||||
benchmark.pedantic(pre.reencrypt, setup=__setup, rounds=1000)
|
||||
benchmark.pedantic(umbral.reencrypt, setup=__setup, rounds=1000)
|
||||
assert True # ensure function finishes and succeeds.
|
||||
|
||||
|
||||
|
@ -128,13 +102,12 @@ def test_random_frag_reencryption_performance(benchmark, m: int, n: int) -> None
|
|||
warmup=True,
|
||||
warmup_iterations=10)
|
||||
@pytest.mark.parametrize("m, n", ((6, 10), ))
|
||||
def test_single_frag_reencryption_performance(benchmark, m: int, n: int) -> None:
|
||||
def test_single_frag_reencryption_performance(benchmark, m: int, n: int, umbral) -> None:
|
||||
|
||||
delegating_privkey, signer, receiving_pubkey, ciphertext, capsule = __standard_encryption_api()
|
||||
kfrags = pre.generate_kfrags(delegating_privkey, receiving_pubkey, m, n, signer)
|
||||
delegating_sk, receiving_pk, signing_sk, ciphertext, capsule = __standard_encryption_api(umbral)
|
||||
kfrags = umbral.generate_kfrags(delegating_sk, receiving_pk, signing_sk, m, n, True, True)
|
||||
one_kfrag, *remaining_kfrags = kfrags
|
||||
args, kwargs = tuple(), {"kfrag": one_kfrag, "capsule": capsule},
|
||||
args, kwargs = (capsule, one_kfrag), {}
|
||||
|
||||
print("\nBenchmarking {} with the same fragment({M} of {N}) repeatedly...".format("pre.reencrypt", M=m, N=n))
|
||||
benchmark.pedantic(pre.reencrypt, args=args, kwargs=kwargs, iterations=20, rounds=100)
|
||||
benchmark.pedantic(umbral.reencrypt, args=args, kwargs=kwargs, iterations=20, rounds=100)
|
||||
assert True # ensure function finishes and succeeds.
|
||||
|
|
|
@ -1,57 +1,30 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import Tuple, List
|
||||
|
||||
sys.path.append(os.path.abspath(os.getcwd()))
|
||||
|
||||
from umbral.kfrags import KFrag
|
||||
from umbral.pre import Capsule
|
||||
from umbral import keys, pre
|
||||
from umbral.config import default_curve
|
||||
from umbral.params import UmbralParameters
|
||||
from umbral.signing import Signer
|
||||
from typing import Tuple, List
|
||||
|
||||
import umbral
|
||||
|
||||
|
||||
CURVE = default_curve()
|
||||
PARAMS = UmbralParameters(curve=CURVE)
|
||||
REENCRYPTIONS = 1000
|
||||
|
||||
|
||||
def __produce_kfrags_and_capsule(m: int, n: int) -> Tuple[List[KFrag], Capsule]:
|
||||
def __produce_kfrags_and_capsule(m: int, n: int) -> Tuple[List[umbral.KeyFrag], umbral.Capsule]:
|
||||
|
||||
delegating_privkey = keys.UmbralPrivateKey.gen_key(params=PARAMS)
|
||||
delegating_pubkey = delegating_privkey.get_pubkey()
|
||||
delegating_sk = umbral.SecretKey.random()
|
||||
delegating_pk = umbral.PublicKey.from_secret_key(delegating_sk)
|
||||
|
||||
signing_privkey = keys.UmbralPrivateKey.gen_key(params=PARAMS)
|
||||
signer = Signer(signing_privkey)
|
||||
signing_sk = umbral.SecretKey.random()
|
||||
|
||||
receiving_privkey = keys.UmbralPrivateKey.gen_key(params=PARAMS)
|
||||
receiving_pubkey = receiving_privkey.get_pubkey()
|
||||
receiving_sk = umbral.SecretKey.random()
|
||||
receiving_pk = umbral.PublicKey.from_secret_key(receiving_sk)
|
||||
|
||||
plain_data = os.urandom(32)
|
||||
ciphertext, capsule = pre.encrypt(delegating_pubkey, plain_data)
|
||||
capsule, ciphertext = umbral.encrypt(delegating_pk, plain_data)
|
||||
|
||||
kfrags = pre.generate_kfrags(delegating_privkey, receiving_pubkey, m, n, signer)
|
||||
|
||||
capsule.set_correctness_keys(delegating=delegating_pubkey,
|
||||
receiving=receiving_pubkey,
|
||||
verifying=signing_privkey.get_pubkey())
|
||||
kfrags = umbral.generate_kfrags(delegating_sk, receiving_pk, signing_sk, m, n)
|
||||
|
||||
return kfrags, capsule
|
||||
|
||||
|
@ -66,7 +39,7 @@ def firehose(m: int=6, n: int=10) -> None:
|
|||
successful_reencryptions = 0
|
||||
for iteration in range(int(REENCRYPTIONS)):
|
||||
|
||||
_cfrag = pre.reencrypt(one_kfrag, capsule) # <<< REENCRYPTION HAPPENS HERE
|
||||
_cfrag = umbral.reencrypt(capsule, one_kfrag) # <<< REENCRYPTION HAPPENS HERE
|
||||
|
||||
successful_reencryptions += 1
|
||||
if iteration % 20 == 0:
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
|
@ -1,167 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from umbral import pre
|
||||
from umbral.cfrags import CapsuleFrag
|
||||
from umbral.kfrags import KFrag
|
||||
from umbral.config import default_curve
|
||||
from umbral.params import UmbralParameters
|
||||
from umbral.signing import Signer
|
||||
from umbral.keys import UmbralPrivateKey, UmbralPublicKey
|
||||
from ..conftest import parameters, other_supported_curves, kfrag_signing_modes
|
||||
|
||||
|
||||
@pytest.mark.parametrize("N, M", parameters)
|
||||
@pytest.mark.parametrize("signing_mode", kfrag_signing_modes)
|
||||
def test_lifecycle_with_serialization(N, M, signing_mode, curve=default_curve()):
|
||||
"""
|
||||
This test is a variant of test_simple_api, but with intermediate
|
||||
serialization/deserialization steps, modeling how pyUmbral artifacts
|
||||
(such as keys, ciphertexts, etc) will actually be used.
|
||||
These intermediate steps are in between the different 'usage domains'
|
||||
in NuCypher, namely, key generation, delegation, encryption, decryption by
|
||||
Alice, re-encryption by Ursula, and decryption by Bob.
|
||||
|
||||
Manually injects UmbralParameters for multi-curve testing.
|
||||
"""
|
||||
|
||||
# Convenience method to avoid replicating key generation code
|
||||
def new_keypair_bytes():
|
||||
privkey = UmbralPrivateKey.gen_key(params=params)
|
||||
return privkey.to_bytes(), privkey.get_pubkey().to_bytes()
|
||||
|
||||
## SETUP
|
||||
params = UmbralParameters(curve=curve)
|
||||
|
||||
delegating_privkey_bytes, delegating_pubkey_bytes = new_keypair_bytes()
|
||||
signing_privkey_bytes, signing_pubkey_bytes = new_keypair_bytes()
|
||||
receiving_privkey_bytes, receiving_pubkey_bytes = new_keypair_bytes()
|
||||
|
||||
## DELEGATION DOMAIN:
|
||||
## Alice delegates decryption rights to some Bob by generating a set of
|
||||
## KFrags, using her delegating private key and Bob's receiving public key
|
||||
|
||||
delegating_privkey = UmbralPrivateKey.from_bytes(delegating_privkey_bytes, params=params)
|
||||
signing_privkey = UmbralPrivateKey.from_bytes(signing_privkey_bytes, params=params)
|
||||
receiving_pubkey = UmbralPublicKey.from_bytes(receiving_pubkey_bytes, params=params)
|
||||
|
||||
signer = Signer(signing_privkey)
|
||||
|
||||
sign_delegating_key, sign_receiving_key = signing_mode
|
||||
|
||||
kfrags = pre.generate_kfrags(delegating_privkey=delegating_privkey,
|
||||
receiving_pubkey=receiving_pubkey,
|
||||
threshold=M,
|
||||
N=N,
|
||||
signer=signer,
|
||||
sign_delegating_key=sign_delegating_key,
|
||||
sign_receiving_key=sign_receiving_key)
|
||||
|
||||
kfrags_bytes = tuple(map(bytes, kfrags))
|
||||
|
||||
del kfrags
|
||||
del signer
|
||||
del delegating_privkey
|
||||
del signing_privkey
|
||||
del receiving_pubkey
|
||||
del params
|
||||
|
||||
## ENCRYPTION DOMAIN ##
|
||||
|
||||
params = UmbralParameters(curve=curve)
|
||||
|
||||
delegating_pubkey = UmbralPublicKey.from_bytes(delegating_pubkey_bytes, params)
|
||||
|
||||
plain_data = b'peace at dawn'
|
||||
ciphertext, capsule = pre.encrypt(delegating_pubkey, plain_data)
|
||||
capsule_bytes = bytes(capsule)
|
||||
|
||||
del capsule
|
||||
del delegating_pubkey
|
||||
del params
|
||||
|
||||
## DECRYPTION BY ALICE ##
|
||||
|
||||
params = UmbralParameters(curve=curve)
|
||||
|
||||
delegating_privkey = UmbralPrivateKey.from_bytes(delegating_privkey_bytes, params=params)
|
||||
capsule = pre.Capsule.from_bytes(capsule_bytes, params)
|
||||
cleartext = pre.decrypt(ciphertext, capsule, delegating_privkey)
|
||||
assert cleartext == plain_data
|
||||
|
||||
del delegating_privkey
|
||||
del capsule
|
||||
del params
|
||||
|
||||
## RE-ENCRYPTION DOMAIN (i.e., Ursula's side)
|
||||
|
||||
cfrags_bytes = list()
|
||||
for kfrag_bytes in kfrags_bytes:
|
||||
params = UmbralParameters(curve=curve)
|
||||
delegating_pubkey = UmbralPublicKey.from_bytes(delegating_pubkey_bytes, params)
|
||||
signing_pubkey = UmbralPublicKey.from_bytes(signing_pubkey_bytes, params)
|
||||
receiving_pubkey = UmbralPublicKey.from_bytes(receiving_pubkey_bytes, params)
|
||||
|
||||
capsule = pre.Capsule.from_bytes(capsule_bytes, params)
|
||||
capsule.set_correctness_keys(delegating=delegating_pubkey,
|
||||
receiving=receiving_pubkey,
|
||||
verifying=signing_pubkey)
|
||||
|
||||
# TODO: use params instead of curve?
|
||||
kfrag = KFrag.from_bytes(kfrag_bytes, params.curve)
|
||||
|
||||
assert kfrag.verify(signing_pubkey, delegating_pubkey, receiving_pubkey, params)
|
||||
|
||||
cfrag_bytes = bytes(pre.reencrypt(kfrag, capsule))
|
||||
cfrags_bytes.append(cfrag_bytes)
|
||||
|
||||
del capsule
|
||||
del kfrag
|
||||
del params
|
||||
del delegating_pubkey
|
||||
del signing_pubkey
|
||||
del receiving_pubkey
|
||||
|
||||
## DECRYPTION DOMAIN (i.e., Bob's side)
|
||||
params = UmbralParameters(curve=curve)
|
||||
|
||||
capsule = pre.Capsule.from_bytes(capsule_bytes, params)
|
||||
delegating_pubkey = UmbralPublicKey.from_bytes(delegating_pubkey_bytes, params)
|
||||
signing_pubkey = UmbralPublicKey.from_bytes(signing_pubkey_bytes, params)
|
||||
receiving_privkey = UmbralPrivateKey.from_bytes(receiving_privkey_bytes, params=params)
|
||||
receiving_pubkey = receiving_privkey.get_pubkey()
|
||||
|
||||
capsule.set_correctness_keys(delegating=delegating_pubkey,
|
||||
receiving=receiving_pubkey,
|
||||
verifying=signing_pubkey)
|
||||
|
||||
for cfrag_bytes in cfrags_bytes:
|
||||
# TODO: use params instead of curve?
|
||||
cfrag = CapsuleFrag.from_bytes(cfrag_bytes, params.curve)
|
||||
capsule.attach_cfrag(cfrag)
|
||||
|
||||
reenc_cleartext = pre.decrypt(ciphertext, capsule, receiving_privkey)
|
||||
assert reenc_cleartext == plain_data
|
||||
|
||||
|
||||
@pytest.mark.parametrize("curve", other_supported_curves)
|
||||
@pytest.mark.parametrize("N, M", parameters)
|
||||
@pytest.mark.parametrize("signing_mode", kfrag_signing_modes)
|
||||
def test_lifecycle_with_serialization_on_multiple_curves(N, M, signing_mode, curve):
|
||||
test_lifecycle_with_serialization(N, M, signing_mode, curve)
|
|
@ -1,98 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from umbral import pre
|
||||
from umbral.config import default_curve
|
||||
from umbral.params import UmbralParameters
|
||||
from umbral.signing import Signer
|
||||
from umbral.keys import UmbralPrivateKey
|
||||
from ..conftest import parameters, other_supported_curves
|
||||
|
||||
|
||||
@pytest.mark.parametrize("N, M", parameters)
|
||||
def test_simple_api(N, M, curve=default_curve()):
|
||||
"""
|
||||
This test models the main interactions between NuCypher actors (i.e., Alice,
|
||||
Bob, Data Source, and Ursulas) and artifacts (i.e., public and private keys,
|
||||
ciphertexts, capsules, KFrags, CFrags, etc).
|
||||
|
||||
The test covers all the main stages of data sharing with NuCypher:
|
||||
key generation, delegation, encryption, decryption by
|
||||
Alice, re-encryption by Ursula, and decryption by Bob.
|
||||
|
||||
Manually injects umbralparameters for multi-curve testing."""
|
||||
|
||||
# Generation of global parameters
|
||||
params = UmbralParameters(curve=curve)
|
||||
|
||||
# Key Generation (Alice)
|
||||
delegating_privkey = UmbralPrivateKey.gen_key(params=params)
|
||||
delegating_pubkey = delegating_privkey.get_pubkey()
|
||||
|
||||
signing_privkey = UmbralPrivateKey.gen_key(params=params)
|
||||
signing_pubkey = signing_privkey.get_pubkey()
|
||||
signer = Signer(signing_privkey)
|
||||
|
||||
# Key Generation (Bob)
|
||||
receiving_privkey = UmbralPrivateKey.gen_key(params=params)
|
||||
receiving_pubkey = receiving_privkey.get_pubkey()
|
||||
|
||||
# Encryption by an unnamed data source
|
||||
plain_data = b'peace at dawn'
|
||||
ciphertext, capsule = pre.encrypt(delegating_pubkey, plain_data)
|
||||
|
||||
# Decryption by Alice
|
||||
cleartext = pre.decrypt(ciphertext, capsule, delegating_privkey)
|
||||
assert cleartext == plain_data
|
||||
|
||||
# Split Re-Encryption Key Generation (aka Delegation)
|
||||
kfrags = pre.generate_kfrags(delegating_privkey, receiving_pubkey, M, N, signer)
|
||||
|
||||
|
||||
# Capsule preparation (necessary before re-encryotion and activation)
|
||||
capsule.set_correctness_keys(delegating=delegating_pubkey,
|
||||
receiving=receiving_pubkey,
|
||||
verifying=signing_pubkey)
|
||||
|
||||
# Bob requests re-encryption to some set of M ursulas
|
||||
cfrags = list()
|
||||
for kfrag in kfrags[:M]:
|
||||
# Ursula checks that the received kfrag is valid
|
||||
assert kfrag.verify(signing_pubkey, delegating_pubkey, receiving_pubkey, params)
|
||||
|
||||
# Re-encryption by an Ursula
|
||||
cfrag = pre.reencrypt(kfrag, capsule)
|
||||
|
||||
# Bob collects the result
|
||||
cfrags.append(cfrag)
|
||||
|
||||
# Capsule activation (by Bob)
|
||||
for cfrag in cfrags:
|
||||
capsule.attach_cfrag(cfrag)
|
||||
|
||||
# Decryption by Bob
|
||||
reenc_cleartext = pre.decrypt(ciphertext, capsule, receiving_privkey)
|
||||
assert reenc_cleartext == plain_data
|
||||
|
||||
|
||||
@pytest.mark.parametrize("curve", other_supported_curves)
|
||||
@pytest.mark.parametrize("N, M", parameters)
|
||||
def test_simple_api_on_multiple_curves(N, M, curve):
|
||||
test_simple_api(N, M, curve)
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
import pytest
|
||||
|
||||
from umbral import (
|
||||
Capsule,
|
||||
SecretKey,
|
||||
PublicKey,
|
||||
GenericError,
|
||||
encrypt,
|
||||
decrypt_original,
|
||||
reencrypt,
|
||||
decrypt_reencrypted,
|
||||
generate_kfrags
|
||||
)
|
||||
from umbral.curve_point import CurvePoint
|
||||
|
||||
|
||||
def test_capsule_serialization(alices_keys):
|
||||
|
||||
delegating_sk, _signing_sk = alices_keys
|
||||
delegating_pk = PublicKey.from_secret_key(delegating_sk)
|
||||
|
||||
capsule, _key = Capsule.from_public_key(delegating_pk)
|
||||
new_capsule = Capsule.from_bytes(bytes(capsule))
|
||||
|
||||
assert capsule == new_capsule
|
||||
|
||||
# Deserializing a bad capsule triggers verification error
|
||||
capsule.point_e = CurvePoint.random()
|
||||
capsule_bytes = bytes(capsule)
|
||||
|
||||
with pytest.raises(GenericError):
|
||||
Capsule.from_bytes(capsule_bytes)
|
||||
|
||||
|
||||
def test_capsule_is_hashable(alices_keys):
|
||||
|
||||
delegating_sk, _signing_sk = alices_keys
|
||||
delegating_pk = PublicKey.from_secret_key(delegating_sk)
|
||||
|
||||
capsule1, key1 = Capsule.from_public_key(delegating_pk)
|
||||
capsule2, key2 = Capsule.from_public_key(delegating_pk)
|
||||
|
||||
assert capsule1 != capsule2
|
||||
assert key1 != key2
|
||||
assert hash(capsule1) != hash(capsule2)
|
||||
|
||||
new_capsule = Capsule.from_bytes(bytes(capsule1))
|
||||
assert hash(new_capsule) == hash(capsule1)
|
||||
|
||||
|
||||
def test_open_original(alices_keys):
|
||||
|
||||
delegating_sk, _signing_sk = alices_keys
|
||||
delegating_pk = PublicKey.from_secret_key(delegating_sk)
|
||||
|
||||
capsule, key = Capsule.from_public_key(delegating_pk)
|
||||
key_back = capsule.open_original(delegating_sk)
|
||||
assert key == key_back
|
||||
|
||||
|
||||
def test_open_reencrypted(alices_keys, bobs_keys):
|
||||
|
||||
threshold = 6
|
||||
num_kfrags = 10
|
||||
|
||||
delegating_sk, signing_sk = alices_keys
|
||||
receiving_sk, receiving_pk = bobs_keys
|
||||
|
||||
signing_pk = PublicKey.from_secret_key(signing_sk)
|
||||
delegating_pk = PublicKey.from_secret_key(delegating_sk)
|
||||
|
||||
capsule, key = Capsule.from_public_key(delegating_pk)
|
||||
kfrags = generate_kfrags(delegating_sk=delegating_sk,
|
||||
signing_sk=signing_sk,
|
||||
receiving_pk=receiving_pk,
|
||||
threshold=threshold,
|
||||
num_kfrags=num_kfrags)
|
||||
|
||||
cfrags = [reencrypt(capsule, kfrag) for kfrag in kfrags]
|
||||
key_back = capsule.open_reencrypted(receiving_sk, delegating_pk, cfrags[:threshold])
|
||||
assert key_back == key
|
||||
|
||||
# No cfrags at all
|
||||
with pytest.raises(ValueError, match="Empty CapsuleFrag sequence"):
|
||||
capsule.open_reencrypted(receiving_sk, delegating_pk, [])
|
||||
|
||||
# Not enough cfrags
|
||||
with pytest.raises(GenericError, match="Internal validation failed"):
|
||||
capsule.open_reencrypted(receiving_sk, delegating_pk, cfrags[:threshold-1])
|
||||
|
||||
# Repeating cfrags
|
||||
with pytest.raises(ValueError, match="Some of the CapsuleFrags are repeated"):
|
||||
capsule.open_reencrypted(receiving_sk, delegating_pk, [cfrags[0]] + cfrags[:threshold-1])
|
||||
|
||||
# Mismatched cfrags
|
||||
kfrags2 = generate_kfrags(delegating_sk=delegating_sk,
|
||||
signing_sk=signing_sk,
|
||||
receiving_pk=receiving_pk,
|
||||
threshold=threshold,
|
||||
num_kfrags=num_kfrags)
|
||||
cfrags2 = [reencrypt(capsule, kfrag) for kfrag in kfrags2]
|
||||
with pytest.raises(ValueError, match="CapsuleFrags are not pairwise consistent"):
|
||||
capsule.open_reencrypted(receiving_sk, delegating_pk, [cfrags2[0]] + cfrags[:threshold-1])
|
||||
|
||||
|
||||
def test_capsule_str(capsule):
|
||||
s = str(capsule)
|
||||
assert 'Capsule' in s
|
|
@ -0,0 +1,149 @@
|
|||
from umbral import reencrypt, CapsuleFrag, PublicKey, Capsule
|
||||
from umbral.curve_point import CurvePoint
|
||||
|
||||
|
||||
def test_cfrag_serialization(alices_keys, bobs_keys, capsule, kfrags):
|
||||
|
||||
delegating_sk, signing_sk = alices_keys
|
||||
_receiving_sk, receiving_pk = bobs_keys
|
||||
|
||||
signing_pk = PublicKey.from_secret_key(signing_sk)
|
||||
delegating_pk = PublicKey.from_secret_key(delegating_sk)
|
||||
|
||||
metadata = b'This is an example of metadata for re-encryption request'
|
||||
for kfrag in kfrags:
|
||||
cfrag = reencrypt(capsule, kfrag, metadata=metadata)
|
||||
cfrag_bytes = bytes(cfrag)
|
||||
|
||||
new_cfrag = CapsuleFrag.from_bytes(cfrag_bytes)
|
||||
assert new_cfrag == cfrag
|
||||
|
||||
assert new_cfrag.verify(capsule,
|
||||
delegating_pk=delegating_pk,
|
||||
receiving_pk=receiving_pk,
|
||||
signing_pk=signing_pk,
|
||||
metadata=metadata)
|
||||
|
||||
# No metadata
|
||||
assert not new_cfrag.verify(capsule,
|
||||
delegating_pk=delegating_pk,
|
||||
receiving_pk=receiving_pk,
|
||||
signing_pk=signing_pk)
|
||||
|
||||
# Wrong metadata
|
||||
assert not new_cfrag.verify(capsule,
|
||||
delegating_pk=delegating_pk,
|
||||
receiving_pk=receiving_pk,
|
||||
signing_pk=signing_pk,
|
||||
metadata=b'Not the same metadata')
|
||||
|
||||
# Wrong delegating key
|
||||
assert not new_cfrag.verify(capsule,
|
||||
delegating_pk=receiving_pk,
|
||||
receiving_pk=receiving_pk,
|
||||
signing_pk=signing_pk,
|
||||
metadata=metadata)
|
||||
|
||||
# Wrong receiving key
|
||||
assert not new_cfrag.verify(capsule,
|
||||
delegating_pk=delegating_pk,
|
||||
receiving_pk=delegating_pk,
|
||||
signing_pk=signing_pk,
|
||||
metadata=metadata)
|
||||
|
||||
# Wrong signing key
|
||||
assert not new_cfrag.verify(capsule,
|
||||
delegating_pk=delegating_pk,
|
||||
receiving_pk=receiving_pk,
|
||||
signing_pk=receiving_pk,
|
||||
metadata=metadata)
|
||||
|
||||
|
||||
def test_cfrag_serialization_no_metadata(alices_keys, bobs_keys, capsule, kfrags):
|
||||
|
||||
delegating_sk, signing_sk = alices_keys
|
||||
_receiving_sk, receiving_pk = bobs_keys
|
||||
|
||||
signing_pk = PublicKey.from_secret_key(signing_sk)
|
||||
delegating_pk = PublicKey.from_secret_key(delegating_sk)
|
||||
|
||||
for kfrag in kfrags:
|
||||
|
||||
# Create with no metadata
|
||||
cfrag = reencrypt(capsule, kfrag)
|
||||
cfrag_bytes = bytes(cfrag)
|
||||
new_cfrag = CapsuleFrag.from_bytes(cfrag_bytes)
|
||||
|
||||
assert new_cfrag.verify(capsule,
|
||||
delegating_pk=delegating_pk,
|
||||
receiving_pk=receiving_pk,
|
||||
signing_pk=signing_pk)
|
||||
|
||||
assert not new_cfrag.verify(capsule,
|
||||
delegating_pk=delegating_pk,
|
||||
receiving_pk=receiving_pk,
|
||||
signing_pk=signing_pk,
|
||||
metadata=b'some metadata')
|
||||
|
||||
|
||||
def test_cfrag_with_wrong_capsule(alices_keys, bobs_keys,
|
||||
kfrags, capsule_and_ciphertext, message):
|
||||
|
||||
capsule, ciphertext = capsule_and_ciphertext
|
||||
|
||||
delegating_sk, signing_sk = alices_keys
|
||||
delegating_pk = PublicKey.from_secret_key(delegating_sk)
|
||||
|
||||
_receiving_sk, receiving_pk = bobs_keys
|
||||
|
||||
capsule_alice1 = capsule
|
||||
capsule_alice2, _unused_key2 = Capsule.from_public_key(delegating_pk)
|
||||
|
||||
metadata = b"some metadata"
|
||||
cfrag = reencrypt(capsule_alice2, kfrags[0], metadata=metadata)
|
||||
|
||||
assert not cfrag.verify(capsule_alice1,
|
||||
delegating_pk=delegating_pk,
|
||||
receiving_pk=receiving_pk,
|
||||
signing_pk=PublicKey.from_secret_key(signing_sk),
|
||||
metadata=metadata)
|
||||
|
||||
|
||||
def test_cfrag_with_wrong_data(kfrags, alices_keys, bobs_keys, capsule_and_ciphertext, message):
|
||||
|
||||
capsule, ciphertext = capsule_and_ciphertext
|
||||
|
||||
delegating_sk, signing_sk = alices_keys
|
||||
delegating_pk = PublicKey.from_secret_key(delegating_sk)
|
||||
|
||||
_receiving_sk, receiving_pk = bobs_keys
|
||||
|
||||
metadata = b"some metadata"
|
||||
cfrag = reencrypt(capsule, kfrags[0], metadata=metadata)
|
||||
|
||||
# Let's put random garbage in one of the cfrags
|
||||
cfrag.point_e1 = CurvePoint.random()
|
||||
cfrag.point_v1 = CurvePoint.random()
|
||||
|
||||
assert not cfrag.verify(capsule,
|
||||
delegating_pk=delegating_pk,
|
||||
receiving_pk=receiving_pk,
|
||||
signing_pk=PublicKey.from_secret_key(signing_sk),
|
||||
metadata=metadata)
|
||||
|
||||
|
||||
def test_cfrag_is_hashable(capsule, kfrags):
|
||||
|
||||
cfrag0 = reencrypt(capsule, kfrags[0], metadata=b'abcdef')
|
||||
cfrag1 = reencrypt(capsule, kfrags[1], metadata=b'abcdef')
|
||||
|
||||
assert hash(cfrag0) != hash(cfrag1)
|
||||
|
||||
new_cfrag = CapsuleFrag.from_bytes(bytes(cfrag0))
|
||||
assert hash(new_cfrag) == hash(cfrag0)
|
||||
|
||||
|
||||
def test_cfrag_str(capsule, kfrags):
|
||||
cfrag0 = reencrypt(capsule, kfrags[0], metadata=b'abcdef')
|
||||
s = str(cfrag0)
|
||||
assert 'CapsuleFrag' in s
|
|
@ -0,0 +1,205 @@
|
|||
import pytest
|
||||
|
||||
try:
|
||||
import umbral_pre as umbral_rs
|
||||
except ImportError:
|
||||
umbral_rs = None
|
||||
|
||||
import umbral as umbral_py
|
||||
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
if 'implementations' in metafunc.fixturenames:
|
||||
implementations = [(umbral_py, umbral_py)]
|
||||
ids = ['python -> python']
|
||||
if umbral_rs is not None:
|
||||
implementations.extend([(umbral_py, umbral_rs), (umbral_rs, umbral_py)])
|
||||
ids.extend(['python -> rust', 'rust -> python'])
|
||||
|
||||
metafunc.parametrize('implementations', implementations, ids=ids)
|
||||
|
||||
|
||||
def _create_keypair(umbral):
|
||||
sk = umbral.SecretKey.random()
|
||||
pk = umbral.PublicKey.from_secret_key(sk)
|
||||
return bytes(sk), bytes(pk)
|
||||
|
||||
|
||||
def _restore_keys(umbral, sk_bytes, pk_bytes):
|
||||
sk = umbral.SecretKey.from_bytes(sk_bytes)
|
||||
pk_from_sk = umbral.PublicKey.from_secret_key(sk)
|
||||
pk_from_bytes = umbral.PublicKey.from_bytes(pk_bytes)
|
||||
assert pk_from_sk == pk_from_bytes
|
||||
|
||||
|
||||
def test_keys(implementations):
|
||||
umbral1, umbral2 = implementations
|
||||
|
||||
# On client 1
|
||||
sk_bytes, pk_bytes = _create_keypair(umbral1)
|
||||
|
||||
# On client 2
|
||||
_restore_keys(umbral2, sk_bytes, pk_bytes)
|
||||
|
||||
|
||||
def _create_sk_factory_and_sk(umbral, label):
|
||||
skf = umbral.SecretKeyFactory.random()
|
||||
sk = skf.secret_key_by_label(label)
|
||||
return bytes(skf), bytes(sk)
|
||||
|
||||
|
||||
def _check_sk_is_same(umbral, label, skf_bytes, sk_bytes):
|
||||
skf = umbral.SecretKeyFactory.from_bytes(skf_bytes)
|
||||
sk_restored = umbral.SecretKey.from_bytes(sk_bytes)
|
||||
sk_generated = skf.secret_key_by_label(label)
|
||||
assert sk_restored == sk_generated
|
||||
|
||||
|
||||
def test_secret_key_factory(implementations):
|
||||
umbral1, umbral2 = implementations
|
||||
label = b'label'
|
||||
|
||||
skf_bytes, sk_bytes = _create_sk_factory_and_sk(umbral1, label)
|
||||
_check_sk_is_same(umbral2, label, skf_bytes, sk_bytes)
|
||||
|
||||
|
||||
def _encrypt(umbral, plaintext, pk_bytes):
|
||||
pk = umbral.PublicKey.from_bytes(pk_bytes)
|
||||
capsule, ciphertext = umbral.encrypt(pk, plaintext)
|
||||
return bytes(capsule), ciphertext
|
||||
|
||||
|
||||
def _decrypt_original(umbral, sk_bytes, capsule_bytes, ciphertext):
|
||||
capsule = umbral.Capsule.from_bytes(bytes(capsule_bytes))
|
||||
sk = umbral.SecretKey.from_bytes(sk_bytes)
|
||||
return umbral.decrypt_original(sk, capsule, ciphertext)
|
||||
|
||||
|
||||
def test_encrypt_decrypt(implementations):
|
||||
|
||||
umbral1, umbral2 = implementations
|
||||
plaintext = b'peace at dawn'
|
||||
|
||||
# On client 1
|
||||
sk_bytes, pk_bytes = _create_keypair(umbral1)
|
||||
|
||||
# On client 2
|
||||
capsule_bytes, ciphertext = _encrypt(umbral2, plaintext, pk_bytes)
|
||||
|
||||
# On client 1
|
||||
plaintext_decrypted = _decrypt_original(umbral1, sk_bytes, capsule_bytes, ciphertext)
|
||||
|
||||
assert plaintext_decrypted == plaintext
|
||||
|
||||
|
||||
def _generate_kfrags(umbral, delegating_sk_bytes, receiving_pk_bytes,
|
||||
signing_sk_bytes, threshold, num_frags):
|
||||
|
||||
delegating_sk = umbral.SecretKey.from_bytes(delegating_sk_bytes)
|
||||
receiving_pk = umbral.PublicKey.from_bytes(receiving_pk_bytes)
|
||||
signing_sk = umbral.SecretKey.from_bytes(signing_sk_bytes)
|
||||
|
||||
kfrags = umbral.generate_kfrags(delegating_sk,
|
||||
receiving_pk,
|
||||
signing_sk,
|
||||
threshold,
|
||||
num_frags,
|
||||
True,
|
||||
True,
|
||||
)
|
||||
|
||||
return [bytes(kfrag) for kfrag in kfrags]
|
||||
|
||||
|
||||
def _verify_kfrags(umbral, kfrags_bytes, signing_pk_bytes, delegating_pk_bytes, receiving_pk_bytes):
|
||||
kfrags = [umbral.KeyFrag.from_bytes(kfrag_bytes) for kfrag_bytes in kfrags_bytes]
|
||||
signing_pk = umbral.PublicKey.from_bytes(signing_pk_bytes)
|
||||
delegating_pk = umbral.PublicKey.from_bytes(delegating_pk_bytes)
|
||||
receiving_pk = umbral.PublicKey.from_bytes(receiving_pk_bytes)
|
||||
assert all(kfrag.verify(signing_pk, delegating_pk, receiving_pk) for kfrag in kfrags)
|
||||
|
||||
|
||||
def test_kfrags(implementations):
|
||||
|
||||
umbral1, umbral2 = implementations
|
||||
|
||||
threshold = 2
|
||||
num_frags = 3
|
||||
plaintext = b'peace at dawn'
|
||||
|
||||
# On client 1
|
||||
|
||||
receiving_sk_bytes, receiving_pk_bytes = _create_keypair(umbral1)
|
||||
delegating_sk_bytes, delegating_pk_bytes = _create_keypair(umbral1)
|
||||
signing_sk_bytes, signing_pk_bytes = _create_keypair(umbral1)
|
||||
kfrags_bytes = _generate_kfrags(umbral1, delegating_sk_bytes, receiving_pk_bytes,
|
||||
signing_sk_bytes, threshold, num_frags)
|
||||
|
||||
# On client 2
|
||||
|
||||
_verify_kfrags(umbral2, kfrags_bytes, signing_pk_bytes, delegating_pk_bytes, receiving_pk_bytes)
|
||||
|
||||
|
||||
def _reencrypt(umbral, capsule_bytes, kfrags_bytes, threshold, metadata):
|
||||
capsule = umbral.Capsule.from_bytes(bytes(capsule_bytes))
|
||||
kfrags = [umbral.KeyFrag.from_bytes(kfrag_bytes) for kfrag_bytes in kfrags_bytes]
|
||||
cfrags = [umbral.reencrypt(capsule, kfrag, metadata=metadata) for kfrag in kfrags[:threshold]]
|
||||
return [bytes(cfrag) for cfrag in cfrags]
|
||||
|
||||
|
||||
def _decrypt_reencrypted(umbral, receiving_sk_bytes, delegating_pk_bytes, signing_pk_bytes,
|
||||
capsule_bytes, cfrags_bytes, ciphertext, metadata):
|
||||
|
||||
receiving_sk = umbral.SecretKey.from_bytes(receiving_sk_bytes)
|
||||
receiving_pk = umbral.PublicKey.from_secret_key(receiving_sk)
|
||||
delegating_pk = umbral.PublicKey.from_bytes(delegating_pk_bytes)
|
||||
signing_pk = umbral.PublicKey.from_bytes(signing_pk_bytes)
|
||||
|
||||
capsule = umbral.Capsule.from_bytes(bytes(capsule_bytes))
|
||||
cfrags = [umbral.CapsuleFrag.from_bytes(cfrag_bytes) for cfrag_bytes in cfrags_bytes]
|
||||
|
||||
assert all(cfrag.verify(capsule, delegating_pk, receiving_pk, signing_pk, metadata=metadata)
|
||||
for cfrag in cfrags)
|
||||
|
||||
# Decryption by Bob
|
||||
plaintext = umbral.decrypt_reencrypted(receiving_sk,
|
||||
delegating_pk,
|
||||
capsule,
|
||||
cfrags,
|
||||
ciphertext,
|
||||
)
|
||||
|
||||
return plaintext
|
||||
|
||||
|
||||
def test_reencrypt(implementations):
|
||||
|
||||
umbral1, umbral2 = implementations
|
||||
|
||||
metadata = b'metadata'
|
||||
threshold = 2
|
||||
num_frags = 3
|
||||
plaintext = b'peace at dawn'
|
||||
|
||||
# On client 1
|
||||
|
||||
receiving_sk_bytes, receiving_pk_bytes = _create_keypair(umbral1)
|
||||
delegating_sk_bytes, delegating_pk_bytes = _create_keypair(umbral1)
|
||||
signing_sk_bytes, signing_pk_bytes = _create_keypair(umbral1)
|
||||
|
||||
capsule_bytes, ciphertext = _encrypt(umbral1, plaintext, delegating_pk_bytes)
|
||||
|
||||
kfrags_bytes = _generate_kfrags(umbral1, delegating_sk_bytes, receiving_pk_bytes,
|
||||
signing_sk_bytes, threshold, num_frags)
|
||||
|
||||
# On client 2
|
||||
|
||||
cfrags_bytes = _reencrypt(umbral2, capsule_bytes, kfrags_bytes, threshold, metadata)
|
||||
|
||||
# On client 1
|
||||
|
||||
plaintext_reencrypted = _decrypt_reencrypted(umbral1,
|
||||
receiving_sk_bytes, delegating_pk_bytes, signing_pk_bytes,
|
||||
capsule_bytes, cfrags_bytes, ciphertext, metadata)
|
||||
|
||||
assert plaintext_reencrypted == plaintext
|
|
@ -0,0 +1,113 @@
|
|||
import pytest
|
||||
|
||||
from umbral.openssl import Curve, bn_to_int, point_to_affine_coords
|
||||
from umbral.curve import CURVE, CURVES, SECP256R1, SECP256K1, SECP384R1
|
||||
|
||||
|
||||
def test_supported_curves():
|
||||
|
||||
# Ensure we have the correct number of supported curves hardcoded
|
||||
number_of_supported_curves = 3
|
||||
assert len(Curve._supported_curves) == number_of_supported_curves
|
||||
|
||||
# Manually ensure the `_supported curves` dict contains only valid supported curves
|
||||
assert Curve._supported_curves[415] == 'secp256r1'
|
||||
assert Curve._supported_curves[714] == 'secp256k1'
|
||||
assert Curve._supported_curves[715] == 'secp384r1'
|
||||
|
||||
|
||||
def test_create_by_nid():
|
||||
|
||||
nid, name = 714, 'secp256k1'
|
||||
|
||||
# supported
|
||||
_curve_714 = Curve(nid=nid)
|
||||
assert _curve_714.nid == nid
|
||||
assert _curve_714.name == name
|
||||
|
||||
# unsuported
|
||||
with pytest.raises(NotImplementedError):
|
||||
Curve(711)
|
||||
|
||||
|
||||
def test_create_by_name():
|
||||
|
||||
nid, name = 714, 'secp256k1'
|
||||
|
||||
# Supported
|
||||
_curve_secp256k1 = Curve.from_name(name)
|
||||
assert _curve_secp256k1.name == name
|
||||
assert _curve_secp256k1.nid == nid
|
||||
|
||||
# Unsupported
|
||||
with pytest.raises(NotImplementedError):
|
||||
Curve.from_name('abcd123e4')
|
||||
|
||||
|
||||
def test_curve_constants():
|
||||
|
||||
test_p256 = SECP256R1
|
||||
test_secp256k1 = SECP256K1
|
||||
test_p384 = SECP384R1
|
||||
|
||||
assert CURVE == SECP256K1
|
||||
|
||||
# Test the hardcoded curve NIDs are correct:
|
||||
assert test_p256.nid == 415
|
||||
assert test_secp256k1.nid == 714
|
||||
assert test_p384.nid == 715
|
||||
|
||||
# Ensure every curve constant is in the CURVES collection
|
||||
number_of_supported_curves = 3
|
||||
assert len(CURVES) == number_of_supported_curves
|
||||
|
||||
# Ensure all supported curves can be initialized
|
||||
for nid, name in Curve._supported_curves.items():
|
||||
by_nid, by_name = Curve(nid=nid), Curve.from_name(name)
|
||||
assert by_nid.name == name
|
||||
assert by_name.nid == nid
|
||||
|
||||
|
||||
def test_curve_str():
|
||||
for curve in CURVES:
|
||||
s = str(curve)
|
||||
assert str(curve.nid) in s
|
||||
assert str(curve.name) in s
|
||||
|
||||
|
||||
def _curve_info(curve: Curve):
|
||||
assert bn_to_int(curve.bn_order) == curve.order
|
||||
return dict(order=curve.order,
|
||||
field_element_size=curve.field_element_size,
|
||||
scalar_size=curve.scalar_size,
|
||||
generator=point_to_affine_coords(curve, curve.point_generator))
|
||||
|
||||
|
||||
def test_secp256k1():
|
||||
info = _curve_info(SECP256K1)
|
||||
assert info['order'] == 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_BAAEDCE6_AF48A03B_BFD25E8C_D0364141
|
||||
assert info['field_element_size'] == 32
|
||||
assert info['scalar_size'] == 32
|
||||
assert info['generator'] == (
|
||||
0x79BE667E_F9DCBBAC_55A06295_CE870B07_029BFCDB_2DCE28D9_59F2815B_16F81798,
|
||||
0x483ADA77_26A3C465_5DA4FBFC_0E1108A8_FD17B448_A6855419_9C47D08F_FB10D4B8)
|
||||
|
||||
|
||||
def test_p256():
|
||||
info = _curve_info(SECP256R1)
|
||||
assert info['order'] == 0xFFFFFFFF_00000000_FFFFFFFF_FFFFFFFF_BCE6FAAD_A7179E84_F3B9CAC2_FC632551
|
||||
assert info['field_element_size'] == 32
|
||||
assert info['scalar_size'] == 32
|
||||
assert info['generator'] == (
|
||||
0x6B17D1F2_E12C4247_F8BCE6E5_63A440F2_77037D81_2DEB33A0_F4A13945_D898C296,
|
||||
0x4FE342E2_FE1A7F9B_8EE7EB4A_7C0F9E16_2BCE3357_6B315ECE_CBB64068_37BF51F5)
|
||||
|
||||
|
||||
def test_p384():
|
||||
info = _curve_info(SECP384R1)
|
||||
assert info['order'] == 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_C7634D81_F4372DDF_581A0DB2_48B0A77A_ECEC196A_CCC52973
|
||||
assert info['field_element_size'] == 48
|
||||
assert info['scalar_size'] == 48
|
||||
assert info['generator'] == (
|
||||
0xAA87CA22_BE8B0537_8EB1C71E_F320AD74_6E1D3B62_8BA79B98_59F741E0_82542A38_5502F25D_BF55296C_3A545E38_72760AB7,
|
||||
0x3617DE4A_96262C6F_5D9E98BF_9292DC29_F8F41DBD_289A147C_E9DA3113_B5F0B8C0_0A60B1CE_1D7E819D_7A431D7C_90EA0E5F)
|
|
@ -0,0 +1,90 @@
|
|||
import pytest
|
||||
|
||||
from umbral.openssl import ErrorInvalidCompressedPoint, ErrorInvalidPointEncoding
|
||||
from umbral.curve_point import CurvePoint
|
||||
from umbral.curve import CURVE
|
||||
|
||||
|
||||
def test_random():
|
||||
p1 = CurvePoint.random()
|
||||
p2 = CurvePoint.random()
|
||||
assert isinstance(p1, CurvePoint)
|
||||
assert isinstance(p2, CurvePoint)
|
||||
assert p1 != p2
|
||||
|
||||
|
||||
def test_generator_point():
|
||||
"""http://www.secg.org/SEC2-Ver-1.0.pdf Section 2.7.1"""
|
||||
g1 = CurvePoint.generator()
|
||||
|
||||
g_compressed = 0x0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
|
||||
g_compressed_bytes = g_compressed.to_bytes(CURVE.field_element_size + 1, byteorder='big')
|
||||
g2 = CurvePoint.from_bytes(g_compressed_bytes)
|
||||
|
||||
assert g1 == g2
|
||||
|
||||
|
||||
def test_to_and_from_affine():
|
||||
|
||||
x = 17004608369308732328368332205668001941491834793934321461466076545247324070015
|
||||
y = 69725941631324401609944843130171147910924748427773762412028916504484868631573
|
||||
|
||||
p = CurvePoint.from_affine(x, y)
|
||||
|
||||
assert p.to_affine() == (x, y)
|
||||
|
||||
|
||||
def test_invalid_serialized_points():
|
||||
|
||||
field_order = 2**256 - 0x1000003D1
|
||||
|
||||
# A point on secp256k1
|
||||
x = 17004608369308732328368332205668001941491834793934321461466076545247324070015
|
||||
y = 69725941631324401609944843130171147910924748427773762412028916504484868631573
|
||||
|
||||
# Check it
|
||||
assert (y**2 - x**3 - 7) % field_order == 0
|
||||
|
||||
# Should load
|
||||
point_data = b'\x03' + x.to_bytes(CURVE.field_element_size, 'big')
|
||||
p = CurvePoint.from_bytes(point_data)
|
||||
|
||||
# Make it invalid
|
||||
bad_x = x - 1
|
||||
assert (y**2 - bad_x**3 - 7) % field_order != 0
|
||||
|
||||
bad_x_data = b'\x03' + bad_x.to_bytes(CURVE.field_element_size, 'big')
|
||||
with pytest.raises(ErrorInvalidCompressedPoint):
|
||||
CurvePoint.from_bytes(bad_x_data)
|
||||
|
||||
# Valid x, invalid prefix
|
||||
bad_format = b'\xff' + x.to_bytes(CURVE.field_element_size, 'big')
|
||||
with pytest.raises(ErrorInvalidPointEncoding):
|
||||
CurvePoint.from_bytes(bad_format)
|
||||
|
||||
|
||||
def test_serialize_point_at_infinity():
|
||||
|
||||
p = CurvePoint.random()
|
||||
point_at_infinity = p - p
|
||||
|
||||
bytes_point_at_infinity = bytes(point_at_infinity)
|
||||
assert bytes_point_at_infinity == b'\x00'
|
||||
|
||||
|
||||
def test_coords_with_special_characteristics():
|
||||
|
||||
# Testing that a point with x coordinate greater than the curve order is still valid.
|
||||
# In particular, we will test the last valid point from the default curve (secp256k1)
|
||||
# whose x coordinate is `field_order - 3` and is greater than the order of the curve
|
||||
|
||||
field_order = 2**256 - 0x1000003D1
|
||||
compressed = b'\x02' + (field_order-3).to_bytes(32, 'big')
|
||||
|
||||
last_point = CurvePoint.from_bytes(compressed)
|
||||
|
||||
# The same point, but obtained through the from_affine method
|
||||
x = 115792089237316195423570985008687907853269984665640564039457584007908834671660
|
||||
y = 109188863561374057667848968960504138135859662956057034999983532397866404169138
|
||||
|
||||
assert last_point == CurvePoint.from_affine(x, y)
|
|
@ -0,0 +1,127 @@
|
|||
import pytest
|
||||
|
||||
from umbral.curve import CURVE
|
||||
from umbral.curve_scalar import CurveScalar
|
||||
from umbral.hashing import Hash
|
||||
|
||||
|
||||
def test_random():
|
||||
r1 = CurveScalar.random_nonzero()
|
||||
r2 = CurveScalar.random_nonzero()
|
||||
assert r1 != r2
|
||||
assert not r1.is_zero()
|
||||
assert not r2.is_zero()
|
||||
|
||||
|
||||
def test_from_and_to_int():
|
||||
zero = CurveScalar.from_int(0)
|
||||
assert zero.is_zero()
|
||||
assert int(zero) == 0
|
||||
|
||||
one = CurveScalar.one()
|
||||
assert not one.is_zero()
|
||||
assert int(one) == 1
|
||||
|
||||
big_int = CURVE.order - 2
|
||||
big_scalar = CurveScalar.from_int(big_int)
|
||||
assert int(big_scalar) == big_int
|
||||
|
||||
# normalization check
|
||||
with pytest.raises(ValueError):
|
||||
CurveScalar.from_int(CURVE.order)
|
||||
|
||||
# disable normalization check
|
||||
too_big = CurveScalar.from_int(CURVE.order, check_normalization=False)
|
||||
|
||||
|
||||
def test_from_digest():
|
||||
digest = Hash(b'asdf')
|
||||
digest.update(b'some info')
|
||||
s1 = CurveScalar.from_digest(digest)
|
||||
|
||||
digest = Hash(b'asdf')
|
||||
digest.update(b'some info')
|
||||
s2 = CurveScalar.from_digest(digest)
|
||||
|
||||
assert s1 == s2
|
||||
assert int(s1) == int(s2)
|
||||
|
||||
|
||||
def test_eq():
|
||||
random = CurveScalar.random_nonzero()
|
||||
same = CurveScalar.from_int(int(random))
|
||||
different = CurveScalar.random_nonzero()
|
||||
assert random == same
|
||||
assert random == int(same)
|
||||
assert random != different
|
||||
assert random != int(different)
|
||||
|
||||
|
||||
def test_serialization_rotations_of_1():
|
||||
|
||||
size_in_bytes = CURVE.scalar_size
|
||||
for i in range(size_in_bytes):
|
||||
lonely_one = 1 << i
|
||||
bn = CurveScalar.from_int(lonely_one)
|
||||
lonely_one_in_bytes = lonely_one.to_bytes(size_in_bytes, 'big')
|
||||
|
||||
# Check serialization
|
||||
assert bytes(bn) == lonely_one_in_bytes
|
||||
|
||||
# Check deserialization
|
||||
assert CurveScalar.from_bytes(lonely_one_in_bytes) == bn
|
||||
|
||||
|
||||
def test_invalid_deserialization():
|
||||
size_in_bytes = CURVE.scalar_size
|
||||
|
||||
# All-ones bytestring is invalid (since it's greater than the order)
|
||||
lots_of_ones = b'\xFF' * size_in_bytes
|
||||
with pytest.raises(ValueError):
|
||||
CurveScalar.from_bytes(lots_of_ones)
|
||||
|
||||
# Serialization of `order` is invalid since it's not strictly lower than
|
||||
# the order of the curve
|
||||
order = CURVE.order
|
||||
with pytest.raises(ValueError):
|
||||
CurveScalar.from_bytes(order.to_bytes(size_in_bytes, 'big'))
|
||||
|
||||
# On the other hand, serialization of `order - 1` is valid
|
||||
order -= 1
|
||||
CurveScalar.from_bytes(order.to_bytes(size_in_bytes, 'big'))
|
||||
|
||||
|
||||
def test_add():
|
||||
r1 = CurveScalar.random_nonzero()
|
||||
r2 = CurveScalar.random_nonzero()
|
||||
r1i = int(r1)
|
||||
r2i = int(r2)
|
||||
assert r1 + r2 == (r1i + r2i) % CURVE.order
|
||||
assert r1 + r2i == (r1i + r2i) % CURVE.order
|
||||
|
||||
|
||||
def test_sub():
|
||||
r1 = CurveScalar.random_nonzero()
|
||||
r2 = CurveScalar.random_nonzero()
|
||||
r1i = int(r1)
|
||||
r2i = int(r2)
|
||||
assert r1 - r2 == (r1i - r2i) % CURVE.order
|
||||
assert r1 - r2i == (r1i - r2i) % CURVE.order
|
||||
|
||||
|
||||
def test_mul():
|
||||
r1 = CurveScalar.random_nonzero()
|
||||
r2 = CurveScalar.random_nonzero()
|
||||
r1i = int(r1)
|
||||
r2i = int(r2)
|
||||
assert r1 * r2 == (r1i * r2i) % CURVE.order
|
||||
assert r1 * r2i == (r1i * r2i) % CURVE.order
|
||||
|
||||
|
||||
def test_invert():
|
||||
r1 = CurveScalar.random_nonzero()
|
||||
r1i = int(r1)
|
||||
r1inv = r1.invert()
|
||||
assert r1 * r1inv == CurveScalar.one()
|
||||
assert (r1i * int(r1inv)) % CURVE.order == 1
|
||||
|
|
@ -1,31 +1,14 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import os
|
||||
|
||||
from umbral.dem import UmbralDEM, DEM_KEYSIZE, DEM_NONCE_SIZE
|
||||
from cryptography.exceptions import InvalidTag
|
||||
from umbral import GenericError
|
||||
from umbral.dem import DEM
|
||||
|
||||
|
||||
def test_encrypt_decrypt():
|
||||
key = os.urandom(32)
|
||||
|
||||
dem = UmbralDEM(key)
|
||||
key = os.urandom(DEM.KEY_SIZE)
|
||||
dem = DEM(key)
|
||||
|
||||
plaintext = b'peace at dawn'
|
||||
|
||||
|
@ -39,7 +22,7 @@ def test_encrypt_decrypt():
|
|||
assert ciphertext0 != ciphertext1
|
||||
|
||||
# Nonce should be different
|
||||
assert ciphertext0[:DEM_NONCE_SIZE] != ciphertext1[:DEM_NONCE_SIZE]
|
||||
assert ciphertext0[:DEM.NONCE_SIZE] != ciphertext1[:DEM.NONCE_SIZE]
|
||||
|
||||
cleartext0 = dem.decrypt(ciphertext0)
|
||||
cleartext1 = dem.decrypt(ciphertext1)
|
||||
|
@ -48,11 +31,32 @@ def test_encrypt_decrypt():
|
|||
assert cleartext1 == plaintext
|
||||
|
||||
|
||||
def test_malformed_ciphertext():
|
||||
|
||||
key = os.urandom(DEM.KEY_SIZE)
|
||||
dem = DEM(key)
|
||||
|
||||
plaintext = b'peace at dawn'
|
||||
ciphertext = dem.encrypt(plaintext)
|
||||
|
||||
# So short it we can tell right away it doesn't even contain a nonce
|
||||
with pytest.raises(ValueError, match="The ciphertext must include the nonce"):
|
||||
dem.decrypt(ciphertext[:DEM.NONCE_SIZE-1])
|
||||
|
||||
# Too short to contain a tag
|
||||
with pytest.raises(ValueError, match="The authentication tag is missing or malformed"):
|
||||
dem.decrypt(ciphertext[:DEM.NONCE_SIZE + DEM.TAG_SIZE - 1])
|
||||
|
||||
# Too long
|
||||
with pytest.raises(GenericError):
|
||||
dem.decrypt(ciphertext + b'abcd')
|
||||
|
||||
|
||||
def test_encrypt_decrypt_associated_data():
|
||||
key = os.urandom(32)
|
||||
aad = b'secret code 1234'
|
||||
|
||||
dem = UmbralDEM(key)
|
||||
dem = DEM(key)
|
||||
|
||||
plaintext = b'peace at dawn'
|
||||
|
||||
|
@ -64,7 +68,7 @@ def test_encrypt_decrypt_associated_data():
|
|||
|
||||
assert ciphertext0 != ciphertext1
|
||||
|
||||
assert ciphertext0[:DEM_NONCE_SIZE] != ciphertext1[:DEM_NONCE_SIZE]
|
||||
assert ciphertext0[:DEM.NONCE_SIZE] != ciphertext1[:DEM.NONCE_SIZE]
|
||||
|
||||
cleartext0 = dem.decrypt(ciphertext0, authenticated_data=aad)
|
||||
cleartext1 = dem.decrypt(ciphertext1, authenticated_data=aad)
|
||||
|
@ -73,5 +77,5 @@ def test_encrypt_decrypt_associated_data():
|
|||
assert cleartext1 == plaintext
|
||||
|
||||
# Attempt decryption with invalid associated data
|
||||
with pytest.raises(InvalidTag):
|
||||
with pytest.raises(GenericError):
|
||||
cleartext2 = dem.decrypt(ciphertext0, authenticated_data=b'wrong data')
|
|
@ -0,0 +1,126 @@
|
|||
import pytest
|
||||
|
||||
from umbral import KeyFrag, PublicKey, generate_kfrags
|
||||
from umbral.key_frag import KeyFragID
|
||||
from umbral.curve_scalar import CurveScalar
|
||||
|
||||
|
||||
def test_kfrag_serialization(alices_keys, bobs_keys, kfrags):
|
||||
|
||||
delegating_sk, signing_sk = alices_keys
|
||||
_receiving_sk, receiving_pk = bobs_keys
|
||||
|
||||
signing_pk = PublicKey.from_secret_key(signing_sk)
|
||||
delegating_pk = PublicKey.from_secret_key(delegating_sk)
|
||||
|
||||
for kfrag in kfrags:
|
||||
kfrag_bytes = bytes(kfrag)
|
||||
new_kfrag = KeyFrag.from_bytes(kfrag_bytes)
|
||||
|
||||
assert new_kfrag.verify(signing_pk=signing_pk,
|
||||
delegating_pk=delegating_pk,
|
||||
receiving_pk=receiving_pk)
|
||||
|
||||
assert new_kfrag == kfrag
|
||||
|
||||
|
||||
def test_kfrag_verification(alices_keys, bobs_keys, kfrags):
|
||||
|
||||
delegating_sk, signing_sk = alices_keys
|
||||
_receiving_sk, receiving_pk = bobs_keys
|
||||
|
||||
signing_pk = PublicKey.from_secret_key(signing_sk)
|
||||
delegating_pk = PublicKey.from_secret_key(delegating_sk)
|
||||
|
||||
# Wrong signature
|
||||
kfrag = kfrags[0]
|
||||
kfrag.id = KeyFragID.random()
|
||||
kfrag_bytes = bytes(kfrag)
|
||||
new_kfrag = KeyFrag.from_bytes(kfrag_bytes)
|
||||
assert not new_kfrag.verify(signing_pk=signing_pk,
|
||||
delegating_pk=delegating_pk,
|
||||
receiving_pk=receiving_pk)
|
||||
|
||||
# Wrong key
|
||||
kfrag = kfrags[1]
|
||||
kfrag.key = CurveScalar.random_nonzero()
|
||||
kfrag_bytes = bytes(kfrag)
|
||||
new_kfrag = KeyFrag.from_bytes(kfrag_bytes)
|
||||
assert not new_kfrag.verify(signing_pk=signing_pk,
|
||||
delegating_pk=delegating_pk,
|
||||
receiving_pk=receiving_pk)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('sign_delegating_key',
|
||||
[False, True],
|
||||
ids=['sign_delegating_key', 'dont_sign_delegating_key'])
|
||||
@pytest.mark.parametrize('sign_receiving_key',
|
||||
[False, True],
|
||||
ids=['sign_receiving_key', 'dont_sign_receiving_key'])
|
||||
def test_kfrag_signing(alices_keys, bobs_keys, sign_delegating_key, sign_receiving_key):
|
||||
|
||||
delegating_sk, signing_sk = alices_keys
|
||||
_receiving_sk, receiving_pk = bobs_keys
|
||||
|
||||
signing_pk = PublicKey.from_secret_key(signing_sk)
|
||||
delegating_pk = PublicKey.from_secret_key(delegating_sk)
|
||||
|
||||
kfrags = generate_kfrags(delegating_sk=delegating_sk,
|
||||
signing_sk=signing_sk,
|
||||
receiving_pk=receiving_pk,
|
||||
threshold=6,
|
||||
num_kfrags=10,
|
||||
sign_delegating_key=sign_delegating_key,
|
||||
sign_receiving_key=sign_receiving_key)
|
||||
|
||||
kfrag = kfrags[0]
|
||||
|
||||
# serialize/deserialize to make sure sign_* fields are serialized correctly
|
||||
kfrag = KeyFrag.from_bytes(bytes(kfrag))
|
||||
|
||||
for pass_delegating_key, pass_receiving_key in zip([False, True], [False, True]):
|
||||
|
||||
delegating_key_ok = (not sign_delegating_key) or pass_delegating_key
|
||||
receiving_key_ok = (not sign_receiving_key) or pass_receiving_key
|
||||
should_verify = delegating_key_ok and receiving_key_ok
|
||||
|
||||
result = kfrag.verify(signing_pk=signing_pk,
|
||||
delegating_pk=delegating_pk if pass_delegating_key else None,
|
||||
receiving_pk=receiving_pk if pass_receiving_key else None)
|
||||
|
||||
assert result == should_verify
|
||||
|
||||
|
||||
def test_kfrag_is_hashable(kfrags):
|
||||
|
||||
assert hash(kfrags[0]) != hash(kfrags[1])
|
||||
|
||||
new_kfrag = KeyFrag.from_bytes(bytes(kfrags[0]))
|
||||
assert hash(new_kfrag) == hash(kfrags[0])
|
||||
|
||||
|
||||
def test_kfrag_str(kfrags):
|
||||
s = str(kfrags[0])
|
||||
assert "KeyFrag" in s
|
||||
|
||||
|
||||
WRONG_PARAMETERS = (
|
||||
# (num_kfrags, threshold)
|
||||
(-1, -1), (-1, 0), (-1, 5),
|
||||
(0, -1), (0, 0), (0, 5),
|
||||
(1, -1), (1, 0), (1, 5),
|
||||
(5, -1), (5, 0), (5, 10)
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("num_kfrags, threshold", WRONG_PARAMETERS)
|
||||
def test_wrong_threshold_and_num_kfrags(num_kfrags, threshold, alices_keys, bobs_keys):
|
||||
|
||||
delegating_sk, signing_sk = alices_keys
|
||||
_receiving_sk, receiving_pk = bobs_keys
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
generate_kfrags(delegating_sk=delegating_sk,
|
||||
signing_sk=signing_sk,
|
||||
receiving_pk=receiving_pk,
|
||||
threshold=threshold,
|
||||
num_kfrags=num_kfrags)
|
|
@ -0,0 +1,203 @@
|
|||
import os
|
||||
import string
|
||||
|
||||
import pytest
|
||||
|
||||
from umbral.keys import PublicKey, SecretKey, SecretKeyFactory, Signature
|
||||
from umbral.hashing import Hash
|
||||
|
||||
|
||||
def test_gen_key():
|
||||
sk = SecretKey.random()
|
||||
assert type(sk) == SecretKey
|
||||
|
||||
pk = PublicKey.from_secret_key(sk)
|
||||
assert type(pk) == PublicKey
|
||||
|
||||
pk2 = PublicKey.from_secret_key(sk)
|
||||
assert pk == pk2
|
||||
|
||||
|
||||
def test_derive_key_from_label():
|
||||
factory = SecretKeyFactory.random()
|
||||
|
||||
label = b"my_healthcare_information"
|
||||
|
||||
sk1 = factory.secret_key_by_label(label)
|
||||
assert type(sk1) == SecretKey
|
||||
|
||||
pk1 = PublicKey.from_secret_key(sk1)
|
||||
assert type(pk1) == PublicKey
|
||||
|
||||
# Check that key derivation is reproducible
|
||||
sk2 = factory.secret_key_by_label(label)
|
||||
pk2 = PublicKey.from_secret_key(sk2)
|
||||
assert sk1 == sk2
|
||||
assert pk1 == pk2
|
||||
|
||||
# Different labels on the same master secret create different keys
|
||||
label = b"my_tax_information"
|
||||
sk3 = factory.secret_key_by_label(label)
|
||||
pk3 = PublicKey.from_secret_key(sk3)
|
||||
assert sk1 != sk3
|
||||
|
||||
|
||||
def test_secret_key_serialization():
|
||||
sk = SecretKey.random()
|
||||
encoded_key = bytes(sk)
|
||||
decoded_key = SecretKey.from_bytes(encoded_key)
|
||||
assert sk == decoded_key
|
||||
|
||||
|
||||
def test_secret_key_str():
|
||||
sk = SecretKey.random()
|
||||
s = str(sk)
|
||||
assert s == "SecretKey:..."
|
||||
|
||||
|
||||
def test_secret_key_hash():
|
||||
sk = SecretKey.random()
|
||||
# Insecure Python hash, shouldn't be available.
|
||||
with pytest.raises(NotImplementedError):
|
||||
hash(sk)
|
||||
|
||||
|
||||
def test_secret_key_factory_str():
|
||||
skf = SecretKeyFactory.random()
|
||||
s = str(skf)
|
||||
assert s == "SecretKeyFactory:..."
|
||||
|
||||
|
||||
def test_secret_key_factory_hash():
|
||||
skf = SecretKeyFactory.random()
|
||||
# Insecure Python hash, shouldn't be available.
|
||||
with pytest.raises(NotImplementedError):
|
||||
hash(skf)
|
||||
|
||||
|
||||
def test_public_key_serialization():
|
||||
sk = SecretKey.random()
|
||||
pk = PublicKey.from_secret_key(sk)
|
||||
|
||||
encoded_key = bytes(pk)
|
||||
decoded_key = PublicKey.from_bytes(encoded_key)
|
||||
assert pk == decoded_key
|
||||
|
||||
|
||||
def test_public_key_point():
|
||||
pk = PublicKey.from_secret_key(SecretKey.random())
|
||||
assert bytes(pk) == bytes(pk.point())
|
||||
|
||||
|
||||
def test_public_key_str():
|
||||
pk = PublicKey.from_secret_key(SecretKey.random())
|
||||
s = str(pk)
|
||||
assert 'PublicKey' in s
|
||||
|
||||
|
||||
def test_keying_material_serialization():
|
||||
factory = SecretKeyFactory.random()
|
||||
|
||||
encoded_factory = bytes(factory)
|
||||
decoded_factory = SecretKeyFactory.from_bytes(encoded_factory)
|
||||
|
||||
label = os.urandom(32)
|
||||
sk1 = factory.secret_key_by_label(label)
|
||||
sk2 = decoded_factory.secret_key_by_label(label)
|
||||
assert sk1 == sk2
|
||||
|
||||
|
||||
def test_public_key_is_hashable():
|
||||
sk = SecretKey.random()
|
||||
pk = PublicKey.from_secret_key(sk)
|
||||
|
||||
sk2 = SecretKey.random()
|
||||
pk2 = PublicKey.from_secret_key(sk2)
|
||||
assert hash(pk) != hash(pk2)
|
||||
|
||||
pk3 = PublicKey.from_bytes(bytes(pk))
|
||||
assert hash(pk) == hash(pk3)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('execution_number', range(20)) # Run this test 20 times.
|
||||
def test_sign_and_verify(execution_number):
|
||||
sk = SecretKey.random()
|
||||
pk = PublicKey.from_secret_key(sk)
|
||||
|
||||
message = b"peace at dawn"
|
||||
dst = b"dst"
|
||||
|
||||
digest = Hash(dst)
|
||||
digest.update(message)
|
||||
signature = sk.sign_digest(digest)
|
||||
|
||||
digest = Hash(dst)
|
||||
digest.update(message)
|
||||
assert signature.verify_digest(pk, digest)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('execution_number', range(20)) # Run this test 20 times.
|
||||
def test_sign_serialize_and_verify(execution_number):
|
||||
sk = SecretKey.random()
|
||||
pk = PublicKey.from_secret_key(sk)
|
||||
|
||||
message = b"peace at dawn"
|
||||
dst = b"dst"
|
||||
|
||||
digest = Hash(dst)
|
||||
digest.update(message)
|
||||
signature = sk.sign_digest(digest)
|
||||
|
||||
signature_bytes = bytes(signature)
|
||||
signature_restored = Signature.from_bytes(signature_bytes)
|
||||
|
||||
digest = Hash(dst)
|
||||
digest.update(message)
|
||||
assert signature_restored.verify_digest(pk, digest)
|
||||
|
||||
|
||||
def test_verification_fail():
|
||||
sk = SecretKey.random()
|
||||
pk = PublicKey.from_secret_key(sk)
|
||||
|
||||
message = b"peace at dawn"
|
||||
dst = b"dst"
|
||||
|
||||
digest = Hash(dst)
|
||||
digest.update(message)
|
||||
signature = sk.sign_digest(digest)
|
||||
|
||||
# wrong DST
|
||||
digest = Hash(b"other dst")
|
||||
digest.update(message)
|
||||
assert not signature.verify_digest(pk, digest)
|
||||
|
||||
# wrong message
|
||||
digest = Hash(dst)
|
||||
digest.update(b"no peace at dawn")
|
||||
assert not signature.verify_digest(pk, digest)
|
||||
|
||||
# bad signature
|
||||
signature_bytes = bytes(signature)
|
||||
signature_bytes = b'\x00' + signature_bytes[1:]
|
||||
signature_restored = Signature.from_bytes(signature_bytes)
|
||||
|
||||
digest = Hash(dst)
|
||||
digest.update(message)
|
||||
assert not signature_restored.verify_digest(pk, digest)
|
||||
|
||||
|
||||
def test_signature_repr():
|
||||
|
||||
sk = SecretKey.random()
|
||||
pk = PublicKey.from_secret_key(sk)
|
||||
|
||||
message = b"peace at dawn"
|
||||
dst = b"dst"
|
||||
|
||||
digest = Hash(dst)
|
||||
digest.update(message)
|
||||
signature = sk.sign_digest(digest)
|
||||
|
||||
s = repr(signature)
|
||||
assert 'Signature' in s
|
|
@ -0,0 +1,95 @@
|
|||
import pytest
|
||||
|
||||
from umbral import (
|
||||
SecretKey,
|
||||
PublicKey,
|
||||
GenericError,
|
||||
encrypt,
|
||||
generate_kfrags,
|
||||
decrypt_original,
|
||||
reencrypt,
|
||||
decrypt_reencrypted,
|
||||
)
|
||||
|
||||
|
||||
def test_public_key_encryption(alices_keys):
|
||||
delegating_sk, _ = alices_keys
|
||||
delegating_pk = PublicKey.from_secret_key(delegating_sk)
|
||||
plaintext = b'peace at dawn'
|
||||
capsule, ciphertext = encrypt(delegating_pk, plaintext)
|
||||
plaintext_decrypted = decrypt_original(delegating_sk, capsule, ciphertext)
|
||||
assert plaintext == plaintext_decrypted
|
||||
|
||||
# Wrong secret key
|
||||
sk = SecretKey.random()
|
||||
with pytest.raises(GenericError):
|
||||
decrypt_original(sk, capsule, ciphertext)
|
||||
|
||||
|
||||
SIMPLE_API_PARAMETERS = (
|
||||
# (num_kfrags, threshold)
|
||||
(1, 1),
|
||||
(6, 1),
|
||||
(6, 4),
|
||||
(6, 6),
|
||||
(50, 30)
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("num_kfrags, threshold", SIMPLE_API_PARAMETERS)
|
||||
def test_simple_api(num_kfrags, threshold):
|
||||
"""
|
||||
This test models the main interactions between actors (i.e., Alice,
|
||||
Bob, Data Source, and Ursulas) and artifacts (i.e., public and private keys,
|
||||
ciphertexts, capsules, KFrags, CFrags, etc).
|
||||
|
||||
The test covers all the main stages of data sharing:
|
||||
key generation, delegation, encryption, decryption by
|
||||
Alice, re-encryption by Ursula, and decryption by Bob.
|
||||
"""
|
||||
|
||||
# Key Generation (Alice)
|
||||
delegating_sk = SecretKey.random()
|
||||
delegating_pk = PublicKey.from_secret_key(delegating_sk)
|
||||
|
||||
signing_sk = SecretKey.random()
|
||||
signing_pk = PublicKey.from_secret_key(signing_sk)
|
||||
|
||||
# Key Generation (Bob)
|
||||
receiving_sk = SecretKey.random()
|
||||
receiving_pk = PublicKey.from_secret_key(receiving_sk)
|
||||
|
||||
# Encryption by an unnamed data source
|
||||
plaintext = b'peace at dawn'
|
||||
capsule, ciphertext = encrypt(delegating_pk, plaintext)
|
||||
|
||||
# Decryption by Alice
|
||||
plaintext_decrypted = decrypt_original(delegating_sk, capsule, ciphertext)
|
||||
assert plaintext_decrypted == plaintext
|
||||
|
||||
# Split Re-Encryption Key Generation (aka Delegation)
|
||||
kfrags = generate_kfrags(delegating_sk, receiving_pk, signing_sk, threshold, num_kfrags)
|
||||
|
||||
# Bob requests re-encryption to some set of M ursulas
|
||||
cfrags = list()
|
||||
for kfrag in kfrags[:threshold]:
|
||||
# Ursula checks that the received kfrag is valid
|
||||
assert kfrag.verify(signing_pk, delegating_pk, receiving_pk)
|
||||
|
||||
# Re-encryption by an Ursula
|
||||
cfrag = reencrypt(capsule, kfrag)
|
||||
|
||||
# Bob collects the result
|
||||
cfrags.append(cfrag)
|
||||
|
||||
# Bob checks that the received cfrags are valid
|
||||
assert all(cfrag.verify(capsule, delegating_pk, receiving_pk, signing_pk) for cfrag in cfrags)
|
||||
|
||||
# Decryption by Bob
|
||||
plaintext_reenc = decrypt_reencrypted(receiving_sk,
|
||||
delegating_pk,
|
||||
capsule,
|
||||
cfrags[:threshold],
|
||||
ciphertext,
|
||||
)
|
||||
|
||||
assert plaintext_reenc == plaintext
|
|
@ -0,0 +1,92 @@
|
|||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from umbral.serializable import Serializable, serialize_bool, take_bool
|
||||
|
||||
|
||||
class A(Serializable):
|
||||
|
||||
def __init__(self, val: int):
|
||||
assert 0 <= val < 2**32
|
||||
self.val = val
|
||||
|
||||
@classmethod
|
||||
def __take__(cls, data):
|
||||
val_bytes, data = cls.__take_bytes__(data, 4)
|
||||
return cls(int.from_bytes(val_bytes, byteorder='big')), data
|
||||
|
||||
def __bytes__(self):
|
||||
return self.val.to_bytes(4, byteorder='big')
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, A) and self.val == other.val
|
||||
|
||||
|
||||
class B(Serializable):
|
||||
|
||||
def __init__(self, val: int):
|
||||
assert 0 <= val < 2**16
|
||||
self.val = val
|
||||
|
||||
@classmethod
|
||||
def __take__(cls, data):
|
||||
val_bytes, data = cls.__take_bytes__(data, 2)
|
||||
return cls(int.from_bytes(val_bytes, byteorder='big')), data
|
||||
|
||||
def __bytes__(self):
|
||||
return self.val.to_bytes(2, byteorder='big')
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, B) and self.val == other.val
|
||||
|
||||
|
||||
class C(Serializable):
|
||||
|
||||
def __init__(self, a: A, b: B):
|
||||
self.a = a
|
||||
self.b = b
|
||||
|
||||
@classmethod
|
||||
def __take__(cls, data):
|
||||
components, data = cls.__take_types__(data, A, B)
|
||||
return cls(*components), data
|
||||
|
||||
def __bytes__(self):
|
||||
return bytes(self.a) + bytes(self.b)
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, C) and self.a == other.a and self.b == other.b
|
||||
|
||||
|
||||
def test_normal_operation():
|
||||
a = A(2**32 - 123)
|
||||
b = B(2**16 - 456)
|
||||
c = C(a, b)
|
||||
c_back = C.from_bytes(bytes(c))
|
||||
assert c_back == c
|
||||
|
||||
|
||||
def test_too_many_bytes():
|
||||
a = A(2**32 - 123)
|
||||
b = B(2**16 - 456)
|
||||
c = C(a, b)
|
||||
with pytest.raises(ValueError, match="1 bytes remaining after deserializing"):
|
||||
C.from_bytes(bytes(c) + b'\x00')
|
||||
|
||||
|
||||
def test_not_enough_bytes():
|
||||
a = A(2**32 - 123)
|
||||
b = B(2**16 - 456)
|
||||
c = C(a, b)
|
||||
# Will happen on deserialization of B - 1 byte missing
|
||||
with pytest.raises(ValueError, match="cannot take 2 bytes from a bytestring of size 1"):
|
||||
C.from_bytes(bytes(c)[:-1])
|
||||
|
||||
|
||||
def test_serialize_bool():
|
||||
assert take_bool(serialize_bool(True) + b'1234') == (True, b'1234')
|
||||
assert take_bool(serialize_bool(False) + b'12') == (False, b'12')
|
||||
error_msg = re.escape("Incorrectly serialized boolean; expected b'\\x00' or b'\\x01', got b'z'")
|
||||
with pytest.raises(ValueError, match=error_msg):
|
||||
take_bool(b'z1234')
|
|
@ -0,0 +1,167 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
from umbral import (
|
||||
Capsule, KeyFrag, CapsuleFrag, SecretKey, PublicKey, encrypt, generate_kfrags, reencrypt)
|
||||
from umbral.curve_scalar import CurveScalar
|
||||
from umbral.curve_point import CurvePoint
|
||||
from umbral.hashing import Hash, unsafe_hash_to_point
|
||||
from umbral.dem import DEM, kdf
|
||||
|
||||
|
||||
def test_scalar_operations():
|
||||
|
||||
vector_file = os.path.join('vectors', 'vectors_scalar_operations.json')
|
||||
try:
|
||||
with open(vector_file) as f:
|
||||
vector_suite = json.load(f)
|
||||
except OSError:
|
||||
raise
|
||||
|
||||
bn1 = CurveScalar.from_bytes(bytes.fromhex(vector_suite['first operand']))
|
||||
bn2 = CurveScalar.from_bytes(bytes.fromhex(vector_suite['second operand']))
|
||||
|
||||
expected = dict()
|
||||
for op_result in vector_suite['vectors']:
|
||||
result = bytes.fromhex(op_result['result'])
|
||||
expected[op_result['operation']] = CurveScalar.from_bytes(result)
|
||||
|
||||
test = [('Addition', bn1 + bn2),
|
||||
('Subtraction', bn1 - bn2),
|
||||
('Multiplication', bn1 * bn2),
|
||||
('Inverse', bn1.invert()),
|
||||
]
|
||||
|
||||
for (operation, result) in test:
|
||||
assert result == expected[operation], 'Error in {}'.format(operation)
|
||||
|
||||
def test_scalar_hash():
|
||||
|
||||
vector_file = os.path.join('vectors', 'vectors_scalar_from_digest.json')
|
||||
try:
|
||||
with open(vector_file) as f:
|
||||
vector_suite = json.load(f)
|
||||
except OSError:
|
||||
raise
|
||||
|
||||
for vector in vector_suite['vectors']:
|
||||
hash_input = [bytes.fromhex(item['bytes']) for item in vector['input']]
|
||||
expected = CurveScalar.from_bytes(bytes.fromhex(vector['output']))
|
||||
|
||||
digest = Hash(b'some_dst')
|
||||
for input_ in hash_input:
|
||||
digest.update(input_)
|
||||
scalar = CurveScalar.from_digest(digest)
|
||||
assert scalar == expected
|
||||
|
||||
|
||||
def test_point_operations():
|
||||
|
||||
vector_file = os.path.join('vectors', 'vectors_point_operations.json')
|
||||
try:
|
||||
with open(vector_file) as f:
|
||||
vector_suite = json.load(f)
|
||||
except OSError:
|
||||
raise
|
||||
|
||||
point1 = CurvePoint.from_bytes(bytes.fromhex(vector_suite['first CurvePoint operand']))
|
||||
point2 = CurvePoint.from_bytes(bytes.fromhex(vector_suite['second CurvePoint operand']))
|
||||
bn1 = CurveScalar.from_bytes(bytes.fromhex(vector_suite['CurveScalar operand']))
|
||||
|
||||
expected = dict()
|
||||
for op_result in vector_suite['vectors']:
|
||||
expected[op_result['operation']] = bytes.fromhex(op_result['result'])
|
||||
|
||||
test = [('Addition', point1 + point2),
|
||||
('Subtraction', point1 - point2),
|
||||
('Multiplication', point1 * bn1),
|
||||
('Inversion', -point1),
|
||||
]
|
||||
|
||||
for (operation, result) in test:
|
||||
assert result == CurvePoint.from_bytes(expected[operation]), 'Error in {}'.format(operation)
|
||||
|
||||
test = [('To_affine.X', point1.to_affine()[0]),
|
||||
('To_affine.Y', point1.to_affine()[1]),
|
||||
]
|
||||
|
||||
for (operation, result) in test:
|
||||
assert result == int.from_bytes(expected[operation], 'big'), 'Error in {}'.format(operation)
|
||||
|
||||
assert kdf(bytes(point1), DEM.KEY_SIZE) == expected['kdf']
|
||||
|
||||
|
||||
def test_unsafe_hash_to_point():
|
||||
|
||||
vector_file = os.path.join('vectors', 'vectors_unsafe_hash_to_point.json')
|
||||
try:
|
||||
with open(vector_file) as f:
|
||||
vector_suite = json.load(f)
|
||||
except OSError:
|
||||
raise
|
||||
|
||||
for item in vector_suite['vectors']:
|
||||
data = bytes.fromhex(item['data'])
|
||||
dst = bytes.fromhex(item['dst'])
|
||||
expected = CurvePoint.from_bytes(bytes.fromhex(item['point']))
|
||||
assert expected == unsafe_hash_to_point(dst=dst, data=data)
|
||||
|
||||
|
||||
def test_kfrags():
|
||||
|
||||
vector_file = os.path.join('vectors', 'vectors_kfrags.json')
|
||||
try:
|
||||
with open(vector_file) as f:
|
||||
vector_suite = json.load(f)
|
||||
except OSError:
|
||||
raise
|
||||
|
||||
verifying_pk = PublicKey.from_bytes(bytes.fromhex(vector_suite['verifying_pk']))
|
||||
delegating_pk = PublicKey.from_bytes(bytes.fromhex(vector_suite['delegating_pk']))
|
||||
receiving_pk = PublicKey.from_bytes(bytes.fromhex(vector_suite['receiving_pk']))
|
||||
|
||||
for json_kfrag in vector_suite['vectors']:
|
||||
kfrag = KeyFrag.from_bytes(bytes.fromhex(json_kfrag['kfrag']))
|
||||
assert kfrag.verify(signing_pk=verifying_pk,
|
||||
delegating_pk=delegating_pk,
|
||||
receiving_pk=receiving_pk), \
|
||||
'Invalid KeyFrag {}'.format(bytes(kfrag).hex())
|
||||
|
||||
|
||||
def test_cfrags():
|
||||
|
||||
vector_file = os.path.join('vectors', 'vectors_cfrags.json')
|
||||
try:
|
||||
with open(vector_file) as f:
|
||||
vector_suite = json.load(f)
|
||||
except OSError:
|
||||
raise
|
||||
|
||||
capsule = Capsule.from_bytes(bytes.fromhex(vector_suite['capsule']))
|
||||
|
||||
verifying_pk = PublicKey.from_bytes(bytes.fromhex(vector_suite['verifying_pk']))
|
||||
delegating_pk = PublicKey.from_bytes(bytes.fromhex(vector_suite['delegating_pk']))
|
||||
receiving_pk = PublicKey.from_bytes(bytes.fromhex(vector_suite['receiving_pk']))
|
||||
|
||||
kfrags_n_cfrags = [(KeyFrag.from_bytes(bytes.fromhex(json_kfrag['kfrag'])),
|
||||
CapsuleFrag.from_bytes(bytes.fromhex(json_kfrag['cfrag'])))
|
||||
for json_kfrag in vector_suite['vectors']]
|
||||
|
||||
metadata = bytes.fromhex(vector_suite['metadata'])
|
||||
|
||||
for kfrag, cfrag in kfrags_n_cfrags:
|
||||
assert kfrag.verify(signing_pk=verifying_pk,
|
||||
delegating_pk=delegating_pk,
|
||||
receiving_pk=receiving_pk), \
|
||||
'Invalid KeyFrag {}'.format(bytes(kfrag.to_bytes).hex())
|
||||
|
||||
new_cfrag = reencrypt(capsule, kfrag, metadata=metadata)
|
||||
assert new_cfrag.point_e1 == cfrag.point_e1
|
||||
assert new_cfrag.point_v1 == cfrag.point_v1
|
||||
assert new_cfrag.kfrag_id == cfrag.kfrag_id
|
||||
assert new_cfrag.precursor == cfrag.precursor
|
||||
assert new_cfrag.verify(capsule,
|
||||
signing_pk=verifying_pk,
|
||||
delegating_pk=delegating_pk,
|
||||
receiving_pk=receiving_pk,
|
||||
metadata=metadata)
|
|
@ -1,104 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.cfrags import CapsuleFrag
|
||||
from umbral.keys import UmbralPrivateKey
|
||||
from umbral.point import Point
|
||||
from umbral.pre import Capsule
|
||||
from umbral.config import default_params
|
||||
|
||||
|
||||
def test_cannot_attach_cfrag_without_keys():
|
||||
"""
|
||||
We need the proper keys to verify the correctness of CFrags
|
||||
in order to attach them to a Capsule.
|
||||
"""
|
||||
params = default_params()
|
||||
|
||||
capsule = Capsule(params,
|
||||
point_e=Point.gen_rand(),
|
||||
point_v=Point.gen_rand(),
|
||||
bn_sig=CurveBN.gen_rand())
|
||||
|
||||
cfrag = CapsuleFrag(point_e1=Point.gen_rand(),
|
||||
point_v1=Point.gen_rand(),
|
||||
kfrag_id=os.urandom(10),
|
||||
point_precursor=Point.gen_rand(),
|
||||
)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
capsule.attach_cfrag(cfrag)
|
||||
|
||||
|
||||
def test_cannot_attach_cfrag_without_proof():
|
||||
"""
|
||||
However, even when properly attaching keys, we can't attach the CFrag
|
||||
if it is unproven.
|
||||
"""
|
||||
params = default_params()
|
||||
|
||||
capsule = Capsule(params,
|
||||
point_e=Point.gen_rand(),
|
||||
point_v=Point.gen_rand(),
|
||||
bn_sig=CurveBN.gen_rand())
|
||||
|
||||
cfrag = CapsuleFrag(point_e1=Point.gen_rand(),
|
||||
point_v1=Point.gen_rand(),
|
||||
kfrag_id=os.urandom(10),
|
||||
point_precursor=Point.gen_rand(),
|
||||
)
|
||||
key_details = capsule.set_correctness_keys(
|
||||
UmbralPrivateKey.gen_key().get_pubkey(),
|
||||
UmbralPrivateKey.gen_key().get_pubkey(),
|
||||
UmbralPrivateKey.gen_key().get_pubkey())
|
||||
|
||||
delegating_details, receiving_details, verifying_details = key_details
|
||||
|
||||
assert all((delegating_details, receiving_details, verifying_details))
|
||||
|
||||
with pytest.raises(cfrag.NoProofProvided):
|
||||
capsule.attach_cfrag(cfrag)
|
||||
|
||||
|
||||
def test_cannot_set_different_keys():
|
||||
"""
|
||||
Once a key is set on a Capsule, it can't be changed to a different key.
|
||||
"""
|
||||
params = default_params()
|
||||
|
||||
capsule = Capsule(params,
|
||||
point_e=Point.gen_rand(),
|
||||
point_v=Point.gen_rand(),
|
||||
bn_sig=CurveBN.gen_rand())
|
||||
|
||||
capsule.set_correctness_keys(delegating=UmbralPrivateKey.gen_key().get_pubkey(),
|
||||
receiving=UmbralPrivateKey.gen_key().get_pubkey(),
|
||||
verifying=UmbralPrivateKey.gen_key().get_pubkey())
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
capsule.set_correctness_keys(delegating=UmbralPrivateKey.gen_key().get_pubkey())
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
capsule.set_correctness_keys(receiving=UmbralPrivateKey.gen_key().get_pubkey())
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
capsule.set_correctness_keys(verifying=UmbralPrivateKey.gen_key().get_pubkey())
|
|
@ -1,135 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from umbral import pre
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.point import Point
|
||||
from umbral.pre import Capsule
|
||||
from umbral.signing import Signer
|
||||
from umbral.keys import UmbralPrivateKey
|
||||
from umbral.config import default_params
|
||||
|
||||
|
||||
def test_capsule_creation(alices_keys):
|
||||
|
||||
params = default_params()
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
rare_capsule = Capsule(params) # Alice cannot make a capsule this way.
|
||||
|
||||
|
||||
|
||||
# Some users may create capsules their own way.
|
||||
custom_capsule = Capsule(params,
|
||||
point_e=Point.gen_rand(),
|
||||
point_v=Point.gen_rand(),
|
||||
bn_sig=CurveBN.gen_rand())
|
||||
|
||||
assert isinstance(custom_capsule, Capsule)
|
||||
|
||||
# Typical Alice, constructing a typical capsule
|
||||
delegating_privkey, _signing_key = alices_keys
|
||||
plaintext = b'peace at dawn'
|
||||
ciphertext, typical_capsule = pre.encrypt(delegating_privkey.get_pubkey(), plaintext)
|
||||
|
||||
assert isinstance(typical_capsule, Capsule)
|
||||
|
||||
|
||||
def test_capsule_equality():
|
||||
params = default_params()
|
||||
|
||||
one_capsule = Capsule(params,
|
||||
point_e=Point.gen_rand(),
|
||||
point_v=Point.gen_rand(),
|
||||
bn_sig=CurveBN.gen_rand())
|
||||
|
||||
another_capsule = Capsule(params,
|
||||
point_e=Point.gen_rand(),
|
||||
point_v=Point.gen_rand(),
|
||||
bn_sig=CurveBN.gen_rand())
|
||||
|
||||
assert one_capsule != another_capsule
|
||||
|
||||
|
||||
def test_decapsulation_by_alice(alices_keys):
|
||||
params = default_params()
|
||||
|
||||
delegating_privkey, _signing_privkey = alices_keys
|
||||
|
||||
sym_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey())
|
||||
assert len(sym_key) == 32
|
||||
|
||||
# The symmetric key sym_key is perhaps used for block cipher here in a real-world scenario.
|
||||
sym_key_2 = pre._decapsulate_original(delegating_privkey, capsule)
|
||||
assert sym_key_2 == sym_key
|
||||
|
||||
|
||||
def test_bad_capsule_fails_reencryption(kfrags):
|
||||
params = default_params()
|
||||
|
||||
bollocks_capsule = Capsule(params,
|
||||
point_e=Point.gen_rand(),
|
||||
point_v=Point.gen_rand(),
|
||||
bn_sig=CurveBN.gen_rand())
|
||||
|
||||
for kfrag in kfrags:
|
||||
with pytest.raises(Capsule.NotValid):
|
||||
pre.reencrypt(kfrag, bollocks_capsule)
|
||||
|
||||
|
||||
def test_capsule_as_dict_key(alices_keys, bobs_keys):
|
||||
delegating_privkey, signing_privkey = alices_keys
|
||||
signer_alice = Signer(signing_privkey)
|
||||
delegating_pubkey = delegating_privkey.get_pubkey()
|
||||
signing_pubkey = signing_privkey.get_pubkey()
|
||||
|
||||
receiving_privkey, receiving_pubkey = bobs_keys
|
||||
|
||||
plain_data = b'peace at dawn'
|
||||
ciphertext, capsule = pre.encrypt(delegating_pubkey, plain_data)
|
||||
|
||||
# We can use the capsule as a key, and successfully lookup using it.
|
||||
some_dict = {capsule: "Thing that Bob wants to try per-Capsule"}
|
||||
assert some_dict[capsule] == "Thing that Bob wants to try per-Capsule"
|
||||
|
||||
# And if we change the value for this key, all is still well.
|
||||
some_dict[capsule] = "Bob has changed his mind."
|
||||
assert some_dict[capsule] == "Bob has changed his mind."
|
||||
assert len(some_dict.keys()) == 1
|
||||
|
||||
|
||||
def test_capsule_length(prepared_capsule, kfrags):
|
||||
capsule = prepared_capsule
|
||||
|
||||
for counter, kfrag in enumerate(kfrags):
|
||||
assert len(capsule) == counter
|
||||
cfrag = pre.reencrypt(kfrag, capsule)
|
||||
capsule.attach_cfrag(cfrag)
|
||||
|
||||
|
||||
def test_capsule_clear(prepared_capsule, kfrags):
|
||||
capsule = prepared_capsule
|
||||
|
||||
for counter, kfrag in enumerate(kfrags):
|
||||
assert len(capsule) == counter
|
||||
cfrag = pre.reencrypt(kfrag, capsule)
|
||||
capsule.attach_cfrag(cfrag)
|
||||
|
||||
capsule.clear_cfrags()
|
||||
assert len(capsule) == 0
|
|
@ -1,63 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from umbral.pre import Capsule
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.point import Point
|
||||
|
||||
|
||||
def test_capsule_serialization(capsule: Capsule):
|
||||
params = capsule.params
|
||||
capsule_bytes = capsule.to_bytes()
|
||||
capsule_bytes_casted = bytes(capsule)
|
||||
assert capsule_bytes == capsule_bytes_casted
|
||||
|
||||
# A Capsule can be represented as the 98 total bytes of two Points (33 each) and a CurveBN (32).
|
||||
assert len(capsule_bytes) == Capsule.expected_bytes_length()
|
||||
|
||||
new_capsule = Capsule.from_bytes(capsule_bytes, params)
|
||||
|
||||
# Three ways to think about equality.
|
||||
# First, the public approach for the Capsule. Simply:
|
||||
assert new_capsule == capsule
|
||||
|
||||
# Second, we show that the original components (which is all we have here since we haven't activated) are the same:
|
||||
assert new_capsule.components() == capsule.components()
|
||||
|
||||
# Third, we can directly compare the private original component attributes
|
||||
# (though this is not a supported approach):
|
||||
assert new_capsule.point_e == capsule.point_e
|
||||
assert new_capsule.point_v == capsule.point_v
|
||||
assert new_capsule.bn_sig == capsule.bn_sig
|
||||
|
||||
|
||||
def test_cannot_create_capsule_from_bogus_material(alices_keys):
|
||||
params = alices_keys[0].params
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
_capsule_of_questionable_parentage = Capsule(params,
|
||||
point_e=Point.gen_rand(),
|
||||
point_v=42,
|
||||
bn_sig=CurveBN.gen_rand())
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
_capsule_of_questionable_parentage = Capsule(params,
|
||||
point_e=Point.gen_rand(),
|
||||
point_v=Point.gen_rand(),
|
||||
bn_sig=42)
|
|
@ -1,126 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from umbral import pre
|
||||
from umbral.cfrags import CapsuleFrag, CorrectnessProof
|
||||
|
||||
|
||||
def test_cfrag_serialization_with_proof_and_metadata(prepared_capsule, kfrags):
|
||||
|
||||
# Example of potential metadata to describe the re-encryption request
|
||||
metadata = b'This is an example of metadata for re-encryption request'
|
||||
for kfrag in kfrags:
|
||||
cfrag = pre.reencrypt(kfrag, prepared_capsule, provide_proof=True, metadata=metadata)
|
||||
cfrag_bytes = cfrag.to_bytes()
|
||||
|
||||
proof = cfrag.proof
|
||||
assert proof is not None
|
||||
assert proof.metadata is not None
|
||||
|
||||
new_cfrag = CapsuleFrag.from_bytes(cfrag_bytes)
|
||||
assert new_cfrag.point_e1 == cfrag.point_e1
|
||||
assert new_cfrag.point_v1 == cfrag.point_v1
|
||||
assert new_cfrag.kfrag_id == cfrag.kfrag_id
|
||||
assert new_cfrag.point_precursor == cfrag.point_precursor
|
||||
|
||||
new_proof = new_cfrag.proof
|
||||
assert new_proof is not None
|
||||
assert new_proof.point_e2 == proof.point_e2
|
||||
assert new_proof.point_v2 == proof.point_v2
|
||||
assert new_proof.point_kfrag_commitment == proof.point_kfrag_commitment
|
||||
assert new_proof.point_kfrag_pok == proof.point_kfrag_pok
|
||||
assert new_proof.bn_sig == proof.bn_sig
|
||||
assert new_proof.metadata == metadata
|
||||
assert new_proof.metadata == proof.metadata
|
||||
|
||||
|
||||
def test_cfrag_serialization_with_proof_but_no_metadata(prepared_capsule, kfrags):
|
||||
|
||||
for kfrag in kfrags:
|
||||
cfrag = pre.reencrypt(kfrag, prepared_capsule, provide_proof=True)
|
||||
cfrag_bytes = cfrag.to_bytes()
|
||||
|
||||
proof = cfrag.proof
|
||||
assert proof is not None
|
||||
assert proof.metadata is None
|
||||
|
||||
# A CFrag can be represented as the 131 total bytes of three Points (33 each) and a CurveBN (32).
|
||||
# TODO: Figure out final size for CFrags with proofs
|
||||
# assert len(cfrag_bytes) == 33 + 33 + 33 + 32 == 131
|
||||
|
||||
new_cfrag = CapsuleFrag.from_bytes(cfrag_bytes)
|
||||
assert new_cfrag.point_e1 == cfrag.point_e1
|
||||
assert new_cfrag.point_v1 == cfrag.point_v1
|
||||
assert new_cfrag.kfrag_id == cfrag.kfrag_id
|
||||
assert new_cfrag.point_precursor == cfrag.point_precursor
|
||||
|
||||
new_proof = new_cfrag.proof
|
||||
assert new_proof is not None
|
||||
assert new_proof.point_e2 == proof.point_e2
|
||||
assert new_proof.point_v2 == proof.point_v2
|
||||
assert new_proof.point_kfrag_commitment == proof.point_kfrag_commitment
|
||||
assert new_proof.point_kfrag_pok == proof.point_kfrag_pok
|
||||
assert new_proof.bn_sig == proof.bn_sig
|
||||
assert new_proof.metadata is None
|
||||
|
||||
|
||||
def test_cfrag_serialization_no_proof_no_metadata(prepared_capsule, kfrags):
|
||||
|
||||
for kfrag in kfrags:
|
||||
cfrag = pre.reencrypt(kfrag, prepared_capsule, provide_proof=False)
|
||||
cfrag_bytes = cfrag.to_bytes()
|
||||
|
||||
proof = cfrag.proof
|
||||
assert proof is None
|
||||
|
||||
assert len(cfrag_bytes) == CapsuleFrag.expected_bytes_length()
|
||||
|
||||
new_cfrag = CapsuleFrag.from_bytes(cfrag_bytes)
|
||||
assert new_cfrag.point_e1 == cfrag.point_e1
|
||||
assert new_cfrag.point_v1 == cfrag.point_v1
|
||||
assert new_cfrag.kfrag_id == cfrag.kfrag_id
|
||||
assert new_cfrag.point_precursor == cfrag.point_precursor
|
||||
|
||||
new_proof = new_cfrag.proof
|
||||
assert new_proof is None
|
||||
|
||||
|
||||
def test_correctness_proof_serialization(prepared_capsule, kfrags):
|
||||
|
||||
# Example of potential metadata to describe the re-encryption request
|
||||
metadata = b"This is an example of metadata for re-encryption request"
|
||||
|
||||
for kfrag in kfrags:
|
||||
cfrag = pre.reencrypt(kfrag, prepared_capsule, metadata=metadata)
|
||||
proof = cfrag.proof
|
||||
proof_bytes = proof.to_bytes()
|
||||
|
||||
# A CorrectnessProof can be represented as
|
||||
# the 228 total bytes of four Points (33 each) and three BigNums (32 each).
|
||||
# TODO: Figure out final size for CorrectnessProofs
|
||||
# assert len(proof_bytes) == (33 * 4) + (32 * 3) == 228
|
||||
|
||||
new_proof = CorrectnessProof.from_bytes(proof_bytes)
|
||||
assert new_proof.point_e2 == proof.point_e2
|
||||
assert new_proof.point_v2 == proof.point_v2
|
||||
assert new_proof.point_kfrag_commitment == proof.point_kfrag_commitment
|
||||
assert new_proof.point_kfrag_pok == proof.point_kfrag_pok
|
||||
assert new_proof.bn_sig == proof.bn_sig
|
||||
assert new_proof.kfrag_signature == proof.kfrag_signature
|
||||
assert new_proof.metadata == proof.metadata
|
||||
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import importlib
|
||||
import pytest
|
||||
import warnings
|
||||
|
||||
from umbral.config import _CONFIG
|
||||
from umbral.curve import SECP256K1, SECP256R1
|
||||
|
||||
|
||||
def _copy_config_for_testing():
|
||||
"""
|
||||
NEVER do this. This is for testing only.
|
||||
This is absolutely not a thing to actually do in production code. At all. Ever.
|
||||
"""
|
||||
config_module_spec = importlib.util.find_spec("umbral.config")
|
||||
config_copy = importlib.util.module_from_spec(config_module_spec)
|
||||
config_module_spec.loader.exec_module(config_copy)
|
||||
assert hasattr(config_copy, "default_curve")
|
||||
assert config_copy is not _CONFIG
|
||||
return config_copy
|
||||
|
||||
|
||||
def test_try_to_use_curve_with_no_default_curve():
|
||||
config = _copy_config_for_testing()
|
||||
|
||||
# No curve is set.
|
||||
assert config._CONFIG._CONFIG__curve is None
|
||||
|
||||
# Getting the default curve if we haven't set one yet sets one and gives us a warning.
|
||||
with warnings.catch_warnings(record=True) as caught_warnings:
|
||||
assert len(caught_warnings) == 0
|
||||
config.default_curve()
|
||||
assert len(caught_warnings) == 1
|
||||
assert caught_warnings[0].message.args[0] == config._CONFIG._CONFIG__WARNING_IF_NO_DEFAULT_SET
|
||||
assert caught_warnings[0].category == RuntimeWarning
|
||||
|
||||
# Now, a default curve has been set.
|
||||
assert config._CONFIG._CONFIG__curve == SECP256K1
|
||||
|
||||
|
||||
def test_try_to_use_default_params_with_no_default_curve():
|
||||
config = _copy_config_for_testing()
|
||||
|
||||
# Again, no curve is set.
|
||||
assert config._CONFIG._CONFIG__curve is None
|
||||
|
||||
# This time, we'll try to use default_params() and get the same warning as above.
|
||||
with warnings.catch_warnings(record=True) as caught_warnings:
|
||||
assert len(caught_warnings) == 0
|
||||
config.default_params()
|
||||
assert len(caught_warnings) == 1
|
||||
assert caught_warnings[0].message.args[0] == config._CONFIG._CONFIG__WARNING_IF_NO_DEFAULT_SET
|
||||
assert caught_warnings[0].category == RuntimeWarning
|
||||
|
||||
# Now, a default curve has been set.
|
||||
assert config._CONFIG._CONFIG__curve == SECP256K1
|
||||
|
||||
|
||||
def test_cannot_set_default_curve_twice():
|
||||
config = _copy_config_for_testing()
|
||||
|
||||
# pyumbral even supports NIST curves!
|
||||
config.set_default_curve(SECP256R1)
|
||||
|
||||
# Our default curve has been set...
|
||||
assert config.default_curve() == SECP256R1
|
||||
|
||||
# ...but once set, you can't set the default curve again, even if you've found a better one.
|
||||
with pytest.raises(config._CONFIG.UmbralConfigurationError):
|
||||
config.set_default_curve(SECP256K1)
|
|
@ -1,82 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from umbral.curve import Curve
|
||||
|
||||
|
||||
def test_supported_curves():
|
||||
|
||||
# Ensure we have the correct number opf supported curves hardcoded
|
||||
number_of_supported_curves = 3
|
||||
assert len(Curve._supported_curves) == number_of_supported_curves
|
||||
|
||||
# Manually ensure the `_supported curves` dict contains only valid supported curves
|
||||
assert Curve._supported_curves[415] == 'secp256r1'
|
||||
assert Curve._supported_curves[714] == 'secp256k1'
|
||||
assert Curve._supported_curves[715] == 'secp384r1'
|
||||
|
||||
nid, name = 714, 'secp256k1'
|
||||
|
||||
#
|
||||
# Create by NID
|
||||
#
|
||||
|
||||
# supported
|
||||
_curve_714 = Curve(nid=nid)
|
||||
assert _curve_714.curve_nid == nid
|
||||
assert _curve_714.name == name
|
||||
|
||||
# unsuported
|
||||
with pytest.raises(NotImplementedError):
|
||||
_ = Curve(711)
|
||||
|
||||
|
||||
#
|
||||
# Create by Name
|
||||
#
|
||||
|
||||
# Supported
|
||||
_curve_secp256k1 = Curve.from_name(name)
|
||||
assert _curve_secp256k1.name == name
|
||||
assert _curve_secp256k1.curve_nid == nid
|
||||
|
||||
# Unsupported
|
||||
with pytest.raises(NotImplementedError):
|
||||
_ = Curve.from_name('abcd123e4')
|
||||
|
||||
# Import curve constants
|
||||
from umbral.curve import SECP256R1, SECP256K1, SECP384R1
|
||||
test_p256 = SECP256R1
|
||||
test_secp256k1 = SECP256K1
|
||||
test_p384 = SECP384R1
|
||||
|
||||
# Test the hardcoded curve NIDs are correct:
|
||||
assert test_p256.curve_nid == 415
|
||||
assert test_secp256k1.curve_nid == 714
|
||||
assert test_p384.curve_nid == 715
|
||||
|
||||
# Ensure every curve constant is in the CURVES collection
|
||||
from umbral.curve import CURVES
|
||||
assert len(CURVES) == number_of_supported_curves
|
||||
|
||||
# Ensure all supported curves can be initialized
|
||||
for nid, name in Curve._supported_curves.items():
|
||||
_curve_nid, _curve_name = Curve(nid=nid), Curve.from_name(name)
|
||||
assert _curve_nid.name == name
|
||||
assert _curve_name.curve_nid == nid
|
|
@ -1,67 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from umbral.kfrags import KFrag
|
||||
|
||||
|
||||
def test_kfrag_serialization(alices_keys, bobs_keys, kfrags):
|
||||
|
||||
delegating_privkey, signing_privkey = alices_keys
|
||||
_receiving_privkey, receiving_pubkey = bobs_keys
|
||||
|
||||
for kfrag in kfrags:
|
||||
kfrag_bytes = kfrag.to_bytes()
|
||||
assert len(kfrag_bytes) == KFrag.expected_bytes_length()
|
||||
|
||||
new_kfrag = KFrag.from_bytes(kfrag_bytes)
|
||||
assert new_kfrag.id == kfrag.id
|
||||
assert new_kfrag.bn_key == kfrag.bn_key
|
||||
assert new_kfrag.point_precursor == kfrag.point_precursor
|
||||
assert new_kfrag.point_commitment == kfrag.point_commitment
|
||||
assert new_kfrag.keys_in_signature == kfrag.keys_in_signature
|
||||
assert new_kfrag.signature_for_proxy == kfrag.signature_for_proxy
|
||||
assert new_kfrag.signature_for_bob == kfrag.signature_for_bob
|
||||
|
||||
assert new_kfrag.verify(signing_pubkey=signing_privkey.get_pubkey(),
|
||||
delegating_pubkey=delegating_privkey.get_pubkey(),
|
||||
receiving_pubkey=receiving_pubkey)
|
||||
|
||||
assert new_kfrag == kfrag
|
||||
|
||||
|
||||
def test_kfrag_verify_for_capsule(prepared_capsule, kfrags):
|
||||
for kfrag in kfrags:
|
||||
assert kfrag.verify_for_capsule(prepared_capsule)
|
||||
|
||||
# If we alter some element, the verification fails
|
||||
previous_id, kfrag.id = kfrag.id, bytes(32)
|
||||
assert not kfrag.verify_for_capsule(prepared_capsule)
|
||||
|
||||
# Let's restore the KFrag, and alter the re-encryption key instead
|
||||
kfrag.id = previous_id
|
||||
kfrag.bn_key += kfrag.bn_key
|
||||
assert not kfrag.verify_for_capsule(prepared_capsule)
|
||||
|
||||
|
||||
def test_kfrag_as_dict_key(kfrags):
|
||||
dict_with_kfrags_as_keys = dict()
|
||||
dict_with_kfrags_as_keys[kfrags[0]] = "Some llamas. Definitely some llamas."
|
||||
dict_with_kfrags_as_keys[kfrags[1]] = "No llamas here. Definitely not."
|
||||
|
||||
assert dict_with_kfrags_as_keys[kfrags[0]] != dict_with_kfrags_as_keys[kfrags[1]]
|
||||
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
|
||||
import pytest
|
||||
from cryptography.hazmat.backends.openssl import backend
|
||||
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.point import Point
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def mock_openssl(mocker, random_ec_point1: Point, random_ec_curvebn1: CurveBN, random_ec_curvebn2: CurveBN):
|
||||
"""
|
||||
Patches openssl backend methods for testing.
|
||||
For all functions, 1 is returned for success, 0 on error.
|
||||
|
||||
"""
|
||||
|
||||
actual_backend = {
|
||||
# Point
|
||||
'EC_POINT_mul': backend._lib.EC_POINT_mul,
|
||||
'EC_POINT_cmp': backend._lib.EC_POINT_cmp,
|
||||
'EC_POINT_add': backend._lib.EC_POINT_add,
|
||||
'EC_POINT_invert': backend._lib.EC_POINT_invert,
|
||||
|
||||
# Bignum
|
||||
'BN_cmp': backend._lib.BN_cmp,
|
||||
'BN_mod_exp': backend._lib.BN_mod_exp,
|
||||
'BN_mod_mul': backend._lib.BN_mod_mul,
|
||||
'BN_mod_inverse': backend._lib.BN_mod_inverse,
|
||||
'BN_mod_add': backend._lib.BN_mod_add,
|
||||
'BN_mod_sub': backend._lib.BN_mod_sub,
|
||||
'BN_nnmod': backend._lib.BN_nnmod,
|
||||
}
|
||||
|
||||
def check_curvebn_ctypes(*curvebns):
|
||||
for bn in curvebns:
|
||||
assert 'BIGNUM' in str(bn)
|
||||
assert bn.__class__.__name__ == 'CDataGCP'
|
||||
|
||||
def check_point_ctypes(*ec_points):
|
||||
for point in ec_points:
|
||||
assert 'EC_POINT' in str(point)
|
||||
assert point.__class__.__name__ == 'CDataGCP'
|
||||
|
||||
@contextlib.contextmanager
|
||||
def mocked_openssl_backend():
|
||||
def mocked_ec_point_equality(group, ec_point, other_point, context):
|
||||
check_point_ctypes(ec_point, other_point)
|
||||
assert 'BN_CTX' in str(context)
|
||||
assert 'EC_GROUP' in str(group)
|
||||
assert random_ec_point1.curve.ec_group == group
|
||||
assert not bool(actual_backend['EC_POINT_cmp'](group, random_ec_point1.ec_point, ec_point, context))
|
||||
result = actual_backend['EC_POINT_cmp'](group, random_ec_point1.ec_point, other_point, context)
|
||||
assert not bool(result)
|
||||
return result
|
||||
|
||||
def mocked_ec_point_addition(group, sum, ec_point, other_point, context):
|
||||
check_point_ctypes(sum, other_point)
|
||||
assert 'BN_CTX' in str(context)
|
||||
assert random_ec_point1.group == group
|
||||
assert not bool(actual_backend['EC_POINT_cmp'](group, random_ec_point1.ec_point, ec_point, context))
|
||||
return actual_backend['EC_POINT_add'](group, sum, ec_point, other_point, context)
|
||||
|
||||
def mocked_ec_point_multiplication(group, product, null, ec_point, curvebn, context):
|
||||
check_point_ctypes(ec_point, product)
|
||||
assert 'BN_CTX' in str(context)
|
||||
assert 'EC_GROUP' in str(group)
|
||||
assert 'NULL' in str(null)
|
||||
assert random_ec_point1.group == group
|
||||
assert random_ec_curvebn1.curve_nid == random_ec_point1.curve_nid
|
||||
assert not bool(actual_backend['EC_POINT_cmp'](group, random_ec_point1.ec_point, ec_point, context))
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
|
||||
return actual_backend['EC_POINT_mul'](group, product, null, ec_point, curvebn, context)
|
||||
|
||||
def mocked_ec_point_inversion(group, inverse, context):
|
||||
check_point_ctypes(inverse)
|
||||
assert 'BN_CTX' in str(context)
|
||||
assert random_ec_point1.group == group
|
||||
return actual_backend['EC_POINT_invert'](group, inverse, context)
|
||||
|
||||
def mocked_bn_compare(curvebn, other):
|
||||
check_curvebn_ctypes(curvebn, other)
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
|
||||
return actual_backend['BN_cmp'](curvebn, other)
|
||||
|
||||
def mocked_bn_mod_exponent(power, curvebn, other, order, context):
|
||||
check_curvebn_ctypes(curvebn, other, power, order)
|
||||
assert 'BN_CTX' in str(context)
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn2.bignum, other))
|
||||
return actual_backend['BN_mod_exp'](power, curvebn, other, order, context)
|
||||
|
||||
def mocked_bn_mod_multiplication(product, curvebn, other, order, context):
|
||||
check_curvebn_ctypes(curvebn, other, product, order)
|
||||
assert 'BN_CTX' in str(context)
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn2.bignum, other))
|
||||
return actual_backend['BN_mod_mul'](product, curvebn, other, order, context)
|
||||
|
||||
def mocked_bn_inverse(null, curvebn, order, context):
|
||||
check_curvebn_ctypes(curvebn, order)
|
||||
assert 'BN_CTX' in str(context)
|
||||
assert 'NULL' in str(null)
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
|
||||
return actual_backend['BN_mod_inverse'](null, curvebn, order, context)
|
||||
|
||||
def mocked_bn_addition(sum, curvebn, other, order, context):
|
||||
check_curvebn_ctypes(curvebn, other, sum, order)
|
||||
assert 'BN_CTX' in str(context)
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn2.bignum, other))
|
||||
return actual_backend['BN_mod_add'](sum, curvebn, other, order, context)
|
||||
|
||||
def mocked_bn_subtraction(diff, curvebn, other, order, context):
|
||||
check_curvebn_ctypes(curvebn, other, diff, order)
|
||||
assert 'BN_CTX' in str(context)
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn2.bignum, other))
|
||||
return actual_backend['BN_mod_sub'](diff, curvebn, other, order, context)
|
||||
|
||||
def mocked_bn_nmodulus(rem, curvebn, other, context):
|
||||
check_curvebn_ctypes(curvebn, other, rem)
|
||||
assert 'BN_CTX' in str(context)
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn2.bignum, other))
|
||||
return actual_backend['BN_nnmod'](rem, curvebn, other, context)
|
||||
|
||||
mock_load = {
|
||||
# Point
|
||||
'EC_POINT_mul': mocked_ec_point_multiplication,
|
||||
'EC_POINT_cmp': mocked_ec_point_equality,
|
||||
'EC_POINT_add': mocked_ec_point_addition,
|
||||
'EC_POINT_invert': mocked_ec_point_inversion,
|
||||
|
||||
# Bignum
|
||||
'BN_cmp': mocked_bn_compare,
|
||||
'BN_mod_exp': mocked_bn_mod_exponent,
|
||||
'BN_mod_mul': mocked_bn_mod_multiplication,
|
||||
'BN_mod_inverse': mocked_bn_inverse,
|
||||
'BN_mod_add': mocked_bn_addition,
|
||||
'BN_mod_sub': mocked_bn_subtraction,
|
||||
'BN_nnmod': mocked_bn_nmodulus,
|
||||
}
|
||||
|
||||
with contextlib.ExitStack() as stack:
|
||||
for method, patch in mock_load.items():
|
||||
stack.enter_context(mocker.mock_module.patch.object(backend._lib, method, patch))
|
||||
yield
|
||||
|
||||
return mocked_openssl_backend
|
|
@ -1,64 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from cryptography.hazmat.backends.openssl import backend
|
||||
from umbral.curvebn import CurveBN
|
||||
|
||||
|
||||
def test_mocked_openssl_curvebn_arithmetic(mock_openssl, random_ec_curvebn1, random_ec_curvebn2):
|
||||
|
||||
operations_that_construct = (
|
||||
random_ec_curvebn1 * random_ec_curvebn2, # __mul__
|
||||
random_ec_curvebn1 ** random_ec_curvebn2, # __pow__
|
||||
random_ec_curvebn1 ** int(random_ec_curvebn2), # __pow__ (as int)
|
||||
random_ec_curvebn1 + random_ec_curvebn2, # __add__
|
||||
random_ec_curvebn1 - random_ec_curvebn2, # __sub__
|
||||
-random_ec_curvebn1, # __neg__
|
||||
random_ec_curvebn1 % random_ec_curvebn2, # __mod__
|
||||
random_ec_curvebn1 % int(random_ec_curvebn2), # __mod__ (as int)
|
||||
~random_ec_curvebn1, # __invert__
|
||||
random_ec_curvebn1 / random_ec_curvebn2 # __truediv__
|
||||
)
|
||||
|
||||
with mock_openssl():
|
||||
assert random_ec_curvebn1 == random_ec_curvebn1 # __eq__
|
||||
for operator_result in operations_that_construct:
|
||||
assert operator_result
|
||||
assert isinstance(operator_result, CurveBN)
|
||||
|
||||
order = backend._bn_to_int(random_ec_curvebn1.curve.order)
|
||||
random_ec_curvebn1 = int(random_ec_curvebn1)
|
||||
random_ec_curvebn2 = int(random_ec_curvebn2)
|
||||
|
||||
# For simplicity, we test these two cases separately
|
||||
assert (int(operations_that_construct[-2]) * random_ec_curvebn1) % order == 1
|
||||
assert (int(operations_that_construct[-1]) * random_ec_curvebn2) % order == random_ec_curvebn1
|
||||
|
||||
# The remaining cases can be tested in bulk
|
||||
expected_results = (
|
||||
(random_ec_curvebn1 * random_ec_curvebn2) % order, # __mul__
|
||||
pow(random_ec_curvebn1, random_ec_curvebn2, order), # __pow__
|
||||
pow(random_ec_curvebn1, random_ec_curvebn2, order), # __pow__ (as int)
|
||||
(random_ec_curvebn1 + random_ec_curvebn2) % order, # __add__
|
||||
(random_ec_curvebn1 - random_ec_curvebn2) % order, # __sub__
|
||||
(-random_ec_curvebn1) % order, # __neg__
|
||||
random_ec_curvebn1 % random_ec_curvebn2, # __mod__
|
||||
random_ec_curvebn1 % int(random_ec_curvebn2), # __mod__ (as int)
|
||||
)
|
||||
|
||||
for (result, expected) in zip(operations_that_construct[:-2], expected_results):
|
||||
assert result == expected
|
|
@ -1,39 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.random_oracles import hash_to_curvebn
|
||||
import pytest
|
||||
|
||||
|
||||
def test_cast_curvebn_to_int():
|
||||
x = CurveBN.gen_rand()
|
||||
|
||||
x_as_int_from_dunder = x.__int__()
|
||||
x_as_int_type_caster = int(x)
|
||||
assert x_as_int_from_dunder == x_as_int_type_caster
|
||||
x = x_as_int_type_caster
|
||||
|
||||
y = CurveBN.from_int(x)
|
||||
assert x == y
|
||||
|
||||
|
||||
def test_cant_hash_arbitrary_object_into_bignum():
|
||||
whatever = object()
|
||||
with pytest.raises(TypeError):
|
||||
hash_to_curvebn(whatever)
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.curve import CURVES
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
@pytest.mark.parametrize("curve", CURVES)
|
||||
def test_serialization_rotations_of_1(curve):
|
||||
|
||||
size_in_bytes = CurveBN.expected_bytes_length(curve)
|
||||
for i in range(size_in_bytes):
|
||||
lonely_one = 1 << i
|
||||
bn = CurveBN.from_int(lonely_one, curve)
|
||||
lonely_one_in_bytes = lonely_one.to_bytes(size_in_bytes, 'big')
|
||||
|
||||
# Check serialization
|
||||
assert bn.to_bytes() == lonely_one_in_bytes
|
||||
|
||||
# Check deserialization
|
||||
assert CurveBN.from_bytes(lonely_one_in_bytes, curve) == bn
|
||||
|
||||
@pytest.mark.parametrize("curve", CURVES)
|
||||
def test_invalid_deserialization(curve):
|
||||
size_in_bytes = CurveBN.expected_bytes_length(curve)
|
||||
|
||||
# All-zeros bytestring are invalid (i.e., 0 < bn < order of the curve)
|
||||
zero_bytes = bytes(size_in_bytes)
|
||||
with pytest.raises(ValueError):
|
||||
_bn = CurveBN.from_bytes(zero_bytes, curve)
|
||||
|
||||
# All-ones bytestring is invalid too (since it's greater than order)
|
||||
lots_of_ones = 2**(8*size_in_bytes) - 1
|
||||
lots_of_ones = lots_of_ones.to_bytes(size_in_bytes, 'big')
|
||||
with pytest.raises(ValueError):
|
||||
_bn = CurveBN.from_bytes(lots_of_ones, curve)
|
||||
|
||||
# Serialization of `order` is invalid since it's not strictly lower than
|
||||
# the order of the curve
|
||||
order = default_backend()._bn_to_int(curve.order)
|
||||
with pytest.raises(ValueError):
|
||||
_bn = CurveBN.from_bytes(order.to_bytes(size_in_bytes, 'big'), curve)
|
||||
|
||||
# On the other hand, serialization of `order - 1` is valid
|
||||
order -= 1
|
||||
_bn = CurveBN.from_bytes(order.to_bytes(size_in_bytes, 'big'), curve)
|
|
@ -1,59 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.point import Point
|
||||
|
||||
|
||||
def test_mocked_openssl_point_arithmetic(mock_openssl, random_ec_point1, random_ec_point2, random_ec_curvebn1):
|
||||
|
||||
operations_that_construct = (
|
||||
random_ec_point1 * random_ec_curvebn1, # __mul__
|
||||
random_ec_point1 + random_ec_point2, # __add__
|
||||
random_ec_point1 - random_ec_point2, # __sub__
|
||||
-random_ec_point1 # __neg__
|
||||
)
|
||||
|
||||
with mock_openssl():
|
||||
assert random_ec_point1 == random_ec_point1 # __eq__
|
||||
for operator_result in operations_that_construct:
|
||||
assert operator_result
|
||||
assert isinstance(operator_result, Point)
|
||||
|
||||
|
||||
def test_point_curve_multiplication_regression():
|
||||
k256_point_bytes = b'\x03\xe0{\x1bQ\xbf@\x1f\x95\x8d\xe1\x17\xa7\xbe\x9e-G`T\xbf\xd7\x9e\xa7\x10\xc8uA\xc0z$\xc0\x92\x8a'
|
||||
k256_bn_bytes = b'4u\xd70-\xa0h\xdeG\xf0\x143\x06!\x91\x05{\xe4jC\n\xf1h\xed7a\xf8\x9d\xec^\x19\x8c'
|
||||
|
||||
k256_point = Point.from_bytes(k256_point_bytes)
|
||||
k256_bn = CurveBN.from_bytes(k256_bn_bytes)
|
||||
|
||||
product_with_star_operator = k256_point * k256_bn
|
||||
|
||||
# Make sure we have instantiated a new, unequal point in the same curve and group
|
||||
assert isinstance(product_with_star_operator, Point), "Point.__mul__ did not return a point instance"
|
||||
assert k256_point != product_with_star_operator
|
||||
assert k256_point.curve == product_with_star_operator.curve
|
||||
|
||||
product_bytes = b'\x03\xc9\xda\xa2\x88\xe2\xa0+\xb1N\xb6\xe6\x1c\xa5(\xe6\xe0p\xf6\xf4\xa9\xfc\xb1\xfaUV\xd3\xb3\x0e4\x94\xbe\x12'
|
||||
product_point = Point.from_bytes(product_bytes)
|
||||
assert product_with_star_operator.to_bytes() == product_bytes
|
||||
assert product_point == product_with_star_operator
|
||||
|
||||
# Repeating the operation, should return the same result.
|
||||
product_with_star_operator_again = k256_point * k256_bn
|
||||
assert product_with_star_operator == product_with_star_operator_again
|
|
@ -1,165 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from cryptography.exceptions import InternalError
|
||||
|
||||
from umbral.curve import SECP256K1, SECP256R1
|
||||
from umbral.point import Point
|
||||
|
||||
|
||||
def generate_test_points_bytes(quantity=2):
|
||||
points_bytes = [
|
||||
(SECP256K1, 714, b'\x02x{DR\x94\x8f\x17\xb8\xa2\x14t\x11\xdb\xb1VK\xdb\xc2\xa0T\x97iCK\x8cz~\xea\xa3\xb7AJ'),
|
||||
]
|
||||
for _ in range(quantity):
|
||||
args = (SECP256K1, 714, Point.gen_rand(curve=SECP256K1).to_bytes())
|
||||
points_bytes.append(args)
|
||||
return points_bytes
|
||||
|
||||
|
||||
def generate_test_points_affine(quantity=2):
|
||||
points_affine = [
|
||||
(SECP256K1, 714, (54495335564072000415434275044935054036617226655045445809732056033758606213450,
|
||||
26274482902044210718566767736429706729731617411738990314884135712590488065008)),
|
||||
]
|
||||
for _ in range(quantity):
|
||||
args = (SECP256K1, 714, Point.gen_rand(curve=SECP256K1).to_affine())
|
||||
points_affine.append(args)
|
||||
return points_affine
|
||||
|
||||
|
||||
def test_generate_random_points():
|
||||
for _ in range(10):
|
||||
point = Point.gen_rand()
|
||||
another_point = Point.gen_rand()
|
||||
assert isinstance(point, Point)
|
||||
assert isinstance(another_point, Point)
|
||||
assert point != another_point
|
||||
|
||||
|
||||
@pytest.mark.parametrize("curve, nid, point_bytes", generate_test_points_bytes())
|
||||
def test_bytes_serializers(point_bytes, nid, curve):
|
||||
point_with_curve = Point.from_bytes(point_bytes, curve=curve) # from curve
|
||||
assert isinstance(point_with_curve, Point)
|
||||
|
||||
the_same_point_bytes = point_with_curve.to_bytes()
|
||||
assert point_bytes == the_same_point_bytes
|
||||
|
||||
representations = (point_bytes, # Compressed representation
|
||||
point_with_curve.to_bytes(is_compressed=False)) # Uncompressed
|
||||
|
||||
for point_representation in representations:
|
||||
|
||||
malformed_point_bytes = point_representation + b'0x'
|
||||
with pytest.raises(InternalError):
|
||||
_ = Point.from_bytes(malformed_point_bytes)
|
||||
|
||||
malformed_point_bytes = point_representation[1:]
|
||||
with pytest.raises(InternalError):
|
||||
_ = Point.from_bytes(malformed_point_bytes)
|
||||
|
||||
malformed_point_bytes = point_representation[:-1]
|
||||
with pytest.raises(InternalError):
|
||||
_ = Point.from_bytes(malformed_point_bytes)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("curve, nid, point_affine", generate_test_points_affine())
|
||||
def test_affine(point_affine, nid, curve):
|
||||
point = Point.from_affine(point_affine, curve=curve) # from curve
|
||||
assert isinstance(point, Point)
|
||||
point_affine2 = point.to_affine()
|
||||
assert point_affine == point_affine2
|
||||
|
||||
|
||||
def test_invalid_points(random_ec_point2):
|
||||
|
||||
point_bytes = bytearray(random_ec_point2.to_bytes(is_compressed=False))
|
||||
point_bytes[-1] = point_bytes[-1] ^ 0x01 # Flips last bit
|
||||
point_bytes = bytes(point_bytes)
|
||||
|
||||
with pytest.raises(InternalError) as e:
|
||||
_point = Point.from_bytes(point_bytes)
|
||||
|
||||
# We want to catch specific InternalExceptions:
|
||||
# - Point not in the curve (code 107)
|
||||
# - Invalid compressed point (code 110)
|
||||
# https://github.com/openssl/openssl/blob/master/include/openssl/ecerr.h#L228
|
||||
assert e.value.err_code[0].reason in (107, 110)
|
||||
|
||||
|
||||
def test_generator_point():
|
||||
"""http://www.secg.org/SEC2-Ver-1.0.pdf Section 2.7.1"""
|
||||
g1 = Point.get_generator_from_curve()
|
||||
|
||||
g_compressed = 0x0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
|
||||
g_uncompressed = 0x0479BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
|
||||
|
||||
g_compressed = g_compressed.to_bytes(32+1, byteorder='big')
|
||||
g_uncompressed = g_uncompressed.to_bytes(64+1, byteorder='big')
|
||||
|
||||
g2 = Point.from_bytes(g_compressed)
|
||||
assert g1 == g2
|
||||
|
||||
g3 = Point.from_bytes(g_uncompressed)
|
||||
assert g1 == g3
|
||||
assert g2 == g3
|
||||
|
||||
|
||||
def test_point_not_on_curve():
|
||||
"""
|
||||
We want to be unable to create a Point that's not on the curve.
|
||||
|
||||
When we try, we get cryptography.exceptions.InternalError - is that specifically because it isn't
|
||||
on the curve? It seems to be reliably raised in the event of the Point being off the curve.
|
||||
|
||||
The OpenSSL docs don't explicitly say that they raise an error for this reason:
|
||||
https://www.openssl.org/docs/man1.1.0/crypto/EC_GFp_simple_method.html
|
||||
"""
|
||||
point_on_koblitz256_but_not_P256 = Point.from_bytes(b'\x03%\x98Dk\x88\xe2\x97\xab?\xabZ\xef\xd4' \
|
||||
b'\x9e\xaa\xc6\xb3\xa4\xa3\x89\xb2\xd7b.\x8f\x16Ci_&\xe0\x7f', curve=SECP256K1)
|
||||
|
||||
from cryptography.exceptions import InternalError
|
||||
with pytest.raises(InternalError):
|
||||
Point.from_bytes(point_on_koblitz256_but_not_P256.to_bytes(), curve=SECP256R1)
|
||||
|
||||
|
||||
def test_serialize_point_at_infinity():
|
||||
|
||||
p = Point.gen_rand()
|
||||
point_at_infinity = p - p
|
||||
|
||||
bytes_point_at_infinity = point_at_infinity.to_bytes()
|
||||
assert bytes_point_at_infinity == b'\x00'
|
||||
|
||||
|
||||
def test_coords_with_special_characteristics():
|
||||
|
||||
# Testing that a point with x coordinate greater than the curve order is still valid.
|
||||
# In particular, we will test the last valid point from the default curve (secp256k1)
|
||||
# whose x coordinate is `field_order - 3` and is greater than the order of the curve
|
||||
|
||||
field_order = 2**256 - 0x1000003D1
|
||||
compressed = b'\x02' + (field_order-3).to_bytes(32, 'big')
|
||||
|
||||
last_point = Point.from_bytes(compressed)
|
||||
|
||||
# The same point, but obtained through the from_affine method
|
||||
coords = (115792089237316195423570985008687907853269984665640564039457584007908834671660,
|
||||
109188863561374057667848968960504138135859662956057034999983532397866404169138)
|
||||
|
||||
assert last_point == Point.from_affine(coords)
|
|
@ -1,115 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from cryptography.hazmat.backends.openssl import backend
|
||||
from hypothesis import HealthCheck, given, settings, unlimited
|
||||
from hypothesis.strategies import binary, booleans, integers, tuples
|
||||
from umbral.config import default_curve
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.cfrags import CorrectnessProof
|
||||
from umbral.kfrags import KFrag
|
||||
from umbral.keys import UmbralPrivateKey, UmbralPublicKey
|
||||
from umbral.params import UmbralParameters
|
||||
from umbral.point import Point
|
||||
from umbral.random_oracles import unsafe_hash_to_point
|
||||
from umbral.pre import Capsule
|
||||
|
||||
# test parameters
|
||||
max_examples = 1000
|
||||
|
||||
# crypto constants
|
||||
curve = default_curve()
|
||||
params = UmbralParameters(curve)
|
||||
bn_size = curve.group_order_size_in_bytes
|
||||
|
||||
# generators
|
||||
bns = integers(min_value=1, max_value=backend._bn_to_int(curve.order)).map(
|
||||
lambda x: CurveBN.from_int(x))
|
||||
|
||||
points = binary(min_size=1).map(
|
||||
lambda x: unsafe_hash_to_point(x, label=b'hypothesis', params=params))
|
||||
|
||||
signatures = tuples(integers(min_value=1, max_value=backend._bn_to_int(curve.order)),
|
||||
integers(min_value=1, max_value=backend._bn_to_int(curve.order))).map(
|
||||
lambda tup: tup[0].to_bytes(bn_size, 'big') + tup[1].to_bytes(bn_size, 'big'))
|
||||
|
||||
# # utility
|
||||
def assert_kfrag_eq(k0, k1):
|
||||
assert(all([ k0.id == k1.id
|
||||
, k0.bn_key == k1.bn_key
|
||||
, k0.point_precursor == k1.point_precursor
|
||||
, k0.point_commitment == k1.point_commitment
|
||||
, k0.signature_for_bob == k1.signature_for_bob
|
||||
, k0.signature_for_proxy == k1.signature_for_proxy
|
||||
]))
|
||||
|
||||
def assert_cp_eq(c0, c1):
|
||||
assert(all([ c0.point_e2 == c1.point_e2
|
||||
, c0.point_v2 == c1.point_v2
|
||||
, c0.point_kfrag_commitment == c1.point_kfrag_commitment
|
||||
, c0.point_kfrag_pok == c1.point_kfrag_pok
|
||||
, c0.kfrag_signature == c1.kfrag_signature
|
||||
, c0.bn_sig == c1.bn_sig
|
||||
, c0.metadata == c1.metadata
|
||||
]))
|
||||
|
||||
# tests
|
||||
|
||||
@given(bns)
|
||||
@settings(max_examples=max_examples, timeout=unlimited)
|
||||
def test_bn_roundtrip(bn):
|
||||
assert(bn == CurveBN.from_bytes(bn.to_bytes()))
|
||||
|
||||
@given(points, booleans())
|
||||
@settings(max_examples=max_examples, timeout=unlimited)
|
||||
def test_point_roundtrip(p, c):
|
||||
assert(p == Point.from_bytes(p.to_bytes(is_compressed=c)))
|
||||
|
||||
@given(binary(min_size=bn_size, max_size=bn_size), bns, points, points, signatures, signatures)
|
||||
@settings(max_examples=max_examples, timeout=unlimited)
|
||||
def test_kfrag_roundtrip(d, b0, p0, p1, sig_proxy, sig_bob):
|
||||
k = KFrag(identifier=d, bn_key=b0, point_commitment=p0, point_precursor=p1,
|
||||
signature_for_proxy=sig_proxy, signature_for_bob=sig_bob)
|
||||
assert_kfrag_eq(k, KFrag.from_bytes(k.to_bytes()))
|
||||
|
||||
@given(points, points, bns)
|
||||
@settings(max_examples=max_examples, timeout=unlimited)
|
||||
def test_capsule_roundtrip_0(p0, p1, b):
|
||||
c = Capsule(params=params, point_e=p0, point_v=p1, bn_sig=b)
|
||||
assert(c == Capsule.from_bytes(c.to_bytes(), params=params))
|
||||
|
||||
@given(points, points, points, points, bns, signatures)
|
||||
@settings(max_examples=max_examples, timeout=unlimited)
|
||||
def test_cp_roundtrip(p0, p1, p2, p3, b0, sig):
|
||||
c = CorrectnessProof(p0, p1, p2, p3, b0, sig)
|
||||
assert_cp_eq(c, CorrectnessProof.from_bytes(c.to_bytes()))
|
||||
|
||||
@given(points)
|
||||
@settings(max_examples=max_examples, timeout=unlimited)
|
||||
def test_pubkey_roundtrip(p):
|
||||
k = UmbralPublicKey(p, params)
|
||||
assert(k == UmbralPublicKey.from_bytes(k.to_bytes(), params=params))
|
||||
|
||||
@given(binary(min_size=1))
|
||||
@settings(max_examples=20, timeout=unlimited, suppress_health_check=[HealthCheck.hung_test])
|
||||
def test_privkey_roundtrip(p):
|
||||
insecure_scrypt_cost = 5 # This is deliberately insecure, just to make it faster
|
||||
k = UmbralPrivateKey.gen_key()
|
||||
rt = UmbralPrivateKey.from_bytes(k.to_bytes(password=p, _scrypt_cost=insecure_scrypt_cost),
|
||||
password=p,
|
||||
_scrypt_cost=insecure_scrypt_cost)
|
||||
assert(k.get_pubkey() == rt.get_pubkey())
|
|
@ -1,62 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from cryptography.hazmat.backends.openssl import backend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from umbral.keys import UmbralPrivateKey
|
||||
from umbral.signing import Signer, Signature, DEFAULT_HASH_ALGORITHM
|
||||
|
||||
|
||||
@pytest.mark.parametrize('execution_number', range(20)) # Run this test 20 times.
|
||||
def test_sign_and_verify(execution_number):
|
||||
privkey = UmbralPrivateKey.gen_key()
|
||||
pubkey = privkey.get_pubkey()
|
||||
signer = Signer(private_key=privkey)
|
||||
message = b"peace at dawn"
|
||||
signature = signer(message=message)
|
||||
|
||||
# Basic signature verification
|
||||
assert signature.verify(message, pubkey)
|
||||
assert not signature.verify(b"another message", pubkey)
|
||||
another_pubkey = UmbralPrivateKey.gen_key().pubkey
|
||||
assert not signature.verify(message, another_pubkey)
|
||||
|
||||
# Signature serialization
|
||||
sig_bytes = bytes(signature)
|
||||
assert len(sig_bytes) == Signature.expected_bytes_length()
|
||||
restored_signature = Signature.from_bytes(sig_bytes)
|
||||
assert restored_signature == signature
|
||||
assert restored_signature.verify(message, pubkey)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('execution_number', range(20)) # Run this test 20 times.
|
||||
def test_prehashed_message(execution_number):
|
||||
privkey = UmbralPrivateKey.gen_key()
|
||||
pubkey = privkey.get_pubkey()
|
||||
signer = Signer(private_key=privkey)
|
||||
|
||||
message = b"peace at dawn"
|
||||
hash_function = hashes.Hash(DEFAULT_HASH_ALGORITHM(), backend=backend)
|
||||
hash_function.update(message)
|
||||
prehashed_message = hash_function.finalize()
|
||||
|
||||
signature = signer(message=prehashed_message, is_prehashed=True)
|
||||
|
||||
assert signature.verify(message=prehashed_message,
|
||||
verifying_key=pubkey,
|
||||
is_prehashed=True)
|
|
@ -1,223 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import os
|
||||
import pytest
|
||||
import string
|
||||
|
||||
from umbral.config import default_params
|
||||
from umbral.keys import UmbralPublicKey, UmbralPrivateKey, UmbralKeyingMaterial
|
||||
from umbral.point import Point
|
||||
|
||||
|
||||
def test_gen_key():
|
||||
# Pass in the parameters to test that manual param selection works
|
||||
umbral_priv_key = UmbralPrivateKey.gen_key()
|
||||
assert type(umbral_priv_key) == UmbralPrivateKey
|
||||
|
||||
umbral_pub_key = umbral_priv_key.get_pubkey()
|
||||
assert type(umbral_pub_key) == UmbralPublicKey
|
||||
|
||||
|
||||
def test_derive_key_from_label():
|
||||
umbral_keying_material = UmbralKeyingMaterial()
|
||||
|
||||
label = b"my_healthcare_information"
|
||||
|
||||
priv_key1 = umbral_keying_material.derive_privkey_by_label(label)
|
||||
assert type(priv_key1) == UmbralPrivateKey
|
||||
|
||||
pub_key1 = priv_key1.get_pubkey()
|
||||
assert type(pub_key1) == UmbralPublicKey
|
||||
|
||||
# Check that key derivation is reproducible
|
||||
priv_key2 = umbral_keying_material.derive_privkey_by_label(label)
|
||||
pub_key2 = priv_key2.get_pubkey()
|
||||
assert priv_key1.bn_key == priv_key2.bn_key
|
||||
assert pub_key1 == pub_key2
|
||||
|
||||
# A salt can be used too, but of course it affects the derived key
|
||||
salt = b"optional, randomly generated salt"
|
||||
priv_key3 = umbral_keying_material.derive_privkey_by_label(label, salt=salt)
|
||||
assert priv_key3.bn_key != priv_key1.bn_key
|
||||
|
||||
# Different labels on the same master secret create different keys
|
||||
label = b"my_tax_information"
|
||||
priv_key4 = umbral_keying_material.derive_privkey_by_label(label)
|
||||
pub_key4 = priv_key4.get_pubkey()
|
||||
assert priv_key1.bn_key != priv_key4.bn_key
|
||||
|
||||
|
||||
def test_private_key_serialization(random_ec_curvebn1):
|
||||
priv_key = random_ec_curvebn1
|
||||
umbral_key = UmbralPrivateKey(priv_key, default_params())
|
||||
|
||||
encoded_key = umbral_key.to_bytes()
|
||||
|
||||
decoded_key = UmbralPrivateKey.from_bytes(encoded_key)
|
||||
assert priv_key == decoded_key.bn_key
|
||||
|
||||
|
||||
def test_private_key_serialization_with_encryption(random_ec_curvebn1):
|
||||
priv_key = random_ec_curvebn1
|
||||
umbral_key = UmbralPrivateKey(priv_key, default_params())
|
||||
|
||||
insecure_cost = 15 # This is deliberately insecure, just to make the tests faster
|
||||
encoded_key = umbral_key.to_bytes(password=b'test',
|
||||
_scrypt_cost=insecure_cost)
|
||||
|
||||
decoded_key = UmbralPrivateKey.from_bytes(encoded_key,
|
||||
password=b'test',
|
||||
_scrypt_cost=insecure_cost)
|
||||
assert priv_key == decoded_key.bn_key
|
||||
|
||||
|
||||
def test_public_key_serialization(random_ec_curvebn1):
|
||||
umbral_key = UmbralPrivateKey(random_ec_curvebn1, default_params()).get_pubkey()
|
||||
pub_point = umbral_key.point_key
|
||||
|
||||
encoded_key = umbral_key.to_bytes()
|
||||
|
||||
decoded_key = UmbralPublicKey.from_bytes(encoded_key)
|
||||
assert pub_point == decoded_key.point_key
|
||||
|
||||
|
||||
def test_public_key_to_compressed_bytes(random_ec_curvebn1):
|
||||
umbral_key = UmbralPrivateKey(random_ec_curvebn1, default_params()).get_pubkey()
|
||||
key_bytes = bytes(umbral_key)
|
||||
assert len(key_bytes) == Point.expected_bytes_length(is_compressed=True)
|
||||
|
||||
|
||||
def test_public_key_to_uncompressed_bytes(random_ec_curvebn1):
|
||||
umbral_key = UmbralPrivateKey(random_ec_curvebn1, default_params()).get_pubkey()
|
||||
key_bytes = umbral_key.to_bytes(is_compressed=False)
|
||||
assert len(key_bytes) == Point.expected_bytes_length(is_compressed=False)
|
||||
|
||||
|
||||
def test_key_encoder_decoder(random_ec_curvebn1):
|
||||
priv_key = random_ec_curvebn1
|
||||
umbral_key = UmbralPrivateKey(priv_key, default_params())
|
||||
|
||||
encoded_key = umbral_key.to_bytes(encoder=base64.urlsafe_b64encode)
|
||||
|
||||
decoded_key = UmbralPrivateKey.from_bytes(encoded_key,
|
||||
decoder=base64.urlsafe_b64decode)
|
||||
assert decoded_key.to_bytes() == umbral_key.to_bytes()
|
||||
|
||||
|
||||
def test_public_key_as_hex(random_ec_curvebn1):
|
||||
pubkey = UmbralPrivateKey(random_ec_curvebn1, default_params()).get_pubkey()
|
||||
hex_string = pubkey.hex()
|
||||
|
||||
assert set(hex_string).issubset(set(string.hexdigits))
|
||||
assert len(hex_string) == 2 * UmbralPublicKey.expected_bytes_length()
|
||||
|
||||
decoded_pubkey = UmbralPublicKey.from_hex(hex_string)
|
||||
|
||||
assert pubkey == decoded_pubkey
|
||||
|
||||
hex_string = pubkey.hex(is_compressed=False)
|
||||
|
||||
assert set(hex_string).issubset(set(string.hexdigits))
|
||||
assert len(hex_string) == 2 * UmbralPublicKey.expected_bytes_length(is_compressed=False)
|
||||
|
||||
decoded_pubkey = UmbralPublicKey.from_hex(hex_string)
|
||||
assert pubkey == decoded_pubkey
|
||||
|
||||
|
||||
def test_umbral_key_to_cryptography_keys():
|
||||
umbral_priv_key = UmbralPrivateKey.gen_key()
|
||||
umbral_pub_key = umbral_priv_key.get_pubkey()
|
||||
|
||||
crypto_privkey = umbral_priv_key.to_cryptography_privkey()
|
||||
assert int(umbral_priv_key.bn_key) == crypto_privkey.private_numbers().private_value
|
||||
|
||||
crypto_pubkey = umbral_pub_key.to_cryptography_pubkey()
|
||||
umbral_affine = umbral_pub_key.point_key.to_affine()
|
||||
x, y = crypto_pubkey.public_numbers().x, crypto_pubkey.public_numbers().y
|
||||
assert umbral_affine == (x, y)
|
||||
|
||||
|
||||
def test_keying_material_serialization():
|
||||
umbral_keying_material = UmbralKeyingMaterial()
|
||||
|
||||
encoded_keying_material = umbral_keying_material.to_bytes()
|
||||
|
||||
decoded_keying_material = UmbralKeyingMaterial.from_bytes(encoded_keying_material)
|
||||
|
||||
label = os.urandom(32)
|
||||
privkey_bytes = umbral_keying_material.derive_privkey_by_label(label).to_bytes()
|
||||
assert privkey_bytes == decoded_keying_material.derive_privkey_by_label(label).to_bytes()
|
||||
|
||||
|
||||
def test_keying_material_serialization_with_encryption():
|
||||
umbral_keying_material = UmbralKeyingMaterial()
|
||||
|
||||
insecure_cost = 15 # This is deliberately insecure, just to make the tests faster
|
||||
encoded_keying_material = umbral_keying_material.to_bytes(password=b'test',
|
||||
_scrypt_cost=insecure_cost)
|
||||
|
||||
decoded_keying_material = UmbralKeyingMaterial.from_bytes(encoded_keying_material,
|
||||
password=b'test',
|
||||
_scrypt_cost=insecure_cost)
|
||||
|
||||
label = os.urandom(32)
|
||||
privkey_bytes = umbral_keying_material.derive_privkey_by_label(label).to_bytes()
|
||||
assert privkey_bytes == decoded_keying_material.derive_privkey_by_label(label).to_bytes()
|
||||
|
||||
|
||||
def test_umbral_public_key_equality():
|
||||
umbral_priv_key = UmbralPrivateKey.gen_key()
|
||||
umbral_pub_key = umbral_priv_key.get_pubkey()
|
||||
|
||||
as_bytes = bytes(umbral_pub_key)
|
||||
assert umbral_pub_key == as_bytes
|
||||
|
||||
reconstructed = UmbralPublicKey.from_bytes(as_bytes)
|
||||
assert reconstructed == umbral_pub_key
|
||||
|
||||
assert not umbral_pub_key == b"some whatever bytes"
|
||||
|
||||
another_umbral_priv_key = UmbralPrivateKey.gen_key()
|
||||
another_umbral_pub_key = another_umbral_priv_key.get_pubkey()
|
||||
|
||||
assert not umbral_pub_key == another_umbral_pub_key
|
||||
|
||||
# Also not equal to a totally disparate type.
|
||||
assert not umbral_pub_key == 107
|
||||
|
||||
|
||||
def test_umbral_public_key_as_dict_key():
|
||||
umbral_priv_key = UmbralPrivateKey.gen_key()
|
||||
umbral_pub_key = umbral_priv_key.get_pubkey()
|
||||
|
||||
d = {umbral_pub_key: 19}
|
||||
assert d[umbral_pub_key] == 19
|
||||
|
||||
another_umbral_priv_key = UmbralPrivateKey.gen_key()
|
||||
another_umbral_pub_key = another_umbral_priv_key.get_pubkey()
|
||||
|
||||
with pytest.raises(KeyError):
|
||||
_ = d[another_umbral_pub_key]
|
||||
|
||||
d[another_umbral_pub_key] = False
|
||||
|
||||
assert d[umbral_pub_key] == 19
|
||||
d[umbral_pub_key] = 20
|
||||
assert d[umbral_pub_key] == 20
|
||||
assert d[another_umbral_pub_key] is False
|
|
@ -1,20 +1,3 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published b
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__all__ = [
|
||||
|
|
|
@ -1,25 +1,33 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published b
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from umbral.__about__ import (
|
||||
from .__about__ import (
|
||||
__author__, __license__, __summary__, __title__, __version__, __copyright__, __email__, __url__
|
||||
)
|
||||
|
||||
from .capsule import Capsule
|
||||
from .capsule_frag import CapsuleFrag
|
||||
from .errors import GenericError
|
||||
from .key_frag import KeyFrag, generate_kfrags
|
||||
from .keys import SecretKey, PublicKey, SecretKeyFactory
|
||||
from .pre import encrypt, decrypt_original, decrypt_reencrypted, reencrypt
|
||||
|
||||
__all__ = [
|
||||
"__title__", "__summary__", "__version__", "__author__", "__license__", "__copyright__", "__email__", "__url__"
|
||||
"__title__",
|
||||
"__summary__",
|
||||
"__version__",
|
||||
"__author__",
|
||||
"__license__",
|
||||
"__copyright__",
|
||||
"__email__",
|
||||
"__url__",
|
||||
"SecretKey",
|
||||
"PublicKey",
|
||||
"SecretKeyFactory",
|
||||
"Capsule",
|
||||
"KeyFrag",
|
||||
"CapsuleFrag",
|
||||
"GenericError",
|
||||
"encrypt",
|
||||
"decrypt_original",
|
||||
"generate_kfrags",
|
||||
"reencrypt",
|
||||
"decrypt_reencrypted",
|
||||
]
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
from typing import TYPE_CHECKING, Tuple, Sequence
|
||||
|
||||
from .curve_point import CurvePoint
|
||||
from .curve_scalar import CurveScalar
|
||||
from .errors import GenericError
|
||||
from .hashing import hash_capsule_points, hash_to_polynomial_arg, hash_to_shared_secret
|
||||
from .keys import PublicKey, SecretKey
|
||||
from .params import PARAMETERS
|
||||
from .serializable import Serializable
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from .capsule_frag import CapsuleFrag
|
||||
|
||||
|
||||
def lambda_coeff(xs: Sequence[CurveScalar], i: int) -> CurveScalar:
|
||||
res = CurveScalar.one()
|
||||
for j in range(len(xs)):
|
||||
if j != i:
|
||||
inv_diff = (xs[j] - xs[i]).invert()
|
||||
res = (res * xs[j]) * inv_diff
|
||||
return res
|
||||
|
||||
|
||||
class Capsule(Serializable):
|
||||
"""
|
||||
Encapsulated symmetric key.
|
||||
"""
|
||||
|
||||
def __init__(self, point_e: CurvePoint, point_v: CurvePoint, signature: CurveScalar):
|
||||
self.point_e = point_e
|
||||
self.point_v = point_v
|
||||
self.signature = signature
|
||||
|
||||
@classmethod
|
||||
def __take__(cls, data: bytes) -> Tuple['Capsule', bytes]:
|
||||
(e, v, sig), data = cls.__take_types__(data, CurvePoint, CurvePoint, CurveScalar)
|
||||
|
||||
capsule = cls(e, v, sig)
|
||||
if not capsule._verify():
|
||||
raise GenericError("Capsule self-verification failed. Serialized data may be damaged.")
|
||||
|
||||
return capsule, data
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
return bytes(self.point_e) + bytes(self.point_v) + bytes(self.signature)
|
||||
|
||||
@classmethod
|
||||
def from_public_key(cls, pk: PublicKey) -> Tuple['Capsule', CurvePoint]:
|
||||
g = CurvePoint.generator()
|
||||
|
||||
priv_r = CurveScalar.random_nonzero()
|
||||
pub_r = g * priv_r
|
||||
|
||||
priv_u = CurveScalar.random_nonzero()
|
||||
pub_u = g * priv_u
|
||||
|
||||
h = hash_capsule_points(pub_r, pub_u)
|
||||
s = priv_u + (priv_r * h)
|
||||
|
||||
shared_key = pk._point_key * (priv_r + priv_u)
|
||||
|
||||
return cls(point_e=pub_r, point_v=pub_u, signature=s), shared_key
|
||||
|
||||
def open_original(self, sk: SecretKey) -> CurvePoint:
|
||||
return (self.point_e + self.point_v) * sk.secret_scalar()
|
||||
|
||||
def open_reencrypted(self,
|
||||
receiving_sk: SecretKey,
|
||||
delegating_pk: PublicKey,
|
||||
cfrags: Sequence['CapsuleFrag'],
|
||||
) -> CurvePoint:
|
||||
|
||||
if len(cfrags) == 0:
|
||||
raise ValueError("Empty CapsuleFrag sequence")
|
||||
|
||||
precursor = cfrags[0].precursor
|
||||
|
||||
if len(set(cfrags)) != len(cfrags):
|
||||
raise ValueError("Some of the CapsuleFrags are repeated")
|
||||
|
||||
if not all(cfrag.precursor == precursor for cfrag in cfrags[1:]):
|
||||
raise ValueError("CapsuleFrags are not pairwise consistent")
|
||||
|
||||
pub_key = PublicKey.from_secret_key(receiving_sk).point()
|
||||
dh_point = precursor * receiving_sk.secret_scalar()
|
||||
|
||||
# Combination of CFrags via Shamir's Secret Sharing reconstruction
|
||||
lc = [hash_to_polynomial_arg(precursor, pub_key, dh_point, cfrag.kfrag_id)
|
||||
for cfrag in cfrags]
|
||||
|
||||
e_primes = []
|
||||
v_primes = []
|
||||
for i, cfrag in enumerate(cfrags):
|
||||
lambda_i = lambda_coeff(lc, i)
|
||||
e_primes.append(cfrag.point_e1 * lambda_i)
|
||||
v_primes.append(cfrag.point_v1 * lambda_i)
|
||||
e_prime = sum(e_primes[1:], e_primes[0])
|
||||
v_prime = sum(v_primes[1:], v_primes[0])
|
||||
|
||||
# Secret value 'd' allows to make Umbral non-interactive
|
||||
d = hash_to_shared_secret(precursor, pub_key, dh_point)
|
||||
|
||||
s = self.signature
|
||||
h = hash_capsule_points(self.point_e, self.point_v)
|
||||
|
||||
orig_pub_key = delegating_pk.point()
|
||||
|
||||
# TODO: check for d == 0? Or just let if fail?
|
||||
inv_d = d.invert()
|
||||
if orig_pub_key * (s * inv_d) != (e_prime * h) + v_prime:
|
||||
raise GenericError("Internal validation failed")
|
||||
|
||||
return (e_prime + v_prime) * d
|
||||
|
||||
def _components(self):
|
||||
return (self.point_e, self.point_v, self.signature)
|
||||
|
||||
def _verify(self) -> bool:
|
||||
g = CurvePoint.generator()
|
||||
e, v, s = self._components()
|
||||
h = hash_capsule_points(e, v)
|
||||
return g * s == v + (e * h)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self._components() == other._components()
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.__class__, bytes(self)))
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.__class__.__name__}:{bytes(self).hex()[:16]}"
|
|
@ -0,0 +1,208 @@
|
|||
from typing import Sequence, Optional
|
||||
|
||||
from .capsule import Capsule
|
||||
from .curve_point import CurvePoint
|
||||
from .curve_scalar import CurveScalar
|
||||
from .hashing import Hash, hash_to_cfrag_verification, hash_to_cfrag_signature
|
||||
from .keys import PublicKey, SecretKey, Signature
|
||||
from .key_frag import KeyFrag, KeyFragID
|
||||
from .params import PARAMETERS
|
||||
from .serializable import Serializable
|
||||
|
||||
|
||||
class CapsuleFragProof(Serializable):
|
||||
|
||||
def __init__(self,
|
||||
point_e2: CurvePoint,
|
||||
point_v2: CurvePoint,
|
||||
kfrag_commitment: CurvePoint,
|
||||
kfrag_pok: CurvePoint,
|
||||
signature: CurveScalar,
|
||||
kfrag_signature: Signature,
|
||||
):
|
||||
|
||||
self.point_e2 = point_e2
|
||||
self.point_v2 = point_v2
|
||||
self.kfrag_commitment = kfrag_commitment
|
||||
self.kfrag_pok = kfrag_pok
|
||||
self.signature = signature
|
||||
self.kfrag_signature = kfrag_signature
|
||||
|
||||
def _components(self):
|
||||
return (self.point_e2, self.point_v2, self.kfrag_commitment,
|
||||
self.kfrag_pok, self.signature, self.kfrag_signature)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self._components() == other._components()
|
||||
|
||||
@classmethod
|
||||
def __take__(cls, data):
|
||||
types = [CurvePoint, CurvePoint, CurvePoint, CurvePoint, CurveScalar, Signature]
|
||||
components, data = cls.__take_types__(data, *types)
|
||||
return cls(*components), data
|
||||
|
||||
def __bytes__(self):
|
||||
return (bytes(self.point_e2) +
|
||||
bytes(self.point_v2) +
|
||||
bytes(self.kfrag_commitment) +
|
||||
bytes(self.kfrag_pok) +
|
||||
bytes(self.signature) +
|
||||
bytes(self.kfrag_signature)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_kfrag_and_cfrag(cls,
|
||||
capsule: Capsule,
|
||||
kfrag: KeyFrag,
|
||||
cfrag_e1: CurvePoint,
|
||||
cfrag_v1: CurvePoint,
|
||||
metadata: Optional[bytes],
|
||||
) -> 'CapsuleFragProof':
|
||||
|
||||
params = PARAMETERS
|
||||
|
||||
rk = kfrag.key
|
||||
t = CurveScalar.random_nonzero()
|
||||
|
||||
# Here are the formulaic constituents shared with `CapsuleFrag.verify()`.
|
||||
|
||||
e = capsule.point_e
|
||||
v = capsule.point_v
|
||||
|
||||
e1 = cfrag_e1
|
||||
v1 = cfrag_v1
|
||||
|
||||
u = params.u
|
||||
u1 = kfrag.proof.commitment
|
||||
|
||||
e2 = e * t
|
||||
v2 = v * t
|
||||
u2 = u * t
|
||||
|
||||
h = hash_to_cfrag_verification([e, e1, e2, v, v1, v2, u, u1, u2], metadata)
|
||||
|
||||
###
|
||||
|
||||
z3 = t + rk * h
|
||||
|
||||
return cls(point_e2=e2,
|
||||
point_v2=v2,
|
||||
kfrag_commitment=u1,
|
||||
kfrag_pok=u2,
|
||||
signature=z3,
|
||||
kfrag_signature=kfrag.proof.signature_for_receiver,
|
||||
)
|
||||
|
||||
|
||||
class CapsuleFrag(Serializable):
|
||||
"""
|
||||
Re-encrypted fragment of :py:class:`Capsule`.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
point_e1: CurvePoint,
|
||||
point_v1: CurvePoint,
|
||||
kfrag_id: KeyFragID,
|
||||
precursor: CurvePoint,
|
||||
proof: CapsuleFragProof,
|
||||
):
|
||||
|
||||
self.point_e1 = point_e1
|
||||
self.point_v1 = point_v1
|
||||
self.kfrag_id = kfrag_id
|
||||
self.precursor = precursor
|
||||
self.proof = proof
|
||||
|
||||
def _components(self):
|
||||
return (self.point_e1, self.point_v1, self.kfrag_id, self.precursor, self.proof)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self._components() == other._components()
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.__class__, bytes(self)))
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.__class__.__name__}:{bytes(self).hex()[:16]}"
|
||||
|
||||
@classmethod
|
||||
def __take__(cls, data):
|
||||
types = CurvePoint, CurvePoint, KeyFragID, CurvePoint, CapsuleFragProof
|
||||
components, data = cls.__take_types__(data, *types)
|
||||
return cls(*components), data
|
||||
|
||||
def __bytes__(self):
|
||||
return (bytes(self.point_e1) +
|
||||
bytes(self.point_v1) +
|
||||
bytes(self.kfrag_id) +
|
||||
bytes(self.precursor) +
|
||||
bytes(self.proof))
|
||||
|
||||
@classmethod
|
||||
def reencrypted(cls,
|
||||
capsule: Capsule,
|
||||
kfrag: KeyFrag,
|
||||
metadata: Optional[bytes] = None,
|
||||
) -> 'CapsuleFrag':
|
||||
rk = kfrag.key
|
||||
e1 = capsule.point_e * rk
|
||||
v1 = capsule.point_v * rk
|
||||
proof = CapsuleFragProof.from_kfrag_and_cfrag(capsule, kfrag, e1, v1, metadata)
|
||||
|
||||
return cls(point_e1=e1,
|
||||
point_v1=v1,
|
||||
kfrag_id=kfrag.id,
|
||||
precursor=kfrag.precursor,
|
||||
proof=proof,
|
||||
)
|
||||
|
||||
def verify(self,
|
||||
capsule: Capsule,
|
||||
delegating_pk: PublicKey,
|
||||
receiving_pk: PublicKey,
|
||||
signing_pk: PublicKey,
|
||||
metadata: Optional[bytes] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Verifies the validity of this fragment.
|
||||
|
||||
``metadata`` should coincide with the one given to :py:func:`reencrypt`.
|
||||
"""
|
||||
|
||||
params = PARAMETERS
|
||||
|
||||
# Here are the formulaic constituents shared with
|
||||
# `CapsuleFragProof.from_kfrag_and_cfrag`.
|
||||
|
||||
e = capsule.point_e
|
||||
v = capsule.point_v
|
||||
|
||||
e1 = self.point_e1
|
||||
v1 = self.point_v1
|
||||
|
||||
u = params.u
|
||||
u1 = self.proof.kfrag_commitment
|
||||
|
||||
e2 = self.proof.point_e2
|
||||
v2 = self.proof.point_v2
|
||||
u2 = self.proof.kfrag_pok
|
||||
|
||||
h = hash_to_cfrag_verification([e, e1, e2, v, v1, v2, u, u1, u2], metadata)
|
||||
|
||||
###
|
||||
|
||||
precursor = self.precursor
|
||||
kfrag_id = self.kfrag_id
|
||||
|
||||
kfrag_signature = hash_to_cfrag_signature(kfrag_id, u1, precursor, delegating_pk, receiving_pk)
|
||||
valid_kfrag_signature = kfrag_signature.verify(signing_pk, self.proof.kfrag_signature)
|
||||
|
||||
z3 = self.proof.signature
|
||||
correct_reencryption_of_e = e * z3 == e2 + e1 * h
|
||||
correct_reencryption_of_v = v * z3 == v2 + v1 * h
|
||||
correct_rk_commitment = u * z3 == u2 + u1 * h
|
||||
|
||||
return (valid_kfrag_signature
|
||||
and correct_reencryption_of_e
|
||||
and correct_reencryption_of_v
|
||||
and correct_rk_commitment)
|
287
umbral/cfrags.py
287
umbral/cfrags.py
|
@ -1,287 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from typing import Optional, Any
|
||||
|
||||
from bytestring_splitter import BytestringSplitter
|
||||
|
||||
from umbral.config import default_curve
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.point import Point
|
||||
from umbral.signing import Signature
|
||||
from umbral.curve import Curve
|
||||
from umbral.random_oracles import hash_to_curvebn, ExtendedKeccak
|
||||
|
||||
|
||||
class CorrectnessProof:
|
||||
def __init__(self, point_e2: Point, point_v2: Point, point_kfrag_commitment: Point,
|
||||
point_kfrag_pok: Point, bn_sig: CurveBN, kfrag_signature: Signature,
|
||||
metadata: Optional[bytes] = None) -> None:
|
||||
self.point_e2 = point_e2
|
||||
self.point_v2 = point_v2
|
||||
self.point_kfrag_commitment = point_kfrag_commitment
|
||||
self.point_kfrag_pok = point_kfrag_pok
|
||||
self.bn_sig = bn_sig
|
||||
self.metadata = metadata
|
||||
self.kfrag_signature = kfrag_signature
|
||||
|
||||
@classmethod
|
||||
def expected_bytes_length(cls, curve: Optional[Curve] = None):
|
||||
"""
|
||||
Returns the size (in bytes) of a CorrectnessProof without the metadata.
|
||||
If no curve is given, it will use the default curve.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
bn_size = CurveBN.expected_bytes_length(curve=curve)
|
||||
point_size = Point.expected_bytes_length(curve=curve)
|
||||
|
||||
return (bn_size * 3) + (point_size * 4)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes, curve: Optional[Curve] = None) -> 'CorrectnessProof':
|
||||
"""
|
||||
Instantiate CorrectnessProof from serialized data.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
bn_size = CurveBN.expected_bytes_length(curve)
|
||||
point_size = Point.expected_bytes_length(curve)
|
||||
arguments = {'curve': curve}
|
||||
splitter = BytestringSplitter(
|
||||
(Point, point_size, arguments), # point_e2
|
||||
(Point, point_size, arguments), # point_v2
|
||||
(Point, point_size, arguments), # point_kfrag_commitment
|
||||
(Point, point_size, arguments), # point_kfrag_pok
|
||||
(CurveBN, bn_size, arguments), # bn_sig
|
||||
(Signature, Signature.expected_bytes_length(curve), arguments), # kfrag_signature
|
||||
)
|
||||
components = splitter(data, return_remainder=True)
|
||||
components.append(components.pop() or None)
|
||||
|
||||
return cls(*components)
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
"""
|
||||
Serialize the CorrectnessProof to a bytestring.
|
||||
"""
|
||||
e2 = self.point_e2.to_bytes()
|
||||
v2 = self.point_v2.to_bytes()
|
||||
kfrag_commitment = self.point_kfrag_commitment.to_bytes()
|
||||
kfrag_pok = self.point_kfrag_pok.to_bytes()
|
||||
|
||||
result = e2 \
|
||||
+ v2 \
|
||||
+ kfrag_commitment \
|
||||
+ kfrag_pok \
|
||||
+ self.bn_sig.to_bytes() \
|
||||
+ self.kfrag_signature
|
||||
|
||||
result += self.metadata or b''
|
||||
|
||||
return result
|
||||
|
||||
def __bytes__(self):
|
||||
return self.to_bytes()
|
||||
|
||||
|
||||
class CapsuleFrag:
|
||||
def __init__(self,
|
||||
point_e1: Point,
|
||||
point_v1: Point,
|
||||
kfrag_id: bytes,
|
||||
point_precursor: Point,
|
||||
proof: Optional[CorrectnessProof] = None) -> None:
|
||||
self.point_e1 = point_e1
|
||||
self.point_v1 = point_v1
|
||||
self.kfrag_id = kfrag_id
|
||||
self.point_precursor = point_precursor
|
||||
self.proof = proof
|
||||
|
||||
class NoProofProvided(TypeError):
|
||||
"""
|
||||
Raised when a cfrag is assessed for correctness, but no proof is attached.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def expected_bytes_length(cls, curve: Optional[Curve] = None) -> int:
|
||||
"""
|
||||
Returns the size (in bytes) of a CapsuleFrag given the curve without
|
||||
the CorrectnessProof.
|
||||
If no curve is provided, it will use the default curve.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
bn_size = CurveBN.expected_bytes_length(curve)
|
||||
point_size = Point.expected_bytes_length(curve)
|
||||
|
||||
return (bn_size * 1) + (point_size * 3)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes, curve: Optional[Curve] = None) -> 'CapsuleFrag':
|
||||
"""
|
||||
Instantiates a CapsuleFrag object from the serialized data.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
|
||||
bn_size = CurveBN.expected_bytes_length(curve)
|
||||
point_size = Point.expected_bytes_length(curve)
|
||||
arguments = {'curve': curve}
|
||||
|
||||
splitter = BytestringSplitter(
|
||||
(Point, point_size, arguments), # point_e1
|
||||
(Point, point_size, arguments), # point_v1
|
||||
bn_size, # kfrag_id
|
||||
(Point, point_size, arguments), # point_precursor
|
||||
)
|
||||
components = splitter(data, return_remainder=True)
|
||||
|
||||
proof = components.pop() or None
|
||||
components.append(CorrectnessProof.from_bytes(proof, curve) if proof else None)
|
||||
|
||||
return cls(*components)
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
"""
|
||||
Serialize the CapsuleFrag into a bytestring.
|
||||
"""
|
||||
e1 = self.point_e1.to_bytes()
|
||||
v1 = self.point_v1.to_bytes()
|
||||
precursor = self.point_precursor.to_bytes()
|
||||
|
||||
serialized_cfrag = e1 + v1 + self.kfrag_id + precursor
|
||||
|
||||
if self.proof is not None:
|
||||
serialized_cfrag += self.proof.to_bytes()
|
||||
|
||||
return serialized_cfrag
|
||||
|
||||
def prove_correctness(self,
|
||||
capsule,
|
||||
kfrag,
|
||||
metadata: Optional[bytes] = None):
|
||||
|
||||
params = capsule.params
|
||||
|
||||
# Check correctness of original ciphertext
|
||||
if not capsule.verify():
|
||||
raise capsule.NotValid("Capsule verification failed.")
|
||||
|
||||
rk = kfrag.bn_key
|
||||
t = CurveBN.gen_rand(params.curve)
|
||||
####
|
||||
# Here are the formulaic constituents shared with `verify_correctness`.
|
||||
####
|
||||
e = capsule.point_e
|
||||
v = capsule.point_v
|
||||
|
||||
e1 = self.point_e1
|
||||
v1 = self.point_v1
|
||||
|
||||
u = params.u
|
||||
u1 = kfrag.point_commitment
|
||||
|
||||
e2 = t * e # type: Any
|
||||
v2 = t * v # type: Any
|
||||
u2 = t * u # type: Any
|
||||
|
||||
hash_input = [e, e1, e2, v, v1, v2, u, u1, u2]
|
||||
if metadata is not None:
|
||||
hash_input.append(metadata)
|
||||
|
||||
h = hash_to_curvebn(*hash_input, params=params, hash_class=ExtendedKeccak)
|
||||
########
|
||||
|
||||
z3 = t + h * rk
|
||||
|
||||
self.attach_proof(e2, v2, u1, u2, metadata=metadata, z3=z3, kfrag_signature=kfrag.signature_for_bob)
|
||||
|
||||
def verify_correctness(self, capsule) -> bool:
|
||||
if self.proof is None:
|
||||
raise CapsuleFrag.NoProofProvided
|
||||
|
||||
correctness_keys = capsule.get_correctness_keys()
|
||||
|
||||
delegating_pubkey = correctness_keys['delegating']
|
||||
signing_pubkey = correctness_keys['verifying']
|
||||
receiving_pubkey = correctness_keys['receiving']
|
||||
|
||||
params = capsule.params
|
||||
|
||||
####
|
||||
# Here are the formulaic constituents shared with `prove_correctness`.
|
||||
####
|
||||
e = capsule.point_e
|
||||
v = capsule.point_v
|
||||
|
||||
e1 = self.point_e1
|
||||
v1 = self.point_v1
|
||||
|
||||
u = params.u
|
||||
u1 = self.proof.point_kfrag_commitment
|
||||
|
||||
e2 = self.proof.point_e2
|
||||
v2 = self.proof.point_v2
|
||||
u2 = self.proof.point_kfrag_pok
|
||||
|
||||
hash_input = [e, e1, e2, v, v1, v2, u, u1, u2]
|
||||
if self.proof.metadata is not None:
|
||||
hash_input.append(self.proof.metadata)
|
||||
|
||||
h = hash_to_curvebn(*hash_input, params=params, hash_class=ExtendedKeccak)
|
||||
########
|
||||
|
||||
precursor = self.point_precursor
|
||||
kfrag_id = self.kfrag_id
|
||||
|
||||
validity_input = (kfrag_id, delegating_pubkey, receiving_pubkey, u1, precursor)
|
||||
|
||||
kfrag_validity_message = bytes().join(bytes(item) for item in validity_input)
|
||||
valid_kfrag_signature = self.proof.kfrag_signature.verify(kfrag_validity_message, signing_pubkey)
|
||||
|
||||
z3 = self.proof.bn_sig
|
||||
correct_reencryption_of_e = z3 * e == e2 + (h * e1)
|
||||
|
||||
correct_reencryption_of_v = z3 * v == v2 + (h * v1)
|
||||
|
||||
correct_rk_commitment = z3 * u == u2 + (h * u1)
|
||||
|
||||
return valid_kfrag_signature \
|
||||
& correct_reencryption_of_e \
|
||||
& correct_reencryption_of_v \
|
||||
& correct_rk_commitment
|
||||
|
||||
def attach_proof(self,
|
||||
e2: Point,
|
||||
v2: Point,
|
||||
u1: Point,
|
||||
u2: Point,
|
||||
z3: CurveBN,
|
||||
kfrag_signature: Signature,
|
||||
metadata: Optional[bytes]) -> None:
|
||||
|
||||
self.proof = CorrectnessProof(point_e2=e2,
|
||||
point_v2=v2,
|
||||
point_kfrag_commitment=u1,
|
||||
point_kfrag_pok=u2,
|
||||
bn_sig=z3,
|
||||
kfrag_signature=kfrag_signature,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
return self.to_bytes()
|
||||
|
||||
def __repr__(self):
|
||||
return "CFrag:{}".format(self.point_e1.to_bytes().hex()[2:17])
|
|
@ -1,77 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from typing import Optional, Type
|
||||
from warnings import warn
|
||||
|
||||
from umbral.curve import Curve, SECP256K1
|
||||
from umbral.params import UmbralParameters
|
||||
|
||||
|
||||
class _CONFIG:
|
||||
__curve = None
|
||||
__params = None
|
||||
__CURVE_TO_USE_IF_NO_DEFAULT_IS_SET_BY_USER = SECP256K1
|
||||
__WARNING_IF_NO_DEFAULT_SET = "No default curve has been set. " \
|
||||
"Using SECP256K1. " \
|
||||
"A slight performance penalty has been " \
|
||||
"incurred for only this call. Set a default " \
|
||||
"curve with umbral.config.set_default_curve()."
|
||||
|
||||
class UmbralConfigurationError(RuntimeError):
|
||||
"""Raised when somebody does something dumb re: configuration."""
|
||||
|
||||
@classmethod
|
||||
def __set_curve_by_default(cls):
|
||||
warn(cls.__WARNING_IF_NO_DEFAULT_SET, RuntimeWarning)
|
||||
cls.set_curve(cls.__CURVE_TO_USE_IF_NO_DEFAULT_IS_SET_BY_USER)
|
||||
|
||||
@classmethod
|
||||
def params(cls) -> UmbralParameters:
|
||||
if not cls.__params:
|
||||
cls.__set_curve_by_default()
|
||||
return cls.__params # type: ignore
|
||||
|
||||
@classmethod
|
||||
def curve(cls) -> Curve:
|
||||
if not cls.__curve:
|
||||
cls.__set_curve_by_default()
|
||||
return cls.__curve # type: ignore
|
||||
|
||||
@classmethod
|
||||
def set_curve(cls, curve: Optional[Curve] = None) -> None:
|
||||
if cls.__curve:
|
||||
raise cls.UmbralConfigurationError(
|
||||
"You can only set the default curve once. Do it once and then leave it alone.")
|
||||
else:
|
||||
from umbral.params import UmbralParameters
|
||||
if curve is None:
|
||||
curve = _CONFIG.__CURVE_TO_USE_IF_NO_DEFAULT_IS_SET_BY_USER
|
||||
cls.__curve = curve
|
||||
cls.__params = UmbralParameters(curve)
|
||||
|
||||
|
||||
def set_default_curve(curve: Optional[Curve] = None) -> None:
|
||||
return _CONFIG.set_curve(curve)
|
||||
|
||||
|
||||
def default_curve() -> Curve:
|
||||
return _CONFIG.curve()
|
||||
|
||||
|
||||
def default_params() -> UmbralParameters:
|
||||
return _CONFIG.params()
|
136
umbral/curve.py
136
umbral/curve.py
|
@ -1,135 +1,11 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
from . import openssl
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
from umbral import openssl
|
||||
|
||||
|
||||
class Curve:
|
||||
"""
|
||||
Acts as a container to store constant variables such as the OpenSSL
|
||||
curve_nid, the EC_GROUP struct, and the order of the curve.
|
||||
|
||||
Contains a whitelist of supported elliptic curves used in pyUmbral.
|
||||
|
||||
"""
|
||||
|
||||
_supported_curves = {
|
||||
415: 'secp256r1',
|
||||
714: 'secp256k1',
|
||||
715: 'secp384r1'
|
||||
}
|
||||
|
||||
def __init__(self, nid: int) -> None:
|
||||
"""
|
||||
Instantiates an OpenSSL curve with the provided curve_nid and derives
|
||||
the proper EC_GROUP struct and order. You can _only_ instantiate curves
|
||||
with supported nids (see `Curve.supported_curves`).
|
||||
"""
|
||||
|
||||
try:
|
||||
self.__curve_name = self._supported_curves[nid]
|
||||
except KeyError:
|
||||
raise NotImplementedError("Curve NID {} is not supported.".format(nid))
|
||||
|
||||
# set only once
|
||||
self.__curve_nid = nid
|
||||
self.__ec_group = openssl._get_ec_group_by_curve_nid(self.__curve_nid)
|
||||
self.__order = openssl._get_ec_order_by_group(self.ec_group)
|
||||
self.__generator = openssl._get_ec_generator_by_group(self.ec_group)
|
||||
|
||||
# Init cache
|
||||
self.__field_order_size_in_bytes = 0
|
||||
self.__group_order_size_in_bytes = 0
|
||||
|
||||
@classmethod
|
||||
def from_name(cls, name: str) -> 'Curve':
|
||||
"""
|
||||
Alternate constructor to generate a curve instance by its name.
|
||||
|
||||
Raises NotImplementedError if the name cannot be mapped to a known
|
||||
supported curve NID.
|
||||
|
||||
"""
|
||||
|
||||
name = name.casefold() # normalize
|
||||
|
||||
for supported_nid, supported_name in cls._supported_curves.items():
|
||||
if name == supported_name:
|
||||
instance = cls(nid=supported_nid)
|
||||
break
|
||||
else:
|
||||
message = "{} is not supported curve name.".format(name)
|
||||
raise NotImplementedError(message)
|
||||
|
||||
return instance
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.__curve_nid == other.curve_nid
|
||||
|
||||
def __repr__(self):
|
||||
return "<OpenSSL Curve(nid={}, name={})>".format(self.__curve_nid, self.__curve_name)
|
||||
|
||||
#
|
||||
# Immutable Curve Data
|
||||
#
|
||||
|
||||
@property
|
||||
def field_order_size_in_bytes(self) -> int:
|
||||
if not self.__field_order_size_in_bytes:
|
||||
size_in_bits = openssl._get_ec_group_degree(self.__ec_group)
|
||||
self.__field_order_size_in_bytes = (size_in_bits + 7) // 8
|
||||
return self.__field_order_size_in_bytes
|
||||
|
||||
@property
|
||||
def group_order_size_in_bytes(self) -> int:
|
||||
if not self.__group_order_size_in_bytes:
|
||||
BN_num_bytes = default_backend()._lib.BN_num_bytes
|
||||
self.__group_order_size_in_bytes = BN_num_bytes(self.order)
|
||||
return self.__group_order_size_in_bytes
|
||||
|
||||
@property
|
||||
def curve_nid(self) -> int:
|
||||
return self.__curve_nid
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self.__curve_name
|
||||
|
||||
@property
|
||||
def ec_group(self):
|
||||
return self.__ec_group
|
||||
|
||||
@property
|
||||
def order(self):
|
||||
return self.__order
|
||||
|
||||
@property
|
||||
def generator(self):
|
||||
return self.__generator
|
||||
|
||||
|
||||
#
|
||||
# Global Curve Instances
|
||||
#
|
||||
|
||||
SECP256R1 = Curve.from_name('secp256r1')
|
||||
SECP256K1 = Curve.from_name('secp256k1')
|
||||
SECP384R1 = Curve.from_name('secp384r1')
|
||||
SECP256R1 = openssl.Curve.from_name('secp256r1')
|
||||
SECP256K1 = openssl.Curve.from_name('secp256k1')
|
||||
SECP384R1 = openssl.Curve.from_name('secp384r1')
|
||||
|
||||
CURVES = (SECP256K1, SECP256R1, SECP384R1)
|
||||
|
||||
CURVE = SECP256K1
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
from typing import Optional, Tuple
|
||||
|
||||
from . import openssl
|
||||
from .curve import CURVE
|
||||
from .curve_scalar import CurveScalar
|
||||
from .serializable import Serializable
|
||||
|
||||
|
||||
class CurvePoint(Serializable):
|
||||
"""
|
||||
Represents an OpenSSL EC_POINT except more Pythonic.
|
||||
"""
|
||||
|
||||
def __init__(self, backend_point) -> None:
|
||||
self._backend_point = backend_point
|
||||
|
||||
@classmethod
|
||||
def generator(cls) -> 'CurvePoint':
|
||||
return cls(CURVE.point_generator)
|
||||
|
||||
@classmethod
|
||||
def random(cls) -> 'CurvePoint':
|
||||
"""
|
||||
Returns a CurvePoint object with a cryptographically secure EC_POINT based
|
||||
on the provided curve.
|
||||
"""
|
||||
return cls.generator() * CurveScalar.random_nonzero()
|
||||
|
||||
@classmethod
|
||||
def from_affine(cls, affine_x: int, affine_y: int) -> 'CurvePoint':
|
||||
"""
|
||||
Returns a CurvePoint object from the given affine coordinates in a tuple in
|
||||
the format of (x, y) and a given curve.
|
||||
"""
|
||||
backend_point = openssl.point_from_affine_coords(CURVE, affine_x, affine_y)
|
||||
return cls(backend_point)
|
||||
|
||||
def to_affine(self) -> Tuple[int, int]:
|
||||
"""
|
||||
Returns a tuple of Python ints in the format of (x, y) that represents
|
||||
the point in the curve.
|
||||
"""
|
||||
return openssl.point_to_affine_coords(CURVE, self._backend_point)
|
||||
|
||||
@classmethod
|
||||
def __take__(cls, data: bytes) -> Tuple['CurvePoint', bytes]:
|
||||
"""
|
||||
Returns a CurvePoint object from the given byte data on the curve provided.
|
||||
"""
|
||||
size = CURVE.field_element_size + 1 # compressed point size
|
||||
point_data, data = cls.__take_bytes__(data, size)
|
||||
point = openssl.point_from_bytes(CURVE, point_data)
|
||||
return cls(point), data
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
"""
|
||||
Returns the CurvePoint serialized as bytes in the compressed form.
|
||||
"""
|
||||
return openssl.point_to_bytes_compressed(CURVE, self._backend_point)
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
Compares two EC_POINTS for equality.
|
||||
"""
|
||||
return openssl.point_eq(CURVE, self._backend_point, other._backend_point)
|
||||
|
||||
def __mul__(self, other: CurveScalar) -> 'CurvePoint':
|
||||
"""
|
||||
Performs an EC_POINT_mul on an EC_POINT and a BIGNUM.
|
||||
"""
|
||||
return CurvePoint(openssl.point_mul_bn(CURVE, self._backend_point, other._backend_bignum))
|
||||
|
||||
def __add__(self, other: 'CurvePoint') -> 'CurvePoint':
|
||||
"""
|
||||
Performs an EC_POINT_add on two EC_POINTS.
|
||||
"""
|
||||
return CurvePoint(openssl.point_add(CURVE, self._backend_point, other._backend_point))
|
||||
|
||||
def __sub__(self, other: 'CurvePoint') -> 'CurvePoint':
|
||||
"""
|
||||
Performs subtraction by adding the inverse of the `other` to the point.
|
||||
"""
|
||||
return (self + (-other))
|
||||
|
||||
def __neg__(self) -> 'CurvePoint':
|
||||
"""
|
||||
Computes the additive inverse of a CurvePoint, by performing an
|
||||
EC_POINT_invert on itself.
|
||||
"""
|
||||
return CurvePoint(openssl.point_neg(CURVE, self._backend_point))
|
|
@ -0,0 +1,109 @@
|
|||
from typing import TYPE_CHECKING, Optional, Union, Tuple
|
||||
|
||||
from . import openssl
|
||||
from .curve import CURVE
|
||||
from .serializable import Serializable
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from .hashing import Hash
|
||||
|
||||
|
||||
class CurveScalar(Serializable):
|
||||
"""
|
||||
Represents an OpenSSL Bignum modulo the order of a curve. Some of these
|
||||
operations will only work with prime numbers.
|
||||
|
||||
By default, the underlying OpenSSL BIGNUM has BN_FLG_CONSTTIME set for
|
||||
constant time operations.
|
||||
"""
|
||||
|
||||
def __init__(self, backend_bignum):
|
||||
self._backend_bignum = backend_bignum
|
||||
|
||||
@classmethod
|
||||
def random_nonzero(cls) -> 'CurveScalar':
|
||||
"""
|
||||
Returns a CurveScalar object with a cryptographically secure OpenSSL BIGNUM.
|
||||
"""
|
||||
return cls(openssl.bn_random_nonzero(CURVE.bn_order))
|
||||
|
||||
@classmethod
|
||||
def from_int(cls, num: int, check_normalization: bool = True) -> 'CurveScalar':
|
||||
"""
|
||||
Returns a CurveScalar object from a given integer on a curve.
|
||||
"""
|
||||
modulus = CURVE.bn_order if check_normalization else None
|
||||
conv_bn = openssl.bn_from_int(num, check_modulus=modulus)
|
||||
return cls(conv_bn)
|
||||
|
||||
@classmethod
|
||||
def from_digest(cls, digest: 'Hash') -> 'CurveScalar':
|
||||
# TODO (#39): to be replaced by the standard algroithm.
|
||||
# Currently just matching what we have in RustCrypto stack
|
||||
# (taking bytes modulo curve order).
|
||||
# Can produce zeros!
|
||||
bn = openssl.bn_from_bytes(digest.finalize(), apply_modulus=CURVE.bn_order)
|
||||
return cls(bn)
|
||||
|
||||
@classmethod
|
||||
def __take__(cls, data: bytes) -> Tuple['CurveScalar', bytes]:
|
||||
scalar_data, data = cls.__take_bytes__(data, CURVE.scalar_size)
|
||||
bignum = openssl.bn_from_bytes(scalar_data, check_modulus=CURVE.bn_order)
|
||||
return cls(bignum), data
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
"""
|
||||
Returns the CurveScalar as bytes.
|
||||
"""
|
||||
return openssl.bn_to_bytes(self._backend_bignum, CURVE.scalar_size)
|
||||
|
||||
def __int__(self) -> int:
|
||||
"""
|
||||
Converts the CurveScalar to a Python int.
|
||||
"""
|
||||
return openssl.bn_to_int(self._backend_bignum)
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
"""
|
||||
Compares the two BIGNUMS or int.
|
||||
"""
|
||||
if type(other) == int:
|
||||
other = CurveScalar.from_int(other)
|
||||
return openssl.bn_cmp(self._backend_bignum, other._backend_bignum) == 0
|
||||
|
||||
@classmethod
|
||||
def one(cls):
|
||||
return cls(openssl.bn_one())
|
||||
|
||||
def is_zero(self):
|
||||
return openssl.bn_is_zero(self._backend_bignum)
|
||||
|
||||
def __mul__(self, other: Union[int, 'CurveScalar']) -> 'CurveScalar':
|
||||
"""
|
||||
Performs a BN_mod_mul between two BIGNUMS.
|
||||
"""
|
||||
if isinstance(other, int):
|
||||
other = CurveScalar.from_int(other)
|
||||
return CurveScalar(openssl.bn_mul(self._backend_bignum, other._backend_bignum, CURVE.bn_order))
|
||||
|
||||
def __add__(self, other : Union[int, 'CurveScalar']) -> 'CurveScalar':
|
||||
"""
|
||||
Performs a BN_mod_add on two BIGNUMs.
|
||||
"""
|
||||
if isinstance(other, int):
|
||||
other = CurveScalar.from_int(other)
|
||||
return CurveScalar(openssl.bn_add(self._backend_bignum, other._backend_bignum, CURVE.bn_order))
|
||||
|
||||
def __sub__(self, other : Union[int, 'CurveScalar']) -> 'CurveScalar':
|
||||
"""
|
||||
Performs a BN_mod_sub on two BIGNUMS.
|
||||
"""
|
||||
if isinstance(other, int):
|
||||
other = CurveScalar.from_int(other)
|
||||
return CurveScalar(openssl.bn_sub(self._backend_bignum, other._backend_bignum, CURVE.bn_order))
|
||||
|
||||
def invert(self) -> 'CurveScalar':
|
||||
"""
|
||||
Performs a BN_mod_inverse.
|
||||
WARNING: Only in constant time if BN_FLG_CONSTTIME is set on the BN.
|
||||
"""
|
||||
return CurveScalar(openssl.bn_invert(self._backend_bignum, CURVE.bn_order))
|
|
@ -1,272 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from typing import Optional, Union, cast
|
||||
|
||||
from cryptography.hazmat.backends.openssl import backend
|
||||
|
||||
from umbral import openssl
|
||||
from umbral.config import default_curve
|
||||
from umbral.curve import Curve
|
||||
|
||||
|
||||
class CurveBN:
|
||||
"""
|
||||
Represents an OpenSSL Bignum modulo the order of a curve. Some of these
|
||||
operations will only work with prime numbers
|
||||
By default, the underlying OpenSSL BIGNUM has BN_FLG_CONSTTIME set for
|
||||
constant time operations.
|
||||
"""
|
||||
|
||||
def __init__(self, bignum, curve: Curve) -> None:
|
||||
on_curve = openssl._bn_is_on_curve(bignum, curve)
|
||||
if not on_curve:
|
||||
raise ValueError("The provided BIGNUM is not on the provided curve.")
|
||||
|
||||
self.bignum = bignum
|
||||
self.curve = curve
|
||||
|
||||
@classmethod
|
||||
def expected_bytes_length(cls, curve: Optional[Curve] = None) -> int:
|
||||
"""
|
||||
Returns the size (in bytes) of a CurveBN given the curve,
|
||||
which comes from the size of the order of the generated group.
|
||||
If no curve is provided, it uses the default.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
return curve.group_order_size_in_bytes
|
||||
|
||||
@classmethod
|
||||
def gen_rand(cls, curve: Optional[Curve] = None) -> 'CurveBN':
|
||||
"""
|
||||
Returns a CurveBN object with a cryptographically secure OpenSSL BIGNUM
|
||||
based on the given curve.
|
||||
By default, the underlying OpenSSL BIGNUM has BN_FLG_CONSTTIME set for
|
||||
constant time operations.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
|
||||
new_rand_bn = openssl._get_new_BN()
|
||||
rand_res = backend._lib.BN_rand_range(new_rand_bn, curve.order)
|
||||
backend.openssl_assert(rand_res == 1)
|
||||
|
||||
if not openssl._bn_is_on_curve(new_rand_bn, curve):
|
||||
new_rand_bn = cls.gen_rand(curve=curve)
|
||||
return new_rand_bn
|
||||
|
||||
return cls(new_rand_bn, curve)
|
||||
|
||||
@classmethod
|
||||
def from_int(cls, num: int, curve: Optional[Curve] = None) -> 'CurveBN':
|
||||
"""
|
||||
Returns a CurveBN object from a given integer on a curve.
|
||||
By default, the underlying OpenSSL BIGNUM has BN_FLG_CONSTTIME set for
|
||||
constant time operations.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
conv_bn = openssl._int_to_bn(num, curve)
|
||||
return cls(conv_bn, curve)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes, curve: Optional[Curve] = None) -> 'CurveBN':
|
||||
"""
|
||||
Returns a CurveBN object from the given byte data that's within the size
|
||||
of the provided curve's order.
|
||||
By default, the underlying OpenSSL BIGNUM has BN_FLG_CONSTTIME set for
|
||||
constant time operations.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
|
||||
size = backend._lib.BN_num_bytes(curve.order)
|
||||
if len(data) != size:
|
||||
raise ValueError("Expected {} B for CurveBNs".format(size))
|
||||
bignum = openssl._bytes_to_bn(data)
|
||||
return cls(bignum, curve)
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
"""
|
||||
Returns the CurveBN as bytes.
|
||||
"""
|
||||
size = backend._lib.BN_num_bytes(self.curve.order)
|
||||
return openssl._bn_to_bytes(self.bignum, size)
|
||||
|
||||
def __int__(self) -> int:
|
||||
"""
|
||||
Converts the CurveBN to a Python int.
|
||||
"""
|
||||
return backend._bn_to_int(self.bignum)
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
"""
|
||||
Compares the two BIGNUMS or int.
|
||||
"""
|
||||
# TODO: Should this stay in or not?
|
||||
if type(other) == int:
|
||||
other = openssl._int_to_bn(other)
|
||||
other = CurveBN(other, self.curve)
|
||||
|
||||
# -1 less than, 0 is equal to, 1 is greater than
|
||||
return not bool(backend._lib.BN_cmp(self.bignum, other.bignum))
|
||||
|
||||
def __pow__(self, other: Union[int, 'CurveBN']) -> 'CurveBN':
|
||||
"""
|
||||
Performs a BN_mod_exp on two BIGNUMS.
|
||||
|
||||
WARNING: Only in constant time if BN_FLG_CONSTTIME is set on the BN.
|
||||
"""
|
||||
# TODO: Should this stay in or not?
|
||||
if type(other) == int:
|
||||
other = openssl._int_to_bn(other)
|
||||
other = CurveBN(other, self.curve)
|
||||
|
||||
other = cast('CurveBN', other) # This is just for mypy
|
||||
|
||||
power = openssl._get_new_BN()
|
||||
with backend._tmp_bn_ctx() as bn_ctx, openssl._tmp_bn_mont_ctx(self.curve.order) as bn_mont_ctx:
|
||||
res = backend._lib.BN_mod_exp_mont(
|
||||
power, self.bignum, other.bignum, self.curve.order, bn_ctx, bn_mont_ctx
|
||||
)
|
||||
backend.openssl_assert(res == 1)
|
||||
|
||||
return CurveBN(power, self.curve)
|
||||
|
||||
def __mul__(self, other) -> 'CurveBN':
|
||||
"""
|
||||
Performs a BN_mod_mul between two BIGNUMS.
|
||||
"""
|
||||
if type(other) != CurveBN:
|
||||
return NotImplemented
|
||||
|
||||
product = openssl._get_new_BN()
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.BN_mod_mul(
|
||||
product, self.bignum, other.bignum, self.curve.order, bn_ctx
|
||||
)
|
||||
backend.openssl_assert(res == 1)
|
||||
|
||||
return CurveBN(product, self.curve)
|
||||
|
||||
def __truediv__(self, other: 'CurveBN') -> 'CurveBN':
|
||||
"""
|
||||
Performs a BN_div on two BIGNUMs (modulo the order of the curve).
|
||||
|
||||
WARNING: Only in constant time if BN_FLG_CONSTTIME is set on the BN.
|
||||
"""
|
||||
product = openssl._get_new_BN()
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
inv_other = backend._lib.BN_mod_inverse(
|
||||
backend._ffi.NULL, other.bignum, self.curve.order, bn_ctx
|
||||
)
|
||||
backend.openssl_assert(inv_other != backend._ffi.NULL)
|
||||
inv_other = backend._ffi.gc(inv_other, backend._lib.BN_clear_free)
|
||||
|
||||
res = backend._lib.BN_mod_mul(
|
||||
product, self.bignum, inv_other, self.curve.order, bn_ctx
|
||||
)
|
||||
backend.openssl_assert(res == 1)
|
||||
|
||||
return CurveBN(product, self.curve)
|
||||
|
||||
def __add__(self, other : Union[int, 'CurveBN']) -> 'CurveBN':
|
||||
"""
|
||||
Performs a BN_mod_add on two BIGNUMs.
|
||||
"""
|
||||
if type(other) == int:
|
||||
other = openssl._int_to_bn(other)
|
||||
other = CurveBN(other, self.curve)
|
||||
|
||||
other = cast('CurveBN', other) # This is just for mypy
|
||||
|
||||
op_sum = openssl._get_new_BN()
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.BN_mod_add(
|
||||
op_sum, self.bignum, other.bignum, self.curve.order, bn_ctx
|
||||
)
|
||||
backend.openssl_assert(res == 1)
|
||||
|
||||
return CurveBN(op_sum, self.curve)
|
||||
|
||||
def __sub__(self, other : Union[int, 'CurveBN']) -> 'CurveBN':
|
||||
"""
|
||||
Performs a BN_mod_sub on two BIGNUMS.
|
||||
"""
|
||||
if type(other) == int:
|
||||
other = openssl._int_to_bn(other)
|
||||
other = CurveBN(other, self.curve)
|
||||
|
||||
other = cast('CurveBN', other) # This is just for mypy
|
||||
|
||||
diff = openssl._get_new_BN()
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.BN_mod_sub(
|
||||
diff, self.bignum, other.bignum, self.curve.order, bn_ctx
|
||||
)
|
||||
backend.openssl_assert(res == 1)
|
||||
|
||||
return CurveBN(diff, self.curve)
|
||||
|
||||
def __invert__(self) -> 'CurveBN':
|
||||
"""
|
||||
Performs a BN_mod_inverse.
|
||||
|
||||
WARNING: Only in constant time if BN_FLG_CONSTTIME is set on the BN.
|
||||
|
||||
"""
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
inv = backend._lib.BN_mod_inverse(
|
||||
backend._ffi.NULL, self.bignum, self.curve.order, bn_ctx
|
||||
)
|
||||
backend.openssl_assert(inv != backend._ffi.NULL)
|
||||
inv = backend._ffi.gc(inv, backend._lib.BN_clear_free)
|
||||
|
||||
return CurveBN(inv, self.curve)
|
||||
|
||||
def __neg__(self) -> 'CurveBN':
|
||||
"""
|
||||
Computes the modular opposite (i.e., additive inverse) of a BIGNUM
|
||||
|
||||
"""
|
||||
zero = backend._int_to_bn(0)
|
||||
zero = backend._ffi.gc(zero, backend._lib.BN_clear_free)
|
||||
|
||||
the_opposite = openssl._get_new_BN()
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.BN_mod_sub(
|
||||
the_opposite, zero, self.bignum, self.curve.order, bn_ctx
|
||||
)
|
||||
backend.openssl_assert(res == 1)
|
||||
|
||||
return CurveBN(the_opposite, self.curve)
|
||||
|
||||
def __mod__(self, other: Union[int, 'CurveBN']) -> 'CurveBN':
|
||||
"""
|
||||
Performs a BN_nnmod on two BIGNUMS.
|
||||
"""
|
||||
if type(other) == int:
|
||||
other = openssl._int_to_bn(other)
|
||||
other = CurveBN(other, self.curve)
|
||||
|
||||
other = cast('CurveBN', other) # This is just for mypy
|
||||
|
||||
rem = openssl._get_new_BN()
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.BN_nnmod(
|
||||
rem, self.bignum, other.bignum, bn_ctx
|
||||
)
|
||||
backend.openssl_assert(res == 1)
|
||||
|
||||
return CurveBN(rem, self.curve)
|
107
umbral/dem.py
107
umbral/dem.py
|
@ -1,58 +1,69 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
|
||||
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
|
||||
import nacl
|
||||
from nacl.bindings.crypto_aead import (
|
||||
crypto_aead_xchacha20poly1305_ietf_encrypt as xchacha_encrypt,
|
||||
crypto_aead_xchacha20poly1305_ietf_decrypt as xchacha_decrypt,
|
||||
crypto_aead_xchacha20poly1305_ietf_KEYBYTES as XCHACHA_KEY_SIZE,
|
||||
crypto_aead_xchacha20poly1305_ietf_NPUBBYTES as XCHACHA_NONCE_SIZE,
|
||||
crypto_aead_xchacha20poly1305_ietf_ABYTES as XCHACHA_TAG_SIZE,
|
||||
)
|
||||
|
||||
from . import openssl
|
||||
from .errors import GenericError
|
||||
|
||||
|
||||
DEM_KEYSIZE = 32
|
||||
DEM_NONCE_SIZE = 12
|
||||
def kdf(data: bytes,
|
||||
key_length: int,
|
||||
salt: Optional[bytes] = None,
|
||||
info: Optional[bytes] = None,
|
||||
) -> bytes:
|
||||
|
||||
hkdf = HKDF(algorithm=hashes.SHA256(),
|
||||
length=key_length,
|
||||
salt=salt,
|
||||
info=info,
|
||||
backend=openssl.backend)
|
||||
return hkdf.derive(data)
|
||||
|
||||
|
||||
class UmbralDEM:
|
||||
def __init__(self, symm_key: bytes) -> None:
|
||||
"""
|
||||
Initializes an UmbralDEM object. Requires a key to perform
|
||||
ChaCha20-Poly1305.
|
||||
"""
|
||||
if len(symm_key) != DEM_KEYSIZE:
|
||||
raise ValueError(
|
||||
"Invalid key size, must be {} bytes".format(DEM_KEYSIZE)
|
||||
)
|
||||
class DEM:
|
||||
|
||||
self.cipher = ChaCha20Poly1305(symm_key)
|
||||
KEY_SIZE = XCHACHA_KEY_SIZE
|
||||
NONCE_SIZE = XCHACHA_NONCE_SIZE
|
||||
TAG_SIZE = XCHACHA_TAG_SIZE
|
||||
|
||||
def encrypt(self, data: bytes, authenticated_data: Optional[bytes] = None) -> bytes:
|
||||
"""
|
||||
Encrypts data using ChaCha20-Poly1305 with optional authenticated data.
|
||||
"""
|
||||
nonce = os.urandom(DEM_NONCE_SIZE)
|
||||
enc_data = self.cipher.encrypt(nonce, data, authenticated_data)
|
||||
# Ciphertext will be a 12 byte nonce, the ciphertext, and a 16 byte tag.
|
||||
return nonce + enc_data
|
||||
def __init__(self,
|
||||
key_material: bytes,
|
||||
salt: Optional[bytes] = None,
|
||||
info: Optional[bytes] = None,
|
||||
):
|
||||
self._key = kdf(key_material, self.KEY_SIZE, salt, info)
|
||||
|
||||
def decrypt(self, ciphertext: bytes, authenticated_data: Optional[bytes] = None) -> bytes:
|
||||
"""
|
||||
Decrypts data using ChaCha20-Poly1305 and validates the provided
|
||||
authenticated data.
|
||||
"""
|
||||
nonce = ciphertext[:DEM_NONCE_SIZE]
|
||||
ciphertext = ciphertext[DEM_NONCE_SIZE:]
|
||||
cleartext = self.cipher.decrypt(nonce, ciphertext, authenticated_data)
|
||||
return cleartext
|
||||
def encrypt(self, plaintext: bytes, authenticated_data: bytes = b"") -> bytes:
|
||||
nonce = os.urandom(self.NONCE_SIZE)
|
||||
ciphertext = xchacha_encrypt(plaintext, authenticated_data, nonce, self._key)
|
||||
return nonce + ciphertext
|
||||
|
||||
def decrypt(self, nonce_and_ciphertext: bytes, authenticated_data: bytes = b"") -> bytes:
|
||||
|
||||
if len(nonce_and_ciphertext) < self.NONCE_SIZE:
|
||||
raise ValueError(f"The ciphertext must include the nonce")
|
||||
|
||||
nonce = nonce_and_ciphertext[:self.NONCE_SIZE]
|
||||
ciphertext = nonce_and_ciphertext[self.NONCE_SIZE:]
|
||||
|
||||
# Prevent an out of bounds error deep in NaCl
|
||||
if len(ciphertext) < self.TAG_SIZE:
|
||||
raise ValueError(f"The authentication tag is missing or malformed")
|
||||
|
||||
try:
|
||||
return xchacha_decrypt(ciphertext, authenticated_data, nonce, self._key)
|
||||
except nacl.exceptions.CryptoError:
|
||||
raise GenericError("Decryption of ciphertext failed: "
|
||||
"either someone tampered with the ciphertext or "
|
||||
"you are using an incorrect decryption key.")
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class GenericError(Exception):
|
||||
"""
|
||||
An interal Umbral error, see the message for details.
|
||||
"""
|
||||
pass
|
|
@ -0,0 +1,146 @@
|
|||
from typing import TYPE_CHECKING, Optional, Type, Iterable, Union
|
||||
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
|
||||
from .openssl import backend, ErrorInvalidCompressedPoint
|
||||
from .curve import CURVE
|
||||
from .curve_scalar import CurveScalar
|
||||
from .curve_point import CurvePoint
|
||||
from .keys import PublicKey, SecretKey, Signature
|
||||
from .serializable import Serializable, serialize_bool
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from .key_frag import KeyFragID
|
||||
|
||||
|
||||
class Hash:
|
||||
|
||||
OUTPUT_SIZE = 32
|
||||
|
||||
def __init__(self, dst: bytes):
|
||||
self._backend_hash_algorithm = hashes.SHA256()
|
||||
self._hash = hashes.Hash(self._backend_hash_algorithm, backend=backend)
|
||||
|
||||
len_dst = len(dst).to_bytes(4, byteorder='big')
|
||||
self.update(len_dst + dst)
|
||||
|
||||
def update(self, data: Union[bytes, Serializable]) -> None:
|
||||
self._hash.update(bytes(data))
|
||||
|
||||
def finalize(self) -> bytes:
|
||||
return self._hash.finalize()
|
||||
|
||||
|
||||
def hash_to_polynomial_arg(precursor: CurvePoint,
|
||||
pubkey: CurvePoint,
|
||||
dh_point: CurvePoint,
|
||||
kfrag_id: 'KeyFragID',
|
||||
) -> CurveScalar:
|
||||
digest = Hash(b"POLYNOMIAL_ARG")
|
||||
digest.update(precursor)
|
||||
digest.update(pubkey)
|
||||
digest.update(dh_point)
|
||||
digest.update(kfrag_id)
|
||||
return CurveScalar.from_digest(digest)
|
||||
|
||||
|
||||
def hash_capsule_points(e: CurvePoint, v: CurvePoint) -> CurveScalar:
|
||||
digest = Hash(b"CAPSULE_POINTS")
|
||||
digest.update(e)
|
||||
digest.update(v)
|
||||
return CurveScalar.from_digest(digest)
|
||||
|
||||
|
||||
def hash_to_shared_secret(precursor: CurvePoint,
|
||||
pubkey: CurvePoint,
|
||||
dh_point: CurvePoint
|
||||
) -> CurveScalar:
|
||||
digest = Hash(b"SHARED_SECRET")
|
||||
digest.update(precursor)
|
||||
digest.update(pubkey)
|
||||
digest.update(dh_point)
|
||||
return CurveScalar.from_digest(digest)
|
||||
|
||||
|
||||
|
||||
def hash_to_cfrag_verification(points: Iterable[CurvePoint], metadata: Optional[bytes] = None) -> CurveScalar:
|
||||
digest = Hash(b"CFRAG_VERIFICATION")
|
||||
for point in points:
|
||||
digest.update(point)
|
||||
if metadata is not None:
|
||||
digest.update(metadata)
|
||||
return CurveScalar.from_digest(digest)
|
||||
|
||||
|
||||
def hash_to_cfrag_signature(kfrag_id: 'KeyFragID',
|
||||
commitment: CurvePoint,
|
||||
precursor: CurvePoint,
|
||||
maybe_delegating_pk: Optional[PublicKey],
|
||||
maybe_receiving_pk: Optional[PublicKey],
|
||||
) -> 'SignatureDigest':
|
||||
|
||||
digest = SignatureDigest(b"CFRAG_SIGNATURE")
|
||||
digest.update(kfrag_id)
|
||||
digest.update(commitment)
|
||||
digest.update(precursor)
|
||||
|
||||
if maybe_delegating_pk:
|
||||
digest.update(serialize_bool(True))
|
||||
digest.update(maybe_delegating_pk)
|
||||
else:
|
||||
digest.update(serialize_bool(False))
|
||||
|
||||
if maybe_receiving_pk:
|
||||
digest.update(serialize_bool(True))
|
||||
digest.update(maybe_receiving_pk)
|
||||
else:
|
||||
digest.update(serialize_bool(False))
|
||||
|
||||
return digest
|
||||
|
||||
|
||||
class SignatureDigest:
|
||||
|
||||
def __init__(self, dst: bytes):
|
||||
self._digest = Hash(dst)
|
||||
|
||||
def update(self, value):
|
||||
self._digest.update(value)
|
||||
|
||||
def sign(self, sk: SecretKey) -> Signature:
|
||||
return sk.sign_digest(self._digest)
|
||||
|
||||
def verify(self, pk: PublicKey, sig: Signature):
|
||||
return sig.verify_digest(pk, self._digest)
|
||||
|
||||
|
||||
def unsafe_hash_to_point(dst: bytes, data: bytes) -> CurvePoint:
|
||||
"""
|
||||
Hashes arbitrary data into a valid EC point of the specified curve,
|
||||
using the try-and-increment method.
|
||||
|
||||
WARNING: Do not use when the input data is secret, as this implementation is not
|
||||
in constant time, and hence, it is not safe with respect to timing attacks.
|
||||
"""
|
||||
|
||||
len_data = len(data).to_bytes(4, byteorder='big')
|
||||
data_with_len = len_data + data
|
||||
sign = b'\x02'
|
||||
|
||||
# We use an internal 32-bit counter as additional input
|
||||
for i in range(2**32):
|
||||
ibytes = i.to_bytes(4, byteorder='big')
|
||||
digest = Hash(dst)
|
||||
digest.update(data_with_len + ibytes)
|
||||
point_data = digest.finalize()[:CURVE.field_element_size]
|
||||
|
||||
compressed_point = sign + point_data
|
||||
|
||||
try:
|
||||
return CurvePoint.from_bytes(compressed_point)
|
||||
except ErrorInvalidCompressedPoint:
|
||||
# If it is not a valid point, continue on
|
||||
pass
|
||||
|
||||
# Only happens with probability 2^(-32)
|
||||
raise ValueError('Could not hash input into the curve') # pragma: no cover
|
|
@ -0,0 +1,305 @@
|
|||
import os
|
||||
from typing import Tuple, List, Optional
|
||||
|
||||
from .curve_point import CurvePoint
|
||||
from .curve_scalar import CurveScalar
|
||||
from .hashing import hash_to_shared_secret, hash_to_cfrag_signature, hash_to_polynomial_arg
|
||||
from .keys import PublicKey, SecretKey, Signature
|
||||
from .params import PARAMETERS
|
||||
from .serializable import Serializable, serialize_bool, take_bool
|
||||
|
||||
|
||||
class KeyFragID(Serializable):
|
||||
|
||||
__SIZE = 32
|
||||
|
||||
def __init__(self, id_: bytes):
|
||||
self._id = id_
|
||||
|
||||
def __eq__(self, other):
|
||||
return self._id == other._id
|
||||
|
||||
@classmethod
|
||||
def random(cls) -> 'KeyFragID':
|
||||
return cls(os.urandom(cls.__SIZE))
|
||||
|
||||
@classmethod
|
||||
def __take__(cls, data):
|
||||
id_, data = cls.__take_bytes__(data, cls.__SIZE)
|
||||
return cls(id_), data
|
||||
|
||||
def __bytes__(self):
|
||||
return self._id
|
||||
|
||||
|
||||
class KeyFragProof(Serializable):
|
||||
|
||||
@classmethod
|
||||
def from_base(cls,
|
||||
base: 'KeyFragBase',
|
||||
kfrag_id: KeyFragID,
|
||||
kfrag_key: CurveScalar,
|
||||
sign_delegating_key: bool,
|
||||
sign_receiving_key: bool,
|
||||
) -> 'KeyFragProof':
|
||||
|
||||
params = PARAMETERS
|
||||
|
||||
kfrag_precursor = base.precursor
|
||||
signing_sk = base.signing_sk
|
||||
delegating_pk = base.delegating_pk
|
||||
receiving_pk = base.receiving_pk
|
||||
|
||||
commitment = params.u * kfrag_key
|
||||
|
||||
signature_for_receiver = hash_to_cfrag_signature(kfrag_id,
|
||||
commitment,
|
||||
kfrag_precursor,
|
||||
delegating_pk,
|
||||
receiving_pk,
|
||||
).sign(signing_sk)
|
||||
|
||||
maybe_delegating_pk = delegating_pk if sign_delegating_key else None
|
||||
maybe_receiving_pk = receiving_pk if sign_receiving_key else None
|
||||
signature_for_proxy = hash_to_cfrag_signature(kfrag_id,
|
||||
commitment,
|
||||
kfrag_precursor,
|
||||
maybe_delegating_pk,
|
||||
maybe_receiving_pk
|
||||
).sign(signing_sk)
|
||||
|
||||
return cls(commitment,
|
||||
signature_for_proxy,
|
||||
signature_for_receiver,
|
||||
sign_delegating_key,
|
||||
sign_receiving_key)
|
||||
|
||||
def __init__(self,
|
||||
commitment: CurvePoint,
|
||||
signature_for_proxy: Signature,
|
||||
signature_for_receiver: Signature,
|
||||
delegating_key_signed: bool,
|
||||
receiving_key_signed: bool
|
||||
):
|
||||
|
||||
self.commitment = commitment
|
||||
self.signature_for_proxy = signature_for_proxy
|
||||
self.signature_for_receiver = signature_for_receiver
|
||||
self.delegating_key_signed = delegating_key_signed
|
||||
self.receiving_key_signed = receiving_key_signed
|
||||
|
||||
def _components(self):
|
||||
return (self.commitment,
|
||||
self.signature_for_proxy,
|
||||
self.signature_for_receiver,
|
||||
self.delegating_key_signed,
|
||||
self.receiving_key_signed)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self._components() == other._components()
|
||||
|
||||
@classmethod
|
||||
def __take__(cls, data):
|
||||
types = [CurvePoint, Signature, Signature]
|
||||
(commitment, sig_proxy, sig_bob), data = cls.__take_types__(data, *types)
|
||||
delegating_key_signed, data = take_bool(data)
|
||||
receiving_key_signed, data = take_bool(data)
|
||||
|
||||
obj = cls(commitment, sig_proxy, sig_bob, delegating_key_signed, receiving_key_signed)
|
||||
return obj, data
|
||||
|
||||
def __bytes__(self):
|
||||
return (bytes(self.commitment) +
|
||||
bytes(self.signature_for_proxy) +
|
||||
bytes(self.signature_for_receiver) +
|
||||
serialize_bool(self.delegating_key_signed) +
|
||||
serialize_bool(self.receiving_key_signed)
|
||||
)
|
||||
|
||||
|
||||
# Coefficients of the generating polynomial
|
||||
def poly_eval(coeffs: List[CurveScalar], x: CurveScalar) -> CurveScalar:
|
||||
result = coeffs[-1]
|
||||
for coeff in reversed(coeffs[:-1]):
|
||||
result = (result * x) + coeff
|
||||
return result
|
||||
|
||||
|
||||
class KeyFrag(Serializable):
|
||||
"""
|
||||
A signed fragment of the delegating key.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
id_: KeyFragID,
|
||||
key: CurveScalar,
|
||||
precursor: CurvePoint,
|
||||
proof: KeyFragProof):
|
||||
self.id = id_
|
||||
self.key = key
|
||||
self.precursor = precursor
|
||||
self.proof = proof
|
||||
|
||||
@classmethod
|
||||
def __take__(cls, data):
|
||||
types = [KeyFragID, CurveScalar, CurvePoint, KeyFragProof]
|
||||
components, data = cls.__take_types__(data, *types)
|
||||
return cls(*components), data
|
||||
|
||||
def __bytes__(self):
|
||||
return bytes(self.id) + bytes(self.key) + bytes(self.precursor) + bytes(self.proof)
|
||||
|
||||
def _components(self):
|
||||
return self.id, self.key, self.precursor, self.proof
|
||||
|
||||
def __eq__(self, other):
|
||||
return self._components() == other._components()
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.__class__, bytes(self)))
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.__class__.__name__}:{bytes(self).hex()[:16]}"
|
||||
|
||||
@classmethod
|
||||
def from_base(cls,
|
||||
base: 'KeyFragBase',
|
||||
sign_delegating_key: bool,
|
||||
sign_receiving_key: bool,
|
||||
) -> 'KeyFrag':
|
||||
|
||||
kfrag_id = KeyFragID.random()
|
||||
|
||||
# The index of the re-encryption key share (which in Shamir's Secret
|
||||
# Sharing corresponds to x in the tuple (x, f(x)), with f being the
|
||||
# generating polynomial), is used to prevent reconstruction of the
|
||||
# re-encryption key without Bob's intervention
|
||||
share_index = hash_to_polynomial_arg(base.precursor,
|
||||
base.receiving_pk.point(),
|
||||
base.dh_point,
|
||||
kfrag_id,
|
||||
)
|
||||
|
||||
# The re-encryption key share is the result of evaluating the generating
|
||||
# polynomial for the index value
|
||||
rk = poly_eval(base.coefficients, share_index)
|
||||
|
||||
proof = KeyFragProof.from_base(base,
|
||||
kfrag_id,
|
||||
rk,
|
||||
sign_delegating_key,
|
||||
sign_receiving_key,
|
||||
)
|
||||
|
||||
return cls(kfrag_id, rk, base.precursor, proof)
|
||||
|
||||
def verify(self,
|
||||
signing_pk: PublicKey,
|
||||
delegating_pk: Optional[PublicKey] = None,
|
||||
receiving_pk: Optional[PublicKey] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Verifies the validity of this fragment.
|
||||
|
||||
If the delegating and/or receiving key were not signed in :py:func:`generate_kfrags`,
|
||||
but are given to this function, they are ignored.
|
||||
"""
|
||||
|
||||
u = PARAMETERS.u
|
||||
|
||||
kfrag_id = self.id
|
||||
key = self.key
|
||||
commitment = self.proof.commitment
|
||||
precursor = self.precursor
|
||||
|
||||
# We check that the commitment is well-formed
|
||||
if commitment != u * key:
|
||||
return False
|
||||
|
||||
# A shortcut, perhaps not necessary
|
||||
delegating_key_missing = self.proof.delegating_key_signed and not bool(delegating_pk)
|
||||
receiving_key_missing = self.proof.receiving_key_signed and not bool(receiving_pk)
|
||||
|
||||
if delegating_key_missing or receiving_key_missing:
|
||||
return False
|
||||
|
||||
delegating_pk = delegating_pk if self.proof.delegating_key_signed else None
|
||||
receiving_pk = receiving_pk if self.proof.receiving_key_signed else None
|
||||
sig = hash_to_cfrag_signature(kfrag_id,
|
||||
commitment,
|
||||
precursor,
|
||||
delegating_pk,
|
||||
receiving_pk)
|
||||
return sig.verify(signing_pk, self.proof.signature_for_proxy)
|
||||
|
||||
|
||||
class KeyFragBase:
|
||||
|
||||
def __init__(self,
|
||||
delegating_sk: SecretKey,
|
||||
receiving_pk: PublicKey,
|
||||
signing_sk: SecretKey,
|
||||
threshold: int,
|
||||
):
|
||||
|
||||
if threshold <= 0:
|
||||
raise ValueError(f"`threshold` must be larger than 0 (given: {threshold})")
|
||||
|
||||
g = CurvePoint.generator()
|
||||
|
||||
delegating_pk = PublicKey.from_secret_key(delegating_sk)
|
||||
|
||||
receiving_pk_point = receiving_pk.point()
|
||||
|
||||
while True:
|
||||
# The precursor point is used as an ephemeral public key in a DH key exchange,
|
||||
# and the resulting shared secret 'dh_point' is used to derive other secret values
|
||||
private_precursor = CurveScalar.random_nonzero()
|
||||
precursor = g * private_precursor
|
||||
|
||||
dh_point = receiving_pk_point * private_precursor
|
||||
|
||||
# Secret value 'd' allows to make Umbral non-interactive
|
||||
d = hash_to_shared_secret(precursor, receiving_pk_point, dh_point)
|
||||
|
||||
# At the moment we cannot statically ensure `d` is not zero,
|
||||
# but we need it to be non-zero for the algorithm to work.
|
||||
if not d.is_zero():
|
||||
break
|
||||
|
||||
# Coefficients of the generating polynomial
|
||||
# `invert()` is guaranteed to work because `d` is nonzero.
|
||||
coefficients = [
|
||||
delegating_sk.secret_scalar() * d.invert(),
|
||||
*[CurveScalar.random_nonzero() for _ in range(threshold-1)]]
|
||||
|
||||
self.signing_sk = signing_sk
|
||||
self.precursor = precursor
|
||||
self.dh_point = dh_point
|
||||
self.delegating_pk = delegating_pk
|
||||
self.receiving_pk = receiving_pk
|
||||
self.coefficients = coefficients
|
||||
|
||||
|
||||
def generate_kfrags(delegating_sk: SecretKey,
|
||||
receiving_pk: PublicKey,
|
||||
signing_sk: SecretKey,
|
||||
threshold: int,
|
||||
num_kfrags: int,
|
||||
sign_delegating_key: bool = True,
|
||||
sign_receiving_key: bool = True,
|
||||
) -> List[KeyFrag]:
|
||||
"""
|
||||
Generates ``num_kfrags`` key fragments to pass to proxies for re-encryption.
|
||||
At least ``threshold`` of them will be needed for decryption.
|
||||
If ``sign_delegating_key`` or ``sign_receiving_key`` are ``True``,
|
||||
the corresponding keys will have to be provided to :py:meth:`KeyFrag.verify`.
|
||||
"""
|
||||
|
||||
base = KeyFragBase(delegating_sk, receiving_pk, signing_sk, threshold)
|
||||
|
||||
# Technically we could allow it, but what would be the use of these kfrags?
|
||||
if num_kfrags < threshold:
|
||||
raise ValueError(f"Creating less kfrags ({num_kfrags}) than threshold ({threshold}) makes them useless")
|
||||
|
||||
return [KeyFrag.from_base(base, sign_delegating_key, sign_receiving_key) for _ in range(num_kfrags)]
|
595
umbral/keys.py
595
umbral/keys.py
|
@ -1,472 +1,205 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
from typing import Callable, Optional, Any
|
||||
from typing import TYPE_CHECKING, Tuple
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.backends.openssl.ec import _EllipticCurvePrivateKey, _EllipticCurvePublicKey
|
||||
from cryptography.exceptions import InternalError
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
||||
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt as CryptographyScrypt
|
||||
from nacl.secret import SecretBox
|
||||
from cryptography.exceptions import InvalidSignature
|
||||
from cryptography.hazmat.primitives.asymmetric import utils
|
||||
from cryptography.hazmat.primitives.asymmetric.ec import ECDSA
|
||||
|
||||
from umbral import openssl
|
||||
from umbral.config import default_params
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.params import UmbralParameters
|
||||
from umbral.point import Point
|
||||
from umbral.curve import Curve
|
||||
from umbral.random_oracles import hash_to_curvebn
|
||||
from . import openssl
|
||||
from .curve import CURVE
|
||||
from .curve_scalar import CurveScalar
|
||||
from .curve_point import CurvePoint
|
||||
from .dem import kdf
|
||||
from .serializable import Serializable
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from .hashing import Hash
|
||||
|
||||
|
||||
__SALT_SIZE = 32
|
||||
|
||||
|
||||
class Scrypt:
|
||||
__DEFAULT_SCRYPT_COST = 20
|
||||
|
||||
def __call__(self,
|
||||
password: bytes,
|
||||
salt: bytes,
|
||||
**kwargs) -> bytes:
|
||||
"""
|
||||
Derives a symmetric encryption key from a pair of password and salt.
|
||||
It also accepts an additional _scrypt_cost argument.
|
||||
WARNING: RFC7914 recommends that you use a 2^20 cost value for sensitive
|
||||
files. It is NOT recommended to change the `_scrypt_cost` value unless
|
||||
you know what you are doing.
|
||||
:param password: byte-encoded password used to derive a symmetric key
|
||||
:param salt: cryptographic salt added during key derivation
|
||||
:return:
|
||||
"""
|
||||
|
||||
_scrypt_cost = kwargs.get('_scrypt_cost', Scrypt.__DEFAULT_SCRYPT_COST)
|
||||
try:
|
||||
derived_key = CryptographyScrypt(
|
||||
salt=salt,
|
||||
length=SecretBox.KEY_SIZE,
|
||||
n=2 ** _scrypt_cost,
|
||||
r=8,
|
||||
p=1,
|
||||
backend=default_backend()
|
||||
).derive(password)
|
||||
except InternalError as e:
|
||||
required_memory = 128 * 2**_scrypt_cost * 8 // (10**6)
|
||||
if e.err_code[0].reason == 65:
|
||||
raise MemoryError(
|
||||
"Scrypt key derivation requires at least {} MB of memory. "
|
||||
"Please free up some memory and try again.".format(required_memory)
|
||||
)
|
||||
else:
|
||||
raise e
|
||||
else:
|
||||
return derived_key
|
||||
|
||||
|
||||
def derive_key_from_password(password: bytes,
|
||||
salt: bytes,
|
||||
**kwargs) -> bytes:
|
||||
class SecretKey(Serializable):
|
||||
"""
|
||||
Derives a symmetric encryption key from a pair of password and salt.
|
||||
It uses Scrypt by default.
|
||||
Umbral secret (private) key.
|
||||
"""
|
||||
kdf = kwargs.get('kdf', Scrypt)()
|
||||
derived_key = kdf(password, salt, **kwargs)
|
||||
return derived_key
|
||||
|
||||
__SERIALIZATION_INFO = b"SECRET_KEY"
|
||||
|
||||
def wrap_key(key_to_wrap: bytes,
|
||||
wrapping_key: Optional[bytes] = None,
|
||||
password: Optional[bytes] = None,
|
||||
**kwargs) -> bytes:
|
||||
"""
|
||||
Wraps a key using a provided wrapping key. Alternatively, it can derive
|
||||
the wrapping key from a password.
|
||||
:param key_to_wrap:
|
||||
:param wrapping_key:
|
||||
:param password:
|
||||
:return:
|
||||
"""
|
||||
if not(bool(password) ^ bool(wrapping_key)):
|
||||
raise ValueError("Either password or wrapping_key must be passed")
|
||||
|
||||
wrapped_key = b''
|
||||
if password:
|
||||
salt = os.urandom(__SALT_SIZE)
|
||||
wrapping_key = derive_key_from_password(password=password,
|
||||
salt=salt,
|
||||
**kwargs)
|
||||
wrapped_key = salt
|
||||
|
||||
wrapped_key += SecretBox(wrapping_key).encrypt(key_to_wrap)
|
||||
return wrapped_key
|
||||
|
||||
|
||||
def unwrap_key(wrapped_key: bytes,
|
||||
wrapping_key: Optional[bytes] = None,
|
||||
password: Optional[bytes] = None,
|
||||
**kwargs) -> bytes:
|
||||
"""
|
||||
Unwraps a key using a provided wrapping key. Alternatively, it can derive
|
||||
the wrapping key from a password.
|
||||
:param wrapped_key:
|
||||
:param wrapping_key:
|
||||
:param password:
|
||||
:return:
|
||||
"""
|
||||
if all((password, wrapping_key)) or not any((password, wrapping_key)):
|
||||
raise ValueError("Either password or wrapping_key must be passed")
|
||||
|
||||
if password:
|
||||
salt = wrapped_key[:__SALT_SIZE]
|
||||
wrapped_key = wrapped_key[__SALT_SIZE:]
|
||||
wrapping_key = derive_key_from_password(password=password,
|
||||
salt=salt,
|
||||
**kwargs)
|
||||
|
||||
key = SecretBox(wrapping_key).decrypt(wrapped_key)
|
||||
return key
|
||||
|
||||
|
||||
class UmbralPrivateKey:
|
||||
def __init__(self, bn_key: CurveBN, params: UmbralParameters) -> None:
|
||||
"""
|
||||
Initializes an Umbral private key.
|
||||
"""
|
||||
self.params = params
|
||||
self.bn_key = bn_key
|
||||
self.pubkey = UmbralPublicKey(self.bn_key * params.g, params=params) # type: ignore
|
||||
def __init__(self, scalar_key: CurveScalar):
|
||||
self._scalar_key = scalar_key
|
||||
# Cached public key. Access it via `PublicKey.from_secret_key()` -
|
||||
# it may be removed later.
|
||||
# We are assuming here that there will be on average more calls to
|
||||
# `PublicKey.from_secret_key()` than secret key instantiations.
|
||||
self._public_key_point = CurvePoint.generator() * self._scalar_key
|
||||
|
||||
@classmethod
|
||||
def gen_key(cls, params: Optional[UmbralParameters] = None) -> 'UmbralPrivateKey':
|
||||
def random(cls) -> 'SecretKey':
|
||||
"""
|
||||
Generates a private key and returns it.
|
||||
Generates a random secret key and returns it.
|
||||
"""
|
||||
if params is None:
|
||||
params = default_params()
|
||||
return cls(CurveScalar.random_nonzero())
|
||||
|
||||
bn_key = CurveBN.gen_rand(params.curve)
|
||||
return cls(bn_key, params)
|
||||
def __eq__(self, other):
|
||||
return self._scalar_key == other._scalar_key
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.__class__.__name__}:..."
|
||||
|
||||
def __hash__(self):
|
||||
raise NotImplementedError("Hashing secret objects is insecure")
|
||||
|
||||
def secret_scalar(self):
|
||||
return self._scalar_key
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls,
|
||||
key_bytes: bytes,
|
||||
wrapping_key: Optional[bytes] = None,
|
||||
password: Optional[bytes] = None,
|
||||
params: Optional[UmbralParameters] = None,
|
||||
decoder: Optional[Callable] = None,
|
||||
**kwargs) -> 'UmbralPrivateKey':
|
||||
"""
|
||||
Loads an Umbral private key from bytes.
|
||||
Optionally, allows a decoder function to be passed as a param to decode
|
||||
the data provided before converting to an Umbral key.
|
||||
Optionally, uses a wrapping key to unwrap an encrypted Umbral private key.
|
||||
Alternatively, if a password is provided it will derive the wrapping key
|
||||
from it.
|
||||
"""
|
||||
if params is None:
|
||||
params = default_params()
|
||||
|
||||
if decoder:
|
||||
key_bytes = decoder(key_bytes)
|
||||
|
||||
if any((wrapping_key, password)):
|
||||
key_bytes = unwrap_key(wrapped_key=key_bytes,
|
||||
wrapping_key=wrapping_key,
|
||||
password=password,
|
||||
**kwargs)
|
||||
|
||||
bn_key = CurveBN.from_bytes(key_bytes, params.curve)
|
||||
return cls(bn_key, params)
|
||||
|
||||
def to_bytes(self,
|
||||
wrapping_key: Optional[bytes] = None,
|
||||
password: Optional[bytes] = None,
|
||||
encoder: Optional[Callable] = None,
|
||||
**kwargs) -> bytes:
|
||||
"""
|
||||
Returns an UmbralPrivateKey as bytes with optional symmetric
|
||||
encryption via nacl's Salsa20-Poly1305.
|
||||
If a password is provided instead of a wrapping key, it will use
|
||||
Scrypt for key derivation.
|
||||
Optionally, allows an encoder to be passed in as a param to encode the
|
||||
data before returning it.
|
||||
"""
|
||||
|
||||
key_bytes = self.bn_key.to_bytes()
|
||||
|
||||
if wrapping_key or password:
|
||||
key_bytes = wrap_key(key_to_wrap=key_bytes,
|
||||
wrapping_key=wrapping_key,
|
||||
password=password,
|
||||
**kwargs)
|
||||
|
||||
if encoder:
|
||||
key_bytes = encoder(key_bytes)
|
||||
|
||||
return key_bytes
|
||||
|
||||
def get_pubkey(self) -> 'UmbralPublicKey':
|
||||
"""
|
||||
Calculates and returns the public key of the private key.
|
||||
"""
|
||||
return self.pubkey
|
||||
|
||||
def to_cryptography_privkey(self) -> _EllipticCurvePrivateKey:
|
||||
"""
|
||||
Returns a cryptography.io EllipticCurvePrivateKey from the Umbral key.
|
||||
"""
|
||||
backend = default_backend()
|
||||
|
||||
backend.openssl_assert(self.bn_key.curve.ec_group != backend._ffi.NULL)
|
||||
backend.openssl_assert(self.bn_key.bignum != backend._ffi.NULL)
|
||||
|
||||
ec_key = backend._lib.EC_KEY_new()
|
||||
backend.openssl_assert(ec_key != backend._ffi.NULL)
|
||||
ec_key = backend._ffi.gc(ec_key, backend._lib.EC_KEY_free)
|
||||
|
||||
set_group_result = backend._lib.EC_KEY_set_group(
|
||||
ec_key, self.bn_key.curve.ec_group
|
||||
)
|
||||
backend.openssl_assert(set_group_result == 1)
|
||||
|
||||
set_privkey_result = backend._lib.EC_KEY_set_private_key(
|
||||
ec_key, self.bn_key.bignum
|
||||
)
|
||||
backend.openssl_assert(set_privkey_result == 1)
|
||||
|
||||
# Get public key
|
||||
point = openssl._get_new_EC_POINT(self.params.curve)
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
mult_result = backend._lib.EC_POINT_mul(
|
||||
self.bn_key.curve.ec_group, point, self.bn_key.bignum,
|
||||
backend._ffi.NULL, backend._ffi.NULL, bn_ctx
|
||||
)
|
||||
backend.openssl_assert(mult_result == 1)
|
||||
|
||||
set_pubkey_result = backend._lib.EC_KEY_set_public_key(ec_key, point)
|
||||
backend.openssl_assert(set_pubkey_result == 1)
|
||||
|
||||
evp_pkey = backend._ec_cdata_to_evp_pkey(ec_key)
|
||||
return _EllipticCurvePrivateKey(backend, ec_key, evp_pkey)
|
||||
|
||||
|
||||
class UmbralPublicKey:
|
||||
def __init__(self, point_key: Point, params: UmbralParameters) -> None:
|
||||
"""
|
||||
Initializes an Umbral public key.
|
||||
"""
|
||||
self.params = params
|
||||
|
||||
if not isinstance(point_key, Point):
|
||||
raise TypeError("point_key can only be a Point. Don't pass anything else.")
|
||||
|
||||
self.point_key = point_key
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls,
|
||||
key_bytes: bytes,
|
||||
params: Optional[UmbralParameters] = None,
|
||||
decoder: Optional[Callable] = None) -> 'UmbralPublicKey':
|
||||
"""
|
||||
Loads an Umbral public key from bytes.
|
||||
Optionally, if an decoder function is provided it will be used to decode
|
||||
the data before returning it as an Umbral key.
|
||||
"""
|
||||
if params is None:
|
||||
params = default_params()
|
||||
|
||||
if decoder:
|
||||
key_bytes = decoder(key_bytes)
|
||||
|
||||
point_key = Point.from_bytes(key_bytes, params.curve)
|
||||
return cls(point_key, params)
|
||||
|
||||
@classmethod
|
||||
def expected_bytes_length(cls, curve: Optional[Curve] = None,
|
||||
is_compressed: bool = True) -> int:
|
||||
"""
|
||||
Returns the size (in bytes) of an UmbralPublicKey given a curve.
|
||||
If no curve is provided, it uses the default curve.
|
||||
By default, it assumes compressed representation (is_compressed = True).
|
||||
"""
|
||||
return Point.expected_bytes_length(curve=curve, is_compressed=is_compressed)
|
||||
|
||||
def to_bytes(self, encoder: Callable = None, is_compressed: bool = True) -> bytes:
|
||||
"""
|
||||
Returns an Umbral public key as bytes.
|
||||
Optionally, if an encoder function is provided it will be used to encode
|
||||
the data before returning it.
|
||||
"""
|
||||
umbral_pubkey = self.point_key.to_bytes(is_compressed=is_compressed)
|
||||
|
||||
if encoder:
|
||||
umbral_pubkey = encoder(umbral_pubkey)
|
||||
|
||||
return umbral_pubkey
|
||||
|
||||
def hex(self, is_compressed: bool = True) -> str:
|
||||
"""
|
||||
Returns an Umbral public key as hex string.
|
||||
"""
|
||||
return self.to_bytes(is_compressed=is_compressed).hex()
|
||||
|
||||
@classmethod
|
||||
def from_hex(cls, hex_string) -> 'UmbralPublicKey':
|
||||
return cls.from_bytes(key_bytes=hex_string, decoder=bytes.fromhex)
|
||||
|
||||
def to_cryptography_pubkey(self) -> _EllipticCurvePublicKey:
|
||||
"""
|
||||
Returns a cryptography.io EllipticCurvePublicKey from the Umbral key.
|
||||
"""
|
||||
backend = default_backend()
|
||||
|
||||
backend.openssl_assert(self.point_key.curve.ec_group != backend._ffi.NULL)
|
||||
backend.openssl_assert(self.point_key.ec_point != backend._ffi.NULL)
|
||||
|
||||
ec_key = backend._lib.EC_KEY_new()
|
||||
backend.openssl_assert(ec_key != backend._ffi.NULL)
|
||||
ec_key = backend._ffi.gc(ec_key, backend._lib.EC_KEY_free)
|
||||
|
||||
set_group_result = backend._lib.EC_KEY_set_group(
|
||||
ec_key, self.point_key.curve.ec_group
|
||||
)
|
||||
backend.openssl_assert(set_group_result == 1)
|
||||
|
||||
set_pubkey_result = backend._lib.EC_KEY_set_public_key(
|
||||
ec_key, self.point_key.ec_point
|
||||
)
|
||||
backend.openssl_assert(set_pubkey_result == 1)
|
||||
|
||||
evp_pkey = backend._ec_cdata_to_evp_pkey(ec_key)
|
||||
return _EllipticCurvePublicKey(backend, ec_key, evp_pkey)
|
||||
def __take__(cls, data: bytes) -> Tuple['SecretKey', bytes]:
|
||||
(scalar_key,), data = cls.__take_types__(data, CurveScalar)
|
||||
return cls(scalar_key), data
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
"""
|
||||
Returns an Umbral Public key as a bytestring.
|
||||
"""
|
||||
return self.point_key.to_bytes()
|
||||
return bytes(self._scalar_key)
|
||||
|
||||
def sign_digest(self, digest: 'Hash') -> 'Signature':
|
||||
|
||||
signature_algorithm = ECDSA(utils.Prehashed(digest._backend_hash_algorithm))
|
||||
message = digest.finalize()
|
||||
|
||||
backend_sk = openssl.bn_to_privkey(CURVE, self._scalar_key._backend_bignum)
|
||||
signature_der_bytes = backend_sk.sign(message, signature_algorithm)
|
||||
r_int, s_int = utils.decode_dss_signature(signature_der_bytes)
|
||||
|
||||
# Normalize s
|
||||
# s is public, so no constant-timeness required here
|
||||
if s_int > (CURVE.order >> 1):
|
||||
s_int = CURVE.order - s_int
|
||||
|
||||
# Already normalized, don't waste time
|
||||
r = CurveScalar.from_int(r_int, check_normalization=False)
|
||||
s = CurveScalar.from_int(s_int, check_normalization=False)
|
||||
|
||||
return Signature(r, s)
|
||||
|
||||
|
||||
class Signature(Serializable):
|
||||
"""
|
||||
Wrapper for ECDSA signatures.
|
||||
"""
|
||||
|
||||
def __init__(self, r: CurveScalar, s: CurveScalar):
|
||||
self.r = r
|
||||
self.s = s
|
||||
|
||||
def __repr__(self):
|
||||
return "{}:{}".format(self.__class__.__name__, self.point_key.to_bytes().hex()[:15])
|
||||
return f"ECDSA Signature: {bytes(self).hex()[:15]}"
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if type(other) == bytes:
|
||||
is_eq = bytes(other) == bytes(self)
|
||||
elif hasattr(other, "point_key") and hasattr(other, "params"):
|
||||
is_eq = (self.point_key, self.params) == (other.point_key, other.params)
|
||||
else:
|
||||
is_eq = False
|
||||
return is_eq
|
||||
def verify_digest(self, verifying_key: 'PublicKey', digest: 'Hash') -> bool:
|
||||
backend_pk = openssl.point_to_pubkey(CURVE, verifying_key.point()._backend_point)
|
||||
signature_algorithm = ECDSA(utils.Prehashed(digest._backend_hash_algorithm))
|
||||
|
||||
message = digest.finalize()
|
||||
signature_der_bytes = utils.encode_dss_signature(int(self.r), int(self.s))
|
||||
|
||||
# TODO: Raise error instead of returning boolean
|
||||
try:
|
||||
backend_pk.verify(signature=signature_der_bytes,
|
||||
data=message,
|
||||
signature_algorithm=signature_algorithm)
|
||||
except InvalidSignature:
|
||||
return False
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def __take__(cls, data):
|
||||
(r, s), data = cls.__take_types__(data, CurveScalar, CurveScalar)
|
||||
return cls(r, s), data
|
||||
|
||||
def __bytes__(self):
|
||||
return bytes(self.r) + bytes(self.s)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.r == other.r and self.s == other.s
|
||||
|
||||
|
||||
class PublicKey(Serializable):
|
||||
"""
|
||||
Umbral public key.
|
||||
"""
|
||||
|
||||
def __init__(self, point_key: CurvePoint):
|
||||
self._point_key = point_key
|
||||
|
||||
def point(self):
|
||||
return self._point_key
|
||||
|
||||
@classmethod
|
||||
def from_secret_key(cls, sk: SecretKey) -> 'PublicKey':
|
||||
"""
|
||||
Creates the public key corresponding to the given secret key.
|
||||
"""
|
||||
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 __bytes__(self) -> bytes:
|
||||
return bytes(self._point_key)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.__class__.__name__}:{bytes(self).hex()[:16]}"
|
||||
|
||||
def __eq__(self, other):
|
||||
return self._point_key == other._point_key
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return int.from_bytes(self.to_bytes(), byteorder="big")
|
||||
return hash((self.__class__, bytes(self)))
|
||||
|
||||
|
||||
class UmbralKeyingMaterial:
|
||||
class SecretKeyFactory(Serializable):
|
||||
"""
|
||||
This class handles keying material for Umbral, by allowing deterministic
|
||||
derivation of UmbralPrivateKeys based on labels.
|
||||
This class handles keyring material for Umbral, by allowing deterministic
|
||||
derivation of :py:class:`SecretKey` objects based on labels.
|
||||
|
||||
Don't use this key material directly as a key.
|
||||
"""
|
||||
|
||||
def __init__(self, keying_material: Optional[bytes] = None) -> None:
|
||||
"""
|
||||
Initializes an UmbralKeyingMaterial.
|
||||
"""
|
||||
if keying_material:
|
||||
if len(keying_material) < 32:
|
||||
raise ValueError("UmbralKeyingMaterial must have size at least 32 bytes.")
|
||||
self.__keying_material = keying_material
|
||||
else:
|
||||
self.__keying_material = os.urandom(64)
|
||||
_KEY_SEED_SIZE = 64
|
||||
_DERIVED_KEY_SIZE = 64
|
||||
|
||||
def derive_privkey_by_label(self,
|
||||
label: bytes,
|
||||
salt: Optional[bytes] = None,
|
||||
params: Optional[UmbralParameters] = None) -> UmbralPrivateKey:
|
||||
"""
|
||||
Derives an UmbralPrivateKey using a KDF from this instance of
|
||||
UmbralKeyingMaterial, a label, and an optional salt.
|
||||
"""
|
||||
params = params if params is not None else default_params()
|
||||
|
||||
key_material = HKDF(
|
||||
algorithm=hashes.BLAKE2b(64),
|
||||
length=64,
|
||||
salt=salt,
|
||||
info=b"NuCypher/KeyDerivation/"+label,
|
||||
backend=default_backend()
|
||||
).derive(self.__keying_material)
|
||||
|
||||
bn_key = hash_to_curvebn(key_material, params=params)
|
||||
return UmbralPrivateKey(bn_key, params)
|
||||
def __init__(self, key_seed: bytes):
|
||||
self.__key_seed = key_seed
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls,
|
||||
key_bytes: bytes,
|
||||
wrapping_key: Optional[bytes] = None,
|
||||
password: Optional[bytes] = None,
|
||||
decoder: Optional[Callable] = None,
|
||||
**kwargs) -> 'UmbralKeyingMaterial':
|
||||
def random(cls) -> 'SecretKeyFactory':
|
||||
"""
|
||||
Loads an UmbralKeyingMaterial from bytes.
|
||||
Optionally, allows a decoder function to be passed as a param to decode
|
||||
the data provided before converting to an Umbral key.
|
||||
Optionally, uses a wrapping key to unwrap an encrypted UmbralKeyingMaterial.
|
||||
Alternatively, if a password is provided it will derive the wrapping key
|
||||
from it.
|
||||
Creates a random factory.
|
||||
"""
|
||||
if decoder:
|
||||
key_bytes = decoder(key_bytes)
|
||||
return cls(os.urandom(cls._KEY_SEED_SIZE))
|
||||
|
||||
if any((password, wrapping_key)):
|
||||
key_bytes = unwrap_key(wrapped_key=key_bytes,
|
||||
wrapping_key=wrapping_key,
|
||||
password=password,
|
||||
**kwargs)
|
||||
|
||||
return cls(keying_material=key_bytes)
|
||||
|
||||
def to_bytes(self,
|
||||
wrapping_key: Optional[bytes] = None,
|
||||
password: Optional[bytes] = None,
|
||||
encoder: Optional[Callable] = None,
|
||||
**kwargs) -> bytes:
|
||||
def secret_key_by_label(self, label: bytes) -> SecretKey:
|
||||
"""
|
||||
Returns an UmbralKeyingMaterial as bytes with optional symmetric
|
||||
encryption via nacl's Salsa20-Poly1305.
|
||||
If a password is provided instead of a wrapping key, it will use
|
||||
Scrypt for key derivation.
|
||||
Optionally, allows an encoder to be passed in as a param to encode the
|
||||
data before returning it.
|
||||
Creates a :py:class:`SecretKey` from the given label.
|
||||
"""
|
||||
tag = b"KEY_DERIVATION/" + label
|
||||
key = kdf(self.__key_seed, self._DERIVED_KEY_SIZE, info=tag)
|
||||
|
||||
key_bytes = self.__keying_material
|
||||
from .hashing import Hash
|
||||
digest = Hash(tag)
|
||||
digest.update(key)
|
||||
scalar_key = CurveScalar.from_digest(digest)
|
||||
|
||||
if any((password, wrapping_key)):
|
||||
key_bytes = wrap_key(key_to_wrap=key_bytes,
|
||||
wrapping_key=wrapping_key,
|
||||
password=password,
|
||||
**kwargs)
|
||||
return SecretKey(scalar_key)
|
||||
|
||||
if encoder:
|
||||
key_bytes = encoder(key_bytes)
|
||||
@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
|
||||
|
||||
return key_bytes
|
||||
def __bytes__(self) -> bytes:
|
||||
return bytes(self.__key_seed)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.__class__.__name__}:..."
|
||||
|
||||
def __hash__(self):
|
||||
raise NotImplementedError("Hashing secret objects is insecure")
|
||||
|
|
199
umbral/kfrags.py
199
umbral/kfrags.py
|
@ -1,199 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import hmac
|
||||
from typing import Optional
|
||||
|
||||
from bytestring_splitter import BytestringSplitter
|
||||
|
||||
from umbral.config import default_curve, default_params
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.keys import UmbralPublicKey
|
||||
from umbral.point import Point
|
||||
from umbral.signing import Signature
|
||||
from umbral.params import UmbralParameters
|
||||
from umbral.curve import Curve
|
||||
|
||||
NO_KEY = b'\x00'
|
||||
DELEGATING_ONLY = b'\x01'
|
||||
RECEIVING_ONLY = b'\x02'
|
||||
DELEGATING_AND_RECEIVING = b'\x03'
|
||||
|
||||
|
||||
class KFrag:
|
||||
|
||||
def __init__(self,
|
||||
identifier: bytes,
|
||||
bn_key: CurveBN,
|
||||
point_commitment: Point,
|
||||
point_precursor: Point,
|
||||
signature_for_proxy: Signature,
|
||||
signature_for_bob: Signature,
|
||||
keys_in_signature=DELEGATING_AND_RECEIVING,
|
||||
) -> None:
|
||||
self.id = identifier
|
||||
self.bn_key = bn_key
|
||||
self.point_commitment = point_commitment
|
||||
self.point_precursor = point_precursor
|
||||
self.signature_for_proxy = signature_for_proxy
|
||||
self.signature_for_bob = signature_for_bob
|
||||
self.keys_in_signature = keys_in_signature
|
||||
|
||||
class NotValid(ValueError):
|
||||
"""
|
||||
raised if the KFrag does not pass verification.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def expected_bytes_length(cls, curve: Optional[Curve] = None) -> int:
|
||||
"""
|
||||
Returns the size (in bytes) of a KFrag given the curve.
|
||||
If no curve is provided, it will use the default curve.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
bn_size = CurveBN.expected_bytes_length(curve)
|
||||
point_size = Point.expected_bytes_length(curve)
|
||||
|
||||
# self.id --> 1 bn_size
|
||||
# self.bn_key --> 1 bn_size
|
||||
# self.point_commitment --> 1 point_size
|
||||
# self.point_precursor --> 1 point_size
|
||||
# self.signature_for_proxy --> 2 bn_size
|
||||
# self.signature_for_bob --> 2 bn_size
|
||||
# self.keys_in_signature --> 1
|
||||
|
||||
return bn_size * 6 + point_size * 2 + 1
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes, curve: Optional[Curve] = None) -> 'KFrag':
|
||||
"""
|
||||
Instantiate a KFrag object from the serialized data.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
|
||||
bn_size = CurveBN.expected_bytes_length(curve)
|
||||
point_size = Point.expected_bytes_length(curve)
|
||||
signature_size = Signature.expected_bytes_length(curve)
|
||||
arguments = {'curve': curve}
|
||||
|
||||
splitter = BytestringSplitter(
|
||||
bn_size, # id
|
||||
(CurveBN, bn_size, arguments), # bn_key
|
||||
(Point, point_size, arguments), # point_commitment
|
||||
(Point, point_size, arguments), # point_precursor
|
||||
1, # keys_in_signature
|
||||
(Signature, signature_size, arguments), # signature_for_proxy
|
||||
(Signature, signature_size, arguments), # signature_for_bob
|
||||
)
|
||||
components = splitter(data)
|
||||
|
||||
return cls(identifier=components[0],
|
||||
bn_key=components[1],
|
||||
point_commitment=components[2],
|
||||
point_precursor=components[3],
|
||||
keys_in_signature=components[4],
|
||||
signature_for_proxy=components[5],
|
||||
signature_for_bob=components[6])
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
"""
|
||||
Serialize the KFrag into a bytestring.
|
||||
"""
|
||||
key = self.bn_key.to_bytes()
|
||||
commitment = self.point_commitment.to_bytes()
|
||||
precursor = self.point_precursor.to_bytes()
|
||||
signature_for_proxy = bytes(self.signature_for_proxy)
|
||||
signature_for_bob = bytes(self.signature_for_bob)
|
||||
mode = bytes(self.keys_in_signature)
|
||||
|
||||
return self.id + key + commitment + precursor \
|
||||
+ mode + signature_for_proxy + signature_for_bob
|
||||
|
||||
def verify(self,
|
||||
signing_pubkey: UmbralPublicKey,
|
||||
delegating_pubkey: Optional[UmbralPublicKey] = None,
|
||||
receiving_pubkey: Optional[UmbralPublicKey] = None,
|
||||
params: Optional[UmbralParameters] = None,
|
||||
) -> bool:
|
||||
if params is None:
|
||||
params = default_params()
|
||||
|
||||
if signing_pubkey is None:
|
||||
raise ValueError("The verifying pubkey is required to verify this KFrag.")
|
||||
|
||||
if self.delegating_key_in_signature():
|
||||
if delegating_pubkey is None:
|
||||
raise ValueError("The delegating pubkey is required to verify this KFrag.")
|
||||
elif delegating_pubkey.params != params:
|
||||
raise ValueError("The delegating pubkey has different UmbralParameters.")
|
||||
|
||||
if self.receiving_key_in_signature():
|
||||
if receiving_pubkey is None:
|
||||
raise ValueError("The receiving pubkey is required to verify this KFrag.")
|
||||
elif receiving_pubkey.params != params:
|
||||
raise ValueError("The receiving pubkey has different UmbralParameters.")
|
||||
|
||||
u = params.u
|
||||
|
||||
kfrag_id = self.id
|
||||
key = self.bn_key
|
||||
commitment = self.point_commitment
|
||||
precursor = self.point_precursor
|
||||
|
||||
# We check that the commitment is well-formed
|
||||
correct_commitment = commitment == key * u
|
||||
|
||||
validity_input = [kfrag_id, commitment, precursor, self.keys_in_signature]
|
||||
|
||||
if self.delegating_key_in_signature():
|
||||
validity_input.append(delegating_pubkey)
|
||||
|
||||
if self.receiving_key_in_signature():
|
||||
validity_input.append(receiving_pubkey)
|
||||
|
||||
kfrag_validity_message = bytes().join(bytes(item) for item in validity_input)
|
||||
valid_kfrag_signature = self.signature_for_proxy.verify(kfrag_validity_message, signing_pubkey)
|
||||
|
||||
return correct_commitment & valid_kfrag_signature
|
||||
|
||||
def verify_for_capsule(self, capsule) -> bool:
|
||||
correctness_keys = capsule.get_correctness_keys()
|
||||
|
||||
return self.verify(params=capsule.params,
|
||||
signing_pubkey=correctness_keys["verifying"],
|
||||
delegating_pubkey=correctness_keys["delegating"],
|
||||
receiving_pubkey=correctness_keys["receiving"])
|
||||
|
||||
def delegating_key_in_signature(self):
|
||||
return self.keys_in_signature == DELEGATING_ONLY or \
|
||||
self.keys_in_signature == DELEGATING_AND_RECEIVING
|
||||
|
||||
def receiving_key_in_signature(self):
|
||||
return self.keys_in_signature == RECEIVING_ONLY or \
|
||||
self.keys_in_signature == DELEGATING_AND_RECEIVING
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
return self.to_bytes()
|
||||
|
||||
def __eq__(self, other):
|
||||
return hmac.compare_digest(bytes(self), bytes(other))
|
||||
|
||||
def __hash__(self):
|
||||
return hash(bytes(self.id))
|
||||
|
||||
def __repr__(self):
|
||||
return "{}:{}".format(self.__class__.__name__, self.id.hex()[:15])
|
|
@ -1,218 +1,440 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import typing
|
||||
from contextlib import contextmanager
|
||||
from typing import Tuple
|
||||
|
||||
from cryptography.exceptions import InternalError
|
||||
from cryptography.hazmat.backends.openssl import backend
|
||||
from cryptography.hazmat.backends.openssl.ec import _EllipticCurvePrivateKey, _EllipticCurvePublicKey
|
||||
|
||||
|
||||
@typing.no_type_check
|
||||
def _get_new_BN(set_consttime_flag=True):
|
||||
class Curve:
|
||||
"""
|
||||
Acts as a container to store constant variables such as the OpenSSL
|
||||
curve_nid, the EC_GROUP struct, and the order of the curve.
|
||||
|
||||
Contains a whitelist of supported elliptic curves used in pyUmbral.
|
||||
"""
|
||||
|
||||
_supported_curves = {
|
||||
415: 'secp256r1',
|
||||
714: 'secp256k1',
|
||||
715: 'secp384r1'
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _get_ec_group_by_curve_nid(nid: int):
|
||||
"""
|
||||
Returns the group of a given curve via its OpenSSL nid. This must be freed
|
||||
after each use otherwise it leaks memory.
|
||||
"""
|
||||
group = backend._lib.EC_GROUP_new_by_curve_name(nid)
|
||||
backend.openssl_assert(group != backend._ffi.NULL)
|
||||
return group
|
||||
|
||||
@staticmethod
|
||||
def _get_ec_order_by_group(ec_group):
|
||||
"""
|
||||
Returns the order of a given curve via its OpenSSL EC_GROUP.
|
||||
"""
|
||||
ec_order = _bn_new()
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.EC_GROUP_get_order(ec_group, ec_order, bn_ctx)
|
||||
backend.openssl_assert(res == 1)
|
||||
return ec_order
|
||||
|
||||
@staticmethod
|
||||
def _get_ec_generator_by_group(ec_group):
|
||||
"""
|
||||
Returns the generator point of a given curve via its OpenSSL EC_GROUP.
|
||||
"""
|
||||
generator = backend._lib.EC_GROUP_get0_generator(ec_group)
|
||||
backend.openssl_assert(generator != backend._ffi.NULL)
|
||||
generator = backend._ffi.gc(generator, backend._lib.EC_POINT_clear_free)
|
||||
|
||||
return generator
|
||||
|
||||
@staticmethod
|
||||
def _get_ec_group_degree(ec_group):
|
||||
"""
|
||||
Returns the number of bits needed to represent the order of the finite
|
||||
field upon the curve is based.
|
||||
"""
|
||||
return backend._lib.EC_GROUP_get_degree(ec_group)
|
||||
|
||||
def __init__(self, nid: int):
|
||||
"""
|
||||
Instantiates an OpenSSL curve with the provided curve_nid and derives
|
||||
the proper EC_GROUP struct and order. You can _only_ instantiate curves
|
||||
with supported nids (see `Curve.supported_curves`).
|
||||
"""
|
||||
|
||||
try:
|
||||
self.name = self._supported_curves[nid]
|
||||
except KeyError:
|
||||
raise NotImplementedError("Curve NID {} is not supported.".format(nid))
|
||||
|
||||
self.nid = nid
|
||||
|
||||
self.ec_group = self._get_ec_group_by_curve_nid(self.nid)
|
||||
self.bn_order = self._get_ec_order_by_group(self.ec_group)
|
||||
self.point_generator = self._get_ec_generator_by_group(self.ec_group)
|
||||
|
||||
size_in_bits = self._get_ec_group_degree(self.ec_group)
|
||||
self.field_element_size = (size_in_bits + 7) // 8
|
||||
|
||||
self.scalar_size = _bn_size(self.bn_order)
|
||||
self.order = bn_to_int(self.bn_order)
|
||||
|
||||
@classmethod
|
||||
def from_name(cls, name: str) -> 'Curve':
|
||||
"""
|
||||
Alternate constructor to generate a curve instance by its name.
|
||||
|
||||
Raises NotImplementedError if the name cannot be mapped to a known
|
||||
supported curve NID.
|
||||
"""
|
||||
|
||||
name = name.casefold() # normalize
|
||||
|
||||
for supported_nid, supported_name in cls._supported_curves.items():
|
||||
if name == supported_name:
|
||||
instance = cls(nid=supported_nid)
|
||||
break
|
||||
else:
|
||||
raise NotImplementedError(f"{name} is not supported curve name.")
|
||||
|
||||
return instance
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.nid == other.nid
|
||||
|
||||
def __str__(self):
|
||||
return "<OpenSSL Curve(nid={}, name={})>".format(self.nid, self.name)
|
||||
|
||||
|
||||
#
|
||||
# OpenSSL bignums
|
||||
#
|
||||
|
||||
|
||||
def _bn_new():
|
||||
"""
|
||||
Returns a new and initialized OpenSSL BIGNUM.
|
||||
The set_consttime_flag is set to True by default. When this instance of a
|
||||
CurveBN object has BN_FLG_CONSTTIME set, OpenSSL will use constant time
|
||||
operations whenever this CurveBN is passed.
|
||||
"""
|
||||
new_bn = backend._lib.BN_new()
|
||||
backend.openssl_assert(new_bn != backend._ffi.NULL)
|
||||
new_bn = backend._ffi.gc(new_bn, backend._lib.BN_clear_free)
|
||||
|
||||
if set_consttime_flag:
|
||||
backend._lib.BN_set_flags(new_bn, backend._lib.BN_FLG_CONSTTIME)
|
||||
# Always use constant time operations.
|
||||
backend._lib.BN_set_flags(new_bn, backend._lib.BN_FLG_CONSTTIME)
|
||||
return new_bn
|
||||
|
||||
|
||||
@typing.no_type_check
|
||||
def _get_ec_group_by_curve_nid(curve_nid: int):
|
||||
def bn_is_normalized(check_bn, modulus):
|
||||
"""
|
||||
Returns the group of a given curve via its OpenSSL nid. This must be freed
|
||||
after each use otherwise it leaks memory.
|
||||
"""
|
||||
group = backend._lib.EC_GROUP_new_by_curve_name(curve_nid)
|
||||
backend.openssl_assert(group != backend._ffi.NULL)
|
||||
|
||||
return group
|
||||
|
||||
|
||||
@typing.no_type_check
|
||||
def _get_ec_order_by_group(ec_group):
|
||||
"""
|
||||
Returns the order of a given curve via its OpenSSL EC_GROUP.
|
||||
"""
|
||||
ec_order = _get_new_BN()
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.EC_GROUP_get_order(ec_group, ec_order, bn_ctx)
|
||||
backend.openssl_assert(res == 1)
|
||||
return ec_order
|
||||
|
||||
|
||||
@typing.no_type_check
|
||||
def _get_ec_generator_by_group(ec_group):
|
||||
"""
|
||||
Returns the generator point of a given curve via its OpenSSL EC_GROUP.
|
||||
"""
|
||||
generator = backend._lib.EC_GROUP_get0_generator(ec_group)
|
||||
backend.openssl_assert(generator != backend._ffi.NULL)
|
||||
generator = backend._ffi.gc(generator, backend._lib.EC_POINT_clear_free)
|
||||
|
||||
return generator
|
||||
|
||||
|
||||
@typing.no_type_check
|
||||
def _get_ec_group_degree(ec_group):
|
||||
"""
|
||||
Returns the number of bits needed to represent the order of the finite
|
||||
field upon the curve is based.
|
||||
"""
|
||||
return backend._lib.EC_GROUP_get_degree(ec_group)
|
||||
|
||||
|
||||
@typing.no_type_check
|
||||
def _bn_is_on_curve(check_bn, curve: 'Curve'):
|
||||
"""
|
||||
Checks if a given OpenSSL BIGNUM is within the provided curve's order.
|
||||
Returns True if the provided BN is on the curve, or False if the BN is zero
|
||||
or not on the curve.
|
||||
Returns ``True`` if ``check_bn`` is in ``[0, modulus)``, ``False`` otherwise.
|
||||
"""
|
||||
zero = backend._int_to_bn(0)
|
||||
zero = backend._ffi.gc(zero, backend._lib.BN_clear_free)
|
||||
|
||||
check_sign = backend._lib.BN_cmp(check_bn, zero)
|
||||
range_check = backend._lib.BN_cmp(check_bn, curve.order)
|
||||
return check_sign == 1 and range_check == -1
|
||||
range_check = backend._lib.BN_cmp(check_bn, modulus)
|
||||
return (check_sign == 1 or check_sign == 0) and range_check == -1
|
||||
|
||||
|
||||
@typing.no_type_check
|
||||
def _int_to_bn(py_int: int, curve: 'Curve'=None, set_consttime_flag=True):
|
||||
def bn_from_int(py_int: int, check_modulus=None):
|
||||
"""
|
||||
Converts the given Python int to an OpenSSL BIGNUM. If a curve is
|
||||
provided, it will check if the Python integer is within the order of that
|
||||
curve. If it's not within the order, it will raise a ValueError.
|
||||
|
||||
If set_consttime_flag is set to True, OpenSSL will use constant time
|
||||
operations when using this CurveBN.
|
||||
Converts the given Python int to an OpenSSL BIGNUM. If ``modulus`` is
|
||||
provided, it will check if the Python integer is within ``[0, modulus)``.
|
||||
"""
|
||||
conv_bn = backend._int_to_bn(py_int)
|
||||
conv_bn = backend._ffi.gc(conv_bn, backend._lib.BN_clear_free)
|
||||
|
||||
if curve:
|
||||
on_curve = _bn_is_on_curve(conv_bn, curve)
|
||||
if not on_curve:
|
||||
raise ValueError("The Python integer given is not on the provided curve.")
|
||||
|
||||
if set_consttime_flag:
|
||||
backend._lib.BN_set_flags(conv_bn, backend._lib.BN_FLG_CONSTTIME)
|
||||
if check_modulus and not bn_is_normalized(conv_bn, check_modulus):
|
||||
raise ValueError(f"The Python integer given ({py_int}) is not under the provided modulus.")
|
||||
|
||||
backend._lib.BN_set_flags(conv_bn, backend._lib.BN_FLG_CONSTTIME)
|
||||
return conv_bn
|
||||
|
||||
@typing.no_type_check
|
||||
def _bytes_to_bn(bytes_seq: bytes, set_consttime_flag=True):
|
||||
"""
|
||||
Converts the given byte sequence to an OpenSSL BIGNUM.
|
||||
If set_consttime_flag is set to True, OpenSSL will use constant time
|
||||
operations when using this BIGNUM.
|
||||
"""
|
||||
bn = _get_new_BN(set_consttime_flag)
|
||||
backend._lib.BN_bin2bn(bytes_seq, len(bytes_seq), bn)
|
||||
backend.openssl_assert(bn != backend._ffi.NULL)
|
||||
return bn
|
||||
|
||||
@typing.no_type_check
|
||||
def _bn_to_bytes(bignum, length : int = None):
|
||||
def bn_from_bytes(bytes_seq: bytes, check_modulus=None, apply_modulus=None):
|
||||
"""
|
||||
Converts the given byte sequence to an OpenSSL BIGNUM.
|
||||
"""
|
||||
bn = _bn_new()
|
||||
backend._lib.BN_bin2bn(bytes_seq, len(bytes_seq), bn)
|
||||
backend.openssl_assert(bn != backend._ffi.NULL)
|
||||
|
||||
if check_modulus and not bn_is_normalized(bn, check_modulus):
|
||||
raise ValueError(f"The integer encoded with given bytes ({repr(bytes_seq)}) "
|
||||
"is not under the provided modulus.")
|
||||
|
||||
if apply_modulus:
|
||||
bignum =_bn_new()
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.BN_mod(bignum, bn, apply_modulus, bn_ctx)
|
||||
backend.openssl_assert(res == 1)
|
||||
return bignum
|
||||
else:
|
||||
return bn
|
||||
|
||||
|
||||
def bn_to_bytes(bn, length: int):
|
||||
"""
|
||||
Converts the given OpenSSL BIGNUM into a Python bytes sequence.
|
||||
If length is given, the return bytes will have such length.
|
||||
If the BIGNUM doesn't fit, it raises a ValueError.
|
||||
"""
|
||||
|
||||
if bignum is None or bignum == backend._ffi.NULL:
|
||||
raise ValueError("Input BIGNUM must have a value")
|
||||
|
||||
bn_num_bytes = backend._lib.BN_num_bytes(bignum)
|
||||
if length is None:
|
||||
length = bn_num_bytes
|
||||
elif bn_num_bytes > length:
|
||||
raise ValueError("Input BIGNUM doesn't fit in {} B".format(length))
|
||||
# Sanity check, CurveScalar ensures it won't happen.
|
||||
bn_num_bytes = backend._lib.BN_num_bytes(bn)
|
||||
assert bn_num_bytes <= length, f"Input BIGNUM doesn't fit in {length} B"
|
||||
|
||||
bin_ptr = backend._ffi.new("unsigned char []", length)
|
||||
bin_len = backend._lib.BN_bn2bin(bignum, bin_ptr)
|
||||
bin_len = backend._lib.BN_bn2bin(bn, bin_ptr)
|
||||
return bytes.rjust(backend._ffi.buffer(bin_ptr, bin_len)[:], length, b'\0')
|
||||
|
||||
|
||||
@typing.no_type_check
|
||||
def _get_new_EC_POINT(curve: 'Curve'):
|
||||
def bn_random_nonzero(modulus):
|
||||
|
||||
one = backend._lib.BN_value_one()
|
||||
|
||||
# TODO: in most cases, we want this number to be secret.
|
||||
# OpenSSL 1.1.1 has `BN_priv_rand_range()`, but it is not
|
||||
# currently exported by `cryptography`.
|
||||
# Use when available.
|
||||
|
||||
# Calculate `modulus - 1`
|
||||
modulus_minus_1 = _bn_new()
|
||||
res = backend._lib.BN_sub(modulus_minus_1, modulus, one)
|
||||
backend.openssl_assert(res == 1)
|
||||
|
||||
# Get a random in range `[0, modulus - 1)`
|
||||
new_rand_bn = _bn_new()
|
||||
res = backend._lib.BN_rand_range(new_rand_bn, modulus_minus_1)
|
||||
backend.openssl_assert(res == 1)
|
||||
|
||||
# Turn it into a random in range `[1, modulus)`
|
||||
op_sum = _bn_new()
|
||||
res = backend._lib.BN_add(op_sum, new_rand_bn, one)
|
||||
backend.openssl_assert(res == 1)
|
||||
|
||||
return op_sum
|
||||
|
||||
|
||||
def _bn_size(bn):
|
||||
return backend._lib.BN_num_bytes(bn)
|
||||
|
||||
|
||||
def bn_to_int(bn):
|
||||
return backend._bn_to_int(bn)
|
||||
|
||||
|
||||
def bn_cmp(bn1, bn2):
|
||||
# -1 less than, 0 is equal to, 1 is greater than
|
||||
return backend._lib.BN_cmp(bn1, bn2)
|
||||
|
||||
|
||||
def bn_one():
|
||||
return backend._lib.BN_value_one()
|
||||
|
||||
|
||||
def bn_is_zero(bn):
|
||||
# No special function exported in the current backend, so this will have to do
|
||||
return bn_cmp(bn, bn_from_int(0)) == 0
|
||||
|
||||
|
||||
def bn_invert(bn, modulus):
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
inv = backend._lib.BN_mod_inverse(backend._ffi.NULL, bn, modulus, bn_ctx)
|
||||
backend.openssl_assert(inv != backend._ffi.NULL)
|
||||
inv = backend._ffi.gc(inv, backend._lib.BN_clear_free)
|
||||
return inv
|
||||
|
||||
|
||||
def bn_sub(bn1, bn2, modulus):
|
||||
diff = _bn_new()
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.BN_mod_sub(diff, bn1, bn2, modulus, bn_ctx)
|
||||
backend.openssl_assert(res == 1)
|
||||
return diff
|
||||
|
||||
|
||||
def bn_add(bn1, bn2, modulus):
|
||||
op_sum = _bn_new()
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.BN_mod_add(op_sum, bn1, bn2, modulus, bn_ctx)
|
||||
backend.openssl_assert(res == 1)
|
||||
return op_sum
|
||||
|
||||
|
||||
def bn_mul(bn1, bn2, modulus):
|
||||
product = _bn_new()
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.BN_mod_mul(product, bn1, bn2, modulus, bn_ctx)
|
||||
backend.openssl_assert(res == 1)
|
||||
return product
|
||||
|
||||
|
||||
def bn_to_privkey(curve: Curve, bn):
|
||||
|
||||
ec_key = backend._lib.EC_KEY_new()
|
||||
backend.openssl_assert(ec_key != backend._ffi.NULL)
|
||||
ec_key = backend._ffi.gc(ec_key, backend._lib.EC_KEY_free)
|
||||
|
||||
set_group_result = backend._lib.EC_KEY_set_group(ec_key, curve.ec_group)
|
||||
backend.openssl_assert(set_group_result == 1)
|
||||
|
||||
set_privkey_result = backend._lib.EC_KEY_set_private_key(ec_key, bn)
|
||||
backend.openssl_assert(set_privkey_result == 1)
|
||||
|
||||
evp_pkey = backend._ec_cdata_to_evp_pkey(ec_key)
|
||||
return _EllipticCurvePrivateKey(backend, ec_key, evp_pkey)
|
||||
|
||||
|
||||
#
|
||||
# OpenSSL EC points
|
||||
#
|
||||
|
||||
|
||||
def _point_new(ec_group):
|
||||
"""
|
||||
Returns a new and initialized OpenSSL EC_POINT given the group of a curve.
|
||||
If __curve_nid is provided, it retrieves the group from the curve provided.
|
||||
"""
|
||||
new_point = backend._lib.EC_POINT_new(curve.ec_group)
|
||||
new_point = backend._lib.EC_POINT_new(ec_group)
|
||||
backend.openssl_assert(new_point != backend._ffi.NULL)
|
||||
new_point = backend._ffi.gc(new_point, backend._lib.EC_POINT_clear_free)
|
||||
|
||||
return new_point
|
||||
|
||||
|
||||
@typing.no_type_check
|
||||
def _get_EC_POINT_via_affine(affine_x, affine_y, curve: 'Curve'):
|
||||
def point_from_affine_coords(curve: Curve, affine_x: int, affine_y: int):
|
||||
"""
|
||||
Returns an EC_POINT given the group of a curve and the affine coordinates
|
||||
provided.
|
||||
"""
|
||||
new_point = _get_new_EC_POINT(curve)
|
||||
bn_affine_x = bn_from_int(affine_x)
|
||||
bn_affine_y = bn_from_int(affine_y)
|
||||
|
||||
new_point = _point_new(curve.ec_group)
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.EC_POINT_set_affine_coordinates_GFp(
|
||||
curve.ec_group, new_point, affine_x, affine_y, bn_ctx
|
||||
curve.ec_group, new_point, bn_affine_x, bn_affine_y, bn_ctx
|
||||
)
|
||||
backend.openssl_assert(res == 1)
|
||||
return new_point
|
||||
|
||||
|
||||
@typing.no_type_check
|
||||
def _get_affine_coords_via_EC_POINT(ec_point, curve: 'Curve'):
|
||||
def point_to_affine_coords(curve: Curve, point) -> Tuple[int, int]:
|
||||
"""
|
||||
Returns the affine coordinates of a given point on the provided ec_group.
|
||||
"""
|
||||
affine_x = _get_new_BN()
|
||||
affine_y = _get_new_BN()
|
||||
affine_x = _bn_new()
|
||||
affine_y = _bn_new()
|
||||
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.EC_POINT_get_affine_coordinates_GFp(
|
||||
curve.ec_group, ec_point, affine_x, affine_y, bn_ctx
|
||||
curve.ec_group, point, affine_x, affine_y, bn_ctx
|
||||
)
|
||||
backend.openssl_assert(res == 1)
|
||||
return (affine_x, affine_y)
|
||||
|
||||
return bn_to_int(affine_x), bn_to_int(affine_y)
|
||||
|
||||
|
||||
@typing.no_type_check
|
||||
@contextmanager
|
||||
def _tmp_bn_mont_ctx(modulus):
|
||||
"""
|
||||
Initializes and returns a BN_MONT_CTX for Montgomery ops.
|
||||
Requires a modulus to place in the Montgomery structure.
|
||||
"""
|
||||
bn_mont_ctx = backend._lib.BN_MONT_CTX_new()
|
||||
backend.openssl_assert(bn_mont_ctx != backend._ffi.NULL)
|
||||
# Don't set the garbage collector. Only free it when the context is done
|
||||
# or else you'll get a null pointer error.
|
||||
class ErrorInvalidCompressedPoint(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ErrorInvalidPointEncoding(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def point_from_bytes(curve: Curve, data):
|
||||
point = _point_new(curve.ec_group)
|
||||
try:
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.BN_MONT_CTX_set(bn_mont_ctx, modulus, bn_ctx)
|
||||
res = backend._lib.EC_POINT_oct2point(curve.ec_group, point, data, len(data), bn_ctx);
|
||||
backend.openssl_assert(res == 1)
|
||||
yield bn_mont_ctx
|
||||
finally:
|
||||
backend._lib.BN_MONT_CTX_free(bn_mont_ctx)
|
||||
except InternalError as e:
|
||||
# We want to catch specific InternalExceptions.
|
||||
# https://github.com/openssl/openssl/blob/master/include/openssl/ecerr.h
|
||||
# There is also EC_R_POINT_IS_NOT_ON_CURVE (code 107),
|
||||
# but somehow it is never triggered during deserialization.
|
||||
if e.err_code[0].reason == 110: # EC_R_INVALID_COMPRESSED_POINT
|
||||
raise ErrorInvalidCompressedPoint
|
||||
elif e.err_code[0].reason == 102: # EC_R_INVALID_ENCODING
|
||||
raise ErrorInvalidPointEncoding
|
||||
else:
|
||||
# Any other exception, we raise it.
|
||||
# (although at the moment I'm not sure what should one do to cause it)
|
||||
raise e # pragma: no cover
|
||||
return point
|
||||
|
||||
|
||||
def point_to_bytes_compressed(curve: Curve, point):
|
||||
point_conversion_form = backend._lib.POINT_CONVERSION_COMPRESSED
|
||||
|
||||
size = curve.field_element_size + 1 # compressed point size
|
||||
|
||||
bin_ptr = backend._ffi.new("unsigned char[]", size)
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
bin_len = backend._lib.EC_POINT_point2oct(
|
||||
curve.ec_group, point, point_conversion_form,
|
||||
bin_ptr, size, bn_ctx
|
||||
)
|
||||
backend.openssl_assert(bin_len != 0)
|
||||
|
||||
return bytes(backend._ffi.buffer(bin_ptr, bin_len)[:])
|
||||
|
||||
|
||||
def point_eq(curve: Curve, point1, point2):
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
is_equal = backend._lib.EC_POINT_cmp(curve.ec_group, point1, point2, bn_ctx)
|
||||
backend.openssl_assert(is_equal != -1)
|
||||
|
||||
# 1 is not-equal, 0 is equal, -1 is error
|
||||
return is_equal == 0
|
||||
|
||||
|
||||
def point_mul_bn(curve: Curve, point, bn):
|
||||
prod = _point_new(curve.ec_group)
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.EC_POINT_mul(curve.ec_group, prod, backend._ffi.NULL, point, bn, bn_ctx)
|
||||
backend.openssl_assert(res == 1)
|
||||
return prod
|
||||
|
||||
|
||||
def point_add(curve: Curve, point1, point2):
|
||||
op_sum = _point_new(curve.ec_group)
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.EC_POINT_add(curve.ec_group, op_sum, point1, point2, bn_ctx)
|
||||
backend.openssl_assert(res == 1)
|
||||
return op_sum
|
||||
|
||||
|
||||
def point_neg(curve: Curve, point):
|
||||
inv = backend._lib.EC_POINT_dup(point, curve.ec_group)
|
||||
backend.openssl_assert(inv != backend._ffi.NULL)
|
||||
inv = backend._ffi.gc(inv, backend._lib.EC_POINT_clear_free)
|
||||
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.EC_POINT_invert(curve.ec_group, inv, bn_ctx)
|
||||
backend.openssl_assert(res == 1)
|
||||
|
||||
return inv
|
||||
|
||||
|
||||
def point_to_pubkey(curve: Curve, point):
|
||||
|
||||
ec_key = backend._lib.EC_KEY_new()
|
||||
backend.openssl_assert(ec_key != backend._ffi.NULL)
|
||||
ec_key = backend._ffi.gc(ec_key, backend._lib.EC_KEY_free)
|
||||
|
||||
set_group_result = backend._lib.EC_KEY_set_group(ec_key, curve.ec_group)
|
||||
backend.openssl_assert(set_group_result == 1)
|
||||
|
||||
set_pubkey_result = backend._lib.EC_KEY_set_public_key(ec_key, point)
|
||||
backend.openssl_assert(set_pubkey_result == 1)
|
||||
|
||||
evp_pkey = backend._ec_cdata_to_evp_pkey(ec_key)
|
||||
return _EllipticCurvePublicKey(backend, ec_key, evp_pkey)
|
||||
|
|
|
@ -1,41 +1,10 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from umbral.curve import Curve
|
||||
from .hashing import unsafe_hash_to_point
|
||||
|
||||
|
||||
class UmbralParameters:
|
||||
def __init__(self, curve: Curve) -> None:
|
||||
from umbral.point import Point
|
||||
from umbral.random_oracles import unsafe_hash_to_point
|
||||
class Parameters:
|
||||
|
||||
self.curve = curve
|
||||
self.CURVE_KEY_SIZE_BYTES = self.curve.field_order_size_in_bytes
|
||||
def __init__(self):
|
||||
self.u = unsafe_hash_to_point(b'PARAMETERS', b'POINT_U')
|
||||
|
||||
self.g = Point.get_generator_from_curve(curve=curve)
|
||||
g_bytes = self.g.to_bytes()
|
||||
|
||||
parameters_seed = b'NuCypher/UmbralParameters/'
|
||||
self.u = unsafe_hash_to_point(g_bytes, self, parameters_seed + b'u')
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
|
||||
# TODO: This is not comparing the order, which currently is an OpenSSL pointer
|
||||
self_attributes = self.curve, self.g, self.CURVE_KEY_SIZE_BYTES, self.u
|
||||
others_attributes = other.curve, other.g, other.CURVE_KEY_SIZE_BYTES, other.u
|
||||
|
||||
return self_attributes == others_attributes
|
||||
PARAMETERS = Parameters()
|
||||
|
|
211
umbral/point.py
211
umbral/point.py
|
@ -1,211 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from cryptography.hazmat.backends.openssl import backend
|
||||
|
||||
from umbral import openssl
|
||||
from umbral.config import default_curve
|
||||
from umbral.curve import Curve
|
||||
from umbral.curvebn import CurveBN
|
||||
|
||||
|
||||
class Point:
|
||||
"""
|
||||
Represents an OpenSSL EC_POINT except more Pythonic
|
||||
"""
|
||||
|
||||
def __init__(self, ec_point, curve: Curve) -> None:
|
||||
self.ec_point = ec_point
|
||||
self.curve = curve
|
||||
|
||||
@classmethod
|
||||
def expected_bytes_length(cls, curve: Optional[Curve] = None,
|
||||
is_compressed: bool = True):
|
||||
"""
|
||||
Returns the size (in bytes) of a Point given a curve.
|
||||
If no curve is provided, it uses the default curve.
|
||||
By default, it assumes compressed representation (is_compressed = True).
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
|
||||
coord_size = curve.field_order_size_in_bytes
|
||||
|
||||
if is_compressed:
|
||||
return 1 + coord_size
|
||||
else:
|
||||
return 1 + 2 * coord_size
|
||||
|
||||
@classmethod
|
||||
def gen_rand(cls, curve: Optional[Curve] = None) -> 'Point':
|
||||
"""
|
||||
Returns a Point object with a cryptographically secure EC_POINT based
|
||||
on the provided curve.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
|
||||
rand_point = openssl._get_new_EC_POINT(curve)
|
||||
rand_bn = CurveBN.gen_rand(curve).bignum
|
||||
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.EC_POINT_mul(
|
||||
curve.ec_group, rand_point, backend._ffi.NULL, curve.generator,
|
||||
rand_bn, bn_ctx
|
||||
)
|
||||
backend.openssl_assert(res == 1)
|
||||
|
||||
return cls(rand_point, curve)
|
||||
|
||||
@classmethod
|
||||
def from_affine(cls, coords: Tuple[int, int], curve: Optional[Curve] = None) -> 'Point':
|
||||
"""
|
||||
Returns a Point object from the given affine coordinates in a tuple in
|
||||
the format of (x, y) and a given curve.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
|
||||
affine_x, affine_y = coords
|
||||
if type(affine_x) == int:
|
||||
affine_x = openssl._int_to_bn(affine_x, curve=None)
|
||||
|
||||
if type(affine_y) == int:
|
||||
affine_y = openssl._int_to_bn(affine_y, curve=None)
|
||||
|
||||
ec_point = openssl._get_EC_POINT_via_affine(affine_x, affine_y, curve)
|
||||
return cls(ec_point, curve)
|
||||
|
||||
def to_affine(self):
|
||||
"""
|
||||
Returns a tuple of Python ints in the format of (x, y) that represents
|
||||
the point in the curve.
|
||||
"""
|
||||
affine_x, affine_y = openssl._get_affine_coords_via_EC_POINT(
|
||||
self.ec_point, self.curve)
|
||||
return (backend._bn_to_int(affine_x), backend._bn_to_int(affine_y))
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes, curve: Optional[Curve] = None) -> 'Point':
|
||||
"""
|
||||
Returns a Point object from the given byte data on the curve provided.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
|
||||
point = openssl._get_new_EC_POINT(curve)
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.EC_POINT_oct2point(
|
||||
curve.ec_group, point, data, len(data), bn_ctx);
|
||||
backend.openssl_assert(res == 1)
|
||||
|
||||
return cls(point, curve)
|
||||
|
||||
def to_bytes(self, is_compressed: bool=True) -> bytes:
|
||||
"""
|
||||
Returns the Point serialized as bytes. It will return a compressed form
|
||||
if is_compressed is set to True.
|
||||
"""
|
||||
length = self.expected_bytes_length(self.curve, is_compressed)
|
||||
|
||||
if is_compressed:
|
||||
point_conversion_form = backend._lib.POINT_CONVERSION_COMPRESSED
|
||||
else:
|
||||
point_conversion_form = backend._lib.POINT_CONVERSION_UNCOMPRESSED
|
||||
|
||||
bin_ptr = backend._ffi.new("unsigned char[]", length)
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
bin_len = backend._lib.EC_POINT_point2oct(
|
||||
self.curve.ec_group, self.ec_point, point_conversion_form,
|
||||
bin_ptr, length, bn_ctx
|
||||
)
|
||||
backend.openssl_assert(bin_len != 0)
|
||||
|
||||
return bytes(backend._ffi.buffer(bin_ptr, bin_len)[:])
|
||||
|
||||
@classmethod
|
||||
def get_generator_from_curve(cls, curve: Optional[Curve] = None) -> 'Point':
|
||||
"""
|
||||
Returns the generator Point from the given curve as a Point object.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
return cls(curve.generator, curve)
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
Compares two EC_POINTS for equality.
|
||||
"""
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
is_equal = backend._lib.EC_POINT_cmp(
|
||||
self.curve.ec_group, self.ec_point, other.ec_point, bn_ctx
|
||||
)
|
||||
backend.openssl_assert(is_equal != -1)
|
||||
|
||||
# 1 is not-equal, 0 is equal, -1 is error
|
||||
return not bool(is_equal)
|
||||
|
||||
def __mul__(self, other: CurveBN) -> 'Point':
|
||||
"""
|
||||
Performs an EC_POINT_mul on an EC_POINT and a BIGNUM.
|
||||
"""
|
||||
# TODO: Check that both points use the same curve.
|
||||
prod = openssl._get_new_EC_POINT(self.curve)
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.EC_POINT_mul(
|
||||
self.curve.ec_group, prod, backend._ffi.NULL,
|
||||
self.ec_point, other.bignum, bn_ctx
|
||||
)
|
||||
backend.openssl_assert(res == 1)
|
||||
|
||||
return Point(prod, self.curve)
|
||||
|
||||
__rmul__ = __mul__
|
||||
|
||||
def __add__(self, other) -> 'Point':
|
||||
"""
|
||||
Performs an EC_POINT_add on two EC_POINTS.
|
||||
"""
|
||||
op_sum = openssl._get_new_EC_POINT(self.curve)
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.EC_POINT_add(
|
||||
self.curve.ec_group, op_sum, self.ec_point, other.ec_point, bn_ctx
|
||||
)
|
||||
backend.openssl_assert(res == 1)
|
||||
return Point(op_sum, self.curve)
|
||||
|
||||
def __sub__(self, other):
|
||||
"""
|
||||
Performs subtraction by adding the inverse of the `other` to the point.
|
||||
"""
|
||||
return (self + (-other))
|
||||
|
||||
def __neg__(self) -> 'Point':
|
||||
"""
|
||||
Computes the additive inverse of a Point, by performing an
|
||||
EC_POINT_invert on itself.
|
||||
"""
|
||||
inv = backend._lib.EC_POINT_dup(self.ec_point, self.curve.ec_group)
|
||||
backend.openssl_assert(inv != backend._ffi.NULL)
|
||||
inv = backend._ffi.gc(inv, backend._lib.EC_POINT_clear_free)
|
||||
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.EC_POINT_invert(
|
||||
self.curve.ec_group, inv, bn_ctx
|
||||
)
|
||||
backend.openssl_assert(res == 1)
|
||||
return Point(inv, self.curve)
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
return self.to_bytes()
|
543
umbral/pre.py
543
umbral/pre.py
|
@ -1,518 +1,57 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
from typing import Tuple, Optional, Sequence
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import typing
|
||||
from typing import Dict, List, Optional, Tuple, Union, Any
|
||||
|
||||
from bytestring_splitter import BytestringSplitter
|
||||
from cryptography.exceptions import InvalidTag
|
||||
from constant_sorrow import constants
|
||||
|
||||
from umbral.cfrags import CapsuleFrag
|
||||
from umbral.config import default_curve
|
||||
from umbral.curve import Curve
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.dem import UmbralDEM, DEM_KEYSIZE, DEM_NONCE_SIZE
|
||||
from umbral.keys import UmbralPrivateKey, UmbralPublicKey
|
||||
from umbral.kfrags import KFrag, NO_KEY, DELEGATING_ONLY, RECEIVING_ONLY, DELEGATING_AND_RECEIVING
|
||||
from umbral.params import UmbralParameters
|
||||
from umbral.point import Point
|
||||
from umbral.random_oracles import kdf, hash_to_curvebn
|
||||
from umbral.signing import Signer
|
||||
from umbral.utils import poly_eval, lambda_coeff
|
||||
from .capsule import Capsule
|
||||
from .capsule_frag import CapsuleFrag
|
||||
from .dem import DEM
|
||||
from .keys import PublicKey, SecretKey
|
||||
from .key_frag import KeyFrag
|
||||
|
||||
|
||||
class GenericUmbralError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UmbralCorrectnessError(GenericUmbralError):
|
||||
def __init__(self, message: str, offending_cfrags: List[CapsuleFrag]) -> None:
|
||||
super().__init__(message)
|
||||
self.offending_cfrags = offending_cfrags
|
||||
|
||||
|
||||
class UmbralDecryptionError(GenericUmbralError):
|
||||
def __init__(self) -> None:
|
||||
super().__init__("Decryption of ciphertext failed: "
|
||||
"either someone tampered with the ciphertext or "
|
||||
"you are using an incorrect decryption key.")
|
||||
|
||||
|
||||
class Capsule:
|
||||
|
||||
def __init__(self,
|
||||
params: UmbralParameters,
|
||||
point_e: Point,
|
||||
point_v: Point,
|
||||
bn_sig: CurveBN,
|
||||
) -> None:
|
||||
|
||||
self.params = params
|
||||
|
||||
if not all((isinstance(point_e, Point),
|
||||
isinstance(point_v, Point),
|
||||
isinstance(bn_sig, CurveBN))):
|
||||
raise TypeError("Need valid point_e, point_v, and bn_sig to make a Capsule.")
|
||||
|
||||
self.point_e = point_e
|
||||
self.point_v = point_v
|
||||
self.bn_sig = bn_sig
|
||||
|
||||
self._attached_cfrags = set() # type: set
|
||||
self._cfrag_correctness_keys = {
|
||||
'delegating': None, 'receiving': None, 'verifying': None
|
||||
} # type: dict
|
||||
|
||||
class NotValid(ValueError):
|
||||
"""
|
||||
raised if the capsule does not pass verification.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def expected_bytes_length(cls, curve: Optional[Curve] = None) -> int:
|
||||
"""
|
||||
Returns the size (in bytes) of a Capsule given the curve.
|
||||
If no curve is provided, it will use the default curve.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
bn_size = CurveBN.expected_bytes_length(curve)
|
||||
point_size = Point.expected_bytes_length(curve)
|
||||
|
||||
return (bn_size * 1) + (point_size * 2)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, capsule_bytes: bytes, params: UmbralParameters) -> 'Capsule':
|
||||
"""
|
||||
Instantiates a Capsule object from the serialized data.
|
||||
"""
|
||||
curve = params.curve
|
||||
|
||||
bn_size = CurveBN.expected_bytes_length(curve)
|
||||
point_size = Point.expected_bytes_length(curve)
|
||||
arguments = {'curve': curve}
|
||||
|
||||
if len(capsule_bytes) == cls.expected_bytes_length(curve):
|
||||
splitter = BytestringSplitter(
|
||||
(Point, point_size, arguments), # point_e
|
||||
(Point, point_size, arguments), # point_v
|
||||
(CurveBN, bn_size, arguments) # bn_sig
|
||||
)
|
||||
else:
|
||||
raise ValueError("Byte string does not have a valid length for a Capsule")
|
||||
|
||||
components = splitter(capsule_bytes)
|
||||
return cls(params, *components)
|
||||
|
||||
def _set_cfrag_correctness_key(self, key_type: str, key: Optional[UmbralPublicKey]) -> bool:
|
||||
if key_type not in ("delegating", "receiving", "verifying"):
|
||||
raise ValueError("You can only set 'delegating', 'receiving' or 'verifying' keys.")
|
||||
|
||||
current_key = self._cfrag_correctness_keys[key_type]
|
||||
|
||||
if current_key is None:
|
||||
if key is None:
|
||||
return False
|
||||
elif self.params != key.params:
|
||||
raise TypeError("You are trying to set a key with different UmbralParameters.")
|
||||
else:
|
||||
self._cfrag_correctness_keys[key_type] = key
|
||||
return True
|
||||
elif key in (None, current_key):
|
||||
return False
|
||||
else:
|
||||
raise ValueError("The {} key is already set; you can't set it again.".format(key_type))
|
||||
|
||||
def get_correctness_keys(self) -> Dict[str, Union[UmbralPublicKey, None]]:
|
||||
return dict(self._cfrag_correctness_keys)
|
||||
|
||||
def set_correctness_keys(self,
|
||||
delegating: Optional[UmbralPublicKey] = None,
|
||||
receiving: Optional[UmbralPublicKey] = None,
|
||||
verifying: Optional[UmbralPublicKey] = None,
|
||||
) -> Tuple[bool, bool, bool]:
|
||||
|
||||
delegating_key_details = self._set_cfrag_correctness_key(key_type="delegating", key=delegating)
|
||||
receiving_key_details = self._set_cfrag_correctness_key(key_type="receiving", key=receiving)
|
||||
verifying_key_details = self._set_cfrag_correctness_key(key_type="verifying", key=verifying)
|
||||
|
||||
return delegating_key_details, receiving_key_details, verifying_key_details
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
"""
|
||||
Serialize the Capsule into a bytestring.
|
||||
"""
|
||||
e, v, s = self.components()
|
||||
return e.to_bytes() + v.to_bytes() + s.to_bytes()
|
||||
|
||||
def verify(self) -> bool:
|
||||
|
||||
g = self.params.g
|
||||
e, v, s = self.components()
|
||||
h = hash_to_curvebn(e, v, params=self.params)
|
||||
|
||||
result = s * g == v + (h * e) # type: bool
|
||||
return result
|
||||
|
||||
def attach_cfrag(self, cfrag: CapsuleFrag) -> None:
|
||||
if cfrag.verify_correctness(self):
|
||||
self._attached_cfrags.add(cfrag)
|
||||
else:
|
||||
error_msg = "CFrag is not correct and cannot be attached to the Capsule"
|
||||
raise UmbralCorrectnessError(error_msg, [cfrag])
|
||||
|
||||
def clear_cfrags(self):
|
||||
self._attached_cfrags = set()
|
||||
|
||||
def first_cfrag(self):
|
||||
try:
|
||||
return list(self._attached_cfrags)[0]
|
||||
except IndexError:
|
||||
raise TypeError("This Capsule doesn't have any CFrags attached. Ergo, you can't get the first one.")
|
||||
|
||||
def components(self) -> Tuple[Point, Point, CurveBN]:
|
||||
return self.point_e, self.point_v, self.bn_sig
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
return self.to_bytes()
|
||||
|
||||
def __contains__(self, cfrag):
|
||||
return cfrag in self._attached_cfrags
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
"""
|
||||
Each component is compared to its counterpart in constant time per the __eq__ of Point and CurveBN.
|
||||
"""
|
||||
return hasattr(other, "components") and self.components() == other.components() and all(self.components())
|
||||
|
||||
@typing.no_type_check
|
||||
def __hash__(self) -> int:
|
||||
# In case this isn't obvious, don't use this as a secure hash. Use BLAKE2b or something.
|
||||
component_bytes = tuple(component.to_bytes() for component in self.components())
|
||||
return hash(component_bytes)
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._attached_cfrags)
|
||||
|
||||
def __repr__(self):
|
||||
return "{}:{}".format(self.__class__.__name__, hex(int(self.bn_sig))[2:17])
|
||||
|
||||
|
||||
def generate_kfrags(delegating_privkey: UmbralPrivateKey,
|
||||
receiving_pubkey: UmbralPublicKey,
|
||||
threshold: int,
|
||||
N: int,
|
||||
signer: Signer,
|
||||
sign_delegating_key: Optional[bool] = True,
|
||||
sign_receiving_key: Optional[bool] = True,
|
||||
) -> List[KFrag]:
|
||||
def encrypt(pk: PublicKey, plaintext: bytes) -> Tuple[Capsule, bytes]:
|
||||
"""
|
||||
Creates a re-encryption key from Alice's delegating public key to Bob's
|
||||
receiving public key, and splits it in KFrags, using Shamir's Secret Sharing.
|
||||
Requires a threshold number of KFrags out of N.
|
||||
Generates and encapsulates a symmetric key and uses it to encrypt the given plaintext.
|
||||
|
||||
Returns a list of N KFrags
|
||||
Returns the KEM Capsule and the ciphertext.
|
||||
"""
|
||||
|
||||
if threshold <= 0 or threshold > N:
|
||||
raise ValueError('Arguments threshold and N must satisfy 0 < threshold <= N')
|
||||
|
||||
if delegating_privkey.params != receiving_pubkey.params:
|
||||
raise ValueError("Keys must have the same parameter set.")
|
||||
|
||||
params = delegating_privkey.params
|
||||
|
||||
g = params.g
|
||||
|
||||
delegating_pubkey = delegating_privkey.get_pubkey()
|
||||
|
||||
bob_pubkey_point = receiving_pubkey.point_key
|
||||
|
||||
# The precursor point is used as an ephemeral public key in a DH key exchange,
|
||||
# and the resulting shared secret 'dh_point' is used to derive other secret values
|
||||
private_precursor = CurveBN.gen_rand(params.curve)
|
||||
precursor = private_precursor * g # type: Any
|
||||
|
||||
dh_point = private_precursor * bob_pubkey_point
|
||||
|
||||
# Secret value 'd' allows to make Umbral non-interactive
|
||||
d = hash_to_curvebn(precursor,
|
||||
bob_pubkey_point,
|
||||
dh_point,
|
||||
bytes(constants.NON_INTERACTIVE),
|
||||
params=params)
|
||||
|
||||
# Coefficients of the generating polynomial
|
||||
coefficients = [delegating_privkey.bn_key * (~d)]
|
||||
coefficients += [CurveBN.gen_rand(params.curve) for _ in range(threshold - 1)]
|
||||
|
||||
bn_size = CurveBN.expected_bytes_length(params.curve)
|
||||
|
||||
kfrags = list()
|
||||
for _ in range(N):
|
||||
kfrag_id = os.urandom(bn_size)
|
||||
|
||||
# The index of the re-encryption key share (which in Shamir's Secret
|
||||
# Sharing corresponds to x in the tuple (x, f(x)), with f being the
|
||||
# generating polynomial), is used to prevent reconstruction of the
|
||||
# re-encryption key without Bob's intervention
|
||||
share_index = hash_to_curvebn(precursor,
|
||||
bob_pubkey_point,
|
||||
dh_point,
|
||||
bytes(constants.X_COORDINATE),
|
||||
kfrag_id,
|
||||
params=params)
|
||||
|
||||
# The re-encryption key share is the result of evaluating the generating
|
||||
# polynomial for the index value
|
||||
rk = poly_eval(coefficients, share_index)
|
||||
|
||||
commitment = rk * params.u # type: Any
|
||||
|
||||
validity_message_for_bob = (kfrag_id,
|
||||
delegating_pubkey,
|
||||
receiving_pubkey,
|
||||
commitment,
|
||||
precursor,
|
||||
) # type: Any
|
||||
validity_message_for_bob = bytes().join(bytes(item) for item in validity_message_for_bob)
|
||||
signature_for_bob = signer(validity_message_for_bob)
|
||||
|
||||
if sign_delegating_key and sign_receiving_key:
|
||||
mode = DELEGATING_AND_RECEIVING
|
||||
elif sign_delegating_key:
|
||||
mode = DELEGATING_ONLY
|
||||
elif sign_receiving_key:
|
||||
mode = RECEIVING_ONLY
|
||||
else:
|
||||
mode = NO_KEY
|
||||
|
||||
validity_message_for_proxy = [kfrag_id, commitment, precursor, mode] # type: Any
|
||||
|
||||
if sign_delegating_key:
|
||||
validity_message_for_proxy.append(delegating_pubkey)
|
||||
if sign_receiving_key:
|
||||
validity_message_for_proxy.append(receiving_pubkey)
|
||||
|
||||
validity_message_for_proxy = bytes().join(bytes(item) for item in validity_message_for_proxy)
|
||||
signature_for_proxy = signer(validity_message_for_proxy)
|
||||
|
||||
kfrag = KFrag(identifier=kfrag_id,
|
||||
bn_key=rk,
|
||||
point_commitment=commitment,
|
||||
point_precursor=precursor,
|
||||
signature_for_proxy=signature_for_proxy,
|
||||
signature_for_bob=signature_for_bob,
|
||||
keys_in_signature=mode,
|
||||
)
|
||||
|
||||
kfrags.append(kfrag)
|
||||
|
||||
return kfrags
|
||||
capsule, key_seed = Capsule.from_public_key(pk)
|
||||
dem = DEM(bytes(key_seed))
|
||||
ciphertext = dem.encrypt(plaintext, authenticated_data=bytes(capsule))
|
||||
return capsule, ciphertext
|
||||
|
||||
|
||||
def reencrypt(kfrag: KFrag,
|
||||
capsule: Capsule,
|
||||
provide_proof: bool = True,
|
||||
metadata: Optional[bytes] = None,
|
||||
verify_kfrag: bool = True) -> CapsuleFrag:
|
||||
|
||||
if not isinstance(capsule, Capsule) or not capsule.verify():
|
||||
raise Capsule.NotValid
|
||||
|
||||
if verify_kfrag:
|
||||
if not isinstance(kfrag, KFrag) or not kfrag.verify_for_capsule(capsule):
|
||||
raise KFrag.NotValid
|
||||
|
||||
rk = kfrag.bn_key
|
||||
e1 = rk * capsule.point_e # type: Any
|
||||
v1 = rk * capsule.point_v # type: Any
|
||||
|
||||
cfrag = CapsuleFrag(point_e1=e1, point_v1=v1, kfrag_id=kfrag.id,
|
||||
point_precursor=kfrag.point_precursor)
|
||||
|
||||
if provide_proof:
|
||||
cfrag.prove_correctness(capsule, kfrag, metadata)
|
||||
|
||||
return cfrag
|
||||
|
||||
|
||||
def _encapsulate(alice_pubkey: UmbralPublicKey,
|
||||
key_length: int = DEM_KEYSIZE) -> Tuple[bytes, Capsule]:
|
||||
"""Generates a symmetric key and its associated KEM ciphertext"""
|
||||
|
||||
params = alice_pubkey.params
|
||||
g = params.g
|
||||
|
||||
priv_r = CurveBN.gen_rand(params.curve)
|
||||
pub_r = priv_r * g # type: Any
|
||||
|
||||
priv_u = CurveBN.gen_rand(params.curve)
|
||||
pub_u = priv_u * g # type: Any
|
||||
|
||||
h = hash_to_curvebn(pub_r, pub_u, params=params)
|
||||
s = priv_u + (priv_r * h)
|
||||
|
||||
shared_key = (priv_r + priv_u) * alice_pubkey.point_key # type: Any
|
||||
|
||||
# Key to be used for symmetric encryption
|
||||
key = kdf(shared_key, key_length)
|
||||
|
||||
return key, Capsule(point_e=pub_r, point_v=pub_u, bn_sig=s, params=params)
|
||||
|
||||
|
||||
def _decapsulate_original(private_key: UmbralPrivateKey,
|
||||
capsule: Capsule,
|
||||
key_length: int = DEM_KEYSIZE) -> bytes:
|
||||
"""Derive the same symmetric key"""
|
||||
|
||||
if not capsule.verify():
|
||||
# Check correctness of original ciphertext
|
||||
raise capsule.NotValid("Capsule verification failed.")
|
||||
|
||||
shared_key = private_key.bn_key * (capsule.point_e + capsule.point_v) # type: Any
|
||||
key = kdf(shared_key, key_length)
|
||||
return key
|
||||
|
||||
|
||||
def _decapsulate_reencrypted(receiving_privkey: UmbralPrivateKey, capsule: Capsule,
|
||||
key_length: int = DEM_KEYSIZE) -> bytes:
|
||||
"""Derive the same symmetric encapsulated_key"""
|
||||
|
||||
params = capsule.params
|
||||
|
||||
pub_key = receiving_privkey.get_pubkey().point_key
|
||||
priv_key = receiving_privkey.bn_key
|
||||
|
||||
precursor = capsule.first_cfrag().point_precursor
|
||||
dh_point = priv_key * precursor
|
||||
|
||||
# Combination of CFrags via Shamir's Secret Sharing reconstruction
|
||||
xs = list()
|
||||
for cfrag in capsule._attached_cfrags:
|
||||
x = hash_to_curvebn(precursor,
|
||||
pub_key,
|
||||
dh_point,
|
||||
bytes(constants.X_COORDINATE),
|
||||
cfrag.kfrag_id,
|
||||
params=params)
|
||||
xs.append(x)
|
||||
|
||||
e_summands, v_summands = list(), list()
|
||||
for cfrag, x in zip(capsule._attached_cfrags, xs):
|
||||
if precursor != cfrag.point_precursor:
|
||||
raise ValueError("Attached CFrags are not pairwise consistent")
|
||||
lambda_i = lambda_coeff(x, xs)
|
||||
e_summands.append(lambda_i * cfrag.point_e1)
|
||||
v_summands.append(lambda_i * cfrag.point_v1)
|
||||
|
||||
e_prime = sum(e_summands[1:], e_summands[0])
|
||||
v_prime = sum(v_summands[1:], v_summands[0])
|
||||
|
||||
# Secret value 'd' allows to make Umbral non-interactive
|
||||
d = hash_to_curvebn(precursor,
|
||||
pub_key,
|
||||
dh_point,
|
||||
bytes(constants.NON_INTERACTIVE),
|
||||
params=params)
|
||||
|
||||
e, v, s = capsule.components()
|
||||
h = hash_to_curvebn(e, v, params=params)
|
||||
|
||||
orig_pub_key = capsule.get_correctness_keys()['delegating'].point_key # type: ignore
|
||||
|
||||
if not (s / d) * orig_pub_key == (h * e_prime) + v_prime:
|
||||
raise GenericUmbralError()
|
||||
|
||||
shared_key = d * (e_prime + v_prime)
|
||||
encapsulated_key = kdf(shared_key, key_length)
|
||||
return encapsulated_key
|
||||
|
||||
|
||||
def encrypt(alice_pubkey: UmbralPublicKey, plaintext: bytes) -> Tuple[bytes, Capsule]:
|
||||
def decrypt_original(sk: SecretKey, capsule: Capsule, ciphertext: bytes) -> bytes:
|
||||
"""
|
||||
Performs an encryption using the UmbralDEM object and encapsulates a key
|
||||
for the sender using the public key provided.
|
||||
|
||||
Returns the ciphertext and the KEM Capsule.
|
||||
"""
|
||||
key, capsule = _encapsulate(alice_pubkey, DEM_KEYSIZE)
|
||||
|
||||
capsule_bytes = bytes(capsule)
|
||||
|
||||
dem = UmbralDEM(key)
|
||||
ciphertext = dem.encrypt(plaintext, authenticated_data=capsule_bytes)
|
||||
|
||||
return ciphertext, capsule
|
||||
|
||||
|
||||
def _open_capsule(capsule: Capsule, receiving_privkey: UmbralPrivateKey,
|
||||
check_proof: bool = True) -> bytes:
|
||||
"""
|
||||
Activates the Capsule from the attached CFrags,
|
||||
opens the Capsule and returns what is inside.
|
||||
|
||||
This will often be a symmetric key.
|
||||
"""
|
||||
|
||||
if check_proof:
|
||||
offending_cfrags = []
|
||||
for cfrag in capsule._attached_cfrags:
|
||||
if not cfrag.verify_correctness(capsule):
|
||||
offending_cfrags.append(cfrag)
|
||||
|
||||
if offending_cfrags:
|
||||
error_msg = "Decryption error: Some CFrags are not correct"
|
||||
raise UmbralCorrectnessError(error_msg, offending_cfrags)
|
||||
|
||||
key = _decapsulate_reencrypted(receiving_privkey, capsule)
|
||||
return key
|
||||
|
||||
|
||||
def decrypt(ciphertext: bytes, capsule: Capsule, decrypting_key: UmbralPrivateKey,
|
||||
check_proof: bool = True) -> bytes:
|
||||
"""
|
||||
Opens the capsule and gets what's inside.
|
||||
|
||||
Opens the capsule using the original (Alice's) key used for encryption and gets what's inside.
|
||||
We hope that's a symmetric key, which we use to decrypt the ciphertext
|
||||
and return the resulting cleartext.
|
||||
"""
|
||||
key_seed = capsule.open_original(sk)
|
||||
dem = DEM(bytes(key_seed))
|
||||
return dem.decrypt(ciphertext, authenticated_data=bytes(capsule))
|
||||
|
||||
if not isinstance(ciphertext, bytes) or len(ciphertext) < DEM_NONCE_SIZE:
|
||||
raise ValueError("Input ciphertext must be a bytes object of length >= {}".format(DEM_NONCE_SIZE))
|
||||
elif not isinstance(capsule, Capsule) or not capsule.verify():
|
||||
raise Capsule.NotValid
|
||||
elif not isinstance(decrypting_key, UmbralPrivateKey):
|
||||
raise TypeError("The decrypting key is not an UmbralPrivateKey")
|
||||
|
||||
if capsule._attached_cfrags:
|
||||
# Since there are cfrags attached, we assume this is Bob opening the Capsule.
|
||||
# (i.e., this is a re-encrypted capsule)
|
||||
encapsulated_key = _open_capsule(capsule, decrypting_key, check_proof=check_proof)
|
||||
else:
|
||||
# Since there aren't cfrags attached, we assume this is Alice opening the Capsule.
|
||||
# (i.e., this is an original capsule)
|
||||
encapsulated_key = _decapsulate_original(decrypting_key, capsule)
|
||||
def reencrypt(capsule: Capsule, kfrag: KeyFrag, metadata: Optional[bytes] = None) -> CapsuleFrag:
|
||||
"""
|
||||
Creates a capsule fragment using the given key fragment.
|
||||
Capsule fragments can later be used to decrypt the ciphertext.
|
||||
|
||||
dem = UmbralDEM(encapsulated_key)
|
||||
try:
|
||||
cleartext = dem.decrypt(ciphertext, authenticated_data=bytes(capsule))
|
||||
except InvalidTag as e:
|
||||
raise UmbralDecryptionError() from e
|
||||
If `metadata` is provided, it will have to be used for verification in
|
||||
:py:meth:`CapsuleFrag.verify`.
|
||||
"""
|
||||
return CapsuleFrag.reencrypted(capsule, kfrag, metadata)
|
||||
|
||||
|
||||
def decrypt_reencrypted(decrypting_sk: SecretKey,
|
||||
delegating_pk: PublicKey,
|
||||
capsule: Capsule,
|
||||
cfrags: Sequence[CapsuleFrag],
|
||||
ciphertext: bytes,
|
||||
) -> bytes:
|
||||
"""
|
||||
Decrypts the ciphertext using the original capsule and the reencrypted capsule fragments.
|
||||
"""
|
||||
|
||||
key_seed = capsule.open_reencrypted(decrypting_sk, delegating_pk, cfrags)
|
||||
dem = DEM(bytes(key_seed))
|
||||
return dem.decrypt(ciphertext, authenticated_data=bytes(capsule))
|
||||
|
||||
return cleartext
|
||||
|
|
|
@ -1,214 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from abc import abstractmethod, ABC
|
||||
from typing import Optional, Type
|
||||
|
||||
from cryptography.hazmat.backends.openssl import backend
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
||||
from cryptography.exceptions import InternalError
|
||||
|
||||
import sha3
|
||||
|
||||
from umbral import openssl
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.point import Point
|
||||
from umbral.params import UmbralParameters
|
||||
from umbral.config import default_params
|
||||
|
||||
|
||||
class Hash(ABC):
|
||||
|
||||
CUSTOMIZATION_STRING_LENGTH = 64
|
||||
CUSTOMIZATION_STRING_PAD = b'\x00'
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self, customization_string: bytes = b''):
|
||||
|
||||
if len(customization_string) > Hash.CUSTOMIZATION_STRING_LENGTH:
|
||||
raise ValueError("The maximum length of the customization string is "
|
||||
"{} bytes".format(Hash.CUSTOMIZATION_STRING_LENGTH))
|
||||
|
||||
self.customization_string = customization_string.ljust(
|
||||
Hash.CUSTOMIZATION_STRING_LENGTH,
|
||||
Hash.CUSTOMIZATION_STRING_PAD
|
||||
)
|
||||
self.update(self.customization_string)
|
||||
|
||||
@abstractmethod
|
||||
def update(self, data: bytes) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def copy(self) -> 'Hash':
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def finalize(self) -> bytes:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Blake2b(Hash):
|
||||
def __init__(self, customization_string: bytes = b''):
|
||||
# TODO: use a Blake2b implementation that supports personalization (see #155)
|
||||
self._blake2b = hashes.Hash(hashes.BLAKE2b(64), backend=backend)
|
||||
super().__init__(customization_string)
|
||||
|
||||
def update(self, data: bytes) -> None:
|
||||
self._blake2b.update(data)
|
||||
|
||||
def copy(self) -> 'Blake2b':
|
||||
replica = type(self)()
|
||||
replica._blake2b = self._blake2b.copy()
|
||||
return replica
|
||||
|
||||
def finalize(self) -> bytes:
|
||||
return self._blake2b.finalize()
|
||||
|
||||
|
||||
class ExtendedKeccak(Hash):
|
||||
|
||||
_UPPER_PREFIX = b'\x00'
|
||||
_LOWER_PREFIX = b'\x01'
|
||||
|
||||
def __init__(self, customization_string: bytes = b''):
|
||||
self._upper = sha3.keccak_256()
|
||||
self._lower = sha3.keccak_256()
|
||||
|
||||
self._upper.update(self._UPPER_PREFIX)
|
||||
self._lower.update(self._LOWER_PREFIX)
|
||||
|
||||
super().__init__(customization_string)
|
||||
|
||||
def update(self, data: bytes) -> None:
|
||||
self._upper.update(data)
|
||||
self._lower.update(data)
|
||||
|
||||
def copy(self) -> 'ExtendedKeccak':
|
||||
replica = type(self)()
|
||||
replica._upper = self._upper.copy()
|
||||
replica._lower = self._lower.copy()
|
||||
return replica
|
||||
|
||||
def finalize(self) -> bytes:
|
||||
return self._upper.digest() + self._lower.digest()
|
||||
|
||||
|
||||
def kdf(ecpoint: Point,
|
||||
key_length: int,
|
||||
salt: Optional[bytes] = None,
|
||||
info: Optional[bytes] = None,
|
||||
) -> bytes:
|
||||
|
||||
data = ecpoint.to_bytes(is_compressed=True)
|
||||
hkdf = HKDF(algorithm=hashes.BLAKE2b(64),
|
||||
length=key_length,
|
||||
salt=salt,
|
||||
info=info,
|
||||
backend=default_backend())
|
||||
return hkdf.derive(data)
|
||||
|
||||
|
||||
# TODO: Common API for all hash_to_curvebn functions.
|
||||
# TODO: ^ It should check the correct number and type args, instead of current approach.
|
||||
def hash_to_curvebn(*crypto_items,
|
||||
params: UmbralParameters,
|
||||
customization_string: bytes = b'',
|
||||
hash_class: Type[Hash] = Blake2b) -> CurveBN:
|
||||
|
||||
customization_string = b'hash_to_curvebn' + customization_string
|
||||
hash_function = hash_class(customization_string=customization_string)
|
||||
|
||||
for item in crypto_items:
|
||||
try:
|
||||
item_bytes = item.to_bytes()
|
||||
except AttributeError:
|
||||
if isinstance(item, bytes):
|
||||
item_bytes = item
|
||||
else:
|
||||
raise TypeError("Input with type {} not accepted".format(type(item)))
|
||||
hash_function.update(item_bytes)
|
||||
|
||||
hash_digest = openssl._bytes_to_bn(hash_function.finalize())
|
||||
|
||||
one = backend._lib.BN_value_one()
|
||||
|
||||
order_minus_1 = openssl._get_new_BN()
|
||||
res = backend._lib.BN_sub(order_minus_1, params.curve.order, one)
|
||||
backend.openssl_assert(res == 1)
|
||||
|
||||
bignum = openssl._get_new_BN()
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.BN_mod(bignum, hash_digest, order_minus_1, bn_ctx)
|
||||
backend.openssl_assert(res == 1)
|
||||
|
||||
res = backend._lib.BN_add(bignum, bignum, one)
|
||||
backend.openssl_assert(res == 1)
|
||||
|
||||
return CurveBN(bignum, params.curve)
|
||||
|
||||
|
||||
def unsafe_hash_to_point(data: bytes = b'',
|
||||
params: UmbralParameters = None,
|
||||
label: bytes = b'',
|
||||
hash_class = Blake2b,
|
||||
) -> 'Point':
|
||||
"""
|
||||
Hashes arbitrary data into a valid EC point of the specified curve,
|
||||
using the try-and-increment method.
|
||||
It admits an optional label as an additional input to the hash function.
|
||||
It uses BLAKE2b (with a digest size of 64 bytes) as the internal hash function.
|
||||
|
||||
WARNING: Do not use when the input data is secret, as this implementation is not
|
||||
in constant time, and hence, it is not safe with respect to timing attacks.
|
||||
"""
|
||||
|
||||
params = params if params is not None else default_params()
|
||||
|
||||
len_data = len(data).to_bytes(4, byteorder='big')
|
||||
len_label = len(label).to_bytes(4, byteorder='big')
|
||||
|
||||
label_data = len_label + label + len_data + data
|
||||
|
||||
# We use an internal 32-bit counter as additional input
|
||||
i = 0
|
||||
while i < 2**32:
|
||||
ibytes = i.to_bytes(4, byteorder='big')
|
||||
hash_function = hash_class()
|
||||
hash_function.update(label_data + ibytes)
|
||||
hash_digest = hash_function.finalize()[:1 + params.CURVE_KEY_SIZE_BYTES]
|
||||
|
||||
sign = b'\x02' if hash_digest[0] & 1 == 0 else b'\x03'
|
||||
compressed_point = sign + hash_digest[1:]
|
||||
|
||||
try:
|
||||
return Point.from_bytes(compressed_point, params.curve)
|
||||
except InternalError as e:
|
||||
# We want to catch specific InternalExceptions:
|
||||
# - Point not in the curve (code 107)
|
||||
# - Invalid compressed point (code 110)
|
||||
# https://github.com/openssl/openssl/blob/master/include/openssl/ecerr.h#L228
|
||||
if e.err_code[0].reason in (107, 110):
|
||||
pass
|
||||
else:
|
||||
# Any other exception, we raise it
|
||||
raise e
|
||||
i += 1
|
||||
|
||||
# Only happens with probability 2^(-32)
|
||||
raise ValueError('Could not hash input into the curve')
|
|
@ -0,0 +1,74 @@
|
|||
from abc import abstractmethod, ABC
|
||||
from typing import Tuple, Type, List, Any, TypeVar
|
||||
|
||||
|
||||
class Serializable(ABC):
|
||||
"""
|
||||
A mixin for composable serialization.
|
||||
"""
|
||||
|
||||
_T = TypeVar('_T', bound='Serializable')
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls: Type[_T], data: bytes) -> _T:
|
||||
"""
|
||||
Restores the object from serialized bytes.
|
||||
"""
|
||||
obj, remainder = cls.__take__(data)
|
||||
if len(remainder) != 0:
|
||||
raise ValueError(f"{len(remainder)} bytes remaining after deserializing {cls}")
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def __take_bytes__(cls, data: bytes, size: int) -> Tuple[bytes, bytes]:
|
||||
"""
|
||||
Takes ``size`` bytes from the bytestring and returns them along with the remainder.
|
||||
"""
|
||||
if len(data) < size:
|
||||
raise ValueError(f"{cls} cannot take {size} bytes from a bytestring of size {len(data)}")
|
||||
return data[:size], data[size:]
|
||||
|
||||
@classmethod
|
||||
def __take_types__(cls, data: bytes, *types: Type) -> Tuple[List[Any], bytes]:
|
||||
"""
|
||||
Given a list of ``Serializable`` types, attempts to deserialize them from the bytestring
|
||||
one by one and returns the list of the resulting objects and the remaining bytestring.
|
||||
"""
|
||||
objs = []
|
||||
for tp in types:
|
||||
obj, data = tp.__take__(data)
|
||||
objs.append(obj)
|
||||
return objs, data
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def __take__(cls: Type[_T], data: bytes) -> Tuple[_T, bytes]:
|
||||
"""
|
||||
Take however much is necessary from ``data`` and instantiate the object,
|
||||
returning it and the remaining bytestring.
|
||||
|
||||
Must be implemented by the derived class.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def __bytes__(self):
|
||||
"""
|
||||
Serializes the object into bytes.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def serialize_bool(b: bool) -> bytes:
|
||||
return b'\x01' if b else b'\x00'
|
||||
|
||||
|
||||
def take_bool(data: bytes) -> Tuple[bool, bytes]:
|
||||
bool_bytes, data = Serializable.__take_bytes__(data, 1)
|
||||
if bool_bytes == b'\x01':
|
||||
b = True
|
||||
elif bool_bytes == b'\x00':
|
||||
b = False
|
||||
else:
|
||||
raise ValueError(f"Incorrectly serialized boolean; expected b'\\x00' or b'\\x01', got {repr(bool_bytes)}")
|
||||
return b, data
|
|
@ -1,151 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published b
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import hmac
|
||||
from typing import Optional, Type
|
||||
|
||||
from cryptography.exceptions import InvalidSignature
|
||||
from cryptography.hazmat.primitives.hashes import HashAlgorithm, SHA256
|
||||
from cryptography.hazmat.primitives.asymmetric import utils
|
||||
from cryptography.hazmat.primitives.asymmetric.ec import ECDSA
|
||||
|
||||
|
||||
from umbral.config import default_curve
|
||||
from umbral.curve import Curve
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.keys import UmbralPublicKey, UmbralPrivateKey
|
||||
|
||||
|
||||
DEFAULT_HASH_ALGORITHM = SHA256
|
||||
|
||||
|
||||
class Signature:
|
||||
"""
|
||||
Wrapper for ECDSA signatures.
|
||||
We store signatures as r and s; this class allows interoperation
|
||||
between (r, s) and DER formatting.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
r: CurveBN,
|
||||
s: CurveBN,
|
||||
hash_algorithm: Type[HashAlgorithm] = DEFAULT_HASH_ALGORITHM) -> None:
|
||||
self.r = r
|
||||
self.s = s
|
||||
self.hash_algorithm = hash_algorithm
|
||||
|
||||
def __repr__(self):
|
||||
return "ECDSA Signature: {}".format(bytes(self).hex()[:15])
|
||||
|
||||
@classmethod
|
||||
def expected_bytes_length(cls, curve: Optional[Curve] = None) -> int:
|
||||
curve = curve if curve is not None else default_curve()
|
||||
return 2 * curve.group_order_size_in_bytes
|
||||
|
||||
def verify(self, message: bytes,
|
||||
verifying_key: UmbralPublicKey,
|
||||
is_prehashed: bool = False) -> bool:
|
||||
"""
|
||||
Verifies that a message's signature was valid.
|
||||
|
||||
:param message: The message to verify
|
||||
:param verifying_key: UmbralPublicKey of the signer
|
||||
:param is_prehashed: True if the message has been prehashed previously
|
||||
:return: True if valid, False if invalid
|
||||
"""
|
||||
cryptography_pub_key = verifying_key.to_cryptography_pubkey()
|
||||
if is_prehashed:
|
||||
signature_algorithm = ECDSA(utils.Prehashed(self.hash_algorithm()))
|
||||
else:
|
||||
signature_algorithm = ECDSA(self.hash_algorithm())
|
||||
|
||||
# TODO: Raise error instead of returning boolean
|
||||
try:
|
||||
cryptography_pub_key.verify(
|
||||
signature=self._der_encoded_bytes(),
|
||||
data=message,
|
||||
signature_algorithm=signature_algorithm
|
||||
)
|
||||
except InvalidSignature:
|
||||
return False
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls,
|
||||
signature_as_bytes: bytes,
|
||||
der_encoded: bool = False,
|
||||
curve: Optional[Curve] = None) -> 'Signature':
|
||||
curve = curve if curve is not None else default_curve()
|
||||
if der_encoded:
|
||||
r, s = utils.decode_dss_signature(signature_as_bytes)
|
||||
else:
|
||||
expected_len = cls.expected_bytes_length(curve)
|
||||
if not len(signature_as_bytes) == expected_len:
|
||||
raise ValueError("Looking for exactly {} bytes if you call from_bytes \
|
||||
with der_encoded=False and curve={}.".format(expected_len, curve))
|
||||
else:
|
||||
r = int.from_bytes(signature_as_bytes[:(expected_len//2)], "big")
|
||||
s = int.from_bytes(signature_as_bytes[(expected_len//2):], "big")
|
||||
|
||||
return cls(CurveBN.from_int(r, curve), CurveBN.from_int(s, curve))
|
||||
|
||||
def _der_encoded_bytes(self) -> bytes:
|
||||
return utils.encode_dss_signature(int(self.r), int(self.s))
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
return self.r.to_bytes() + self.s.to_bytes()
|
||||
|
||||
def __len__(self):
|
||||
return len(bytes(self))
|
||||
|
||||
def __add__(self, other):
|
||||
return bytes(self) + other
|
||||
|
||||
def __radd__(self, other: bytes) -> bytes:
|
||||
return other + bytes(self)
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
simple_bytes_match = hmac.compare_digest(bytes(self), bytes(other))
|
||||
der_encoded_match = hmac.compare_digest(self._der_encoded_bytes(), bytes(other))
|
||||
return simple_bytes_match or der_encoded_match
|
||||
|
||||
|
||||
class Signer:
|
||||
"""Callable wrapping ECDSA signing with UmbralPrivateKeys"""
|
||||
|
||||
def __init__(self,
|
||||
private_key: UmbralPrivateKey,
|
||||
hash_algorithm: Type[HashAlgorithm] = DEFAULT_HASH_ALGORITHM) -> None:
|
||||
self.__cryptography_private_key = private_key.to_cryptography_privkey()
|
||||
self.curve = private_key.params.curve
|
||||
self.hash_algorithm = hash_algorithm
|
||||
|
||||
def __call__(self, message: bytes, is_prehashed: bool = False) -> Signature:
|
||||
"""
|
||||
Signs the message with this instance's private key.
|
||||
|
||||
:param message: Message to hash and sign
|
||||
:param is_prehashed: True if the message has been prehashed previously
|
||||
:return: signature
|
||||
"""
|
||||
if is_prehashed:
|
||||
signature_algorithm = ECDSA(utils.Prehashed(self.hash_algorithm()))
|
||||
else:
|
||||
signature_algorithm = ECDSA(self.hash_algorithm())
|
||||
|
||||
signature_der_bytes = self.__cryptography_private_key.sign(message, signature_algorithm)
|
||||
return Signature.from_bytes(signature_der_bytes, der_encoded=True, curve=self.curve)
|
|
@ -1,41 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published b
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from typing import List
|
||||
|
||||
from umbral.curvebn import CurveBN
|
||||
|
||||
|
||||
def lambda_coeff(id_i: CurveBN, selected_ids: List[CurveBN]) -> CurveBN:
|
||||
ids = [x for x in selected_ids if x != id_i]
|
||||
|
||||
if not ids:
|
||||
return CurveBN.from_int(1, id_i.curve)
|
||||
|
||||
result = ids[0] / (ids[0] - id_i)
|
||||
for id_j in ids[1:]:
|
||||
result = result * id_j / (id_j - id_i)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def poly_eval(coeff: List[CurveBN], x: CurveBN) -> CurveBN:
|
||||
result = coeff[-1]
|
||||
for i in range(-2, -len(coeff) - 1, -1):
|
||||
result = (result * x) + coeff[i]
|
||||
|
||||
return result
|
|
@ -1,13 +1,11 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
from umbral import pre
|
||||
from umbral.keys import UmbralPrivateKey
|
||||
from umbral.signing import Signer
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.point import Point
|
||||
from umbral.random_oracles import hash_to_curvebn, unsafe_hash_to_point, kdf
|
||||
from umbral.config import set_default_curve, default_params
|
||||
from umbral import SecretKey, PublicKey, encrypt, generate_kfrags, reencrypt
|
||||
from umbral.curve_scalar import CurveScalar
|
||||
from umbral.curve_point import CurvePoint
|
||||
from umbral.hashing import Hash, unsafe_hash_to_point
|
||||
from umbral.dem import DEM, kdf
|
||||
|
||||
|
||||
#######################
|
||||
|
@ -35,90 +33,77 @@ def create_test_vector_file(vector, filename, generate_again=False):
|
|||
|
||||
|
||||
# If True, this will overwrite existing test vector files with new randomly generated instances
|
||||
generate_again = False
|
||||
generate_again = True
|
||||
|
||||
#########
|
||||
# SETUP #
|
||||
#########
|
||||
set_default_curve()
|
||||
params = default_params()
|
||||
curve = params.curve
|
||||
|
||||
# We create also some Umbral objects for later
|
||||
delegating_privkey = UmbralPrivateKey.gen_key(params=params)
|
||||
receiving_privkey = UmbralPrivateKey.gen_key(params=params)
|
||||
signing_privkey = UmbralPrivateKey.gen_key(params=params)
|
||||
delegating_sk = SecretKey.random()
|
||||
receiving_sk = SecretKey.random()
|
||||
signing_sk = SecretKey.random()
|
||||
|
||||
verifying_key = signing_privkey.get_pubkey()
|
||||
delegating_key = delegating_privkey.get_pubkey()
|
||||
receiving_key = receiving_privkey.get_pubkey()
|
||||
verifying_pk = PublicKey.from_secret_key(signing_sk)
|
||||
delegating_pk = PublicKey.from_secret_key(delegating_sk)
|
||||
receiving_pk = PublicKey.from_secret_key(receiving_sk)
|
||||
|
||||
signer = Signer(signing_privkey)
|
||||
|
||||
kfrags = pre.generate_kfrags(delegating_privkey=delegating_privkey,
|
||||
receiving_pubkey=receiving_key,
|
||||
threshold=6,
|
||||
N=10,
|
||||
signer=signer,
|
||||
)
|
||||
kfrags = generate_kfrags(delegating_sk=delegating_sk,
|
||||
receiving_pk=receiving_pk,
|
||||
signing_sk=signing_sk,
|
||||
threshold=6,
|
||||
num_kfrags=10,
|
||||
)
|
||||
|
||||
plain_data = b'peace at dawn'
|
||||
|
||||
ciphertext, capsule = pre.encrypt(delegating_key, plain_data)
|
||||
capsule, ciphertext = encrypt(delegating_pk, plain_data)
|
||||
|
||||
capsule.set_correctness_keys(delegating=delegating_key,
|
||||
receiving=receiving_key,
|
||||
verifying=verifying_key)
|
||||
|
||||
cfrag = pre.reencrypt(kfrags[0], capsule)
|
||||
cfrag = reencrypt(capsule, kfrags[0])
|
||||
points = [capsule.point_e, cfrag.point_e1, cfrag.proof.point_e2,
|
||||
capsule.point_v, cfrag.point_v1, cfrag.proof.point_v2,
|
||||
capsule.params.u, cfrag.proof.point_kfrag_commitment, cfrag.proof.point_kfrag_pok]
|
||||
cfrag.proof.kfrag_commitment, cfrag.proof.kfrag_pok]
|
||||
|
||||
z = cfrag.proof.bn_sig
|
||||
z = cfrag.proof.signature
|
||||
|
||||
|
||||
#######################
|
||||
# CurveBN arithmetics #
|
||||
#######################
|
||||
###########################
|
||||
# CurveScalar arithmetics #
|
||||
###########################
|
||||
|
||||
# Let's generate two random CurveBNs
|
||||
bn1 = CurveBN.gen_rand(curve)
|
||||
bn2 = CurveBN.gen_rand(curve)
|
||||
# Let's generate two random CurveScalars
|
||||
bn1 = CurveScalar.random_nonzero()
|
||||
bn2 = CurveScalar.random_nonzero()
|
||||
|
||||
# Expected results for some binary operations
|
||||
expected = [('Addition', bn1 + bn2),
|
||||
('Subtraction', bn1 - bn2),
|
||||
('Multiplication', bn1 * bn2),
|
||||
('Division', bn1 / bn2),
|
||||
('Pow', bn1 ** bn2),
|
||||
('Mod', bn1 % bn2),
|
||||
('Inverse', ~bn1),
|
||||
('Neg', -bn1),
|
||||
('Inverse', bn1.invert()),
|
||||
]
|
||||
|
||||
expected = [{'operation': op, 'result': hexlify(result)} for (op, result) in expected]
|
||||
|
||||
# Definition of test vector
|
||||
vector_suite = {
|
||||
'name': 'Test vectors for CurveBN operations',
|
||||
'name': 'Test vectors for CurveScalar operations',
|
||||
'params': 'default',
|
||||
'first operand': hexlify(bn1),
|
||||
'second operand': hexlify(bn2),
|
||||
'vectors': expected
|
||||
}
|
||||
|
||||
json_file = 'vectors_curvebn_operations.json'
|
||||
json_file = 'vectors_scalar_operations.json'
|
||||
|
||||
create_test_vector_file(vector_suite, json_file, generate_again=generate_again)
|
||||
|
||||
|
||||
|
||||
###################
|
||||
# hash_to_curvebn #
|
||||
###################
|
||||
###############################
|
||||
# CurveScalar.from_digest() #
|
||||
###############################
|
||||
|
||||
# Test vectors for different kinds of inputs (bytes, Points, CurveBNs, etc.)
|
||||
# Test vectors for different kinds of inputs (bytes, CurvePoints, CurveScalars, etc.)
|
||||
inputs = ([b''],
|
||||
[b'abc'],
|
||||
[capsule.point_e],
|
||||
|
@ -129,51 +114,54 @@ inputs = ([b''],
|
|||
|
||||
vectors = list()
|
||||
for input_to_hash in inputs:
|
||||
bn_output = hash_to_curvebn(*input_to_hash, params=params)
|
||||
digest = Hash(b'some_dst')
|
||||
for input_ in input_to_hash:
|
||||
digest.update(input_)
|
||||
scalar = CurveScalar.from_digest(digest)
|
||||
json_input = [{'class': data.__class__.__name__,
|
||||
'bytes': hexlify(data),
|
||||
} for data in input_to_hash]
|
||||
|
||||
json_input = {'input': json_input, 'output': hexlify(bn_output) }
|
||||
json_input = {'input': json_input, 'output': hexlify(scalar) }
|
||||
|
||||
vectors.append(json_input)
|
||||
|
||||
vector_suite = {
|
||||
'name' : 'Test vectors for umbral.curvebn.CurveBN.hash()',
|
||||
'name' : 'Test vectors for umbral.curvebn.CurveScalar.from_digest()',
|
||||
'params' : 'default',
|
||||
'vectors' : vectors
|
||||
}
|
||||
|
||||
create_test_vector_file(vector_suite, 'vectors_curvebn_hash.json', generate_again=generate_again)
|
||||
create_test_vector_file(vector_suite, 'vectors_scalar_from_digest.json', generate_again=generate_again)
|
||||
#print(json.dumps(vector_suite, indent=2))
|
||||
|
||||
|
||||
##########
|
||||
# Points #
|
||||
##########
|
||||
###############
|
||||
# CurvePoints #
|
||||
###############
|
||||
|
||||
point1 = Point.gen_rand(curve)
|
||||
point2 = Point.gen_rand(curve)
|
||||
point1 = CurvePoint.random()
|
||||
point2 = CurvePoint.random()
|
||||
|
||||
# Expected results for some Point operations
|
||||
# Expected results for some CurvePoint operations
|
||||
expected = [('Addition', point1 + point2),
|
||||
('Subtraction', point1 - point2),
|
||||
('Multiplication', bn1 * point1),
|
||||
('Multiplication', point1 * bn1),
|
||||
('Inversion', -point1),
|
||||
('To_affine.X', point1.to_affine()[0]),
|
||||
('To_affine.Y', point1.to_affine()[1]),
|
||||
('kdf', kdf(point1, pre.DEM_KEYSIZE)),
|
||||
('kdf', kdf(bytes(point1), DEM.KEY_SIZE)),
|
||||
]
|
||||
|
||||
expected = [{'operation': op, 'result': hexlify(result)} for (op, result) in expected]
|
||||
|
||||
# Definition of test vector
|
||||
vector_suite = {
|
||||
'name': 'Test vectors for Point operations',
|
||||
'name': 'Test vectors for CurvePoint operations',
|
||||
'params': 'default',
|
||||
'first Point operand': hexlify(point1),
|
||||
'second Point operand': hexlify(point2),
|
||||
'CurveBN operand': hexlify(bn1),
|
||||
'first CurvePoint operand': hexlify(point1),
|
||||
'second CurvePoint operand': hexlify(point2),
|
||||
'CurveScalar operand': hexlify(bn1),
|
||||
'vectors': expected
|
||||
}
|
||||
|
||||
|
@ -194,17 +182,17 @@ inputs = (b'',
|
|||
|
||||
vectors = list()
|
||||
for data in inputs:
|
||||
for label in inputs:
|
||||
point = unsafe_hash_to_point(label=label, data=data, params=params)
|
||||
for dst in inputs:
|
||||
point = unsafe_hash_to_point(dst=dst, data=data)
|
||||
json_input = {'data': hexlify(data),
|
||||
'label': hexlify(label),
|
||||
'dst': hexlify(dst),
|
||||
'point': hexlify(point),
|
||||
}
|
||||
|
||||
vectors.append(json_input)
|
||||
|
||||
vector_suite = {
|
||||
'name': 'Test vectors for umbral.point.Point.unsafe_hash_to_point',
|
||||
'name': 'Test vectors for unsafe_hash_to_point()',
|
||||
'params': 'default',
|
||||
'vectors': vectors
|
||||
}
|
||||
|
@ -219,7 +207,7 @@ create_test_vector_file(vector_suite, 'vectors_unsafe_hash_to_point.json', gener
|
|||
|
||||
vectors = list()
|
||||
for kfrag in kfrags:
|
||||
assert kfrag.verify(verifying_key, delegating_key, receiving_key)
|
||||
assert kfrag.verify(verifying_pk, delegating_pk, receiving_pk)
|
||||
|
||||
json_input = {'kfrag': hexlify(kfrag)}
|
||||
|
||||
|
@ -232,9 +220,9 @@ vector_suite = {
|
|||
'Each of them must deserialize correctly and the '
|
||||
'call to verify() must succeed.'),
|
||||
'params': 'default',
|
||||
'verifying_key': hexlify(verifying_key),
|
||||
'delegating_key': hexlify(delegating_key),
|
||||
'receiving_key': hexlify(receiving_key),
|
||||
'verifying_pk': hexlify(verifying_pk),
|
||||
'delegating_pk': hexlify(delegating_pk),
|
||||
'receiving_pk': hexlify(receiving_pk),
|
||||
'vectors': vectors
|
||||
}
|
||||
|
||||
|
@ -246,14 +234,11 @@ create_test_vector_file(vector_suite, 'vectors_kfrags.json', generate_again=gene
|
|||
# CFrags #
|
||||
##########
|
||||
|
||||
capsule.set_correctness_keys(delegating=delegating_key,
|
||||
receiving=receiving_key,
|
||||
verifying=verifying_key)
|
||||
|
||||
vectors = list()
|
||||
|
||||
metadata = b'kfrag_metadata'
|
||||
for kfrag in kfrags:
|
||||
cfrag = pre.reencrypt(kfrag, capsule, provide_proof=False)
|
||||
cfrag = reencrypt(capsule, kfrag, metadata)
|
||||
json_input = {'kfrag': hexlify(kfrag), 'cfrag': hexlify(cfrag)}
|
||||
vectors.append(json_input)
|
||||
|
||||
|
@ -263,18 +248,15 @@ 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 '
|
||||
'`pre.reencrypt(kfrag, capsule, provide_proof=False)`'),
|
||||
'`reencrypt(kfrag, capsule, , b\'kfrag_metadata\')`'),
|
||||
'params': 'default',
|
||||
'capsule': hexlify(capsule),
|
||||
'verifying_key': hexlify(verifying_key),
|
||||
'delegating_key': hexlify(delegating_key),
|
||||
'receiving_key': hexlify(receiving_key),
|
||||
'metadata': hexlify(metadata),
|
||||
'verifying_pk': hexlify(verifying_pk),
|
||||
'delegating_pk': hexlify(delegating_pk),
|
||||
'receiving_pk': hexlify(receiving_pk),
|
||||
'vectors': vectors
|
||||
}
|
||||
|
||||
#print(json.dumps(vector_suite, indent=2))
|
||||
create_test_vector_file(vector_suite, 'vectors_cfrags.json', generate_again=generate_again)
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,51 +1,52 @@
|
|||
{
|
||||
"name": "Test vectors for CFrags",
|
||||
"description": "This is a collection of CFrags, originated from the enclosed Capsule, under the enclosed delegating, verifying and receiving keys. Each CFrag must deserialize correctly and can be replicated with a call to `pre.reencrypt(kfrag, capsule, provide_proof=False)`",
|
||||
"description": "This is a collection of CFrags, originated from the enclosed Capsule, under the enclosed delegating, verifying and receiving keys. Each CFrag must deserialize correctly and can be replicated with a call to `reencrypt(kfrag, capsule, , b'kfrag_metadata')`",
|
||||
"params": "default",
|
||||
"capsule": "03cdaadd5e0493c8426fd65004ea1e10ddcd3fcb37ac87ce84d0ffdda36d8d60720278adf9ca33ea46b2c0fb7b5a14a2820b171a97f8c69846e9866194ed1315438a1a525f615e246d744b82f7283aeba923f38cff0181e9013cfefe4747e32dc076",
|
||||
"verifying_key": "03714109aee5bded1ea98ced8660ad1d49697e14f2a1bbefd5944e4ce443493d08",
|
||||
"delegating_key": "021c0e2868064f20441df0050bf501137ec84db6ca3596b88a967308e14bea2caa",
|
||||
"receiving_key": "036285c48fbb7c38bccceb6e15959a33ed3dd5ab5c96daaff67636b8557d6885d4",
|
||||
"capsule": "02a03893438c0502dd13818f65c039b2ef4fce33bfed150c6ad166554b4e8a51c2028da9ebf8cc0966bc010152fd3917a8f12dfff0af3b06e34e17f300d622893159367ce07602310c78fc65a8b6115d75f65d362abdf2fd799bebc55aa0cda44dbb",
|
||||
"metadata": "6b667261675f6d65746164617461",
|
||||
"verifying_pk": "03f24761ac8b02de08ad1622d023f669d6214c3bab81a33087ed3ec5505e4d43db",
|
||||
"delegating_pk": "03a73623a2e72fd52b2d313214c7495580c14fe6cd8de7ad0d63bbfbfd6fb6bd4d",
|
||||
"receiving_pk": "02952a1903b9c929f0d93d935b34b272ea25a84833a04e22d887f27bc3bf0cc409",
|
||||
"vectors": [
|
||||
{
|
||||
"kfrag": "417225b396fb91a82f0ed97b4dfb235200742a58d9632d46e352e507c5c716863b349c46905501861f950d11aefd2c69a1f4fe31ae91b4c8624c7556ec7d01ef026fa5450221297328a37043220b0f82a7fdf2a4ab01c9b30cd572181736390ad303b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f4103428244169c9d2d3cfefa6b88251e7600c4a6f9a4edb5e371a0af7bc5134bf68ce9ad3b8a1acbd66926687e2cf7a30762527a85d41b1eaee133866df9dc4fe49b43601bb4d9b496f59e44e2e172141f9480bad0cfda18e5dd6c8dd7a5e72b4bc59281ed11ba75b87f7f1919c30b56d7f0c522b77fd15d37f893fc784eb77d1493",
|
||||
"cfrag": "02aa6a41b809ba8d2816444788d597e6af5b81c56f7645f67e8dde417fca052828027adba799a3cf3935abb5366b5ec3f132f05a9baa0647734e7be50952e3309bd2417225b396fb91a82f0ed97b4dfb235200742a58d9632d46e352e507c5c7168603b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41"
|
||||
"kfrag": "3f3453856117dedb3d7518d0435c04b10734700c8aca48fa5a8b85eded515cdffefbefddcd3cb728dcc6d05061b5e201aa1cef55ce89e94c34b6887b307d5ab2025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb7490374d2e59ea274c8011f6bf26ca8fe8eb8e7837cafed8547485e3fde6ffe0368e98d26b0d7e12095941daffae8ca429147d9686dc3285ea182c0d7b15db41c7afd3fae3c88100707ef28b53d7cf93961dc509864eb319f0cce274544306c96099b5792af0b820bf758f5c8f2a69cfc1e0a5f91fab96ab82c10c7740602f90efdd315ff5dbd07323526ca1c9a93132b4d008fdf1796a55b92e2fe75c544652256c80101",
|
||||
"cfrag": "02ecbc4dc0aed60efa211c7bf0e238593d292a042373b891928bcee459151c2f440281171b7e330ebd097575dadb210e8a405bc162e293881457a301da03f7571c7a3f3453856117dedb3d7518d0435c04b10734700c8aca48fa5a8b85eded515cdf025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74902889960dbc86c87e5a1275f2c1df31e43ed0b3e4616126845a7394b27a83f6d1e03f0b7c60a76c7c9f590c9ac2d1f0ebf6aecaa9d371e2dcd04e19caa134e9c1b530374d2e59ea274c8011f6bf26ca8fe8eb8e7837cafed8547485e3fde6ffe0368e902660d6c64d749050f027983ebd4631838373a47c887537bea95aa139d53bfe38ea532a6b7464b0b5f45522c495a2b8a772b98c4950aa2bb830f6602e7636a1bd95792af0b820bf758f5c8f2a69cfc1e0a5f91fab96ab82c10c7740602f90efdd315ff5dbd07323526ca1c9a93132b4d008fdf1796a55b92e2fe75c544652256c8"
|
||||
},
|
||||
{
|
||||
"kfrag": "fe11bce6ea1d451fbe396c6c7d00747264324eebc93bd6ca9e949c2970be214e56571ecd1c02064be2058043cbe5d7ddde90771c5709c1cb48f2a4866bef246b03cb7014d5978aba65b7849bc5756d0e94ce515cc31725ed86550e4e38424b63d903b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f4103adbaf6ef97a68ad61ca39b1ab244b5d1881b00fbf7e2f0eec06907a0078ec4fcb573d101c2d1976985fa7d6d7c96b327030333ac3c66fc0223b456a70e996e19a31822dea4781891b054e86ff34abbfcce05f7c613b7296133ad5b10455061368a102dff42a913edb8e5d248a8d979879a3cfca1f8d3fbf6b8a24ca5357ae057",
|
||||
"cfrag": "03bfcdf5dafbc9482fab6da942ed7849869d92333a815e9e81116eeff31a97ac680243d1d352ea857eef383300d41f1e1df14d00beb54abd43bc112c2d8af98e4be7fe11bce6ea1d451fbe396c6c7d00747264324eebc93bd6ca9e949c2970be214e03b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41"
|
||||
"kfrag": "d15ac028be9938b5e2d7a9fb9957b416db538a11d141d320a5f656ab4e3cb0cab3a2ddd2672f3514e7da2f29cbe3815c83b6d704e09d595a68fdd5aeff52ef8a025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb749033d6984555d41614a2ba08c2182e27a7105cb60ed414c9732a156c3e44d196dd3507335e3781d6c9a4543e3ae81a4c7538cf280292c9c0d92e4f513e4e073721f2f72a1ea0e69912e21754e32edf9cefd3a96183f5501266bedb301f291709ffb25ed5213660db4c8bfc086e794650455bfde1251f92e1fe49f1feb016ad44fe859b342964db14dddfa6a33eba53021149d8663d306850b8907f9dabf4a5d3ecd0101",
|
||||
"cfrag": "03a98cc0d807a4f2b6183dfb9b7ff589f376183b33f88ae07a50323edaac946d9b02fc04f97b7afa5c8664082e4433a24184d54724e9e18afd44922bd052bea87e49d15ac028be9938b5e2d7a9fb9957b416db538a11d141d320a5f656ab4e3cb0ca025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74903bf5c856a6b918df7a70a10db9854d43009ee517e7fc4403185c99eb05f42638c0213c86d5b3f3816a4807bf797731b8b23ef2563e6698e9380a0ee4044755504a9033d6984555d41614a2ba08c2182e27a7105cb60ed414c9732a156c3e44d196dd303de157a0d01a46d89e57d5586bee352b17bb123ea52dea60ef283456fa50cb5a98eca7141ac4bd117511c10922216c69e548c5005e49571087fefb69d50cecc8225ed5213660db4c8bfc086e794650455bfde1251f92e1fe49f1feb016ad44fe859b342964db14dddfa6a33eba53021149d8663d306850b8907f9dabf4a5d3ecd"
|
||||
},
|
||||
{
|
||||
"kfrag": "4edffcacb49f5620c1cebd67f43366aa22dc380dd9fd0f4853ffbbd44e31986f4a99f953e5c553f08a83cb16c0b5c793a4f691d4d3d239e29f232d2b058970d903041b77b8891d845c240a8a12b71dfe9651de67ceb817b54eb578636fe8a9605103b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41035ba90e2a06117e205dd807c780b5687ff6dadf19454fffb367e7af6b3f9cadf03572dc3311c0e1706354ba3340d1b1f915afc82007f9e09ddd34ce07a038a7bb7ecc7343b1532b175e5b8c7d3c712e3eecb18ff69c22237ba9ab6b47b4572529a35b29c48874f396def9971c0230a51e26975a953ff87af8fdbfd53bf42aae07",
|
||||
"cfrag": "02c6870bd1336ec336752ce7d6b99f36d3ae69983d7a06cb5a743f20c562c0b2ba037fb7d9db1faf763c49e257f906e3345e33df144eeba8b9794462a31a305459c44edffcacb49f5620c1cebd67f43366aa22dc380dd9fd0f4853ffbbd44e31986f03b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41"
|
||||
"kfrag": "f780e25b78581e70d28f102264e1f04b482eb9ecd45c188f2f9cd90026627f904b903c6da7529805e1d91fbaebc384588083e2c5f235c31e7f768b8d0df43513025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb749032f4d57c068c255281703ba6d449345e2f6240a4b8b3047209ff2aca5edec0ad2394020e796b7b9f9130c0ab02b4c8151d050f38f3d0ff5f38dc164e4d254ad2737758c4b67c0b54267b113692f11a410ffc5fe3e41c2f8ec9246854baa63dbfaebb58f0cc89c528e119788c521bbfa8dd33c69129c1a05ed454d7534224c9df35007ef11b38769ee8d2788c72745e50e14e32b0c2b9d988e2550dd3f89cbd6b50101",
|
||||
"cfrag": "03ec5772868ec577fb8e054a7f0717dc56b7eded542a06c593ac7166093ae196c10394fc79d3d700a9536b10233ad1cf145a533a43d35c48028c7b2a6137a8d6097ff780e25b78581e70d28f102264e1f04b482eb9ecd45c188f2f9cd90026627f90025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74903ad08b8333f7a44acf71a2738e7e6213d715d7cee759a825abce04b72ca8d56d0033eba87004940ec899c4eb593265e55138f2d17e8b4c712027009388c18724bd0032f4d57c068c255281703ba6d449345e2f6240a4b8b3047209ff2aca5edec0ad2037403376f5789c4d7c755e4a0b45541a88cd045c77fe61a615e9b6ab6af9e97bbd868593f2b973cd492439c4fdbc6351739e8e9e7f5ba501fb9bbc96605dcf144ebb58f0cc89c528e119788c521bbfa8dd33c69129c1a05ed454d7534224c9df35007ef11b38769ee8d2788c72745e50e14e32b0c2b9d988e2550dd3f89cbd6b5"
|
||||
},
|
||||
{
|
||||
"kfrag": "762a4ae9fdbb74de327a29d25c577c46521ee44a951c6e33739e5ceb2a340aefd2a93d8cbe37b9149dd26ce59b0b7ffcd9adf1ca822c5b477595ecba1933291503409c76b74f0f415f5b6a8421514c250f71a5c5b6c5b0d89148e03ba2a624a9a503b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41034f04b1666bc36ac5d3b7ba66420b660d19caf9a995b6cc7cc0aa7c9d0e739f25b1048e754ffd0703e16b453ae8279e5410eb16d2d53683f2d80c30c39918834d70970a09ae17c014fc939979c1e3659b92a40d8688ed77861407316a22d4a8e0b5628c5eec0a2123129ebdb627eae78f8c404f8e129dd407feb2e970b0ba5e39",
|
||||
"cfrag": "038bbe8c32677553121c7b708c4266e85930ae38541fec43df9b4eb131fcda4b06036ab69b2d77584016302c8391a13bc821811fe8837bd28523ad04081faa6fc498762a4ae9fdbb74de327a29d25c577c46521ee44a951c6e33739e5ceb2a340aef03b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41"
|
||||
"kfrag": "afdbd2183491cec86259c4e6785c9048f17e1f0c86df597983e78f50eb9e88ebfc725b6addb07ce67e5e33e404237dfd9baf15825329d780bbcf07952d899709025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74902fbba98644ea41f7abbef7f0a80d645d428844eb6a08c848b73122057f02cc264f27b1b019d667b1a3c89e176b9203870c42dcf3bb6af9191a89db98b2cd1a1b82c61d188f2d2a5d8e1c92a64d3402e98ca09e4594dd67ad74a9583e47b5ee2fd0d38772f8dee198e6e7fa2f7acca079a90b280f98e880106bc337c314bdea20b1e8d1d43ed0912a4a55475c2587ba81a16b9775681774a8bc9d1af2395875e000101",
|
||||
"cfrag": "02b6b320eb5ef6bb58adc6b0379b3052d7fe3ce290d62d8a9edc5d9560c7472f75036dd5f864310b2f9f7239bca239877edf63b42ac00637fb6cdcdcf944fb0ef2f2afdbd2183491cec86259c4e6785c9048f17e1f0c86df597983e78f50eb9e88eb025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb749037b55bdc05a83cf07d672c695d0c57a961be2a7289d9386b6e86953d6abe90f17036e5e32b140cf076873542eb4551c328608c8f2d951a3d52083b7a2f6612c21d902fbba98644ea41f7abbef7f0a80d645d428844eb6a08c848b73122057f02cc264027ddbf0d8eec3118c1765eb0c636184375b5ce593c1fd83bfbf501d94687695954edccdff64dd78e9ae94aa137754c502c03cacbaeeb09b1dcb798bb8442476ba0d38772f8dee198e6e7fa2f7acca079a90b280f98e880106bc337c314bdea20b1e8d1d43ed0912a4a55475c2587ba81a16b9775681774a8bc9d1af2395875e00"
|
||||
},
|
||||
{
|
||||
"kfrag": "b40778198920434d132c4d3724a7b7422428c9921bd627710c3764a6c54376f6a03be8af10cb0f1c3b74d132dda5feeb4340a9f6213da859c05b6b57249e54e903e9198cc0e86b880251931ab162130f93242164a5b242015195290ba75b5a6bea03b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f4103f27829a4798466ed68c2c53137e4e658e86faebfabf2556837b60b502a1721de3e073a98974686d1b847c27167f118eeea6a27029775800cb1618b393bca9cc35b341b546c8851395b3b68945b73b01fdbe94a9f6c400b22bfb5e3bebeee95887d888c49ccf99553a2941de1cbd3f845c5725d1b6b26017dea5cf161fadb5fda",
|
||||
"cfrag": "020604cacae76ef0f2a81a582b2c642accc4bc9d29ea9b172211a3d8225cfc4975026ac1a78becd4be2f09b939b3bdac3901bbeec488ab1013965c40565034586824b40778198920434d132c4d3724a7b7422428c9921bd627710c3764a6c54376f603b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41"
|
||||
"kfrag": "831f7b7b14181746fcfd7e26e03e3fb63e7a8903acff3dd3c7ad0c2c94550d9abcced3aacb62fa59fd2847a2b18cc6a6f0a6f646e39ec94f05ef186186dfbe10025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb749020db8647602d7401b47c556f25540bd45da99e69462ee8eef1b9c26a26a3757c77f90b015143d8516a5bf2ada51a24b8192e1326d38bf47c0f54591619db42c7c122fd0d955f788dfdeee8d762022887216a3de332632d44092f1f06793eb351fd6acaf0f2060ace737a11e1c1cec72ba361ce754c913765113e1695de323225f24c3f0bbf832c9062d004b69e40090930b40d5986eb9ede462e5d044083127450101",
|
||||
"cfrag": "038a54e78c4dd408df7b242610d5c21bc6a34f7b16af21a059ee8e44eea24337c702d4584beebc883104f375e922aba70ad1449d54f6a835ba6716b36271bfae8e53831f7b7b14181746fcfd7e26e03e3fb63e7a8903acff3dd3c7ad0c2c94550d9a025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74903c1d11486753d8d4b60a4e8a890007d021ea00aaed7d608b25475e69364e5f3120304ef459543d9482aa3b48c5605176e87bf55c0bef3f03244bac83446dd84fe3d020db8647602d7401b47c556f25540bd45da99e69462ee8eef1b9c26a26a3757c702182da47b2bef272ed9840eb2cff17f8f62d3e9544cfc699905e40e8ab061d4d17dc4e840d39308e81fae434b30ed124ce08f846a1a9ab73c926a6a222d15657ad6acaf0f2060ace737a11e1c1cec72ba361ce754c913765113e1695de323225f24c3f0bbf832c9062d004b69e40090930b40d5986eb9ede462e5d04408312745"
|
||||
},
|
||||
{
|
||||
"kfrag": "bb063b1ae2a2e2c804671a40571b5d9a7746a7b11b98fd1794207daf68e8d408e4ad0acf429b3e97d7d098766cf304fc43454a79d541a8e85cd95ee93b7bfb460242325b9a16de25bca5ce9225091b2d9eec4b98fa627f6c2fec591cfba3561bd303b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41034c2742c62c69c571623a751dc842998cc47a6d33c7e2d30e099bc10d2675fa8bed049ff1474d604eb97e6c1b997dc506bfcefd6aed3838db23fd08f0cebe176fad3a0c5734d986b506145f0770e6689eb1425eb2817d699be1c993d64b26f2dd59d3acf01c5372adabebaa04336ff03219eadf1fd085a6957bd5e90b83919774",
|
||||
"cfrag": "022edd320a1560725a60c8116810ac0debf2595bdf274510477181e37e1ce9684c02b56d7468d8a29c5034602dd1960d4a09cc19edf11fd7b90b536a0463f465854bbb063b1ae2a2e2c804671a40571b5d9a7746a7b11b98fd1794207daf68e8d40803b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41"
|
||||
"kfrag": "c63acdf5fb6820df50354e74e65eefacaf75e7e22c780b2b5e2c49a0db9d353e7217ca6a942fce879bd4b596aabd489e0c5af75bd94b72aed37999799e9c8dc9025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74902fcaa0a22aabe3b4564972a9368d6a752bc5e359b306cfaff1626b49911158cda599e662db88909f430575f44b3677a8161a5003519e48cf0da635cb20b15f18d632a1bf41eb525b9ee9fd47f196fdd15059d989f9166e5b437cbbd66459e03fad404248f1da8048a5bf9dd9650fbdd50d1afcbadabbcd7a5aca1882b5eeb803443cd4caff8c4b090bc263f56f496aca086bb6f1fedfd5a99e1715ebfe2c6b5010101",
|
||||
"cfrag": "038caa0566b556fb81617617ee8d4a4808cfaac1fa65b1e1999b58bbefafb30623022a43ae7cd2595e155f3cc7cf5e716168f2b959d574e07f7037ba30329dc384dec63acdf5fb6820df50354e74e65eefacaf75e7e22c780b2b5e2c49a0db9d353e025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74903361957fd737be52d9423a400e677264d8e57ffc1ab35f19aff29ac7d13275ece03a1b57ec36f35089242d2586be843853ce830f1fff20cea9e6a354598ec10c1e002fcaa0a22aabe3b4564972a9368d6a752bc5e359b306cfaff1626b49911158cda0300a120a88ee5994de9a4a5287dbb2e63bd1cd3fbca77740ddad0789dc4a40547a792377ec089eadb1771196797c6835cf01bd4307c6c33be4e61acdb6e05f583d404248f1da8048a5bf9dd9650fbdd50d1afcbadabbcd7a5aca1882b5eeb803443cd4caff8c4b090bc263f56f496aca086bb6f1fedfd5a99e1715ebfe2c6b501"
|
||||
},
|
||||
{
|
||||
"kfrag": "9a2d97e3bba40307b7c60f6f22c5965f6587206cef4d5d7980ad8d75f0f4ae69de90b3ac071a58a06c4d2d0895db9e71a4f36b86fd078dfce6e436514f70797d03d959ebb3d7ee2fd90f2a390da0d7377f7549877f7720739e7e84e0c488a6d16203b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f410394ba445cfe40a9ebf1bc6a6f9588b704b11b129a2a102e8c5bf892fbb695e2d37d396c1b34f8eed4f348ecb11a29fa2b68c3790ef2ddc169e6fa7dc86e03b1a7fd8faf67773903168ee5f9c2ac60468cd81c7ee389af0f03f3fed356c65e7f182faa22867aa4544554d235f78d28afe7d90fff1cf60184bb1ea947a343c13d07",
|
||||
"cfrag": "035f87f2fecce4d4734a01d444e3614e2f32be3f9277373f1338ddb0044a4b79ae03a604f360df7cb23097d7b49826088bd3628d82f601f2b4ea9262931c3b4bc5809a2d97e3bba40307b7c60f6f22c5965f6587206cef4d5d7980ad8d75f0f4ae6903b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41"
|
||||
"kfrag": "1365f76b3cecb0d27cd839263d6e91e8a3e703cb10590fc4ee8c533caed2fcb4e390938ab2eb832d51d7436058182fb46fb4288c9409a2465c25e447919e61eb025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74903ffe0345c282a9890aa6759dadd14afdd50a82c19123a110d37f4582a1549ce5d4308de0dbbc76897e910253bd1fdb10e65bbbe45cede7dcbf494eba5dee18f9c141cc095ff275208485abe81b229b730c11a1fd87de00756e3d705faff9e6f508cff3f81aec66079b4737c5d55ada03f7a749da2605e6b1bb57324528c373cac773526c02a0cf876744a5e7cd7dee1bad8ccf1e81a46d4c7049743cabca94dad0101",
|
||||
"cfrag": "03e39b8d144411b2db704a4394b9291bc8897ce619df95e2a8355066a9da8fe91b02037fe5e553f4e7e711502abe59bff33fae5ed0eee63747252744701ad1c550f31365f76b3cecb0d27cd839263d6e91e8a3e703cb10590fc4ee8c533caed2fcb4025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74903a0bf927bc8b75413f899b06ec3bb3ebba8b73f2a5d22c007e157440c2ec6496703be800d325be2e0d10d3cf81d9fe65e035a31d9f4b857b81af38d52d1a815a40b03ffe0345c282a9890aa6759dadd14afdd50a82c19123a110d37f4582a1549ce5d02518eea3159155d5ec2437907ae06a3bf0ece2e8747e13c1528ccffc62eedeebeb50690a4f2f1e5ebcec48789e7d08039ae4853de86a2a2fd991e70ba37e7dc1c8cff3f81aec66079b4737c5d55ada03f7a749da2605e6b1bb57324528c373cac773526c02a0cf876744a5e7cd7dee1bad8ccf1e81a46d4c7049743cabca94dad"
|
||||
},
|
||||
{
|
||||
"kfrag": "3d09cbb50ab9ff970c93a03976efaf5f13aef88fc4e1b8840cebad96d0469605b343d7f3d4297ada1fcc08009260250f29dbb64bfb6dd64c2dd72174be3d87a202f665886dabd4fd94513aa1082a403efcb9722115e7c44f8ce5394799e10c2ca203b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41037ec97969ef20834c753f460c1c29360ce535cb19e0dd0da3bc9c568157137c3f741dadb514586112bfa22c4aa64c28ba854f7393a04ce77a3c4b731e6eaa41e29f2d240b3adf5a973688c7691e8bde140647f4d2c4d0c06a0d690415a82f18f095fb69a084607734579a590c6d1e477d93716919c67d6627a890fa5a2250109a",
|
||||
"cfrag": "0321674813fe3ba72c9164b7016299862ce1efdbf57c7eef83bd4d3f6535bc3eae03940fdcd41dfaeb7667a0d2a3e929944ea6a3c2c6d7e9f2fff4265ffb011169b53d09cbb50ab9ff970c93a03976efaf5f13aef88fc4e1b8840cebad96d046960503b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41"
|
||||
"kfrag": "d961d60a31b197ee26437b1ff6b484c63bcfd208f509af1225a8db8f65240e366f7924ae66d6cd64c25c74761067bcb3a388cf2d4d2e90738666252868e790b0025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb749037c88f9bf80bb6a406843e339dcc8548b3f8bf16e525be9c94d5e3b8c243f466fbb19cf3eec376f9c46d9c52040555927c7eb506af29718ed9b728ac210d43b2b4958eae22852a1e3761a71cedff210a52f5d312e5cc1b12bdbb2edbe3f955f5535b8b1ce9e3e8b04a010a2dd227c9d906d7806fdf9df07d8ce379597bf7513c23db95d84e7774a3fe0de392cd7390c6ec7cab60ad22317d8c00de0865bd0ceb80101",
|
||||
"cfrag": "03367a497f07271e9b505f58d762527cfd8b0bd30f4b925219b495c8ff825c8cef023b856c54ee517300280f9b9109ee6f8e260341c4013a0b74911fda9eac562b71d961d60a31b197ee26437b1ff6b484c63bcfd208f509af1225a8db8f65240e36025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74902d5dd9d1d29e06800648c0b2b45a2755f5a36ed4e7179d17c5ed9506d1d455daf02fe8deea5266b7059e6bdf53cb4f23c3f27814e14f955eb6236ca911639c3d6f8037c88f9bf80bb6a406843e339dcc8548b3f8bf16e525be9c94d5e3b8c243f466f03bdd9ce48dbeafe54277c44c4cf3cb5d5eb10e036640180c0d0e197594f2bebe029f79a6eafcac19331d83eab043df3aa6486a70409c46c2509a4b03ead49666b35b8b1ce9e3e8b04a010a2dd227c9d906d7806fdf9df07d8ce379597bf7513c23db95d84e7774a3fe0de392cd7390c6ec7cab60ad22317d8c00de0865bd0ceb8"
|
||||
},
|
||||
{
|
||||
"kfrag": "71c39f563bdda903d8397370b077ba1348e07cf644eaffdfbca251f63d8e0c8377d0f143019bca10090a07c15ab9d6d71857451443e0e77ca6768b45b93892b802b54fd786330796eebd5df0a7d5dc26422177d5637028aaea293dfa223a72a56203b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f4103aaaf48203b37dc6152c0735493517a31284be4c46b57a21f01359572d1e1e3d1b0fd64fc82efb401f25dab88e48474450b274625c28e16cb51f31259206aba6fe4886083237bdb5f7da67c8e1e2da7fdef57789c919ccceae6b5453eaa22fb6dcb58a2f9f1f20f05d03326031d4817ec52cf3dc9eb37f2d3b014ba315f5b8c16",
|
||||
"cfrag": "02b37b1fc91e5d99f8249e6c70d19ac9af80cfeab42d2c8b5dbb43b791e5818c480345339e46528e5f7f2d48ab5dd6ba50dc7a44ed7a99a9ef0cee902839076f13a671c39f563bdda903d8397370b077ba1348e07cf644eaffdfbca251f63d8e0c8303b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41"
|
||||
"kfrag": "8618ade86dd8b8f15c206274caba6955c30171e466e220ef2344af25f3ca5976ea2eb761d4e8d083e4bf30052c90f6fde525716bd19b5abe98d66bdd48745f85025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74902bb441051315ea81c8d9d9cc4e63d3f66e6fe3ef52217e8639e0af421d5dbba1be604f5168408cc4bf118346126b9a105b57524b741e141d5cebca15c8964669858bed28994196a23e4f99706cdb8d912d9c53b7c58f98e6d5eb5a4ef3aa2b7b9e0a270af089c667647613c6abe1b7aba8103ee386a04df80341e51e132d6d35d28753fa65e7f8d8c8f3a10d8adc8499a174bcb8041cf6078520ef5e9e00853290101",
|
||||
"cfrag": "03ae6db34ac0125757de78dc64cb563d32ef5ce3983b67f8053cbb9e3539f5240c03855d0862b9930d7fa36e5c60785352de497b84e6ca6d1712627a35fa49df79688618ade86dd8b8f15c206274caba6955c30171e466e220ef2344af25f3ca5976025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb749021594688234f9a0ba6331cf6d341dcea4992d0354492247c528a011620fe7271a03ebb8652e960aa91511db011d16dc3f15df71d81b6e5555a41dc29480158fd8e302bb441051315ea81c8d9d9cc4e63d3f66e6fe3ef52217e8639e0af421d5dbba1b0398e26bb2caa42e1cc1f55f3688bfcaaadc7714a9f79f1910f5c158faa0d53b370668c081a5622181957a2af1fed562695ec390b01b8855411518f401638e185de0a270af089c667647613c6abe1b7aba8103ee386a04df80341e51e132d6d35d28753fa65e7f8d8c8f3a10d8adc8499a174bcb8041cf6078520ef5e9e0085329"
|
||||
},
|
||||
{
|
||||
"kfrag": "55faf9c9be6d59da90b2c2e9f09d85e3965e00629b31c3854367f86039725ecb5c560659b6dc7d16a5f3202da0489aa02cbbabb65d8e1107c3645ca7e5bf36c3020e06cdf7d03bafc42e77c86d07b76840ffa7db6a5c511f3530d44b34edb69aba03b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f4103f5674cd594e92cace034b8b443a7aae0ae48ed57ca9eeca5ad6e2855334a989c0ea976c30e4f5eb2527b01caa468f150db4a19cae08ba6015df41fc07d4ea6bb33433a543608fd59cacba037daecf6e069f0954ce9a5f1651297c28cf31fdf76b655ed10284f5bb436c93934bb89b2434b7ff9f7bcb414b3e879a5571c88a0b1",
|
||||
"cfrag": "03a48d0c6578a259efdebdf0b12ab6e1d29b472a520a1f14ecf628eb161a386ce2021ab6cafd3c7778bef9c199911306939ed679e7b4c09fbbd02c897f994417a81f55faf9c9be6d59da90b2c2e9f09d85e3965e00629b31c3854367f86039725ecb03b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41"
|
||||
"kfrag": "939cfdfc368ae2c011ade7b7d18543f66f6b2f5aea0ed6732830d8ab281ec492cd90cd1756a44414e51d741e99f9a7d46cbedbfc7bdb60f6b8d33ffb1d4d6b31025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb749020d6a191832eed71367b495a514ee4ac442ce19040c00c4e6ab331f844b2fff11d1db98034e5ff8f86a5b2d015cfa439774dd83644adb80bcba1881f09bdb1b105e37329495d03775e99da0f1c99e753e6868fbe04766572a086ce8e1c575e0d253698578672fbfdcdf2607513d0d0c03f346cb8183506d179e9b999988c7ca317c10e8606e3f7a18ac9146dcc2f484dc6c46a149c371c7a22b17c14f0e3e84220101",
|
||||
"cfrag": "03b45ef82c8e3439a698e3cc4383cd223d6ae84a6c1c7e6ea6b1e89fb501273ef1024e2b1a83a2e675e7f3e4f1d803f456dc83d6536bc33c07b01fc521380cec39a4939cfdfc368ae2c011ade7b7d18543f66f6b2f5aea0ed6732830d8ab281ec492025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74903893bc5023c9da3e0acf0028256ae37fe1015e09b8fcaea89b3aff10a49b1ac5d03c295e648f0dc3df65314ce141ff21b04fcf53432300ff8994ba765d8205bd90d020d6a191832eed71367b495a514ee4ac442ce19040c00c4e6ab331f844b2fff110390d4511e87f7dee7091cbffb952da38c7efbf1ee5a6890ea8afcfa22ee425c0cecd183c8351af7f917ccaa9bd7771de0ef9cf240bc02a7dc32c10e0bd4ed725653698578672fbfdcdf2607513d0d0c03f346cb8183506d179e9b999988c7ca317c10e8606e3f7a18ac9146dcc2f484dc6c46a149c371c7a22b17c14f0e3e8422"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
{
|
||||
"name": "Test vectors for umbral.curvebn.CurveBN.hash()",
|
||||
"params": "default",
|
||||
"vectors": [
|
||||
{
|
||||
"input": [
|
||||
{
|
||||
"class": "bytes",
|
||||
"bytes": ""
|
||||
}
|
||||
],
|
||||
"output": "fd307d78e9f94e1b76762b8efd1284067c4ddd49f64f95441d57784d05a6323d"
|
||||
},
|
||||
{
|
||||
"input": [
|
||||
{
|
||||
"class": "bytes",
|
||||
"bytes": "616263"
|
||||
}
|
||||
],
|
||||
"output": "26249427a90ff70e82597a97a80f62fd133038a1ddae58b446eddf1a08098da3"
|
||||
},
|
||||
{
|
||||
"input": [
|
||||
{
|
||||
"class": "Point",
|
||||
"bytes": "03cdaadd5e0493c8426fd65004ea1e10ddcd3fcb37ac87ce84d0ffdda36d8d6072"
|
||||
}
|
||||
],
|
||||
"output": "b8070774566dbc192f9aeb4e34eb94b39bacdee67f7c373ca1371dd4b8b1ab41"
|
||||
},
|
||||
{
|
||||
"input": [
|
||||
{
|
||||
"class": "CurveBN",
|
||||
"bytes": "b32e9abad43a3724ff37dd9d15253fe66339f978e7581dff5b34b4db970b7bf3"
|
||||
}
|
||||
],
|
||||
"output": "7d1978b2203d57abb5fe3383d7929ff8aec7b2ea08bc7f4120d12843792ceadb"
|
||||
},
|
||||
{
|
||||
"input": [
|
||||
{
|
||||
"class": "Point",
|
||||
"bytes": "03cdaadd5e0493c8426fd65004ea1e10ddcd3fcb37ac87ce84d0ffdda36d8d6072"
|
||||
},
|
||||
{
|
||||
"class": "CurveBN",
|
||||
"bytes": "b32e9abad43a3724ff37dd9d15253fe66339f978e7581dff5b34b4db970b7bf3"
|
||||
}
|
||||
],
|
||||
"output": "57d604fa36e3acbe841c5367f29393868c57290fa7f411b1b9fbdbfe1c320c4f"
|
||||
},
|
||||
{
|
||||
"input": [
|
||||
{
|
||||
"class": "Point",
|
||||
"bytes": "03cdaadd5e0493c8426fd65004ea1e10ddcd3fcb37ac87ce84d0ffdda36d8d6072"
|
||||
},
|
||||
{
|
||||
"class": "Point",
|
||||
"bytes": "02aa6a41b809ba8d2816444788d597e6af5b81c56f7645f67e8dde417fca052828"
|
||||
},
|
||||
{
|
||||
"class": "Point",
|
||||
"bytes": "0336877b38451dd8baaa4830794091a38b7abb2c8187e1062cc2512e202739d5eb"
|
||||
},
|
||||
{
|
||||
"class": "Point",
|
||||
"bytes": "0278adf9ca33ea46b2c0fb7b5a14a2820b171a97f8c69846e9866194ed1315438a"
|
||||
},
|
||||
{
|
||||
"class": "Point",
|
||||
"bytes": "027adba799a3cf3935abb5366b5ec3f132f05a9baa0647734e7be50952e3309bd2"
|
||||
},
|
||||
{
|
||||
"class": "Point",
|
||||
"bytes": "03ee94ece91a0091e3f2c132f348716b0ab01d4ec320ad408ff9308e092dfe1e1b"
|
||||
},
|
||||
{
|
||||
"class": "Point",
|
||||
"bytes": "0203c98795773ff1c241fc0b1cced85e80f8366581dda5c9452175ebd41385fa1f"
|
||||
},
|
||||
{
|
||||
"class": "Point",
|
||||
"bytes": "026fa5450221297328a37043220b0f82a7fdf2a4ab01c9b30cd572181736390ad3"
|
||||
},
|
||||
{
|
||||
"class": "Point",
|
||||
"bytes": "038a295338304923b70a233778a1b93f869062a5d1663127a6e5a5a5b07739f21d"
|
||||
}
|
||||
],
|
||||
"output": "c384b9566bc17b5ae424862116c89f882374c5bb2ec2c5c54bbf7b4e2cc8e179"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
{
|
||||
"name": "Test vectors for CurveBN operations",
|
||||
"params": "default",
|
||||
"first operand": "42bd8598f460a2334e921feaa58a64700bdd5eeb4a6d5f59f7363f5d167a2830",
|
||||
"second operand": "cead0bb7e8b078069664f7a4ec81945b9c3b2233a39097f0310670c29e1e34fc",
|
||||
"vectors": [
|
||||
{
|
||||
"operation": "Addition",
|
||||
"result": "116a9150dd111a39e4f7178f920bf8cced69a4383eb5570e686a5192e4621beb"
|
||||
},
|
||||
{
|
||||
"operation": "Subtraction",
|
||||
"result": "741079e10bb02a2cb82d2845b908d0132a51199e562567a586022d2748923475"
|
||||
},
|
||||
{
|
||||
"operation": "Multiplication",
|
||||
"result": "14ae3ce9b69ed69bbd9fe81e50c9482afc72651b5a5f69b40e8b9235d6fd459b"
|
||||
},
|
||||
{
|
||||
"operation": "Division",
|
||||
"result": "1ef7189cfb9918d4fdde0298e811631ccb147036fdc2557bcc6b81db7e3e7222"
|
||||
},
|
||||
{
|
||||
"operation": "Pow",
|
||||
"result": "f558c003bc71493f8ebbe39b42316c514ccc610437f1b4f37c73f81fe903dad2"
|
||||
},
|
||||
{
|
||||
"operation": "Mod",
|
||||
"result": "42bd8598f460a2334e921feaa58a64700bdd5eeb4a6d5f59f7363f5d167a2830"
|
||||
},
|
||||
{
|
||||
"operation": "Inverse",
|
||||
"result": "6c6640d846d233a6c705c1c2c06bb1372d95bca81ea8cdbb6840ccb9a415dc3d"
|
||||
},
|
||||
{
|
||||
"operation": "Neg",
|
||||
"result": "bd427a670b9f5dccb16de0155a759b8eaed17dfb64db40e1c89c1f2fb9bc1911"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -2,39 +2,39 @@
|
|||
"name": "Test vectors for KFrags",
|
||||
"description": "This is a collection of KFrags generated under the enclosed delegating, verifying and receiving keys. Each of them must deserialize correctly and the call to verify() must succeed.",
|
||||
"params": "default",
|
||||
"verifying_key": "03714109aee5bded1ea98ced8660ad1d49697e14f2a1bbefd5944e4ce443493d08",
|
||||
"delegating_key": "021c0e2868064f20441df0050bf501137ec84db6ca3596b88a967308e14bea2caa",
|
||||
"receiving_key": "036285c48fbb7c38bccceb6e15959a33ed3dd5ab5c96daaff67636b8557d6885d4",
|
||||
"verifying_pk": "03f24761ac8b02de08ad1622d023f669d6214c3bab81a33087ed3ec5505e4d43db",
|
||||
"delegating_pk": "03a73623a2e72fd52b2d313214c7495580c14fe6cd8de7ad0d63bbfbfd6fb6bd4d",
|
||||
"receiving_pk": "02952a1903b9c929f0d93d935b34b272ea25a84833a04e22d887f27bc3bf0cc409",
|
||||
"vectors": [
|
||||
{
|
||||
"kfrag": "417225b396fb91a82f0ed97b4dfb235200742a58d9632d46e352e507c5c716863b349c46905501861f950d11aefd2c69a1f4fe31ae91b4c8624c7556ec7d01ef026fa5450221297328a37043220b0f82a7fdf2a4ab01c9b30cd572181736390ad303b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f4103428244169c9d2d3cfefa6b88251e7600c4a6f9a4edb5e371a0af7bc5134bf68ce9ad3b8a1acbd66926687e2cf7a30762527a85d41b1eaee133866df9dc4fe49b43601bb4d9b496f59e44e2e172141f9480bad0cfda18e5dd6c8dd7a5e72b4bc59281ed11ba75b87f7f1919c30b56d7f0c522b77fd15d37f893fc784eb77d1493"
|
||||
"kfrag": "3f3453856117dedb3d7518d0435c04b10734700c8aca48fa5a8b85eded515cdffefbefddcd3cb728dcc6d05061b5e201aa1cef55ce89e94c34b6887b307d5ab2025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb7490374d2e59ea274c8011f6bf26ca8fe8eb8e7837cafed8547485e3fde6ffe0368e98d26b0d7e12095941daffae8ca429147d9686dc3285ea182c0d7b15db41c7afd3fae3c88100707ef28b53d7cf93961dc509864eb319f0cce274544306c96099b5792af0b820bf758f5c8f2a69cfc1e0a5f91fab96ab82c10c7740602f90efdd315ff5dbd07323526ca1c9a93132b4d008fdf1796a55b92e2fe75c544652256c80101"
|
||||
},
|
||||
{
|
||||
"kfrag": "fe11bce6ea1d451fbe396c6c7d00747264324eebc93bd6ca9e949c2970be214e56571ecd1c02064be2058043cbe5d7ddde90771c5709c1cb48f2a4866bef246b03cb7014d5978aba65b7849bc5756d0e94ce515cc31725ed86550e4e38424b63d903b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f4103adbaf6ef97a68ad61ca39b1ab244b5d1881b00fbf7e2f0eec06907a0078ec4fcb573d101c2d1976985fa7d6d7c96b327030333ac3c66fc0223b456a70e996e19a31822dea4781891b054e86ff34abbfcce05f7c613b7296133ad5b10455061368a102dff42a913edb8e5d248a8d979879a3cfca1f8d3fbf6b8a24ca5357ae057"
|
||||
"kfrag": "d15ac028be9938b5e2d7a9fb9957b416db538a11d141d320a5f656ab4e3cb0cab3a2ddd2672f3514e7da2f29cbe3815c83b6d704e09d595a68fdd5aeff52ef8a025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb749033d6984555d41614a2ba08c2182e27a7105cb60ed414c9732a156c3e44d196dd3507335e3781d6c9a4543e3ae81a4c7538cf280292c9c0d92e4f513e4e073721f2f72a1ea0e69912e21754e32edf9cefd3a96183f5501266bedb301f291709ffb25ed5213660db4c8bfc086e794650455bfde1251f92e1fe49f1feb016ad44fe859b342964db14dddfa6a33eba53021149d8663d306850b8907f9dabf4a5d3ecd0101"
|
||||
},
|
||||
{
|
||||
"kfrag": "4edffcacb49f5620c1cebd67f43366aa22dc380dd9fd0f4853ffbbd44e31986f4a99f953e5c553f08a83cb16c0b5c793a4f691d4d3d239e29f232d2b058970d903041b77b8891d845c240a8a12b71dfe9651de67ceb817b54eb578636fe8a9605103b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41035ba90e2a06117e205dd807c780b5687ff6dadf19454fffb367e7af6b3f9cadf03572dc3311c0e1706354ba3340d1b1f915afc82007f9e09ddd34ce07a038a7bb7ecc7343b1532b175e5b8c7d3c712e3eecb18ff69c22237ba9ab6b47b4572529a35b29c48874f396def9971c0230a51e26975a953ff87af8fdbfd53bf42aae07"
|
||||
"kfrag": "f780e25b78581e70d28f102264e1f04b482eb9ecd45c188f2f9cd90026627f904b903c6da7529805e1d91fbaebc384588083e2c5f235c31e7f768b8d0df43513025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb749032f4d57c068c255281703ba6d449345e2f6240a4b8b3047209ff2aca5edec0ad2394020e796b7b9f9130c0ab02b4c8151d050f38f3d0ff5f38dc164e4d254ad2737758c4b67c0b54267b113692f11a410ffc5fe3e41c2f8ec9246854baa63dbfaebb58f0cc89c528e119788c521bbfa8dd33c69129c1a05ed454d7534224c9df35007ef11b38769ee8d2788c72745e50e14e32b0c2b9d988e2550dd3f89cbd6b50101"
|
||||
},
|
||||
{
|
||||
"kfrag": "762a4ae9fdbb74de327a29d25c577c46521ee44a951c6e33739e5ceb2a340aefd2a93d8cbe37b9149dd26ce59b0b7ffcd9adf1ca822c5b477595ecba1933291503409c76b74f0f415f5b6a8421514c250f71a5c5b6c5b0d89148e03ba2a624a9a503b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41034f04b1666bc36ac5d3b7ba66420b660d19caf9a995b6cc7cc0aa7c9d0e739f25b1048e754ffd0703e16b453ae8279e5410eb16d2d53683f2d80c30c39918834d70970a09ae17c014fc939979c1e3659b92a40d8688ed77861407316a22d4a8e0b5628c5eec0a2123129ebdb627eae78f8c404f8e129dd407feb2e970b0ba5e39"
|
||||
"kfrag": "afdbd2183491cec86259c4e6785c9048f17e1f0c86df597983e78f50eb9e88ebfc725b6addb07ce67e5e33e404237dfd9baf15825329d780bbcf07952d899709025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74902fbba98644ea41f7abbef7f0a80d645d428844eb6a08c848b73122057f02cc264f27b1b019d667b1a3c89e176b9203870c42dcf3bb6af9191a89db98b2cd1a1b82c61d188f2d2a5d8e1c92a64d3402e98ca09e4594dd67ad74a9583e47b5ee2fd0d38772f8dee198e6e7fa2f7acca079a90b280f98e880106bc337c314bdea20b1e8d1d43ed0912a4a55475c2587ba81a16b9775681774a8bc9d1af2395875e000101"
|
||||
},
|
||||
{
|
||||
"kfrag": "b40778198920434d132c4d3724a7b7422428c9921bd627710c3764a6c54376f6a03be8af10cb0f1c3b74d132dda5feeb4340a9f6213da859c05b6b57249e54e903e9198cc0e86b880251931ab162130f93242164a5b242015195290ba75b5a6bea03b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f4103f27829a4798466ed68c2c53137e4e658e86faebfabf2556837b60b502a1721de3e073a98974686d1b847c27167f118eeea6a27029775800cb1618b393bca9cc35b341b546c8851395b3b68945b73b01fdbe94a9f6c400b22bfb5e3bebeee95887d888c49ccf99553a2941de1cbd3f845c5725d1b6b26017dea5cf161fadb5fda"
|
||||
"kfrag": "831f7b7b14181746fcfd7e26e03e3fb63e7a8903acff3dd3c7ad0c2c94550d9abcced3aacb62fa59fd2847a2b18cc6a6f0a6f646e39ec94f05ef186186dfbe10025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb749020db8647602d7401b47c556f25540bd45da99e69462ee8eef1b9c26a26a3757c77f90b015143d8516a5bf2ada51a24b8192e1326d38bf47c0f54591619db42c7c122fd0d955f788dfdeee8d762022887216a3de332632d44092f1f06793eb351fd6acaf0f2060ace737a11e1c1cec72ba361ce754c913765113e1695de323225f24c3f0bbf832c9062d004b69e40090930b40d5986eb9ede462e5d044083127450101"
|
||||
},
|
||||
{
|
||||
"kfrag": "bb063b1ae2a2e2c804671a40571b5d9a7746a7b11b98fd1794207daf68e8d408e4ad0acf429b3e97d7d098766cf304fc43454a79d541a8e85cd95ee93b7bfb460242325b9a16de25bca5ce9225091b2d9eec4b98fa627f6c2fec591cfba3561bd303b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41034c2742c62c69c571623a751dc842998cc47a6d33c7e2d30e099bc10d2675fa8bed049ff1474d604eb97e6c1b997dc506bfcefd6aed3838db23fd08f0cebe176fad3a0c5734d986b506145f0770e6689eb1425eb2817d699be1c993d64b26f2dd59d3acf01c5372adabebaa04336ff03219eadf1fd085a6957bd5e90b83919774"
|
||||
"kfrag": "c63acdf5fb6820df50354e74e65eefacaf75e7e22c780b2b5e2c49a0db9d353e7217ca6a942fce879bd4b596aabd489e0c5af75bd94b72aed37999799e9c8dc9025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74902fcaa0a22aabe3b4564972a9368d6a752bc5e359b306cfaff1626b49911158cda599e662db88909f430575f44b3677a8161a5003519e48cf0da635cb20b15f18d632a1bf41eb525b9ee9fd47f196fdd15059d989f9166e5b437cbbd66459e03fad404248f1da8048a5bf9dd9650fbdd50d1afcbadabbcd7a5aca1882b5eeb803443cd4caff8c4b090bc263f56f496aca086bb6f1fedfd5a99e1715ebfe2c6b5010101"
|
||||
},
|
||||
{
|
||||
"kfrag": "9a2d97e3bba40307b7c60f6f22c5965f6587206cef4d5d7980ad8d75f0f4ae69de90b3ac071a58a06c4d2d0895db9e71a4f36b86fd078dfce6e436514f70797d03d959ebb3d7ee2fd90f2a390da0d7377f7549877f7720739e7e84e0c488a6d16203b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f410394ba445cfe40a9ebf1bc6a6f9588b704b11b129a2a102e8c5bf892fbb695e2d37d396c1b34f8eed4f348ecb11a29fa2b68c3790ef2ddc169e6fa7dc86e03b1a7fd8faf67773903168ee5f9c2ac60468cd81c7ee389af0f03f3fed356c65e7f182faa22867aa4544554d235f78d28afe7d90fff1cf60184bb1ea947a343c13d07"
|
||||
"kfrag": "1365f76b3cecb0d27cd839263d6e91e8a3e703cb10590fc4ee8c533caed2fcb4e390938ab2eb832d51d7436058182fb46fb4288c9409a2465c25e447919e61eb025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74903ffe0345c282a9890aa6759dadd14afdd50a82c19123a110d37f4582a1549ce5d4308de0dbbc76897e910253bd1fdb10e65bbbe45cede7dcbf494eba5dee18f9c141cc095ff275208485abe81b229b730c11a1fd87de00756e3d705faff9e6f508cff3f81aec66079b4737c5d55ada03f7a749da2605e6b1bb57324528c373cac773526c02a0cf876744a5e7cd7dee1bad8ccf1e81a46d4c7049743cabca94dad0101"
|
||||
},
|
||||
{
|
||||
"kfrag": "3d09cbb50ab9ff970c93a03976efaf5f13aef88fc4e1b8840cebad96d0469605b343d7f3d4297ada1fcc08009260250f29dbb64bfb6dd64c2dd72174be3d87a202f665886dabd4fd94513aa1082a403efcb9722115e7c44f8ce5394799e10c2ca203b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f41037ec97969ef20834c753f460c1c29360ce535cb19e0dd0da3bc9c568157137c3f741dadb514586112bfa22c4aa64c28ba854f7393a04ce77a3c4b731e6eaa41e29f2d240b3adf5a973688c7691e8bde140647f4d2c4d0c06a0d690415a82f18f095fb69a084607734579a590c6d1e477d93716919c67d6627a890fa5a2250109a"
|
||||
"kfrag": "d961d60a31b197ee26437b1ff6b484c63bcfd208f509af1225a8db8f65240e366f7924ae66d6cd64c25c74761067bcb3a388cf2d4d2e90738666252868e790b0025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb749037c88f9bf80bb6a406843e339dcc8548b3f8bf16e525be9c94d5e3b8c243f466fbb19cf3eec376f9c46d9c52040555927c7eb506af29718ed9b728ac210d43b2b4958eae22852a1e3761a71cedff210a52f5d312e5cc1b12bdbb2edbe3f955f5535b8b1ce9e3e8b04a010a2dd227c9d906d7806fdf9df07d8ce379597bf7513c23db95d84e7774a3fe0de392cd7390c6ec7cab60ad22317d8c00de0865bd0ceb80101"
|
||||
},
|
||||
{
|
||||
"kfrag": "71c39f563bdda903d8397370b077ba1348e07cf644eaffdfbca251f63d8e0c8377d0f143019bca10090a07c15ab9d6d71857451443e0e77ca6768b45b93892b802b54fd786330796eebd5df0a7d5dc26422177d5637028aaea293dfa223a72a56203b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f4103aaaf48203b37dc6152c0735493517a31284be4c46b57a21f01359572d1e1e3d1b0fd64fc82efb401f25dab88e48474450b274625c28e16cb51f31259206aba6fe4886083237bdb5f7da67c8e1e2da7fdef57789c919ccceae6b5453eaa22fb6dcb58a2f9f1f20f05d03326031d4817ec52cf3dc9eb37f2d3b014ba315f5b8c16"
|
||||
"kfrag": "8618ade86dd8b8f15c206274caba6955c30171e466e220ef2344af25f3ca5976ea2eb761d4e8d083e4bf30052c90f6fde525716bd19b5abe98d66bdd48745f85025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb74902bb441051315ea81c8d9d9cc4e63d3f66e6fe3ef52217e8639e0af421d5dbba1be604f5168408cc4bf118346126b9a105b57524b741e141d5cebca15c8964669858bed28994196a23e4f99706cdb8d912d9c53b7c58f98e6d5eb5a4ef3aa2b7b9e0a270af089c667647613c6abe1b7aba8103ee386a04df80341e51e132d6d35d28753fa65e7f8d8c8f3a10d8adc8499a174bcb8041cf6078520ef5e9e00853290101"
|
||||
},
|
||||
{
|
||||
"kfrag": "55faf9c9be6d59da90b2c2e9f09d85e3965e00629b31c3854367f86039725ecb5c560659b6dc7d16a5f3202da0489aa02cbbabb65d8e1107c3645ca7e5bf36c3020e06cdf7d03bafc42e77c86d07b76840ffa7db6a5c511f3530d44b34edb69aba03b890c5614684ac49f59bae91f699cf15761d4c994e0561a1dd570d8dc1081f4103f5674cd594e92cace034b8b443a7aae0ae48ed57ca9eeca5ad6e2855334a989c0ea976c30e4f5eb2527b01caa468f150db4a19cae08ba6015df41fc07d4ea6bb33433a543608fd59cacba037daecf6e069f0954ce9a5f1651297c28cf31fdf76b655ed10284f5bb436c93934bb89b2434b7ff9f7bcb414b3e879a5571c88a0b1"
|
||||
"kfrag": "939cfdfc368ae2c011ade7b7d18543f66f6b2f5aea0ed6732830d8ab281ec492cd90cd1756a44414e51d741e99f9a7d46cbedbfc7bdb60f6b8d33ffb1d4d6b31025141c7f166303d33491fb9aae65c941fd7f40e5c9270a9600c20281090dbb749020d6a191832eed71367b495a514ee4ac442ce19040c00c4e6ab331f844b2fff11d1db98034e5ff8f86a5b2d015cfa439774dd83644adb80bcba1881f09bdb1b105e37329495d03775e99da0f1c99e753e6868fbe04766572a086ce8e1c575e0d253698578672fbfdcdf2607513d0d0c03f346cb8183506d179e9b999988c7ca317c10e8606e3f7a18ac9146dcc2f484dc6c46a149c371c7a22b17c14f0e3e84220101"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,37 +1,37 @@
|
|||
{
|
||||
"name": "Test vectors for Point operations",
|
||||
"name": "Test vectors for CurvePoint operations",
|
||||
"params": "default",
|
||||
"first Point operand": "036893b75f97f4200270c07d043c86d4b9a48e0f0cf4a649b5311cb557cd49f65d",
|
||||
"second Point operand": "022a7baeacffd711253616cc83da6c17451e5511b92fbef51da2a74f17db1989c6",
|
||||
"CurveBN operand": "42bd8598f460a2334e921feaa58a64700bdd5eeb4a6d5f59f7363f5d167a2830",
|
||||
"first CurvePoint operand": "02eb0184eedd9d14e10ad0714afd9915c58b2b40b582283e3e741d0141189246c0",
|
||||
"second CurvePoint operand": "02b925f594ea60040f470195c72fc7aa8caeac7161af3abe65dceb2908bc866754",
|
||||
"CurveScalar operand": "d63d8806eba7bfc2f5fd77d21aa5d7cc7cffcee26ac096f7c5904629c0db1c12",
|
||||
"vectors": [
|
||||
{
|
||||
"operation": "Addition",
|
||||
"result": "024e520b33f362bf072339db00c591ed9082914fa3c4f3e32b0e614019bf4961de"
|
||||
"result": "02de2d20749111f538575c81e0abc406c71cf6d4c7c1b3555476d6f8778f271d5a"
|
||||
},
|
||||
{
|
||||
"operation": "Subtraction",
|
||||
"result": "021e4e230982f00e23b7b95eb4fcd36881f478b90928a35f5fb0a46fe36d7eb8ed"
|
||||
"result": "031bd3e6f5b33b3f94601d3a243efaf2c359c3b097aff1aa3fa8fbf5210f110b8d"
|
||||
},
|
||||
{
|
||||
"operation": "Multiplication",
|
||||
"result": "03f72a2a0bfef906e5ae98931f156db679bfbb3e72aed461b217d9a035cb2882ad"
|
||||
"result": "020fd46d21ec56d94a787c6d717222489b7a070a4f57065f442f588789d1feed87"
|
||||
},
|
||||
{
|
||||
"operation": "Inversion",
|
||||
"result": "026893b75f97f4200270c07d043c86d4b9a48e0f0cf4a649b5311cb557cd49f65d"
|
||||
"result": "03eb0184eedd9d14e10ad0714afd9915c58b2b40b582283e3e741d0141189246c0"
|
||||
},
|
||||
{
|
||||
"operation": "To_affine.X",
|
||||
"result": "6893b75f97f4200270c07d043c86d4b9a48e0f0cf4a649b5311cb557cd49f65d"
|
||||
"result": "eb0184eedd9d14e10ad0714afd9915c58b2b40b582283e3e741d0141189246c0"
|
||||
},
|
||||
{
|
||||
"operation": "To_affine.Y",
|
||||
"result": "f940cab3f294e54a5ccd5643a212350be822b145e6dab7cae5ad1d98ffb9bbad"
|
||||
"result": "d9bed51d198e8ccd919b54a6eabfed2032cc737d410e0364643716986de89afc"
|
||||
},
|
||||
{
|
||||
"operation": "kdf",
|
||||
"result": "1bbe934cc018de9b123002acdf658b80d63456206c948279ea832832349328f3"
|
||||
"result": "40b49492ba7924c421dd61ea39bf94ac6566feff43a1ef14e7adc2b9af3f6664"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
{
|
||||
"name": "Test vectors for umbral.curvebn.CurveScalar.from_digest()",
|
||||
"params": "default",
|
||||
"vectors": [
|
||||
{
|
||||
"input": [
|
||||
{
|
||||
"class": "bytes",
|
||||
"bytes": ""
|
||||
}
|
||||
],
|
||||
"output": "42184a0ea1e39037cad1ed7f3bb0cd8b7fe978e6d8b94f965e47d582cbdb8208"
|
||||
},
|
||||
{
|
||||
"input": [
|
||||
{
|
||||
"class": "bytes",
|
||||
"bytes": "616263"
|
||||
}
|
||||
],
|
||||
"output": "02e2c58350c30e80f9deea1ae19e21a0baa7761f4448c792f205b8e9b7ac1ab3"
|
||||
},
|
||||
{
|
||||
"input": [
|
||||
{
|
||||
"class": "CurvePoint",
|
||||
"bytes": "02a03893438c0502dd13818f65c039b2ef4fce33bfed150c6ad166554b4e8a51c2"
|
||||
}
|
||||
],
|
||||
"output": "cd175898869252f3d6c6e77eaed94a72e74410d99534b349f27df2362c35745a"
|
||||
},
|
||||
{
|
||||
"input": [
|
||||
{
|
||||
"class": "CurveScalar",
|
||||
"bytes": "5e8601ee29241f263bf49b9999594413f863193fa1b8a985fff981cda7cc9087"
|
||||
}
|
||||
],
|
||||
"output": "2b83903fedca70169024365a4a5b387536a9ba38bd7b9fa0462f5f932f41a493"
|
||||
},
|
||||
{
|
||||
"input": [
|
||||
{
|
||||
"class": "CurvePoint",
|
||||
"bytes": "02a03893438c0502dd13818f65c039b2ef4fce33bfed150c6ad166554b4e8a51c2"
|
||||
},
|
||||
{
|
||||
"class": "CurveScalar",
|
||||
"bytes": "5e8601ee29241f263bf49b9999594413f863193fa1b8a985fff981cda7cc9087"
|
||||
}
|
||||
],
|
||||
"output": "9f7cea094a5ed29ab2ec83391527db31850f915781c07c0b13853869e9885968"
|
||||
},
|
||||
{
|
||||
"input": [
|
||||
{
|
||||
"class": "CurvePoint",
|
||||
"bytes": "02a03893438c0502dd13818f65c039b2ef4fce33bfed150c6ad166554b4e8a51c2"
|
||||
},
|
||||
{
|
||||
"class": "CurvePoint",
|
||||
"bytes": "02ecbc4dc0aed60efa211c7bf0e238593d292a042373b891928bcee459151c2f44"
|
||||
},
|
||||
{
|
||||
"class": "CurvePoint",
|
||||
"bytes": "02aa6a09c61286e36d82f6371038f1c33b2095b3b6dc8d09de7489f516c2dfe49a"
|
||||
},
|
||||
{
|
||||
"class": "CurvePoint",
|
||||
"bytes": "028da9ebf8cc0966bc010152fd3917a8f12dfff0af3b06e34e17f300d622893159"
|
||||
},
|
||||
{
|
||||
"class": "CurvePoint",
|
||||
"bytes": "0281171b7e330ebd097575dadb210e8a405bc162e293881457a301da03f7571c7a"
|
||||
},
|
||||
{
|
||||
"class": "CurvePoint",
|
||||
"bytes": "03e2869b26bbf46a2e1f46116d8d9eeeb45f18acf4a808defee52040221dd08af9"
|
||||
},
|
||||
{
|
||||
"class": "CurvePoint",
|
||||
"bytes": "0374d2e59ea274c8011f6bf26ca8fe8eb8e7837cafed8547485e3fde6ffe0368e9"
|
||||
},
|
||||
{
|
||||
"class": "CurvePoint",
|
||||
"bytes": "024526ff9ab3c9c4cf619166ff897b8c023a6ff01f54c42a921ec1ab564d5a65eb"
|
||||
}
|
||||
],
|
||||
"output": "072c408c4631491eb12b00b38b8f7f20080b802e3e7c5c421f89887055248b1a"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "Test vectors for CurveScalar operations",
|
||||
"params": "default",
|
||||
"first operand": "d63d8806eba7bfc2f5fd77d21aa5d7cc7cffcee26ac096f7c5904629c0db1c12",
|
||||
"second operand": "c2cc9d2f0b39201a5d4d4aa755c0506eab19c1abc89068d216f23f4965427ac4",
|
||||
"vectors": [
|
||||
{
|
||||
"operation": "Addition",
|
||||
"result": "990a2535f6e0dfdd534ac2797066283c6d6ab3a784085f8e1cb026e655e75595"
|
||||
},
|
||||
{
|
||||
"operation": "Subtraction",
|
||||
"result": "1370ead7e06e9fa898b02d2ac4e5875dd1e60d36a2302e25ae9e06e05b98a14e"
|
||||
},
|
||||
{
|
||||
"operation": "Multiplication",
|
||||
"result": "88cdbd2959262c74f26d4315e65b7e8c4fb5d1326fb9f1c6dbfd7c951d43485f"
|
||||
},
|
||||
{
|
||||
"operation": "Inverse",
|
||||
"result": "663c74e198bd4dcd2db7b78895fe8994a727d8bcba073818475a22483bb0103a"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,86 +1,86 @@
|
|||
{
|
||||
"name": "Test vectors for umbral.point.Point.unsafe_hash_to_point",
|
||||
"name": "Test vectors for unsafe_hash_to_point()",
|
||||
"params": "default",
|
||||
"vectors": [
|
||||
{
|
||||
"data": "",
|
||||
"label": "",
|
||||
"point": "03bb27bd2a7b7ff7500284bc44285d80ce5527448c79e7ef25843d1209c5df40c3"
|
||||
"dst": "",
|
||||
"point": "0215ec7bf0b50732b49f8228e07d24365338f9e3ab994b00af08e5a3bffe55fd8b"
|
||||
},
|
||||
{
|
||||
"data": "",
|
||||
"label": "616263",
|
||||
"point": "03eec257593bbefd7f617350bd2aea9b14cc14a93528bad07b6e39c5db433adbd8"
|
||||
"dst": "616263",
|
||||
"point": "0297427e8f434c897d66d7ad40b51e0a11f8bdfed31e724ca4ac86c14fb5e2668f"
|
||||
},
|
||||
{
|
||||
"data": "",
|
||||
"label": "4e75437970686572",
|
||||
"point": "0352184687070d943946f5da637b8e2aa00640b0da2231e7598ceea0286657a7cb"
|
||||
"dst": "4e75437970686572",
|
||||
"point": "025aa967c88c73854f4f4d9286a7ce4292898dfa42c7593b75cf823cd1b6dada96"
|
||||
},
|
||||
{
|
||||
"data": "",
|
||||
"label": "4e75637970686572",
|
||||
"point": "0290c2fff33ad722da7cc813f57153afbcf33fc2acd1607f188770f9dcc7c00a0d"
|
||||
"dst": "4e75637970686572",
|
||||
"point": "02f3fcae4fb3596fbb34feaf3e2fff938b177b55d89c66f728e51fef220d9b702f"
|
||||
},
|
||||
{
|
||||
"data": "616263",
|
||||
"label": "",
|
||||
"point": "02a79e7fe8a6559b5de0614702a4e13d97a672a899d5943e5c6b1a0c45fca933b0"
|
||||
"dst": "",
|
||||
"point": "0204c19746f60b6c4abbce9dbe31f2e0df9b22d8130cc0844cbf67db154d944db3"
|
||||
},
|
||||
{
|
||||
"data": "616263",
|
||||
"label": "616263",
|
||||
"point": "0234cb950681a734bc29feaa5c1dd029a9062352488bae035acd56c0ebe99f0b7f"
|
||||
"dst": "616263",
|
||||
"point": "024c70ca862edba77a8265ee46e0137729826a79721855888bf7791feea42b9990"
|
||||
},
|
||||
{
|
||||
"data": "616263",
|
||||
"label": "4e75437970686572",
|
||||
"point": "032106a37075e17b6f01b4ab63d5c84622efe5111843155c7d53e34548f287115d"
|
||||
"dst": "4e75437970686572",
|
||||
"point": "02357334755ceedaef03cb81b6dbbebd8399e0cf40a122a586069ae241e34fc869"
|
||||
},
|
||||
{
|
||||
"data": "616263",
|
||||
"label": "4e75637970686572",
|
||||
"point": "034b4db429ccf36858718cb864c0c27520fb16218992a3290d1fc4758756ee0bef"
|
||||
"dst": "4e75637970686572",
|
||||
"point": "0256ec5dbf81d55fbdad3c2095177982a068bb0043dd2cf2834cc6a53e538157bf"
|
||||
},
|
||||
{
|
||||
"data": "4e75437970686572",
|
||||
"label": "",
|
||||
"point": "03828335c1f3ccac7ec40d6dd4771e9ba527b039d7104fa0477a96cdccbf16c7cf"
|
||||
"dst": "",
|
||||
"point": "02b0cd14ef08638d57804c768d3b0a171461268f6faede586751f2919bdd7490b6"
|
||||
},
|
||||
{
|
||||
"data": "4e75437970686572",
|
||||
"label": "616263",
|
||||
"point": "03193846a6ba4d9dab59990e53317050fe301477d3ac699f8cc260dd33cbc2c9c5"
|
||||
"dst": "616263",
|
||||
"point": "02a1c3e1c00f45a059fcf7749e31c5206388aa72bcc7c10195907e9c70c2a0a700"
|
||||
},
|
||||
{
|
||||
"data": "4e75437970686572",
|
||||
"label": "4e75437970686572",
|
||||
"point": "0324215e7df37205a23c0b7f0a7e168c7984d9109ef31ebe7b37edc7d81ede4b55"
|
||||
"dst": "4e75437970686572",
|
||||
"point": "02100c656eed3ed2e175e5430bbd644ac86f24fa69fc1c5b3fd65ece562b480764"
|
||||
},
|
||||
{
|
||||
"data": "4e75437970686572",
|
||||
"label": "4e75637970686572",
|
||||
"point": "03d5744667f3f2ff36217380a5d1701edf939d70b79d17d78a3969b533acb7c326"
|
||||
"dst": "4e75637970686572",
|
||||
"point": "0227a46bc66817fcaa803535a1c109674d300de5df0d8d11f6588325cf6cedf2b1"
|
||||
},
|
||||
{
|
||||
"data": "4e75637970686572",
|
||||
"label": "",
|
||||
"point": "03f31c20a8264446d224c4ae6368193ac8b97247cf386bafe2cc27ffb6783ace19"
|
||||
"dst": "",
|
||||
"point": "02483315691815818fa1f1804406fc4246940cc8cb39405401e2aa5fd8d94bfa64"
|
||||
},
|
||||
{
|
||||
"data": "4e75637970686572",
|
||||
"label": "616263",
|
||||
"point": "0347b4a8559bf9eb00cdc4c183ee4ff0a5c3a12692988d88927b07157c3fdd50a1"
|
||||
"dst": "616263",
|
||||
"point": "02dc01829e4725f8cacf6990c12ab0a5f837770b21e41bdd9964bb0f1ad52fcc31"
|
||||
},
|
||||
{
|
||||
"data": "4e75637970686572",
|
||||
"label": "4e75437970686572",
|
||||
"point": "02b158ccb0b3384c2a8af92ac3e0e59ed9d0647fb62715fefdc9c0796c0b28bebb"
|
||||
"dst": "4e75437970686572",
|
||||
"point": "02b6653e2ed79579380104598cf83fc2b119dd8b91afae2a2a8077ffdca0b212ad"
|
||||
},
|
||||
{
|
||||
"data": "4e75637970686572",
|
||||
"label": "4e75637970686572",
|
||||
"point": "0356b5aa5ef6be8229f3611095a47842aad8a3e54b3aa1f6caea696fa4bfdfe95e"
|
||||
"dst": "4e75637970686572",
|
||||
"point": "02192de02d9c15a52d90ef7192794a2fc925c09f7dcdb4b584b8c7fab33bbda1df"
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue