Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Porter now supports CBD threshold decryption (base support and not yet E2EE) #26

Merged
merged 11 commits into from
May 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ repos:

- id: tests
name: Run Unit Tests
entry: scripts/run_unit_tests.sh
entry: scripts/run_tests.sh
language: system
types: [python]
stages: [push] # required additional setup: pre-commit install && pre-commit install -t pre-push
Expand Down Expand Up @@ -39,7 +39,7 @@ repos:


- repo: https://github.com/akaihola/darker
rev: 1.4.2
rev: 1.6.1
hooks:
- id: darker
args: [--isort]
Expand Down
28 changes: 14 additions & 14 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 8 additions & 8 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@ eth-hash==0.5.1 ; python_version >= '3.7' and python_version < '4'
eth-keyfile==0.6.1
eth-keys==0.4.0
eth-rlp==0.3.0 ; python_version >= '3.7' and python_version < '4'
eth-tester==0.8.0b3 ; python_version < '4' and python_full_version >= '3.6.8'
eth-typing==3.3.0 ; python_version < '4' and python_full_version >= '3.7.2'
eth-tester==0.8.0b3 ; python_full_version >= '3.6.8' and python_version < '4'
eth-typing==3.3.0 ; python_full_version >= '3.7.2' and python_version < '4'
eth-utils==2.1.0 ; python_version >= '3.7' and python_version < '4'
ethpm-types==0.4.5 ; python_version >= '3.8' and python_version < '4'
evm-trace==0.1.0a18 ; python_version >= '3.8' and python_version < '4'
exceptiongroup==1.1.1 ; python_version >= '3.7'
executing==1.2.0
ferveo==0.1.8 ; python_version >= '3.7'
ferveo==0.1.11 ; python_version >= '3.7'
filelock==3.12.0 ; python_version >= '3.7'
flask==2.2.5 ; python_version >= '3.7'
frozenlist==1.3.3 ; python_version >= '3.7'
Expand Down Expand Up @@ -86,7 +86,7 @@ msgspec==0.14.2 ; python_version >= '3.8'
multidict==5.2.0 ; python_version >= '3.6'
mypy-extensions==0.4.4 ; python_version >= '2.7'
nodeenv==1.7.0 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'
nucypher @ git+https://github.com/nucypher/nucypher.git@20d750a1c6869beee8a6d137cea7e3c7d3e1f108
nucypher @ git+https://github.com/nucypher/nucypher.git@5a06721d7d18c523217cb3308942fb5c378eb081
nucypher-core==0.7.0
numpy==1.24.3 ; python_version >= '3.8'
packaging==23.1 ; python_version >= '3.7'
Expand Down Expand Up @@ -133,7 +133,7 @@ pytest-mock==3.10.0
pytest-timeout==2.1.0 ; python_version >= '3.6'
pytest-twisted==1.14.0 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
python-baseconv==1.2.2
python-dateutil==2.8.2 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
python-dateutil==2.8.2 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'
pytz==2023.3
pyyaml==6.0 ; python_version >= '3.6'
referencing==0.28.0 ; python_version >= '3.8'
Expand All @@ -143,17 +143,17 @@ rich==12.6.0 ; python_full_version >= '3.6.3' and python_full_version < '4.0.0'
rlp==3.0.0
rpds-py==0.7.1 ; python_version >= '3.8'
semantic-version==2.10.0 ; python_version >= '2.7'
sentry-sdk==1.22.0
sentry-sdk==1.22.2
service-identity==21.1.0
setuptools==67.7.2 ; python_version >= '3.7'
six==1.16.0 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
six==1.16.0 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'
snaptime==0.2.4
sortedcontainers==2.4.0
sqlalchemy==2.0.12 ; python_version >= '3.7'
stack-data==0.6.2
tabulate==0.9.0 ; python_version >= '3.7'
time-machine==2.9.0 ; python_version >= '3.7'
toml==0.10.2 ; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'
toml==0.10.2 ; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'
tomli==2.0.1 ; python_version >= '3.7'
toolz==0.12.0 ; python_version >= '3.5'
tqdm==4.65.0 ; python_version >= '3.7'
Expand Down
37 changes: 37 additions & 0 deletions porter/fields/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,40 @@ def _deserialize(self, value, attr, data, **kwargs):
f"Unexpected object type, {type(result)}; expected {self.expected_type}")

return result


class JSONDict(BaseField, fields.Dict):
"""Serializes/Deserializes Dictionaries to/from JSON strings."""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

def _serialize(self, value, attr, obj, **kwargs):
try:
value = super()._serialize(value, attr, obj, **kwargs)
except Exception as e:
raise InvalidInputData(
f"Could not convert input for {self.name} to JSON: {e}"
)
try:
value_json = json.dumps(value)
return value_json
except Exception as e:
raise InvalidInputData(
f"Could not convert input for {self.name} to JSON: {e}"
)

def _deserialize(self, value, attr, data, **kwargs):
try:
result = json.loads(value)
except Exception as e:
raise InvalidInputData(
f"Could not convert input for {self.name} to dictionary: {e}"
)

try:
return super()._deserialize(result, attr, data, **kwargs)
except Exception as e:
raise InvalidInputData(
f"Could not convert input for {self.name} to dictionary: {e}"
)
4 changes: 2 additions & 2 deletions porter/fields/key.py → porter/fields/umbralkey.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from porter.fields.exceptions import InvalidInputData, InvalidNativeDataTypes


class Key(BaseField, fields.Field):
class UmbralKey(BaseField, fields.Field):

def _serialize(self, value, attr, obj, **kwargs):
if isinstance(value, PublicKey):
Expand All @@ -19,4 +19,4 @@ def _deserialize(self, value, attr, data, **kwargs):
try:
return PublicKey.from_compressed_bytes(bytes.fromhex(value))
except InvalidNativeDataTypes as e:
raise InvalidInputData(f"Could not convert input for {self.name} to an Umbral Key: {e}")
raise InvalidInputData(f"Could not convert input for {self.name} to an Umbral Key: {e}")
8 changes: 8 additions & 0 deletions porter/fields/ursula.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ class UrsulaChecksumAddress(String):
"""Ursula checksum address."""
click_type = EIP55_CHECKSUM_ADDRESS

def _serialize(self, value, attr, obj, **kwargs):
try:
return to_checksum_address(value=value)
except ValueError as e:
raise InvalidInputData(
f"Could not convert input for {self.name} to a valid checksum address: {e}"
)

def _deserialize(self, value, attr, data, **kwargs):
try:
return to_checksum_address(value=value)
Expand Down
13 changes: 13 additions & 0 deletions porter/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,16 @@ def retrieve_cfrags(self,
"retrieval_results": retrieval_outcomes
} # list of RetrievalOutcome objects
return response_data

@attach_schema(schema.CBDDecrypt)
def cbd_decrypt(
self,
threshold: int,
theref marked this conversation as resolved.
Show resolved Hide resolved
encrypted_decryption_requests: Dict[ChecksumAddress, bytes],
):
cbd_outcome = self.implementer.cbd_decrypt(
threshold=threshold,
encrypted_decryption_requests=encrypted_decryption_requests,
)
response_data = {"decryption_results": cbd_outcome}
return response_data
91 changes: 61 additions & 30 deletions porter/main.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
from pathlib import Path
from typing import Dict, List, NamedTuple, Optional, Sequence

from constant_sorrow.constants import (
NO_BLOCKCHAIN_CONNECTION,
NO_CONTROL_PROTOCOL
)
from constant_sorrow.constants import NO_CONTROL_PROTOCOL
from eth_typing import ChecksumAddress
from eth_utils import to_checksum_address
from flask import Response, request
from nucypher_core import RetrievalKit, TreasureMap
from nucypher_core.umbral import PublicKey

from nucypher.blockchain.eth.agents import ContractAgency, PREApplicationAgent
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
from nucypher.blockchain.eth.registry import (
Expand All @@ -19,16 +13,16 @@
)
from nucypher.characters.lawful import Ursula
from nucypher.crypto.powers import DecryptingPower
from nucypher.network.decryption import ThresholdDecryptionClient
from nucypher.network.nodes import Learner
from nucypher.network.retrieval import RetrievalClient
from nucypher.policy.reservoir import (
PrefetchStrategy,
make_staking_provider_reservoir,
)
from nucypher.network.retrieval import PRERetrievalClient
from nucypher.policy.reservoir import PrefetchStrategy, make_staking_provider_reservoir
from nucypher.utilities.concurrency import WorkerPool
from nucypher.utilities.logging import Logger
from porter.controllers import PorterCLIController
from porter.controllers import WebController
from nucypher_core import RetrievalKit, TreasureMap
from nucypher_core.umbral import PublicKey

from porter.controllers import PorterCLIController, WebController
from porter.interfaces import PorterInterface

BANNER = r"""
Expand Down Expand Up @@ -64,7 +58,7 @@ class UrsulaInfo(NamedTuple):
uri: str
encrypting_key: PublicKey

class RetrievalOutcome(NamedTuple):
class PRERetrievalOutcome(NamedTuple):
"""
Simple object that stores the results and errors of re-encryption operations across
one or more Ursulas.
Expand All @@ -73,6 +67,15 @@ class RetrievalOutcome(NamedTuple):
cfrags: Dict
errors: Dict

class CBDDecryptionOutcome(NamedTuple):
"""
Simple object that stores the results and errors of CBD decryption operations across
one or more Ursulas.
"""

decryption_responses: Dict[ChecksumAddress, bytes]
errors: Dict[ChecksumAddress, str]

def __init__(self,
domain: str = None,
registry: BaseContractRegistry = None,
Expand Down Expand Up @@ -147,14 +150,16 @@ def get_ursula_info(ursula_address) -> Porter.UrsulaInfo:
ursulas_info = successes.values()
return list(ursulas_info)

def retrieve_cfrags(self,
treasure_map: TreasureMap,
retrieval_kits: Sequence[RetrievalKit],
alice_verifying_key: PublicKey,
bob_encrypting_key: PublicKey,
bob_verifying_key: PublicKey,
context: Optional[Dict] = None) -> List[RetrievalOutcome]:
client = RetrievalClient(self)
def retrieve_cfrags(
self,
treasure_map: TreasureMap,
retrieval_kits: Sequence[RetrievalKit],
alice_verifying_key: PublicKey,
bob_encrypting_key: PublicKey,
bob_verifying_key: PublicKey,
context: Optional[Dict] = None,
) -> List[PRERetrievalOutcome]:
client = PRERetrievalClient(self)
context = context or dict() # must not be None
results, errors = client.retrieve_cfrags(
treasure_map,
Expand All @@ -166,18 +171,38 @@ def retrieve_cfrags(self,
)
result_outcomes = []
for result, error in zip(results, errors):
result_outcome = Porter.RetrievalOutcome(
result_outcome = Porter.PRERetrievalOutcome(
cfrags=result.cfrags, errors=error.errors
)
result_outcomes.append(result_outcome)
return result_outcomes

def _make_reservoir(self,
exclude_ursulas: Optional[Sequence[ChecksumAddress]] = None,
include_ursulas: Optional[Sequence[ChecksumAddress]] = None):
return make_staking_provider_reservoir(application_agent=self.application_agent,
exclude_addresses=exclude_ursulas,
include_addresses=include_ursulas)
def cbd_decrypt(
self,
threshold: int,
encrypted_decryption_requests: Dict[ChecksumAddress, bytes],
) -> CBDDecryptionOutcome:
decryption_client = ThresholdDecryptionClient(self)
successes, failures = decryption_client.gather_encrypted_decryption_shares(
encrypted_requests=encrypted_decryption_requests, threshold=threshold
)

cbd_outcome = Porter.CBDDecryptionOutcome(
decryption_responses=successes, errors=failures
)
return cbd_outcome


def _make_reservoir(
self,
exclude_ursulas: Optional[Sequence[ChecksumAddress]] = None,
include_ursulas: Optional[Sequence[ChecksumAddress]] = None,
):
return make_staking_provider_reservoir(
application_agent=self.application_agent,
exclude_addresses=exclude_ursulas,
include_addresses=include_ursulas,
)

def make_cli_controller(self, crash_on_error: bool = False):
controller = PorterCLIController(app_name=self.APP_NAME,
Expand Down Expand Up @@ -241,4 +266,10 @@ def retrieve_cfrags() -> Response:
response = controller(method_name='retrieve_cfrags', control_request=request)
return response

@porter_flask_control.route("/cbd_decrypt", methods=["POST"])
def cbd_decrypt() -> Response:
"""Porter control endpoint for executing a CBD decryption request."""
response = controller(method_name="cbd_decrypt", control_request=request)
return response

return controller
Loading