def main(): # generate 16 bytes of entropy and # convert to a mnemonic phrase (12 words) entropy = bytes([random.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("\nMultisig 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))
def scriptpubkey(self, idx=None): if idx is None and "*" in self.serialize(): raise RuntimeError("Index is required") if self.is_multisig: keys = [] for i, key in enumerate(self.base_key): keys.append(derive_pubkey(key, self.path_suffix[i], idx)) if self.sort_keys: keys = sorted(keys) sc = script.multisig(int(self.multisig_M), keys) if self.sh: return script.p2sh(sc) elif self.sh_wsh: return script.p2sh(script.p2wsh(sc)) elif self.wsh: return script.p2wsh(sc) else: key = derive_pubkey(self.base_key, self.path_suffix, idx) if self.wpkh: return script.p2wpkh(key) elif self.sh_wpkh: return script.p2sh(script.p2wpkh(key)) else: return script.p2pkh(key)
def main(): #Extended public key string to an address, tested against https://iancoleman.io/bip39/ #Mnemonic used: february deposit cram leopard ripple turtle impulse history accident noodle love crazy limit pond tourist #bip44, first address "124d7o3DmtXqvn1dJLxj9TQYA7v9Cmcyes" key = "xpub6D2VGgnY7WJ8H3SKBPhqbarfA6uZbcUSLnuwgzE5qvmXyq23yNg42GKJgDXMJ27Q3NBeFjQDyz8Za27sYUfmWs7PNow8i3FUNwHufbyyWQK" #bip49, first address "3CntmFG5nBG9sCCr2b6MWhTAk9RLEicERn" #key = "ypub6YFkZihjEjQdftpu4NwwNobdTB7eRHQjcTjyDj4hhANcj3KqXVTQuEuz1rL49NtzFLCxvnSAd4gnCG1yfWdDv1evXSrMKZfCcYQ3yR6eyP2" #bip84, first address "bc1qv5rgjspu3kljw8kczdvyl4x0gkk09jp7ddmrp8" #key = "zpub6riY8dgyiTYHfTE3xqKscWYjZrKtwWwtc4LBfvnkqPHH9gUAC4HJrydyphFihghnGnEw5cyDDgq5iZULuheBJqJW825Cv5LCgUh7VJTTLhw" k = bip32.HDKey.from_base58(key) child = k.derive([0, 0]) if key[0:4] == "xpub": address = script.p2pkh(child).address() elif key[0:4] == "zpub": address = script.p2wpkh(child).address() elif key[0:4] == "ypub": address = script.p2sh(script.p2wpkh(child)).address() print(address)
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'))
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"))
def __parseOutputs(self): self.spend_amount = 0 self.change_amount = 0 self.fee_amount = 0 self.destination_addresses = [] self.self_addresses = [] for i, out in enumerate(self.psbt.outputs): out_policy = PSBTParser.__get_policy( out, self.psbt.tx.vout[i].script_pubkey, self.psbt.xpubs) is_change = False # if policy is the same - probably change if out_policy == self.policy: # double-check that it's change # we already checked in get_cosigners and parse_multisig # that pubkeys are generated from cosigners, # and witness script is corresponding multisig # so we only need to check that scriptpubkey is generated from # witness script # empty script by default sc = script.Script(b"") # multisig, we know witness script if self.policy["type"] == "p2wsh": sc = script.p2wsh(out.witness_script) elif self.policy["type"] == "p2sh-p2wsh": sc = script.p2sh(script.p2wsh(out.witness_script)) # single-sig elif "pkh" in self.policy["type"]: my_pubkey = None # should be one or zero for single-key addresses if len(out.bip32_derivations.values()) > 0: der = list( out.bip32_derivations.values())[0].derivation my_pubkey = self.root.derive(der) if self.policy[ "type"] == "p2wpkh" and my_pubkey is not None: sc = script.p2wpkh(my_pubkey) elif self.policy[ "type"] == "p2sh-p2wpkh" and my_pubkey is not None: sc = script.p2sh(script.p2wpkh(my_pubkey)) if sc.data == self.psbt.tx.vout[i].script_pubkey.data: is_change = True if sc.data == self.psbt.tx.vout[i].script_pubkey.data: is_change = True if is_change: self.change_amount += self.psbt.tx.vout[i].value self.self_addresses.append( self.psbt.tx.vout[i].script_pubkey.address( NETWORKS[self.network])) else: self.spend_amount += self.psbt.tx.vout[i].value self.destination_addresses.append( self.psbt.tx.vout[i].script_pubkey.address( NETWORKS[self.network])) self.fee_amount = self.psbt.fee() return True
def change_fee_spend_amounts( self, tx, inp_amount, policy, currentnetwork) -> (float, float, float, str, float, float): spend = 0 change = 0 destinationaddress = "" dest_addr_cnt = 0 outs = 0 for i, out in enumerate(tx.outputs): outs += 1 out_policy = self.get_policy(out, tx.tx.vout[i].script_pubkey, tx.xpubs) is_change = False # if policy is the same - probably change if out_policy == policy: # double-check that it's change # we already checked in get_cosigners and parse_multisig # that pubkeys are generated from cosigners, # and witness script is corresponding multisig # so we only need to check that scriptpubkey is generated from # witness script # empty script by default sc = script.Script(b"") # multisig, we know witness script if policy["type"] == "p2wsh": sc = script.p2wsh(out.witness_script) elif policy["type"] == "p2sh-p2wsh": sc = script.p2sh(script.p2wsh(out.witness_script)) # single-sig native segwit elif "pkh" in policy["type"]: 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: is_change = True if sc.data == tx.tx.vout[i].script_pubkey.data: is_change = True if is_change: change += tx.tx.vout[i].value print("Change %d sats" % tx.tx.vout[i].value) else: spend += tx.tx.vout[i].value print( "Spending %d sats to %s" % (tx.tx.vout[i].value, tx.tx.vout[i].script_pubkey.address( NETWORKS[currentnetwork]))) destinationaddress = tx.tx.vout[i].script_pubkey.address( NETWORKS[currentnetwork]) dest_addr_cnt += 1 fee = inp_amount - change - spend return (change, fee, spend, destinationaddress, outs, dest_addr_cnt)
def main(): # mnemonic we use mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" seed = bip39.mnemonic_to_seed(mnemonic) root = bip32.HDKey.from_seed(seed) # A few transactions b64psbts = [ # p2wsh, 2 inputs "cHNidP8BALICAAAAAq1DhxRK+mUH4T6uUNob8bUaZ7MP+44MW4+Y9bOxpjhZAAAAAAD9////aWclWQ+45HKrI07r878E2UrAupT2paT4QurbmtNjYNQBAAAAAP3///8CQEIPAAAAAAAiACCpkDPDhmIzPlkJrjw9A71xjbIUWf3VUB7ooFJhTVm04tjSIQEAAAAAIgAgjQKFDauIXsV5u23LBdYgOwX1FwGGrLiQfWzBtFKZ7dIAAAAATwEENYfPBD5i336AAAACQStJhNVJul7vHKbo83VdmuAW2m0WaXLKDlFANn7dUNoCNbhLMdw4Knz7Q7o6exdL6UFhQegW9nJb0SUStbLEpawUAgjLdzAAAIABAACAAAAAgAIAAIBPAQQ1h88EnbHQAIAAAAI/2Nc7x7iMpJNapTe/OJTV4oifqzQcYY9KV2+PGRjCdQJoww1WnSNqfcxXGyux0q1PqfmzUqgJNqKJCpmqI9t47BQmu4PEMAAAgAEAAIAAAACAAgAAgE8BBDWHzwS6wUg5gAAAAh1Pvr3ZZ+GvcUwJl9OPz2cLXOnTAcBEC7zDtqIOt3IcA1aOofNgUZFu0baQw54SqOcGA7KAvTDOXygfKRilU2OqFHPF2gowAACAAQAAgAAAAIACAACAAAEBK4CWmAAAAAAAIgAgiYAxcG7dnrEiZ4VHFVHOo18XCalvhZYuMqBr9n7HESQBBWlSIQJOjQgMfX26XEf+trHIEk3rYkEX5Y2NfrFKQARPcd2X8iEDBWHUgq25PfHvE+hlcBryJG7wo2y8jKUSPY7sd85OOMchA2iVcuKLD+2p1pgcAjfZ5d7b/sFt5xQ/aAoC7V0Vn3WHU64iBgJOjQgMfX26XEf+trHIEk3rYkEX5Y2NfrFKQARPcd2X8hwmu4PEMAAAgAEAAIAAAACAAgAAgAAAAAABAAAAIgYDBWHUgq25PfHvE+hlcBryJG7wo2y8jKUSPY7sd85OOMccAgjLdzAAAIABAACAAAAAgAIAAIAAAAAAAQAAACIGA2iVcuKLD+2p1pgcAjfZ5d7b/sFt5xQ/aAoC7V0Vn3WHHHPF2gowAACAAQAAgAAAAIACAACAAAAAAAEAAAAAAQErgJaYAAAAAAAiACAzd60wM9EFnPHSNbsSJfyipL8myVLVP2/vwzotVUSNxQEFaVIhAiKCMRLlzIhLkRbLIUIMx5KYJM0v6LcjW/mS6K7eFGwiIQKDzUflU23LeecRgzDo5IBCEvaWGfHW7JkNxzXvuc7FdCEDC5DtLoa61/Kk/pdpu0F9e6nKoRJIB9v7Ni377rZefgFTriIGAiKCMRLlzIhLkRbLIUIMx5KYJM0v6LcjW/mS6K7eFGwiHAIIy3cwAACAAQAAgAAAAIACAACAAAAAAAAAAAAiBgKDzUflU23LeecRgzDo5IBCEvaWGfHW7JkNxzXvuc7FdBwmu4PEMAAAgAEAAIAAAACAAgAAgAAAAAAAAAAAIgYDC5DtLoa61/Kk/pdpu0F9e6nKoRJIB9v7Ni377rZefgEcc8XaCjAAAIABAACAAAAAgAIAAIAAAAAAAAAAAAABAWlSIQKtIdmtKKuZrH7f2R4iIU8RWVOrCdHVWBCS+0e9pZJy/iEDoH074LrWPIA10hyXtBCJDT06GdLkA6+z/PxoJqomPHYhA6GoQ/otQdk71nUpYZFfbkSKdBkkSj4CuPTPYrzGp6JrU64iAgKtIdmtKKuZrH7f2R4iIU8RWVOrCdHVWBCS+0e9pZJy/hwCCMt3MAAAgAEAAIAAAACAAgAAgAEAAAAAAAAAIgIDoH074LrWPIA10hyXtBCJDT06GdLkA6+z/PxoJqomPHYcc8XaCjAAAIABAACAAAAAgAIAAIABAAAAAAAAACICA6GoQ/otQdk71nUpYZFfbkSKdBkkSj4CuPTPYrzGp6JrHCa7g8QwAACAAQAAgAAAAIACAACAAQAAAAAAAAAAAA==", # p2sh-p2wsh "cHNidP8BAHICAAAAAR30J629i3Y/R8woRpLQ9JUa31rKxyM+Ny4NEsme48GWAAAAAAD9////Atw5XQUAAAAAF6kUdSESczdYagEyToVUSXyT8VTNz+OHgJaYAAAAAAAWABTmav7/w4OOcfCiewfjsA7eaujhYAAAAABPAQQ1h88EPmLffoAAAAHdXEj2dn8EYJ+rRdXEYu5laq6lJI5Mp+3t63ckwty05QKrJBNPewhwQaGPYRif6+XaxozFXvTXn7pU24H6fRy1FxQCCMt3MAAAgAEAAIAAAACAAQAAgE8BBDWHzwSdsdAAgAAAAeeOv56oeaaFTrNonMKDHk1C8brbWGFvdlecVue+v0/RAn/g4yI3oYsyen7OOcT7caYl4Mn7nQbyonHcusUR+GhzFCa7g8QwAACAAQAAgAAAAIABAACATwEENYfPBLrBSDmAAAABpzrb4oeEh2NNy/w/fr3osfyZTx7AaGDPAcP+LqeR3bYC5ioqmXPuazp69HwimlvecLylm9BLuyl/VpPXqiVrl20Uc8XaCjAAAIABAACAAAAAgAEAAIAAAQEgAOH1BQAAAAAXqRSv3gkn8731qcPbSDu4TJOlJJZ/PocBBCIAIOeiFBX5x0vX6CacrAUVovrs1DDCcKJS5qptFS3sjpDpAQVpUiECZ+pFYkOTVjB+eG+vQFA3MNjZWiA6DjRcs1Wl36A/zgMhA2Ygckuwjah29wiVRgA6wFx51+6ayrzeCIQ2eE4zfxPtIQOnUlBn22cn2CPCZkMSI6cDaZK2SlLV20rT6pqMoQCJsFOuIgYCZ+pFYkOTVjB+eG+vQFA3MNjZWiA6DjRcs1Wl36A/zgMcc8XaCjAAAIABAACAAAAAgAEAAIAAAAAAAAAAACIGA2Ygckuwjah29wiVRgA6wFx51+6ayrzeCIQ2eE4zfxPtHCa7g8QwAACAAQAAgAAAAIABAACAAAAAAAAAAAAiBgOnUlBn22cn2CPCZkMSI6cDaZK2SlLV20rT6pqMoQCJsBwCCMt3MAAAgAEAAIAAAACAAQAAgAAAAAAAAAAAAAEAIgAgZBwTq05RkpqKv6FV6LQjuM07Qv0/bYfWVc9NUQOFvwQBAWlSIQI32jVSdTgeu7+YZKrWfgOZ2J/LV36c5rBoApTzhrNlDCEC+hEqm3XmRt862AFFeyJ7p1m8A+V7czj6OajUNgCfg4EhA/MUVfxGh4k3Po3LB8CmMRsvIHcGNO0elUgETaITZA3UU64iAgI32jVSdTgeu7+YZKrWfgOZ2J/LV36c5rBoApTzhrNlDBwCCMt3MAAAgAEAAIAAAACAAQAAgAEAAAAAAAAAIgIC+hEqm3XmRt862AFFeyJ7p1m8A+V7czj6OajUNgCfg4EcJruDxDAAAIABAACAAAAAgAEAAIABAAAAAAAAACICA/MUVfxGh4k3Po3LB8CmMRsvIHcGNO0elUgETaITZA3UHHPF2gowAACAAQAAgAAAAIABAACAAQAAAAAAAAAAAA==", # p2wpkh "cHNidP8BAHECAAAAAc88WMMpgq4gUIjZvUnrmwKs3009rnalFsazBrFd46FOAAAAAAD9////Anw/XQUAAAAAFgAULzSqHPAKU7BVopGgOn1F8KaYi1KAlpgAAAAAABYAFOZq/v/Dg45x8KJ7B+OwDt5q6OFgAAAAAAABAR8A4fUFAAAAABYAFNDEo+8J6Ze26Z45flGP4+QaEYyhIgYC56slN7XUnpcDCargbp5J82zhyf671E7I4NHMoLT5wxkYc8XaClQAAIABAACAAAAAgAAAAAAAAAAAACICA11J7M1U0AmeQ2did8em1GJdYR2oil30m/lReneRp3elGHPF2gpUAACAAQAAgAAAAIABAAAAAAAAAAAA", # p2sh-p2wpkh "cHNidP8BAHICAAAAAXbva/K90EDzwdg6zLl0OfGrsaVWrR0PUpaB/6foypSKAQAAAAD9////Apw9XQUAAAAAF6kUJR3RFFeiWcO6R+XMo3F/5CFOApiHgJaYAAAAAAAWABTmav7/w4OOcfCiewfjsA7eaujhYAAAAAAAAQEgAOH1BQAAAAAXqRQzbKoT4IuWCAoytdgY1ZtKs7NnQocBBBYAFDiXH3OTD2wUHZd6xP1KcnyFSTWzIgYDoa+ASsEIqKUXghmMLQNLKL+QyIA/WlP3Ynb6aaTq538Yc8XaCjEAAIABAACAAAAAgAAAAAAAAAAAAAEAFgAUcL6x4EpQCUDp86uqZuGkmsVbjzUiAgKi/ImWxSYiSLXa78Wk0M3NAMEwR9DLEwKBNupjDYdahxhzxdoKMQAAgAEAAIAAAACAAQAAAAAAAAAAAA==", ] for i, b64psbt in enumerate(b64psbts): print("\nTransaction #%d" % (i + 1)) raw = a2b_base64(b64psbt) tx = psbt.PSBT.parse(raw) # Check inputs of the transaction and check that they use the same script type # For multisig parsed policy will look like this: # { script_type: p2wsh, cosigners: [xpubs strings], m: 2, n: 3} policy = None inp_amount = 0 for inp in tx.inputs: inp_amount += inp.witness_utxo.value # get policy of the input inp_policy = get_policy(inp, inp.witness_utxo.script_pubkey, tx.xpubs) # if policy is None - assign current if policy is None: policy = inp_policy # otherwise check that everything in the policy is the same else: # check policy is the same if policy != inp_policy: raise RuntimeError("Mixed inputs in the transaction") wallet = "Native Segwit " if "p2sh-" in policy["type"]: wallet = "Nested Segwit " if "m" in policy: wallet += "Multisig (%d of %d)" % (policy["m"], policy["n"]) else: wallet += "Single sig" print("Spending from: %s" % wallet) print("Input amount: %d sat" % inp_amount) # now go through outputs and check if they are change spending = 0 change = 0 for i, out in enumerate(tx.outputs): out_policy = get_policy(out, tx.tx.vout[i].script_pubkey, tx.xpubs) is_change = False # if policy is the same - probably change if out_policy == policy: # double-check that it's change # we already checked in get_cosigners and parse_multisig # that pubkeys are generated from cosigners, # and witness script is corresponding multisig # so we only need to check that scriptpubkey is generated from # witness script # empty script by default sc = script.Script(b"") # multisig, we know witness script if policy["type"] == "p2wsh": sc = script.p2wsh(out.witness_script) elif policy["type"] == "p2sh-p2wsh": sc = script.p2sh(script.p2wsh(out.witness_script)) # single-sig elif "pkh" in policy["type"]: if len(out.bip32_derivations.values()) > 0: der = list( out.bip32_derivations.values())[0].derivation my_pubkey = root.derive(der) if policy["type"] == "p2wpkh": sc = script.p2wpkh(my_pubkey) elif policy["type"] == "p2sh-p2wpkh": sc = script.p2sh(script.p2wpkh(my_pubkey)) if sc.data == tx.tx.vout[i].script_pubkey.data: is_change = True if is_change: change += tx.tx.vout[i].value print("Change %d sats" % tx.tx.vout[i].value) else: spending += tx.tx.vout[i].value print("Spending %d sats to %s" % (tx.tx.vout[i].value, tx.tx.vout[i].script_pubkey.address())) fee = inp_amount - change - spending print("Fee: %d sats" % fee)