def _mock_ursula_reencrypts(ursula): delegating_privkey = SecretKey.random() capsule, _ciphertext = encrypt(delegating_privkey.public_key(), b'unused') signing_privkey = SecretKey.random() signing_pubkey = signing_privkey.public_key() signer = Signer(signing_privkey) priv_key_bob = SecretKey.random() pub_key_bob = priv_key_bob.public_key() kfrags = generate_kfrags(delegating_sk=delegating_privkey, signer=signer, receiving_pk=pub_key_bob, threshold=2, num_kfrags=4, sign_delegating_key=False, sign_receiving_key=False) ursula_pubkey = ursula.stamp.as_umbral_pubkey() alice_address = canonical_address_from_umbral_key(signing_pubkey) blockhash = bytes(32) specification = b''.join((bytes(capsule), bytes(ursula_pubkey), bytes(ursula.decentralized_identity_evidence), alice_address, blockhash)) bobs_signer = Signer(priv_key_bob) task_signature = bytes(bobs_signer.sign(specification)) cfrag = reencrypt(capsule, kfrags[0]) cfrag_signature = ursula.stamp(bytes(cfrag)) bob = Bob.from_public_keys(verifying_key=pub_key_bob) return WorkOrder.PRETask(capsule, task_signature, cfrag, cfrag_signature)
def test_verify(testerchain, signature_verifier): message = os.urandom(100) # Generate Umbral key umbral_privkey = SecretKey.random() umbral_pubkey = umbral_privkey.public_key() umbral_pubkey_bytes = pubkey_as_uncompressed_bytes(umbral_pubkey) # Sign message using SHA-256 hash signer = Signer(umbral_privkey) signature = signer.sign(message) # Get recovery id (v) before using contract v = get_signature_recovery_value(message, signature, umbral_pubkey) recoverable_signature = bytes(signature) + v # Verify signature assert signature_verifier.functions.verify(message, recoverable_signature, umbral_pubkey_bytes, ALGORITHM_SHA256).call() # Verify signature using wrong key umbral_privkey = SecretKey.random() umbral_pubkey_bytes = pubkey_as_uncompressed_bytes( umbral_privkey.public_key()) assert not signature_verifier.functions.verify( message, recoverable_signature, umbral_pubkey_bytes, ALGORITHM_SHA256).call()
def make_random_bob(): """Generates a random ephemeral Bob instance.""" bob_verifying_secret = SecretKey.random() bob_verifying_key = bob_verifying_secret.public_key() decrypting_secret = SecretKey.random() decrypting_key = decrypting_secret.public_key() bob = Bob.from_public_keys(verifying_key=bob_verifying_key, encrypting_key=decrypting_key, federated_only=False) print(f'Created BOB - {bytes(bob.stamp).hex()}') return bob
def test_create_bob_card_inline(click_runner, alice_verifying_key, alice_nickname): command = ('contacts', 'create', '--type', 'b', '--verifying-key', bytes(SecretKey.random().public_key()).hex(), '--encrypting-key', bytes(SecretKey.random().public_key()).hex(), '--nickname', 'hans') assert len(os.listdir(Card.CARD_DIR)) == 3 result = click_runner.invoke(nucypher_cli, command, catch_exceptions=False) assert result.exit_code == 0, result.output assert 'Saved new card' in result.output assert len(os.listdir(Card.CARD_DIR)) == 4
def test_default_character_configuration_preservation( configuration_class, testerchain, test_registry_source_manager): configuration_class.DEFAULT_CONFIG_ROOT = '/tmp' fake_address = '0xdeadbeef' network = TEMPORARY_DOMAIN expected_filename = f'{configuration_class.NAME}.{configuration_class._CONFIG_FILE_EXTENSION}' generated_filename = configuration_class.generate_filename() assert generated_filename == expected_filename expected_filepath = os.path.join('/', 'tmp', generated_filename) if os.path.exists(expected_filepath): os.remove(expected_filepath) assert not os.path.exists(expected_filepath) if configuration_class == StakeHolderConfiguration: # special case for defaults character_config = StakeHolderConfiguration( provider_uri=testerchain.provider_uri, domain=network) elif configuration_class == UrsulaConfiguration: # special case for rest_host & dev mode # use keyring keyring = Mock(spec=NucypherKeyring) keyring.signing_public_key = SecretKey.random().public_key() character_config = configuration_class(checksum_address=fake_address, domain=network, rest_host=MOCK_IP_ADDRESS, keyring=keyring) else: character_config = configuration_class(checksum_address=fake_address, domain=network) generated_filepath = character_config.generate_filepath() assert generated_filepath == expected_filepath written_filepath = character_config.to_configuration_file() assert written_filepath == expected_filepath assert os.path.exists(written_filepath) try: # Read with open(character_config.filepath, 'r') as f: contents = f.read() # Restore from JSON file restored_configuration = configuration_class.from_configuration_file() assert character_config.serialize( ) == restored_configuration.serialize() # File still exists after reading assert os.path.exists(written_filepath) finally: if os.path.exists(expected_filepath): os.remove(expected_filepath)
def fragments(): delegating_privkey = SecretKey.random() delegating_pubkey = delegating_privkey.public_key() signing_privkey = SecretKey.random() signer = Signer(signing_privkey) priv_key_bob = SecretKey.random() pub_key_bob = priv_key_bob.public_key() kfrags = generate_kfrags(delegating_sk=delegating_privkey, signer=signer, receiving_pk=pub_key_bob, threshold=2, num_kfrags=4, sign_delegating_key=False, sign_receiving_key=False) capsule, _ciphertext = encrypt(delegating_pubkey, b'unused') cfrag = reencrypt(capsule, kfrags[0]) return capsule, cfrag
def test_enrico_control_starts(click_runner): policy_encrypting_key = bytes(SecretKey.random().public_key()).hex() run_args = ('enrico', 'run', '--policy-encrypting-key', policy_encrypting_key, '--dry-run') result = click_runner.invoke(nucypher_cli, run_args, catch_exceptions=False) assert result.exit_code == 0 assert policy_encrypting_key in result.output
def ursula(mocker): identity_evidence = os.urandom(LENGTH_ECDSA_SIGNATURE_WITH_RECOVERY) ursula_privkey = SecretKey.random() ursula_stamp = SignatureStamp(verifying_key=ursula_privkey.public_key(), signer=Signer(ursula_privkey)) ursula = mocker.Mock(stamp=ursula_stamp, decentralized_identity_evidence=identity_evidence) ursula.mature = lambda: True ursula._stamp_has_valid_signature_by_worker = lambda: True return ursula
def test_signing(): umbral_privkey = SecretKey.random() sig_keypair = keypairs.SigningKeypair(umbral_privkey) msg = b'peace at dawn' signature = sig_keypair.sign(msg) assert signature.verify(sig_keypair.pubkey, msg) bad_msg = b'bad message' assert not signature.verify(sig_keypair.pubkey, bad_msg)
def test_keypair_fingerprint(): umbral_pubkey = SecretKey.random().public_key() new_keypair = keypairs.Keypair(public_key=umbral_pubkey) fingerprint = new_keypair.fingerprint() assert fingerprint is not None umbral_fingerprint = sha3.keccak_256( bytes(umbral_pubkey)).hexdigest().encode() assert fingerprint == umbral_fingerprint
def test_address(testerchain, signature_verifier): # Generate Umbral key and extract "address" from the public key umbral_privkey = SecretKey.random() umbral_pubkey = umbral_privkey.public_key() signer_address = pubkey_as_address(umbral_pubkey) umbral_pubkey_bytes = pubkey_as_uncompressed_bytes(umbral_pubkey) # Check extracting address in library result_address = signature_verifier.functions.toAddress( umbral_pubkey_bytes).call() assert signer_address == to_normalized_address(result_address)
def generate_doctor_keys(): enc_privkey = SecretKey.random() sig_privkey = SecretKey.random() doctor_privkeys = { 'enc': bytes(enc_privkey).hex(), 'sig': bytes(sig_privkey).hex(), } with open(DOCTOR_PRIVATE_JSON, 'w') as f: json.dump(doctor_privkeys, f) enc_pubkey = enc_privkey.public_key() sig_pubkey = sig_privkey.public_key() doctor_pubkeys = { 'enc': bytes(enc_pubkey).hex(), 'sig': bytes(sig_pubkey).hex() } with open(DOCTOR_PUBLIC_JSON, 'w') as f: json.dump(doctor_pubkeys, f)
def test_keypair_with_umbral_keys(): umbral_privkey = SecretKey.random() umbral_pubkey = umbral_privkey.public_key() new_keypair_from_priv = keypairs.Keypair(umbral_privkey) assert new_keypair_from_priv._privkey == umbral_privkey assert bytes(new_keypair_from_priv.pubkey) == bytes(umbral_pubkey) new_keypair_from_pub = keypairs.Keypair(public_key=umbral_pubkey) assert bytes(new_keypair_from_pub.pubkey) == bytes(umbral_pubkey) assert new_keypair_from_pub._privkey == PUBLIC_ONLY
def mock_ursula(testerchain, account): ursula_privkey = SecretKey.random() ursula_stamp = SignatureStamp(verifying_key=ursula_privkey.public_key(), signer=Signer(ursula_privkey)) signed_stamp = testerchain.client.sign_message(account=account, message=bytes(ursula_stamp)) ursula = Mock(stamp=ursula_stamp, decentralized_identity_evidence=signed_stamp) return ursula
def test_enrico_encrypt(click_runner): policy_encrypting_key = bytes(SecretKey.random().public_key()).hex() encrypt_args = ('enrico', 'encrypt', '--message', 'to be or not to be', '--policy-encrypting-key', policy_encrypting_key) result = click_runner.invoke(nucypher_cli, encrypt_args, catch_exceptions=False) assert result.exit_code == 0 assert policy_encrypting_key in result.output assert "message_kit" in result.output assert "signature" in result.output
def test_generate_alice_keyring(tmpdir): keyring = NucypherKeyring.generate( checksum_address=FEDERATED_ADDRESS, password=INSECURE_DEVELOPMENT_PASSWORD, encrypting=True, rest=False, keyring_root=tmpdir ) enc_pubkey = keyring.encrypting_public_key assert enc_pubkey is not None with pytest.raises(NucypherKeyring.KeyringLocked): _dec_keypair = keyring.derive_crypto_power(DecryptingPower).keypair keyring.unlock(password=INSECURE_DEVELOPMENT_PASSWORD) dec_keypair = keyring.derive_crypto_power(DecryptingPower).keypair assert enc_pubkey == dec_keypair.pubkey label = b'test' delegating_power = keyring.derive_crypto_power(DelegatingPower) delegating_pubkey = delegating_power.get_pubkey_from_label(label) bob_pubkey = SecretKey.random().public_key() signer = Signer(SecretKey.random()) delegating_pubkey_again, _kfrags = delegating_power.generate_kfrags( bob_pubkey, signer, label, m=2, n=3 ) assert delegating_pubkey == delegating_pubkey_again another_delegating_power = keyring.derive_crypto_power(DelegatingPower) another_delegating_pubkey = another_delegating_power.get_pubkey_from_label(label) assert delegating_pubkey == another_delegating_pubkey
def test_recover(testerchain, signature_verifier): message = os.urandom(100) # Prepare message hash hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend) hash_ctx.update(message) message_hash = hash_ctx.finalize() # Generate Umbral key and extract "address" from the public key umbral_privkey = SecretKey.random() umbral_pubkey = umbral_privkey.public_key() signer_address = pubkey_as_address(umbral_pubkey) # Sign message signer = Signer(umbral_privkey) signature = signer.sign(message) # Get recovery id (v) before using contract # If we don't have recovery id while signing then we should try to recover public key with different v # Only the correct v will match the correct public key v = get_signature_recovery_value(message, signature, umbral_pubkey) recoverable_signature = bytes(signature) + v # Check recovery method in the contract assert signer_address == to_normalized_address( signature_verifier.functions.recover(message_hash, recoverable_signature).call()) # Also numbers 27 and 28 can be used for v recoverable_signature = recoverable_signature[:-1] + bytes( [recoverable_signature[-1] + 27]) assert signer_address == to_normalized_address( signature_verifier.functions.recover(message_hash, recoverable_signature).call()) # Only number 0,1,27,28 are supported for v recoverable_signature = bytes(signature) + bytes([2]) with pytest.raises((TransactionFailed, ValueError)): signature_verifier.functions.recover(message_hash, recoverable_signature).call() # Signature must include r, s and v recoverable_signature = bytes(signature) with pytest.raises((TransactionFailed, ValueError)): signature_verifier.functions.recover(message_hash, recoverable_signature).call()
def _generate_signing_keys() -> Tuple[SecretKey, PublicKey]: """ """ privkey = SecretKey.random() pubkey = privkey.public_key() return privkey, pubkey
def _generate_encryption_keys() -> Tuple[SecretKey, PublicKey]: """Use pyUmbral keys to generate a new encrypting key pair""" privkey = SecretKey.random() pubkey = privkey.public_key() return privkey, pubkey
def test_coexisting_configurations(click_runner, custom_filepath, testerchain, agency_local_registry): # # Setup # if custom_filepath.exists(): shutil.rmtree(str(custom_filepath), ignore_errors=True) assert not custom_filepath.exists() # Parse node addresses # TODO: Is testerchain & Full contract deployment needed here (causes massive slowdown)? alice, ursula, another_ursula, felix, staker, *all_yall = testerchain.unassigned_accounts envvars = { NUCYPHER_ENVVAR_KEYRING_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD, NUCYPHER_ENVVAR_ALICE_ETH_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD, NUCYPHER_ENVVAR_BOB_ETH_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD } # Future configuration filepaths for assertions... public_keys_dir = custom_filepath / 'keyring' / 'public' known_nodes_dir = custom_filepath / 'known_nodes' # ... Ensure they do not exist to begin with. # No keys have been generated... assert not public_keys_dir.exists() # No known nodes exist... assert not known_nodes_dir.exists() # Not the configuration root... assert not os.path.isdir(custom_filepath) # ... nothing None # # Create # # Expected config files felix_file_location = custom_filepath / FelixConfiguration.generate_filename( ) alice_file_location = custom_filepath / AliceConfiguration.generate_filename( ) ursula_file_location = custom_filepath / UrsulaConfiguration.generate_filename( ) another_ursula_configuration_file_location = custom_filepath / UrsulaConfiguration.generate_filename( modifier=another_ursula) # Felix creates a system configuration felix_init_args = ('felix', 'init', '--config-root', custom_filepath, '--network', TEMPORARY_DOMAIN, '--provider', TEST_PROVIDER_URI, '--checksum-address', felix, '--registry-filepath', agency_local_registry.filepath, '--debug') result = click_runner.invoke(nucypher_cli, felix_init_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 # All configuration files still exist. assert os.path.isdir(custom_filepath) assert os.path.isfile(felix_file_location) assert os.path.isdir(public_keys_dir) assert len(os.listdir(public_keys_dir)) == 3 # Use a custom local filepath to init a persistent Alice alice_init_args = ('alice', 'init', '--network', TEMPORARY_DOMAIN, '--provider', TEST_PROVIDER_URI, '--pay-with', alice, '--registry-filepath', agency_local_registry.filepath, '--config-root', custom_filepath) result = click_runner.invoke(nucypher_cli, alice_init_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 # All configuration files still exist. assert os.path.isfile(felix_file_location) assert os.path.isfile(alice_file_location) assert len(os.listdir(public_keys_dir)) == 5 # Use the same local filepath to init a persistent Ursula init_args = ('ursula', 'init', '--network', TEMPORARY_DOMAIN, '--provider', TEST_PROVIDER_URI, '--worker-address', ursula, '--rest-host', MOCK_IP_ADDRESS, '--registry-filepath', agency_local_registry.filepath, '--config-root', custom_filepath) result = click_runner.invoke(nucypher_cli, init_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0, result.output # All configuration files still exist. assert len(os.listdir(public_keys_dir)) == 8 assert os.path.isfile(felix_file_location) assert os.path.isfile(alice_file_location) assert os.path.isfile(ursula_file_location) # keyring signing key signing_public_key = SecretKey.random().public_key() with patch('nucypher.config.keyring.NucypherKeyring.signing_public_key', PropertyMock(return_value=signing_public_key)): # Use the same local filepath to init another persistent Ursula init_args = ('ursula', 'init', '--network', TEMPORARY_DOMAIN, '--worker-address', another_ursula, '--rest-host', MOCK_IP_ADDRESS_2, '--registry-filepath', agency_local_registry.filepath, '--provider', TEST_PROVIDER_URI, '--config-root', custom_filepath) result = click_runner.invoke(nucypher_cli, init_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 another_ursula_configuration_file_location = custom_filepath / UrsulaConfiguration.generate_filename( modifier=bytes(signing_public_key).hex()[:8]) # All configuration files still exist. assert os.path.isfile(felix_file_location) assert os.path.isfile(alice_file_location) assert os.path.isfile(another_ursula_configuration_file_location) assert os.path.isfile(ursula_file_location) assert len(os.listdir(public_keys_dir)) == 11 # # Run # # Run an Ursula amidst the other configuration files run_args = ('ursula', 'run', '--dry-run', '--no-ip-checkup', '--config-file', another_ursula_configuration_file_location) user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}\n' * 2 Worker.READY_POLL_RATE = 1 Worker.READY_TIMEOUT = 1 with pytest.raises(Teacher.UnbondedWorker): # Worker init success, but not bonded. result = click_runner.invoke(nucypher_cli, run_args, input=user_input, catch_exceptions=False) assert result.exit_code == 0 Worker.READY_TIMEOUT = None # All configuration files still exist. assert os.path.isfile(felix_file_location) assert os.path.isfile(alice_file_location) assert os.path.isfile(another_ursula_configuration_file_location) assert os.path.isfile(ursula_file_location) assert len(os.listdir(public_keys_dir)) == 11 # Check that the proper Ursula console is attached assert another_ursula in result.output # # Destroy # another_ursula_destruction_args = ( 'ursula', 'destroy', '--force', '--config-file', another_ursula_configuration_file_location) result = click_runner.invoke(nucypher_cli, another_ursula_destruction_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 assert len(os.listdir(public_keys_dir)) == 8 assert not os.path.isfile(another_ursula_configuration_file_location) ursula_destruction_args = ('ursula', 'destroy', '--config-file', ursula_file_location) result = click_runner.invoke(nucypher_cli, ursula_destruction_args, input='Y', catch_exceptions=False, env=envvars) assert result.exit_code == 0 assert 'y/N' in result.output assert len(os.listdir(public_keys_dir)) == 5 assert not os.path.isfile(ursula_file_location) alice_destruction_args = ('alice', 'destroy', '--force', '--config-file', alice_file_location) result = click_runner.invoke(nucypher_cli, alice_destruction_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 assert len(os.listdir(public_keys_dir)) == 3 assert not os.path.isfile(alice_file_location) felix_destruction_args = ('felix', 'destroy', '--force', '--config-file', felix_file_location) result = click_runner.invoke(nucypher_cli, felix_destruction_args, catch_exceptions=False, env=envvars) assert result.exit_code == 0 assert len(os.listdir(public_keys_dir)) == 0 assert not os.path.isfile(felix_file_location)
def test_verify_eip191(testerchain, signature_verifier): message = os.urandom(100) # Generate Umbral key umbral_privkey = SecretKey.random() umbral_pubkey = umbral_privkey.public_key() umbral_pubkey_bytes = pubkey_as_uncompressed_bytes(umbral_pubkey) # # Check EIP191 signatures: Version E # # Produce EIP191 signature (version E) signable_message = encode_defunct(primitive=message) signature = Account.sign_message(signable_message=signable_message, private_key=bytes(umbral_privkey)) signature = bytes(signature.signature) # Off-chain verify, just in case checksum_address = to_checksum_address( canonical_address_from_umbral_key(umbral_pubkey)) assert verify_eip_191(address=checksum_address, message=message, signature=signature) # Verify signature on-chain version_E = b'E' assert signature_verifier.functions.verifyEIP191(message, signature, umbral_pubkey_bytes, version_E).call() # Of course, it'll fail if we try using version 0 version_0 = b'\x00' assert not signature_verifier.functions.verifyEIP191( message, signature, umbral_pubkey_bytes, version_0).call() # Check that the hash-based method also works independently hash = signature_verifier.functions.hashEIP191(message, version_E).call() eip191_header = "\x19Ethereum Signed Message:\n" + str(len(message)) assert hash == keccak_digest(eip191_header.encode() + message) address = signature_verifier.functions.recover(hash, signature).call() assert address == checksum_address # # Check EIP191 signatures: Version 0 # # Produce EIP191 signature (version 0) validator = to_canonical_address(signature_verifier.address) signable_message = SignableMessage(version=HexBytes(version_0), header=HexBytes(validator), body=HexBytes(message)) signature = Account.sign_message(signable_message=signable_message, private_key=bytes(umbral_privkey)) signature = bytes(signature.signature) # Off-chain verify, just in case checksum_address = to_checksum_address( canonical_address_from_umbral_key(umbral_pubkey)) assert checksum_address == Account.recover_message( signable_message=signable_message, signature=signature) # On chain verify signature assert signature_verifier.functions.verifyEIP191(message, signature, umbral_pubkey_bytes, version_0).call() # Of course, now it fails if we try with version E assert not signature_verifier.functions.verifyEIP191( message, signature, umbral_pubkey_bytes, version_E).call() # Check that the hash-based method also works independently hash = signature_verifier.functions.hashEIP191(message, version_0).call() eip191_header = b"\x19\x00" + validator assert hash == keccak_digest(eip191_header + message) address = signature_verifier.functions.recover(hash, signature).call() assert address == checksum_address
def bob_encrypting_key(): return bytes(SecretKey.random().public_key()).hex()
def bob_verifying_key(): return bytes(SecretKey.random().public_key()).hex()
def test_keypair_serialization(): umbral_pubkey = SecretKey.random().public_key() new_keypair = keypairs.Keypair(public_key=umbral_pubkey) pubkey_bytes = bytes(new_keypair.pubkey) assert pubkey_bytes == bytes(umbral_pubkey)