Define initial schema for /retrieve_cfrags Porter endpoint - (untested at the moment)

pull/2768/head
derekpierre 2021-08-11 14:32:12 -04:00
parent 2cd7698da7
commit 61c0978e52
7 changed files with 164 additions and 21 deletions

View File

@ -164,7 +164,7 @@ class BobInterface(CharacterPublicInterface):
policy_encrypting_key: PublicKey,
alice_verifying_key: PublicKey,
message_kit: bytes,
encrypted_treasure_map: 'EncryptedTreasureMap'):
encrypted_treasure_map: 'EncryptedTreasureMap') -> dict:
"""
Character control endpoint for re-encrypting and decrypting policy data.
"""
@ -180,7 +180,7 @@ class BobInterface(CharacterPublicInterface):
return response_data
@attach_schema(bob.PublicKeys)
def public_keys(self):
def public_keys(self) -> dict:
"""
Character control endpoint for getting Bob's encrypting and signing public keys
"""
@ -193,7 +193,7 @@ class BobInterface(CharacterPublicInterface):
class EnricoInterface(CharacterPublicInterface):
@attach_schema(enrico.EncryptMessage)
def encrypt_message(self, plaintext: Union[str, bytes]):
def encrypt_message(self, plaintext: Union[str, bytes]) -> dict:
"""
Character control endpoint for encrypting data for a policy and
receiving the messagekit (and signature) to give to Bob.

View File

@ -19,6 +19,8 @@ from typing import List, Optional
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.utilities.porter.control.specifications import porter_schema
@ -40,9 +42,7 @@ class PorterInterface(ControlInterface):
exclude_ursulas=exclude_ursulas,
include_ursulas=include_ursulas)
response_data = {
"ursulas": ursulas_info
}
response_data = {"ursulas": ursulas_info}
return response_data
@attach_schema(porter_schema.AliceRevoke)
@ -52,3 +52,24 @@ class PorterInterface(ControlInterface):
# 2. call self.implementer.some_function() i.e. Porter learner has an associated function to call
# 3. create response
pass
@attach_schema(porter_schema.BobRetrieveCFrags)
def retrieve_cfrags(self,
treasure_map: 'TreasureMap',
retrieval_kits: List[RetrievalKit],
alice_verifying_key: PublicKey,
bob_encrypting_key: PublicKey,
bob_verifying_key: PublicKey,
policy_encrypting_key: PublicKey,
publisher_verifying_key: Optional[PublicKey] = None,
) -> dict:
retrieval_results = self.implementer.retrieve_cfrags(treasure_map=treasure_map,
retrieval_kits=retrieval_kits,
alice_verifying_key=alice_verifying_key,
bob_encrypting_key=bob_encrypting_key,
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
response_data = {'retrieve_results': results}
return response_data

View File

@ -17,3 +17,4 @@
from nucypher.utilities.porter.control.specifications.fields.ursula import *
from nucypher.utilities.porter.control.specifications.fields.hrac import *
from nucypher.utilities.porter.control.specifications.fields.retrieve import *

View File

@ -14,10 +14,13 @@
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
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.utilities.porter.control.specifications.fields import UrsulaChecksumAddress
# TODO should this be moved to character control - would this be used by a Bob character control endpoint?
@ -29,3 +32,27 @@ class RetrievalKit(Base64BytesRepresentation):
return RetrievalKitClass.from_bytes(retrieval_kit_bytes)
except Exception as e:
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):
def _deserialize(self, value, attr, data, **kwargs):
try:
capsule_bytes = super()._deserialize(value, attr, data, **kwargs)
return CapsuleFragClass.from_bytes(capsule_bytes)
except Exception as e:
raise InvalidInputData(f"Could not parse {self.name}: {e}")
class RetrievalResultSchema(BaseSchema):
"""Schema for the result of retrieve_cfrags."""
capsule = Capsule()
cfrags = fields.Dict(keys=UrsulaChecksumAddress(), values=CapsuleFrag())

View File

@ -117,3 +117,74 @@ class AliceGetUrsulas(BaseSchema):
class AliceRevoke(BaseSchema):
pass # TODO need to understand revoke process better
#
# Bob Endpoints
#
class BobRetrieveCFrags(BaseSchema):
treasure_map = character_fields.TreasureMap(
required=True,
load_only=True,
click=click.option(
'--treasure-map',
'-t',
help="Treasure Map to publish",
type=click.STRING,
required=True))
retrieval_kits = base_fields.List(
fields.RetrievalKit(),
click=click.option(
'--retrieval-kits',
'-r',
help="Retrieval kits for reencryption",
multiple=True,
type=click.STRING,
required=True,
default=[]),
required=True,
load_only=True)
alice_verifying_key = character_fields.Key(
required=True,
load_only=True,
click=click.option(
'--alice-verifying-key',
'-avk',
help="Alice's verifying key as a hexadecimal string",
type=click.STRING,
required=True))
bob_encrypting_key = character_fields.Key(
required=True,
load_only=True,
click=option_bob_encrypting_key())
bob_verifying_key = character_fields.Key(
required=True,
load_only=True,
click=click.option(
'--bob-verifying-key',
'-bvk',
help="Bob's verifying key as a hexadecimal string",
type=click.STRING,
required=True))
policy_encrypting_key = character_fields.Key(
required=True,
load_only=True,
click=click.option(
'--policy-encrypting-key',
help="Encrypting Public Key for Policy as hexadecimal string",
type=click.STRING,
required=True))
# optional
publisher_verifying_key = character_fields.Key(
required=False,
load_only=True,
click=click.option(
'--publisher-verifying-key',
'-pvk',
help="Alice's verifying key as a hexadecimal string",
type=click.STRING,
required=False))
# output
retrieval_results = marshmallow_fields.List(marshmallow_fields.Nested(fields.RetrievalResultSchema), dump_only=True)

View File

@ -23,11 +23,14 @@ from flask import request, Response
from nucypher.blockchain.eth.agents import ContractAgency, StakingEscrowAgent
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
from nucypher.blockchain.eth.registry import BaseContractRegistry, InMemoryContractRegistry
from nucypher.characters.lawful import Ursula
from nucypher.characters.lawful import RetrievalClient, Ursula
from nucypher.control.controllers import JSONRPCController, WebController
from nucypher.crypto.powers import DecryptingPower
from nucypher.crypto.umbral_adapter import PublicKey
from nucypher.network.nodes import Learner
from nucypher.policy.kits import RetrievalKit
from nucypher.policy.maps import TreasureMap
from nucypher.policy.orders import RetrievalResult
from nucypher.policy.reservoir import (
make_federated_staker_reservoir,
make_decentralized_staker_reservoir,
@ -148,14 +151,18 @@ the Pipe for nucypher network operations
ursulas_info = successes.values()
return list(ursulas_info)
def exec_work_order(self, ursula_address: ChecksumAddress, work_order_payload: bytes) -> bytes:
self.block_until_specific_nodes_are_known(addresses={ursula_address}, learn_on_this_thread=True)
ursula = self.known_nodes[ursula_address]
ursula_rest_response = self.network_middleware.send_work_order_payload_to_ursula(
ursula=ursula,
work_order_payload=work_order_payload)
result = ursula_rest_response.content
return result
def retrieve_cfrags(self,
treasure_map: TreasureMap,
retrieval_kits: Sequence[RetrievalKit],
alice_verifying_key: PublicKey,
bob_encrypting_key: PublicKey,
bob_verifying_key: PublicKey,
policy_encrypting_key: PublicKey,
publisher_verifying_key: Optional[PublicKey] = None) -> List[RetrievalResult]:
client = RetrievalClient(self)
return client.retrieve_cfrags(treasure_map, retrieval_kits,
alice_verifying_key, bob_encrypting_key, bob_verifying_key, policy_encrypting_key,
publisher_verifying_key)
def _make_staker_reservoir(self,
quantity: int,
@ -229,10 +236,10 @@ the Pipe for nucypher network operations
response = controller(method_name='revoke', control_request=request)
return response
@porter_flask_control.route("/exec_work_order", methods=['POST'])
def exec_work_order() -> Response:
@porter_flask_control.route("/retrieve_cfrags", methods=['POST'])
def retrieve_cfrags() -> Response:
"""Porter control endpoint for executing a PRE work order on behalf of Bob."""
response = controller(method_name='exec_work_order', control_request=request)
response = controller(method_name='retrieve_cfrags', control_request=request)
return response
return controller

View File

@ -21,12 +21,12 @@ 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
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
from nucypher.utilities.porter.control.specifications.fields.retrieve import RetrievalKit, Capsule
from tests.utils.policy import retrieval_request_setup
@ -156,3 +156,19 @@ 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)