3

My goal

Learn how to send an imported tr(wif) and wpkh() utxo in the same transaction.

What I've tried

I imported a WIF utxo containing a v1 segwit utxo.

bitcoin-cli importdescriptors '[{"desc":"tr(wifkey)#checksum", "timestamp":unixts}]'

I didn't know the checksum, but when importing I got an error that told me what the checksum should be, so I used that.

The balance showed up after import, I assume it was imported correctly.

Learned createrawtransaction, fundrawtransaction, signrawtransactionwithwallet are not acceptable for taproot. This was resulting in "Witness program was passed an empty witness".

It seems psbt commands are more appropriate. However, I couldn't figure out how to successfully sign with cli. I kept getting "complete": false returned.

I then tried bitcoin-qt and attempted to sweep funds to an address as a test. I received this: fail signing

While using bitcoin-cli and running bitcoin-cli listunspent I see:

[
  {
    "txid": "TX_1",
    "vout": x,
    "address": "ADDR_1",
    "label": "xxx1",
    "scriptPubKey": "44_LEGNTH_HEX_STRING",
    "amount": xxxxxx.xxxxxx,
    "confirmations": xxx,
    "spendable": true,
    "solvable": true,
    "desc": "wpkh([FINGERPRINT/84'/0'/0'/0/0]KEY)#CHECKSUM1",
    "parent_descs": [
      "wpkh(xpub_key/84'/0'/0'/0/*)#CHECKSUM-parent"
    ],
    "safe": true
  },
  {
    "txid": "TX_2",
    "vout": xx,
    "address": "ADDR_2",
    "label": "xxx2",
    "scriptPubKey": "68_LENGTH_HEX_STRING",
    "amount": xxxxxx.xxxxxx,
    "confirmations": xxxx,
    "spendable": true,
    "solvable": true,
    "desc": "tr([FINGERPRINT2]KEY2)#CHECKSUM2",
    "parent_descs": [
      "tr(KEY2)#CHECKSUM2"
    ],
    "safe": true
  }
]

Output sanitized

Question

  • What are the methodical steps starting with an imported WIF/taproot utxo + wpkh utxo and creating a signed tx to send funds?
Antoine Poinsot
  • 5,881
  • 2
  • 11
  • 28
Michael Tidwell
  • 472
  • 2
  • 11
  • 1
    You need to use a descriptor wallet, and import the key as a taproot descriptor (if it's just a key path key, it would be `tr(WIF)`, but scripts can also be included there). Further, you need to use PSBT RPCs; the old raw transacrion RPCs in general do not support taproot. – Pieter Wuille Jun 04 '23 at 11:32
  • I'm currently doing `createpsbt` then `walletcreatefundedpsbt` but not sure where to go from here. I've tried both `walletprocesspsbt` and `finalizepsbt` but they return the base64 encoding and `"complete": false`. I've tried `utxoupdatepsbt` but unsure what I would need to do since I've already successfully imported this into my wallet. What steps am I missing? – Michael Tidwell Jun 04 '23 at 21:51
  • It would be useful if you'd include the exact commands and output you're seeing in your question. If need be, you can try to recreate on regtest / signet / testnet. – Pieter Wuille Jun 04 '23 at 21:58
  • I tried to reproduce everything on regtest, but I keep getting `"complete": true` and no issues like what I'm seeing on mainnet. The main difference I see is when I do `getaddressinfo` on regtest it shows I'm using an hd key I believe via: ```"desc": "tr([e4b96d59/86'/1'/0'/0/0]d5de651411b49df50bc37a17620755ec019866332794cc3e73a6effbb766aa1c)#kh27dth2",``` on mainnet when I run `getaddressinfo` I see something similar minus the `/86'/1'/0'/0/0`. – Michael Tidwell Jun 04 '23 at 23:36
  • It would seem that the way you import things on mainnet and on regtest differs then. – Pieter Wuille Jun 04 '23 at 23:55
  • Yeah, I'm trying to reproduce, I did find another discrepancy though, `"isrange": false,` on mainnet but true on regtest. What does this mean? I get this after running the `getdescriptorinfo` command – Michael Tidwell Jun 05 '23 at 00:12
  • 1
    A `tr(WIF)` descriptor is not ranged, so that's clearly not what you're doing on regtest – Pieter Wuille Jun 05 '23 at 00:18
  • Let us [continue this discussion in chat](https://chat.stackexchange.com/rooms/146473/discussion-between-michael-tidwell-and-pieter-wuille). – Michael Tidwell Jun 05 '23 at 00:40
  • When looking at the output of `getdescriptorinfo` why would `solvable` be `true` but `hasprivatekeys` be `false`. After doing a non ranged tr(wif) import? Shouldn't they both be `true`? – Michael Tidwell Jun 05 '23 at 02:56
  • This is all impossible to say without knowing what your actual commands are. – Pieter Wuille Jun 05 '23 at 03:11
  • 1. `bitcoin-cli importdescriptors '[{"desc":"tr(WIF)", "timestamp":UNIXTIME}]'` 2. `bitcoin-cli listdescriptors` > (grab the `desc` for the imported wif = "tr(hex)#CHECKSUM") 3. `bitcoin-cli getdescriptorinfo "DESC"` – Michael Tidwell Jun 05 '23 at 03:22
  • `listdescriptors` will give descriptors without private key material (the "hex" you state is the public key); that's why it says hasprivatekeys false. But please update your question to reflect all information, with the commands you did, and the responses you get. It's very hard to discuss this via comments. – Pieter Wuille Jun 05 '23 at 03:27
  • so.. just working through this 1. `bitcoin-cli createpsbt '[{"txid":"TX_ASSOCIATED_WITH_WIF","vout":INDEX}]' '[{"BTC_ADDRESS":AMOUNT},{ "data": "WHAT_IS_SUPPOSED_TO_GO_HERE?"}]'` I've been putting `00` for data maybe that is incorrect? – Michael Tidwell Jun 05 '23 at 03:37
  • Similarly for `walletcreatefundedpsbt` with the same json parameter. Not sure if `00` is ok. Or how to find out what to put for this. – Michael Tidwell Jun 05 '23 at 03:45
  • 2
    Whatever you want to go in an OP_RETURN goes there. But again, please update your question with whatever your question has now evolved to be – Pieter Wuille Jun 05 '23 at 11:30
  • In the future, especially if you don’t have answers yet, please edit instead of opening a new question. – Murch Jun 06 '23 at 02:38
  • @Murch thanks for the tip. I edited the original post with most up to date info. – Michael Tidwell Jun 06 '23 at 03:11
  • Wrong: _"Further, you need to use PSBT RPCs; the old raw transacrion RPCs in general do not support taproot. – Pieter Wuille Jun 4 at 11:32"_ . As a matter of fact, Bitcoin Core v. 24 RPC commands like `createrawtransaction` and `signrawtransactionwithwallet` support Taproot (P2TR). Also, the goal could be acheived without using any PSBT-related RPC. – Greg Tonoski Jun 07 '23 at 12:35
  • There is the example of using the raw transaction RPC commands: [https://bitcoin.stackexchange.com/a/117013/135945](https://bitcoin.stackexchange.com/a/117013/135945). – Greg Tonoski Jun 07 '23 at 12:42

1 Answers1

2

Regularly you'd use walletcreatefundedpsbt and then walletprocesspsbt. But since you've only got two coins in your wallet that you want to sweep you can simply use sendall.

Let's walk through a reproduction of your scenario. I've created two regtest-WIF-encoded private keys:

  • cRZ9wuYhwKYm82rXfSk9hhc9beJy6p7rV4y3aoAdzqFD6YAUVQcJ (corresponding to 029d759b09cec94b6f2bd322cedd5d136e7f24a4936998fe24da09827d8a12e9dd)
  • cVFkDn9fS1rwU1SR7geC3rgoGNXLpZXsnrCq2gutYVsGA2FoLpCD (corresponding to 03bd0d0b820dae844107803c9323fe73e81b9adf8abe329930ae9311dab6c3573e)

I'm creating a new wallet and importing a tr() and wpkh() descriptor.

$ bitcoin-cli -regtest createwallet repro_stackexchange
{
  "name": "repro_stackexchange",
  "warning": ""
}
$ bitcoin-cli -regtest -rpcwallet=repro_stackexchange importdescriptors '[{"desc":"tr(cRZ9wuYhwKYm82rXfSk9hhc9beJy6p7rV4y3aoAdzqFD6YAUVQcJ)#uj39yr2v","timestamp":"now"},{"desc":"wpkh(cVFkDn9fS1rwU1SR7geC3rgoGNXLpZXsnrCq2gutYVsGA2FoLpCD)#nayxphlw","timestamp":"now"}]'
[
  {
    "success": true
  },
  {
    "success": true
  }
]

Now i'm sending a coin to each descriptor:

$ bitcoin-cli -named -regtest -rpcwallet=main sendtoaddress address=bcrt1ps66a3wss3l0ay0agnkd57cfu6qrw26z632e0kez0ppjzkyaq0vhskxlrvs amount=0.00042 fee_rate=1
b2e975dee4ab0c68748e85fc847d7ef3fef44f53ee95e4765b1cc2b2c33939d2
$ bitcoin-cli -named -regtest -rpcwallet=main sendtoaddress address=bcrt1q9vsfxgf4t3wmtj9429g539txyv4p2qnuak03sv amount=0.00043 fee_rate=1
e34a3b8116f328d2b1529134dfcc795fbe27eae3991eeb017bc582879cb73b81

We can see that the reproduction wallet got the two coins:

$ bitcoin-cli -regtest -rpcwallet=repro_stackexchange listunspent 0
[
  {
    "txid": "e34a3b8116f328d2b1529134dfcc795fbe27eae3991eeb017bc582879cb73b81",
    "vout": 0,
    "address": "bcrt1q9vsfxgf4t3wmtj9429g539txyv4p2qnuak03sv",
    "label": "",
    "scriptPubKey": "00142b209321355c5db5c8b55151489566232a15027c",
    "amount": 0.00043000,
    "confirmations": 0,
    "ancestorcount": 1,
    "ancestorsize": 130,
    "ancestorfees": 130,
    "spendable": true,
    "solvable": true,
    "desc": "wpkh([2b209321]03bd0d0b820dae844107803c9323fe73e81b9adf8abe329930ae9311dab6c3573e)#k6qfdnw5",
    "parent_descs": [
      "wpkh(03bd0d0b820dae844107803c9323fe73e81b9adf8abe329930ae9311dab6c3573e)#45n94hja"
    ],
    "safe": false
  },
  {
    "txid": "b2e975dee4ab0c68748e85fc847d7ef3fef44f53ee95e4765b1cc2b2c33939d2",
    "vout": 1,
    "address": "bcrt1ps66a3wss3l0ay0agnkd57cfu6qrw26z632e0kez0ppjzkyaq0vhskxlrvs",
    "label": "",
    "scriptPubKey": "512086b5d8ba108fdfd23fa89d9b4f613cd006e5685a8ab2fb644f08642b13a07b2f",
    "amount": 0.00042000,
    "confirmations": 0,
    "ancestorcount": 1,
    "ancestorsize": 154,
    "ancestorfees": 154,
    "spendable": true,
    "solvable": true,
    "desc": "tr([caa3ae90]9d759b09cec94b6f2bd322cedd5d136e7f24a4936998fe24da09827d8a12e9dd)#e2lr407t",
    "parent_descs": [
      "tr(9d759b09cec94b6f2bd322cedd5d136e7f24a4936998fe24da09827d8a12e9dd)#u9ras3xm"
    ],
    "safe": false
  }
]

Now i'll get the transactions confirmed and sweep the coins using sendall:

$ bitcoin-cli -regtest -rpcwallet=main generatetoaddress 1 $(bitcoin-cli -regtest -rpcwallet=main getnewaddress)
$ bitcoin-cli -named -regtest -rpcwallet=repro_stackexchange sendall recipients='["bcrt1qre2fezscuke8qaj9ham2klddj2rt7m4s78tvqq"]' fee_rate=1
{
  "txid": "a39b70bf4636bb78053d10a3bc03eb6866e6777e84aeb274bb4002a543b7bb99",
  "complete": true
}

Let's inspect the transaction. It does indeed spend the two coins from the wallet (one Taproot and one native segwit v0 as you can see from the witness):

$ bitcoin-cli -regtest getrawtransaction a39b70bf4636bb78053d10a3bc03eb6866e6777e84aeb274bb4002a543b7bb99 1
{
  "txid": "a39b70bf4636bb78053d10a3bc03eb6866e6777e84aeb274bb4002a543b7bb99",
  "hash": "be1fcf89ec92649ee0780a8ea86ade1512a7fff6cfc8f75430b1115f1321b8f1",
  "version": 2,
  "size": 298,
  "vsize": 167,
  "weight": 667,
  "locktime": 0,
  "vin": [
    {
      "txid": "e34a3b8116f328d2b1529134dfcc795fbe27eae3991eeb017bc582879cb73b81",
      "vout": 0,
      "scriptSig": {
        "asm": "",
        "hex": ""
      },
      "txinwitness": [
        "304402201e3ba83ca58a357e72d9df4d555f11a747e128ab2131868cb5620afc9b383d540220011c6fe77e37ed8ca83d50870fc5cc3995ecbd1cbd6305ede59bd4fd8aa05c2901",
        "03bd0d0b820dae844107803c9323fe73e81b9adf8abe329930ae9311dab6c3573e"
      ],
      "sequence": 4294967293
    },
    {
      "txid": "b2e975dee4ab0c68748e85fc847d7ef3fef44f53ee95e4765b1cc2b2c33939d2",
      "vout": 1,
      "scriptSig": {
        "asm": "",
        "hex": ""
      },
      "txinwitness": [
        "6cd85a806478d00f062e0bd7a43f46d7be082e4287819fcf07d3dda1d6a613a11e1197d15021f87aad16a697e326181d82156ff957fcb6593f5d030f33516471"
      ],
      "sequence": 4294967293
    }
  ],
  "vout": [
    {
      "value": 0.00084833,
      "n": 0,
      "scriptPubKey": {
        "asm": "0 1e549c8a18e5b2707645bf76ab7dad9286bf6eb0",
        "desc": "addr(bcrt1qre2fezscuke8qaj9ham2klddj2rt7m4s78tvqq)#5gumgp89",
        "hex": "00141e549c8a18e5b2707645bf76ab7dad9286bf6eb0",
        "address": "bcrt1qre2fezscuke8qaj9ham2klddj2rt7m4s78tvqq",
        "type": "witness_v0_keyhash"
      }
    }
  ],
  "hex": "02000000000102813bb79c8782c57b01eb1e99e3ea27be5f79ccdf349152b1d228f316813b4ae30000000000fdffffffd23939c3b2c21c5b76e495ee534ff4fef37e7d84fc858e74680cabe4de75e9b20100000000fdffffff01614b0100000000001600141e549c8a18e5b2707645bf76ab7dad9286bf6eb00247304402201e3ba83ca58a357e72d9df4d555f11a747e128ab2131868cb5620afc9b383d540220011c6fe77e37ed8ca83d50870fc5cc3995ecbd1cbd6305ede59bd4fd8aa05c29012103bd0d0b820dae844107803c9323fe73e81b9adf8abe329930ae9311dab6c3573e01406cd85a806478d00f062e0bd7a43f46d7be082e4287819fcf07d3dda1d6a613a11e1197d15021f87aad16a697e326181d82156ff957fcb6593f5d030f3351647100000000"
}
Antoine Poinsot
  • 5,881
  • 2
  • 11
  • 28
  • Antoine, great thorough answer. I do have a concept question follow up. Can you take any WIF and import it as any descriptor type? For instance if you previously had a taproot utxo/descriptor would you need to make sure that was imported as a tr()? or could you import also as wpkh()? – Michael Tidwell Jun 07 '23 at 03:06
  • Antoine, another followup question. When i run something like `bitcoin-cli -rpcwallet=wallet_x2 getnewaddress "label" "bech32m"` How do I know it's using the imported tr() descriptor? and not the wallet's generic tr ranged descriptor? – Michael Tidwell Jun 07 '23 at 03:43
  • You can use a WIF encoded private key everywhere a key expression is expected. – Antoine Poinsot Jun 07 '23 at 07:54
  • 1
    You can set a descriptor as active when importing it to use it to generate receive addresses (but it needs to be ranged if i'm remembering correctly). Otherwise just use `deriveaddresses`. – Antoine Poinsot Jun 07 '23 at 07:56