mirror of https://github.com/nucypher/pyUmbral.git
Clean slate
parent
2ef43a6df5
commit
bbf168e08b
|
@ -1,16 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
|
@ -1,139 +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 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)
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def alices_keys():
|
||||
delegating_priv = keys.UmbralPrivateKey.gen_key()
|
||||
signing_priv = keys.UmbralPrivateKey.gen_key()
|
||||
return delegating_priv, signing_priv
|
||||
|
||||
|
||||
@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
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def kfrags(alices_keys, bobs_keys):
|
||||
delegating_privkey, signing_privkey = alices_keys
|
||||
signer_alice = Signer(signing_privkey)
|
||||
|
||||
receiving_privkey, receiving_pubkey = bobs_keys
|
||||
|
||||
yield pre.generate_kfrags(delegating_privkey=delegating_privkey,
|
||||
signer=signer_alice,
|
||||
receiving_pubkey=receiving_pubkey,
|
||||
threshold=6, N=10)
|
|
@ -1,16 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
|
@ -1,156 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from umbral import pre
|
||||
from umbral.point import Point
|
||||
from umbral.signing import Signer
|
||||
from umbral.cfrags import CapsuleFrag
|
||||
|
||||
|
||||
def test_cheating_ursula_replays_old_reencryption(alices_keys, bobs_keys,
|
||||
kfrags, prepared_capsule):
|
||||
delegating_privkey, signing_privkey = alices_keys
|
||||
delegating_pubkey = delegating_privkey.get_pubkey()
|
||||
|
||||
receiving_privkey, receiving_pubkey = bobs_keys
|
||||
|
||||
capsule_alice1 = prepared_capsule
|
||||
|
||||
_unused_key2, capsule_alice2 = pre._encapsulate(delegating_pubkey)
|
||||
|
||||
capsule_alice2.set_correctness_keys(delegating=delegating_pubkey,
|
||||
receiving=receiving_pubkey,
|
||||
verifying=signing_privkey.get_pubkey())
|
||||
|
||||
cfrags = []
|
||||
for i, kfrag in enumerate(kfrags):
|
||||
|
||||
# Example of potential metadata to describe the re-encryption request
|
||||
metadata_i = "This is an example of metadata for re-encryption request #{}"
|
||||
metadata_i = metadata_i.format(i).encode()
|
||||
|
||||
if i == 0:
|
||||
# Let's put the re-encryption of a different Alice ciphertext
|
||||
cfrag = pre.reencrypt(kfrag, capsule_alice2, metadata=metadata_i)
|
||||
else:
|
||||
cfrag = pre.reencrypt(kfrag, capsule_alice1, metadata=metadata_i)
|
||||
|
||||
cfrags.append(cfrag)
|
||||
|
||||
# CFrag 0 is not valid ...
|
||||
assert not cfrags[0].verify_correctness(capsule_alice1)
|
||||
|
||||
# ... and trying to attach it raises an error.
|
||||
with pytest.raises(pre.UmbralCorrectnessError) as exception_info:
|
||||
capsule_alice1.attach_cfrag(cfrags[0])
|
||||
|
||||
correctness_error = exception_info.value
|
||||
assert cfrags[0] in correctness_error.offending_cfrags
|
||||
assert len(correctness_error.offending_cfrags) == 1
|
||||
|
||||
# The rest of CFrags should be correct:
|
||||
correct_cases = 0
|
||||
for cfrag_i in cfrags[1:]:
|
||||
assert cfrag_i.verify_correctness(capsule_alice1)
|
||||
capsule_alice1.attach_cfrag(cfrag_i)
|
||||
correct_cases += 1
|
||||
|
||||
assert correct_cases == len(cfrags[1:])
|
||||
|
||||
|
||||
def test_cheating_ursula_sends_garbage(kfrags, prepared_capsule):
|
||||
capsule_alice = prepared_capsule
|
||||
|
||||
cfrags = []
|
||||
for i, kfrag in enumerate(kfrags):
|
||||
# Example of potential metadata to describe the re-encryption request
|
||||
metadata_i = "This is an example of metadata for re-encryption request #{}"
|
||||
metadata_i = metadata_i.format(i).encode()
|
||||
|
||||
cfrag = pre.reencrypt(kfrag, capsule_alice, metadata=metadata_i)
|
||||
cfrags.append(cfrag)
|
||||
|
||||
# Let's put random garbage in one of the cfrags
|
||||
cfrags[0].point_e1 = Point.gen_rand()
|
||||
cfrags[0].point_v1 = Point.gen_rand()
|
||||
|
||||
# Of course, this CFrag is not valid ...
|
||||
assert not cfrags[0].verify_correctness(capsule_alice)
|
||||
|
||||
# ... and trying to attach it raises an error.
|
||||
with pytest.raises(pre.UmbralCorrectnessError) as exception_info:
|
||||
capsule_alice.attach_cfrag(cfrags[0])
|
||||
|
||||
correctness_error = exception_info.value
|
||||
assert cfrags[0] in correctness_error.offending_cfrags
|
||||
assert len(correctness_error.offending_cfrags) == 1
|
||||
|
||||
# The response of cheating Ursula is in cfrags[0],
|
||||
# so the rest of CFrags should be correct:
|
||||
for cfrag_i in cfrags[1:]:
|
||||
assert cfrag_i.verify_correctness(capsule_alice)
|
||||
capsule_alice.attach_cfrag(cfrag_i)
|
||||
|
||||
|
||||
def test_cfrag_with_missing_proof_cannot_be_attached(kfrags, prepared_capsule):
|
||||
capsule = prepared_capsule
|
||||
|
||||
cfrags = []
|
||||
for kfrag in kfrags:
|
||||
cfrag = pre.reencrypt(kfrag, capsule)
|
||||
cfrags.append(cfrag)
|
||||
|
||||
# If the proof is lost (e.g., it is chopped off a serialized CFrag or similar),
|
||||
# then the CFrag cannot be attached.
|
||||
cfrags[0].proof = None
|
||||
with pytest.raises(CapsuleFrag.NoProofProvided):
|
||||
capsule.attach_cfrag(cfrags[0])
|
||||
|
||||
# The remaining CFrags are fine, so they can be attached correctly
|
||||
for cfrag in cfrags[1:]:
|
||||
capsule.attach_cfrag(cfrag)
|
||||
|
||||
|
||||
def test_kfrags_signed_without_correctness_keys(alices_keys, bobs_keys, capsule):
|
||||
delegating_privkey, signing_privkey = alices_keys
|
||||
delegating_pubkey = delegating_privkey.get_pubkey()
|
||||
verifying_key = signing_privkey.get_pubkey()
|
||||
|
||||
receiving_privkey, receiving_pubkey = bobs_keys
|
||||
|
||||
kfrags = pre.generate_kfrags(delegating_privkey=delegating_privkey,
|
||||
signer=Signer(signing_privkey),
|
||||
receiving_pubkey=receiving_pubkey,
|
||||
threshold=6,
|
||||
N=10,
|
||||
sign_delegating_key=False,
|
||||
sign_receiving_key=False)
|
||||
|
||||
for kfrag in kfrags:
|
||||
# You can verify the KFrag specifying only the verifying key
|
||||
assert kfrag.verify(signing_pubkey=verifying_key)
|
||||
|
||||
# ... or if it is set in the capsule, using the capsule
|
||||
capsule.set_correctness_keys(verifying=verifying_key)
|
||||
assert kfrag.verify_for_capsule(capsule)
|
||||
|
||||
# It should even work when other keys are set in the capsule
|
||||
assert kfrag.verify(signing_pubkey=verifying_key,
|
||||
delegating_pubkey=delegating_pubkey,
|
||||
receiving_pubkey=receiving_pubkey)
|
|
@ -1,81 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from umbral import pre
|
||||
from umbral.keys import UmbralPrivateKey
|
||||
from umbral.kfrags import KFrag
|
||||
|
||||
|
||||
def test_set_correctness_keys(alices_keys, bobs_keys, capsule, kfrags):
|
||||
"""
|
||||
If the three keys do appear together, along with the capsule,
|
||||
we can attach them all at once.
|
||||
"""
|
||||
|
||||
delegating_privkey, signing_privkey = alices_keys
|
||||
_receiving_privkey, receiving_pubkey = bobs_keys
|
||||
|
||||
capsule.set_correctness_keys(delegating_privkey.get_pubkey(),
|
||||
receiving_pubkey,
|
||||
signing_privkey.get_pubkey()
|
||||
)
|
||||
|
||||
for kfrag in kfrags:
|
||||
cfrag = pre.reencrypt(kfrag, capsule)
|
||||
capsule.attach_cfrag(cfrag)
|
||||
|
||||
|
||||
def test_setting_one_correctness_keys(alices_keys, capsule):
|
||||
# The capsule doesn't have any correctness keys set initially
|
||||
assert capsule.get_correctness_keys()['delegating'] is None
|
||||
assert capsule.get_correctness_keys()['receiving'] is None
|
||||
assert capsule.get_correctness_keys()['verifying'] is None
|
||||
|
||||
# Let's set only one of them, e.g., the delegating key
|
||||
delegating_privkey, _signing_privkey = alices_keys
|
||||
delegating_pubkey = delegating_privkey.get_pubkey()
|
||||
|
||||
details = capsule.set_correctness_keys(delegating=delegating_pubkey)
|
||||
|
||||
# Since we are only setting the first key ("delegating"),
|
||||
# the other keys are not set
|
||||
assert details == (True, False, False)
|
||||
|
||||
assert capsule.get_correctness_keys()['delegating'] == delegating_pubkey
|
||||
assert capsule.get_correctness_keys()['receiving'] is None
|
||||
assert capsule.get_correctness_keys()['verifying'] is None
|
||||
|
||||
|
||||
def test_set_invalid_correctness_keys(alices_keys, capsule, kfrags):
|
||||
"""
|
||||
If the three keys do appear together, along with the capsule,
|
||||
we can attach them all at once.
|
||||
"""
|
||||
|
||||
delegating_privkey, signing_privkey = alices_keys
|
||||
unrelated_receiving_pubkey = UmbralPrivateKey.gen_key().get_pubkey()
|
||||
|
||||
capsule.set_correctness_keys(delegating_privkey.get_pubkey(),
|
||||
unrelated_receiving_pubkey,
|
||||
signing_privkey.get_pubkey()
|
||||
)
|
||||
|
||||
for kfrag in kfrags:
|
||||
with pytest.raises(KFrag.NotValid):
|
||||
cfrag = pre.reencrypt(kfrag, capsule)
|
|
@ -1,56 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from umbral import pre
|
||||
from umbral.signing import Signer
|
||||
from ..conftest import wrong_parameters
|
||||
|
||||
|
||||
def test_public_key_encryption(alices_keys):
|
||||
delegating_privkey, _ = alices_keys
|
||||
plain_data = b'peace at dawn'
|
||||
ciphertext, capsule = pre.encrypt(delegating_privkey.get_pubkey(), plain_data)
|
||||
cleartext = pre.decrypt(ciphertext, capsule, delegating_privkey)
|
||||
assert cleartext == plain_data
|
||||
|
||||
|
||||
@pytest.mark.parametrize("N, M", wrong_parameters)
|
||||
def test_wrong_N_M_in_split_rekey(N, M, alices_keys, bobs_keys):
|
||||
delegating_privkey, signing_privkey = alices_keys
|
||||
signer = Signer(signing_privkey)
|
||||
_receiving_privkey, receiving_pubkey = bobs_keys
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
_kfrags = pre.generate_kfrags(delegating_privkey=delegating_privkey,
|
||||
signer=signer,
|
||||
receiving_pubkey=receiving_pubkey,
|
||||
threshold=M,
|
||||
N=N)
|
||||
|
||||
|
||||
def test_decryption_error(alices_keys, bobs_keys, ciphertext_and_capsule, message):
|
||||
delegating_privkey, _signing_privkey = alices_keys
|
||||
receiving_privkey, _receiving_pubkey = bobs_keys
|
||||
ciphertext, capsule = ciphertext_and_capsule
|
||||
|
||||
cleartext = pre.decrypt(ciphertext, capsule, delegating_privkey)
|
||||
assert message == cleartext
|
||||
|
||||
with pytest.raises(pre.UmbralDecryptionError) as e:
|
||||
_cleartext = pre.decrypt(ciphertext, capsule, receiving_privkey)
|
|
@ -1,190 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.point import Point
|
||||
from umbral.keys import UmbralPublicKey
|
||||
from umbral.config import default_params
|
||||
from umbral.kfrags import KFrag
|
||||
from umbral.cfrags import CapsuleFrag
|
||||
from umbral.random_oracles import hash_to_curvebn, unsafe_hash_to_point, kdf
|
||||
from umbral import pre
|
||||
|
||||
def test_curvebn_operations():
|
||||
|
||||
vector_file = os.path.join('vectors', 'vectors_curvebn_operations.json')
|
||||
try:
|
||||
with open(vector_file) as f:
|
||||
vector_suite = json.load(f)
|
||||
except OSError:
|
||||
raise
|
||||
|
||||
bn1 = CurveBN.from_bytes(bytes.fromhex(vector_suite['first operand']))
|
||||
bn2 = CurveBN.from_bytes(bytes.fromhex(vector_suite['second operand']))
|
||||
|
||||
expected = dict()
|
||||
for op_result in vector_suite['vectors']:
|
||||
result = bytes.fromhex(op_result['result'])
|
||||
expected[op_result['operation']] = CurveBN.from_bytes(result)
|
||||
|
||||
test = [('Addition', bn1 + bn2),
|
||||
('Subtraction', bn1 - bn2),
|
||||
('Multiplication', bn1 * bn2),
|
||||
('Division', bn1 / bn2),
|
||||
('Pow', bn1 ** bn2),
|
||||
('Mod', bn1 % bn2),
|
||||
('Inverse', ~bn1),
|
||||
('Neg', -bn1),
|
||||
]
|
||||
|
||||
for (operation, result) in test:
|
||||
assert result == expected[operation], 'Error in {}'.format(operation)
|
||||
|
||||
def test_curvebn_hash():
|
||||
|
||||
vector_file = os.path.join('vectors', 'vectors_curvebn_hash.json')
|
||||
try:
|
||||
with open(vector_file) as f:
|
||||
vector_suite = json.load(f)
|
||||
except OSError:
|
||||
raise
|
||||
|
||||
params = default_params()
|
||||
|
||||
for vector in vector_suite['vectors']:
|
||||
hash_input = [bytes.fromhex(item['bytes']) for item in vector['input']]
|
||||
expected = CurveBN.from_bytes(bytes.fromhex(vector['output']))
|
||||
assert hash_to_curvebn(*hash_input, params=params) == expected
|
||||
|
||||
|
||||
def test_point_operations():
|
||||
|
||||
vector_file = os.path.join('vectors', 'vectors_point_operations.json')
|
||||
try:
|
||||
with open(vector_file) as f:
|
||||
vector_suite = json.load(f)
|
||||
except OSError:
|
||||
raise
|
||||
|
||||
point1 = Point.from_bytes(bytes.fromhex(vector_suite['first Point operand']))
|
||||
point2 = Point.from_bytes(bytes.fromhex(vector_suite['second Point operand']))
|
||||
bn1 = CurveBN.from_bytes(bytes.fromhex(vector_suite['CurveBN operand']))
|
||||
|
||||
expected = dict()
|
||||
for op_result in vector_suite['vectors']:
|
||||
expected[op_result['operation']] = bytes.fromhex(op_result['result'])
|
||||
|
||||
test = [('Addition', point1 + point2),
|
||||
('Subtraction', point1 - point2),
|
||||
('Multiplication', bn1 * point1),
|
||||
('Inversion', -point1),
|
||||
]
|
||||
|
||||
for (operation, result) in test:
|
||||
assert result == Point.from_bytes(expected[operation]), 'Error in {}'.format(operation)
|
||||
|
||||
test = [('To_affine.X', point1.to_affine()[0]),
|
||||
('To_affine.Y', point1.to_affine()[1]),
|
||||
]
|
||||
|
||||
for (operation, result) in test:
|
||||
assert result == int.from_bytes(expected[operation], 'big'), 'Error in {}'.format(operation)
|
||||
|
||||
assert kdf(point1, pre.DEM_KEYSIZE) == expected['kdf']
|
||||
|
||||
|
||||
def test_unsafe_hash_to_point():
|
||||
|
||||
vector_file = os.path.join('vectors', 'vectors_unsafe_hash_to_point.json')
|
||||
try:
|
||||
with open(vector_file) as f:
|
||||
vector_suite = json.load(f)
|
||||
except OSError:
|
||||
raise
|
||||
|
||||
params = default_params()
|
||||
|
||||
for item in vector_suite['vectors']:
|
||||
data = bytes.fromhex(item['data'])
|
||||
label = bytes.fromhex(item['label'])
|
||||
expected = Point.from_bytes(bytes.fromhex(item['point']))
|
||||
assert expected == unsafe_hash_to_point(label=label, data=data, params=params)
|
||||
|
||||
|
||||
def test_kfrags():
|
||||
|
||||
vector_file = os.path.join('vectors', 'vectors_kfrags.json')
|
||||
try:
|
||||
with open(vector_file) as f:
|
||||
vector_suite = json.load(f)
|
||||
except OSError:
|
||||
raise
|
||||
|
||||
verifying_key = UmbralPublicKey.from_bytes(bytes.fromhex(vector_suite['verifying_key']))
|
||||
delegating_key = UmbralPublicKey.from_bytes(bytes.fromhex(vector_suite['delegating_key']))
|
||||
receiving_key = UmbralPublicKey.from_bytes(bytes.fromhex(vector_suite['receiving_key']))
|
||||
|
||||
for json_kfrag in vector_suite['vectors']:
|
||||
kfrag = KFrag.from_bytes(bytes.fromhex(json_kfrag['kfrag']))
|
||||
assert kfrag.verify(signing_pubkey=verifying_key,
|
||||
delegating_pubkey=delegating_key,
|
||||
receiving_pubkey=receiving_key), \
|
||||
'Invalid KFrag {}'.format(kfrag.to_bytes().hex())
|
||||
|
||||
|
||||
def test_cfrags():
|
||||
|
||||
vector_file = os.path.join('vectors', 'vectors_cfrags.json')
|
||||
try:
|
||||
with open(vector_file) as f:
|
||||
vector_suite = json.load(f)
|
||||
except OSError:
|
||||
raise
|
||||
|
||||
params = default_params()
|
||||
|
||||
capsule = pre.Capsule.from_bytes(bytes.fromhex(vector_suite['capsule']),
|
||||
params=params)
|
||||
|
||||
verifying_key = UmbralPublicKey.from_bytes(bytes.fromhex(vector_suite['verifying_key']))
|
||||
delegating_key = UmbralPublicKey.from_bytes(bytes.fromhex(vector_suite['delegating_key']))
|
||||
receiving_key = UmbralPublicKey.from_bytes(bytes.fromhex(vector_suite['receiving_key']))
|
||||
|
||||
kfrags_n_cfrags = [(KFrag.from_bytes(bytes.fromhex(json_kfrag['kfrag'])),
|
||||
CapsuleFrag.from_bytes(bytes.fromhex(json_kfrag['cfrag'])))
|
||||
for json_kfrag in vector_suite['vectors']]
|
||||
|
||||
capsule.set_correctness_keys(delegating=delegating_key,
|
||||
receiving=receiving_key,
|
||||
verifying=verifying_key)
|
||||
|
||||
for kfrag, cfrag in kfrags_n_cfrags:
|
||||
assert kfrag.verify(signing_pubkey=verifying_key,
|
||||
delegating_pubkey=delegating_key,
|
||||
receiving_pubkey=receiving_key), \
|
||||
'Invalid KFrag {}'.format(kfrag.to_bytes().hex())
|
||||
|
||||
new_cfrag = pre.reencrypt(kfrag, capsule, provide_proof=False)
|
||||
assert new_cfrag.point_e1 == cfrag.point_e1
|
||||
assert new_cfrag.point_v1 == cfrag.point_v1
|
||||
assert new_cfrag.kfrag_id == cfrag.kfrag_id
|
||||
assert new_cfrag.point_precursor == cfrag.point_precursor
|
||||
assert new_cfrag.proof is None
|
||||
assert cfrag.to_bytes() == new_cfrag.to_bytes()
|
|
@ -1,140 +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 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
|
||||
|
||||
|
||||
#
|
||||
# Setup
|
||||
#
|
||||
|
||||
|
||||
CURVE = default_curve()
|
||||
PARAMS = UmbralParameters(curve=CURVE)
|
||||
|
||||
# Faster
|
||||
# (M, N) # |
|
||||
FRAG_VALUES = ((1, 1), # |
|
||||
(2, 3), # |
|
||||
(5, 8), # |
|
||||
(6, 10), # |
|
||||
(10, 30), # |
|
||||
# (20, 30), # | # FIXME: CircleCi build killed
|
||||
# (10, 100) # |
|
||||
# |
|
||||
) # |
|
||||
# Slower
|
||||
|
||||
|
||||
def __standard_encryption_api() -> tuple:
|
||||
|
||||
delegating_privkey = keys.UmbralPrivateKey.gen_key(params=PARAMS)
|
||||
delegating_pubkey = delegating_privkey.get_pubkey()
|
||||
|
||||
signing_privkey = keys.UmbralPrivateKey.gen_key(params=PARAMS)
|
||||
signer = Signer(signing_privkey)
|
||||
|
||||
receiving_privkey = keys.UmbralPrivateKey.gen_key(params=PARAMS)
|
||||
receiving_pubkey = receiving_privkey.get_pubkey()
|
||||
|
||||
plain_data = os.urandom(32)
|
||||
ciphertext, capsule = pre.encrypt(delegating_pubkey, plain_data)
|
||||
|
||||
capsule.set_correctness_keys(delegating=delegating_pubkey,
|
||||
receiving=receiving_pubkey,
|
||||
verifying=signing_privkey.get_pubkey())
|
||||
|
||||
return delegating_privkey, signer, receiving_pubkey, ciphertext, capsule
|
||||
|
||||
|
||||
#
|
||||
# KFrag Generation Benchmarks
|
||||
#
|
||||
|
||||
|
||||
@pytest.mark.benchmark(group="Reencryption Key Generation Performance",
|
||||
disable_gc=True,
|
||||
warmup=True,
|
||||
warmup_iterations=10)
|
||||
@pytest.mark.parametrize("m, n", FRAG_VALUES)
|
||||
def test_generate_kfrags_performance(benchmark, m: int, n: int) -> 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
|
||||
|
||||
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)
|
||||
assert True # ensure function finishes and succeeds.
|
||||
|
||||
|
||||
#
|
||||
# Reencryption Benchmarks
|
||||
#
|
||||
|
||||
@pytest.mark.benchmark(group="Reencryption Performance",
|
||||
timer=time.perf_counter,
|
||||
disable_gc=True,
|
||||
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 __setup():
|
||||
delegating_privkey, signer, receiving_pubkey, ciphertext, capsule = __standard_encryption_api()
|
||||
kfrags = pre.generate_kfrags(delegating_privkey, receiving_pubkey, m, n, signer)
|
||||
one_kfrag, *remaining_kfrags = kfrags
|
||||
args, kwargs = tuple(), {"kfrag": one_kfrag, "capsule": capsule},
|
||||
return args, kwargs
|
||||
|
||||
print("\nBenchmarking {} with randomly created fragments...".format("pre.reencrypt"))
|
||||
benchmark.pedantic(pre.reencrypt, setup=__setup, rounds=1000)
|
||||
assert True # ensure function finishes and succeeds.
|
||||
|
||||
|
||||
@pytest.mark.benchmark(group="Reencryption Performance",
|
||||
timer=time.perf_counter,
|
||||
disable_gc=True,
|
||||
min_time=0.00005,
|
||||
max_time=0.005,
|
||||
min_rounds=7,
|
||||
warmup=True,
|
||||
warmup_iterations=10)
|
||||
@pytest.mark.parametrize("m, n", ((6, 10), ))
|
||||
def test_single_frag_reencryption_performance(benchmark, m: int, n: int) -> None:
|
||||
|
||||
delegating_privkey, signer, receiving_pubkey, ciphertext, capsule = __standard_encryption_api()
|
||||
kfrags = pre.generate_kfrags(delegating_privkey, receiving_pubkey, m, n, signer)
|
||||
one_kfrag, *remaining_kfrags = kfrags
|
||||
args, kwargs = tuple(), {"kfrag": one_kfrag, "capsule": capsule},
|
||||
|
||||
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)
|
||||
assert True # ensure function finishes and succeeds.
|
|
@ -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 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
|
||||
|
||||
CURVE = default_curve()
|
||||
PARAMS = UmbralParameters(curve=CURVE)
|
||||
REENCRYPTIONS = 1000
|
||||
|
||||
|
||||
def __produce_kfrags_and_capsule(m: int, n: int) -> Tuple[List[KFrag], Capsule]:
|
||||
|
||||
delegating_privkey = keys.UmbralPrivateKey.gen_key(params=PARAMS)
|
||||
delegating_pubkey = delegating_privkey.get_pubkey()
|
||||
|
||||
signing_privkey = keys.UmbralPrivateKey.gen_key(params=PARAMS)
|
||||
signer = Signer(signing_privkey)
|
||||
|
||||
receiving_privkey = keys.UmbralPrivateKey.gen_key(params=PARAMS)
|
||||
receiving_pubkey = receiving_privkey.get_pubkey()
|
||||
|
||||
plain_data = os.urandom(32)
|
||||
ciphertext, capsule = pre.encrypt(delegating_pubkey, plain_data)
|
||||
|
||||
kfrags = pre.generate_kfrags(delegating_privkey, receiving_pubkey, m, n, signer)
|
||||
|
||||
capsule.set_correctness_keys(delegating=delegating_pubkey,
|
||||
receiving=receiving_pubkey,
|
||||
verifying=signing_privkey.get_pubkey())
|
||||
|
||||
return kfrags, capsule
|
||||
|
||||
|
||||
def firehose(m: int=6, n: int=10) -> None:
|
||||
|
||||
print("Making kfrags...")
|
||||
kfrags, capsule = __produce_kfrags_and_capsule(m=m, n=n)
|
||||
one_kfrag, *remaining_kfrags = kfrags
|
||||
|
||||
print('Re-encrypting...')
|
||||
successful_reencryptions = 0
|
||||
for iteration in range(int(REENCRYPTIONS)):
|
||||
|
||||
_cfrag = pre.reencrypt(one_kfrag, capsule) # <<< REENCRYPTION HAPPENS HERE
|
||||
|
||||
successful_reencryptions += 1
|
||||
if iteration % 20 == 0:
|
||||
print('Performed {} Re-encryptions...'.format(iteration))
|
||||
|
||||
failure_message = "A Reencryption failed. {} of {} succeeded".format(successful_reencryptions, REENCRYPTIONS)
|
||||
assert successful_reencryptions == REENCRYPTIONS, failure_message
|
||||
print("Successfully performed {} reencryptions".format(successful_reencryptions), end='\n')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
firehose() # do
|
|
@ -1,16 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
|
@ -1,167 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from umbral import pre
|
||||
from umbral.cfrags import CapsuleFrag
|
||||
from umbral.kfrags import KFrag
|
||||
from umbral.config import default_curve
|
||||
from umbral.params import UmbralParameters
|
||||
from umbral.signing import Signer
|
||||
from umbral.keys import UmbralPrivateKey, UmbralPublicKey
|
||||
from ..conftest import parameters, other_supported_curves, kfrag_signing_modes
|
||||
|
||||
|
||||
@pytest.mark.parametrize("N, M", parameters)
|
||||
@pytest.mark.parametrize("signing_mode", kfrag_signing_modes)
|
||||
def test_lifecycle_with_serialization(N, M, signing_mode, curve=default_curve()):
|
||||
"""
|
||||
This test is a variant of test_simple_api, but with intermediate
|
||||
serialization/deserialization steps, modeling how pyUmbral artifacts
|
||||
(such as keys, ciphertexts, etc) will actually be used.
|
||||
These intermediate steps are in between the different 'usage domains'
|
||||
in NuCypher, namely, key generation, delegation, encryption, decryption by
|
||||
Alice, re-encryption by Ursula, and decryption by Bob.
|
||||
|
||||
Manually injects UmbralParameters for multi-curve testing.
|
||||
"""
|
||||
|
||||
# Convenience method to avoid replicating key generation code
|
||||
def new_keypair_bytes():
|
||||
privkey = UmbralPrivateKey.gen_key(params=params)
|
||||
return privkey.to_bytes(), privkey.get_pubkey().to_bytes()
|
||||
|
||||
## SETUP
|
||||
params = UmbralParameters(curve=curve)
|
||||
|
||||
delegating_privkey_bytes, delegating_pubkey_bytes = new_keypair_bytes()
|
||||
signing_privkey_bytes, signing_pubkey_bytes = new_keypair_bytes()
|
||||
receiving_privkey_bytes, receiving_pubkey_bytes = new_keypair_bytes()
|
||||
|
||||
## DELEGATION DOMAIN:
|
||||
## Alice delegates decryption rights to some Bob by generating a set of
|
||||
## KFrags, using her delegating private key and Bob's receiving public key
|
||||
|
||||
delegating_privkey = UmbralPrivateKey.from_bytes(delegating_privkey_bytes, params=params)
|
||||
signing_privkey = UmbralPrivateKey.from_bytes(signing_privkey_bytes, params=params)
|
||||
receiving_pubkey = UmbralPublicKey.from_bytes(receiving_pubkey_bytes, params=params)
|
||||
|
||||
signer = Signer(signing_privkey)
|
||||
|
||||
sign_delegating_key, sign_receiving_key = signing_mode
|
||||
|
||||
kfrags = pre.generate_kfrags(delegating_privkey=delegating_privkey,
|
||||
receiving_pubkey=receiving_pubkey,
|
||||
threshold=M,
|
||||
N=N,
|
||||
signer=signer,
|
||||
sign_delegating_key=sign_delegating_key,
|
||||
sign_receiving_key=sign_receiving_key)
|
||||
|
||||
kfrags_bytes = tuple(map(bytes, kfrags))
|
||||
|
||||
del kfrags
|
||||
del signer
|
||||
del delegating_privkey
|
||||
del signing_privkey
|
||||
del receiving_pubkey
|
||||
del params
|
||||
|
||||
## ENCRYPTION DOMAIN ##
|
||||
|
||||
params = UmbralParameters(curve=curve)
|
||||
|
||||
delegating_pubkey = UmbralPublicKey.from_bytes(delegating_pubkey_bytes, params)
|
||||
|
||||
plain_data = b'peace at dawn'
|
||||
ciphertext, capsule = pre.encrypt(delegating_pubkey, plain_data)
|
||||
capsule_bytes = bytes(capsule)
|
||||
|
||||
del capsule
|
||||
del delegating_pubkey
|
||||
del params
|
||||
|
||||
## DECRYPTION BY ALICE ##
|
||||
|
||||
params = UmbralParameters(curve=curve)
|
||||
|
||||
delegating_privkey = UmbralPrivateKey.from_bytes(delegating_privkey_bytes, params=params)
|
||||
capsule = pre.Capsule.from_bytes(capsule_bytes, params)
|
||||
cleartext = pre.decrypt(ciphertext, capsule, delegating_privkey)
|
||||
assert cleartext == plain_data
|
||||
|
||||
del delegating_privkey
|
||||
del capsule
|
||||
del params
|
||||
|
||||
## RE-ENCRYPTION DOMAIN (i.e., Ursula's side)
|
||||
|
||||
cfrags_bytes = list()
|
||||
for kfrag_bytes in kfrags_bytes:
|
||||
params = UmbralParameters(curve=curve)
|
||||
delegating_pubkey = UmbralPublicKey.from_bytes(delegating_pubkey_bytes, params)
|
||||
signing_pubkey = UmbralPublicKey.from_bytes(signing_pubkey_bytes, params)
|
||||
receiving_pubkey = UmbralPublicKey.from_bytes(receiving_pubkey_bytes, params)
|
||||
|
||||
capsule = pre.Capsule.from_bytes(capsule_bytes, params)
|
||||
capsule.set_correctness_keys(delegating=delegating_pubkey,
|
||||
receiving=receiving_pubkey,
|
||||
verifying=signing_pubkey)
|
||||
|
||||
# TODO: use params instead of curve?
|
||||
kfrag = KFrag.from_bytes(kfrag_bytes, params.curve)
|
||||
|
||||
assert kfrag.verify(signing_pubkey, delegating_pubkey, receiving_pubkey, params)
|
||||
|
||||
cfrag_bytes = bytes(pre.reencrypt(kfrag, capsule))
|
||||
cfrags_bytes.append(cfrag_bytes)
|
||||
|
||||
del capsule
|
||||
del kfrag
|
||||
del params
|
||||
del delegating_pubkey
|
||||
del signing_pubkey
|
||||
del receiving_pubkey
|
||||
|
||||
## DECRYPTION DOMAIN (i.e., Bob's side)
|
||||
params = UmbralParameters(curve=curve)
|
||||
|
||||
capsule = pre.Capsule.from_bytes(capsule_bytes, params)
|
||||
delegating_pubkey = UmbralPublicKey.from_bytes(delegating_pubkey_bytes, params)
|
||||
signing_pubkey = UmbralPublicKey.from_bytes(signing_pubkey_bytes, params)
|
||||
receiving_privkey = UmbralPrivateKey.from_bytes(receiving_privkey_bytes, params=params)
|
||||
receiving_pubkey = receiving_privkey.get_pubkey()
|
||||
|
||||
capsule.set_correctness_keys(delegating=delegating_pubkey,
|
||||
receiving=receiving_pubkey,
|
||||
verifying=signing_pubkey)
|
||||
|
||||
for cfrag_bytes in cfrags_bytes:
|
||||
# TODO: use params instead of curve?
|
||||
cfrag = CapsuleFrag.from_bytes(cfrag_bytes, params.curve)
|
||||
capsule.attach_cfrag(cfrag)
|
||||
|
||||
reenc_cleartext = pre.decrypt(ciphertext, capsule, receiving_privkey)
|
||||
assert reenc_cleartext == plain_data
|
||||
|
||||
|
||||
@pytest.mark.parametrize("curve", other_supported_curves)
|
||||
@pytest.mark.parametrize("N, M", parameters)
|
||||
@pytest.mark.parametrize("signing_mode", kfrag_signing_modes)
|
||||
def test_lifecycle_with_serialization_on_multiple_curves(N, M, signing_mode, curve):
|
||||
test_lifecycle_with_serialization(N, M, signing_mode, curve)
|
|
@ -1,98 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from umbral import pre
|
||||
from umbral.config import default_curve
|
||||
from umbral.params import UmbralParameters
|
||||
from umbral.signing import Signer
|
||||
from umbral.keys import UmbralPrivateKey
|
||||
from ..conftest import parameters, other_supported_curves
|
||||
|
||||
|
||||
@pytest.mark.parametrize("N, M", parameters)
|
||||
def test_simple_api(N, M, curve=default_curve()):
|
||||
"""
|
||||
This test models the main interactions between NuCypher actors (i.e., Alice,
|
||||
Bob, Data Source, and Ursulas) and artifacts (i.e., public and private keys,
|
||||
ciphertexts, capsules, KFrags, CFrags, etc).
|
||||
|
||||
The test covers all the main stages of data sharing with NuCypher:
|
||||
key generation, delegation, encryption, decryption by
|
||||
Alice, re-encryption by Ursula, and decryption by Bob.
|
||||
|
||||
Manually injects umbralparameters for multi-curve testing."""
|
||||
|
||||
# Generation of global parameters
|
||||
params = UmbralParameters(curve=curve)
|
||||
|
||||
# Key Generation (Alice)
|
||||
delegating_privkey = UmbralPrivateKey.gen_key(params=params)
|
||||
delegating_pubkey = delegating_privkey.get_pubkey()
|
||||
|
||||
signing_privkey = UmbralPrivateKey.gen_key(params=params)
|
||||
signing_pubkey = signing_privkey.get_pubkey()
|
||||
signer = Signer(signing_privkey)
|
||||
|
||||
# Key Generation (Bob)
|
||||
receiving_privkey = UmbralPrivateKey.gen_key(params=params)
|
||||
receiving_pubkey = receiving_privkey.get_pubkey()
|
||||
|
||||
# Encryption by an unnamed data source
|
||||
plain_data = b'peace at dawn'
|
||||
ciphertext, capsule = pre.encrypt(delegating_pubkey, plain_data)
|
||||
|
||||
# Decryption by Alice
|
||||
cleartext = pre.decrypt(ciphertext, capsule, delegating_privkey)
|
||||
assert cleartext == plain_data
|
||||
|
||||
# Split Re-Encryption Key Generation (aka Delegation)
|
||||
kfrags = pre.generate_kfrags(delegating_privkey, receiving_pubkey, M, N, signer)
|
||||
|
||||
|
||||
# Capsule preparation (necessary before re-encryotion and activation)
|
||||
capsule.set_correctness_keys(delegating=delegating_pubkey,
|
||||
receiving=receiving_pubkey,
|
||||
verifying=signing_pubkey)
|
||||
|
||||
# Bob requests re-encryption to some set of M ursulas
|
||||
cfrags = list()
|
||||
for kfrag in kfrags[:M]:
|
||||
# Ursula checks that the received kfrag is valid
|
||||
assert kfrag.verify(signing_pubkey, delegating_pubkey, receiving_pubkey, params)
|
||||
|
||||
# Re-encryption by an Ursula
|
||||
cfrag = pre.reencrypt(kfrag, capsule)
|
||||
|
||||
# Bob collects the result
|
||||
cfrags.append(cfrag)
|
||||
|
||||
# Capsule activation (by Bob)
|
||||
for cfrag in cfrags:
|
||||
capsule.attach_cfrag(cfrag)
|
||||
|
||||
# Decryption by Bob
|
||||
reenc_cleartext = pre.decrypt(ciphertext, capsule, receiving_privkey)
|
||||
assert reenc_cleartext == plain_data
|
||||
|
||||
|
||||
@pytest.mark.parametrize("curve", other_supported_curves)
|
||||
@pytest.mark.parametrize("N, M", parameters)
|
||||
def test_simple_api_on_multiple_curves(N, M, curve):
|
||||
test_simple_api(N, M, curve)
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.cfrags import CapsuleFrag
|
||||
from umbral.keys import UmbralPrivateKey
|
||||
from umbral.point import Point
|
||||
from umbral.pre import Capsule
|
||||
from umbral.config import default_params
|
||||
|
||||
|
||||
def test_cannot_attach_cfrag_without_keys():
|
||||
"""
|
||||
We need the proper keys to verify the correctness of CFrags
|
||||
in order to attach them to a Capsule.
|
||||
"""
|
||||
params = default_params()
|
||||
|
||||
capsule = Capsule(params,
|
||||
point_e=Point.gen_rand(),
|
||||
point_v=Point.gen_rand(),
|
||||
bn_sig=CurveBN.gen_rand())
|
||||
|
||||
cfrag = CapsuleFrag(point_e1=Point.gen_rand(),
|
||||
point_v1=Point.gen_rand(),
|
||||
kfrag_id=os.urandom(10),
|
||||
point_precursor=Point.gen_rand(),
|
||||
)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
capsule.attach_cfrag(cfrag)
|
||||
|
||||
|
||||
def test_cannot_attach_cfrag_without_proof():
|
||||
"""
|
||||
However, even when properly attaching keys, we can't attach the CFrag
|
||||
if it is unproven.
|
||||
"""
|
||||
params = default_params()
|
||||
|
||||
capsule = Capsule(params,
|
||||
point_e=Point.gen_rand(),
|
||||
point_v=Point.gen_rand(),
|
||||
bn_sig=CurveBN.gen_rand())
|
||||
|
||||
cfrag = CapsuleFrag(point_e1=Point.gen_rand(),
|
||||
point_v1=Point.gen_rand(),
|
||||
kfrag_id=os.urandom(10),
|
||||
point_precursor=Point.gen_rand(),
|
||||
)
|
||||
key_details = capsule.set_correctness_keys(
|
||||
UmbralPrivateKey.gen_key().get_pubkey(),
|
||||
UmbralPrivateKey.gen_key().get_pubkey(),
|
||||
UmbralPrivateKey.gen_key().get_pubkey())
|
||||
|
||||
delegating_details, receiving_details, verifying_details = key_details
|
||||
|
||||
assert all((delegating_details, receiving_details, verifying_details))
|
||||
|
||||
with pytest.raises(cfrag.NoProofProvided):
|
||||
capsule.attach_cfrag(cfrag)
|
||||
|
||||
|
||||
def test_cannot_set_different_keys():
|
||||
"""
|
||||
Once a key is set on a Capsule, it can't be changed to a different key.
|
||||
"""
|
||||
params = default_params()
|
||||
|
||||
capsule = Capsule(params,
|
||||
point_e=Point.gen_rand(),
|
||||
point_v=Point.gen_rand(),
|
||||
bn_sig=CurveBN.gen_rand())
|
||||
|
||||
capsule.set_correctness_keys(delegating=UmbralPrivateKey.gen_key().get_pubkey(),
|
||||
receiving=UmbralPrivateKey.gen_key().get_pubkey(),
|
||||
verifying=UmbralPrivateKey.gen_key().get_pubkey())
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
capsule.set_correctness_keys(delegating=UmbralPrivateKey.gen_key().get_pubkey())
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
capsule.set_correctness_keys(receiving=UmbralPrivateKey.gen_key().get_pubkey())
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
capsule.set_correctness_keys(verifying=UmbralPrivateKey.gen_key().get_pubkey())
|
|
@ -1,135 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from umbral import pre
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.point import Point
|
||||
from umbral.pre import Capsule
|
||||
from umbral.signing import Signer
|
||||
from umbral.keys import UmbralPrivateKey
|
||||
from umbral.config import default_params
|
||||
|
||||
|
||||
def test_capsule_creation(alices_keys):
|
||||
|
||||
params = default_params()
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
rare_capsule = Capsule(params) # Alice cannot make a capsule this way.
|
||||
|
||||
|
||||
|
||||
# Some users may create capsules their own way.
|
||||
custom_capsule = Capsule(params,
|
||||
point_e=Point.gen_rand(),
|
||||
point_v=Point.gen_rand(),
|
||||
bn_sig=CurveBN.gen_rand())
|
||||
|
||||
assert isinstance(custom_capsule, Capsule)
|
||||
|
||||
# Typical Alice, constructing a typical capsule
|
||||
delegating_privkey, _signing_key = alices_keys
|
||||
plaintext = b'peace at dawn'
|
||||
ciphertext, typical_capsule = pre.encrypt(delegating_privkey.get_pubkey(), plaintext)
|
||||
|
||||
assert isinstance(typical_capsule, Capsule)
|
||||
|
||||
|
||||
def test_capsule_equality():
|
||||
params = default_params()
|
||||
|
||||
one_capsule = Capsule(params,
|
||||
point_e=Point.gen_rand(),
|
||||
point_v=Point.gen_rand(),
|
||||
bn_sig=CurveBN.gen_rand())
|
||||
|
||||
another_capsule = Capsule(params,
|
||||
point_e=Point.gen_rand(),
|
||||
point_v=Point.gen_rand(),
|
||||
bn_sig=CurveBN.gen_rand())
|
||||
|
||||
assert one_capsule != another_capsule
|
||||
|
||||
|
||||
def test_decapsulation_by_alice(alices_keys):
|
||||
params = default_params()
|
||||
|
||||
delegating_privkey, _signing_privkey = alices_keys
|
||||
|
||||
sym_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey())
|
||||
assert len(sym_key) == 32
|
||||
|
||||
# The symmetric key sym_key is perhaps used for block cipher here in a real-world scenario.
|
||||
sym_key_2 = pre._decapsulate_original(delegating_privkey, capsule)
|
||||
assert sym_key_2 == sym_key
|
||||
|
||||
|
||||
def test_bad_capsule_fails_reencryption(kfrags):
|
||||
params = default_params()
|
||||
|
||||
bollocks_capsule = Capsule(params,
|
||||
point_e=Point.gen_rand(),
|
||||
point_v=Point.gen_rand(),
|
||||
bn_sig=CurveBN.gen_rand())
|
||||
|
||||
for kfrag in kfrags:
|
||||
with pytest.raises(Capsule.NotValid):
|
||||
pre.reencrypt(kfrag, bollocks_capsule)
|
||||
|
||||
|
||||
def test_capsule_as_dict_key(alices_keys, bobs_keys):
|
||||
delegating_privkey, signing_privkey = alices_keys
|
||||
signer_alice = Signer(signing_privkey)
|
||||
delegating_pubkey = delegating_privkey.get_pubkey()
|
||||
signing_pubkey = signing_privkey.get_pubkey()
|
||||
|
||||
receiving_privkey, receiving_pubkey = bobs_keys
|
||||
|
||||
plain_data = b'peace at dawn'
|
||||
ciphertext, capsule = pre.encrypt(delegating_pubkey, plain_data)
|
||||
|
||||
# We can use the capsule as a key, and successfully lookup using it.
|
||||
some_dict = {capsule: "Thing that Bob wants to try per-Capsule"}
|
||||
assert some_dict[capsule] == "Thing that Bob wants to try per-Capsule"
|
||||
|
||||
# And if we change the value for this key, all is still well.
|
||||
some_dict[capsule] = "Bob has changed his mind."
|
||||
assert some_dict[capsule] == "Bob has changed his mind."
|
||||
assert len(some_dict.keys()) == 1
|
||||
|
||||
|
||||
def test_capsule_length(prepared_capsule, kfrags):
|
||||
capsule = prepared_capsule
|
||||
|
||||
for counter, kfrag in enumerate(kfrags):
|
||||
assert len(capsule) == counter
|
||||
cfrag = pre.reencrypt(kfrag, capsule)
|
||||
capsule.attach_cfrag(cfrag)
|
||||
|
||||
|
||||
def test_capsule_clear(prepared_capsule, kfrags):
|
||||
capsule = prepared_capsule
|
||||
|
||||
for counter, kfrag in enumerate(kfrags):
|
||||
assert len(capsule) == counter
|
||||
cfrag = pre.reencrypt(kfrag, capsule)
|
||||
capsule.attach_cfrag(cfrag)
|
||||
|
||||
capsule.clear_cfrags()
|
||||
assert len(capsule) == 0
|
|
@ -1,63 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from umbral.pre import Capsule
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.point import Point
|
||||
|
||||
|
||||
def test_capsule_serialization(capsule: Capsule):
|
||||
params = capsule.params
|
||||
capsule_bytes = capsule.to_bytes()
|
||||
capsule_bytes_casted = bytes(capsule)
|
||||
assert capsule_bytes == capsule_bytes_casted
|
||||
|
||||
# A Capsule can be represented as the 98 total bytes of two Points (33 each) and a CurveBN (32).
|
||||
assert len(capsule_bytes) == Capsule.expected_bytes_length()
|
||||
|
||||
new_capsule = Capsule.from_bytes(capsule_bytes, params)
|
||||
|
||||
# Three ways to think about equality.
|
||||
# First, the public approach for the Capsule. Simply:
|
||||
assert new_capsule == capsule
|
||||
|
||||
# Second, we show that the original components (which is all we have here since we haven't activated) are the same:
|
||||
assert new_capsule.components() == capsule.components()
|
||||
|
||||
# Third, we can directly compare the private original component attributes
|
||||
# (though this is not a supported approach):
|
||||
assert new_capsule.point_e == capsule.point_e
|
||||
assert new_capsule.point_v == capsule.point_v
|
||||
assert new_capsule.bn_sig == capsule.bn_sig
|
||||
|
||||
|
||||
def test_cannot_create_capsule_from_bogus_material(alices_keys):
|
||||
params = alices_keys[0].params
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
_capsule_of_questionable_parentage = Capsule(params,
|
||||
point_e=Point.gen_rand(),
|
||||
point_v=42,
|
||||
bn_sig=CurveBN.gen_rand())
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
_capsule_of_questionable_parentage = Capsule(params,
|
||||
point_e=Point.gen_rand(),
|
||||
point_v=Point.gen_rand(),
|
||||
bn_sig=42)
|
|
@ -1,126 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from umbral import pre
|
||||
from umbral.cfrags import CapsuleFrag, CorrectnessProof
|
||||
|
||||
|
||||
def test_cfrag_serialization_with_proof_and_metadata(prepared_capsule, kfrags):
|
||||
|
||||
# Example of potential metadata to describe the re-encryption request
|
||||
metadata = b'This is an example of metadata for re-encryption request'
|
||||
for kfrag in kfrags:
|
||||
cfrag = pre.reencrypt(kfrag, prepared_capsule, provide_proof=True, metadata=metadata)
|
||||
cfrag_bytes = cfrag.to_bytes()
|
||||
|
||||
proof = cfrag.proof
|
||||
assert proof is not None
|
||||
assert proof.metadata is not None
|
||||
|
||||
new_cfrag = CapsuleFrag.from_bytes(cfrag_bytes)
|
||||
assert new_cfrag.point_e1 == cfrag.point_e1
|
||||
assert new_cfrag.point_v1 == cfrag.point_v1
|
||||
assert new_cfrag.kfrag_id == cfrag.kfrag_id
|
||||
assert new_cfrag.point_precursor == cfrag.point_precursor
|
||||
|
||||
new_proof = new_cfrag.proof
|
||||
assert new_proof is not None
|
||||
assert new_proof.point_e2 == proof.point_e2
|
||||
assert new_proof.point_v2 == proof.point_v2
|
||||
assert new_proof.point_kfrag_commitment == proof.point_kfrag_commitment
|
||||
assert new_proof.point_kfrag_pok == proof.point_kfrag_pok
|
||||
assert new_proof.bn_sig == proof.bn_sig
|
||||
assert new_proof.metadata == metadata
|
||||
assert new_proof.metadata == proof.metadata
|
||||
|
||||
|
||||
def test_cfrag_serialization_with_proof_but_no_metadata(prepared_capsule, kfrags):
|
||||
|
||||
for kfrag in kfrags:
|
||||
cfrag = pre.reencrypt(kfrag, prepared_capsule, provide_proof=True)
|
||||
cfrag_bytes = cfrag.to_bytes()
|
||||
|
||||
proof = cfrag.proof
|
||||
assert proof is not None
|
||||
assert proof.metadata is None
|
||||
|
||||
# A CFrag can be represented as the 131 total bytes of three Points (33 each) and a CurveBN (32).
|
||||
# TODO: Figure out final size for CFrags with proofs
|
||||
# assert len(cfrag_bytes) == 33 + 33 + 33 + 32 == 131
|
||||
|
||||
new_cfrag = CapsuleFrag.from_bytes(cfrag_bytes)
|
||||
assert new_cfrag.point_e1 == cfrag.point_e1
|
||||
assert new_cfrag.point_v1 == cfrag.point_v1
|
||||
assert new_cfrag.kfrag_id == cfrag.kfrag_id
|
||||
assert new_cfrag.point_precursor == cfrag.point_precursor
|
||||
|
||||
new_proof = new_cfrag.proof
|
||||
assert new_proof is not None
|
||||
assert new_proof.point_e2 == proof.point_e2
|
||||
assert new_proof.point_v2 == proof.point_v2
|
||||
assert new_proof.point_kfrag_commitment == proof.point_kfrag_commitment
|
||||
assert new_proof.point_kfrag_pok == proof.point_kfrag_pok
|
||||
assert new_proof.bn_sig == proof.bn_sig
|
||||
assert new_proof.metadata is None
|
||||
|
||||
|
||||
def test_cfrag_serialization_no_proof_no_metadata(prepared_capsule, kfrags):
|
||||
|
||||
for kfrag in kfrags:
|
||||
cfrag = pre.reencrypt(kfrag, prepared_capsule, provide_proof=False)
|
||||
cfrag_bytes = cfrag.to_bytes()
|
||||
|
||||
proof = cfrag.proof
|
||||
assert proof is None
|
||||
|
||||
assert len(cfrag_bytes) == CapsuleFrag.expected_bytes_length()
|
||||
|
||||
new_cfrag = CapsuleFrag.from_bytes(cfrag_bytes)
|
||||
assert new_cfrag.point_e1 == cfrag.point_e1
|
||||
assert new_cfrag.point_v1 == cfrag.point_v1
|
||||
assert new_cfrag.kfrag_id == cfrag.kfrag_id
|
||||
assert new_cfrag.point_precursor == cfrag.point_precursor
|
||||
|
||||
new_proof = new_cfrag.proof
|
||||
assert new_proof is None
|
||||
|
||||
|
||||
def test_correctness_proof_serialization(prepared_capsule, kfrags):
|
||||
|
||||
# Example of potential metadata to describe the re-encryption request
|
||||
metadata = b"This is an example of metadata for re-encryption request"
|
||||
|
||||
for kfrag in kfrags:
|
||||
cfrag = pre.reencrypt(kfrag, prepared_capsule, metadata=metadata)
|
||||
proof = cfrag.proof
|
||||
proof_bytes = proof.to_bytes()
|
||||
|
||||
# A CorrectnessProof can be represented as
|
||||
# the 228 total bytes of four Points (33 each) and three BigNums (32 each).
|
||||
# TODO: Figure out final size for CorrectnessProofs
|
||||
# assert len(proof_bytes) == (33 * 4) + (32 * 3) == 228
|
||||
|
||||
new_proof = CorrectnessProof.from_bytes(proof_bytes)
|
||||
assert new_proof.point_e2 == proof.point_e2
|
||||
assert new_proof.point_v2 == proof.point_v2
|
||||
assert new_proof.point_kfrag_commitment == proof.point_kfrag_commitment
|
||||
assert new_proof.point_kfrag_pok == proof.point_kfrag_pok
|
||||
assert new_proof.bn_sig == proof.bn_sig
|
||||
assert new_proof.kfrag_signature == proof.kfrag_signature
|
||||
assert new_proof.metadata == proof.metadata
|
||||
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import importlib
|
||||
import pytest
|
||||
import warnings
|
||||
|
||||
from umbral.config import _CONFIG
|
||||
from umbral.curve import SECP256K1, SECP256R1
|
||||
|
||||
|
||||
def _copy_config_for_testing():
|
||||
"""
|
||||
NEVER do this. This is for testing only.
|
||||
This is absolutely not a thing to actually do in production code. At all. Ever.
|
||||
"""
|
||||
config_module_spec = importlib.util.find_spec("umbral.config")
|
||||
config_copy = importlib.util.module_from_spec(config_module_spec)
|
||||
config_module_spec.loader.exec_module(config_copy)
|
||||
assert hasattr(config_copy, "default_curve")
|
||||
assert config_copy is not _CONFIG
|
||||
return config_copy
|
||||
|
||||
|
||||
def test_try_to_use_curve_with_no_default_curve():
|
||||
config = _copy_config_for_testing()
|
||||
|
||||
# No curve is set.
|
||||
assert config._CONFIG._CONFIG__curve is None
|
||||
|
||||
# Getting the default curve if we haven't set one yet sets one and gives us a warning.
|
||||
with warnings.catch_warnings(record=True) as caught_warnings:
|
||||
assert len(caught_warnings) == 0
|
||||
config.default_curve()
|
||||
assert len(caught_warnings) == 1
|
||||
assert caught_warnings[0].message.args[0] == config._CONFIG._CONFIG__WARNING_IF_NO_DEFAULT_SET
|
||||
assert caught_warnings[0].category == RuntimeWarning
|
||||
|
||||
# Now, a default curve has been set.
|
||||
assert config._CONFIG._CONFIG__curve == SECP256K1
|
||||
|
||||
|
||||
def test_try_to_use_default_params_with_no_default_curve():
|
||||
config = _copy_config_for_testing()
|
||||
|
||||
# Again, no curve is set.
|
||||
assert config._CONFIG._CONFIG__curve is None
|
||||
|
||||
# This time, we'll try to use default_params() and get the same warning as above.
|
||||
with warnings.catch_warnings(record=True) as caught_warnings:
|
||||
assert len(caught_warnings) == 0
|
||||
config.default_params()
|
||||
assert len(caught_warnings) == 1
|
||||
assert caught_warnings[0].message.args[0] == config._CONFIG._CONFIG__WARNING_IF_NO_DEFAULT_SET
|
||||
assert caught_warnings[0].category == RuntimeWarning
|
||||
|
||||
# Now, a default curve has been set.
|
||||
assert config._CONFIG._CONFIG__curve == SECP256K1
|
||||
|
||||
|
||||
def test_cannot_set_default_curve_twice():
|
||||
config = _copy_config_for_testing()
|
||||
|
||||
# pyumbral even supports NIST curves!
|
||||
config.set_default_curve(SECP256R1)
|
||||
|
||||
# Our default curve has been set...
|
||||
assert config.default_curve() == SECP256R1
|
||||
|
||||
# ...but once set, you can't set the default curve again, even if you've found a better one.
|
||||
with pytest.raises(config._CONFIG.UmbralConfigurationError):
|
||||
config.set_default_curve(SECP256K1)
|
|
@ -1,82 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from umbral.curve import Curve
|
||||
|
||||
|
||||
def test_supported_curves():
|
||||
|
||||
# Ensure we have the correct number opf supported curves hardcoded
|
||||
number_of_supported_curves = 3
|
||||
assert len(Curve._supported_curves) == number_of_supported_curves
|
||||
|
||||
# Manually ensure the `_supported curves` dict contains only valid supported curves
|
||||
assert Curve._supported_curves[415] == 'secp256r1'
|
||||
assert Curve._supported_curves[714] == 'secp256k1'
|
||||
assert Curve._supported_curves[715] == 'secp384r1'
|
||||
|
||||
nid, name = 714, 'secp256k1'
|
||||
|
||||
#
|
||||
# Create by NID
|
||||
#
|
||||
|
||||
# supported
|
||||
_curve_714 = Curve(nid=nid)
|
||||
assert _curve_714.curve_nid == nid
|
||||
assert _curve_714.name == name
|
||||
|
||||
# unsuported
|
||||
with pytest.raises(NotImplementedError):
|
||||
_ = Curve(711)
|
||||
|
||||
|
||||
#
|
||||
# Create by Name
|
||||
#
|
||||
|
||||
# Supported
|
||||
_curve_secp256k1 = Curve.from_name(name)
|
||||
assert _curve_secp256k1.name == name
|
||||
assert _curve_secp256k1.curve_nid == nid
|
||||
|
||||
# Unsupported
|
||||
with pytest.raises(NotImplementedError):
|
||||
_ = Curve.from_name('abcd123e4')
|
||||
|
||||
# Import curve constants
|
||||
from umbral.curve import SECP256R1, SECP256K1, SECP384R1
|
||||
test_p256 = SECP256R1
|
||||
test_secp256k1 = SECP256K1
|
||||
test_p384 = SECP384R1
|
||||
|
||||
# Test the hardcoded curve NIDs are correct:
|
||||
assert test_p256.curve_nid == 415
|
||||
assert test_secp256k1.curve_nid == 714
|
||||
assert test_p384.curve_nid == 715
|
||||
|
||||
# Ensure every curve constant is in the CURVES collection
|
||||
from umbral.curve import CURVES
|
||||
assert len(CURVES) == number_of_supported_curves
|
||||
|
||||
# Ensure all supported curves can be initialized
|
||||
for nid, name in Curve._supported_curves.items():
|
||||
_curve_nid, _curve_name = Curve(nid=nid), Curve.from_name(name)
|
||||
assert _curve_nid.name == name
|
||||
assert _curve_name.curve_nid == nid
|
|
@ -1,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/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import os
|
||||
|
||||
from umbral.dem import UmbralDEM, DEM_KEYSIZE, DEM_NONCE_SIZE
|
||||
from cryptography.exceptions import InvalidTag
|
||||
|
||||
|
||||
def test_encrypt_decrypt():
|
||||
key = os.urandom(32)
|
||||
|
||||
dem = UmbralDEM(key)
|
||||
|
||||
plaintext = b'peace at dawn'
|
||||
|
||||
ciphertext0 = dem.encrypt(plaintext)
|
||||
ciphertext1 = dem.encrypt(plaintext)
|
||||
|
||||
assert ciphertext0 != plaintext
|
||||
assert ciphertext1 != plaintext
|
||||
|
||||
# Ciphertext should be different even with same plaintext.
|
||||
assert ciphertext0 != ciphertext1
|
||||
|
||||
# Nonce should be different
|
||||
assert ciphertext0[:DEM_NONCE_SIZE] != ciphertext1[:DEM_NONCE_SIZE]
|
||||
|
||||
cleartext0 = dem.decrypt(ciphertext0)
|
||||
cleartext1 = dem.decrypt(ciphertext1)
|
||||
|
||||
assert cleartext0 == plaintext
|
||||
assert cleartext1 == plaintext
|
||||
|
||||
|
||||
def test_encrypt_decrypt_associated_data():
|
||||
key = os.urandom(32)
|
||||
aad = b'secret code 1234'
|
||||
|
||||
dem = UmbralDEM(key)
|
||||
|
||||
plaintext = b'peace at dawn'
|
||||
|
||||
ciphertext0 = dem.encrypt(plaintext, authenticated_data=aad)
|
||||
ciphertext1 = dem.encrypt(plaintext, authenticated_data=aad)
|
||||
|
||||
assert ciphertext0 != plaintext
|
||||
assert ciphertext1 != plaintext
|
||||
|
||||
assert ciphertext0 != ciphertext1
|
||||
|
||||
assert ciphertext0[:DEM_NONCE_SIZE] != ciphertext1[:DEM_NONCE_SIZE]
|
||||
|
||||
cleartext0 = dem.decrypt(ciphertext0, authenticated_data=aad)
|
||||
cleartext1 = dem.decrypt(ciphertext1, authenticated_data=aad)
|
||||
|
||||
assert cleartext0 == plaintext
|
||||
assert cleartext1 == plaintext
|
||||
|
||||
# Attempt decryption with invalid associated data
|
||||
with pytest.raises(InvalidTag):
|
||||
cleartext2 = dem.decrypt(ciphertext0, authenticated_data=b'wrong data')
|
|
@ -1,67 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from umbral.kfrags import KFrag
|
||||
|
||||
|
||||
def test_kfrag_serialization(alices_keys, bobs_keys, kfrags):
|
||||
|
||||
delegating_privkey, signing_privkey = alices_keys
|
||||
_receiving_privkey, receiving_pubkey = bobs_keys
|
||||
|
||||
for kfrag in kfrags:
|
||||
kfrag_bytes = kfrag.to_bytes()
|
||||
assert len(kfrag_bytes) == KFrag.expected_bytes_length()
|
||||
|
||||
new_kfrag = KFrag.from_bytes(kfrag_bytes)
|
||||
assert new_kfrag.id == kfrag.id
|
||||
assert new_kfrag.bn_key == kfrag.bn_key
|
||||
assert new_kfrag.point_precursor == kfrag.point_precursor
|
||||
assert new_kfrag.point_commitment == kfrag.point_commitment
|
||||
assert new_kfrag.keys_in_signature == kfrag.keys_in_signature
|
||||
assert new_kfrag.signature_for_proxy == kfrag.signature_for_proxy
|
||||
assert new_kfrag.signature_for_bob == kfrag.signature_for_bob
|
||||
|
||||
assert new_kfrag.verify(signing_pubkey=signing_privkey.get_pubkey(),
|
||||
delegating_pubkey=delegating_privkey.get_pubkey(),
|
||||
receiving_pubkey=receiving_pubkey)
|
||||
|
||||
assert new_kfrag == kfrag
|
||||
|
||||
|
||||
def test_kfrag_verify_for_capsule(prepared_capsule, kfrags):
|
||||
for kfrag in kfrags:
|
||||
assert kfrag.verify_for_capsule(prepared_capsule)
|
||||
|
||||
# If we alter some element, the verification fails
|
||||
previous_id, kfrag.id = kfrag.id, bytes(32)
|
||||
assert not kfrag.verify_for_capsule(prepared_capsule)
|
||||
|
||||
# Let's restore the KFrag, and alter the re-encryption key instead
|
||||
kfrag.id = previous_id
|
||||
kfrag.bn_key += kfrag.bn_key
|
||||
assert not kfrag.verify_for_capsule(prepared_capsule)
|
||||
|
||||
|
||||
def test_kfrag_as_dict_key(kfrags):
|
||||
dict_with_kfrags_as_keys = dict()
|
||||
dict_with_kfrags_as_keys[kfrags[0]] = "Some llamas. Definitely some llamas."
|
||||
dict_with_kfrags_as_keys[kfrags[1]] = "No llamas here. Definitely not."
|
||||
|
||||
assert dict_with_kfrags_as_keys[kfrags[0]] != dict_with_kfrags_as_keys[kfrags[1]]
|
||||
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
|
||||
import pytest
|
||||
from cryptography.hazmat.backends.openssl import backend
|
||||
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.point import Point
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def mock_openssl(mocker, random_ec_point1: Point, random_ec_curvebn1: CurveBN, random_ec_curvebn2: CurveBN):
|
||||
"""
|
||||
Patches openssl backend methods for testing.
|
||||
For all functions, 1 is returned for success, 0 on error.
|
||||
|
||||
"""
|
||||
|
||||
actual_backend = {
|
||||
# Point
|
||||
'EC_POINT_mul': backend._lib.EC_POINT_mul,
|
||||
'EC_POINT_cmp': backend._lib.EC_POINT_cmp,
|
||||
'EC_POINT_add': backend._lib.EC_POINT_add,
|
||||
'EC_POINT_invert': backend._lib.EC_POINT_invert,
|
||||
|
||||
# Bignum
|
||||
'BN_cmp': backend._lib.BN_cmp,
|
||||
'BN_mod_exp': backend._lib.BN_mod_exp,
|
||||
'BN_mod_mul': backend._lib.BN_mod_mul,
|
||||
'BN_mod_inverse': backend._lib.BN_mod_inverse,
|
||||
'BN_mod_add': backend._lib.BN_mod_add,
|
||||
'BN_mod_sub': backend._lib.BN_mod_sub,
|
||||
'BN_nnmod': backend._lib.BN_nnmod,
|
||||
}
|
||||
|
||||
def check_curvebn_ctypes(*curvebns):
|
||||
for bn in curvebns:
|
||||
assert 'BIGNUM' in str(bn)
|
||||
assert bn.__class__.__name__ == 'CDataGCP'
|
||||
|
||||
def check_point_ctypes(*ec_points):
|
||||
for point in ec_points:
|
||||
assert 'EC_POINT' in str(point)
|
||||
assert point.__class__.__name__ == 'CDataGCP'
|
||||
|
||||
@contextlib.contextmanager
|
||||
def mocked_openssl_backend():
|
||||
def mocked_ec_point_equality(group, ec_point, other_point, context):
|
||||
check_point_ctypes(ec_point, other_point)
|
||||
assert 'BN_CTX' in str(context)
|
||||
assert 'EC_GROUP' in str(group)
|
||||
assert random_ec_point1.curve.ec_group == group
|
||||
assert not bool(actual_backend['EC_POINT_cmp'](group, random_ec_point1.ec_point, ec_point, context))
|
||||
result = actual_backend['EC_POINT_cmp'](group, random_ec_point1.ec_point, other_point, context)
|
||||
assert not bool(result)
|
||||
return result
|
||||
|
||||
def mocked_ec_point_addition(group, sum, ec_point, other_point, context):
|
||||
check_point_ctypes(sum, other_point)
|
||||
assert 'BN_CTX' in str(context)
|
||||
assert random_ec_point1.group == group
|
||||
assert not bool(actual_backend['EC_POINT_cmp'](group, random_ec_point1.ec_point, ec_point, context))
|
||||
return actual_backend['EC_POINT_add'](group, sum, ec_point, other_point, context)
|
||||
|
||||
def mocked_ec_point_multiplication(group, product, null, ec_point, curvebn, context):
|
||||
check_point_ctypes(ec_point, product)
|
||||
assert 'BN_CTX' in str(context)
|
||||
assert 'EC_GROUP' in str(group)
|
||||
assert 'NULL' in str(null)
|
||||
assert random_ec_point1.group == group
|
||||
assert random_ec_curvebn1.curve_nid == random_ec_point1.curve_nid
|
||||
assert not bool(actual_backend['EC_POINT_cmp'](group, random_ec_point1.ec_point, ec_point, context))
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
|
||||
return actual_backend['EC_POINT_mul'](group, product, null, ec_point, curvebn, context)
|
||||
|
||||
def mocked_ec_point_inversion(group, inverse, context):
|
||||
check_point_ctypes(inverse)
|
||||
assert 'BN_CTX' in str(context)
|
||||
assert random_ec_point1.group == group
|
||||
return actual_backend['EC_POINT_invert'](group, inverse, context)
|
||||
|
||||
def mocked_bn_compare(curvebn, other):
|
||||
check_curvebn_ctypes(curvebn, other)
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
|
||||
return actual_backend['BN_cmp'](curvebn, other)
|
||||
|
||||
def mocked_bn_mod_exponent(power, curvebn, other, order, context):
|
||||
check_curvebn_ctypes(curvebn, other, power, order)
|
||||
assert 'BN_CTX' in str(context)
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn2.bignum, other))
|
||||
return actual_backend['BN_mod_exp'](power, curvebn, other, order, context)
|
||||
|
||||
def mocked_bn_mod_multiplication(product, curvebn, other, order, context):
|
||||
check_curvebn_ctypes(curvebn, other, product, order)
|
||||
assert 'BN_CTX' in str(context)
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn2.bignum, other))
|
||||
return actual_backend['BN_mod_mul'](product, curvebn, other, order, context)
|
||||
|
||||
def mocked_bn_inverse(null, curvebn, order, context):
|
||||
check_curvebn_ctypes(curvebn, order)
|
||||
assert 'BN_CTX' in str(context)
|
||||
assert 'NULL' in str(null)
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
|
||||
return actual_backend['BN_mod_inverse'](null, curvebn, order, context)
|
||||
|
||||
def mocked_bn_addition(sum, curvebn, other, order, context):
|
||||
check_curvebn_ctypes(curvebn, other, sum, order)
|
||||
assert 'BN_CTX' in str(context)
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn2.bignum, other))
|
||||
return actual_backend['BN_mod_add'](sum, curvebn, other, order, context)
|
||||
|
||||
def mocked_bn_subtraction(diff, curvebn, other, order, context):
|
||||
check_curvebn_ctypes(curvebn, other, diff, order)
|
||||
assert 'BN_CTX' in str(context)
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn2.bignum, other))
|
||||
return actual_backend['BN_mod_sub'](diff, curvebn, other, order, context)
|
||||
|
||||
def mocked_bn_nmodulus(rem, curvebn, other, context):
|
||||
check_curvebn_ctypes(curvebn, other, rem)
|
||||
assert 'BN_CTX' in str(context)
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn1.bignum, curvebn))
|
||||
assert not bool(actual_backend['BN_cmp'](random_ec_curvebn2.bignum, other))
|
||||
return actual_backend['BN_nnmod'](rem, curvebn, other, context)
|
||||
|
||||
mock_load = {
|
||||
# Point
|
||||
'EC_POINT_mul': mocked_ec_point_multiplication,
|
||||
'EC_POINT_cmp': mocked_ec_point_equality,
|
||||
'EC_POINT_add': mocked_ec_point_addition,
|
||||
'EC_POINT_invert': mocked_ec_point_inversion,
|
||||
|
||||
# Bignum
|
||||
'BN_cmp': mocked_bn_compare,
|
||||
'BN_mod_exp': mocked_bn_mod_exponent,
|
||||
'BN_mod_mul': mocked_bn_mod_multiplication,
|
||||
'BN_mod_inverse': mocked_bn_inverse,
|
||||
'BN_mod_add': mocked_bn_addition,
|
||||
'BN_mod_sub': mocked_bn_subtraction,
|
||||
'BN_nnmod': mocked_bn_nmodulus,
|
||||
}
|
||||
|
||||
with contextlib.ExitStack() as stack:
|
||||
for method, patch in mock_load.items():
|
||||
stack.enter_context(mocker.mock_module.patch.object(backend._lib, method, patch))
|
||||
yield
|
||||
|
||||
return mocked_openssl_backend
|
|
@ -1,64 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from cryptography.hazmat.backends.openssl import backend
|
||||
from umbral.curvebn import CurveBN
|
||||
|
||||
|
||||
def test_mocked_openssl_curvebn_arithmetic(mock_openssl, random_ec_curvebn1, random_ec_curvebn2):
|
||||
|
||||
operations_that_construct = (
|
||||
random_ec_curvebn1 * random_ec_curvebn2, # __mul__
|
||||
random_ec_curvebn1 ** random_ec_curvebn2, # __pow__
|
||||
random_ec_curvebn1 ** int(random_ec_curvebn2), # __pow__ (as int)
|
||||
random_ec_curvebn1 + random_ec_curvebn2, # __add__
|
||||
random_ec_curvebn1 - random_ec_curvebn2, # __sub__
|
||||
-random_ec_curvebn1, # __neg__
|
||||
random_ec_curvebn1 % random_ec_curvebn2, # __mod__
|
||||
random_ec_curvebn1 % int(random_ec_curvebn2), # __mod__ (as int)
|
||||
~random_ec_curvebn1, # __invert__
|
||||
random_ec_curvebn1 / random_ec_curvebn2 # __truediv__
|
||||
)
|
||||
|
||||
with mock_openssl():
|
||||
assert random_ec_curvebn1 == random_ec_curvebn1 # __eq__
|
||||
for operator_result in operations_that_construct:
|
||||
assert operator_result
|
||||
assert isinstance(operator_result, CurveBN)
|
||||
|
||||
order = backend._bn_to_int(random_ec_curvebn1.curve.order)
|
||||
random_ec_curvebn1 = int(random_ec_curvebn1)
|
||||
random_ec_curvebn2 = int(random_ec_curvebn2)
|
||||
|
||||
# For simplicity, we test these two cases separately
|
||||
assert (int(operations_that_construct[-2]) * random_ec_curvebn1) % order == 1
|
||||
assert (int(operations_that_construct[-1]) * random_ec_curvebn2) % order == random_ec_curvebn1
|
||||
|
||||
# The remaining cases can be tested in bulk
|
||||
expected_results = (
|
||||
(random_ec_curvebn1 * random_ec_curvebn2) % order, # __mul__
|
||||
pow(random_ec_curvebn1, random_ec_curvebn2, order), # __pow__
|
||||
pow(random_ec_curvebn1, random_ec_curvebn2, order), # __pow__ (as int)
|
||||
(random_ec_curvebn1 + random_ec_curvebn2) % order, # __add__
|
||||
(random_ec_curvebn1 - random_ec_curvebn2) % order, # __sub__
|
||||
(-random_ec_curvebn1) % order, # __neg__
|
||||
random_ec_curvebn1 % random_ec_curvebn2, # __mod__
|
||||
random_ec_curvebn1 % int(random_ec_curvebn2), # __mod__ (as int)
|
||||
)
|
||||
|
||||
for (result, expected) in zip(operations_that_construct[:-2], expected_results):
|
||||
assert result == expected
|
|
@ -1,39 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.random_oracles import hash_to_curvebn
|
||||
import pytest
|
||||
|
||||
|
||||
def test_cast_curvebn_to_int():
|
||||
x = CurveBN.gen_rand()
|
||||
|
||||
x_as_int_from_dunder = x.__int__()
|
||||
x_as_int_type_caster = int(x)
|
||||
assert x_as_int_from_dunder == x_as_int_type_caster
|
||||
x = x_as_int_type_caster
|
||||
|
||||
y = CurveBN.from_int(x)
|
||||
assert x == y
|
||||
|
||||
|
||||
def test_cant_hash_arbitrary_object_into_bignum():
|
||||
whatever = object()
|
||||
with pytest.raises(TypeError):
|
||||
hash_to_curvebn(whatever)
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.curve import CURVES
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
@pytest.mark.parametrize("curve", CURVES)
|
||||
def test_serialization_rotations_of_1(curve):
|
||||
|
||||
size_in_bytes = CurveBN.expected_bytes_length(curve)
|
||||
for i in range(size_in_bytes):
|
||||
lonely_one = 1 << i
|
||||
bn = CurveBN.from_int(lonely_one, curve)
|
||||
lonely_one_in_bytes = lonely_one.to_bytes(size_in_bytes, 'big')
|
||||
|
||||
# Check serialization
|
||||
assert bn.to_bytes() == lonely_one_in_bytes
|
||||
|
||||
# Check deserialization
|
||||
assert CurveBN.from_bytes(lonely_one_in_bytes, curve) == bn
|
||||
|
||||
@pytest.mark.parametrize("curve", CURVES)
|
||||
def test_invalid_deserialization(curve):
|
||||
size_in_bytes = CurveBN.expected_bytes_length(curve)
|
||||
|
||||
# All-zeros bytestring are invalid (i.e., 0 < bn < order of the curve)
|
||||
zero_bytes = bytes(size_in_bytes)
|
||||
with pytest.raises(ValueError):
|
||||
_bn = CurveBN.from_bytes(zero_bytes, curve)
|
||||
|
||||
# All-ones bytestring is invalid too (since it's greater than order)
|
||||
lots_of_ones = 2**(8*size_in_bytes) - 1
|
||||
lots_of_ones = lots_of_ones.to_bytes(size_in_bytes, 'big')
|
||||
with pytest.raises(ValueError):
|
||||
_bn = CurveBN.from_bytes(lots_of_ones, curve)
|
||||
|
||||
# Serialization of `order` is invalid since it's not strictly lower than
|
||||
# the order of the curve
|
||||
order = default_backend()._bn_to_int(curve.order)
|
||||
with pytest.raises(ValueError):
|
||||
_bn = CurveBN.from_bytes(order.to_bytes(size_in_bytes, 'big'), curve)
|
||||
|
||||
# On the other hand, serialization of `order - 1` is valid
|
||||
order -= 1
|
||||
_bn = CurveBN.from_bytes(order.to_bytes(size_in_bytes, 'big'), curve)
|
|
@ -1,59 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.point import Point
|
||||
|
||||
|
||||
def test_mocked_openssl_point_arithmetic(mock_openssl, random_ec_point1, random_ec_point2, random_ec_curvebn1):
|
||||
|
||||
operations_that_construct = (
|
||||
random_ec_point1 * random_ec_curvebn1, # __mul__
|
||||
random_ec_point1 + random_ec_point2, # __add__
|
||||
random_ec_point1 - random_ec_point2, # __sub__
|
||||
-random_ec_point1 # __neg__
|
||||
)
|
||||
|
||||
with mock_openssl():
|
||||
assert random_ec_point1 == random_ec_point1 # __eq__
|
||||
for operator_result in operations_that_construct:
|
||||
assert operator_result
|
||||
assert isinstance(operator_result, Point)
|
||||
|
||||
|
||||
def test_point_curve_multiplication_regression():
|
||||
k256_point_bytes = b'\x03\xe0{\x1bQ\xbf@\x1f\x95\x8d\xe1\x17\xa7\xbe\x9e-G`T\xbf\xd7\x9e\xa7\x10\xc8uA\xc0z$\xc0\x92\x8a'
|
||||
k256_bn_bytes = b'4u\xd70-\xa0h\xdeG\xf0\x143\x06!\x91\x05{\xe4jC\n\xf1h\xed7a\xf8\x9d\xec^\x19\x8c'
|
||||
|
||||
k256_point = Point.from_bytes(k256_point_bytes)
|
||||
k256_bn = CurveBN.from_bytes(k256_bn_bytes)
|
||||
|
||||
product_with_star_operator = k256_point * k256_bn
|
||||
|
||||
# Make sure we have instantiated a new, unequal point in the same curve and group
|
||||
assert isinstance(product_with_star_operator, Point), "Point.__mul__ did not return a point instance"
|
||||
assert k256_point != product_with_star_operator
|
||||
assert k256_point.curve == product_with_star_operator.curve
|
||||
|
||||
product_bytes = b'\x03\xc9\xda\xa2\x88\xe2\xa0+\xb1N\xb6\xe6\x1c\xa5(\xe6\xe0p\xf6\xf4\xa9\xfc\xb1\xfaUV\xd3\xb3\x0e4\x94\xbe\x12'
|
||||
product_point = Point.from_bytes(product_bytes)
|
||||
assert product_with_star_operator.to_bytes() == product_bytes
|
||||
assert product_point == product_with_star_operator
|
||||
|
||||
# Repeating the operation, should return the same result.
|
||||
product_with_star_operator_again = k256_point * k256_bn
|
||||
assert product_with_star_operator == product_with_star_operator_again
|
|
@ -1,165 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from cryptography.exceptions import InternalError
|
||||
|
||||
from umbral.curve import SECP256K1, SECP256R1
|
||||
from umbral.point import Point
|
||||
|
||||
|
||||
def generate_test_points_bytes(quantity=2):
|
||||
points_bytes = [
|
||||
(SECP256K1, 714, b'\x02x{DR\x94\x8f\x17\xb8\xa2\x14t\x11\xdb\xb1VK\xdb\xc2\xa0T\x97iCK\x8cz~\xea\xa3\xb7AJ'),
|
||||
]
|
||||
for _ in range(quantity):
|
||||
args = (SECP256K1, 714, Point.gen_rand(curve=SECP256K1).to_bytes())
|
||||
points_bytes.append(args)
|
||||
return points_bytes
|
||||
|
||||
|
||||
def generate_test_points_affine(quantity=2):
|
||||
points_affine = [
|
||||
(SECP256K1, 714, (54495335564072000415434275044935054036617226655045445809732056033758606213450,
|
||||
26274482902044210718566767736429706729731617411738990314884135712590488065008)),
|
||||
]
|
||||
for _ in range(quantity):
|
||||
args = (SECP256K1, 714, Point.gen_rand(curve=SECP256K1).to_affine())
|
||||
points_affine.append(args)
|
||||
return points_affine
|
||||
|
||||
|
||||
def test_generate_random_points():
|
||||
for _ in range(10):
|
||||
point = Point.gen_rand()
|
||||
another_point = Point.gen_rand()
|
||||
assert isinstance(point, Point)
|
||||
assert isinstance(another_point, Point)
|
||||
assert point != another_point
|
||||
|
||||
|
||||
@pytest.mark.parametrize("curve, nid, point_bytes", generate_test_points_bytes())
|
||||
def test_bytes_serializers(point_bytes, nid, curve):
|
||||
point_with_curve = Point.from_bytes(point_bytes, curve=curve) # from curve
|
||||
assert isinstance(point_with_curve, Point)
|
||||
|
||||
the_same_point_bytes = point_with_curve.to_bytes()
|
||||
assert point_bytes == the_same_point_bytes
|
||||
|
||||
representations = (point_bytes, # Compressed representation
|
||||
point_with_curve.to_bytes(is_compressed=False)) # Uncompressed
|
||||
|
||||
for point_representation in representations:
|
||||
|
||||
malformed_point_bytes = point_representation + b'0x'
|
||||
with pytest.raises(InternalError):
|
||||
_ = Point.from_bytes(malformed_point_bytes)
|
||||
|
||||
malformed_point_bytes = point_representation[1:]
|
||||
with pytest.raises(InternalError):
|
||||
_ = Point.from_bytes(malformed_point_bytes)
|
||||
|
||||
malformed_point_bytes = point_representation[:-1]
|
||||
with pytest.raises(InternalError):
|
||||
_ = Point.from_bytes(malformed_point_bytes)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("curve, nid, point_affine", generate_test_points_affine())
|
||||
def test_affine(point_affine, nid, curve):
|
||||
point = Point.from_affine(point_affine, curve=curve) # from curve
|
||||
assert isinstance(point, Point)
|
||||
point_affine2 = point.to_affine()
|
||||
assert point_affine == point_affine2
|
||||
|
||||
|
||||
def test_invalid_points(random_ec_point2):
|
||||
|
||||
point_bytes = bytearray(random_ec_point2.to_bytes(is_compressed=False))
|
||||
point_bytes[-1] = point_bytes[-1] ^ 0x01 # Flips last bit
|
||||
point_bytes = bytes(point_bytes)
|
||||
|
||||
with pytest.raises(InternalError) as e:
|
||||
_point = Point.from_bytes(point_bytes)
|
||||
|
||||
# We want to catch specific InternalExceptions:
|
||||
# - Point not in the curve (code 107)
|
||||
# - Invalid compressed point (code 110)
|
||||
# https://github.com/openssl/openssl/blob/master/include/openssl/ecerr.h#L228
|
||||
assert e.value.err_code[0].reason in (107, 110)
|
||||
|
||||
|
||||
def test_generator_point():
|
||||
"""http://www.secg.org/SEC2-Ver-1.0.pdf Section 2.7.1"""
|
||||
g1 = Point.get_generator_from_curve()
|
||||
|
||||
g_compressed = 0x0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
|
||||
g_uncompressed = 0x0479BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
|
||||
|
||||
g_compressed = g_compressed.to_bytes(32+1, byteorder='big')
|
||||
g_uncompressed = g_uncompressed.to_bytes(64+1, byteorder='big')
|
||||
|
||||
g2 = Point.from_bytes(g_compressed)
|
||||
assert g1 == g2
|
||||
|
||||
g3 = Point.from_bytes(g_uncompressed)
|
||||
assert g1 == g3
|
||||
assert g2 == g3
|
||||
|
||||
|
||||
def test_point_not_on_curve():
|
||||
"""
|
||||
We want to be unable to create a Point that's not on the curve.
|
||||
|
||||
When we try, we get cryptography.exceptions.InternalError - is that specifically because it isn't
|
||||
on the curve? It seems to be reliably raised in the event of the Point being off the curve.
|
||||
|
||||
The OpenSSL docs don't explicitly say that they raise an error for this reason:
|
||||
https://www.openssl.org/docs/man1.1.0/crypto/EC_GFp_simple_method.html
|
||||
"""
|
||||
point_on_koblitz256_but_not_P256 = Point.from_bytes(b'\x03%\x98Dk\x88\xe2\x97\xab?\xabZ\xef\xd4' \
|
||||
b'\x9e\xaa\xc6\xb3\xa4\xa3\x89\xb2\xd7b.\x8f\x16Ci_&\xe0\x7f', curve=SECP256K1)
|
||||
|
||||
from cryptography.exceptions import InternalError
|
||||
with pytest.raises(InternalError):
|
||||
Point.from_bytes(point_on_koblitz256_but_not_P256.to_bytes(), curve=SECP256R1)
|
||||
|
||||
|
||||
def test_serialize_point_at_infinity():
|
||||
|
||||
p = Point.gen_rand()
|
||||
point_at_infinity = p - p
|
||||
|
||||
bytes_point_at_infinity = point_at_infinity.to_bytes()
|
||||
assert bytes_point_at_infinity == b'\x00'
|
||||
|
||||
|
||||
def test_coords_with_special_characteristics():
|
||||
|
||||
# Testing that a point with x coordinate greater than the curve order is still valid.
|
||||
# In particular, we will test the last valid point from the default curve (secp256k1)
|
||||
# whose x coordinate is `field_order - 3` and is greater than the order of the curve
|
||||
|
||||
field_order = 2**256 - 0x1000003D1
|
||||
compressed = b'\x02' + (field_order-3).to_bytes(32, 'big')
|
||||
|
||||
last_point = Point.from_bytes(compressed)
|
||||
|
||||
# The same point, but obtained through the from_affine method
|
||||
coords = (115792089237316195423570985008687907853269984665640564039457584007908834671660,
|
||||
109188863561374057667848968960504138135859662956057034999983532397866404169138)
|
||||
|
||||
assert last_point == Point.from_affine(coords)
|
|
@ -1,115 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from cryptography.hazmat.backends.openssl import backend
|
||||
from hypothesis import HealthCheck, given, settings, unlimited
|
||||
from hypothesis.strategies import binary, booleans, integers, tuples
|
||||
from umbral.config import default_curve
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.cfrags import CorrectnessProof
|
||||
from umbral.kfrags import KFrag
|
||||
from umbral.keys import UmbralPrivateKey, UmbralPublicKey
|
||||
from umbral.params import UmbralParameters
|
||||
from umbral.point import Point
|
||||
from umbral.random_oracles import unsafe_hash_to_point
|
||||
from umbral.pre import Capsule
|
||||
|
||||
# test parameters
|
||||
max_examples = 1000
|
||||
|
||||
# crypto constants
|
||||
curve = default_curve()
|
||||
params = UmbralParameters(curve)
|
||||
bn_size = curve.group_order_size_in_bytes
|
||||
|
||||
# generators
|
||||
bns = integers(min_value=1, max_value=backend._bn_to_int(curve.order)).map(
|
||||
lambda x: CurveBN.from_int(x))
|
||||
|
||||
points = binary(min_size=1).map(
|
||||
lambda x: unsafe_hash_to_point(x, label=b'hypothesis', params=params))
|
||||
|
||||
signatures = tuples(integers(min_value=1, max_value=backend._bn_to_int(curve.order)),
|
||||
integers(min_value=1, max_value=backend._bn_to_int(curve.order))).map(
|
||||
lambda tup: tup[0].to_bytes(bn_size, 'big') + tup[1].to_bytes(bn_size, 'big'))
|
||||
|
||||
# # utility
|
||||
def assert_kfrag_eq(k0, k1):
|
||||
assert(all([ k0.id == k1.id
|
||||
, k0.bn_key == k1.bn_key
|
||||
, k0.point_precursor == k1.point_precursor
|
||||
, k0.point_commitment == k1.point_commitment
|
||||
, k0.signature_for_bob == k1.signature_for_bob
|
||||
, k0.signature_for_proxy == k1.signature_for_proxy
|
||||
]))
|
||||
|
||||
def assert_cp_eq(c0, c1):
|
||||
assert(all([ c0.point_e2 == c1.point_e2
|
||||
, c0.point_v2 == c1.point_v2
|
||||
, c0.point_kfrag_commitment == c1.point_kfrag_commitment
|
||||
, c0.point_kfrag_pok == c1.point_kfrag_pok
|
||||
, c0.kfrag_signature == c1.kfrag_signature
|
||||
, c0.bn_sig == c1.bn_sig
|
||||
, c0.metadata == c1.metadata
|
||||
]))
|
||||
|
||||
# tests
|
||||
|
||||
@given(bns)
|
||||
@settings(max_examples=max_examples, timeout=unlimited)
|
||||
def test_bn_roundtrip(bn):
|
||||
assert(bn == CurveBN.from_bytes(bn.to_bytes()))
|
||||
|
||||
@given(points, booleans())
|
||||
@settings(max_examples=max_examples, timeout=unlimited)
|
||||
def test_point_roundtrip(p, c):
|
||||
assert(p == Point.from_bytes(p.to_bytes(is_compressed=c)))
|
||||
|
||||
@given(binary(min_size=bn_size, max_size=bn_size), bns, points, points, signatures, signatures)
|
||||
@settings(max_examples=max_examples, timeout=unlimited)
|
||||
def test_kfrag_roundtrip(d, b0, p0, p1, sig_proxy, sig_bob):
|
||||
k = KFrag(identifier=d, bn_key=b0, point_commitment=p0, point_precursor=p1,
|
||||
signature_for_proxy=sig_proxy, signature_for_bob=sig_bob)
|
||||
assert_kfrag_eq(k, KFrag.from_bytes(k.to_bytes()))
|
||||
|
||||
@given(points, points, bns)
|
||||
@settings(max_examples=max_examples, timeout=unlimited)
|
||||
def test_capsule_roundtrip_0(p0, p1, b):
|
||||
c = Capsule(params=params, point_e=p0, point_v=p1, bn_sig=b)
|
||||
assert(c == Capsule.from_bytes(c.to_bytes(), params=params))
|
||||
|
||||
@given(points, points, points, points, bns, signatures)
|
||||
@settings(max_examples=max_examples, timeout=unlimited)
|
||||
def test_cp_roundtrip(p0, p1, p2, p3, b0, sig):
|
||||
c = CorrectnessProof(p0, p1, p2, p3, b0, sig)
|
||||
assert_cp_eq(c, CorrectnessProof.from_bytes(c.to_bytes()))
|
||||
|
||||
@given(points)
|
||||
@settings(max_examples=max_examples, timeout=unlimited)
|
||||
def test_pubkey_roundtrip(p):
|
||||
k = UmbralPublicKey(p, params)
|
||||
assert(k == UmbralPublicKey.from_bytes(k.to_bytes(), params=params))
|
||||
|
||||
@given(binary(min_size=1))
|
||||
@settings(max_examples=20, timeout=unlimited, suppress_health_check=[HealthCheck.hung_test])
|
||||
def test_privkey_roundtrip(p):
|
||||
insecure_scrypt_cost = 5 # This is deliberately insecure, just to make it faster
|
||||
k = UmbralPrivateKey.gen_key()
|
||||
rt = UmbralPrivateKey.from_bytes(k.to_bytes(password=p, _scrypt_cost=insecure_scrypt_cost),
|
||||
password=p,
|
||||
_scrypt_cost=insecure_scrypt_cost)
|
||||
assert(k.get_pubkey() == rt.get_pubkey())
|
|
@ -1,62 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from cryptography.hazmat.backends.openssl import backend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from umbral.keys import UmbralPrivateKey
|
||||
from umbral.signing import Signer, Signature, DEFAULT_HASH_ALGORITHM
|
||||
|
||||
|
||||
@pytest.mark.parametrize('execution_number', range(20)) # Run this test 20 times.
|
||||
def test_sign_and_verify(execution_number):
|
||||
privkey = UmbralPrivateKey.gen_key()
|
||||
pubkey = privkey.get_pubkey()
|
||||
signer = Signer(private_key=privkey)
|
||||
message = b"peace at dawn"
|
||||
signature = signer(message=message)
|
||||
|
||||
# Basic signature verification
|
||||
assert signature.verify(message, pubkey)
|
||||
assert not signature.verify(b"another message", pubkey)
|
||||
another_pubkey = UmbralPrivateKey.gen_key().pubkey
|
||||
assert not signature.verify(message, another_pubkey)
|
||||
|
||||
# Signature serialization
|
||||
sig_bytes = bytes(signature)
|
||||
assert len(sig_bytes) == Signature.expected_bytes_length()
|
||||
restored_signature = Signature.from_bytes(sig_bytes)
|
||||
assert restored_signature == signature
|
||||
assert restored_signature.verify(message, pubkey)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('execution_number', range(20)) # Run this test 20 times.
|
||||
def test_prehashed_message(execution_number):
|
||||
privkey = UmbralPrivateKey.gen_key()
|
||||
pubkey = privkey.get_pubkey()
|
||||
signer = Signer(private_key=privkey)
|
||||
|
||||
message = b"peace at dawn"
|
||||
hash_function = hashes.Hash(DEFAULT_HASH_ALGORITHM(), backend=backend)
|
||||
hash_function.update(message)
|
||||
prehashed_message = hash_function.finalize()
|
||||
|
||||
signature = signer(message=prehashed_message, is_prehashed=True)
|
||||
|
||||
assert signature.verify(message=prehashed_message,
|
||||
verifying_key=pubkey,
|
||||
is_prehashed=True)
|
|
@ -1,223 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import os
|
||||
import pytest
|
||||
import string
|
||||
|
||||
from umbral.config import default_params
|
||||
from umbral.keys import UmbralPublicKey, UmbralPrivateKey, UmbralKeyingMaterial
|
||||
from umbral.point import Point
|
||||
|
||||
|
||||
def test_gen_key():
|
||||
# Pass in the parameters to test that manual param selection works
|
||||
umbral_priv_key = UmbralPrivateKey.gen_key()
|
||||
assert type(umbral_priv_key) == UmbralPrivateKey
|
||||
|
||||
umbral_pub_key = umbral_priv_key.get_pubkey()
|
||||
assert type(umbral_pub_key) == UmbralPublicKey
|
||||
|
||||
|
||||
def test_derive_key_from_label():
|
||||
umbral_keying_material = UmbralKeyingMaterial()
|
||||
|
||||
label = b"my_healthcare_information"
|
||||
|
||||
priv_key1 = umbral_keying_material.derive_privkey_by_label(label)
|
||||
assert type(priv_key1) == UmbralPrivateKey
|
||||
|
||||
pub_key1 = priv_key1.get_pubkey()
|
||||
assert type(pub_key1) == UmbralPublicKey
|
||||
|
||||
# Check that key derivation is reproducible
|
||||
priv_key2 = umbral_keying_material.derive_privkey_by_label(label)
|
||||
pub_key2 = priv_key2.get_pubkey()
|
||||
assert priv_key1.bn_key == priv_key2.bn_key
|
||||
assert pub_key1 == pub_key2
|
||||
|
||||
# A salt can be used too, but of course it affects the derived key
|
||||
salt = b"optional, randomly generated salt"
|
||||
priv_key3 = umbral_keying_material.derive_privkey_by_label(label, salt=salt)
|
||||
assert priv_key3.bn_key != priv_key1.bn_key
|
||||
|
||||
# Different labels on the same master secret create different keys
|
||||
label = b"my_tax_information"
|
||||
priv_key4 = umbral_keying_material.derive_privkey_by_label(label)
|
||||
pub_key4 = priv_key4.get_pubkey()
|
||||
assert priv_key1.bn_key != priv_key4.bn_key
|
||||
|
||||
|
||||
def test_private_key_serialization(random_ec_curvebn1):
|
||||
priv_key = random_ec_curvebn1
|
||||
umbral_key = UmbralPrivateKey(priv_key, default_params())
|
||||
|
||||
encoded_key = umbral_key.to_bytes()
|
||||
|
||||
decoded_key = UmbralPrivateKey.from_bytes(encoded_key)
|
||||
assert priv_key == decoded_key.bn_key
|
||||
|
||||
|
||||
def test_private_key_serialization_with_encryption(random_ec_curvebn1):
|
||||
priv_key = random_ec_curvebn1
|
||||
umbral_key = UmbralPrivateKey(priv_key, default_params())
|
||||
|
||||
insecure_cost = 15 # This is deliberately insecure, just to make the tests faster
|
||||
encoded_key = umbral_key.to_bytes(password=b'test',
|
||||
_scrypt_cost=insecure_cost)
|
||||
|
||||
decoded_key = UmbralPrivateKey.from_bytes(encoded_key,
|
||||
password=b'test',
|
||||
_scrypt_cost=insecure_cost)
|
||||
assert priv_key == decoded_key.bn_key
|
||||
|
||||
|
||||
def test_public_key_serialization(random_ec_curvebn1):
|
||||
umbral_key = UmbralPrivateKey(random_ec_curvebn1, default_params()).get_pubkey()
|
||||
pub_point = umbral_key.point_key
|
||||
|
||||
encoded_key = umbral_key.to_bytes()
|
||||
|
||||
decoded_key = UmbralPublicKey.from_bytes(encoded_key)
|
||||
assert pub_point == decoded_key.point_key
|
||||
|
||||
|
||||
def test_public_key_to_compressed_bytes(random_ec_curvebn1):
|
||||
umbral_key = UmbralPrivateKey(random_ec_curvebn1, default_params()).get_pubkey()
|
||||
key_bytes = bytes(umbral_key)
|
||||
assert len(key_bytes) == Point.expected_bytes_length(is_compressed=True)
|
||||
|
||||
|
||||
def test_public_key_to_uncompressed_bytes(random_ec_curvebn1):
|
||||
umbral_key = UmbralPrivateKey(random_ec_curvebn1, default_params()).get_pubkey()
|
||||
key_bytes = umbral_key.to_bytes(is_compressed=False)
|
||||
assert len(key_bytes) == Point.expected_bytes_length(is_compressed=False)
|
||||
|
||||
|
||||
def test_key_encoder_decoder(random_ec_curvebn1):
|
||||
priv_key = random_ec_curvebn1
|
||||
umbral_key = UmbralPrivateKey(priv_key, default_params())
|
||||
|
||||
encoded_key = umbral_key.to_bytes(encoder=base64.urlsafe_b64encode)
|
||||
|
||||
decoded_key = UmbralPrivateKey.from_bytes(encoded_key,
|
||||
decoder=base64.urlsafe_b64decode)
|
||||
assert decoded_key.to_bytes() == umbral_key.to_bytes()
|
||||
|
||||
|
||||
def test_public_key_as_hex(random_ec_curvebn1):
|
||||
pubkey = UmbralPrivateKey(random_ec_curvebn1, default_params()).get_pubkey()
|
||||
hex_string = pubkey.hex()
|
||||
|
||||
assert set(hex_string).issubset(set(string.hexdigits))
|
||||
assert len(hex_string) == 2 * UmbralPublicKey.expected_bytes_length()
|
||||
|
||||
decoded_pubkey = UmbralPublicKey.from_hex(hex_string)
|
||||
|
||||
assert pubkey == decoded_pubkey
|
||||
|
||||
hex_string = pubkey.hex(is_compressed=False)
|
||||
|
||||
assert set(hex_string).issubset(set(string.hexdigits))
|
||||
assert len(hex_string) == 2 * UmbralPublicKey.expected_bytes_length(is_compressed=False)
|
||||
|
||||
decoded_pubkey = UmbralPublicKey.from_hex(hex_string)
|
||||
assert pubkey == decoded_pubkey
|
||||
|
||||
|
||||
def test_umbral_key_to_cryptography_keys():
|
||||
umbral_priv_key = UmbralPrivateKey.gen_key()
|
||||
umbral_pub_key = umbral_priv_key.get_pubkey()
|
||||
|
||||
crypto_privkey = umbral_priv_key.to_cryptography_privkey()
|
||||
assert int(umbral_priv_key.bn_key) == crypto_privkey.private_numbers().private_value
|
||||
|
||||
crypto_pubkey = umbral_pub_key.to_cryptography_pubkey()
|
||||
umbral_affine = umbral_pub_key.point_key.to_affine()
|
||||
x, y = crypto_pubkey.public_numbers().x, crypto_pubkey.public_numbers().y
|
||||
assert umbral_affine == (x, y)
|
||||
|
||||
|
||||
def test_keying_material_serialization():
|
||||
umbral_keying_material = UmbralKeyingMaterial()
|
||||
|
||||
encoded_keying_material = umbral_keying_material.to_bytes()
|
||||
|
||||
decoded_keying_material = UmbralKeyingMaterial.from_bytes(encoded_keying_material)
|
||||
|
||||
label = os.urandom(32)
|
||||
privkey_bytes = umbral_keying_material.derive_privkey_by_label(label).to_bytes()
|
||||
assert privkey_bytes == decoded_keying_material.derive_privkey_by_label(label).to_bytes()
|
||||
|
||||
|
||||
def test_keying_material_serialization_with_encryption():
|
||||
umbral_keying_material = UmbralKeyingMaterial()
|
||||
|
||||
insecure_cost = 15 # This is deliberately insecure, just to make the tests faster
|
||||
encoded_keying_material = umbral_keying_material.to_bytes(password=b'test',
|
||||
_scrypt_cost=insecure_cost)
|
||||
|
||||
decoded_keying_material = UmbralKeyingMaterial.from_bytes(encoded_keying_material,
|
||||
password=b'test',
|
||||
_scrypt_cost=insecure_cost)
|
||||
|
||||
label = os.urandom(32)
|
||||
privkey_bytes = umbral_keying_material.derive_privkey_by_label(label).to_bytes()
|
||||
assert privkey_bytes == decoded_keying_material.derive_privkey_by_label(label).to_bytes()
|
||||
|
||||
|
||||
def test_umbral_public_key_equality():
|
||||
umbral_priv_key = UmbralPrivateKey.gen_key()
|
||||
umbral_pub_key = umbral_priv_key.get_pubkey()
|
||||
|
||||
as_bytes = bytes(umbral_pub_key)
|
||||
assert umbral_pub_key == as_bytes
|
||||
|
||||
reconstructed = UmbralPublicKey.from_bytes(as_bytes)
|
||||
assert reconstructed == umbral_pub_key
|
||||
|
||||
assert not umbral_pub_key == b"some whatever bytes"
|
||||
|
||||
another_umbral_priv_key = UmbralPrivateKey.gen_key()
|
||||
another_umbral_pub_key = another_umbral_priv_key.get_pubkey()
|
||||
|
||||
assert not umbral_pub_key == another_umbral_pub_key
|
||||
|
||||
# Also not equal to a totally disparate type.
|
||||
assert not umbral_pub_key == 107
|
||||
|
||||
|
||||
def test_umbral_public_key_as_dict_key():
|
||||
umbral_priv_key = UmbralPrivateKey.gen_key()
|
||||
umbral_pub_key = umbral_priv_key.get_pubkey()
|
||||
|
||||
d = {umbral_pub_key: 19}
|
||||
assert d[umbral_pub_key] == 19
|
||||
|
||||
another_umbral_priv_key = UmbralPrivateKey.gen_key()
|
||||
another_umbral_pub_key = another_umbral_priv_key.get_pubkey()
|
||||
|
||||
with pytest.raises(KeyError):
|
||||
_ = d[another_umbral_pub_key]
|
||||
|
||||
d[another_umbral_pub_key] = False
|
||||
|
||||
assert d[umbral_pub_key] == 19
|
||||
d[umbral_pub_key] = 20
|
||||
assert d[umbral_pub_key] == 20
|
||||
assert d[another_umbral_pub_key] is False
|
|
@ -1,20 +1,3 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published b
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__all__ = [
|
||||
|
|
|
@ -1,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 umbral.__about__ import (
|
||||
__author__, __license__, __summary__, __title__, __version__, __copyright__, __email__, __url__
|
||||
)
|
||||
|
|
287
umbral/cfrags.py
287
umbral/cfrags.py
|
@ -1,287 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from typing import Optional, Any
|
||||
|
||||
from bytestring_splitter import BytestringSplitter
|
||||
|
||||
from umbral.config import default_curve
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.point import Point
|
||||
from umbral.signing import Signature
|
||||
from umbral.curve import Curve
|
||||
from umbral.random_oracles import hash_to_curvebn, ExtendedKeccak
|
||||
|
||||
|
||||
class CorrectnessProof:
|
||||
def __init__(self, point_e2: Point, point_v2: Point, point_kfrag_commitment: Point,
|
||||
point_kfrag_pok: Point, bn_sig: CurveBN, kfrag_signature: Signature,
|
||||
metadata: Optional[bytes] = None) -> None:
|
||||
self.point_e2 = point_e2
|
||||
self.point_v2 = point_v2
|
||||
self.point_kfrag_commitment = point_kfrag_commitment
|
||||
self.point_kfrag_pok = point_kfrag_pok
|
||||
self.bn_sig = bn_sig
|
||||
self.metadata = metadata
|
||||
self.kfrag_signature = kfrag_signature
|
||||
|
||||
@classmethod
|
||||
def expected_bytes_length(cls, curve: Optional[Curve] = None):
|
||||
"""
|
||||
Returns the size (in bytes) of a CorrectnessProof without the metadata.
|
||||
If no curve is given, it will use the default curve.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
bn_size = CurveBN.expected_bytes_length(curve=curve)
|
||||
point_size = Point.expected_bytes_length(curve=curve)
|
||||
|
||||
return (bn_size * 3) + (point_size * 4)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes, curve: Optional[Curve] = None) -> 'CorrectnessProof':
|
||||
"""
|
||||
Instantiate CorrectnessProof from serialized data.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
bn_size = CurveBN.expected_bytes_length(curve)
|
||||
point_size = Point.expected_bytes_length(curve)
|
||||
arguments = {'curve': curve}
|
||||
splitter = BytestringSplitter(
|
||||
(Point, point_size, arguments), # point_e2
|
||||
(Point, point_size, arguments), # point_v2
|
||||
(Point, point_size, arguments), # point_kfrag_commitment
|
||||
(Point, point_size, arguments), # point_kfrag_pok
|
||||
(CurveBN, bn_size, arguments), # bn_sig
|
||||
(Signature, Signature.expected_bytes_length(curve), arguments), # kfrag_signature
|
||||
)
|
||||
components = splitter(data, return_remainder=True)
|
||||
components.append(components.pop() or None)
|
||||
|
||||
return cls(*components)
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
"""
|
||||
Serialize the CorrectnessProof to a bytestring.
|
||||
"""
|
||||
e2 = self.point_e2.to_bytes()
|
||||
v2 = self.point_v2.to_bytes()
|
||||
kfrag_commitment = self.point_kfrag_commitment.to_bytes()
|
||||
kfrag_pok = self.point_kfrag_pok.to_bytes()
|
||||
|
||||
result = e2 \
|
||||
+ v2 \
|
||||
+ kfrag_commitment \
|
||||
+ kfrag_pok \
|
||||
+ self.bn_sig.to_bytes() \
|
||||
+ self.kfrag_signature
|
||||
|
||||
result += self.metadata or b''
|
||||
|
||||
return result
|
||||
|
||||
def __bytes__(self):
|
||||
return self.to_bytes()
|
||||
|
||||
|
||||
class CapsuleFrag:
|
||||
def __init__(self,
|
||||
point_e1: Point,
|
||||
point_v1: Point,
|
||||
kfrag_id: bytes,
|
||||
point_precursor: Point,
|
||||
proof: Optional[CorrectnessProof] = None) -> None:
|
||||
self.point_e1 = point_e1
|
||||
self.point_v1 = point_v1
|
||||
self.kfrag_id = kfrag_id
|
||||
self.point_precursor = point_precursor
|
||||
self.proof = proof
|
||||
|
||||
class NoProofProvided(TypeError):
|
||||
"""
|
||||
Raised when a cfrag is assessed for correctness, but no proof is attached.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def expected_bytes_length(cls, curve: Optional[Curve] = None) -> int:
|
||||
"""
|
||||
Returns the size (in bytes) of a CapsuleFrag given the curve without
|
||||
the CorrectnessProof.
|
||||
If no curve is provided, it will use the default curve.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
bn_size = CurveBN.expected_bytes_length(curve)
|
||||
point_size = Point.expected_bytes_length(curve)
|
||||
|
||||
return (bn_size * 1) + (point_size * 3)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes, curve: Optional[Curve] = None) -> 'CapsuleFrag':
|
||||
"""
|
||||
Instantiates a CapsuleFrag object from the serialized data.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
|
||||
bn_size = CurveBN.expected_bytes_length(curve)
|
||||
point_size = Point.expected_bytes_length(curve)
|
||||
arguments = {'curve': curve}
|
||||
|
||||
splitter = BytestringSplitter(
|
||||
(Point, point_size, arguments), # point_e1
|
||||
(Point, point_size, arguments), # point_v1
|
||||
bn_size, # kfrag_id
|
||||
(Point, point_size, arguments), # point_precursor
|
||||
)
|
||||
components = splitter(data, return_remainder=True)
|
||||
|
||||
proof = components.pop() or None
|
||||
components.append(CorrectnessProof.from_bytes(proof, curve) if proof else None)
|
||||
|
||||
return cls(*components)
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
"""
|
||||
Serialize the CapsuleFrag into a bytestring.
|
||||
"""
|
||||
e1 = self.point_e1.to_bytes()
|
||||
v1 = self.point_v1.to_bytes()
|
||||
precursor = self.point_precursor.to_bytes()
|
||||
|
||||
serialized_cfrag = e1 + v1 + self.kfrag_id + precursor
|
||||
|
||||
if self.proof is not None:
|
||||
serialized_cfrag += self.proof.to_bytes()
|
||||
|
||||
return serialized_cfrag
|
||||
|
||||
def prove_correctness(self,
|
||||
capsule,
|
||||
kfrag,
|
||||
metadata: Optional[bytes] = None):
|
||||
|
||||
params = capsule.params
|
||||
|
||||
# Check correctness of original ciphertext
|
||||
if not capsule.verify():
|
||||
raise capsule.NotValid("Capsule verification failed.")
|
||||
|
||||
rk = kfrag.bn_key
|
||||
t = CurveBN.gen_rand(params.curve)
|
||||
####
|
||||
# Here are the formulaic constituents shared with `verify_correctness`.
|
||||
####
|
||||
e = capsule.point_e
|
||||
v = capsule.point_v
|
||||
|
||||
e1 = self.point_e1
|
||||
v1 = self.point_v1
|
||||
|
||||
u = params.u
|
||||
u1 = kfrag.point_commitment
|
||||
|
||||
e2 = t * e # type: Any
|
||||
v2 = t * v # type: Any
|
||||
u2 = t * u # type: Any
|
||||
|
||||
hash_input = [e, e1, e2, v, v1, v2, u, u1, u2]
|
||||
if metadata is not None:
|
||||
hash_input.append(metadata)
|
||||
|
||||
h = hash_to_curvebn(*hash_input, params=params, hash_class=ExtendedKeccak)
|
||||
########
|
||||
|
||||
z3 = t + h * rk
|
||||
|
||||
self.attach_proof(e2, v2, u1, u2, metadata=metadata, z3=z3, kfrag_signature=kfrag.signature_for_bob)
|
||||
|
||||
def verify_correctness(self, capsule) -> bool:
|
||||
if self.proof is None:
|
||||
raise CapsuleFrag.NoProofProvided
|
||||
|
||||
correctness_keys = capsule.get_correctness_keys()
|
||||
|
||||
delegating_pubkey = correctness_keys['delegating']
|
||||
signing_pubkey = correctness_keys['verifying']
|
||||
receiving_pubkey = correctness_keys['receiving']
|
||||
|
||||
params = capsule.params
|
||||
|
||||
####
|
||||
# Here are the formulaic constituents shared with `prove_correctness`.
|
||||
####
|
||||
e = capsule.point_e
|
||||
v = capsule.point_v
|
||||
|
||||
e1 = self.point_e1
|
||||
v1 = self.point_v1
|
||||
|
||||
u = params.u
|
||||
u1 = self.proof.point_kfrag_commitment
|
||||
|
||||
e2 = self.proof.point_e2
|
||||
v2 = self.proof.point_v2
|
||||
u2 = self.proof.point_kfrag_pok
|
||||
|
||||
hash_input = [e, e1, e2, v, v1, v2, u, u1, u2]
|
||||
if self.proof.metadata is not None:
|
||||
hash_input.append(self.proof.metadata)
|
||||
|
||||
h = hash_to_curvebn(*hash_input, params=params, hash_class=ExtendedKeccak)
|
||||
########
|
||||
|
||||
precursor = self.point_precursor
|
||||
kfrag_id = self.kfrag_id
|
||||
|
||||
validity_input = (kfrag_id, delegating_pubkey, receiving_pubkey, u1, precursor)
|
||||
|
||||
kfrag_validity_message = bytes().join(bytes(item) for item in validity_input)
|
||||
valid_kfrag_signature = self.proof.kfrag_signature.verify(kfrag_validity_message, signing_pubkey)
|
||||
|
||||
z3 = self.proof.bn_sig
|
||||
correct_reencryption_of_e = z3 * e == e2 + (h * e1)
|
||||
|
||||
correct_reencryption_of_v = z3 * v == v2 + (h * v1)
|
||||
|
||||
correct_rk_commitment = z3 * u == u2 + (h * u1)
|
||||
|
||||
return valid_kfrag_signature \
|
||||
& correct_reencryption_of_e \
|
||||
& correct_reencryption_of_v \
|
||||
& correct_rk_commitment
|
||||
|
||||
def attach_proof(self,
|
||||
e2: Point,
|
||||
v2: Point,
|
||||
u1: Point,
|
||||
u2: Point,
|
||||
z3: CurveBN,
|
||||
kfrag_signature: Signature,
|
||||
metadata: Optional[bytes]) -> None:
|
||||
|
||||
self.proof = CorrectnessProof(point_e2=e2,
|
||||
point_v2=v2,
|
||||
point_kfrag_commitment=u1,
|
||||
point_kfrag_pok=u2,
|
||||
bn_sig=z3,
|
||||
kfrag_signature=kfrag_signature,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
return self.to_bytes()
|
||||
|
||||
def __repr__(self):
|
||||
return "CFrag:{}".format(self.point_e1.to_bytes().hex()[2:17])
|
|
@ -1,77 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from typing import Optional, Type
|
||||
from warnings import warn
|
||||
|
||||
from umbral.curve import Curve, SECP256K1
|
||||
from umbral.params import UmbralParameters
|
||||
|
||||
|
||||
class _CONFIG:
|
||||
__curve = None
|
||||
__params = None
|
||||
__CURVE_TO_USE_IF_NO_DEFAULT_IS_SET_BY_USER = SECP256K1
|
||||
__WARNING_IF_NO_DEFAULT_SET = "No default curve has been set. " \
|
||||
"Using SECP256K1. " \
|
||||
"A slight performance penalty has been " \
|
||||
"incurred for only this call. Set a default " \
|
||||
"curve with umbral.config.set_default_curve()."
|
||||
|
||||
class UmbralConfigurationError(RuntimeError):
|
||||
"""Raised when somebody does something dumb re: configuration."""
|
||||
|
||||
@classmethod
|
||||
def __set_curve_by_default(cls):
|
||||
warn(cls.__WARNING_IF_NO_DEFAULT_SET, RuntimeWarning)
|
||||
cls.set_curve(cls.__CURVE_TO_USE_IF_NO_DEFAULT_IS_SET_BY_USER)
|
||||
|
||||
@classmethod
|
||||
def params(cls) -> UmbralParameters:
|
||||
if not cls.__params:
|
||||
cls.__set_curve_by_default()
|
||||
return cls.__params # type: ignore
|
||||
|
||||
@classmethod
|
||||
def curve(cls) -> Curve:
|
||||
if not cls.__curve:
|
||||
cls.__set_curve_by_default()
|
||||
return cls.__curve # type: ignore
|
||||
|
||||
@classmethod
|
||||
def set_curve(cls, curve: Optional[Curve] = None) -> None:
|
||||
if cls.__curve:
|
||||
raise cls.UmbralConfigurationError(
|
||||
"You can only set the default curve once. Do it once and then leave it alone.")
|
||||
else:
|
||||
from umbral.params import UmbralParameters
|
||||
if curve is None:
|
||||
curve = _CONFIG.__CURVE_TO_USE_IF_NO_DEFAULT_IS_SET_BY_USER
|
||||
cls.__curve = curve
|
||||
cls.__params = UmbralParameters(curve)
|
||||
|
||||
|
||||
def set_default_curve(curve: Optional[Curve] = None) -> None:
|
||||
return _CONFIG.set_curve(curve)
|
||||
|
||||
|
||||
def default_curve() -> Curve:
|
||||
return _CONFIG.curve()
|
||||
|
||||
|
||||
def default_params() -> UmbralParameters:
|
||||
return _CONFIG.params()
|
135
umbral/curve.py
135
umbral/curve.py
|
@ -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/>.
|
||||
"""
|
||||
|
||||
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')
|
||||
|
||||
CURVES = (SECP256K1, SECP256R1, SECP384R1)
|
|
@ -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)
|
|
@ -1,58 +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
|
||||
from typing import Optional
|
||||
|
||||
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
|
||||
|
||||
|
||||
DEM_KEYSIZE = 32
|
||||
DEM_NONCE_SIZE = 12
|
||||
|
||||
|
||||
class UmbralDEM:
|
||||
def __init__(self, symm_key: bytes) -> None:
|
||||
"""
|
||||
Initializes an UmbralDEM object. Requires a key to perform
|
||||
ChaCha20-Poly1305.
|
||||
"""
|
||||
if len(symm_key) != DEM_KEYSIZE:
|
||||
raise ValueError(
|
||||
"Invalid key size, must be {} bytes".format(DEM_KEYSIZE)
|
||||
)
|
||||
|
||||
self.cipher = ChaCha20Poly1305(symm_key)
|
||||
|
||||
def encrypt(self, data: bytes, authenticated_data: Optional[bytes] = None) -> bytes:
|
||||
"""
|
||||
Encrypts data using ChaCha20-Poly1305 with optional authenticated data.
|
||||
"""
|
||||
nonce = os.urandom(DEM_NONCE_SIZE)
|
||||
enc_data = self.cipher.encrypt(nonce, data, authenticated_data)
|
||||
# Ciphertext will be a 12 byte nonce, the ciphertext, and a 16 byte tag.
|
||||
return nonce + enc_data
|
||||
|
||||
def decrypt(self, ciphertext: bytes, authenticated_data: Optional[bytes] = None) -> bytes:
|
||||
"""
|
||||
Decrypts data using ChaCha20-Poly1305 and validates the provided
|
||||
authenticated data.
|
||||
"""
|
||||
nonce = ciphertext[:DEM_NONCE_SIZE]
|
||||
ciphertext = ciphertext[DEM_NONCE_SIZE:]
|
||||
cleartext = self.cipher.decrypt(nonce, ciphertext, authenticated_data)
|
||||
return cleartext
|
472
umbral/keys.py
472
umbral/keys.py
|
@ -1,472 +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
|
||||
from typing import Callable, Optional, Any
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.backends.openssl.ec import _EllipticCurvePrivateKey, _EllipticCurvePublicKey
|
||||
from cryptography.exceptions import InternalError
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
||||
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt as CryptographyScrypt
|
||||
from nacl.secret import SecretBox
|
||||
|
||||
from 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
|
||||
|
||||
|
||||
__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:
|
||||
"""
|
||||
Derives a symmetric encryption key from a pair of password and salt.
|
||||
It uses Scrypt by default.
|
||||
"""
|
||||
kdf = kwargs.get('kdf', Scrypt)()
|
||||
derived_key = kdf(password, salt, **kwargs)
|
||||
return derived_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
|
||||
|
||||
@classmethod
|
||||
def gen_key(cls, params: Optional[UmbralParameters] = None) -> 'UmbralPrivateKey':
|
||||
"""
|
||||
Generates a private key and returns it.
|
||||
"""
|
||||
if params is None:
|
||||
params = default_params()
|
||||
|
||||
bn_key = CurveBN.gen_rand(params.curve)
|
||||
return cls(bn_key, params)
|
||||
|
||||
@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 __bytes__(self) -> bytes:
|
||||
"""
|
||||
Returns an Umbral Public key as a bytestring.
|
||||
"""
|
||||
return self.point_key.to_bytes()
|
||||
|
||||
def __repr__(self):
|
||||
return "{}:{}".format(self.__class__.__name__, self.point_key.to_bytes().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 __hash__(self) -> int:
|
||||
return int.from_bytes(self.to_bytes(), byteorder="big")
|
||||
|
||||
|
||||
class UmbralKeyingMaterial:
|
||||
"""
|
||||
This class handles keying material for Umbral, by allowing deterministic
|
||||
derivation of UmbralPrivateKeys based on labels.
|
||||
Don't use this key material directly as a key.
|
||||
"""
|
||||
|
||||
def __init__(self, keying_material: Optional[bytes] = None) -> None:
|
||||
"""
|
||||
Initializes an UmbralKeyingMaterial.
|
||||
"""
|
||||
if keying_material:
|
||||
if len(keying_material) < 32:
|
||||
raise ValueError("UmbralKeyingMaterial must have size at least 32 bytes.")
|
||||
self.__keying_material = keying_material
|
||||
else:
|
||||
self.__keying_material = os.urandom(64)
|
||||
|
||||
def derive_privkey_by_label(self,
|
||||
label: bytes,
|
||||
salt: Optional[bytes] = None,
|
||||
params: Optional[UmbralParameters] = None) -> UmbralPrivateKey:
|
||||
"""
|
||||
Derives an UmbralPrivateKey using a KDF from this instance of
|
||||
UmbralKeyingMaterial, a label, and an optional salt.
|
||||
"""
|
||||
params = params if params is not None else default_params()
|
||||
|
||||
key_material = HKDF(
|
||||
algorithm=hashes.BLAKE2b(64),
|
||||
length=64,
|
||||
salt=salt,
|
||||
info=b"NuCypher/KeyDerivation/"+label,
|
||||
backend=default_backend()
|
||||
).derive(self.__keying_material)
|
||||
|
||||
bn_key = hash_to_curvebn(key_material, params=params)
|
||||
return UmbralPrivateKey(bn_key, params)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls,
|
||||
key_bytes: bytes,
|
||||
wrapping_key: Optional[bytes] = None,
|
||||
password: Optional[bytes] = None,
|
||||
decoder: Optional[Callable] = None,
|
||||
**kwargs) -> 'UmbralKeyingMaterial':
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
if decoder:
|
||||
key_bytes = decoder(key_bytes)
|
||||
|
||||
if any((password, wrapping_key)):
|
||||
key_bytes = unwrap_key(wrapped_key=key_bytes,
|
||||
wrapping_key=wrapping_key,
|
||||
password=password,
|
||||
**kwargs)
|
||||
|
||||
return cls(keying_material=key_bytes)
|
||||
|
||||
def to_bytes(self,
|
||||
wrapping_key: Optional[bytes] = None,
|
||||
password: Optional[bytes] = None,
|
||||
encoder: Optional[Callable] = None,
|
||||
**kwargs) -> bytes:
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
key_bytes = self.__keying_material
|
||||
|
||||
if any((password, wrapping_key)):
|
||||
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
|
199
umbral/kfrags.py
199
umbral/kfrags.py
|
@ -1,199 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import hmac
|
||||
from typing import Optional
|
||||
|
||||
from bytestring_splitter import BytestringSplitter
|
||||
|
||||
from umbral.config import default_curve, default_params
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.keys import UmbralPublicKey
|
||||
from umbral.point import Point
|
||||
from umbral.signing import Signature
|
||||
from umbral.params import UmbralParameters
|
||||
from umbral.curve import Curve
|
||||
|
||||
NO_KEY = b'\x00'
|
||||
DELEGATING_ONLY = b'\x01'
|
||||
RECEIVING_ONLY = b'\x02'
|
||||
DELEGATING_AND_RECEIVING = b'\x03'
|
||||
|
||||
|
||||
class KFrag:
|
||||
|
||||
def __init__(self,
|
||||
identifier: bytes,
|
||||
bn_key: CurveBN,
|
||||
point_commitment: Point,
|
||||
point_precursor: Point,
|
||||
signature_for_proxy: Signature,
|
||||
signature_for_bob: Signature,
|
||||
keys_in_signature=DELEGATING_AND_RECEIVING,
|
||||
) -> None:
|
||||
self.id = identifier
|
||||
self.bn_key = bn_key
|
||||
self.point_commitment = point_commitment
|
||||
self.point_precursor = point_precursor
|
||||
self.signature_for_proxy = signature_for_proxy
|
||||
self.signature_for_bob = signature_for_bob
|
||||
self.keys_in_signature = keys_in_signature
|
||||
|
||||
class NotValid(ValueError):
|
||||
"""
|
||||
raised if the KFrag does not pass verification.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def expected_bytes_length(cls, curve: Optional[Curve] = None) -> int:
|
||||
"""
|
||||
Returns the size (in bytes) of a KFrag given the curve.
|
||||
If no curve is provided, it will use the default curve.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
bn_size = CurveBN.expected_bytes_length(curve)
|
||||
point_size = Point.expected_bytes_length(curve)
|
||||
|
||||
# self.id --> 1 bn_size
|
||||
# self.bn_key --> 1 bn_size
|
||||
# self.point_commitment --> 1 point_size
|
||||
# self.point_precursor --> 1 point_size
|
||||
# self.signature_for_proxy --> 2 bn_size
|
||||
# self.signature_for_bob --> 2 bn_size
|
||||
# self.keys_in_signature --> 1
|
||||
|
||||
return bn_size * 6 + point_size * 2 + 1
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes, curve: Optional[Curve] = None) -> 'KFrag':
|
||||
"""
|
||||
Instantiate a KFrag object from the serialized data.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
|
||||
bn_size = CurveBN.expected_bytes_length(curve)
|
||||
point_size = Point.expected_bytes_length(curve)
|
||||
signature_size = Signature.expected_bytes_length(curve)
|
||||
arguments = {'curve': curve}
|
||||
|
||||
splitter = BytestringSplitter(
|
||||
bn_size, # id
|
||||
(CurveBN, bn_size, arguments), # bn_key
|
||||
(Point, point_size, arguments), # point_commitment
|
||||
(Point, point_size, arguments), # point_precursor
|
||||
1, # keys_in_signature
|
||||
(Signature, signature_size, arguments), # signature_for_proxy
|
||||
(Signature, signature_size, arguments), # signature_for_bob
|
||||
)
|
||||
components = splitter(data)
|
||||
|
||||
return cls(identifier=components[0],
|
||||
bn_key=components[1],
|
||||
point_commitment=components[2],
|
||||
point_precursor=components[3],
|
||||
keys_in_signature=components[4],
|
||||
signature_for_proxy=components[5],
|
||||
signature_for_bob=components[6])
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
"""
|
||||
Serialize the KFrag into a bytestring.
|
||||
"""
|
||||
key = self.bn_key.to_bytes()
|
||||
commitment = self.point_commitment.to_bytes()
|
||||
precursor = self.point_precursor.to_bytes()
|
||||
signature_for_proxy = bytes(self.signature_for_proxy)
|
||||
signature_for_bob = bytes(self.signature_for_bob)
|
||||
mode = bytes(self.keys_in_signature)
|
||||
|
||||
return self.id + key + commitment + precursor \
|
||||
+ mode + signature_for_proxy + signature_for_bob
|
||||
|
||||
def verify(self,
|
||||
signing_pubkey: UmbralPublicKey,
|
||||
delegating_pubkey: Optional[UmbralPublicKey] = None,
|
||||
receiving_pubkey: Optional[UmbralPublicKey] = None,
|
||||
params: Optional[UmbralParameters] = None,
|
||||
) -> bool:
|
||||
if params is None:
|
||||
params = default_params()
|
||||
|
||||
if signing_pubkey is None:
|
||||
raise ValueError("The verifying pubkey is required to verify this KFrag.")
|
||||
|
||||
if self.delegating_key_in_signature():
|
||||
if delegating_pubkey is None:
|
||||
raise ValueError("The delegating pubkey is required to verify this KFrag.")
|
||||
elif delegating_pubkey.params != params:
|
||||
raise ValueError("The delegating pubkey has different UmbralParameters.")
|
||||
|
||||
if self.receiving_key_in_signature():
|
||||
if receiving_pubkey is None:
|
||||
raise ValueError("The receiving pubkey is required to verify this KFrag.")
|
||||
elif receiving_pubkey.params != params:
|
||||
raise ValueError("The receiving pubkey has different UmbralParameters.")
|
||||
|
||||
u = params.u
|
||||
|
||||
kfrag_id = self.id
|
||||
key = self.bn_key
|
||||
commitment = self.point_commitment
|
||||
precursor = self.point_precursor
|
||||
|
||||
# We check that the commitment is well-formed
|
||||
correct_commitment = commitment == key * u
|
||||
|
||||
validity_input = [kfrag_id, commitment, precursor, self.keys_in_signature]
|
||||
|
||||
if self.delegating_key_in_signature():
|
||||
validity_input.append(delegating_pubkey)
|
||||
|
||||
if self.receiving_key_in_signature():
|
||||
validity_input.append(receiving_pubkey)
|
||||
|
||||
kfrag_validity_message = bytes().join(bytes(item) for item in validity_input)
|
||||
valid_kfrag_signature = self.signature_for_proxy.verify(kfrag_validity_message, signing_pubkey)
|
||||
|
||||
return correct_commitment & valid_kfrag_signature
|
||||
|
||||
def verify_for_capsule(self, capsule) -> bool:
|
||||
correctness_keys = capsule.get_correctness_keys()
|
||||
|
||||
return self.verify(params=capsule.params,
|
||||
signing_pubkey=correctness_keys["verifying"],
|
||||
delegating_pubkey=correctness_keys["delegating"],
|
||||
receiving_pubkey=correctness_keys["receiving"])
|
||||
|
||||
def delegating_key_in_signature(self):
|
||||
return self.keys_in_signature == DELEGATING_ONLY or \
|
||||
self.keys_in_signature == DELEGATING_AND_RECEIVING
|
||||
|
||||
def receiving_key_in_signature(self):
|
||||
return self.keys_in_signature == RECEIVING_ONLY or \
|
||||
self.keys_in_signature == DELEGATING_AND_RECEIVING
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
return self.to_bytes()
|
||||
|
||||
def __eq__(self, other):
|
||||
return hmac.compare_digest(bytes(self), bytes(other))
|
||||
|
||||
def __hash__(self):
|
||||
return hash(bytes(self.id))
|
||||
|
||||
def __repr__(self):
|
||||
return "{}:{}".format(self.__class__.__name__, self.id.hex()[:15])
|
|
@ -1,218 +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 typing
|
||||
from contextlib import contextmanager
|
||||
from cryptography.hazmat.backends.openssl import backend
|
||||
|
||||
|
||||
@typing.no_type_check
|
||||
def _get_new_BN(set_consttime_flag=True):
|
||||
"""
|
||||
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)
|
||||
return new_bn
|
||||
|
||||
|
||||
@typing.no_type_check
|
||||
def _get_ec_group_by_curve_nid(curve_nid: int):
|
||||
"""
|
||||
Returns the group of a given curve via its OpenSSL nid. This must be freed
|
||||
after each use otherwise it leaks memory.
|
||||
"""
|
||||
group = backend._lib.EC_GROUP_new_by_curve_name(curve_nid)
|
||||
backend.openssl_assert(group != backend._ffi.NULL)
|
||||
|
||||
return 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.
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
@typing.no_type_check
|
||||
def _int_to_bn(py_int: int, curve: 'Curve'=None, set_consttime_flag=True):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
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)
|
||||
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):
|
||||
"""
|
||||
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))
|
||||
|
||||
bin_ptr = backend._ffi.new("unsigned char []", length)
|
||||
bin_len = backend._lib.BN_bn2bin(bignum, 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'):
|
||||
"""
|
||||
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)
|
||||
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'):
|
||||
"""
|
||||
Returns an EC_POINT given the group of a curve and the affine coordinates
|
||||
provided.
|
||||
"""
|
||||
new_point = _get_new_EC_POINT(curve)
|
||||
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
|
||||
)
|
||||
backend.openssl_assert(res == 1)
|
||||
return new_point
|
||||
|
||||
|
||||
@typing.no_type_check
|
||||
def _get_affine_coords_via_EC_POINT(ec_point, curve: 'Curve'):
|
||||
"""
|
||||
Returns the affine coordinates of a given point on the provided ec_group.
|
||||
"""
|
||||
affine_x = _get_new_BN()
|
||||
affine_y = _get_new_BN()
|
||||
|
||||
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
|
||||
)
|
||||
backend.openssl_assert(res == 1)
|
||||
return (affine_x, 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.
|
||||
|
||||
try:
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.BN_MONT_CTX_set(bn_mont_ctx, modulus, bn_ctx)
|
||||
backend.openssl_assert(res == 1)
|
||||
yield bn_mont_ctx
|
||||
finally:
|
||||
backend._lib.BN_MONT_CTX_free(bn_mont_ctx)
|
|
@ -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 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
|
||||
|
||||
|
||||
class UmbralParameters:
|
||||
def __init__(self, curve: Curve) -> None:
|
||||
from umbral.point import Point
|
||||
from umbral.random_oracles import unsafe_hash_to_point
|
||||
|
||||
self.curve = curve
|
||||
self.CURVE_KEY_SIZE_BYTES = self.curve.field_order_size_in_bytes
|
||||
|
||||
self.g = Point.get_generator_from_curve(curve=curve)
|
||||
g_bytes = self.g.to_bytes()
|
||||
|
||||
parameters_seed = b'NuCypher/UmbralParameters/'
|
||||
self.u = unsafe_hash_to_point(g_bytes, self, parameters_seed + b'u')
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
|
||||
# TODO: This is not comparing the order, which currently is an OpenSSL pointer
|
||||
self_attributes = self.curve, self.g, self.CURVE_KEY_SIZE_BYTES, self.u
|
||||
others_attributes = other.curve, other.g, other.CURVE_KEY_SIZE_BYTES, other.u
|
||||
|
||||
return self_attributes == others_attributes
|
211
umbral/point.py
211
umbral/point.py
|
@ -1,211 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from cryptography.hazmat.backends.openssl import backend
|
||||
|
||||
from umbral import openssl
|
||||
from umbral.config import default_curve
|
||||
from umbral.curve import Curve
|
||||
from umbral.curvebn import CurveBN
|
||||
|
||||
|
||||
class Point:
|
||||
"""
|
||||
Represents an OpenSSL EC_POINT except more Pythonic
|
||||
"""
|
||||
|
||||
def __init__(self, ec_point, curve: Curve) -> None:
|
||||
self.ec_point = ec_point
|
||||
self.curve = curve
|
||||
|
||||
@classmethod
|
||||
def expected_bytes_length(cls, curve: Optional[Curve] = None,
|
||||
is_compressed: bool = True):
|
||||
"""
|
||||
Returns the size (in bytes) of a Point given a curve.
|
||||
If no curve is provided, it uses the default curve.
|
||||
By default, it assumes compressed representation (is_compressed = True).
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
|
||||
coord_size = curve.field_order_size_in_bytes
|
||||
|
||||
if is_compressed:
|
||||
return 1 + coord_size
|
||||
else:
|
||||
return 1 + 2 * coord_size
|
||||
|
||||
@classmethod
|
||||
def gen_rand(cls, curve: Optional[Curve] = None) -> 'Point':
|
||||
"""
|
||||
Returns a Point object with a cryptographically secure EC_POINT based
|
||||
on the provided curve.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
|
||||
rand_point = openssl._get_new_EC_POINT(curve)
|
||||
rand_bn = CurveBN.gen_rand(curve).bignum
|
||||
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.EC_POINT_mul(
|
||||
curve.ec_group, rand_point, backend._ffi.NULL, curve.generator,
|
||||
rand_bn, bn_ctx
|
||||
)
|
||||
backend.openssl_assert(res == 1)
|
||||
|
||||
return cls(rand_point, curve)
|
||||
|
||||
@classmethod
|
||||
def from_affine(cls, coords: Tuple[int, int], curve: Optional[Curve] = None) -> 'Point':
|
||||
"""
|
||||
Returns a Point object from the given affine coordinates in a tuple in
|
||||
the format of (x, y) and a given curve.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
|
||||
affine_x, affine_y = coords
|
||||
if type(affine_x) == int:
|
||||
affine_x = openssl._int_to_bn(affine_x, curve=None)
|
||||
|
||||
if type(affine_y) == int:
|
||||
affine_y = openssl._int_to_bn(affine_y, curve=None)
|
||||
|
||||
ec_point = openssl._get_EC_POINT_via_affine(affine_x, affine_y, curve)
|
||||
return cls(ec_point, curve)
|
||||
|
||||
def to_affine(self):
|
||||
"""
|
||||
Returns a tuple of Python ints in the format of (x, y) that represents
|
||||
the point in the curve.
|
||||
"""
|
||||
affine_x, affine_y = openssl._get_affine_coords_via_EC_POINT(
|
||||
self.ec_point, self.curve)
|
||||
return (backend._bn_to_int(affine_x), backend._bn_to_int(affine_y))
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes, curve: Optional[Curve] = None) -> 'Point':
|
||||
"""
|
||||
Returns a Point object from the given byte data on the curve provided.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
|
||||
point = openssl._get_new_EC_POINT(curve)
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.EC_POINT_oct2point(
|
||||
curve.ec_group, point, data, len(data), bn_ctx);
|
||||
backend.openssl_assert(res == 1)
|
||||
|
||||
return cls(point, curve)
|
||||
|
||||
def to_bytes(self, is_compressed: bool=True) -> bytes:
|
||||
"""
|
||||
Returns the Point serialized as bytes. It will return a compressed form
|
||||
if is_compressed is set to True.
|
||||
"""
|
||||
length = self.expected_bytes_length(self.curve, is_compressed)
|
||||
|
||||
if is_compressed:
|
||||
point_conversion_form = backend._lib.POINT_CONVERSION_COMPRESSED
|
||||
else:
|
||||
point_conversion_form = backend._lib.POINT_CONVERSION_UNCOMPRESSED
|
||||
|
||||
bin_ptr = backend._ffi.new("unsigned char[]", length)
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
bin_len = backend._lib.EC_POINT_point2oct(
|
||||
self.curve.ec_group, self.ec_point, point_conversion_form,
|
||||
bin_ptr, length, bn_ctx
|
||||
)
|
||||
backend.openssl_assert(bin_len != 0)
|
||||
|
||||
return bytes(backend._ffi.buffer(bin_ptr, bin_len)[:])
|
||||
|
||||
@classmethod
|
||||
def get_generator_from_curve(cls, curve: Optional[Curve] = None) -> 'Point':
|
||||
"""
|
||||
Returns the generator Point from the given curve as a Point object.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
return cls(curve.generator, curve)
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
Compares two EC_POINTS for equality.
|
||||
"""
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
is_equal = backend._lib.EC_POINT_cmp(
|
||||
self.curve.ec_group, self.ec_point, other.ec_point, bn_ctx
|
||||
)
|
||||
backend.openssl_assert(is_equal != -1)
|
||||
|
||||
# 1 is not-equal, 0 is equal, -1 is error
|
||||
return not bool(is_equal)
|
||||
|
||||
def __mul__(self, other: CurveBN) -> 'Point':
|
||||
"""
|
||||
Performs an EC_POINT_mul on an EC_POINT and a BIGNUM.
|
||||
"""
|
||||
# TODO: Check that both points use the same curve.
|
||||
prod = openssl._get_new_EC_POINT(self.curve)
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.EC_POINT_mul(
|
||||
self.curve.ec_group, prod, backend._ffi.NULL,
|
||||
self.ec_point, other.bignum, bn_ctx
|
||||
)
|
||||
backend.openssl_assert(res == 1)
|
||||
|
||||
return Point(prod, self.curve)
|
||||
|
||||
__rmul__ = __mul__
|
||||
|
||||
def __add__(self, other) -> 'Point':
|
||||
"""
|
||||
Performs an EC_POINT_add on two EC_POINTS.
|
||||
"""
|
||||
op_sum = openssl._get_new_EC_POINT(self.curve)
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.EC_POINT_add(
|
||||
self.curve.ec_group, op_sum, self.ec_point, other.ec_point, bn_ctx
|
||||
)
|
||||
backend.openssl_assert(res == 1)
|
||||
return Point(op_sum, self.curve)
|
||||
|
||||
def __sub__(self, other):
|
||||
"""
|
||||
Performs subtraction by adding the inverse of the `other` to the point.
|
||||
"""
|
||||
return (self + (-other))
|
||||
|
||||
def __neg__(self) -> 'Point':
|
||||
"""
|
||||
Computes the additive inverse of a Point, by performing an
|
||||
EC_POINT_invert on itself.
|
||||
"""
|
||||
inv = backend._lib.EC_POINT_dup(self.ec_point, self.curve.ec_group)
|
||||
backend.openssl_assert(inv != backend._ffi.NULL)
|
||||
inv = backend._ffi.gc(inv, backend._lib.EC_POINT_clear_free)
|
||||
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.EC_POINT_invert(
|
||||
self.curve.ec_group, inv, bn_ctx
|
||||
)
|
||||
backend.openssl_assert(res == 1)
|
||||
return Point(inv, self.curve)
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
return self.to_bytes()
|
518
umbral/pre.py
518
umbral/pre.py
|
@ -1,518 +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 typing
|
||||
from typing import Dict, List, Optional, Tuple, Union, Any
|
||||
|
||||
from bytestring_splitter import BytestringSplitter
|
||||
from cryptography.exceptions import InvalidTag
|
||||
from constant_sorrow import constants
|
||||
|
||||
from umbral.cfrags import CapsuleFrag
|
||||
from umbral.config import default_curve
|
||||
from umbral.curve import Curve
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.dem import UmbralDEM, DEM_KEYSIZE, DEM_NONCE_SIZE
|
||||
from umbral.keys import UmbralPrivateKey, UmbralPublicKey
|
||||
from umbral.kfrags import KFrag, NO_KEY, DELEGATING_ONLY, RECEIVING_ONLY, DELEGATING_AND_RECEIVING
|
||||
from umbral.params import UmbralParameters
|
||||
from umbral.point import Point
|
||||
from umbral.random_oracles import kdf, hash_to_curvebn
|
||||
from umbral.signing import Signer
|
||||
from umbral.utils import poly_eval, lambda_coeff
|
||||
|
||||
|
||||
class GenericUmbralError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UmbralCorrectnessError(GenericUmbralError):
|
||||
def __init__(self, message: str, offending_cfrags: List[CapsuleFrag]) -> None:
|
||||
super().__init__(message)
|
||||
self.offending_cfrags = offending_cfrags
|
||||
|
||||
|
||||
class UmbralDecryptionError(GenericUmbralError):
|
||||
def __init__(self) -> None:
|
||||
super().__init__("Decryption of ciphertext failed: "
|
||||
"either someone tampered with the ciphertext or "
|
||||
"you are using an incorrect decryption key.")
|
||||
|
||||
|
||||
class Capsule:
|
||||
|
||||
def __init__(self,
|
||||
params: UmbralParameters,
|
||||
point_e: Point,
|
||||
point_v: Point,
|
||||
bn_sig: CurveBN,
|
||||
) -> None:
|
||||
|
||||
self.params = params
|
||||
|
||||
if not all((isinstance(point_e, Point),
|
||||
isinstance(point_v, Point),
|
||||
isinstance(bn_sig, CurveBN))):
|
||||
raise TypeError("Need valid point_e, point_v, and bn_sig to make a Capsule.")
|
||||
|
||||
self.point_e = point_e
|
||||
self.point_v = point_v
|
||||
self.bn_sig = bn_sig
|
||||
|
||||
self._attached_cfrags = set() # type: set
|
||||
self._cfrag_correctness_keys = {
|
||||
'delegating': None, 'receiving': None, 'verifying': None
|
||||
} # type: dict
|
||||
|
||||
class NotValid(ValueError):
|
||||
"""
|
||||
raised if the capsule does not pass verification.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def expected_bytes_length(cls, curve: Optional[Curve] = None) -> int:
|
||||
"""
|
||||
Returns the size (in bytes) of a Capsule given the curve.
|
||||
If no curve is provided, it will use the default curve.
|
||||
"""
|
||||
curve = curve if curve is not None else default_curve()
|
||||
bn_size = CurveBN.expected_bytes_length(curve)
|
||||
point_size = Point.expected_bytes_length(curve)
|
||||
|
||||
return (bn_size * 1) + (point_size * 2)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, capsule_bytes: bytes, params: UmbralParameters) -> 'Capsule':
|
||||
"""
|
||||
Instantiates a Capsule object from the serialized data.
|
||||
"""
|
||||
curve = params.curve
|
||||
|
||||
bn_size = CurveBN.expected_bytes_length(curve)
|
||||
point_size = Point.expected_bytes_length(curve)
|
||||
arguments = {'curve': curve}
|
||||
|
||||
if len(capsule_bytes) == cls.expected_bytes_length(curve):
|
||||
splitter = BytestringSplitter(
|
||||
(Point, point_size, arguments), # point_e
|
||||
(Point, point_size, arguments), # point_v
|
||||
(CurveBN, bn_size, arguments) # bn_sig
|
||||
)
|
||||
else:
|
||||
raise ValueError("Byte string does not have a valid length for a Capsule")
|
||||
|
||||
components = splitter(capsule_bytes)
|
||||
return cls(params, *components)
|
||||
|
||||
def _set_cfrag_correctness_key(self, key_type: str, key: Optional[UmbralPublicKey]) -> bool:
|
||||
if key_type not in ("delegating", "receiving", "verifying"):
|
||||
raise ValueError("You can only set 'delegating', 'receiving' or 'verifying' keys.")
|
||||
|
||||
current_key = self._cfrag_correctness_keys[key_type]
|
||||
|
||||
if current_key is None:
|
||||
if key is None:
|
||||
return False
|
||||
elif self.params != key.params:
|
||||
raise TypeError("You are trying to set a key with different UmbralParameters.")
|
||||
else:
|
||||
self._cfrag_correctness_keys[key_type] = key
|
||||
return True
|
||||
elif key in (None, current_key):
|
||||
return False
|
||||
else:
|
||||
raise ValueError("The {} key is already set; you can't set it again.".format(key_type))
|
||||
|
||||
def get_correctness_keys(self) -> Dict[str, Union[UmbralPublicKey, None]]:
|
||||
return dict(self._cfrag_correctness_keys)
|
||||
|
||||
def set_correctness_keys(self,
|
||||
delegating: Optional[UmbralPublicKey] = None,
|
||||
receiving: Optional[UmbralPublicKey] = None,
|
||||
verifying: Optional[UmbralPublicKey] = None,
|
||||
) -> Tuple[bool, bool, bool]:
|
||||
|
||||
delegating_key_details = self._set_cfrag_correctness_key(key_type="delegating", key=delegating)
|
||||
receiving_key_details = self._set_cfrag_correctness_key(key_type="receiving", key=receiving)
|
||||
verifying_key_details = self._set_cfrag_correctness_key(key_type="verifying", key=verifying)
|
||||
|
||||
return delegating_key_details, receiving_key_details, verifying_key_details
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
"""
|
||||
Serialize the Capsule into a bytestring.
|
||||
"""
|
||||
e, v, s = self.components()
|
||||
return e.to_bytes() + v.to_bytes() + s.to_bytes()
|
||||
|
||||
def verify(self) -> bool:
|
||||
|
||||
g = self.params.g
|
||||
e, v, s = self.components()
|
||||
h = hash_to_curvebn(e, v, params=self.params)
|
||||
|
||||
result = s * g == v + (h * e) # type: bool
|
||||
return result
|
||||
|
||||
def attach_cfrag(self, cfrag: CapsuleFrag) -> None:
|
||||
if cfrag.verify_correctness(self):
|
||||
self._attached_cfrags.add(cfrag)
|
||||
else:
|
||||
error_msg = "CFrag is not correct and cannot be attached to the Capsule"
|
||||
raise UmbralCorrectnessError(error_msg, [cfrag])
|
||||
|
||||
def clear_cfrags(self):
|
||||
self._attached_cfrags = set()
|
||||
|
||||
def first_cfrag(self):
|
||||
try:
|
||||
return list(self._attached_cfrags)[0]
|
||||
except IndexError:
|
||||
raise TypeError("This Capsule doesn't have any CFrags attached. Ergo, you can't get the first one.")
|
||||
|
||||
def components(self) -> Tuple[Point, Point, CurveBN]:
|
||||
return self.point_e, self.point_v, self.bn_sig
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
return self.to_bytes()
|
||||
|
||||
def __contains__(self, cfrag):
|
||||
return cfrag in self._attached_cfrags
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
"""
|
||||
Each component is compared to its counterpart in constant time per the __eq__ of Point and CurveBN.
|
||||
"""
|
||||
return hasattr(other, "components") and self.components() == other.components() and all(self.components())
|
||||
|
||||
@typing.no_type_check
|
||||
def __hash__(self) -> int:
|
||||
# In case this isn't obvious, don't use this as a secure hash. Use BLAKE2b or something.
|
||||
component_bytes = tuple(component.to_bytes() for component in self.components())
|
||||
return hash(component_bytes)
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._attached_cfrags)
|
||||
|
||||
def __repr__(self):
|
||||
return "{}:{}".format(self.__class__.__name__, hex(int(self.bn_sig))[2:17])
|
||||
|
||||
|
||||
def generate_kfrags(delegating_privkey: UmbralPrivateKey,
|
||||
receiving_pubkey: UmbralPublicKey,
|
||||
threshold: int,
|
||||
N: int,
|
||||
signer: Signer,
|
||||
sign_delegating_key: Optional[bool] = True,
|
||||
sign_receiving_key: Optional[bool] = True,
|
||||
) -> List[KFrag]:
|
||||
"""
|
||||
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.
|
||||
|
||||
Returns a list of N KFrags
|
||||
"""
|
||||
|
||||
if threshold <= 0 or threshold > N:
|
||||
raise ValueError('Arguments threshold and N must satisfy 0 < threshold <= N')
|
||||
|
||||
if delegating_privkey.params != receiving_pubkey.params:
|
||||
raise ValueError("Keys must have the same parameter set.")
|
||||
|
||||
params = delegating_privkey.params
|
||||
|
||||
g = params.g
|
||||
|
||||
delegating_pubkey = delegating_privkey.get_pubkey()
|
||||
|
||||
bob_pubkey_point = receiving_pubkey.point_key
|
||||
|
||||
# The precursor point is used as an ephemeral public key in a DH key exchange,
|
||||
# and the resulting shared secret 'dh_point' is used to derive other secret values
|
||||
private_precursor = CurveBN.gen_rand(params.curve)
|
||||
precursor = private_precursor * g # type: Any
|
||||
|
||||
dh_point = private_precursor * bob_pubkey_point
|
||||
|
||||
# Secret value 'd' allows to make Umbral non-interactive
|
||||
d = hash_to_curvebn(precursor,
|
||||
bob_pubkey_point,
|
||||
dh_point,
|
||||
bytes(constants.NON_INTERACTIVE),
|
||||
params=params)
|
||||
|
||||
# Coefficients of the generating polynomial
|
||||
coefficients = [delegating_privkey.bn_key * (~d)]
|
||||
coefficients += [CurveBN.gen_rand(params.curve) for _ in range(threshold - 1)]
|
||||
|
||||
bn_size = CurveBN.expected_bytes_length(params.curve)
|
||||
|
||||
kfrags = list()
|
||||
for _ in range(N):
|
||||
kfrag_id = os.urandom(bn_size)
|
||||
|
||||
# The index of the re-encryption key share (which in Shamir's Secret
|
||||
# Sharing corresponds to x in the tuple (x, f(x)), with f being the
|
||||
# generating polynomial), is used to prevent reconstruction of the
|
||||
# re-encryption key without Bob's intervention
|
||||
share_index = hash_to_curvebn(precursor,
|
||||
bob_pubkey_point,
|
||||
dh_point,
|
||||
bytes(constants.X_COORDINATE),
|
||||
kfrag_id,
|
||||
params=params)
|
||||
|
||||
# The re-encryption key share is the result of evaluating the generating
|
||||
# polynomial for the index value
|
||||
rk = poly_eval(coefficients, share_index)
|
||||
|
||||
commitment = rk * params.u # type: Any
|
||||
|
||||
validity_message_for_bob = (kfrag_id,
|
||||
delegating_pubkey,
|
||||
receiving_pubkey,
|
||||
commitment,
|
||||
precursor,
|
||||
) # type: Any
|
||||
validity_message_for_bob = bytes().join(bytes(item) for item in validity_message_for_bob)
|
||||
signature_for_bob = signer(validity_message_for_bob)
|
||||
|
||||
if sign_delegating_key and sign_receiving_key:
|
||||
mode = DELEGATING_AND_RECEIVING
|
||||
elif sign_delegating_key:
|
||||
mode = DELEGATING_ONLY
|
||||
elif sign_receiving_key:
|
||||
mode = RECEIVING_ONLY
|
||||
else:
|
||||
mode = NO_KEY
|
||||
|
||||
validity_message_for_proxy = [kfrag_id, commitment, precursor, mode] # type: Any
|
||||
|
||||
if sign_delegating_key:
|
||||
validity_message_for_proxy.append(delegating_pubkey)
|
||||
if sign_receiving_key:
|
||||
validity_message_for_proxy.append(receiving_pubkey)
|
||||
|
||||
validity_message_for_proxy = bytes().join(bytes(item) for item in validity_message_for_proxy)
|
||||
signature_for_proxy = signer(validity_message_for_proxy)
|
||||
|
||||
kfrag = KFrag(identifier=kfrag_id,
|
||||
bn_key=rk,
|
||||
point_commitment=commitment,
|
||||
point_precursor=precursor,
|
||||
signature_for_proxy=signature_for_proxy,
|
||||
signature_for_bob=signature_for_bob,
|
||||
keys_in_signature=mode,
|
||||
)
|
||||
|
||||
kfrags.append(kfrag)
|
||||
|
||||
return kfrags
|
||||
|
||||
|
||||
def reencrypt(kfrag: KFrag,
|
||||
capsule: Capsule,
|
||||
provide_proof: bool = True,
|
||||
metadata: Optional[bytes] = None,
|
||||
verify_kfrag: bool = True) -> CapsuleFrag:
|
||||
|
||||
if not isinstance(capsule, Capsule) or not capsule.verify():
|
||||
raise Capsule.NotValid
|
||||
|
||||
if verify_kfrag:
|
||||
if not isinstance(kfrag, KFrag) or not kfrag.verify_for_capsule(capsule):
|
||||
raise KFrag.NotValid
|
||||
|
||||
rk = kfrag.bn_key
|
||||
e1 = rk * capsule.point_e # type: Any
|
||||
v1 = rk * capsule.point_v # type: Any
|
||||
|
||||
cfrag = CapsuleFrag(point_e1=e1, point_v1=v1, kfrag_id=kfrag.id,
|
||||
point_precursor=kfrag.point_precursor)
|
||||
|
||||
if provide_proof:
|
||||
cfrag.prove_correctness(capsule, kfrag, metadata)
|
||||
|
||||
return cfrag
|
||||
|
||||
|
||||
def _encapsulate(alice_pubkey: UmbralPublicKey,
|
||||
key_length: int = DEM_KEYSIZE) -> Tuple[bytes, Capsule]:
|
||||
"""Generates a symmetric key and its associated KEM ciphertext"""
|
||||
|
||||
params = alice_pubkey.params
|
||||
g = params.g
|
||||
|
||||
priv_r = CurveBN.gen_rand(params.curve)
|
||||
pub_r = priv_r * g # type: Any
|
||||
|
||||
priv_u = CurveBN.gen_rand(params.curve)
|
||||
pub_u = priv_u * g # type: Any
|
||||
|
||||
h = hash_to_curvebn(pub_r, pub_u, params=params)
|
||||
s = priv_u + (priv_r * h)
|
||||
|
||||
shared_key = (priv_r + priv_u) * alice_pubkey.point_key # type: Any
|
||||
|
||||
# Key to be used for symmetric encryption
|
||||
key = kdf(shared_key, key_length)
|
||||
|
||||
return key, Capsule(point_e=pub_r, point_v=pub_u, bn_sig=s, params=params)
|
||||
|
||||
|
||||
def _decapsulate_original(private_key: UmbralPrivateKey,
|
||||
capsule: Capsule,
|
||||
key_length: int = DEM_KEYSIZE) -> bytes:
|
||||
"""Derive the same symmetric key"""
|
||||
|
||||
if not capsule.verify():
|
||||
# Check correctness of original ciphertext
|
||||
raise capsule.NotValid("Capsule verification failed.")
|
||||
|
||||
shared_key = private_key.bn_key * (capsule.point_e + capsule.point_v) # type: Any
|
||||
key = kdf(shared_key, key_length)
|
||||
return key
|
||||
|
||||
|
||||
def _decapsulate_reencrypted(receiving_privkey: UmbralPrivateKey, capsule: Capsule,
|
||||
key_length: int = DEM_KEYSIZE) -> bytes:
|
||||
"""Derive the same symmetric encapsulated_key"""
|
||||
|
||||
params = capsule.params
|
||||
|
||||
pub_key = receiving_privkey.get_pubkey().point_key
|
||||
priv_key = receiving_privkey.bn_key
|
||||
|
||||
precursor = capsule.first_cfrag().point_precursor
|
||||
dh_point = priv_key * precursor
|
||||
|
||||
# Combination of CFrags via Shamir's Secret Sharing reconstruction
|
||||
xs = list()
|
||||
for cfrag in capsule._attached_cfrags:
|
||||
x = hash_to_curvebn(precursor,
|
||||
pub_key,
|
||||
dh_point,
|
||||
bytes(constants.X_COORDINATE),
|
||||
cfrag.kfrag_id,
|
||||
params=params)
|
||||
xs.append(x)
|
||||
|
||||
e_summands, v_summands = list(), list()
|
||||
for cfrag, x in zip(capsule._attached_cfrags, xs):
|
||||
if precursor != cfrag.point_precursor:
|
||||
raise ValueError("Attached CFrags are not pairwise consistent")
|
||||
lambda_i = lambda_coeff(x, xs)
|
||||
e_summands.append(lambda_i * cfrag.point_e1)
|
||||
v_summands.append(lambda_i * cfrag.point_v1)
|
||||
|
||||
e_prime = sum(e_summands[1:], e_summands[0])
|
||||
v_prime = sum(v_summands[1:], v_summands[0])
|
||||
|
||||
# Secret value 'd' allows to make Umbral non-interactive
|
||||
d = hash_to_curvebn(precursor,
|
||||
pub_key,
|
||||
dh_point,
|
||||
bytes(constants.NON_INTERACTIVE),
|
||||
params=params)
|
||||
|
||||
e, v, s = capsule.components()
|
||||
h = hash_to_curvebn(e, v, params=params)
|
||||
|
||||
orig_pub_key = capsule.get_correctness_keys()['delegating'].point_key # type: ignore
|
||||
|
||||
if not (s / d) * orig_pub_key == (h * e_prime) + v_prime:
|
||||
raise GenericUmbralError()
|
||||
|
||||
shared_key = d * (e_prime + v_prime)
|
||||
encapsulated_key = kdf(shared_key, key_length)
|
||||
return encapsulated_key
|
||||
|
||||
|
||||
def encrypt(alice_pubkey: UmbralPublicKey, plaintext: bytes) -> Tuple[bytes, Capsule]:
|
||||
"""
|
||||
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.
|
||||
|
||||
We hope that's a symmetric key, which we use to decrypt the ciphertext
|
||||
and return the resulting cleartext.
|
||||
"""
|
||||
|
||||
if not isinstance(ciphertext, bytes) or len(ciphertext) < DEM_NONCE_SIZE:
|
||||
raise ValueError("Input ciphertext must be a bytes object of length >= {}".format(DEM_NONCE_SIZE))
|
||||
elif not isinstance(capsule, Capsule) or not capsule.verify():
|
||||
raise Capsule.NotValid
|
||||
elif not isinstance(decrypting_key, UmbralPrivateKey):
|
||||
raise TypeError("The decrypting key is not an UmbralPrivateKey")
|
||||
|
||||
if capsule._attached_cfrags:
|
||||
# Since there are cfrags attached, we assume this is Bob opening the Capsule.
|
||||
# (i.e., this is a re-encrypted capsule)
|
||||
encapsulated_key = _open_capsule(capsule, decrypting_key, check_proof=check_proof)
|
||||
else:
|
||||
# Since there aren't cfrags attached, we assume this is Alice opening the Capsule.
|
||||
# (i.e., this is an original capsule)
|
||||
encapsulated_key = _decapsulate_original(decrypting_key, capsule)
|
||||
|
||||
dem = UmbralDEM(encapsulated_key)
|
||||
try:
|
||||
cleartext = dem.decrypt(ciphertext, authenticated_data=bytes(capsule))
|
||||
except InvalidTag as e:
|
||||
raise UmbralDecryptionError() from e
|
||||
|
||||
return cleartext
|
|
@ -1,214 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from abc import abstractmethod, ABC
|
||||
from typing import Optional, Type
|
||||
|
||||
from cryptography.hazmat.backends.openssl import backend
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
||||
from cryptography.exceptions import InternalError
|
||||
|
||||
import sha3
|
||||
|
||||
from umbral import openssl
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.point import Point
|
||||
from umbral.params import UmbralParameters
|
||||
from umbral.config import default_params
|
||||
|
||||
|
||||
class Hash(ABC):
|
||||
|
||||
CUSTOMIZATION_STRING_LENGTH = 64
|
||||
CUSTOMIZATION_STRING_PAD = b'\x00'
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self, customization_string: bytes = b''):
|
||||
|
||||
if len(customization_string) > Hash.CUSTOMIZATION_STRING_LENGTH:
|
||||
raise ValueError("The maximum length of the customization string is "
|
||||
"{} bytes".format(Hash.CUSTOMIZATION_STRING_LENGTH))
|
||||
|
||||
self.customization_string = customization_string.ljust(
|
||||
Hash.CUSTOMIZATION_STRING_LENGTH,
|
||||
Hash.CUSTOMIZATION_STRING_PAD
|
||||
)
|
||||
self.update(self.customization_string)
|
||||
|
||||
@abstractmethod
|
||||
def update(self, data: bytes) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def copy(self) -> 'Hash':
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def finalize(self) -> bytes:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Blake2b(Hash):
|
||||
def __init__(self, customization_string: bytes = b''):
|
||||
# TODO: use a Blake2b implementation that supports personalization (see #155)
|
||||
self._blake2b = hashes.Hash(hashes.BLAKE2b(64), backend=backend)
|
||||
super().__init__(customization_string)
|
||||
|
||||
def update(self, data: bytes) -> None:
|
||||
self._blake2b.update(data)
|
||||
|
||||
def copy(self) -> 'Blake2b':
|
||||
replica = type(self)()
|
||||
replica._blake2b = self._blake2b.copy()
|
||||
return replica
|
||||
|
||||
def finalize(self) -> bytes:
|
||||
return self._blake2b.finalize()
|
||||
|
||||
|
||||
class ExtendedKeccak(Hash):
|
||||
|
||||
_UPPER_PREFIX = b'\x00'
|
||||
_LOWER_PREFIX = b'\x01'
|
||||
|
||||
def __init__(self, customization_string: bytes = b''):
|
||||
self._upper = sha3.keccak_256()
|
||||
self._lower = sha3.keccak_256()
|
||||
|
||||
self._upper.update(self._UPPER_PREFIX)
|
||||
self._lower.update(self._LOWER_PREFIX)
|
||||
|
||||
super().__init__(customization_string)
|
||||
|
||||
def update(self, data: bytes) -> None:
|
||||
self._upper.update(data)
|
||||
self._lower.update(data)
|
||||
|
||||
def copy(self) -> 'ExtendedKeccak':
|
||||
replica = type(self)()
|
||||
replica._upper = self._upper.copy()
|
||||
replica._lower = self._lower.copy()
|
||||
return replica
|
||||
|
||||
def finalize(self) -> bytes:
|
||||
return self._upper.digest() + self._lower.digest()
|
||||
|
||||
|
||||
def kdf(ecpoint: Point,
|
||||
key_length: int,
|
||||
salt: Optional[bytes] = None,
|
||||
info: Optional[bytes] = None,
|
||||
) -> bytes:
|
||||
|
||||
data = ecpoint.to_bytes(is_compressed=True)
|
||||
hkdf = HKDF(algorithm=hashes.BLAKE2b(64),
|
||||
length=key_length,
|
||||
salt=salt,
|
||||
info=info,
|
||||
backend=default_backend())
|
||||
return hkdf.derive(data)
|
||||
|
||||
|
||||
# TODO: Common API for all hash_to_curvebn functions.
|
||||
# TODO: ^ It should check the correct number and type args, instead of current approach.
|
||||
def hash_to_curvebn(*crypto_items,
|
||||
params: UmbralParameters,
|
||||
customization_string: bytes = b'',
|
||||
hash_class: Type[Hash] = Blake2b) -> CurveBN:
|
||||
|
||||
customization_string = b'hash_to_curvebn' + customization_string
|
||||
hash_function = hash_class(customization_string=customization_string)
|
||||
|
||||
for item in crypto_items:
|
||||
try:
|
||||
item_bytes = item.to_bytes()
|
||||
except AttributeError:
|
||||
if isinstance(item, bytes):
|
||||
item_bytes = item
|
||||
else:
|
||||
raise TypeError("Input with type {} not accepted".format(type(item)))
|
||||
hash_function.update(item_bytes)
|
||||
|
||||
hash_digest = openssl._bytes_to_bn(hash_function.finalize())
|
||||
|
||||
one = backend._lib.BN_value_one()
|
||||
|
||||
order_minus_1 = openssl._get_new_BN()
|
||||
res = backend._lib.BN_sub(order_minus_1, params.curve.order, one)
|
||||
backend.openssl_assert(res == 1)
|
||||
|
||||
bignum = openssl._get_new_BN()
|
||||
with backend._tmp_bn_ctx() as bn_ctx:
|
||||
res = backend._lib.BN_mod(bignum, hash_digest, order_minus_1, bn_ctx)
|
||||
backend.openssl_assert(res == 1)
|
||||
|
||||
res = backend._lib.BN_add(bignum, bignum, one)
|
||||
backend.openssl_assert(res == 1)
|
||||
|
||||
return CurveBN(bignum, params.curve)
|
||||
|
||||
|
||||
def unsafe_hash_to_point(data: bytes = b'',
|
||||
params: UmbralParameters = None,
|
||||
label: bytes = b'',
|
||||
hash_class = Blake2b,
|
||||
) -> 'Point':
|
||||
"""
|
||||
Hashes arbitrary data into a valid EC point of the specified curve,
|
||||
using the try-and-increment method.
|
||||
It admits an optional label as an additional input to the hash function.
|
||||
It uses BLAKE2b (with a digest size of 64 bytes) as the internal hash function.
|
||||
|
||||
WARNING: Do not use when the input data is secret, as this implementation is not
|
||||
in constant time, and hence, it is not safe with respect to timing attacks.
|
||||
"""
|
||||
|
||||
params = params if params is not None else default_params()
|
||||
|
||||
len_data = len(data).to_bytes(4, byteorder='big')
|
||||
len_label = len(label).to_bytes(4, byteorder='big')
|
||||
|
||||
label_data = len_label + label + len_data + data
|
||||
|
||||
# We use an internal 32-bit counter as additional input
|
||||
i = 0
|
||||
while i < 2**32:
|
||||
ibytes = i.to_bytes(4, byteorder='big')
|
||||
hash_function = hash_class()
|
||||
hash_function.update(label_data + ibytes)
|
||||
hash_digest = hash_function.finalize()[:1 + params.CURVE_KEY_SIZE_BYTES]
|
||||
|
||||
sign = b'\x02' if hash_digest[0] & 1 == 0 else b'\x03'
|
||||
compressed_point = sign + hash_digest[1:]
|
||||
|
||||
try:
|
||||
return Point.from_bytes(compressed_point, params.curve)
|
||||
except InternalError as e:
|
||||
# We want to catch specific InternalExceptions:
|
||||
# - Point not in the curve (code 107)
|
||||
# - Invalid compressed point (code 110)
|
||||
# https://github.com/openssl/openssl/blob/master/include/openssl/ecerr.h#L228
|
||||
if e.err_code[0].reason in (107, 110):
|
||||
pass
|
||||
else:
|
||||
# Any other exception, we raise it
|
||||
raise e
|
||||
i += 1
|
||||
|
||||
# Only happens with probability 2^(-32)
|
||||
raise ValueError('Could not hash input into the curve')
|
|
@ -1,151 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published b
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import hmac
|
||||
from typing import Optional, Type
|
||||
|
||||
from cryptography.exceptions import InvalidSignature
|
||||
from cryptography.hazmat.primitives.hashes import HashAlgorithm, SHA256
|
||||
from cryptography.hazmat.primitives.asymmetric import utils
|
||||
from cryptography.hazmat.primitives.asymmetric.ec import ECDSA
|
||||
|
||||
|
||||
from umbral.config import default_curve
|
||||
from umbral.curve import Curve
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.keys import UmbralPublicKey, UmbralPrivateKey
|
||||
|
||||
|
||||
DEFAULT_HASH_ALGORITHM = SHA256
|
||||
|
||||
|
||||
class Signature:
|
||||
"""
|
||||
Wrapper for ECDSA signatures.
|
||||
We store signatures as r and s; this class allows interoperation
|
||||
between (r, s) and DER formatting.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
r: CurveBN,
|
||||
s: CurveBN,
|
||||
hash_algorithm: Type[HashAlgorithm] = DEFAULT_HASH_ALGORITHM) -> None:
|
||||
self.r = r
|
||||
self.s = s
|
||||
self.hash_algorithm = hash_algorithm
|
||||
|
||||
def __repr__(self):
|
||||
return "ECDSA Signature: {}".format(bytes(self).hex()[:15])
|
||||
|
||||
@classmethod
|
||||
def expected_bytes_length(cls, curve: Optional[Curve] = None) -> int:
|
||||
curve = curve if curve is not None else default_curve()
|
||||
return 2 * curve.group_order_size_in_bytes
|
||||
|
||||
def verify(self, message: bytes,
|
||||
verifying_key: UmbralPublicKey,
|
||||
is_prehashed: bool = False) -> bool:
|
||||
"""
|
||||
Verifies that a message's signature was valid.
|
||||
|
||||
:param message: The message to verify
|
||||
:param verifying_key: UmbralPublicKey of the signer
|
||||
:param is_prehashed: True if the message has been prehashed previously
|
||||
:return: True if valid, False if invalid
|
||||
"""
|
||||
cryptography_pub_key = verifying_key.to_cryptography_pubkey()
|
||||
if is_prehashed:
|
||||
signature_algorithm = ECDSA(utils.Prehashed(self.hash_algorithm()))
|
||||
else:
|
||||
signature_algorithm = ECDSA(self.hash_algorithm())
|
||||
|
||||
# TODO: Raise error instead of returning boolean
|
||||
try:
|
||||
cryptography_pub_key.verify(
|
||||
signature=self._der_encoded_bytes(),
|
||||
data=message,
|
||||
signature_algorithm=signature_algorithm
|
||||
)
|
||||
except InvalidSignature:
|
||||
return False
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls,
|
||||
signature_as_bytes: bytes,
|
||||
der_encoded: bool = False,
|
||||
curve: Optional[Curve] = None) -> 'Signature':
|
||||
curve = curve if curve is not None else default_curve()
|
||||
if der_encoded:
|
||||
r, s = utils.decode_dss_signature(signature_as_bytes)
|
||||
else:
|
||||
expected_len = cls.expected_bytes_length(curve)
|
||||
if not len(signature_as_bytes) == expected_len:
|
||||
raise ValueError("Looking for exactly {} bytes if you call from_bytes \
|
||||
with der_encoded=False and curve={}.".format(expected_len, curve))
|
||||
else:
|
||||
r = int.from_bytes(signature_as_bytes[:(expected_len//2)], "big")
|
||||
s = int.from_bytes(signature_as_bytes[(expected_len//2):], "big")
|
||||
|
||||
return cls(CurveBN.from_int(r, curve), CurveBN.from_int(s, curve))
|
||||
|
||||
def _der_encoded_bytes(self) -> bytes:
|
||||
return utils.encode_dss_signature(int(self.r), int(self.s))
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
return self.r.to_bytes() + self.s.to_bytes()
|
||||
|
||||
def __len__(self):
|
||||
return len(bytes(self))
|
||||
|
||||
def __add__(self, other):
|
||||
return bytes(self) + other
|
||||
|
||||
def __radd__(self, other: bytes) -> bytes:
|
||||
return other + bytes(self)
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
simple_bytes_match = hmac.compare_digest(bytes(self), bytes(other))
|
||||
der_encoded_match = hmac.compare_digest(self._der_encoded_bytes(), bytes(other))
|
||||
return simple_bytes_match or der_encoded_match
|
||||
|
||||
|
||||
class Signer:
|
||||
"""Callable wrapping ECDSA signing with UmbralPrivateKeys"""
|
||||
|
||||
def __init__(self,
|
||||
private_key: UmbralPrivateKey,
|
||||
hash_algorithm: Type[HashAlgorithm] = DEFAULT_HASH_ALGORITHM) -> None:
|
||||
self.__cryptography_private_key = private_key.to_cryptography_privkey()
|
||||
self.curve = private_key.params.curve
|
||||
self.hash_algorithm = hash_algorithm
|
||||
|
||||
def __call__(self, message: bytes, is_prehashed: bool = False) -> Signature:
|
||||
"""
|
||||
Signs the message with this instance's private key.
|
||||
|
||||
:param message: Message to hash and sign
|
||||
:param is_prehashed: True if the message has been prehashed previously
|
||||
:return: signature
|
||||
"""
|
||||
if is_prehashed:
|
||||
signature_algorithm = ECDSA(utils.Prehashed(self.hash_algorithm()))
|
||||
else:
|
||||
signature_algorithm = ECDSA(self.hash_algorithm())
|
||||
|
||||
signature_der_bytes = self.__cryptography_private_key.sign(message, signature_algorithm)
|
||||
return Signature.from_bytes(signature_der_bytes, der_encoded=True, curve=self.curve)
|
|
@ -1,41 +0,0 @@
|
|||
"""
|
||||
This file is part of pyUmbral.
|
||||
|
||||
pyUmbral is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published b
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
pyUmbral is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with pyUmbral. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from typing import List
|
||||
|
||||
from umbral.curvebn import CurveBN
|
||||
|
||||
|
||||
def lambda_coeff(id_i: CurveBN, selected_ids: List[CurveBN]) -> CurveBN:
|
||||
ids = [x for x in selected_ids if x != id_i]
|
||||
|
||||
if not ids:
|
||||
return CurveBN.from_int(1, id_i.curve)
|
||||
|
||||
result = ids[0] / (ids[0] - id_i)
|
||||
for id_j in ids[1:]:
|
||||
result = result * id_j / (id_j - id_i)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def poly_eval(coeff: List[CurveBN], x: CurveBN) -> CurveBN:
|
||||
result = coeff[-1]
|
||||
for i in range(-2, -len(coeff) - 1, -1):
|
||||
result = (result * x) + coeff[i]
|
||||
|
||||
return result
|
Loading…
Reference in New Issue