> ## Documentation Index
> Fetch the complete documentation index at: https://trustodian-recovery.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Bitcoin vaults

> Rebuilding a Bitcoin multisig vault from its output descriptor in Sparrow, Specter or Bitcoin Core

A Bitcoin vault is an M-of-N multisig. Its address is not a key: it is the hash of a **witness
script** listing every owner's public key. Your recovery phrase gives you one of those keys. It does
not, by itself, tell any wallet what the vault is.

What does is the **output descriptor**, printed on the vault's page in your Recovery Kit.

> [!WARNING]
>
> Download the Recovery Kit before you need it. It covers only the vaults that existed when it was
> generated. Without the descriptor, or without enough of its parts to rebuild it, the funds cannot
> be reached, no matter how many recovery phrases you hold.

## What you need

From the vault's page in the Recovery Kit:

| Field | Why |
| :- | :- |
| Output descriptor | The whole vault in one line. This is the import |
| Receive descriptor, Change descriptor | The same wallet split in two, for importers that cannot read the multipath form |
| Vault address | What you check the import against |
| First receive address | A second, stronger check, derived from the descriptor |
| Witness script | The script behind the vault address, in hex. A fallback if the descriptor is unusable |
| Threshold | How many owners must sign |
| Birthday block height | Where a chain scan starts, instead of at genesis |
| Cosigners: derivation path, master fingerprint, account xpub | The parts the descriptor is built from |

> [!WARNING]
>
> **Plus enough owners, when the time comes to spend.** The descriptor rebuilds the vault and finds
> the money; it authorises nothing. Moving funds still needs the threshold met by separate people
> holding separate phrases.
>
> This is usually the hard part of a real recovery. Finding the descriptor takes minutes; reaching
> three owners may not be possible at all. Know who they are and how to contact them before you need
> them.

> [!TIP]
>
> The app can save the descriptor as a file, `trustodian-vault-<id>-descriptor.txt`, containing the
> descriptor alone on one line. Take that rather than retyping. A descriptor copied off the printed
> page arrives with the page's line breaks inside it and must be rejoined into a single unbroken
> line.

## Reading the descriptor

A 2-of-2 vault, with the extended keys shortened so the shape is visible:

```
wsh(sortedmulti(2,[a1b2c3d4/48'/0'/0'/2']xpub6.../<0;1>/*,[e5f6a7b8/48'/0'/1'/2']xpub6.../<0;1>/*))#checksum
```

| Part | Meaning |
| :- | :- |
| `wsh(...)` | Native segwit P2WSH. The address begins `bc1q` and is long |
| `sortedmulti(2,...)` | 2 of the listed keys must sign |
| `[a1b2c3d4/48'/0'/0'/2']` | Key origin: that cosigner's master fingerprint and the BIP-48 account the xpub came from |
| `xpub6...` | The cosigner's neutered account extended public key |
| `/<0;1>/*` | Multipath: branch 0 is receive, branch 1 is change, `*` ranges over the index |
| `#checksum` | Eight characters: the BIP-380 checksum over everything before the `#` |

> [!NOTE]
>
> **The checksum is a safety net, not decoration.** Change any character of the descriptor and the
> checksum no longer matches, and a wallet rejects the import outright instead of quietly building a
> different wallet at a different address. Always keep the `#` and the eight characters after it. If
> an importer says the checksum is invalid, you have a transcription error. Find it rather than
> stripping the checksum off.

> [!WARNING]
>
> **`sortedmulti`, never `multi`.** They are not interchangeable. `sortedmulti` sorts the derived
> public keys at each index before building the script; `multi` keeps the order as written. The same
> keys under `multi` produce a **different address** with no funds in it.
>
> If you retype the descriptor, retype it exactly. A vault imported as `multi` looks like a
> successful import of an empty wallet, which is the most convincing way to believe your money is
> gone when it is not.

## Your own key inside the vault

Your cosigner key is not your personal Bitcoin wallet key. It lives under BIP-48:

```
m/48'/0'/{account}'/2'
```

The trailing `2'` is the BIP-48 script type for P2WSH. `{account}` is the vault's account index, read
off the Recovery Kit, not your Bitcoin wallet number. Signing keys sit beneath that account at
`/0/{n}` for receive addresses and `/1/{n}` for change.

When you add your seed to a restored vault as a signing keystore, this is the derivation the wallet
must use. Check the master fingerprint it reports against the one in the Recovery Kit: they match
only if the phrase and the path are both right.

## Sparrow

Sparrow reads the multipath descriptor directly and is the least painful of the three.

> **[Sparrow: importing a vault descriptor](https://trustodian-recovery.mintlify.site/wallets/sparrow.md#importing-a-vault-descriptor)**
>
> The full procedure, including the birthday step and adding your seed as a signing keystore

## Specter Desktop

Specter also takes the multipath descriptor.

**1. Add a new wallet from an existing descriptor**

Specter's add-wallet flow includes an option to import a wallet that already exists, given its
descriptor. Choose that rather than creating a new multisig from devices.

**2. Paste the descriptor**

One unbroken line, checksum included.

**3. Associate your device**

Specter organises signing around devices. Add your seed as a device with the BIP-48 account
derivation so that Specter can match it to one of the descriptor's keys by fingerprint.

**4. Set the rescan start height**

Specter drives Bitcoin Core's rescan. Give it the birthday height rather than letting it start
from genesis.

**5. Verify the addresses against the Recovery Kit**

Vault address and first receive address, both exactly.

## Bitcoin Core

Core is the most literal route and the one that will still work in ten years.

**1. Create a watch-only descriptor wallet**

```bash theme={null}
bitcoin-cli -named createwallet \
  wallet_name=trustodian-vault \
  disable_private_keys=true \
  blank=true \
  descriptors=true
```

**2. Convert the birthday height to a timestamp**

`importdescriptors` takes a Unix timestamp, not a block height, so translate the Recovery Kit's
**Birthday block height** first:

```bash theme={null}
bitcoin-cli getblockheader $(bitcoin-cli getblockhash 812345) | grep '"time"'
```

Use that value, or any earlier one. Core scans from shortly before the timestamp you give, so a
slightly early value is safe; a timestamp later than the vault's first payment causes Core to
miss it. Passing `0` scans the entire chain, and passing `"now"` skips scanning altogether.

**3. Import the receive and change descriptors separately**

```bash theme={null}
bitcoin-cli -rpcwallet=trustodian-vault importdescriptors '[
  {"desc": "<receive descriptor>", "active": true,  "internal": false,
   "range": [0, 999], "timestamp": 1699999999},
  {"desc": "<change descriptor>",  "active": true,  "internal": true,
   "range": [0, 999], "timestamp": 1699999999}
]'
```

Use the **Receive descriptor** and **Change descriptor** from the Recovery Kit, each with its own
checksum. Whether Core accepts the multipath `<0;1>` form of the main descriptor depends on the
node version, which is exactly why the kit prints both single-path forms.

**4. Rescan if the import did not**

```bash theme={null}
bitcoin-cli -rpcwallet=trustodian-vault rescanblockchain 812345
```

**5. Check the derived addresses**

```bash theme={null}
bitcoin-cli -rpcwallet=trustodian-vault deriveaddresses "<receive descriptor>" '[0,5]'
```

The first entry must equal the **First receive address** from the Recovery Kit.

Core's wallet here is watch-only: it finds the money and builds unsigned transactions, but the
signing happens elsewhere, with each owner's own key.

## Vaults that cannot be imported at all

> [!WARNING]
>
> **A vault whose owners include a wallet imported by private key cannot be imported into any
> external wallet.** Not Sparrow, not Specter, not Bitcoin Core.
>
> An imported private key is one key with no HD tree above it, so it has no extended public key and
> no origin to declare. In the descriptor it appears as a bare public key standing at every index,
> which means the descriptor cannot be ranged: there is no `/*` to walk. Importers reject it.
>
> Your Recovery Kit says so on the affected vault's page, above the artifacts, so that you learn it
> before spending an hour on an import that was never going to work.

Such a vault is not lost. Everything needed is still printed: the witness script, the address, the
threshold and every cosigner's key. It can be rebuilt and spent by hand with `bitcoin-cli`, given
enough owners' private keys. It just cannot be handed to a wallet as a descriptor.

## If the descriptor is unreadable

The **Witness script** in the Recovery Kit is the script behind the vault address in hex. It contains
the threshold and the cosigners' public keys for that one address, so it is enough to spend that
address's funds by hand even with no working descriptor. It is a per-address fallback, not a
replacement for the descriptor: it describes one address, not the ranged wallet.

The cosigner rows (master fingerprint, derivation path, account xpub) are the other fallback. From
them the descriptor can be reassembled by hand, as long as you keep `sortedmulti`, the threshold and
every cosigner's origin and key.

**The order you write the key expressions in does not change the addresses.** `sortedmulti` sorts the
derived public keys at each index itself. The kit writes them in sorted order so that regenerating it
produces a byte-identical string, but that is for reproducibility, not correctness.

A reassembled descriptor needs a valid checksum before any importer will take it. Bitcoin Core
computes one for you, and returns the descriptor with the checksum appended:

```bash theme={null}
bitcoin-cli getdescriptorinfo "<descriptor without checksum>"
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.