diff --git a/nucypher/characters/control/interfaces.py b/nucypher/characters/control/interfaces.py
index 6b47a2667..dd52211b5 100644
--- a/nucypher/characters/control/interfaces.py
+++ b/nucypher/characters/control/interfaces.py
@@ -116,7 +116,7 @@ class AliceInterface(CharacterPublicInterface):
revocation, fail_reason = attempt
if fail_reason == RestMiddleware.NotFound:
del (failed_revocations[node_id])
- if len(failed_revocations) <= (policy.n - policy.treasure_map.m + 1):
+ if len(failed_revocations) <= (policy.n - policy.m + 1):
del (self.implementer.active_policies[policy_id])
response_data = {'failed_revocations': len(failed_revocations)}
@@ -192,18 +192,14 @@ class BobInterface(CharacterPublicInterface):
self.implementer.join_policy(label=label, publisher_verifying_key=alice_verifying_key)
- if self.implementer.federated_only:
- from nucypher.policy.maps import TreasureMap as _MapClass
- else:
- from nucypher.policy.maps import SignedTreasureMap as _MapClass
+ from nucypher.policy.maps import EncryptedTreasureMap
- # TODO: This LBYL is ugly and fraught with danger. NRN - #2751
if isinstance(treasure_map, bytes):
- treasure_map = _MapClass.from_bytes(treasure_map)
+ treasure_map = EncryptedTreasureMap.from_bytes(treasure_map)
if isinstance(treasure_map, str):
tmap_bytes = treasure_map.encode()
- treasure_map = _MapClass.from_bytes(b64decode(tmap_bytes))
+ treasure_map = EncryptedTreasureMap.from_bytes(b64decode(tmap_bytes))
plaintexts = self.implementer.retrieve(message_kit,
enrico=enrico,
diff --git a/nucypher/characters/control/specifications/fields/treasuremap.py b/nucypher/characters/control/specifications/fields/treasuremap.py
index eb902b2a5..be79fefe4 100644
--- a/nucypher/characters/control/specifications/fields/treasuremap.py
+++ b/nucypher/characters/control/specifications/fields/treasuremap.py
@@ -22,30 +22,13 @@ from marshmallow import fields
from nucypher.characters.control.specifications.exceptions import InvalidNativeDataTypes
from nucypher.control.specifications.exceptions import InvalidInputData
from nucypher.control.specifications.fields.base import BaseField
+from nucypher.policy.maps import EncryptedTreasureMap as EncryptedTreasureMapClass
class EncryptedTreasureMap(BaseField, fields.Field):
"""
JSON Parameter representation of TreasureMap.
-
- Requires that either federated or non-federated (blockchain) treasure maps are expected to function correcty. This
- information is indicated either:
- - At creation time of the field via constructor parameter 'federated_only' (takes precedence)
- OR
- - Via the parent Schema context it is running in. In this case, the parent Schema context dictionary should have a
- key-value entry in it with the IS_FEDERATED_CONTEXT_KEY class constant as the key, and a value of True/False.
-
- If neither is provided, the TreasureMap is assumed to be a SignedTreasureMap. The federated/non-federated context
- of the TreasureMap only applies to deserialization and validation since the received value is in base64 encoded
- bytes.
"""
- IS_FEDERATED_CONTEXT_KEY = 'federated'
-
- def __init__(self, federated_only=None, *args, **kwargs):
- self.federated_only = federated_only
- BaseField.__init__(self, *args, **kwargs)
- fields.Field.__init__(self, *args, **kwargs)
-
def _serialize(self, value, attr, obj, **kwargs):
return b64encode(bytes(value)).decode()
@@ -56,23 +39,7 @@ class EncryptedTreasureMap(BaseField, fields.Field):
raise InvalidInputData(f"Could not parse {self.name}: {e}")
def _validate(self, value):
- from nucypher.policy.maps import SignedTreasureMap
- from nucypher.policy.maps import TreasureMap as UnsignedTreasureMap
-
- # determine context: federated or non-federated defined by field or schema
- is_federated_context = False # default to non-federated
- if self.federated_only is not None:
- # defined by field itself
- is_federated_context = self.federated_only
- else:
- # defined by schema
- if self.parent is not None and self.parent.context.get('federated') is not None:
- is_federated_context = self.context.get('federated')
-
try:
- splitter = SignedTreasureMap.get_splitter(value) if not is_federated_context else UnsignedTreasureMap.get_splitter(value)
- _ = splitter(value)
- return True
- except InvalidNativeDataTypes as e:
- # store exception
- raise InvalidInputData(f"Could not parse {self.name} (federated={is_federated_context}): {e}")
+ EncryptedTreasureMapClass.from_bytes(value)
+ except Exception as e:
+ raise InvalidInputData(f"Could not convert input for {self.name} to an EncryptedTreasureMap: {e}") from e
diff --git a/nucypher/characters/lawful.py b/nucypher/characters/lawful.py
index db6e30dab..314039a08 100644
--- a/nucypher/characters/lawful.py
+++ b/nucypher/characters/lawful.py
@@ -77,7 +77,6 @@ from nucypher.config.constants import END_OF_POLICIES_PROBATIONARY_PERIOD
from nucypher.config.storages import ForgetfulNodeStorage, NodeStorage
from nucypher.control.controllers import WebController
from nucypher.control.emitters import StdoutEmitter
-from nucypher.crypto.constants import HRAC_LENGTH
from nucypher.crypto.keypairs import HostingKeypair
from nucypher.crypto.kits import UmbralMessageKit
from nucypher.crypto.powers import (
@@ -108,7 +107,7 @@ from nucypher.network.nodes import NodeSprout, TEACHER_NODES, Teacher
from nucypher.network.protocols import InterfaceInfo, parse_node_uri
from nucypher.network.server import ProxyRESTServer, TLSHostingPower, make_rest_app
from nucypher.network.trackers import AvailabilityTracker
-from nucypher.policy.maps import TreasureMap, AuthorizedKeyFrag
+from nucypher.policy.maps import TreasureMap, EncryptedTreasureMap, AuthorizedKeyFrag
from nucypher.policy.orders import WorkOrder
from nucypher.policy.policies import Policy
from nucypher.utilities.logging import Logger
@@ -404,7 +403,7 @@ class Alice(Character, BlockchainPolicyAuthor):
"""
try:
# Wait for a revocation threshold of nodes to be known ((n - m) + 1)
- revocation_threshold = ((policy.n - policy.treasure_map.m) + 1)
+ revocation_threshold = ((policy.n - policy.m) + 1)
self.block_until_specific_nodes_are_known(
policy.revocation_kit.revokable_addresses,
allow_missing=(policy.n - revocation_threshold))
@@ -624,13 +623,13 @@ class Bob(Character):
return unknown_ursulas, known_ursulas, treasure_map.m
- def _try_orient(self, treasure_map, publisher_verifying_key):
+ def _try_orient(self, enc_treasure_map, publisher_verifying_key):
# TODO: should be rather a Publisher character
alice = Alice.from_public_keys(verifying_key=publisher_verifying_key)
compass = self.make_compass_for_alice(alice)
try:
- treasure_map.orient(compass)
- except treasure_map.InvalidSignature:
+ return enc_treasure_map.orient(compass)
+ except EncryptedTreasureMap.InvalidSignature:
raise # TODO: Maybe do something here? NRN
def get_treasure_map(self, publisher_verifying_key: PublicKey, label: bytes):
@@ -646,9 +645,9 @@ class Bob(Character):
if not self.known_nodes:
raise self.NotEnoughTeachers("Can't retrieve without knowing about any nodes at all. Pass a teacher or seed node.")
- treasure_map = self.get_treasure_map_from_known_ursulas(hrac)
+ enc_treasure_map = self.get_treasure_map_from_known_ursulas(hrac)
- self._try_orient(treasure_map, publisher_verifying_key)
+ treasure_map = self._try_orient(enc_treasure_map, publisher_verifying_key)
self.treasure_maps[hrac] = treasure_map
return treasure_map
@@ -656,7 +655,9 @@ class Bob(Character):
return partial(self.verify_from, alice, decrypt=True)
def construct_policy_hrac(self, publisher_verifying_key: PublicKey, label: bytes) -> bytes:
- _hrac = keccak_digest(bytes(publisher_verifying_key) + bytes(self.stamp) + label)[:HRAC_LENGTH]
+ _hrac = TreasureMap.derive_hrac(publisher_verifying_key=publisher_verifying_key,
+ bob_verifying_key=self.stamp.as_umbral_pubkey(),
+ label=label)
return _hrac
def get_treasure_map_from_known_ursulas(self, hrac: bytes, timeout=3):
@@ -931,11 +932,11 @@ class Bob(Character):
def _handle_treasure_map(self,
publisher_verifying_key: PublicKey,
label: bytes,
- treasure_map: Optional['TreasureMap'] = None,
+ enc_treasure_map: Optional['EncryptedTreasureMap'] = None,
) -> 'TreasureMap':
- if treasure_map is not None:
- self._try_orient(treasure_map, publisher_verifying_key)
- # self.treasure_maps[treasure_map.hrac] = treasure_map # TODO: Can we?
+ if enc_treasure_map is not None:
+ treasure_map = self._try_orient(enc_treasure_map, publisher_verifying_key)
+ self.treasure_maps[treasure_map.hrac] = treasure_map
else:
hrac = self.construct_policy_hrac(publisher_verifying_key, label)
try:
@@ -954,7 +955,7 @@ class Bob(Character):
*message_kits: UmbralMessageKit,
label: bytes,
policy_encrypting_key: Optional[PublicKey] = None,
- treasure_map: Optional['TreasureMap'] = None,
+ treasure_map: Optional['EncryptedTreasureMap'] = None,
# Source Authentication
enrico: Optional["Enrico"] = None,
@@ -972,7 +973,7 @@ class Bob(Character):
# If a policy publisher's verifying key is not passed, use Alice's by default.
publisher_verifying_key = alice_verifying_key
- treasure_map, m = self._handle_treasure_map(treasure_map=treasure_map,
+ treasure_map, m = self._handle_treasure_map(enc_treasure_map=treasure_map,
publisher_verifying_key=publisher_verifying_key,
label=label)
diff --git a/nucypher/characters/unlawful.py b/nucypher/characters/unlawful.py
index 87bf3f848..da797b1b4 100644
--- a/nucypher/characters/unlawful.py
+++ b/nucypher/characters/unlawful.py
@@ -28,7 +28,7 @@ from nucypher.config.constants import TEMPORARY_DOMAIN
from nucypher.crypto.utils import encrypt_and_sign
from nucypher.crypto.powers import CryptoPower, SigningPower, DecryptingPower, TransactingPower
from nucypher.exceptions import DevelopmentInstallationRequired
-from nucypher.policy.maps import SignedTreasureMap
+from nucypher.policy.maps import EncryptedTreasureMap
class Vladimir(Ursula):
@@ -113,12 +113,11 @@ class Vladimir(Ursula):
If I see a TreasureMap being published, I can substitute my own payload and hope
that Ursula will store it for me for free.
"""
- old_message_kit = legit_treasure_map.message_kit
+ old_message_kit = legit_treasure_map._encrypted_tmap
new_message_kit, _signature = self.encrypt_for(self, b"I want to store this message for free.")
- legit_treasure_map.message_kit = new_message_kit
+ legit_treasure_map._encrypted_tmap = new_message_kit
# I'll copy Alice's key so that Ursula thinks that the HRAC has been properly signed.
- legit_treasure_map.message_kit.sender_verifying_key = old_message_kit.sender_verifying_key
- legit_treasure_map._set_payload()
+ legit_treasure_map._encrypted_tmap.sender_verifying_key = old_message_kit.sender_verifying_key
response = self.network_middleware.put_treasure_map_on_node(node=target_node,
map_payload=bytes(legit_treasure_map))
@@ -210,13 +209,13 @@ class Amonia(Alice):
the_map = policy.treasure_map
# I'll make a copy of it to modify for use in this attack.
- like_a_map_but_awful = SignedTreasureMap.from_bytes(bytes(the_map))
+ like_a_map_but_awful = EncryptedTreasureMap.from_bytes(bytes(the_map))
# I'll split the film up into segments, because I know Ursula checks that the file size is under 50k.
for i in range(50):
# I'll include a small portion of this awful film in a new message kit. We don't care about the signature for bob.
not_the_bees = b"Not the bees!" + int(i).to_bytes(length=4, byteorder="big")
- like_a_map_but_awful.message_kit, _signature_for_bob_which_is_never_Used = encrypt_and_sign(
+ like_a_map_but_awful._encrypted_tmap, _signature_for_bob_which_is_never_Used = encrypt_and_sign(
bob.public_keys(DecryptingPower),
plaintext=not_the_bees,
signer=self.stamp,
@@ -228,15 +227,15 @@ class Amonia(Alice):
# I know Ursula checks the public signature because she thinks I'm Alice. So I'll sign my bad hrac.
like_a_map_but_awful._public_signature = self.stamp(bytes(self.stamp) + bad_hrac)
- like_a_map_but_awful._hrac = bad_hrac
-
- # With the bad hrac and the segment of the film, I'm ready to make a phony payload and map ID.
- like_a_map_but_awful._set_payload()
- like_a_map_but_awful._set_id()
+ like_a_map_but_awful.hrac = bad_hrac
# I'll sign it again, so that it appears to match the policy for which I already paid.
transacting_power = self._crypto_power.power_ups(TransactingPower)
- like_a_map_but_awful.include_blockchain_signature(blockchain_signer=transacting_power.sign_message)
+ like_a_map_but_awful._blockchain_signature = EncryptedTreasureMap._make_blockchain_signature(
+ blockchain_signer=transacting_power.sign_message,
+ public_signature=like_a_map_but_awful._public_signature,
+ hrac=like_a_map_but_awful.hrac,
+ encrypted_tmap=like_a_map_but_awful._encrypted_tmap)
# Sucker.
response = self.network_middleware.put_treasure_map_on_node(sucker_ursula, map_payload=bytes(like_a_map_but_awful))
diff --git a/nucypher/crypto/constants.py b/nucypher/crypto/constants.py
index ff368c2a7..9eff147cf 100644
--- a/nucypher/crypto/constants.py
+++ b/nucypher/crypto/constants.py
@@ -19,7 +19,6 @@ along with nucypher. If not, see .
from cryptography.hazmat.primitives import hashes
# Policy component sizes
-HRAC_LENGTH = 16
SIGNATURE_SIZE = 64
EIP712_MESSAGE_SIGNATURE_SIZE = 65
diff --git a/nucypher/crypto/splitters.py b/nucypher/crypto/splitters.py
index 1667f711f..3828527a3 100644
--- a/nucypher/crypto/splitters.py
+++ b/nucypher/crypto/splitters.py
@@ -18,7 +18,6 @@ along with nucypher. If not, see .
from bytestring_splitter import BytestringSplitter
-from nucypher.crypto.constants import HRAC_LENGTH
from nucypher.crypto.umbral_adapter import CapsuleFrag, PublicKey, Capsule, Signature, KeyFrag
key_splitter = BytestringSplitter((PublicKey, PublicKey.serialized_size()))
@@ -26,4 +25,3 @@ signature_splitter = BytestringSplitter((Signature, Signature.serialized_size())
capsule_splitter = BytestringSplitter((Capsule, Capsule.serialized_size()))
cfrag_splitter = BytestringSplitter((CapsuleFrag, CapsuleFrag.serialized_size()))
kfrag_splitter = BytestringSplitter((KeyFrag, KeyFrag.serialized_size()))
-hrac_splitter = BytestringSplitter((bytes, HRAC_LENGTH))
diff --git a/nucypher/datastore/models.py b/nucypher/datastore/models.py
index c001b0cd1..0c591dee0 100644
--- a/nucypher/datastore/models.py
+++ b/nucypher/datastore/models.py
@@ -51,7 +51,7 @@ class Workorder(DatastoreRecord):
class EncryptedTreasureMap(DatastoreRecord):
- # Ideally this is a `policy.collections.TreasureMap`, but it causes a huge
+ # Ideally this is a `policy.maps.EncryptedTreasureMap`, but it causes a huge
# circular import due to `Bob` and `Character` in `policy.collections`.
# TODO #2126
_treasure_map = RecordField(bytes)
diff --git a/nucypher/network/server.py b/nucypher/network/server.py
index 8eb06bb88..2dd237d1a 100644
--- a/nucypher/network/server.py
+++ b/nucypher/network/server.py
@@ -308,25 +308,22 @@ def _make_rest_app(datastore: Datastore, this_node, domain: str, log: Logger) ->
We set the datastore identifier as the HRAC.
"""
- if not this_node.federated_only:
- from nucypher.policy.maps import SignedTreasureMap as _MapClass
- else:
- from nucypher.policy.maps import TreasureMap as _MapClass
+ from nucypher.policy.maps import EncryptedTreasureMap
# Step 1: First, we verify the signature of the received treasure map.
# This step also deserializes the treasure map if it's signed correctly.
try:
- received_treasure_map = _MapClass.from_bytes(bytes_representation=request.data, verify=True)
- except _MapClass.InvalidSignature:
- log.info(f"Bad TreasureMap HRAC Signature; not storing for HRAC {received_treasure_map._hrac.hex()}")
+ received_treasure_map = EncryptedTreasureMap.from_bytes(request.data)
+ except EncryptedTreasureMap.InvalidSignature:
+ log.info(f"Bad TreasureMap HRAC Signature; not storing for HRAC {received_treasure_map.hrac.hex()}")
return Response("This TreasureMap's HRAC is not properly signed.", status=401)
- hrac = received_treasure_map._hrac
+ hrac = received_treasure_map.hrac
# Step 2: Check if we already have the treasure map.
try:
with datastore.describe(TreasureMapModel, hrac) as stored_treasure_map:
- if _MapClass.from_bytes(stored_treasure_map.treasure_map) == received_treasure_map:
+ if EncryptedTreasureMap.from_bytes(stored_treasure_map.treasure_map) == received_treasure_map:
return Response("Already have this map.", status=303)
except RecordNotFound:
# This appears to be a new treasure map that we don't have!
@@ -336,7 +333,7 @@ def _make_rest_app(datastore: Datastore, this_node, domain: str, log: Logger) ->
# treasure map is valid pursuant to an active policy.
# We also set the expiration from the data on the blockchain here.
if not this_node.federated_only:
- policy = this_node.policy_agent.fetch_policy(policy_id=received_treasure_map._hrac)
+ policy = this_node.policy_agent.fetch_policy(policy_id=received_treasure_map.hrac)
# If the Policy doesn't exist, the policy_data is all zeros.
if policy.sponsor is NULL_ADDRESS:
log.info(f"TreasureMap is for non-existent Policy; not storing {hrac.hex()}")
diff --git a/nucypher/network/treasuremap.py b/nucypher/network/treasuremap.py
index e07ade596..da9e8e04f 100644
--- a/nucypher/network/treasuremap.py
+++ b/nucypher/network/treasuremap.py
@@ -23,6 +23,7 @@ from nucypher.acumen.perception import FleetSensor
from nucypher.crypto.signing import InvalidSignature
from nucypher.network.exceptions import NodeSeemsToBeDown
from nucypher.network.nodes import Learner
+from nucypher.policy.maps import TreasureMap, EncryptedTreasureMap
def get_treasure_map_from_known_ursulas(learner: Learner,
@@ -33,11 +34,6 @@ def get_treasure_map_from_known_ursulas(learner: Learner,
Iterate through the nodes we know, asking for the TreasureMap.
Return the first one who has it.
"""
- if learner.federated_only:
- from nucypher.policy.maps import TreasureMap as _MapClass
- else:
- from nucypher.policy.maps import SignedTreasureMap as _MapClass
-
start = maya.now()
# Spend no more than half the timeout finding the nodes. 8 nodes is arbitrary. Come at me.
@@ -63,7 +59,7 @@ def get_treasure_map_from_known_ursulas(learner: Learner,
if response.status_code == 200 and response.content:
try:
- treasure_map = _MapClass.from_bytes(response.content)
+ treasure_map = EncryptedTreasureMap.from_bytes(response.content)
return treasure_map
except InvalidSignature:
# TODO: What if a node gives a bunk TreasureMap? NRN
@@ -74,8 +70,8 @@ def get_treasure_map_from_known_ursulas(learner: Learner,
learner.learn_from_teacher_node()
if (start - maya.now()).seconds > timeout:
- raise _MapClass.NowhereToBeFound(f"Asked {len(learner.known_nodes)} nodes, "
- f"but none had map {hrac.hex()}")
+ raise TreasureMap.NowhereToBeFound(f"Asked {len(learner.known_nodes)} nodes, "
+ f"but none had map {hrac.hex()}")
def find_matching_nodes(known_nodes: FleetSensor,
diff --git a/nucypher/policy/maps.py b/nucypher/policy/maps.py
index 0a517afe2..220de41b2 100644
--- a/nucypher/policy/maps.py
+++ b/nucypher/policy/maps.py
@@ -16,31 +16,150 @@ along with nucypher. If not, see .
"""
-from typing import Optional, Callable, Union, Sequence
+from typing import Optional, Callable, Union, Sequence, Dict
from bytestring_splitter import (
BytestringSplitter,
VariableLengthBytestring,
- BytestringKwargifier,
BytestringSplittingError,
- VersioningMixin,
- BrandingMixin
)
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 nucypher.blockchain.eth.constants import ETH_ADDRESS_BYTE_LENGTH
from nucypher.characters.base import Character
-from nucypher.crypto.constants import HRAC_LENGTH, EIP712_MESSAGE_SIGNATURE_SIZE
+from nucypher.crypto.constants import EIP712_MESSAGE_SIGNATURE_SIZE
from nucypher.crypto.kits import UmbralMessageKit
from nucypher.crypto.powers import DecryptingPower, SigningPower
from nucypher.crypto.signing import SignatureStamp
-from nucypher.crypto.splitters import signature_splitter, hrac_splitter, kfrag_splitter
+from nucypher.crypto.splitters import signature_splitter, kfrag_splitter
from nucypher.crypto.umbral_adapter import KeyFrag, VerifiedKeyFrag, PublicKey, Signature
from nucypher.crypto.utils import keccak_digest, encrypt_and_sign, verify_eip_191
from nucypher.network.middleware import RestMiddleware
+class TreasureMap:
+
+ HRAC_LENGTH = 16
+
+ class NowhereToBeFound(RestMiddleware.NotFound):
+ """
+ Called when no known nodes have it.
+ """
+
+ class IsDisorienting(Exception):
+ """
+ Called when an oriented TreasureMap lists fewer than m destinations, which
+ leaves Bob disoriented.
+ """
+
+ main_splitter = BytestringSplitter(
+ (int, 1, {'byteorder': 'big'}),
+ (bytes, HRAC_LENGTH),
+ )
+
+ ursula_and_kfrag_payload_splitter = BytestringSplitter(
+ (to_checksum_address, ETH_ADDRESS_BYTE_LENGTH),
+ (UmbralMessageKit, VariableLengthBytestring),
+ )
+
+ @staticmethod
+ def derive_hrac(publisher_verifying_key: PublicKey, bob_verifying_key: PublicKey, label: bytes) -> bytes:
+ """
+ Here's our "hashed resource access code".
+
+ A hash of:
+ * Alice's public key
+ * Bob's public key
+ * the label
+
+ Alice and Bob have all the information they need to construct this.
+ Ursula does not, so we share it with her.
+
+ This way, Bob can generate it and use it to find the TreasureMap.
+ """
+ return keccak_digest(bytes(publisher_verifying_key) + bytes(bob_verifying_key) + label)[:TreasureMap.HRAC_LENGTH]
+
+ @classmethod
+ def construct_by_publisher(cls,
+ publisher: 'Alice',
+ bob: 'Bob',
+ label: bytes,
+ ursulas: Sequence['Ursula'],
+ verified_kfrags: Sequence[VerifiedKeyFrag],
+ m: int,
+ ) -> 'TreasureMap':
+ """Create a new treasure map for a collection of ursulas and kfrags."""
+
+ if m < 1 or m > 255:
+ raise ValueError("The threshold must be between 1 and 255.")
+
+ if len(ursulas) < m:
+ raise ValueError(
+ f"The number of destinations ({len(ursulas)}) "
+ f"must be equal or greater than the threshold ({m})")
+
+ # The HRAC is needed to produce kfrag writs.
+ hrac = cls.derive_hrac(publisher_verifying_key=publisher.stamp.as_umbral_pubkey(),
+ bob_verifying_key=bob.public_keys(SigningPower),
+ label=label)
+
+ # Encrypt each kfrag for an Ursula.
+ destinations = {}
+ for ursula, verified_kfrag in zip(ursulas, verified_kfrags):
+ kfrag_payload = bytes(AuthorizedKeyFrag.construct_by_publisher(hrac=hrac,
+ verified_kfrag=verified_kfrag,
+ publisher_stamp=publisher.stamp))
+ encrypted_kfrag, _signature = encrypt_and_sign(recipient_pubkey_enc=ursula.public_keys(DecryptingPower),
+ plaintext=kfrag_payload,
+ signer=publisher.stamp)
+
+ destinations[ursula.checksum_address] = encrypted_kfrag
+
+ return cls(m=m, hrac=hrac, destinations=destinations)
+
+ def __init__(self,
+ m: int,
+ hrac: bytes,
+ destinations: Dict[ChecksumAddress, bytes],
+ ):
+ self.m = m
+ self.destinations = destinations
+ self.hrac = hrac
+
+ def prepare_for_publication(self, publisher, bob, blockchain_signer=None):
+ return EncryptedTreasureMap.construct_by_publisher(
+ self, publisher, bob, blockchain_signer=blockchain_signer)
+
+ def _nodes_as_bytes(self):
+ nodes_as_bytes = b""
+ for ursula_address, encrypted_kfrag in self.destinations.items():
+ node_id = to_canonical_address(ursula_address)
+ kfrag = bytes(VariableLengthBytestring(encrypted_kfrag.to_bytes()))
+ nodes_as_bytes += (node_id + kfrag)
+ return nodes_as_bytes
+
+ def __bytes__(self):
+ return self.m.to_bytes(1, "big") + self.hrac + self._nodes_as_bytes()
+
+ @classmethod
+ def from_bytes(cls, data):
+ try:
+ m, hrac, remainder = cls.main_splitter(data, return_remainder=True)
+ ursula_and_kfrags = cls.ursula_and_kfrag_payload_splitter.repeat(remainder)
+ except BytestringSplittingError as e:
+ raise cls.IsDisorienting('Invalid treasure map contents.') from e
+ destinations = {u: k for u, k in ursula_and_kfrags}
+ return cls(m, hrac, destinations)
+
+ def __iter__(self):
+ return iter(self.destinations.items())
+
+ def __len__(self):
+ return len(self.destinations)
+
+
class AuthorizedKeyFrag:
_WRIT_CHECKSUM_SIZE = 32
@@ -50,8 +169,8 @@ class AuthorizedKeyFrag:
ENCRYPTED_SIZE = 619
_splitter = BytestringSplitter(
- hrac_splitter, # HRAC
- BytestringSplitter((bytes, _WRIT_CHECKSUM_SIZE)), # kfrag checksum
+ (bytes, TreasureMap.HRAC_LENGTH), # HRAC
+ (bytes, _WRIT_CHECKSUM_SIZE), # kfrag checksum
signature_splitter, # Publisher's signature
kfrag_splitter,
)
@@ -103,215 +222,49 @@ class AuthorizedKeyFrag:
return cls(hrac, kfrag_checksum, writ_signature, kfrag)
-class TreasureMapSplitter(BrandingMixin, VersioningMixin, BytestringKwargifier):
- pass
+class EncryptedTreasureMap:
-
-class TreasureMap:
- VERSION_NUMBER = 1 # Increment when serialization format changes.
-
- _BRAND = b'TM'
- _VERSION = int(VERSION_NUMBER).to_bytes(VersioningMixin.HEADER_LENGTH, 'big')
-
- class NowhereToBeFound(RestMiddleware.NotFound):
- """
- Called when no known nodes have it.
- """
-
- class IsDisorienting(Exception):
- """
- Called when an oriented TreasureMap lists fewer than m destinations, which
- leaves Bob disoriented.
- """
-
- class OldVersion(Exception):
- """Raised when a treasure map's version is too old or contents are incompatible."""
-
- ursula_and_kfrag_payload_splitter = BytestringSplitter(
- (to_checksum_address, ETH_ADDRESS_BYTE_LENGTH),
+ _splitter = BytestringSplitter(
+ signature_splitter,
+ (bytes, TreasureMap.HRAC_LENGTH),
(UmbralMessageKit, VariableLengthBytestring),
- )
+ (bytes, 1))
from nucypher.crypto.signing import \
InvalidSignature # Raised when the public signature (typically intended for Ursula) is not valid.
- def __init__(self,
- m: int = None,
- destinations=None,
- message_kit: UmbralMessageKit = None,
- public_signature: Signature = None,
- hrac: Optional[bytes] = None,
- version: bytes = None
- ) -> None:
+ @staticmethod
+ def _make_blockchain_signature(blockchain_signer, public_signature, hrac, encrypted_tmap):
+ # This method exists mainly to link this scheme to the corresponding test
+ payload = bytes(public_signature) + hrac + encrypted_tmap.to_bytes()
+ return blockchain_signer(payload)
- if version is not None:
- self.version = version
+ @classmethod
+ def construct_by_publisher(cls, treasure_map, publisher, bob, blockchain_signer=None):
+ # TODO: `publisher` here can be different from the one in TreasureMap, it seems.
+ # Do we ever cross-check them? Do we want to enforce them to be the same?
- if m is not None:
- if m > 255:
- raise ValueError("Largest allowed value for m is 255.")
- self._m = m
+ bob_encrypting_key = bob.public_keys(DecryptingPower)
- self._destinations = destinations or {}
+ encrypted_tmap, _signature_for_bob = encrypt_and_sign(bob_encrypting_key,
+ plaintext=bytes(treasure_map),
+ signer=publisher.stamp)
+ public_signature = publisher.stamp(bytes(publisher.stamp) + treasure_map.hrac)
+
+ if blockchain_signer is not None:
+ blockchain_signature = EncryptedTreasureMap._make_blockchain_signature(
+ blockchain_signer, public_signature, treasure_map.hrac, encrypted_tmap)
else:
- self._m = NO_DECRYPTION_PERFORMED
- self._destinations = NO_DECRYPTION_PERFORMED
+ blockchain_signature = None
- self._id = None
+ return cls(treasure_map.hrac, public_signature, encrypted_tmap, blockchain_signature=blockchain_signature)
- self.message_kit = message_kit
+ def __init__(self, hrac, public_signature, encrypted_tmap, blockchain_signature=None):
+ self.hrac = hrac
self._public_signature = public_signature
- self._hrac = hrac
- self._payload = None
-
- if message_kit is not None:
- self.message_kit = message_kit
- self._set_id()
- else:
- self.message_kit = None
-
- def __eq__(self, other):
- try:
- return self.public_id() == other.public_id()
- except AttributeError:
- raise TypeError(
- f"Can't compare {type(other).__name__} to a TreasureMap (it needs to implement public_id() )")
-
- def __iter__(self):
- return iter(self.destinations.items())
-
- def __len__(self):
- return len(self.destinations)
-
- def __bytes__(self):
- if self._payload is None:
- self._set_payload()
- return self._BRAND + self._VERSION + self._payload
-
- @classmethod
- def get_splitter(cls, bytes_representation: bytes) -> BytestringKwargifier:
- """
- Takes a bytes representation of a treasure map and returns a splitter matching the apparent format.
- In the event of a missing or malformed header, returns the splitter designed for unversioned maps.
- """
- representation_metadata = TreasureMapSplitter.get_metadata(bytes_representation)
-
- # header = bytes_representation[:cls._HEADER_SIZE]
- brand_matches = representation_metadata['brand'] == cls._BRAND
- version = representation_metadata['version']
-
- if version in cls._splitters and brand_matches:
- # This representation is compatible with a known stencil.
- splitter = cls._splitters[version]
- else:
- # It's possible that this is a preversioned representation.
- splitter = cls._splitters['unversioned'] # TODO: In this case, it's still a map from a previous version - how will we handle sin KFrags?
- return splitter
-
- @classmethod
- def from_bytes(cls, bytes_representation: bytes, verify: bool = True) -> Union['TreasureMap', 'SignedTreasureMap']:
- splitter = cls.get_splitter(bytes_representation)
- treasure_map = splitter(bytes_representation)
- if verify:
- treasure_map.public_verify()
- return treasure_map
-
- @property
- def _verifying_key(self):
- return self.message_kit.sender_verifying_key
-
- @property
- def m(self) -> int:
- if self._m == NO_DECRYPTION_PERFORMED:
- raise TypeError("The TreasureMap is probably encrypted. You must decrypt it first.")
- return self._m
-
- @property
- def destinations(self):
- if self._destinations == NO_DECRYPTION_PERFORMED:
- raise TypeError("The TreasureMap is probably encrypted. You must decrypt it first.")
- return self._destinations
-
- def _set_id(self) -> None:
- self._id = keccak_digest(bytes(self._verifying_key) + bytes(self._hrac)).hex()
-
- def _set_payload(self) -> None:
- self._payload = bytes(self._public_signature) \
- + self._hrac \
- + bytes(VariableLengthBytestring(self.message_kit.to_bytes()))
-
- def derive_hrac(self, publisher_stamp: SignatureStamp, bob_verifying_key: PublicKey, label: bytes) -> None:
- """
- Here's our "hashed resource access code".
-
- A hash of:
- * Alice's public key
- * Bob's public key
- * the label
-
- Alice and Bob have all the information they need to construct this.
- Ursula does not, so we share it with her.
-
- This way, Bob can generate it and use it to find the TreasureMap.
- """
- self._hrac = keccak_digest(bytes(publisher_stamp) + bytes(bob_verifying_key) + label)[:HRAC_LENGTH]
-
- def prepare_for_publication(self, bob_encrypting_key, publisher_stamp):
- plaintext = self._m.to_bytes(1, "big") + self.nodes_as_bytes()
- self.message_kit, _signature_for_bob = encrypt_and_sign(bob_encrypting_key,
- plaintext=plaintext,
- signer=publisher_stamp)
- self._public_signature = publisher_stamp(bytes(publisher_stamp) + self._hrac)
- self._set_payload()
- self._set_id()
-
- def nodes_as_bytes(self):
- if self.destinations == NO_DECRYPTION_PERFORMED:
- return NO_DECRYPTION_PERFORMED
- else:
- nodes_as_bytes = b""
- for ursula_address, encrypted_kfrag in self.destinations.items():
- node_id = to_canonical_address(ursula_address)
- kfrag = bytes(VariableLengthBytestring(encrypted_kfrag.to_bytes()))
- nodes_as_bytes += (node_id + kfrag)
- return nodes_as_bytes
-
- def add_kfrag(self, ursula, verified_kfrag: VerifiedKeyFrag, publisher_stamp: SignatureStamp) -> None:
- if self.destinations == NO_DECRYPTION_PERFORMED:
- # Unsure how this situation can arise, but let's raise an error just in case.
- raise TypeError("This TreasureMap is encrypted. You can't add another node without decrypting it.")
-
- if not self._hrac:
- # TODO: Use a better / different exception or encapsulate HRAC derivation with KFrag population.
- raise RuntimeError(
- 'Cannot add KFrag to TreasureMap without an HRAC set. Call "derive_hrac" and try again.')
-
- # Encrypt this kfrag payload for Ursula.
- kfrag_payload = bytes(AuthorizedKeyFrag.construct_by_publisher(hrac=self._hrac,
- verified_kfrag=verified_kfrag,
- publisher_stamp=publisher_stamp))
- encrypted_kfrag, _signature = encrypt_and_sign(recipient_pubkey_enc=ursula.public_keys(DecryptingPower),
- plaintext=kfrag_payload,
- signer=publisher_stamp)
-
- # Set the encrypted kfrag payload into the map.
- self.destinations[ursula.checksum_address] = encrypted_kfrag
-
- def public_id(self) -> str:
- """
- We need an ID that Bob can glean from knowledge he already has *and* which Ursula can verify came from Alice.
- Ursula will refuse to propagate this if it she can't prove the payload is signed by Alice's public key,
- which is included in it,
- """
- return self._id
-
- def public_verify(self) -> bool:
- message = bytes(self._verifying_key) + self._hrac
- verified = self._public_signature.verify(self._verifying_key, message=message)
- if verified:
- return True
- else:
- raise self.InvalidSignature("This TreasureMap is not properly publicly signed by Alice.")
+ self._verifying_key = encrypted_tmap.sender_verifying_key
+ self._encrypted_tmap = encrypted_tmap
+ self._blockchain_signature = blockchain_signature
def orient(self, compass: Callable):
"""
@@ -319,113 +272,47 @@ class TreasureMap:
payload message kit).
"""
try:
- map_in_the_clear = compass(message_kit=self.message_kit)
+ map_in_the_clear = compass(self._encrypted_tmap)
except Character.InvalidSignature:
raise self.InvalidSignature("This TreasureMap does not contain the correct signature from Alice to Bob.")
- self._m = map_in_the_clear[0]
- try:
- ursula_and_kfrags = self.ursula_and_kfrag_payload_splitter.repeat(map_in_the_clear[1:])
- except BytestringSplittingError:
- raise self.IsDisorienting('Invalid treasure map contents.')
- self._destinations = {u: k for u, k in ursula_and_kfrags}
- self.check_for_sufficient_destinations() # TODO: Remove this check, how is this even possible?
- def check_for_sufficient_destinations(self):
- if len(self._destinations) < self._m or self._m == 0:
- raise self.IsDisorienting(
- f"TreasureMap lists only {len(self._destinations)} destination, "
- f"but requires interaction with {self._m} nodes.")
+ return TreasureMap.from_bytes(map_in_the_clear)
- @classmethod
- def construct_by_publisher(cls,
- publisher: 'Alice',
- bob: 'Bob',
- label: bytes,
- ursulas: Sequence['Ursula'],
- verified_kfrags: Sequence[VerifiedKeyFrag],
- m: int
- ) -> 'TreasureMap':
- """Create a new treasure map for a collection of ursulas and kfrags."""
-
- # The HRAC is needed to produce kfrag writs.
- treasure_map = cls(m=m)
- treasure_map.derive_hrac(publisher_stamp=publisher.stamp,
- bob_verifying_key=bob.public_keys(SigningPower),
- label=label)
-
- # Encrypt each kfrag for an Ursula.
- for ursula, verified_kfrag in zip(ursulas, verified_kfrags):
- treasure_map.add_kfrag(ursula=ursula,
- verified_kfrag=verified_kfrag,
- publisher_stamp=publisher.stamp)
-
- # Sign the map if needed before sending it out into the world.
- treasure_map.prepare_for_publication(bob_encrypting_key=bob.public_keys(DecryptingPower),
- publisher_stamp=publisher.stamp)
-
- return treasure_map
-
-
-# FIXME: a dirty hack to make the tests pass. Fix it ASAP.
-# The problem with __new__ is that it does not get called before the first object of the class
-# is instantiated, so when we call `from_bytes()` for the first time, the `_splitters` dict
-# needs to already be populated.
-TreasureMap._splitters = {
- 'unversioned': BytestringKwargifier(TreasureMap,
- public_signature=signature_splitter,
- hrac=(bytes, HRAC_LENGTH),
- message_kit=(UmbralMessageKit, VariableLengthBytestring),
- ),
- 1: TreasureMapSplitter(TreasureMap,
- public_signature=signature_splitter,
- hrac=(bytes, HRAC_LENGTH),
- message_kit=(UmbralMessageKit, VariableLengthBytestring),
- )
- }
-
-
-class SignedTreasureMap(TreasureMap):
-
- _BRAND = b'SM'
-
- def __init__(self, blockchain_signature=NOT_SIGNED, *args, **kwargs):
- self._blockchain_signature = blockchain_signature
- super().__init__(*args, **kwargs)
-
- def include_blockchain_signature(self, blockchain_signer):
- if self._payload is None:
- self._set_payload()
- self._blockchain_signature = blockchain_signer(self._payload)
+ def __bytes__(self):
+ return (bytes(self._public_signature) +
+ self.hrac +
+ bytes(VariableLengthBytestring(self._encrypted_tmap.to_bytes())) +
+ # Other variants:
+ # - store all zeros if no blockchain signature
+ # - store the flag too in addition to all zeros, for added sanity check
+ # - use a faux blockchain signer in federated case
+ (b'\x00' if self._blockchain_signature is None else (b'\x01' + bytes(self._blockchain_signature)))
+ )
def verify_blockchain_signature(self, checksum_address):
- self._set_payload()
- return verify_eip_191(message=self._payload,
+ if self._blockchain_signature is None:
+ raise ValueError("This EncryptedTreasureMap is not blockchain-signed")
+ payload = bytes(self._public_signature) + self.hrac + self._encrypted_tmap.to_bytes()
+ return verify_eip_191(message=payload,
signature=self._blockchain_signature,
address=checksum_address)
- def __bytes__(self):
- if self._blockchain_signature is NOT_SIGNED:
- raise self.InvalidSignature(
- "Can't cast a SignedTreasureMap to bytes until it has a blockchain signature "
- "(otherwise, is it really a 'SignedTreasureMap'?")
- if self._payload is None:
- self._set_payload()
- return self._BRAND + self._VERSION + self._blockchain_signature + self._payload
+ def _public_verify(self) -> bool:
+ message = bytes(self._verifying_key) + self.hrac
+ if not self._public_signature.verify(self._verifying_key, message=message):
+ raise self.InvalidSignature("This TreasureMap is not properly publicly signed by Alice.")
+ @classmethod
+ def from_bytes(cls, data):
+ try:
+ public_signature, hrac, message_kit, bc_sig, remainder = cls._splitter(data, return_remainder=True)
+ if bc_sig == b'\x01':
+ blockchain_signature, = BytestringSplitter((bytes, EIP712_MESSAGE_SIGNATURE_SIZE))(remainder)
+ else:
+ blockchain_signature = None
+ except BytestringSplittingError as e:
+ raise TreasureMap.IsDisorienting('Invalid treasure map contents.') from e
-# FIXME: a dirty hack to make the tests pass. Fix it ASAP.
-# See the comment at `TreasureMap._splitters` above.
-SignedTreasureMap._splitters = {
- 'unversioned': BytestringKwargifier(SignedTreasureMap,
- blockchain_signature=EIP712_MESSAGE_SIGNATURE_SIZE,
- public_signature=signature_splitter,
- hrac=(bytes, HRAC_LENGTH),
- message_kit=(UmbralMessageKit, VariableLengthBytestring),
- ),
- 1: TreasureMapSplitter(SignedTreasureMap,
- blockchain_signature=EIP712_MESSAGE_SIGNATURE_SIZE,
- public_signature=signature_splitter,
- hrac=(bytes, HRAC_LENGTH),
- message_kit=(UmbralMessageKit, VariableLengthBytestring),
- )
- }
+ result = cls(hrac, public_signature, message_kit, blockchain_signature=blockchain_signature)
+ result._public_verify()
+ return result
diff --git a/nucypher/policy/orders.py b/nucypher/policy/orders.py
index 36a46726a..ed92e22ed 100644
--- a/nucypher/policy/orders.py
+++ b/nucypher/policy/orders.py
@@ -32,7 +32,6 @@ from nucypher.crypto.splitters import (
capsule_splitter,
cfrag_splitter,
signature_splitter,
- hrac_splitter,
kfrag_splitter
)
from nucypher.crypto.umbral_adapter import (
@@ -41,7 +40,7 @@ from nucypher.crypto.umbral_adapter import (
PublicKey,
Signature
)
-from nucypher.policy.maps import AuthorizedKeyFrag
+from nucypher.policy.maps import AuthorizedKeyFrag, TreasureMap
class WorkOrder:
@@ -59,7 +58,7 @@ class WorkOrder:
+ key_splitter \
+ key_splitter \
+ key_splitter \
- + hrac_splitter \
+ + BytestringSplitter((bytes, TreasureMap.HRAC_LENGTH)) \
+ BytestringSplitter((bytes, VariableLengthBytestring))
class PRETask:
diff --git a/nucypher/policy/policies.py b/nucypher/policy/policies.py
index 559817ce0..eda2dbe02 100644
--- a/nucypher/policy/policies.py
+++ b/nucypher/policy/policies.py
@@ -26,14 +26,14 @@ from eth_typing.evm import ChecksumAddress
from twisted.internet import reactor
from nucypher.blockchain.eth.constants import POLICY_ID_LENGTH
-from nucypher.crypto.constants import HRAC_LENGTH
from nucypher.crypto.kits import RevocationKit
-from nucypher.crypto.powers import TransactingPower
+from nucypher.crypto.powers import TransactingPower, DecryptingPower
from nucypher.crypto.splitters import key_splitter
from nucypher.crypto.utils import keccak_digest
from nucypher.crypto.umbral_adapter import PublicKey, VerifiedKeyFrag, Signature
from nucypher.crypto.utils import construct_policy_id
from nucypher.network.middleware import RestMiddleware
+from nucypher.policy.maps import TreasureMap
from nucypher.policy.reservoir import (
make_federated_staker_reservoir,
MergedReservoir,
@@ -231,7 +231,9 @@ class Policy(ABC):
Alice and Bob have all the information they need to construct this.
'Ursula' does not, so we share it with her.
"""
- self.hrac = keccak_digest(bytes(self.publisher.stamp) + bytes(self.bob.stamp) + self.label)[:HRAC_LENGTH]
+ self.hrac = TreasureMap.derive_hrac(publisher_verifying_key=self.publisher.stamp.as_umbral_pubkey(),
+ bob_verifying_key=self.bob.stamp.as_umbral_pubkey(),
+ label=self.label)
def __repr__(self):
return f"{self.__class__.__name__}:{self._id.hex()[:6]}"
@@ -342,21 +344,8 @@ class Policy(ABC):
return accepted_arrangements
- def _make_treasure_map(self,
- network_middleware: RestMiddleware,
- arrangements: Dict['Ursula', Arrangement],
- ) -> 'TreasureMap':
- """Author a new treasure map for this policy as Alice.."""
- treasure_map = self._treasure_map_class.construct_by_publisher(publisher=self.publisher,
- bob=self.bob,
- label=self.label,
- ursulas=list(arrangements),
- verified_kfrags=self.kfrags,
- m=self.m)
- return treasure_map
-
def _make_publisher(self,
- treasure_map: 'TreasureMap',
+ treasure_map: 'EncryptedTreasureMap',
network_middleware: RestMiddleware,
) -> TreasureMapPublisher:
@@ -369,6 +358,9 @@ class Policy(ABC):
nodes=target_nodes,
network_middleware=network_middleware)
+ def _encrypt_treasure_map(self, treasure_map):
+ return treasure_map.prepare_for_publication(self.publisher, self.bob)
+
def enact(self,
network_middleware: RestMiddleware,
handpicked_ursulas: Optional[Iterable['Ursula']] = None,
@@ -388,18 +380,27 @@ class Policy(ABC):
self._enact_arrangements(arrangements)
- treasure_map = self._make_treasure_map(network_middleware=network_middleware,
- arrangements=arrangements)
- treasure_map_publisher = self._make_publisher(treasure_map=treasure_map,
+ treasure_map = TreasureMap.construct_by_publisher(publisher=self.publisher,
+ bob=self.bob,
+ label=self.label,
+ ursulas=list(arrangements),
+ verified_kfrags=self.kfrags,
+ m=self.m)
+
+ enc_treasure_map = self._encrypt_treasure_map(treasure_map)
+
+ treasure_map_publisher = self._make_publisher(treasure_map=enc_treasure_map,
network_middleware=network_middleware)
- revocation_kit = RevocationKit(treasure_map=treasure_map, signer=self.publisher.stamp) # TODO: Signal revocation without using encrypted kfrag
+ # TODO: Signal revocation without using encrypted kfrag
+ revocation_kit = RevocationKit(treasure_map=treasure_map, signer=self.publisher.stamp)
enacted_policy = EnactedPolicy(self._id,
self.hrac,
self.label,
self.public_key,
- treasure_map,
+ treasure_map.m,
+ enc_treasure_map,
treasure_map_publisher,
revocation_kit,
self.publisher.stamp.as_umbral_pubkey())
@@ -426,8 +427,6 @@ class Policy(ABC):
class FederatedPolicy(Policy):
- from nucypher.policy.maps import TreasureMap as __map_class
- _treasure_map_class = __map_class
def _not_enough_ursulas_exception(self):
return Policy.NotEnoughUrsulas
@@ -445,9 +444,6 @@ class BlockchainPolicy(Policy):
A collection of n Arrangements representing a single Policy
"""
- from nucypher.policy.maps import SignedTreasureMap as __map_class
- _treasure_map_class = __map_class
-
class InvalidPolicyValue(ValueError):
pass
@@ -542,15 +538,12 @@ class BlockchainPolicy(Policy):
def _enact_arrangements(self, arrangements: Dict['Ursula', Arrangement]) -> None:
self._publish_to_blockchain(ursulas=list(arrangements))
- def _make_treasure_map(self,
- network_middleware: RestMiddleware,
- arrangements: Dict['Ursula', Arrangement],
- ) -> 'TreasureMap':
-
- treasure_map = super()._make_treasure_map(network_middleware, arrangements)
+ def _encrypt_treasure_map(self, treasure_map):
transacting_power = self.publisher._crypto_power.power_ups(TransactingPower)
- treasure_map.include_blockchain_signature(transacting_power.sign_message)
- return treasure_map
+ return treasure_map.prepare_for_publication(
+ self.publisher,
+ self.bob,
+ blockchain_signer=transacting_power.sign_message)
class EnactedPolicy:
@@ -560,7 +553,8 @@ class EnactedPolicy:
hrac: bytes,
label: bytes,
public_key: PublicKey,
- treasure_map: 'TreasureMap',
+ m: int,
+ treasure_map: 'EncryptedTreasureMap',
treasure_map_publisher: TreasureMapPublisher,
revocation_kit: RevocationKit,
publisher_verifying_key: PublicKey,
@@ -573,7 +567,8 @@ class EnactedPolicy:
self.treasure_map = treasure_map
self.treasure_map_publisher = treasure_map_publisher
self.revocation_kit = revocation_kit
- self.n = len(self.treasure_map.destinations)
+ self.m = m
+ self.n = len(self.revocation_kit)
self.publisher_verifying_key = publisher_verifying_key
def publish_treasure_map(self):
diff --git a/nucypher/utilities/porter/control/interfaces.py b/nucypher/utilities/porter/control/interfaces.py
index e5aad1829..11b47ef2b 100644
--- a/nucypher/utilities/porter/control/interfaces.py
+++ b/nucypher/utilities/porter/control/interfaces.py
@@ -19,7 +19,6 @@ from typing import List, Optional
from eth_typing import ChecksumAddress
from nucypher.crypto.umbral_adapter import PublicKey
-from nucypher.characters.control.specifications.fields import EncryptedTreasureMap
from nucypher.control.interfaces import ControlInterface, attach_schema
from nucypher.utilities.porter.control.specifications import porter_schema
@@ -27,8 +26,6 @@ from nucypher.utilities.porter.control.specifications import porter_schema
class PorterInterface(ControlInterface):
def __init__(self, porter: 'Porter' = None, *args, **kwargs):
super().__init__(implementer=porter, *args, **kwargs)
- # set federated/non-federated context for publish treasure map schema
- PorterInterface.publish_treasure_map._schema.context[EncryptedTreasureMap.IS_FEDERATED_CONTEXT_KEY] = porter.federated_only
#
# Alice Endpoints
diff --git a/nucypher/utilities/porter/control/specifications/fields/hrac.py b/nucypher/utilities/porter/control/specifications/fields/hrac.py
index 806f35698..dd31d8ebf 100644
--- a/nucypher/utilities/porter/control/specifications/fields/hrac.py
+++ b/nucypher/utilities/porter/control/specifications/fields/hrac.py
@@ -20,7 +20,7 @@ from marshmallow import fields
from nucypher.characters.control.specifications.exceptions import InvalidNativeDataTypes
from nucypher.control.specifications.exceptions import InvalidInputData
from nucypher.control.specifications.fields.base import BaseField
-from nucypher.crypto.constants import HRAC_LENGTH
+from nucypher.policy.maps import TreasureMap
class HRAC(BaseField, fields.String):
@@ -40,5 +40,5 @@ class HRAC(BaseField, fields.String):
if not isinstance(value, bytes):
raise InvalidInputData(f"Could not convert input for {self.name} to a valid TreasureMap HRAC: must be a bytestring")
- if len(value) != HRAC_LENGTH:
+ if len(value) != TreasureMap.HRAC_LENGTH:
raise InvalidInputData(f"Could not convert input for {self.name} to a valid TreasureMap HRAC: invalid length")
diff --git a/tests/acceptance/characters/control/test_rpc_control_blockchain.py b/tests/acceptance/characters/control/test_rpc_control_blockchain.py
index 94954c4b9..d3ef6c6ac 100644
--- a/tests/acceptance/characters/control/test_rpc_control_blockchain.py
+++ b/tests/acceptance/characters/control/test_rpc_control_blockchain.py
@@ -22,13 +22,18 @@ from nucypher.characters.control.interfaces import AliceInterface
from nucypher.characters.control.interfaces import BobInterface, EnricoInterface
from nucypher.crypto.constants import EIP712_MESSAGE_SIGNATURE_SIZE
from nucypher.crypto.powers import DecryptingPower
-from nucypher.policy.maps import SignedTreasureMap
+from nucypher.policy.maps import TreasureMap
from tests.utils.controllers import get_fields, validate_json_rpc_response_data
-def test_bob_rpc_character_control_join_policy(bob_rpc_controller, join_control_request, enacted_blockchain_policy, blockchain_bob, blockchain_ursulas):
+def test_bob_rpc_character_control_join_policy(bob_rpc_controller,
+ join_control_request,
+ blockchain_treasure_map,
+ enacted_blockchain_policy,
+ blockchain_bob,
+ blockchain_ursulas):
for ursula in blockchain_ursulas:
- if ursula.checksum_address in enacted_blockchain_policy.treasure_map.destinations:
+ if ursula.checksum_address in blockchain_treasure_map.destinations:
# Simulate passing in a teacher-uri
blockchain_bob.remember_node(ursula)
break
@@ -68,21 +73,13 @@ def test_bob_rpc_character_control_retrieve_with_tmap(
response = bob_rpc_controller.send(request_data)
assert response.data['result']['cleartexts'][0] == 'Welcome to flippering number 1.'
- # Make a wrong (empty) treasure map
+ # Make a wrong treasure map
+ enc_wrong_tmap = bytes(enacted_blockchain_policy.treasure_map)[:-1]
- wrong_tmap = SignedTreasureMap(m=0)
- wrong_tmap.derive_hrac(publisher_stamp=blockchain_alice.stamp, bob_verifying_key=blockchain_bob.stamp, label=b"Wrong")
- wrong_tmap.prepare_for_publication(
- bob_encrypting_key=blockchain_bob.public_keys(DecryptingPower),
- publisher_stamp=blockchain_alice.stamp)
- wrong_tmap._blockchain_signature = b"this is not a signature, but we don't need one for this test....." # ...because it only matters when Ursula looks at it. (65 bytes)
-
- assert len(wrong_tmap._blockchain_signature) == EIP712_MESSAGE_SIGNATURE_SIZE
-
- tmap_bytes = bytes(wrong_tmap)
+ tmap_bytes = bytes(enc_wrong_tmap)
tmap_64 = b64encode(tmap_bytes).decode()
request_data['params']['treasure_map'] = tmap_64
- with pytest.raises(SignedTreasureMap.IsDisorienting):
+ with pytest.raises(TreasureMap.IsDisorienting):
bob_rpc_controller.send(request_data)
diff --git a/tests/acceptance/characters/control/test_treasure_map_field_non_federated.py b/tests/acceptance/characters/control/test_treasure_map_field_non_federated.py
index b3b009c93..e2cfcfe36 100644
--- a/tests/acceptance/characters/control/test_treasure_map_field_non_federated.py
+++ b/tests/acceptance/characters/control/test_treasure_map_field_non_federated.py
@@ -25,7 +25,7 @@ from nucypher.control.specifications.exceptions import InvalidInputData
def test_treasure_map(enacted_blockchain_policy):
treasure_map = enacted_blockchain_policy.treasure_map
- field = EncryptedTreasureMap(federated_only=False)
+ field = EncryptedTreasureMap()
serialized = field._serialize(value=treasure_map, attr=None, obj=None)
assert serialized == b64encode(bytes(treasure_map)).decode()
diff --git a/tests/acceptance/characters/control/test_web_control_blockchain.py b/tests/acceptance/characters/control/test_web_control_blockchain.py
index 324c80f96..2dd7343c5 100644
--- a/tests/acceptance/characters/control/test_web_control_blockchain.py
+++ b/tests/acceptance/characters/control/test_web_control_blockchain.py
@@ -26,7 +26,7 @@ from click.testing import CliRunner
import nucypher
from nucypher.crypto.kits import UmbralMessageKit
from nucypher.crypto.powers import DecryptingPower
-from nucypher.policy.maps import TreasureMap, SignedTreasureMap
+from nucypher.policy.maps import TreasureMap, EncryptedTreasureMap
click_runner = CliRunner()
@@ -89,8 +89,8 @@ def test_alice_web_character_control_grant(alice_web_controller_test_client, gra
assert 'alice_verifying_key' in response_data['result']
map_bytes = b64decode(response_data['result']['treasure_map'])
- encrypted_map = SignedTreasureMap.from_bytes(map_bytes)
- assert encrypted_map._hrac is not None
+ encrypted_map = EncryptedTreasureMap.from_bytes(map_bytes)
+ assert encrypted_map.hrac is not None
# Send bad data to assert error returns
response = alice_web_controller_test_client.put(endpoint, data=json.dumps({'bad': 'input'}))
@@ -175,14 +175,19 @@ def test_alice_character_control_decrypt(alice_web_controller_test_client,
assert response.status_code == 405
-def test_bob_character_control_join_policy(bob_web_controller_test_client, enacted_blockchain_policy, blockchain_alice, blockchain_bob, blockchain_ursulas):
+def test_bob_character_control_join_policy(bob_web_controller_test_client,
+ blockchain_treasure_map,
+ enacted_blockchain_policy,
+ blockchain_alice,
+ blockchain_bob,
+ blockchain_ursulas):
request_data = {
'label': enacted_blockchain_policy.label.decode(),
'publisher_verifying_key': bytes(enacted_blockchain_policy.publisher_verifying_key).hex(),
}
for ursula in blockchain_ursulas:
- if ursula.checksum_address in enacted_blockchain_policy.treasure_map.destinations:
+ if ursula.checksum_address in blockchain_treasure_map.destinations:
# Simulate passing in a teacher-uri
blockchain_bob.remember_node(ursula)
break
diff --git a/tests/acceptance/characters/test_decentralized_grant.py b/tests/acceptance/characters/test_decentralized_grant.py
index c9a40ac9b..8bb7a10ec 100644
--- a/tests/acceptance/characters/test_decentralized_grant.py
+++ b/tests/acceptance/characters/test_decentralized_grant.py
@@ -24,7 +24,7 @@ import pytest
from nucypher.crypto.kits import PolicyMessageKit
from nucypher.crypto.utils import keccak_digest
from nucypher.datastore.models import EncryptedTreasureMap as DatastoreTreasureMap
-from nucypher.policy.maps import SignedTreasureMap
+from nucypher.policy.maps import EncryptedTreasureMap
def test_decentralized_grant(blockchain_alice, blockchain_bob, blockchain_ursulas):
@@ -45,13 +45,15 @@ def test_decentralized_grant(blockchain_alice, blockchain_bob, blockchain_ursula
policy_id = keccak_digest(label + bytes(blockchain_bob.stamp))
assert policy_id == policy.id
+ treasure_map = blockchain_bob._try_orient(policy.treasure_map, policy.publisher_verifying_key)
+
# The number of actually enacted arrangements is exactly equal to n.
- assert len(policy.treasure_map.destinations) == n
+ assert len(treasure_map.destinations) == n
# Let's look at the enacted arrangements.
for ursula in blockchain_ursulas:
- if ursula.checksum_address in policy.treasure_map.destinations:
- kfrag_kit = policy.treasure_map.destinations[ursula.checksum_address]
+ if ursula.checksum_address in treasure_map.destinations:
+ kfrag_kit = treasure_map.destinations[ursula.checksum_address]
# TODO: try to decrypt?
# TODO: Use a new type for EncryptedKFrags?
@@ -62,11 +64,11 @@ def test_alice_sets_treasure_map_decentralized(enacted_blockchain_policy, blockc
"""
Same as test_alice_sets_treasure_map except with a blockchain policy.
"""
- treasure_map_hrac = enacted_blockchain_policy.treasure_map._hrac
+ treasure_map_hrac = enacted_blockchain_policy.treasure_map.hrac
found = 0
for node in blockchain_bob.matching_nodes_among(blockchain_alice.known_nodes):
with node.datastore.describe(DatastoreTreasureMap, treasure_map_hrac) as treasure_map_on_node:
- assert SignedTreasureMap.from_bytes(treasure_map_on_node.treasure_map) == enacted_blockchain_policy.treasure_map
+ assert EncryptedTreasureMap.from_bytes(treasure_map_on_node.treasure_map).hrac == enacted_blockchain_policy.hrac
found += 1
assert found
@@ -94,4 +96,4 @@ def test_bob_retrieves_treasure_map_from_decentralized_node(enacted_blockchain_p
# Now he'll have better success finding that map.
treasure_map_from_wire = bob.get_treasure_map(blockchain_alice.stamp.as_umbral_pubkey(),
enacted_blockchain_policy.label)
- assert enacted_blockchain_policy.treasure_map == treasure_map_from_wire
+ assert enacted_blockchain_policy.treasure_map.hrac == treasure_map_from_wire.hrac
diff --git a/tests/acceptance/cli/ursula/test_stakeholder_and_ursula.py b/tests/acceptance/cli/ursula/test_stakeholder_and_ursula.py
index 173c26c6d..5255e5dd8 100644
--- a/tests/acceptance/cli/ursula/test_stakeholder_and_ursula.py
+++ b/tests/acceptance/cli/ursula/test_stakeholder_and_ursula.py
@@ -619,7 +619,8 @@ def test_collect_rewards_integration(click_runner,
handpicked_ursulas={ursula})
# Ensure that the handpicked Ursula was selected for the policy
- assert ursula.checksum_address in blockchain_policy.treasure_map.destinations
+ treasure_map = blockchain_bob._try_orient(blockchain_policy.treasure_map, blockchain_policy.publisher_verifying_key)
+ assert ursula.checksum_address in treasure_map.destinations
# Bob learns about the new staker and joins the policy
blockchain_bob.start_learning_loop()
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 ec9daf65e..41067a4f1 100644
--- a/tests/acceptance/porter/control/test_porter_rpc_control_blockchain.py
+++ b/tests/acceptance/porter/control/test_porter_rpc_control_blockchain.py
@@ -20,7 +20,6 @@ from base64 import b64encode, b64decode
import pytest
from nucypher.crypto.umbral_adapter import PublicKey
-from nucypher.crypto.constants import HRAC_LENGTH
from nucypher.crypto.powers import DecryptingPower
from nucypher.network.nodes import Learner
from nucypher.policy.maps import TreasureMap
@@ -90,7 +89,7 @@ def test_publish_and_get_treasure_map(blockchain_porter_rpc_controller,
random_bob_encrypting_key = PublicKey.from_bytes(
bytes.fromhex("026d1f4ce5b2474e0dae499d6737a8d987ed3c9ab1a55e00f57ad2d8e81fe9e9ac"))
random_hrac = "93a9482bdf3b4f2e9df906a35144ca84"
- assert len(bytes.fromhex(random_hrac)) == HRAC_LENGTH
+ assert len(bytes.fromhex(random_hrac)) == TreasureMap.HRAC_LENGTH
get_treasure_map_params = {
'hrac': random_hrac,
'bob_encrypting_key': bytes(random_bob_encrypting_key).hex()
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 c8f4c216d..d65652936 100644
--- a/tests/acceptance/porter/control/test_porter_web_control_blockchain.py
+++ b/tests/acceptance/porter/control/test_porter_web_control_blockchain.py
@@ -22,7 +22,6 @@ from base64 import b64encode
import pytest
from nucypher.crypto.umbral_adapter import PublicKey
-from nucypher.crypto.constants import HRAC_LENGTH
from nucypher.crypto.powers import DecryptingPower
from nucypher.network.nodes import Learner
from nucypher.policy.maps import TreasureMap
@@ -106,7 +105,7 @@ def test_publish_and_get_treasure_map(blockchain_porter_web_controller,
random_bob_encrypting_key = PublicKey.from_bytes(
bytes.fromhex("026d1f4ce5b2474e0dae499d6737a8d987ed3c9ab1a55e00f57ad2d8e81fe9e9ac"))
random_hrac = "93a9482bdf3b4f2e9df906a35144ca84"
- assert len(bytes.fromhex(random_hrac)) == HRAC_LENGTH
+ assert len(bytes.fromhex(random_hrac)) == TreasureMap.HRAC_LENGTH
get_treasure_map_params = {
'hrac': random_hrac,
'bob_encrypting_key': bytes(random_bob_encrypting_key).hex()
diff --git a/tests/acceptance/porter/test_decentralized_porter.py b/tests/acceptance/porter/test_decentralized_porter.py
index 3bd55d0a8..9c286db5b 100644
--- a/tests/acceptance/porter/test_decentralized_porter.py
+++ b/tests/acceptance/porter/test_decentralized_porter.py
@@ -18,7 +18,6 @@
import pytest
from nucypher.crypto.umbral_adapter import PublicKey
-from nucypher.crypto.constants import HRAC_LENGTH
from nucypher.crypto.powers import DecryptingPower
from nucypher.policy.maps import TreasureMap
from tests.utils.middleware import MockRestMiddleware
@@ -82,7 +81,7 @@ def test_publish_and_get_treasure_map(blockchain_porter,
random_bob_encrypting_key = PublicKey.from_bytes(
bytes.fromhex("026d1f4ce5b2474e0dae499d6737a8d987ed3c9ab1a55e00f57ad2d8e81fe9e9ac"))
random_hrac = bytes.fromhex("93a9482bdf3b4f2e9df906a35144ca84")
- assert len(random_hrac) == HRAC_LENGTH
+ assert len(random_hrac) == TreasureMap.HRAC_LENGTH
blockchain_porter.get_treasure_map(hrac=random_hrac,
bob_encrypting_key=random_bob_encrypting_key)
@@ -100,7 +99,7 @@ def test_publish_and_get_treasure_map(blockchain_porter,
enacted_policy.label)
retrieved_treasure_map = blockchain_porter.get_treasure_map(hrac=hrac,
bob_encrypting_key=blockchain_bob_encrypting_key)
- assert retrieved_treasure_map == treasure_map
+ assert retrieved_treasure_map.hrac == treasure_map.hrac
def test_exec_work_order(blockchain_porter,
diff --git a/tests/acceptance/porter/test_publish_treasure_map_schema_non_federated_context.py b/tests/acceptance/porter/test_publish_treasure_map_schema_non_federated_context.py
index 5b9a849eb..aa902d18a 100644
--- a/tests/acceptance/porter/test_publish_treasure_map_schema_non_federated_context.py
+++ b/tests/acceptance/porter/test_publish_treasure_map_schema_non_federated_context.py
@@ -19,29 +19,18 @@ from base64 import b64encode
import pytest
-from nucypher.characters.control.specifications.fields import EncryptedTreasureMap
from nucypher.control.specifications.exceptions import InvalidInputData
from nucypher.crypto.powers import DecryptingPower
from nucypher.utilities.porter.control.specifications.porter_schema import AlicePublishTreasureMap
-def test_alice_publish_treasure_map_schema_blockchain_context_default(enacted_blockchain_policy, blockchain_bob):
+def test_alice_publish_treasure_map_schema_blockchain_context(enacted_blockchain_policy, blockchain_bob):
alice_publish_treasure_map_schema = AlicePublishTreasureMap() # default is decentralized
run_publish_treasuremap_schema_tests(alice_publish_treasure_map_schema=alice_publish_treasure_map_schema,
enacted_blockchain_policy=enacted_blockchain_policy,
blockchain_bob=blockchain_bob)
-def test_alice_publish_treasure_map_schema_blockchain_context_set_false(enacted_blockchain_policy, blockchain_bob):
- # since non-federated, schema's context doesn't have to be set, but set it anyway to ensure that setting to
- # False still works as expected.
- alice_publish_treasure_map_schema = AlicePublishTreasureMap() # default is decentralized
- alice_publish_treasure_map_schema.context[EncryptedTreasureMap.IS_FEDERATED_CONTEXT_KEY] = False
- run_publish_treasuremap_schema_tests(alice_publish_treasure_map_schema=alice_publish_treasure_map_schema,
- enacted_blockchain_policy=enacted_blockchain_policy,
- blockchain_bob=blockchain_bob)
-
-
def run_publish_treasuremap_schema_tests(alice_publish_treasure_map_schema, enacted_blockchain_policy, blockchain_bob):
# no args
with pytest.raises(InvalidInputData):
@@ -84,9 +73,3 @@ def run_publish_treasuremap_schema_tests(alice_publish_treasure_map_schema, enac
response_data = {'published': True}
output = alice_publish_treasure_map_schema.dump(obj=response_data)
assert output == response_data
-
- # setting federated context to True
- alice_publish_treasure_map_schema.context[EncryptedTreasureMap.IS_FEDERATED_CONTEXT_KEY] = True
- with pytest.raises(InvalidInputData):
- # failed because federated treasure map expected, but instead non-federated (blockchain) treasure map provided
- alice_publish_treasure_map_schema.load(required_data)
diff --git a/tests/fixtures.py b/tests/fixtures.py
index 37f394de5..1137be17a 100644
--- a/tests/fixtures.py
+++ b/tests/fixtures.py
@@ -244,6 +244,15 @@ def enacted_federated_policy(idle_federated_policy, federated_ursulas):
return enacted_policy
+@pytest.fixture(scope="module")
+def federated_treasure_map(enacted_federated_policy, federated_bob):
+ """
+ The unencrypted treasure map corresponding to the one in `enacted_federated_policy`
+ """
+ yield federated_bob._try_orient(enacted_federated_policy.treasure_map,
+ enacted_federated_policy.publisher_verifying_key)
+
+
@pytest.fixture(scope="module")
def idle_blockchain_policy(testerchain, blockchain_alice, blockchain_bob, token_economics):
"""
@@ -281,6 +290,15 @@ def enacted_blockchain_policy(idle_blockchain_policy, blockchain_ursulas):
return enacted_policy
+@pytest.fixture(scope="module")
+def blockchain_treasure_map(enacted_blockchain_policy, blockchain_bob):
+ """
+ The unencrypted treasure map corresponding to the one in `enacted_blockchain_policy`
+ """
+ yield blockchain_bob._try_orient(enacted_blockchain_policy.treasure_map,
+ enacted_blockchain_policy.publisher_verifying_key)
+
+
@pytest.fixture(scope="function")
def random_blockchain_policy(testerchain, blockchain_alice, blockchain_bob, token_economics):
random_label = generate_random_label()
diff --git a/tests/integration/characters/control/test_rpc_control_federated.py b/tests/integration/characters/control/test_rpc_control_federated.py
index fae6f3e52..366d466fe 100644
--- a/tests/integration/characters/control/test_rpc_control_federated.py
+++ b/tests/integration/characters/control/test_rpc_control_federated.py
@@ -72,10 +72,15 @@ def test_alice_rpc_character_control_grant(alice_rpc_test_client, grant_control_
assert 'jsonrpc' in response.data
-def test_bob_rpc_character_control_join_policy(bob_rpc_controller, join_control_request, enacted_federated_policy, federated_bob, federated_ursulas):
+def test_bob_rpc_character_control_join_policy(bob_rpc_controller,
+ join_control_request,
+ federated_treasure_map,
+ enacted_federated_policy,
+ federated_bob,
+ federated_ursulas):
for ursula in federated_ursulas:
- if ursula.checksum_address in enacted_federated_policy.treasure_map.destinations:
+ if ursula.checksum_address in federated_treasure_map.destinations:
# Simulate passing in a teacher-uri
federated_bob.remember_node(ursula)
break
diff --git a/tests/integration/characters/control/test_web_control_federated.py b/tests/integration/characters/control/test_web_control_federated.py
index 1f2b1f261..3022b4b85 100644
--- a/tests/integration/characters/control/test_web_control_federated.py
+++ b/tests/integration/characters/control/test_web_control_federated.py
@@ -27,7 +27,7 @@ from click.testing import CliRunner
import nucypher
from nucypher.crypto.kits import UmbralMessageKit
from nucypher.crypto.powers import DecryptingPower
-from nucypher.policy.maps import TreasureMap
+from nucypher.policy.maps import EncryptedTreasureMap
click_runner = CliRunner()
@@ -89,8 +89,8 @@ def test_alice_web_character_control_grant(alice_web_controller_test_client, gra
assert 'alice_verifying_key' in response_data['result']
map_bytes = b64decode(response_data['result']['treasure_map'])
- encrypted_map = TreasureMap.from_bytes(map_bytes)
- assert encrypted_map._hrac is not None
+ encrypted_map = EncryptedTreasureMap.from_bytes(map_bytes)
+ assert encrypted_map.hrac is not None
# Send bad data to assert error returns
response = alice_web_controller_test_client.put(endpoint, data=json.dumps({'bad': 'input'}))
@@ -169,14 +169,18 @@ def test_alice_character_control_decrypt(alice_web_controller_test_client,
assert response.status_code == 405
-def test_bob_character_control_join_policy(bob_web_controller_test_client, federated_bob, federated_ursulas, enacted_federated_policy):
+def test_bob_character_control_join_policy(bob_web_controller_test_client,
+ federated_bob,
+ federated_ursulas,
+ federated_treasure_map,
+ enacted_federated_policy):
request_data = {
'label': enacted_federated_policy.label.decode(),
'publisher_verifying_key': bytes(enacted_federated_policy.publisher_verifying_key).hex(),
}
for ursula in federated_ursulas:
- if ursula.checksum_address in enacted_federated_policy.treasure_map.destinations:
+ if ursula.checksum_address in federated_treasure_map.destinations:
# Simulate passing in a teacher-uri
federated_bob.remember_node(ursula)
break
diff --git a/tests/integration/characters/test_bob_handles_frags.py b/tests/integration/characters/test_bob_handles_frags.py
index 28b2e8c9a..9664e3cc4 100644
--- a/tests/integration/characters/test_bob_handles_frags.py
+++ b/tests/integration/characters/test_bob_handles_frags.py
@@ -30,20 +30,19 @@ from nucypher.policy.maps import AuthorizedKeyFrag
from tests.utils.middleware import MockRestMiddleware, NodeIsDownMiddleware
-def test_bob_cannot_follow_the_treasure_map_in_isolation(enacted_federated_policy, federated_bob):
+def test_bob_cannot_follow_the_treasure_map_in_isolation(federated_treasure_map, federated_bob):
# Assume for the moment that Bob has already received a TreasureMap, perhaps via a side channel.
- hrac, treasure_map = enacted_federated_policy.hrac, enacted_federated_policy.treasure_map
# Bob knows of no Ursulas.
assert len(federated_bob.known_nodes) == 0
# He can't successfully follow the TreasureMap until he learns of a node to ask.
- unknown, known = federated_bob.peek_at_treasure_map(treasure_map=treasure_map)
+ unknown, known = federated_bob.peek_at_treasure_map(treasure_map=federated_treasure_map)
assert len(known) == 0
# TODO: Show that even with learning loop going, nothing happens here.
# Probably use Clock?
- federated_bob.follow_treasure_map(treasure_map=treasure_map)
+ federated_bob.follow_treasure_map(treasure_map=federated_treasure_map)
assert len(known) == 0
@@ -67,11 +66,11 @@ def test_bob_already_knows_all_nodes_in_treasure_map(enacted_federated_policy,
assert len(unknown) == 0
# ...because he already knew of all the Ursulas on the map.
- assert len(known) == len(enacted_federated_policy.treasure_map)
+ assert len(known) == enacted_federated_policy.n
@pytest_twisted.inlineCallbacks
-def test_bob_can_follow_treasure_map_even_if_he_only_knows_of_one_node(enacted_federated_policy,
+def test_bob_can_follow_treasure_map_even_if_he_only_knows_of_one_node(federated_treasure_map,
federated_ursulas,
certificates_tempdir):
"""
@@ -90,7 +89,6 @@ def test_bob_can_follow_treasure_map_even_if_he_only_knows_of_one_node(enacted_f
federated_only=True)
# Again, let's assume that he received the TreasureMap via a side channel.
- hrac, treasure_map = enacted_federated_policy.hrac, enacted_federated_policy.treasure_map
# Now, let's create a scenario in which Bob knows of only one node.
assert len(bob.known_nodes) == 0
@@ -99,13 +97,13 @@ def test_bob_can_follow_treasure_map_even_if_he_only_knows_of_one_node(enacted_f
assert len(bob.known_nodes) == 1
# This time, when he follows the TreasureMap...
- unknown_nodes, known_nodes = bob.peek_at_treasure_map(treasure_map=treasure_map)
+ unknown_nodes, known_nodes = bob.peek_at_treasure_map(treasure_map=federated_treasure_map)
# Bob already knew about one node; the rest are unknown.
- assert len(unknown_nodes) == len(treasure_map) - 1
+ assert len(unknown_nodes) == len(federated_treasure_map) - 1
# He needs to actually follow the treasure map to get the rest.
- bob.follow_treasure_map(treasure_map=treasure_map)
+ bob.follow_treasure_map(treasure_map=federated_treasure_map)
# The nodes in the learning loop are now his top target, but he's not learning yet.
assert not bob._learning_task.running
@@ -121,11 +119,12 @@ def test_bob_can_follow_treasure_map_even_if_he_only_knows_of_one_node(enacted_f
yield d
# ...and he now has no more unknown_nodes.
- assert len(bob.known_nodes) == len(treasure_map)
+ assert len(bob.known_nodes) == len(federated_treasure_map)
bob.disenchant()
def test_bob_can_issue_a_work_order_to_a_specific_ursula(enacted_federated_policy,
+ federated_treasure_map,
federated_bob,
federated_alice,
federated_ursulas,
@@ -139,10 +138,9 @@ def test_bob_can_issue_a_work_order_to_a_specific_ursula(enacted_federated_polic
"""
# We pick up our story with Bob already having followed the treasure map above, ie:
- hrac, treasure_map = enacted_federated_policy.hrac, enacted_federated_policy.treasure_map
federated_bob.start_learning_loop()
- federated_bob.follow_treasure_map(treasure_map=treasure_map, block=True, timeout=1)
+ federated_bob.follow_treasure_map(treasure_map=federated_treasure_map, block=True, timeout=1)
assert len(federated_bob.known_nodes) == len(federated_ursulas)
@@ -158,7 +156,7 @@ def test_bob_can_issue_a_work_order_to_a_specific_ursula(enacted_federated_polic
work_orders, _ = federated_bob.work_orders_for_capsules(
message_kit.capsule,
label=enacted_federated_policy.label,
- treasure_map=treasure_map,
+ treasure_map=federated_treasure_map,
alice_verifying_key=federated_alice.stamp.as_umbral_pubkey(),
num_ursulas=1)
@@ -172,7 +170,7 @@ def test_bob_can_issue_a_work_order_to_a_specific_ursula(enacted_federated_polic
retained_work_orders, _ = federated_bob.work_orders_for_capsules(
message_kit.capsule,
label=enacted_federated_policy.label,
- treasure_map=treasure_map,
+ treasure_map=federated_treasure_map,
alice_verifying_key=federated_alice.stamp.as_umbral_pubkey(),
num_ursulas=1)
@@ -206,7 +204,7 @@ def test_bob_can_issue_a_work_order_to_a_specific_ursula(enacted_federated_polic
raise RuntimeError("We've lost track of the Ursula that has the WorkOrder. Can't really proceed.")
# Ursula decrypts the encrypted KFrag
- encrypted_kfrag = enacted_federated_policy.treasure_map.destinations[ursula.checksum_address]
+ encrypted_kfrag = federated_treasure_map.destinations[ursula.checksum_address]
alice = Alice.from_public_keys(verifying_key=federated_alice.stamp.as_umbral_pubkey())
plaintext_kfrag_payload = ursula.verify_from(stranger=alice,
message_kit=encrypted_kfrag,
@@ -227,6 +225,7 @@ def test_bob_can_issue_a_work_order_to_a_specific_ursula(enacted_federated_polic
def test_bob_can_use_cfrag_attached_to_completed_workorder(enacted_federated_policy,
+ federated_treasure_map,
federated_alice,
federated_bob,
federated_ursulas,
@@ -243,7 +242,7 @@ def test_bob_can_use_cfrag_attached_to_completed_workorder(enacted_federated_pol
incomplete_work_orders, complete_work_orders = federated_bob.work_orders_for_capsules(
last_capsule_on_side_channel,
label=enacted_federated_policy.label,
- treasure_map=enacted_federated_policy.treasure_map,
+ treasure_map=federated_treasure_map,
alice_verifying_key=federated_alice.stamp.as_umbral_pubkey(),
num_ursulas=1,
)
@@ -262,9 +261,12 @@ def test_bob_can_use_cfrag_attached_to_completed_workorder(enacted_federated_pol
federated_bob._reencrypt(new_work_order, message_kit_dict)
-def test_bob_remembers_that_he_has_cfrags_for_a_particular_capsule(enacted_federated_policy, federated_alice,
+def test_bob_remembers_that_he_has_cfrags_for_a_particular_capsule(enacted_federated_policy,
+ federated_treasure_map,
+ federated_alice,
federated_bob,
- federated_ursulas, capsule_side_channel):
+ federated_ursulas,
+ capsule_side_channel):
# In our last episode, Bob made a single WorkOrder...
work_orders = list(federated_bob._completed_work_orders.by_ursula.values())
assert len(work_orders) == 1
@@ -293,7 +295,7 @@ def test_bob_remembers_that_he_has_cfrags_for_a_particular_capsule(enacted_feder
incomplete_work_orders, complete_work_orders = federated_bob.work_orders_for_capsules(
last_capsule_on_side_channel,
label=enacted_federated_policy.label,
- treasure_map=enacted_federated_policy.treasure_map,
+ treasure_map=federated_treasure_map,
alice_verifying_key=federated_alice.stamp.as_umbral_pubkey(),
num_ursulas=1)
id_of_this_new_ursula, new_work_order = list(incomplete_work_orders.items())[0]
@@ -321,7 +323,7 @@ def test_bob_remembers_that_he_has_cfrags_for_a_particular_capsule(enacted_feder
last_message_kit_on_side_channel.attach_cfrag(new_cfrag)
-def test_bob_gathers_and_combines(enacted_federated_policy, federated_bob, federated_alice, capsule_side_channel):
+def test_bob_gathers_and_combines(enacted_federated_policy, federated_treasure_map, federated_bob, federated_alice, capsule_side_channel):
# The side channel delivers all that Bob needs at this point:
# - A single MessageKit, containing a Capsule
# - A representation of the data source
@@ -331,13 +333,13 @@ def test_bob_gathers_and_combines(enacted_federated_policy, federated_bob, feder
assert len(federated_bob._completed_work_orders) == 2
# ...but the policy requires us to collect more cfrags.
- assert len(federated_bob._completed_work_orders) < enacted_federated_policy.treasure_map.m
+ assert len(federated_bob._completed_work_orders) < federated_treasure_map.m
# Bob can't decrypt yet with just two CFrags. He needs to gather at least m.
with pytest.raises(DecryptingKeypair.DecryptionFailed):
federated_bob.decrypt(the_message_kit)
- number_left_to_collect = enacted_federated_policy.treasure_map.m - len(federated_bob._completed_work_orders)
+ number_left_to_collect = federated_treasure_map.m - len(federated_bob._completed_work_orders)
the_message_kit.set_correctness_keys(
delegating=the_data_source.policy_pubkey,
@@ -347,7 +349,7 @@ def test_bob_gathers_and_combines(enacted_federated_policy, federated_bob, feder
new_incomplete_work_orders, _ = federated_bob.work_orders_for_capsules(
the_message_kit.capsule,
label=enacted_federated_policy.label,
- treasure_map=enacted_federated_policy.treasure_map,
+ treasure_map=federated_treasure_map,
alice_verifying_key=federated_alice.stamp.as_umbral_pubkey(),
num_ursulas=number_left_to_collect)
_id_of_yet_another_ursula, new_work_order = list(new_incomplete_work_orders.items())[0]
diff --git a/tests/integration/characters/test_bob_joins_policy_and_retrieves.py b/tests/integration/characters/test_bob_joins_policy_and_retrieves.py
index ceffab412..182c05172 100644
--- a/tests/integration/characters/test_bob_joins_policy_and_retrieves.py
+++ b/tests/integration/characters/test_bob_joins_policy_and_retrieves.py
@@ -35,10 +35,10 @@ def test_federated_bob_full_retrieve_flow(federated_ursulas,
federated_bob,
federated_alice,
capsule_side_channel,
+ federated_treasure_map,
enacted_federated_policy):
# Assume for the moment that Bob has already received a TreasureMap.
- treasure_map = enacted_federated_policy.treasure_map
- federated_bob.treasure_maps[treasure_map._hrac] = treasure_map
+ federated_bob.treasure_maps[federated_treasure_map.hrac] = federated_treasure_map
for ursula in federated_ursulas:
federated_bob.remember_node(ursula)
@@ -98,12 +98,8 @@ def test_bob_joins_policy_and_retrieves(federated_alice,
bob.join_policy(label=label,
publisher_verifying_key=federated_alice.stamp.as_umbral_pubkey(),
block=True)
- except policy.treasure_map.NowhereToBeFound:
- maps = []
- for ursula in federated_ursulas:
- for map in ursula.treasure_maps.values():
- maps.append(map)
- if policy.treasure_map in maps:
+ except TreasureMap.NowhereToBeFound:
+ if policy.treasure_map.hrac in ursula.treasure_maps:
# This is a nice place to put a breakpoint to examine Bob's failure to join a policy.
bob.join_policy(label=label,
publisher_verifying_key=federated_alice.stamp.as_umbral_pubkey(),
@@ -173,29 +169,6 @@ def test_bob_joins_policy_and_retrieves(federated_alice,
bob.disenchant()
-def test_treasure_map_serialization(enacted_federated_policy, federated_alice, federated_bob):
- treasure_map = enacted_federated_policy.treasure_map
- assert treasure_map.m is not None
- assert treasure_map.m != NO_DECRYPTION_PERFORMED
- assert treasure_map.m == MOCK_POLICY_DEFAULT_M, 'm value is not correct'
-
- serialized_map = bytes(treasure_map)
- deserialized_map = TreasureMap.from_bytes(serialized_map)
- assert deserialized_map._hrac == treasure_map._hrac
-
- # TreasureMap is currently encrypted
- with pytest.raises(TypeError):
- deserialized_map.m
-
- with pytest.raises(TypeError):
- deserialized_map.destinations
-
- compass = federated_bob.make_compass_for_alice(federated_alice)
- deserialized_map.orient(compass)
- assert deserialized_map.m == treasure_map.m
- assert deserialized_map.destinations == treasure_map.destinations
-
-
def test_bob_retrieves_with_treasure_map(
federated_bob, federated_ursulas,
enacted_federated_policy, capsule_side_channel):
diff --git a/tests/integration/characters/test_federated_grant_and_revoke.py b/tests/integration/characters/test_federated_grant_and_revoke.py
index 2ef62fe39..fe5b0b8bd 100644
--- a/tests/integration/characters/test_federated_grant_and_revoke.py
+++ b/tests/integration/characters/test_federated_grant_and_revoke.py
@@ -21,6 +21,7 @@ import datetime
import maya
import pytest
+from nucypher.crypto.kits import PolicyMessageKit
from nucypher.characters.lawful import Enrico
from nucypher.crypto.utils import keccak_digest
from nucypher.policy.orders import Revocation
@@ -43,13 +44,19 @@ def test_federated_grant(federated_alice, federated_bob, federated_ursulas):
assert policy_id in federated_alice.active_policies
assert federated_alice.active_policies[policy_id] == policy
+ treasure_map = federated_bob._try_orient(policy.treasure_map, policy.publisher_verifying_key)
+
# The number of actually enacted arrangements is exactly equal to n.
- assert len(policy.treasure_map.destinations) == n
+ assert len(treasure_map.destinations) == n
# Let's look at the enacted arrangements.
for ursula in federated_ursulas:
- if ursula.checksum_address in policy.treasure_map.destinations:
- assert ursula.checksum_address in policy.treasure_map.destinations
+ if ursula.checksum_address in treasure_map.destinations:
+ kfrag_kit = treasure_map.destinations[ursula.checksum_address]
+
+ # TODO: try to decrypt?
+ # TODO: Use a new type for EncryptedKFrags?
+ assert isinstance(kfrag_kit, PolicyMessageKit)
def test_federated_alice_can_decrypt(federated_alice, federated_bob):
diff --git a/tests/integration/characters/test_specifications.py b/tests/integration/characters/test_specifications.py
index d322ce1e3..1d8b3cb0b 100644
--- a/tests/integration/characters/test_specifications.py
+++ b/tests/integration/characters/test_specifications.py
@@ -73,7 +73,7 @@ def test_treasuremap_validation(enacted_federated_policy):
"""Tell people exactly what's wrong with their treasuremaps"""
class TreasureMapsOnly(BaseSchema):
- tmap = EncryptedTreasureMap(federated_only=True)
+ tmap = EncryptedTreasureMap()
# this will raise a base64 error
with pytest.raises(SpecificationError) as e:
@@ -87,8 +87,8 @@ def test_treasuremap_validation(enacted_federated_policy):
with pytest.raises(InvalidInputData) as e:
TreasureMapsOnly().load({'tmap': "VGhpcyBpcyB0b3RhbGx5IG5vdCBhIHRyZWFzdXJlbWFwLg=="})
- assert "Could not parse tmap" in str(e)
- assert "Can't split a message with more bytes than the original splittable" in str(e)
+ assert "Could not convert input for tmap" in str(e)
+ assert "Invalid treasure map contents" in str(e)
# a valid treasuremap for once...
tmap_bytes = bytes(enacted_federated_policy.treasure_map)
diff --git a/tests/integration/network/test_failure_modes.py b/tests/integration/network/test_failure_modes.py
index b2bfccdce..960eed602 100644
--- a/tests/integration/network/test_failure_modes.py
+++ b/tests/integration/network/test_failure_modes.py
@@ -87,7 +87,7 @@ def test_alice_can_grant_even_when_the_first_nodes_she_tries_are_down(federated_
# policy = alice_grant_action()
# The number of actually enacted arrangements is exactly equal to n.
- assert len(policy.treasure_map.destinations) == n
+ assert policy.n == n
def test_node_has_changed_cert(federated_alice, federated_ursulas):
diff --git a/tests/integration/network/test_treasure_map_integration.py b/tests/integration/network/test_treasure_map_integration.py
index 799f71ed1..d65131412 100644
--- a/tests/integration/network/test_treasure_map_integration.py
+++ b/tests/integration/network/test_treasure_map_integration.py
@@ -21,7 +21,7 @@ import pytest
from nucypher.characters.lawful import Ursula
from nucypher.crypto.utils import keccak_digest
from nucypher.datastore.models import EncryptedTreasureMap as DatastoreTreasureMap
-from nucypher.policy.maps import TreasureMap as FederatedTreasureMap
+from nucypher.policy.maps import EncryptedTreasureMap
def test_alice_creates_policy_with_correct_hrac(federated_alice, federated_bob, idle_federated_policy):
@@ -37,11 +37,11 @@ def test_alice_sets_treasure_map(federated_alice, federated_bob, enacted_federat
"""
Having enacted all the policies of a PolicyGroup, Alice creates a TreasureMap and ...... TODO
"""
- hrac = enacted_federated_policy.treasure_map._hrac
+ hrac = enacted_federated_policy.treasure_map.hrac
found = 0
for node in federated_bob.matching_nodes_among(federated_alice.known_nodes):
with node.datastore.describe(DatastoreTreasureMap, hrac) as treasure_map_on_node:
- assert FederatedTreasureMap.from_bytes(treasure_map_on_node.treasure_map) == enacted_federated_policy.treasure_map
+ assert EncryptedTreasureMap.from_bytes(treasure_map_on_node.treasure_map).hrac == enacted_federated_policy.hrac
found += 1
assert found
@@ -52,10 +52,10 @@ def test_treasure_map_stored_by_ursula_is_the_correct_one_for_bob(federated_alic
The TreasureMap given by Alice to Ursula is the correct one for Bob; he can decrypt and read it.
"""
- hrac = enacted_federated_policy.treasure_map._hrac
+ hrac = enacted_federated_policy.treasure_map.hrac
an_ursula = federated_bob.matching_nodes_among(federated_ursulas)[0]
with an_ursula.datastore.describe(DatastoreTreasureMap, hrac) as treasure_map_record:
- treasure_map_on_network = FederatedTreasureMap.from_bytes(treasure_map_record.treasure_map)
+ treasure_map_on_network = EncryptedTreasureMap.from_bytes(treasure_map_record.treasure_map)
hrac_by_bob = federated_bob.construct_policy_hrac(federated_alice.stamp, enacted_federated_policy.label)
assert enacted_federated_policy.hrac == hrac_by_bob
@@ -90,14 +90,14 @@ def test_bob_can_retrieve_the_treasure_map_and_decrypt_it(federated_alice, feder
treasure_map_from_wire = bob.get_treasure_map(federated_alice.stamp.as_umbral_pubkey(),
enacted_federated_policy.label)
- assert enacted_federated_policy.treasure_map == treasure_map_from_wire
+ assert enacted_federated_policy.hrac == treasure_map_from_wire.hrac
-def test_treasure_map_is_legit(federated_bob, enacted_federated_policy):
+def test_treasure_map_is_legit(federated_bob, federated_treasure_map, enacted_federated_policy):
"""
Sure, the TreasureMap can get to Bob, but we also need to know that each Ursula in the TreasureMap is on the network.
"""
- for ursula_address, _node_id in enacted_federated_policy.treasure_map:
+ for ursula_address, _node_id in federated_treasure_map:
if ursula_address not in federated_bob.known_nodes.addresses():
pytest.fail(f"Bob didn't know about {ursula_address}")
diff --git a/tests/integration/porter/conftest.py b/tests/integration/porter/conftest.py
index 9e2cf2c96..816046ed9 100644
--- a/tests/integration/porter/conftest.py
+++ b/tests/integration/porter/conftest.py
@@ -36,4 +36,7 @@ def random_federated_treasure_map_data(federated_alice, federated_bob, federated
verified_kfrags=kfrags,
m=threshold)
- yield federated_bob.public_keys(DecryptingPower), random_treasure_map
+ enc_treasure_map = random_treasure_map.prepare_for_publication(publisher=federated_alice,
+ bob=federated_bob)
+
+ yield federated_bob.public_keys(DecryptingPower), enc_treasure_map
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 c51e69648..cbee787dc 100644
--- a/tests/integration/porter/control/test_porter_rpc_control_federated.py
+++ b/tests/integration/porter/control/test_porter_rpc_control_federated.py
@@ -89,7 +89,7 @@ def test_publish_and_get_treasure_map(federated_porter_rpc_controller,
# ensure that random treasure map cannot be obtained since not available
with pytest.raises(TreasureMap.NowhereToBeFound):
get_treasure_map_params = {
- 'hrac': random_treasure_map._hrac.hex(),
+ 'hrac': random_treasure_map.hrac.hex(),
'bob_encrypting_key': bytes(random_bob_encrypting_key).hex()
}
request_data = {'method': 'get_treasure_map', 'params': get_treasure_map_params}
@@ -106,7 +106,7 @@ def test_publish_and_get_treasure_map(federated_porter_rpc_controller,
# try getting the random treasure map now
get_treasure_map_params = {
- 'hrac': random_treasure_map._hrac.hex(),
+ 'hrac': random_treasure_map.hrac.hex(),
'bob_encrypting_key': bytes(random_bob_encrypting_key).hex()
}
request_data = {'method': 'get_treasure_map', 'params': get_treasure_map_params}
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 21416f01e..b40d202a7 100644
--- a/tests/integration/porter/control/test_porter_web_control_federated.py
+++ b/tests/integration/porter/control/test_porter_web_control_federated.py
@@ -103,7 +103,7 @@ def test_publish_and_get_treasure_map(federated_porter_web_controller,
# ensure that random treasure map cannot be obtained since not available
with pytest.raises(TreasureMap.NowhereToBeFound):
get_treasure_map_params = {
- 'hrac': random_treasure_map._hrac.hex(),
+ 'hrac': random_treasure_map.hrac.hex(),
'bob_encrypting_key': bytes(random_bob_encrypting_key).hex()
}
federated_porter_web_controller.get('/get_treasure_map', data=json.dumps(get_treasure_map_params))
@@ -121,7 +121,7 @@ def test_publish_and_get_treasure_map(federated_porter_web_controller,
# try getting the random treasure map now
get_treasure_map_params = {
- 'hrac': random_treasure_map._hrac.hex(),
+ 'hrac': random_treasure_map.hrac.hex(),
'bob_encrypting_key': bytes(random_bob_encrypting_key).hex()
}
response = federated_porter_web_controller.get('/get_treasure_map',
@@ -206,7 +206,7 @@ def test_endpoints_basic_auth(federated_porter_basic_auth_web_controller,
# /get_treasure_map
get_treasure_map_params = {
- 'hrac': random_treasure_map._hrac.hex(),
+ 'hrac': random_treasure_map.hrac.hex(),
'bob_encrypting_key': bytes(random_bob_encrypting_key).hex()
}
response = federated_porter_basic_auth_web_controller.get('/get_treasure_map',
diff --git a/tests/integration/porter/test_federated_porter.py b/tests/integration/porter/test_federated_porter.py
index 0859d3bf5..caf50b4da 100644
--- a/tests/integration/porter/test_federated_porter.py
+++ b/tests/integration/porter/test_federated_porter.py
@@ -77,7 +77,7 @@ def test_publish_and_get_treasure_map(federated_porter,
# ensure that random treasure map cannot be obtained since not available
with pytest.raises(TreasureMap.NowhereToBeFound):
- federated_porter.get_treasure_map(hrac=random_treasure_map._hrac,
+ federated_porter.get_treasure_map(hrac=random_treasure_map.hrac,
bob_encrypting_key=random_bob_encrypting_key)
# publish the random treasure map
@@ -85,16 +85,16 @@ def test_publish_and_get_treasure_map(federated_porter,
bob_encrypting_key=random_bob_encrypting_key)
# try getting the random treasure map now
- treasure_map = federated_porter.get_treasure_map(hrac=random_treasure_map._hrac,
+ treasure_map = federated_porter.get_treasure_map(hrac=random_treasure_map.hrac,
bob_encrypting_key=random_bob_encrypting_key)
- assert treasure_map._hrac == random_treasure_map._hrac
+ assert treasure_map.hrac == random_treasure_map.hrac
# try getting an already existing policy
hrac = federated_bob.construct_policy_hrac(federated_alice.stamp.as_umbral_pubkey(),
enacted_federated_policy.label)
treasure_map = federated_porter.get_treasure_map(hrac=hrac,
bob_encrypting_key=federated_bob.public_keys(DecryptingPower))
- assert treasure_map == enacted_federated_policy.treasure_map
+ assert treasure_map.hrac == enacted_federated_policy.hrac
def test_exec_work_order(federated_porter,
diff --git a/tests/integration/porter/test_porter_specifications.py b/tests/integration/porter/test_porter_specifications.py
index 084b1218f..44614ff80 100644
--- a/tests/integration/porter/test_porter_specifications.py
+++ b/tests/integration/porter/test_porter_specifications.py
@@ -19,7 +19,6 @@ from base64 import b64encode
import pytest
-from nucypher.characters.control.specifications.fields import EncryptedTreasureMap
from nucypher.control.specifications.exceptions import InvalidArgumentCombo, InvalidInputData
from nucypher.crypto.powers import DecryptingPower
from nucypher.crypto.umbral_adapter import SecretKey
@@ -169,7 +168,6 @@ def test_alice_publish_treasure_map_schema_federated_context(enacted_federated_p
# since federated, schema's context must be set - so create one schema
# and reuse (it doesn't hold state other than the context)
alice_publish_treasure_map_schema = AlicePublishTreasureMap()
- alice_publish_treasure_map_schema.context[EncryptedTreasureMap.IS_FEDERATED_CONTEXT_KEY] = True
# no args
with pytest.raises(InvalidInputData):
@@ -213,12 +211,6 @@ def test_alice_publish_treasure_map_schema_federated_context(enacted_federated_p
output = alice_publish_treasure_map_schema.dump(obj=response_data)
assert output == response_data
- # setting federated context to False fails
- alice_publish_treasure_map_schema.context[EncryptedTreasureMap.IS_FEDERATED_CONTEXT_KEY] = False
- with pytest.raises(InvalidInputData):
- # failed because non-federated (blockchain) treasure map expected, but instead federated treasure map provided
- alice_publish_treasure_map_schema.load(required_data)
-
def test_alice_revoke():
pass # TODO
diff --git a/tests/unit/characters/control/test_character_fields.py b/tests/unit/characters/control/test_character_fields.py
index bc658b07b..17b4bf8fb 100644
--- a/tests/unit/characters/control/test_character_fields.py
+++ b/tests/unit/characters/control/test_character_fields.py
@@ -164,7 +164,7 @@ def test_umbral_signature():
def test_treasure_map(enacted_federated_policy):
treasure_map = enacted_federated_policy.treasure_map
- field = EncryptedTreasureMap(federated_only=True)
+ field = EncryptedTreasureMap()
serialized = field._serialize(value=treasure_map, attr=None, obj=None)
assert serialized == b64encode(bytes(treasure_map)).decode()
diff --git a/tests/unit/test_porter.py b/tests/unit/test_porter.py
index af0b6424f..138393adb 100644
--- a/tests/unit/test_porter.py
+++ b/tests/unit/test_porter.py
@@ -30,7 +30,7 @@ from tests.utils.policy import work_order_setup
def test_hrac_field(enacted_federated_policy):
- hrac = enacted_federated_policy.treasure_map._hrac
+ hrac = enacted_federated_policy.treasure_map.hrac
field = HRAC()
serialized = field._serialize(value=hrac, attr=None, obj=None)
diff --git a/tests/unit/test_treasure_maps.py b/tests/unit/test_treasure_maps.py
index 9f7d2e053..4e61dbce4 100644
--- a/tests/unit/test_treasure_maps.py
+++ b/tests/unit/test_treasure_maps.py
@@ -20,50 +20,55 @@ import os
import pytest
-from nucypher.crypto.powers import DecryptingPower, SigningPower
from nucypher.crypto.umbral_adapter import KeyFrag
-from nucypher.policy.maps import TreasureMap, AuthorizedKeyFrag
+from nucypher.policy.maps import TreasureMap, EncryptedTreasureMap, AuthorizedKeyFrag
def test_complete_treasure_map_journey(federated_alice, federated_bob, federated_ursulas, idle_federated_policy, mocker):
- treasure_map = TreasureMap(m=1)
+ label = "chili con carne 🔥".encode('utf-8')
+ kfrags = idle_federated_policy.kfrags
+ ursulas = list(federated_ursulas)[:len(kfrags)]
- bob_encrypting_key = federated_bob.public_keys(DecryptingPower)
- bob_verifying_key = federated_bob.public_keys(SigningPower)
+ treasure_map = TreasureMap.construct_by_publisher(publisher=federated_alice,
+ bob=federated_bob,
+ label=label,
+ ursulas=ursulas,
+ verified_kfrags=kfrags,
+ m=1)
- kfrag = idle_federated_policy.kfrags[0]
- make_kfrag_payload_spy = mocker.spy(AuthorizedKeyFrag, '__bytes__')
-
- treasure_map.derive_hrac(publisher_stamp=federated_alice.stamp,
- bob_verifying_key=bob_verifying_key,
- label="chili con carne 🔥".encode('utf-8'))
-
- encrypted_kfrags = dict()
- for ursula in federated_ursulas:
- treasure_map.add_kfrag(ursula, kfrag, federated_alice.stamp)
- encrypted_kfrags[ursula.checksum_address] = make_kfrag_payload_spy.spy_return
-
- treasure_map.prepare_for_publication(bob_encrypting_key=bob_encrypting_key,
- publisher_stamp=federated_alice.stamp)
-
- ursula_rolodex = {u.checksum_address: u for u in federated_ursulas}
+ ursula_rolodex = {u.checksum_address: u for u in ursulas}
for ursula_address, encrypted_kfrag in treasure_map.destinations.items():
assert ursula_address in ursula_rolodex
ursula = ursula_rolodex[ursula_address]
- kfrag_payload = encrypted_kfrags[ursula.checksum_address]
- assert kfrag_payload == ursula.verify_from(federated_alice, encrypted_kfrag, decrypt=True) # FIXME: 2203
+ auth_kfrag_bytes = ursula.verify_from(federated_alice, encrypted_kfrag, decrypt=True) # FIXME: 2203
+ auth_kfrag = AuthorizedKeyFrag.from_bytes(auth_kfrag_bytes)
+ ursula.verify_kfrag_authorization(hrac=treasure_map.hrac,
+ author=federated_alice,
+ publisher=federated_alice,
+ authorized_kfrag=auth_kfrag)
serialized_map = bytes(treasure_map)
# ...
deserialized_map = TreasureMap.from_bytes(serialized_map)
- compass = federated_bob.make_compass_for_alice(federated_alice)
- deserialized_map.orient(compass)
+ assert treasure_map.destinations == deserialized_map.destinations
+ assert treasure_map.hrac == deserialized_map.hrac
- assert treasure_map.m == deserialized_map.m == 1
- assert set(treasure_map.destinations) == set(deserialized_map.destinations)
- assert treasure_map == deserialized_map
+
+ enc_treasure_map = treasure_map.prepare_for_publication(publisher=federated_alice,
+ bob=federated_bob)
+
+ enc_serialized_map = bytes(enc_treasure_map)
+ # ...
+ enc_deserialized_map = EncryptedTreasureMap.from_bytes(enc_serialized_map)
+
+ compass = federated_bob.make_compass_for_alice(federated_alice)
+ decrypted_map = enc_deserialized_map.orient(compass)
+
+ assert treasure_map.m == decrypted_map.m == 1
+ assert treasure_map.destinations == decrypted_map.destinations
+ assert treasure_map.hrac == decrypted_map.hrac
@pytest.mark.skip(reason='Backwards-incompatible with umbral 0.2+')
diff --git a/tests/utils/policy.py b/tests/utils/policy.py
index a7eeb98e4..f7e6f9abd 100644
--- a/tests/utils/policy.py
+++ b/tests/utils/policy.py
@@ -42,8 +42,9 @@ def work_order_setup(enacted_policy,
bob,
alice):
+ treasure_map = bob._try_orient(enacted_policy.treasure_map, enacted_policy.publisher_verifying_key)
+
# We pick up our story with Bob already having followed the treasure map above, ie:
- treasure_map = enacted_policy.treasure_map
bob.start_learning_loop()
bob.follow_treasure_map(treasure_map=treasure_map, block=True, timeout=1)