nucypher/nkms/policy/models.py

233 lines
7.8 KiB
Python

import msgpack
from nkms.characters import Alice, Bob, Ursula
from nkms.crypto import api
from nkms.crypto.powers import EncryptingPower
from npre.constants import UNKNOWN_KFRAG
class PolicyOffer(object):
"""
An offer from Alice to Ursula to enter into a contract for Re-Encryption services.
"""
def __init__(self, n, deposit, contract_end_datetime):
"""
:param n: The total number of Policies which Alice wishes to create.
:param deposit: Funds which will pay for the timeframe of the contract (not the actual re-encryptions);
a portion will be locked for each Ursula that accepts.
:param contract_end_datetime: The moment which Alice wants the contract to end.
"""
self.n = n
self.deposit = deposit
self.contract_end_datetime = contract_end_datetime
class PolicyOfferResponse(object):
pass
class PolicyManager(object):
pass
class PolicyManagerForAlice(PolicyManager):
def __init__(self, owner: Alice):
self.owner = owner
def create_policy_group(self,
bob: Bob,
uri: bytes,
m: int,
n: int,
):
"""
Alice dictates a new group of policies.
"""
##### Temporary until we decide on an API for private key access
alice_priv_enc = self.owner._crypto_power._power_ups[EncryptingPower].priv_key
re_enc_keys, encrypted_key = self.owner.generate_rekey_frags(alice_priv_enc, bob, m,
n) # TODO: Access Alice's private key inside this method.
policies = []
for kfrag_id, rekey in enumerate(re_enc_keys):
policy = Policy.from_alice(
alice=self.owner,
kfrag=rekey,
)
policies.append(policy)
return PolicyGroup(uri, bob, policies)
class PolicyGroup(object):
"""
The terms and conditions by which Alice shares with Bob.
"""
_id = None
def __init__(self, uri: str, bob: Bob, policies=None):
self.policies = policies or []
self.bob = bob
self.uri = uri
self.treasure_map = TreasureMap()
@property
def n(self):
return len(self.policies)
def find_n_ursulas(self, networky_stuff, offer: PolicyOffer) -> list:
"""
:param networky_stuff: A compliant interface (maybe a Client instance) to be used to engage the DHT swarm.
:return: A list, with each element containing an Ursula and an OfferResult.
"""
for policy in self.policies:
try:
ursula, result = networky_stuff.find_ursula(self.id, offer)
# TODO: Here, we need to assess the result and see if we're actually good to go.
if result.was_accepted:
policy.activate(ursula, result)
except networky_stuff.NotEnoughQualifiedUrsulas:
pass # Tell Alice to either wait or lower the value of n.
def transmit_payloads(self, networky_stuff):
for policy in self.policies:
payload = policy.encrypt_payload_for_ursula()
_response = networky_stuff.animate_policy(policy.ursula,
payload) # TODO: Parse response for confirmation and update TreasureMap with new Ursula friend.
# Assuming response is what we hope for
self.treasure_map.add_ursula(policy.ursula)
@property
def id(self):
if not self._id:
self._id = api.keccak_digest(self.uri, bytes(self.bob.seal))
return self._id
class Policy(object):
"""
An individual agreement between Alice and Ursula. Together, all of the Policies by which
Ursula nodes which enter into an agreement regarding the same series of kFrags constitute
a PolicyGroup.
A Policy has a unique ID, which includes a fingerprint of Alice's public key so that
only she can set a policy with that ID. Ursula must verify this; otherwise a collision
attack is possible.
"""
_ursula = None
hashed_part = None
_id = None
def __init__(self, alice, kfrag=UNKNOWN_KFRAG, deterministic_id_portion=None, challenge_size=20, set_id=True):
"""
:param kfrag:
The kFrag obviously, but defaults to UNKNOWN_KFRAG in case the user wants to set it later.
:param deterministic_id_portion: Probably the fingerprint of Alice's public key.
Any part that Ursula can use to verify that Alice is the rightful setter of this ID.
If it's not included, the Policy ID will be completely random.
:param challenge_size: The number of challenges to create in the ChallengePack.
"""
self.alice = alice
self.kfrag = kfrag
self.deterministic_id_portion = deterministic_id_portion
self.random_id_portion = api.secure_random(32) # TOOD: Where do we actually want this to live?
self.challenge_size = challenge_size
self.treasure_map = []
if set_id:
self.set_id()
@property
def id(self):
if self._id:
return self._id
else:
raise RuntimeError("No implemented way to get id yet.")
def set_id(self):
if self.deterministic_id_portion:
self._id = "{}-{}".format(
api.keccak_digest(*[str(d).encode() for d in self.deterministic_id_portion], self.random_id_portion),
api.keccak_digest(self.random_id_portion))
else:
self._id = api.keccak_digest(self.random_id_portion)
@property
def ursula(self):
if not self._ursula:
raise Ursula.NotFound
else:
return self._ursula
@ursula.setter
def ursula(self, ursula_object):
self.alice.learn_about_actor(ursula_object)
self._ursula = ursula_object
@staticmethod
def from_alice(kfrag,
alice,
):
policy = Policy(alice, kfrag, deterministic_id_portion=alice.seal)
policy.generate_challenge_pack()
return policy
def payload(self):
return msgpack.dumps({b"kf": bytes(self.kfrag), b"cp": msgpack.dumps(self.challenge_pack)})
def activate(self, ursula, negotiation_result):
self.ursula = ursula
self.negotiation_result = negotiation_result
def hash(self, pubkey_sig_alice, hash_input):
self.hashed_part = api.keccak_digest(hash_input)
hash_input_for_id = str(pubkey_sig_alice).encode() + str(self.hashed_part).encode()
self._id = api.keccak_digest(hash_input_for_id)
return self._id
def generate_challenge_pack(self):
if self.kfrag == UNKNOWN_KFRAG:
raise TypeError(
"Can't generate a challenge pack unless we know the kfrag. Are you Alice?")
# TODO: make this work instead of being random.
import random
self.challenge_pack = [(random.getrandbits(32), random.getrandbits(32)) for x in
range(self.challenge_size)]
return True
def encrypt_payload_for_ursula(self):
"""
Craft an offer to send to Ursula.
"""
return self.alice.encrypt_for(self.ursula, self.payload())
def update_treasure_map(self, policy_offer_result):
# TODO: parse the result and add the node information to the treasure map.
self.treasure_map.append(policy_offer_result)
class TreasureMap(object):
def __init__(self, ursula_interface_ids=None):
self.ids = ursula_interface_ids or []
def packed_payload(self):
return msgpack.dumps(self.ids)
def add_ursula(self, ursula):
self.ids.append(ursula.ip_dht_key())
def __eq__(self, other):
return self.ids == other.ids
def __iter__(self):
return iter(self.ids)