mirror of https://github.com/nucypher/nucypher.git
commit
9305d4d76c
9
Pipfile
9
Pipfile
|
@ -11,7 +11,7 @@ python_version = "3"
|
|||
constant-sorrow = ">=0.1.0a9"
|
||||
bytestring-splitter = ">=2.4.0"
|
||||
hendrix = ">=4.0"
|
||||
nucypher-core = ">=0.7.0"
|
||||
nucypher-core = ">=0.8.0"
|
||||
# Cryptography
|
||||
cryptography = ">=3.2"
|
||||
ferveo = ">=0.1.11"
|
||||
|
@ -30,7 +30,7 @@ requests = "*"
|
|||
# Third-Party Ethereum
|
||||
eip712-structs = "*"
|
||||
eth-tester = "*" # providers.py still uses this
|
||||
py-evm = "==0.6.1a2" # ape -> evm-trace needs this version
|
||||
py-evm = "*"
|
||||
web3 = ">=6.0.0"
|
||||
watchdog = "<3.0.0" # needed for eth-ape to be happy
|
||||
|
||||
|
@ -53,8 +53,9 @@ pytest-cov = "*"
|
|||
pytest-mock = "*"
|
||||
pytest-timeout = "*"
|
||||
# Tools
|
||||
eth-ape = ">=0.6.3,<0.7.0"
|
||||
ape-solidity = ">=0.6.0,<0.7.0"
|
||||
eth-ape = "*"
|
||||
# TODO eventually change to official release, issue #3131, once fix is available
|
||||
ape-solidity = ">=0.6.5"
|
||||
hypothesis = "*"
|
||||
pre-commit = "2.12.1"
|
||||
coverage = "<=6.5.0"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,8 @@
|
|||
-i https://pypi.python.org/simple
|
||||
aiohttp==3.8.2
|
||||
aiosignal==1.3.1 ; python_version >= '3.7'
|
||||
ape-solidity==0.6.3
|
||||
ape-solidity==0.6.5
|
||||
appnope==0.1.3 ; sys_platform == 'darwin'
|
||||
asttokens==2.2.1
|
||||
async-timeout==4.0.2 ; python_version >= '3.6'
|
||||
attrs==23.1.0 ; python_version >= '3.7'
|
||||
|
@ -9,7 +10,7 @@ backcall==0.2.0
|
|||
base58==1.0.3
|
||||
bitarray==2.7.3
|
||||
cached-property==1.5.2
|
||||
certifi==2022.12.7 ; python_version >= '3.6'
|
||||
certifi==2023.5.7 ; python_version >= '3.6'
|
||||
cffi==1.15.1
|
||||
cfgv==3.3.1 ; python_full_version >= '3.6.1'
|
||||
charset-normalizer==2.1.1 ; python_full_version >= '3.6.0'
|
||||
|
@ -25,41 +26,42 @@ distlib==0.3.6
|
|||
eip712==0.2.1 ; python_version >= '3.8' and python_version < '4'
|
||||
eth-abi==4.0.0 ; python_version >= '3.7' and python_version < '4'
|
||||
eth-account==0.8.0 ; python_version >= '3.6' and python_version < '4'
|
||||
eth-ape==0.6.8
|
||||
eth-ape==0.6.9
|
||||
eth-bloom==2.0.0 ; python_version >= '3.7' and python_version < '4'
|
||||
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
|
||||
eth-tester==0.9.0b1
|
||||
eth-typing==3.3.0 ; python_full_version >= '3.7.2' and python_version < '4'
|
||||
eth-utils==2.1.0
|
||||
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'
|
||||
ethpm-types==0.5.1 ; python_version >= '3.8' and python_version < '4'
|
||||
evm-trace==0.1.0a20 ; python_version >= '3.8' and python_version < '4'
|
||||
exceptiongroup==1.1.1 ; python_version < '3.11'
|
||||
executing==1.2.0
|
||||
filelock==3.12.0 ; python_version >= '3.7'
|
||||
frozenlist==1.3.3 ; python_version >= '3.7'
|
||||
greenlet==2.0.2 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
||||
hexbytes==0.3.0 ; python_version >= '3.7' and python_version < '4'
|
||||
hypothesis==6.75.1
|
||||
identify==2.5.23 ; python_version >= '3.7'
|
||||
hypothesis==6.75.3
|
||||
identify==2.5.24 ; python_version >= '3.7'
|
||||
idna==3.4 ; python_version >= '3.5'
|
||||
ijson==3.2.0.post0
|
||||
importlib-metadata==6.6.0 ; python_version < '3.10'
|
||||
importlib-resources==5.12.0 ; python_version < '3.9'
|
||||
iniconfig==2.0.0 ; python_version >= '3.7'
|
||||
ipython==8.12.1 ; python_version >= '3.8'
|
||||
ipython==8.12.2 ; python_version >= '3.8'
|
||||
jedi==0.18.2 ; python_version >= '3.6'
|
||||
jsonschema==4.18.0a6 ; python_version >= '3.8'
|
||||
jsonschema-specifications==2023.3.6 ; python_version >= '3.8'
|
||||
jsonschema==4.18.0a7 ; python_version >= '3.8'
|
||||
jsonschema-specifications==2023.5.1 ; python_version >= '3.8'
|
||||
lazyasd==0.1.4
|
||||
lru-dict==1.1.8
|
||||
matplotlib-inline==0.1.6 ; python_version >= '3.5'
|
||||
morphys==1.0
|
||||
msgspec==0.14.2 ; python_version >= '3.8'
|
||||
msgspec==0.15.1 ; 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'
|
||||
nodeenv==1.8.0 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'
|
||||
numpy==1.24.3 ; python_version < '3.10'
|
||||
packaging==23.1 ; python_version >= '3.7'
|
||||
pandas==1.5.3 ; python_version >= '3.8'
|
||||
|
@ -68,29 +70,29 @@ parso==0.8.3 ; python_version >= '3.6'
|
|||
pexpect==4.8.0 ; sys_platform != 'win32'
|
||||
pickleshare==0.7.5
|
||||
pkgutil-resolve-name==1.3.10 ; python_version < '3.9'
|
||||
platformdirs==3.5.0 ; python_version >= '3.7'
|
||||
platformdirs==3.5.1 ; python_version >= '3.7'
|
||||
pluggy==1.0.0 ; python_version >= '3.6'
|
||||
pre-commit==3.3.1
|
||||
pre-commit==3.3.2
|
||||
prompt-toolkit==3.0.38 ; python_full_version >= '3.7.0'
|
||||
protobuf==4.23.0rc2 ; python_version >= '3.7'
|
||||
protobuf==4.23.1 ; python_version >= '3.7'
|
||||
ptyprocess==0.7.0
|
||||
pure-eval==0.2.2
|
||||
py==1.11.0 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
||||
py-cid==0.3.0
|
||||
py-ecc==6.0.0 ; python_version >= '3.6' and python_version < '4'
|
||||
py-evm==0.6.1a2
|
||||
py-evm==0.7.0a2
|
||||
py-geth==3.12.0 ; python_version >= '3'
|
||||
py-multibase==1.0.3
|
||||
py-multicodec==0.2.1
|
||||
py-multihash==0.2.3
|
||||
py-solc-x==1.1.1 ; python_version >= '3.6' and python_version < '4'
|
||||
pycparser==2.21
|
||||
pycryptodome==3.17
|
||||
pydantic==1.10.7 ; python_version >= '3.7'
|
||||
pycryptodome==3.18.0
|
||||
pydantic==1.10.8 ; python_version >= '3.7'
|
||||
pyethash==0.1.27
|
||||
pygithub==1.58.1 ; python_version >= '3.7'
|
||||
pygithub==1.58.2 ; python_version >= '3.7'
|
||||
pygments==2.15.1 ; python_version >= '3.7'
|
||||
pyjwt[crypto]==2.6.0 ; python_version >= '3.7'
|
||||
pyjwt[crypto]==2.7.0 ; python_version >= '3.7'
|
||||
pynacl==1.5.0
|
||||
pysha3==1.0.2
|
||||
pytest==6.2.5
|
||||
|
@ -102,17 +104,17 @@ 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'
|
||||
pytz==2023.3
|
||||
pyyaml==6.0 ; python_version >= '3.6'
|
||||
referencing==0.28.0 ; python_version >= '3.8'
|
||||
regex==2023.5.4 ; python_version >= '3.6'
|
||||
requests==2.29.0
|
||||
referencing==0.28.3 ; python_version >= '3.8'
|
||||
regex==2023.5.5 ; python_version >= '3.6'
|
||||
requests==2.31.0
|
||||
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'
|
||||
setuptools==67.7.2 ; python_version >= '3.7'
|
||||
setuptools==67.8.0 ; python_version >= '3.7'
|
||||
six==1.16.0 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'
|
||||
sortedcontainers==2.4.0
|
||||
sqlalchemy==2.0.12 ; python_version >= '3.7'
|
||||
sqlalchemy==2.0.15 ; python_version >= '3.7'
|
||||
stack-data==0.6.2
|
||||
toml==0.10.2 ; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'
|
||||
tomli==2.0.1
|
||||
|
@ -120,14 +122,14 @@ toolz==0.12.0 ; python_version >= '3.5'
|
|||
tqdm==4.65.0 ; python_version >= '3.7'
|
||||
traitlets==5.9.0 ; python_version >= '3.7'
|
||||
trie==2.1.0 ; python_version >= '3.7' and python_version < '4'
|
||||
typing-extensions==4.5.0 ; python_version >= '3.7'
|
||||
urllib3==1.26.15 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'
|
||||
typing-extensions==4.6.0 ; python_version >= '3.7'
|
||||
urllib3==2.0.2 ; python_version >= '3.7'
|
||||
varint==1.0.2
|
||||
virtualenv==20.23.0 ; python_version >= '3.7'
|
||||
watchdog==2.3.1
|
||||
wcwidth==0.2.6
|
||||
web3==6.2.0
|
||||
websockets==11.0.2 ; python_version >= '3.7'
|
||||
web3==6.4.0
|
||||
websockets==11.0.3 ; python_version >= '3.7'
|
||||
wrapt==1.15.0 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
||||
yarl==1.9.2 ; python_version >= '3.7'
|
||||
zipp==3.15.0 ; python_version >= '3.7'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
-i https://pypi.python.org/simple
|
||||
alabaster==0.7.13 ; python_version >= '3.6'
|
||||
babel==2.12.1 ; python_version >= '3.7'
|
||||
certifi==2022.12.7 ; python_version >= '3.6'
|
||||
certifi==2023.5.7 ; python_version >= '3.6'
|
||||
charset-normalizer==2.1.1
|
||||
click==8.1.3 ; python_version >= '3.7'
|
||||
click-default-group==1.2.2
|
||||
|
@ -14,11 +14,11 @@ markupsafe==2.1.2 ; python_version >= '3.7'
|
|||
packaging==23.1 ; python_version >= '3.7'
|
||||
pygments==2.15.1 ; python_version >= '3.7'
|
||||
pytz==2023.3 ; python_version < '3.9'
|
||||
requests==2.29.0 ; python_version >= '3.7'
|
||||
setuptools==67.7.2 ; python_version >= '3.7'
|
||||
requests==2.31.0 ; python_version >= '3.7'
|
||||
setuptools==67.8.0 ; python_version >= '3.7'
|
||||
snowballstemmer==2.2.0
|
||||
sphinx==3.0.1
|
||||
sphinx-rtd-theme==1.2.0
|
||||
sphinx-rtd-theme==1.2.1
|
||||
sphinxcontrib-applehelp==1.0.4 ; python_version >= '3.8'
|
||||
sphinxcontrib-devhelp==1.0.2 ; python_version >= '3.5'
|
||||
sphinxcontrib-htmlhelp==2.0.1 ; python_version >= '3.8'
|
||||
|
@ -28,4 +28,4 @@ sphinxcontrib-qthelp==1.0.3 ; python_version >= '3.5'
|
|||
sphinxcontrib-serializinghtml==1.1.5 ; python_version >= '3.5'
|
||||
tomli==2.0.1 ; python_version < '3.11'
|
||||
towncrier==22.12.0
|
||||
urllib3==1.26.15 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'
|
||||
urllib3==2.0.2 ; python_version >= '3.7'
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
End-to-end encryption for CBD decryption requests.
|
|
@ -4,8 +4,15 @@ from typing import List, Optional, Tuple, Union
|
|||
|
||||
import maya
|
||||
from eth_typing import ChecksumAddress
|
||||
from ferveo_py import AggregatedTranscript, Ciphertext, PublicKey, Validator
|
||||
from ferveo_py import AggregatedTranscript, Ciphertext
|
||||
from ferveo_py import DkgPublicKey as FerveoPublicKey
|
||||
from ferveo_py import Validator
|
||||
from hexbytes import HexBytes
|
||||
from nucypher_core import (
|
||||
EncryptedThresholdDecryptionRequest,
|
||||
ThresholdDecryptionRequest,
|
||||
)
|
||||
from nucypher_core.umbral import PublicKey
|
||||
from web3 import Web3
|
||||
from web3.types import TxReceipt
|
||||
|
||||
|
@ -27,7 +34,12 @@ from nucypher.blockchain.eth.token import NU
|
|||
from nucypher.blockchain.eth.trackers.dkg import ActiveRitualTracker
|
||||
from nucypher.blockchain.eth.trackers.pre import WorkTracker
|
||||
from nucypher.crypto.ferveo.dkg import DecryptionShareSimple, FerveoVariant, Transcript
|
||||
from nucypher.crypto.powers import CryptoPower, RitualisticPower, TransactingPower
|
||||
from nucypher.crypto.powers import (
|
||||
CryptoPower,
|
||||
RitualisticPower,
|
||||
ThresholdRequestDecryptingPower,
|
||||
TransactingPower,
|
||||
)
|
||||
from nucypher.datastore.dkg import DKGStorage
|
||||
from nucypher.network.trackers import OperatorBondedTracker
|
||||
from nucypher.policy.conditions.lingo import ConditionLingo
|
||||
|
@ -293,9 +305,18 @@ class Ritualist(BaseActor):
|
|||
contract=self.coordinator_agent.contract
|
||||
)
|
||||
|
||||
self.publish_finalization = publish_finalization # publish the DKG final key if True
|
||||
self.dkg_storage = DKGStorage() # TODO: #3052 stores locally generated public DKG artifacts
|
||||
self.ritual_power = crypto_power.power_ups(RitualisticPower) # ferveo material contained within
|
||||
self.publish_finalization = (
|
||||
publish_finalization # publish the DKG final key if True
|
||||
)
|
||||
self.dkg_storage = (
|
||||
DKGStorage()
|
||||
) # TODO: #3052 stores locally generated public DKG artifacts
|
||||
self.ritual_power = crypto_power.power_ups(
|
||||
RitualisticPower
|
||||
) # ferveo material contained within
|
||||
self.threshold_request_power = crypto_power.power_ups(
|
||||
ThresholdRequestDecryptingPower
|
||||
) # used for secure decryption request channel
|
||||
|
||||
def get_ritual(self, ritual_id: int) -> CoordinatorAgent.Ritual:
|
||||
try:
|
||||
|
@ -353,7 +374,7 @@ class Ritualist(BaseActor):
|
|||
# look up the node index for this node on the blockchain
|
||||
receipt = self.coordinator_agent.post_transcript(
|
||||
ritual_id=ritual_id,
|
||||
transcript=bytes(transcript),
|
||||
transcript=transcript,
|
||||
transacting_power=self.transacting_power
|
||||
)
|
||||
return receipt
|
||||
|
@ -362,14 +383,18 @@ class Ritualist(BaseActor):
|
|||
self,
|
||||
ritual_id: int,
|
||||
aggregated_transcript: AggregatedTranscript,
|
||||
public_key: PublicKey,
|
||||
public_key: FerveoPublicKey,
|
||||
) -> TxReceipt:
|
||||
"""Publish an aggregated transcript to publicly available storage."""
|
||||
# look up the node index for this node on the blockchain
|
||||
request_encrypting_key = self.threshold_request_power.get_pubkey_from_ritual_id(
|
||||
ritual_id
|
||||
)
|
||||
receipt = self.coordinator_agent.post_aggregation(
|
||||
ritual_id=ritual_id,
|
||||
aggregated_transcript=bytes(aggregated_transcript),
|
||||
aggregated_transcript=aggregated_transcript,
|
||||
public_key=public_key,
|
||||
request_encrypting_key=request_encrypting_key,
|
||||
transacting_power=self.transacting_power
|
||||
)
|
||||
return receipt
|
||||
|
@ -553,6 +578,13 @@ class Ritualist(BaseActor):
|
|||
|
||||
return decryption_share
|
||||
|
||||
def decrypt_threshold_decryption_request(
|
||||
self, encrypted_request: EncryptedThresholdDecryptionRequest
|
||||
) -> Tuple[ThresholdDecryptionRequest, PublicKey]:
|
||||
return self.threshold_request_power.decrypt_encrypted_request(
|
||||
encrypted_request=encrypted_request
|
||||
)
|
||||
|
||||
|
||||
class PolicyAuthor(NucypherTokenActor):
|
||||
"""Alice base class for blockchain operations, mocking up new policies!"""
|
||||
|
|
|
@ -10,7 +10,8 @@ from constant_sorrow.constants import CONTRACT_ATTRIBUTE # type: ignore
|
|||
from constant_sorrow.constants import CONTRACT_CALL, TRANSACTION
|
||||
from eth_typing.evm import ChecksumAddress
|
||||
from eth_utils.address import to_checksum_address
|
||||
from ferveo_py.ferveo_py import DkgPublicKey
|
||||
from ferveo_py.ferveo_py import AggregatedTranscript, DkgPublicKey, Transcript
|
||||
from nucypher_core.umbral import PublicKey
|
||||
from web3.contract.contract import Contract, ContractFunction
|
||||
from web3.types import Timestamp, TxParams, TxReceipt, Wei
|
||||
|
||||
|
@ -555,6 +556,7 @@ class CoordinatorAgent(EthereumContractAgent):
|
|||
provider: ChecksumAddress
|
||||
aggregated: bool = False
|
||||
transcript: bytes = bytes()
|
||||
requestEncryptingKey: bytes = bytes()
|
||||
|
||||
class G1Point(NamedTuple):
|
||||
"""Coordinator contract representation of DkgPublicKey."""
|
||||
|
@ -610,6 +612,16 @@ class CoordinatorAgent(EthereumContractAgent):
|
|||
def shares(self) -> int:
|
||||
return len(self.providers)
|
||||
|
||||
@property
|
||||
def request_encrypting_keys(self):
|
||||
request_encrypting_keys = {}
|
||||
for p in self.participants:
|
||||
request_encrypting_keys[p.provider] = PublicKey.from_compressed_bytes(
|
||||
p.requestEncryptingKey
|
||||
)
|
||||
|
||||
return request_encrypting_keys
|
||||
|
||||
@contract_api(CONTRACT_CALL)
|
||||
def get_timeout(self) -> int:
|
||||
return self.contract.functions.timeout().call()
|
||||
|
@ -648,7 +660,10 @@ class CoordinatorAgent(EthereumContractAgent):
|
|||
participants = list()
|
||||
for r in result:
|
||||
participant = self.Ritual.Participant(
|
||||
provider=ChecksumAddress(r[0]), aggregated=r[1], transcript=bytes(r[2])
|
||||
provider=ChecksumAddress(r[0]),
|
||||
aggregated=r[1],
|
||||
transcript=bytes(r[2]),
|
||||
requestEncryptingKey=bytes(r[3]),
|
||||
)
|
||||
participants.append(participant)
|
||||
return participants
|
||||
|
@ -669,6 +684,7 @@ class CoordinatorAgent(EthereumContractAgent):
|
|||
provider=ChecksumAddress(result[0]),
|
||||
aggregated=result[1],
|
||||
transcript=bytes(result[2]),
|
||||
requestEncryptingKey=bytes(result[3]),
|
||||
)
|
||||
return participant
|
||||
|
||||
|
@ -688,12 +704,11 @@ class CoordinatorAgent(EthereumContractAgent):
|
|||
def post_transcript(
|
||||
self,
|
||||
ritual_id: int,
|
||||
transcript: bytes,
|
||||
transcript: Transcript,
|
||||
transacting_power: TransactingPower,
|
||||
) -> TxReceipt:
|
||||
contract_function: ContractFunction = self.contract.functions.postTranscript(
|
||||
ritualId=ritual_id,
|
||||
transcript=transcript
|
||||
ritualId=ritual_id, transcript=bytes(transcript)
|
||||
)
|
||||
receipt = self.blockchain.send_transaction(contract_function=contract_function,
|
||||
transacting_power=transacting_power)
|
||||
|
@ -703,14 +718,16 @@ class CoordinatorAgent(EthereumContractAgent):
|
|||
def post_aggregation(
|
||||
self,
|
||||
ritual_id: int,
|
||||
aggregated_transcript: bytes,
|
||||
aggregated_transcript: AggregatedTranscript,
|
||||
public_key: DkgPublicKey,
|
||||
request_encrypting_key: PublicKey,
|
||||
transacting_power: TransactingPower,
|
||||
) -> TxReceipt:
|
||||
contract_function: ContractFunction = self.contract.functions.postAggregation(
|
||||
ritualId=ritual_id,
|
||||
aggregatedTranscript=aggregated_transcript,
|
||||
aggregatedTranscript=bytes(aggregated_transcript),
|
||||
publicKey=self.Ritual.G1Point.from_dkg_public_key(public_key),
|
||||
requestEncryptingKey=request_encrypting_key.to_compressed_bytes(),
|
||||
)
|
||||
receipt = self.blockchain.send_transaction(
|
||||
contract_function=contract_function,
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
|
||||
|
||||
|
||||
from eth_tester import EthereumTester, PyEVMBackend
|
||||
from eth_tester.backends.mock.main import MockBackend
|
||||
from typing import Union
|
||||
|
|
|
@ -53,12 +53,12 @@ from nucypher_core import (
|
|||
NodeMetadataPayload,
|
||||
ReencryptionResponse,
|
||||
ThresholdDecryptionRequest,
|
||||
ThresholdDecryptionResponse,
|
||||
TreasureMap,
|
||||
)
|
||||
from nucypher_core.umbral import (
|
||||
PublicKey,
|
||||
RecoverableSignature,
|
||||
SecretKey,
|
||||
VerifiedKeyFrag,
|
||||
reencrypt,
|
||||
)
|
||||
|
@ -94,6 +94,7 @@ from nucypher.crypto.powers import (
|
|||
PowerUpError,
|
||||
RitualisticPower,
|
||||
SigningPower,
|
||||
ThresholdRequestDecryptingPower,
|
||||
TLSHostingPower,
|
||||
TransactingPower,
|
||||
)
|
||||
|
@ -574,7 +575,7 @@ class Bob(Character):
|
|||
if context:
|
||||
context = Context(json.dumps(context))
|
||||
decryption_request = ThresholdDecryptionRequest(
|
||||
id=ritual_id,
|
||||
ritual_id=ritual_id,
|
||||
variant=int(variant.value),
|
||||
ciphertext=bytes(ciphertext),
|
||||
conditions=conditions,
|
||||
|
@ -582,22 +583,37 @@ class Bob(Character):
|
|||
)
|
||||
return decryption_request
|
||||
|
||||
def get_decryption_shares_using_existing_decryption_request(self,
|
||||
decryption_request: ThresholdDecryptionRequest,
|
||||
variant: FerveoVariant,
|
||||
cohort: List["Ursula"],
|
||||
threshold: int,
|
||||
):
|
||||
def get_decryption_shares_using_existing_decryption_request(
|
||||
self,
|
||||
decryption_request: ThresholdDecryptionRequest,
|
||||
request_encrypting_keys: Dict[ChecksumAddress, PublicKey],
|
||||
variant: FerveoVariant,
|
||||
cohort: List["Ursula"],
|
||||
threshold: int,
|
||||
) -> Dict[
|
||||
ChecksumAddress, Union[DecryptionShareSimple, DecryptionSharePrecomputed]
|
||||
]:
|
||||
if variant == FerveoVariant.PRECOMPUTED:
|
||||
share_type = DecryptionSharePrecomputed
|
||||
elif variant == FerveoVariant.SIMPLE:
|
||||
share_type = DecryptionShareSimple
|
||||
|
||||
# use ephemeral key for request
|
||||
# TODO don't use Umbral in the long-run
|
||||
response_sk = SecretKey.random()
|
||||
response_encrypting_key = response_sk.public_key()
|
||||
|
||||
decryption_request_mapping = {}
|
||||
for ursula in cohort:
|
||||
ursula_checksum_address = to_checksum_address(ursula.checksum_address)
|
||||
request_encrypting_key = request_encrypting_keys[ursula_checksum_address]
|
||||
encrypted_decryption_request = decryption_request.encrypt(
|
||||
request_encrypting_key=request_encrypting_key,
|
||||
response_encrypting_key=response_encrypting_key,
|
||||
)
|
||||
decryption_request_mapping[
|
||||
to_checksum_address(ursula.checksum_address)
|
||||
] = bytes(decryption_request)
|
||||
ursula_checksum_address
|
||||
] = encrypted_decryption_request
|
||||
|
||||
decryption_client = ThresholdDecryptionClient(learner=self)
|
||||
successes, failures = decryption_client.gather_encrypted_decryption_shares(
|
||||
|
@ -605,12 +621,12 @@ class Bob(Character):
|
|||
)
|
||||
|
||||
if len(successes) < threshold:
|
||||
raise Ursula.NotEnoughUrsulas(f"Not enough Ursulas to decrypt")
|
||||
raise Ursula.NotEnoughUrsulas(f"Not enough Ursulas to decrypt: {failures}")
|
||||
self.log.debug(f"Got enough shares to decrypt.")
|
||||
|
||||
gathered_shares = {}
|
||||
for provider_address, response_bytes in successes.items():
|
||||
decryption_response = ThresholdDecryptionResponse.from_bytes(response_bytes)
|
||||
for provider_address, encrypted_decryption_response in successes.items():
|
||||
decryption_response = encrypted_decryption_response.decrypt(sk=response_sk)
|
||||
decryption_share = share_type.from_bytes(
|
||||
decryption_response.decryption_share
|
||||
)
|
||||
|
@ -618,37 +634,40 @@ class Bob(Character):
|
|||
return gathered_shares
|
||||
|
||||
def gather_decryption_shares(
|
||||
self,
|
||||
ritual_id: int,
|
||||
cohort: List["Ursula"],
|
||||
ciphertext: Ciphertext,
|
||||
lingo: LingoList,
|
||||
threshold: int,
|
||||
variant: FerveoVariant,
|
||||
context: Optional[dict] = None,
|
||||
self,
|
||||
ritual_id: int,
|
||||
cohort: List["Ursula"],
|
||||
ciphertext: Ciphertext,
|
||||
lingo: LingoList,
|
||||
threshold: int,
|
||||
variant: FerveoVariant,
|
||||
request_encrypting_keys: Dict[ChecksumAddress, PublicKey],
|
||||
context: Optional[dict] = None,
|
||||
) -> Dict[
|
||||
ChecksumAddress, Union[DecryptionShareSimple, DecryptionSharePrecomputed]
|
||||
]:
|
||||
decryption_request = self.make_decryption_request(
|
||||
ritual_id=ritual_id,
|
||||
ciphertext=ciphertext,
|
||||
lingo=lingo,
|
||||
variant=variant,
|
||||
context=context,
|
||||
)
|
||||
return self.get_decryption_shares_using_existing_decryption_request(
|
||||
decryption_request, request_encrypting_keys, variant, cohort, threshold
|
||||
)
|
||||
|
||||
decryption_request = self.make_decryption_request(ritual_id=ritual_id,
|
||||
ciphertext=ciphertext,
|
||||
lingo=lingo,
|
||||
variant=variant,
|
||||
context=context)
|
||||
return self.get_decryption_shares_using_existing_decryption_request(decryption_request, variant, cohort,
|
||||
threshold)
|
||||
|
||||
def threshold_decrypt(self,
|
||||
ritual_id: int,
|
||||
ciphertext: Ciphertext,
|
||||
conditions: LingoList,
|
||||
context: Optional[dict] = None,
|
||||
params: Optional[DkgPublicParameters] = None,
|
||||
ursulas: Optional[List['Ursula']] = None,
|
||||
variant: str = 'simple',
|
||||
peering_timeout: int = 60,
|
||||
) -> bytes:
|
||||
|
||||
def threshold_decrypt(
|
||||
self,
|
||||
ritual_id: int,
|
||||
ciphertext: Ciphertext,
|
||||
conditions: LingoList,
|
||||
context: Optional[dict] = None,
|
||||
params: Optional[DkgPublicParameters] = None,
|
||||
ursulas: Optional[List["Ursula"]] = None,
|
||||
variant: str = "simple",
|
||||
peering_timeout: int = 60,
|
||||
) -> bytes:
|
||||
# blockchain reads: get the DKG parameters and the cohort.
|
||||
coordinator_agent = ContractAgency.get_agent(CoordinatorAgent, registry=self.registry)
|
||||
ritual = coordinator_agent.get_ritual(ritual_id, with_participants=True)
|
||||
|
@ -660,19 +679,25 @@ class Bob(Character):
|
|||
ursulas = self.resolve_cohort(ritual=ritual, timeout=peering_timeout)
|
||||
else:
|
||||
for ursula in ursulas:
|
||||
if ursula.staking_provider_address not in ritual.participants:
|
||||
raise ValueError(f"{ursula} is not part of the cohort")
|
||||
if ursula.staking_provider_address not in ritual.providers:
|
||||
raise ValueError(
|
||||
f"{ursula} ({ursula.staking_provider_address}) is not part of the cohort"
|
||||
)
|
||||
self.remember_node(ursula)
|
||||
try:
|
||||
variant = FerveoVariant(getattr(FerveoVariant, variant.upper()).value)
|
||||
except AttributeError:
|
||||
raise ValueError(f"Invalid variant: {variant}; Options are: {list(v.name.lower() for v in list(FerveoVariant))}")
|
||||
raise ValueError(
|
||||
f"Invalid variant: {variant}; Options are: {list(v.name.lower() for v in list(FerveoVariant))}"
|
||||
)
|
||||
|
||||
threshold = (
|
||||
(ritual.shares // 2) + 1
|
||||
if variant == FerveoVariant.SIMPLE
|
||||
else ritual.shares
|
||||
) # TODO: #3095 get this from the ritual / put it on-chain?
|
||||
|
||||
request_encrypting_keys = ritual.request_encrypting_keys
|
||||
decryption_shares = self.gather_decryption_shares(
|
||||
ritual_id=ritual_id,
|
||||
cohort=ursulas,
|
||||
|
@ -681,6 +706,7 @@ class Bob(Character):
|
|||
lingo=conditions,
|
||||
threshold=threshold,
|
||||
variant=variant,
|
||||
request_encrypting_keys=request_encrypting_keys,
|
||||
)
|
||||
|
||||
if not params:
|
||||
|
@ -746,6 +772,7 @@ class Ursula(Teacher, Character, Operator, Ritualist):
|
|||
SigningPower,
|
||||
DecryptingPower,
|
||||
RitualisticPower,
|
||||
ThresholdRequestDecryptingPower,
|
||||
# TLSHostingPower # Still considered a default for Ursula, but needs the host context
|
||||
]
|
||||
|
||||
|
@ -1392,7 +1419,7 @@ class Enrico:
|
|||
ciphertext = self.encrypt_for_dkg(plaintext=plaintext,
|
||||
conditions=conditions)
|
||||
tdr = ThresholdDecryptionRequest(
|
||||
id=ritual_id,
|
||||
ritual_id=ritual_id,
|
||||
ciphertext=bytes(ciphertext),
|
||||
conditions=Conditions(json.dumps(conditions)),
|
||||
context=context,
|
||||
|
|
|
@ -105,7 +105,7 @@ class DecryptingKeypair(Keypair):
|
|||
|
||||
|
||||
class RitualisticKeypair(Keypair):
|
||||
"""A keypair for Ferveo"""
|
||||
"""A keypair for Ferveo DKG"""
|
||||
|
||||
_private_key_source = ferveo_py.Keypair.random
|
||||
_public_key_method = "public_key"
|
||||
|
|
|
@ -1,47 +1,50 @@
|
|||
from json import JSONDecodeError
|
||||
from os.path import abspath
|
||||
|
||||
import click
|
||||
import json
|
||||
import os
|
||||
import stat
|
||||
import string
|
||||
import time
|
||||
from json import JSONDecodeError
|
||||
from os.path import abspath
|
||||
from pathlib import Path
|
||||
from secrets import token_bytes
|
||||
from typing import Callable, ClassVar, Dict, List, Optional, Tuple, Union
|
||||
|
||||
import click
|
||||
from constant_sorrow.constants import KEYSTORE_LOCKED
|
||||
from ferveo_py import ferveo_py
|
||||
from mnemonic.mnemonic import Mnemonic
|
||||
from nucypher_core.umbral import SecretKeyFactory
|
||||
from pathlib import Path
|
||||
from secrets import token_bytes
|
||||
from typing import Callable, ClassVar, Dict, List, Union, Optional, Tuple
|
||||
|
||||
from nucypher.config.constants import DEFAULT_CONFIG_ROOT
|
||||
from nucypher.crypto.keypairs import HostingKeypair, RitualisticKeypair
|
||||
from nucypher.crypto.passwords import (
|
||||
SecretBoxAuthenticationError,
|
||||
derive_key_material_from_password,
|
||||
secret_box_decrypt,
|
||||
secret_box_encrypt,
|
||||
derive_key_material_from_password,
|
||||
SecretBoxAuthenticationError
|
||||
)
|
||||
from nucypher.crypto.powers import (
|
||||
CryptoPowerUp,
|
||||
DecryptingPower,
|
||||
DelegatingPower,
|
||||
DerivedKeyBasedPower,
|
||||
KeyPairBasedPower,
|
||||
RitualisticPower,
|
||||
SigningPower,
|
||||
CryptoPowerUp,
|
||||
DelegatingPower,
|
||||
TLSHostingPower, RitualisticPower,
|
||||
ThresholdRequestDecryptingPower,
|
||||
TLSHostingPower,
|
||||
)
|
||||
from nucypher.crypto.tls import generate_self_signed_certificate
|
||||
from nucypher.utilities.emitters import StdoutEmitter
|
||||
|
||||
# HKDF
|
||||
__INFO_BASE = b'NuCypher/'
|
||||
_SIGNING_INFO = __INFO_BASE + b'signing'
|
||||
_DECRYPTING_INFO = __INFO_BASE + b'decrypting'
|
||||
_DELEGATING_INFO = __INFO_BASE + b'delegating'
|
||||
_RITUALISTIC_INFO = __INFO_BASE + b'ritualistic'
|
||||
_TLS_INFO = __INFO_BASE + b'tls'
|
||||
__INFO_BASE = b"NuCypher/"
|
||||
_SIGNING_INFO = __INFO_BASE + b"signing"
|
||||
_DECRYPTING_INFO = __INFO_BASE + b"decrypting"
|
||||
_DELEGATING_INFO = __INFO_BASE + b"delegating"
|
||||
_RITUALISTIC_INFO = __INFO_BASE + b"ritualistic"
|
||||
_THRESHOLD_REQUEST_DECRYPTING_INFO = __INFO_BASE + b"threshoold_request_decrypting"
|
||||
_TLS_INFO = __INFO_BASE + b"tls"
|
||||
|
||||
# Wrapping key
|
||||
_SALT_SIZE = 32
|
||||
|
@ -224,11 +227,14 @@ class Keystore:
|
|||
_SUFFIX = 'priv'
|
||||
|
||||
# Powers derivation
|
||||
__HKDF_INFO = {SigningPower: _SIGNING_INFO,
|
||||
DecryptingPower: _DECRYPTING_INFO,
|
||||
DelegatingPower: _DELEGATING_INFO,
|
||||
TLSHostingPower: _TLS_INFO,
|
||||
RitualisticPower: _RITUALISTIC_INFO}
|
||||
__HKDF_INFO = {
|
||||
SigningPower: _SIGNING_INFO,
|
||||
DecryptingPower: _DECRYPTING_INFO,
|
||||
DelegatingPower: _DELEGATING_INFO,
|
||||
TLSHostingPower: _TLS_INFO,
|
||||
RitualisticPower: _RITUALISTIC_INFO,
|
||||
ThresholdRequestDecryptingPower: _THRESHOLD_REQUEST_DECRYPTING_INFO,
|
||||
}
|
||||
|
||||
class Exists(FileExistsError):
|
||||
pass
|
||||
|
|
|
@ -12,6 +12,10 @@ from ferveo_py import (
|
|||
Validator,
|
||||
)
|
||||
from hexbytes import HexBytes
|
||||
from nucypher_core import (
|
||||
EncryptedThresholdDecryptionRequest,
|
||||
ThresholdDecryptionRequest,
|
||||
)
|
||||
from nucypher_core.umbral import PublicKey, SecretKey, SecretKeyFactory, generate_kfrags
|
||||
|
||||
from nucypher.blockchain.eth.decorators import validate_checksum_address
|
||||
|
@ -47,6 +51,10 @@ class NoRitualisticPower(PowerUpError):
|
|||
pass
|
||||
|
||||
|
||||
class NoThresholdRequestDecryptingPower(PowerUpError):
|
||||
pass
|
||||
|
||||
|
||||
class CryptoPower(object):
|
||||
def __init__(self, power_ups: list = None) -> None:
|
||||
self.__power_ups = {} # type: dict
|
||||
|
@ -318,6 +326,35 @@ class DerivedKeyBasedPower(CryptoPowerUp):
|
|||
"""
|
||||
|
||||
|
||||
class ThresholdRequestDecryptingPower(DerivedKeyBasedPower):
|
||||
class ThresholdRequestDecryptionFailed(Exception):
|
||||
"""Raised when decryption of the request fails."""
|
||||
|
||||
def __init__(self, secret_key_factory: Optional[SecretKeyFactory] = None):
|
||||
if not secret_key_factory:
|
||||
secret_key_factory = SecretKeyFactory.random()
|
||||
self.__secret_key_factory = secret_key_factory
|
||||
|
||||
def _get_privkey_from_ritual_id(self, ritual_id: int):
|
||||
return self.__secret_key_factory.make_key(bytes(ritual_id))
|
||||
|
||||
def get_pubkey_from_ritual_id(self, ritual_id: int) -> PublicKey:
|
||||
return self._get_privkey_from_ritual_id(ritual_id).public_key()
|
||||
|
||||
def decrypt_encrypted_request(
|
||||
self, encrypted_request: EncryptedThresholdDecryptionRequest
|
||||
) -> Tuple[ThresholdDecryptionRequest, PublicKey]:
|
||||
try:
|
||||
priv_key = self._get_privkey_from_ritual_id(encrypted_request.ritual_id)
|
||||
e2e_request = encrypted_request.decrypt(sk=priv_key)
|
||||
return (
|
||||
e2e_request.decryption_request,
|
||||
e2e_request.response_encrypting_key,
|
||||
)
|
||||
except Exception as e:
|
||||
raise self.ThresholdRequestDecryptionFailed from e
|
||||
|
||||
|
||||
class DelegatingPower(DerivedKeyBasedPower):
|
||||
|
||||
def __init__(self, secret_key_factory: Optional[SecretKeyFactory] = None):
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
from typing import Dict, List, Tuple
|
||||
|
||||
from eth_typing import ChecksumAddress
|
||||
from nucypher_core import (
|
||||
EncryptedThresholdDecryptionRequest,
|
||||
EncryptedThresholdDecryptionResponse,
|
||||
)
|
||||
|
||||
from nucypher.network.client import ThresholdAccessControlClient
|
||||
from nucypher.utilities.concurrency import BatchValueFactory, WorkerPool
|
||||
|
||||
|
||||
class ThresholdDecryptionClient(ThresholdAccessControlClient):
|
||||
class DecryptionRequestFailed(Exception):
|
||||
class ThresholdDecryptionRequestFailed(Exception):
|
||||
"""Raised when a decryption request returns a non-zero status code."""
|
||||
|
||||
class DecryptionRequestFactory(BatchValueFactory):
|
||||
class ThresholdDecryptionRequestFactory(BatchValueFactory):
|
||||
def __init__(self, ursula_to_contact: List[ChecksumAddress], threshold: int):
|
||||
# TODO should we batch the ursulas to contact i.e. pass `batch_size` parameter
|
||||
super().__init__(values=ursula_to_contact, required_successes=threshold)
|
||||
|
@ -20,17 +24,22 @@ class ThresholdDecryptionClient(ThresholdAccessControlClient):
|
|||
|
||||
def gather_encrypted_decryption_shares(
|
||||
self,
|
||||
encrypted_requests: Dict[ChecksumAddress, bytes],
|
||||
encrypted_requests: Dict[ChecksumAddress, EncryptedThresholdDecryptionRequest],
|
||||
threshold: int,
|
||||
timeout: float = 10,
|
||||
) -> Tuple[Dict[ChecksumAddress, bytes], Dict[ChecksumAddress, str]]:
|
||||
) -> Tuple[
|
||||
Dict[ChecksumAddress, EncryptedThresholdDecryptionResponse],
|
||||
Dict[ChecksumAddress, str],
|
||||
]:
|
||||
self._ensure_ursula_availability(
|
||||
ursulas=list(encrypted_requests.keys()),
|
||||
threshold=threshold,
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
def worker(ursula_address: ChecksumAddress) -> bytes:
|
||||
def worker(
|
||||
ursula_address: ChecksumAddress,
|
||||
) -> EncryptedThresholdDecryptionResponse:
|
||||
encrypted_request = encrypted_requests[ursula_address]
|
||||
|
||||
try:
|
||||
|
@ -38,23 +47,24 @@ class ThresholdDecryptionClient(ThresholdAccessControlClient):
|
|||
node_or_sprout.mature()
|
||||
response = (
|
||||
self._learner.network_middleware.get_encrypted_decryption_share(
|
||||
node_or_sprout, encrypted_request
|
||||
node_or_sprout, bytes(encrypted_request)
|
||||
)
|
||||
)
|
||||
if response.status_code == 200:
|
||||
return EncryptedThresholdDecryptionResponse.from_bytes(
|
||||
response.content
|
||||
)
|
||||
except Exception as e:
|
||||
self.log.warn(f"Node {ursula_address} raised {e}")
|
||||
raise
|
||||
else:
|
||||
if response.status_code != 200:
|
||||
message = f"Node {ursula_address} returned {response.status_code} - {response.content}."
|
||||
self.log.warn(message)
|
||||
raise self.DecryptionRequestFailed(message)
|
||||
|
||||
return response.content
|
||||
message = f"Node {ursula_address} returned {response.status_code} - {response.content}."
|
||||
self.log.warn(message)
|
||||
raise self.ThresholdDecryptionRequestFailed(message)
|
||||
|
||||
worker_pool = WorkerPool(
|
||||
worker=worker,
|
||||
value_factory=self.DecryptionRequestFactory(
|
||||
value_factory=self.ThresholdDecryptionRequestFactory(
|
||||
ursula_to_contact=list(encrypted_requests.keys()), threshold=threshold
|
||||
),
|
||||
target_successes=threshold,
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import ferveo_py
|
||||
import time
|
||||
from collections import defaultdict, deque
|
||||
from contextlib import suppress
|
||||
from pathlib import Path
|
||||
from queue import Queue
|
||||
|
||||
from typing import Callable, List, Optional, Set, Tuple, Union
|
||||
|
||||
import ferveo_py
|
||||
import maya
|
||||
import requests
|
||||
from constant_sorrow.constants import (
|
||||
|
@ -37,7 +36,8 @@ from nucypher.crypto.powers import (
|
|||
CryptoPower,
|
||||
DecryptingPower,
|
||||
NoSigningPower,
|
||||
SigningPower, RitualisticPower,
|
||||
RitualisticPower,
|
||||
SigningPower,
|
||||
)
|
||||
from nucypher.crypto.signing import InvalidSignature, SignatureStamp
|
||||
from nucypher.network.exceptions import NodeSeemsToBeDown
|
||||
|
|
|
@ -10,6 +10,7 @@ from flask import Flask, Response, jsonify, request
|
|||
from mako import exceptions as mako_exceptions
|
||||
from mako.template import Template
|
||||
from nucypher_core import (
|
||||
EncryptedThresholdDecryptionRequest,
|
||||
MetadataRequest,
|
||||
MetadataResponse,
|
||||
MetadataResponsePayload,
|
||||
|
@ -145,9 +146,19 @@ def _make_rest_app(this_node, log: Logger) -> Flask:
|
|||
def threshold_decrypt():
|
||||
|
||||
# Deserialize and instantiate ThresholdDecryptionRequest from the request data
|
||||
decryption_request = ThresholdDecryptionRequest.from_bytes(request.data)
|
||||
encrypted_decryption_request = EncryptedThresholdDecryptionRequest.from_bytes(
|
||||
request.data
|
||||
)
|
||||
(
|
||||
decryption_request,
|
||||
response_encrypting_key,
|
||||
) = this_node.decrypt_threshold_decryption_request(
|
||||
encrypted_request=encrypted_decryption_request
|
||||
)
|
||||
|
||||
log.info(f"Threshold decryption request for ritual ID #{decryption_request.id}")
|
||||
log.info(
|
||||
f"Threshold decryption request for ritual ID #{decryption_request.ritual_id}"
|
||||
)
|
||||
|
||||
# Deserialize and instantiate ConditionLingo from the request data
|
||||
conditions_data = str(decryption_request.conditions) # nucypher_core.Conditions -> str
|
||||
|
@ -169,17 +180,22 @@ def _make_rest_app(this_node, log: Logger) -> Flask:
|
|||
|
||||
# TODO: #3052 consider using the DKGStorage cache instead of the coordinator agent
|
||||
# dkg_public_key = this_node.dkg_storage.get_public_key(decryption_request.ritual_id)
|
||||
ritual = this_node.coordinator_agent.get_ritual(decryption_request.id, with_participants=True)
|
||||
ritual = this_node.coordinator_agent.get_ritual(
|
||||
decryption_request.ritual_id, with_participants=True
|
||||
)
|
||||
participants = [p.provider for p in ritual.participants]
|
||||
|
||||
# enforces that the node is part of the ritual
|
||||
if this_node.checksum_address not in participants:
|
||||
return Response(f'Node not part of ritual {decryption_request.id}', status=HTTPStatus.FORBIDDEN)
|
||||
return Response(
|
||||
f"Node not part of ritual {decryption_request.ritual_id}",
|
||||
status=HTTPStatus.FORBIDDEN,
|
||||
)
|
||||
|
||||
# derive the decryption share
|
||||
ciphertext = Ciphertext.from_bytes(decryption_request.ciphertext)
|
||||
decryption_share = this_node.derive_decryption_share(
|
||||
ritual_id=decryption_request.id,
|
||||
ritual_id=decryption_request.ritual_id,
|
||||
ciphertext=ciphertext,
|
||||
conditions=decryption_request.conditions,
|
||||
variant=FerveoVariant(decryption_request.variant),
|
||||
|
@ -189,7 +205,11 @@ def _make_rest_app(this_node, log: Logger) -> Flask:
|
|||
# TODO: #3079 #3081 encrypt the response with the requester's public key
|
||||
# TODO: #3098 nucypher-core#49 Use DecryptionShare type
|
||||
response = ThresholdDecryptionResponse(decryption_share=bytes(decryption_share))
|
||||
return Response(bytes(response), headers={'Content-Type': 'application/octet-stream'})
|
||||
encrypted_response = response.encrypt(encrypting_key=response_encrypting_key)
|
||||
return Response(
|
||||
bytes(encrypted_response),
|
||||
headers={"Content-Type": "application/octet-stream"},
|
||||
)
|
||||
|
||||
@rest_app.route('/reencrypt', methods=["POST"])
|
||||
def reencrypt():
|
||||
|
|
|
@ -10,7 +10,7 @@ backports.zoneinfo==0.2.1 ; python_version >= '3.7' and python_version < '3.9'
|
|||
bitarray==2.7.3
|
||||
bytestring-splitter==2.4.1
|
||||
cached-property==1.5.2
|
||||
certifi==2022.12.7 ; python_version >= '3.6'
|
||||
certifi==2023.5.7 ; python_version >= '3.6'
|
||||
cffi==1.15.1
|
||||
charset-normalizer==2.1.1 ; python_full_version >= '3.6.0'
|
||||
click==8.1.3
|
||||
|
@ -28,10 +28,10 @@ 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
|
||||
eth-tester==0.9.0b1
|
||||
eth-typing==3.3.0 ; python_version < '4' and python_full_version >= '3.7.2'
|
||||
eth-utils==2.1.0
|
||||
ferveo==0.1.11
|
||||
ferveo==0.1.13
|
||||
flask==2.2.5
|
||||
frozenlist==1.3.3 ; python_version >= '3.7'
|
||||
hendrix==4.0.0
|
||||
|
@ -44,8 +44,8 @@ importlib-resources==5.12.0 ; python_version < '3.9'
|
|||
incremental==22.10.0
|
||||
itsdangerous==2.1.2 ; python_version >= '3.7'
|
||||
jinja2==3.0.3
|
||||
jsonschema==4.18.0a6 ; python_version >= '3.8'
|
||||
jsonschema-specifications==2023.3.6 ; python_version >= '3.8'
|
||||
jsonschema==4.18.0a7 ; python_version >= '3.8'
|
||||
jsonschema-specifications==2023.5.1 ; python_version >= '3.8'
|
||||
lru-dict==1.1.8
|
||||
mako==1.2.4
|
||||
markupsafe==2.1.2 ; python_version >= '3.7'
|
||||
|
@ -56,33 +56,33 @@ msgpack==1.0.5
|
|||
msgpack-python==0.5.6
|
||||
multidict==5.2.0 ; python_version >= '3.6'
|
||||
mypy-extensions==0.4.4 ; python_version >= '2.7'
|
||||
nucypher-core==0.7.0
|
||||
nucypher-core==0.8.0
|
||||
packaging==23.1 ; python_version >= '3.7'
|
||||
parsimonious==0.9.0
|
||||
pendulum==3.0.0a1 ; python_version >= '3.7' and python_version < '4.0'
|
||||
pkgutil-resolve-name==1.3.10 ; python_version < '3.9'
|
||||
protobuf==4.23.0rc2 ; python_version >= '3.7'
|
||||
protobuf==4.23.1 ; python_version >= '3.7'
|
||||
py-ecc==6.0.0 ; python_version >= '3.6' and python_version < '4'
|
||||
py-evm==0.6.1a2
|
||||
py-evm==0.7.0a2
|
||||
pyasn1==0.5.0 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'
|
||||
pyasn1-modules==0.3.0 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'
|
||||
pychalk==2.0.1
|
||||
pycparser==2.21
|
||||
pycryptodome==3.17
|
||||
pycryptodome==3.18.0
|
||||
pyethash==0.1.27
|
||||
pynacl==1.5.0
|
||||
pyopenssl==23.1.1
|
||||
pysha3==1.0.2
|
||||
python-dateutil==2.8.2 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
||||
pytz==2023.3
|
||||
referencing==0.28.0 ; python_version >= '3.8'
|
||||
regex==2023.5.4 ; python_version >= '3.6'
|
||||
requests==2.29.0
|
||||
referencing==0.28.3 ; python_version >= '3.8'
|
||||
regex==2023.5.5 ; python_version >= '3.6'
|
||||
requests==2.31.0
|
||||
rlp==3.0.0
|
||||
rpds-py==0.7.1 ; python_version >= '3.8'
|
||||
semantic-version==2.10.0 ; python_version >= '2.7'
|
||||
service-identity==21.1.0
|
||||
setuptools==67.7.2 ; python_version >= '3.7'
|
||||
setuptools==67.8.0 ; 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'
|
||||
snaptime==0.2.4
|
||||
sortedcontainers==2.4.0
|
||||
|
@ -92,14 +92,14 @@ toolz==0.12.0 ; python_version >= '3.5'
|
|||
trie==2.1.0 ; python_version >= '3.7' and python_version < '4'
|
||||
twisted==22.10.0 ; python_full_version >= '3.7.1'
|
||||
txaio==23.1.1 ; python_version >= '3.7'
|
||||
typing-extensions==4.5.0 ; python_version >= '3.7'
|
||||
typing-extensions==4.6.0 ; python_version >= '3.7'
|
||||
tzdata==2023.3 ; python_version >= '2'
|
||||
tzlocal==5.0b2 ; python_version >= '3.7'
|
||||
urllib3==1.26.15 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'
|
||||
tzlocal==5.0.1 ; python_version >= '3.7'
|
||||
urllib3==2.0.2 ; python_version >= '3.7'
|
||||
watchdog==2.3.1
|
||||
web3==6.2.0
|
||||
websockets==11.0.2 ; python_version >= '3.7'
|
||||
werkzeug==2.3.3 ; python_version >= '3.8'
|
||||
web3==6.4.0
|
||||
websockets==11.0.3 ; python_version >= '3.7'
|
||||
werkzeug==2.3.4 ; python_version >= '3.8'
|
||||
yarl==1.9.2 ; python_version >= '3.7'
|
||||
zipp==3.15.0 ; python_version >= '3.7'
|
||||
zope.interface==6.1a2 ; python_version >= '3.7'
|
||||
|
|
|
@ -2,6 +2,7 @@ import os
|
|||
|
||||
import pytest
|
||||
from eth_utils import keccak
|
||||
from nucypher_core.umbral import SecretKey
|
||||
|
||||
from nucypher.blockchain.eth.agents import (
|
||||
ContractAgency,
|
||||
|
@ -25,11 +26,6 @@ def transcripts():
|
|||
return [os.urandom(32), os.urandom(32)]
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def aggregated_transcript():
|
||||
return os.urandom(32)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def cohort(testerchain, staking_providers):
|
||||
deployer, cohort_provider_1, cohort_provider_2, *everybody_else = staking_providers
|
||||
|
@ -126,16 +122,20 @@ def test_post_transcript(agent, transcripts, transacting_powers):
|
|||
|
||||
|
||||
def test_post_aggregation(
|
||||
agent, aggregated_transcript, dkg_public_key, transacting_powers
|
||||
agent, aggregated_transcript, dkg_public_key, transacting_powers, cohort
|
||||
):
|
||||
ritual_id = agent.number_of_rituals() - 1
|
||||
request_encrypting_keys = {}
|
||||
for i, transacting_power in enumerate(transacting_powers):
|
||||
request_encrypting_key = SecretKey.random().public_key()
|
||||
receipt = agent.post_aggregation(
|
||||
ritual_id=ritual_id,
|
||||
aggregated_transcript=aggregated_transcript,
|
||||
public_key=dkg_public_key,
|
||||
request_encrypting_key=request_encrypting_key,
|
||||
transacting_power=transacting_power,
|
||||
)
|
||||
request_encrypting_keys[cohort[i]] = request_encrypting_key
|
||||
assert receipt["status"] == 1
|
||||
|
||||
post_aggregation_events = (
|
||||
|
@ -145,11 +145,19 @@ def test_post_aggregation(
|
|||
event = post_aggregation_events[0]
|
||||
assert event["args"]["ritualId"] == ritual_id
|
||||
assert event["args"]["aggregatedTranscriptDigest"] == keccak(
|
||||
aggregated_transcript
|
||||
bytes(aggregated_transcript)
|
||||
)
|
||||
|
||||
participants = agent.get_participants(ritual_id)
|
||||
assert all([p.aggregated for p in participants])
|
||||
for p in participants:
|
||||
assert p.aggregated
|
||||
assert (
|
||||
p.requestEncryptingKey
|
||||
== request_encrypting_keys[p.provider].to_compressed_bytes()
|
||||
)
|
||||
|
||||
ritual = agent.get_ritual(ritual_id)
|
||||
assert ritual.request_encrypting_keys == request_encrypting_keys
|
||||
|
||||
assert agent.get_ritual_status(ritual_id=ritual_id) == agent.Ritual.Status.FINALIZED
|
||||
|
||||
|
|
|
@ -12,7 +12,8 @@ dependencies:
|
|||
version: 4.8.1
|
||||
|
||||
solidity:
|
||||
version: 0.8.17
|
||||
version: 0.8.20
|
||||
evm_version: paris
|
||||
import_remapping:
|
||||
- "@openzeppelin/contracts=openzeppelin/v4.8.1"
|
||||
|
||||
|
@ -33,8 +34,6 @@ deployments:
|
|||
ritual_timeout: 3600
|
||||
max_dkg_size: 8
|
||||
|
||||
|
||||
|
||||
test:
|
||||
mnemonic: test test test test test test test test test test test junk
|
||||
number_of_accounts: 30
|
||||
|
|
|
@ -156,6 +156,3 @@ RPC_SUCCESSFUL_RESPONSE = {
|
|||
"id": 1,
|
||||
"result": "Geth/v1.9.20-stable-979fc968/linux-amd64/go1.15"
|
||||
}
|
||||
|
||||
|
||||
FAKE_TRANSCRIPT = b'\x98\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\xae\xdb_-\xeaj\x9bz\xdd\xd6\x98\xf8\xf91A\xc1\x8f;\x13@\x89\xcb\xcf>\x86\xc4T\xfb\x0c\x1ety\x8b\xd8mSkk\xbb\xcaU\xe5]v}E\xfa\xbc\xae\xb6\xa1\xf4e\x19\x86\xf2L\xcaZj\x03]h:\xbfP\x03Q\x8c\x95e\xe0c\xaa\xc2\xb4\xbby}\xecW%\xdet\xc8\xfc\xe7ky\xe5\xf6\xe9\xf5\x05\xe5\xdf\x81\x9bx\x18\xa4\x15\x85\xdeA9\x9f\x99\xceQ\xb0\xd0&\x9a\xa7\xaed&\x99\xdc\xa7\xfeLM\x01\x02\x87\xc8\x14$\x89"kA\x0b\x91\t\x1e\x1c/f\x00N,\x88\x01\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\xab\x0f\tFA\xdcB\xd4\xb3\x08\xd7IVkmw6za\xb6)\x13\x014]f.\xa1\xcd\xe27\xee\xc0\x95\xf6\xa4\x12\xa9\x19\x94\xed\x05\xffF\x81\xb2\xb2\xcb\x06\xaf-\xe4\xb5\x98\xbd\x81\x0f\xb8\xb7\xa1<\xf6/\xe5\xa4\x11\x83}\xfaH\x15\x80h\n\xe7\xc6\xc2\xb3\xd5{dH\xeb\x1e]v\xb4\x88v\x88\xb7N1\xff\x80\xd0\x88\x04.\x00\x82K\x1e\x96\xa0\xbd}X\xbb{?6\xeb\xe7\rg\x03\xeeG\x01\x10^\xee\x9cH\x94[\x9d8s\xa3\xb6\x8f\xfc\xf1\xdf\x01m\xf9\x08_N\xb5-\x16O\x89n\x95\xf3\x8b[\x1f&Yk?*\x07\x8fQ\x98\x85\xd5\xc1YL\xe0CB\xb2"!\x8d,\x90Q7\xca\x9c\x0e\xb2\x7f\xb0\xe1\xc8\xdd\xe7\xe1\xe4\x14\xb3\xa6\xb4\x8e\x8b\xed\xacM\xc3\x9d\xc4|U\x93k\x17\xac\x14\x86\x16\xd7\xebk\xbd{\xad}\x87\x13Y\x83\x9d\x88\x1e\x1b4\xa7r\xa6\x80\xbf\xf0\x15\x99\x11Q\xdb\xeb\xdf\x15ns\xc6\x85\xb3\x1d\xf5j\xc5\x87`=OD\x86\x86\x08\x8d\xb6\x0b\xec\x1d\x15\xc9\x93\x9a\xed\xa3\xe2\x96\xa4\xa2b\xa6\xa5h\xb0\xbb4\xb3\x0c\xa5\xdcu\x1f{\xb9\xaf\xd0W\xe1\xa3&\xa8\xb5\xea\xe5c\xfd\xc7?\xbdLg\xb3\xae\xb9\xb8*\xfc\xd5\xa6\xeeI\x15v\xdc\xa2`1VZ\xb5\x1c_`\x86\xbe{\xef\xae\t\xf2\xa9N\x00\x9a\xa1F\x84\xb2\xe3\xbc\xfa\xf7I\xee\xe8[~\x99;i\xfc%\xa8\x80\x80\x8e%\'\x9c+\x9c\xa9\x13R!\x80w\xc0\xda[\x84\xf6X\xfe\xc2\xe3\x0f\x94-\xbb`\x00\x00\x00\x00\x00\x00\x00\x93\xff\x1e\x1b\x15;e\xfe}\x83v K\xf9\r\xc9\xad\x9d\xddN\xcd\xcaWq\xfa\x8e\x98sn\x9b~t\x01 =p\xe5\xb1\x7f"!\xb4\xb9\xc9W\x90\x86\x80\x17\nm\xa0\x8dD\xb5\xaf\xfc\xa5\xf5%V]\xb9\x89a@\xe5\x0c@#%x\xecW\xed\xb0a\x98\x1a!C\x80B@{\xf0\xffJ{\xa3\xeayDP\'u'
|
||||
|
|
|
@ -6,15 +6,16 @@ import tempfile
|
|||
from datetime import timedelta
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
from typing import Tuple
|
||||
|
||||
import maya
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
from eth_account import Account
|
||||
from eth_utils import to_checksum_address
|
||||
from ferveo_py.ferveo_py import DkgPublicKey
|
||||
from ferveo_py.ferveo_py import AggregatedTranscript, DkgPublicKey, DkgPublicParameters
|
||||
from ferveo_py.ferveo_py import Keypair as FerveoKeyPair
|
||||
from ferveo_py.ferveo_py import Validator
|
||||
from ferveo_py.ferveo_py import Transcript, Validator
|
||||
from twisted.internet.task import Clock
|
||||
from web3 import Web3
|
||||
|
||||
|
@ -380,7 +381,7 @@ def log_in_and_out_of_test(request):
|
|||
test_logger.info(f"Finalized {module_name}.py::{test_name}")
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.fixture(scope="session")
|
||||
def get_random_checksum_address():
|
||||
def _get_random_checksum_address():
|
||||
canonical_address = os.urandom(20)
|
||||
|
@ -699,8 +700,10 @@ def ursulas(testerchain, staking_providers, ursula_test_config):
|
|||
_ursulas.clear()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def dkg_public_key(get_random_checksum_address) -> DkgPublicKey:
|
||||
@pytest.fixture(scope="session")
|
||||
def dkg_public_key_data(
|
||||
get_random_checksum_address,
|
||||
) -> Tuple[AggregatedTranscript, DkgPublicKey, DkgPublicParameters]:
|
||||
ritual_id = 0
|
||||
num_shares = 4
|
||||
threshold = 3
|
||||
|
@ -726,7 +729,7 @@ def dkg_public_key(get_random_checksum_address) -> DkgPublicKey:
|
|||
)
|
||||
transcripts.append(transcript)
|
||||
|
||||
_, public_key, _ = dkg.aggregate_transcripts(
|
||||
aggregate_transcript, public_key, params = dkg.aggregate_transcripts(
|
||||
ritual_id=ritual_id,
|
||||
me=validators[0],
|
||||
shares=num_shares,
|
||||
|
@ -734,4 +737,16 @@ def dkg_public_key(get_random_checksum_address) -> DkgPublicKey:
|
|||
transcripts=list(zip(validators, transcripts)),
|
||||
)
|
||||
|
||||
return public_key
|
||||
return aggregate_transcript, public_key, params
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def dkg_public_key(dkg_public_key_data) -> DkgPublicKey:
|
||||
_, dkg_public_key, _ = dkg_public_key_data
|
||||
return dkg_public_key
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def aggregated_transcript(dkg_public_key_data) -> AggregatedTranscript:
|
||||
aggregated_transcript, _, _ = dkg_public_key_data
|
||||
return aggregated_transcript
|
||||
|
|
|
@ -1,23 +1,26 @@
|
|||
import json
|
||||
from unittest.mock import ANY
|
||||
|
||||
import pytest
|
||||
from cryptography.hazmat.primitives.serialization import Encoding
|
||||
from flask import Flask
|
||||
from nucypher_core import Conditions, ThresholdDecryptionRequest
|
||||
from nucypher_core.umbral import SecretKey, Signer
|
||||
|
||||
from nucypher.blockchain.eth.signers.software import Web3Signer
|
||||
from nucypher.characters.lawful import Alice, Bob, Ursula
|
||||
from nucypher.characters.lawful import Alice, Bob, Enrico, Ursula
|
||||
from nucypher.config.constants import TEMPORARY_DOMAIN
|
||||
from nucypher.crypto.keystore import Keystore
|
||||
from nucypher.crypto.powers import DecryptingPower, DelegatingPower, TLSHostingPower
|
||||
from nucypher.crypto.powers import (
|
||||
DecryptingPower,
|
||||
DelegatingPower,
|
||||
ThresholdRequestDecryptingPower,
|
||||
TLSHostingPower,
|
||||
)
|
||||
from nucypher.network.server import ProxyRESTServer
|
||||
from nucypher.policy.payment import SubscriptionManagerPayment
|
||||
from nucypher.utilities.networking import LOOPBACK_ADDRESS
|
||||
from tests.constants import (
|
||||
INSECURE_DEVELOPMENT_PASSWORD,
|
||||
MOCK_ETH_PROVIDER_URI,
|
||||
MOCK_IP_ADDRESS,
|
||||
)
|
||||
from tests.constants import INSECURE_DEVELOPMENT_PASSWORD, MOCK_ETH_PROVIDER_URI
|
||||
from tests.utils.matchers import IsType
|
||||
|
||||
|
||||
|
@ -131,3 +134,76 @@ def test_tls_hosting_certificate_remains_the_same(temp_dir_path, mocker):
|
|||
rest_app=IsType(Flask),
|
||||
hosting_power=tls_hosting_power)
|
||||
recreated_ursula.disenchant()
|
||||
|
||||
|
||||
def test_ritualist(temp_dir_path, testerchain, dkg_public_key):
|
||||
keystore = Keystore.generate(
|
||||
password=INSECURE_DEVELOPMENT_PASSWORD, keystore_dir=temp_dir_path
|
||||
)
|
||||
keystore.unlock(password=INSECURE_DEVELOPMENT_PASSWORD)
|
||||
|
||||
payment_method = SubscriptionManagerPayment(
|
||||
eth_provider=MOCK_ETH_PROVIDER_URI, network=TEMPORARY_DOMAIN
|
||||
)
|
||||
|
||||
ursula = Ursula(
|
||||
start_learning_now=False,
|
||||
keystore=keystore,
|
||||
rest_host=LOOPBACK_ADDRESS,
|
||||
rest_port=12345,
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
payment_method=payment_method,
|
||||
operator_address=testerchain.ursulas_accounts[0],
|
||||
signer=Web3Signer(testerchain.client),
|
||||
eth_provider_uri=MOCK_ETH_PROVIDER_URI,
|
||||
)
|
||||
|
||||
ritual_id = 23
|
||||
# Use actual decryption request
|
||||
plaintext = b"Records break when you don't" # Jordan branch ad tagline
|
||||
CONDITIONS = [
|
||||
{"returnValueTest": {"value": "0", "comparator": ">"}, "method": "timelock"}
|
||||
]
|
||||
|
||||
# encrypt
|
||||
enrico = Enrico(encrypting_key=dkg_public_key)
|
||||
ciphertext = enrico.encrypt_for_dkg(plaintext=plaintext, conditions=CONDITIONS)
|
||||
decryption_request = ThresholdDecryptionRequest(
|
||||
ritual_id=ritual_id,
|
||||
variant=0,
|
||||
ciphertext=bytes(ciphertext),
|
||||
conditions=Conditions(json.dumps(CONDITIONS)),
|
||||
)
|
||||
|
||||
request_encrypting_key = ursula.threshold_request_power.get_pubkey_from_ritual_id(
|
||||
ritual_id=ritual_id
|
||||
)
|
||||
response_encrypting_key = SecretKey.random().public_key()
|
||||
encrypted_decryption_request = decryption_request.encrypt(
|
||||
request_encrypting_key=request_encrypting_key,
|
||||
response_encrypting_key=response_encrypting_key,
|
||||
)
|
||||
# successful decryption
|
||||
(
|
||||
decrypted_decryption_request,
|
||||
decrypted_response_encrypting_key,
|
||||
) = ursula.threshold_request_power.decrypt_encrypted_request(
|
||||
encrypted_decryption_request
|
||||
)
|
||||
assert bytes(decrypted_decryption_request) == bytes(decryption_request)
|
||||
assert (
|
||||
decrypted_response_encrypting_key.to_compressed_bytes()
|
||||
== response_encrypting_key.to_compressed_bytes()
|
||||
)
|
||||
|
||||
# failed decryption - incorrect encrypting key used
|
||||
invalid_encrypted_decryption_request = decryption_request.encrypt(
|
||||
request_encrypting_key=SecretKey.random().public_key(),
|
||||
response_encrypting_key=response_encrypting_key,
|
||||
)
|
||||
with pytest.raises(
|
||||
ThresholdRequestDecryptingPower.ThresholdRequestDecryptionFailed
|
||||
):
|
||||
ursula.threshold_request_power.decrypt_encrypted_request(
|
||||
invalid_encrypted_decryption_request
|
||||
)
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import time
|
||||
from enum import Enum
|
||||
from typing import Dict, List, Union
|
||||
from typing import Dict, List
|
||||
|
||||
from eth_typing import ChecksumAddress
|
||||
from eth_utils import keccak
|
||||
from ferveo_py.ferveo_py import DkgPublicKey
|
||||
from ferveo_py.ferveo_py import AggregatedTranscript, DkgPublicKey, Transcript
|
||||
from nucypher_core.umbral import PublicKey
|
||||
from web3.types import TxReceipt
|
||||
|
||||
from nucypher.blockchain.eth.agents import CoordinatorAgent
|
||||
|
@ -80,10 +81,10 @@ class MockCoordinatorAgent(MockContractAgent):
|
|||
return self.blockchain.FAKE_RECEIPT
|
||||
|
||||
def post_transcript(
|
||||
self,
|
||||
ritual_id: int,
|
||||
transcript: bytes,
|
||||
transacting_power: TransactingPower
|
||||
self,
|
||||
ritual_id: int,
|
||||
transcript: Transcript,
|
||||
transacting_power: TransactingPower,
|
||||
) -> TxReceipt:
|
||||
ritual = self.rituals[ritual_id]
|
||||
operator_address = transacting_power.account
|
||||
|
@ -93,7 +94,7 @@ class MockCoordinatorAgent(MockContractAgent):
|
|||
or transacting_power.account
|
||||
)
|
||||
participant = self.get_participant_from_provider(ritual_id, provider)
|
||||
participant.transcript = transcript
|
||||
participant.transcript = bytes(transcript)
|
||||
ritual.total_transcripts += 1
|
||||
if ritual.total_transcripts == ritual.dkg_size:
|
||||
ritual.status = self.RitualStatus.AWAITING_AGGREGATIONS
|
||||
|
@ -109,8 +110,9 @@ class MockCoordinatorAgent(MockContractAgent):
|
|||
def post_aggregation(
|
||||
self,
|
||||
ritual_id: int,
|
||||
aggregated_transcript: bytes,
|
||||
aggregated_transcript: AggregatedTranscript,
|
||||
public_key: DkgPublicKey,
|
||||
request_encrypting_key: PublicKey,
|
||||
transacting_power: TransactingPower,
|
||||
) -> TxReceipt:
|
||||
ritual = self.rituals[ritual_id]
|
||||
|
@ -122,14 +124,15 @@ class MockCoordinatorAgent(MockContractAgent):
|
|||
)
|
||||
participant = self.get_participant_from_provider(ritual_id, provider)
|
||||
participant.aggregated = True
|
||||
participant.requestEncryptingKey = request_encrypting_key.to_compressed_bytes()
|
||||
|
||||
g1_point = self.Ritual.G1Point.from_dkg_public_key(public_key)
|
||||
if len(ritual.aggregated_transcript) == 0:
|
||||
ritual.aggregated_transcript = aggregated_transcript
|
||||
ritual.aggregated_transcript = bytes(aggregated_transcript)
|
||||
ritual.public_key = g1_point
|
||||
elif bytes(ritual.public_key) != bytes(g1_point) or keccak(
|
||||
ritual.aggregated_transcript
|
||||
) != keccak(aggregated_transcript):
|
||||
) != keccak(bytes(aggregated_transcript)):
|
||||
ritual.aggregation_mismatch = True
|
||||
# don't increment aggregations
|
||||
# TODO Emit EndRitual here?
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import pytest
|
||||
from ferveo_py.ferveo_py import Keypair as FerveoKeyPair
|
||||
from ferveo_py.ferveo_py import Validator
|
||||
|
||||
from nucypher.blockchain.economics import EconomicsFactory
|
||||
from nucypher.blockchain.eth.actors import Operator
|
||||
from nucypher.blockchain.eth.agents import ContractAgency
|
||||
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
|
||||
from nucypher.blockchain.eth.registry import InMemoryContractRegistry
|
||||
from nucypher.crypto.ferveo import dkg
|
||||
from nucypher.crypto.powers import TransactingPower
|
||||
from nucypher.network.nodes import Teacher
|
||||
from tests.mock.interfaces import MockBlockchain, MockEthereumClient
|
||||
|
@ -88,3 +91,30 @@ def mock_substantiate_stamp(module_mocker, monkeymodule):
|
|||
module_mocker.patch.object(Ursula, "_substantiate_stamp", autospec=True)
|
||||
module_mocker.patch.object(Ursula, "operator_signature", fake_signature)
|
||||
module_mocker.patch.object(Teacher, "validate_operator")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def random_transcript(get_random_checksum_address):
|
||||
ritual_id = 0
|
||||
num_shares = 4
|
||||
threshold = 3
|
||||
validators = []
|
||||
for i in range(0, num_shares):
|
||||
validators.append(
|
||||
Validator(
|
||||
address=get_random_checksum_address(),
|
||||
public_key=FerveoKeyPair.random().public_key(),
|
||||
)
|
||||
)
|
||||
|
||||
validators.sort(key=lambda x: x.address) # must be sorte
|
||||
|
||||
transcript = dkg.generate_transcript(
|
||||
ritual_id=ritual_id,
|
||||
me=validators[0],
|
||||
shares=num_shares,
|
||||
threshold=threshold,
|
||||
nodes=validators,
|
||||
)
|
||||
|
||||
return transcript
|
||||
|
|
|
@ -7,25 +7,27 @@ import pytest
|
|||
from constant_sorrow.constants import KEYSTORE_LOCKED
|
||||
from cryptography.hazmat.primitives._serialization import Encoding
|
||||
from mnemonic.mnemonic import Mnemonic
|
||||
from nucypher_core.umbral import SecretKeyFactory
|
||||
|
||||
from nucypher_core.umbral import SecretKey, SecretKeyFactory
|
||||
|
||||
from nucypher.crypto.constants import UMBRAL_SECRET_KEY_SIZE
|
||||
from nucypher.crypto.keystore import (
|
||||
Keystore,
|
||||
InvalidPassword,
|
||||
validate_keystore_filename,
|
||||
_MNEMONIC_LANGUAGE,
|
||||
_DELEGATING_INFO,
|
||||
)
|
||||
from nucypher.crypto.keystore import (
|
||||
_MNEMONIC_LANGUAGE,
|
||||
InvalidPassword,
|
||||
Keystore,
|
||||
_assemble_keystore,
|
||||
_serialize_keystore,
|
||||
_deserialize_keystore,
|
||||
_read_keystore,
|
||||
_serialize_keystore,
|
||||
_write_keystore,
|
||||
_read_keystore
|
||||
validate_keystore_filename,
|
||||
)
|
||||
from nucypher.crypto.powers import (
|
||||
DecryptingPower,
|
||||
DelegatingPower,
|
||||
SigningPower,
|
||||
ThresholdRequestDecryptingPower,
|
||||
TLSHostingPower,
|
||||
)
|
||||
from nucypher.crypto.powers import DecryptingPower, SigningPower, DelegatingPower, TLSHostingPower
|
||||
from nucypher.utilities.networking import LOOPBACK_ADDRESS
|
||||
from tests.constants import INSECURE_DEVELOPMENT_PASSWORD
|
||||
|
||||
|
@ -311,3 +313,35 @@ def test_derive_hosting_power(tmpdir):
|
|||
assert hosting_power.keypair.certificate.public_bytes(encoding=Encoding.PEM)
|
||||
rederived_hosting_power = keystore.derive_crypto_power(power_class=TLSHostingPower, host=LOOPBACK_ADDRESS)
|
||||
assert hosting_power.public_key().public_numbers() == rederived_hosting_power.public_key().public_numbers()
|
||||
|
||||
|
||||
def test_derive_threshold_request_decrypting_power(tmpdir):
|
||||
keystore = Keystore.generate(INSECURE_DEVELOPMENT_PASSWORD, keystore_dir=tmpdir)
|
||||
keystore.unlock(password=INSECURE_DEVELOPMENT_PASSWORD)
|
||||
threshold_request_decrypting_power = keystore.derive_crypto_power(
|
||||
power_class=ThresholdRequestDecryptingPower
|
||||
)
|
||||
|
||||
ritual_id = 23
|
||||
request_encrypting_key = (
|
||||
threshold_request_decrypting_power.get_pubkey_from_ritual_id(
|
||||
ritual_id=ritual_id
|
||||
)
|
||||
)
|
||||
other_request_encrypting_key = (
|
||||
threshold_request_decrypting_power.get_pubkey_from_ritual_id(
|
||||
ritual_id=ritual_id
|
||||
)
|
||||
)
|
||||
assert (
|
||||
request_encrypting_key.to_compressed_bytes()
|
||||
== other_request_encrypting_key.to_compressed_bytes()
|
||||
)
|
||||
|
||||
different_ritual_request_encrypting_key = (
|
||||
threshold_request_decrypting_power.get_pubkey_from_ritual_id(ritual_id=0)
|
||||
)
|
||||
assert (
|
||||
request_encrypting_key.to_compressed_bytes
|
||||
!= different_ritual_request_encrypting_key.to_compressed_bytes()
|
||||
)
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import os
|
||||
from collections import OrderedDict
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
from eth_account import Account
|
||||
from nucypher_core.umbral import SecretKey
|
||||
|
||||
from tests.constants import FAKE_TRANSCRIPT
|
||||
from tests.mock.coordinator import MockCoordinatorAgent
|
||||
from tests.mock.interfaces import MockBlockchain
|
||||
|
||||
|
@ -59,7 +58,9 @@ def test_mock_coordinator_initiation(mocker, nodes_transacting_powers, coordinat
|
|||
assert set(signal_data["participants"]) == nodes_transacting_powers.keys()
|
||||
|
||||
|
||||
def test_mock_coordinator_round_1(nodes_transacting_powers, coordinator):
|
||||
def test_mock_coordinator_round_1(
|
||||
nodes_transacting_powers, coordinator, random_transcript
|
||||
):
|
||||
ritual = coordinator.rituals[0]
|
||||
assert (
|
||||
coordinator.get_ritual_status(0)
|
||||
|
@ -70,7 +71,7 @@ def test_mock_coordinator_round_1(nodes_transacting_powers, coordinator):
|
|||
assert p.transcript == bytes()
|
||||
|
||||
for index, node_address in enumerate(nodes_transacting_powers):
|
||||
transcript = FAKE_TRANSCRIPT
|
||||
transcript = random_transcript
|
||||
|
||||
coordinator.post_transcript(
|
||||
ritual_id=0,
|
||||
|
@ -79,7 +80,7 @@ def test_mock_coordinator_round_1(nodes_transacting_powers, coordinator):
|
|||
)
|
||||
|
||||
performance = ritual.participants[index]
|
||||
assert performance.transcript == transcript
|
||||
assert performance.transcript == bytes(transcript)
|
||||
|
||||
if index == len(nodes_transacting_powers) - 1:
|
||||
assert len(coordinator.EVENTS) == 2
|
||||
|
@ -91,7 +92,11 @@ def test_mock_coordinator_round_1(nodes_transacting_powers, coordinator):
|
|||
|
||||
|
||||
def test_mock_coordinator_round_2(
|
||||
nodes_transacting_powers, coordinator, dkg_public_key
|
||||
nodes_transacting_powers,
|
||||
coordinator,
|
||||
aggregated_transcript,
|
||||
dkg_public_key,
|
||||
random_transcript,
|
||||
):
|
||||
ritual = coordinator.rituals[0]
|
||||
assert (
|
||||
|
@ -100,26 +105,33 @@ def test_mock_coordinator_round_2(
|
|||
)
|
||||
|
||||
for p in ritual.participants:
|
||||
assert p.transcript == FAKE_TRANSCRIPT
|
||||
assert p.transcript == bytes(random_transcript)
|
||||
|
||||
aggregated_transcript = os.urandom(len(FAKE_TRANSCRIPT))
|
||||
request_encrypting_keys = []
|
||||
for index, node_address in enumerate(nodes_transacting_powers):
|
||||
request_encrypting_key = SecretKey.random().public_key()
|
||||
coordinator.post_aggregation(
|
||||
ritual_id=0,
|
||||
aggregated_transcript=aggregated_transcript,
|
||||
public_key=dkg_public_key,
|
||||
request_encrypting_key=request_encrypting_key,
|
||||
transacting_power=nodes_transacting_powers[node_address]
|
||||
)
|
||||
request_encrypting_keys.append(request_encrypting_key)
|
||||
if index == len(nodes_transacting_powers) - 1:
|
||||
assert len(coordinator.EVENTS) == 2
|
||||
|
||||
assert ritual.aggregated_transcript == aggregated_transcript
|
||||
assert ritual.aggregated_transcript == bytes(aggregated_transcript)
|
||||
|
||||
assert bytes(ritual.public_key) == bytes(dkg_public_key)
|
||||
for p in ritual.participants:
|
||||
for index, p in enumerate(ritual.participants):
|
||||
# unchanged
|
||||
assert p.transcript == FAKE_TRANSCRIPT
|
||||
assert p.transcript != aggregated_transcript
|
||||
assert p.transcript == bytes(random_transcript)
|
||||
assert p.transcript != bytes(aggregated_transcript)
|
||||
assert (
|
||||
p.requestEncryptingKey
|
||||
== request_encrypting_keys[index].to_compressed_bytes()
|
||||
)
|
||||
|
||||
assert len(coordinator.EVENTS) == 2 # no additional event emitted here?
|
||||
assert (
|
||||
|
|
|
@ -3,7 +3,6 @@ import pytest
|
|||
from nucypher.blockchain.eth.agents import CoordinatorAgent
|
||||
from nucypher.blockchain.eth.signers.software import Web3Signer
|
||||
from nucypher.crypto.powers import TransactingPower
|
||||
from tests.constants import FAKE_TRANSCRIPT
|
||||
from tests.mock.coordinator import MockCoordinatorAgent
|
||||
|
||||
|
||||
|
@ -84,10 +83,12 @@ def test_perform_round_1(ursula, random_address, cohort, agent):
|
|||
)
|
||||
|
||||
|
||||
def test_perform_round_2(ursula, cohort, transacting_power, agent, mocker):
|
||||
def test_perform_round_2(
|
||||
ursula, cohort, transacting_power, agent, mocker, random_transcript
|
||||
):
|
||||
participants = [
|
||||
CoordinatorAgent.Ritual.Participant(
|
||||
provider=c, aggregated=False, transcript=FAKE_TRANSCRIPT
|
||||
provider=c, aggregated=False, transcript=bytes(random_transcript)
|
||||
)
|
||||
for c in cohort
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue