Beispiel #1
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()
Beispiel #2
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
Beispiel #3
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
Beispiel #4
0
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 extract_xpub(
        self,
        derivation=None,
        device_type=None,
        path=None,
        fingerprint=None,
        passphrase="",
        chain="",
    ):
        with self._get_client(
                device_type=device_type,
                fingerprint=fingerprint,
                path=path,
                passphrase=passphrase,
                chain=chain,
        ) as client:
            # Client will be configured for testnet if our Specter instance is
            #   currently connected to testnet. This will prevent us from
            #   getting mainnet xpubs unless we set is_testnet here:
            der = bip32.parse_path(derivation)
            client.chain = (Chain.TEST if len(der) > 2 and der[1] == 0x80000001
                            else Chain.MAIN)

            network = networks.get_network("main" if client.chain ==
                                           Chain.MAIN else "test")

            master_fpr = client.get_master_fingerprint().hex()

            try:
                xpub = client.get_pubkey_at_path(derivation).to_string()
                slip132_prefix = bip32.detect_version(derivation,
                                                      default="xpub",
                                                      network=network)
                xpub = convert_xpub_prefix(xpub, slip132_prefix)
                return "[{}/{}]{}\n".format(master_fpr,
                                            derivation.split("m/")[1], xpub)
            except Exception as e:
                logger.warning(
                    f"Failed to import Nested Segwit singlesig mainnet key. Error: {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)
Beispiel #7
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