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()
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
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
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)
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