Author

Topic: Comment my bitcoin address generation workflow (Read 93 times)

copper member
Activity: 821
Merit: 1992
December 12, 2024, 06:58:03 AM
#5
Quote
Does it still mean that if I save that "listdescriptors true" output, I can always get all my coins back from it, change or not?
Yes.

Quote
I think my descriptors are precisely "hardened", ie: they have single quotes in them.
But: if you use default settings, then probably not all keys are hardened. And the last keys are probably non-hardened. You can compare it with my example. You should get the same results, even if no wallet is loaded:
Code:
deriveaddresses "wpkh([cb4cc245/84h/1h/0h]tpubDCinLkxqJCQE4Bqj9UC4nmsrVrWQJFTMh5uT3LwsuU9PNA8QW6MZE9Gr6oLMRnMJpUqHvY8BkjKP8ECZBvhwFda52pXgamyJ1czQ8APe9ca/0/*)#l34fcp03" "[0,1]"
[
  "bcrt1qt3g6gh0j0ry2j837jm0wgv726arcxvehulkk6a",
  "bcrt1q79gdtgxpz577kdhfdhvveyfg2fff6h5ptdzuyt"
]
listdescriptors
No wallet is loaded. Load a wallet using loadwallet or create a new one with createwallet. (Note: A default wallet is no longer automatically created) (code -18)
Which means, that if you put your results from "listdescriptors" here, then you can check, if it works for your keys. For deriving keys, you need only "tpub" (testnet master public keys), because "tprv" (testnet master private keys) are needed only for spending.

Quote
Do I understand correctly that the best is to use "fundrawtransaction" in between, in order to let the wallet decide on the best UTXO set to use?
Well, I always manually pick all inputs and outputs. Also because Core wallet usually gives too huge fee estimates, or pick UTXOs differently, than I want. But: in case of automating things, I would probably batch as many things, as possible, which means collecting all withdrawal requests, and handling them every sometimes (once per 6 hours, once per day, or something similar). It is also possible to offer faster processing, but then, fees will naturally be higher, because then, transactions are not batched, if you send coins to a single user.

I remember, when as a customer, I paid for example only 80 satoshis, when my withdrawal was batched in a group of hundreds of users, and when mempools were below 4vMB, and accepted 1 sat/vB fees. As a single user, if I would handle it manually, I would probably pay at least 110 satoshis, if not more. So: batching is the way to go, if customers are not in a hurry.
copper member
Activity: 125
Merit: 15
Thanks for your replies.

Quote
Quote
Should I also "pre-generate" change addresses?
In case of descriptor wallets, you don't have to "pre-generate" anything. As long as you have non-hardened derivation, and you know the master public key, you can derive public keys from that.

I think my descriptors are precisely "hardened", ie: they have single quotes in them. Does it still mean that if I save that "listdescriptors true" output, I can always get all my coins back from it, change or not?


Quote
I just do things manually, by using "createrawtransaction", and then "signrawtransactionwithwallet", to control everything.
Do I understand correctly that the best is to use "fundrawtransaction" in between, in order to let the wallet decide on the best UTXO set to use? Might as well just make the effort and ditch "sendtoaddress" alltogether at this point.

copper member
Activity: 821
Merit: 1992
Quote
Does it look reasonable?
Yes.

Quote
What about change addresses?
They are similar, but they just use a little bit different descriptors. You import them in a similar way, as regular ones, just put a different path here (as far as I remember, replacing "0" with "1" in derivation path, should do the trick).

This is regular address:
Code:
getnewaddress
bcrt1qt3g6gh0j0ry2j837jm0wgv726arcxvehulkk6a
getaddressinfo bcrt1qt3g6gh0j0ry2j837jm0wgv726arcxvehulkk6a
{
  "address": "bcrt1qt3g6gh0j0ry2j837jm0wgv726arcxvehulkk6a",
  "scriptPubKey": "00145c51a45df278c8a91e3e96dee433cad747833337",
  "ismine": true,
  "solvable": true,
  "desc": "wpkh([cb4cc245/84h/1h/0h/0/0]0252b21103c4875db871378a804c32b28432e7aa258b6c9faf947f4ac562257f72)#9kjzpwfl",
  "parent_desc": "wpkh([cb4cc245/84h/1h/0h]tpubDCinLkxqJCQE4Bqj9UC4nmsrVrWQJFTMh5uT3LwsuU9PNA8QW6MZE9Gr6oLMRnMJpUqHvY8BkjKP8ECZBvhwFda52pXgamyJ1czQ8APe9ca/0/*)#l34fcp03",
  "iswatchonly": false,
  "isscript": false,
  "iswitness": true,
  "witness_version": 0,
  "witness_program": "5c51a45df278c8a91e3e96dee433cad747833337",
  "pubkey": "0252b21103c4875db871378a804c32b28432e7aa258b6c9faf947f4ac562257f72",
  "ischange": false,
  "timestamp": 1733986112,
  "hdkeypath": "m/84h/1h/0h/0/0",
  "hdseedid": "0000000000000000000000000000000000000000",
  "hdmasterfingerprint": "cb4cc245",
  "labels": [
    ""
  ]
}
This is change address:
Code:
decoderawtransaction 02000000000101977f70fe8d23e93b3c864a2e21f97962c7c2ac38aeefb374cf17a18d7e3ddf560000000000fdffffff0200ca9a3b00000000160014f150d5a0c1153deb36e96dd8cc912852529d5e8173276bee00000000160014d7ba14dab01391b5b10d22ccff306e3995a5caaa02473044022052d8131d43c7131b8430e28a14d79d2626357f1754187ab4b19b4097f739648e022026bb5b9d5c4541541922b57186f076a495e72c67e22acf974b54f8853b0be77801210252b21103c4875db871378a804c32b28432e7aa258b6c9faf947f4ac562257f7265000000
{
  "txid": "2e7ad6ee50db4d4505097e4815e50abd9fc12614388bd1ec90748b779e3902b6",
  "hash": "0189c8f460d3731f0fc47758517863f1591450d590867eba9b6511d88452342d",
  "version": 2,
  "size": 222,
  "vsize": 141,
  "weight": 561,
  "locktime": 101,
  "vin": [
    {
      "txid": "56df3d7e8da117cf74b3efae38acc2c76279f9212e4a863c3be9238dfe707f97",
      "vout": 0,
      "scriptSig": {
        "asm": "",
        "hex": ""
      },
      "txinwitness": [
        "3044022052d8131d43c7131b8430e28a14d79d2626357f1754187ab4b19b4097f739648e022026bb5b9d5c4541541922b57186f076a495e72c67e22acf974b54f8853b0be77801",
        "0252b21103c4875db871378a804c32b28432e7aa258b6c9faf947f4ac562257f72"
      ],
      "sequence": 4294967293
    }
  ],
  "vout": [
    {
      "value": 10.00000000,
      "n": 0,
      "scriptPubKey": {
        "asm": "0 f150d5a0c1153deb36e96dd8cc912852529d5e81",
        "desc": "addr(bcrt1q79gdtgxpz577kdhfdhvveyfg2fff6h5ptdzuyt)#9yhcs8hc",
        "hex": "0014f150d5a0c1153deb36e96dd8cc912852529d5e81",
        "address": "bcrt1q79gdtgxpz577kdhfdhvveyfg2fff6h5ptdzuyt",
        "type": "witness_v0_keyhash"
      }
    },
    {
      "value": 39.99999859,
      "n": 1,
      "scriptPubKey": {
        "asm": "0 d7ba14dab01391b5b10d22ccff306e3995a5caaa",
        "desc": "addr(bcrt1q67apfk4szwgmtvgdytx07vrw8x26tj4260jefq)#5jcwue40",
        "hex": "0014d7ba14dab01391b5b10d22ccff306e3995a5caaa",
        "address": "bcrt1q67apfk4szwgmtvgdytx07vrw8x26tj4260jefq",
        "type": "witness_v0_keyhash"
      }
    }
  ]
}
getaddressinfo bcrt1q67apfk4szwgmtvgdytx07vrw8x26tj4260jefq
{
  "address": "bcrt1q67apfk4szwgmtvgdytx07vrw8x26tj4260jefq",
  "scriptPubKey": "0014d7ba14dab01391b5b10d22ccff306e3995a5caaa",
  "ismine": true,
  "solvable": true,
  "desc": "wpkh([cb4cc245/84h/1h/0h/1/0]032f34c09c0d3d8f0e5ddc1291d26d4f4678d2204d97c1fe5bfc2d1b9d8e06af86)#qwg0al2l",
  "parent_desc": "wpkh([cb4cc245/84h/1h/0h]tpubDCinLkxqJCQE4Bqj9UC4nmsrVrWQJFTMh5uT3LwsuU9PNA8QW6MZE9Gr6oLMRnMJpUqHvY8BkjKP8ECZBvhwFda52pXgamyJ1czQ8APe9ca/1/*)#w9sg95lf",
  "iswatchonly": false,
  "isscript": false,
  "iswitness": true,
  "witness_version": 0,
  "witness_program": "d7ba14dab01391b5b10d22ccff306e3995a5caaa",
  "pubkey": "032f34c09c0d3d8f0e5ddc1291d26d4f4678d2204d97c1fe5bfc2d1b9d8e06af86",
  "ischange": true,
  "timestamp": 1733986112,
  "hdkeypath": "m/84h/1h/0h/1/0",
  "hdseedid": "0000000000000000000000000000000000000000",
  "hdmasterfingerprint": "cb4cc245",
  "labels": [
  ]
}
As you can see, they are similar:
Code:
"parent_desc": "wpkh([cb4cc245/84h/1h/0h]tpubDCinLkxqJCQE4Bqj9UC4nmsrVrWQJFTMh5uT3LwsuU9PNA8QW6MZE9Gr6oLMRnMJpUqHvY8BkjKP8ECZBvhwFda52pXgamyJ1czQ8APe9ca/0/*)#l34fcp03"   regular address
"parent_desc": "wpkh([cb4cc245/84h/1h/0h]tpubDCinLkxqJCQE4Bqj9UC4nmsrVrWQJFTMh5uT3LwsuU9PNA8QW6MZE9Gr6oLMRnMJpUqHvY8BkjKP8ECZBvhwFda52pXgamyJ1czQ8APe9ca/1/*)#w9sg95lf"   change address

Quote
Should I also "pre-generate" change addresses?
In case of descriptor wallets, you don't have to "pre-generate" anything. As long as you have non-hardened derivation, and you know the master public key, you can derive public keys from that.
Code:
deriveaddresses "wpkh([cb4cc245/84h/1h/0h]tpubDCinLkxqJCQE4Bqj9UC4nmsrVrWQJFTMh5uT3LwsuU9PNA8QW6MZE9Gr6oLMRnMJpUqHvY8BkjKP8ECZBvhwFda52pXgamyJ1czQ8APe9ca/0/*)#l34fcp03" "[0,1]"
[
  "bcrt1qt3g6gh0j0ry2j837jm0wgv726arcxvehulkk6a",
  "bcrt1q79gdtgxpz577kdhfdhvveyfg2fff6h5ptdzuyt"
]
deriveaddresses "wpkh([cb4cc245/84h/1h/0h]tpubDCinLkxqJCQE4Bqj9UC4nmsrVrWQJFTMh5uT3LwsuU9PNA8QW6MZE9Gr6oLMRnMJpUqHvY8BkjKP8ECZBvhwFda52pXgamyJ1czQ8APe9ca/1/*)#w9sg95lf" 0
[
  "bcrt1q67apfk4szwgmtvgdytx07vrw8x26tj4260jefq"
]
One important thing: compromising the master public key, and a child private key, will compromise all private keys, derived from this particular public key. But: if you never reveal those private keys, you shouldn't worry about that.

Quote
Is using "getnewaddress" the proper way to do it?
As shown above, if you know the descriptor, then you can derive keys from any range. But: if you call "getnewaddress", then it will be marked as "used", so the next call to "getnewaddress" will give you the next one in the queue.

Quote
Why oh why is there not an option to set a change address in "sendtoaddress"?
I don't know. But usually, I just do things manually, by using "createrawtransaction", and then "signrawtransactionwithwallet", to control everything.

Quote
Is it me or dealing with crypto daemons only gets more complex as time passes, instead of getting simpler?
Yes, things are getting more and more complex. In the past, you could just stick with P2PK, and not worry too much about it. And there was also just "generate" command, which was enough to mine some blocks, without connecting to any mining pool. But: as more people are jumping into the crypto world, it will be more and more complex, because they will create new systems, new layers, and sooner or later, you won't even have a single UTXO per user, because it would be too expensive, to do that on-chain (and then, you would need another API, to join and split many keys and signatures into single addresses and coins, and to handle multi-user transaction, just moving a single coin, from one UTXO to another).
legendary
Activity: 2618
Merit: 6452
Self-proclaimed Genius
QUESTIONS:
  • Does it look reasonable?
  • What about change addresses? I couldn't figure out how to deal with them. My app uses sendtoaddress at this time and I'm not planning on implementing raw transactions for a bit. I need to make sure that a deposit address is never used for sending change and also that after descriptors restoring on a new server, I properly get all change UTXOs available and visible in listunspent and whatnot. Should I also "pre-generate" change addresses?
  • Is using "getnewaddress" the proper way to do it? Should I use something else, like "derivateaddresses" (but then "listreceivedbyaddress 0 true" doesn't display them)?
  • Reasonable, yes.

  • In your descriptors, those active descriptors with "internal: true" result are used for generating your change addresses, those are never used when using getnewaddress command.
    Notice that each has "range" values, those indicate how many keys Bitcoin Core will check upon scanning for related transactions.
    The default is 1000 from the last used key, so when importing the descriptor, you may increase the range based from the number of change address that you think you've used before.

  • Considering the info above, you can set the range when importing the descriptors to: "range": [0,10000] or more so Bitcoin Core will scan 10,000 addresses per descriptor which will be included in listreceivedbyaddress command result. (after a rescan)

Quote
  • Why oh why is there not an option to set a change address in "sendtoaddress"?
The experimental send command has it but as stated in its help tooltip, it may change in future releases.
copper member
Activity: 125
Merit: 15
Hi all,

So I'm reviving a paused project and it involves receiving bitcoin deposits from users, each user getting assigned a distinct deposit address.

My concern is to be able to safely backup the private keys in case the bitcoin node crashes.

A few years ago, using a vanilla BDB wallet, I implemented it by pre-generating a large list of addresses through "getnewaddress", setting like half of them to the "change" label, using "dumpprivkey" on them all, encrypting the file and saving it somewhere safe. In case of a dramatic crash, I could just import those private keys on the new server, and recover control of all UTXOs.

Enter the descriptor wallets.

While I have no doubt descriptors are vastly superior to whatever we had before (I skipped the hdseed era alltogether), there is an obvious problem - they are a lot trickier to deal with if you're not THAT well versed into bitcoin core. So after a few days of wading through docs, stackexchange, random articles here and there, I came up with a workflow and would appreciate if somebody with a clue could (in)validate it.

My concern is obviously to make sure I never lose an UTXO due to a server/disk disaster. I'll use "test" for the wallet name as I'm working with the testnet. I suppose everything should translate to mainnet seamlessly. Bitcoin core version is 28.0. For the sake of the example, let's pretend that I want to generate 10k deposit addresses.


A. GET A SET OF DESCRIPTORS
  • start bitcoind
  • bitcoin-cli createwallet test
  • bitcoin-cli listdescriptors true
  • save the JSON output of the last command somewhere safe, name it descriptors.txt. I can always spend any coin received thanks to it.

B. GENERATE ADDRESSES
  • repeat 10,000 times: bitcoin-cli getnewaddress
  • save the output of the last command batch - it's the deposit addresses that I can now assign to users as needed

C. DISK CRASH - I HAVE A FRESH BITCOIN CORE SERVER
  • grab the descriptors.txt file
  • start bitcoind
  • bitcoin-cli createwallet test false true (we create an empty wallet with no descriptors)
  • use bitcoin-cli importdescriptors with the contents of descriptors.txt
  • repeat 10,000 times: bitcoin-cli getnewaddress (this is supposed to give us the exact same 10k addresses)
  • all done!



QUESTIONS:
  • Does it look reasonable?
  • What about change addresses? I couldn't figure out how to deal with them. My app uses sendtoaddress at this time and I'm not planning on implementing raw transactions for a bit. I need to make sure that a deposit address is never used for sending change and also that after descriptors restoring on a new server, I properly get all change UTXOs available and visible in listunspent and whatnot. Should I also "pre-generate" change addresses?
  • Is using "getnewaddress" the proper way to do it? Should I use something else, like "derivateaddresses" (but then "listreceivedbyaddress 0 true" doesn't display them)?

RHETORIC QUESTIONS:
  • Why oh why is there not an option to set a change address in "sendtoaddress"? I mean, I understand the privacy concern, but it could be disabled by default, so only people who don't care about concealing the payment/change discrimination would use it, on purpose. Maybe only allow it through RPC and disallow in the GUI, etc.[/size]
  • Is it me or dealing with crypto daemons only gets more complex as time passes, instead of getting simpler? Last Geth update ditching personal_ namespace commands just made stuff harder too. Sheesh.

Thanks for your comments.


Jump to: