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