Initial Commit
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
[default]
|
||||
|
||||
url=ws://localhost:9000/ws
|
||||
privkey=default.priv
|
||||
pubkey=default.pub
|
||||
@@ -0,0 +1,9 @@
|
||||
Crossbar.io user private key - KEEP THIS SAFE!
|
||||
|
||||
creator: oberstet@intel-nuci7
|
||||
created-at: 2022-05-06T14:40:09.639Z
|
||||
user-id: oberstet@intel-nuci7
|
||||
public-key-ed25519: 15cfa4acef5cc312e0b9ba77634849d0a8c6222a546f90eb5123667935d2f561
|
||||
public-adr-eth: 0xe59C7418403CF1D973485B36660728a5f4A8fF9c
|
||||
private-key-ed25519: 20e8c05d0ede9506462bb049c4843032b18e8e75b314583d0c8d8a4942f9be40
|
||||
private-key-eth: 6b08b6e186bd2a3b9b2f36e6ece3f8031fe788ab3dc4a1cfd3a489ea387c496b
|
||||
@@ -0,0 +1,7 @@
|
||||
Crossbar.io user public key
|
||||
|
||||
creator: oberstet@intel-nuci7
|
||||
created-at: 2022-05-06T14:40:09.639Z
|
||||
user-id: oberstet@intel-nuci7
|
||||
public-key-ed25519: 15cfa4acef5cc312e0b9ba77634849d0a8c6222a546f90eb5123667935d2f561
|
||||
public-adr-eth: 0xe59C7418403CF1D973485B36660728a5f4A8fF9c
|
||||
@@ -0,0 +1,70 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import unittest
|
||||
from binascii import a2b_hex
|
||||
|
||||
from autobahn.xbr import HAS_XBR
|
||||
|
||||
TESTVECTORS = [
|
||||
{
|
||||
'email': 'foobar@example.com',
|
||||
'password': 'secret123',
|
||||
'salt': None,
|
||||
'pkm': '5fa91eecfc1414db0db3cca9bfec64af495bdfa0bc8c135a8c17b6b5e3686cab',
|
||||
'contexts': {
|
||||
'wamp-cryptosign': '5c55e767c927e17e2a03a23ab46b516e41fb9e40377d2617104dd5622a9209cf',
|
||||
}
|
||||
},
|
||||
{
|
||||
'email': 'foobar@example.com',
|
||||
'password': 'secret123',
|
||||
'salt': a2b_hex('3761e806cda3c35d859c933d46e5d57b'),
|
||||
'pkm': 'c49556ca4c39dbfe147187b03b1dfcff7026d748cb27738a849a1cd5bfcf4bed',
|
||||
'contexts': {
|
||||
'wamp-cryptosign': '807af48521b1ecf4a7045814d75339159ecb157c1bffb00461edfebbaffcda71',
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
if HAS_XBR:
|
||||
from autobahn.xbr import stretch_argon2_secret, pkm_from_argon2_secret
|
||||
|
||||
class TestXbrArgon2(unittest.TestCase):
|
||||
|
||||
def test_stretch_argon2_secret(self):
|
||||
for tv in TESTVECTORS:
|
||||
email, password, salt = tv['email'], tv['password'], tv['salt']
|
||||
pkm = stretch_argon2_secret(email, password, salt=salt)
|
||||
self.assertEqual(pkm, a2b_hex(tv['pkm']))
|
||||
|
||||
def test_pkm_from_argon2_secret(self):
|
||||
for tv in TESTVECTORS:
|
||||
email, password, salt = tv['email'], tv['password'], tv['salt']
|
||||
for context, expected_priv_key in tv['contexts'].items():
|
||||
expected_priv_key = a2b_hex(expected_priv_key)
|
||||
priv_key = pkm_from_argon2_secret(email=email, password=password, context=context, salt=salt)
|
||||
self.assertEqual(priv_key, expected_priv_key)
|
||||
@@ -0,0 +1,78 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from autobahn.xbr import HAS_XBR
|
||||
|
||||
if HAS_XBR:
|
||||
from autobahn.xbr import Profile, UserConfig
|
||||
|
||||
class TestXbrUserConfig(unittest.TestCase):
|
||||
|
||||
DOTDIR = '~/.xbrnetwork'
|
||||
PROFILE_NAME = 'default'
|
||||
NETWORK_URL = 'wss://planet.xbr.network/ws'
|
||||
NETWORK_REALM = 'xbrnetwork'
|
||||
PASSWORD = 'secret123'
|
||||
|
||||
def test_create_empty_config(self):
|
||||
c = UserConfig('config.ini')
|
||||
self.assertEqual(c.profiles, {})
|
||||
|
||||
def test_create_empty_profile(self):
|
||||
p = Profile()
|
||||
self.assertTrue(p.path is None)
|
||||
|
||||
def test_load_home(self):
|
||||
config_dir = os.path.expanduser(self.DOTDIR)
|
||||
if not os.path.isdir(config_dir):
|
||||
os.mkdir(config_dir)
|
||||
config_path = os.path.join(config_dir, 'config.ini')
|
||||
if os.path.exists(config_path):
|
||||
c = UserConfig(config_path)
|
||||
c.load()
|
||||
self.assertIn(self.PROFILE_NAME, c.profiles)
|
||||
|
||||
def test_write_default_config(self):
|
||||
config_dir = os.path.expanduser(self.DOTDIR)
|
||||
if not os.path.isdir(config_dir):
|
||||
os.mkdir(config_dir)
|
||||
config_path = os.path.join(config_dir, 'test.ini')
|
||||
|
||||
c = UserConfig(config_path)
|
||||
p = Profile()
|
||||
c.profiles[self.PROFILE_NAME] = p
|
||||
c.save(self.PASSWORD)
|
||||
|
||||
c2 = UserConfig(config_path)
|
||||
|
||||
def get_pw():
|
||||
return self.PASSWORD
|
||||
|
||||
c2.load(cb_get_password=get_pw)
|
||||
self.assertIn(self.PROFILE_NAME, c2.profiles)
|
||||
@@ -0,0 +1,511 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
from binascii import a2b_hex, b2a_hex
|
||||
from unittest import skipIf
|
||||
|
||||
from twisted.internet.defer import inlineCallbacks
|
||||
from twisted.trial.unittest import TestCase
|
||||
|
||||
from autobahn.wamp.cryptosign import HAS_CRYPTOSIGN
|
||||
from autobahn.xbr import HAS_XBR
|
||||
|
||||
if HAS_XBR and HAS_CRYPTOSIGN:
|
||||
from autobahn.wamp.cryptosign import CryptosignKey
|
||||
from autobahn.xbr import make_w3, EthereumKey
|
||||
from autobahn.xbr._secmod import SecurityModuleMemory
|
||||
from autobahn.xbr import create_eip712_delegate_certificate, create_eip712_authority_certificate
|
||||
from autobahn.xbr._eip712_delegate_certificate import EIP712DelegateCertificate
|
||||
from autobahn.xbr._eip712_authority_certificate import EIP712AuthorityCertificate
|
||||
from autobahn.xbr._eip712_certificate_chain import parse_certificate_chain
|
||||
|
||||
# https://web3py.readthedocs.io/en/stable/providers.html#infura-mainnet
|
||||
HAS_INFURA = 'WEB3_INFURA_PROJECT_ID' in os.environ and len(os.environ['WEB3_INFURA_PROJECT_ID']) > 0
|
||||
|
||||
# TypeError: As of 3.10, the *loop* parameter was removed from Lock() since it is no longer necessary
|
||||
IS_CPY_310 = sys.version_info.minor == 10
|
||||
|
||||
|
||||
@skipIf(not os.environ.get('USE_TWISTED', False), 'only for Twisted')
|
||||
@skipIf(not HAS_INFURA, 'env var WEB3_INFURA_PROJECT_ID not defined')
|
||||
@skipIf(not (HAS_XBR and HAS_CRYPTOSIGN), 'package autobahn[encryption,xbr] not installed')
|
||||
class TestEip712Certificate(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self._gw_config = {
|
||||
'type': 'infura',
|
||||
'key': os.environ.get('WEB3_INFURA_PROJECT_ID', ''),
|
||||
'network': 'mainnet',
|
||||
}
|
||||
self._w3 = make_w3(self._gw_config)
|
||||
|
||||
self._seedphrase = "avocado style uncover thrive same grace crunch want essay reduce current edge"
|
||||
self._sm: SecurityModuleMemory = SecurityModuleMemory.from_seedphrase(self._seedphrase, num_eth_keys=5,
|
||||
num_cs_keys=5)
|
||||
|
||||
@inlineCallbacks
|
||||
def test_eip712_delegate_certificate(self):
|
||||
yield self._sm.open()
|
||||
|
||||
delegate_eth_key: EthereumKey = self._sm[1]
|
||||
delegate_cs_key: CryptosignKey = self._sm[6]
|
||||
|
||||
chainId = 1
|
||||
verifyingContract = a2b_hex('0xf766Dc789CF04CD18aE75af2c5fAf2DA6650Ff57'[2:])
|
||||
validFrom = 15124128
|
||||
delegate = delegate_eth_key.address(binary=True)
|
||||
csPubKey = delegate_cs_key.public_key(binary=True)
|
||||
bootedAt = 1657579546469365046 # txaio.time_ns()
|
||||
meta = 'Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu'
|
||||
|
||||
cert_data = create_eip712_delegate_certificate(chainId=chainId, verifyingContract=verifyingContract,
|
||||
validFrom=validFrom, delegate=delegate, csPubKey=csPubKey,
|
||||
bootedAt=bootedAt, meta=meta)
|
||||
|
||||
# print('\n\n{}\n\n'.format(pformat(cert_data)))
|
||||
|
||||
cert_sig = yield delegate_eth_key.sign_typed_data(cert_data, binary=False)
|
||||
|
||||
self.assertEqual(cert_sig,
|
||||
'2bd697b2bdb9bc2c2494e53e9440ddb3e8a596eedaad717f8ecdb732d091a7de48d72d9a26d7e092ec55c074979ab039f8e003acf80224819ff396c9529eb1d11b')
|
||||
|
||||
yield self._sm.close()
|
||||
|
||||
@inlineCallbacks
|
||||
def test_eip712_authority_certificate(self):
|
||||
yield self._sm.open()
|
||||
|
||||
trustroot_eth_key: EthereumKey = self._sm[0]
|
||||
delegate_eth_key: EthereumKey = self._sm[1]
|
||||
|
||||
chainId = 1
|
||||
verifyingContract = a2b_hex('0xf766Dc789CF04CD18aE75af2c5fAf2DA6650Ff57'[2:])
|
||||
validFrom = 15124128
|
||||
issuer = trustroot_eth_key.address(binary=True)
|
||||
subject = delegate_eth_key.address(binary=True)
|
||||
realm = a2b_hex('0xA6e693CC4A2b4F1400391a728D26369D9b82ef96'[2:])
|
||||
capabilities = 3
|
||||
meta = 'Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu'
|
||||
|
||||
cert_data = create_eip712_authority_certificate(chainId=chainId, verifyingContract=verifyingContract,
|
||||
validFrom=validFrom, issuer=issuer, subject=subject,
|
||||
realm=realm, capabilities=capabilities, meta=meta)
|
||||
|
||||
# print('\n\n{}\n\n'.format(pformat(cert_data)))
|
||||
|
||||
cert_sig = yield trustroot_eth_key.sign_typed_data(cert_data, binary=False)
|
||||
|
||||
self.assertEqual(cert_sig,
|
||||
'83590d4304cc5f6024d6a85ed2c511a60e804d609e4f498c8af777d5102c6d22657673e7b68876795e3c72f857b68e13cf616ee4c2ea559bceb344021bf977b61c')
|
||||
|
||||
yield self._sm.close()
|
||||
|
||||
|
||||
@skipIf(not os.environ.get('USE_TWISTED', False), 'only for Twisted')
|
||||
@skipIf(not HAS_INFURA, 'env var WEB3_INFURA_PROJECT_ID not defined')
|
||||
@skipIf(not (HAS_XBR and HAS_CRYPTOSIGN), 'package autobahn[encryption,xbr] not installed')
|
||||
class TestEip712CertificateChain(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self._gw_config = {
|
||||
'type': 'infura',
|
||||
'key': os.environ.get('WEB3_INFURA_PROJECT_ID', ''),
|
||||
'network': 'mainnet',
|
||||
}
|
||||
self._w3 = make_w3(self._gw_config)
|
||||
|
||||
self._seedphrase = "avocado style uncover thrive same grace crunch want essay reduce current edge"
|
||||
self._sm: SecurityModuleMemory = SecurityModuleMemory.from_seedphrase(self._seedphrase, num_eth_keys=5,
|
||||
num_cs_keys=5)
|
||||
|
||||
# HELLO.Details.authextra.certificates
|
||||
#
|
||||
self._certs_expected1 = [(None,
|
||||
{'domain': {'name': 'WMP', 'version': '1'},
|
||||
'message': {'bootedAt': 1657781999086394759,
|
||||
'chainId': 1,
|
||||
'csPubKey': '12ae0184b180e9a9c5e45be4a1afbce3c6491320063701cd9c4011a777d04089',
|
||||
'delegate': '0xf5173a6111B2A6B3C20fceD53B2A8405EC142bF6',
|
||||
'meta': 'Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu',
|
||||
'validFrom': 15139218,
|
||||
'verifyingContract': '0xf766Dc789CF04CD18aE75af2c5fAf2DA6650Ff57'},
|
||||
'primaryType': 'EIP712DelegateCertificate',
|
||||
'types': {'EIP712DelegateCertificate': [{'name': 'chainId',
|
||||
'type': 'uint256'},
|
||||
{'name': 'verifyingContract',
|
||||
'type': 'address'},
|
||||
{'name': 'validFrom',
|
||||
'type': 'uint256'},
|
||||
{'name': 'delegate',
|
||||
'type': 'address'},
|
||||
{'name': 'csPubKey',
|
||||
'type': 'bytes32'},
|
||||
{'name': 'bootedAt',
|
||||
'type': 'uint64'},
|
||||
{'name': 'meta', 'type': 'string'}],
|
||||
'EIP712Domain': [{'name': 'name', 'type': 'string'},
|
||||
{'name': 'version', 'type': 'string'}]}},
|
||||
'70726dda677cac8f21366f8023d17203b2f4f9099e954f9bebb2134086e2ac291d80ce038a1342a7748d4b0750f06b8de491561d581c90c99f1c09c91cfa7e191c'),
|
||||
(None,
|
||||
{'domain': {'name': 'WMP', 'version': '1'},
|
||||
'message': {'capabilities': 12,
|
||||
'chainId': 1,
|
||||
'issuer': '0xf766Dc789CF04CD18aE75af2c5fAf2DA6650Ff57',
|
||||
'meta': 'QmNbMM6TMLAgqBKzY69mJKk5VKvpcTtAtwAaLC2FV4zC3G',
|
||||
'realm': '0xA6e693CC4A2b4F1400391a728D26369D9b82ef96',
|
||||
'subject': '0xf5173a6111B2A6B3C20fceD53B2A8405EC142bF6',
|
||||
'validFrom': 15139218,
|
||||
'verifyingContract': '0xf766Dc789CF04CD18aE75af2c5fAf2DA6650Ff57'},
|
||||
'primaryType': 'EIP712AuthorityCertificate',
|
||||
'types': {'EIP712AuthorityCertificate': [{'name': 'chainId',
|
||||
'type': 'uint256'},
|
||||
{'name': 'verifyingContract',
|
||||
'type': 'address'},
|
||||
{'name': 'validFrom',
|
||||
'type': 'uint256'},
|
||||
{'name': 'issuer',
|
||||
'type': 'address'},
|
||||
{'name': 'subject',
|
||||
'type': 'address'},
|
||||
{'name': 'realm',
|
||||
'type': 'address'},
|
||||
{'name': 'capabilities',
|
||||
'type': 'uint64'},
|
||||
{'name': 'meta', 'type': 'string'}],
|
||||
'EIP712Domain': [{'name': 'name', 'type': 'string'},
|
||||
{'name': 'version', 'type': 'string'}]}},
|
||||
'f031b2625ae7e32e7eec3a8fa09f4db3a43217f282b7695e5b09dd2e13c25dc679c1f3ce27b94a3074786f7f12183a2a275a00aea5a66b83c431281f1069bd841c'),
|
||||
(None,
|
||||
{'domain': {'name': 'WMP', 'version': '1'},
|
||||
'message': {'capabilities': 63,
|
||||
'chainId': 1,
|
||||
'issuer': '0xf766Dc789CF04CD18aE75af2c5fAf2DA6650Ff57',
|
||||
'meta': 'QmNbMM6TMLAgqBKzY69mJKk5VKvpcTtAtwAaLC2FV4zC3G',
|
||||
'realm': '0xA6e693CC4A2b4F1400391a728D26369D9b82ef96',
|
||||
'subject': '0xf766Dc789CF04CD18aE75af2c5fAf2DA6650Ff57',
|
||||
'validFrom': 15139218,
|
||||
'verifyingContract': '0xf766Dc789CF04CD18aE75af2c5fAf2DA6650Ff57'},
|
||||
'primaryType': 'EIP712AuthorityCertificate',
|
||||
'types': {'EIP712AuthorityCertificate': [{'name': 'chainId',
|
||||
'type': 'uint256'},
|
||||
{'name': 'verifyingContract',
|
||||
'type': 'address'},
|
||||
{'name': 'validFrom',
|
||||
'type': 'uint256'},
|
||||
{'name': 'issuer',
|
||||
'type': 'address'},
|
||||
{'name': 'subject',
|
||||
'type': 'address'},
|
||||
{'name': 'realm',
|
||||
'type': 'address'},
|
||||
{'name': 'capabilities',
|
||||
'type': 'uint64'},
|
||||
{'name': 'meta', 'type': 'string'}],
|
||||
'EIP712Domain': [{'name': 'name', 'type': 'string'},
|
||||
{'name': 'version', 'type': 'string'}]}},
|
||||
'c3bcd7a3c3c45ae45a24cd7745db3b39c4113e6b71a4220f943f0969282246b4083ef61277bd7ba9e92c9a07b79869ce63bc6206986480f9c5daddb27b91bebe1b')]
|
||||
|
||||
@skipIf(True, 'FIXME: builtins.TypeError: to_checksum_address() takes 1 positional argument but 2 were given')
|
||||
@inlineCallbacks
|
||||
def test_eip712_create_certificate_chain_manual(self):
|
||||
yield self._sm.open()
|
||||
|
||||
# keys needed to create all certificates in certificate chain
|
||||
#
|
||||
trustroot_eth_key: EthereumKey = self._sm[0]
|
||||
delegate_eth_key: EthereumKey = self._sm[1]
|
||||
delegate_cs_key: CryptosignKey = self._sm[6]
|
||||
|
||||
# data needed for delegate certificate: cert1
|
||||
#
|
||||
chainId = 1 # self._w3.eth.chain_id
|
||||
verifyingContract = a2b_hex('0xf766Dc789CF04CD18aE75af2c5fAf2DA6650Ff57'[2:])
|
||||
validFrom = 15139218 # self._w3.eth.block_number
|
||||
delegate = delegate_eth_key.address(binary=True)
|
||||
csPubKey = delegate_cs_key.public_key(binary=True)
|
||||
bootedAt = 1657781999086394759 # txaio.time_ns()
|
||||
delegateMeta = 'Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu'
|
||||
|
||||
# data needed for intermediate authority certificate: cert2
|
||||
#
|
||||
issuer_cert2 = trustroot_eth_key.address(binary=True)
|
||||
subject_cert2 = delegate
|
||||
realm_cert2 = a2b_hex('0xA6e693CC4A2b4F1400391a728D26369D9b82ef96'[2:])
|
||||
capabilities_cert2 = EIP712AuthorityCertificate.CAPABILITY_PUBLIC_RELAY | EIP712AuthorityCertificate.CAPABILITY_PRIVATE_RELAY
|
||||
meta_cert2 = 'QmNbMM6TMLAgqBKzY69mJKk5VKvpcTtAtwAaLC2FV4zC3G'
|
||||
|
||||
# data needed for root authority certificate: cert3
|
||||
#
|
||||
issuer_cert3 = trustroot_eth_key.address(binary=True)
|
||||
subject_cert3 = issuer_cert3
|
||||
realm_cert3 = a2b_hex('0xA6e693CC4A2b4F1400391a728D26369D9b82ef96'[2:])
|
||||
capabilities_cert3 = EIP712AuthorityCertificate.CAPABILITY_ROOT_CA | EIP712AuthorityCertificate.CAPABILITY_INTERMEDIATE_CA | EIP712AuthorityCertificate.CAPABILITY_PUBLIC_RELAY | EIP712AuthorityCertificate.CAPABILITY_PRIVATE_RELAY | EIP712AuthorityCertificate.CAPABILITY_PROVIDER | EIP712AuthorityCertificate.CAPABILITY_CONSUMER
|
||||
meta_cert3 = 'QmNbMM6TMLAgqBKzY69mJKk5VKvpcTtAtwAaLC2FV4zC3G'
|
||||
|
||||
# FIXME: builtins.TypeError: to_checksum_address() takes 1 positional argument but 2 were given
|
||||
|
||||
# create delegate certificate
|
||||
#
|
||||
cert1_data = create_eip712_delegate_certificate(chainId=chainId, verifyingContract=verifyingContract,
|
||||
validFrom=validFrom, delegate=delegate, csPubKey=csPubKey,
|
||||
bootedAt=bootedAt, meta=delegateMeta)
|
||||
|
||||
cert1_sig = yield delegate_eth_key.sign_typed_data(cert1_data, binary=False)
|
||||
|
||||
cert1_data['message']['csPubKey'] = b2a_hex(cert1_data['message']['csPubKey']).decode()
|
||||
cert1_data['message']['delegate'] = self._w3.toChecksumAddress(cert1_data['message']['delegate'])
|
||||
cert1_data['message']['verifyingContract'] = self._w3.toChecksumAddress(
|
||||
cert1_data['message']['verifyingContract'])
|
||||
|
||||
# create intermediate authority certificate
|
||||
#
|
||||
cert2_data = create_eip712_authority_certificate(chainId=chainId, verifyingContract=verifyingContract,
|
||||
validFrom=validFrom, issuer=issuer_cert2,
|
||||
subject=subject_cert2,
|
||||
realm=realm_cert2, capabilities=capabilities_cert2,
|
||||
meta=meta_cert2)
|
||||
|
||||
cert2_sig = yield trustroot_eth_key.sign_typed_data(cert2_data, binary=False)
|
||||
|
||||
cert2_data['message']['verifyingContract'] = self._w3.toChecksumAddress(
|
||||
cert2_data['message']['verifyingContract'])
|
||||
cert2_data['message']['issuer'] = self._w3.toChecksumAddress(cert2_data['message']['issuer'])
|
||||
cert2_data['message']['subject'] = self._w3.toChecksumAddress(cert2_data['message']['subject'])
|
||||
cert2_data['message']['realm'] = self._w3.toChecksumAddress(cert2_data['message']['realm'])
|
||||
|
||||
# create root authority certificate
|
||||
#
|
||||
cert3_data = create_eip712_authority_certificate(chainId=chainId, verifyingContract=verifyingContract,
|
||||
validFrom=validFrom, issuer=issuer_cert3,
|
||||
subject=subject_cert3,
|
||||
realm=realm_cert3, capabilities=capabilities_cert3,
|
||||
meta=meta_cert3)
|
||||
|
||||
cert3_sig = yield trustroot_eth_key.sign_typed_data(cert3_data, binary=False)
|
||||
|
||||
cert3_data['message']['verifyingContract'] = self._w3.toChecksumAddress(
|
||||
cert3_data['message']['verifyingContract'])
|
||||
cert3_data['message']['issuer'] = self._w3.toChecksumAddress(cert3_data['message']['issuer'])
|
||||
cert3_data['message']['subject'] = self._w3.toChecksumAddress(cert3_data['message']['subject'])
|
||||
cert3_data['message']['realm'] = self._w3.toChecksumAddress(cert3_data['message']['realm'])
|
||||
|
||||
# create certificates chain
|
||||
#
|
||||
certificates = [(None, cert1_data, cert1_sig), (None, cert2_data, cert2_sig), (None, cert3_data, cert3_sig)]
|
||||
|
||||
if False:
|
||||
from pprint import pprint
|
||||
print()
|
||||
pprint(certificates)
|
||||
print()
|
||||
|
||||
# check certificates and certificate signatures of whole chain
|
||||
#
|
||||
self.assertEqual(certificates, self._certs_expected1)
|
||||
|
||||
yield self._sm.close()
|
||||
|
||||
@inlineCallbacks
|
||||
def test_eip712_create_certificate_chain_highlevel(self):
|
||||
yield self._sm.open()
|
||||
|
||||
# keys needed to create all certificates in certificate chain
|
||||
ca_key: EthereumKey = self._sm[0]
|
||||
|
||||
# data needed for root authority certificate: cert3
|
||||
ca_cert_chainId = 1
|
||||
ca_cert_verifyingContract = a2b_hex('0xf766Dc789CF04CD18aE75af2c5fAf2DA6650Ff57'[2:])
|
||||
ca_cert_validFrom = 666666
|
||||
ca_cert_issuer = ca_key.address(binary=True)
|
||||
ca_cert_subject = ca_cert_issuer
|
||||
ca_cert_realm = a2b_hex('0xA6e693CC4A2b4F1400391a728D26369D9b82ef96'[2:])
|
||||
ca_cert_capabilities = EIP712AuthorityCertificate.CAPABILITY_ROOT_CA | EIP712AuthorityCertificate.CAPABILITY_INTERMEDIATE_CA | EIP712AuthorityCertificate.CAPABILITY_PUBLIC_RELAY | EIP712AuthorityCertificate.CAPABILITY_PRIVATE_RELAY | EIP712AuthorityCertificate.CAPABILITY_PROVIDER | EIP712AuthorityCertificate.CAPABILITY_CONSUMER
|
||||
ca_cert_meta = ''
|
||||
|
||||
# create root authority certificate signature: directly from provided data attributes
|
||||
ca_cert_data = create_eip712_authority_certificate(chainId=ca_cert_chainId,
|
||||
verifyingContract=ca_cert_verifyingContract,
|
||||
validFrom=ca_cert_validFrom, issuer=ca_cert_issuer,
|
||||
subject=ca_cert_subject, realm=ca_cert_realm,
|
||||
capabilities=ca_cert_capabilities, meta=ca_cert_meta)
|
||||
ca_cert_sig = yield ca_key.sign_typed_data(ca_cert_data, binary=False)
|
||||
|
||||
# create root authority certificate signature: from certificate object
|
||||
ca_cert = EIP712AuthorityCertificate(chainId=ca_cert_chainId,
|
||||
verifyingContract=ca_cert_verifyingContract,
|
||||
validFrom=ca_cert_validFrom,
|
||||
issuer=ca_cert_issuer,
|
||||
subject=ca_cert_subject,
|
||||
realm=ca_cert_realm,
|
||||
capabilities=ca_cert_capabilities,
|
||||
meta=ca_cert_meta)
|
||||
ca_cert_sig2 = yield ca_cert.sign(ca_key)
|
||||
|
||||
# re-create root authority certificate from round-tripping (marshal-parse)
|
||||
ca_cert2 = EIP712AuthorityCertificate.parse(ca_cert.marshal())
|
||||
ca_cert_sig3 = yield ca_cert2.sign(ca_key)
|
||||
|
||||
# all different ways to compute signature must result in same signature value
|
||||
self.assertEqual(ca_cert_sig, ca_cert_sig2)
|
||||
self.assertEqual(ca_cert_sig, ca_cert_sig3)
|
||||
|
||||
# and match this signature value
|
||||
self.assertEqual(ca_cert_sig, 'd9e679753e1120a8ba8edea4895d2e056ba98eaa1acbe11bf6210f3a48a56de830aa6a566cc4920'
|
||||
'c74a284ffcd9f7d1af5fe229268a44030522db19d5a75f4131c')
|
||||
|
||||
# test save/load instance to/from file
|
||||
with tempfile.NamedTemporaryFile() as fd:
|
||||
# save certificate to file
|
||||
ca_cert.save(fd.name)
|
||||
|
||||
# load certificate from file
|
||||
ca_cert3 = EIP712AuthorityCertificate.load(fd.name)
|
||||
|
||||
# ensure it produces the same signature
|
||||
ca_cert_sig4 = yield ca_cert3.sign(ca_key)
|
||||
self.assertEqual(ca_cert_sig, ca_cert_sig4)
|
||||
|
||||
yield self._sm.close()
|
||||
|
||||
@inlineCallbacks
|
||||
def test_eip712_verify_certificate_chain_manual(self):
|
||||
yield self._sm.open()
|
||||
|
||||
# keys originally used to sign the certificates in the certificate chain
|
||||
trustroot_eth_key: EthereumKey = self._sm[0]
|
||||
delegate_eth_key: EthereumKey = self._sm[1]
|
||||
delegate_cs_key: CryptosignKey = self._sm[6]
|
||||
|
||||
# parse the whole certificate chain
|
||||
cert_chain = []
|
||||
cert_sigs = []
|
||||
for cert_hash, cert_data, cert_sig in self._certs_expected1:
|
||||
self.assertIn('domain', cert_data)
|
||||
self.assertIn('message', cert_data)
|
||||
self.assertIn('primaryType', cert_data)
|
||||
self.assertIn('types', cert_data)
|
||||
self.assertIn(cert_data['primaryType'], cert_data['types'])
|
||||
self.assertIn(cert_data['primaryType'], ['EIP712DelegateCertificate', 'EIP712AuthorityCertificate'])
|
||||
if cert_data['primaryType'] == 'EIP712DelegateCertificate':
|
||||
cert = EIP712DelegateCertificate.parse(cert_data)
|
||||
elif cert_data['primaryType'] == 'EIP712AuthorityCertificate':
|
||||
cert = EIP712AuthorityCertificate.parse(cert_data)
|
||||
else:
|
||||
assert False, 'should not arrive here'
|
||||
cert_chain.append(cert)
|
||||
cert_sigs.append(cert_sig)
|
||||
|
||||
# FIXME: allow length 2 and length > 3
|
||||
self.assertEqual(len(cert_chain), 3)
|
||||
self.assertEqual(cert_chain[0].delegate, delegate_eth_key.address(binary=True))
|
||||
self.assertEqual(cert_chain[0].csPubKey, delegate_cs_key.public_key(binary=True))
|
||||
self.assertEqual(cert_chain[1].issuer, trustroot_eth_key.address(binary=True))
|
||||
self.assertEqual(cert_chain[2].issuer, trustroot_eth_key.address(binary=True))
|
||||
|
||||
# Certificate Chain Rules (CCR):
|
||||
#
|
||||
# 1. **CCR-1**: The `chainId` and `verifyingContract` must match for all certificates to what we expect, and `validFrom` before current block number on the respective chain.
|
||||
# 2. **CCR-2**: The `realm` must match for all certificates to the respective realm.
|
||||
# 3. **CCR-3**: The type of the first certificate in the chain must be a `EIP712DelegateCertificate`, and all subsequent certificates must be of type `EIP712AuthorityCertificate`.
|
||||
# 4. **CCR-4**: The last certificate must be self-signed (`issuer` equals `subject`), it is a root CA certificate.
|
||||
# 5. **CCR-5**: The intermediate certificate's `issuer` must be equal to the `subject` of the previous certificate.
|
||||
# 6. **CCR-6**: The root certificate must be `validFrom` before the intermediate certificate
|
||||
# 7. **CCR-7**: The `capabilities` of intermediate certificate must be a subset of the root cert
|
||||
# 8. **CCR-8**: The intermediate certificate's `subject` must be the delegate certificate `delegate`
|
||||
# 9. **CCR-9**: The intermediate certificate must be `validFrom` before the delegate certificate
|
||||
# 10. **CCR-10**: The root certificate's signature must be valid and signed by the root certificate's `issuer`.
|
||||
# 11. **CCR-11**: The intermediate certificate's signature must be valid and signed by the intermediate certificate's `issuer`.
|
||||
# 12. **CCR-12**: The delegate certificate's signature must be valid and signed by the `delegate`.
|
||||
|
||||
# CCR-3
|
||||
self.assertIsInstance(cert_chain[0], EIP712DelegateCertificate)
|
||||
for i in [1, len(cert_chain) - 1]:
|
||||
self.assertIsInstance(cert_chain[i], EIP712AuthorityCertificate)
|
||||
|
||||
# CCR-1
|
||||
chainId = cert_chain[2].chainId
|
||||
verifyingContract = cert_chain[2].verifyingContract
|
||||
for cert in cert_chain:
|
||||
self.assertEqual(cert.chainId, chainId)
|
||||
self.assertEqual(cert.verifyingContract, verifyingContract)
|
||||
|
||||
# CCR-2
|
||||
realm = cert_chain[2].realm
|
||||
for cert in cert_chain[1:]:
|
||||
self.assertEqual(cert.realm, realm)
|
||||
|
||||
# CCR-4
|
||||
self.assertEqual(cert_chain[2].subject, cert_chain[2].issuer)
|
||||
|
||||
# CCR-5
|
||||
self.assertEqual(cert_chain[1].issuer, cert_chain[2].subject)
|
||||
|
||||
# CCR-6
|
||||
self.assertLessEqual(cert_chain[2].validFrom, cert_chain[1].validFrom)
|
||||
|
||||
# CCR-7
|
||||
self.assertTrue(cert_chain[2].capabilities == cert_chain[2].capabilities | cert_chain[1].capabilities)
|
||||
|
||||
# CCR-8
|
||||
self.assertEqual(cert_chain[1].subject, cert_chain[0].delegate)
|
||||
|
||||
# CCR-9
|
||||
self.assertLessEqual(cert_chain[1].validFrom, cert_chain[0].validFrom)
|
||||
|
||||
# CCR-10
|
||||
_issuer = cert_chain[2].recover(a2b_hex(cert_sigs[2]))
|
||||
self.assertEqual(_issuer, trustroot_eth_key.address(binary=True))
|
||||
|
||||
# CCR-11
|
||||
_issuer = cert_chain[1].recover(a2b_hex(cert_sigs[1]))
|
||||
self.assertEqual(_issuer, trustroot_eth_key.address(binary=True))
|
||||
|
||||
# CCR-12
|
||||
_issuer = cert_chain[0].recover(a2b_hex(cert_sigs[0]))
|
||||
self.assertEqual(_issuer, delegate_eth_key.address(binary=True))
|
||||
|
||||
yield self._sm.close()
|
||||
|
||||
@inlineCallbacks
|
||||
def test_eip712_verify_certificate_chain_highlevel(self):
|
||||
yield self._sm.open()
|
||||
|
||||
# keys originally used to sign the certificates in the certificate chain
|
||||
trustroot_eth_key: EthereumKey = self._sm[0]
|
||||
delegate_eth_key: EthereumKey = self._sm[1]
|
||||
delegate_cs_key: CryptosignKey = self._sm[6]
|
||||
|
||||
certificates = parse_certificate_chain(self._certs_expected1)
|
||||
|
||||
self.assertEqual(certificates[2].issuer, trustroot_eth_key.address(binary=True))
|
||||
|
||||
self.assertEqual(certificates[0].delegate, delegate_eth_key.address(binary=True))
|
||||
self.assertEqual(certificates[0].csPubKey, delegate_cs_key.public_key(binary=True))
|
||||
|
||||
yield self._sm.close()
|
||||
@@ -0,0 +1,183 @@
|
||||
import os
|
||||
import sys
|
||||
from unittest import skipIf
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from twisted.trial.unittest import TestCase
|
||||
from twisted.internet.defer import inlineCallbacks
|
||||
|
||||
from autobahn.xbr import HAS_XBR
|
||||
from autobahn.wamp.cryptosign import HAS_CRYPTOSIGN
|
||||
|
||||
if HAS_XBR and HAS_CRYPTOSIGN:
|
||||
from autobahn.xbr._frealm import Seeder, FederatedRealm
|
||||
from autobahn.xbr._secmod import SecurityModuleMemory, EthereumKey
|
||||
from autobahn.wamp.cryptosign import CryptosignKey
|
||||
|
||||
# https://web3py.readthedocs.io/en/stable/providers.html#infura-mainnet
|
||||
HAS_INFURA = 'WEB3_INFURA_PROJECT_ID' in os.environ and len(os.environ['WEB3_INFURA_PROJECT_ID']) > 0
|
||||
|
||||
# TypeError: As of 3.10, the *loop* parameter was removed from Lock() since it is no longer necessary
|
||||
IS_CPY_310 = sys.version_info.minor == 10
|
||||
|
||||
|
||||
@skipIf(not os.environ.get('USE_TWISTED', False), 'only for Twisted')
|
||||
@skipIf(not HAS_INFURA, 'env var WEB3_INFURA_PROJECT_ID not defined')
|
||||
@skipIf(not (HAS_XBR and HAS_CRYPTOSIGN), 'package autobahn[encryption,xbr] not installed')
|
||||
class TestFederatedRealm(TestCase):
|
||||
|
||||
gw_config = {
|
||||
'type': 'infura',
|
||||
'key': os.environ.get('WEB3_INFURA_PROJECT_ID', ''),
|
||||
'network': 'mainnet',
|
||||
}
|
||||
|
||||
# "builtins.TypeError: As of 3.10, the *loop* parameter was removed from Lock() since
|
||||
# it is no longer necessary"
|
||||
#
|
||||
# solved via websockets>=10.3, but web3==5.29.0 requires websockets<10
|
||||
#
|
||||
@skipIf(IS_CPY_310, 'Web3 v5.29.0 (web3.auto.infura) raises TypeError on Python 3.10')
|
||||
def test_frealm_ctor_auto(self):
|
||||
name = 'wamp-proto.eth'
|
||||
|
||||
fr = FederatedRealm(name)
|
||||
self.assertEqual(fr.status, 'STOPPED')
|
||||
self.assertEqual(fr.name_or_address, name)
|
||||
self.assertEqual(fr.gateway_config, None)
|
||||
self.assertEqual(fr.name_category, 'ens')
|
||||
|
||||
def test_frealm_ctor_gw(self):
|
||||
name = 'wamp-proto.eth'
|
||||
|
||||
fr = FederatedRealm(name, self.gw_config)
|
||||
self.assertEqual(fr.status, 'STOPPED')
|
||||
self.assertEqual(fr.name_or_address, name)
|
||||
self.assertEqual(fr.gateway_config, self.gw_config)
|
||||
self.assertEqual(fr.name_category, 'ens')
|
||||
|
||||
@inlineCallbacks
|
||||
def test_frealm_initialize(self):
|
||||
name = 'wamp-proto.eth'
|
||||
fr1 = FederatedRealm(name, self.gw_config)
|
||||
|
||||
self.assertEqual(fr1.status, 'STOPPED')
|
||||
yield fr1.initialize()
|
||||
self.assertEqual(fr1.status, 'RUNNING')
|
||||
|
||||
self.assertEqual(fr1.address, '0x66267d0b1114cFae80C37942177a846d666b114a')
|
||||
|
||||
def test_frealm_seeders(self):
|
||||
fr1 = MagicMock()
|
||||
fr1.name_or_address = 'wamp-proto.eth'
|
||||
fr1.address = '0x66267d0b1114cFae80C37942177a846d666b114a'
|
||||
fr1.status = 'RUNNING'
|
||||
fr1.seeders = [
|
||||
Seeder(frealm=fr1,
|
||||
endpoint='wss://frealm1.example.com/ws',
|
||||
label='Example Inc.',
|
||||
operator='0xf5fb56886f033855C1a36F651E927551749361bC',
|
||||
country='US'),
|
||||
Seeder(frealm=fr1,
|
||||
endpoint='wss://fr1.foobar.org/ws',
|
||||
label='Foobar Foundation',
|
||||
operator='0xe59C7418403CF1D973485B36660728a5f4A8fF9c',
|
||||
country='DE'),
|
||||
Seeder(frealm=fr1,
|
||||
endpoint='wss://public-frealm1.pierre.fr:443',
|
||||
label='Pierre PP',
|
||||
operator='0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B',
|
||||
country='FR'),
|
||||
]
|
||||
self.assertEqual(len(fr1.seeders), 3)
|
||||
|
||||
transports = [s.endpoint for s in fr1.seeders]
|
||||
self.assertEqual(transports, ['wss://frealm1.example.com/ws', 'wss://fr1.foobar.org/ws',
|
||||
'wss://public-frealm1.pierre.fr:443'])
|
||||
|
||||
@inlineCallbacks
|
||||
def test_frealm_secmod(self):
|
||||
name = 'wamp-proto.eth'
|
||||
seedphrase = "myth like bonus scare over problem client lizard pioneer submit female collect"
|
||||
|
||||
sm = SecurityModuleMemory.from_seedphrase(seedphrase)
|
||||
yield sm.open()
|
||||
self.assertEqual(len(sm), 2)
|
||||
self.assertTrue(isinstance(sm[0], EthereumKey), 'unexpected type {} at index 0'.format(type(sm[0])))
|
||||
self.assertTrue(isinstance(sm[1], CryptosignKey), 'unexpected type {} at index 1'.format(type(sm[1])))
|
||||
|
||||
fr = FederatedRealm(name, self.gw_config)
|
||||
|
||||
# FIXME
|
||||
fr._seeders = [
|
||||
Seeder(frealm=fr,
|
||||
endpoint='wss://frealm1.example.com/ws',
|
||||
label='Example Inc.',
|
||||
operator='0xf5fb56886f033855C1a36F651E927551749361bC',
|
||||
country='US'),
|
||||
Seeder(frealm=fr,
|
||||
endpoint='wss://fr1.foobar.org/ws',
|
||||
label='Foobar Foundation',
|
||||
operator='0xe59C7418403CF1D973485B36660728a5f4A8fF9c',
|
||||
country='DE'),
|
||||
Seeder(frealm=fr,
|
||||
endpoint='wss://public-frealm1.pierre.fr:443',
|
||||
label='Pierre PP',
|
||||
operator='0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B',
|
||||
country='FR'),
|
||||
]
|
||||
|
||||
yield fr.initialize()
|
||||
self.assertEqual(fr.status, 'RUNNING')
|
||||
self.assertEqual(fr.address, '0x66267d0b1114cFae80C37942177a846d666b114a')
|
||||
self.assertEqual(len(fr.seeders), 3)
|
||||
|
||||
delegate_key = sm[0]
|
||||
client_key = sm[1]
|
||||
authextra = yield fr.seeders[0].create_authextra(client_key=client_key,
|
||||
delegate_key=delegate_key,
|
||||
bandwidth_requested=512,
|
||||
channel_id=None,
|
||||
channel_binding=None)
|
||||
|
||||
self.assertEqual(authextra.get('pubkey', None), client_key.public_key(binary=False))
|
||||
|
||||
# print(authextra)
|
||||
|
||||
self.assertTrue('signature' in authextra)
|
||||
self.assertTrue(type(authextra['signature']) == str)
|
||||
self.assertEqual(len(authextra['signature']), 65 * 2)
|
||||
|
||||
# @skipIf(not os.environ.get('WAMP_ROUTER_URLS', None), 'WAMP_ROUTER_URLS not defined')
|
||||
# @skipIf(not os.environ.get('USE_TWISTED', False), 'only for Twisted')
|
||||
# @skipIf(not HAS_XBR, 'package autobahn[xbr] not installed')
|
||||
# class TestFederatedRealmNetworked(TestCase):
|
||||
#
|
||||
# def test_seeders_multi_reconnect(self):
|
||||
# from autobahn.twisted.component import Component, run
|
||||
#
|
||||
# # WAMP_ROUTER_URLS=ws://localhost:8080/ws,ws://localhost:8081/ws,ws://localhost:8082/ws
|
||||
# # crossbar start --cbdir=./autobahn/xbr/test/.crossbar --config=config1.json
|
||||
# transports = os.environ.get('WAMP_ROUTER_URLS', '').split(',')
|
||||
# realm = 'realm1'
|
||||
# authentication = {
|
||||
# 'cryptosign': {
|
||||
# 'privkey': '20e8c05d0ede9506462bb049c4843032b18e8e75b314583d0c8d8a4942f9be40',
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# component = Component(transports=transports, realm=realm, authentication=authentication)
|
||||
# # component.start()
|
||||
#
|
||||
# # @inlineCallbacks
|
||||
# # def main(reactor, session):
|
||||
# # print("Client session={}".format(session))
|
||||
# # res = yield session.call('user.add2', 23, 666)
|
||||
# # print(res)
|
||||
# # session.leave()
|
||||
# #
|
||||
# # from autobahn.wamp.component import _run
|
||||
# # from twisted.internet import reactor
|
||||
# # d = _run(reactor, [component])
|
||||
# # #d = run([component], log_level='info', stop_at_close=True)
|
||||
# # res = yield d
|
||||
@@ -0,0 +1,83 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
from autobahn.xbr import HAS_XBR
|
||||
|
||||
if HAS_XBR:
|
||||
import unittest
|
||||
import binascii
|
||||
from autobahn.xbr import generate_seedphrase, check_seedphrase, account_from_seedphrase
|
||||
|
||||
_SEEDPHRASE = "myth like bonus scare over problem client lizard pioneer submit female collect"
|
||||
_INVALID_SEEDPHRASE = "9 nn \0 kk$"
|
||||
|
||||
_EXPECTED = [
|
||||
('0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1', '0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d'),
|
||||
('0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0', '0x6cbed15c793ce57650b9877cf6fa156fbef513c4e6134f022a85b1ffdd59b2a1'),
|
||||
('0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b', '0x6370fd033278c143179d81c5526140625662b8daa446c22ee2d73db3707e620c'),
|
||||
('0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d', '0x646f1ce2fdad0e6deeeb5c7e8e5543bdde65e86029e2fd9fc169899c440a7913'),
|
||||
('0xd03ea8624C8C5987235048901fB614fDcA89b117', '0xadd53f9a7e588d003326d1cbf9e4a43c061aadd9bc938c843a79e7b4fd2ad743'),
|
||||
('0x95cED938F7991cd0dFcb48F0a06a40FA1aF46EBC', '0x395df67f0c2d2d9fe1ad08d1bc8b6627011959b79c53d7dd6a3536a33ab8a4fd'),
|
||||
('0x3E5e9111Ae8eB78Fe1CC3bb8915d5D461F3Ef9A9', '0xe485d098507f54e7733a205420dfddbe58db035fa577fc294ebd14db90767a52'),
|
||||
('0x28a8746e75304c0780E011BEd21C72cD78cd535E', '0xa453611d9419d0e56f499079478fd72c37b251a94bfde4d19872c44cf65386e3'),
|
||||
('0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E', '0x829e924fdf021ba3dbbc4225edfece9aca04b929d6e75613329ca6f1d31c0bb4'),
|
||||
('0x1dF62f291b2E969fB0849d99D9Ce41e2F137006e', '0xb0057716d5917badaf911b193b12b910811c1497b5bada8d7711f758981c3773'),
|
||||
('0x610Bb1573d1046FCb8A70Bbbd395754cD57C2b60', '0x77c5495fbb039eed474fc940f29955ed0531693cc9212911efd35dff0373153f'),
|
||||
('0x855FA758c77D68a04990E992aA4dcdeF899F654A', '0xd99b5b29e6da2528bf458b26237a6cf8655a3e3276c1cdc0de1f98cefee81c01'),
|
||||
('0xfA2435Eacf10Ca62ae6787ba2fB044f8733Ee843', '0x9b9c613a36396172eab2d34d72331c8ca83a358781883a535d2941f66db07b24'),
|
||||
('0x64E078A8Aa15A41B85890265648e965De686bAE6', '0x0874049f95d55fb76916262dc70571701b5c4cc5900c0691af75f1a8a52c8268'),
|
||||
('0x2F560290FEF1B3Ada194b6aA9c40aa71f8e95598', '0x21d7212f3b4e5332fd465877b64926e3532653e2798a11255a46f533852dfe46'),
|
||||
('0xf408f04F9b7691f7174FA2bb73ad6d45fD5d3CBe', '0x47b65307d0d654fd4f786b908c04af8fface7710fc998b37d219de19c39ee58c'),
|
||||
('0x66FC63C2572bF3ADD0Fe5d44b97c2E614E35e9a3', '0x66109972a14d82dbdb6894e61f74708f26128814b3359b64f8b66565679f7299'),
|
||||
('0xF0D5BC18421fa04D0a2A2ef540ba5A9f04014BE3', '0x2eac15546def97adc6d69ca6e28eec831189baa2533e7910755d15403a0749e8'),
|
||||
('0x325A621DeA613BCFb5B1A69a7aCED0ea4AfBD73A', '0x2e114163041d2fb8d45f9251db259a68ee6bdbfd6d10fe1ae87c5c4bcd6ba491'),
|
||||
('0x3fD652C93dFA333979ad762Cf581Df89BaBa6795', '0xae9a2e131e9b359b198fa280de53ddbe2247730b881faae7af08e567e58915bd'),
|
||||
]
|
||||
|
||||
class TestEthereumMnemonic(unittest.TestCase):
|
||||
|
||||
def test_check_valid_seedphrase(self):
|
||||
self.assertTrue(check_seedphrase(_SEEDPHRASE))
|
||||
|
||||
def test_check_invalid_seedphrase(self):
|
||||
self.assertFalse(check_seedphrase(_INVALID_SEEDPHRASE))
|
||||
|
||||
def test_generate_seedphrase(self):
|
||||
for strength in [128, 160, 192, 224, 256]:
|
||||
seedphrase = generate_seedphrase(strength)
|
||||
|
||||
self.assertEqual(type(seedphrase), str)
|
||||
for word in seedphrase.split():
|
||||
self.assertTrue(type(word) == str)
|
||||
self.assertTrue(check_seedphrase(seedphrase))
|
||||
|
||||
def test_derive_wallet(self):
|
||||
for i, (public_adr, private_key) in enumerate(_EXPECTED):
|
||||
account = account_from_seedphrase(_SEEDPHRASE, i)
|
||||
private_key = binascii.a2b_hex(private_key[2:])
|
||||
|
||||
self.assertEqual(account.address, public_adr)
|
||||
self.assertEqual(account.key, private_key)
|
||||
@@ -0,0 +1,103 @@
|
||||
import os
|
||||
import copy
|
||||
import pkg_resources
|
||||
from random import randint, random
|
||||
import txaio
|
||||
from unittest import skipIf
|
||||
|
||||
if 'USE_TWISTED' in os.environ and os.environ['USE_TWISTED']:
|
||||
from twisted.trial import unittest
|
||||
|
||||
txaio.use_twisted()
|
||||
else:
|
||||
import unittest
|
||||
|
||||
txaio.use_asyncio()
|
||||
|
||||
from autobahn.xbr import HAS_XBR
|
||||
from autobahn.wamp.exception import InvalidPayload
|
||||
|
||||
if HAS_XBR:
|
||||
from autobahn.xbr import FbsRepository
|
||||
|
||||
|
||||
@skipIf(not HAS_XBR, 'package autobahn[xbr] not installed')
|
||||
class TestFbsBase(unittest.TestCase):
|
||||
"""
|
||||
FlatBuffers tests base class, loads test schemas.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.repo = FbsRepository('autobahn')
|
||||
self.archives = []
|
||||
for fbs_file in ['demo.bfbs', 'wamp-control.bfbs']:
|
||||
archive = pkg_resources.resource_filename('autobahn', 'xbr/test/catalog/schema/{}'.format(fbs_file))
|
||||
self.repo.load(archive)
|
||||
self.archives.append(archive)
|
||||
|
||||
|
||||
class TestFbsValidateTestTableA(TestFbsBase):
|
||||
|
||||
def test_validate_TestTableA_valid(self):
|
||||
valid_args = [
|
||||
True,
|
||||
randint(-127, -1),
|
||||
randint(1, 255),
|
||||
randint(-2 ** 15, -1),
|
||||
randint(1, 2 ** 16 - 1),
|
||||
randint(-2 ** 31, -1),
|
||||
randint(1, 2 ** 32 - 1),
|
||||
randint(-2 ** 63, -1),
|
||||
randint(1, 2 ** 64 - 1),
|
||||
2.0 + random(),
|
||||
2.0 + random(),
|
||||
]
|
||||
|
||||
try:
|
||||
self.repo.validate('demo.TestTableA', args=valid_args, kwargs={})
|
||||
except Exception as exc:
|
||||
self.assertTrue(False, f'Inventory.validate() raised an exception: {exc}')
|
||||
|
||||
def test_validate_TestTableA_invalid(self):
|
||||
valid_args = [
|
||||
True,
|
||||
randint(-127, -1),
|
||||
randint(1, 255),
|
||||
randint(-2 ** 15, -1),
|
||||
randint(1, 2 ** 16 - 1),
|
||||
randint(-2 ** 31, -1),
|
||||
randint(1, 2 ** 32 - 1),
|
||||
randint(-2 ** 63, -1),
|
||||
randint(1, 2 ** 64 - 1),
|
||||
2.0 + random(),
|
||||
2.0 + random(),
|
||||
]
|
||||
|
||||
# mandatory field with wrong type
|
||||
for i in range(len(valid_args)):
|
||||
# copy valid value, and set one column to a value of wrong type
|
||||
invalid_args = copy.copy(valid_args)
|
||||
if i == 0:
|
||||
# first column should be bool, so make it invalid with an int value
|
||||
invalid_args[0] = 666
|
||||
else:
|
||||
# all other columns are something different from bool, so make it invalid with a bool value
|
||||
invalid_args[i] = True
|
||||
self.assertRaisesRegex(InvalidPayload, 'invalid type', self.repo.validate,
|
||||
'demo.TestTableA', invalid_args, {})
|
||||
|
||||
# mandatory field with wrong type `None`
|
||||
if True:
|
||||
for i in range(len(valid_args)):
|
||||
# copy valid value, and set one column to a value of wrong type
|
||||
invalid_args = copy.copy(valid_args)
|
||||
invalid_args[i] = None
|
||||
self.assertRaisesRegex(InvalidPayload, 'invalid type', self.repo.validate,
|
||||
'demo.TestTableA', invalid_args, {})
|
||||
|
||||
# mandatory field missing
|
||||
if True:
|
||||
for i in range(len(valid_args)):
|
||||
invalid_args = valid_args[:i]
|
||||
self.assertRaisesRegex(InvalidPayload, 'missing positional argument', self.repo.validate,
|
||||
'demo.TestTableA', invalid_args, {})
|
||||
@@ -0,0 +1,325 @@
|
||||
import os
|
||||
import pkg_resources
|
||||
from binascii import a2b_hex
|
||||
import txaio
|
||||
from unittest import skipIf
|
||||
|
||||
if 'USE_TWISTED' in os.environ and os.environ['USE_TWISTED']:
|
||||
from twisted.trial import unittest
|
||||
|
||||
txaio.use_twisted()
|
||||
else:
|
||||
import unittest
|
||||
|
||||
txaio.use_asyncio()
|
||||
|
||||
from autobahn.xbr import HAS_XBR
|
||||
from autobahn.wamp.exception import InvalidPayload
|
||||
|
||||
if HAS_XBR:
|
||||
from autobahn.xbr._util import pack_ethadr, unpack_ethadr
|
||||
from autobahn.xbr import FbsType, FbsObject, FbsService, FbsRPCCall, FbsRepository, FbsSchema, FbsField, FbsEnum, \
|
||||
FbsEnumValue
|
||||
|
||||
|
||||
@skipIf(not HAS_XBR, 'package autobahn[xbr] not installed')
|
||||
class TestPackEthAdr(unittest.TestCase):
|
||||
"""
|
||||
Test :func:`pack_ethadr` and :func:`unpack_ethadr` helpers.
|
||||
"""
|
||||
|
||||
def test_roundtrip(self):
|
||||
original_value_str = '0xecdb40C2B34f3bA162C413CC53BA3ca99ff8A047'
|
||||
original_value_bin = a2b_hex(original_value_str[2:])
|
||||
|
||||
# count number of test cases run
|
||||
cnt = 0
|
||||
|
||||
# test 2 cases for return_dict option
|
||||
for return_dict in [False, True]:
|
||||
|
||||
# test 2 cases for input type
|
||||
for original_value in [original_value_str, original_value_bin]:
|
||||
packed_value = pack_ethadr(original_value, return_dict=return_dict)
|
||||
if return_dict:
|
||||
self.assertIsInstance(packed_value, dict)
|
||||
for i in range(5):
|
||||
self.assertIn('w{}'.format(i), packed_value)
|
||||
self.assertTrue(type(packed_value['w{}'.format(i)]) == int)
|
||||
else:
|
||||
self.assertIsInstance(packed_value, list)
|
||||
self.assertEqual(len(packed_value), 5)
|
||||
for i in range(5):
|
||||
self.assertTrue(type(packed_value[i]) == int)
|
||||
|
||||
# test 2 cases for return_str option
|
||||
for return_str in [False, True]:
|
||||
unpacked_value = unpack_ethadr(packed_value, return_str=return_str)
|
||||
if return_str:
|
||||
self.assertIsInstance(unpacked_value, str)
|
||||
self.assertEqual(unpacked_value, original_value_str)
|
||||
else:
|
||||
self.assertIsInstance(unpacked_value, bytes)
|
||||
self.assertEqual(unpacked_value, original_value_bin)
|
||||
|
||||
cnt += 1
|
||||
|
||||
# assure we actually completed as many test cases as we expect
|
||||
self.assertEqual(cnt, 8)
|
||||
|
||||
|
||||
@skipIf(not HAS_XBR, 'package autobahn[xbr] not installed')
|
||||
class TestFbsBase(unittest.TestCase):
|
||||
"""
|
||||
FlatBuffers tests base class, loads test schemas.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.repo = FbsRepository('autobahn')
|
||||
self.archives = []
|
||||
for fbs_file in ['wamp.bfbs', 'testsvc1.bfbs']:
|
||||
archive = pkg_resources.resource_filename('autobahn', 'xbr/test/catalog/schema/{}'.format(fbs_file))
|
||||
self.repo.load(archive)
|
||||
self.archives.append(archive)
|
||||
|
||||
|
||||
class TestFbsRepository(TestFbsBase):
|
||||
"""
|
||||
Test :class:`FbsRepository` schema loading and verify loaded types.
|
||||
"""
|
||||
|
||||
def test_create_from_archive(self):
|
||||
self.assertIn('uint160_t', self.repo.objs)
|
||||
self.assertIsInstance(self.repo.objs['uint160_t'], FbsObject)
|
||||
self.assertIn('testsvc1.TestRequest', self.repo.objs)
|
||||
self.assertIsInstance(self.repo.objs['testsvc1.TestRequest'], FbsObject)
|
||||
self.assertIn('testsvc1.TestResponse', self.repo.objs)
|
||||
self.assertIsInstance(self.repo.objs['testsvc1.TestResponse'], FbsObject)
|
||||
self.assertIn('testsvc1.ITestService1', self.repo.services)
|
||||
self.assertIsInstance(self.repo.services['testsvc1.ITestService1'], FbsService)
|
||||
|
||||
def test_loaded_schema(self):
|
||||
schema_fn = pkg_resources.resource_filename('autobahn', 'xbr/test/catalog/schema/testsvc1.bfbs')
|
||||
|
||||
# get reflection schema loaded
|
||||
schema: FbsSchema = self.repo.schemas[schema_fn]
|
||||
|
||||
# get call from service defined in schema
|
||||
call: FbsRPCCall = schema.services['testsvc1.ITestService1'].calls['run_something1']
|
||||
|
||||
# for each of the call request and call response type names ...
|
||||
call_type: FbsObject
|
||||
for call_type in [schema.objs[call.request.name], schema.objs[call.response.name]]:
|
||||
|
||||
# ... iterate over all fields
|
||||
field: FbsField
|
||||
for field in call_type.fields_by_id:
|
||||
# we only need to process the "_type" fields auto-added for Union types
|
||||
if field.type.basetype == FbsType.UType:
|
||||
assert field.name.endswith('_type')
|
||||
|
||||
# get the enum storing the Union
|
||||
call_type_enum: FbsEnum = schema.enums_by_id[field.type.index]
|
||||
assert call_type_enum.is_union
|
||||
|
||||
# get all enum values, which store Union types
|
||||
union_type_value: FbsEnumValue
|
||||
for union_type_value in call_type_enum.values:
|
||||
if union_type_value != 'NONE':
|
||||
# resolve union type value names in same namespace as containing union type [???]
|
||||
if '.' in call_type_enum.name:
|
||||
namespace = call_type_enum.name.split('.')[0]
|
||||
union_type_qn = '{}.{}'.format(namespace, union_type_value)
|
||||
else:
|
||||
union_type_qn = union_type_value
|
||||
# get type object for Union type by fully qualified name
|
||||
union_type = schema.objs[union_type_qn]
|
||||
print(union_type)
|
||||
|
||||
# print(self.repo.objs['testsvc1.TestRequest'])
|
||||
# print(self.repo.enums['testsvc1.TestRequestAny'])
|
||||
# print(self.repo.objs['testsvc1.TestRequestArgument'])
|
||||
# print(self.repo.objs['testsvc1.TestRequestProgress'])
|
||||
# print()
|
||||
|
||||
# print(self.repo.objs['testsvc1.TestResponse'])
|
||||
# print(self.repo.enums['testsvc1.TestResponseAny'])
|
||||
# print(self.repo.objs['testsvc1.TestResponseResult'])
|
||||
# print(self.repo.objs['testsvc1.TestResponseProgress'])
|
||||
# print(self.repo.objs['testsvc1.TestResponseError1'])
|
||||
# print(self.repo.objs['testsvc1.TestResponseError2'])
|
||||
# print()
|
||||
|
||||
# svc1 = self.repo.services['testsvc1.ITestService1']
|
||||
# for key in svc1.calls.keys():
|
||||
# ep: FbsRPCCall = svc1.calls[key]
|
||||
# print(ep)
|
||||
|
||||
# svc2 = self.repo.services['trading.ITradingClock']
|
||||
# for key in svc2.calls.keys():
|
||||
# ep: FbsRPCCall = svc2.calls[key]
|
||||
# print(ep)
|
||||
|
||||
|
||||
class TestFbsValidateUint160(TestFbsBase):
|
||||
"""
|
||||
Test struct uint160_t validation.
|
||||
"""
|
||||
|
||||
def test_validate_obj_uint160_valid(self):
|
||||
element_max = 2 ** 32 - 1
|
||||
valid_values = [
|
||||
[0, 0, 0, 0, 0],
|
||||
[element_max, element_max, element_max, element_max, element_max],
|
||||
pack_ethadr('0x0000000000000000000000000000000000000000'),
|
||||
pack_ethadr('0xecdb40C2B34f3bA162C413CC53BA3ca99ff8A047'),
|
||||
|
||||
{'w0': 0, 'w1': 0, 'w2': 0, 'w3': 0, 'w4': 0},
|
||||
{'w0': element_max, 'w1': element_max, 'w2': element_max, 'w3': element_max, 'w4': element_max},
|
||||
pack_ethadr('0x0000000000000000000000000000000000000000', return_dict=True),
|
||||
pack_ethadr('0xecdb40C2B34f3bA162C413CC53BA3ca99ff8A047', return_dict=True),
|
||||
]
|
||||
try:
|
||||
for value in valid_values:
|
||||
self.repo.validate_obj('uint160_t', value)
|
||||
except Exception as exc:
|
||||
self.assertTrue(False, f'Inventory.validate() raised an exception: {exc}')
|
||||
|
||||
def test_validate_obj_uint160_invalid(self):
|
||||
tests = [
|
||||
(None, 'invalid type'),
|
||||
([], 'missing argument'),
|
||||
({}, 'missing argument'),
|
||||
|
||||
([0, 0, None, 0, 0], 'invalid type'),
|
||||
([0, 0, 0, 0, 'bogus'], 'invalid type'),
|
||||
([0, 0, 0, 0], 'missing argument'),
|
||||
([0, 0, 0, 0, 0, 0], 'unexpected argument'),
|
||||
|
||||
({'w0': 0, 'w1': 0, 'w2': None, 'w3': 0, 'w4': 0}, 'invalid type'),
|
||||
({'w0': 0, 'w1': 0, 'w2': 0, 'w3': 0, 'w4': 'bogus'}, 'invalid type'),
|
||||
({'w0': 0, 'w1': 0, 'w2': 0, 'w3': 0}, 'missing argument'),
|
||||
({'w0': 0, 'w1': 0, 'w2': 0, 'w3': 0, 'w4': 0, 'w5': 0}, 'unexpected argument'),
|
||||
]
|
||||
for value, expected_regex in tests:
|
||||
self.assertRaisesRegex(InvalidPayload, expected_regex,
|
||||
self.repo.validate_obj, 'uint160_t', value)
|
||||
|
||||
|
||||
class TestFbsValidateEthAddress(TestFbsBase):
|
||||
|
||||
def test_validate_obj_EthAddress_valid(self):
|
||||
|
||||
for value in [
|
||||
{'value': {'w0': 0, 'w1': 0, 'w2': 0, 'w3': 0, 'w4': 0}},
|
||||
{'value': pack_ethadr('0x0000000000000000000000000000000000000000')},
|
||||
{'value': pack_ethadr('0xecdb40C2B34f3bA162C413CC53BA3ca99ff8A047')},
|
||||
{'value': pack_ethadr('0x0000000000000000000000000000000000000000', return_dict=True)},
|
||||
{'value': pack_ethadr('0xecdb40C2B34f3bA162C413CC53BA3ca99ff8A047', return_dict=True)},
|
||||
]:
|
||||
try:
|
||||
self.repo.validate_obj('EthAddress', value)
|
||||
except Exception as exc:
|
||||
self.assertTrue(False, f'Inventory.validate() raised an exception: {exc}')
|
||||
|
||||
def test_validate_obj_EthAddress_invalid(self):
|
||||
tests = [
|
||||
# FIXME
|
||||
# (None, 'invalid type'),
|
||||
# ([], 'invalid type'),
|
||||
({'invalid_key': pack_ethadr('0xecdb40C2B34f3bA162C413CC53BA3ca99ff8A047')}, 'unexpected argument'),
|
||||
({'value': None}, 'invalid type'),
|
||||
({'value': {}}, 'missing argument'),
|
||||
({'value': []}, 'missing argument'),
|
||||
]
|
||||
for value, expected_regex in tests:
|
||||
self.assertRaisesRegex(InvalidPayload, expected_regex,
|
||||
self.repo.validate_obj, 'EthAddress', value)
|
||||
|
||||
|
||||
class TestFbsValidateKeyValue(TestFbsBase):
|
||||
|
||||
def test_validate_KeyValue_valid(self):
|
||||
try:
|
||||
self.repo.validate('KeyValue', args=['foo', '23'], kwargs={})
|
||||
except Exception as exc:
|
||||
self.assertTrue(False, f'Inventory.validate() raised an exception: {exc}')
|
||||
|
||||
def test_validate_KeyValue_invalid(self):
|
||||
self.assertRaisesRegex(InvalidPayload, 'missing positional argument', self.repo.validate,
|
||||
'KeyValue', [], {})
|
||||
|
||||
self.assertRaisesRegex(InvalidPayload, 'missing positional argument', self.repo.validate,
|
||||
'KeyValue', ['foo'], {})
|
||||
|
||||
self.assertRaisesRegex(InvalidPayload, 'unexpected positional arguments', self.repo.validate,
|
||||
'KeyValue', ['foo', '23', 'unexpected'], {})
|
||||
|
||||
self.assertRaisesRegex(InvalidPayload, 'unexpected keyword arguments', self.repo.validate,
|
||||
'KeyValue', ['foo', '23'], {'unexpected_kwarg': '23'})
|
||||
|
||||
self.assertRaisesRegex(InvalidPayload, 'invalid type', self.repo.validate,
|
||||
'KeyValue', ['foo', 23], {})
|
||||
|
||||
def test_validate_KeyValues_valid(self):
|
||||
# empty list
|
||||
valid_value = {}
|
||||
try:
|
||||
self.repo.validate_obj('KeyValues', valid_value)
|
||||
except Exception as exc:
|
||||
self.assertTrue(False, f'Inventory.validate() raised an exception: {exc}')
|
||||
|
||||
# non-empty list
|
||||
valid_value = {
|
||||
'value': []
|
||||
}
|
||||
for i in range(10):
|
||||
# valid_value['value'].append(['key{}'.format(i), 'value{}'.format(i)])
|
||||
valid_value['value'].append({'key': 'key{}'.format(i), 'value': 'value{}'.format(i)})
|
||||
try:
|
||||
self.repo.validate_obj('KeyValues', valid_value)
|
||||
except Exception as exc:
|
||||
self.assertTrue(False, f'Inventory.validate() raised an exception: {exc}')
|
||||
|
||||
def test_validate_KeyValues_invalid(self):
|
||||
tests = [
|
||||
(None, 'invalid type'),
|
||||
([], 'invalid type'),
|
||||
({'invalid_key': 'something'}, 'unexpected argument'),
|
||||
({'value': None}, 'invalid type'),
|
||||
({'value': {}}, 'invalid type'),
|
||||
]
|
||||
for value, expected_regex in tests:
|
||||
self.assertRaisesRegex(InvalidPayload, expected_regex,
|
||||
self.repo.validate_obj, 'KeyValues', value)
|
||||
|
||||
|
||||
class TestFbsValidateVoid(TestFbsBase):
|
||||
|
||||
def test_validate_Void_valid(self):
|
||||
try:
|
||||
self.repo.validate(None, args=[], kwargs={})
|
||||
self.repo.validate('Void', args=[], kwargs={})
|
||||
except Exception as exc:
|
||||
self.assertTrue(False, f'Inventory.validate() raised an exception: {exc}')
|
||||
|
||||
def test_validate_Void_invalid(self):
|
||||
valid_adr = pack_ethadr('0xecdb40C2B34f3bA162C413CC53BA3ca99ff8A047')
|
||||
|
||||
self.assertRaisesRegex(InvalidPayload, 'unexpected positional argument', self.repo.validate,
|
||||
'Void', [23], {})
|
||||
|
||||
self.assertRaisesRegex(InvalidPayload, 'unexpected positional argument', self.repo.validate,
|
||||
'Void', [{}], {})
|
||||
|
||||
self.assertRaisesRegex(InvalidPayload, 'unexpected positional argument', self.repo.validate,
|
||||
'Void', [None], {})
|
||||
|
||||
self.assertRaisesRegex(InvalidPayload, 'unexpected positional argument', self.repo.validate,
|
||||
'Void', [{'value': valid_adr}], {})
|
||||
|
||||
self.assertRaisesRegex(InvalidPayload, 'unexpected keyword argument', self.repo.validate,
|
||||
'Void', [], {'unexpected_kwarg': None})
|
||||
|
||||
self.assertRaisesRegex(InvalidPayload, 'unexpected keyword argument', self.repo.validate,
|
||||
'Void', [], {'unexpected_kwarg': 23})
|
||||
@@ -0,0 +1,224 @@
|
||||
import os
|
||||
import copy
|
||||
import pkg_resources
|
||||
import txaio
|
||||
from unittest import skipIf
|
||||
|
||||
if 'USE_TWISTED' in os.environ and os.environ['USE_TWISTED']:
|
||||
from twisted.trial import unittest
|
||||
|
||||
txaio.use_twisted()
|
||||
else:
|
||||
import unittest
|
||||
|
||||
txaio.use_asyncio()
|
||||
|
||||
from autobahn.xbr import HAS_XBR
|
||||
from autobahn.wamp.exception import InvalidPayload
|
||||
|
||||
if HAS_XBR:
|
||||
from autobahn.xbr import FbsRepository
|
||||
|
||||
|
||||
@skipIf(not HAS_XBR, 'package autobahn[xbr] not installed')
|
||||
class TestFbsBase(unittest.TestCase):
|
||||
"""
|
||||
FlatBuffers tests base class, loads test schemas.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.repo = FbsRepository('autobahn')
|
||||
self.archives = []
|
||||
for fbs_file in ['wamp-control.bfbs']:
|
||||
archive = pkg_resources.resource_filename('autobahn', 'xbr/test/catalog/schema/{}'.format(fbs_file))
|
||||
self.repo.load(archive)
|
||||
self.archives.append(archive)
|
||||
|
||||
|
||||
class TestFbsValidatePermissionAllow(TestFbsBase):
|
||||
|
||||
def test_validate_PermissionAllow_valid(self):
|
||||
tests = [
|
||||
{
|
||||
'call': True,
|
||||
'register': True,
|
||||
'publish': True,
|
||||
'subscribe': True
|
||||
},
|
||||
{
|
||||
'call': False,
|
||||
'register': False,
|
||||
'publish': False,
|
||||
'subscribe': False
|
||||
},
|
||||
]
|
||||
for value in tests:
|
||||
try:
|
||||
self.repo.validate_obj('wamp.PermissionAllow', value)
|
||||
except Exception as exc:
|
||||
self.assertTrue(False, f'Inventory.validate() raised an exception: {exc}')
|
||||
|
||||
def test_validate_PermissionAllow_invalid(self):
|
||||
tests = [
|
||||
(None, 'invalid type'),
|
||||
(666, 'invalid type'),
|
||||
(True, 'invalid type'),
|
||||
({'some_unexpected_key': 666}, 'unexpected argument'),
|
||||
({'call': True, 'register': True, 'publish': True}, 'missing argument'),
|
||||
({'call': True, 'register': True, 'publish': True, 'subscribe': 666}, 'invalid type'),
|
||||
({'call': True, 'register': True, 'publish': True, 'subscribe': None}, 'invalid type'),
|
||||
({'call': True, 'register': True, 'publish': True, 'subscribe': True, 'some_unexpected_key': 666},
|
||||
'unexpected argument'),
|
||||
]
|
||||
for value, expected_regex in tests:
|
||||
self.assertRaisesRegex(InvalidPayload, expected_regex,
|
||||
self.repo.validate_obj, 'wamp.PermissionAllow', value)
|
||||
|
||||
|
||||
class TestFbsValidateRolePermission(TestFbsBase):
|
||||
|
||||
def test_validate_RolePermission_valid(self):
|
||||
tests = [
|
||||
{},
|
||||
{
|
||||
'uri': 'com.example.',
|
||||
'match': 'prefix',
|
||||
'allow': {
|
||||
'call': True,
|
||||
'register': True,
|
||||
'publish': True,
|
||||
'subscribe': True
|
||||
},
|
||||
'disclose': {
|
||||
'caller': True,
|
||||
'publisher': True,
|
||||
},
|
||||
'cache': True
|
||||
},
|
||||
]
|
||||
for value in tests:
|
||||
try:
|
||||
self.repo.validate_obj('wamp.RolePermission', value)
|
||||
except Exception as exc:
|
||||
self.assertTrue(False, f'Inventory.validate() raised an exception: {exc}')
|
||||
|
||||
def test_validate_RolePermission_invalid(self):
|
||||
tests = [
|
||||
(None, 'invalid type'),
|
||||
({'some_unexpected_key': True}, 'unexpected argument'),
|
||||
({'uri': 'com.example.', 'allow': {'some_unexpected_key': True}}, 'unexpected argument'),
|
||||
({'uri': 666}, 'invalid type'),
|
||||
({'uri': 'com.example.', 'match': 'prefix', 'allow': {'call': 666}}, 'invalid type'),
|
||||
({'uri': 666, 'match': 'prefix',
|
||||
'allow': {'call': True, 'register': True, 'publish': True, 'subscribe': True},
|
||||
'disclose': {'caller': True, 'publisher': True}, 'cache': True}, 'invalid type'),
|
||||
]
|
||||
for value, expected_regex in tests:
|
||||
self.assertRaisesRegex(InvalidPayload, expected_regex,
|
||||
self.repo.validate_obj, 'wamp.RolePermission', value)
|
||||
|
||||
|
||||
class TestFbsValidateRoleConfig(TestFbsBase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.role_config1 = {
|
||||
"name": "anonymous",
|
||||
"permissions": [{
|
||||
"uri": "",
|
||||
"match": "prefix",
|
||||
"allow": {
|
||||
"call": True,
|
||||
"register": True,
|
||||
"publish": True,
|
||||
"subscribe": True
|
||||
},
|
||||
"disclose": {
|
||||
"caller": True,
|
||||
"publisher": True
|
||||
},
|
||||
"cache": True
|
||||
}]
|
||||
}
|
||||
|
||||
def test_RoleConfig_valid(self):
|
||||
try:
|
||||
self.repo.validate_obj('wamp.RoleConfig', self.role_config1)
|
||||
except Exception as exc:
|
||||
self.assertTrue(False, f'Inventory.validate() raised an exception: {exc}')
|
||||
|
||||
def test_RoleConfig_invalid(self):
|
||||
config = copy.copy(self.role_config1)
|
||||
config['name'] = 666
|
||||
self.assertRaisesRegex(InvalidPayload, 'invalid type', self.repo.validate_obj,
|
||||
'wamp.RoleConfig', config)
|
||||
|
||||
# config = copy.copy(self.realm_config1)
|
||||
# del config['roles']
|
||||
# config['foobar'] = 666
|
||||
# self.assertRaisesRegex(InvalidPayload, 'missing positional argument', self.repo.validate_obj,
|
||||
# 'wamp.RealmConfig', config)
|
||||
|
||||
|
||||
class TestFbsValidateRealmConfig(TestFbsBase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.realm_config1 = {
|
||||
"name": "realm1",
|
||||
"roles": [{
|
||||
"name": "anonymous",
|
||||
"permissions": [{
|
||||
"uri": "",
|
||||
"match": "prefix",
|
||||
"allow": {
|
||||
"call": True,
|
||||
"register": True,
|
||||
"publish": True,
|
||||
"subscribe": True
|
||||
},
|
||||
"disclose": {
|
||||
"caller": True,
|
||||
"publisher": True
|
||||
},
|
||||
"cache": True
|
||||
}]
|
||||
}]
|
||||
}
|
||||
|
||||
def test_RealmConfig_valid(self):
|
||||
try:
|
||||
self.repo.validate_obj('wamp.RealmConfig', self.realm_config1)
|
||||
except Exception as exc:
|
||||
self.assertTrue(False, f'Inventory.validate() raised an exception: {exc}')
|
||||
|
||||
def test_RealmConfig_invalid(self):
|
||||
config = copy.copy(self.realm_config1)
|
||||
config['name'] = 666
|
||||
self.assertRaisesRegex(InvalidPayload, 'invalid type', self.repo.validate_obj,
|
||||
'wamp.RealmConfig', config)
|
||||
|
||||
def test_start_router_realm_valid(self):
|
||||
valid_args = ['realm023', self.realm_config1]
|
||||
try:
|
||||
self.repo.validate('wamp.StartRealm', args=valid_args, kwargs={})
|
||||
except Exception as exc:
|
||||
self.assertTrue(False, f'Inventory.validate() raised an exception: {exc}')
|
||||
|
||||
def test_start_router_realm_invalid(self):
|
||||
tests = [
|
||||
(None, None, 'missing positional argument'),
|
||||
(None, {}, 'missing positional argument'),
|
||||
(['realm023', {}], {'bogus': 666}, 'unexpected keyword arguments'),
|
||||
([], None, 'missing positional argument'),
|
||||
(['realm023'], None, 'missing positional argument'),
|
||||
(['realm023', None], None, 'invalid type'),
|
||||
(['realm023', 666], None, 'invalid type'),
|
||||
(['realm023', {'name': 'realm1', 'bogus': []}], None, 'unexpected argument'),
|
||||
(['realm023', {'name': 666}], None, 'invalid type'),
|
||||
(['realm023', {'name': 'realm1', 'roles': 666}], None, 'invalid type'),
|
||||
(['realm023', {'name': 'realm1', 'roles': None}], None, 'invalid type'),
|
||||
(['realm023', {'name': 'realm1', 'roles': {}}], None, 'invalid type'),
|
||||
(['realm023', {'name': 'realm1', 'roles': [{'name': 666}]}], None, 'invalid type'),
|
||||
]
|
||||
for args, kwargs, expected_regex in tests:
|
||||
self.assertRaisesRegex(InvalidPayload, expected_regex,
|
||||
self.repo.validate, 'wamp.StartRealm', args=args, kwargs=kwargs)
|
||||
@@ -0,0 +1,459 @@
|
||||
###############################################################################
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) typedef int GmbH
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import os
|
||||
import sys
|
||||
import pkg_resources
|
||||
from random import randint, random
|
||||
from binascii import a2b_hex
|
||||
from typing import List
|
||||
from unittest import skipIf
|
||||
|
||||
from twisted.internet.defer import inlineCallbacks
|
||||
from twisted.trial.unittest import TestCase
|
||||
|
||||
from autobahn.wamp.cryptosign import HAS_CRYPTOSIGN
|
||||
from autobahn.xbr import HAS_XBR
|
||||
|
||||
if HAS_XBR and HAS_CRYPTOSIGN:
|
||||
from py_eth_sig_utils.eip712 import encode_typed_data
|
||||
from py_eth_sig_utils.utils import ecsign, ecrecover_to_pub, checksum_encode, sha3
|
||||
from py_eth_sig_utils.signing import v_r_s_to_signature, signature_to_v_r_s
|
||||
from py_eth_sig_utils.signing import sign_typed_data, recover_typed_data
|
||||
|
||||
from autobahn.xbr import make_w3, EthereumKey, mnemonic_to_private_key
|
||||
from autobahn.xbr._eip712_member_register import _create_eip712_member_register
|
||||
from autobahn.xbr._eip712_market_create import _create_eip712_market_create
|
||||
from autobahn.xbr._secmod import SecurityModuleMemory
|
||||
from autobahn.wamp.cryptosign import CryptosignKey
|
||||
|
||||
|
||||
# https://web3py.readthedocs.io/en/stable/providers.html#infura-mainnet
|
||||
HAS_INFURA = 'WEB3_INFURA_PROJECT_ID' in os.environ and len(os.environ['WEB3_INFURA_PROJECT_ID']) > 0
|
||||
|
||||
# TypeError: As of 3.10, the *loop* parameter was removed from Lock() since it is no longer necessary
|
||||
IS_CPY_310 = sys.version_info.minor == 10
|
||||
|
||||
|
||||
@skipIf(not os.environ.get('USE_TWISTED', False), 'only for Twisted')
|
||||
@skipIf(not HAS_INFURA, 'env var WEB3_INFURA_PROJECT_ID not defined')
|
||||
@skipIf(not (HAS_XBR and HAS_CRYPTOSIGN), 'package autobahn[encryption,xbr] not installed')
|
||||
class TestSecurityModule(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self._gw_config = {
|
||||
'type': 'infura',
|
||||
'key': os.environ.get('WEB3_INFURA_PROJECT_ID', ''),
|
||||
'network': 'mainnet',
|
||||
}
|
||||
self._w3 = make_w3(self._gw_config)
|
||||
|
||||
self._seedphrase = "avocado style uncover thrive same grace crunch want essay reduce current edge"
|
||||
self._addresses = [
|
||||
'0xf766Dc789CF04CD18aE75af2c5fAf2DA6650Ff57',
|
||||
'0xf5173a6111B2A6B3C20fceD53B2A8405EC142bF6',
|
||||
'0xecdb40C2B34f3bA162C413CC53BA3ca99ff8A047',
|
||||
'0x2F070c2f49a59159A0346396f1139203355ACA43',
|
||||
'0x66290fA8ADcD901Fd994e4f64Cfb53F4c359a326',
|
||||
]
|
||||
self._keys = [
|
||||
'0x805f84af7e182359db0610ffb07c801012b699b5610646937704aa5cfc28b15e',
|
||||
'0x991c8f7609f3236ad5ef6d498b2ec0c9793c2865dd337ddc3033067c1da0e735',
|
||||
'0x75848ddb1155cd1cdf6d74a6e7fbed06aeaa21ef2d8a05df7af2d95cdc127672',
|
||||
'0x5be599a34927a1110922d7704ba316144b31699d8e7f229e2684d5575a84214e',
|
||||
'0xc1bb7ce3481e95b28bb8c026667b6009c504c79a98e6c7237ba0788c37b473c9',
|
||||
]
|
||||
|
||||
# create EIP712 typed data dicts from message data and schemata
|
||||
|
||||
verifying_contract = a2b_hex(self._addresses[0][2:])
|
||||
member = a2b_hex(self._addresses[1][2:])
|
||||
maker = a2b_hex(self._addresses[2][2:])
|
||||
coin = a2b_hex(self._addresses[3][2:])
|
||||
|
||||
eula = 'QmU7Gizbre17x6V2VR1Q2GJEjz6m8S1bXmBtVxS2vmvb81'
|
||||
profile = 'QmcNsPV7QZFHKb2DNn8GWsU5dtd8zH5DNRa31geC63ceb4'
|
||||
terms = 'QmaozNR7DZHQK1ZcU9p7QdrshMvXqWK6gpu5rmrkPdT3L4'
|
||||
meta = 'Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD'
|
||||
|
||||
market_id = a2b_hex('5b7ee23c9353479ca49a2461c0a1deb2')
|
||||
|
||||
self._eip_data_objects = [
|
||||
_create_eip712_member_register(chainId=1, verifyingContract=verifying_contract, member=member,
|
||||
registered=666, eula=eula, profile=profile),
|
||||
_create_eip712_member_register(chainId=23, verifyingContract=a2b_hex(self._addresses[0][2:]),
|
||||
member=a2b_hex(self._addresses[1][2:]), registered=9999, eula=eula,
|
||||
profile=profile),
|
||||
_create_eip712_market_create(chainId=1, verifyingContract=verifying_contract, member=member, created=666,
|
||||
marketId=market_id, coin=coin, terms=terms, meta=meta, maker=maker,
|
||||
providerSecurity=10 ** 6, consumerSecurity=10 ** 6, marketFee=100),
|
||||
]
|
||||
self._eip_data_obj_hashes = [
|
||||
'8abee87b2cf457841d173083d5f205183f3e78c6cee30ca77776344e11f612b3',
|
||||
'6a4f10dc41080c445a86acaae652ce80878fe768f6b459af08d14465c5310138',
|
||||
'f1b80df26ec6cc7dafeb8a5c69de77e8ec5a2c0e93f5d6e475124f18cf4c595f',
|
||||
]
|
||||
self._eip_data_obj_signatures = [
|
||||
'17ed35d8fd41fcb507ae11a3745d9775f37ff1c155257074fe2245cfb186f4336151fd018bf83a5e9902d825b645213a111630f78bbbc3c96f68d60b7e65dafd1c',
|
||||
'1c0fa4d8e2b2d0d0391c4b7c5cf2f494eab5c7074aa46cfd11a2d8a6b8c087030db7a5b74128d9bb04f6baa12abaa45457e0cfe790e9ebbd62721c075d79335e1c',
|
||||
'236660f4cc04df21289538bf15e83d5bd2858b9dad27022d6b83fc3374ce887d5789e1d40126823abf7ccef04d06e4a1717e6b6a00cbfacf5cc2e7b2e4cb384e1c',
|
||||
]
|
||||
|
||||
def test_ethereum_key_from_seedphrase(self):
|
||||
"""
|
||||
Create key from seedphrase and index.
|
||||
"""
|
||||
for i in range(len(self._keys)):
|
||||
key = EthereumKey.from_seedphrase(self._seedphrase, i)
|
||||
self.assertEqual(key.address(binary=False), self._addresses[i])
|
||||
|
||||
def test_ethereum_key_from_bytes(self):
|
||||
"""
|
||||
Create key from raw bytes.
|
||||
"""
|
||||
for i in range(len(self._keys)):
|
||||
key_raw = a2b_hex(self._keys[i][2:])
|
||||
key = EthereumKey.from_bytes(key_raw)
|
||||
self.assertEqual(key.address(binary=False), self._addresses[i])
|
||||
self.assertEqual(key._key.key, key_raw)
|
||||
|
||||
def test_ethereum_sign_typed_data_pesu_manual(self):
|
||||
"""
|
||||
Test using py_eth_sig_utils by doing individual steps / manually.
|
||||
"""
|
||||
key_raw = a2b_hex(self._keys[0][2:])
|
||||
|
||||
for i in range(len(self._eip_data_objects)):
|
||||
data = self._eip_data_objects[i]
|
||||
|
||||
# encode typed data dict and return message hash
|
||||
msg_hash = encode_typed_data(data)
|
||||
# print('0' * 100, b2a_hex(msg_hash).decode())
|
||||
self.assertEqual(msg_hash, a2b_hex(self._eip_data_obj_hashes[i]))
|
||||
|
||||
# sign message hash with private key
|
||||
signature_vrs = ecsign(msg_hash, key_raw)
|
||||
|
||||
# concatenate signature components into byte string
|
||||
signature = v_r_s_to_signature(*signature_vrs)
|
||||
# print('1' * 100, b2a_hex(signature).decode())
|
||||
|
||||
# ECDSA signatures in Ethereum consist of three parameters: v, r and s.
|
||||
# The signature is always 65-bytes in length.
|
||||
# r = first 32 bytes of signature
|
||||
# s = second 32 bytes of signature
|
||||
# v = final 1 byte of signature
|
||||
self.assertEqual(len(signature), 65)
|
||||
self.assertEqual(signature, a2b_hex(self._eip_data_obj_signatures[i]))
|
||||
|
||||
def test_ethereum_sign_typed_data_pesu_highlevel(self):
|
||||
"""
|
||||
Test using py_eth_sig_utils with high level functions.
|
||||
"""
|
||||
key_raw = a2b_hex(self._keys[0][2:])
|
||||
for i in range(len(self._eip_data_objects)):
|
||||
data = self._eip_data_objects[i]
|
||||
|
||||
signature_vrs = sign_typed_data(data, key_raw)
|
||||
signature = v_r_s_to_signature(*signature_vrs)
|
||||
# print('2' * 100, b2a_hex(signature).decode())
|
||||
|
||||
self.assertEqual(len(signature), 65)
|
||||
self.assertEqual(signature, a2b_hex(self._eip_data_obj_signatures[i]))
|
||||
|
||||
@inlineCallbacks
|
||||
def test_ethereum_sign_typed_data_ab_async(self):
|
||||
"""
|
||||
Test using autobahn with async functions.
|
||||
"""
|
||||
key_raw = a2b_hex(self._keys[0][2:])
|
||||
key = EthereumKey.from_bytes(key_raw)
|
||||
for i in range(len(self._eip_data_objects)):
|
||||
data = self._eip_data_objects[i]
|
||||
signature = yield key.sign_typed_data(data)
|
||||
self.assertEqual(signature, a2b_hex(self._eip_data_obj_signatures[i]))
|
||||
|
||||
def test_ethereum_verify_typed_data_pesu_manual(self):
|
||||
"""
|
||||
Test using py_eth_sig_utils by doing individual steps / manually.
|
||||
"""
|
||||
for i in range(len(self._eip_data_objects)):
|
||||
data = self._eip_data_objects[i]
|
||||
|
||||
# encode typed data dict and return message hash
|
||||
msg_hash = encode_typed_data(data)
|
||||
|
||||
signature = a2b_hex(self._eip_data_obj_signatures[i])
|
||||
signature_vrs = signature_to_v_r_s(signature)
|
||||
|
||||
public_key = ecrecover_to_pub(msg_hash, *signature_vrs)
|
||||
address_bytes = sha3(public_key)[-20:]
|
||||
address = checksum_encode(address_bytes)
|
||||
|
||||
self.assertEqual(address, self._addresses[0])
|
||||
|
||||
def test_ethereum_verify_typed_data_pesu_highlevel(self):
|
||||
"""
|
||||
Test using py_eth_sig_utils with high level functions.
|
||||
"""
|
||||
for i in range(len(self._eip_data_objects)):
|
||||
data = self._eip_data_objects[i]
|
||||
|
||||
signature = a2b_hex(self._eip_data_obj_signatures[i])
|
||||
signature_vrs = signature_to_v_r_s(signature)
|
||||
|
||||
address = recover_typed_data(data, *signature_vrs)
|
||||
|
||||
self.assertEqual(address, self._addresses[0])
|
||||
|
||||
@inlineCallbacks
|
||||
def test_ethereum_verify_typed_data_ab_async(self):
|
||||
"""
|
||||
Test using autobahn with async functions.
|
||||
"""
|
||||
key = EthereumKey.from_address(self._addresses[0])
|
||||
for i in range(len(self._eip_data_objects)):
|
||||
data = self._eip_data_objects[i]
|
||||
signature = a2b_hex(self._eip_data_obj_signatures[i])
|
||||
sig_valid = yield key.verify_typed_data(data, signature)
|
||||
self.assertTrue(sig_valid)
|
||||
|
||||
@inlineCallbacks
|
||||
def test_secmod_iterable(self):
|
||||
"""
|
||||
This tests:
|
||||
|
||||
* :meth:`SecurityModuleMemory.from_seedphrase`
|
||||
* :meth:`SecurityModuleMemory.__len__`
|
||||
* :meth:`SecurityModuleMemory.__iter__`
|
||||
* :meth:`SecurityModuleMemory.__getitem__`
|
||||
"""
|
||||
sm = SecurityModuleMemory.from_seedphrase(self._seedphrase, 5, 5)
|
||||
yield sm.open()
|
||||
|
||||
self.assertEqual(len(sm), 10)
|
||||
|
||||
for i, key in sm.items():
|
||||
self.assertTrue(isinstance(key, EthereumKey) or isinstance(key, CryptosignKey),
|
||||
'unexpected type {} returned in security module'.format(type(key)))
|
||||
key_ = sm[i]
|
||||
self.assertEqual(key_, key)
|
||||
|
||||
@inlineCallbacks
|
||||
def test_secmod_create_key(self):
|
||||
"""
|
||||
This tests:
|
||||
|
||||
* :meth:`SecurityModuleMemory.create_key`
|
||||
"""
|
||||
sm = SecurityModuleMemory()
|
||||
yield sm.open()
|
||||
|
||||
self.assertEqual(len(sm), 0)
|
||||
|
||||
for i in range(3):
|
||||
idx = yield sm.create_key('ethereum')
|
||||
self.assertEqual(idx, i * 2)
|
||||
self.assertEqual(len(sm), i * 2 + 1)
|
||||
key = sm[idx]
|
||||
self.assertTrue(isinstance(key, EthereumKey))
|
||||
self.assertEqual(key.security_module, sm)
|
||||
self.assertEqual(key.key_no, i * 2)
|
||||
self.assertEqual(key.key_type, 'ethereum')
|
||||
self.assertEqual(key.can_sign, True)
|
||||
|
||||
idx = yield sm.create_key('cryptosign')
|
||||
self.assertEqual(idx, i * 2 + 1)
|
||||
self.assertEqual(len(sm), i * 2 + 2)
|
||||
key = sm[idx]
|
||||
self.assertTrue(isinstance(key, CryptosignKey))
|
||||
self.assertEqual(key.security_module, sm)
|
||||
self.assertEqual(key.key_no, i * 2 + 1)
|
||||
self.assertEqual(key.key_type, 'cryptosign')
|
||||
self.assertEqual(key.can_sign, True)
|
||||
|
||||
self.assertEqual(len(sm), 6)
|
||||
|
||||
@inlineCallbacks
|
||||
def test_secmod_delete_key(self):
|
||||
"""
|
||||
This tests:
|
||||
|
||||
* :meth:`SecurityModuleMemory.create_key`
|
||||
* :meth:`SecurityModuleMemory.delete_key`
|
||||
"""
|
||||
sm = SecurityModuleMemory()
|
||||
yield sm.open()
|
||||
|
||||
self.assertEqual(len(sm), 0)
|
||||
|
||||
n = 10
|
||||
keys = []
|
||||
|
||||
for i in range(n):
|
||||
if random() > .5:
|
||||
yield sm.create_key('ethereum')
|
||||
else:
|
||||
yield sm.create_key('cryptosign')
|
||||
key = sm[i]
|
||||
keys.append(key)
|
||||
self.assertEqual(len(sm), 10)
|
||||
|
||||
for i in range(n):
|
||||
self.assertTrue(i in sm)
|
||||
yield sm.delete_key(i)
|
||||
self.assertFalse(i in sm)
|
||||
self.assertEqual(len(sm), n - i - 1)
|
||||
|
||||
@inlineCallbacks
|
||||
def test_secmod_counters(self):
|
||||
"""
|
||||
This tests:
|
||||
|
||||
* :meth:`SecurityModuleMemory.__init__`
|
||||
* :meth:`SecurityModuleMemory.get_counter`
|
||||
* :meth:`SecurityModuleMemory.increment_counter`
|
||||
"""
|
||||
sm = SecurityModuleMemory()
|
||||
yield sm.open()
|
||||
|
||||
# counters are indexed beginning with 0
|
||||
counter = 0
|
||||
|
||||
# initially, no counters exist, and hence value must be 0
|
||||
value = yield sm.get_counter(counter)
|
||||
self.assertEqual(value, 0)
|
||||
|
||||
yield sm.get_counter(randint(0, 100))
|
||||
self.assertEqual(value, 0)
|
||||
|
||||
# once incremented, counters exist
|
||||
for counter in range(10):
|
||||
for i in range(100):
|
||||
value = yield sm.increment_counter(counter)
|
||||
self.assertEqual(value, i + 1)
|
||||
|
||||
value = yield sm.get_counter(counter)
|
||||
self.assertEqual(value, i + 1)
|
||||
|
||||
def test_cryptosign_key_from_seedphrase(self):
|
||||
# seedphrase to compute keys from
|
||||
seedphrase = "myth like bonus scare over problem client lizard pioneer submit female collect"
|
||||
|
||||
# pubkeys we expect
|
||||
pubs_keys: List[str] = [
|
||||
'30b2e1af1406c5f5254ddc456a045808796d13417f3b56500b0321a908cd89ca',
|
||||
'262b6812802deac81dd2be53d69cb32a05eb9296265e9698f02772867ede002f',
|
||||
'2d2ae42f8927b6c20fe4463151c3468367852c370a3b7db73ef10f97ce262739',
|
||||
'fab0eab3e14b24288b816dd590f21f90700a96306648cb2a031c7451dc5ee616',
|
||||
'1ce310832e5acb0359516400a881cf41d94ca60d9a529ce48a1b5f857cde0aa8',
|
||||
]
|
||||
|
||||
# create keys from seedphrase
|
||||
keys: List[CryptosignKey] = []
|
||||
for i in range(5):
|
||||
|
||||
# BIP44 path for WAMP
|
||||
# https://github.com/wamp-proto/wamp-proto/issues/401
|
||||
# https://github.com/satoshilabs/slips/pull/1322
|
||||
derivation_path = "m/44'/655'/0'/0/{}".format(i)
|
||||
|
||||
# compute private key from WAMP-Cryptosign from seedphrase and BIP44 path
|
||||
key_raw = mnemonic_to_private_key(seedphrase, derivation_path)
|
||||
assert type(key_raw) == bytes
|
||||
assert len(key_raw) == 32
|
||||
|
||||
# create WAMP-Cryptosign key object from raw bytes
|
||||
key = CryptosignKey.from_bytes(key_raw)
|
||||
keys.append(key)
|
||||
|
||||
# check public keys we expect
|
||||
for i in range(5):
|
||||
pub_key = keys[i].public_key(binary=False)
|
||||
self.assertEqual(pub_key, pubs_keys[i])
|
||||
|
||||
@inlineCallbacks
|
||||
def test_secmod_from_seedphrase(self):
|
||||
# seedphrase to compute keys from
|
||||
seedphrase = "myth like bonus scare over problem client lizard pioneer submit female collect"
|
||||
|
||||
sm = SecurityModuleMemory.from_seedphrase(seedphrase)
|
||||
yield sm.open()
|
||||
self.assertEqual(len(sm), 2)
|
||||
self.assertTrue(isinstance(sm[0], EthereumKey), 'unexpected type {} at index 0'.format(type(sm[0])))
|
||||
self.assertTrue(isinstance(sm[1], CryptosignKey), 'unexpected type {} at index 1'.format(type(sm[1])))
|
||||
yield sm.close()
|
||||
|
||||
sm = SecurityModuleMemory.from_seedphrase(seedphrase, num_eth_keys=5, num_cs_keys=5)
|
||||
yield sm.open()
|
||||
self.assertEqual(len(sm), 10)
|
||||
for i in range(5):
|
||||
self.assertTrue(isinstance(sm[i], EthereumKey))
|
||||
for i in range(5, 10):
|
||||
self.assertTrue(isinstance(sm[i], CryptosignKey))
|
||||
yield sm.close()
|
||||
|
||||
@inlineCallbacks
|
||||
def test_secmod_from_config(self):
|
||||
config = pkg_resources.resource_filename('autobahn', 'xbr/test/profile/config.ini')
|
||||
|
||||
sm = SecurityModuleMemory.from_config(config)
|
||||
yield sm.open()
|
||||
self.assertEqual(len(sm), 2)
|
||||
|
||||
self.assertTrue(isinstance(sm[0], EthereumKey), 'unexpected type {} at index 0'.format(type(sm[0])))
|
||||
self.assertTrue(isinstance(sm[1], CryptosignKey), 'unexpected type {} at index 1'.format(type(sm[1])))
|
||||
|
||||
key1: EthereumKey = sm[0]
|
||||
key2: CryptosignKey = sm[1]
|
||||
|
||||
# public-key-ed25519: 15cfa4acef5cc312e0b9ba77634849d0a8c6222a546f90eb5123667935d2f561
|
||||
# public-adr-eth: 0xe59C7418403CF1D973485B36660728a5f4A8fF9c
|
||||
self.assertEqual(key1.address(binary=False), '0xe59C7418403CF1D973485B36660728a5f4A8fF9c')
|
||||
self.assertEqual(key2.public_key(binary=False), '15cfa4acef5cc312e0b9ba77634849d0a8c6222a546f90eb5123667935d2f561')
|
||||
|
||||
yield sm.close()
|
||||
|
||||
@inlineCallbacks
|
||||
def test_secmod_from_keyfile(self):
|
||||
keyfile = pkg_resources.resource_filename('autobahn', 'xbr/test/profile/default.priv')
|
||||
|
||||
sm = SecurityModuleMemory.from_keyfile(keyfile)
|
||||
yield sm.open()
|
||||
self.assertEqual(len(sm), 2)
|
||||
|
||||
self.assertTrue(isinstance(sm[0], EthereumKey), 'unexpected type {} at index 0'.format(type(sm[0])))
|
||||
self.assertTrue(isinstance(sm[1], CryptosignKey), 'unexpected type {} at index 1'.format(type(sm[1])))
|
||||
|
||||
key1: EthereumKey = sm[0]
|
||||
key2: CryptosignKey = sm[1]
|
||||
|
||||
# public-key-ed25519: 15cfa4acef5cc312e0b9ba77634849d0a8c6222a546f90eb5123667935d2f561
|
||||
# public-adr-eth: 0xe59C7418403CF1D973485B36660728a5f4A8fF9c
|
||||
self.assertEqual(key1.address(binary=False), '0xe59C7418403CF1D973485B36660728a5f4A8fF9c')
|
||||
self.assertEqual(key2.public_key(binary=False), '15cfa4acef5cc312e0b9ba77634849d0a8c6222a546f90eb5123667935d2f561')
|
||||
|
||||
yield sm.close()
|
||||
@@ -0,0 +1,57 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from unittest import skipIf
|
||||
from twisted.trial.unittest import TestCase
|
||||
|
||||
from autobahn.xbr import HAS_XBR
|
||||
|
||||
# https://web3py.readthedocs.io/en/stable/providers.html#infura-mainnet
|
||||
HAS_INFURA = 'WEB3_INFURA_PROJECT_ID' in os.environ and len(os.environ['WEB3_INFURA_PROJECT_ID']) > 0
|
||||
|
||||
# TypeError: As of 3.10, the *loop* parameter was removed from Lock() since it is no longer necessary
|
||||
IS_CPY_310 = sys.version_info.minor >= 10
|
||||
|
||||
|
||||
@skipIf(not HAS_XBR, 'package autobahn[xbr] not installed')
|
||||
@skipIf(not HAS_INFURA, 'env var WEB3_INFURA_PROJECT_ID not defined')
|
||||
class TestWeb3(TestCase):
|
||||
gw_config = {
|
||||
'type': 'infura',
|
||||
'key': os.environ.get('WEB3_INFURA_PROJECT_ID', ''),
|
||||
'network': 'mainnet',
|
||||
}
|
||||
|
||||
# "builtins.TypeError: As of 3.10, the *loop* parameter was removed from Lock() since
|
||||
# it is no longer necessary"
|
||||
#
|
||||
# solved via websockets>=10.3, but web3==5.29.0 requires websockets<10
|
||||
#
|
||||
@skipIf(True, 'FIXME: web3.auto.infura was removed')
|
||||
def test_connect_w3_infura_auto(self):
|
||||
from web3.auto.infura import w3
|
||||
|
||||
self.assertTrue(w3.isConnected())
|
||||
|
||||
def test_connect_w3_autobahn(self):
|
||||
from autobahn.xbr import make_w3
|
||||
w3 = make_w3(self.gw_config)
|
||||
self.assertTrue(w3.isConnected())
|
||||
|
||||
def test_ens_valid_names(self):
|
||||
from ens.ens import ENS
|
||||
|
||||
for name in ['wamp-proto.eth']:
|
||||
self.assertTrue(ENS.is_valid_name(name))
|
||||
|
||||
def test_ens_resolve_names(self):
|
||||
from autobahn.xbr import make_w3
|
||||
from ens.ens import ENS
|
||||
|
||||
w3 = make_w3(self.gw_config)
|
||||
ens = ENS.from_web3(w3)
|
||||
for name, adr in [
|
||||
('wamp-proto.eth', '0x66267d0b1114cFae80C37942177a846d666b114a'),
|
||||
]:
|
||||
_adr = ens.address(name)
|
||||
self.assertEqual(adr, _adr)
|
||||
Reference in New Issue
Block a user