Esempio n. 1
0
class TestClaim(unittest.TestCase):
    def setUp(self):
        self.gas_price = 500
        self.gas_limit = 20000
        self.kid = 'did:dna:TRAtosUZHNSiLhzBdHacyxMX4Bg3cjWy3r#keys-1'
        self.claim_header = Header(self.kid)

    def test_head_kid(self):
        self.assertEqual(self.kid, self.claim_header.kid)
        self.assertRaises(SDKException, setattr, self.claim_header, 'kid', 1)
        self.assertRaises(SDKException, setattr, self.claim_header, 'kid',
                          'test')
        self.assertRaises(SDKException, setattr, self.claim_header, 'kid',
                          'did:dna:test')
        self.kid = self.kid.replace('keys-1', 'keys-2')
        self.claim_header.kid = self.kid
        self.assertEqual(self.kid, self.claim_header.kid)

    def test_head_to_json(self):
        self.assertEqual(96, len(self.claim_header.to_json()))

    def test_claim_head_b64(self):
        self.assertEqual(
            dict(self.claim_header),
            dict(Header.from_base64(self.claim_header.to_base64())))

    def test_head_dict(self):
        claim_header_dict = dict(self.claim_header)
        self.assertEqual(self.kid, claim_header_dict['kid'])
        self.assertTrue(isinstance(claim_header_dict, dict))
        self.assertEqual('ONT-ES256', claim_header_dict['alg'])
        self.assertEqual(ClmType.witness_claim, self.claim_header.type)

    def test_from_str(self):
        clm_alg_lst = [
            ClmAlg.ES224, ClmAlg.ES256, ClmAlg.ES384, ClmAlg.ES512,
            ClmAlg.ES3_224, ClmAlg.ES3_256, ClmAlg.ES3_384, ClmAlg.ES3_512,
            ClmAlg.ER160, ClmAlg.SM, ClmAlg.EDS512
        ]
        alg_str_lst = [
            'ONT-ES224', 'ONT-ES256', 'ONT-ES384', 'ONT-ES512', 'ONT-ES3-224',
            'ONT-ES3-256', 'ONT-ES3-384', 'ONT-ES3-512', 'ONT-ER160', 'ONT-SM',
            'ONT-EDS512'
        ]
        for index, alg in enumerate(alg_str_lst):
            self.assertEqual(clm_alg_lst[index], ClmAlg.from_str(alg))

    def test_payload(self):
        ver = '0.7.0'
        iss = 'did:dna:TRAtosUZHNSiLhzBdHacyxMX4Bg3cjWy3r'
        sub = 'did:dna:SI59Js0zpNSiPOzBdB5cyxu80BO3cjGT70'
        iat = 1525465044
        exp = 1530735444
        jti = '4d9546fdf2eb94a364208fa65a9996b03ba0ca4ab2f56d106dac92e891b6f7fc'
        context = 'https://example.com/template/v1'
        clm = dict(Name='Bob Dylan', Age='22')
        clm_rev = dict(typ='AttestContract',
                       addr='8055b362904715fd84536e754868f4c8d27ca3f6')
        claim_payload = Payload(ver, iss, sub, iat, exp, context, clm, clm_rev,
                                jti)
        claim_payload_dict = dict(claim_payload)
        self.assertEqual(ver, claim_payload_dict['ver'])
        self.assertEqual(iss, claim_payload_dict['iss'])
        self.assertEqual(sub, claim_payload_dict['sub'])
        self.assertEqual(iat, claim_payload_dict['iat'])
        self.assertEqual(exp, claim_payload_dict['exp'])
        self.assertEqual(jti, claim_payload_dict['jti'])
        self.assertEqual(64, len(claim_payload_dict['jti']))
        self.assertEqual(context, claim_payload_dict['@context'])
        self.assertEqual(clm, claim_payload_dict['clm'])
        self.assertEqual(clm_rev, claim_payload_dict['clm-rev'])
        b64_payload = claim_payload.to_base64()
        claim_payload_recv = Payload.from_base64(b64_payload)
        self.assertEqual(dict(claim_payload), dict(claim_payload_recv))

    @not_panic_exception
    def test_signature_info(self):
        try:
            pub_keys = sdk.native_vm.ont_id().get_public_keys(identity2.ont_id)
            pk = pub_keys[0]
            kid = pk['PubKeyId']
        except IndexError:
            kid = '03eca83d4e4a0ed1e96d87942f76275c49a2f61a6b1e0f33be5a65f9beae71cd3f#keys-1'
        iss_ont_id = identity2.ont_id
        sub_ont_id = identity1.ont_id
        exp = int(time()) + 100
        context = 'https://github.com/NashMiao'
        clm = dict(Name='NashMiao', Position='Mars', Birthday=str(time()))
        clm_rev = dict(type='AttestContract',
                       addr='8055b362904715fd84536e754868f4c8d27ca3f6')
        claim = sdk.service.claim()
        claim.set_claim(kid, iss_ont_id, sub_ont_id, exp, context, clm,
                        clm_rev)
        try:
            claim.generate_signature(identity2_ctrl_acct)
        except SDKException as e:
            if 'get key failed' not in e.args[1]:
                raise e
            claim.generate_signature(identity2_ctrl_acct, verify_kid=False)
        b64_claim = claim.to_base64()
        try:
            self.assertTrue(claim.validate_signature(b64_claim))
        except SDKException as e:
            msg = 'invalid claim head parameter'
            self.assertTrue(msg in e.args[1])

    @not_panic_exception
    def test_claim_demo(self):
        pub_keys = sdk.native_vm.ont_id().get_public_keys(identity1.ont_id)
        try:
            pk = pub_keys[0]
            kid = pk['PubKeyId']
        except IndexError:
            kid = '03d0fdb54acba3f81db3a6e16fa02e7ea3678bd205eb4ed2f1cfa8ab5e5d45633e#keys-1'
        iss_ont_id = identity2.ont_id
        sub_ont_id = identity1.ont_id
        exp = int(time()) + 1000
        context = 'https://example.com/template/v1'
        clm = dict(Name='NashMiao',
                   JobTitle='SoftwareEngineer',
                   HireData=str(time()))
        clm_rev = dict(type='AttestContract',
                       addr='8055b362904715fd84536e754868f4c8d27ca3f6')
        claim = sdk.service.claim()
        claim.set_claim(kid, iss_ont_id, sub_ont_id, exp, context, clm,
                        clm_rev)
        try:
            claim.generate_signature(identity2_ctrl_acct)
        except SDKException as e:
            msg = 'get key failed'
            self.assertTrue(msg in e.args[1])
            claim.generate_signature(identity2_ctrl_acct, verify_kid=False)
        tx = claim.new_commit_tx(identity2_ctrl_acct.get_address_base58(),
                                 acct1.get_address_base58(), self.gas_price,
                                 self.gas_limit)
        tx.sign_transaction(identity2_ctrl_acct)
        tx.add_sign_transaction(acct1)
        tx_hash = sdk.rpc.send_raw_transaction(tx)
        sleep(12)
        blockchain_proof = claim.generate_blk_proof(tx_hash)
        self.assertTrue(claim.validate_blk_proof())
        b64_claim = claim.to_base64()
        claim_list = b64_claim.split('.')
        self.assertEqual(4, len(claim_list))
        claim_recv = sdk.service.claim()
        claim_recv.from_base64(b64_claim)
        self.assertEqual(dict(claim), dict(claim_recv))
        proof_node = blockchain_proof.proof_node
        if proof_node[0]['Direction'] == 'Right':
            proof_node[0]['Direction'] = 'Left'
        else:
            proof_node[0]['Direction'] = 'Right'
        claim.blk_proof.proof_node = proof_node
        self.assertFalse(claim.validate_blk_proof())

    @not_panic_exception
    def test_compatibility(self):
        b64_claim = (
            'eyJraWQiOiJkaWQ6b250OkFUWmhhVmlyZEVZa3BzSFFEbjlQTXQ1a0RDcTFWUEhjVHIja2V5cy0xIiwidHlwIjoiSldULVgiL'
            'CJhbGciOiJPTlQtRVMyNTYifQ==.eyJjbG0tcmV2Ijp7InR5cCI6IkF0dGVzdENvbnRyYWN0IiwiYWRkciI6IjM2YmI1YzA1M'
            '2I2YjgzOWM4ZjZiOTIzZmU4NTJmOTEyMzliOWZjY2MifSwic3ViIjoiZGlkOm9udDpBYmt3UjNENmQya3A0cUpTVDI5dDlHa3'
            'JZVldVdmJFSERxIiwidmVyIjoidjEuMCIsImNsbSI6eyJOYXRpb25hbGl0eSI6IkNOIiwiTmFtZSI6IueCjuacsSIsIkJpcnR'
            'oRGF5IjoiMTk4Mi0wMi0xMSIsIklzc3VlRGF0ZSI6IjIwMTYtMDYtMjgiLCJFeHBpcmF0aW9uRGF0ZSI6IjIwMzYtMDYtMjgi'
            'LCJJRERvY051bWJlciI6IjMxMDEwNDE5ODIwMjExMzY1MyIsIklzc3Vlck5hbWUiOiJTaHVmdGlwcm8ifSwiaXNzIjoiZGlkO'
            'm9udDpBVFpoYVZpcmRFWWtwc0hRRG45UE10NWtEQ3ExVlBIY1RyIiwiZXhwIjoxNTc3NTIyNzQwLCJpYXQiOjE1NDU5ODY3ND'
            'AsIkBjb250ZXh0IjoiY2xhaW06c2ZwX2lkY2FyZF9hdXRoZW50aWNhdGlvbiIsImp0aSI6ImFkM2MzODFkZjRhNzg0MTVkMGU'
            '0MGUxMTM0MDZmY2JkYmZkMzNhMTQzMDg0ZjM2ZTE4ODk2NDgwMGUxN2IzMGEifQ==.AWAvGV7YfP7wLh+1y9qq49ox8yxn5ZR'
            '+U/p1UE6fDKlKzWN0ZZolimnyuuo2rssbAZt0deO3QMF4DMEfsAxrEPo=\.eyJUeXBlIjoiTWVya2xlUHJvb2YiLCJNZXJrbG'
            'VSb290IjoiYjg0M2NhOGQxMjMwODdmODE4YjU1YjdkOTk4MThjM2UxOTVjYzU2NjRjOGZlYmFlZGU4NmZjZWNhODc3NGU1ZCI'
            'sIlR4bkhhc2giOiJjMTdlNTc0ZGRhMTdmMjY4NzkzNzU3ZmFiMGMyNzRkNDQ0MjdmYTFiNjdkNjMxMTNiZDMxZTM5YjMxZDFh'
            'MDI2IiwiQmxvY2tIZWlnaHQiOjE0NDUwNDQsIk5vZGVzIjpbeyJUYXJnZXRIYXNoIjoiNTYxZjg0ZmU4Y2M1ZjE0M2U4OTU5Z'
            'WM5N2U4ZWMwZjFmY2Y3MmZjMGYwZTcwOWY5NTU3OTBiODAxYTc0ZjdiMSIsIkRpcmVjdGlvbiI6IlJpZ2h0In0seyJUYXJnZX'
            'RIYXNoIjoiNzI2MjIwZDg0N2MyZTgyYjFjY2U1YzI0ZmE3MmQ0MzdmZDgwMWMyY2E5MGZjZjY2ZDBlNTYzOGIwZTU2OWQwNSI'
            'sIkRpcmVjdGlvbiI6IkxlZnQifSx7IlRhcmdldEhhc2giOiI5OTc4MmUwOTVhM2YzZTc3Yjk0YTIxNjc1YmIzMmY0MDI2OGYz'
            'MDAwZDdjOTU0YjVjZTBlMjhhODdhMTU1NjMxIiwiRGlyZWN0aW9uIjoiTGVmdCJ9LHsiVGFyZ2V0SGFzaCI6IjFkMGVlNmVmZ'
            'GQwYTAzOTQ5NTE3NmRkNTljOTEwMTNjOGE5MTQ5MWIyMWQxN2RlYzg3NzRkY2RjN2E0OTZiYjYiLCJEaXJlY3Rpb24iOiJMZW'
            'Z0In0seyJUYXJnZXRIYXNoIjoiZjQyOWE1NDY2NDUzMjczNjU5NWFhZjk1MjFmZmJjZDllMGM3NGJiMjVkODBjOGJiN2NhNzc'
            'yY2VlOWIzODUwYSIsIkRpcmVjdGlvbiI6IkxlZnQifSx7IlRhcmdldEhhc2giOiJlYTk0NzgwZjQzZWE4NDJhMzNjMzY3MjQ4'
            'NDQzNGQxNmZlMDI5ZjE4MWZiYTAzNDEwMWZkYzFkZDAxNzM2MGI0IiwiRGlyZWN0aW9uIjoiTGVmdCJ9LHsiVGFyZ2V0SGFza'
            'CI6IjkzOTgxNjAwMDViMDdlMDgyYzA0ZGU0ZDIzYmE5YjYxZDNhYzFiYjRlNzJiZWRhMmY2N2U5MWIwMGI3MzllOTkiLCJEaX'
            'JlY3Rpb24iOiJMZWZ0In0seyJUYXJnZXRIYXNoIjoiM2IyNmQxMzA4NDFmMTQyNGYwMjFlODI2MzI5ODI2YjVlYTQxOTMzMWI'
            'yMjlhYmRjZjMwYmU2MmIzNDgyMTBhZCIsIkRpcmVjdGlvbiI6IkxlZnQifSx7IlRhcmdldEhhc2giOiIwNTU5NmIzYTY1Yjg1'
            'NjU4YmEyZDFjODcxYzMzYjY1ZGY3NDVlODA3YzBmNWVjZTU5YTIyNTE4N2YxZDIxYzRmIiwiRGlyZWN0aW9uIjoiTGVmdCJ9L'
            'HsiVGFyZ2V0SGFzaCI6ImRlMzE0YzgxMmU5NTVkZDBhNTczYzJkNGJjMzlmMmI3MGQxMTllNTBlNTZlMWRhMjVjYzMzOTM1N2'
            'QwOTA4OTAiLCJEaXJlY3Rpb24iOiJMZWZ0In1dLCJDb250cmFjdEFkZHIiOiIzNmJiNWMwNTNiNmI4MzljOGY2YjkyM2ZlODU'
            'yZjkxMjM5YjlmY2NjIn0=')
        try:
            sdk.default_network.connect_to_main_net()
            claim = sdk.service.claim()
            try:
                self.assertTrue(claim.validate_signature(b64_claim))
            except SDKException as e:
                if 'invalid claim head parameter' not in e.args[1]:
                    raise e
            claim.from_base64(b64_claim, True)
            self.assertTrue(claim.validate_blk_proof())
            self.assertTrue(isinstance(claim, Claim))
        finally:
            sdk.default_network.connect_to_localhost()
Esempio n. 2
0
class Claim(object):
    def __init__(self, sdk):
        self.__sdk = sdk
        self.__head = None
        self.__payload = None
        self.__signature = b''
        self.__blk_proof = BlockchainProof(sdk)

    def __iter__(self):
        data = dict(Header=dict(self.__head),
                    Payload=dict(self.__payload),
                    Signature=self.to_str_signature(),
                    Proof=dict(self.__blk_proof))
        for key, value in data.items():
            yield (key, value)

    @property
    def claim_id(self):
        if not isinstance(self.__payload, Payload):
            return ''
        return self.__payload.jti

    @property
    def head(self):
        return self.__head

    @head.setter
    def head(self, kid: str):
        if not isinstance(kid, str):
            raise SDKException(ErrorCode.require_str_params)
        self.__head = Header(kid)

    @property
    def payload(self):
        return self.__payload

    @property
    def signature(self):
        return self.__signature

    @property
    def blk_proof(self):
        return self.__blk_proof

    @blk_proof.setter
    def blk_proof(self, blk_proof: dict):
        self.__blk_proof.proof = blk_proof

    def set_claim(self,
                  kid: str,
                  iss_ont_id: str,
                  sub_ont_id: str,
                  exp: int,
                  context: str,
                  clm: dict,
                  clm_rev: dict,
                  jti: str = '',
                  ver: str = 'v1.0'):
        if not isinstance(jti, str):
            raise SDKException(ErrorCode.require_str_params)
        if jti == '':
            jti = Digest.sha256(uuid.uuid1().bytes, is_hex=True)
        self.__head = Header(kid)
        self.__payload = Payload(ver, iss_ont_id, sub_ont_id, int(time()), exp,
                                 context, clm, clm_rev, jti)

    def generate_signature(self, iss: Account, verify_kid: bool = True):
        if not isinstance(self.__head, Header) or not isinstance(
                self.__payload, Payload):
            raise SDKException(
                ErrorCode.other_error('Please set claim parameters first.'))
        if verify_kid:
            key_index = int(self.__head.kid.split('-')[1])
            result = self.__sdk.native_vm.ont_id().verify_signature(
                iss.get_ont_id(), key_index, iss)
            if not result:
                raise SDKException(
                    ErrorCode.other_error('Issuer account error.'))
        b64_head = self.__head.to_base64()
        b64_payload = self.__payload.to_base64()
        msg = f'{b64_head}.{b64_payload}'.encode('utf-8')
        self.__signature = iss.generate_signature(msg)
        return self.__signature

    def validate_signature(self, b64_claim: str):
        try:
            b64_head, b64_payload, b64_signature, _ = b64_claim.split('.')
        except ValueError:
            raise SDKException(ErrorCode.invalid_b64_claim_data)
        head = Header.from_base64(b64_head)
        payload = Payload.from_base64(b64_payload)
        signature = base64.b64decode(b64_signature)
        kid = head.kid
        iss_ont_id = payload.iss
        msg = f'{b64_head}.{b64_payload}'.encode('ascii')
        pk = ''
        pub_keys = self.__sdk.native_vm.ont_id().get_public_keys(iss_ont_id)
        if len(pub_keys) == 0:
            raise SDKException(ErrorCode.invalid_claim_head_params)
        for pk_info in pub_keys:
            if kid == pk_info.get('PubKeyId', ''):
                pk = pk_info.get('Value', '')
                break
        if pk == '':
            raise SDKException(ErrorCode.invalid_b64_claim_data)
        handler = SignatureHandler(head.alg)
        result = handler.verify_signature(pk, msg, signature)
        return result

    def to_bytes_signature(self):
        return self.__signature

    def to_str_signature(self):
        return self.__signature.decode('latin-1')

    def to_b64_signature(self):
        return base64.b64encode(self.to_bytes_signature()).decode('ascii')

    @staticmethod
    def from_base64_signature(b64_signature: str):
        return bytes.hex(base64.b64decode(b64_signature))

    def new_commit_tx(self,
                      b58_iss_address: str,
                      b58_payer_address: str,
                      gas_limit: int,
                      gas_price: int,
                      hex_contract_address: str = '') -> InvokeTransaction:
        if not isinstance(hex_contract_address, str):
            raise SDKException(ErrorCode.require_str_params)
        if len(hex_contract_address) == 40:
            self.__sdk.neo_vm.claim_record(
            ).hex_contract_address = hex_contract_address
        tx = self.__sdk.neo_vm.claim_record().new_commit_tx(
            self.payload.jti, b58_iss_address, self.payload.sub,
            b58_payer_address, gas_limit, gas_price)
        return tx

    def generate_blk_proof(self,
                           commit_tx_hash: str,
                           is_big_endian: bool = True,
                           hex_contract_address: str = ''):
        if len(hex_contract_address) == 0:
            hex_contract_address = self.__sdk.neo_vm.claim_record(
            ).hex_contract_address
        count = 0
        while True:
            try:
                merkle_proof = self.__sdk.default_network.get_merkle_proof(
                    commit_tx_hash)
                if isinstance(merkle_proof, dict):
                    break
            except SDKException as e:
                if count > 5 or 'INVALID PARAMS' not in e.args[1]:
                    raise e
                sleep(6)
            count += 1
        tx_block_height = merkle_proof['BlockHeight']
        current_block_height = merkle_proof['CurBlockHeight']
        target_hash = merkle_proof['TransactionsRoot']
        merkle_root = merkle_proof['CurBlockRoot']
        target_hash_list = merkle_proof['TargetHashes']
        proof_node = MerkleVerifier.get_proof(tx_block_height,
                                              target_hash_list,
                                              current_block_height)
        result = MerkleVerifier.validate_proof(proof_node, target_hash,
                                               merkle_root, is_big_endian)
        if not result:
            raise SDKException(ErrorCode.other_error('Invalid merkle proof'))
        self.__blk_proof.set_proof(commit_tx_hash, hex_contract_address,
                                   tx_block_height, merkle_root, proof_node)
        return self.__blk_proof

    def validate_blk_proof(self, is_big_endian: bool = True):
        return self.__blk_proof.validate_blk_proof(is_big_endian)

    def to_base64(self):
        b64_head = self.__head.to_base64()
        b64_payload = self.__payload.to_base64()
        b64_signature = self.to_b64_signature()
        b64_blockchain_proof = self.__blk_proof.to_base64()
        return f'{b64_head}.{b64_payload}.{b64_signature}.{b64_blockchain_proof}'

    def from_base64(self, b64_claim: str, is_big_endian: bool = True):
        try:
            b64_head, b64_payload, b64_signature, b64_blk_proof = b64_claim.split(
                '.')
        except ValueError:
            raise SDKException(ErrorCode.invalid_b64_claim_data)
        self.__head = Header.from_base64(b64_head)
        self.__payload = Payload.from_base64(b64_payload)
        self.__signature = base64.b64decode(b64_signature)
        self.__blk_proof = BlockchainProof(self.__sdk).from_base64(
            b64_blk_proof, is_big_endian)