def main(): # generate 16 bytes of entropy and # convert to a mnemonic phrase (12 words) entropy = bytes([urandom.getrandbits(8) for i in range(16)]) mnemonic = bip39.mnemonic_from_bytes(entropy) # or just define hardcoded: mnemonic = "alien visual jealous source coral memory embark certain radar capable clip edit" print(mnemonic) # convert to seed, empty password seed = bip39.mnemonic_to_seed(mnemonic) # convert to the root key # you can define the version - x/y/zprv for desired network root = bip32.HDKey.from_seed(seed, version=NETWORKS["test"]["xprv"]) print(root.to_base58()) print("\nBIP-44 - legacy") # derive account according to bip44 bip44_xprv = root.derive("m/44h/1h/0h") print(bip44_xprv.to_base58()) # corresponding master public key: bip44_xpub = bip44_xprv.to_public() print(bip44_xpub.to_base58()) # first 5 receiving addresses for i in range(5): # .key member is a public key for HD public keys # and a private key for HD private keys pub = bip44_xpub.derive("m/0/%d" % i).key sc = script.p2pkh(pub) print(sc.address(NETWORKS["test"])) print("\nBIP-84 - native segwit") # derive account according to bip84 bip84_xprv = root.derive("m/84h/1h/0h") # you can also change version of the key to get zpub (vpub on testnet) bip84_xprv.version = NETWORKS["test"]["zprv"] print(bip84_xprv.to_base58()) # corresponding master public key: bip84_xpub = bip84_xprv.to_public() print(bip84_xpub.to_base58()) # first 5 receiving addresses for i in range(5): pub = bip84_xpub.derive("m/0/%d" % i).key sc = script.p2wpkh(pub) print(sc.address(NETWORKS["test"])) print("\nBIP-49 - nested segwit") # derive account according to bip49 bip49_xprv = root.derive("m/49h/1h/0h") # you can also change version of the key to get ypub (upub on testnet) bip49_xprv.version = NETWORKS["test"]["yprv"] print(bip49_xprv.to_base58()) # corresponding master public key: bip49_xpub = bip49_xprv.to_public() print(bip49_xpub.to_base58()) # first 5 receiving addresses for i in range(5): pub = bip49_xpub.derive("m/0/%d" % i).key # use p2sh(p2wpkh(pubkey)) to get nested segwit scriptpubkey sc = script.p2sh(script.p2wpkh(pub)) print(sc.address(NETWORKS["test"]))
def main(): # all from the same private key prv = ec.PrivateKey.from_wif( "L2e5y14ZD3U1J7Yr62t331RtYe2hRW2TBBP8qNQHB8nSPBNgt6dM") pub = prv.get_public_key() print("Public key:") print(hexlify(pub.serialize())) # we will generate regtest addresses network = NETWORKS['regtest'] print("Legacy (pay to pubkey hash):") sc = script.p2pkh(pub) # default network is main print(sc.address(network)) print("Segwit (pay to witness pubkey hash):") sc = script.p2wpkh(pub) print(sc.address(network)) print("Nested segwit (p2sh-p2wpkh):") sc = script.p2sh(script.p2wpkh(pub)) print(sc.address(network)) print("\nMiltisig address (2 of 3):") # unsorted pubs = [ ec.PublicKey.parse( unhexlify( "02edd7a58d2ff1e483d35f92a32e53607423f936b29bf95613cab24b0b7f92e0f1" )), ec.PublicKey.parse( unhexlify( "03a4a6d360acc45cb281e0022b03218fad6ee93881643488ae39d22b854d9fa261" )), ec.PublicKey.parse( unhexlify( "02e1fdc3b011effbba4b0771eb0f7193dee24cfe101ab7e8b64516d83f7116a615" )), ] # 2 of 3 multisig script sc = script.multisig(2, pubs) print("Legacy, unsorted (p2sh):") redeem_sc = script.p2sh(sc) print(redeem_sc.address(network)) print("Native segwit, sorted (p2wsh):") sc = script.multisig(2, sorted(pubs)) witness_sc = script.p2wsh(sc) print(witness_sc.address(network)) print("Nested segwit, sorted (p2sh-p2wsh):") sc = script.multisig(2, sorted(pubs)) witness_sc = script.p2wsh(sc) redeem_sc = script.p2sh(witness_sc) print(redeem_sc.address(network))
async def showaddr(self, paths: list, script_type: str, redeem_script=None, show_screen=None) -> str: if redeem_script is not None: redeem_script = script.Script(unhexlify(redeem_script)) # first check if we have corresponding wallet: # - just take last 2 indexes of the derivation # and see if redeem script matches address = None if redeem_script is not None: if script_type == b"wsh": address = script.p2wsh(redeem_script).address( NETWORKS[self.network]) elif script_type == b"sh-wsh": address = script.p2sh(script.p2wsh(redeem_script)).address( NETWORKS[self.network]) else: raise HostError("Unsupported script type: %s" % script_type) # in our wallets every key # has the same two last indexes for derivation path = paths[0] if not path.startswith(b"m/"): path = b"m" + path[8:] derivation = bip32.parse_path(path.decode()) # if not multisig: if address is None and len(paths) == 1: pub = self.keystore.get_xpub(derivation) if script_type == b"wpkh": address = script.p2wpkh(pub).address(NETWORKS[self.network]) elif script_type == b"sh-wpkh": address = script.p2sh( script.p2wpkh(pub).address(NETWORKS[self.network])) else: raise WalletError("Unsupported script type: %s" % script_type) if len(derivation) >= 2: derivation = derivation[-2:] else: raise WalletError("Invalid derivation") if address is None: raise WalletError("Can't derive address. Provide redeem script.") try: change = bool(derivation[0]) w = self.find_wallet_from_address(address, derivation[1], change=change) except Exception as e: raise WalletError("%s" % e) if show_screen is not None: await show_screen( WalletScreen(w, self.network, derivation[1], change=change)) return address
def derive_address_from_keypath(keypath, address_pubkey, network): address_type_keypath = get_address_type_keypath(keypath) if address_type_keypath == BIP_44_KEYPATH: return p2pkh(address_pubkey).address(NETWORKS[network]) elif address_type_keypath == BIP_49_KEYPATH: redeem_script = p2wpkh(address_pubkey) return p2sh(redeem_script).address(NETWORKS[network]) elif address_type_keypath == BIP_84_KEYPATH: return p2wpkh(address_pubkey).address(NETWORKS[network]) else: raise Exception("Invalid keypath")
def scriptpubkey(self, derivation): """Derivation should be an array of two ints: [change 0 or 1, index]""" if len(derivation) != 2: raise ScriptError("Derivation should be of len 2") change, idx = derivation if change not in [0, 1] or idx < 0: raise ScriptError("Invalid change or index") pub = self.key.derive([change, idx]) return script.p2wpkh(pub)
async def showaddr(self, paths: list, script_type: str, redeem_script=None, show_screen=None) -> str: net = self.Networks[self.network] if redeem_script is not None: redeem_script = script.Script(unhexlify(redeem_script)) # first check if we have corresponding wallet: address = None if redeem_script is not None: if script_type == b"wsh": address = script.p2wsh(redeem_script).address(net) elif script_type == b"sh-wsh": address = script.p2sh(script.p2wsh(redeem_script)).address(net) elif script_type == b"sh": address = script.p2sh(redeem_script).address(net) else: raise WalletError("Unsupported script type: %s" % script_type) else: if len(paths) != 1: raise WalletError("Invalid number of paths, expected 1") path = paths[0] if not path.startswith("m/"): path = "m" + path[8:] derivation = bip32.parse_path(path) pub = self.keystore.get_xpub(derivation) if script_type == b"wpkh": address = script.p2wpkh(pub).address(net) elif script_type == b"sh-wpkh": address = script.p2sh(script.p2wpkh(pub)).address(net) elif script_type == b"pkh": address = script.p2pkh(pub).address(net) else: raise WalletError("Unsupported script type: %s" % script_type) w, (idx, branch_idx) = self.find_wallet_from_address(address, paths=paths) if show_screen is not None: await show_screen( WalletScreen(w, self.network, idx, branch_index=branch_idx)) addr, _ = w.get_address(idx, self.network, branch_idx) return addr
def parse_psbt(self, tx): # our result will be a dict of the form: # { # "total_in": amount_in_sat, # "spending": amount_in_sat, # "spending_outputs": [ (address, amount_in_sat), ... ], # "fee": amount_in_sat # } res = {} # print how much we are spending and where total_in = 0 for inp in tx.inputs: total_in += inp.witness_utxo.value res["total_in"] = total_in change_out = 0 # value that goes back to us send_outputs = [] for i, out in enumerate(tx.outputs): # check if it is a change or not: change = False # should be one or zero for single-key addresses for pub in out.bip32_derivations: # check if it is our key if out.bip32_derivations[pub].fingerprint == self.fingerprint: hdkey = self.root.derive( out.bip32_derivations[pub].derivation) mypub = hdkey.key.get_public_key() if mypub != pub: raise ValueError("Derivation path doesn't look right") # now check if provided scriptpubkey matches sc = script.p2wpkh(mypub) if sc == tx.tx.vout[i].script_pubkey: change = True continue if change: change_out += tx.tx.vout[i].value else: send_outputs.append(tx.tx.vout[i]) res["spending"] = total_in - change_out res["spending_outputs"] = [] fee = total_in - change_out for out in send_outputs: fee -= out.value res["spending_outputs"].append( (out.script_pubkey.address(self.network), out.value)) res["fee"] = fee return res
def host_callback(data): # close all existing popups popups.close_all_popups() if data=="fingerprint": usb_host.respond(hexlify(keystore.fingerprint).decode('ascii')) return if data.startswith("xpub "): path = data[5:].strip(" /\r\n") try: if path == "m": hd = keystore.root.to_public() else: hd = keystore.get_xpub(path) xpub = hd.to_base58(network["xpub"]) usb_host.respond(xpub) show_xpub("Master key requested from host:", path, hd) except Exception as e: print(e) usb_host.respond("error: bad derivation path '%s'" % path) return if data.startswith("sign "): def success_cb(signed_tx): usb_host.respond(signed_tx) def error_cb(error): usb_host.respond("error: %s" % error) parse_transaction(data[5:], success_callback=success_cb, error_callback=error_cb) return if data.startswith("showaddr "): arr = data.split(" ") path = arr[-1].strip() addrtype = "wpkh" if len(arr) > 2: addrtype = arr[-2].strip() # TODO: detect wallet this address belongs to try: key = keystore.get_xpub(path) if addrtype == "wpkh": sc = script.p2wpkh(key) elif addrtype == "pkh": sc = script.p2pkh(key) elif addrtype == "sh-wpkh": sc = script.p2sh(script.p2wpkh(key)) else: raise RuntimeError() addr=sc.address(network) usb_host.respond(addr) popups.qr_alert("Address with path %s\n(requested by host)" % (path), "bitcoin:"+addr, message_text=addr) except Exception as e: print(e) usb_host.respond("error: invalid argument") return if data.startswith("importwallet "): parse_new_wallet(data[13:])
def main(): # all from the same private key prv = ec.PrivateKey.from_wif("L2e5y14ZD3U1J7Yr62t331RtYe2hRW2TBBP8qNQHB8nSPBNgt6dM") pub = prv.get_public_key() inputs = [ # legacy { "txid": unhexlify("7f0c7538e898bbe5531fa47d4057b52c914ec45e20ae1a5572ea1005a8ba50f8"), "vout": 0, "value": int(1e8), "script": script.p2pkh(pub) }, # native segwit { "txid": unhexlify("f51e6fc2392558a70ae970e93538f368828ad2800a7370f372a652de463429fc"), "vout": 0, "value": int(2e8), "script": script.p2wpkh(pub) }, # nested segwit { "txid": unhexlify("2e4cb680ed008b6e529c4c83f00d55326a2e68b48ddf11267e3f5323006966a6"), "vout": 1, "value": int(3e8), "script": script.p2sh(script.p2wpkh(pub)), "redeem": script.p2wpkh(pub) } ] # sending back almost the same amount vin = [TransactionInput(inp["txid"], inp["vout"]) for inp in inputs] vout = [TransactionOutput(inp["value"]-1500, inp["script"]) for inp in inputs] tx = Transaction(vin=vin,vout=vout) print("Unsigned transaction:") print(hexlify(tx.serialize()).decode('utf-8')) for i in range(len(inputs)): inp = inputs[i] script_type = inp["script"].script_type() # legacy input if script_type == "p2pkh": h = tx.sighash_legacy(i, inp["script"]) sig = prv.sign(h) tx.vin[i].script_sig = script.script_sig_p2pkh(sig, pub) # native segwit elif script_type == "p2wpkh": sc = script.p2pkh_from_p2wpkh(inp["script"]) h = tx.sighash_segwit(i, sc, inp["value"]) sig = prv.sign(h) tx.vin[i].witness = script.witness_p2wpkh(sig, pub) # nested segwit elif script_type == "p2sh": if "redeem" in inp and inp["redeem"].script_type() == "p2wpkh": sc = script.p2pkh_from_p2wpkh(inp["redeem"]) h = tx.sighash_segwit(i, sc, inp["value"]) sig = prv.sign(h) tx.vin[i].script_sig = script.script_sig_p2sh(inp["redeem"]) tx.vin[i].witness = script.witness_p2wpkh(sig, pub) else: raise NotImplementedError("Script type is not supported") else: raise NotImplementedError("Script type is not supported") print("Signed transaction:") print(hexlify(tx.serialize()).decode('utf-8'))
self.title.set_text("Address #%d" % (idx+1)) self.title.align(self, lv.ALIGN.IN_TOP_MID, 0, 50) pub = self.account.derive([int(change), idx]).key addr = self.script_fn(pub).address(network=self.network) self.qr.set_text("bitcoin:"+addr) self.lbl.set_text(addr) self.lbl.set_align(lv.label.ALIGN.CENTER) self.lbl.align(self.qr, lv.ALIGN.OUT_BOTTOM_MID, 0, 20) self.prev_btn.set_state(lv.btn.STATE.INA if idx == 0 else lv.btn.STATE.REL) self.next_btn.align(self.qr, lv.ALIGN.OUT_BOTTOM_MID, 90, 70) self.prev_btn.align(self.qr, lv.ALIGN.OUT_BOTTOM_MID, -90, 70) def next_address(self, obj, event): if event == lv.EVENT.RELEASED: self._index += 1 self.show_address(self._index) def prev_address(self, obj, event): if event == lv.EVENT.RELEASED and self._index > 0: self._index -= 1 self.show_address(self._index) scr = AddressNavigator(root.derive("m/49h/1h/0h").to_public(), # bip-49 account xpub script_fn=lambda pub: script.p2sh(script.p2wpkh(pub)), # p2sh-p2wpkh network=NETWORKS["test"] # testnet ) lv.scr_load(scr)
# for Electrum and others who cares about SLIP-0132 # used for bip-84 by many wallets print("\nYour zpub:", account_pub.to_base58(version=NETWORKS["test"]["zpub"])) print("\nLegacy addresses:") xpub_bip44 = root_key.derive("m/44h/1h/0h").to_public() print("Legacy xpub:", xpub_bip44.to_base58(version=network["xpub"])) for i in range(5): # m/0/i is used for receiving addresses and m/1/i for change addresses pub = xpub_bip44.derive("m/0/%d" % i) # get p2pkh script sc = script.p2pkh(pub) print("Address %i: %s" % (i, sc.address(network))) print("\nSegwit addresses:") xpub_bip84 = root_key.derive("m/84h/1h/0h").to_public() print("Segwit zpub:", xpub_bip84.to_base58(version=network["zpub"])) for i in range(5): pub = xpub_bip84.derive("m/0/%d" % i) # get p2wsh script sc = script.p2wpkh(pub) print("Address %i: %s" % (i, sc.address(network))) print("\nNested segwit addresses:") xpub_bip49 = root_key.derive("m/49h/1h/0h").to_public() print("Nested Segwit ypub:", xpub_bip49.to_base58(version=network["ypub"])) for i in range(5): pub = xpub_bip49.derive("m/0/%d" % i) # get p2sh(p2wpkh) script sc = script.p2sh(script.p2wpkh(pub)) print("Address %i: %s" % (i, sc.address(network)))
print("Inputs:", total_in, "satoshi") change_out = 0 # value that goes back to us send_outputs = [] for i, out in enumerate(tx.outputs): # check if it is a change or not: change = False # should be one or zero for single-key addresses for pub in out.bip32_derivations: # check if it is our key if out.bip32_derivations[pub].fingerprint == fingerprint: hdkey = root.derive(out.bip32_derivations[pub].derivation) mypub = hdkey.key.get_public_key() if mypub != pub: raise ValueError("Derivation path doesn't look right") # now check if provided scriptpubkey matches sc = script.p2wpkh(mypub) if sc == tx.tx.vout[i].script_pubkey: change = True continue if change: change_out += tx.tx.vout[i].value else: send_outputs.append(tx.tx.vout[i]) print("Spending", total_in - change_out, "satoshi") fee = total_in - change_out for out in send_outputs: fee -= out.value print(out.value, "to", out.script_pubkey.address(NETWORKS["test"])) print("Fee:", fee, "satoshi") # sign the transaction
def address(self, idx, change=False): sc = script.p2wpkh(self.account.derive([int(change), idx])) return sc.address(self.network)
def main(): # get root key from the mnemonic mnemonic = "alien visual jealous source coral memory embark certain radar capable clip edit" seed = bip39.mnemonic_to_seed(mnemonic) root = bip32.HDKey.from_seed(seed, version=NETWORKS["test"]["xprv"]) # get bip84-xpub to import to Bitcoin Core: # we will use the form [fingerprint/derivation]xpub # to import to Bitcoin Core with descriptors # first let's get the root fingerprint # we can get it from any child of the root key fingerprint = root.child(0).fingerprint hardened_derivation = "m/84h/1h/0h" # derive account according to bip84 bip84_xprv = root.derive(hardened_derivation) # corresponding master public key: bip84_xpub = bip84_xprv.to_public() print("[%s%s]%s" % (hexlify(fingerprint).decode('utf-8'), hardened_derivation[1:], bip84_xpub.to_base58())) # parse psbt transaction b64_psbt = "cHNidP8BAHICAAAAAY3LB6teEH6qJHluFYG3AQe8n0HDUcUSEuw2WIJ1ECDUAAAAAAD/////AoDDyQEAAAAAF6kU882+nVMDKGj4rKzjDB6NjyJqSBCHaPMhCgAAAAAWABQUbW8/trQg4d3PKL8WLi2kUa1BqAAAAAAAAQEfAMLrCwAAAAAWABTR6Cr4flM2A0LMGjGiaZ+fhod37SIGAhHf737H1jCUjkJ1K5DqFkaY0keihxeWBQpm1kDtVZyxGLMX7IZUAACAAQAAgAAAAIAAAAAAAAAAAAAAIgIDPtTTi27VFw59jdmWDV8b1YciQzhYGO7m8zB9CvD0brcYsxfshlQAAIABAACAAAAAgAEAAAAAAAAAAA==" # first convert it to binary raw = a2b_base64(b64_psbt) # then parse tx = psbt.PSBT.parse(raw) # print how much we are spending and where total_in = 0 for inp in tx.inputs: total_in += inp.witness_utxo.value print("Inputs:", total_in, "satoshi") change_out = 0 # value that goes back to us send_outputs = [] for i, out in enumerate(tx.outputs): # check if it is a change or not: change = False # should be one or zero for single-key addresses for pub in out.bip32_derivations: # check if it is our key if out.bip32_derivations[pub].fingerprint == fingerprint: hdkey = root.derive(out.bip32_derivations[pub].derivation) mypub = hdkey.key.get_public_key() if mypub != pub: raise ValueError("Derivation path doesn't look right") # now check if provided scriptpubkey matches sc = script.p2wpkh(mypub) if sc == tx.tx.vout[i].script_pubkey: change = True continue if change: change_out += tx.tx.vout[i].value else: send_outputs.append(tx.tx.vout[i]) print("Spending", total_in - change_out, "satoshi") fee = total_in - change_out for out in send_outputs: fee -= out.value print(out.value, "to", out.script_pubkey.address(NETWORKS["test"])) print("Fee:", fee, "satoshi") # sign the transaction tx.sign_with(root) raw = tx.serialize() # convert to base64 b64_psbt = b2a_base64(raw) # somehow b2a ends with \n... if b64_psbt[-1:] == b"\n": b64_psbt = b64_psbt[:-1] # print print("\nSigned transaction:") print(b64_psbt.decode('utf-8'))