Source code for xbr._frealm

###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) typedef int GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################

import os
from binascii import b2a_hex
from typing import Any, Dict, List, Optional

import web3
from autobahn.wamp.interfaces import ICryptosignKey, IEthereumKey
from autobahn.wamp.message import identify_realm_name_category
from ens import ENS
from twisted.internet.defer import Deferred, inlineCallbacks
from twisted.internet.threads import deferToThread
from web3.contract import Contract

from xbr._eip712_authority_certificate import EIP712AuthorityCertificate
from xbr._util import make_w3


[docs] class Seeder(object): """ """
[docs] __slots__ = ( "_frealm", "_operator", "_label", "_country", "_legal", "_endpoint", "_bandwidth_requested", "_bandwidth_offered", )
def __init__( self, frealm: "FederatedRealm", operator: Optional[str] = None, label: Optional[str] = None, country: Optional[str] = None, legal: Optional[str] = None, endpoint: Optional[str] = None, bandwidth_requested: Optional[int] = None, bandwidth_offered: Optional[int] = None, ): """ :param frealm: :param operator: :param label: :param country: :param legal: :param endpoint: :param bandwidth_requested: :param bandwidth_offered: """
[docs] self._frealm: FederatedRealm = frealm
[docs] self._operator: Optional[str] = operator
[docs] self._label: Optional[str] = label
[docs] self._country: Optional[str] = country
[docs] self._endpoint: Optional[str] = endpoint
[docs] self._bandwidth_requested: Optional[str] = bandwidth_requested
[docs] self._bandwidth_offered: Optional[str] = bandwidth_offered
@staticmethod
[docs] def _create_eip712_connect( chain_id: int, verifying_contract: bytes, channel_binding: str, channel_id: bytes, block_no: int, challenge: bytes, pubkey: bytes, realm: bytes, delegate: bytes, seeder: bytes, bandwidth: int, ): channel_binding = channel_binding or "" channel_id = channel_id or b"" assert chain_id assert verifying_contract assert channel_binding is not None assert channel_id is not None assert block_no assert challenge assert pubkey assert realm assert delegate assert bandwidth data = { "types": { "EIP712Domain": [ {"name": "name", "type": "string"}, {"name": "version", "type": "string"}, ], "EIP712SeederConnect": [ {"name": "chainId", "type": "uint256"}, {"name": "verifyingContract", "type": "address"}, {"name": "channel_binding", "type": "string"}, {"name": "channel_id", "type": "bytes32"}, {"name": "block_no", "type": "uint256"}, {"name": "challenge", "type": "bytes32"}, {"name": "pubkey", "type": "bytes32"}, {"name": "realm", "type": "address"}, {"name": "delegate", "type": "address"}, {"name": "seeder", "type": "address"}, {"name": "bandwidth", "type": "uint32"}, ], }, "primaryType": "EIP712SeederConnect", "domain": { "name": "XBR", "version": "1", }, "message": { "chainId": chain_id, "verifyingContract": verifying_contract, "channel_binding": channel_binding, "channel_id": channel_id, "block_no": block_no, "challenge": challenge, "pubkey": pubkey, "realm": realm, "delegate": delegate, "seeder": seeder, "bandwidth": bandwidth, }, } return data
@inlineCallbacks
[docs] def create_authextra( self, client_key: ICryptosignKey, delegate_key: IEthereumKey, bandwidth_requested: int, channel_id: Optional[bytes] = None, channel_binding: Optional[str] = None, ) -> Deferred: """ :param client_key: :param delegate_key: :param bandwidth_requested: :param channel_id: :param channel_binding: :return: """ chain_id = 1 # FIXME verifying_contract = b"\x01" * 20 block_no = 1 challenge = os.urandom(32) eip712_data = Seeder._create_eip712_connect( chain_id=chain_id, verifying_contract=verifying_contract, channel_binding=channel_binding, channel_id=channel_id, block_no=block_no, challenge=challenge, pubkey=client_key.public_key(binary=True), # FIXME # realm=self._frealm.address(binary=True), realm=b"\x02" * 20, delegate=delegate_key.address(binary=False), # FIXME # seeder=self._operator, seeder=b"\x03" * 20, bandwidth=bandwidth_requested, ) signature = yield delegate_key.sign_typed_data(eip712_data) authextra = { # string "pubkey": client_key.public_key(binary=False), # string "challenge": challenge, # string "channel_binding": channel_binding, # string "channel_id": channel_id, # address # FIXME "realm": "7f" * 20, # int "chain_id": chain_id, # int "block_no": block_no, # address "delegate": delegate_key.address(binary=False), # address "seeder": self._operator, # int: requested bandwidth in kbps "bandwidth": bandwidth_requested, # string: Eth signature by delegate_key over EIP712 typed data as above "signature": b2a_hex(signature).decode(), } return authextra
@property
[docs] def frealm(self) -> "FederatedRealm": """ :return: """ return self._frealm
@property
[docs] def operator(self) -> Optional[str]: """ Operator address, e.g. ``"0xe59C7418403CF1D973485B36660728a5f4A8fF9c"``. :return: The Ethereum address of the endpoint operator. """ return self._operator
@property
[docs] def label(self) -> Optional[str]: """ Operator endpoint label. :return: A human readable label for the operator or specific operator endpoint. """ return self._label
@property
[docs] def country(self) -> Optional[str]: """ Operator country (ISO 3166-1 alpha-2), e.g. ``"US"``. :return: ISO 3166-1 alpha-2 country code. """ return self._country
@property
[docs] def legal(self) -> Optional[str]: """ :return: """ return self._legal
@property
[docs] def endpoint(self) -> Optional[str]: """ Public WAMP endpoint of seeder. Secure WebSocket URL resolving to a public IPv4 or IPv6 listening url accepting incoming WAMP-WebSocket connections, e.g. ``wss://service1.example.com/ws``. :return: The endpoint URL. """ return self._endpoint
@property
[docs] def bandwidth_requested(self) -> Optional[int]: """ :return: """ return self._bandwidth_requested
@property
[docs] def bandwidth_offered(self) -> Optional[int]: """ :return: """ return self._bandwidth_offered
[docs] class FederatedRealm(object): """ A federated realm is a WAMP application realm with a trust anchor rooted in Ethereum, and which can be shared between multiple parties. A federated realm is globally identified on an Ethereum chain (e.g. on Mainnet or Rinkeby) by an Ethereum address associated to a federated realm owner by an on-chain record stored in the WAMP Network contract. The federated realm address thus only needs to exist as an identifier of the federated realm-owner record. """
[docs] __slots__ = ( "_name_or_address", "_gateway_config", "_status", "_name_category", "_w3", "_ens", "_address", "_contract", "_seeders", "_root_ca", "_catalog", "_meta", )
# FIXME
[docs] CONTRACT_ADDRESS = web3.Web3.toChecksumAddress("0xF7acf1C4CB4a9550B8969576573C2688B48988C2")
[docs] CONTRACT_ABI: str = ""
def __init__(self, name_or_address: str, gateway_config: Optional[Dict[str, Any]] = None): """ Instantiate a federated realm from a federated realm ENS name (which is resolved to an Ethereum address) or Ethereum address. :param name_or_address: Ethereum ENS name or address. :param gateway_config: If provided, use this Ethereum gateway. If not provided, connect via Infura to Ethereum Mainnet, which requires an environment variable ``WEB3_INFURA_PROJECT_ID`` with your Infura project ID. """
[docs] self._name_or_address = name_or_address
[docs] self._gateway_config = gateway_config
# status, will change to 'RUNNING' after initialize() has completed
[docs] self._status = "STOPPED"
[docs] self._name_category: Optional[str] = identify_realm_name_category(self._name_or_address)
if self._name_category not in ["eth", "ens", "reverse_ens"]: raise ValueError('name_or_address "{}" not an Ethereum address or ENS name'.format(self._name_or_address)) # will be filled once initialize()'ed
[docs] self._w3 = None
[docs] self._ens = None
# address identifying the federated realm
[docs] self._address: Optional[str] = None
# will be initialized with a FederatedRealm contract instance
[docs] self._contract: Optional[Contract] = None
# cache of federated realm seeders, filled once in status running
[docs] self._seeders: List[Seeder] = []
[docs] self._root_ca = None
@property
[docs] def status(self) -> str: return self._status
@property
[docs] def name_or_address(self) -> str: return self._name_or_address
@property
[docs] def gateway_config(self) -> Optional[Dict[str, Any]]: return self._gateway_config
@property
[docs] def name_category(self) -> Optional[str]: return self._name_category
@property
[docs] def address(self): return self._address
[docs] def root_ca(self) -> EIP712AuthorityCertificate: assert self._status == "RUNNING" return self._root_ca
@property
[docs] def seeders(self) -> List[Seeder]: return self._seeders
[docs] def initialize(self): """ :return: """ if self._status != "STOPPED": raise RuntimeError('cannot start in status "{}"'.format(self._status)) d = deferToThread(self._initialize_background) return d
[docs] def _initialize_background(self): self._status = "STARTING" if self._gateway_config: self._w3 = make_w3(self._gateway_config) else: raise RuntimeError("cannot auto-configure ethereum connection (was removed from web3.py in v6)") # https://github.com/ethereum/web3.py/issues/1416 # https://github.com/ethereum/web3.py/pull/2706 # from web3.auto.infura import w3 # self._w3 = w3 self._ens = ENS.from_web3(self._w3) if self._name_category in ["ens", "reverse_ens"]: if self._name_category == "reverse_ens": name = "".join(reversed(self._name_or_address.split("."))) else: name = self._name_or_address self._address = self._ens.address(name) elif self._name_category == "eth": self._address = self._w3.toChecksumAddress(self._name_or_address) else: assert False, "should not arrive here" # https://web3py.readthedocs.io/en/stable/contracts.html#web3.contract.Contract # https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.contract # self._contract = self._w3.eth.contract(address=self.CONTRACT_ADDRESS, abi=self.CONTRACT_ABI) # FIXME: get IPFS hash, download file, unzip seeders self._status = "RUNNING"