def nucypher_cli(click_config, verbose, mock_networking, json_ipc, no_logs, quiet, debug, no_registry, log_level): # Session Emitter for pre and post character control engagement. if json_ipc: emitter = JSONRPCStdoutEmitter( quiet=quiet, capture_stdout=NucypherClickConfig.capture_stdout) else: emitter = StdoutEmitter( quiet=quiet, capture_stdout=NucypherClickConfig.capture_stdout) click_config.attach_emitter(emitter) if not json_ipc: click_config.emit(message=NUCYPHER_BANNER) if log_level: GlobalConsoleLogger.set_log_level(log_level_name=log_level) globalLogPublisher.addObserver(SimpleObserver()) if debug and quiet: raise click.BadOptionUsage( option_name="quiet", message="--debug and --quiet cannot be used at the same time.") if debug: click_config.log_to_sentry = False click_config.log_to_file = True # File Logging globalLogPublisher.addObserver(SimpleObserver()) # Console Logging globalLogPublisher.removeObserver(logToSentry) # No Sentry GlobalConsoleLogger.set_log_level(log_level_name='debug') elif quiet: # Disable Logging globalLogPublisher.removeObserver(logToSentry) globalLogPublisher.removeObserver(SimpleObserver) globalLogPublisher.removeObserver(getJsonFileObserver()) # Logging if not no_logs: GlobalConsoleLogger.start_if_not_started() # CLI Session Configuration click_config.verbose = verbose click_config.mock_networking = mock_networking click_config.json_ipc = json_ipc click_config.no_logs = no_logs click_config.quiet = quiet click_config.no_registry = no_registry click_config.debug = debug # Only used for testing outputs; # Redirects outputs to in-memory python containers. if mock_networking: click_config.emit(message="WARNING: Mock networking is enabled") click_config.middleware = MockRestMiddleware() else: click_config.middleware = RestMiddleware() # Global Warnings if click_config.verbose: click_config.emit(message="Verbose mode is enabled", color='blue')
def pytest_collection_modifyitems(config, items): if not config.getoption( "--runslow"): # --runslow given in cli: do not skip slow tests skip_slow = pytest.mark.skip(reason="need --runslow option to run") for item in items: if "slow" in item.keywords: item.add_marker(skip_slow) log_level_name = config.getoption("--log-level", "info", skip=True) observer = SimpleObserver(log_level_name) globalLogPublisher.addObserver(observer) # Timber! log_level_name = config.getoption("--log-level", "info", skip=True) observer = SimpleObserver(log_level_name) globalLogPublisher.addObserver(observer)
def spin_up_federated_ursulas(quantity: int = FLEET_POPULATION): # Logger globalLogPublisher.addObserver(SimpleObserver()) # Ports starting_port = DEMO_NODE_STARTING_PORT ports = list(map(str, range(starting_port, starting_port + quantity))) ursula_processes = list() for index, port in enumerate(ports): args = ['nucypher', 'ursula', 'run', '--rest-port', port, '--teacher-uri', TEACHER_URI, '--federated-only', '--dev', '--debug', '--config-root', 'demo-ursula-{}'.format(port) ] env = {'PATH': os.environ['PATH'], 'NUCYPHER_SENTRY_LOGS': '0', 'NUCYPHER_FILE_LOGS': '0', 'LC_ALL': 'en_US.UTF-8', 'LANG': 'en_US.UTF-8'} childFDs = {0: 0, 1: 1, 2: 2} class UrsulaProcessProtocol(protocol.Protocol): def __init__(self, command): self.command = command processProtocol = UrsulaProcessProtocol(command=args) p = reactor.spawnProcess(processProtocol, 'nucypher', args, env=env, childFDs=childFDs) ursula_processes.append(p) reactor.run() # GO!
import datetime import os import shutil import maya import json import sys from twisted.logger import globalLogPublisher POLICY_FILENAME = "policy-metadata.json" ###################### # Boring setup stuff # ###################### # # # Twisted Logger globalLogPublisher.addObserver(SimpleObserver()) # TEMP_ALICE_DIR = "alicia-files".format( os.path.dirname(os.path.abspath(__file__))) # We expect the url of the seednode as the first argument. SEEDNODE_URL = 'localhost:11500' ####################################### # Alicia, the Authority of the Policy # ####################################### # We get a persistent Alice. # If we had an existing Alicia in disk, let's get it from there passphrase = "TEST_ALICIA_INSECURE_DEVELOPMENT_PASSWORD"
def initialize_alice_policy_pubkey( alice_encryption_password: str = "TEST_ALICE_PASSWORD"): """ Takes in alice's encryption password as input and generates and stores the policy pubkey. """ globalLogPublisher.addObserver(SimpleObserver()) NUCYPHER_DATA_DIR = os.path.join( settings.BASE_DIR, 'nucypher_utils', 'nucypher_data', ) if not os.path.exists(NUCYPHER_DATA_DIR): os.mkdir(NUCYPHER_DATA_DIR) if os.listdir(NUCYPHER_DATA_DIR): print("data directory not clean...Cleaning...") shutil.rmtree(NUCYPHER_DATA_DIR) if not os.path.exists(NUCYPHER_DATA_DIR): os.mkdir(NUCYPHER_DATA_DIR) if os.listdir(NUCYPHER_DATA_DIR): print("Data Directory Cleaned") ALICE_CONFIG_DIR = os.path.join(settings.BASE_DIR, 'nucypher_utils', 'nucypher_data', 'nucypher_char_configs', 'stridon-demo-alice') # We expect the url of the seednode as the first argument. SEEDNODE_URL = 'localhost:11500' POLICY_FILENAME = "policy-metadata.json" ursula = Ursula.from_seed_and_stake_info(seed_uri=SEEDNODE_URL, federated_only=True, minimum_stake=0) passphrase = alice_encryption_password alice_config = AliceConfiguration( config_root=os.path.join(ALICE_CONFIG_DIR), is_me=True, known_nodes={ursula}, start_learning_now=False, federated_only=True, learn_on_same_thread=True, ) alice_config.initialize(password=passphrase) alice_config.keyring.unlock(password=passphrase) alice_config_file = alice_config.to_configuration_file() print(alice_config_file) alice = alice_config.produce() label = "stridon-premium-service" label = label.encode() policy_pubkey = alice.get_policy_pubkey_from_label(label) print("The policy public key for label '{}' is {}".format( label.decode("utf-8"), policy_pubkey.to_bytes().hex())) policy_json = { "policy_pubkey": policy_pubkey.to_bytes().hex(), } POLICY_FILE = os.path.join( os.getcwd(), 'nucypher_utils', 'nucypher_data', POLICY_FILENAME, ) with open(POLICY_FILE, 'w') as f: json.dump(policy_json, f) from nucypher.crypto.powers import SigningPower, DecryptingPower print(alice.public_keys(SigningPower)) print(alice.public_keys(DecryptingPower))
def alicia_encrypt(data_fields, pid): POLICY_FILENAME = "policy-metadata.json" globalLogPublisher.addObserver(SimpleObserver()) TEMP_ALICE_DIR = "alicia-files".format( os.path.dirname(os.path.abspath(__file__))) SEEDNODE_URL = 'localhost:11500' passphrase = "TEST_ALICIA_INSECURE_DEVELOPMENT_PASSWORD" try: alice_config_file = os.path.join(TEMP_ALICE_DIR, "alice.config") new_alice_config = AliceConfiguration.from_configuration_file( filepath=alice_config_file, network_middleware=RestMiddleware(), start_learning_now=False, save_metadata=False, ) alicia = new_alice_config(passphrase=passphrase) except: shutil.rmtree(TEMP_ALICE_DIR, ignore_errors=True) ursula = Ursula.from_seed_and_stake_info(seed_uri=SEEDNODE_URL, federated_only=True, minimum_stake=0) alice_config = AliceConfiguration( config_root=os.path.join(TEMP_ALICE_DIR), is_me=True, known_nodes={ursula}, start_learning_now=False, federated_only=True, learn_on_same_thread=True, ) alice_config.initialize(password=passphrase) alice_config.keyring.unlock(password=passphrase) alicia = alice_config.produce() alice_config_file = alice_config.to_configuration_file() alicia.start_learning_loop(now=True) label = "doctor" label = label.encode() policy_pubkey = alicia.get_policy_pubkey_from_label(label) print("The policy public key for " "label '{}' is {}".format(label.decode("utf-8"), policy_pubkey.to_bytes().hex())) import data_ipfs res = data_ipfs.encrypt_patient_data(policy_pubkey, data_fields, pid, label=label, save_as_file=True) print(res) from doctor_keys import get_doctor_pubkeys doctor_pubkeys = get_doctor_pubkeys() powers_and_material = { DecryptingPower: doctor_pubkeys['enc'], SigningPower: doctor_pubkeys['sig'] } doctor_strange = Bob.from_public_keys( powers_and_material=powers_and_material, federated_only=True) policy_end_datetime = maya.now() + datetime.timedelta(days=5) m, n = 2, 3 print("Creating access policy for the Doctor...") policy = alicia.grant(bob=doctor_strange, label=label, m=m, n=n, expiration=policy_end_datetime) print("Done!") policy_info = { "policy_pubkey": policy.public_key.to_bytes().hex(), "alice_sig_pubkey": bytes(alicia.stamp).hex(), "label": label.decode("utf-8"), } filename = POLICY_FILENAME with open(filename, 'w') as f: json.dump(policy_info, f) return res
def run_doc(): globalLogPublisher.addObserver(SimpleObserver()) ###################### # Boring setup stuff # ###################### SEEDNODE_URL = 'localhost:11501' # TODO: path joins? TEMP_DOCTOR_DIR = "{}/doctor-files".format( os.path.dirname(os.path.abspath(__file__))) # Remove previous demo files and create new ones shutil.rmtree(TEMP_DOCTOR_DIR, ignore_errors=True) ursula = Ursula.from_seed_and_stake_info(seed_uri=SEEDNODE_URL, federated_only=True, minimum_stake=0) # To create a Bob, we need the doctor's private keys previously generated. doctor_keys = get_doctor_privkeys() bob_enc_keypair = DecryptingKeypair(private_key=doctor_keys["enc"]) bob_sig_keypair = SigningKeypair(private_key=doctor_keys["sig"]) enc_power = DecryptingPower(keypair=bob_enc_keypair) sig_power = SigningPower(keypair=bob_sig_keypair) power_ups = [enc_power, sig_power] print("Creating the Doctor ...") doctor = Bob( is_me=True, federated_only=True, crypto_power_ups=power_ups, start_learning_now=True, abort_on_learning_error=True, known_nodes=[ursula], save_metadata=False, network_middleware=RestMiddleware(), ) print("Doctor = ", doctor) # Let's join the policy generated by Alicia. We just need some info about it. with open("policy-metadata.json", 'r') as f: policy_data = json.load(f) policy_pubkey = UmbralPublicKey.from_bytes( bytes.fromhex(policy_data["policy_pubkey"])) alices_sig_pubkey = UmbralPublicKey.from_bytes( bytes.fromhex(policy_data["alice_sig_pubkey"])) label = policy_data["label"].encode() print("The Doctor joins policy for label '{}'".format( label.decode("utf-8"))) doctor.join_policy(label, alices_sig_pubkey) # Now that the Doctor joined the policy in the NuCypher network, # he can retrieve encrypted data which he can decrypt with his private key. # But first we need some encrypted data! # Let's read the file produced by the heart monitor and unpack the MessageKits, # which are the individual ciphertexts. data = msgpack.load(open("heart_data.msgpack", "rb"), raw=False) message_kits = (UmbralMessageKit.from_bytes(k) for k in data['kits']) # The doctor also needs to create a view of the Data Source from its public keys data_source = Enrico.from_public_keys({SigningPower: data['data_source']}, policy_encrypting_key=policy_pubkey) # Now he can ask the NuCypher network to get a re-encrypted version of each MessageKit. for message_kit in message_kits: try: start = timer() retrieved_plaintexts = doctor.retrieve( label=label, message_kit=message_kit, data_source=data_source, alice_verifying_key=alices_sig_pubkey) end = timer() plaintext = msgpack.loads(retrieved_plaintexts[0], raw=False) # Now we can get the heart rate and the associated timestamp, # generated by the heart rate monitor. heart_rate = plaintext['heart_rate'] timestamp = maya.MayaDT(plaintext['timestamp']) # This code block simply pretty prints the heart rate info terminal_size = shutil.get_terminal_size().columns max_width = min(terminal_size, 120) columns = max_width - 12 - 27 scale = columns / 40 scaled_heart_rate = int(scale * (heart_rate - 60)) retrieval_time = "Retrieval time: {:8.2f} ms".format(1000 * (end - start)) line = ("-" * scaled_heart_rate) + "❤︎ ({} BPM)".format(heart_rate) line = line.ljust(max_width - 27, " ") + retrieval_time print(line) except Exception as e: # We just want to know what went wrong and continue the demo traceback.print_exc()
def doctor_decrypt(hash_key): globalLogPublisher.addObserver(SimpleObserver()) SEEDNODE_URL = 'localhost:11501' TEMP_DOCTOR_DIR = "{}/doctor-files".format( os.path.dirname(os.path.abspath(__file__))) shutil.rmtree(TEMP_DOCTOR_DIR, ignore_errors=True) ursula = Ursula.from_seed_and_stake_info(seed_uri=SEEDNODE_URL, federated_only=True, minimum_stake=0) from doctor_keys import get_doctor_privkeys doctor_keys = get_doctor_privkeys() bob_enc_keypair = DecryptingKeypair(private_key=doctor_keys["enc"]) bob_sig_keypair = SigningKeypair(private_key=doctor_keys["sig"]) enc_power = DecryptingPower(keypair=bob_enc_keypair) sig_power = SigningPower(keypair=bob_sig_keypair) power_ups = [enc_power, sig_power] print("Creating the Doctor ...") doctor = Bob( is_me=True, federated_only=True, crypto_power_ups=power_ups, start_learning_now=True, abort_on_learning_error=True, known_nodes=[ursula], save_metadata=False, network_middleware=RestMiddleware(), ) print("Doctor = ", doctor) with open("policy-metadata.json", 'r') as f: policy_data = json.load(f) policy_pubkey = UmbralPublicKey.from_bytes( bytes.fromhex(policy_data["policy_pubkey"])) alices_sig_pubkey = UmbralPublicKey.from_bytes( bytes.fromhex(policy_data["alice_sig_pubkey"])) label = policy_data["label"].encode() print("The Doctor joins policy for label '{}'".format( label.decode("utf-8"))) doctor.join_policy(label, alices_sig_pubkey) ipfs_api = ipfsapi.connect() file = ipfs_api.get(hash_key) print(file) os.rename(hash_key, 'patient_details.msgpack') data = msgpack.load(open("patient_details.msgpack", "rb"), raw=False) message_kits = (UmbralMessageKit.from_bytes(k) for k in data['kits']) data_source = DataSource.from_public_keys( policy_public_key=policy_pubkey, datasource_public_key=data['data_source'], label=label) complete_message = [] for message_kit in message_kits: print(message_kit) try: start = timer() retrieved_plaintexts = doctor.retrieve( message_kit=message_kit, data_source=data_source, alice_verifying_key=alices_sig_pubkey) end = timer() plaintext = msgpack.loads(retrieved_plaintexts[0], raw=False) complete_message.append(plaintext) print(plaintext) #with open("details.json", "w") as write_file: # json.dump(plaintext, write_file) except Exception as e: traceback.print_exc() with open("details.json", "w") as write_file: json.dump(complete_message, write_file) return complete_message
def ursula( click_config, action, debug, dev, quiet, dry_run, force, lonely, network, teacher_uri, min_stake, rest_host, rest_port, db_filepath, checksum_address, federated_only, poa, config_root, config_file, metadata_dir, # TODO: Start nodes from an additional existing metadata dir provider_uri, no_registry, registry_filepath) -> None: """ Manage and run an Ursula node. \b Actions ------------------------------------------------- \b run Run an "Ursula" node. init Create a new Ursula node configuration. view View the Ursula node's configuration. forget Forget all known nodes. save-metadata Manually write node metadata to disk without running destroy Delete Ursula node configuration. """ # # Boring Setup Stuff # if not quiet: log = Logger('ursula.cli') if debug and quiet: raise click.BadOptionUsage( option_name="quiet", message="--debug and --quiet cannot be used at the same time.") if debug: click_config.log_to_sentry = False click_config.log_to_file = True globalLogPublisher.removeObserver(logToSentry) # Sentry globalLogPublisher.addObserver( SimpleObserver(log_level_name='debug')) # Print elif quiet: globalLogPublisher.removeObserver(logToSentry) globalLogPublisher.removeObserver(SimpleObserver) globalLogPublisher.removeObserver(getJsonFileObserver()) # # Pre-Launch Warnings # if not quiet: if dev: click.secho("WARNING: Running in development mode", fg='yellow') if force: click.secho("WARNING: Force is enabled", fg='yellow') # # Unauthenticated Configurations # if action == "init": """Create a brand-new persistent Ursula""" if dev and not quiet: click.secho("WARNING: Using temporary storage area", fg='yellow') if not config_root: # Flag config_root = click_config.config_file # Envvar if not rest_host: rest_host = click.prompt( "Enter Ursula's public-facing IPv4 address") ursula_config = UrsulaConfiguration.generate( password=click_config.get_password(confirm=True), config_root=config_root, rest_host=rest_host, rest_port=rest_port, db_filepath=db_filepath, domains={network} if network else None, federated_only=federated_only, checksum_public_address=checksum_address, no_registry=federated_only or no_registry, registry_filepath=registry_filepath, provider_uri=provider_uri, poa=poa) if not quiet: click.secho("Generated keyring {}".format( ursula_config.keyring_dir), fg='green') click.secho("Saved configuration file {}".format( ursula_config.config_file_location), fg='green') # Give the use a suggestion as to what to do next... how_to_run_message = "\nTo run an Ursula node from the default configuration filepath run: \n\n'{}'\n" suggested_command = 'nucypher ursula run' if config_root is not None: config_file_location = os.path.join( config_root, config_file or UrsulaConfiguration.CONFIG_FILENAME) suggested_command += ' --config-file {}'.format( config_file_location) click.secho(how_to_run_message.format(suggested_command), fg='green') return # FIN else: click.secho("OK") elif action == "destroy": """Delete all configuration files from the disk""" if dev: message = "'nucypher ursula destroy' cannot be used in --dev mode" raise click.BadOptionUsage(option_name='--dev', message=message) try: ursula_config = UrsulaConfiguration.from_configuration_file( filepath=config_file, domains={network}) except FileNotFoundError: config_root = config_root or DEFAULT_CONFIG_ROOT config_file_location = config_file or UrsulaConfiguration.DEFAULT_CONFIG_FILE_LOCATION if not force: message = "No configuration file found at {}; \n" \ "Destroy top-level configuration directory: {}?".format(config_file_location, config_root) click.confirm(message, abort=True) # ABORT shutil.rmtree(config_root, ignore_errors=False) else: if not force: click.confirm(''' *Permanently and irreversibly delete all* nucypher files including - Private and Public Keys - Known Nodes - TLS certificates - Node Configurations - Log Files Delete {}?'''.format(ursula_config.config_root), abort=True) try: ursula_config.destroy(force=force) except FileNotFoundError: message = 'Failed: No nucypher files found at {}'.format( ursula_config.config_root) click.secho(message, fg='red') log.debug(message) raise click.Abort() else: message = "Deleted configuration files at {}".format( ursula_config.config_root) click.secho(message, fg='green') log.debug(message) if not quiet: click.secho("Destroyed {}".format(config_root)) return # Development Configuration if dev: ursula_config = UrsulaConfiguration( dev_mode=True, domains={TEMPORARY_DOMAIN}, poa=poa, registry_filepath=registry_filepath, provider_uri=provider_uri, checksum_public_address=checksum_address, federated_only=federated_only, rest_host=rest_host, rest_port=rest_port, db_filepath=db_filepath) # Authenticated Configurations else: # Deserialize network domain name if override passed if network: domain_constant = getattr(constants, network.upper()) domains = {domain_constant} else: domains = None ursula_config = UrsulaConfiguration.from_configuration_file( filepath=config_file, domains=domains, registry_filepath=registry_filepath, provider_uri=provider_uri, rest_host=rest_host, rest_port=rest_port, db_filepath=db_filepath, # TODO: Handle Boolean overrides # poa=poa, # federated_only=federated_only, ) try: # Unlock Keyring if not quiet: click.secho('Decrypting keyring...', fg='blue') ursula_config.keyring.unlock(password=click_config.get_password() ) # Takes ~3 seconds, ~1GB Ram except CryptoError: raise ursula_config.keyring.AuthenticationFailed if not ursula_config.federated_only: try: ursula_config.connect_to_blockchain(recompile_contracts=False) ursula_config.connect_to_contracts() except EthereumContractRegistry.NoRegistry: message = "Cannot configure blockchain character: No contract registry found; " \ "Did you mean to pass --federated-only?" raise EthereumContractRegistry.NoRegistry(message) click_config.ursula_config = ursula_config # Pass Ursula's config onto staking sub-command # # Launch Warnings # if not quiet: if ursula_config.federated_only: click.secho("WARNING: Running in Federated mode", fg='yellow') # # Action Switch # if action == 'run': """Seed, Produce, Run!""" # # Seed - Step 1 # teacher_nodes = list() if teacher_uri: node = Ursula.from_teacher_uri( teacher_uri=teacher_uri, min_stake=min_stake, federated_only=ursula_config.federated_only) teacher_nodes.append(node) # # Produce - Step 2 # ursula = ursula_config(known_nodes=teacher_nodes, lonely=lonely) # GO! try: # # Run - Step 3 # click.secho("Connecting to {}".format(','.join( str(d) for d in ursula_config.domains)), fg='blue', bold=True) click.secho("Running Ursula {} on {}".format( ursula, ursula.rest_interface), fg='green', bold=True) if not debug: stdio.StandardIO(UrsulaCommandProtocol(ursula=ursula)) if dry_run: # That's all folks! return ursula.get_deployer().run() # <--- Blocking Call (Reactor) except Exception as e: ursula_config.log.critical(str(e)) click.secho("{} {}".format(e.__class__.__name__, str(e)), fg='red') raise # Crash :-( finally: if not quiet: click.secho("Stopping Ursula") ursula_config.cleanup() if not quiet: click.secho("Ursula Stopped", fg='red') return elif action == "save-metadata": """Manually save a node self-metadata file""" ursula = ursula_config.produce(ursula_config=ursula_config) metadata_path = ursula.write_node_metadata(node=ursula) if not quiet: click.secho("Successfully saved node metadata to {}.".format( metadata_path), fg='green') return elif action == "view": """Paint an existing configuration to the console""" paint_configuration( config_filepath=config_file or ursula_config.config_file_location) return elif action == "forget": """Forget all known nodes via storages""" click.confirm("Permanently delete all known node data?", abort=True) ursula_config.forget_nodes() message = "Removed all stored node node metadata and certificates" click.secho(message=message, fg='red') return else: raise click.BadArgumentUsage("No such argument {}".format(action))
def subscribe_and_grant_permission_to(username): globalLogPublisher.addObserver(SimpleObserver()) # Reinitialize Alice from our config file ALICE_CONFIG_DIR = os.path.join(settings.BASE_DIR, 'nucypher_utils', 'nucypher_data', 'nucypher_char_configs', 'stridon-demo-alice') ALICE_CONFIG_FILE = os.path.join(ALICE_CONFIG_DIR, "alice.config") passphrase = "TEST_ALICE_PASSWORD" new_alice_config = AliceConfiguration.from_configuration_file( filepath=ALICE_CONFIG_FILE, network_middleware=RestMiddleware(), start_learning_now=False, save_metadata=False, ) new_alice_config.keyring.unlock(password=passphrase) alice = new_alice_config() # Now onto Bob SEEDNODE_URL = 'localhost:11500' BOB_CONFIG_DIR = os.path.join(settings.BASE_DIR, 'nucypher_utils', 'nucypher_data', 'nucypher_char_configs', username) ursula = Ursula.from_seed_and_stake_info(seed_uri=SEEDNODE_URL, federated_only=True, minimum_stake=0) bob_config = BobConfiguration( config_root=os.path.join(BOB_CONFIG_DIR), is_me=True, known_nodes={ursula}, start_learning_now=False, federated_only=True, learn_on_same_thread=True, ) bob_config.initialize(password=passphrase) bob_config.keyring.unlock(password=passphrase) bob_config_file = bob_config.to_configuration_file() premium_user = bob_config.produce() policy_end_datetime = maya.now() + datetime.timedelta(days=5) label = b'stridon-premium-service' policy_pubkey = alice.get_policy_pubkey_from_label(label) alice.start_learning_loop(now=True) policy = alice.grant(bob=premium_user, label=label, m=1, n=1, expiration=policy_end_datetime) assert policy.public_key == policy_pubkey alices_pubkey_bytes = bytes(alice.stamp) premium_user.join_policy(label, alices_pubkey_bytes) from nucypher.crypto.powers import SigningPower, DecryptingPower print("ALICE") print(alice.public_keys(SigningPower)) print(alice.public_keys(DecryptingPower)) print("PREMIUM_USER") print(premium_user.public_keys(SigningPower)) print(premium_user.public_keys(DecryptingPower)) return policy.public_key == policy_pubkey