This commit is contained in:
2024-12-17 14:36:15 -08:00
parent b2dbf46d28
commit 06d106de53
17731 changed files with 3037186 additions and 144 deletions

View File

@@ -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.
#
###############################################################################

View File

@@ -0,0 +1,5 @@
[default]
url=ws://localhost:9000/ws
privkey=default.priv
pubkey=default.pub

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()

View File

@@ -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

View File

@@ -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)

View File

@@ -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, {})

View File

@@ -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})

View File

@@ -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)

View File

@@ -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()

View File

@@ -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)