Example #1
0
    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)
Example #2
0
 def test_invalid_psbts(self):
     for psbt_str in INVALID_VECTORS:
         psbt_bytes = unhexlify(psbt_str)
         try:
             PSBT.parse(psbt_bytes)
         except:
             continue  # succeed if exception was thrown
         self.fail(
             "Invalid psbt was parsed successfully: {}".format(psbt_str))
    def test_sign(self):
        """Test if we can sign psbtview and get the same as from signing psbt"""
        for compress in [True, False]:
            for b64 in PSBTS:
                psbt = PSBT.from_string(b64, compress=compress)
                stream = BytesIO(a2b_base64(b64))
                psbtv = PSBTView.view(stream, compress=compress)

                # incomplete psbtview
                xpsbt = PSBT.from_string(b64, compress=compress)
                # remove derivations and other important data from original
                for sc in xpsbt.inputs:
                    sc.bip32_derivations = {}
                    sc.witness_script = None
                    sc.redeem_script = None

                xstream = BytesIO(xpsbt.serialize())
                xpsbtv = PSBTView.view(xstream, compress=compress)

                csigs1 = psbt.sign_with(ROOT)
                sigs_stream2 = BytesIO()
                csigs2 = psbtv.sign_with(ROOT, sigs_stream2)
                # signing incomplete psbtview
                sigs_stream3 = BytesIO()
                csigs3 = 0
                for i in range(xpsbtv.num_inputs):
                    csigs3 += xpsbtv.sign_input(
                        i, ROOT, sigs_stream3, extra_scope_data=psbt.inputs[i])
                    # add separator
                    sigs_stream3.write(b"\x00")

                self.assertEqual(csigs1, csigs2)
                self.assertEqual(csigs1, csigs3)
                for sigs_stream in [sigs_stream2, sigs_stream3]:
                    sigs_stream.seek(0)
                    # check all sigs
                    signed_inputs = [
                        InputScope.read_from(sigs_stream)
                        for i in range(len(psbt.inputs))
                    ]
                    self.assertEqual(len(signed_inputs), len(psbt.inputs))
                    for i, inp in enumerate(signed_inputs):
                        inp2 = psbt.inputs[i]
                        self.assertEqual(inp.partial_sigs, inp2.partial_sigs)
                    # check serialization with signatures
                    sigs_stream.seek(0)
                ser = BytesIO()
                psbtv.write_to(ser, extra_input_streams=[sigs_stream2])
                self.assertEqual(ser.getvalue(), psbt.serialize())

                # check compress reduces the size of psbt
                sigs_stream2.seek(0)
                ser2 = BytesIO()
                psbtv.write_to(ser2,
                               extra_input_streams=[sigs_stream2],
                               compress=True)
                self.assertTrue(len(ser2.getvalue()) < len(ser.getvalue()))
Example #4
0
 def create_psbts(self, base64_psbt, wallet):
     psbts = super().create_psbts(base64_psbt, wallet)
     # remove non-witness utxo if they are there to reduce QR code size
     updated_psbt = wallet.fill_psbt(base64_psbt,
                                     non_witness=False,
                                     xpubs=False)
     qr_psbt = PSBT.parse(a2b_base64(updated_psbt))
     # find my key
     fgp = None
     derivation = None
     for k in wallet.keys:
         if k in self.keys and k.fingerprint and k.derivation:
             fgp = bytes.fromhex(k.fingerprint)
             derivation = bip32.parse_path(k.derivation)
             break
     # remove unnecessary derivations from inputs and outputs
     for inp in qr_psbt.inputs + qr_psbt.outputs:
         # keep only my derivation
         for k in list(inp.bip32_derivations.keys()):
             if inp.bip32_derivations[k].fingerprint != fgp:
                 inp.bip32_derivations.pop(k, None)
     # remove scripts from outputs (DIY should know about the wallet)
     for out in qr_psbt.outputs:
         out.witness_script = None
         out.redeem_script = None
     # remove partial sigs from inputs
     for inp in qr_psbt.inputs:
         inp.partial_sigs = {}
     psbts["qrcode"] = b2a_base64(qr_psbt.serialize()).strip().decode()
     # we can add xpubs to SD card, but non_witness can be too large for MCU
     psbts["sdcard"] = wallet.fill_psbt(psbts["qrcode"],
                                        non_witness=False,
                                        xpubs=True)
     return psbts
Example #5
0
 def replace_derivations(self, wallet, psbts):
     # cc wants everyone to use the same derivation
     fgp = None
     derivation = None
     for k in wallet.keys:
         if k in self.keys and k.fingerprint and k.derivation:
             fgp = k.fingerprint
             derivation = k.derivation
             break
     if not fgp:
         return
     path = bip32.parse_path(derivation)
     for kk in list(psbts.keys()):
         psbt = PSBT.parse(a2b_base64(psbts[kk]))
         for xpub in psbt.xpubs:
             psbt.xpubs[xpub].derivation = list(path)
         # remove partial signatures from device psbt
         for scope in psbt.inputs:
             scope.partial_sigs = OrderedDict()
         for scope in psbt.inputs + psbt.outputs:
             for k in list(scope.bip32_derivations.keys()):
                 original = scope.bip32_derivations[k].derivation
                 scope.bip32_derivations[
                     k].derivation = path + original[-2:]
         psbts[kk] = b2a_base64(psbt.serialize()).decode().strip()
Example #6
0
 def test_sign(self):
     """Parses a PSBT, signs both inputs (1 segwit and 1 legacy), and verifies each signature is correct"""
     xkey = HDKey.from_base58(
         "tprv8ZgxMBicQKsPd9TeAdPADNnSyH9SSUUbTVeFszDE23Ki6TBB5nCefAdHkK8Fm3qMQR6sHwA56zqRmKmxnHk37JkiFzvncDqoKmPWubu7hDF"
     )
     psbt_str = "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000"
     exp_partial_sigs = [
         {
             "029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f":
             "3044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01",
             "02dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7":
             "3045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01",
         },
         {
             "03089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc":
             "3044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01",
             "023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73":
             "3044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d201",
         },
     ]
     psbt = PSBT.parse(unhexlify(psbt_str))
     psbt.sign_with(xkey)
     for i in range(len(psbt.inputs)):
         inp = psbt.inputs[i]
         self.assertEqual(len(inp.partial_sigs), len(exp_partial_sigs[i]))
         for act_pub, act_sig in inp.partial_sigs.items():
             act_pub_str = hexlify(act_pub.serialize()).decode("utf-8")
             self.assertIn(act_pub_str, exp_partial_sigs[i].keys())
             self.assertEqual(
                 hexlify(act_sig).decode("utf-8"),
                 exp_partial_sigs[i][act_pub_str])
Example #7
0
 def test_valid_psbts(self):
     for psbt_str in VALID_VECTORS:
         psbt_bytes = unhexlify(psbt_str)
         psbt_act = PSBT.parse(psbt_bytes)
         msg = "Valid psbt changed after being parsed & serialized: {}".format(
             psbt_str)
         self.assertEqual(psbt_act.serialize(), psbt_bytes, msg)
Example #8
0
def convert_rawtransaction_to_psbt(wallet_rpc, rawtransaction) -> str:
    """
    Converts a raw transaction in HEX format into a PSBT in b64 format
    """
    tx = Transaction.from_string(rawtransaction)
    psbt = PSBT(tx)  # this empties the signatures
    psbt = wallet_rpc.walletprocesspsbt(str(psbt),
                                        False).get("psbt", str(psbt))
    psbt = PSBT.from_string(psbt)  # we need the class object again
    # Recover signatures (witness or scriptsig) if available in raw tx
    for vin, psbtin in zip(tx.vin, psbt.inputs):
        if vin.witness:
            psbtin.final_scriptwitness = vin.witness
        if vin.script_sig:
            psbtin.final_scriptsig = vin.script_sig
    b64_psbt = str(psbt)
    return b64_psbt
Example #9
0
    def sign_with_descriptor(self, wname, d1, d2, all_sighashes=False):
        # to derive addresses
        desc1 = Descriptor.from_string(d1)
        desc2 = Descriptor.from_string(d2)
        # recv addr 2
        addr1 = desc1.derive(2).address(NETWORKS['regtest'])
        # change addr 3
        addr2 = desc2.derive(3).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)

        # to add checksums
        d1 = rpc.getdescriptorinfo(d1)["descriptor"]
        d2 = rpc.getdescriptorinfo(d2)["descriptor"]
        rpc.createwallet(wname, True, True)
        w = rpc.wallet(wname)
        res = w.importmulti([{
                "desc": d1,
                "internal": False,
                "timestamp": "now",
                "watchonly": True,
                "range": 10,
            },{
                "desc": d2,
                "internal": True,
                "timestamp": "now",
                "watchonly": True,
                "range": 10,
            }],{"rescan": False})
        self.assertTrue(all([k["success"] for k in res]))
        wdefault.sendtoaddress(addr1, 0.1)
        rpc.mine()
        psbt = w.walletcreatefundedpsbt([], [{wdefault.getnewaddress(): 0.002}], 0, {"includeWatching": True, "changeAddress": addr2}, True)
        unsigned = psbt["psbt"]
        sighashes = [None]
        if all_sighashes:
            sh = [SIGHASH.ALL, SIGHASH.NONE, SIGHASH.SINGLE]
            sighashes += sh + [s | SIGHASH.ANYONECANPAY for s in sh]
        tx = PSBT.from_base64(unsigned)
        for sh in sighashes:
            for inp in tx.inputs:
                inp.sighash_type = sh
            unsigned = tx.to_base64().encode()
            # confirm signing
            if sh in [SIGHASH.ALL, None]:
                signed = sim.query(b"sign "+unsigned, [True])
            else:
                # confirm warning
                signed = sim.query(b"sign "+unsigned, [True, True])
            # signed tx
            self.assertTrue(signed.startswith(b"cHNi"))
            combined = rpc.combinepsbt([unsigned.decode(), signed.decode()])
            final = rpc.finalizepsbt(combined)
            self.assertTrue(final["complete"])
            # broadcast
            res = rpc.testmempoolaccept([final["hex"]])
            self.assertTrue(res[0]["allowed"])
Example #10
0
def clean_psbt(b64psbt):
    try:
        psbt = PSBT.from_string(b64psbt)
    except:
        psbt = PSET.from_string(b64psbt)
    for inp in psbt.inputs:
        if inp.witness_utxo is not None and inp.non_witness_utxo is not None:
            inp.non_witness_utxo = None
    return psbt.to_string()
Example #11
0
    def sign_with_descriptor(self, d1, d2, root):
        rpc = daemon.rpc
        wname = random_wallet_name()
        # to derive addresses
        desc1 = Descriptor.from_string(d1)
        desc2 = Descriptor.from_string(d2)
        # recv addr 2
        addr1 = desc1.derive(2).address(net)
        # change addr 3
        addr2 = desc2.derive(3).address(net)

        # to add checksums
        d1 = add_checksum(str(d1))
        d2 = add_checksum(str(d2))
        rpc.createwallet(wname, True, True)
        w = daemon.wallet(wname)
        res = w.importmulti([{
            "desc": d1,
            "internal": False,
            "timestamp": "now",
            "watchonly": True,
            "range": 10,
        }, {
            "desc": d2,
            "internal": True,
            "timestamp": "now",
            "watchonly": True,
            "range": 10,
        }], {"rescan": False})
        self.assertTrue(all([k["success"] for k in res]))
        wdefault = daemon.wallet()
        wdefault.sendtoaddress(addr1, 0.1)
        daemon.mine()
        psbt = w.walletcreatefundedpsbt([], [{
            wdefault.getnewaddress(): 0.002
        }], 0, {
            "includeWatching": True,
            "changeAddress": addr2
        }, True)
        unsigned = psbt["psbt"]
        psbt = PSBT.from_string(unsigned)
        psbt.sign_with(root)
        final = rpc.finalizepsbt(str(psbt))
        self.assertTrue(final["complete"])
        # test accept
        res = rpc.testmempoolaccept([final["hex"]])
        self.assertTrue(res[0]["allowed"])
Example #12
0
    def create_psbts(self, base64_psbt, wallet):
        try:
            # remove rangeproofs and add sighash alls
            psbt = PSET.from_string(base64_psbt)
            for out in psbt.outputs:
                out.range_proof = None
                out.surjection_proof = None
            for inp in psbt.inputs:
                if not inp.sighash_type:
                    inp.sighash_type = LSIGHASH.ALL
            base64_psbt = psbt.to_string()
        except:
            pass
        psbts = super().create_psbts(base64_psbt, wallet)
        # remove non-witness utxo if they are there to reduce QR code size
        updated_psbt = wallet.fill_psbt(base64_psbt, non_witness=False, xpubs=False)
        try:
            qr_psbt = PSBT.from_string(updated_psbt)
        except:
            qr_psbt = PSET.from_string(updated_psbt)
        # find my key
        fgp = None
        derivation = None
        for k in wallet.keys:
            if k in self.keys and k.fingerprint and k.derivation:
                fgp = bytes.fromhex(k.fingerprint)
                derivation = bip32.parse_path(k.derivation)
                break
        # remove unnecessary derivations from inputs and outputs
        for inp in qr_psbt.inputs + qr_psbt.outputs:
            # keep only my derivation
            for k in list(inp.bip32_derivations.keys()):
                if fgp and inp.bip32_derivations[k].fingerprint != fgp:
                    inp.bip32_derivations.pop(k, None)
        # remove scripts from outputs (DIY should know about the wallet)
        for out in qr_psbt.outputs:
            out.witness_script = None
            out.redeem_script = None
        # remove partial sigs from inputs
        for inp in qr_psbt.inputs:
            inp.partial_sigs = {}
        psbts["qrcode"] = qr_psbt.to_string()

        # we can add xpubs to SD card, but non_witness can be too large for MCU
        psbts["sdcard"] = wallet.fill_psbt(base64_psbt, non_witness=False, xpubs=True)
        return psbts
Example #13
0
 def test_scopes(self):
     """Tests that PSBT and PSBTView result in the same scopes and other constants"""
     for compress in [True, False]:
         for b64 in PSBTS:
             psbt = PSBT.from_string(b64, compress=compress)
             stream = BytesIO(a2b_base64(b64))
             psbtv = PSBTView.view(stream, compress=compress)
             self.assertEqual(len(psbt.inputs), psbtv.num_inputs)
             self.assertEqual(len(psbt.outputs), psbtv.num_outputs)
             self.assertEqual(psbt.version, psbtv.version)
             if psbt.version != 2:
                 self.assertTrue(psbtv.tx_offset > 0)
             # check something left when seek to n-1
             psbtv.seek_to_scope(psbtv.num_inputs + psbtv.num_outputs - 1)
             self.assertEqual(len(stream.read(1)), 1)
             # check nothing left in the stream when seeking to the end
             psbtv.seek_to_scope(psbtv.num_inputs + psbtv.num_outputs)
             self.assertEqual(stream.read(1), b"")
             # check that all scopes are the same in psbt and psbtv
             # check random input scope first
             idx = len(psbt.inputs) // 2
             self.assertEqual(psbt.inputs[idx].serialize(),
                              psbtv.input(idx).serialize())
             # check input scopes sequentially
             for i, inp in enumerate(psbt.inputs):
                 self.assertEqual(inp.serialize(),
                                  psbtv.input(i).serialize())
                 self.assertEqual(inp.vin.serialize(),
                                  psbtv.vin(i).serialize())
             # check random output scope
             idx = len(psbt.outputs) // 2
             self.assertEqual(psbt.outputs[idx].serialize(),
                              psbtv.output(idx).serialize())
             # check input scopes sequentially
             for i, out in enumerate(psbt.outputs):
                 self.assertEqual(out.serialize(),
                                  psbtv.output(i).serialize())
                 self.assertEqual(out.vout.serialize(),
                                  psbtv.vout(i).serialize())
             self.assertEqual(psbt.tx_version, psbtv.tx_version)
             self.assertEqual(psbt.locktime, psbtv.locktime)
    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)
Example #15
0
    def create_psbts(self, base64_psbt, wallet):
        # liquid transaction
        if base64_psbt.startswith("cHNl"):
            # remove rangeproofs and add sighash alls
            psbt = PSET.from_string(base64_psbt)
            # make sure we have tx blinding seed in the transaction
            if psbt.unknown.get(b"\xfc\x07specter\x00"):
                for out in psbt.outputs:
                    out.range_proof = None
                    # out.surjection_proof = None
                    # we know assets - we can blind it
                    if out.asset:
                        out.asset_commitment = None
                        out.asset_blinding_factor = None
                    # we know value - we can blind it
                    if out.value:
                        out.value_commitment = None
                        out.value_blinding_factor = None
                for inp in psbt.inputs:
                    if inp.value and inp.asset:
                        inp.range_proof = None
        else:
            psbt = PSBT.from_string(base64_psbt)

        fill_external_wallet_derivations(psbt, wallet)

        base64_psbt = psbt.to_string()
        psbts = super().create_psbts(base64_psbt, wallet)
        # remove non-witness utxo if they are there to reduce QR code size
        updated_psbt = wallet.fill_psbt(base64_psbt,
                                        non_witness=False,
                                        xpubs=False,
                                        taproot_derivations=True)
        try:
            qr_psbt = PSBT.from_string(updated_psbt)
        except:
            qr_psbt = PSET.from_string(updated_psbt)
        # find my key
        fgp = None
        derivation = None
        for k in wallet.keys:
            if k in self.keys and k.fingerprint and k.derivation:
                fgp = bytes.fromhex(k.fingerprint)
                derivation = bip32.parse_path(k.derivation)
                break
        # remove unnecessary derivations from inputs and outputs
        for inp in qr_psbt.inputs + qr_psbt.outputs:
            # keep only one derivation path (idealy ours)
            found = False
            pubkeys = list(inp.bip32_derivations.keys())
            for i, pub in enumerate(pubkeys):
                if fgp and inp.bip32_derivations[pub].fingerprint != fgp:
                    # only pop if we already saw our derivation
                    # or if it's not the last one
                    if found or i < len(pubkeys) - 1:
                        inp.bip32_derivations.pop(k, None)
                else:
                    found = True
        # remove scripts from outputs (DIY should know about the wallet)
        for out in qr_psbt.outputs:
            out.witness_script = None
            out.redeem_script = None
        # remove partial sigs from inputs
        for inp in qr_psbt.inputs:
            inp.partial_sigs = {}
        psbts["qrcode"] = qr_psbt.to_string()

        # we can add xpubs to SD card, but non_witness can be too large for MCU
        psbts["sdcard"] = wallet.fill_psbt(base64_psbt,
                                           non_witness=False,
                                           xpubs=True,
                                           taproot_derivations=True)
        psbts["hwi"] = wallet.fill_psbt(base64_psbt,
                                        non_witness=False,
                                        xpubs=True,
                                        taproot_derivations=True)
        return psbts
Example #16
0
# round fee to satoshis
fee = int(fee) + 1

vin = [
    TransactionInput(bytes.fromhex(inp["txid"]), inp["vout"]) for inp in inputs
]
vout = [TransactionOutput(AMOUNT, script.address_to_scriptpubkey(DESTINATION))]
# add change output
if spending_amount - fee - AMOUNT > DUST_LIMIT:
    vout.append(
        TransactionOutput(spending_amount - fee - AMOUNT,
                          change_output.script_pubkey()), )

tx = Transaction(vin=vin, vout=vout)
# now create PSBT from this transaction
psbt = PSBT(tx)
# fill missing information for all inputs and change output
for i, inp in enumerate(inputs):
    psbt.inputs[i].bip32_derivations = inp["bip32_derivations"]
    psbt.inputs[i].witness_script = inp["witness_script"]
    psbt.inputs[i].redeem_script = inp["redeem_script"]
    psbt.inputs[i].witness_utxo = inp.get("witness_utxo", None)
    psbt.inputs[i].non_witness_utxo = inp.get("non_witness_utxo", None)

if len(psbt.outputs) > 1:
    # derivation information for utxos
    bip32_derivations = {}
    for k in change_output.keys:
        bip32_derivations[PublicKey.parse(k.sec())] = DerivationPath(
            k.origin.fingerprint, k.origin.derivation)