Skip to content

Commit

Permalink
Merge pull request #26 from derekpierre/unencrypted-dkg-support
Browse files Browse the repository at this point in the history
Porter now supports CBD threshold decryption (base support and not yet E2EE)
  • Loading branch information
derekpierre authored May 23, 2023
2 parents b58a0bf + 7388ad2 commit 52035f7
Show file tree
Hide file tree
Showing 22 changed files with 1,180 additions and 490 deletions.
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,
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

0 comments on commit 52035f7

Please sign in to comment.