Пример #1
0
 def test_valid_psbt(self):
     for valid in self.data['valid']:
         with self.subTest(valid=valid):
             psbt = PSBT()
             psbt.deserialize(valid)
             serd = psbt.serialize()
             self.assertEqual(valid, serd)
Пример #2
0
def clean_psbt(b64psbt):
    psbt = PSBT()
    psbt.deserialize(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.serialize()
Пример #3
0
 def fill_psbt(self, b64psbt, non_witness: bool = True, xpubs: bool = True):
     psbt = PSBT()
     psbt.deserialize(b64psbt)
     if non_witness:
         for i, inp in enumerate(psbt.tx.vin):
             txid = inp.prevout.hash.to_bytes(32, 'big').hex()
             try:
                 res = self.cli.gettransaction(txid)
             except:
                 raise SpecterError(
                     "Can't find previous transaction in the wallet.")
             stream = BytesIO(bytes.fromhex(res["hex"]))
             prevtx = CTransaction()
             prevtx.deserialize(stream)
             psbt.inputs[i].non_witness_utxo = prevtx
     if xpubs:
         # for multisig add xpub fields
         if len(self.keys) > 1:
             for k in self.keys:
                 key = b'\x01' + decode_base58(k.xpub)
                 if k.fingerprint != '':
                     fingerprint = bytes.fromhex(k.fingerprint)
                 else:
                     fingerprint = get_xpub_fingerprint(k.xpub)
                 if k.derivation != '':
                     der = der_to_bytes(k.derivation)
                 else:
                     der = b''
                 value = fingerprint + der
                 psbt.unknown[key] = value
     return psbt.serialize()
Пример #4
0
    def _generate_and_finalize(self, unknown_inputs, psbt):
        if not unknown_inputs:
            # Just do the normal signing process to test "all inputs" case
            sign_res = self.do_command(self.dev_args +
                                       ['signtx', psbt['psbt']])
            finalize_res = self.wrpc.finalizepsbt(sign_res['psbt'])
        else:
            # Sign only input one on first pass
            # then rest on second pass to test ability to successfully
            # ignore inputs that are not its own. Then combine both
            # signing passes to ensure they are actually properly being
            # partially signed at each step.
            first_psbt = PSBT()
            first_psbt.deserialize(psbt['psbt'])
            second_psbt = PSBT()
            second_psbt.deserialize(psbt['psbt'])

            # Blank master fingerprint to make hww fail to sign
            # Single input PSBTs will be fully signed by first signer
            for psbt_input in first_psbt.inputs[1:]:
                for pubkey, path in psbt_input.hd_keypaths.items():
                    psbt_input.hd_keypaths[pubkey] = KeyOriginInfo(
                        b"\x00\x00\x00\x00", path.path)
            for pubkey, path in second_psbt.inputs[0].hd_keypaths.items():
                second_psbt.inputs[0].hd_keypaths[pubkey] = KeyOriginInfo(
                    b"\x00\x00\x00\x00", path.path)

            single_input = len(first_psbt.inputs) == 1

            # Process the psbts
            first_psbt = first_psbt.serialize()
            second_psbt = second_psbt.serialize()

            # First will always have something to sign
            first_sign_res = self.do_command(self.dev_args +
                                             ['signtx', first_psbt])
            self.assertTrue(single_input == self.wrpc.finalizepsbt(
                first_sign_res['psbt'])['complete'])
            # Second may have nothing to sign (1 input case)
            # and also may throw an error(e.g., ColdCard)
            second_sign_res = self.do_command(self.dev_args +
                                              ['signtx', second_psbt])
            if 'psbt' in second_sign_res:
                self.assertTrue(not self.wrpc.finalizepsbt(
                    second_sign_res['psbt'])['complete'])
                combined_psbt = self.wrpc.combinepsbt(
                    [first_sign_res['psbt'], second_sign_res['psbt']])

            else:
                self.assertTrue('error' in second_sign_res)
                combined_psbt = first_sign_res['psbt']

            finalize_res = self.wrpc.finalizepsbt(combined_psbt)
            self.assertTrue(finalize_res['complete'])
            self.assertTrue(
                self.wrpc.testmempoolaccept([finalize_res['hex']
                                             ])[0]["allowed"])
        return finalize_res['hex']
Пример #5
0
 def fill_psbt(self, b64psbt):
     psbt = PSBT()
     psbt.deserialize(b64psbt)
     for i, inp in enumerate(psbt.tx.vin):
         txid = inp.prevout.hash.to_bytes(32,'big').hex()
         try:
             res = self.cli.gettransaction(txid)
         except:
             raise SpecterError("Can't find previous transaction in the wallet.")
         stream = BytesIO(bytes.fromhex(res["hex"]))
         prevtx = CTransaction()
         prevtx.deserialize(stream)
         psbt.inputs[i].non_witness_utxo = prevtx
     return psbt.serialize()
Пример #6
0
    def sign_tx(self, psbt: PSBT) -> Dict[str, str]:
        """Sign a partially signed bitcoin transaction (PSBT).

        Return {"psbt": <base64 psbt string>}.
        """
        # this one can hang for quite some time
        response = self.query("sign %s" % psbt.serialize())
        signed_psbt = PSBT()
        signed_psbt.deserialize(response)
        # adding partial sigs to initial tx
        for i in range(len(psbt.inputs)):
            for k in signed_psbt.inputs[i].partial_sigs:
                psbt.inputs[i].partial_sigs[k] = signed_psbt.inputs[
                    i].partial_sigs[k]
        return {'psbt': psbt.serialize()}
Пример #7
0
 def create_psbts(self, base64_psbt, wallet):
     psbts = HWIDevice.create_psbts(self, base64_psbt, wallet)
     sdcard_psbt = PSBT()
     sdcard_psbt.deserialize(base64_psbt)
     if len(wallet.keys) > 1:
         for k in wallet.keys:
             key = b'\x01' + decode_base58(k.xpub)
             if k.fingerprint != '':
                 fingerprint = bytes.fromhex(k.fingerprint)
             else:
                 fingerprint = _get_xpub_fingerprint(k.xpub)
             if k.derivation != '':
                 der = _der_to_bytes(k.derivation)
             else:
                 der = b''
             value = fingerprint + der
             sdcard_psbt.unknown[key] = value
     psbts['sdcard'] = sdcard_psbt.serialize()
     return psbts
Пример #8
0
 def create_psbts(self, base64_psbt, wallet):
     psbts = super().create_psbts(base64_psbt, wallet)
     qr_psbt = PSBT()
     qr_psbt.deserialize(base64_psbt)
     for inp in qr_psbt.inputs + qr_psbt.outputs:
         inp.witness_script = b""
         inp.redeem_script = b""
         if len(inp.hd_keypaths) > 0:
             k = list(inp.hd_keypaths.keys())[0]
             # proprietary field - wallet derivation path
             # only contains two last derivation indexes - change and index
             inp.unknown[b"\xfc\xca\x01" +
                         get_wallet_fingerprint(wallet)] = b"".join([
                             i.to_bytes(4, "little")
                             for i in inp.hd_keypaths[k][-2:]
                         ])
             inp.hd_keypaths = {}
     psbts['qrcode'] = qr_psbt.serialize()
     return psbts
Пример #9
0
 def create_psbts(self, base64_psbt, wallet):
     psbts = super().create_psbts(base64_psbt, wallet)
     qr_psbt = PSBT()
     # 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.deserialize(updated_psbt)
     # replace with compressed wallet information
     for inp in qr_psbt.inputs + qr_psbt.outputs:
         inp.witness_script = b""
         inp.redeem_script = b""
         if len(inp.hd_keypaths) > 0:
             k = list(inp.hd_keypaths.keys())[0]
             # proprietary field - wallet derivation path
             # only contains two last derivation indexes - change and index
             wallet_key = b"\xfc\xca\x01" + get_wallet_fingerprint(wallet)
             inp.unknown[wallet_key] = b"".join(
                 [i.to_bytes(4, "little") for i in inp.hd_keypaths[k][-2:]])
             inp.hd_keypaths = {}
     psbts['qrcode'] = qr_psbt.serialize()
     return psbts
Пример #10
0
    def fill_psbt(self, b64psbt, non_witness: bool = True, xpubs: bool = True):
        psbt = PSBT()
        psbt.deserialize(b64psbt)
        if non_witness:
            for i, inp in enumerate(psbt.tx.vin):
                txid = inp.prevout.hash.to_bytes(32, "big").hex()
                try:
                    res = self.gettransaction(txid)
                    stream = BytesIO(bytes.fromhex(res["hex"]))
                    prevtx = CTransaction()
                    prevtx.deserialize(stream)
                    psbt.inputs[i].non_witness_utxo = prevtx
                except:
                    logger.error(
                        "Can't find previous transaction in the wallet. Signing might not be possible for certain devices..."
                    )
        else:
            # remove non_witness_utxo if we don't want them
            for inp in psbt.inputs:
                if inp.witness_utxo is not None:
                    inp.non_witness_utxo = None

        if xpubs:
            # for multisig add xpub fields
            if len(self.keys) > 1:
                for k in self.keys:
                    key = b"\x01" + decode_base58(k.xpub)
                    if k.fingerprint != "":
                        fingerprint = bytes.fromhex(k.fingerprint)
                    else:
                        fingerprint = get_xpub_fingerprint(k.xpub)
                    if k.derivation != "":
                        der = der_to_bytes(k.derivation)
                    else:
                        der = b""
                    value = fingerprint + der
                    psbt.unknown[key] = value
        return psbt.serialize()
Пример #11
0
 def test_invalid_psbt(self):
     for invalid in self.data['invalid']:
         with self.subTest(invalid=invalid):
             with self.assertRaises(PSBTSerializationError) as cm:
                 psbt = PSBT()
                 psbt.deserialize(invalid)
Пример #12
0
def send(args):
    # Load the wallet file
    wallet = load_wallet_file(args.wallet)

    # Load the watch only wallet
    rpc = LoadWalletAndGetRPC(args.wallet, wallet['rpcurl'])

    # Get a change address from the internal keypool
    change_addr = wallet['internal_keypool'][wallet['internal_next']]
    wallet['internal_next'] += 1

    # Write to the wallet
    write_wallet_to_file(args.wallet, wallet)

    # Create the transaction
    outputs = json.loads(args.recipients)
    locktime = rpc.getblockcount()
    psbtx = rpc.walletcreatefundedpsbt([], outputs, locktime, {'changeAddress' : change_addr, 'replaceable' : True, 'includeWatching' : True}, True)
    psbt = psbtx['psbt']

    # HACK: Hack to prevent change detection because hardware wallets are apparently really bad at change detection for multisig change
    tx = PSBT()
    tx.deserialize(psbt)
    for output in tx.outputs:
        output.set_null()
    psbt = tx.serialize()

    # Send psbt to devices to sign
    out = {}
    out['devices'] = []
    psbts = []
    for d in wallet['devices']:
        if 'core_wallet_name' in d:
            wrpc = LoadWalletAndGetRPC(d['core_wallet_name'], d['rpcurl'])
            result = wrpc.walletprocesspsbt(psbt)
            core_out = {'success' : True}
            out['core'] = core_out
            psbts.append(result['psbt'])
        else:
            d_out = {}

            hwi_args = []
            if args.testnet or args.regtest:
                hwi_args.append('--testnet')
            if 'password' in d:
                hwi_args.append('-p')
                hwi_args.append(d['password'])
            hwi_args.append('-f')
            hwi_args.append(d['fingerprint'])
            hwi_args.append('signtx')
            hwi_args.append(psbt)
            result = hwi_command(hwi_args)
            psbts.append(result['psbt'])
            d_out['fingerprint'] = d['fingerprint']
            d_out['success'] = True
            out['devices'].append(d_out)

    # Combine, finalize, and send psbts
    combined = rpc.combinepsbt(psbts)
    finalized = rpc.finalizepsbt(combined)
    if not finalized['complete']:
        out['success'] = False
        out['psbt'] = finalized['psbt']
        return out
    out['success'] = True
    if args.inspecttx:
        out['tx'] = finalized['hex']
    else:
        out['txid'] = rpc.sendrawtransaction(finalized['hex'])
    return out
Пример #13
0
# Open the data file
with open(os.path.join(os.path.dirname(os.path.realpath(__file__)),
                       'data/test_psbt.json'),
          encoding='utf-8') as f:
    d = json.load(f)
    invalids = d['invalid']
    valids = d['valid']
    creators = d['creator']
    signers = d['signer']
    combiners = d['combiner']
    finalizers = d['finalizer']
    extractors = d['extractor']

print("Testing invalid PSBTs")
for invalid in invalids:
    try:
        psbt = PSBT()
        psbt.deserialize(invalid)
        assert False
    except:
        pass

print("Testing valid PSBTs")
for valid in valids:
    psbt = PSBT()
    psbt.deserialize(valid)
    serd = psbt.serialize()
    assert (valid == serd)

print("PSBT Serialization tests pass")