<p>An extended public key contains the chaincode, so it is not stored separately. So the <code>tpub</code>s that you have found will contain the chaincode.</p>
<p>The private keys are not encrypted, and unencrypted private keys are stored using DER serialization. This method produces a private key that is 214 bytes, with the vast majority of the data being irrelevant. Private keys will always begin with the bytes <code>d63081d30201010420</code>. Then the next 32 bytes are the private key's bytes. Everything after that can be discarded.</p>
<p>Once you have the private key, concatenate it with <code>00</code> (so it will be <code>00&lt;private key&gt;</code>), and insert it into the decoded tpub in the place of the public key. Re-encode it as a tprv and you should have the corresponding extended private key for the public key that you have found.</p>

CC BY-SA 4.0
2021-12-20T23:30:16.490Z
2021-12-20T23:30:16.490Z
Ava Chow
<p>I'm trying to explore the database format of the new descriptor wallets in Bitcoin Core v0.21 and v22.0, but I can't quite figure it out.  I've created a new descriptor wallet and dumped it's contents using SQLite viewers:</p>
<pre><code>{wallet.dat}.main.{minversion} = ac970200
{wallet.dat}.main.{walletdescriptorkey, 5bc2366434...be2be5636f} = d63081d302...eb80b8a63f
{wallet.dat}.main.{walletdescriptorcache, 5bc2366434...bc00000000} = 4a0481d4f2...e2b5797bc6
{wallet.dat}.main.{walletdescriptorlhcache, 5bc2366434...bc00000000} = 4a0334157e...3cd72210c0
{wallet.dat}.main.{activeexternalspk, 00} = 5bc2366434...17e16409bc
{wallet.dat}.main.{walletdescriptorkey, 179b711c2e...be2be5636f} = d63081d302...eb80b8a63f
{wallet.dat}.main.{walletdescriptorcache, 179b711c2e...3a00000000} = 4a04c1b8ae...534fc80e4a
{wallet.dat}.main.{walletdescriptorlhcache, 179b711c2e...3a00000000} = 4a03774319...6b8a432640
{wallet.dat}.main.{activeexternalspk, 01} = 179b711c2e...5fffee163a
{wallet.dat}.main.{walletdescriptorkey, 7cf4a0f984...be2be5636f} = d63081d302...eb80b8a63f
{wallet.dat}.main.{walletdescriptorcache, 7cf4a0f984...4b00000000} = 4a045ffe0a...0d113a8596
{wallet.dat}.main.{walletdescriptorlhcache, 7cf4a0f984...4b00000000} = 4a03673c1f...e647a32c18
{wallet.dat}.main.{activeexternalspk, 02} = 7cf4a0f984...8c3dd80a4b
{wallet.dat}.main.{walletdescriptorkey, 7457e8edcb...be2be5636f} = d63081d302...eb80b8a63f
{wallet.dat}.main.{walletdescriptorcache, 7457e8edcb...d300000000} = 4a0481d4f2...c713790faf
{wallet.dat}.main.{walletdescriptorlhcache, 7457e8edcb...d300000000} = 4a0334157e...3cd72210c0
{wallet.dat}.main.{activeinternalspk, 00} = 7457e8edcb...d76c288ed3
{wallet.dat}.main.{walletdescriptorkey, ccd36c1209...be2be5636f} = d63081d302...eb80b8a63f
{wallet.dat}.main.{walletdescriptorcache, ccd36c1209...9400000000} = 4a04c1b8ae...18821059e5
{wallet.dat}.main.{walletdescriptorlhcache, ccd36c1209...9400000000} = 4a03774319...6b8a432640
{wallet.dat}.main.{activeinternalspk, 01} = ccd36c1209...ef094d8e94
{wallet.dat}.main.{walletdescriptorkey, db2cf1cdc6...be2be5636f} = d63081d302...eb80b8a63f
{wallet.dat}.main.{walletdescriptorcache, db2cf1cdc6...6d00000000} = 4a045ffe0a...497189e5c3
{wallet.dat}.main.{walletdescriptorlhcache, db2cf1cdc6...6d00000000} = 4a03673c1f...e647a32c18
{wallet.dat}.main.{flags} = 0000000004000000
{wallet.dat}.main.{activeinternalspk, 02} = db2cf1cdc6...0129f3106d
{wallet.dat}.main.{walletdescriptor, db2cf1cdc6...0129f3106d} = wpkh(tpubD...)#znsakxap, 13159b6100...00e8030000
{wallet.dat}.main.{walletdescriptor, 7457e8edcb...d76c288ed3} = pkh(tpubD6...)#25g5mpq6, 12159b6100...00e8030000
{wallet.dat}.main.{walletdescriptor, 5bc2366434...17e16409bc} = pkh(tpubD6...)#mqd4x5sz, 11159b6100...00e8030000
{wallet.dat}.main.{walletdescriptor, 179b711c2e...5fffee163a} = sh(wpkh(tp...)#0v4dg3a0, 11159b6100...00e8030000
{wallet.dat}.main.{walletdescriptor, 7cf4a0f984...8c3dd80a4b} = wpkh(tpubD...)#n84utnde, 12159b6100...00e8030000
{wallet.dat}.main.{walletdescriptor, ccd36c1209...ef094d8e94} = sh(wpkh(tp...)#f0agnukm, 13159b6100...00e8030000
{wallet.dat}.main.{bestblock} = 605b030000
{wallet.dat}.main.{bestblock_nomerkle} = 605b030021...0900000000
</code></pre>
<p>The descriptors fields clearly show the <code>tpubs</code>, and there may be private keys in the <code>*spk</code> fields.  But in order to do BIP32 derivations, you need both a private key and a chain-code.</p>
<p>I can't seem to create the <code>tprvs</code> from the <code>tpubs</code>, even given private keys.  I've looked through the code at <a href="https://github.com/bitcoin/bitcoin/tree/4018e23/src" rel="nofollow noreferrer">commit 4018e23</a>, and have some vague ideas on how it is constructed, but can't quite seem to figure out which fields hold either a private key and chain-code, or simply a <code>tprv</code>.</p>
<p>There is also the possibility that the key value itself is actually encrypted at rest in the database, but I had assumed that these would use the <code>walletdescriptorckey</code> field for <code>crypted-key</code> as opposed to the <code>walletdescriptorkey</code> fields.  Notice my dump only contains <code>*rkey</code> fields and no <code>rckey</code> fields.</p>
<p>I realize that the next release will expose the <code>tprv</code> in the future <code>listdescriptors</code> command, but I was trying to figure out how <a href="https://github.com/bitcoin/bitcoin/pull/21500" rel="nofollow noreferrer">PR#21500</a> did it.</p>
<p>The code to create the SQL dump file is... essentially:</p>
<pre><code>from sqlite_utils import Database

if __name__ == &quot;__main__&quot;:
    db=Database(&quot;wallet.dat&quot;)
    for row in db['main'].rows:
        key = fmt_key(row['key'])
        value = fmt_key(row['value'])
        
        pref = &quot;{wallet.dat}.main.&quot;
        print(f&quot;{pref}{{{key}}} = {value}&quot;)
</code></pre>
<p>An unabridged copy of the <a href="https://github.com/brianddk/reddit/blob/master/python/sqldump.py" rel="nofollow noreferrer">the code</a> and <a href="https://github.com/brianddk/reddit/blob/master/python/sqldump.txt" rel="nofollow noreferrer">the data</a> can be found here:</p>
<ul>
<li><a href="https://github.com/brianddk/reddit/blob/master/python/sqldump.py" rel="nofollow noreferrer">https://github.com/brianddk/reddit/blob/master/python/sqldump.py</a></li>
<li><a href="https://github.com/brianddk/reddit/blob/master/python/sqldump.txt" rel="nofollow noreferrer">https://github.com/brianddk/reddit/blob/master/python/sqldump.txt</a></li>
</ul>
<p>If you lack sufficent Karma to comment or answer, I've <a href="https://www.reddit.com/r/Bitcoin/comments/r300yi/layout_of_new_sqlite_descriptor_wallets_in_v021/" rel="nofollow noreferrer">XPosted the question on reddit</a></p>

<bitcoin-core><wallet.dat><database><output-script-descriptors>
Layout of new SQLite descriptor wallets in v0.21 and v22.0?
Dan
# Layout of new SQLite descriptor wallets in v0.21 and v22.0?

(Asked by: username_0 on 2021-11-26)

I'm trying to explore the database format of the new descriptor wallets in Bitcoin Core v0.21 and v22.0, but I can't quite figure it out. I've created a new descriptor wallet and dumped it's contents using SQLite viewers:

```
{wallet.dat}.main.{minversion} = ac970200
{wallet.dat}.main.{walletdescriptorkey, 5bc2366434...be2be5636f} = d63081d302...eb80b8a63f
{wallet.dat}.main.{walletdescriptorcache, 5bc2366434...bc00000000} = 4a0481d4f2...e2b5797bc6
{wallet.dat}.main.{walletdescriptorlhcache, 5bc2366434...bc00000000} = 4a0334157e...3cd72210c0
{wallet.dat}.main.{activeexternalspk, 00} = 5bc2366434...17e16409bc
{wallet.dat}.main.{walletdescriptorkey, 179b711c2e...be2be5636f} = d63081d302...eb80b8a63f
{wallet.dat}.main.{walletdescriptorcache, 179b711c2e...3a00000000} = 4a04c1b8ae...534fc80e4a
{wallet.dat}.main.{walletdescriptorlhcache, 179b711c2e...3a00000000} = 4a03774319...6b8a432640
{wallet.dat}.main.{activeexternalspk, 01} = 179b711c2e...5fffee163a
{wallet.dat}.main.{walletdescriptorkey, 7cf4a0f984...be2be5636f} = d63081d302...eb80b8a63f
{wallet.dat}.main.{walletdescriptorcache, 7cf4a0f984...4b00000000} = 4a045ffe0a...0d113a8596
{wallet.dat}.main.{walletdescriptorlhcache, 7cf4a0f984...4b00000000} = 4a03673c1f...e647a32c18
{wallet.dat}.main.{activeexternalspk, 02} = 7cf4a0f984...8c3dd80a4b
{wallet.dat}.main.{walletdescriptorkey, 7457e8edcb...be2be5636f} = d63081d302...eb80b8a63f
{wallet.dat}.main.{walletdescriptorcache, 7457e8edcb...d300000000} = 4a0481d4f2...c713790faf
{wallet.dat}.main.{walletdescriptorlhcache, 7457e8edcb...d300000000} = 4a0334157e...3cd72210c0
{wallet.dat}.main.{activeinternalspk, 00} = 7457e8edcb...d76c288ed3
{wallet.dat}.main.{walletdescriptorkey, ccd36c1209...be2be5636f} = d63081d302...eb80b8a63f
{wallet.dat}.main.{walletdescriptorcache, ccd36c1209...9400000000} = 4a04c1b8ae...18821059e5
{wallet.dat}.main.{walletdescriptorlhcache, ccd36c1209...9400000000} = 4a03774319...6b8a432640
{wallet.dat}.main.{activeinternalspk, 01} = ccd36c1209...ef094d8e94
{wallet.dat}.main.{walletdescriptorkey, db2cf1cdc6...be2be5636f} = d63081d302...eb80b8a63f
{wallet.dat}.main.{walletdescriptorcache, db2cf1cdc6...6d00000000} = 4a045ffe0a...497189e5c3
{wallet.dat}.main.{walletdescriptorlhcache, db2cf1cdc6...6d00000000} = 4a03673c1f...e647a32c18
{wallet.dat}.main.{flags} = 0000000004000000
{wallet.dat}.main.{activeinternalspk, 02} = db2cf1cdc6...0129f3106d
{wallet.dat}.main.{walletdescriptor, db2cf1cdc6...0129f3106d} = wpkh(tpubD...)#znsakxap, 13159b6100...00e8030000
{wallet.dat}.main.{walletdescriptor, 7457e8edcb...d76c288ed3} = pkh(tpubD6...)#25g5mpq6, 12159b6100...00e8030000
{wallet.dat}.main.{walletdescriptor, 5bc2366434...17e16409bc} = pkh(tpubD6...)#mqd4x5sz, 11159b6100...00e8030000
{wallet.dat}.main.{walletdescriptor, 179b711c2e...5fffee163a} = sh(wpkh(tp...)#0v4dg3a0, 11159b6100...00e8030000
{wallet.dat}.main.{walletdescriptor, 7cf4a0f984...8c3dd80a4b} = wpkh(tpubD...)#n84utnde, 12159b6100...00e8030000
{wallet.dat}.main.{walletdescriptor, ccd36c1209...ef094d8e94} = sh(wpkh(tp...)#f0agnukm, 13159b6100...00e8030000
{wallet.dat}.main.{bestblock} = 605b030000
{wallet.dat}.main.{bestblock_nomerkle} = 605b030021...0900000000

```

The descriptors fields clearly show the `tpubs`, and there may be private keys in the `*spk` fields. But in order to do BIP32 derivations, you need both a private key and a chain-code.

I can't seem to create the `tprvs` from the `tpubs`, even given private keys. I've looked through the code at [commit 4018e23](https://github.com/bitcoin/bitcoin/tree/4018e23/src), and have some vague ideas on how it is constructed, but can't quite seem to figure out which fields hold either a private key and chain-code, or simply a `tprv`.

There is also the possibility that the key value itself is actually encrypted at rest in the database, but I had assumed that these would use the `walletdescriptorckey` field for `crypted-key` as opposed to the `walletdescriptorkey` fields. Notice my dump only contains `*rkey` fields and no `rckey` fields.

I realize that the next release will expose the `tprv` in the future `listdescriptors` command, but I was trying to figure out how [PR#21500](https://github.com/bitcoin/bitcoin/pull/21500) did it.

The code to create the SQL dump file is... essentially:

```
from sqlite_utils import Database

if __name__ == "__main__":
    db=Database("wallet.dat")
    for row in db['main'].rows:
        key = fmt_key(row['key'])
        value = fmt_key(row['value'])
        
        pref = "{wallet.dat}.main."
        print(f"{pref}{{{key}}} = {value}")

```

An unabridged copy of the [the code](https://github.com/brianddk/reddit/blob/master/python/sqldump.py) and [the data](https://github.com/brianddk/reddit/blob/master/python/sqldump.txt) can be found here:

* <https://github.com/brianddk/reddit/blob/master/python/sqldump.py>
* <https://github.com/brianddk/reddit/blob/master/python/sqldump.txt>

If you lack sufficent Karma to comment or answer, I've [XPosted the question on reddit](https://www.reddit.com/r/Bitcoin/comments/r300yi/layout_of_new_sqlite_descriptor_wallets_in_v021/)

**Tags:** bitcoin-core, wallet.dat, database, output-script-descriptors


---

## 1 answers

### Answer by username_1 on 2021-12-20. Score: 2 | Accepted answer

An extended public key contains the chaincode, so it is not stored separately. So the `tpub`s that you have found will contain the chaincode.

The private keys are not encrypted, and unencrypted private keys are stored using DER serialization. This method produces a private key that is 214 bytes, with the vast majority of the data being irrelevant. Private keys will always begin with the bytes `d63081d30201010420`. Then the next 32 bytes are the private key's bytes. Everything after that can be discarded.

Once you have the private key, concatenate it with `00` (so it will be `00<private key>`), and insert it into the decoded tpub in the place of the public key. Re-encode it as a tprv and you should have the corresponding extended private key for the public key that you have found.


---

