diff --git a/autobahn/_version.py b/autobahn/_version.py index e20d25b7a..7692c1e0b 100644 --- a/autobahn/_version.py +++ b/autobahn/_version.py @@ -24,4 +24,4 @@ # ############################################################################### -__version__ = '20.5.1.dev1' +__version__ = '20.6.1' diff --git a/autobahn/xbr/README.md b/autobahn/xbr/README.md new file mode 100644 index 000000000..92d85a52d --- /dev/null +++ b/autobahn/xbr/README.md @@ -0,0 +1,18 @@ +# XBR + +## EIP712 + +* [x] [Base](_eip712_base.py) +* [x] [EIP712MemberRegister](_eip712_member_register.py) +* [x] [EIP712MemberUnregister](_eip712_member_unregister.py) +* [x] [EIP712MemberLogin](_eip712_member_login.py) +* [x] [EIP712DomainCreate](_eip712_domain_create.py) +* [x] [EIP712NodePair](_eip712_node_pair.py) +* [x] [EIP712CatalogCreate](_eip712_catalog_create.py) +* [x] [EIP712ApiPublish](_eip712_api_publish.py) +* [x] [EIP712MarketCreate](_eip712_market_create.py) +* [x] [EIP712MarketJoin](_eip712_market_join.py) +* [x] [EIP712MarketLeave](_eip712_market_leave.py) +* [x] [EIP712Consent](_eip712_consent.py) +* [x] [EIP712ChannelOpen](_eip712_channel_open.py) +* [x] [EIP712ChannelClose](_eip712_channel_close.py) diff --git a/autobahn/xbr/_abi.py b/autobahn/xbr/_abi.py index ec5b3a1ce..eb5890888 100644 --- a/autobahn/xbr/_abi.py +++ b/autobahn/xbr/_abi.py @@ -38,7 +38,7 @@ # # Set default XBR contract addresses to -# XBR v20.4.2 @ Rinkeby (https://github.com/crossbario/xbr-protocol/issues/106) +# XBR v20.5.1. @ Rinkeby (https://github.com/crossbario/xbr-protocol/issues/127) # if 'XBR_DEBUG_TOKEN_ADDR' in os.environ: _token_adr = os.environ['XBR_DEBUG_TOKEN_ADDR'] @@ -48,9 +48,9 @@ except Exception as e: raise RuntimeError('could not parse Ethereum address for XBR_DEBUG_TOKEN_ADDR={} - {}'.format(_token_adr, e)) XBR_DEBUG_TOKEN_ADDR = _token_adr - XBR_DEBUG_TOKEN_ADDR_SRC = 'env' + XBR_DEBUG_TOKEN_ADDR_SRC = 'envvar $XBR_DEBUG_TOKEN_ADDR' else: - XBR_DEBUG_TOKEN_ADDR = '0x8d41eF64D49eA1550B4b41a8959D856601441503' + XBR_DEBUG_TOKEN_ADDR = '0xaCef957D54c639575f4DB68b1992B36504f33FEA' XBR_DEBUG_TOKEN_ADDR_SRC = 'builtin' if 'XBR_DEBUG_NETWORK_ADDR' in os.environ: @@ -61,23 +61,23 @@ except Exception as e: raise RuntimeError('could not parse Ethereum address for XBR_DEBUG_NETWORK_ADDR={} - {}'.format(_netw_adr, e)) XBR_DEBUG_NETWORK_ADDR = _netw_adr - XBR_DEBUG_NETWORK_ADDR_SRC = 'env' + XBR_DEBUG_NETWORK_ADDR_SRC = 'envvar $XBR_DEBUG_NETWORK_ADDR' else: - XBR_DEBUG_NETWORK_ADDR = '0xBfB616f885D581328FC6c3ad53481231Cc9b1bcf' + XBR_DEBUG_NETWORK_ADDR = '0x7A3d22c59e8F8f1b88ba7205f3f5a65Bc86D04Bc' XBR_DEBUG_NETWORK_ADDR_SRC = 'builtin' -if 'XBR_DEBUG_MARKET_ADDR' in os.environ: - _mrkt_adr = os.environ['XBR_DEBUG_MARKET_ADDR'] +if 'XBR_DEBUG_DOMAIN_ADDR' in os.environ: + _domain_adr = os.environ['XBR_DEBUG_DOMAIN_ADDR'] try: - _mrkt_adr = binascii.a2b_hex(_mrkt_adr[2:]) - _mrkt_adr = web3.Web3.toChecksumAddress(_mrkt_adr) + _domain_adr = binascii.a2b_hex(_domain_adr[2:]) + _domain_adr = web3.Web3.toChecksumAddress(_domain_adr) except Exception as e: - raise RuntimeError('could not parse Ethereum address for XBR_DEBUG_MARKET_ADDR={} - {}'.format(_mrkt_adr, e)) - XBR_DEBUG_MARKET_ADDR = _mrkt_adr - XBR_DEBUG_MARKET_ADDR_SRC = 'env' + raise RuntimeError('could not parse Ethereum address for XBR_DEBUG_DOMAIN_ADDR={} - {}'.format(_domain_adr, e)) + XBR_DEBUG_DOMAIN_ADDR = _domain_adr + XBR_DEBUG_DOMAIN_ADDR_SRC = 'envvar $XBR_DEBUG_DOMAIN_ADDR' else: - XBR_DEBUG_MARKET_ADDR = '0x27d4E6534134d9B1b5E2190cf8Ea170C8D05fb66' - XBR_DEBUG_MARKET_ADDR_SRC = 'builtin' + XBR_DEBUG_DOMAIN_ADDR = '0xf5fb56886f033855C1a36F651E927551749361bC' + XBR_DEBUG_DOMAIN_ADDR_SRC = 'builtin' if 'XBR_DEBUG_CATALOG_ADDR' in os.environ: _ctlg_adr = os.environ['XBR_DEBUG_CATALOG_ADDR'] @@ -87,11 +87,24 @@ except Exception as e: raise RuntimeError('could not parse Ethereum address for XBR_DEBUG_CATALOG_ADDR={} - {}'.format(_ctlg_adr, e)) XBR_DEBUG_CATALOG_ADDR = _ctlg_adr - XBR_DEBUG_CATALOG_ADDR_SRC = 'env' + XBR_DEBUG_CATALOG_ADDR_SRC = 'envvar $XBR_DEBUG_CATALOG_ADDR' else: - XBR_DEBUG_CATALOG_ADDR = '0x96284C34bD2A805589F9673F2534ED691672cAa0' + XBR_DEBUG_CATALOG_ADDR = '0x2C77E46Ea9502B363343e8c826c41c7fdb25Db66' XBR_DEBUG_CATALOG_ADDR_SRC = 'builtin' +if 'XBR_DEBUG_MARKET_ADDR' in os.environ: + _mrkt_adr = os.environ['XBR_DEBUG_MARKET_ADDR'] + try: + _mrkt_adr = binascii.a2b_hex(_mrkt_adr[2:]) + _mrkt_adr = web3.Web3.toChecksumAddress(_mrkt_adr) + except Exception as e: + raise RuntimeError('could not parse Ethereum address for XBR_DEBUG_MARKET_ADDR={} - {}'.format(_mrkt_adr, e)) + XBR_DEBUG_MARKET_ADDR = _mrkt_adr + XBR_DEBUG_MARKET_ADDR_SRC = 'envvar $XBR_DEBUG_MARKET_ADDR' +else: + XBR_DEBUG_MARKET_ADDR = '0x0DcF924ab0846101d31514E9fb3adf5070d4B83d' + XBR_DEBUG_MARKET_ADDR_SRC = 'builtin' + if 'XBR_DEBUG_CHANNEL_ADDR' in os.environ: _chnl_adr = os.environ['XBR_DEBUG_CHANNEL_ADDR'] try: @@ -100,9 +113,9 @@ except Exception as e: raise RuntimeError('could not parse Ethereum address for XBR_DEBUG_CHANNEL_ADDR={} - {}'.format(_chnl_adr, e)) XBR_DEBUG_CHANNEL_ADDR = _chnl_adr - XBR_DEBUG_CHANNEL_ADDR_SRC = 'env' + XBR_DEBUG_CHANNEL_ADDR_SRC = 'envvar $XBR_DEBUG_CHANNEL_ADDR' else: - XBR_DEBUG_CHANNEL_ADDR = '0xA20C8bA0e86606cCBEE14A50acA0604Ce667F508' + XBR_DEBUG_CHANNEL_ADDR = '0x670497A012322B99a5C18B8463940996141Cb952' XBR_DEBUG_CHANNEL_ADDR_SRC = 'builtin' @@ -118,8 +131,9 @@ def _load_json(contract_name): # XBR_TOKEN_FN = pkg_resources.resource_filename('autobahn', 'xbr/contracts/XBRToken.json') XBR_NETWORK_FN = pkg_resources.resource_filename('autobahn', 'xbr/contracts/XBRNetwork.json') -XBR_MARKET_FN = pkg_resources.resource_filename('autobahn', 'xbr/contracts/XBRMarket.json') +XBR_DOMAIN_FN = pkg_resources.resource_filename('autobahn', 'xbr/contracts/XBRDomain.json') XBR_CATALOG_FN = pkg_resources.resource_filename('autobahn', 'xbr/contracts/XBRCatalog.json') +XBR_MARKET_FN = pkg_resources.resource_filename('autobahn', 'xbr/contracts/XBRMarket.json') XBR_CHANNEL_FN = pkg_resources.resource_filename('autobahn', 'xbr/contracts/XBRChannel.json') @@ -128,6 +142,7 @@ def _load_json(contract_name): # XBR_TOKEN_ABI = _load_json('XBRToken')['abi'] XBR_NETWORK_ABI = _load_json('XBRNetwork')['abi'] -XBR_MARKET_ABI = _load_json('XBRMarket')['abi'] +XBR_DOMAIN_ABI = _load_json('XBRDomain')['abi'] XBR_CATALOG_ABI = _load_json('XBRCatalog')['abi'] +XBR_MARKET_ABI = _load_json('XBRMarket')['abi'] XBR_CHANNEL_ABI = _load_json('XBRChannel')['abi'] diff --git a/autobahn/xbr/_cli.py b/autobahn/xbr/_cli.py index ff16d6391..cdabe22b1 100644 --- a/autobahn/xbr/_cli.py +++ b/autobahn/xbr/_cli.py @@ -33,11 +33,11 @@ print("For example, \"pip install autobahn[xbr]\".") sys.exit(1) -from autobahn.xbr._abi import XBR_DEBUG_TOKEN_ADDR, XBR_DEBUG_NETWORK_ADDR, XBR_DEBUG_MARKET_ADDR, \ - XBR_DEBUG_CATALOG_ADDR, XBR_DEBUG_CHANNEL_ADDR +from autobahn.xbr._abi import XBR_DEBUG_TOKEN_ADDR, XBR_DEBUG_NETWORK_ADDR, XBR_DEBUG_DOMAIN_ADDR, \ + XBR_DEBUG_CATALOG_ADDR, XBR_DEBUG_MARKET_ADDR, XBR_DEBUG_CHANNEL_ADDR -from autobahn.xbr._abi import XBR_DEBUG_TOKEN_ADDR_SRC, XBR_DEBUG_NETWORK_ADDR_SRC, XBR_DEBUG_MARKET_ADDR_SRC, \ - XBR_DEBUG_CATALOG_ADDR_SRC, XBR_DEBUG_CHANNEL_ADDR_SRC +from autobahn.xbr._abi import XBR_DEBUG_TOKEN_ADDR_SRC, XBR_DEBUG_NETWORK_ADDR_SRC, XBR_DEBUG_DOMAIN_ADDR_SRC, \ + XBR_DEBUG_CATALOG_ADDR_SRC, XBR_DEBUG_MARKET_ADDR_SRC, XBR_DEBUG_CHANNEL_ADDR_SRC import uuid import binascii @@ -911,23 +911,26 @@ def _main(): args = parser.parse_args() + # read or create a user profile + profile = load_or_create_profile() + if args.command == 'version': print('') - print(' XBR CLI v{}\n'.format(__version__)) - print(' XBRToken contract address: {} [source: {}]'.format(hlid(XBR_DEBUG_TOKEN_ADDR), XBR_DEBUG_TOKEN_ADDR_SRC)) - print(' XBRNetwork contract address: {} [source: {}]'.format(hlid(XBR_DEBUG_NETWORK_ADDR), XBR_DEBUG_NETWORK_ADDR_SRC)) - print(' XBRMarket contract address: {} [source: {}]'.format(hlid(XBR_DEBUG_MARKET_ADDR), XBR_DEBUG_MARKET_ADDR_SRC)) - print(' XBRCatalog contract address: {} [source: {}]'.format(hlid(XBR_DEBUG_CATALOG_ADDR), XBR_DEBUG_CATALOG_ADDR_SRC)) - print(' XBRChannel contract address: {} [source: {}]'.format(hlid(XBR_DEBUG_CHANNEL_ADDR), XBR_DEBUG_CHANNEL_ADDR_SRC)) + print(' XBR CLI {}\n'.format(hlval('v' + __version__))) + print(' Profile {profile} loaded from {path}\n'.format(profile=hlval(profile.name), path=hlval(profile.path))) + print(' Contract addresses:\n') + print(' XBRToken : {} [source: {}]'.format(hlid(XBR_DEBUG_TOKEN_ADDR), XBR_DEBUG_TOKEN_ADDR_SRC)) + print(' XBRNetwork : {} [source: {}]'.format(hlid(XBR_DEBUG_NETWORK_ADDR), XBR_DEBUG_NETWORK_ADDR_SRC)) + print(' XBRDomain : {} [source: {}]'.format(hlid(XBR_DEBUG_DOMAIN_ADDR), XBR_DEBUG_DOMAIN_ADDR_SRC)) + print(' XBRCatalog : {} [source: {}]'.format(hlid(XBR_DEBUG_CATALOG_ADDR), XBR_DEBUG_CATALOG_ADDR_SRC)) + print(' XBRMarket : {} [source: {}]'.format(hlid(XBR_DEBUG_MARKET_ADDR), XBR_DEBUG_MARKET_ADDR_SRC)) + print(' XBRChannel : {} [source: {}]'.format(hlid(XBR_DEBUG_CHANNEL_ADDR), XBR_DEBUG_CHANNEL_ADDR_SRC)) print('') else: if args.command is None or args.command == 'noop': print('no command given. select from: {}'.format(', '.join(_COMMANDS))) sys.exit(0) - # read or create a user profile - profile = load_or_create_profile() - # only start txaio logging after above, which runs click (interactively) if args.debug: txaio.start_logging(level='debug') diff --git a/autobahn/xbr/_config.py b/autobahn/xbr/_config.py index 7644001ae..e89b0b9a6 100644 --- a/autobahn/xbr/_config.py +++ b/autobahn/xbr/_config.py @@ -286,8 +286,8 @@ def load_or_create_profile(dotdir=None, profile=None): market_realm = click.prompt('enter the WAMP realm of the XBR data market', type=str) ethkey = prompt_for_key('your private Etherum key', 32) cskey = prompt_for_key('your private WAMP client key', 32) - infura_network = click.prompt('enter your Infura gateway URL', type=str, default='rinkeby') - infura_url = click.prompt('enter your Infura gateway URL', type=str) + infura_network = click.prompt('enter Ethereum network to use', type=str, default='rinkeby') + infura_url = click.prompt('enter Infura gateway URL', type=str) infura_key = click.prompt('your Infura gateway key', type=str) infura_secret = click.prompt('your Infura gateway secret', type=str) f.write(_DEFAULT_CONFIG.format(market_url=market_url, market_realm=market_realm, ethkey=ethkey, diff --git a/autobahn/xbr/_eip712_api_publish.py b/autobahn/xbr/_eip712_api_publish.py index 38e5bc224..3c7caf2cb 100644 --- a/autobahn/xbr/_eip712_api_publish.py +++ b/autobahn/xbr/_eip712_api_publish.py @@ -24,11 +24,13 @@ # ############################################################################### -from ._eip712_base import sign, recover, is_address, is_bytes16 +from typing import Optional +from ._eip712_base import sign, recover, is_address, is_bytes16, is_block_number, \ + is_chain_id, is_eth_privkey, is_signature def _create_eip712_api_publish(chainId: int, verifyingContract: bytes, member: bytes, published: int, - catalogId: bytes, apiId: bytes, schema: str, meta: str) -> dict: + catalogId: bytes, apiId: bytes, schema: str, meta: Optional[str]) -> dict: """ :param chainId: @@ -41,10 +43,10 @@ def _create_eip712_api_publish(chainId: int, verifyingContract: bytes, member: b :param meta: :return: """ - assert type(chainId) == int + assert is_chain_id(chainId) assert is_address(verifyingContract) assert is_address(member) - assert type(published) == int + assert is_block_number(published) assert is_bytes16(catalogId) assert is_bytes16(apiId) assert type(schema) == str @@ -118,7 +120,7 @@ def _create_eip712_api_publish(chainId: int, verifyingContract: bytes, member: b def sign_eip712_api_publish(eth_privkey: bytes, chainId: int, verifyingContract: bytes, member: bytes, - published: int, catalogId: bytes, apiId: bytes, schema: str, meta: str) -> bytes: + published: int, catalogId: bytes, apiId: bytes, schema: str, meta: Optional[str]) -> bytes: """ :param eth_privkey: Ethereum address of buyer (a raw 20 bytes Ethereum address). @@ -127,14 +129,15 @@ def sign_eip712_api_publish(eth_privkey: bytes, chainId: int, verifyingContract: :return: The signature according to EIP712 (32+32+1 raw bytes). :rtype: bytes """ - # create EIP712 typed data object + assert is_eth_privkey(eth_privkey) + data = _create_eip712_api_publish(chainId, verifyingContract, member, published, catalogId, apiId, schema, meta) return sign(eth_privkey, data) def recover_eip712_api_publish(chainId: int, verifyingContract: bytes, member: bytes, published: int, - catalogId: bytes, apiId: bytes, schema: str, meta: str, + catalogId: bytes, apiId: bytes, schema: str, meta: Optional[str], signature: bytes) -> bytes: """ Recover the signer address the given EIP712 signature was signed with. @@ -142,7 +145,8 @@ def recover_eip712_api_publish(chainId: int, verifyingContract: bytes, member: b :return: The (computed) signer address the signature was signed with. :rtype: bytes """ - # recreate EIP712 typed data object + assert is_signature(signature) + data = _create_eip712_api_publish(chainId, verifyingContract, member, published, catalogId, apiId, schema, meta) return recover(data, signature) diff --git a/autobahn/xbr/_eip712_base.py b/autobahn/xbr/_eip712_base.py index b44988d00..3119d3362 100644 --- a/autobahn/xbr/_eip712_base.py +++ b/autobahn/xbr/_eip712_base.py @@ -31,10 +31,14 @@ def sign(eth_privkey, data): - # FIXME: this fails on PyPy (but ot on CPy!) with - # Unknown format b'%M\xff\xcd2w\xc0\xb1f\x0fmB\xef\xbbuN\xda\xba\xbc+', attempted to normalize to 0x254dffcd3277c0b1660f6d42efbb754edababc2b - _args = signing.sign_typed_data(data, eth_privkey) + """ + Sign the given data using the given Ethereum private key. + :param eth_privkey: Signing key. + :param data: Data to sign. + :return: Signature. + """ + _args = signing.sign_typed_data(data, eth_privkey) signature = signing.v_r_s_to_signature(*_args) assert len(signature) == _EIP712_SIG_LEN @@ -42,6 +46,13 @@ def sign(eth_privkey, data): def recover(data, signature): + """ + Recover the Ethereum address of the signer, given the data and signature. + + :param data: Signed data. + :param signature: Signature. + :return: Signing address. + """ assert type(signature) == bytes and len(signature) == _EIP712_SIG_LEN signer_address = signing.recover_typed_data(data, *signing.signature_to_v_r_s(signature)) @@ -49,8 +60,72 @@ def recover(data, signature): def is_address(provided): + """ + Check if the value is a proper Ethereum address. + + :param provided: The value to check. + :return: True iff the value is of correct type. + """ return type(provided) == bytes and len(provided) == 20 def is_bytes16(provided): + """ + Check if the value is a proper (binary) UUID. + + :param provided: The value to check. + :return: True iff the value is of correct type. + """ return type(provided) == bytes and len(provided) == 16 + + +def is_signature(provided): + """ + Check if the value is a proper Ethereum signature. + + :param provided: The value to check. + :return: True iff the value is of correct type. + """ + return type(provided) == bytes and len(provided) == _EIP712_SIG_LEN + + +def is_eth_privkey(provided): + """ + Check if the value is a proper WAMP-cryptosign private key. + + :param provided: The value to check. + :return: True iff the value is of correct type. + """ + return type(provided) == bytes and len(provided) == 32 + + +def is_cs_pubkey(provided): + """ + Check if the value is a proper WAMP-cryptosign public key. + + :param provided: The value to check. + :return: True iff the value is of correct type. + """ + return type(provided) == bytes and len(provided) == 32 + + +def is_block_number(provided): + """ + Check if the value is a proper Ethereum block number. + + :param provided: The value to check. + :return: True iff the value is of correct type. + """ + return type(provided) == int + + +def is_chain_id(provided): + """ + Check if the value is a proper Ethereum chain ID. + + :param provided: The value to check. + :return: True iff the value is of correct type. + """ + # here is a list of public networks: https://chainid.network/ + # note: we allow any positive integer to account for private networks + return type(provided) == int and provided > 0 diff --git a/autobahn/xbr/_eip712_catalog_create.py b/autobahn/xbr/_eip712_catalog_create.py index 97134ba42..cb20c4f78 100644 --- a/autobahn/xbr/_eip712_catalog_create.py +++ b/autobahn/xbr/_eip712_catalog_create.py @@ -25,7 +25,8 @@ ############################################################################### from typing import Optional -from ._eip712_base import sign, recover +from ._eip712_base import sign, recover, is_address, is_signature, is_eth_privkey, \ + is_bytes16, is_chain_id def _create_eip712_catalog_create(chainId: int, verifyingContract: bytes, member: bytes, created: int, @@ -41,9 +42,10 @@ def _create_eip712_catalog_create(chainId: int, verifyingContract: bytes, member :param meta: :return: """ - assert len(verifyingContract) == 20, 'Invalid contract' - assert len(member) == 20, 'Invalid member oid' - assert len(catalogId) == 16, 'Invalid catalog id' + assert is_chain_id(chainId) + assert is_address(verifyingContract) + assert is_address(member) + assert is_bytes16(catalogId) data = { 'types': { @@ -108,7 +110,7 @@ def _create_eip712_catalog_create(chainId: int, verifyingContract: bytes, member def sign_eip712_catalog_create(eth_privkey: bytes, chainId: int, verifyingContract: bytes, member: bytes, - created: int, catalogId: bytes, terms: str, meta: str) -> bytes: + created: int, catalogId: bytes, terms: str, meta: Optional[str]) -> bytes: """ :param eth_privkey: Ethereum address of buyer (a raw 20 bytes Ethereum address). @@ -117,19 +119,23 @@ def sign_eip712_catalog_create(eth_privkey: bytes, chainId: int, verifyingContra :return: The signature according to EIP712 (32+32+1 raw bytes). :rtype: bytes """ - # create EIP712 typed data object + assert is_eth_privkey(eth_privkey) + data = _create_eip712_catalog_create(chainId, verifyingContract, member, created, catalogId, terms, meta) + return sign(eth_privkey, data) def recover_eip712_catalog_create(chainId: int, verifyingContract: bytes, member: bytes, created: int, - catalogId: bytes, terms: str, meta: str, signature: bytes) -> bytes: + catalogId: bytes, terms: str, meta: Optional[str], signature: bytes) -> bytes: """ Recover the signer address the given EIP712 signature was signed with. :return: The (computed) signer address the signature was signed with. :rtype: bytes """ - # recreate EIP712 typed data object + assert is_signature(signature) + data = _create_eip712_catalog_create(chainId, verifyingContract, member, created, catalogId, terms, meta) + return recover(data, signature) diff --git a/autobahn/xbr/_eip712_channel_close.py b/autobahn/xbr/_eip712_channel_close.py index b06985f1d..ffd8261f9 100644 --- a/autobahn/xbr/_eip712_channel_close.py +++ b/autobahn/xbr/_eip712_channel_close.py @@ -24,10 +24,8 @@ # ############################################################################### -from binascii import a2b_hex -from py_eth_sig_utils import signing - -_EIP712_SIG_LEN = 32 + 32 + 1 +from ._eip712_base import sign, recover, is_address, is_signature, is_eth_privkey, is_bytes16, \ + is_block_number, is_chain_id def _create_eip712_channel_close(chainId: int, verifyingContract: bytes, closeAt: int, marketId: bytes, channelId: bytes, @@ -43,11 +41,11 @@ def _create_eip712_channel_close(chainId: int, verifyingContract: bytes, closeAt :param isFinal: :return: """ - assert type(chainId) == int - assert type(verifyingContract) == bytes and len(verifyingContract) == 20 - assert type(closeAt) == int - assert type(marketId) == bytes and len(marketId) == 16 - assert type(channelId) == bytes and len(channelId) == 16 + assert is_chain_id(chainId) + assert is_address(verifyingContract) + assert is_block_number(closeAt) + assert is_bytes16(marketId) + assert is_bytes16(channelId) assert type(channelSeq) == int assert type(balance) == int assert type(isFinal) == bool @@ -120,18 +118,11 @@ def sign_eip712_channel_close(eth_privkey: bytes, chainId: int, verifyingContrac :return: The signature according to EIP712 (32+32+1 raw bytes). :rtype: bytes """ - # create EIP712 typed data object + assert is_eth_privkey(eth_privkey) + data = _create_eip712_channel_close(chainId, verifyingContract, closeAt, marketId, channelId, channelSeq, balance, isFinal) - - # FIXME: this fails on PyPy (but ot on CPy!) with - # Unknown format b'%M\xff\xcd2w\xc0\xb1f\x0fmB\xef\xbbuN\xda\xba\xbc+', attempted to normalize to 0x254dffcd3277c0b1660f6d42efbb754edababc2b - _args = signing.sign_typed_data(data, eth_privkey) - - signature = signing.v_r_s_to_signature(*_args) - assert len(signature) == _EIP712_SIG_LEN - - return signature + return sign(eth_privkey, data) def recover_eip712_channel_close(chainId: int, verifyingContract: bytes, closeAt: int, marketId: bytes, channelId: bytes, @@ -142,11 +133,8 @@ def recover_eip712_channel_close(chainId: int, verifyingContract: bytes, closeAt :return: The (computed) signer address the signature was signed with. :rtype: bytes """ - # create EIP712 typed data object + assert is_signature(signature) + data = _create_eip712_channel_close(chainId, verifyingContract, closeAt, marketId, channelId, channelSeq, balance, isFinal) - - assert type(signature) == bytes and len(signature) == _EIP712_SIG_LEN - signer_address = signing.recover_typed_data(data, *signing.signature_to_v_r_s(signature)) - - return a2b_hex(signer_address[2:]) + return recover(data, signature) diff --git a/autobahn/xbr/_eip712_channel_open.py b/autobahn/xbr/_eip712_channel_open.py index 07ef4eed7..ed07c00a4 100644 --- a/autobahn/xbr/_eip712_channel_open.py +++ b/autobahn/xbr/_eip712_channel_open.py @@ -24,10 +24,8 @@ # ############################################################################### -from binascii import a2b_hex -from py_eth_sig_utils import signing - -_EIP712_SIG_LEN = 32 + 32 + 1 +from ._eip712_base import sign, recover, is_address, is_signature, is_eth_privkey, is_bytes16, \ + is_block_number, is_chain_id def _create_eip712_channel_open(chainId: int, verifyingContract: bytes, ctype: int, openedAt: int, @@ -48,16 +46,16 @@ def _create_eip712_channel_open(chainId: int, verifyingContract: bytes, ctype: i :param amount: :return: """ - assert type(chainId) == int - assert type(verifyingContract) == bytes and len(verifyingContract) == 20 + assert is_chain_id(chainId) + assert is_address(verifyingContract) assert type(ctype) == int - assert type(openedAt) == int - assert type(marketId) == bytes and len(marketId) == 16 - assert type(channelId) == bytes and len(channelId) == 16 - assert type(actor) == bytes and len(actor) == 20 - assert type(delegate) == bytes and len(delegate) == 20 - assert type(marketmaker) == bytes and len(marketmaker) == 20 - assert type(recipient) == bytes and len(recipient) == 20 + assert is_block_number(openedAt) + assert is_bytes16(marketId) + assert is_bytes16(channelId) + assert is_address(actor) + assert is_address(delegate) + assert is_address(marketmaker) + assert is_address(recipient) assert type(amount) == int data = { @@ -141,18 +139,11 @@ def sign_eip712_channel_open(eth_privkey: bytes, chainId: int, verifyingContract :return: The signature according to EIP712 (32+32+1 raw bytes). :rtype: bytes """ - # create EIP712 typed data object + assert is_eth_privkey(eth_privkey) + data = _create_eip712_channel_open(chainId, verifyingContract, ctype, openedAt, marketId, channelId, actor, delegate, marketmaker, recipient, amount) - - # FIXME: this fails on PyPy (but ot on CPy!) with - # Unknown format b'%M\xff\xcd2w\xc0\xb1f\x0fmB\xef\xbbuN\xda\xba\xbc+', attempted to normalize to 0x254dffcd3277c0b1660f6d42efbb754edababc2b - _args = signing.sign_typed_data(data, eth_privkey) - - signature = signing.v_r_s_to_signature(*_args) - assert len(signature) == _EIP712_SIG_LEN - - return signature + return sign(eth_privkey, data) def recover_eip712_channel_open(chainId: int, verifyingContract: bytes, ctype: int, openedAt: int, @@ -164,11 +155,8 @@ def recover_eip712_channel_open(chainId: int, verifyingContract: bytes, ctype: i :return: The (computed) signer address the signature was signed with. :rtype: bytes """ - # create EIP712 typed data object + assert is_signature(signature) + data = _create_eip712_channel_open(chainId, verifyingContract, ctype, openedAt, marketId, channelId, actor, delegate, marketmaker, recipient, amount) - - assert type(signature) == bytes and len(signature) == _EIP712_SIG_LEN - signer_address = signing.recover_typed_data(data, *signing.signature_to_v_r_s(signature)) - - return a2b_hex(signer_address[2:]) + return recover(data, signature) diff --git a/autobahn/xbr/_eip712_consent.py b/autobahn/xbr/_eip712_consent.py index 79e7284af..6d7a0b5b5 100644 --- a/autobahn/xbr/_eip712_consent.py +++ b/autobahn/xbr/_eip712_consent.py @@ -24,12 +24,14 @@ # ############################################################################### -from ._eip712_base import sign, recover, is_address, is_bytes16 +from typing import Optional +from ._eip712_base import sign, recover, is_address, is_bytes16, is_eth_privkey, \ + is_signature, is_chain_id, is_block_number def _create_eip712_consent(chainId: int, verifyingContract: bytes, member: bytes, updated: int, marketId: bytes, delegate: bytes, delegateType: int, apiCatalog: bytes, - consent: bool, servicePrefix: str) -> dict: + consent: bool, servicePrefix: Optional[str]) -> dict: """ :param chainId: @@ -44,10 +46,10 @@ def _create_eip712_consent(chainId: int, verifyingContract: bytes, member: bytes :param servicePrefix: :return: """ - assert type(chainId) == int + assert is_chain_id(chainId) assert is_address(verifyingContract) assert is_address(member) - assert type(updated) == int + assert is_block_number(updated) assert is_bytes16(marketId) assert is_address(delegate) assert type(delegateType) == int @@ -143,7 +145,8 @@ def sign_eip712_consent(eth_privkey: bytes, chainId: int, verifyingContract: byt :return: The signature according to EIP712 (32+32+1 raw bytes). :rtype: bytes """ - # create EIP712 typed data object + assert is_eth_privkey(eth_privkey) + data = _create_eip712_consent(chainId, verifyingContract, member, updated, marketId, delegate, delegateType, apiCatalog, consent, servicePrefix) return sign(eth_privkey, data) @@ -158,7 +161,8 @@ def recover_eip712_consent(chainId: int, verifyingContract: bytes, member: bytes :return: The (computed) signer address the signature was signed with. :rtype: bytes """ - # create EIP712 typed data object + assert is_signature(signature) + data = _create_eip712_consent(chainId, verifyingContract, member, updated, marketId, delegate, delegateType, apiCatalog, consent, servicePrefix) return recover(data, signature) diff --git a/autobahn/xbr/_eip712_domain_create.py b/autobahn/xbr/_eip712_domain_create.py new file mode 100644 index 000000000..128df8c71 --- /dev/null +++ b/autobahn/xbr/_eip712_domain_create.py @@ -0,0 +1,157 @@ +############################################################################### +# +# The MIT License (MIT) +# +# Copyright (c) Crossbar.io Technologies 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. +# +############################################################################### + +from typing import Optional +from ._eip712_base import sign, recover, is_chain_id, is_address, is_bytes16, is_cs_pubkey, \ + is_block_number, is_signature, is_eth_privkey + + +def _create_eip712_domain_create(chainId: int, verifyingContract: bytes, member: bytes, created: int, + domainId: bytes, domainKey: bytes, license: str, terms: str, + meta: Optional[str]) -> dict: + """ + + :param chainId: + :param verifyingContract: + :param member: + :param created: + :param domainId: + :param domainKey: + :param license: + :param terms: + :param meta: + :return: + """ + assert is_chain_id(chainId) + assert is_address(verifyingContract) + assert is_address(member) + assert is_block_number(created) + assert is_bytes16(domainId) + assert is_cs_pubkey(domainKey) + + data = { + 'types': { + 'EIP712Domain': [ + { + 'name': 'name', + 'type': 'string' + }, + { + 'name': 'version', + 'type': 'string' + }, + ], + 'EIP712DomainCreate': [ + { + 'name': 'chainId', + 'type': 'uint256' + }, + { + 'name': 'verifyingContract', + 'type': 'address' + }, + { + 'name': 'member', + 'type': 'address' + }, + { + 'name': 'created', + 'type': 'uint256' + }, + { + 'name': 'domainId', + 'type': 'bytes16' + }, + { + 'name': 'domainKey', + 'type': 'bytes32' + }, + { + 'name': 'license', + 'type': 'string' + }, + { + 'name': 'terms', + 'type': 'string' + }, + { + 'name': 'meta', + 'type': 'string' + }, + ] + }, + 'primaryType': 'EIP712DomainCreate', + 'domain': { + 'name': 'XBR', + 'version': '1', + }, + 'message': { + 'chainId': chainId, + 'verifyingContract': verifyingContract, + 'member': member, + 'created': created, + 'domainId': domainId, + 'domainKey': domainKey, + 'license': license, + 'terms': terms, + 'meta': meta or '', + } + } + + return data + + +def sign_eip712_domain_create(eth_privkey: bytes, chainId: int, verifyingContract: bytes, member: bytes, created: int, + domainId: bytes, domainKey: bytes, license: str, terms: str, + meta: str) -> bytes: + """ + + :param eth_privkey: Ethereum address of member (a raw 20 bytes Ethereum address). + :type eth_privkey: bytes + + :return: The signature according to EIP712 (32+32+1 raw bytes). + :rtype: bytes + """ + assert is_eth_privkey(eth_privkey) + + data = _create_eip712_domain_create(chainId, verifyingContract, member, created, domainId, domainKey, license, + terms, meta) + return sign(eth_privkey, data) + + +def recover_eip712_domain_create(chainId: int, verifyingContract: bytes, member: bytes, created: int, domainId: bytes, + domainKey: bytes, license: str, terms: str, meta: str, signature: bytes) -> bytes: + """ + Recover the signer address the given EIP712 signature was signed with. + + :return: The (computed) signer address the signature was signed with. + :rtype: bytes + """ + assert is_signature(signature) + + data = _create_eip712_domain_create(chainId, verifyingContract, member, created, domainId, domainKey, license, + terms, meta) + return recover(data, signature) diff --git a/autobahn/xbr/_eip712_market_create.py b/autobahn/xbr/_eip712_market_create.py index 1a8d8cc27..b08fe2053 100644 --- a/autobahn/xbr/_eip712_market_create.py +++ b/autobahn/xbr/_eip712_market_create.py @@ -24,11 +24,13 @@ # ############################################################################### -from ._eip712_base import sign, recover +from typing import Optional +from ._eip712_base import sign, recover, is_address, is_bytes16, is_block_number, \ + is_chain_id, is_eth_privkey, is_signature def _create_eip712_market_create(chainId: int, verifyingContract: bytes, member: bytes, created: int, - marketId: bytes, coin: bytes, terms: str, meta: str, maker: bytes, + marketId: bytes, coin: bytes, terms: str, meta: Optional[str], maker: bytes, providerSecurity: int, consumerSecurity: int, marketFee: int) -> dict: """ @@ -46,15 +48,15 @@ def _create_eip712_market_create(chainId: int, verifyingContract: bytes, member: :param marketFee: :return: """ - assert type(chainId) == int - assert type(verifyingContract) == bytes and len(verifyingContract) == 20 - assert type(member) == bytes and len(member) == 20 - assert type(created) == int - assert type(marketId) == bytes and len(marketId) == 16 - assert type(coin) == bytes and len(coin) == 20 + assert is_chain_id(chainId) + assert is_address(verifyingContract) + assert is_address(member) + assert is_block_number(created) + assert is_bytes16(marketId) + assert is_address(coin) assert type(terms) == str - assert type(meta) == str - assert type(maker) == bytes and len(maker) == 20 + assert meta is None or type(meta) == str + assert is_address(maker) assert type(providerSecurity) == int assert type(consumerSecurity) == int assert type(marketFee) == int @@ -117,7 +119,7 @@ def _create_eip712_market_create(chainId: int, verifyingContract: bytes, member: 'created': created, 'marketId': marketId, 'coin': coin, - 'terms': terms or '', + 'terms': terms, 'meta': meta or '', 'maker': maker, 'marketFee': marketFee, @@ -138,7 +140,8 @@ def sign_eip712_market_create(eth_privkey: bytes, chainId: int, verifyingContrac :return: The signature according to EIP712 (32+32+1 raw bytes). :rtype: bytes """ - # create EIP712 typed data object + assert is_eth_privkey(eth_privkey) + data = _create_eip712_market_create(chainId, verifyingContract, member, created, marketId, coin, terms, meta, maker, providerSecurity, consumerSecurity, marketFee) return sign(eth_privkey, data) @@ -154,7 +157,8 @@ def recover_eip712_market_create(chainId: int, verifyingContract: bytes, member: :return: The (computed) signer address the signature was signed with. :rtype: bytes """ - # create EIP712 typed data object + assert is_signature(signature) + data = _create_eip712_market_create(chainId, verifyingContract, member, created, marketId, coin, terms, meta, maker, providerSecurity, consumerSecurity, marketFee) diff --git a/autobahn/xbr/_eip712_market_join.py b/autobahn/xbr/_eip712_market_join.py index d4cd3ed87..ad624b615 100644 --- a/autobahn/xbr/_eip712_market_join.py +++ b/autobahn/xbr/_eip712_market_join.py @@ -24,14 +24,13 @@ # ############################################################################### -from binascii import a2b_hex -from py_eth_sig_utils import signing - -_EIP712_SIG_LEN = 32 + 32 + 1 +from typing import Optional +from ._eip712_base import sign, recover, is_address, is_bytes16, is_block_number, \ + is_chain_id, is_eth_privkey, is_signature def _create_eip712_market_join(chainId: int, verifyingContract: bytes, member: bytes, joined: int, - marketId: bytes, actorType: int, meta: str) -> dict: + marketId: bytes, actorType: int, meta: Optional[str]) -> dict: """ :param chainId: @@ -43,11 +42,11 @@ def _create_eip712_market_join(chainId: int, verifyingContract: bytes, member: b :param meta: :return: """ - assert type(chainId) == int - assert type(verifyingContract) == bytes and len(verifyingContract) == 20 - assert type(member) == bytes and len(member) == 20 - assert type(joined) == int - assert type(marketId) == bytes and len(marketId) == 16 + assert is_chain_id(chainId) + assert is_address(verifyingContract) + assert is_address(member) + assert is_block_number(joined) + assert is_bytes16(marketId) assert type(actorType) == int assert meta is None or type(meta) == str @@ -123,33 +122,11 @@ def sign_eip712_market_join(eth_privkey: bytes, chainId: int, verifyingContract: :return: The signature according to EIP712 (32+32+1 raw bytes). :rtype: bytes """ - assert type(eth_privkey) == bytes and len(eth_privkey) == 32 - assert type(chainId) == int - assert type(verifyingContract) == bytes and len(verifyingContract) == 20 - assert type(member) == bytes and len(member) == 20 - assert type(joined) == int - assert type(marketId) == bytes and len(marketId) == 16 - assert type(actorType) == int - assert meta is None or type(meta) == str - - # make a private key object from the raw private key bytes - # pkey = eth_keys.keys.PrivateKey(eth_privkey) - - # get the canonical address of the account - # eth_adr = web3.Web3.toChecksumAddress(pkey.public_key.to_canonical_address()) - # eth_adr = pkey.public_key.to_canonical_address() + assert is_eth_privkey(eth_privkey) - # create EIP712 typed data object data = _create_eip712_market_join(chainId, verifyingContract, member, joined, marketId, actorType, meta) - # FIXME: this fails on PyPy (but ot on CPy!) with - # Unknown format b'%M\xff\xcd2w\xc0\xb1f\x0fmB\xef\xbbuN\xda\xba\xbc+', attempted to normalize to 0x254dffcd3277c0b1660f6d42efbb754edababc2b - _args = signing.sign_typed_data(data, eth_privkey) - - signature = signing.v_r_s_to_signature(*_args) - assert len(signature) == _EIP712_SIG_LEN - - return signature + return sign(eth_privkey, data) def recover_eip712_market_join(chainId: int, verifyingContract: bytes, member: bytes, joined: int, @@ -160,19 +137,8 @@ def recover_eip712_market_join(chainId: int, verifyingContract: bytes, member: b :return: The (computed) signer address the signature was signed with. :rtype: bytes """ - assert type(chainId) == int - assert type(verifyingContract) == bytes and len(verifyingContract) == 20 - assert type(member) == bytes and len(member) == 20 - assert type(joined) == int - assert type(marketId) == bytes and len(marketId) == 16 - assert type(actorType) == int - assert meta is None or type(meta) == str - assert type(signature) == bytes and len(signature) == _EIP712_SIG_LEN + assert is_signature(signature) - # recreate EIP712 typed data object data = _create_eip712_market_join(chainId, verifyingContract, member, joined, marketId, actorType, meta) - # this returns the signer (checksummed) address as a string, eg "0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d" - signer_address = signing.recover_typed_data(data, *signing.signature_to_v_r_s(signature)) - - return a2b_hex(signer_address[2:]) + return recover(data, signature) diff --git a/autobahn/xbr/_eip712_market_leave.py b/autobahn/xbr/_eip712_market_leave.py new file mode 100644 index 000000000..27c2bd0b1 --- /dev/null +++ b/autobahn/xbr/_eip712_market_leave.py @@ -0,0 +1,137 @@ +############################################################################### +# +# The MIT License (MIT) +# +# Copyright (c) Crossbar.io Technologies 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. +# +############################################################################### + +from ._eip712_base import sign, recover, is_address, is_bytes16, is_block_number, \ + is_chain_id, is_eth_privkey, is_signature + + +def _create_eip712_market_leave(chainId: int, verifyingContract: bytes, member: bytes, left: int, + marketId: bytes, actorType: int) -> dict: + """ + + :param chainId: + :param verifyingContract: + :param member: + :param joined: + :param marketId: + :param actorType: + :param meta: + :return: + """ + assert is_chain_id(chainId) + assert is_address(verifyingContract) + assert is_address(member) + assert is_block_number(left) + assert is_bytes16(marketId) + assert type(actorType) == int + + data = { + 'types': { + 'EIP712Domain': [ + { + 'name': 'name', + 'type': 'string' + }, + { + 'name': 'version', + 'type': 'string' + }, + ], + 'EIP712MarketLeave': [ + { + 'name': 'chainId', + 'type': 'uint256' + }, + { + 'name': 'verifyingContract', + 'type': 'address' + }, + { + 'name': 'member', + 'type': 'address' + }, + { + 'name': 'left', + 'type': 'uint256' + }, + { + 'name': 'marketId', + 'type': 'bytes16' + }, + { + 'name': 'actorType', + 'type': 'uint8' + }, + ] + }, + 'primaryType': 'EIP712MarketLeave', + 'domain': { + 'name': 'XBR', + 'version': '1', + }, + 'message': { + 'chainId': chainId, + 'verifyingContract': verifyingContract, + 'member': member, + 'left': left, + 'marketId': marketId, + 'actorType': actorType, + } + } + + return data + + +def sign_eip712_market_leave(eth_privkey: bytes, chainId: int, verifyingContract: bytes, member: bytes, left: int, + marketId: bytes, actorType: int) -> bytes: + """ + + :param eth_privkey: Ethereum address of buyer (a raw 20 bytes Ethereum address). + :type eth_privkey: bytes + + :return: The signature according to EIP712 (32+32+1 raw bytes). + :rtype: bytes + """ + assert is_eth_privkey(eth_privkey) + + data = _create_eip712_market_leave(chainId, verifyingContract, member, left, marketId, actorType) + + return sign(eth_privkey, data) + + +def recover_eip712_market_leave(chainId: int, verifyingContract: bytes, member: bytes, left: int, + marketId: bytes, actorType: int, signature: bytes) -> bytes: + """ + Recover the signer address the given EIP712 signature was signed with. + + :return: The (computed) signer address the signature was signed with. + :rtype: bytes + """ + assert is_signature(signature) + + data = _create_eip712_market_leave(chainId, verifyingContract, member, left, marketId, actorType) + + return recover(data, signature) diff --git a/autobahn/xbr/_eip712_member_login.py b/autobahn/xbr/_eip712_member_login.py index 27d1aab86..fa015f757 100644 --- a/autobahn/xbr/_eip712_member_login.py +++ b/autobahn/xbr/_eip712_member_login.py @@ -24,10 +24,8 @@ # ############################################################################### -from binascii import a2b_hex -from py_eth_sig_utils import signing - -_EIP712_SIG_LEN = 32 + 32 + 1 +from ._eip712_base import sign, recover, is_address, is_block_number, \ + is_chain_id, is_eth_privkey, is_signature, is_cs_pubkey def _create_eip712_member_login(chainId: int, verifyingContract: bytes, member: bytes, loggedIn: int, @@ -43,13 +41,13 @@ def _create_eip712_member_login(chainId: int, verifyingContract: bytes, member: :param client_pubkey: :return: """ - assert type(chainId) == int - assert type(verifyingContract) == bytes and len(verifyingContract) == 20 - assert type(member) == bytes and len(member) == 20 - assert type(loggedIn) == int + assert is_chain_id(chainId) + assert is_address(verifyingContract) + assert is_address(member) + assert is_block_number(loggedIn) assert type(timestamp) == int assert type(member_email) == str - assert type(client_pubkey) == bytes and len(client_pubkey) == 32 + assert is_cs_pubkey(client_pubkey) data = { 'types': { @@ -123,34 +121,12 @@ def sign_eip712_member_login(eth_privkey: bytes, chainId: int, verifyingContract :return: The signature according to EIP712 (32+32+1 raw bytes). :rtype: bytes """ - assert type(eth_privkey) == bytes and len(eth_privkey) == 32 - assert type(chainId) == int - assert type(verifyingContract) == bytes and len(verifyingContract) == 20 - assert type(member) == bytes and len(member) == 20 - assert type(loggedIn) == int - assert type(timestamp) == int - assert type(member_email) == str - assert type(client_pubkey) == bytes and len(client_pubkey) == 32 - - # make a private key object from the raw private key bytes - # pkey = eth_keys.keys.PrivateKey(eth_privkey) - - # get the canonical address of the account - # eth_adr = web3.Web3.toChecksumAddress(pkey.public_key.to_canonical_address()) - # eth_adr = pkey.public_key.to_canonical_address() + assert is_eth_privkey(eth_privkey) - # create EIP712 typed data object data = _create_eip712_member_login(chainId, verifyingContract, member, loggedIn, timestamp, member_email, client_pubkey) - # FIXME: this fails on PyPy (but ot on CPy!) with - # Unknown format b'%M\xff\xcd2w\xc0\xb1f\x0fmB\xef\xbbuN\xda\xba\xbc+', attempted to normalize to 0x254dffcd3277c0b1660f6d42efbb754edababc2b - _args = signing.sign_typed_data(data, eth_privkey) - - signature = signing.v_r_s_to_signature(*_args) - assert len(signature) == _EIP712_SIG_LEN - - return signature + return sign(eth_privkey, data) def recover_eip712_member_login(chainId: int, verifyingContract: bytes, member: bytes, loggedIn: int, @@ -162,20 +138,9 @@ def recover_eip712_member_login(chainId: int, verifyingContract: bytes, member: :return: The (computed) signer address the signature was signed with. :rtype: bytes """ - assert type(chainId) == int - assert type(verifyingContract) == bytes and len(verifyingContract) == 20 - assert type(member) == bytes and len(member) == 20 - assert type(loggedIn) == int - assert type(timestamp) == int - assert type(member_email) == str - assert type(client_pubkey) == bytes and len(client_pubkey) == 32 - assert type(signature) == bytes and len(signature) == _EIP712_SIG_LEN + assert is_signature(signature) - # recreate EIP712 typed data object data = _create_eip712_member_login(chainId, verifyingContract, member, loggedIn, timestamp, member_email, client_pubkey) - # this returns the signer (checksummed) address as a string, eg "0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d" - signer_address = signing.recover_typed_data(data, *signing.signature_to_v_r_s(signature)) - - return a2b_hex(signer_address[2:]) + return recover(data, signature) diff --git a/autobahn/xbr/_eip712_member_register.py b/autobahn/xbr/_eip712_member_register.py index 4714d9eb1..02a903830 100644 --- a/autobahn/xbr/_eip712_member_register.py +++ b/autobahn/xbr/_eip712_member_register.py @@ -24,14 +24,13 @@ # ############################################################################### -from binascii import a2b_hex -from py_eth_sig_utils import signing - -_EIP712_SIG_LEN = 32 + 32 + 1 +from typing import Optional +from ._eip712_base import sign, recover, is_address, is_block_number, \ + is_chain_id, is_eth_privkey, is_signature def _create_eip712_member_register(chainId: int, verifyingContract: bytes, member: bytes, registered: int, - eula: str, profile: str) -> dict: + eula: str, profile: Optional[str]) -> dict: """ :param chainId: @@ -42,10 +41,10 @@ def _create_eip712_member_register(chainId: int, verifyingContract: bytes, membe :param profile: :return: """ - assert type(chainId) == int - assert type(verifyingContract) == bytes and len(verifyingContract) == 20 - assert type(member) == bytes and len(member) == 20 - assert type(registered) == int + assert is_chain_id(chainId) + assert is_address(verifyingContract) + assert is_address(member) + assert is_block_number(registered) assert type(eula) == str assert profile is None or type(profile) == str @@ -107,7 +106,7 @@ def _create_eip712_member_register(chainId: int, verifyingContract: bytes, membe def sign_eip712_member_register(eth_privkey: bytes, chainId: int, verifyingContract: bytes, member: bytes, - registered: int, eula: str, profile: str) -> bytes: + registered: int, eula: Optional[str], profile: str) -> bytes: """ :param eth_privkey: Ethereum address of buyer (a raw 20 bytes Ethereum address). @@ -116,54 +115,23 @@ def sign_eip712_member_register(eth_privkey: bytes, chainId: int, verifyingContr :return: The signature according to EIP712 (32+32+1 raw bytes). :rtype: bytes """ - assert type(eth_privkey) == bytes and len(eth_privkey) == 32 - assert type(chainId) == int - assert type(verifyingContract) == bytes and len(verifyingContract) == 20 - assert type(member) == bytes and len(member) == 20 - assert type(registered) == int - assert type(eula) == str - assert profile is None or type(profile) == str - - # make a private key object from the raw private key bytes - # pkey = eth_keys.keys.PrivateKey(eth_privkey) - - # get the canonical address of the account - # eth_adr = web3.Web3.toChecksumAddress(pkey.public_key.to_canonical_address()) - # eth_adr = pkey.public_key.to_canonical_address() + assert is_eth_privkey(eth_privkey) - # create EIP712 typed data object data = _create_eip712_member_register(chainId, verifyingContract, member, registered, eula, profile) - # FIXME: this fails on PyPy (but ot on CPy!) with - # Unknown format b'%M\xff\xcd2w\xc0\xb1f\x0fmB\xef\xbbuN\xda\xba\xbc+', attempted to normalize to 0x254dffcd3277c0b1660f6d42efbb754edababc2b - _args = signing.sign_typed_data(data, eth_privkey) - - signature = signing.v_r_s_to_signature(*_args) - assert len(signature) == _EIP712_SIG_LEN - - return signature + return sign(eth_privkey, data) def recover_eip712_member_register(chainId: int, verifyingContract: bytes, member: bytes, registered: int, - eula: str, profile: str, signature: bytes) -> bytes: + eula: str, profile: Optional[str], signature: bytes) -> bytes: """ Recover the signer address the given EIP712 signature was signed with. :return: The (computed) signer address the signature was signed with. :rtype: bytes """ - assert type(chainId) == int - assert type(verifyingContract) == bytes and len(verifyingContract) == 20 - assert type(member) == bytes and len(member) == 20 - assert type(registered) == int - assert type(eula) == str - assert profile is None or type(profile) == str - assert type(signature) == bytes and len(signature) == _EIP712_SIG_LEN + assert is_signature(signature) - # recreate EIP712 typed data object data = _create_eip712_member_register(chainId, verifyingContract, member, registered, eula, profile) - # this returns the signer (checksummed) address as a string, eg "0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d" - signer_address = signing.recover_typed_data(data, *signing.signature_to_v_r_s(signature)) - - return a2b_hex(signer_address[2:]) + return recover(data, signature) diff --git a/autobahn/xbr/_eip712_member_unregister.py b/autobahn/xbr/_eip712_member_unregister.py new file mode 100644 index 000000000..308fde029 --- /dev/null +++ b/autobahn/xbr/_eip712_member_unregister.py @@ -0,0 +1,122 @@ +############################################################################### +# +# The MIT License (MIT) +# +# Copyright (c) Crossbar.io Technologies 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. +# +############################################################################### + +from ._eip712_base import sign, recover, is_chain_id, is_address, \ + is_block_number, is_signature, is_eth_privkey + + +def _create_eip712_member_unregister(chainId: int, verifyingContract: bytes, member: bytes, + retired: int) -> dict: + """ + + :param chainId: + :param verifyingContract: + :param member: + :param retired: + :return: + """ + assert is_chain_id(chainId) + assert is_address(verifyingContract) + assert is_address(member) + assert is_block_number(retired) + + data = { + 'types': { + 'EIP712Domain': [ + { + 'name': 'name', + 'type': 'string' + }, + { + 'name': 'version', + 'type': 'string' + }, + ], + 'EIP712MemberUnregister': [ + { + 'name': 'chainId', + 'type': 'uint256' + }, + { + 'name': 'verifyingContract', + 'type': 'address' + }, + { + 'name': 'member', + 'type': 'address' + }, + { + 'name': 'retired', + 'type': 'uint256' + }, + ] + }, + 'primaryType': 'EIP712MemberUnregister', + 'domain': { + 'name': 'XBR', + 'version': '1', + }, + 'message': { + 'chainId': chainId, + 'verifyingContract': verifyingContract, + 'member': member, + 'paired': retired, + } + } + + return data + + +def sign_eip712_member_unregister(eth_privkey: bytes, chainId: int, verifyingContract: bytes, member: bytes, + retired: int) -> bytes: + """ + + :param eth_privkey: Ethereum address of buyer (a raw 20 bytes Ethereum address). + :type eth_privkey: bytes + + :return: The signature according to EIP712 (32+32+1 raw bytes). + :rtype: bytes + """ + assert is_eth_privkey(eth_privkey) + + data = _create_eip712_member_unregister(chainId, verifyingContract, member, retired) + + return sign(eth_privkey, data) + + +def recover_eip712_member_unregister(chainId: int, verifyingContract: bytes, member: bytes, retired: int, + signature: bytes) -> bytes: + """ + Recover the signer address the given EIP712 signature was signed with. + + :return: The (computed) signer address the signature was signed with. + :rtype: bytes + """ + assert is_signature(signature) + + data = _create_eip712_member_unregister(chainId, verifyingContract, member, retired) + + return recover(data, signature) diff --git a/autobahn/xbr/_eip712_node_pair.py b/autobahn/xbr/_eip712_node_pair.py new file mode 100644 index 000000000..db286fb0e --- /dev/null +++ b/autobahn/xbr/_eip712_node_pair.py @@ -0,0 +1,159 @@ +############################################################################### +# +# The MIT License (MIT) +# +# Copyright (c) Crossbar.io Technologies 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. +# +############################################################################### + +from typing import Optional +from ._eip712_base import sign, recover, is_chain_id, is_address, is_bytes16, is_cs_pubkey, \ + is_block_number, is_signature, is_eth_privkey + + +def _create_eip712_node_pair(chainId: int, verifyingContract: bytes, member: bytes, paired: int, + nodeId: bytes, domainId: bytes, nodeType: int, nodeKey: bytes, + config: Optional[str]) -> dict: + """ + + :param chainId: + :param verifyingContract: + :param member: + :param joined: + :param marketId: + :param actorType: + :param meta: + :return: + """ + assert is_chain_id(chainId) + assert is_address(verifyingContract) + assert is_address(member) + assert is_block_number(paired) + assert is_bytes16(nodeId) + assert is_bytes16(domainId) + assert type(nodeType) == int + assert is_cs_pubkey(nodeKey) + assert config is None or type(config) == str + + data = { + 'types': { + 'EIP712Domain': [ + { + 'name': 'name', + 'type': 'string' + }, + { + 'name': 'version', + 'type': 'string' + }, + ], + 'EIP712NodePair': [ + { + 'name': 'chainId', + 'type': 'uint256' + }, + { + 'name': 'verifyingContract', + 'type': 'address' + }, + { + 'name': 'member', + 'type': 'address' + }, + { + 'name': 'paired', + 'type': 'uint256' + }, + { + 'name': 'nodeId', + 'type': 'bytes16' + }, + { + 'name': 'domainId', + 'type': 'bytes16' + }, + { + 'name': 'nodeType', + 'type': 'uint8' + }, + { + 'name': 'nodeKey', + 'type': 'bytes16' + }, + { + 'name': 'config', + 'type': 'string', + }, + ] + }, + 'primaryType': 'EIP712NodePair', + 'domain': { + 'name': 'XBR', + 'version': '1', + }, + 'message': { + 'chainId': chainId, + 'verifyingContract': verifyingContract, + 'member': member, + 'paired': paired, + 'nodeId': nodeId, + 'domainId': domainId, + 'nodeType': nodeType, + 'nodeKey': nodeKey, + 'config': config or '', + } + } + + return data + + +def sign_eip712_node_pair(eth_privkey: bytes, chainId: int, verifyingContract: bytes, member: bytes, paired: int, + nodeId: bytes, domainId: bytes, nodeType: int, nodeKey: bytes, + config: Optional[str]) -> bytes: + """ + + :param eth_privkey: Ethereum address of buyer (a raw 20 bytes Ethereum address). + :type eth_privkey: bytes + + :return: The signature according to EIP712 (32+32+1 raw bytes). + :rtype: bytes + """ + assert is_eth_privkey(eth_privkey) + + data = _create_eip712_node_pair(chainId, verifyingContract, member, paired, nodeId, domainId, nodeType, + nodeKey, config) + return sign(eth_privkey, data) + + +def recover_eip712_node_pair(chainId: int, verifyingContract: bytes, member: bytes, paired: int, + nodeId: bytes, domainId: bytes, nodeType: int, nodeKey: bytes, + config: str, signature: bytes) -> bytes: + """ + Recover the signer address the given EIP712 signature was signed with. + + :return: The (computed) signer address the signature was signed with. + :rtype: bytes + """ + assert is_signature(signature) + + data = _create_eip712_node_pair(chainId, verifyingContract, member, paired, nodeId, domainId, nodeType, + nodeKey, config) + return recover(data, signature) diff --git a/docs/_static/screenshots/xbr-cli-not-a-member-yet.png b/docs/_static/screenshots/xbr-cli-not-a-member-yet.png new file mode 100644 index 000000000..32471c113 Binary files /dev/null and b/docs/_static/screenshots/xbr-cli-not-a-member-yet.png differ diff --git a/docs/_static/screenshots/xbr-token-transfer-after.png b/docs/_static/screenshots/xbr-token-transfer-after.png new file mode 100644 index 000000000..a9786292f Binary files /dev/null and b/docs/_static/screenshots/xbr-token-transfer-after.png differ diff --git a/docs/_static/screenshots/xbr-token-transfer.png b/docs/_static/screenshots/xbr-token-transfer.png new file mode 100644 index 000000000..43de65b8b Binary files /dev/null and b/docs/_static/screenshots/xbr-token-transfer.png differ diff --git a/docs/changelog.rst b/docs/changelog.rst index c9af96ad1..66343b673 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,6 +5,20 @@ Changelog ========= +20.6.1 +------ + +* new: massive expansion of XBR CLI and EIP712 helpers +* new: more (exhaustive) serializer cross-tripping tests +* fix: some code quality and bug-risk issues (#1379) +* fix: removed externalPort assignment when not set (#1378) +* fix: docs link in README (#1381) +* fix: docs typo frameword -> framework (#1380) +* fix: improve logging; track results on observable mixin +* new: add environmental variable that strips xbr. (#1374) +* fix: trollius is gone (#1373) +* new: added ability to disable TLS channel binding (#1368) + 20.4.3 ------ diff --git a/docs/conf.py b/docs/conf.py index 8675502b0..0eec88dc2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -110,15 +110,20 @@ def autodoc_skip_member(app, what, name, obj, skip, options): # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.viewcode', - 'sphinx.ext.intersphinx', - - 'sphinx.ext.ifconfig', - 'sphinx.ext.todo', - 'sphinx.ext.doctest', - #'sphinxcontrib.spelling', - 'txsphinx' + 'sphinx.ext.autodoc', + 'sphinx.ext.viewcode', + 'sphinx.ext.intersphinx', + + # Usage: .. thumbnail:: picture.png + # Installation: pip install sphinxcontrib-images + # Source: https://github.com/sphinx-contrib/images + 'sphinxcontrib.images', + + 'sphinx.ext.ifconfig', + 'sphinx.ext.todo', + 'sphinx.ext.doctest', + #'sphinxcontrib.spelling', + 'txsphinx' ] # extensions not available on RTD diff --git a/docs/xbr-cli.rst b/docs/xbr-cli.rst index 55bc07d4e..54dc50b57 100644 --- a/docs/xbr-cli.rst +++ b/docs/xbr-cli.rst @@ -6,15 +6,18 @@ Autobahn includes a command-line interface for the `XBR network `__: -.. image:: _static/screenshots/xbr-metamask-2.png +.. thumbnail:: _static/screenshots/xbr-metamask-2.png Then, to use your Ethereum private key with the XBR CLI, export the private key: -.. image:: _static/screenshots/xbr-metamask-3.png +.. thumbnail:: _static/screenshots/xbr-metamask-3.png + +When using the XBR CLI, you can provide your Ethereum private key using the command line argument ``--ethkey=0x`` +appended with your key: .. code-block:: console --ethkey=0x4C1F... +You can also persistently store your Ethereum private key in the CLI configuration file for one of the user +profiles you have there: + +.. code-block:: console + + $ cat ${HOME}/.xbrnetwork/config.ini + [default] + + # user private Ethereum key + ethkey=0x4C1F... + .. note:: Obviously, you must protect your *private key*! The *public address* of your wallet is not security - sensitive. Even the public address however should always be treated carefully regarding privacy. + sensitive. Even the public address however should always be treated carefully regarding privacy. When you + store your private key in the CLI configuration file, make sure to protect this file using + ``chmod 600 ${HOME}/.xbrnetwork/config.ini``. Finally, for testing on Rinkeby, get yourself some Ether from the `Rinkeby faucet `__: -.. image:: _static/screenshots/rinkeby-faucet.png +.. thumbnail:: _static/screenshots/rinkeby-faucet.png If you want to use the accounts from your MetaMask wallet derived from your wallet's seedphrase, you can use a helper included with Autobahn to derive private keys for all accounts, eg account `0`: @@ -163,18 +188,38 @@ A new key can be created by generating 32 random bytes: $ openssl rand -hex 32 ecdc5e97... -When using the XBR CLI, provide your WAMP client key using the command line argument ``--cskey=0x`` appended +When using the XBR CLI, provide your WAMP client private key using the command line argument ``--cskey=0x`` appended with your key: .. code-block:: console --cskey=0xecdc5e97... +You can also persistently store your WAMP client private key in the CLI configuration file for one of the user +profiles you have there: + +.. code-block:: console + + $ cat ${HOME}/.xbrnetwork/config.ini + [default] -Profile -------- + # user private WAMP client key + cskey=0xecdc5e97... -To create a new user profile: +.. note:: + + Obviously, you must protect your *private key*! The *public key* of WAMP client key pair is not security + sensitive. Even the public key however should always be treated carefully regarding privacy. When you + store your private key in the CLI configuration file, make sure to protect this file using + ``chmod 600 ${HOME}/.xbrnetwork/config.ini``. + + +CLI User Profile +---------------- + +The CLI maintains a local user configuration file in ``${HOME}/.xbrnetwork/config.ini``. +The configuration file will contain at least one (CLI) user profile. +To create a new CLI configuration file and user profile: .. code-block:: console @@ -191,6 +236,17 @@ To create a new user profile: user profile "default" loaded +Not a member yet +---------------- + +Before you have registered in the XBR Network, this is what you get: + +.. code-block:: console + + $ xbrnetwork get-member + +.. thumbnail:: _static/screenshots/xbr-cli-not-a-member-yet.png + On-boarding ----------- @@ -210,6 +266,26 @@ client key, as well as your username and email: Of course, neither your username nor your email is stored on-chain (on the blockchain). Your email is required so that we can send a verification code to you (see next step). +This is what you should see: + +.. code-block:: console + + (cpy382_1) oberstet@intel-nuci7:~/scm/crossbario/autobahn-python$ xbrnetwork register-member \ + > --username=oberstet6 \ + > --email=tobias.oberstein@gmail.com + 2020-06-05T18:23:18+0200 XBR CLI v20.6.1 + 2020-06-05T18:23:18+0200 Profile default loaded from /home/oberstet/.xbrnetwork/config.ini + 2020-06-05T18:23:18+0200 Connecting to "wss://planet.xbr.network/ws" at realm "xbrnetwork" .. + 2020-06-05T18:23:18+0200 Client Ethereum key loaded, public address is 0x66290fA8ADcD901Fd994e4f64Cfb53F4c359a326 + 2020-06-05T18:23:18+0200 Client WAMP authentication key loaded, public key is 0x7172c38631864153e16f4db7a4a7ff0e2fbe7a180591d28d60e909d77d644964 + 2020-06-05T18:23:18+0200 Client connected, now joining realm "xbrnetwork" with WAMP-cryptosign authentication .. + 2020-06-05T18:23:18+0200 Ok, client joined on realm "xbrnetwork" [session=3664895410309954, authid="anonymous-T3WJ-LLHN-AYFW-64WP-TXPM-W53F", authrole="anonymous"] + 2020-06-05T18:23:18+0200 not yet a member in the XBR network + 2020-06-05T18:23:20+0200 On-boarding member - verification "dc65d1e9-2387-4226-a1dc-d50f80531574" created + 2020-06-05T18:23:20+0200 Client left realm (reason="wamp.close.normal") + 2020-06-05T18:23:20+0200 Client disconnected + 2020-06-05T18:23:20+0200 Main loop terminated. + You should receive an email with a verification action ID such as ``072061e8-d1b4-4988-9524-6873b4d5784e`` and a verification code such as ``5QRM-R5KR-7PGU``. @@ -218,18 +294,154 @@ Verify the on-boarding request using the verification action and code: .. code-block:: console $ xbrnetwork register-member-verify \ - --cskey=0x7e8f... \ - --ethkey=0x4C1F7... \ - --vaction=072061e8-d1b4-4988-9524-6873b4d5784e \ - --vcode=5QRM-R5KR-7PGU + --vaction=dc65d1e9-2387-4226-a1dc-d50f80531574 \ + --vcode=A346-GJLE-64SW -To access your member profile, run: +This is what you should see: .. code-block:: console - xbrnetwork get-member \ - --cskey=0x7e8f... \ - --ethkey=0x4C1F7... + $ xbrnetwork register-member-verify \ + > --vaction=dc65d1e9-2387-4226-a1dc-d50f80531574 \ + > --vcode=A346-GJLE-64SW + 2020-06-05T18:27:08+0200 XBR CLI v20.6.1 + 2020-06-05T18:27:08+0200 Profile default loaded from /home/oberstet/.xbrnetwork/config.ini + 2020-06-05T18:27:08+0200 Connecting to "wss://planet.xbr.network/ws" at realm "xbrnetwork" .. + 2020-06-05T18:27:08+0200 Client Ethereum key loaded, public address is 0x66290fA8ADcD901Fd994e4f64Cfb53F4c359a326 + 2020-06-05T18:27:08+0200 Client WAMP authentication key loaded, public key is 0x7172c38631864153e16f4db7a4a7ff0e2fbe7a180591d28d60e909d77d644964 + 2020-06-05T18:27:08+0200 Client connected, now joining realm "xbrnetwork" with WAMP-cryptosign authentication .. + 2020-06-05T18:27:08+0200 Ok, client joined on realm "xbrnetwork" [session=8735495987511209, authid="anonymous-N7PW-H7F7-TPCP-ATFP-9X49-GA9C", authrole="anonymous"] + 2020-06-05T18:27:08+0200 not yet a member in the XBR network + 2020-06-05T18:27:08+0200 Verifying member using vaction_oid=dc65d1e9-2387-4226-a1dc-d50f80531574, vaction_code=A346-GJLE-64SW .. + 2020-06-05T18:27:08+0200 SUCCESS! New XBR Member onboarded: member_oid=ab4dd6fd-6250-4cda-81ba-97f7d52ceac9, result= + {'created': 1591374428420644124, + 'member_oid': b'\xabM\xd6\xfdbPL\xda\x81\xba\x97\xf7\xd5,\xea\xc9', + 'transaction': b'\xa4\xa7W\xfe"\x16\xe1l\x9f\xe0\xf7\x18\x8ak\xeb\xba' + b'J\xd5S\xb6\xd9\x99\x9d\x96\xce\x1c\xbcw1\xcd\xec%'} + 2020-06-05T18:27:08+0200 Client left realm (reason="wamp.close.normal") + 2020-06-05T18:27:08+0200 Client disconnected + 2020-06-05T18:27:08+0200 Main loop terminated. + +To access your new XBR Network member profile, run: + +.. code-block:: console + + $ xbrnetwork get-member + +This is what you should see: + +.. code-block:: console + + $ xbrnetwork get-member + 2020-06-05T18:28:38+0200 XBR CLI v20.6.1 + 2020-06-05T18:28:38+0200 Profile default loaded from /home/oberstet/.xbrnetwork/config.ini + 2020-06-05T18:28:38+0200 Connecting to "wss://planet.xbr.network/ws" at realm "xbrnetwork" .. + 2020-06-05T18:28:38+0200 Client Ethereum key loaded, public address is 0x66290fA8ADcD901Fd994e4f64Cfb53F4c359a326 + 2020-06-05T18:28:38+0200 Client WAMP authentication key loaded, public key is 0x7172c38631864153e16f4db7a4a7ff0e2fbe7a180591d28d60e909d77d644964 + 2020-06-05T18:28:38+0200 Client connected, now joining realm "xbrnetwork" with WAMP-cryptosign authentication .. + 2020-06-05T18:28:38+0200 Ok, client joined on realm "xbrnetwork" [session=6918284698412513, authid="member-ab4dd6fd-6250-4cda-81ba-97f7d52ceac9", authrole="member"] + 2020-06-05T18:28:39+0200 Member found: + + {'address': '0x66290fA8ADcD901Fd994e4f64Cfb53F4c359a326', + 'balance': {'eth': Decimal('0.199585784'), 'xbr': 0}, + 'catalogs': 0, + 'created': numpy.datetime64('2020-06-05T16:27:08.420644124'), + 'domains': 0, + 'email': 'tobias.oberstein@gmail.com', + 'eula': 'QmeHTWw717jPEF6aJqhNMrXx4KLrDiTHi5m7gfbjA1BqMj', + 'level': 'ACTIVE', + 'markets': 0, + 'oid': UUID('ab4dd6fd-6250-4cda-81ba-97f7d52ceac9'), + 'profile': 'QmV1eeDextSdUrRUQp9tUXF8SdvVeykaiwYLgrXHHVyULY', + 'username': 'oberstet6'} + + 2020-06-05T18:28:39+0200 Client left realm (reason="wamp.close.normal") + 2020-06-05T18:28:39+0200 Client disconnected + 2020-06-05T18:28:39+0200 Main loop terminated. + + +XBR Token Transfer +------------------ + +When doing ``xbrnetwork get-member``, the information returned will include both your current on-chain ETH balance, +as well as your balance of XBR Token (which is one coin that can be used as a market-payment-coin in markets +configured to use XBR as a means of payment). + +Transfering XBR tokens looks like this + +.. thumbnail:: _static/screenshots/xbr-token-transfer.png + +This transfer of 1000 XBR to some target address did cost 0.001541 ETH (or 0.33 EUR) on Rinkeby testnet. + +After the transfer (to that member), the member information returned will look like this: + +.. thumbnail:: _static/screenshots/xbr-token-transfer-after.png + + +Getting market information +-------------------------- + +To get information about an existing XBR data market: + +.. code-block:: console + + $ xbrnetwork get-market \ + --market=1388ddf6-fe36-4201-b1aa-cb7e36b4cfb3 + + +Creating a market +----------------- + +To create a new XBR data market, generate a new market UUID: + +.. code-block:: console + + $ /usr/bin/uuidgen + 394205e5-5d3d-4eab-a7e8-6c4de21bc76d + +.. code-block:: console + + xbrnetwork create-market \ + --market 394205e5-5d3d-4eab-a7e8-6c4de21bc76d \ + --market_title "IDMA test market 1" \ + --market_label "idma-market1" \ + --market_homepage https://markets.international-data-monetization-award.com/market1 \ + --provider_security 0 \ + --consumer_security 0 \ + --market_fee 0 \ + --marketmaker 0x163D58cE482560B7826b4612f40aa2A7d53310C4 + +.. code-block:: console + + $ xbrnetwork create-market \ + > --market 394205e5-5d3d-4eab-a7e8-6c4de21bc76d \ + > --market_title "IDMA test market 1" \ + > --market_label "idma-market1" \ + > --market_homepage https://markets.international-data-monetization-award.com/market1 \ + > --provider_security 0 \ + > --consumer_security 0 \ + > --market_fee 0 \ + > --marketmaker 0x163D58cE482560B7826b4612f40aa2A7d53310C4 + 2020-06-05T20:54:47+0200 XBR CLI v20.6.1 + 2020-06-05T20:54:47+0200 Profile default loaded from /home/oberstet/.xbrnetwork/config.ini + 2020-06-05T20:54:47+0200 Connecting to "wss://planet.xbr.network/ws" at realm "xbrnetwork" .. + 2020-06-05T20:54:48+0200 Client Ethereum key loaded, public address is 0x66290fA8ADcD901Fd994e4f64Cfb53F4c359a326 + 2020-06-05T20:54:48+0200 Client WAMP authentication key loaded, public key is 0x7172c38631864153e16f4db7a4a7ff0e2fbe7a180591d28d60e909d77d644964 + 2020-06-05T20:54:48+0200 Client connected, now joining realm "xbrnetwork" with WAMP-cryptosign authentication .. + 2020-06-05T20:54:48+0200 Ok, client joined on realm "xbrnetwork" [session=6290962938304946, authid="member-ab4dd6fd-6250-4cda-81ba-97f7d52ceac9", authrole="member"] + 2020-06-05T20:54:49+0200 Total markets before: 3 + 2020-06-05T20:54:49+0200 Market for owner: 0 + 2020-06-05T20:54:55+0200 SUCCESS: Create market request submitted: + {'action': 'create_market', + 'timestamp': 1591383295497514499, + 'vaction_oid': b'\x14\xd5\xe8\xf8\x0f\x9aM\\\x97{\xbd4\x159\xf1\xba'} + + 2020-06-05T20:54:55+0200 SUCCESS: New Market verification "14d5e8f8-0f9a-4d5c-977b-bd341539f1ba" created + 2020-06-05T20:54:55+0200 Client left realm (reason="wamp.close.normal") + 2020-06-05T20:54:55+0200 Client disconnected + 2020-06-05T20:54:55+0200 Main loop terminated. + + Joining a market @@ -243,8 +455,6 @@ Here is how to join as an actor in that market as both a buyer and seller: .. code-block:: console $ xbrnetwork join-market \ - --cskey=0x7e8f... \ - --ethkey=0x4C1F7... \ --market=1388ddf6-fe36-4201-b1aa-cb7e36b4cfb3 \ --actor_type=3 @@ -254,8 +464,6 @@ to complete joining the market: .. code-block:: console xbrnetwork join-market-verify \ - --cskey=0x7e8f... \ - --ethkey=0x4C1F7... \ --vaction=ddcd5452-28cc-4ecb-a0f3-8fc8b596f9a5 \ --vcode=AGGA-PK6G-57NY @@ -264,8 +472,6 @@ To access your actor status in a market, run: .. code-block:: console $ xbrnetwork get-actor \ - --cskey=0x7e8f... \ - --ethkey=0x4C1F7... \ --market=1388ddf6-fe36-4201-b1aa-cb7e36b4cfb3 diff --git a/requirements-rtd.txt b/requirements-rtd.txt index c99a7e97a..bfdad3887 100644 --- a/requirements-rtd.txt +++ b/requirements-rtd.txt @@ -1,3 +1,4 @@ # requirements for building the docs on RTD txaio twisted +sphinxcontrib-images diff --git a/setup.py b/setup.py index 3970ccfc6..af2a67e5a 100644 --- a/setup.py +++ b/setup.py @@ -195,6 +195,7 @@ "twine>=1.6.5", # Apache 2.0 'sphinx>=1.2.3', # BSD + 'sphinxcontrib-images>=0.9.2', # Apache 2.0 'pyenchant>=1.6.6', # LGPL 'sphinxcontrib-spelling>=2.1.2', # BSD 'sphinx_rtd_theme>=0.1.9', # BSD