Source code for autobahn.xbr.test.test_xbr_eip712

###############################################################################
#
# The MIT License (MIT)
#
# Copyright (c) Crossbar.io Technologies GmbH
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
###############################################################################

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


[docs]@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):
[docs] 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)
[docs] @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()
[docs] @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()
[docs]@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):
[docs] 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')]
[docs] @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' # 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()
[docs] @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()
[docs] @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()
[docs] @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()