From 2ab9c303d4022b7d0670865524fa68aab7c44fb0 Mon Sep 17 00:00:00 2001 From: "Kieran R. Prasch" Date: Sat, 1 Jun 2019 15:34:37 -0700 Subject: [PATCH] Ensure node discovery verification and identity_evidence remian compadible with federated nodes --- nucypher/characters/lawful.py | 2 +- nucypher/cli/config.py | 1 - nucypher/config/keyring.py | 16 +++-- nucypher/config/node.py | 3 +- nucypher/crypto/api.py | 2 +- nucypher/crypto/signing.py | 2 + nucypher/network/nodes.py | 68 +++++++++++-------- ...t_ursula_prepares_to_act_as_mining_node.py | 2 +- tests/cli/test_felix.py | 1 - tests/learning/test_fault_tolerance.py | 4 +- 10 files changed, 57 insertions(+), 44 deletions(-) diff --git a/nucypher/characters/lawful.py b/nucypher/characters/lawful.py index 3f4bce189..1e4260a3c 100644 --- a/nucypher/characters/lawful.py +++ b/nucypher/characters/lawful.py @@ -880,7 +880,7 @@ class Ursula(Teacher, Character, Miner): version = self.TEACHER_VERSION.to_bytes(2, "big") interface_info = VariableLengthBytestring(bytes(self.rest_information()[0])) - identity_evidence = VariableLengthBytestring(self._evidence_of_decentralized_identity) + identity_evidence = VariableLengthBytestring(self._identity_evidence) certificate = self.rest_server_certificate() cert_vbytes = VariableLengthBytestring(certificate.public_bytes(Encoding.PEM)) diff --git a/nucypher/cli/config.py b/nucypher/cli/config.py index 69ceb0142..b6abe70a8 100644 --- a/nucypher/cli/config.py +++ b/nucypher/cli/config.py @@ -21,7 +21,6 @@ import collections import os import click -import requests from constant_sorrow.constants import NO_PASSWORD, NO_BLOCKCHAIN_CONNECTION from nacl.exceptions import CryptoError from twisted.logger import Logger diff --git a/nucypher/config/keyring.py b/nucypher/config/keyring.py index a6047b510..e08ce7a0d 100644 --- a/nucypher/config/keyring.py +++ b/nucypher/config/keyring.py @@ -18,10 +18,11 @@ import base64 import contextlib import json import os -import shutil import stat from json import JSONDecodeError +from typing import ClassVar, Tuple, Callable, Union, Dict, List +from constant_sorrow.constants import KEYRING_LOCKED from cryptography import x509 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends.openssl.ec import _EllipticCurvePrivateKey @@ -37,19 +38,20 @@ from eth_utils import to_checksum_address, is_checksum_address from nacl.exceptions import CryptoError from nacl.secret import SecretBox from twisted.logger import Logger -from typing import ClassVar, Tuple, Callable, Union, Dict, List from umbral.keys import UmbralPrivateKey, UmbralPublicKey, UmbralKeyingMaterial, derive_key_from_password -from constant_sorrow.constants import KEYRING_LOCKED +from nucypher.blockchain.eth.chains import Blockchain from nucypher.config.constants import DEFAULT_CONFIG_ROOT from nucypher.crypto.api import generate_self_signed_certificate from nucypher.crypto.constants import BLAKE2B -from nucypher.crypto.powers import SigningPower, DecryptingPower, KeyPairBasedPower, DerivedKeyBasedPower, \ +from nucypher.crypto.powers import ( + SigningPower, + DecryptingPower, + KeyPairBasedPower, + DerivedKeyBasedPower, BlockchainPower +) from nucypher.network.server import TLSHostingPower -from nucypher.blockchain.eth.chains import Blockchain - - FILE_ENCODING = 'utf-8' diff --git a/nucypher/config/node.py b/nucypher/config/node.py index 58579af9f..b18846bb2 100644 --- a/nucypher/config/node.py +++ b/nucypher/config/node.py @@ -16,6 +16,7 @@ along with nucypher. If not, see . """ +import binascii import json import os import secrets @@ -25,7 +26,6 @@ from json import JSONDecodeError from tempfile import TemporaryDirectory from typing import List, Set -import binascii import eth_utils from constant_sorrow.constants import ( UNINITIALIZED_CONFIGURATION, @@ -43,7 +43,6 @@ from umbral.signing import Signature from nucypher.blockchain.eth.agents import PolicyAgent, MinerAgent, NucypherTokenAgent from nucypher.blockchain.eth.chains import Blockchain -from nucypher.blockchain.eth.clients import NuCypherGethDevnetProcess from nucypher.blockchain.eth.registry import EthereumContractRegistry from nucypher.config.constants import DEFAULT_CONFIG_ROOT, BASE_DIR from nucypher.config.keyring import NucypherKeyring diff --git a/nucypher/crypto/api.py b/nucypher/crypto/api.py index f2fef65ad..77e8307fd 100644 --- a/nucypher/crypto/api.py +++ b/nucypher/crypto/api.py @@ -22,7 +22,7 @@ from typing import Tuple import sha3 from constant_sorrow import constants from cryptography import x509 -from cryptography.exceptions import InvalidSignature # TODO: Use nucypher exceptions +from cryptography.exceptions import InvalidSignature from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends.openssl.ec import _EllipticCurvePrivateKey from cryptography.hazmat.primitives import hashes diff --git a/nucypher/crypto/signing.py b/nucypher/crypto/signing.py index 393e26f5c..7e792e181 100644 --- a/nucypher/crypto/signing.py +++ b/nucypher/crypto/signing.py @@ -14,6 +14,8 @@ 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 . """ + + from bytestring_splitter import BytestringSplitter from umbral.signing import Signature, Signer diff --git a/nucypher/network/nodes.py b/nucypher/network/nodes.py index bf0985a14..d6463d03c 100644 --- a/nucypher/network/nodes.py +++ b/nucypher/network/nodes.py @@ -267,7 +267,7 @@ class Learner: version_splitter = BytestringSplitter((int, 2, {"byteorder": "big"})) tracker_class = FleetStateTracker - invalid_metadata_message = "{} has invalid metadata. Maybe its stake is over? Or maybe it is transitioning to a new interface. Ignoring." + invalid_metadata_message = "{} has invalid metadata. The node's stake may have ended, or it is transitioning to a new interface. Ignoring." unknown_version_message = "{} purported to be of version {}, but we're only version {}. Is there a new version of NuCypher?" really_unknown_version_message = "Unable to glean address from node that perhaps purported to be version {}. We're only version {}." fleet_state_icon = "" @@ -389,8 +389,8 @@ class Learner: self.log.warn("No seednodes were available after {} attempts".format(retry_attempts)) # TODO: Need some actual logic here for situation with no seed nodes (ie, maybe try again much later) - def read_nodes_from_storage(self) -> set: - stored_nodes = self.node_storage.all(federated_only=self.federated_only) # TODO: 466 + def read_nodes_from_storage(self) -> None: + stored_nodes = self.node_storage.all(federated_only=self.federated_only) # TODO: #466 for node in stored_nodes: self.remember_node(node) @@ -815,9 +815,8 @@ class Learner: self.log.warn(node.invalid_metadata_message.format(node)) except node.SuspiciousActivity: - # FIXME: NameError - message = f"Suspicious Activity: Discovered node with bad signature: {current_teacher.checksum_public_address}." \ - f"Propagated by: {teacher_uri}" + message = f"Suspicious Activity: Discovered node with bad signature: {node}." \ + f"Propagated by: {current_teacher}" self.log.warn(message) else: @@ -865,10 +864,10 @@ class Teacher: self.certificate_filepath = certificate_filepath self._interface_signature_object = interface_signature self._timestamp = timestamp - self.last_seen = NEVER_SEEN("Haven't connected to this node yet.") + self.last_seen = NEVER_SEEN("No Connection to Node") self.fleet_state_checksum = None self.fleet_state_updated = None - self._evidence_of_decentralized_identity = constant_or_bytes(identity_evidence) + self._identity_evidence = constant_or_bytes(identity_evidence) if substantiate_immediately: self.substantiate_stamp(password=password) # TODO: Derive from keyring @@ -926,7 +925,7 @@ class Teacher: def _stamp_has_valid_wallet_signature(self): """Offline Signature Verification""" - signature_bytes = self._evidence_of_decentralized_identity + signature_bytes = self._identity_evidence if signature_bytes is NOT_SIGNED: return False signature_is_valid = verify_eip_191(message=bytes(self.stamp), @@ -945,20 +944,37 @@ class Teacher: if not locked_tokens: raise self.InvalidNode(f"{self.checksum_public_address} has no active stakes.") - def stamp_is_valid(self) -> bool: + def stamp_is_valid(self, verify_staking: bool = True) -> bool: """Offline-only check of valid stamp signature""" - signature = self._evidence_of_decentralized_identity - if self._stamp_has_valid_wallet_signature(): - self.verified_stamp = True - return True - elif self.federated_only and signature is NOT_SIGNED: - message = "This node can't be verified in this manner, " \ - "but is OK to use in federated mode if you" \ - " have reason to believe it is trustworthy." - raise self.WrongMode(message) + # + # Federated + # + + if self.federated_only: + if self._identity_evidence is not NOT_SIGNED: + message = "This node cannot be verified in this manner, " \ + "but is OK to use in federated mode if you " \ + "have reason to believe it is trustworthy." + raise self.WrongMode(message) + + # + # Decentralized + # + else: - raise self.InvalidNode + + if self._identity_evidence is NOT_SIGNED: + raise self.InvalidNode + + # Off-chain signature verification + if self._stamp_has_valid_wallet_signature(): + self.verified_stamp = True + return True + + # On-chain staking check + if verify_staking: + self._is_valid_worker() def verify_id(self, ursula_id, digest_factory=bytes): self.verify() @@ -979,15 +995,11 @@ class Teacher: # Offline check of valid stamp signature by worker try: - self.stamp_is_valid() + self.stamp_is_valid(verify_staking=verify_staking) except self.WrongMode: if not accept_federated_only: raise - # Check for on-chain staking - if verify_staking and not self.federated_only: - self._is_valid_worker() - def verify_node(self, network_middleware, certificate_filepath: str = None, @@ -1014,7 +1026,7 @@ class Teacher: return True # This is both the stamp's client signature and interface metadata check. - self.validate_metadata(accept_federated_only) + self.validate_metadata(accept_federated_only=accept_federated_only) if not certificate_filepath: @@ -1036,7 +1048,7 @@ class Teacher: verifying_keys_match = node_details['verifying_key'] == self.public_keys(SigningPower) encrypting_keys_match = node_details['encrypting_key'] == self.public_keys(DecryptingPower) addresses_match = node_details['public_address'] == self.canonical_public_address - evidence_matches = node_details['identity_evidence'] == self._evidence_of_decentralized_identity + evidence_matches = node_details['identity_evidence'] == self._identity_evidence if not all((encrypting_keys_match, verifying_keys_match, addresses_match, evidence_matches)): # TODO: #355 - Optional reporting. @@ -1052,7 +1064,7 @@ class Teacher: blockchain_power = self._crypto_power.power_ups(BlockchainPower) blockchain_power.unlock_account(password=password) # TODO: #349 signature = blockchain_power.sign_message(bytes(self.stamp)) - self._evidence_of_decentralized_identity = signature + self._identity_evidence = signature # # Interface diff --git a/tests/characters/test_ursula_prepares_to_act_as_mining_node.py b/tests/characters/test_ursula_prepares_to_act_as_mining_node.py index 59bc35ab6..a519056f5 100644 --- a/tests/characters/test_ursula_prepares_to_act_as_mining_node.py +++ b/tests/characters/test_ursula_prepares_to_act_as_mining_node.py @@ -53,7 +53,7 @@ def test_new_federated_ursula_announces_herself(ursula_federated_test_config): def test_blockchain_ursula_substantiates_stamp(blockchain_ursulas): first_ursula = list(blockchain_ursulas)[0] - signature_as_bytes = first_ursula._evidence_of_decentralized_identity + signature_as_bytes = first_ursula._identity_evidence signature_as_bytes = to_standard_signature_bytes(signature_as_bytes) assert verify_eip_191(address=first_ursula.checksum_public_address, message=bytes(first_ursula.stamp), diff --git a/tests/cli/test_felix.py b/tests/cli/test_felix.py index 492595c24..1dedd8dca 100644 --- a/tests/cli/test_felix.py +++ b/tests/cli/test_felix.py @@ -21,7 +21,6 @@ from nucypher.utilities.sandbox.constants import ( @pytest_twisted.inlineCallbacks def test_run_felix(click_runner, testerchain, - federated_ursulas, # TODO: Make these blockchain Ursulas deploy_user_input, mock_primary_registry_filepath): diff --git a/tests/learning/test_fault_tolerance.py b/tests/learning/test_fault_tolerance.py index 72d1a1d83..60f1059f6 100644 --- a/tests/learning/test_fault_tolerance.py +++ b/tests/learning/test_fault_tolerance.py @@ -13,9 +13,9 @@ from nucypher.utilities.sandbox.ursula import make_federated_ursulas def test_blockchain_ursula_is_not_valid_with_unsigned_identity_evidence(blockchain_ursulas, caplog): lonely_blockchain_learner, blockchain_teacher, unsigned = list(blockchain_ursulas)[0:3] - unsigned._evidence_of_decentralized_identity = NOT_SIGNED + unsigned._identity_evidence = NOT_SIGNED - # Wipe known nodes. + # Wipe known nodes . lonely_blockchain_learner._Learner__known_nodes = FleetStateTracker() lonely_blockchain_learner._current_teacher_node = blockchain_teacher