Merge pull request #263 from fjarri/interfacing

Interfacing with Rust
pull/269/head
Bogdan Opanchuk 2021-05-26 22:12:20 -07:00 committed by GitHub
commit 7b7fdfa285
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
86 changed files with 4519 additions and 6633 deletions

View File

@ -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

View File

@ -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

View File

@ -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 = "*"

1111
Pipfile.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -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:

View File

@ -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

View File

@ -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,

60
docs/source/api.rst Normal file
View File

@ -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__

View File

@ -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

View File

@ -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

View File

@ -1,7 +1,6 @@
Installing pyUmbral
====================
v0.1.3-alpha.2
Using pip
-------------------------

View File

@ -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:

View File

@ -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",

View File

@ -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/>.
"""

View File

@ -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

View File

@ -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/>.
"""

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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.

View File

@ -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:

View File

@ -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/>.
"""

View File

@ -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)

View File

@ -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)

108
tests/test_capsule.py Normal file
View File

@ -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

149
tests/test_capsule_frag.py Normal file
View File

@ -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

205
tests/test_compatibility.py Normal file
View File

@ -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

113
tests/test_curve.py Normal file
View File

@ -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)

90
tests/test_curve_point.py Normal file
View File

@ -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)

127
tests/test_curve_scalar.py Normal file
View File

@ -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

View File

@ -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')

126
tests/test_key_frag.py Normal file
View File

@ -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)

203
tests/test_keys.py Normal file
View File

@ -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

95
tests/test_pre.py Normal file
View File

@ -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

View File

@ -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')

167
tests/test_vectors.py Normal file
View File

@ -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)

View File

@ -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())

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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]]

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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())

View File

@ -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)

View File

@ -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

View File

@ -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__ = [

View File

@ -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",
]

130
umbral/capsule.py Normal file
View File

@ -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]}"

208
umbral/capsule_frag.py Normal file
View File

@ -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)

View File

@ -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])

View File

@ -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()

View File

@ -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

90
umbral/curve_point.py Normal file
View File

@ -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))

109
umbral/curve_scalar.py Normal file
View File

@ -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))

View File

@ -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)

View File

@ -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.")

5
umbral/errors.py Normal file
View File

@ -0,0 +1,5 @@
class GenericError(Exception):
"""
An interal Umbral error, see the message for details.
"""
pass

146
umbral/hashing.py Normal file
View File

@ -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

305
umbral/key_frag.py Normal file
View File

@ -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)]

View File

@ -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")

View File

@ -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])

View File

@ -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)

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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')

74
umbral/serializable.py Normal file
View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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"
}
]
}

View File

@ -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"
}
]
}

View File

@ -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"
}
]
}

View File

@ -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"
}
]
}

View File

@ -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"
}
]
}

View File

@ -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"
}
]
}

View File

@ -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"
}
]
}

View File

@ -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"
}
]
}