diff --git a/nucypher/characters/lawful.py b/nucypher/characters/lawful.py index 373f7ee43..443a131e6 100644 --- a/nucypher/characters/lawful.py +++ b/nucypher/characters/lawful.py @@ -1252,21 +1252,42 @@ class Ursula(Teacher, Character, Worker): return constants.BYTESTRING_IS_URSULA_IFACE_INFO + bytes(self) # - # Utilities + # Work Orders & Re-Encryption # - def work_orders(self, bob=None): - """ - TODO: This is better written as a model method for Ursula's datastore. - """ - if not bob: - return self._work_orders - else: - work_orders_from_bob = [] - for work_order in self._work_orders: - if work_order.bob == bob: - work_orders_from_bob.append(work_order) - return work_orders_from_bob + def work_orders(self, bob=None) -> List['WorkOrder']: + with ThreadedSession(self.datastore.engine): + if not bob: # All + return self.datastore.get_workorders() + else: # Filter + work_orders_from_bob = self.datastore.get_workorders(bob_verifying_key=bytes(bob.stamp)) + return work_orders_from_bob + + def _reencrypt(self, kfrag: KFrag, work_order: 'WorkOrder', alice_verifying_key: UmbralPublicKey): + + # Prepare a bytestring for concatenating re-encrypted + # capsule data for each work order task. + cfrag_byte_stream = bytes() + for task in work_order.tasks: + + # Ursula signs on top of Bob's signature of each task. + # Now both are committed to the same task. See #259. + reencryption_metadata = bytes(self.stamp(bytes(task.signature))) + + # Ursula sets Alice's verifying key for capsule correctness verification. + capsule = task.capsule + capsule.set_correctness_keys(verifying=alice_verifying_key) + + # Then re-encrypts the fragment. + cfrag = pre.reencrypt(kfrag, capsule, metadata=reencryption_metadata) # <--- pyUmbral + self.log.info(f"Re-encrypted capsule {capsule} -> made {cfrag}.") + + # Next, Ursula signs to commit to her results. + reencryption_signature = self.stamp(bytes(cfrag)) + cfrag_byte_stream += VariableLengthBytestring(cfrag) + reencryption_signature + + # ... and finally returns all the re-encrypted bytes + return cfrag_byte_stream class Enrico(Character): diff --git a/nucypher/cli/painting.py b/nucypher/cli/painting.py index 72cc6a1db..e6b75ddeb 100644 --- a/nucypher/cli/painting.py +++ b/nucypher/cli/painting.py @@ -124,7 +124,7 @@ def paint_node_status(emitter, ursula, start_time): 'Rest Interface ...... {}'.format(ursula.rest_url()), 'Node Storage Type ... {}'.format(ursula.node_storage._name.capitalize()), 'Known Nodes ......... {}'.format(len(ursula.known_nodes)), - 'Work Orders ......... {}'.format(len(ursula._work_orders)), + 'Work Orders ......... {}'.format(len(ursula.work_orders())), teacher] if not ursula.federated_only: diff --git a/nucypher/network/server.py b/nucypher/network/server.py index ea2ce8b5e..849f6eb7c 100644 --- a/nucypher/network/server.py +++ b/nucypher/network/server.py @@ -291,29 +291,37 @@ def make_rest_app( @rest_app.route('/kFrag//reencrypt', methods=["POST"]) def reencrypt_via_rest(id_as_hex): - from nucypher.policy.collections import WorkOrder # Avoid circular import - arrangement_id = binascii.unhexlify(id_as_hex) + + # Get Policy Arrangement + try: + arrangement_id = binascii.unhexlify(id_as_hex) + except (binascii.Error, TypeError): + return Response(response=b'Invalid arrangement ID', status=405) try: with ThreadedSession(db_engine) as session: arrangement = datastore.get_policy_arrangement(arrangement_id=id_as_hex.encode(), session=session) except NotFound: return Response(response=arrangement_id, status=404) - kfrag_bytes = policy_arrangement.kfrag # Careful! :-) - verifying_key_bytes = policy_arrangement.alice_verifying_key.key_data - # TODO: Push this to a lower level. Perhaps to Ursula character? #619 - kfrag = KFrag.from_bytes(kfrag_bytes) - alices_verifying_key = UmbralPublicKey.from_bytes(verifying_key_bytes) - alices_address = canonical_address_from_umbral_key(alices_verifying_key) + # Get KFrag + kfrag = KFrag.from_bytes(arrangement.kfrag) # Careful! :-) + # Get Work Order + from nucypher.policy.collections import WorkOrder # Avoid circular import + alice_verifying_key_bytes = arrangement.alice_verifying_key.key_data + alice_verifying_key = UmbralPublicKey.from_bytes(alice_verifying_key_bytes) + alice_address = canonical_address_from_umbral_key(alice_verifying_key) + work_order_payload = request.data work_order = WorkOrder.from_rest_payload(arrangement_id=arrangement_id, - rest_payload=request.data, + rest_payload=work_order_payload, ursula=this_node, - alice_address=alices_address) - + alice_address=alice_address) log.info(f"Work Order from {work_order.bob}, signed {work_order.receipt_signature}") - cfrag_byte_stream = b"" + # Re-encrypt + response = this_node._reencrypt(kfrag=kfrag, + work_order=work_order, + alice_verifying_key=alice_verifying_key) # Now, Ursula saves this workorder to her database... with ThreadedSession(db_engine): diff --git a/tests/keystore/test_keystore.py b/tests/keystore/test_keystore.py index 524bbe56e..a8b3247f3 100644 --- a/tests/keystore/test_keystore.py +++ b/tests/keystore/test_keystore.py @@ -74,4 +74,4 @@ def test_workorder_sqlite_keystore(test_keystore): # Test del workorder deleted = test_keystore.del_workorders(arrangement_id) assert deleted > 0 - assert test_keystore.get_workorders(arrangement_id) == 0 + assert len(test_keystore.get_workorders(arrangement_id)) == 0