RAHHH
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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