Several touchups to Finnegan's Wake logic and the high-level APIs that power it.

pull/248/head
jMyles 2018-04-17 21:55:25 -07:00
parent 50915fc1f2
commit 7890dcc2b2
8 changed files with 151 additions and 59 deletions

View File

@ -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))

View File

@ -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)

View File

@ -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()

View File

@ -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,
)

View File

@ -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):
"""

View File

@ -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(

View File

@ -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...

View File

@ -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