mirror of https://github.com/nucypher/nucypher.git
Several touchups to Finnegan's Wake logic and the high-level APIs that power it.
parent
50915fc1f2
commit
7890dcc2b2
|
@ -1,25 +1,28 @@
|
|||
# This is an example of Alice setting a Policy on the NuCypher network.
|
||||
# In this example, Alice uses n=1, which is almost always a bad idea. Don't do it.
|
||||
|
||||
# WIP w/ hendrix@8227c4abcb37ee6d27528a13ec22d55ee106107f
|
||||
# WIP w/ hendrix@3.0.0
|
||||
|
||||
import datetime
|
||||
import sys
|
||||
|
||||
from examples.sandbox_resources import SandboxNetworkyStuff
|
||||
from nkms.characters import Alice, Bob, Ursula
|
||||
from nkms.crypto.api import keccak_digest
|
||||
from nkms.data_sources import DataSource
|
||||
from nkms.network.node import NetworkyStuff
|
||||
import maya
|
||||
|
||||
# Some basic setup.
|
||||
|
||||
ALICE = Alice()
|
||||
BOB = Bob()
|
||||
# This is already running in another process.
|
||||
URSULA = Ursula.from_rest_url(NetworkyStuff(), address="localhost", port=3601)
|
||||
network_middleware = SandboxNetworkyStuff([URSULA])
|
||||
|
||||
|
||||
#########
|
||||
# Alice #
|
||||
#########
|
||||
|
||||
ALICE = Alice(network_middleware=network_middleware)
|
||||
|
||||
# Here are our Policy details.
|
||||
policy_end_datetime = maya.now() + datetime.timedelta(days=5)
|
||||
m = 1
|
||||
|
@ -32,17 +35,43 @@ label = b"secret/files/and/stuff"
|
|||
ALICE.network_bootstrap([("localhost", 3601)])
|
||||
|
||||
# Alice grants to Bob.
|
||||
policy = ALICE.grant(BOB, label, network_middleware, m=m, n=n,
|
||||
BOB = Bob()
|
||||
policy = ALICE.grant(BOB, label, m=m, n=n,
|
||||
expiration=policy_end_datetime)
|
||||
hrac, treasure_map = policy.hrac(), policy.treasure_map
|
||||
|
||||
# Bob can re-assemble the hrac himself with knowledge he already has.
|
||||
hrac = keccak_digest(bytes(ALICE.stamp) + bytes(BOB.stamp) + label)
|
||||
BOB.join_policy(ALICE, hrac, node_list=[("localhost", 3601)])
|
||||
# Alice puts her public key somewhere for Bob to find later...
|
||||
alices_pubkey_saved_for_posterity = bytes(ALICE.stamp)
|
||||
|
||||
# Now, Alice and Bob are ready for some throughput.
|
||||
# ...and then disappears from the internet.
|
||||
del ALICE
|
||||
# (this is optional of course - she may wish to remain in order to create
|
||||
# new policies in the future. The point is - she is no longer obligated.
|
||||
|
||||
#####################
|
||||
# some time passes. #
|
||||
# ... #
|
||||
# And now for Bob. #
|
||||
#####################
|
||||
|
||||
# Bob wants to join the policy so that he can receive any future
|
||||
# data shared on it.
|
||||
# He needs a few piece of knowledge to do that.
|
||||
BOB.join_policy(label, # The label - he needs to know what data he's after.
|
||||
alices_pubkey_saved_for_posterity, # To verify the signature, he'll need Alice's public key.
|
||||
verify_sig=True, # And yes, he usually wants to verify that signature.
|
||||
# He can also bootstrap himself onto the network more quickly
|
||||
# by providing a list of known nodes at this time.
|
||||
node_list=[("localhost", 3601)]
|
||||
)
|
||||
|
||||
# Now that Bob has joined the Policy, let's show how DataSources
|
||||
# can share data with the members of this Policy and then how Bob retrieves it.
|
||||
finnegans_wake = open(sys.argv[1], 'rb')
|
||||
|
||||
# We'll also keep track of some metadata to gauge performance.
|
||||
# You can safely ignore from here until...
|
||||
################################################################################
|
||||
|
||||
start_time = datetime.datetime.now()
|
||||
|
||||
for counter, plaintext in enumerate(finnegans_wake):
|
||||
|
@ -56,10 +85,58 @@ for counter, plaintext in enumerate(finnegans_wake):
|
|||
print("PREs per second: {}".format(counter / seconds))
|
||||
print("********************************")
|
||||
|
||||
data_source = DataSource(policy_pubkey_enc=policy.public_key())
|
||||
|
||||
################################################################################
|
||||
# ...here. OK, pay attention again.
|
||||
# Now it's time for...
|
||||
|
||||
#####################
|
||||
# Using DataSources #
|
||||
#####################
|
||||
|
||||
# Now Alice has set a Policy and Bob has joined it.
|
||||
# You're ready to make some DataSources and encrypt for Bob.
|
||||
|
||||
# It may also be helpful to imagine that you have multiple Bobs,
|
||||
# multiple Labels, or both.
|
||||
|
||||
# First we make a DataSource for this policy.
|
||||
data_source = DataSource(policy_pubkey_enc=policy.public_key)
|
||||
|
||||
# Here's how we generate a MessageKit for the Policy. We also get a signature
|
||||
# here, which can be passed via a side-channel (or posted somewhere public as
|
||||
# testimony) and verified if desired. In this case, the plaintext is a
|
||||
# single passage from James Joyce's Finnegan's Wake.
|
||||
# The matter of whether encryption makes the passage more or less readable
|
||||
# is left to the reader to determine.
|
||||
message_kit, _signature = data_source.encapsulate_single_message(plaintext)
|
||||
|
||||
delivered_cleartext = BOB.retrieve(hrac=hrac, message_kit=message_kit, data_source=data_source)
|
||||
# The DataSource will want to be able to be verified by Bob, so it leaves
|
||||
# its Public Key somewhere.
|
||||
data_source_public_key = bytes(data_source.stamp)
|
||||
|
||||
# It can save the MessageKit somewhere (IPFS, etc) and then it too can
|
||||
# choose to disappear (although it may also opt to continue transmitting
|
||||
# as many messages as may be appropriate).
|
||||
del data_source
|
||||
|
||||
###############
|
||||
# Back to Bob #
|
||||
###############
|
||||
|
||||
# Bob needs to reconstruct the DataSource.
|
||||
datasource_as_understood_by_bob = DataSource.from_public_keys(
|
||||
policy_public_key=policy.public_key,
|
||||
datasource_public_key=data_source_public_key,
|
||||
label=label
|
||||
)
|
||||
|
||||
# Now Bob can retrieve the original message. He just needs the MessageKit
|
||||
# and the DataSource which produced it.
|
||||
delivered_cleartext = BOB.retrieve(message_kit=message_kit,
|
||||
data_source=datasource_as_understood_by_bob,
|
||||
alice_pubkey_sig=alices_pubkey_saved_for_posterity)
|
||||
|
||||
# We show that indeed this is the passage originally encrypted by the DataSource.
|
||||
assert plaintext == delivered_cleartext
|
||||
print("Retrieved: {}".format(delivered_cleartext))
|
||||
|
|
|
@ -304,7 +304,8 @@ class Character(object):
|
|||
powers_and_keys=({SigningPower: pubkey})
|
||||
)
|
||||
else:
|
||||
self.log.warn("Discovered node with bad signature: {}".format(node_meta))
|
||||
message = "Suspicious Activity: Discovered node with bad signature: {}. Propagated by: {}:{}".format(node_meta, address, port)
|
||||
self.log.warn(message)
|
||||
return new_nodes
|
||||
|
||||
def network_bootstrap(self, node_list):
|
||||
|
@ -355,8 +356,7 @@ class Alice(Character, PolicyAuthor):
|
|||
|
||||
return policy
|
||||
|
||||
def grant(self, bob, uri, networky_stuff,
|
||||
m=None, n=None, expiration=None, deposit=None):
|
||||
def grant(self, bob, uri, m=None, n=None, expiration=None, deposit=None):
|
||||
if not m:
|
||||
# TODO: get m from config #176
|
||||
raise NotImplementedError
|
||||
|
@ -369,7 +369,7 @@ class Alice(Character, PolicyAuthor):
|
|||
if not deposit:
|
||||
default_deposit = None # TODO: Check default deposit in config. #176
|
||||
if not default_deposit:
|
||||
deposit = networky_stuff.get_competitive_rate()
|
||||
deposit = self.network_middleware.get_competitive_rate()
|
||||
if deposit == NotImplemented:
|
||||
deposit = constants.NON_PAYMENT(b"0000000")
|
||||
|
||||
|
@ -379,12 +379,12 @@ class Alice(Character, PolicyAuthor):
|
|||
# by trying differet
|
||||
# deposits and expirations on a limited number of Ursulas.
|
||||
# Users may decide to inject some market strategies here.
|
||||
found_ursulas = policy.find_ursulas(networky_stuff, deposit,
|
||||
found_ursulas = policy.find_ursulas(self.network_middleware, deposit,
|
||||
expiration, num_ursulas=n)
|
||||
policy.match_kfrags_to_found_ursulas(found_ursulas)
|
||||
# REST call happens here, as does population of TreasureMap.
|
||||
policy.enact(networky_stuff)
|
||||
policy.publish_treasure_map(networky_stuff)
|
||||
policy.enact(self.network_middleware)
|
||||
policy.publish_treasure_map(self.network_middleware)
|
||||
|
||||
return policy # Now with TreasureMap affixed!
|
||||
|
||||
|
@ -456,8 +456,8 @@ class Bob(Character):
|
|||
powers_and_keys=({SigningPower: ursula_pubkey_sig})
|
||||
)
|
||||
|
||||
def get_treasure_map(self, alice, hrac, using_dht=False):
|
||||
map_id = keccak_digest(bytes(alice.stamp) + hrac)
|
||||
def get_treasure_map(self, alice_pubkey_sig, hrac, using_dht=False, verify_sig=True):
|
||||
map_id = keccak_digest(alice_pubkey_sig + hrac)
|
||||
|
||||
if using_dht:
|
||||
ursula_coro = self.server.get(map_id)
|
||||
|
@ -470,10 +470,14 @@ class Bob(Character):
|
|||
tmap_message_kit = self.get_treasure_map_from_known_ursulas(self.network_middleware,
|
||||
map_id)
|
||||
|
||||
verified, packed_node_list = self.verify_from(
|
||||
alice, tmap_message_kit,
|
||||
decrypt=True
|
||||
)
|
||||
if verify_sig:
|
||||
alice = Alice.from_public_keys({SigningPower: alice_pubkey_sig})
|
||||
verified, packed_node_list = self.verify_from(
|
||||
alice, tmap_message_kit,
|
||||
decrypt=True
|
||||
)
|
||||
else:
|
||||
assert False
|
||||
|
||||
if not verified:
|
||||
return constants.NOT_FROM_ALICE
|
||||
|
@ -554,14 +558,18 @@ class Bob(Character):
|
|||
def get_ursula(self, ursula_id):
|
||||
return self._ursulas[ursula_id]
|
||||
|
||||
def join_policy(self, alice, hrac, using_dht=False, node_list=None):
|
||||
# TODO: unfuckify this
|
||||
def join_policy(self, policy_pubkey, label, alice_pubkey_sig=None,
|
||||
using_dht=False, node_list=None, verify_sig=True):
|
||||
if verify_sig and not alice_pubkey_sig:
|
||||
raise ValueError("Can't verify the signature without Alice's key.")
|
||||
hrac = keccak_digest(bytes(policy_pubkey) + bytes(self.stamp) + label)
|
||||
if node_list:
|
||||
self.network_bootstrap(node_list)
|
||||
self.get_treasure_map(alice, hrac, using_dht=using_dht)
|
||||
self.get_treasure_map(alice_pubkey_sig, hrac, using_dht=using_dht, verify_sig=verify_sig)
|
||||
self.follow_treasure_map(hrac, using_dht=using_dht)
|
||||
|
||||
def retrieve(self, hrac, message_kit, data_source):
|
||||
def retrieve(self, message_kit, data_source):
|
||||
hrac = keccak_digest(bytes(data_source.policy_pubkey) + self.stamp + data_source.label)
|
||||
treasure_map = self.treasure_maps[hrac]
|
||||
|
||||
# First, a quick sanity check to make sure we know about at least m nodes.
|
||||
|
@ -574,7 +582,7 @@ class Bob(Character):
|
|||
work_orders = self.generate_work_orders(hrac, message_kit.capsule)
|
||||
for node_id in self.treasure_maps[hrac]:
|
||||
node = self.known_nodes[UmbralPublicKey.from_bytes(node_id)]
|
||||
cfrags = self.get_reencrypted_c_frags(self.network_middleware, work_orders[bytes(node.stamp)])
|
||||
cfrags = self.get_reencrypted_c_frags(work_orders[bytes(node.stamp)])
|
||||
message_kit.capsule.attach_cfrag(cfrags[0])
|
||||
verified, delivered_cleartext = self.verify_from(data_source, message_kit, decrypt=True)
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import inspect
|
||||
|
||||
from nkms.keystore import keypairs
|
||||
from nkms.keystore.keypairs import SigningKeypair, EncryptingKeypair
|
||||
from umbral.keys import UmbralPublicKey, UmbralPrivateKey
|
||||
|
@ -75,10 +74,16 @@ class KeyPairBasedPower(CryptoPowerUp):
|
|||
elif keypair:
|
||||
self.keypair = keypair
|
||||
else:
|
||||
# They didn't pass a keypair; we'll make one with the bytes (if any)
|
||||
# they provided.
|
||||
# They didn't pass a keypair; we'll make one with the bytes or
|
||||
# UmbralPublicKey if they provided such a thing.
|
||||
if pubkey:
|
||||
key_to_pass_to_keypair = pubkey
|
||||
try:
|
||||
key_to_pass_to_keypair = pubkey.as_umbral_pubkey()
|
||||
except AttributeError:
|
||||
try:
|
||||
key_to_pass_to_keypair = UmbralPublicKey.from_bytes(pubkey)
|
||||
except TypeError:
|
||||
key_to_pass_to_keypair = pubkey
|
||||
else:
|
||||
# They didn't even pass pubkey_bytes. We'll generate a keypair.
|
||||
key_to_pass_to_keypair = UmbralPrivateKey.gen_key()
|
||||
|
|
|
@ -2,18 +2,29 @@ from nkms.crypto.api import encrypt_and_sign
|
|||
from nkms.crypto.signature import SignatureStamp
|
||||
from nkms.keystore.keypairs import SigningKeypair
|
||||
from constant_sorrow.constants import NO_SIGNING_POWER
|
||||
from umbral.keys import UmbralPublicKey
|
||||
|
||||
|
||||
class DataSource:
|
||||
|
||||
def __init__(self, policy_pubkey_enc, signer=NO_SIGNING_POWER):
|
||||
self._policy_pubkey_enc = policy_pubkey_enc
|
||||
def __init__(self, policy_pubkey_enc, signer=NO_SIGNING_POWER, label=None):
|
||||
self.policy_pubkey = policy_pubkey_enc
|
||||
if signer is NO_SIGNING_POWER:
|
||||
signer = SignatureStamp(SigningKeypair()) # TODO: Generate signing key properly. #241
|
||||
self.stamp = signer
|
||||
self.label = label
|
||||
|
||||
def encapsulate_single_message(self, message):
|
||||
message_kit, signature = encrypt_and_sign(self._policy_pubkey_enc,
|
||||
message_kit, signature = encrypt_and_sign(self.policy_pubkey,
|
||||
plaintext=message,
|
||||
signer=self.stamp)
|
||||
message_kit.policy_pubkey = self._policy_pubkey_enc # TODO: We can probably do better here.
|
||||
message_kit.policy_pubkey = self.policy_pubkey # TODO: We can probably do better here.
|
||||
return message_kit, signature
|
||||
|
||||
@classmethod
|
||||
def from_public_keys(cls, policy_public_key, datasource_public_key, label):
|
||||
umbral_public_key = UmbralPublicKey.from_bytes(datasource_public_key)
|
||||
return cls(policy_public_key,
|
||||
signer=SignatureStamp(SigningKeypair(umbral_public_key)),
|
||||
label=label,
|
||||
)
|
||||
|
|
|
@ -165,13 +165,6 @@ class Policy(object):
|
|||
return policy
|
||||
|
||||
def hrac(self):
|
||||
"""
|
||||
A convenience method for generating an hrac for this instance.
|
||||
"""
|
||||
return self.hrac_for(self.alice, self.bob, self.uri)
|
||||
|
||||
@staticmethod
|
||||
def hrac_for(alice, bob, uri):
|
||||
"""
|
||||
The "hashed resource authentication code".
|
||||
|
||||
|
@ -183,7 +176,7 @@ class Policy(object):
|
|||
Alice and Bob have all the information they need to construct this.
|
||||
Ursula does not, so we share it with her.
|
||||
"""
|
||||
return keccak_digest(bytes(alice.stamp) + bytes(bob.stamp) + uri)
|
||||
return keccak_digest(bytes(self.public_key()) + bytes(self.bob.stamp) + self.uri)
|
||||
|
||||
def treasure_map_dht_key(self):
|
||||
"""
|
||||
|
|
|
@ -7,19 +7,17 @@ from nkms.crypto.api import keccak_digest
|
|||
from nkms.crypto.constants import PUBLIC_KEY_LENGTH
|
||||
from nkms.crypto.powers import SigningPower, EncryptingPower
|
||||
from bytestring_splitter import BytestringSplitter
|
||||
from tests.utilities import MockNetworkyStuff
|
||||
from umbral.fragments import KFrag
|
||||
from umbral.keys import UmbralPublicKey
|
||||
import maya
|
||||
|
||||
|
||||
def test_grant(alice, bob, ursulas):
|
||||
networky_stuff = MockNetworkyStuff(ursulas)
|
||||
def test_grant(alice, bob):
|
||||
policy_end_datetime = maya.now() + datetime.timedelta(days=5)
|
||||
n = 5
|
||||
uri = b"this_is_the_path_to_which_access_is_being_granted"
|
||||
policy = alice.grant(bob, uri, networky_stuff, m=3, n=n,
|
||||
expiration=policy_end_datetime)
|
||||
policy = alice.grant(bob, uri, m=3, n=n,
|
||||
expiration=policy_end_datetime)
|
||||
|
||||
# The number of policies is equal to the number of Ursulas we're using (n)
|
||||
assert len(policy._accepted_arrangements) == n
|
||||
|
@ -28,7 +26,7 @@ def test_grant(alice, bob, ursulas):
|
|||
ursula = list(policy._accepted_arrangements.values())[0].ursula
|
||||
|
||||
# Get the Policy from Ursula's datastore, looking up by hrac.
|
||||
proper_hrac = keccak_digest(bytes(alice.stamp) + bytes(bob.stamp) + uri)
|
||||
proper_hrac = keccak_digest(bytes(policy.public_key()) + bytes(bob.stamp) + uri)
|
||||
retrieved_policy = ursula.datastore.get_policy_arrangement(proper_hrac.hex().encode())
|
||||
|
||||
# TODO: Make this a legit KFrag, not bytes.
|
||||
|
@ -42,7 +40,7 @@ def test_grant(alice, bob, ursulas):
|
|||
assert found
|
||||
|
||||
|
||||
def test_alice_can_get_ursulas_keys_via_rest(alice, ursulas):
|
||||
def test_alice_can_get_ursulas_keys_via_rest(ursulas):
|
||||
mock_client = TestClient(ursulas[0].rest_app)
|
||||
response = mock_client.get('http://localhost/public_keys')
|
||||
splitter = BytestringSplitter(
|
||||
|
|
|
@ -39,7 +39,7 @@ def test_bob_can_follow_treasure_map(enacted_policy, ursulas, bob, alice):
|
|||
assert len(bob.known_nodes) == len(ursulas)
|
||||
|
||||
# Now, Bob can get the TreasureMap all by himself, and doesn't need a side channel.
|
||||
bob.get_treasure_map(alice, hrac)
|
||||
bob.get_treasure_map(alice.stamp, hrac)
|
||||
newly_discovered, total_known = bob.follow_treasure_map(hrac)
|
||||
|
||||
# He finds that he didn't need to discover any new nodes...
|
||||
|
|
|
@ -84,7 +84,7 @@ def test_alice_creates_policy_group_with_correct_hrac(idle_policy):
|
|||
bob = idle_policy.bob
|
||||
|
||||
assert idle_policy.hrac() == keccak_digest(
|
||||
bytes(alice.stamp) + bytes(bob.stamp) + alice.__resource_id)
|
||||
bytes(idle_policy.public_key()) + bytes(bob.stamp) + alice.__resource_id)
|
||||
|
||||
|
||||
def test_alice_sets_treasure_map_on_network(enacted_policy, ursulas):
|
||||
|
@ -154,13 +154,13 @@ def test_bob_can_retreive_the_treasure_map_and_decrypt_it(enacted_policy, ursula
|
|||
|
||||
# If Bob doesn't know about any Ursulas, he can't find the TreasureMap via the REST swarm:
|
||||
with pytest.raises(bob.NotEnoughUrsulas):
|
||||
treasure_map_from_wire = bob.get_treasure_map(enacted_policy.alice, enacted_policy.hrac())
|
||||
treasure_map_from_wire = bob.get_treasure_map(enacted_policy.alice.stamp, enacted_policy.hrac())
|
||||
|
||||
# Let's imagine he has learned about some - say, from the blockchain.
|
||||
bob.known_nodes = {u.interface_info_with_metadata(): u for u in ursulas}
|
||||
|
||||
# Now try.
|
||||
treasure_map_from_wire = bob.get_treasure_map(enacted_policy.alice, enacted_policy.hrac())
|
||||
treasure_map_from_wire = bob.get_treasure_map(enacted_policy.alice.stamp, enacted_policy.hrac())
|
||||
|
||||
assert enacted_policy.treasure_map == treasure_map_from_wire
|
||||
|
||||
|
|
Loading…
Reference in New Issue