Merge pull request #2832 from fjarri/rust-core

Rust core
pull/2747/head^2
KPrasch 2022-01-14 12:29:53 -08:00 committed by GitHub
commit 8885cc94bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
84 changed files with 423 additions and 2018 deletions

View File

@ -483,7 +483,7 @@ commands:
steps:
- chown_user_paths
- save_cache:
key: pip-v1-{{ .Branch }}-{{ checksum "Pipfile.lock" }}
key: pip-v3-{{ .Branch }}-{{ checksum "Pipfile.lock" }}
paths:
- "~/.local/bin"
- "~/.local/lib/python3.7/site-packages"
@ -496,7 +496,7 @@ commands:
description: "Restore cached python installation files"
steps:
- restore_cache: # ensure this step occurs *before* installing dependencies
key: pip-v1-{{ .Branch }}-{{ checksum "Pipfile.lock" }}
key: pip-v3-{{ .Branch }}-{{ checksum "Pipfile.lock" }}
- restore_cache:
key: solc-v2-{{ checksum "nucypher/blockchain/eth/sol/__conf__.py" }}

View File

@ -1,8 +1,4 @@
FROM python:3.8.7-slim
# Update
RUN apt update -y && apt upgrade -y
RUN apt install patch gcc libffi-dev wget -y
FROM kprasch/rust-python:3.8.9
WORKDIR /code
COPY . /code

View File

@ -2,7 +2,7 @@ FROM python:3.8.7-slim
# Update
RUN apt update -y && apt upgrade -y
RUN apt install patch gcc libffi-dev wget -y
RUN apt install patch gcc libffi-dev wget git -y
WORKDIR /code
COPY . /code

View File

@ -24,13 +24,13 @@ import msgpack
import shutil
import sys
from nucypher.core import MessageKit, EncryptedTreasureMap
from nucypher_core import MessageKit, EncryptedTreasureMap
from nucypher_core.umbral import PublicKey
from nucypher.characters.lawful import Bob, Enrico, Ursula
from nucypher.config.constants import TEMPORARY_DOMAIN
from nucypher.crypto.keypairs import DecryptingKeypair, SigningKeypair
from nucypher.crypto.powers import DecryptingPower, SigningPower
from nucypher.crypto.umbral_adapter import PublicKey
from nucypher.network.middleware import RestMiddleware
from nucypher.utilities.logging import GlobalLoggerSettings

View File

@ -18,7 +18,7 @@
import json
from pathlib import Path
from nucypher.crypto.umbral_adapter import SecretKey, PublicKey
from nucypher_core.umbral import SecretKey, PublicKey
DOCTOR_PUBLIC_JSON = Path('doctor.public.json')
DOCTOR_PRIVATE_JSON = Path('doctor.private.json')

View File

@ -0,0 +1 @@
Switched to Rust implementation of the protocol types (``nucypher-core``). Correspondingly, API has been simplified, and type requirements have been made more strict.

View File

@ -22,12 +22,11 @@ from collections import deque
from collections.abc import KeysView
from typing import Optional, Dict, Iterable, List, Tuple, NamedTuple, Union, Any
import binascii
import itertools
import maya
from eth_typing import ChecksumAddress
from ..crypto.utils import keccak_digest
from nucypher_core import FleetStateChecksum, NodeMetadata
from nucypher.utilities.logging import Logger
from .nicknames import Nickname
@ -40,7 +39,7 @@ class ArchivedFleetState(NamedTuple):
population: int
def to_json(self):
return dict(checksum=self.checksum,
return dict(checksum=bytes(self.checksum).hex(),
nickname=self.nickname.to_json(),
timestamp=self.timestamp.rfc2822(),
population=self.population)
@ -74,22 +73,21 @@ class FleetState:
@classmethod
def new(cls, this_node: Optional['Ursula'] = None) -> 'FleetState':
this_node_ref = weakref.ref(this_node) if this_node is not None else None
# Using empty checksum so that JSON library is not confused.
# Plus, we do need some checksum anyway. It's a legitimate state after all.
return cls(checksum=keccak_digest(b"").hex(),
nodes={},
this_node_ref = weakref.ref(this_node) if this_node else None
# `this_node` might not have its metadata available yet.
this_node_metadata = None
return cls(nodes={},
this_node_ref=this_node_ref,
this_node_metadata=None)
this_node_metadata=this_node_metadata)
def __init__(self,
checksum: str,
nodes: Dict[ChecksumAddress, 'Ursula'],
this_node_ref: Optional[weakref.ReferenceType],
this_node_metadata: Optional[bytes]):
self.checksum = checksum
self.nickname = Nickname.from_seed(checksum, length=1)
this_node_metadata: Optional[NodeMetadata]):
self.checksum = FleetStateChecksum(this_node=this_node_metadata,
other_nodes=[node.metadata() for node in nodes.values()])
self.nickname = Nickname.from_seed(bytes(self.checksum), length=1)
self._nodes = nodes
self.timestamp = maya.now()
self._this_node_ref = this_node_ref
@ -132,7 +130,7 @@ class FleetState:
if self._this_node_ref is not None and not skip_this_node:
this_node = self._this_node_ref()
this_node_metadata = bytes(this_node.metadata())
this_node_metadata = this_node.metadata()
this_node_updated = self._this_node_metadata != this_node_metadata
this_node_list = [this_node]
else:
@ -153,17 +151,10 @@ class FleetState:
nodes[checksum_address] = new_node
for checksum_address in diff.nodes_removed:
del nodes[checksum_address]
all_nodes_sorted = sorted(itertools.chain(this_node_list, nodes.values()),
key=lambda node: node.checksum_address)
joined_metadata = b"".join(bytes(node.metadata()) for node in all_nodes_sorted)
checksum = keccak_digest(joined_metadata).hex()
else:
nodes = self._nodes
checksum = self.checksum
new_state = FleetState(checksum=checksum,
nodes=nodes,
new_state = FleetState(nodes=nodes,
this_node_ref=self._this_node_ref,
this_node_metadata=this_node_metadata)
@ -217,7 +208,7 @@ class FleetState:
def __str__(self):
return '{checksum}{nickname}{icon} '.format(icon=self.nickname.icon,
nickname=self.nickname,
checksum=self.checksum[:7])
checksum=bytes(self.checksum).hex()[:7])
def __repr__(self):
return f"FleetState({self.checksum}, {self._nodes}, {self._this_node_ref}, {self._this_node_metadata})"
@ -362,14 +353,14 @@ class FleetSensor:
def record_remote_fleet_state(self,
checksum_address: ChecksumAddress,
state_checksum: str,
state_checksum: FleetStateChecksum,
timestamp: maya.MayaDT,
population: int):
if checksum_address not in self._current_state:
raise KeyError(f"A node {checksum_address} is not present in the current fleet state")
nickname = Nickname.from_seed(state_checksum, length=1)
nickname = Nickname.from_seed(bytes(state_checksum), length=1)
state = ArchivedFleetState(checksum=state_checksum,
nickname=nickname,
timestamp=timestamp,

View File

@ -34,7 +34,7 @@ from web3 import Web3
from web3.exceptions import ValidationError
from web3.types import TxReceipt
from nucypher.core import HRAC
from nucypher_core import HRAC
from nucypher.acumen.nicknames import Nickname
from nucypher.blockchain.economics import (

View File

@ -28,9 +28,10 @@ from constant_sorrow.constants import (
STRANGER
)
from eth_keys import KeyAPI as EthKeyAPI
from eth_utils import to_canonical_address, to_checksum_address
from eth_utils import to_canonical_address
from nucypher.core import MessageKit
from nucypher_core import MessageKit
from nucypher_core.umbral import PublicKey
from nucypher.acumen.nicknames import Nickname
from nucypher.blockchain.eth.registry import BaseContractRegistry, InMemoryContractRegistry
@ -49,7 +50,6 @@ from nucypher.crypto.signing import (
SignatureStamp,
StrangerStamp,
)
from nucypher.crypto.umbral_adapter import PublicKey
from nucypher.network.middleware import RestMiddleware
from nucypher.network.nodes import Learner
@ -276,14 +276,10 @@ class Character(Learner):
return self._stamp
@property
def canonical_public_address(self):
def canonical_address(self):
# TODO: This is wasteful. #1995
return to_canonical_address(self.checksum_address)
@canonical_public_address.setter
def canonical_public_address(self, address_bytes):
self._checksum_address = to_checksum_address(address_bytes)
@classmethod
def from_config(cls, config, **overrides) -> 'Character':
return config.produce(**overrides)
@ -364,8 +360,8 @@ class Character(Learner):
# TODO: who even uses this method except for tests?
message_kit = MessageKit.author(policy_encrypting_key=recipient.public_keys(DecryptingPower),
plaintext=plaintext)
message_kit = MessageKit(policy_encrypting_key=recipient.public_keys(DecryptingPower),
plaintext=plaintext)
return message_kit
def public_keys(self, power_up_class: ClassVar):

View File

@ -19,13 +19,13 @@ from typing import Union, List
import maya
from nucypher.core import MessageKit, HRAC, EncryptedTreasureMap
from nucypher_core import MessageKit, HRAC, EncryptedTreasureMap
from nucypher_core.umbral import PublicKey
from nucypher.characters.base import Character
from nucypher.characters.control.specifications import alice, bob, enrico
from nucypher.control.interfaces import attach_schema, ControlInterface
from nucypher.crypto.powers import DecryptingPower, SigningPower
from nucypher.crypto.umbral_adapter import PublicKey
from nucypher.network.middleware import RestMiddleware
@ -105,7 +105,7 @@ class AliceInterface(CharacterPublicInterface):
def revoke(self, label: bytes, bob_verifying_key: PublicKey) -> dict:
# TODO: Move deeper into characters
policy_hrac = HRAC.derive(self.implementer.stamp.as_umbral_pubkey(), bob_verifying_key, label)
policy_hrac = HRAC(self.implementer.stamp.as_umbral_pubkey(), bob_verifying_key, label)
policy = self.implementer.active_policies[policy_hrac]
receipt, failed_revocations = self.implementer.revoke(policy)

View File

@ -23,4 +23,3 @@ from nucypher.characters.control.specifications.fields.label import *
from nucypher.characters.control.specifications.fields.cleartext import *
from nucypher.characters.control.specifications.fields.misc import *
from nucypher.characters.control.specifications.fields.file import *
from nucypher.characters.control.specifications.fields.signature import *

View File

@ -17,9 +17,10 @@
from marshmallow import fields
from nucypher_core.umbral import PublicKey
from nucypher.control.specifications.exceptions import InvalidInputData, InvalidNativeDataTypes
from nucypher.control.specifications.fields.base import BaseField
from nucypher.crypto.umbral_adapter import PublicKey
class Key(BaseField, fields.Field):

View File

@ -15,7 +15,7 @@
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
from nucypher.core import MessageKit as MessageKitClass
from nucypher_core import MessageKit as MessageKitClass
from nucypher.control.specifications.exceptions import InvalidInputData
from nucypher.control.specifications.fields.base import Base64BytesRepresentation

View File

@ -1,44 +0,0 @@
"""
This file is part of nucypher.
nucypher is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
nucypher is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
from base64 import b64decode, b64encode
from marshmallow import fields
from nucypher.control.specifications.exceptions import InvalidInputData, InvalidNativeDataTypes
from nucypher.control.specifications.fields.base import BaseField
from nucypher.crypto.umbral_adapter import Signature
class UmbralSignature(BaseField, fields.Field):
def _serialize(self, value: Signature, attr, obj, **kwargs):
return b64encode(bytes(value)).decode()
def _deserialize(self, value, attr, data, **kwargs):
if isinstance(value, bytes):
return value
try:
return Signature.from_bytes(b64decode(value))
except InvalidNativeDataTypes as e:
raise InvalidInputData(f"Could not parse {self.name}: {e}")
def _validate(self, value):
try:
Signature.from_bytes(value)
except InvalidNativeDataTypes as e:
raise InvalidInputData(f"Could not parse {self.name}: {e}")

View File

@ -15,7 +15,7 @@
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
from nucypher.core import EncryptedTreasureMap as EncryptedTreasureMapClass, TreasureMap as TreasureMapClass
from nucypher_core import EncryptedTreasureMap as EncryptedTreasureMapClass, TreasureMap as TreasureMapClass
from nucypher.control.specifications.exceptions import InvalidInputData
from nucypher.control.specifications.fields.base import Base64BytesRepresentation

View File

@ -21,7 +21,6 @@ from http import HTTPStatus
import json
import time
from base64 import b64encode
from datetime import datetime
from json.decoder import JSONDecodeError
from pathlib import Path
from queue import Queue
@ -42,19 +41,24 @@ from eth_typing.evm import ChecksumAddress
from flask import Response, request
from twisted.internet import reactor, stdio
from twisted.internet.defer import Deferred
from twisted.internet.task import LoopingCall
from twisted.logger import Logger
from web3.types import TxReceipt
from nucypher.core import (
from nucypher_core import (
MessageKit,
AuthorizedKeyFrag,
EncryptedKeyFrag,
TreasureMap,
EncryptedTreasureMap,
ReencryptionResponse,
NodeMetadata
NodeMetadata,
NodeMetadataPayload,
HRAC,
)
from nucypher_core.umbral import (
PublicKey,
reencrypt,
VerifiedKeyFrag,
)
import nucypher
from nucypher.acumen.nicknames import Nickname
@ -81,12 +85,6 @@ from nucypher.crypto.powers import (
TransactingPower,
TLSHostingPower,
)
from nucypher.crypto.umbral_adapter import (
PublicKey,
reencrypt,
VerifiedKeyFrag,
)
from nucypher.datastore.datastore import DatastoreTransactionError, RecordNotFound
from nucypher.network.exceptions import NodeSeemsToBeDown
from nucypher.network.middleware import RestMiddleware
from nucypher.network.nodes import NodeSprout, TEACHER_NODES, Teacher
@ -358,7 +356,7 @@ class Alice(Character, BlockchainPolicyAuthor):
return policy_pubkey
def revoke(self,
policy: 'Policy',
policy: Policy,
onchain: bool = True, # forced to False for federated mode
offchain: bool = True
) -> Tuple[TxReceipt, Dict[ChecksumAddress, Tuple['Revocation', Exception]]]:
@ -413,7 +411,7 @@ class Alice(Character, BlockchainPolicyAuthor):
delegating_power = self._crypto_power.power_ups(DelegatingPower)
decrypting_power = delegating_power.get_decrypting_power_from_label(label)
cleartext = decrypting_power.decrypt(message_kit)
cleartext = decrypting_power.decrypt_message_kit(message_kit)
# TODO: why does it return a list of cleartexts but takes a single message kit?
# Shouldn't it be able to take a list of them too?
@ -531,10 +529,8 @@ class Bob(Character):
publisher_verifying_key: PublicKey
) -> TreasureMap:
decrypting_power = self._crypto_power.power_ups(DecryptingPower)
auth_tmap = decrypting_power.decrypt(encrypted_treasure_map)
treasure_map = auth_tmap.verify(recipient_key=decrypting_power.keypair.pubkey,
publisher_verifying_key=publisher_verifying_key)
return treasure_map
return decrypting_power.decrypt_treasure_map(encrypted_treasure_map,
publisher_verifying_key=publisher_verifying_key)
def retrieve(
self,
@ -561,7 +557,7 @@ class Bob(Character):
publisher_verifying_key = alice_verifying_key
# A small optimization to avoid multiple treasure map decryptions.
map_hash = hash(encrypted_treasure_map)
map_hash = hash(bytes(encrypted_treasure_map))
if map_hash in self._treasure_maps:
treasure_map = self._treasure_maps[map_hash]
else:
@ -613,7 +609,7 @@ class Bob(Character):
cleartexts = []
decrypting_power = self._crypto_power.power_ups(DecryptingPower)
for message_kit in message_kits:
cleartext = decrypting_power.decrypt(message_kit)
cleartext = decrypting_power.decrypt_message_kit(message_kit)
cleartexts.append(cleartext)
return cleartexts
@ -766,8 +762,7 @@ class Ursula(Teacher, Character, Worker):
self.rest_server = self._make_local_server(host=rest_host,
port=rest_port,
db_filepath=db_filepath,
domain=domain)
db_filepath=db_filepath)
# Self-signed TLS certificate of self for Teacher.__init__
certificate_filepath = self._crypto_power.power_ups(TLSHostingPower).keypair.certificate_filepath
@ -809,11 +804,10 @@ class Ursula(Teacher, Character, Worker):
self._crypto_power.consume_power_up(tls_hosting_power) # Consume!
return tls_hosting_power
def _make_local_server(self, host, port, domain, db_filepath) -> ProxyRESTServer:
def _make_local_server(self, host, port, db_filepath) -> ProxyRESTServer:
rest_app, datastore = make_rest_app(
this_node=self,
db_filepath=db_filepath,
domain=domain,
)
rest_server = ProxyRESTServer(rest_host=host,
rest_port=port,
@ -991,17 +985,18 @@ class Ursula(Teacher, Character, Worker):
decentralized_identity_evidence = None
else:
decentralized_identity_evidence = self.decentralized_identity_evidence
return NodeMetadata.author(signer=self.stamp.as_umbral_signer(),
public_address=self.canonical_public_address,
domain=self.domain,
timestamp_epoch=timestamp.epoch,
decentralized_identity_evidence=decentralized_identity_evidence,
verifying_key=self.public_keys(SigningPower),
encrypting_key=self.public_keys(DecryptingPower),
certificate_bytes=self.certificate.public_bytes(Encoding.PEM),
host=self.rest_interface.host,
port=self.rest_interface.port,
)
payload = NodeMetadataPayload(canonical_address=self.canonical_address,
domain=self.domain,
timestamp_epoch=timestamp.epoch,
decentralized_identity_evidence=decentralized_identity_evidence,
verifying_key=self.public_keys(SigningPower),
encrypting_key=self.public_keys(DecryptingPower),
certificate_bytes=self.certificate.public_bytes(Encoding.PEM),
host=self.rest_interface.host,
port=self.rest_interface.port,
)
return NodeMetadata(signer=self.stamp.as_umbral_signer(),
payload=payload)
def metadata(self):
if not self._metadata:
@ -1010,7 +1005,7 @@ class Ursula(Teacher, Character, Worker):
@property
def timestamp(self):
return maya.MayaDT(self.metadata().timestamp_epoch)
return maya.MayaDT(self.metadata().payload.timestamp_epoch)
#
# Alternate Constructors
@ -1187,9 +1182,9 @@ class Ursula(Teacher, Character, Worker):
# Re-Encryption
#
def _decrypt_kfrag(self, encrypted_kfrag: EncryptedKeyFrag) -> AuthorizedKeyFrag:
def _decrypt_kfrag(self, encrypted_kfrag: EncryptedKeyFrag, hrac: HRAC, publisher_verifying_key: PublicKey) -> VerifiedKeyFrag:
decrypting_power = self._crypto_power.power_ups(DecryptingPower)
return decrypting_power.decrypt(encrypted_kfrag)
return decrypting_power.decrypt_kfrag(encrypted_kfrag, hrac, publisher_verifying_key)
def _reencrypt(self, kfrag: VerifiedKeyFrag, capsules) -> ReencryptionResponse:
cfrags = []
@ -1198,9 +1193,9 @@ class Ursula(Teacher, Character, Worker):
cfrags.append(cfrag)
self.log.info(f"Re-encrypted capsule {capsule} -> made {cfrag}.")
return ReencryptionResponse.construct_by_ursula(signer=self.stamp.as_umbral_signer(),
capsules=capsules,
cfrags=cfrags)
return ReencryptionResponse(signer=self.stamp.as_umbral_signer(),
capsules=capsules,
vcfrags=cfrags)
def status_info(self, omit_known_nodes: bool = False) -> 'LocalUrsulaStatus':
@ -1310,8 +1305,8 @@ class Enrico(Character):
def encrypt_message(self, plaintext: bytes) -> MessageKit:
# TODO: #2107 Rename to "encrypt"
message_kit = MessageKit.author(policy_encrypting_key=self.policy_pubkey,
plaintext=plaintext)
message_kit = MessageKit(policy_encrypting_key=self.policy_pubkey,
plaintext=plaintext)
return message_kit
@classmethod

View File

@ -22,7 +22,7 @@ from unittest.mock import patch
from eth_tester.exceptions import ValidationError
from nucypher.core import NodeMetadata
from nucypher_core import NodeMetadata
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.characters.lawful import Alice, Ursula
@ -83,26 +83,32 @@ class Vladimir(Ursula):
)
# Let's use the target's public info, and try to make some changes.
# We are going to mutate it, so make a copy (it is cached in the Ursula).
metadata = NodeMetadata.from_bytes(bytes(target_ursula.metadata()))
metadata_payload = metadata._metadata_payload
metadata = target_ursula.metadata()
metadata_bytes = bytes(metadata)
# Since it is an object from a Rust extension, we cannot directly modify it,
# so we have to replace stuff in the byte representation and then deserialize.
# We are replacinig objects with constant size,
# so it should work regardless of the binary format.
# Our basic replacement. We want to impersonate the target Ursula.
metadata_payload = metadata_payload._replace(public_address=vladimir.canonical_public_address)
metadata_bytes = metadata_bytes.replace(metadata.payload.canonical_address,
vladimir.canonical_address)
# Use our own verifying key
if substitute_verifying_key:
metadata_payload = metadata_payload._replace(
verifying_key=vladimir.stamp.as_umbral_pubkey())
metadata_bytes = metadata_bytes.replace(bytes(metadata.payload.verifying_key),
bytes(vladimir.stamp.as_umbral_pubkey()))
fake_metadata = NodeMetadata.from_bytes(metadata_bytes)
# Re-generate metadata signature using our signing key
if sign_metadata:
signature = vladimir.stamp(bytes(metadata_payload))
else:
signature = metadata.signature
fake_metadata = NodeMetadata(vladimir.stamp.as_umbral_signer(), fake_metadata.payload)
# Put metadata back
vladimir._metadata = NodeMetadata(signature, metadata_payload)
vladimir._metadata = fake_metadata
return vladimir

View File

@ -18,13 +18,14 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
import click
from nucypher_core.umbral import PublicKey
from nucypher.characters.control.interfaces import EnricoInterface
from nucypher.characters.lawful import Enrico
from nucypher.cli.utils import setup_emitter
from nucypher.cli.config import group_general_config
from nucypher.cli.options import option_dry_run, option_policy_encrypting_key
from nucypher.cli.types import NETWORK_PORT
from nucypher.crypto.umbral_adapter import PublicKey
@click.group()

View File

@ -22,11 +22,12 @@ from decimal import Decimal, DecimalException
from eth_utils import to_checksum_address
from ipaddress import ip_address
from nucypher_core.umbral import PublicKey
from nucypher.blockchain.economics import StandardTokenEconomics
from nucypher.blockchain.eth.interfaces import BlockchainInterface
from nucypher.blockchain.eth.networks import NetworksInventory
from nucypher.blockchain.eth.token import NU
from nucypher.crypto.umbral_adapter import PublicKey
from nucypher.utilities.networking import validate_worker_ip, InvalidWorkerIP

File diff suppressed because it is too large Load Diff

View File

@ -25,17 +25,24 @@ from cryptography.hazmat.primitives.asymmetric import ec
from hendrix.deploy.tls import HendrixDeployTLS
from hendrix.facilities.services import ExistingKeyTLSContextFactory
from nucypher.core import MessageKit, EncryptedTreasureMap, EncryptedKeyFrag
from nucypher.config.constants import MAX_UPLOAD_CONTENT_LENGTH
from nucypher.crypto.signing import SignatureStamp, StrangerStamp
from nucypher.crypto.tls import _read_tls_certificate, _TLS_CURVE, generate_self_signed_certificate
from nucypher.crypto.umbral_adapter import (
from nucypher_core import (
MessageKit,
EncryptedTreasureMap,
EncryptedKeyFrag,
HRAC,
TreasureMap,
)
from nucypher_core.umbral import (
SecretKey,
PublicKey,
Signature,
Signer,
VerifiedKeyFrag,
)
from nucypher.config.constants import MAX_UPLOAD_CONTENT_LENGTH
from nucypher.crypto.signing import SignatureStamp, StrangerStamp
from nucypher.crypto.tls import _read_tls_certificate, _TLS_CURVE, generate_self_signed_certificate
from nucypher.network.resources import get_static_resources
@ -90,7 +97,7 @@ class DecryptingKeypair(Keypair):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
def decrypt(self, message_kit: Union[MessageKit, EncryptedKeyFrag, EncryptedTreasureMap]) -> bytes:
def decrypt_message_kit(self, message_kit: MessageKit) -> bytes:
"""
Decrypt data encrypted with Umbral.
@ -101,6 +108,12 @@ class DecryptingKeypair(Keypair):
except ValueError as e:
raise self.DecryptionFailed() from e
def decrypt_kfrag(self, ekfrag: EncryptedKeyFrag, hrac: HRAC, publisher_verifying_key: PublicKey) -> VerifiedKeyFrag:
return ekfrag.decrypt(self._privkey, hrac, publisher_verifying_key)
def decrypt_treasure_map(self, etmap: EncryptedTreasureMap, publisher_verifying_key: PublicKey) -> TreasureMap:
return etmap.decrypt(self._privkey, publisher_verifying_key)
class SigningKeypair(Keypair):
"""

View File

@ -31,6 +31,8 @@ import click
from constant_sorrow.constants import KEYSTORE_LOCKED
from mnemonic.mnemonic import Mnemonic
from nucypher_core.umbral import SecretKey, SecretKeyFactory
from nucypher.config.constants import DEFAULT_CONFIG_ROOT
from nucypher.control.emitters import StdoutEmitter
from nucypher.crypto.keypairs import HostingKeypair
@ -50,7 +52,6 @@ from nucypher.crypto.powers import (
TLSHostingPower,
)
from nucypher.crypto.tls import generate_self_signed_certificate
from nucypher.crypto.umbral_adapter import SecretKey, SecretKeyFactory
# HKDF
__INFO_BASE = b'NuCypher/'

View File

@ -22,11 +22,12 @@ from typing import List, Optional, Tuple
from eth_typing.evm import ChecksumAddress
from hexbytes import HexBytes
from nucypher_core.umbral import generate_kfrags, SecretKeyFactory, SecretKey, PublicKey
from nucypher.blockchain.eth.decorators import validate_checksum_address
from nucypher.blockchain.eth.signers.base import Signer
from nucypher.crypto import keypairs
from nucypher.crypto.keypairs import DecryptingKeypair, SigningKeypair, HostingKeypair
from nucypher.crypto.umbral_adapter import generate_kfrags, SecretKeyFactory, SecretKey, PublicKey
class PowerUpError(TypeError):
@ -234,7 +235,7 @@ class SigningPower(KeyPairBasedPower):
class DecryptingPower(KeyPairBasedPower):
_keypair_class = DecryptingKeypair
not_found_error = NoDecryptingPower
provides = ("decrypt",)
provides = ("decrypt_message_kit", "decrypt_kfrag", "decrypt_treasure_map")
class DerivedKeyBasedPower(CryptoPowerUp):

View File

@ -15,7 +15,7 @@ You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
from nucypher.crypto.umbral_adapter import Signer
from nucypher_core.umbral import Signer
class SignatureStamp(object):

View File

@ -30,7 +30,7 @@ from cryptography.hazmat.primitives.serialization import Encoding
from cryptography.x509 import Certificate
from cryptography.x509.oid import NameOID
from nucypher.crypto.umbral_adapter import SecretKey
from nucypher_core.umbral import SecretKey
_TLS_CERTIFICATE_ENCODING = Encoding.PEM
_TLS_CURVE = ec.SECP384R1

View File

@ -1,39 +0,0 @@
"""
This file is part of nucypher.
nucypher is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
nucypher is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
# This module is used to have a single point where the Umbral implementation is chosen.
# Do not import Umbral directly, use re-exports from this module.
from umbral import (
SecretKey,
PublicKey,
SecretKeyFactory,
Signature,
Signer,
Capsule,
KeyFrag,
VerifiedKeyFrag,
CapsuleFrag,
VerifiedCapsuleFrag,
VerificationError,
encrypt,
decrypt_original,
generate_kfrags,
reencrypt,
decrypt_reencrypted,
)

View File

@ -26,8 +26,9 @@ from eth_account.messages import encode_defunct
from eth_keys import KeyAPI as EthKeyAPI
from eth_utils.address import to_checksum_address
from nucypher_core.umbral import PublicKey
from nucypher.crypto.signing import SignatureStamp
from nucypher.crypto.umbral_adapter import PublicKey
SYSTEM_RAND = SystemRandom()

View File

@ -15,7 +15,8 @@ You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
from nucypher.crypto.umbral_adapter import PublicKey
from nucypher_core.umbral import PublicKey
from nucypher.datastore.base import DatastoreRecord, RecordField

View File

@ -20,9 +20,10 @@ from http import HTTPStatus
import socket
import ssl
import time
from typing import Sequence
import requests
from nucypher.core import MetadataRequest
from nucypher_core import MetadataRequest, FleetStateChecksum, NodeMetadata
from constant_sorrow.constants import CERTIFICATE_NOT_SAVED, EXEMPT_FROM_VERIFICATION
from cryptography import x509
@ -235,8 +236,8 @@ class RestMiddleware:
def get_nodes_via_rest(self,
node,
fleet_state_checksum: str,
announce_nodes=None):
fleet_state_checksum: FleetStateChecksum,
announce_nodes: Sequence[NodeMetadata]):
request = MetadataRequest(fleet_state_checksum=fleet_state_checksum,
announce_nodes=announce_nodes)

View File

@ -39,7 +39,8 @@ from requests.exceptions import SSLError
from twisted.internet import reactor, task
from twisted.internet.defer import Deferred
from nucypher.core import NodeMetadata, MetadataResponse
from nucypher_core import NodeMetadata, MetadataResponse, MetadataResponsePayload
from nucypher_core.umbral import Signature
from nucypher.acumen.nicknames import Nickname
from nucypher.acumen.perception import FleetSensor
@ -57,7 +58,6 @@ from nucypher.crypto.powers import (
SigningPower,
)
from nucypher.crypto.signing import SignatureStamp, InvalidSignature
from nucypher.crypto.umbral_adapter import Signature
from nucypher.crypto.utils import recover_address_eip_191, verify_eip_191
from nucypher.network.exceptions import NodeSeemsToBeDown
from nucypher.network.middleware import RestMiddleware
@ -81,8 +81,9 @@ class NodeSprout:
"""
verified_node = False
def __init__(self, node_metadata):
self._metadata = node_metadata
def __init__(self, metadata: NodeMetadata):
self._metadata = metadata
self._metadata_payload = metadata.payload
# cached properties
self._checksum_address = None
@ -114,9 +115,13 @@ class NodeSprout:
@property
def checksum_address(self):
if not self._checksum_address:
self._checksum_address = to_checksum_address(self._metadata.public_address)
self._checksum_address = to_checksum_address(self.canonical_address)
return self._checksum_address
@property
def canonical_address(self):
return self._metadata_payload.canonical_address
@property
def nickname(self):
if not self._nickname:
@ -126,7 +131,7 @@ class NodeSprout:
@property
def rest_interface(self):
if not self._rest_interface:
self._rest_interface = InterfaceInfo(self._metadata.host, self._metadata.port)
self._rest_interface = InterfaceInfo(self._metadata_payload.host, self._metadata_payload.port)
return self._rest_interface
def rest_url(self):
@ -137,48 +142,44 @@ class NodeSprout:
@property
def verifying_key(self):
return self._metadata.verifying_key
return self._metadata_payload.verifying_key
@property
def encrypting_key(self):
return self._metadata.encrypting_key
return self._metadata_payload.encrypting_key
@property
def decentralized_identity_evidence(self):
return self._metadata.decentralized_identity_evidence or NOT_SIGNED
@property
def public_address(self):
return self._metadata.public_address
return self._metadata_payload.decentralized_identity_evidence or NOT_SIGNED
@property
def timestamp(self):
return maya.MayaDT(self._metadata.timestamp_epoch)
return maya.MayaDT(self._metadata_payload.timestamp_epoch)
@property
def stamp(self) -> SignatureStamp:
return SignatureStamp(self._metadata.verifying_key)
return SignatureStamp(self._metadata_payload.verifying_key)
@property
def domain(self) -> str:
return self._metadata.domain
return self._metadata_payload.domain
def finish(self):
from nucypher.characters.lawful import Ursula
crypto_power = CryptoPower()
crypto_power.consume_power_up(SigningPower(public_key=self._metadata.verifying_key))
crypto_power.consume_power_up(DecryptingPower(public_key=self._metadata.encrypting_key))
crypto_power.consume_power_up(SigningPower(public_key=self._metadata_payload.verifying_key))
crypto_power.consume_power_up(DecryptingPower(public_key=self._metadata_payload.encrypting_key))
return Ursula(is_me=False,
crypto_power=crypto_power,
rest_host=self._metadata.host,
rest_port=self._metadata.port,
rest_host=self._metadata_payload.host,
rest_port=self._metadata_payload.port,
checksum_address=self.checksum_address,
domain=self._metadata.domain,
domain=self._metadata_payload.domain,
timestamp=self.timestamp,
decentralized_identity_evidence=self.decentralized_identity_evidence,
certificate=load_pem_x509_certificate(self._metadata.certificate_bytes, backend=default_backend()),
certificate=load_pem_x509_certificate(self._metadata_payload.certificate_bytes, backend=default_backend()),
metadata=self._metadata
)
@ -787,7 +788,7 @@ class Learner:
if isinstance(self, Teacher):
announce_nodes = [self.metadata()]
else:
announce_nodes = None
announce_nodes = []
unresponsive_nodes = set()
@ -855,18 +856,18 @@ class Learner:
return
try:
metadata.verify(current_teacher.stamp.as_umbral_pubkey())
except InvalidSignature:
metadata_payload = metadata.verify(current_teacher.stamp.as_umbral_pubkey())
except Exception as e:
# TODO (#567): bucket the node as suspicious
self.log.warn(
f"Invalid signature received from teacher {current_teacher} for MetadataResponse {response.content}")
f"Failed to verify MetadataResponse from Teacher {current_teacher} ({e}): {response.content}")
return
# End edge case handling.
fleet_state_updated = maya.MayaDT(metadata.timestamp_epoch)
fleet_state_updated = maya.MayaDT(metadata_payload.timestamp_epoch)
if not metadata.this_node and not metadata.other_nodes:
if not metadata_payload.announce_nodes:
# The teacher had the same fleet state
self.known_nodes.record_remote_fleet_state(
current_teacher.checksum_address,
@ -876,15 +877,7 @@ class Learner:
return FLEET_STATES_MATCH
# Note: There was previously a version check here, but that required iterating through node bytestrings twice,
# so it has been removed. When we create a new Ursula bytestring version, let's put the check
# somewhere more performant, like mature() or verify_node().
nodes = (
([metadata.this_node] if metadata.this_node else []) +
(metadata.other_nodes if metadata.other_nodes else [])
)
sprouts = [NodeSprout(node) for node in nodes]
sprouts = [NodeSprout(node) for node in metadata_payload.announce_nodes]
for sprout in sprouts:
try:
@ -1033,11 +1026,10 @@ class Teacher:
def bytestring_of_known_nodes(self):
# TODO (#1537): FleetSensor does metadata-to-byte conversion as well,
# we may be able to cache the results there.
response = MetadataResponse.author(signer=self.stamp.as_umbral_signer(),
timestamp_epoch=self.known_nodes.timestamp.epoch,
this_node=self.metadata(),
other_nodes=[node.metadata() for node in self.known_nodes],
)
announce_nodes = [self.metadata()] + [node.metadata() for node in self.known_nodes]
response_payload = MetadataResponsePayload(timestamp_epoch=self.known_nodes.timestamp.epoch,
announce_nodes=announce_nodes)
response = MetadataResponse(self.stamp.as_umbral_signer(), response_payload)
return bytes(response)
#
@ -1128,7 +1120,7 @@ class Teacher:
"""
Checks that the interface info is valid for this node's canonical address.
"""
metadata_is_valid = self._metadata.verify()
metadata_is_valid = self.metadata().verify()
self.verified_metadata = metadata_is_valid
if metadata_is_valid:
return True
@ -1198,11 +1190,14 @@ class Teacher:
port=self.rest_interface.port,
certificate_filepath=certificate_filepath)
sprout = NodeSprout(NodeMetadata.from_bytes(response_data))
try:
sprout = self.from_metadata_bytes(response_data)
except Exception as e:
raise self.InvalidNode(str(e))
verifying_keys_match = sprout.verifying_key == self.public_keys(SigningPower)
encrypting_keys_match = sprout.encrypting_key == self.public_keys(DecryptingPower)
addresses_match = sprout.public_address == self.canonical_public_address
addresses_match = sprout.checksum_address == self.checksum_address
evidence_matches = sprout.decentralized_identity_evidence == self.__decentralized_identity_evidence
if not all((encrypting_keys_match, verifying_keys_match, addresses_match, evidence_matches)):

View File

@ -20,22 +20,23 @@ import random
from typing import Dict, Sequence, List
from eth_typing.evm import ChecksumAddress
from eth_utils import to_checksum_address
from twisted.logger import Logger
from nucypher.core import (
from nucypher_core import (
TreasureMap,
ReencryptionResponse,
ReencryptionRequest,
RetrievalKit,
)
from nucypher.crypto.signing import InvalidSignature
from nucypher.crypto.umbral_adapter import (
from nucypher_core.umbral import (
Capsule,
PublicKey,
VerifiedCapsuleFrag,
VerificationError,
)
from nucypher.crypto.signing import InvalidSignature
from nucypher.network.exceptions import NodeSeemsToBeDown
from nucypher.network.nodes import Learner
from nucypher.policy.kits import RetrievalResult
@ -121,7 +122,10 @@ class RetrievalPlan:
)
def results(self) -> List['RetrievalResult']:
return [RetrievalResult(self._results[capsule]) for capsule in self._capsules]
# TODO (#1995): when that issue is fixed, conversion is no longer needed
return [RetrievalResult({to_checksum_address(address): cfrag
for address, cfrag in self._results[capsule].items()})
for capsule in self._capsules]
class RetrievalWorkOrder:
@ -155,6 +159,10 @@ class RetrievalClient:
self._learner.learn_from_teacher_node()
ursulas_in_map = treasure_map.destinations.keys()
# TODO (#1995): when that issue is fixed, conversion is no longer needed
ursulas_in_map = [to_checksum_address(address) for address in ursulas_in_map]
all_known_ursulas = self._learner.known_nodes.addresses()
# Push all unknown Ursulas from the map in the queue for learning
@ -236,7 +244,8 @@ class RetrievalClient:
self.log.warn(message)
raise RuntimeError(message)
return verified_cfrags
return {capsule: vcfrag for capsule, vcfrag
in zip(reencryption_request.capsules, verified_cfrags)}
def retrieve_cfrags(
self,
@ -257,15 +266,19 @@ class RetrievalClient:
work_order = retrieval_plan.get_work_order()
if work_order.ursula_address not in self._learner.known_nodes:
# TODO (#1995): when that issue is fixed, conversion is no longer needed
ursula_checksum_address = to_checksum_address(work_order.ursula_address)
if ursula_checksum_address not in self._learner.known_nodes:
continue
ursula = self._learner.known_nodes[work_order.ursula_address]
reencryption_request = ReencryptionRequest.from_treasure_map(
ursula_address=work_order.ursula_address,
ursula = self._learner.known_nodes[ursula_checksum_address]
reencryption_request = ReencryptionRequest(
hrac=treasure_map.hrac,
capsules=work_order.capsules,
treasure_map=treasure_map,
bob_verifying_key=bob_verifying_key)
encrypted_kfrag=treasure_map.destinations[work_order.ursula_address],
bob_verifying_key=bob_verifying_key,
publisher_verifying_key=treasure_map.publisher_verifying_key)
try:
cfrags = self._request_reencryption(ursula=ursula,

View File

@ -23,20 +23,19 @@ from pathlib import Path
from typing import Tuple
from constant_sorrow import constants
from constant_sorrow.constants import RELAX, NOT_STAKING
from constant_sorrow.constants import RELAX
from flask import Flask, Response, jsonify, request
from mako import exceptions as mako_exceptions
from mako.template import Template
from nucypher.core import (
from nucypher_core import (
ReencryptionRequest,
RevocationOrder,
NodeMetadata,
MetadataRequest,
MetadataResponse,
MetadataResponsePayload,
)
from nucypher.blockchain.eth.utils import period_to_epoch
from nucypher.config.constants import MAX_UPLOAD_CONTENT_LENGTH
from nucypher.crypto.keypairs import DecryptingKeypair
from nucypher.crypto.signing import InvalidSignature
@ -81,7 +80,6 @@ class ProxyRESTServer:
def make_rest_app(
db_filepath: Path,
this_node,
domain,
log: Logger = Logger("http-application-layer")
) -> Tuple[Flask, Datastore]:
"""
@ -98,12 +96,12 @@ def make_rest_app(
log.info("Starting datastore {}".format(db_filepath))
datastore = Datastore(db_filepath)
rest_app = _make_rest_app(weakref.proxy(datastore), weakref.proxy(this_node), domain, log)
rest_app = _make_rest_app(weakref.proxy(datastore), weakref.proxy(this_node), log)
return rest_app, datastore
def _make_rest_app(datastore: Datastore, this_node, domain: str, log: Logger) -> Flask:
def _make_rest_app(datastore: Datastore, this_node, log: Logger) -> Flask:
# TODO: Avoid circular imports :-(
from nucypher.characters.lawful import Alice, Bob, Ursula
@ -145,13 +143,21 @@ def _make_rest_app(datastore: Datastore, this_node, domain: str, log: Logger) ->
# log.debug("Learner already knew fleet state {}; doing nothing.".format(learner_fleet_state)) # 1712
headers = {'Content-Type': 'application/octet-stream'}
# No nodes in the response: same fleet state
response = MetadataResponse.author(signer=this_node.stamp.as_umbral_signer(),
timestamp_epoch=this_node.known_nodes.timestamp.epoch)
response_payload = MetadataResponsePayload(timestamp_epoch=this_node.known_nodes.timestamp.epoch,
announce_nodes=[])
response = MetadataResponse(this_node.stamp.as_umbral_signer(),
response_payload)
return Response(bytes(response), headers=headers)
if metadata_request.announce_nodes:
for node in metadata_request.announce_nodes:
this_node.remember_node(NodeSprout(node))
for metadata in metadata_request.announce_nodes:
try:
metadata.verify()
except Exception:
# inconsistent metadata
pass
else:
this_node.remember_node(NodeSprout(metadata))
# TODO: generate a new fleet state here?
@ -183,28 +189,19 @@ def _make_rest_app(datastore: Datastore, this_node, domain: str, log: Logger) ->
# Verify & Decrypt KFrag Payload
try:
authorized_kfrag = this_node._decrypt_kfrag(reenc_request.encrypted_kfrag)
verified_kfrag = this_node._decrypt_kfrag(reenc_request.encrypted_kfrag, hrac, publisher_verifying_key)
except DecryptingKeypair.DecryptionFailed:
# TODO: don't we want to record suspicious activities here too?
return Response(response="EncryptedKeyFrag decryption failed.", status=HTTPStatus.FORBIDDEN)
except Exception as e:
message = f'{bob_identity_message} Invalid EncryptedKeyFrag: {e}.'
log.info(message)
# TODO (#567): bucket the node as suspicious
return Response(message, status=HTTPStatus.BAD_REQUEST)
# Verify KFrag Authorization (offchain)
try:
verified_kfrag = authorized_kfrag.verify(hrac=hrac,
publisher_verifying_key=publisher_verifying_key)
except InvalidSignature as e:
message = f'{bob_identity_message} Invalid signature for KeyFrag: {e}.'
log.info(message)
# TODO (#567): bucket the node as suspicious
return Response(message, status=HTTPStatus.UNAUTHORIZED) # 401 - Unauthorized
except Exception as e:
message = f'{bob_identity_message} Invalid KeyFrag: {e}.'
message = f'{bob_identity_message} Invalid EncryptedKeyFrag: {e}.'
log.info(message)
# TODO (#567): bucket the node as suspicious
return Response(message, status=HTTPStatus.BAD_REQUEST)
if not this_node.federated_only:

View File

@ -35,7 +35,7 @@ def character_span(character):
<td>
<span>${state.population} nodes</span>
<br/>
<span class="checksum">${state.checksum[0:8]}</span>
<span class="checksum">${bytes(state.checksum)[0:8]}</span>
</td>
</tr>
</table>

View File

@ -27,11 +27,12 @@ from bytestring_splitter import BytestringKwargifier, VariableLengthBytestring
from constant_sorrow.constants import ALICE, BOB, NO_SIGNATURE
from hexbytes.main import HexBytes
from nucypher_core.umbral import PublicKey
from nucypher.characters.base import Character
from nucypher.characters.lawful import Alice, Bob
from nucypher.config.constants import DEFAULT_CONFIG_ROOT
from nucypher.crypto.powers import DecryptingPower, SigningPower
from nucypher.crypto.umbral_adapter import PublicKey
class Card:

View File

@ -18,11 +18,11 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
from typing import Dict, Set, Union
from eth_utils import to_canonical_address
from eth_typing import ChecksumAddress
from nucypher.core import MessageKit, RetrievalKit
from nucypher.crypto.umbral_adapter import PublicKey, VerifiedCapsuleFrag, SecretKey
from nucypher_core import MessageKit, RetrievalKit
from nucypher_core.umbral import PublicKey, VerifiedCapsuleFrag, SecretKey
class PolicyMessageKit:
@ -47,10 +47,12 @@ class PolicyMessageKit:
self._result = result
def as_retrieval_kit(self) -> RetrievalKit:
return RetrievalKit(self.message_kit.capsule, self._result.addresses())
return RetrievalKit(self.message_kit.capsule, self._result.canonical_addresses())
def decrypt(self, sk: SecretKey) -> bytes:
return self.message_kit.decrypt_reencrypted(sk, self.policy_encrypting_key, self._result.cfrags.values())
return self.message_kit.decrypt_reencrypted(sk,
self.policy_encrypting_key,
list(self._result.cfrags.values()))
def is_decryptable_by_receiver(self) -> bool:
return len(self._result.cfrags) >= self.threshold
@ -75,8 +77,9 @@ class RetrievalResult:
def __init__(self, cfrags: Dict[ChecksumAddress, VerifiedCapsuleFrag]):
self.cfrags = cfrags
def addresses(self) -> Set[ChecksumAddress]:
return set(self.cfrags)
def canonical_addresses(self) -> Set[bytes]:
# TODO (#1995): propagate this to use canonical addresses everywhere
return set([to_canonical_address(address) for address in self.cfrags])
def with_result(self, result: 'RetrievalResult') -> 'RetrievalResult':
"""

View File

@ -22,9 +22,10 @@ from typing import Sequence, Optional, Iterable, List
import maya
from eth_typing.evm import ChecksumAddress
from nucypher.core import HRAC, TreasureMap
from nucypher_core.umbral import PublicKey, VerifiedKeyFrag
from nucypher_core import HRAC, TreasureMap
from nucypher.crypto.powers import DecryptingPower
from nucypher.crypto.umbral_adapter import PublicKey, VerifiedKeyFrag
from nucypher.network.middleware import RestMiddleware
from nucypher.policy.reservoir import (
make_federated_staker_reservoir,
@ -97,9 +98,9 @@ class Policy(ABC):
self.kfrags = kfrags
self.public_key = public_key
self.expiration = expiration
self.hrac = HRAC.derive(publisher_verifying_key=self.publisher.stamp.as_umbral_pubkey(),
bob_verifying_key=self.bob.stamp.as_umbral_pubkey(),
label=self.label)
self.hrac = 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__}:{bytes(self.hrac).hex()[:6]}"
@ -181,15 +182,15 @@ class Policy(ABC):
self._publish(ursulas=ursulas)
assigned_kfrags = {
ursula.checksum_address: (ursula.public_keys(DecryptingPower), vkfrag)
ursula.canonical_address: (ursula.public_keys(DecryptingPower), vkfrag)
for ursula, vkfrag in zip(ursulas, self.kfrags)
}
treasure_map = TreasureMap.construct_by_publisher(signer=self.publisher.stamp.as_umbral_signer(),
hrac=self.hrac,
policy_encrypting_key=self.public_key,
assigned_kfrags=assigned_kfrags,
threshold=self.threshold)
treasure_map = TreasureMap(signer=self.publisher.stamp.as_umbral_signer(),
hrac=self.hrac,
policy_encrypting_key=self.public_key,
assigned_kfrags=assigned_kfrags,
threshold=self.threshold)
enc_treasure_map = treasure_map.encrypt(signer=self.publisher.stamp.as_umbral_signer(),
recipient_key=self.bob.public_keys(DecryptingPower))

View File

@ -15,8 +15,10 @@ You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
from eth_typing.evm import ChecksumAddress
from eth_utils import to_checksum_address, to_canonical_address
from nucypher.core import RevocationOrder
from nucypher_core import RevocationOrder
from nucypher.crypto.signing import SignatureStamp
@ -25,16 +27,17 @@ class RevocationKit:
def __init__(self, treasure_map, signer: SignatureStamp):
# TODO: move to core and make a method of TreasureMap?
self.revocations = dict()
for node_id, encrypted_kfrag in treasure_map:
self.revocations[node_id] = RevocationOrder.author(signer=signer.as_umbral_signer(),
ursula_address=node_id,
for ursula_address, encrypted_kfrag in treasure_map.destinations.items():
self.revocations[ursula_address] = RevocationOrder(signer=signer.as_umbral_signer(),
ursula_address=ursula_address,
encrypted_kfrag=encrypted_kfrag)
def __iter__(self):
return iter(self.revocations.values())
def __getitem__(self, node_id):
return self.revocations[node_id]
def __getitem__(self, ursula_address: ChecksumAddress):
# TODO (#1995): when that issue is fixed, conversion is no longer needed
return self.revocations[to_canonical_address(ursula_address)]
def __len__(self):
return len(self.revocations)
@ -45,8 +48,9 @@ class RevocationKit:
@property
def revokable_addresses(self):
"""Returns a Set of revokable addresses in the checksum address formatting"""
return set(self.revocations.keys())
# TODO (#1995): when that issue is fixed, conversion is no longer needed
return set([to_checksum_address(address) for address in self.revocations.keys()])
def add_confirmation(self, node_id, signed_receipt):
def add_confirmation(self, ursula_address, signed_receipt):
"""Adds a signed confirmation of Ursula's ability to revoke the node."""
raise NotImplementedError

View File

@ -18,10 +18,10 @@ from typing import List, Optional
from eth_typing import ChecksumAddress
from nucypher.core import TreasureMap, RetrievalKit
from nucypher_core import TreasureMap, RetrievalKit
from nucypher_core.umbral import PublicKey
from nucypher.control.interfaces import ControlInterface, attach_schema
from nucypher.crypto.umbral_adapter import PublicKey
from nucypher.utilities.porter.control.specifications import porter_schema

View File

@ -16,12 +16,12 @@
"""
from marshmallow import fields
from nucypher.core import RetrievalKit as RetrievalKitClass
from nucypher_core import RetrievalKit as RetrievalKitClass
from nucypher_core.umbral import CapsuleFrag as CapsuleFragClass
from nucypher.control.specifications.base import BaseSchema
from nucypher.control.specifications.exceptions import InvalidInputData
from nucypher.control.specifications.fields import Base64BytesRepresentation
from nucypher.crypto.umbral_adapter import CapsuleFrag as CapsuleFragClass
from nucypher.utilities.porter.control.specifications.fields import UrsulaChecksumAddress

View File

@ -21,7 +21,8 @@ from constant_sorrow.constants import NO_BLOCKCHAIN_CONNECTION, NO_CONTROL_PROTO
from eth_typing import ChecksumAddress
from flask import request, Response
from nucypher.core import TreasureMap, RetrievalKit
from nucypher_core import TreasureMap, RetrievalKit
from nucypher_core.umbral import PublicKey
from nucypher.blockchain.eth.agents import ContractAgency, StakingEscrowAgent
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
@ -29,7 +30,6 @@ from nucypher.blockchain.eth.registry import BaseContractRegistry, InMemoryContr
from nucypher.characters.lawful import Ursula
from nucypher.control.controllers import JSONRPCController, WebController
from nucypher.crypto.powers import DecryptingPower
from nucypher.crypto.umbral_adapter import PublicKey
from nucypher.network.nodes import Learner
from nucypher.network.retrieval import RetrievalClient
from nucypher.policy.kits import RetrievalResult

View File

@ -1,179 +0,0 @@
"""
This file is part of nucypher.
nucypher is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
nucypher is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
from abc import abstractmethod, ABC
import re
from typing import Dict, Tuple, Callable
class Versioned(ABC):
"""Base class for serializable entities"""
_VERSION_PARTS = 2
_VERSION_PART_SIZE = 2 # bytes
_BRAND_SIZE = 4
_VERSION_SIZE = _VERSION_PART_SIZE * _VERSION_PARTS
_HEADER_SIZE = _BRAND_SIZE + _VERSION_SIZE
class InvalidHeader(ValueError):
"""Raised when an unexpected or invalid bytes header is encountered."""
class IncompatibleVersion(ValueError):
"""Raised when attempting to deserialize incompatible bytes"""
class Empty(ValueError):
"""Raised when 0 bytes are remaining after parsing the header."""
@classmethod
@abstractmethod
def _brand(cls) -> bytes:
raise NotImplementedError
@classmethod
@abstractmethod
def _version(cls) -> Tuple[int, int]:
"""tuple(major, minor)"""
raise NotImplementedError
@classmethod
def version_string(cls) -> str:
major, minor = cls._version()
return f'{major}.{minor}'
#
# Serialize
#
def __bytes__(self) -> bytes:
return self._header() + self._payload()
@classmethod
def _header(cls) -> bytes:
"""The entire bytes header to prepend to the instance payload."""
major, minor = cls._version()
major_bytes = major.to_bytes(cls._VERSION_PART_SIZE, 'big')
minor_bytes = minor.to_bytes(cls._VERSION_PART_SIZE, 'big')
header = cls._brand() + major_bytes + minor_bytes
return header
@abstractmethod
def _payload(self) -> bytes:
"""The unbranded and unversioned bytes-serialized representation of this instance."""
raise NotImplementedError
#
# Deserialize
#
@classmethod
@abstractmethod
def _from_bytes_current(cls, data):
"""The current deserializer"""
raise NotImplementedError
@classmethod
@abstractmethod
def _old_version_handlers(cls) -> Dict[Tuple[int, int], Callable]:
"""Old deserializer callables keyed by version."""
raise NotImplementedError
@classmethod
def take(cls, data: bytes):
"""
Deserializes the object from the given bytestring
and returns the object and the remainder of the bytestring.
"""
brand, version, payload = cls._parse_header(data)
version = cls._resolve_version(version=version)
handlers = cls._deserializers()
obj, remainder = handlers[version](payload)
return obj, remainder
@classmethod
def from_bytes(cls, data: bytes):
""""Public deserialization API"""
obj, remainder = cls.take(data)
if remainder:
raise ValueError(f"{len(remainder)} bytes remaining after deserializing {cls}")
return obj
@classmethod
def _resolve_version(cls, version: Tuple[int, int]) -> Tuple[int, int]:
# Unpack version metadata
bytrestring_major, bytrestring_minor = version
latest_major_version, latest_minor_version = cls._version()
# Enforce major version compatibility
if not bytrestring_major == latest_major_version:
message = f'Incompatible versioned bytes for {cls.__name__}. ' \
f'Compatible version is {latest_major_version}.x, ' \
f'Got {bytrestring_major}.{bytrestring_minor}.'
raise cls.IncompatibleVersion(message)
# Enforce minor version compatibility.
# Pass future minor versions to the latest minor handler.
if bytrestring_minor >= latest_minor_version:
version = cls._version()
return version
@classmethod
def _parse_header(cls, data: bytes) -> Tuple[bytes, Tuple[int, int], bytes]:
if len(data) < cls._HEADER_SIZE:
# handles edge case when input is too short.
raise ValueError(f'Invalid bytes for {cls.__name__}.')
brand = cls._parse_brand(data)
version = cls._parse_version(data)
payload = cls._parse_payload(data)
return brand, version, payload
@classmethod
def _parse_brand(cls, data: bytes) -> bytes:
brand = data[:cls._BRAND_SIZE]
if brand != cls._brand():
error = f"Incorrect brand. Expected {cls._brand()}, Got {brand}."
if not re.fullmatch(rb'\w+', brand):
# unversioned entities for older versions will most likely land here.
error = f"Incompatible bytes for {cls.__name__}."
raise cls.InvalidHeader(error)
return brand
@classmethod
def _parse_version(cls, data: bytes) -> Tuple[int, int]:
version_data = data[cls._BRAND_SIZE:cls._HEADER_SIZE]
major, minor = version_data[:cls._VERSION_PART_SIZE], version_data[cls._VERSION_PART_SIZE:]
major, minor = int.from_bytes(major, 'big'), int.from_bytes(minor, 'big')
version = major, minor
return version
@classmethod
def _parse_payload(cls, data: bytes) -> bytes:
payload = data[cls._HEADER_SIZE:]
if len(payload) == 0:
raise ValueError(f'No content to deserialize {cls.__name__}.')
return payload
@classmethod
def _deserializers(cls) -> Dict[Tuple[int, int], Callable]:
"""Return a dict of all known deserialization handlers for this class keyed by version"""
return {cls._version(): cls._from_bytes_current, **cls._old_version_handlers()}
# Collects the brands of every serializable entity, potentially useful for documentation.
# SERIALIZABLE_ENTITIES = {v.__class__.__name__: v._brand() for v in Versioned.__subclasses__()}

View File

@ -104,3 +104,4 @@ web3==5.12.3
websockets==8.1; python_full_version >= '3.6.1'
werkzeug==2.0.1; python_version >= '3.6'
zope.interface==5.4.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
nucypher-core==0.0.1; python_version >= '3.7'

View File

@ -18,6 +18,8 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
import pytest
from nucypher_core.umbral import SecretKeyFactory, Signer
from nucypher.blockchain.eth.actors import Investigator, Staker
from nucypher.blockchain.eth.agents import ContractAgency, NucypherTokenAgent, StakingEscrowAgent
from nucypher.blockchain.eth.constants import NULL_ADDRESS
@ -26,7 +28,6 @@ from nucypher.blockchain.eth.token import NU
from nucypher.config.constants import TEMPORARY_DOMAIN
from nucypher.crypto.powers import TransactingPower
from nucypher.crypto.signing import SignatureStamp
from nucypher.crypto.umbral_adapter import SecretKeyFactory, Signer
def mock_ursula(testerchain, account, mocker):

View File

@ -18,6 +18,8 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
import pytest
from nucypher_core.umbral import SecretKeyFactory, Signer
from nucypher.blockchain.eth.actors import NucypherTokenActor, Staker
from nucypher.blockchain.eth.agents import (
AdjudicatorAgent,
@ -31,7 +33,6 @@ from nucypher.blockchain.eth.token import NU
from nucypher.config.constants import TEMPORARY_DOMAIN
from nucypher.crypto.powers import TransactingPower
from nucypher.crypto.signing import SignatureStamp
from nucypher.crypto.umbral_adapter import SecretKeyFactory, Signer
def mock_ursula(testerchain, account, mocker):

View File

@ -30,7 +30,7 @@ def test_treasure_map(enacted_blockchain_policy):
assert serialized == b64encode(bytes(treasure_map)).decode()
deserialized = field._deserialize(value=serialized, attr=None, data=None)
assert deserialized == treasure_map
assert bytes(deserialized) == bytes(treasure_map)
with pytest.raises(InvalidInputData):
field._deserialize(value=b64encode(b"TreasureMap").decode(), attr=None, data=None)

View File

@ -23,7 +23,7 @@ import maya
import pytest
from click.testing import CliRunner
from nucypher.core import MessageKit, EncryptedTreasureMap
from nucypher_core import MessageKit, EncryptedTreasureMap
import nucypher
from nucypher.crypto.powers import DecryptingPower

View File

@ -21,7 +21,7 @@ import datetime
import maya
import pytest
from nucypher.core import EncryptedKeyFrag
from nucypher_core import EncryptedKeyFrag
from nucypher.crypto.utils import keccak_digest
@ -48,8 +48,8 @@ def test_decentralized_grant(blockchain_alice, blockchain_bob, blockchain_ursula
# Let's look at the destinations.
for ursula in blockchain_ursulas:
if ursula.checksum_address in treasure_map.destinations:
kfrag_kit = treasure_map.destinations[ursula.checksum_address]
if ursula.canonical_address in treasure_map.destinations:
kfrag_kit = treasure_map.destinations[ursula.canonical_address]
# TODO: try to decrypt?
assert isinstance(kfrag_kit, EncryptedKeyFrag)

View File

@ -83,7 +83,7 @@ def test_vladimir_cannot_verify_interface_with_ursulas_signing_key(blockchain_ur
vladimir = remote_vladimir(target_ursula=his_target)
# Now, even though his public signing key matches Ursulas...
assert vladimir.metadata().verifying_key == his_target.stamp.as_umbral_pubkey()
assert vladimir.metadata().payload.verifying_key == his_target.stamp.as_umbral_pubkey()
# ...he is unable to pretend that his interface is valid
# because the validity check contains the canonical public address as part of its message.

View File

@ -29,7 +29,7 @@ import pytest
from twisted.internet import threads
from web3 import Web3
from nucypher.core import MessageKit, EncryptedTreasureMap
from nucypher_core import MessageKit, EncryptedTreasureMap
from nucypher.cli.main import nucypher_cli
from nucypher.config.characters import AliceConfiguration, BobConfiguration

View File

@ -21,8 +21,9 @@ import pytest
import tempfile
from pathlib import Path
from nucypher_core.umbral import SecretKey
from nucypher.cli.main import nucypher_cli
from nucypher.crypto.umbral_adapter import SecretKey
from nucypher.policy.identity import Card

View File

@ -15,8 +15,9 @@
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
from nucypher_core.umbral import SecretKey
from nucypher.cli.main import nucypher_cli
from nucypher.crypto.umbral_adapter import SecretKey
def test_enrico_encrypt(click_runner):

View File

@ -622,7 +622,7 @@ def test_collect_rewards_integration(click_runner,
# Ensure that the handpicked Ursula was selected for the policy
treasure_map = blockchain_bob._decrypt_treasure_map(blockchain_policy.treasure_map,
blockchain_policy.publisher_verifying_key)
assert ursula.checksum_address in treasure_map.destinations
assert ursula.canonical_address in treasure_map.destinations
# Bob learns about the new staker and joins the policy
blockchain_bob.start_learning_loop()

View File

@ -19,7 +19,7 @@ import pytest
from twisted.logger import LogLevel, globalLogPublisher
from constant_sorrow.constants import NOT_SIGNED
from nucypher.core import MetadataResponse
from nucypher_core import MetadataResponse, MetadataResponsePayload
from nucypher.acumen.perception import FleetSensor
from nucypher.config.constants import TEMPORARY_DOMAIN
@ -74,8 +74,10 @@ def test_blockchain_ursula_stamp_verification_tolerance(blockchain_ursulas, mock
def bad_bytestring_of_known_nodes():
# Signing with the learner's signer instead of the teacher's signer
response = MetadataResponse.author(signer=lonely_blockchain_learner.stamp.as_umbral_signer(),
timestamp_epoch=blockchain_teacher.known_nodes.timestamp.epoch)
response_payload = MetadataResponsePayload(timestamp_epoch=blockchain_teacher.known_nodes.timestamp.epoch,
announce_nodes=[])
response = MetadataResponse(signer=lonely_blockchain_learner.stamp.as_umbral_signer(),
payload=response_payload)
return bytes(response)
mocker.patch.object(blockchain_teacher, 'bytestring_of_known_nodes', bad_bytestring_of_known_nodes)
@ -87,7 +89,7 @@ def test_blockchain_ursula_stamp_verification_tolerance(blockchain_ursulas, mock
assert len(warnings) == 2
warning = warnings[1]['log_format']
assert str(blockchain_teacher) in warning
assert "Invalid signature received from teacher" in warning # TODO: Cleanup logging templates
assert "Failed to verify MetadataResponse from Teacher" in warning # TODO: Cleanup logging templates
@pytest.mark.skip("See Issue #1075") # TODO: Issue #1075

View File

@ -20,7 +20,7 @@ import os
from base64 import b64encode
from urllib.parse import urlencode
from nucypher.core import RetrievalKit
from nucypher_core import RetrievalKit
from nucypher.characters.lawful import Enrico
from nucypher.crypto.powers import DecryptingPower
@ -147,7 +147,7 @@ def test_retrieve_cfrags(blockchain_porter,
assert policy_message_kit.is_decryptable_by_receiver()
cleartext = blockchain_bob._crypto_power.power_ups(DecryptingPower).keypair.decrypt(policy_message_kit)
cleartext = blockchain_bob._crypto_power.power_ups(DecryptingPower).keypair.decrypt_message_kit(policy_message_kit)
assert cleartext == original_message
#

View File

@ -21,11 +21,12 @@ from eth_tester.exceptions import TransactionFailed
from eth_utils import to_canonical_address, to_wei
from web3.contract import Contract
from nucypher_core.umbral import SecretKey, Signer
from nucypher.blockchain.economics import BaseEconomics
from nucypher.blockchain.eth.constants import NULL_ADDRESS, POLICY_ID_LENGTH
from nucypher.crypto.utils import sha256_digest
from nucypher.crypto.signing import SignatureStamp
from nucypher.crypto.umbral_adapter import SecretKey, Signer
from nucypher.utilities.ethereum import to_32byte_hex

View File

@ -28,7 +28,8 @@ from eth_keys import KeyAPI as EthKeyAPI
from eth_tester.exceptions import TransactionFailed
from eth_utils import to_canonical_address, to_checksum_address, to_normalized_address
from nucypher.crypto.umbral_adapter import SecretKey, PublicKey, Signer, Signature
from nucypher_core.umbral import SecretKey, PublicKey, Signer, Signature
from nucypher.crypto.utils import (
canonical_address_from_umbral_key,
keccak_digest,

View File

@ -17,9 +17,12 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
import os
import pytest
from eth_tester.exceptions import TransactionFailed
from nucypher.crypto.umbral_adapter import Signer, SecretKey, generate_kfrags, encrypt, reencrypt
import pytest
from nucypher_core import MessageKit
from nucypher_core.umbral import Signer, SecretKey, generate_kfrags, reencrypt
@pytest.fixture()
@ -44,7 +47,7 @@ def fragments():
sign_delegating_key=False,
sign_receiving_key=False)
capsule, _ciphertext = encrypt(delegating_pubkey, b'unused')
capsule = MessageKit(delegating_pubkey, b'unused').capsule
cfrag = reencrypt(capsule, kfrags[0])
return capsule, cfrag

View File

@ -93,7 +93,6 @@ from tests.mock.performance_mocks import (
mock_record_fleet_state,
mock_remember_node,
mock_rest_app_creation,
mock_secret_source,
mock_verify_node
)
from tests.utils.blockchain import TesterBlockchain, token_airdrop
@ -940,7 +939,6 @@ def get_random_checksum_address():
def fleet_of_highperf_mocked_ursulas(ursula_federated_test_config, request):
mocks = (
mock_secret_source(),
mock_cert_storage,
mock_cert_loading,
mock_rest_app_creation,

View File

@ -24,7 +24,7 @@ import maya
import pytest
from click.testing import CliRunner
from nucypher.core import MessageKit, EncryptedTreasureMap
from nucypher_core import MessageKit, EncryptedTreasureMap
import nucypher
from nucypher.crypto.powers import DecryptingPower

View File

@ -19,7 +19,7 @@ import pytest
import pytest_twisted
from twisted.internet import threads
from nucypher.core import RetrievalKit
from nucypher_core import RetrievalKit
from nucypher.characters.lawful import Enrico, Bob
from nucypher.config.constants import TEMPORARY_DOMAIN
@ -52,7 +52,7 @@ def test_retrieval_kit(enacted_federated_policy, federated_ursulas):
messages, message_kits = _make_message_kits(enacted_federated_policy.public_key)
capsule = message_kits[0].capsule
addresses = [ursula.checksum_address for ursula in list(federated_ursulas)[:2]]
addresses = {ursula.canonical_address for ursula in list(federated_ursulas)[:2]}
retrieval_kit = RetrievalKit(capsule, addresses)
serialized = bytes(retrieval_kit)

View File

@ -21,7 +21,7 @@ import datetime
import maya
import pytest
from nucypher.core import EncryptedKeyFrag, RevocationOrder
from nucypher_core import EncryptedKeyFrag, RevocationOrder
from nucypher.characters.lawful import Enrico
from nucypher.crypto.utils import keccak_digest
@ -48,8 +48,8 @@ def test_federated_grant(federated_alice, federated_bob, federated_ursulas):
# Let's look at the destinations.
for ursula in federated_ursulas:
if ursula.checksum_address in treasure_map.destinations:
kfrag_kit = treasure_map.destinations[ursula.checksum_address]
if ursula.canonical_address in treasure_map.destinations:
kfrag_kit = treasure_map.destinations[ursula.canonical_address]
# TODO: Deeper testing here: try to decrypt?
# TODO: Use a new type for EncryptedKFrags?

View File

@ -22,11 +22,12 @@ import datetime
import maya
import pytest
from nucypher.core import (
from nucypher_core import (
MessageKit,
EncryptedTreasureMap as EncryptedTreasureMapClass,
TreasureMap as TreasureMapClass,
)
from nucypher_core.umbral import PublicKey
from nucypher.characters.control.specifications import fields
from nucypher.characters.control.specifications.alice import GrantPolicy
@ -34,7 +35,15 @@ from nucypher.characters.control.specifications.fields.treasuremap import Encryp
from nucypher.control.specifications.base import BaseSchema
from nucypher.control.specifications.exceptions import SpecificationError, InvalidInputData, InvalidArgumentCombo
from nucypher.crypto.powers import DecryptingPower
from nucypher.crypto.umbral_adapter import PublicKey
def make_header(brand: bytes, major: int, minor: int) -> bytes:
# Hardcoding this since it's too much trouble to expose it all the way from Rust
assert len(brand) == 4
major_bytes = major.to_bytes(2, 'big')
minor_bytes = minor.to_bytes(2, 'big')
header = brand + major_bytes + minor_bytes
return header
def test_various_field_validations_by_way_of_alice_grant(federated_bob):
@ -94,14 +103,14 @@ def test_treasure_map_validation(enacted_federated_policy,
assert "Invalid base64-encoded string" in str(e)
# valid base64 but invalid treasuremap
bad_map = EncryptedTreasureMapClass._header() + b"your face looks like a treasure map"
bad_map = make_header(b'EMap', 1, 0) + b"your face looks like a treasure map"
bad_map_b64 = base64.b64encode(bad_map).decode()
with pytest.raises(InvalidInputData) as e:
EncryptedTreasureMapsOnly().load({'tmap': bad_map_b64})
assert "Could not convert input for tmap to an EncryptedTreasureMap" in str(e)
assert "Can't split a message with more bytes than the original splittable." in str(e)
assert "Failed to deserialize" in str(e)
# a valid treasuremap for once...
tmap_bytes = bytes(enacted_federated_policy.treasure_map)
@ -124,14 +133,14 @@ def test_treasure_map_validation(enacted_federated_policy,
assert "Invalid base64-encoded string" in str(e)
# valid base64 but invalid treasuremap
bad_map = TreasureMapClass._header() + b"your face looks like a treasure map"
bad_map = make_header(b'TMap', 1, 0) + b"your face looks like a treasure map"
bad_map_b64 = base64.b64encode(bad_map).decode()
with pytest.raises(InvalidInputData) as e:
UnenncryptedTreasureMapsOnly().load({'tmap': bad_map_b64})
assert "Could not convert input for tmap to a TreasureMap" in str(e)
assert "Can't split a message with more bytes than the original splittable." in str(e)
assert "Failed to deserialize" in str(e)
# a valid treasuremap
decrypted_treasure_map = federated_bob._decrypt_treasure_map(enacted_federated_policy.treasure_map,
@ -158,14 +167,14 @@ def test_messagekit_validation(capsule_side_channel):
assert "Incorrect padding" in str(e)
# valid base64 but invalid messagekit
bad_kit = MessageKit._header() + b"I got a message for you"
bad_kit = make_header(b'MKit', 1, 0) + b"I got a message for you"
bad_kit_b64 = base64.b64encode(bad_kit).decode()
with pytest.raises(SpecificationError) as e:
MessageKitsOnly().load({'mkit': bad_kit_b64})
assert "Could not parse mkit" in str(e)
assert "Can't split a message with more bytes than the original splittable." in str(e)
assert "Failed to deserialize" in str(e)
# test a valid messagekit
valid_kit = capsule_side_channel.messages[0][0]

View File

@ -22,6 +22,7 @@ import pytest
import tempfile
from constant_sorrow.constants import CERTIFICATE_NOT_SAVED, NO_KEYSTORE_ATTACHED
from nucypher_core.umbral import SecretKey
from nucypher.blockchain.eth.actors import StakeHolder
from nucypher.characters.chaotic import Felix
@ -39,7 +40,6 @@ from nucypher.config.characters import (
from nucypher.config.constants import TEMPORARY_DOMAIN
from nucypher.config.storages import ForgetfulNodeStorage
from nucypher.crypto.keystore import Keystore
from nucypher.crypto.umbral_adapter import SecretKey
from tests.constants import INSECURE_DEVELOPMENT_PASSWORD
from tests.constants import MOCK_IP_ADDRESS

View File

@ -24,11 +24,12 @@ from constant_sorrow.constants import FEDERATED_ADDRESS
from cryptography.hazmat.primitives.serialization import Encoding
from flask import Flask
from nucypher_core.umbral import SecretKey, Signer
from nucypher.characters.lawful import Alice, Bob, Ursula
from nucypher.config.constants import TEMPORARY_DOMAIN
from nucypher.crypto.keystore import Keystore
from nucypher.crypto.powers import DecryptingPower, DelegatingPower, TLSHostingPower
from nucypher.crypto.umbral_adapter import SecretKey, Signer
from nucypher.datastore.datastore import Datastore
from nucypher.network.server import ProxyRESTServer
from nucypher.utilities.networking import LOOPBACK_ADDRESS

View File

@ -19,7 +19,7 @@ import tempfile
import pytest
from nucypher.core import NodeMetadata
from nucypher_core import NodeMetadata
from nucypher.characters.lawful import Ursula
from nucypher.config.constants import TEMPORARY_DOMAIN
@ -32,6 +32,15 @@ ADDITIONAL_NODES_TO_LEARN_ABOUT = 10
MOCK_URSULA_DB_FILEPATH = tempfile.mkdtemp()
def make_header(brand: bytes, major: int, minor: int) -> bytes:
# Hardcoding this since it's too much trouble to expose it all the way from Rust
assert len(brand) == 4
major_bytes = major.to_bytes(2, 'big')
minor_bytes = minor.to_bytes(2, 'big')
header = brand + major_bytes + minor_bytes
return header
class BaseTestNodeStorageBackends:
@pytest.fixture(scope='class')
@ -112,7 +121,7 @@ class TestTemporaryFileBasedNodeStorage(BaseTestNodeStorageBackends):
# Let's break the metadata (but not the version)
metadata_path = self.storage_backend.metadata_dir / some_node
with open(metadata_path, 'wb') as file:
file.write(NodeMetadata._header() + b'invalid')
file.write(make_header(b'NdMd', 1, 0) + b'invalid')
with pytest.raises(TemporaryFileBasedNodeStorage.InvalidNodeMetadata):
self.storage_backend.get(stamp=some_node.name[:-5],
@ -122,7 +131,7 @@ class TestTemporaryFileBasedNodeStorage(BaseTestNodeStorageBackends):
# Let's break the metadata, by putting a completely wrong version
metadata_path = self.storage_backend.metadata_dir / another_node
with open(metadata_path, 'wb') as file:
full_header = NodeMetadata._header()
full_header = make_header(b'NdMd', 1, 0)
file.write(full_header[:-1]) # Not even a valid header
with pytest.raises(TemporaryFileBasedNodeStorage.InvalidNodeMetadata):

View File

@ -25,9 +25,10 @@ import maya
import pytest
from flask import Response
from nucypher_core.umbral import SecretKey, Signer, PublicKey
from nucypher.characters.lawful import Ursula
from nucypher.crypto.signing import SignatureStamp
from nucypher.crypto.umbral_adapter import SecretKey, Signer, PublicKey, encrypt
from nucypher.datastore.base import RecordField
from nucypher.network.nodes import Teacher
from tests.markers import skip_on_circleci
@ -39,7 +40,6 @@ from tests.mock.performance_mocks import (
mock_cert_storage,
mock_message_verification,
mock_metadata_validation,
mock_pubkey_from_bytes,
mock_secret_source,
mock_verify_node
)
@ -65,7 +65,7 @@ performance bottlenecks.
"""
@skip_on_circleci # TODO: #2552 Taking 6-10 seconds on CircleCI, passing locally.
#@skip_on_circleci # TODO: #2552 Taking 6-10 seconds on CircleCI, passing locally.
def test_alice_can_learn_about_a_whole_bunch_of_ursulas(highperf_mocked_alice):
# During the fixture execution, Alice verified one node.
# TODO: Consider changing this - #1449
@ -102,24 +102,16 @@ def test_alice_can_learn_about_a_whole_bunch_of_ursulas(highperf_mocked_alice):
_POLICY_PRESERVER = []
@skip_on_circleci # TODO: #2552 Taking 6-10 seconds on CircleCI, passing locally.
#@skip_on_circleci # TODO: #2552 Taking 6-10 seconds on CircleCI, passing locally.
def test_alice_verifies_ursula_just_in_time(fleet_of_highperf_mocked_ursulas,
highperf_mocked_alice,
highperf_mocked_bob):
def mock_encrypt(public_key, plaintext):
if not isinstance(public_key, PublicKey):
public_key = public_key.i_want_to_be_a_real_boy()
return encrypt(public_key, plaintext)
mocks = (
patch('nucypher.crypto.umbral_adapter.PublicKey.__eq__', lambda *args, **kwargs: True),
mock_pubkey_from_bytes(),
mock_secret_source(),
mock_cert_loading,
mock_metadata_validation,
mock_message_verification,
patch('nucypher.crypto.umbral_adapter.encrypt', new=mock_encrypt),
)
with contextlib.ExitStack() as stack:

View File

@ -18,7 +18,7 @@
import pytest
from nucypher.core import HRAC
from nucypher_core import HRAC
from nucypher.characters.lawful import Ursula
from nucypher.crypto.utils import keccak_digest
@ -29,9 +29,9 @@ def test_alice_creates_policy_with_correct_hrac(federated_alice, federated_bob,
Alice creates a Policy. It has the proper HRAC, unique per her, Bob, and the label
"""
# TODO: what are we actually testing here?
assert idle_federated_policy.hrac == HRAC.derive(federated_alice.stamp.as_umbral_pubkey(),
federated_bob.stamp.as_umbral_pubkey(),
idle_federated_policy.label)
assert idle_federated_policy.hrac == HRAC(federated_alice.stamp.as_umbral_pubkey(),
federated_bob.stamp.as_umbral_pubkey(),
idle_federated_policy.label)
def test_alice_does_not_update_with_old_ursula_info(federated_alice, federated_ursulas):

View File

@ -18,7 +18,7 @@ from base64 import b64decode
import pytest
from nucypher.core import HRAC, TreasureMap
from nucypher_core import HRAC, TreasureMap
from nucypher.crypto.powers import DecryptingPower
@ -30,19 +30,19 @@ def random_federated_treasure_map_data(federated_alice, federated_bob, federated
threshold = 2
shares = threshold + 1
policy_key, kfrags = federated_alice.generate_kfrags(bob=federated_bob, label=label, threshold=threshold, shares=shares)
hrac = HRAC.derive(publisher_verifying_key=federated_alice.stamp.as_umbral_pubkey(),
bob_verifying_key=federated_bob.stamp.as_umbral_pubkey(),
label=label)
hrac = HRAC(publisher_verifying_key=federated_alice.stamp.as_umbral_pubkey(),
bob_verifying_key=federated_bob.stamp.as_umbral_pubkey(),
label=label)
assigned_kfrags = {
ursula.checksum_address: (ursula.public_keys(DecryptingPower), vkfrag)
ursula.canonical_address: (ursula.public_keys(DecryptingPower), vkfrag)
for ursula, vkfrag in zip(list(federated_ursulas)[:shares], kfrags)}
random_treasure_map = TreasureMap.construct_by_publisher(signer=federated_alice.stamp.as_umbral_signer(),
hrac=hrac,
policy_encrypting_key=policy_key,
assigned_kfrags=assigned_kfrags,
threshold=threshold)
random_treasure_map = TreasureMap(signer=federated_alice.stamp.as_umbral_signer(),
hrac=hrac,
policy_encrypting_key=policy_key,
assigned_kfrags=assigned_kfrags,
threshold=threshold)
bob_key = federated_bob.public_keys(DecryptingPower)
enc_treasure_map = random_treasure_map.encrypt(signer=federated_alice.stamp.as_umbral_signer(),

View File

@ -20,7 +20,7 @@ import json
from base64 import b64encode
from urllib.parse import urlencode
from nucypher.core import RetrievalKit
from nucypher_core import RetrievalKit
from nucypher.characters.lawful import Enrico
from nucypher.crypto.powers import DecryptingPower
@ -144,7 +144,7 @@ def test_retrieve_cfrags(federated_porter,
assert policy_message_kit.is_decryptable_by_receiver()
cleartext = federated_bob._crypto_power.power_ups(DecryptingPower).keypair.decrypt(policy_message_kit)
cleartext = federated_bob._crypto_power.power_ups(DecryptingPower).keypair.decrypt_message_kit(policy_message_kit)
assert cleartext == original_message
#

View File

@ -18,9 +18,10 @@ import random
import pytest
from nucypher_core.umbral import SecretKey
from nucypher.characters.control.specifications.fields import Key
from nucypher.control.specifications.exceptions import InvalidArgumentCombo, InvalidInputData
from nucypher.crypto.umbral_adapter import SecretKey
from nucypher.utilities.porter.control.specifications.fields import UrsulaInfoSchema, RetrievalResultSchema
from nucypher.utilities.porter.control.specifications.porter_schema import (
AliceGetUrsulas,
@ -199,6 +200,7 @@ def test_bob_retrieve_cfrags(federated_porter,
retrieval_results = federated_porter.retrieve_cfrags(**non_encoded_retrieval_args)
expected_retrieval_results_json = []
retrieval_result_schema = RetrievalResultSchema()
for result in retrieval_results:
data = retrieval_result_schema.dump(result)
expected_retrieval_results_json.append(data)

View File

@ -20,24 +20,23 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
import json
import io
import os
import re
from pathlib import Path
from unittest.mock import Mock
import tabulate
import time
from twisted.logger import ILogObserver, globalLogPublisher, jsonFileLogObserver
from web3.contract import Contract
from zope.interface import provider
from nucypher_core.umbral import SecretKey, Signer
from nucypher.blockchain.eth.registry import InMemoryContractRegistry
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.crypto.powers import TransactingPower
from unittest.mock import Mock
from zope.interface import provider
from nucypher.blockchain.economics import StandardTokenEconomics
from nucypher.blockchain.eth.agents import (
AdjudicatorAgent,
@ -47,7 +46,6 @@ from nucypher.blockchain.eth.agents import (
)
from nucypher.blockchain.eth.constants import NUCYPHER_CONTRACT_NAMES, NULL_ADDRESS, POLICY_ID_LENGTH
from nucypher.crypto.signing import SignatureStamp
from nucypher.crypto.umbral_adapter import SecretKey, Signer
from nucypher.exceptions import DevelopmentInstallationRequired
from nucypher.policy.policies import Policy
from nucypher.utilities.logging import Logger

View File

@ -38,10 +38,11 @@ from typing import Set, Optional, List, Tuple
from web3.main import Web3
from web3.types import Wei
from nucypher_core.umbral import SecretKey
from nucypher.network.middleware import RestMiddleware
from nucypher.characters.lawful import Bob, Ursula, Alice
from nucypher.config.characters import AliceConfiguration
from nucypher.crypto.umbral_adapter import SecretKey
from nucypher.policy.policies import Policy
from nucypher.utilities.logging import GlobalLoggerSettings

View File

@ -18,7 +18,8 @@
from contextlib import contextmanager
from unittest.mock import patch
from nucypher.crypto.umbral_adapter import PublicKey, Signature
from nucypher_core.umbral import PublicKey, Signature
from nucypher.network.server import make_rest_app
from tests.mock.serials import good_serials
@ -173,8 +174,7 @@ class NotARestApp:
def actual_rest_app(self):
if self._actual_rest_app is None:
self._actual_rest_app, self._datastore = make_rest_app(db_filepath=self.db_filepath,
this_node=self.this_node,
domain=None)
this_node=self.this_node)
_new_view_functions = self._ViewFunctions(self._actual_rest_app.view_functions)
self._actual_rest_app.view_functions = _new_view_functions
self._actual_rest_apps.append(
@ -218,13 +218,6 @@ def mock_secret_source(*args, **kwargs):
NotAPublicKey.reset()
@contextmanager
def mock_pubkey_from_bytes(*args, **kwargs):
with patch('nucypher.crypto.umbral_adapter.PublicKey.from_bytes', NotAPublicKey.from_bytes):
yield
NotAPublicKey.reset()
def _determine_good_serials(start, end):
'''
Figure out which serials are good to use in mocks because they won't result in non-viable public keys.

View File

@ -20,15 +20,16 @@ from base64 import b64encode, b64decode
import maya
import pytest
from nucypher.core import MessageKit as MessageKitClass
from nucypher_core import (
MessageKit as MessageKitClass,
EncryptedTreasureMap as EncryptedTreasureMapClass)
from nucypher_core.umbral import SecretKey, Signer
from nucypher.crypto.umbral_adapter import SecretKey, Signer
from nucypher.characters.control.specifications.fields import (
DateTime,
FileField,
Key,
MessageKit,
UmbralSignature,
EncryptedTreasureMap
)
from nucypher.characters.lawful import Enrico
@ -126,36 +127,13 @@ def test_message_kit(enacted_federated_policy, federated_alice):
assert serialized == b64encode(bytes(message_kit)).decode()
deserialized = field._deserialize(value=serialized, attr=None, data=None)
assert deserialized == message_kit
deserialized_plaintext = federated_alice.decrypt_message_kit(enacted_federated_policy.label, deserialized)[0]
assert deserialized_plaintext == plaintext_bytes
with pytest.raises(InvalidInputData):
field._deserialize(value=b"MessageKit", attr=None, data=None)
def test_umbral_signature():
umbral_priv_key = SecretKey.random()
signer = Signer(umbral_priv_key)
message = b'this is a message'
signature = signer.sign(message)
other_signature = signer.sign(b'this is a different message')
field = UmbralSignature()
serialized = field._serialize(value=signature, attr=None, obj=None)
assert serialized == b64encode(bytes(signature)).decode()
assert serialized != b64encode(bytes(other_signature)).decode()
deserialized = field._deserialize(value=serialized, attr=None, data=None)
assert deserialized == signature
assert deserialized != other_signature
field._validate(value=bytes(signature))
field._validate(value=bytes(other_signature))
with pytest.raises(InvalidInputData):
field._validate(value=b"UmbralSignature")
def test_treasure_map(enacted_federated_policy):
treasure_map = enacted_federated_policy.treasure_map
@ -164,7 +142,8 @@ def test_treasure_map(enacted_federated_policy):
assert serialized == b64encode(bytes(treasure_map)).decode()
deserialized = field._deserialize(value=serialized, attr=None, data=None)
assert deserialized == treasure_map
assert isinstance(deserialized, EncryptedTreasureMapClass)
assert bytes(deserialized) == bytes(treasure_map)
with pytest.raises(InvalidInputData):
field._deserialize(value=b64encode(b"TreasureMap").decode(), attr=None, data=None)

View File

@ -18,8 +18,9 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
import sha3
from constant_sorrow.constants import PUBLIC_ONLY
from nucypher_core.umbral import SecretKey
from nucypher.crypto import keypairs
from nucypher.crypto.umbral_adapter import SecretKey
def test_gen_keypair_if_needed():

View File

@ -26,6 +26,8 @@ from constant_sorrow.constants import KEYSTORE_LOCKED
from cryptography.hazmat.primitives._serialization import Encoding
from mnemonic.mnemonic import Mnemonic
from nucypher_core.umbral import SecretKey, SecretKeyFactory
from nucypher.crypto.keystore import (
Keystore,
InvalidPassword,
@ -41,7 +43,6 @@ from nucypher.crypto.keystore import (
_read_keystore
)
from nucypher.crypto.powers import DecryptingPower, SigningPower, DelegatingPower, TLSHostingPower
from nucypher.crypto.umbral_adapter import SecretKey, SecretKeyFactory
from nucypher.utilities.networking import LOOPBACK_ADDRESS
from tests.constants import INSECURE_DEVELOPMENT_PASSWORD

View File

@ -15,7 +15,7 @@ You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
from nucypher.core import MessageKit
from nucypher_core import MessageKit
from nucypher.characters.lawful import Enrico

View File

@ -15,9 +15,14 @@
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
from eth_utils import to_checksum_address
import pytest
from nucypher_core import NodeMetadata, NodeMetadataPayload
from nucypher_core.umbral import SecretKey, Signer
from nucypher.acumen.perception import FleetSensor
from nucypher.blockchain.eth.constants import LENGTH_ECDSA_SIGNATURE_WITH_RECOVERY
from nucypher.characters.lawful import Ursula
from nucypher.network.exceptions import NodeSeemsToBeDown
from nucypher.network.middleware import NucypherMiddlewareClient
@ -37,8 +42,9 @@ MOCK_NETWORK = 'holodeck'
class Dummy: # Teacher
def __init__(self, checksum_address):
self.checksum_address = checksum_address
def __init__(self, canonical_address):
self.canonical_address = canonical_address
self.checksum_address = to_checksum_address(canonical_address)
self.certificate_filepath = None
self.domain = MOCK_NETWORK
@ -61,7 +67,19 @@ class Dummy: # Teacher
return MOCK_IP_ADDRESS
def metadata(self):
return self.checksum_address.encode()
signer = Signer(SecretKey.random())
payload = NodeMetadataPayload(canonical_address=self.canonical_address,
domain=':dummy:',
timestamp_epoch=0,
decentralized_identity_evidence=b'\x00' * LENGTH_ECDSA_SIGNATURE_WITH_RECOVERY,
verifying_key=signer.verifying_key(),
encrypting_key=SecretKey.random().public_key(),
certificate_bytes=b'not a certificate',
host='127.0.0.1',
port=1111,
)
return NodeMetadata(signer=signer,
payload=payload)
@pytest.fixture(autouse=True)
@ -97,7 +115,7 @@ def test_get_external_ip_from_empty_known_nodes(mock_requests):
def test_get_external_ip_from_known_nodes_with_one_known_node(mock_requests):
sensor = FleetSensor(domain=MOCK_NETWORK)
sensor.record_node(Dummy('0xdeadbeef'))
sensor.record_node(Dummy(b'deadbeefdeadbeefdead'))
sensor.record_fleet_state()
assert len(sensor) == 1
get_external_ip_from_known_nodes(known_nodes=sensor)
@ -110,9 +128,9 @@ def test_get_external_ip_from_known_nodes(mock_client):
# Setup FleetSensor
sensor = FleetSensor(domain=MOCK_NETWORK)
sample_size = 3
sensor.record_node(Dummy('0xdeadbeef'))
sensor.record_node(Dummy('0xdeadllama'))
sensor.record_node(Dummy('0xdeadmouse'))
sensor.record_node(Dummy(b'deadbeefdeadbeefdead'))
sensor.record_node(Dummy(b'deadllamadeadllamade'))
sensor.record_node(Dummy(b'deadmousedeadmousede'))
sensor.record_fleet_state()
assert len(sensor) == sample_size
@ -132,14 +150,14 @@ def test_get_external_ip_from_known_nodes_client(mocker, mock_client):
# Setup FleetSensor
sensor = FleetSensor(domain=MOCK_NETWORK)
sample_size = 3
sensor.record_node(Dummy('0xdeadbeef'))
sensor.record_node(Dummy('0xdeadllama'))
sensor.record_node(Dummy('0xdeadmouse'))
sensor.record_node(Dummy(b'deadbeefdeadbeefdead'))
sensor.record_node(Dummy(b'deadllamadeadllamade'))
sensor.record_node(Dummy(b'deadmousedeadmousede'))
sensor.record_fleet_state()
assert len(sensor) == sample_size
# Setup HTTP Client
mocker.patch.object(Ursula, 'from_teacher_uri', return_value=Dummy('0xdeadpork'))
mocker.patch.object(Ursula, 'from_teacher_uri', return_value=Dummy(b'deadporkdeadporkdead'))
teacher_uri = TEACHER_NODES[MOCK_NETWORK][0]
get_external_ip_from_known_nodes(known_nodes=sensor, sample_size=sample_size)
@ -162,7 +180,7 @@ def test_get_external_ip_from_default_teacher(mocker, mock_client, mock_requests
mock_client.return_value = Dummy.GoodResponse
teacher_uri = TEACHER_NODES[MOCK_NETWORK][0]
mocker.patch.object(Ursula, 'from_teacher_uri', return_value=Dummy('0xdeadbeef'))
mocker.patch.object(Ursula, 'from_teacher_uri', return_value=Dummy(b'deadbeefdeadbeefdead'))
# "Success"
ip = get_external_ip_from_default_teacher(network=MOCK_NETWORK)
@ -195,7 +213,7 @@ def test_get_external_ip_cascade_failure(mocker, mock_requests):
third = mocker.patch('nucypher.utilities.networking.get_external_ip_from_centralized_source', return_value=None)
sensor = FleetSensor(domain=MOCK_NETWORK)
sensor.record_node(Dummy('0xdeadbeef'))
sensor.record_node(Dummy(b'deadbeefdeadbeefdead'))
sensor.record_fleet_state()
with pytest.raises(UnknownIPAddress, match='External IP address detection failed'):

View File

@ -17,14 +17,15 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
from base64 import b64encode
from eth_utils import to_canonical_address
import pytest
from nucypher.core import RetrievalKit as RetrievalKitClass
from nucypher_core import RetrievalKit as RetrievalKitClass, MessageKit
from nucypher_core.umbral import SecretKey
from nucypher.control.specifications.exceptions import InvalidInputData
from nucypher.control.specifications.fields import StringList
from nucypher.crypto.umbral_adapter import SecretKey, encrypt
from nucypher.utilities.porter.control.specifications.fields import UrsulaChecksumAddress
from nucypher.utilities.porter.control.specifications.fields.retrieve import RetrievalKit
@ -103,14 +104,14 @@ def test_retrieval_kit_field(get_random_checksum_address):
# kit with list of ursulas
encrypting_key = SecretKey.random().public_key()
capsule, _ = encrypt(encrypting_key, b'testing retrieval kit with 2 ursulas')
capsule = MessageKit(encrypting_key, b'testing retrieval kit with 2 ursulas').capsule
ursulas = [get_random_checksum_address(), get_random_checksum_address()]
run_tests_on_kit(kit=RetrievalKitClass(capsule, ursulas))
run_tests_on_kit(kit=RetrievalKitClass(capsule, {to_canonical_address(ursula) for ursula in ursulas}))
# kit with no ursulas
encrypting_key = SecretKey.random().public_key()
capsule, _ = encrypt(encrypting_key, b'testing retrieval kit with no ursulas')
run_tests_on_kit(kit=RetrievalKitClass(capsule, []))
capsule = MessageKit(encrypting_key, b'testing retrieval kit with no ursulas').capsule
run_tests_on_kit(kit=RetrievalKitClass(capsule, set()))
with pytest.raises(InvalidInputData):
field._deserialize(value=b"non_base_64_data", attr=None, data=None)

View File

@ -1,107 +0,0 @@
"""
This file is part of nucypher.
nucypher is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
nucypher is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import pytest
from nucypher.core import HRAC, TreasureMap, EncryptedTreasureMap
from nucypher.crypto.powers import DecryptingPower
from nucypher.crypto.umbral_adapter import KeyFrag
def test_complete_treasure_map_journey(federated_alice, federated_bob, federated_ursulas, idle_federated_policy, mocker):
label = "chili con carne 🔥".encode('utf-8')
kfrags = idle_federated_policy.kfrags
ursulas = list(federated_ursulas)[:len(kfrags)]
hrac = HRAC.derive(publisher_verifying_key=federated_alice.stamp.as_umbral_pubkey(),
bob_verifying_key=federated_bob.stamp.as_umbral_pubkey(),
label=label)
assigned_kfrags = {
ursula.checksum_address: (ursula.public_keys(DecryptingPower), vkfrag)
for ursula, vkfrag in zip(ursulas, kfrags)}
treasure_map = TreasureMap.construct_by_publisher(signer=federated_alice.stamp.as_umbral_signer(),
hrac=hrac,
policy_encrypting_key=idle_federated_policy.public_key,
assigned_kfrags=assigned_kfrags,
threshold=1)
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]
auth_kfrag = ursula._decrypt_kfrag(encrypted_kfrag)
auth_kfrag.verify(hrac=treasure_map.hrac,
publisher_verifying_key=federated_alice.stamp.as_umbral_pubkey())
serialized_map = bytes(treasure_map)
# ...
deserialized_map = TreasureMap.from_bytes(serialized_map)
assert treasure_map.destinations == deserialized_map.destinations
assert treasure_map.hrac == deserialized_map.hrac
enc_treasure_map = treasure_map.encrypt(signer=federated_alice.stamp.as_umbral_signer(),
recipient_key=federated_bob.public_keys(DecryptingPower))
enc_serialized_map = bytes(enc_treasure_map)
# ...
enc_deserialized_map = EncryptedTreasureMap.from_bytes(enc_serialized_map)
decrypted_map = federated_bob._decrypt_treasure_map(enc_deserialized_map,
federated_alice.stamp.as_umbral_pubkey())
assert treasure_map.threshold == decrypted_map.threshold == 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+')
def test_treasure_map_versioning(mocker, federated_alice, federated_bob, federated_ursulas, idle_federated_policy):
# Produced using f04d564a1
map_from_previous_version = b'\x87T\x19\xceV_1\x8e\xb0\x87\xf6\xd9\x9d\x80\xba\xaf\xc4\x84\xa1\xd9|P=\x02\x13\xa0r1\x9eB\xf4\xfc\xc6w\xdf\xd1\x88\xc4\x83\x8f \x1c|\xec\xfcnW~k\x95f8\x19\r\xb1\xad\xe9\xa8\xc9\x06\x93j\xaf\xc50&[\xe5Cy\x9cr_R\xcd\xb1\xb1F\xed\x01\x00\x00\x02\xf4\x02][\xb8VP\xfa%D\xc3\xeb\xd4\x8b\xd2SW\x0f\xfe\xe5\x0f\xaa\xe6\x83\x9a\xa1\x91\xf6\x8e\xca\x00\x95\xf9\x90\x02-\x7f\xca\xe8$L\xcd0\x1d\xa1D\x80\xafjY\xea2\xbc\x04\x94\x1c\xd6E\xa4l\x8fu\xdf\x8a#\x04\xe1\x8eKN\xc9Y\xfbB7I\x9b\xa153\xcef\xfd\xb2/9[\x1b^\xe3\xcf\x08/\xf4%k\x06\xf4\xa5\x03\xfa\xf1\xdc\xec\xe1\t\xeb%\x0c\x11{\xbb\xc7Z\xb2^\x1d.\x18\xeaJ\xaa\xa6f\xd8\xb0\x92U\x84;\xbe6\x00\x00\x02m\x89\x97?\xcavL\xa7q\x13\x01\x1e\x1f6\x05)\xc2?\xcd\x96\xafhH/>6\x8d\x1a\xf8\xfd\xd5\x8a\xf9e\xb0\xc5\xa8\xbd(\x86\x9f\xb9L\xb9n=\xcb\xa0\xd2\t\x94\x90l\xc0\xb7\x85\x90N\xe0\xc9M{\x08\xc4\xf5\x80\xb7\xd1\x10\x18P\x8bl\x0f\x87fS\x836\xa6q\'\xabr\xd1l\x1e\xe2\xe7\xce\xccZ1[\x0b\xe7\xaa\x9c\x92Qh"2F\x1f\x9f-7HylC\xad\x03\x8ek_\xb6M\x19\xb2\xef\xde~\xa6\x10F<\xac\x94\xa6e\xc3\xb5\x132\x94\x96\xc4\xd9\'\xf9h\x1c\xe8\xb8Zm\x86M\xed\x00\x86\xc3\xf4\x93\x03/J\x1d6$\x1a\xe5+\xad\xf93\x17n\xc3\x19sQ2C\xaf\x9d\x89p\xb9557O\x9a\xc3O\xf0\x1f\xb3M.\xa9\x89\xeb\xb9\xf6\xe8\xcc@\xb0\\)\x9d\xdb\'\xfc\xc4_\xfd\xe1\xef\x01\xe3\xe7va\xac\xd7y\xb2\xcfm\xda\x85\x06(\x92H\xe2p\xf1\x9aw\xaf\x83\x1c\xd3@a\xaa\xf6\xee\xfc\xae&;\xdd*\x94I\'r1JG\xca\xdb\x9e\xef\x18Z\x9f\x15\x81\xe3\x1c\xcfJ\xd6;2H\xe8\xed\xfc\x98\x8e\xc6\x94\x1f\x1d\x95A\xa5\x8e\xe5\xc6f\x85\xbb\xc3\xd0\x9d\x83\xd3\xdf\x91]\x16\xe6)\xfa\xc0\xf3\xba\x7fAb\x81\xe0\x8f\x1bu0\x0b\x82^\xe9\x16\xf0\xfc\xc3p\xd4\x9f\'\xa6\xe5\xb4\xf7\xe1\x99\xa5\xfe\x12\x0e{L\xb0\xd6\xa1\x049\xcf\xe0\xca\x06\xe3\xd6u\x9e\xb3P\xb7\x1a\xc5X\xb7\xb2\xfa\x1dJ\xe1\xa9Gb\xf6l~DG\x8e5X\xc2^\x87\xac\x89W(\xaf\xd3\x15o\xde\xf7\xe4\x18\xd9\x98\xc3\tcL\xd3\x9dF\x8e3\xe5u\x03\x0b\xe7\tj\xdb\xd3B\xa1\x85\x9d \x9c\xa4{n\x01"\xab\xe1509\xdaoL\xc9\x8d\xc9\xfd"\xad\xd8\xfd\xf5\x14\xa2\xa8N\xf5\xa0\xf4\x04Y\x85i\xe0zj34\xc9\xbd\xac\xb9gn\x19J]\x0eL\x81C\xb9\x95\x86Q,\x81\xdf\xcbh\x13\xae8\xe8\x06y\xd1\xcd\x867\x1a\x1c\xe1\x05\xba\xfaL\x1a\x1f\x9f~\x18O1p@\xee\xee\xc4\xed\x84%\xb4\xb4\x12\xb6\x81\x0c\xcamf.\x9c\xe1\xfe\xc4\x87I\'\xc7e\xc1\x7f\xeb\x9c\xe1\xca\xa5\r.\x15\xa8r\xa8\x82Q\x13\x99K\x12X3\x04\xbc\x99\x96\xf8\xc3\x1es\x0c\x85\x8d\xd3\xee\x1b^\xc8\xf5\x1d^\x1a&6#\xbc\xa8~wp}]8\xb5\xe6v\xa4D\xfe:\xb8<q\xd9\x02\xfa\x7f\xcfWA\xad\xd1#\xac\x8b\xd7\xff\xca\xf7[dm\x9b\x06\xcc\x03\x1b\xfa\xd1\xf6:\xad\x1c\xb6\xb8'
kfrags = idle_federated_policy.kfrags[:3]
hrac = HRAC.derive(publisher_verifying_key=federated_alice.stamp.as_umbral_pubkey(),
bob_verifying_key=federated_bob.stamp.as_umbral_pubkey(),
label=label)
assigned_kfrags = {
ursula.checksum_address: (ursula.public_keys(DecryptingPower), vkfrag)
for ursula, vkfrag in zip(list(federated_ursulas)[:len(kfrags)], kfrags)}
treasure_map = TreasureMap.construct_by_publisher(signer=federated_alice.stamp.as_umbral_signer(),
hrac=hrac,
policy_encrypting_key=idle_federated_policy.public_key,
assigned_kfrags=assigned_kfrags,
threshold=2)
# Good version (baseline)
serialized_map = bytes(treasure_map)
deserialized_map = TreasureMap.from_bytes(serialized_map)
assert treasure_map == deserialized_map
map_from_f04d564a1 = TreasureMap.from_bytes(map_from_previous_version)
assert map_from_f04d564a1.public_verify()

View File

@ -1,236 +0,0 @@
"""
This file is part of nucypher.
nucypher is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
nucypher is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import re
from typing import Tuple, Any, Type
import pytest
from nucypher.utilities.versioning import Versioned
def _check_valid_version_tuple(version: Any, cls: Type):
if not isinstance(version, tuple):
pytest.fail(f"Old version handlers keys for {cls.__name__} must be a tuple")
if not len(version) == Versioned._VERSION_PARTS:
pytest.fail(f"Old version handlers keys for {cls.__name__} must be a {str(Versioned._VERSION_PARTS)}-tuple")
if not all(isinstance(part, int) for part in version):
pytest.fail(f"Old version handlers version parts {cls.__name__} must be integers")
class A(Versioned):
def __init__(self, x: int):
self.x = x
@classmethod
def _brand(cls):
return b"ABCD"
@classmethod
def _version(cls) -> Tuple[int, int]:
return 2, 1
def _payload(self) -> bytes:
return self.x.to_bytes(1, 'big')
@classmethod
def _old_version_handlers(cls):
return {
(2, 0): cls._from_bytes_v2_0,
}
@classmethod
def _from_bytes_v2_0(cls, data):
# v2.0 saved a 4 byte integer in hex format
int_hex, remainder = data[:2], data[2:]
int_bytes = bytes.fromhex(int_hex.decode())
return cls(int.from_bytes(int_bytes, 'big')), remainder
@classmethod
def _from_bytes_current(cls, data):
# v2.1 saves a 4 byte integer as 4 bytes
int_bytes, remainder = data[:1], data[1:]
return cls(int.from_bytes(int_bytes, 'big')), remainder
def test_unique_branding():
brands = tuple(v._brand() for v in Versioned.__subclasses__())
brands_set = set(brands)
if len(brands) != len(brands_set):
duplicate_brands = list(brands)
for brand in brands_set:
duplicate_brands.remove(brand)
pytest.fail(f"Duplicated brand(s) {duplicate_brands}.")
def test_valid_branding():
for cls in Versioned.__subclasses__():
if len(cls._brand()) != cls._BRAND_SIZE:
pytest.fail(f"Brand must be exactly {str(Versioned._BRAND_SIZE)} bytes.")
if not re.fullmatch(rb'\w+', cls._brand()):
pytest.fail(f"Brand must be alphanumeric; Got {cls._brand()}")
def test_valid_version_implementation():
for cls in Versioned.__subclasses__():
_check_valid_version_tuple(version=cls._version(), cls=cls)
def test_valid_old_handlers_index():
for cls in Versioned.__subclasses__():
for version in cls._deserializers():
_check_valid_version_tuple(version=version, cls=cls)
def test_version_metadata():
major, minor = A._version()
assert A.version_string() == f'{major}.{minor}'
def test_versioning_header_prepend():
a = A(1) # stake sauce
assert a.x == 1
serialized = bytes(a)
assert len(serialized) > Versioned._HEADER_SIZE
header = serialized[:Versioned._HEADER_SIZE]
brand = header[:Versioned._BRAND_SIZE]
assert brand == A._brand()
version = header[Versioned._BRAND_SIZE:]
major, minor = version[:Versioned._VERSION_PART_SIZE], version[Versioned._VERSION_PART_SIZE:]
major_number = int.from_bytes(major, 'big')
minor_number = int.from_bytes(minor, 'big')
assert (major_number, minor_number) == A._version()
def test_versioning_input_too_short():
empty = b'ABCD\x00\x01'
with pytest.raises(ValueError, match='Invalid bytes for A.'):
A.from_bytes(empty)
def test_versioning_empty_payload():
empty = b'ABCD\x00\x02\x00\x01'
with pytest.raises(ValueError, match='No content to deserialize A.'):
A.from_bytes(empty)
def test_versioning_invalid_brand():
invalid = b'\x01\x02\x00\x03\x00\x0112'
with pytest.raises(Versioned.InvalidHeader, match="Incompatible bytes for A."):
A.from_bytes(invalid)
# A partially invalid brand, to check that the regexp validates
# the whole brand and not just the beginning of it.
invalid = b'ABC \x00\x02\x00\x0112'
with pytest.raises(Versioned.InvalidHeader, match="Incompatible bytes for A."):
A.from_bytes(invalid)
def test_versioning_incorrect_brand():
incorrect = b'ABAB\x00\x0112'
with pytest.raises(Versioned.InvalidHeader, match="Incorrect brand. Expected b'ABCD', Got b'ABAB'."):
A.from_bytes(incorrect)
def test_unknown_future_major_version():
empty = b'ABCD\x00\x03\x00\x0212'
message = 'Incompatible versioned bytes for A. Compatible version is 2.x, Got 3.2.'
with pytest.raises(ValueError, match=message):
A.from_bytes(empty)
def test_incompatible_old_major_version(mocker):
current_spy = mocker.spy(A, "_from_bytes_current")
v1_data = b'ABCD\x00\x01\x00\x0012'
message = 'Incompatible versioned bytes for A. Compatible version is 2.x, Got 1.0.'
with pytest.raises(Versioned.IncompatibleVersion, match=message):
A.from_bytes(v1_data)
assert not current_spy.call_count
def test_incompatible_future_major_version(mocker):
current_spy = mocker.spy(A, "_from_bytes_current")
v1_data = b'ABCD\x00\x03\x00\x0012'
message = 'Incompatible versioned bytes for A. Compatible version is 2.x, Got 3.0.'
with pytest.raises(Versioned.IncompatibleVersion, match=message):
A.from_bytes(v1_data)
assert not current_spy.call_count
def test_resolve_version():
# past
v2_0 = 2, 0
resolved_version = A._resolve_version(version=v2_0)
assert resolved_version == v2_0
# present
v2_1 = 2, 1
resolved_version = A._resolve_version(version=v2_1)
assert resolved_version == v2_1
# future minor version resolves to the latest minor version.
v2_2 = 2, 2
resolved_version = A._resolve_version(version=v2_2)
assert resolved_version == v2_1
def test_old_minor_version_handler_routing(mocker):
current_spy = mocker.spy(A, "_from_bytes_current")
v2_0_spy = mocker.spy(A, "_from_bytes_v2_0")
# Old minor version
v2_0_data = b'ABCD\x00\x02\x00\x0012'
a = A.from_bytes(v2_0_data)
assert a.x == 18
# Old minor version was correctly routed to the v2.0 handler.
assert v2_0_spy.call_count == 1
v2_0_spy.assert_called_with(b'12')
assert not current_spy.call_count
def test_current_minor_version_handler_routing(mocker):
current_spy = mocker.spy(A, "_from_bytes_current")
v2_0_spy = mocker.spy(A, "_from_bytes_v2_0")
v2_1_data = b'ABCD\x00\x02\x00\x01\x12'
a = A.from_bytes(v2_1_data)
assert a.x == 18
# Current version was correctly routed to the v2.1 handler.
assert current_spy.call_count == 1
current_spy.assert_called_with(b'\x12')
assert not v2_0_spy.call_count
def test_future_minor_version_handler_routing(mocker):
current_spy = mocker.spy(A, "_from_bytes_current")
v2_0_spy = mocker.spy(A, "_from_bytes_v2_0")
v2_2_data = b'ABCD\x00\x02\x02\x01\x12'
a = A.from_bytes(v2_2_data)
assert a.x == 18
# Future minor version was correctly routed to
# the current minor version handler.
assert current_spy.call_count == 1
current_spy.assert_called_with(b'\x12')
assert not v2_0_spy.call_count

View File

@ -23,7 +23,7 @@ import socket
from constant_sorrow.constants import CERTIFICATE_NOT_SAVED
from flask import Response
from nucypher.core import MetadataRequest
from nucypher_core import MetadataRequest, FleetStateChecksum
from nucypher.characters.lawful import Ursula
from nucypher.config.constants import TEMPORARY_DOMAIN
@ -186,7 +186,7 @@ class EvilMiddleWare(MockRestMiddleware):
"""
Try to get Ursula to propagate a malicious (or otherwise shitty) interface ID.
"""
fleet_state_checksum = os.urandom(32).hex()
fleet_state_checksum = FleetStateChecksum(this_node=None, other_nodes=[])
request = MetadataRequest(fleet_state_checksum=fleet_state_checksum, announce_nodes=[shitty_metadata])
response = self.client.post(node_or_sprout=ursula,
path="node_metadata",

View File

@ -20,7 +20,7 @@ import random
import string
from typing import Dict, Tuple
from nucypher.core import MessageKit, RetrievalKit
from nucypher_core import MessageKit, RetrievalKit
from nucypher.characters.control.specifications.fields import Key, TreasureMap
from nucypher.characters.lawful import Enrico

View File

@ -21,12 +21,13 @@ from typing import Iterable, List, Optional, Set
from cryptography.x509 import Certificate
from nucypher_core.umbral import SecretKey, Signer, generate_kfrags
from nucypher.blockchain.eth.actors import Staker
from nucypher.blockchain.eth.interfaces import BlockchainInterface
from nucypher.characters.lawful import Bob
from nucypher.characters.lawful import Ursula
from nucypher.config.characters import UrsulaConfiguration
from nucypher.crypto.umbral_adapter import SecretKey, Signer, encrypt, generate_kfrags, reencrypt
from tests.constants import NUMBER_OF_URSULAS_IN_DEVELOPMENT_NETWORK
from tests.mock.datastore import MOCK_DB