def test_sign_verify(self): unsigned = PSBT.from_string(B64PSBT) signed = PSBT.from_string(B64SIGNED) tx = unsigned.tx values = [inp.utxo.value for inp in unsigned.inputs] scripts = [inp.utxo.script_pubkey for inp in unsigned.inputs] for i, inp in enumerate(signed.inputs): wit = inp.final_scriptwitness.items[0] sig = SchnorrSig.parse(wit[:64]) if len(wit) == 65: sighash = wit[64] else: sighash = SIGHASH.DEFAULT hsh = tx.sighash_taproot(i, script_pubkeys=scripts, values=values, sighash=sighash) pub = PublicKey.from_xonly(inp.utxo.script_pubkey.data[2:]) self.assertTrue(pub.schnorr_verify(sig, hsh)) # check signing and derivation prv = ROOT.derive([0, i]) tweaked = prv.taproot_tweak(b"") self.assertEqual(pub.xonly(), tweaked.xonly()) sig2 = tweaked.schnorr_sign(hsh) self.assertTrue(pub.schnorr_verify(sig2, hsh)) self.assertEqual(sig, sig2) # sign with individual pks sigcount = unsigned.sign_with(prv.key, SIGHASH.ALL) self.assertEqual(sigcount, 1) self.assertEqual(unsigned.inputs[i].final_scriptwitness, signed.inputs[i].final_scriptwitness) for i, inp in enumerate(unsigned.inputs): prv = ROOT.derive([0, i]) # remove final scriptwitness to test signing with root inp.final_scriptwitness = None # populate derivation inp.bip32_derivations[prv.key.get_public_key()] = DerivationPath( ROOT.my_fingerprint, [0, i]) # test signing with root key counter = unsigned.sign_with(ROOT, SIGHASH.ALL) self.assertEqual(counter, 2) for inp1, inp2 in zip(unsigned.inputs, signed.inputs): self.assertEqual(inp1.final_scriptwitness, inp2.final_scriptwitness) inp1.final_scriptwitness = None # test signing with psbtview, unsigned already has derivations stream = BytesIO(unsigned.serialize()) psbtv = PSBTView.view(stream, compress=False) sigs = BytesIO() sigcount = psbtv.sign_with(ROOT, sigs, SIGHASH.ALL) self.assertEqual(sigcount, len(unsigned.inputs)) v = sigs.getvalue() # check sigs are in the stream for inp in signed.inputs: self.assertTrue(inp.final_scriptwitness.items[0] in v)
def fill_output(cls, out: OutputScope, desc: Descriptor) -> bool: """ Fills derivations and all other information in PSBT output from derived descriptor """ if desc.script_pubkey() != out.script_pubkey: return False out.redeem_script = desc.redeem_script() out.witness_script = desc.witness_script() out.bip32_derivations = { key.get_public_key(): DerivationPath(key.origin.fingerprint, key.origin.derivation) for key in desc.keys } return True
def fill_external_wallet_derivations(psbt, wallet): # fill derivations for receiving wallets if they own address net = get_network(wallet.manager.chain) wallets = wallet.manager.wallets.values() for out in psbt.outputs: try: # if we fail - not a big deal # check if output derivation is empty if out.bip32_derivations: continue try: # if there is no address representation - continue addr = out.script_pubkey.address(net) except: continue for w in wallets: # skip sending wallet if w == wallet: continue if not w.is_address_mine(addr): continue # we get here if wallet owns address info = w.get_address_info(addr) derivation = [int(info.change), int(info.index)] # we first one key and build derivation for it # it's enough for DIY to do the res k = w.keys[0] key = bip32.HDKey.from_string(k.xpub) if k.fingerprint != "": fingerprint = bytes.fromhex(k.fingerprint) else: fingerprint = key.my_fingerprint if k.derivation != "": der = bip32.parse_path(k.derivation) else: der = [] pub = key.derive(derivation).key out.bip32_derivations[pub] = DerivationPath( fingerprint, der + derivation) break except Exception as e: logger.exception(e)
def test_miniscript(self): # and(pk(A),after(100)) -> and_v(v:pk(A),after(100)) path = "49h/1h/0h/2h" fgp = sim.query("fingerprint").decode() xpub = sim.query(f"xpub m/{path}").decode() desc = f"wsh(and_v(v:pk([{fgp}/{path}]{xpub}"+"/{0,1}/*),after(10)))" res = sim.query("addwallet mini&"+desc, [True]) wname = wallet_prefix+"_mini" d = Descriptor.from_string(desc) addr = d.derive(5).address(NETWORKS['regtest']) # check it finds the wallet correctly sc = d.derive(5).witness_script().data.hex() res = sim.query(f"showaddr wsh m/{path}/0/5 {sc}", [True]) self.assertEqual(res.decode(), addr) d1 = d.derive(2, branch_index=0) d2 = d.derive(3, branch_index=1) # recv addr 2 addr1 = d1.address(NETWORKS['regtest']) # change addr 3 addr2 = d2.address(NETWORKS['regtest']) res = sim.query(f"bitcoin:{addr1}?index=2", [True]) # check it's found self.assertFalse(b"Can't find wallet" in res) rpc.createwallet(wname, True, True) w = rpc.wallet(wname) res = w.importmulti([{ "scriptPubKey": {"address": addr1},#d1.script_pubkey().data.hex(), # "witnessscript": d1.witness_script().data.hex(), # "pubkeys": [k.sec().hex() for k in d1.keys], "internal": False, "timestamp": "now", "watchonly": True, },{ "scriptPubKey": {"address": addr2},#d2.script_pubkey().data.hex(), # "witnessscript": d2.witness_script().data.hex(), # "pubkeys": [k.sec().hex() for k in d2.keys], "internal": True, "timestamp": "now", "watchonly": True, }],{"rescan": False}) self.assertTrue(all([k["success"] for k in res])) wdefault.sendtoaddress(addr1, 0.1) rpc.mine() unspent = w.listunspent() self.assertTrue(len(unspent) > 0) unspent = [{"txid": u["txid"], "vout": u["vout"]} for u in unspent[:1]] tx = w.createrawtransaction(unspent, [{wdefault.getnewaddress(): 0.002},{addr2: 0.09799}]) psbt = PSBT.from_base64(w.converttopsbt(tx)) # locktime magic :) psbt.tx.locktime = 11 psbt.tx.vin[0].sequence = 10 # fillinig psbt with data psbt.inputs[0].witness_script = d1.witness_script() pub = ec.PublicKey.parse(d1.keys[0].sec()) psbt.inputs[0].bip32_derivations[pub] = DerivationPath(bytes.fromhex(fgp), bip32.parse_path(f"m/{path}")+[0,2]) tx = w.gettransaction(unspent[0]["txid"]) t = Transaction.from_string(tx["hex"]) psbt.inputs[0].witness_utxo = t.vout[unspent[0]["vout"]] psbt.outputs[1].witness_script = d2.witness_script() pub2 = ec.PublicKey.parse(d2.keys[0].sec()) psbt.outputs[1].bip32_derivations[pub2] = DerivationPath(bytes.fromhex(fgp), bip32.parse_path(f"m/{path}")+[1,3]) unsigned = psbt.to_base64() # confirm signing signed = sim.query("sign "+unsigned, [True]) stx = PSBT.from_base64(signed.decode()) # signed tx t = psbt.tx # print(stx) t.vin[0].witness = Witness([stx.inputs[0].partial_sigs[pub], psbt.inputs[0].witness_script.data]) # broadcast with self.assertRaises(Exception): res = rpc.sendrawtransaction(t.serialize().hex()) rpc.mine(11) res = rpc.sendrawtransaction(t.serialize().hex()) rpc.mine() self.assertEqual(len(bytes.fromhex(res)), 32)
change_output = None # if used - check the balance: balance = (res.get("chain_stats", {}).get("funded_txo_sum", 0) + res.get("mempool_stats", {}).get("funded_txo_sum", 0) - res.get("chain_stats", {}).get("spent_txo_sum", 0) - res.get("mempool_stats", {}).get("spent_txo_sum", 0)) # positive balance - we have utxos there if balance > 0: print(addr, "utxos!", balance) utxoarr = s.get(f"{API}/address/{addr}/utxo").json() # derivation information for utxos bip32_derivations = {} for k in d.keys: bip32_derivations[PublicKey.parse(k.sec())] = DerivationPath( k.origin.fingerprint, k.origin.derivation) # for multisig this is important, # for native segwit single sig it's None ws = d.witness_script() rs = d.redeem_script() script_pubkey = d.script_pubkey() utxos += [{ "txid": utxo["txid"], "vout": utxo["vout"], "value": utxo["value"], "witness_script":