def main(): # always remember to setup the network setup('mainnet') # create a private key (deterministically) priv = PrivateKey(secret_exponent=1) # compressed is the default print("\nPrivate key WIF:", priv.to_wif(compressed=True)) # could also instantiate from existing WIF key #priv = PrivateKey.from_wif('KwDiBf89qGgbjEhKnhxjUh7LrciVRzI3qYjgd9m7Rfu73SvHnOwn') # get the public key pub = priv.get_public_key() # compressed is the default print("Public key:", pub.to_hex(compressed=True)) # get address from public key address = pub.get_address() # print the address and hash160 - default is compressed address print("Address:", address.to_string()) print("Hash160:", address.to_hash160()) print("\n--------------------------------------\n") # sign a message with the private key and verify it message = "The test!" signature = priv.sign_message(message) print("The message to sign:", message) print("The signature is:", signature) if PublicKey.verify_message(address.to_string(), signature, message): print("The signature is valid!") else: print("The signature is NOT valid!")
def test_pubkey_to_hash160(self): pub = PublicKey(self.public_key_hex) self.assertEqual(pub.get_address().to_hash160(), pub.to_hash160())
def test_pubkey_uncompressed(self): pub = PublicKey(self.public_key_hexc) self.assertEqual(pub.to_hex(compressed=False), self.public_key_hex)
def test_get_uncompressed_address(self): pub = PublicKey(self.public_key_hex) self.assertEqual( pub.get_address(compressed=False).to_string(), self.address)
def test_pubkey_creation(self): pub1 = PublicKey(self.public_key_hex) self.assertEqual(pub1.to_bytes(), self.public_key_bytes) pub2 = PublicKey(self.public_key_hexc) self.assertEqual(pub2.to_bytes(), self.public_key_bytes)
def test_verify_external(self): self.assertTrue( PublicKey.verify_message(self.external_address, self.external_signature, self.message))
def test_sign_and_verify(self): signature = self.priv.sign_message(self.message) self.assertEqual(signature, self.deterministic_signature) self.assertTrue( PublicKey.verify_message(self.address, signature, self.message))
def validate_certificate(cert, issuer_identifier, blockchain_services): filename = os.path.basename(cert) tmp_filename = '__' + filename shutil.copy(cert, tmp_filename) # returned proof can be ignored here but could compare with proof later on issuer_address, _ = get_issuer_address_and_proof(tmp_filename) proof = get_and_remove_chainpoint_proof(tmp_filename) if proof == None: return False, "no chainpoint_proof in metadata" # get the hash after removing the metadata filehash = '' with open(tmp_filename, 'rb') as pdf_file: filehash = hashlib.sha256(pdf_file.read()).hexdigest() # instantiate chainpoint object cp = ChainPointV2() chain, testnet, txid = get_chain_testnet_txid_from_chainpoint_proof( proof, issuer_address) # make request to get txs regarding this address # issuance is the first element of data_before_issuance data_before_issuance, data_after_issuance = \ network_utils.get_all_op_return_hexes(issuer_address, txid, blockchain_services, chain, testnet) # validate receipt valid, reason = cp.validate_receipt(proof, data_before_issuance[0], filehash, issuer_identifier) # display error except when the certificate expired; this is because we want # revoked certificate error to be displayed before cert expired error # TODO clean hard-coded reason if not valid and not reason.startswith("certificate expired"): return False, reason # load apropriate blockchain libraries if (chain == 'litecoin'): from litecoinutils.setup import setup from litecoinutils.keys import P2pkhAddress, P2wpkhAddress, PublicKey from litecoinutils.utils import is_address_bech32 else: from bitcoinutils.setup import setup from bitcoinutils.keys import P2pkhAddress, P2wpkhAddress, PublicKey from litecoinutils.utils import is_address_bech32 # set appropriate network (required for addr->pkh in revoke address) if testnet: setup('testnet') else: setup('mainnet') # check if cert's issuance is after a revoke address cmd on that address # and if yes then the issuance is invalid (address was revoked) # we check before checking for cert revocations since if the issuance was # after an address revocation it should show that as an invalid reason # 0 index is the actual issuance -- ignore it for i in range(len(data_before_issuance))[1:]: cred_dict = cred_protocol.parse_op_return_hex(data_before_issuance[i]) if cred_dict: if cred_dict['cmd'] == cred_protocol.hex_op('op_revoke_address'): if (is_address_bech32(issuer_address)): issuer_pkh = P2wpkhAddress(issuer_address).to_hash160() else: issuer_pkh = P2pkhAddress(issuer_address).to_hash160() if issuer_pkh == cred_dict['data']['pkh']: return False, "address was revoked" # check if cert or batch was revoked from oldest to newest for op_return in reversed(data_after_issuance): cred_dict = cred_protocol.parse_op_return_hex(op_return) if cred_dict: if cred_dict['cmd'] == cred_protocol.hex_op('op_revoke_batch'): if txid == cred_dict['data']['txid']: return False, "batch was revoked" elif cred_dict['cmd'] == cred_protocol.hex_op('op_revoke_creds'): if txid == cred_dict['data']['txid']: # compare the certificate hash bytes filehash_bytes = utils.hex_to_bytes(filehash) ripemd_filehash = utils.ripemd160(filehash_bytes) ripemd_hex = utils.bytes_to_hex(ripemd_filehash) if ripemd_hex == cred_dict['data']['hashes'][0]: return False, "cert hash was revoked" if len(cred_dict['data']['hashes']) > 1: if ripemd_hex == cred_dict['data']['hashes'][1]: return False, "cert hash was revoked" elif cred_dict['cmd'] == cred_protocol.hex_op('op_revoke_address'): # if address revocation is found stop looking since all other # revocations will be invalid if (is_address_bech32(issuer_address)): issuer_pkh = P2wpkhAddress(issuer_address).to_hash160() else: issuer_pkh = P2pkhAddress(issuer_address).to_hash160() if issuer_pkh == cred_dict['data']['pkh']: break # if not revoked but not valid this means that it was expired; now that we # checked for revocations we can show the expiry error if not valid: return False, reason # now that the issuer (anchoring) was validated validate the certificate # with the owner's public key (vpdf v2) # get owner and owner_proof removing the latter owner, owner_proof = get_owner_and_remove_owner_proof(tmp_filename) if owner: # get public key pk = PublicKey.from_hex(owner['pk']) # get file hash sha256_hash = None with open(tmp_filename, 'rb') as pdf: sha256_hash = hashlib.sha256(pdf.read()).hexdigest() # finally check if owner signature is valid #print(pk.get_address().to_string(), pk.to_hex(), sha256_hash, owner_proof) try: if (pk.verify(owner_proof, sha256_hash)): pass except Exception: #BadSignatureError: return False, 'owner signature could not be validated' finally: # cleanup os.remove(tmp_filename) else: # cleanup now that we know it validated os.remove(tmp_filename) # in a valid credential the reason could contain an expiry date return True, reason
def _fill_pdf_metadata(out_file, issuer, issuer_address, column_fields, data, global_columns, verify_issuer, conf, interactive=False): # create version version = 2 # create issuer object (json) issuer = { "name": issuer, "identity": { "address": issuer_address, "verification": json.loads(verify_issuer)['methods'] } } # create metadata object (json) and add metadata metadata = {} # add custom metadata if column_fields: metadata_fields = json.loads(column_fields)['columns'] for f in metadata_fields: key = list(f)[0] if key in data: field_properties = f[key] field_properties['value'] = data[key] metadata[key] = field_properties # add global field metadata if global_columns: global_fields = json.loads(global_columns)['fields'] for g in global_fields: key = list(g)[0] # note that global fields override column data metadata[key] = g[key] # now look at special owner name/pubkey columns explicitly in code # TODO we should probably check if the public key is valid owner = None owner_address = None owner_pk = None if '__OWNER_PK__' in data and data[ '__OWNER_PK__'] and '__OWNER_ADDRESS__' in data and data[ '__OWNER_ADDRESS__']: # TODO maybe just calculate address from public key? owner_address = data['__OWNER_ADDRESS__'] owner_pk = data['__OWNER_PK__'] owner = { "name": data['__OWNER_NAME__'], "owner_address": data['__OWNER_ADDRESS__'], # TODO needed? - can be derived "pk": owner_pk } # add the metadata pdf_metadata = PdfDict(version=version, issuer=json.dumps(issuer), metadata=json.dumps(metadata), owner=json.dumps(owner), owner_proof='', chainpoint_proof='') else: # add the metadata (without dumps(owner) to keep owner empty) pdf_metadata = PdfDict(version=version, issuer=json.dumps(issuer), metadata=json.dumps(metadata), owner='', owner_proof='', chainpoint_proof='') pdf = PdfReader(out_file) if pdf.Info: pdf.Info.update(pdf_metadata) else: pdf.Info = pdf_metadata PdfWriter().write(out_file, pdf) # if owner exists then need to add owner_proof # hash pdf, sign hash message using node and add in owner_proof if owner: ##import time ##start = time.time() sha256_hash = None with open(out_file, 'rb') as pdf: sha256_hash = hashlib.sha256(pdf.read()).hexdigest() if (conf.blockchain == 'litecoin'): from litecoinutils.setup import setup from litecoinutils.proxy import NodeProxy from litecoinutils.keys import PublicKey else: from bitcoinutils.setup import setup from bitcoinutils.proxy import NodeProxy from bitcoinutils.keys import PublicKey if (conf.testnet): setup('testnet') else: setup('mainnet') host, port = conf.full_node_url.split( ':') #TODO: update when NodeProxy accepts full url! proxy = NodeProxy(conf.full_node_rpc_user, conf.full_node_rpc_password, host, port).get_proxy() # Due to an old unresolved issue still pending in Bitcoin v0.20.0 # signmessage does not support signing with bech32 key. # To resolve we use the public key to get the base58check encoding that # signmessage is happy with so that we can sign! if (owner_address.startswith('bc') or owner_address.startswith('tb') or owner_address.startswith('ltc') or owner_address.startswith('tltc')): owner_address = PublicKey(owner_pk).get_address().to_string() # NOTE that address (the encoding) might have changed here from bech32 # to legacy... take care if you use it again in this function! sig = proxy.signmessage(owner_address, sha256_hash) # add owner_proof to metadata pdf_metadata = PdfDict(owner_proof=sig) pdf = PdfReader(out_file) pdf.Info.update(pdf_metadata) PdfWriter().write(out_file, pdf) ##end = time.time() ##print(end-start, " seconds") ##exit() if interactive: # print progress print('.', end="", flush=True)