def add_in_transaction(self, tx_hash, index: int, keys: KeysBTC): """Add an input to the transaction""" # Convert tx_hash to bytes if it's str tx_hash = bytes.fromhex(tx_hash) \ if isinstance(tx_hash, str) else tx_hash # Init a dictionary for an input tx_in = {} # Previous transaction hash -- tx_hash tx_in["previous_tx_hash"] = tx_hash[::-1] # Output point index -- index tx_in["previous_txout_index"] = struct.pack("<L", index) # Temporary signature script = output script # Generate a script with BTC-address for -- keys tx_in["script_sig"] = self.get_script_p2pkh(keys.get_pubkey_hash()) # Temporary signature script length tx_in["script_length"] = struct.pack("<B", len(tx_in["script_sig"])) # Sequence 0xFFFFFFFF tx_in["sequence"] = b"\xff\xff\xff\xff" # Set a KeysBTC object using to sign this input tx_in["keys"] = keys # Add the dictionary with the input to the transaction self.ins[len(self.ins)] = tx_in return
def add_out_transaction(self, to_hash, value: int, tx_type: str = "p2pkh"): """Add an output to the transaction""" # Convert to_hash to public key hash if it's str to_hash = KeysBTC.address_to_pubkey_hash(to_hash) \ if isinstance(to_hash, str) else to_hash # Init a dictionary for an output tx_out = {} # Amount in satoshies (8 bytes) -- value tx_out["value"] = struct.pack("<Q", value) # Output script (depends on type: p2pkh or p2sh) if tx_type == "p2pkh": tx_out["script_pubkey"] = self.get_script_p2pkh(to_hash) elif tx_type == "p2sh": tx_out["script_pubkey"] = self.get_script_p2sh(to_hash) else: raise ValueError("Invalid transaction type: {:s}".format(tx_type)) # Output script length tx_out["script_length"] = \ struct.pack("<B", len(tx_out["script_pubkey"])) # Add the dictionary with the output to the transaction self.outs[len(self.outs)] = tx_out return
def serialize(self): """Return serialized format (str) of this key (xprv / xpub)""" if self.is_private(): ser_key = ( MAINNET_PRIVATE + int2bytes(self.level, 1) + self.fingerprint + int2bytes(self.index, 4) + self.chain_code + b"\x00" + self.key ) elif self.is_public(): ser_key = ( MAINNET_PUBLIC + int2bytes(self.level, 1) + self.fingerprint + int2bytes(self.index, 4) + self.chain_code + KeysBTC.point_to_publickey(self.key) ) else: raise ValueError("Invalid extended key") checksum = sha256(sha256(ser_key))[:4] return base58_encode(ser_key + checksum)
def prv_to_child(parent_prv: ExtendedKey, index: int): """Return an extended child private key. Parameters: parent_prv -- a parent private key, index -- an index of a parent private key """ cur_key = KeysBTC(parent_prv.key) ser32_index = int2bytes(index, 4) # If a hardened index the take the private key, # otherwise, take the public key. if index >= 2 ** 31: data = b"\x00" + cur_key.get_private_key() + ser32_index else: data = cur_key.get_public_key() + ser32_index child_hash = hmac_sha512(parent_prv.chain_code, data) child_hash_left = bytes2int(child_hash[:32]) k_i = (child_hash_left + cur_key.get_private_key_int()) % \ ECPoint.get_secp256k1_order() # Check the left part if child_hash_left >= ECPoint.get_secp256k1_order() or k_i == 0: raise ValueError("The resulting key is invalid") # The right part is child_hash[32:] return ExtendedKey( int2bytes(k_i), child_hash[32:], parent_prv.level + 1, # increase level index, ExtendedKey.get_fingerprint(cur_key.get_public_key()) )
def pub_to_child(parent_pub: ExtendedKey, index: int): """Return an extended child public key. Parameters: parent_pub -- a parent public key, index -- an index of a parent public key """ # Check if index is not a hardened key if index >= 2 ** 31: raise ValueError( "Cannot generate a child public key because " "it is a hardened key" ) ser32_index = int2bytes(index, 4) public_key = KeysBTC.point_to_publickey(parent_pub.key) data = public_key + ser32_index child_hash = hmac_sha512(parent_pub.chain_code, data) child_hash_left = bytes2int(child_hash[:32]) K_i = KeysBTC.get_generator_point() * child_hash_left + parent_pub.key # Check the left part if child_hash_left >= ECPoint.get_secp256k1_order() or \ K_i == ECPoint.infinity(): raise ValueError( "The resulting key is invalid for index {:d}".format(index) ) # The right part is child_hash[32:] return ExtendedKey( K_i, child_hash[32:], parent_pub.level + 1, # increase level index, ExtendedKey.get_fingerprint(public_key) )
def __init__(self, master: ExtendedKey, level_indexes=None): """Construct an object. Parameters: master -- an extended master (root) key (ExtendedKey) level_indexes -- a path to the last key level (list), ex. for m/0/1/0/<list> - [0, 1, 0] """ self.level_indexes = [] if level_indexes is None else level_indexes if master.is_private(): self.master_prv = master self.master_pub = \ ExtendedKey( KeysBTC(self.master_prv.key).get_public_point(), self.master_prv.chain_code ) elif master.is_public(): self.master_prv = None self.master_pub = master else: raise ValueError("Invalid master key: {:s}", master)
# --- Usage and testing --- if __name__ == "__main__": from btc.utils import sha256 from btc.keys import KeysBTC k = KeysBTC("96a69d6682a4b2eb522e896c2fa1b8ada485c472b983e27266d1d5c8c77ec374") # Check the right values at https://www.bitaddress.org # An object methods print("Private key (hex): ", k.get_private_key_hex()) print("Private key (WIF-compressed): ", k.get_private_key_wif(True)) print("Private key (WIF-uncompressed): ", k.get_private_key_wif(False)) print("Public point: ", k.get_public_point()) print("Public key (hex): ", k.get_public_key(compressed=True).hex(),) print("Is public key compressed: ", k.is_pubkey_compressed()) print("Public key HASH160: ", k.get_pubkey_hash().hex()) print("Address: ", k.get_address()) # Static methods (convertions) print("Generator point: ", k.get_generator_point()) print("Private key to WIF: ", k.privatekey_to_wif(k._private_key, compressed=False)) print("WIF to private key", k.privatekey_from_wif(k.get_private_key_wif()).hex()) print(k.address_to_pubkey_hash(k.get_address()).hex()) # Sign and verify message = sha256(b"sample") r, s = k.sign(message) print(k.verify(message, r, s))
if __name__ == "__main__": from btc.keys import KeysBTC from btc.transact import TransactBTC # Prepare transaction keys_from = KeysBTC( "5842f1ee4fe0517a09acf03a21798bd88b30611e34a3a6092ac2ae4c27c2ae27") keys_from.get_address(version=keys_from.get_addr_ver_test()) # testnet print(keys_from) # Prepare a transaction tx = TransactBTC(keys_from) tx.add_in_transaction( # previous ttransaction hash "fd17a13054a3c1647120d5280f416878d5f375ccc864d29e7902cfd8fbde6284", 1, # output keys_from # KeysBTC object for signing ) # for example, the output is 0.50000000 btc tx.add_out_transaction( "2N8hwP1WmJrFF5QWABn38y63uYLhnJYJYTF", 49950000, # value tx_type="p2sh" # pay-to-script-hash ) tx.sign_all_inputs() print(tx)
[ BIP32.hardened_index(44), BIP32.hardened_index(0), BIP32.hardened_index(0) ] # path -- m/44'/0'/0' ) # Master private print(bip32_1.master_prv.serialize()) # Master public print(bip32_1.master_pub.serialize()) # Child private keys with indexes in [0, 9] from parent private key child_prv = [ KeysBTC.privatekey_to_wif(c.key) for c in bip32_1.ckd_priv(range(0, 10)) ] print("Child private keys from the parent private m/44'/0'/0'/[0, 9]: ", dict(zip(range(0, 10), child_prv))) # BIP32 with m/517/377/11 root_pub = ExtendedKey( KeysBTC(root_prv.key).get_public_point(), root_prv.chain_code) bip32_2 = BIP32(root_pub, [517, 377, 11]) # path -- m/517/377/11 # Child public keys from parent public key, only for non-hardened keys child_public = [ KeysBTC.point_to_publickey(c.key).hex() for c in bip32_2.ckd_pub(range(0, 10)) ]