Enrico uses a web3 wallet to encrypt-then-sign.

pull/3213/head
Kieran Prasch 2023-09-11 19:05:12 +02:00
parent 243448d015
commit 783f2dcd76
8 changed files with 63 additions and 21 deletions

View File

@ -81,6 +81,7 @@ from nucypher.blockchain.eth.registry import (
BaseContractRegistry,
InMemoryContractRegistry,
)
from nucypher.blockchain.eth.signers import Signer
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.characters.banners import (
ALICE_BANNER,
@ -1457,9 +1458,14 @@ class Enrico:
banner = ENRICO_BANNER
def __init__(self, encrypting_key: Union[PublicKey, DkgPublicKey]):
def __init__(
self,
encrypting_key: Union[PublicKey, DkgPublicKey],
signer: Optional[Signer] = None,
):
self.signer = signer
self.signing_power = SigningPower()
self._policy_pubkey = encrypting_key
self._encrypting_key = encrypting_key
self.log = Logger(f"{self.__class__.__name__}-{encrypting_key}")
self.log.info(self.banner.format(encrypting_key))
@ -1477,20 +1483,27 @@ class Enrico:
return message_kit
def encrypt_for_dkg(
self, plaintext: bytes, conditions: Lingo
self,
plaintext: bytes,
conditions: Lingo,
) -> ThresholdMessageKit:
if not self.signer:
raise TypeError("This Enrico doesn't have a signer.")
validate_condition_lingo(conditions)
conditions_json = json.dumps(conditions)
access_conditions = Conditions(conditions_json)
# encrypt for DKG
ciphertext, auth_data = encrypt_for_dkg(
plaintext, self.policy_pubkey, access_conditions
)
# authentication for AllowLogic
# TODO Replace with `Signer` to be passed as parameter
# authentication message for TACo
header_hash = keccak_digest(bytes(ciphertext.header))
authorization = self.signing_power.keypair.sign(header_hash).to_be_bytes()
authorization = self.signer.sign_message(
message=header_hash, account=self.signer.accounts[0]
)
return ThresholdMessageKit(
ciphertext=ciphertext,
@ -1509,8 +1522,8 @@ class Enrico:
@property
def policy_pubkey(self):
if not self._policy_pubkey:
if not self._encrypting_key:
raise TypeError(
"This Enrico doesn't know which policy encrypting key he used. Oh well."
)
return self._policy_pubkey
return self._encrypting_key

View File

@ -2,6 +2,7 @@ import pytest
import pytest_twisted
from twisted.internet.threads import deferToThread
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.blockchain.eth.trackers.dkg import EventScannerTask
from nucypher.characters.lawful import Enrico
from nucypher.policy.conditions.lingo import ConditionLingo
@ -134,9 +135,12 @@ def test_ursula_ritualist(
# prepare message and conditions
plaintext = PLAINTEXT.encode()
# create Enrico
signer = Web3Signer(client=testerchain.client)
enrico = Enrico(encrypting_key=encrypting_key, signer=signer)
# encrypt
# print(f'encrypting for DKG with key {bytes(encrypting_key.to_bytes()).hex()}')
enrico = Enrico(encrypting_key=encrypting_key)
print(f"encrypting for DKG with key {bytes(encrypting_key).hex()}")
threshold_message_kit = enrico.encrypt_for_dkg(
plaintext=plaintext, conditions=CONDITIONS
)

View File

@ -60,6 +60,7 @@ def test_character_transacting_power_signing(testerchain, test_registry):
restored_dict = restored_transaction.as_dict()
assert to_checksum_address(restored_dict['to']) == transaction_dict['to']
def test_transacting_power_sign_message(testerchain):
# Manually create a TransactingPower

View File

@ -10,6 +10,7 @@ from twisted.internet.threads import deferToThread
from web3.datastructures import AttributeDict
from nucypher.blockchain.eth.agents import CoordinatorAgent
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.characters.lawful import Enrico, Ursula
from nucypher.policy.conditions.lingo import ConditionLingo
from tests.constants import TESTERCHAIN_CHAIN_ID
@ -116,6 +117,7 @@ def execute_round_2(ritual_id: int, cohort: List[Ursula]):
ursula.ritual_tracker._handle_ritual_event(event, get_block_when=lambda x: event)
@pytest.mark.usefixtures("mock_sign_message")
@pytest.mark.parametrize('dkg_size, ritual_id, variant', PARAMS)
@pytest_twisted.inlineCallbacks()
def test_ursula_ritualist(
@ -195,9 +197,12 @@ def test_ursula_ritualist(
# prepare message and conditions
plaintext = PLAINTEXT.encode()
# create Enrico
signer = Web3Signer(client=testerchain.client)
enrico = Enrico(encrypting_key=encrypting_key, signer=signer)
# encrypt
# print(f'encrypting for DKG with key {bytes(encrypting_key.to_bytes()).hex()}')
enrico = Enrico(encrypting_key=encrypting_key)
print(f"encrypting for DKG with key {bytes(encrypting_key).hex()}")
threshold_message_kit = enrico.encrypt_for_dkg(
plaintext=plaintext, conditions=CONDITIONS
)

View File

@ -1,5 +1,6 @@
import pytest
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.characters.chaotic import (
NiceGuyEddie,
ThisBobAlwaysDecrypts,
@ -14,10 +15,11 @@ from tests.constants import (
)
def _attempt_decryption(BobClass, plaintext):
def _attempt_decryption(BobClass, plaintext, testerchain):
trinket = 80 # Doens't matter.
enrico = NiceGuyEddie(encrypting_key=trinket)
signer = Web3Signer(client=testerchain.client)
enrico = NiceGuyEddie(encrypting_key=trinket, signer=signer)
bob = BobClass(
registry=MOCK_REGISTRY_FILEPATH,
domain="lynx",
@ -46,13 +48,15 @@ def _attempt_decryption(BobClass, plaintext):
return decrypted_cleartext
def test_user_controls_success():
@pytest.mark.usefixtures("mock_sign_message")
def test_user_controls_success(testerchain):
plaintext = b"ever thus to deadbeats"
result = _attempt_decryption(ThisBobAlwaysDecrypts, plaintext)
result = _attempt_decryption(ThisBobAlwaysDecrypts, plaintext, testerchain)
assert bytes(result) == bytes(plaintext)
def test_user_controls_failure():
@pytest.mark.usefixtures("mock_sign_message")
def test_user_controls_failure(testerchain):
plaintext = b"ever thus to deadbeats"
with pytest.raises(Ursula.NotEnoughUrsulas):
_ = _attempt_decryption(ThisBobAlwaysFails, plaintext)
_ = _attempt_decryption(ThisBobAlwaysFails, plaintext, testerchain)

View File

@ -151,6 +151,7 @@ def test_tls_hosting_certificate_remains_the_same(temp_dir_path, mocker):
recreated_ursula.disenchant()
@pytest.mark.usefixtures("mock_sign_message")
def test_ritualist(temp_dir_path, testerchain, dkg_public_key):
keystore = Keystore.generate(
password=INSECURE_DEVELOPMENT_PASSWORD, keystore_dir=temp_dir_path
@ -186,11 +187,15 @@ def test_ritualist(temp_dir_path, testerchain, dkg_public_key):
},
}
# create enrico
signer = Web3Signer(client=testerchain.client)
enrico = Enrico(encrypting_key=dkg_public_key, signer=signer)
# encrypt
enrico = Enrico(encrypting_key=dkg_public_key)
threshold_message_kit = enrico.encrypt_for_dkg(
plaintext=plaintext, conditions=CONDITIONS
)
decryption_request = ThresholdDecryptionRequest(
ritual_id=ritual_id,
variant=FerveoVariant.Simple,

View File

@ -1,3 +1,4 @@
import os
from pathlib import Path
from typing import Iterable, Optional
@ -20,6 +21,7 @@ from nucypher.blockchain.eth.interfaces import (
from nucypher.blockchain.eth.networks import NetworksInventory
from nucypher.blockchain.eth.registry import InMemoryContractRegistry
from nucypher.blockchain.eth.signers import KeystoreSigner
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.characters.lawful import Ursula
from nucypher.cli.types import ChecksumAddress
from nucypher.config.characters import UrsulaConfiguration
@ -59,6 +61,14 @@ def mock_sample_reservoir(testerchain, mock_contract_agency):
mock_agent.get_staking_provider_reservoir = mock_reservoir
@pytest.fixture(scope="function")
def mock_sign_message(mocker):
mocked_sign_message = mocker.patch.object(
Web3Signer, "sign_message", return_value=os.urandom(32)
)
return mocked_sign_message
@pytest.fixture(scope="function", autouse=True)
def mock_application_agent(
testerchain, application_economics, mock_contract_agency, mocker

View File

@ -7,7 +7,7 @@ from typing import Union
from hexbytes import HexBytes
from nucypher.blockchain.eth.clients import EthereumClient
from nucypher.blockchain.eth.clients import EthereumTesterClient
from nucypher.blockchain.eth.networks import NetworksInventory
from nucypher.blockchain.eth.registry import (
BaseContractRegistry,
@ -82,7 +82,7 @@ class MockBlockchain(TesterBlockchain):
return self.FAKE_RECEIPT
class MockEthereumClient(EthereumClient):
class MockEthereumClient(EthereumTesterClient):
def __init__(self, w3):
super().__init__(w3=w3, node_technology=None, version=None, platform=None, backend=None)