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