Example #1
0
 async def sign_psbt(self, stream, show_screen):
     data = a2b_base64(stream.read())
     psbt = PSBT.parse(data)
     wallets, meta = self.parse_psbt(psbt=psbt)
     spends = []
     for w, amount in wallets:
         if w is None:
             name = "Unkown wallet"
         else:
             name = w.name
         spends.append("%.8f BTC\nfrom \"%s\"" % (amount / 1e8, name))
     title = "Spending:\n" + "\n".join(spends)
     res = await show_screen(TransactionScreen(title, meta))
     if res:
         for w, _ in wallets:
             if w is None:
                 continue
             # fill derivation paths from proprietary fields
             w.update_gaps(psbt=psbt)
             w.save(self.keystore)
             psbt = w.fill_psbt(psbt, self.keystore.fingerprint)
         self.keystore.sign_psbt(psbt)
         # remove unnecessary stuff:
         out_psbt = PSBT(psbt.tx)
         for i, inp in enumerate(psbt.inputs):
             out_psbt.inputs[i].partial_sigs = inp.partial_sigs
         txt = b2a_base64(out_psbt.serialize()).decode().strip()
         return BytesIO(txt)
Example #2
0
    def test_psbt(self):
        """Basic signing of the PSBT"""
        clear_testdir()
        ks = get_keystore(mnemonic="ability "*11+"acid", password="")
        wapp = get_wallets_app(ks, 'regtest')
        # at this stage only wpkh wallet exists
        # so this tx be parsed and signed just fine
        unsigned, signed = PSBTS["wpkh"]
        psbt = PSBT.from_string(unsigned)
        # check it can sign b64-psbt
        s = BytesIO(psbt.to_string().encode())
        self.assertTrue(wapp.can_process(s))

        # check it can sign b64 with sign prefix
        s = BytesIO(b"sign "+psbt.to_string().encode()+b"\n\n")
        self.assertTrue(wapp.can_process(s))

        # check it can sign raw psbt
        s = BytesIO(psbt.serialize())
        self.assertTrue(wapp.can_process(s))
        fout = BytesIO()
        wallets, meta = wapp.manager.preprocess_psbt(s, fout)

        # found a wallet
        self.assertEqual(len(wallets), 1)
        self.assertTrue(wapp.manager.wallets[0] in wallets)

        fout.seek(0)
        psbtv = PSBTView.view(fout)

        b = BytesIO()
        sig_count = wapp.manager.sign_psbtview(psbtv, b, wallets, None)
        self.assertEqual(PSBT.parse(b.getvalue()).to_string(), signed)
 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):
     """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])
 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 #6
0
 async def sign_psbt(self, stream, show_screen):
     data = a2b_base64(stream.read())
     psbt = PSBT.parse(data)
     wallet, meta = self.parse_psbt(psbt=psbt)
     res = await show_screen(TransactionScreen(wallet.name, meta))
     if res:
         # fill derivation paths from proprietary fields
         wallet.update_gaps(psbt=psbt)
         wallet.save(self.keystore)
         psbt = wallet.fill_psbt(psbt, self.keystore.fingerprint)
         self.keystore.sign_psbt(psbt)
         # remove unnecessary stuff:
         out_psbt = PSBT(psbt.tx)
         for i, inp in enumerate(psbt.inputs):
             out_psbt.inputs[i].partial_sigs = inp.partial_sigs
         txt = b2a_base64(out_psbt.serialize()).decode().strip()
         return BytesIO(txt)
Example #7
0
    def test_revault_sign(self):
        """Basic signing of the PSBT"""
        for i, mnemonic in enumerate(MNEMONICS):
            clear_testdir()
            ks = get_keystore(mnemonic=mnemonic, password="")
            wapp = get_wallets_app(ks, 'main')
            # add wallets
            for wdesc in WALLETS:
                w = wapp.manager.parse_wallet(wdesc)
                wapp.manager.add_wallet(w)
            for j, (unsigned, signed, mnemonic_idx,
                    wnames) in enumerate(PSBTS):
                psbt = PSBT.from_string(unsigned)
                s = BytesIO(psbt.to_string().encode())
                # check it can sign b64-psbt
                self.assertTrue(wapp.can_process(s))
                # check it can sign raw psbt
                s = BytesIO(psbt.serialize())
                self.assertTrue(wapp.can_process(s))

                fout = BytesIO()
                wallets, meta = wapp.manager.preprocess_psbt(s, fout)
                # check that we detected wallet and a non-standard sighash
                self.assertEqual([w.name for w in wallets], wnames)
                self.assertEqual([
                    inp.get("label").replace(" (watch-only)", "")
                    for inp in meta["inputs"]
                ], wnames)
                self.assertEqual(meta["inputs"][0].get("sighash"),
                                 "ALL | ANYONECANPAY")

                fout.seek(0)
                psbtv = PSBTView.view(fout)

                b = BytesIO()
                if i not in mnemonic_idx:
                    with self.assertRaises(WalletError):
                        sig_count = wapp.manager.sign_psbtview(
                            psbtv, b, wallets, None)
                elif i == mnemonic_idx[0]:
                    sig_count = wapp.manager.sign_psbtview(
                        psbtv, b, wallets, None)
                    self.assertEqual(
                        PSBT.parse(b.getvalue()).to_string(), signed)
Example #8
0
    async def sign_psbt(self, stream, show_screen, encoding=BASE64_STREAM):
        if encoding == BASE64_STREAM:
            data = a2b_base64(stream.read())
            psbt = PSBT.parse(data)
        else:
            psbt = PSBT.read_from(stream)
        # check if all utxos are there and if there are custom sighashes
        sighash = SIGHASH.ALL
        custom_sighashes = []
        for i, inp in enumerate(psbt.inputs):
            if inp.witness_utxo is None:
                if inp.non_witness_utxo is None:
                    raise WalletError(
                        "Invalid PSBT - missing previous transaction")
            if inp.sighash_type and inp.sighash_type != SIGHASH.ALL:
                custom_sighashes.append((i, inp.sighash_type))

        if len(custom_sighashes) > 0:
            txt = [("Input %d: " % i) + SIGHASH_NAMES[sh]
                   for (i, sh) in custom_sighashes]
            canceltxt = "Only sign ALL" if len(custom_sighashes) != len(
                psbt.inputs) else "Cancel"
            confirm = await show_screen(
                Prompt("Warning!",
                       "\nCustom SIGHASH flags are used!\n\n" + "\n".join(txt),
                       confirm_text="Sign anyway",
                       cancel_text=canceltxt))
            if confirm:
                sighash = None
            else:
                if len(custom_sighashes) == len(psbt.inputs):
                    # nothing to sign
                    return
        wallets, meta = self.parse_psbt(psbt=psbt)
        # there is an unknown wallet
        # wallet is a list of tuples: (wallet, amount)
        if None in [w[0] for w in wallets]:
            scr = Prompt(
                "Warning!",
                "\nUnknown wallet in inputs!\n\n\n"
                "Wallet for some inpunts is unknown! This means we can't verify change addresses.\n\n\n"
                "Hint:\nYou can cancel this transaction and import the wallet by scanning it's descriptor.\n\n\n"
                "Proceed to the transaction confirmation?",
            )
            proceed = await show_screen(scr)
            if not proceed:
                return None
        spends = []
        for w, amount in wallets:
            if w is None:
                name = "Unknown wallet"
            else:
                name = w.name
            spends.append('%.8f BTC\nfrom "%s"' % (amount / 1e8, name))
        title = "Spending:\n" + "\n".join(spends)
        res = await show_screen(TransactionScreen(title, meta))
        if res:
            self.show_loader(title="Signing transaction...")
            sigsStart = 0
            for i, inp in enumerate(psbt.inputs):
                sigsStart += len(list(inp.partial_sigs.keys()))
            for w, _ in wallets:
                if w is None:
                    continue
                # fill derivation paths from proprietary fields
                w.update_gaps(psbt=psbt)
                w.save(self.keystore)
                w.fill_psbt(psbt, self.keystore.fingerprint)
                if w.has_private_keys:
                    w.sign_psbt(psbt, sighash)
            self.keystore.sign_psbt(psbt, sighash)
            # remove unnecessary stuff:
            out_psbt = PSBT(psbt.tx)
            sigsEnd = 0
            for i, inp in enumerate(psbt.inputs):
                sigsEnd += len(list(inp.partial_sigs.keys()))
                out_psbt.inputs[i].partial_sigs = inp.partial_sigs
            del psbt
            gc.collect()
            if sigsEnd == sigsStart:
                raise WalletError(
                    "We didn't add any signatures!\n\nMaybe you forgot to import the wallet?\n\nScan the wallet descriptor to import it."
                )
            if encoding == BASE64_STREAM:
                txt = b2a_base64(out_psbt.serialize()).decode().strip()
            else:
                txt = out_psbt.serialize()
            return BytesIO(txt)