mirror of https://github.com/nucypher/nucypher.git
commit
6922739fd9
|
@ -15,6 +15,7 @@ from sqlalchemy.exc import IntegrityError
|
||||||
|
|
||||||
from umbral.fragments import KFrag
|
from umbral.fragments import KFrag
|
||||||
from umbral.keys import UmbralPublicKey
|
from umbral.keys import UmbralPublicKey
|
||||||
|
import umbral
|
||||||
|
|
||||||
from nkms.crypto import api as API
|
from nkms.crypto import api as API
|
||||||
from nkms.crypto.api import secure_random, keccak_digest
|
from nkms.crypto.api import secure_random, keccak_digest
|
||||||
|
@ -238,7 +239,7 @@ class Alice(Character):
|
||||||
_server_class = NuCypherSeedOnlyDHTServer
|
_server_class = NuCypherSeedOnlyDHTServer
|
||||||
_default_crypto_powerups = [SigningPower, EncryptingPower]
|
_default_crypto_powerups = [SigningPower, EncryptingPower]
|
||||||
|
|
||||||
def generate_rekey_frags(self, alice_privkey, bob, m, n):
|
def generate_kfrags(self, bob, m, n):
|
||||||
"""
|
"""
|
||||||
Generates re-encryption key frags and returns the frags and encrypted
|
Generates re-encryption key frags and returns the frags and encrypted
|
||||||
ephemeral key data.
|
ephemeral key data.
|
||||||
|
@ -250,9 +251,10 @@ class Alice(Character):
|
||||||
|
|
||||||
:return: Tuple(kfrags, eph_key_data)
|
:return: Tuple(kfrags, eph_key_data)
|
||||||
"""
|
"""
|
||||||
kfrags, eph_key_data = API.ecies_ephemeral_split_rekey(
|
# TODO: Is this how we want to access Alice's private key?
|
||||||
alice_privkey, bytes(bob.seal.without_metabytes()), m, n)
|
alice_priv_enc = self._crypto_power._power_ups[EncryptingPower].keypair.privkey
|
||||||
return (kfrags, eph_key_data)
|
k_frags, _v_keys = umbral.umbral.split_rekey(alice_priv_enc, bob.public_key(EncryptingPower), m, n)
|
||||||
|
return k_frags
|
||||||
|
|
||||||
def create_policy(self,
|
def create_policy(self,
|
||||||
bob: "Bob",
|
bob: "Bob",
|
||||||
|
@ -263,17 +265,13 @@ class Alice(Character):
|
||||||
"""
|
"""
|
||||||
Alice dictates a new group of policies.
|
Alice dictates a new group of policies.
|
||||||
"""
|
"""
|
||||||
|
kfrags = self.generate_kfrags(bob, m, n)
|
||||||
##### Temporary until we decide on an API for private key access
|
|
||||||
alice_priv_enc = self._crypto_power._power_ups[EncryptingPower].priv_key
|
|
||||||
kfrags, pfrag = self.generate_rekey_frags(alice_priv_enc, bob, m, n)
|
|
||||||
# TODO: Access Alice's private key inside this method.
|
# TODO: Access Alice's private key inside this method.
|
||||||
from nkms.policy.models import Policy
|
from nkms.policy.models import Policy
|
||||||
policy = Policy.from_alice(
|
policy = Policy.from_alice(
|
||||||
alice=self,
|
alice=self,
|
||||||
bob=bob,
|
bob=bob,
|
||||||
kfrags=kfrags,
|
kfrags=kfrags,
|
||||||
pfrag=pfrag,
|
|
||||||
uri=uri,
|
uri=uri,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ from cryptography.exceptions import InvalidSignature
|
||||||
from py_ecc.secp256k1 import ecdsa_raw_recover
|
from py_ecc.secp256k1 import ecdsa_raw_recover
|
||||||
|
|
||||||
from umbral.keys import UmbralPrivateKey, UmbralPublicKey
|
from umbral.keys import UmbralPrivateKey, UmbralPublicKey
|
||||||
from nkms.crypto.signature import Signature
|
|
||||||
|
|
||||||
SYSTEM_RAND = SystemRandom()
|
SYSTEM_RAND = SystemRandom()
|
||||||
|
|
||||||
|
@ -75,8 +75,8 @@ def ecdsa_sign(message: bytes, privkey: UmbralPrivateKey) -> bytes:
|
||||||
:return: signature
|
:return: signature
|
||||||
"""
|
"""
|
||||||
cryptography_priv_key = privkey.bn_key.to_cryptography_priv_key()
|
cryptography_priv_key = privkey.bn_key.to_cryptography_priv_key()
|
||||||
signature_bytes = cryptography_priv_key.sign(message, ec.ECDSA(hashes.BLAKE2b(64)))
|
signature_der_bytes = cryptography_priv_key.sign(message, ec.ECDSA(hashes.BLAKE2b(64)))
|
||||||
return Signature(signature_bytes)
|
return signature_der_bytes
|
||||||
|
|
||||||
|
|
||||||
def ecdsa_verify(
|
def ecdsa_verify(
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
# TODO: Turn these into classes?
|
BLAKE2B_DIGEST_LENGTH = 64
|
||||||
HASH_DIGEST_LENGTH = 64
|
KECCAK_DIGEST_LENGTH = 32
|
||||||
|
|
||||||
NOT_SIGNED = 445
|
NOT_SIGNED = 445
|
||||||
NO_DECRYPTION_PERFORMED = 455
|
NO_DECRYPTION_PERFORMED = 455
|
||||||
|
|
||||||
# These lengths are centric to secp256k1
|
# These lengths are specific to secp256k1
|
||||||
KFRAG_LENGTH = 194
|
KFRAG_LENGTH = 194
|
||||||
CFRAG_LENGTH = 131
|
CFRAG_LENGTH = 131
|
||||||
CAPSULE_LENGTH = 98
|
CAPSULE_LENGTH = 98
|
||||||
|
|
|
@ -21,15 +21,18 @@ class NoEncryptingPower(PowerUpError):
|
||||||
|
|
||||||
|
|
||||||
class CryptoPower(object):
|
class CryptoPower(object):
|
||||||
def __init__(self, power_ups=[], generate_keys_if_needed=False):
|
def __init__(self, power_ups=None, generate_keys_if_needed=False):
|
||||||
|
|
||||||
self._power_ups = {}
|
self._power_ups = {}
|
||||||
# TODO: The keys here will actually be IDs for looking up in a KeyStore.
|
# TODO: The keys here will actually be IDs for looking up in a KeyStore.
|
||||||
self.public_keys = {}
|
self.public_keys = {}
|
||||||
self.generate_keys = generate_keys_if_needed
|
self.generate_keys = generate_keys_if_needed
|
||||||
|
|
||||||
if power_ups:
|
if power_ups is not None:
|
||||||
for power_up in power_ups:
|
for power_up in power_ups:
|
||||||
self.consume_power_up(power_up)
|
self.consume_power_up(power_up)
|
||||||
|
else:
|
||||||
|
power_ups = [] # default
|
||||||
|
|
||||||
def consume_power_up(self, power_up):
|
def consume_power_up(self, power_up):
|
||||||
if isinstance(power_up, CryptoPowerUp):
|
if isinstance(power_up, CryptoPowerUp):
|
||||||
|
@ -47,14 +50,13 @@ class CryptoPower(object):
|
||||||
|
|
||||||
if power_up.confers_public_key:
|
if power_up.confers_public_key:
|
||||||
# TODO: Make this an ID for later lookup on a KeyStore.
|
# TODO: Make this an ID for later lookup on a KeyStore.
|
||||||
self.public_keys[
|
self.public_keys[power_up_class] = power_up_instance.public_key()
|
||||||
power_up_class] = power_up_instance.public_key()
|
|
||||||
|
|
||||||
def pubkey_sig_bytes(self):
|
def pubkey_sig_bytes(self):
|
||||||
try:
|
try:
|
||||||
# TODO: Turn this into an ID lookup on a KeyStore.
|
# TODO: Turn this into an ID lookup on a KeyStore.
|
||||||
return self._power_ups[
|
pubkey_sig = self._power_ups[SigningPower].public_key()
|
||||||
SigningPower].pub_key
|
return bytes(pubkey_sig)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise NoSigningPower
|
raise NoSigningPower
|
||||||
|
|
||||||
|
@ -78,8 +80,7 @@ class CryptoPower(object):
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
raise NoSigningPower(e)
|
raise NoSigningPower(e)
|
||||||
msg_digest = b"".join(API.keccak_digest(m) for m in messages)
|
msg_digest = b"".join(API.keccak_digest(m) for m in messages)
|
||||||
|
return sig_keypair.sign(msg_digest)
|
||||||
return Signature(sig_keypair.sign(msg_digest))
|
|
||||||
|
|
||||||
def decrypt(self, ciphertext):
|
def decrypt(self, ciphertext):
|
||||||
try:
|
try:
|
||||||
|
@ -118,8 +119,8 @@ class KeyPairBasedPower(CryptoPowerUp):
|
||||||
else:
|
else:
|
||||||
# They didn't pass a keypair; we'll make one with the bytes (if any)
|
# They didn't pass a keypair; we'll make one with the bytes (if any)
|
||||||
# they provided.
|
# they provided.
|
||||||
self.keypair = self._keypair_class.load_key(
|
self.keypair = self._keypair_class(
|
||||||
UmbralPublicKey(pubkey_bytes),
|
UmbralPublicKey.from_bytes(pubkey_bytes),
|
||||||
generate_keys_if_needed=generate_keys_if_needed)
|
generate_keys_if_needed=generate_keys_if_needed)
|
||||||
|
|
||||||
|
|
||||||
|
@ -127,15 +128,11 @@ class SigningPower(KeyPairBasedPower):
|
||||||
confers_public_key = True
|
confers_public_key = True
|
||||||
_keypair_class = SigningKeypair
|
_keypair_class = SigningKeypair
|
||||||
|
|
||||||
def sign(self, msghash):
|
def sign(self, message):
|
||||||
"""
|
"""
|
||||||
Signs a hashed message and returns a signature.
|
Signs a message message and returns a Signature.
|
||||||
|
|
||||||
:param msghash: The hashed message to sign
|
|
||||||
|
|
||||||
:return: Signature in bytes
|
|
||||||
"""
|
"""
|
||||||
return self.keypair.sign(msghash)
|
return self.keypair.sign(message)
|
||||||
|
|
||||||
def public_key(self):
|
def public_key(self):
|
||||||
return self.keypair.pubkey
|
return self.keypair.pubkey
|
||||||
|
|
|
@ -1,23 +1,20 @@
|
||||||
from nkms.crypto import api as API
|
from nkms.crypto import api as API
|
||||||
from umbral.keys import UmbralPublicKey
|
from umbral.keys import UmbralPublicKey
|
||||||
|
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature, encode_dss_signature
|
||||||
|
|
||||||
|
|
||||||
class Signature(bytes):
|
class Signature(object):
|
||||||
"""
|
"""
|
||||||
The Signature object allows signatures to be made and verified.
|
The Signature object allows signatures to be made and verified.
|
||||||
"""
|
"""
|
||||||
_EXPECTED_LENGTH = 70
|
_EXPECTED_LENGTH = 64 # With secp256k1 and BLAKE2b(64).
|
||||||
|
|
||||||
def __init__(self, sig_as_bytes: bytes):
|
def __init__(self, r: int, s: int):
|
||||||
"""
|
self.r = r
|
||||||
Initializes a Signature object.
|
self.s = s
|
||||||
:param sig_as_bytes: Cryptography.io signature as bytes.
|
|
||||||
:return: Signature object
|
|
||||||
"""
|
|
||||||
self.sig_as_bytes = sig_as_bytes
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "ECDSA Signature: {}".format(sig_as_bytes.decode())
|
return "ECDSA Signature: {}".format(bytes(self).hex()[:15])
|
||||||
|
|
||||||
def verify(self, message: bytes, pubkey: UmbralPublicKey) -> bool:
|
def verify(self, message: bytes, pubkey: UmbralPublicKey) -> bool:
|
||||||
"""
|
"""
|
||||||
|
@ -28,11 +25,32 @@ class Signature(bytes):
|
||||||
|
|
||||||
:return: True if valid, False if invalid
|
:return: True if valid, False if invalid
|
||||||
"""
|
"""
|
||||||
return API.ecdsa_verify(message, self.sig_as_bytes, pubkey)
|
return API.ecdsa_verify(message, self._der_encoded_bytes(), pubkey)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_bytes(cls, signature_as_bytes, der_encoded=False):
|
||||||
|
if der_encoded:
|
||||||
|
r, s = decode_dss_signature(signature_as_bytes)
|
||||||
|
else:
|
||||||
|
if not len(signature_as_bytes) == 64:
|
||||||
|
raise ValueError("Looking for exactly 64 bytes if you call from_bytes with der_encoded=False.")
|
||||||
|
else:
|
||||||
|
r = int.from_bytes(signature_as_bytes[:32], "big")
|
||||||
|
s = int.from_bytes(signature_as_bytes[32:], "big")
|
||||||
|
return cls(r, s)
|
||||||
|
|
||||||
|
def _der_encoded_bytes(self):
|
||||||
|
return encode_dss_signature(self.r, self.s)
|
||||||
|
|
||||||
def __bytes__(self):
|
def __bytes__(self):
|
||||||
"""
|
# A couple quick assertions to be sure this is OK. Remove these at some point.
|
||||||
Implements the __bytes__ call for Signature to transform into a
|
assert self.r.to_bytes(33, "big")[0] == 0, "Is 32 bytes enough?"
|
||||||
transportable mode.
|
assert self.s.to_bytes(33, "big")[0] == 0, "Is 32 bytes enough?"
|
||||||
"""
|
return self.r.to_bytes(32, "big") + self.s.to_bytes(32, "big")
|
||||||
return self.sig_as_bytes
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(bytes(self))
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
return bytes(self) + other
|
||||||
|
__radd__ = __add__
|
||||||
|
|
|
@ -47,7 +47,15 @@ class BytestringSplitter(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_message_meta(message_type):
|
def get_message_meta(message_type):
|
||||||
return message_type if isinstance(message_type, tuple) else (message_type, message_type._EXPECTED_LENGTH)
|
if isinstance(message_type, tuple):
|
||||||
|
message_meta = message_type
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
message_meta = message_type, message_type._EXPECTED_LENGTH
|
||||||
|
except AttributeError:
|
||||||
|
raise TypeError("No way to know the expected length. Either pass it as the second member of a tuple or set _EXPECTED_LENGTH on the class you're passing.")
|
||||||
|
|
||||||
|
return message_meta
|
||||||
|
|
||||||
|
|
||||||
class RepeatingBytestringSplitter(BytestringSplitter):
|
class RepeatingBytestringSplitter(BytestringSplitter):
|
||||||
|
|
|
@ -6,6 +6,7 @@ from nkms.crypto import api as API
|
||||||
from umbral.keys import UmbralPrivateKey, UmbralPublicKey
|
from umbral.keys import UmbralPrivateKey, UmbralPublicKey
|
||||||
from umbral import umbral
|
from umbral import umbral
|
||||||
from nkms.crypto.kits import MessageKit
|
from nkms.crypto.kits import MessageKit
|
||||||
|
from nkms.crypto.signature import Signature
|
||||||
|
|
||||||
|
|
||||||
class Keypair(object):
|
class Keypair(object):
|
||||||
|
@ -22,7 +23,7 @@ class Keypair(object):
|
||||||
:param generate_keys_if_needed: Generate keys or not?
|
:param generate_keys_if_needed: Generate keys or not?
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.pubkey = umbral_key.get_pub_key()
|
self.pubkey = umbral_key.get_pubkey()
|
||||||
self.privkey = umbral_key
|
self.privkey = umbral_key
|
||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
self.pubkey = umbral_key
|
self.pubkey = umbral_key
|
||||||
|
@ -30,7 +31,7 @@ class Keypair(object):
|
||||||
# They didn't pass anything we recognize as a valid key.
|
# They didn't pass anything we recognize as a valid key.
|
||||||
if generate_keys_if_needed:
|
if generate_keys_if_needed:
|
||||||
self.privkey = UmbralPrivateKey.gen_key()
|
self.privkey = UmbralPrivateKey.gen_key()
|
||||||
self.pubkey = self.priv_key.get_pub_key()
|
self.pubkey = self.privkey.get_pub_key()
|
||||||
else:
|
else:
|
||||||
raise ValueError("Either pass a valid key as umbral_key or, if you want to generate keys, set generate_keys_if_needed to True.")
|
raise ValueError("Either pass a valid key as umbral_key or, if you want to generate keys, set generate_keys_if_needed to True.")
|
||||||
|
|
||||||
|
@ -79,8 +80,9 @@ class SigningKeypair(Keypair):
|
||||||
"""
|
"""
|
||||||
Signs a hashed message and returns a signature.
|
Signs a hashed message and returns a signature.
|
||||||
|
|
||||||
:param msghash: The hashed message to sign
|
:param message: The message to sign
|
||||||
|
|
||||||
:return: Signature in bytes
|
:return: Signature in bytes
|
||||||
"""
|
"""
|
||||||
return API.ecdsa_sign(message, self.privkey)
|
signature_der_bytes = API.ecdsa_sign(message, self.privkey)
|
||||||
|
return Signature.from_bytes(signature_der_bytes, der_encoded=True)
|
||||||
|
|
|
@ -2,7 +2,7 @@ from kademlia.node import Node
|
||||||
from kademlia.protocol import KademliaProtocol
|
from kademlia.protocol import KademliaProtocol
|
||||||
from kademlia.utils import digest
|
from kademlia.utils import digest
|
||||||
from nkms.crypto.api import keccak_digest
|
from nkms.crypto.api import keccak_digest
|
||||||
from nkms.crypto.constants import HASH_DIGEST_LENGTH, PUBLIC_KEY_LENGTH
|
from nkms.crypto.constants import PUBLIC_KEY_LENGTH, KECCAK_DIGEST_LENGTH
|
||||||
from nkms.crypto.signature import Signature
|
from nkms.crypto.signature import Signature
|
||||||
from nkms.crypto.utils import BytestringSplitter
|
from nkms.crypto.utils import BytestringSplitter
|
||||||
from nkms.network.constants import NODE_HAS_NO_STORAGE
|
from nkms.network.constants import NODE_HAS_NO_STORAGE
|
||||||
|
@ -10,7 +10,7 @@ from nkms.network.node import NuCypherNode
|
||||||
from nkms.network.routing import NuCypherRoutingTable
|
from nkms.network.routing import NuCypherRoutingTable
|
||||||
from umbral.keys import UmbralPublicKey
|
from umbral.keys import UmbralPublicKey
|
||||||
|
|
||||||
dht_value_splitter = BytestringSplitter(Signature, (UmbralPublicKey, PUBLIC_KEY_LENGTH), (bytes, HASH_DIGEST_LENGTH))
|
dht_value_splitter = BytestringSplitter(Signature, (UmbralPublicKey, PUBLIC_KEY_LENGTH), (bytes, KECCAK_DIGEST_LENGTH))
|
||||||
|
|
||||||
|
|
||||||
class NuCypherHashProtocol(KademliaProtocol):
|
class NuCypherHashProtocol(KademliaProtocol):
|
||||||
|
|
|
@ -7,10 +7,12 @@ from npre.constants import UNKNOWN_KFRAG
|
||||||
|
|
||||||
from nkms.characters import Alice, Bob, Ursula
|
from nkms.characters import Alice, Bob, Ursula
|
||||||
from nkms.crypto.api import keccak_digest
|
from nkms.crypto.api import keccak_digest
|
||||||
from nkms.crypto.constants import NOT_SIGNED, HASH_DIGEST_LENGTH
|
from nkms.crypto.constants import NOT_SIGNED, KECCAK_DIGEST_LENGTH, \
|
||||||
|
PUBLIC_KEY_LENGTH
|
||||||
from nkms.crypto.powers import SigningPower
|
from nkms.crypto.powers import SigningPower
|
||||||
from nkms.crypto.signature import Signature
|
from nkms.crypto.signature import Signature
|
||||||
from nkms.crypto.utils import BytestringSplitter
|
from nkms.crypto.utils import BytestringSplitter
|
||||||
|
from umbral.keys import UmbralPublicKey
|
||||||
|
|
||||||
|
|
||||||
class Contract(object):
|
class Contract(object):
|
||||||
|
@ -19,7 +21,8 @@ class Contract(object):
|
||||||
"""
|
"""
|
||||||
_EXPECTED_LENGTH = 124
|
_EXPECTED_LENGTH = 124
|
||||||
|
|
||||||
def __init__(self, alice, hrac, expiration, deposit=None, ursula=None, kfrag=UNKNOWN_KFRAG, alices_signature=None):
|
def __init__(self, alice, hrac, expiration, deposit=None, ursula=None,
|
||||||
|
kfrag=UNKNOWN_KFRAG, alices_signature=None):
|
||||||
"""
|
"""
|
||||||
:param deposit: Funds which will pay for the timeframe of this Contract (not the actual re-encryptions);
|
:param deposit: Funds which will pay for the timeframe of this Contract (not the actual re-encryptions);
|
||||||
a portion will be locked for each Ursula that accepts.
|
a portion will be locked for each Ursula that accepts.
|
||||||
|
@ -40,12 +43,17 @@ class Contract(object):
|
||||||
self.ursula = ursula
|
self.ursula = ursula
|
||||||
|
|
||||||
def __bytes__(self):
|
def __bytes__(self):
|
||||||
return bytes(self.alice.seal) + bytes(self.hrac) + self.expiration.isoformat().encode() + bytes(self.deposit)
|
return bytes(self.alice.seal) + bytes(
|
||||||
|
self.hrac) + self.expiration.isoformat().encode() + bytes(
|
||||||
|
self.deposit)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_bytes(cls, contract_as_bytes):
|
def from_bytes(cls, contract_as_bytes):
|
||||||
contract_splitter = BytestringSplitter(PublicKey, (bytes, HASH_DIGEST_LENGTH), (bytes, 26))
|
contract_splitter = BytestringSplitter(
|
||||||
alice_pubkey_sig, hrac, expiration_bytes = contract_splitter(contract_as_bytes)
|
(UmbralPublicKey, PUBLIC_KEY_LENGTH), (bytes, KECCAK_DIGEST_LENGTH),
|
||||||
|
(bytes, 26))
|
||||||
|
alice_pubkey_sig, hrac, expiration_bytes, deposit_bytes = contract_splitter(
|
||||||
|
contract_as_bytes, return_remainder=True)
|
||||||
expiration = maya.parse(expiration_bytes.decode())
|
expiration = maya.parse(expiration_bytes.decode())
|
||||||
alice = Alice.from_public_keys((SigningPower, alice_pubkey_sig))
|
alice = Alice.from_public_keys((SigningPower, alice_pubkey_sig))
|
||||||
return cls(alice=alice, hrac=hrac, expiration=expiration)
|
return cls(alice=alice, hrac=hrac, expiration=expiration)
|
||||||
|
@ -59,7 +67,8 @@ class Contract(object):
|
||||||
"""
|
"""
|
||||||
Craft an offer to send to Ursula.
|
Craft an offer to send to Ursula.
|
||||||
"""
|
"""
|
||||||
return self.alice.encrypt_for(self.ursula, self.payload())[0] # We don't need the signature separately.
|
# We don't need the signature separately.
|
||||||
|
return self.alice.encrypt_for(self.ursula, self.payload())[0]
|
||||||
|
|
||||||
def payload(self):
|
def payload(self):
|
||||||
# TODO: Ship the expiration again? Or some other way of alerting Ursula to recall her previous dialogue regarding this Contract. Update: We'll probably have her store the Contract by hrac. See #127.
|
# TODO: Ship the expiration again? Or some other way of alerting Ursula to recall her previous dialogue regarding this Contract. Update: We'll probably have her store the Contract by hrac. See #127.
|
||||||
|
@ -82,10 +91,9 @@ class Policy(object):
|
||||||
Once Alice has secured agreement with n Ursulas to enact a Policy, she sends each a KFrag,
|
Once Alice has secured agreement with n Ursulas to enact a Policy, she sends each a KFrag,
|
||||||
and generates a TreasureMap for the Policy, recording which Ursulas got a KFrag.
|
and generates a TreasureMap for the Policy, recording which Ursulas got a KFrag.
|
||||||
"""
|
"""
|
||||||
_ursula = None
|
|
||||||
hashed_part = None
|
|
||||||
|
|
||||||
def __init__(self, alice, bob=None, kfrags=(UNKNOWN_KFRAG,), pfrag=None, uri=None, alices_signature=NOT_SIGNED):
|
def __init__(self, alice, bob=None, kfrags=(UNKNOWN_KFRAG,), uri=None,
|
||||||
|
alices_signature=NOT_SIGNED):
|
||||||
"""
|
"""
|
||||||
:param kfrags: A list of KFrags to distribute per this Policy.
|
:param kfrags: A list of KFrags to distribute per this Policy.
|
||||||
:param pfrag: The input ciphertext which Bob will give to Ursula to re-encrypt.
|
:param pfrag: The input ciphertext which Bob will give to Ursula to re-encrypt.
|
||||||
|
@ -94,7 +102,6 @@ class Policy(object):
|
||||||
self.alice = alice
|
self.alice = alice
|
||||||
self.bob = bob
|
self.bob = bob
|
||||||
self.kfrags = kfrags
|
self.kfrags = kfrags
|
||||||
self.pfrag = pfrag
|
|
||||||
self.uri = uri
|
self.uri = uri
|
||||||
self.treasure_map = TreasureMap()
|
self.treasure_map = TreasureMap()
|
||||||
self._accepted_contracts = {}
|
self._accepted_contracts = {}
|
||||||
|
@ -125,13 +132,12 @@ class Policy(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_alice(kfrags,
|
def from_alice(kfrags,
|
||||||
pfrag,
|
|
||||||
alice,
|
alice,
|
||||||
bob,
|
bob,
|
||||||
uri,
|
uri,
|
||||||
):
|
):
|
||||||
# TODO: What happened to Alice's signature - don't we include it here?
|
# TODO: What happened to Alice's signature - don't we include it here?
|
||||||
policy = Policy(alice, bob, kfrags, pfrag, uri)
|
policy = Policy(alice, bob, kfrags, uri)
|
||||||
|
|
||||||
return policy
|
return policy
|
||||||
|
|
||||||
|
@ -143,7 +149,6 @@ class Policy(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def hrac_for(alice, bob, uri):
|
def hrac_for(alice, bob, uri):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The "hashed resource authentication code".
|
The "hashed resource authentication code".
|
||||||
|
|
||||||
|
@ -172,9 +177,10 @@ class Policy(object):
|
||||||
return self.hash(bytes(self.alice.seal) + self.hrac())
|
return self.hash(bytes(self.alice.seal) + self.hrac())
|
||||||
|
|
||||||
def publish_treasure_map(self):
|
def publish_treasure_map(self):
|
||||||
encrypted_treasure_map, signature_for_bob = self.alice.encrypt_for(self.bob,
|
encrypted_treasure_map, signature_for_bob = self.alice.encrypt_for(
|
||||||
|
self.bob,
|
||||||
self.treasure_map.packed_payload())
|
self.treasure_map.packed_payload())
|
||||||
signature_for_ursula = self.alice.seal(self.hrac()) # TODO: Great use-case for Ciphertext class
|
signature_for_ursula = self.alice.seal(self.hrac())
|
||||||
|
|
||||||
# In order to know this is safe to propagate, Ursula needs to see a signature, our public key,
|
# In order to know this is safe to propagate, Ursula needs to see a signature, our public key,
|
||||||
# and, reasons explained in treasure_map_dht_key above, the uri_hash.
|
# and, reasons explained in treasure_map_dht_key above, the uri_hash.
|
||||||
|
@ -188,7 +194,6 @@ class Policy(object):
|
||||||
return encrypted_treasure_map, dht_value, signature_for_bob, signature_for_ursula
|
return encrypted_treasure_map, dht_value, signature_for_bob, signature_for_ursula
|
||||||
|
|
||||||
def enact(self, networky_stuff):
|
def enact(self, networky_stuff):
|
||||||
|
|
||||||
for contract in self._accepted_contracts.values():
|
for contract in self._accepted_contracts.values():
|
||||||
policy_payload = contract.encrypt_payload_for_ursula()
|
policy_payload = contract.encrypt_payload_for_ursula()
|
||||||
full_payload = self.alice.seal + msgpack.dumps(policy_payload)
|
full_payload = self.alice.seal + msgpack.dumps(policy_payload)
|
||||||
|
@ -200,9 +205,11 @@ class Policy(object):
|
||||||
self.treasure_map.add_ursula(contract.ursula)
|
self.treasure_map.add_ursula(contract.ursula)
|
||||||
|
|
||||||
def draw_up_contract(self, deposit, expiration):
|
def draw_up_contract(self, deposit, expiration):
|
||||||
return Contract(self.alice, self.hrac(), expiration=expiration, deposit=deposit)
|
return Contract(self.alice, self.hrac(), expiration=expiration,
|
||||||
|
deposit=deposit)
|
||||||
|
|
||||||
def find_ursulas(self, networky_stuff, deposit, expiration, num_ursulas=None):
|
def find_ursulas(self, networky_stuff, deposit, expiration,
|
||||||
|
num_ursulas=None):
|
||||||
# TODO: This is a number mismatch - we need not one contract, but n contracts.
|
# TODO: This is a number mismatch - we need not one contract, but n contracts.
|
||||||
"""
|
"""
|
||||||
:param networky_stuff: A compliant interface (maybe a Client instance) to be used to engage the DHT swarm.
|
:param networky_stuff: A compliant interface (maybe a Client instance) to be used to engage the DHT swarm.
|
||||||
|
@ -258,7 +265,8 @@ class TreasureMap(object):
|
||||||
|
|
||||||
|
|
||||||
class WorkOrder(object):
|
class WorkOrder(object):
|
||||||
def __init__(self, bob, kfrag_hrac, pfrags, receipt_bytes, receipt_signature, ursula_id=None):
|
def __init__(self, bob, kfrag_hrac, pfrags, receipt_bytes,
|
||||||
|
receipt_signature, ursula_id=None):
|
||||||
self.bob = bob
|
self.bob = bob
|
||||||
self.kfrag_hrac = kfrag_hrac
|
self.kfrag_hrac = kfrag_hrac
|
||||||
self.pfrags = pfrags
|
self.pfrags = pfrags
|
||||||
|
@ -267,12 +275,14 @@ class WorkOrder(object):
|
||||||
self.ursula_id = ursula_id # TODO: We may still need a more elegant system for ID'ing Ursula. See #136.
|
self.ursula_id = ursula_id # TODO: We may still need a more elegant system for ID'ing Ursula. See #136.
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "WorkOrder (pfrags: {}) {} for {}".format([binascii.hexlify(bytes(p))[:6] for p in self.pfrags],
|
return "WorkOrder (pfrags: {}) {} for {}".format(
|
||||||
|
[binascii.hexlify(bytes(p))[:6] for p in self.pfrags],
|
||||||
binascii.hexlify(self.receipt_bytes)[:6],
|
binascii.hexlify(self.receipt_bytes)[:6],
|
||||||
binascii.hexlify(self.ursula_id)[:6])
|
binascii.hexlify(self.ursula_id)[:6])
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return (self.receipt_bytes, self.receipt_signature) == (other.receipt_bytes, other.receipt_signature)
|
return (self.receipt_bytes, self.receipt_signature) == (
|
||||||
|
other.receipt_bytes, other.receipt_signature)
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return len(self.pfrags)
|
return len(self.pfrags)
|
||||||
|
|
|
@ -2,11 +2,14 @@ import datetime
|
||||||
|
|
||||||
from nkms.characters import Ursula
|
from nkms.characters import Ursula
|
||||||
from nkms.crypto.api import keccak_digest
|
from nkms.crypto.api import keccak_digest
|
||||||
|
from nkms.crypto.constants import PUBLIC_KEY_LENGTH
|
||||||
from nkms.crypto.powers import SigningPower, EncryptingPower
|
from nkms.crypto.powers import SigningPower, EncryptingPower
|
||||||
from nkms.crypto.utils import BytestringSplitter
|
from nkms.crypto.utils import BytestringSplitter
|
||||||
from tests.utilities import MockNetworkyStuff
|
from tests.utilities import MockNetworkyStuff
|
||||||
from apistar.test import TestClient
|
from apistar.test import TestClient
|
||||||
|
|
||||||
|
from umbral.keys import UmbralPublicKey
|
||||||
|
|
||||||
|
|
||||||
def test_grant(alice, bob, ursulas):
|
def test_grant(alice, bob, ursulas):
|
||||||
networky_stuff = MockNetworkyStuff(ursulas)
|
networky_stuff = MockNetworkyStuff(ursulas)
|
||||||
|
@ -30,6 +33,7 @@ def test_grant(alice, bob, ursulas):
|
||||||
def test_alice_can_get_ursulas_keys_via_rest(alice, ursulas):
|
def test_alice_can_get_ursulas_keys_via_rest(alice, ursulas):
|
||||||
mock_client = TestClient(ursulas[0].rest_app)
|
mock_client = TestClient(ursulas[0].rest_app)
|
||||||
response = mock_client.get('http://localhost/public_keys')
|
response = mock_client.get('http://localhost/public_keys')
|
||||||
signing_key_bytes, encrypting_key_bytes = BytestringSplitter(PublicKey)(response.content, return_remainder=True)
|
splitter = BytestringSplitter((UmbralPublicKey, PUBLIC_KEY_LENGTH))
|
||||||
|
signing_key_bytes, encrypting_key_bytes = splitter(response.content, return_remainder=True)
|
||||||
stranger_ursula_from_public_keys = Ursula.from_public_keys((SigningPower, signing_key_bytes), (EncryptingPower, encrypting_key_bytes))
|
stranger_ursula_from_public_keys = Ursula.from_public_keys((SigningPower, signing_key_bytes), (EncryptingPower, encrypting_key_bytes))
|
||||||
assert stranger_ursula_from_public_keys == ursulas[0]
|
assert stranger_ursula_from_public_keys == ursulas[0]
|
||||||
|
|
Loading…
Reference in New Issue