diff --git a/docs/source/application_development/porter.rst b/docs/source/application_development/porter.rst index ac5a06a00..5ed2ee81a 100644 --- a/docs/source/application_development/porter.rst +++ b/docs/source/application_development/porter.rst @@ -475,8 +475,8 @@ Parameters +-------------------------------------------+---------------+----------------------------------------+ | **Parameter** | **Type** | **Description** | +===========================================+===============+========================================+ -| ``treasure_map`` | String | | Decrypted treasure map bytes encoded | -| | | | as base64. | +| ``treasure_map`` | String | | Unencrypted treasure map bytes | +| | | | encoded as base64. | +-------------------------------------------+---------------+----------------------------------------+ | ``retrieval_kits`` | List[String] | | List of retrieval kits bytes encoded | | | | | as base64. | diff --git a/nucypher/characters/control/specifications/bob.py b/nucypher/characters/control/specifications/bob.py index 5e7e62c2e..535762f85 100644 --- a/nucypher/characters/control/specifications/bob.py +++ b/nucypher/characters/control/specifications/bob.py @@ -20,8 +20,8 @@ import click import nucypher.control.specifications.fields as base_fields from nucypher.characters.control.specifications import fields as character_fields from nucypher.characters.control.specifications.fields.treasuremap import EncryptedTreasureMap -from nucypher.control.specifications.base import BaseSchema from nucypher.cli import options +from nucypher.control.specifications.base import BaseSchema class RetrieveAndDecrypt(BaseSchema): @@ -46,17 +46,6 @@ class RetrieveAndDecrypt(BaseSchema): load_only=True, click=options.option_message_kit(required=True) ) - # optional - treasure_map = character_fields.TreasureMap( - required=False, - load_only=True, - click=click.option( - '--treasure-map', - '-t', - help="Treasure Map for retrieve", - type=click.STRING, - required=False)) - encrypted_treasure_map = EncryptedTreasureMap(required=True, load_only=True, click=options.option_treasure_map) diff --git a/nucypher/characters/control/specifications/exceptions.py b/nucypher/characters/control/specifications/exceptions.py deleted file mode 100644 index ec74c47bc..000000000 --- a/nucypher/characters/control/specifications/exceptions.py +++ /dev/null @@ -1,22 +0,0 @@ -""" - This file is part of nucypher. - - nucypher is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - nucypher 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with nucypher. If not, see . -""" - -from bytestring_splitter import BytestringSplittingError -from cryptography.exceptions import InternalError - -# TODO: catch cryptography.exceptions.InternalError in PyUmbral -InvalidNativeDataTypes = (ValueError, TypeError, BytestringSplittingError, InternalError) diff --git a/nucypher/characters/control/specifications/fields/key.py b/nucypher/characters/control/specifications/fields/key.py index e33020d78..c55b701d0 100644 --- a/nucypher/characters/control/specifications/fields/key.py +++ b/nucypher/characters/control/specifications/fields/key.py @@ -17,8 +17,7 @@ from marshmallow import fields -from nucypher.characters.control.specifications.exceptions import InvalidNativeDataTypes -from nucypher.control.specifications.exceptions import InvalidInputData +from nucypher.control.specifications.exceptions import InvalidInputData, InvalidNativeDataTypes from nucypher.control.specifications.fields.base import BaseField from nucypher.crypto.umbral_adapter import PublicKey diff --git a/nucypher/characters/control/specifications/fields/messagekit.py b/nucypher/characters/control/specifications/fields/messagekit.py index e7b96412e..fd9211a31 100644 --- a/nucypher/characters/control/specifications/fields/messagekit.py +++ b/nucypher/characters/control/specifications/fields/messagekit.py @@ -19,8 +19,7 @@ from base64 import b64decode, b64encode from marshmallow import fields -from nucypher.characters.control.specifications.exceptions import InvalidNativeDataTypes -from nucypher.control.specifications.exceptions import InvalidInputData +from nucypher.control.specifications.exceptions import InvalidInputData, InvalidNativeDataTypes from nucypher.control.specifications.fields.base import BaseField from nucypher.policy.kits import MessageKit as MessageKitClass diff --git a/nucypher/characters/control/specifications/fields/signature.py b/nucypher/characters/control/specifications/fields/signature.py index 420b62009..cda78a710 100644 --- a/nucypher/characters/control/specifications/fields/signature.py +++ b/nucypher/characters/control/specifications/fields/signature.py @@ -19,8 +19,7 @@ from base64 import b64decode, b64encode from marshmallow import fields -from nucypher.characters.control.specifications.exceptions import InvalidNativeDataTypes -from nucypher.control.specifications.exceptions import InvalidInputData +from nucypher.control.specifications.exceptions import InvalidInputData, InvalidNativeDataTypes from nucypher.control.specifications.fields.base import BaseField from nucypher.crypto.umbral_adapter import Signature diff --git a/nucypher/characters/control/specifications/fields/treasuremap.py b/nucypher/characters/control/specifications/fields/treasuremap.py index 8196bd951..af8559789 100644 --- a/nucypher/characters/control/specifications/fields/treasuremap.py +++ b/nucypher/characters/control/specifications/fields/treasuremap.py @@ -15,40 +15,30 @@ along with nucypher. If not, see . """ -from base64 import b64decode, b64encode - -from marshmallow import fields - from nucypher.control.specifications.exceptions import InvalidInputData -from nucypher.control.specifications.fields.base import BaseField -from nucypher.policy.maps import EncryptedTreasureMap as EncryptedTreasureMapClass, TreasureMap as TreaureMapClass +from nucypher.control.specifications.fields.base import Base64BytesRepresentation +from nucypher.policy.maps import EncryptedTreasureMap as EncryptedTreasureMapClass, TreasureMap as TreasureMapClass -class EncryptedTreasureMap(BaseField, fields.Field): +class EncryptedTreasureMap(Base64BytesRepresentation): """ JSON Parameter representation of EncryptedTreasureMap. """ - def _serialize(self, value, attr, obj, **kwargs): - return b64encode(bytes(value)).decode() - def _deserialize(self, value, attr, data, **kwargs): try: - treasure_map_bytes = b64decode(value) - return EncryptedTreasureMapClass.from_bytes(treasure_map_bytes) + encrypted_treasure_map_bytes = super()._deserialize(value, attr, data, **kwargs) + return EncryptedTreasureMapClass.from_bytes(encrypted_treasure_map_bytes) except Exception as e: raise InvalidInputData(f"Could not convert input for {self.name} to an EncryptedTreasureMap: {e}") from e -class TreasureMap(BaseField, fields.Field): +class TreasureMap(Base64BytesRepresentation): """ - JSON Parameter representation of (decrypted) TreasureMap. + JSON Parameter representation of (unencrypted) TreasureMap. """ - def _serialize(self, value, attr, obj, **kwargs): - return b64encode(bytes(value)).decode() - def _deserialize(self, value, attr, data, **kwargs): try: - treasure_map_bytes = b64decode(value) - return TreaureMapClass.from_bytes(treasure_map_bytes) + treasure_map_bytes = super()._deserialize(value, attr, data, **kwargs) + return TreasureMapClass.from_bytes(treasure_map_bytes) except Exception as e: - raise InvalidInputData(f"Could not convert input for {self.name} to a TreaureMap: {e}") from e \ No newline at end of file + raise InvalidInputData(f"Could not convert input for {self.name} to a TreasureMap: {e}") from e diff --git a/nucypher/control/specifications/exceptions.py b/nucypher/control/specifications/exceptions.py index c7efb288a..51ff7825c 100644 --- a/nucypher/control/specifications/exceptions.py +++ b/nucypher/control/specifications/exceptions.py @@ -14,6 +14,8 @@ You should have received a copy of the GNU Affero General Public License along with nucypher. If not, see . """ +from bytestring_splitter import BytestringSplittingError +from cryptography.exceptions import InternalError class SpecificationError(ValueError): @@ -34,3 +36,6 @@ class InvalidOutputData(SpecificationError): class InvalidArgumentCombo(SpecificationError): """Arguments specified are incompatible""" + + +InvalidNativeDataTypes = (ValueError, TypeError, BytestringSplittingError, InternalError) diff --git a/nucypher/policy/maps.py b/nucypher/policy/maps.py index 948633cda..e4c038320 100644 --- a/nucypher/policy/maps.py +++ b/nucypher/policy/maps.py @@ -16,23 +16,22 @@ along with nucypher. If not, see . """ -from typing import Optional, Callable, Union, Sequence, Dict +from typing import Optional, Callable, Sequence, Dict from bytestring_splitter import ( BytestringSplitter, VariableLengthBytestring, BytestringSplittingError, ) -from constant_sorrow.constants import NO_DECRYPTION_PERFORMED, NOT_SIGNED -from eth_utils.address import to_checksum_address, to_canonical_address from eth_typing.evm import ChecksumAddress +from eth_utils.address import to_checksum_address, to_canonical_address from nucypher.blockchain.eth.constants import ETH_ADDRESS_BYTE_LENGTH from nucypher.crypto.constants import EIP712_MESSAGE_SIGNATURE_SIZE -from nucypher.crypto.powers import DecryptingPower, SigningPower +from nucypher.crypto.powers import DecryptingPower from nucypher.crypto.signing import SignatureStamp, InvalidSignature from nucypher.crypto.splitters import signature_splitter, kfrag_splitter -from nucypher.crypto.umbral_adapter import KeyFrag, VerifiedKeyFrag, PublicKey, Signature +from nucypher.crypto.umbral_adapter import KeyFrag, VerifiedKeyFrag, Signature from nucypher.crypto.utils import keccak_digest, verify_eip_191 from nucypher.network.middleware import RestMiddleware from nucypher.policy.hrac import HRAC, hrac_splitter @@ -131,6 +130,9 @@ class TreasureMap: destinations = {u: k for u, k in ursula_and_kfrags} return cls(threshold, hrac, destinations) + def __eq__(self, other): + return bytes(self) == bytes(other) + def __iter__(self): return iter(self.destinations.items()) @@ -313,3 +315,6 @@ class EncryptedTreasureMap: result = cls(hrac, public_signature, message_kit, blockchain_signature=blockchain_signature) result._public_verify() return result + + def __eq__(self, other): + return bytes(self) == bytes(other) diff --git a/nucypher/utilities/porter/control/interfaces.py b/nucypher/utilities/porter/control/interfaces.py index cb0cfeb6c..d4a50deca 100644 --- a/nucypher/utilities/porter/control/interfaces.py +++ b/nucypher/utilities/porter/control/interfaces.py @@ -21,6 +21,7 @@ from eth_typing import ChecksumAddress from nucypher.control.interfaces import ControlInterface, attach_schema from nucypher.crypto.umbral_adapter import PublicKey from nucypher.policy.kits import RetrievalKit +from nucypher.policy.maps import TreasureMap from nucypher.utilities.porter.control.specifications import porter_schema @@ -55,7 +56,7 @@ class PorterInterface(ControlInterface): @attach_schema(porter_schema.BobRetrieveCFrags) def retrieve_cfrags(self, - treasure_map: 'TreasureMap', + treasure_map: TreasureMap, retrieval_kits: List[RetrievalKit], alice_verifying_key: PublicKey, bob_encrypting_key: PublicKey, @@ -70,6 +71,6 @@ class PorterInterface(ControlInterface): bob_verifying_key=bob_verifying_key, policy_encrypting_key=policy_encrypting_key, publisher_verifying_key=publisher_verifying_key) - results = retrieval_results.results # list of RetrievalResult objects + results = retrieval_results # list of RetrievalResult objects response_data = {'retrieval_results': results} return response_data diff --git a/nucypher/utilities/porter/control/specifications/fields/hrac.py b/nucypher/utilities/porter/control/specifications/fields/hrac.py index ddecfda61..24b60d19f 100644 --- a/nucypher/utilities/porter/control/specifications/fields/hrac.py +++ b/nucypher/utilities/porter/control/specifications/fields/hrac.py @@ -17,8 +17,7 @@ from marshmallow import fields -from nucypher.characters.control.specifications.exceptions import InvalidNativeDataTypes -from nucypher.control.specifications.exceptions import InvalidInputData +from nucypher.control.specifications.exceptions import InvalidInputData, InvalidNativeDataTypes from nucypher.control.specifications.fields.base import BaseField from nucypher.policy.hrac import HRAC as HRACClass @@ -30,12 +29,7 @@ class HRAC(BaseField, fields.String): def _deserialize(self, value, attr, data, **kwargs): try: - return bytes.fromhex(value) - except InvalidNativeDataTypes as e: - raise InvalidInputData(f"Could not convert input for {self.name} to a valid HRAC serialization: {e}") - - def _validate(self, value): - try: - HRACClass.from_bytes(value) + hrac_bytes = bytes.fromhex(value) + return HRACClass.from_bytes(hrac_bytes) except InvalidNativeDataTypes as e: raise InvalidInputData(f"Could not convert input for {self.name} to a valid HRAC: {e}") diff --git a/nucypher/utilities/porter/control/specifications/fields/retrieve.py b/nucypher/utilities/porter/control/specifications/fields/retrieve.py index a03fc179e..a91f26a20 100644 --- a/nucypher/utilities/porter/control/specifications/fields/retrieve.py +++ b/nucypher/utilities/porter/control/specifications/fields/retrieve.py @@ -15,11 +15,12 @@ along with nucypher. If not, see . """ from marshmallow import fields + from nucypher.control.specifications.base import BaseSchema from nucypher.control.specifications.exceptions import InvalidInputData from nucypher.control.specifications.fields import Base64BytesRepresentation -from nucypher.crypto.kits import RetrievalKit as RetrievalKitClass -from nucypher.crypto.umbral_adapter import Capsule as CapsuleClass, CapsuleFrag as CapsuleFragClass +from nucypher.crypto.umbral_adapter import CapsuleFrag as CapsuleFragClass +from nucypher.policy.kits import RetrievalKit as RetrievalKitClass from nucypher.utilities.porter.control.specifications.fields import UrsulaChecksumAddress @@ -34,16 +35,7 @@ class RetrievalKit(Base64BytesRepresentation): raise InvalidInputData(f"Could not convert input for {self.name} to a valid checksum address: {e}") -class Capsule(Base64BytesRepresentation): - def _deserialize(self, value, attr, data, **kwargs): - try: - capsule_bytes = super()._deserialize(value, attr, data, **kwargs) - return CapsuleClass.from_bytes(capsule_bytes) - except Exception as e: - raise InvalidInputData(f"Could not parse {self.name}: {e}") - - -class CapsuleFrag(Base64BytesRepresentation): +class VerifiedCapsuleFrag(Base64BytesRepresentation): def _deserialize(self, value, attr, data, **kwargs): try: capsule_frag_bytes = super()._deserialize(value, attr, data, **kwargs) @@ -54,8 +46,7 @@ class CapsuleFrag(Base64BytesRepresentation): class RetrievalResultSchema(BaseSchema): """Schema for the result of retrieve_cfrags.""" - capsule = Capsule() - cfrags = fields.Dict(keys=UrsulaChecksumAddress(), values=CapsuleFrag()) + cfrags = fields.Dict(keys=UrsulaChecksumAddress(), values=VerifiedCapsuleFrag()) # maintain field declaration ordering class Meta: diff --git a/nucypher/utilities/porter/control/specifications/porter_schema.py b/nucypher/utilities/porter/control/specifications/porter_schema.py index 69c7dd3b6..d580aced9 100644 --- a/nucypher/utilities/porter/control/specifications/porter_schema.py +++ b/nucypher/utilities/porter/control/specifications/porter_schema.py @@ -17,15 +17,15 @@ import click -from marshmallow import validates_schema from marshmallow import fields as marshmallow_fields +from marshmallow import validates_schema -from nucypher.control.specifications.base import BaseSchema -from nucypher.control.specifications import fields as base_fields -from nucypher.control.specifications.exceptions import InvalidArgumentCombo -from nucypher.utilities.porter.control.specifications import fields from nucypher.characters.control.specifications import fields as character_fields from nucypher.cli import types +from nucypher.control.specifications import fields as base_fields +from nucypher.control.specifications.base import BaseSchema +from nucypher.control.specifications.exceptions import InvalidArgumentCombo +from nucypher.utilities.porter.control.specifications import fields def option_ursula(): diff --git a/tests/acceptance/characters/control/test_rpc_control_blockchain.py b/tests/acceptance/characters/control/test_rpc_control_blockchain.py index 86675ba1d..7f430c0d2 100644 --- a/tests/acceptance/characters/control/test_rpc_control_blockchain.py +++ b/tests/acceptance/characters/control/test_rpc_control_blockchain.py @@ -14,8 +14,6 @@ You should have received a copy of the GNU Affero General Public License along with nucypher. If not, see . """ - - from base64 import b64encode import pytest @@ -34,10 +32,10 @@ def test_enrico_rpc_character_control_encrypt_message(enrico_rpc_controller_test interface=EnricoInterface) -def test_bob_rpc_character_control_retrieve_with_tmap( - enacted_blockchain_policy, blockchain_bob, blockchain_alice, - bob_rpc_controller, retrieve_control_request): - +def test_bob_rpc_character_control_retrieve_with_tmap(enacted_blockchain_policy, + blockchain_bob, + bob_rpc_controller, + retrieve_control_request): # So that this test can run even independently. if not blockchain_bob.done_seeding: blockchain_bob.learn_from_teacher_node() diff --git a/tests/acceptance/porter/control/test_porter_rpc_control_blockchain.py b/tests/acceptance/porter/control/test_porter_rpc_control_blockchain.py index de6f798a3..5157c75d8 100644 --- a/tests/acceptance/porter/control/test_porter_rpc_control_blockchain.py +++ b/tests/acceptance/porter/control/test_porter_rpc_control_blockchain.py @@ -21,7 +21,6 @@ import pytest from nucypher.control.specifications.exceptions import InvalidInputData from nucypher.network.nodes import Learner -from nucypher.utilities.porter.control.specifications.fields import Capsule from tests.utils.middleware import MockRestMiddleware from tests.utils.policy import retrieval_request_setup, retrieval_params_decode_from_rest @@ -100,15 +99,10 @@ def test_retrieve_cfrags(blockchain_porter, retrieval_results = response.data['result']['retrieval_results'] assert retrieval_results - # expected results - can only compare capsules, ursulas are randomized to obtain cfrags + # expected results - can only compare length of results, ursulas are randomized to obtain cfrags retrieve_args = retrieval_params_decode_from_rest(retrieve_cfrags_params) - expected_results = blockchain_porter.retrieve_cfrags(**retrieve_args).results - capsule_field = Capsule() + expected_results = blockchain_porter.retrieve_cfrags(**retrieve_args) assert len(retrieval_results) == len(expected_results) - for i, result in enumerate(retrieval_results): - # compare capsule - capsule = capsule_field._deserialize(value=result['capsule'], attr=None, data=None) - assert capsule == expected_results[i].capsule # Failure - use encrypted treasure map failure_retrieve_cfrags_params = dict(retrieve_cfrags_params) diff --git a/tests/acceptance/porter/control/test_porter_web_control_blockchain.py b/tests/acceptance/porter/control/test_porter_web_control_blockchain.py index 2b57e86d8..c8f08e3a0 100644 --- a/tests/acceptance/porter/control/test_porter_web_control_blockchain.py +++ b/tests/acceptance/porter/control/test_porter_web_control_blockchain.py @@ -18,8 +18,8 @@ import json import os from base64 import b64encode +from urllib.parse import urlencode -from nucypher.utilities.porter.control.specifications.fields import Capsule from tests.utils.middleware import MockRestMiddleware from tests.utils.policy import retrieval_request_setup, retrieval_params_decode_from_rest @@ -110,15 +110,10 @@ def test_retrieve_cfrags(blockchain_porter, retrieval_results = response_data['result']['retrieval_results'] assert retrieval_results - # expected results - can only compare capsules, ursulas are randomized to obtain cfrags + # expected results - can only compare length of results, ursulas are randomized to obtain cfrags retrieve_args = retrieval_params_decode_from_rest(retrieve_cfrags_params) - expected_results = blockchain_porter.retrieve_cfrags(**retrieve_args).results - capsule_field = Capsule() + expected_results = blockchain_porter.retrieve_cfrags(**retrieve_args) assert len(retrieval_results) == len(expected_results) - for i, result in enumerate(retrieval_results): - # compare capsule - capsule = capsule_field._deserialize(value=result['capsule'], attr=None, data=None) - assert capsule == expected_results[i].capsule # try same retrieval using query parameters url_retrieve_params = dict(retrieve_cfrags_params) diff --git a/tests/integration/characters/test_specifications.py b/tests/integration/characters/test_specifications.py index 543d26a16..9dab95e6f 100644 --- a/tests/integration/characters/test_specifications.py +++ b/tests/integration/characters/test_specifications.py @@ -22,14 +22,14 @@ from base64 import b64encode import maya import pytest -from nucypher.policy.maps import TreasureMap as TreasureMapClass from nucypher.characters.control.specifications import fields from nucypher.characters.control.specifications.alice import GrantPolicy -from nucypher.characters.control.specifications.fields.treasuremap import EncryptedTreasureMap +from nucypher.characters.control.specifications.fields.treasuremap import EncryptedTreasureMap, TreasureMap from nucypher.control.specifications.base import BaseSchema from nucypher.control.specifications.exceptions import SpecificationError, InvalidInputData, InvalidArgumentCombo from nucypher.crypto.powers import DecryptingPower from nucypher.crypto.umbral_adapter import PublicKey +from nucypher.policy.maps import EncryptedTreasureMap as EncryptedTreasureMapClass, TreasureMap as TreasureMapClass def test_various_field_validations_by_way_of_alice_grant(federated_bob): @@ -52,7 +52,7 @@ def test_various_field_validations_by_way_of_alice_grant(federated_bob): } # validate data with both rate and value fails validation - with pytest.raises(InvalidArgumentCombo) as e: + with pytest.raises(InvalidArgumentCombo): GrantPolicy().load(data) # remove value and now it works @@ -62,24 +62,27 @@ def test_various_field_validations_by_way_of_alice_grant(federated_bob): # validate that negative "m" value fails data['threshold'] = -5 - with pytest.raises(SpecificationError) as e: + with pytest.raises(SpecificationError): GrantPolicy().load(data) # validate that m > n fails validation data['threshold'] = data['shares'] + 19 - with pytest.raises(SpecificationError) as e: + with pytest.raises(SpecificationError): GrantPolicy().load(data) -def test_treasuremap_validation(enacted_federated_policy): +def test_treasure_map_validation(enacted_federated_policy, + federated_bob): """Tell people exactly what's wrong with their treasuremaps""" - - class TreasureMapsOnly(BaseSchema): + # + # encrypted treasure map + # + class EncryptedTreasureMapsOnly(BaseSchema): tmap = EncryptedTreasureMap() # this will raise a base64 error with pytest.raises(SpecificationError) as e: - TreasureMapsOnly().load({'tmap': "your face looks like a treasure map"}) + EncryptedTreasureMapsOnly().load({'tmap': "your face looks like a treasure map"}) # assert that field name is in the error message assert "Could not parse tmap" in str(e) @@ -87,7 +90,7 @@ def test_treasuremap_validation(enacted_federated_policy): # valid base64 but invalid treasuremap with pytest.raises(InvalidInputData) as e: - TreasureMapsOnly().load({'tmap': "VGhpcyBpcyB0b3RhbGx5IG5vdCBhIHRyZWFzdXJlbWFwLg=="}) + EncryptedTreasureMapsOnly().load({'tmap': "VGhpcyBpcyB0b3RhbGx5IG5vdCBhIHRyZWFzdXJlbWFwLg=="}) assert "Could not convert input for tmap to an EncryptedTreasureMap" in str(e) assert "Invalid encrypted treasure map contents." in str(e) @@ -95,7 +98,36 @@ def test_treasuremap_validation(enacted_federated_policy): # a valid treasuremap for once... tmap_bytes = bytes(enacted_federated_policy.treasure_map) tmap_b64 = b64encode(tmap_bytes) - result = TreasureMapsOnly().load({'tmap': tmap_b64.decode()}) + result = EncryptedTreasureMapsOnly().load({'tmap': tmap_b64.decode()}) + assert isinstance(result['tmap'], EncryptedTreasureMapClass) + + # + # unencrypted treasure map + # + class UnenncryptedTreasureMapsOnly(BaseSchema): + tmap = TreasureMap() + + # this will raise a base64 error + with pytest.raises(SpecificationError) as e: + UnenncryptedTreasureMapsOnly().load({'tmap': "your face looks like a treasure map"}) + + # assert that field name is in the error message + assert "Could not parse tmap" in str(e) + assert "Invalid base64-encoded string" in str(e) + + # valid base64 but invalid treasuremap + with pytest.raises(InvalidInputData) as e: + UnenncryptedTreasureMapsOnly().load({'tmap': "VGhpcyBpcyB0b3RhbGx5IG5vdCBhIHRyZWFzdXJlbWFwLg=="}) + + assert "Could not convert input for tmap to a TreasureMap" in str(e) + assert "Invalid treasure map contents." in str(e) + + # a valid treasuremap + decrypted_treasure_map = federated_bob._decrypt_treasure_map(enacted_federated_policy.treasure_map, + enacted_federated_policy.publisher_verifying_key) + tmap_bytes = bytes(decrypted_treasure_map) + tmap_b64 = b64encode(tmap_bytes).decode() + result = UnenncryptedTreasureMapsOnly().load({'tmap': tmap_b64}) assert isinstance(result['tmap'], TreasureMapClass) diff --git a/tests/integration/porter/control/test_porter_rpc_control_federated.py b/tests/integration/porter/control/test_porter_rpc_control_federated.py index 8ba174a92..eb2a8b572 100644 --- a/tests/integration/porter/control/test_porter_rpc_control_federated.py +++ b/tests/integration/porter/control/test_porter_rpc_control_federated.py @@ -21,7 +21,6 @@ import pytest from nucypher.control.specifications.exceptions import InvalidInputData from nucypher.network.nodes import Learner -from nucypher.utilities.porter.control.specifications.fields import Capsule from tests.utils.policy import retrieval_request_setup, retrieval_params_decode_from_rest @@ -95,19 +94,14 @@ def test_retrieve_cfrags(federated_porter, retrieval_results = response.data['result']['retrieval_results'] assert retrieval_results - # expected results - can only compare capsules, ursulas are randomized to obtain cfrags + # expected results - can only compare length of results, ursulas are randomized to obtain cfrags retrieve_args = retrieval_params_decode_from_rest(retrieve_cfrags_params) - expected_results = federated_porter.retrieve_cfrags(**retrieve_args).results - capsule_field = Capsule() + expected_results = federated_porter.retrieve_cfrags(**retrieve_args) assert len(retrieval_results) == len(expected_results) - for i, result in enumerate(retrieval_results): - # compare capsule - capsule = capsule_field._deserialize(value=result['capsule'], attr=None, data=None) - assert capsule == expected_results[i].capsule # Failure - use encrypted treasure map failure_retrieve_cfrags_params = dict(retrieve_cfrags_params) - _, _, random_treasure_map = random_federated_treasure_map_data + _, random_treasure_map = random_federated_treasure_map_data failure_retrieve_cfrags_params['treasure_map'] = b64encode(bytes(random_treasure_map)).decode() request_data = {'method': method, 'params': failure_retrieve_cfrags_params} with pytest.raises(InvalidInputData): diff --git a/tests/integration/porter/control/test_porter_web_control_federated.py b/tests/integration/porter/control/test_porter_web_control_federated.py index a9f939803..6e79f6f29 100644 --- a/tests/integration/porter/control/test_porter_web_control_federated.py +++ b/tests/integration/porter/control/test_porter_web_control_federated.py @@ -18,8 +18,8 @@ import json from base64 import b64encode +from urllib.parse import urlencode -from nucypher.utilities.porter.control.specifications.fields import Capsule from tests.utils.policy import retrieval_request_setup, retrieval_params_decode_from_rest @@ -106,15 +106,10 @@ def test_retrieve_cfrags(federated_porter, retrieval_results = response_data['result']['retrieval_results'] assert retrieval_results - # expected results - can only compare capsules, ursulas are randomized to obtain cfrags + # expected results - can only compare length of results, ursulas are randomized to obtain cfrags retrieve_args = retrieval_params_decode_from_rest(retrieve_cfrags_params) - expected_results = federated_porter.retrieve_cfrags(**retrieve_args).results - capsule_field = Capsule() + expected_results = federated_porter.retrieve_cfrags(**retrieve_args) assert len(retrieval_results) == len(expected_results) - for i, result in enumerate(retrieval_results): - # compare capsule - capsule = capsule_field._deserialize(value=result['capsule'], attr=None, data=None) - assert capsule == expected_results[i].capsule # try same retrieval using query parameters url_retrieve_params = dict(retrieve_cfrags_params) @@ -127,7 +122,7 @@ def test_retrieve_cfrags(federated_porter, failure_retrieve_cfrags_params = dict(retrieve_cfrags_params) # use encrypted treasure map - _, _, random_treasure_map = random_federated_treasure_map_data + _, random_treasure_map = random_federated_treasure_map_data failure_retrieve_cfrags_params['treasure_map'] = b64encode(bytes(random_treasure_map)).decode() response = federated_porter_web_controller.post('/retrieve_cfrags', data=json.dumps(failure_retrieve_cfrags_params)) assert response.status_code == 400 # invalid treasure map provided diff --git a/tests/integration/porter/test_federated_porter.py b/tests/integration/porter/test_federated_porter.py index 8622b9046..7a57f28d8 100644 --- a/tests/integration/porter/test_federated_porter.py +++ b/tests/integration/porter/test_federated_porter.py @@ -73,4 +73,4 @@ def test_retrieve_cfrags(federated_porter, result = federated_porter.retrieve_cfrags(**retrieval_args) - assert result, "valid result returned" \ No newline at end of file + assert result, "valid result returned" diff --git a/tests/integration/porter/test_porter_specifications.py b/tests/integration/porter/test_porter_specifications.py index 23ba2c071..c8b717dbf 100644 --- a/tests/integration/porter/test_porter_specifications.py +++ b/tests/integration/porter/test_porter_specifications.py @@ -205,9 +205,9 @@ def test_bob_retrieve_cfrags(federated_porter, retrieval_results = federated_porter.retrieve_cfrags(**non_encoded_retrieval_args) expected_retrieval_results_json = [] retrieval_result_schema = RetrievalResultSchema() - for result in retrieval_results.results: + for result in retrieval_results: data = retrieval_result_schema.dump(result) expected_retrieval_results_json.append(data) - output = bob_retrieve_cfrags_schema.dump(obj={'retrieval_results': retrieval_results.results}) + output = bob_retrieve_cfrags_schema.dump(obj={'retrieval_results': retrieval_results}) assert output == {"retrieval_results": expected_retrieval_results_json} diff --git a/tests/unit/test_porter.py b/tests/unit/test_porter.py index a75e85fd2..3d9a5420c 100644 --- a/tests/unit/test_porter.py +++ b/tests/unit/test_porter.py @@ -20,13 +20,10 @@ import pytest from nucypher.control.specifications.exceptions import InvalidInputData from nucypher.control.specifications.fields import StringList -from nucypher.crypto.kits import RetrievalKit as RetrievalKitClass -from nucypher.crypto.umbral_adapter import SecretKey, encrypt, Capsule as CapsuleClass -from nucypher.utilities.porter.control.specifications.fields import ( - HRAC, - UrsulaChecksumAddress, -) -from nucypher.utilities.porter.control.specifications.fields.retrieve import RetrievalKit, Capsule +from nucypher.crypto.umbral_adapter import SecretKey, encrypt +from nucypher.policy.kits import RetrievalKit as RetrievalKitClass +from nucypher.utilities.porter.control.specifications.fields import HRAC, UrsulaChecksumAddress +from nucypher.utilities.porter.control.specifications.fields.retrieve import RetrievalKit def test_hrac_field(enacted_federated_policy): @@ -37,11 +34,10 @@ def test_hrac_field(enacted_federated_policy): assert serialized == bytes(hrac).hex() deserialized = field._deserialize(value=serialized, attr=None, data=None) - assert deserialized == bytes(hrac) + assert deserialized == hrac - field._validate(value=bytes(hrac)) with pytest.raises(InvalidInputData): - field._validate(value=b'not hrac') + field._deserialize(value=b'not hrac', attr=None, data=None) def test_ursula_checksum_address_field(get_random_checksum_address): @@ -124,19 +120,3 @@ def test_retrieval_kit_field(): with pytest.raises(InvalidInputData): field.deserialize(value=b64encode(b"invalid_retrieval_kit_bytes").decode(), attr=None, data=None) - - -def test_capsule_field(): - encrypting_key = SecretKey.random().public_key() - capsule, _ = encrypt(encrypting_key, b'testing_retrieval_kit') - - field = Capsule() - serialized = field._serialize(value=capsule, attr=None, obj=None) - assert serialized == b64encode(bytes(capsule)).decode() - - deserialized = field._deserialize(value=serialized, attr=None, data=None) - assert isinstance(deserialized, CapsuleClass) - assert deserialized == capsule - - with pytest.raises(InvalidInputData): - field._deserialize(value=b64encode(b"faux_capsule").decode(), attr=None, data=None) diff --git a/tests/utils/policy.py b/tests/utils/policy.py index 020d0390a..bdf62e194 100644 --- a/tests/utils/policy.py +++ b/tests/utils/policy.py @@ -20,7 +20,7 @@ import random import string from typing import Dict -from nucypher.characters.control.specifications.fields import Key, DecryptedTreasureMap +from nucypher.characters.control.specifications.fields import Key, TreasureMap from nucypher.characters.lawful import Enrico from nucypher.crypto.powers import DecryptingPower from nucypher.utilities.porter.control.specifications.fields import RetrievalKit @@ -47,7 +47,8 @@ def retrieval_request_setup(enacted_policy, bob, alice, encode_for_rest=False) - bob.start_learning_loop() bob.follow_treasure_map(treasure_map=treasure_map, block=True, timeout=1) - decrypted_map = treasure_map.as_decrypted_map() + decrypted_treasure_map = bob._decrypt_treasure_map(enacted_policy.treasure_map, + enacted_policy.publisher_verifying_key) # We'll test against just a single Ursula - here, we make a WorkOrder for just one. # We can pass any number of capsules as args; here we pass just one. @@ -57,7 +58,7 @@ def retrieval_request_setup(enacted_policy, bob, alice, encode_for_rest=False) - encode_bytes = (lambda field, obj: field()._serialize(value=obj, attr=None, obj=None)) if encode_for_rest else (lambda field, obj: obj) - return dict(treasure_map=encode_bytes(DecryptedTreasureMap, decrypted_map), + return dict(treasure_map=encode_bytes(TreasureMap, decrypted_treasure_map), retrieval_kits=[encode_bytes(RetrievalKit, message_kit.as_retrieval_kit())], alice_verifying_key=encode_bytes(Key, alice.stamp.as_umbral_pubkey()), bob_encrypting_key=encode_bytes(Key, bob.public_keys(DecryptingPower)), @@ -67,7 +68,7 @@ def retrieval_request_setup(enacted_policy, bob, alice, encode_for_rest=False) - def retrieval_params_decode_from_rest(retrieval_params: Dict) -> Dict: decode_bytes = (lambda field, data: field()._deserialize(value=data, attr=None, data=None)) - return dict(treasure_map=decode_bytes(DecryptedTreasureMap, retrieval_params['treasure_map']), + return dict(treasure_map=decode_bytes(TreasureMap, retrieval_params['treasure_map']), retrieval_kits=[decode_bytes(RetrievalKit, kit) for kit in retrieval_params['retrieval_kits']], alice_verifying_key=decode_bytes(Key, retrieval_params['alice_verifying_key']), bob_encrypting_key=decode_bytes(Key, retrieval_params['bob_encrypting_key']),