2025-08-22 11:59:20 +02:00
|
|
|
|
```
|
2026-05-11 12:26:05 -07:00
|
|
|
|
BIP: 138
|
|
|
|
|
|
Layer: Applications
|
|
|
|
|
|
Title: Compact encryption scheme for non-seed wallet data
|
|
|
|
|
|
Authors: Pyth <pythcoiner@wizardsardine.com>
|
|
|
|
|
|
Status: Draft
|
|
|
|
|
|
Type: Specification
|
|
|
|
|
|
Assigned: 2026-05-11
|
|
|
|
|
|
License: BSD-2-Clause
|
|
|
|
|
|
Discussion: https://delvingbitcoin.org/t/a-simple-backup-scheme-for-wallet-accounts/1607
|
|
|
|
|
|
https://groups.google.com/g/bitcoindev/c/5NgJbpVDgEc/m/TtGK9sF9BgAJ
|
2025-08-22 11:59:20 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## Introduction
|
|
|
|
|
|
|
|
|
|
|
|
### Abstract
|
|
|
|
|
|
|
|
|
|
|
|
This BIP defines a compact encryption scheme for **output script descriptors** (BIP-0380),
|
2026-06-18 10:43:37 -04:00
|
|
|
|
**wallet policies** (BIP-0388), **labels** (BIP-0329), and **wallet backup metadata** (BIP-0139).
|
2025-08-22 11:59:20 +02:00
|
|
|
|
The payload must not contain any private key material.
|
|
|
|
|
|
|
|
|
|
|
|
Users can store encrypted backups on untrusted media or cloud services without leaking
|
|
|
|
|
|
addresses, script structures, or cosigner counts. The encryption key derives from the
|
|
|
|
|
|
lexicographically-sorted public keys in the descriptor, allowing any keyholder to decrypt
|
|
|
|
|
|
without additional secrets.
|
|
|
|
|
|
|
|
|
|
|
|
Though designed for descriptors and policies, the scheme works equally well for labels
|
|
|
|
|
|
and backup metadata.
|
|
|
|
|
|
|
|
|
|
|
|
### Copyright
|
|
|
|
|
|
|
|
|
|
|
|
This BIP is licensed under the BSD 2-Clause License.
|
|
|
|
|
|
Redistribution and use in source and binary forms, with or without modification, are
|
|
|
|
|
|
permitted provided that the above copyright notice and this permission notice appear
|
|
|
|
|
|
in all copies.
|
|
|
|
|
|
|
|
|
|
|
|
### Motivation
|
|
|
|
|
|
|
|
|
|
|
|
Losing the **wallet descriptor** (or **wallet policy**) is just as catastrophic as
|
|
|
|
|
|
losing the seed itself. The seed lets you sign, but the descriptor maps you to your coins.
|
|
|
|
|
|
For multisig or miniscript wallets, keys alone won't help: without the descriptor, you
|
|
|
|
|
|
can't reconstruct the script.
|
|
|
|
|
|
|
|
|
|
|
|
Offline storage of descriptors has two practical obstacles:
|
|
|
|
|
|
|
|
|
|
|
|
1. **Descriptors are hard to store offline.**
|
|
|
|
|
|
Descriptors can be much longer than a 12/24-word seed. Paper and steel backups
|
|
|
|
|
|
become impractical or error-prone.
|
|
|
|
|
|
|
|
|
|
|
|
2. **Online redundancy carries privacy risk.**
|
|
|
|
|
|
USB drives, phones, and cloud storage solve the length problem but expose your
|
|
|
|
|
|
wallet structure. Plaintext descriptors leak your pubkeys and script details.
|
|
|
|
|
|
Cloud storage is often unencrypted, and even cloud encryption could be compromised,
|
|
|
|
|
|
depending on (often opaque) implementation details. Its security also reduces to
|
|
|
|
|
|
that of the weakest device with cloud access. Each copy increases the attack surface.
|
|
|
|
|
|
|
|
|
|
|
|
This BIP therefore proposes an **encrypted**, compact backup format that:
|
|
|
|
|
|
|
|
|
|
|
|
* can be **safely stored in multiple places**, including untrusted online services,
|
|
|
|
|
|
* can be **decrypted only by intended holders** of specified public keys,
|
|
|
|
|
|
|
|
|
|
|
|
See the original [Delving post](https://delvingbitcoin.org/t/a-simple-backup-scheme-for-wallet-accounts/1607/31)
|
|
|
|
|
|
for more background.
|
|
|
|
|
|
|
|
|
|
|
|
### Expected properties
|
|
|
|
|
|
|
|
|
|
|
|
* **Encrypted**: safe to store with untrusted cloud providers or backup services
|
|
|
|
|
|
* **Access controlled**: only designated cosigners can decrypt
|
|
|
|
|
|
* **Easy to implement**: it should not require any sophisticated tools/libraries.
|
|
|
|
|
|
* **Vendor-neutral**: works with any hardware signer
|
|
|
|
|
|
|
|
|
|
|
|
### Scope
|
|
|
|
|
|
|
|
|
|
|
|
This proposal targets output script descriptors (BIP-0380) and policies (BIP-0388), but the
|
|
|
|
|
|
scheme also works for labels (BIP-0329) and other wallet metadata like
|
2026-06-18 10:43:37 -04:00
|
|
|
|
wallet backup metadata (BIP-0139).
|
2025-08-22 11:59:20 +02:00
|
|
|
|
|
|
|
|
|
|
Private key material MUST be removed before encrypting any payload.
|
|
|
|
|
|
|
|
|
|
|
|
## Specification
|
|
|
|
|
|
|
|
|
|
|
|
Note: in the followings sections, the operator ⊕ refers to the bitwise XOR operation.
|
|
|
|
|
|
|
|
|
|
|
|
### Secret generation
|
|
|
|
|
|
|
|
|
|
|
|
- Let $p_1, p_2, \dots, p_n$, be the public keys in the descriptor/wallet policy, in
|
|
|
|
|
|
increasing lexicographical order. The scheme is defined for any $n \geq 1$,
|
|
|
|
|
|
in particular it supports single-signature descriptors ($n = 1$). Each $p_i$
|
|
|
|
|
|
is the x-only-normalized root public key of one *allowed* key expression
|
|
|
|
|
|
(see [Descriptor key requirements](#descriptor-key-requirements)).
|
2026-06-18 00:11:49 -04:00
|
|
|
|
- Let $s$ = sha256(sha256("BIP138_DECRYPTION_SECRET") | sha256("BIP138_DECRYPTION_SECRET") | $p_1$ | $p_2$ | ... | $p_n$)
|
|
|
|
|
|
- Let $s_i$ = sha256(sha256("BIP138_INDIVIDUAL_SECRET") | sha256("BIP138_INDIVIDUAL_SECRET") | $p_i$)
|
2025-08-22 11:59:20 +02:00
|
|
|
|
- Let $c_i$ = $s$ ⊕ $s_i$
|
|
|
|
|
|
|
|
|
|
|
|
Because $s$ and $s_i$ use distinct domain-separation tags, $s \neq s_i$ and
|
|
|
|
|
|
therefore $c_i$ is never the all-zero string.
|
|
|
|
|
|
|
|
|
|
|
|
**Note:** To prevent attackers from decrypting the backup using publicly known
|
|
|
|
|
|
keys, explicitly exclude any public keys with x coordinate
|
|
|
|
|
|
`50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0` (the BIP341 NUMS
|
|
|
|
|
|
point, used as a taproot internal key in some applications). Additionally, exclude any
|
|
|
|
|
|
other publicly known keys.
|
|
|
|
|
|
|
|
|
|
|
|
Applications that exclude additional keys SHOULD document this, although decryption
|
|
|
|
|
|
using these keys will simply fail. This does not affect decryption with the remaining
|
|
|
|
|
|
keys.
|
|
|
|
|
|
|
|
|
|
|
|
### Descriptor key requirements
|
|
|
|
|
|
|
|
|
|
|
|
Only key expressions of the extended-public-key type with a trailing
|
|
|
|
|
|
derivation step or wildcard contribute to $\{p_1, \dots, p_n\}$. Without
|
|
|
|
|
|
that derivation, the xpub's root pubkey, used to seed $s$, would also be
|
|
|
|
|
|
the on-chain pubkey of every spend, so observing one spend would let
|
|
|
|
|
|
anyone recompute $s$ and decrypt the backup.[^trailing-derivation] The
|
|
|
|
|
|
rule generalizes to any network prefix (`xpub`/`tpub`/`Vpub`/...).
|
|
|
|
|
|
|
|
|
|
|
|
[^trailing-derivation]: **Why require trailing derivation?**
|
|
|
|
|
|
Any trailing derivation step, or the implicit child derivation forced by
|
|
|
|
|
|
a wildcard, breaks the identity between the xpub root pubkey and the
|
|
|
|
|
|
on-chain pubkey. Fixed-derivation expressions like `xpub.../0/5` are
|
|
|
|
|
|
allowed because the on-chain key (`xpub/0/5`) already differs from the
|
|
|
|
|
|
encryption seed (the xpub root). Literal pubkeys and bare xpubs are
|
|
|
|
|
|
disallowed for the same reason: the literal value is exactly what goes
|
|
|
|
|
|
on-chain, so $s$ would become recoverable from a single observed spend.
|
|
|
|
|
|
|
|
|
|
|
|
Allowed forms (origin information `[...]` is optional and orthogonal):
|
|
|
|
|
|
|
|
|
|
|
|
| Form | Example |
|
|
|
|
|
|
|-------------------------------------------------------------|-------------------|
|
|
|
|
|
|
| `<xpub>/<path>` (fixed derivation, no wildcard) | `xpub.../0/5` |
|
|
|
|
|
|
| `<xpub>/*` (wildcard only) | `xpub.../*` |
|
|
|
|
|
|
| `<xpub>/<path>/*` (fixed derivation followed by a wildcard) | `xpub.../0/*` |
|
|
|
|
|
|
| `<xpub>/<a;b;...>` (multipath, no wildcard) | `xpub.../<0;1>` |
|
|
|
|
|
|
| `<xpub>/<a;b;...>/*` (multipath followed by a wildcard) | `xpub.../<0;1>/*` |
|
|
|
|
|
|
|
|
|
|
|
|
Implementations:
|
|
|
|
|
|
|
|
|
|
|
|
- MUST exclude every other form (literal pubkeys, bare xpubs, ...) from
|
|
|
|
|
|
$\{p_1, \dots, p_n\}$.
|
|
|
|
|
|
- MUST refuse to encode a backup if the resulting set is empty.
|
|
|
|
|
|
- SHOULD make the user aware of each excluded expression, since the
|
|
|
|
|
|
cosigner holding that key will be unable to decrypt the backup with
|
|
|
|
|
|
their key.
|
|
|
|
|
|
|
|
|
|
|
|
### Key Normalization
|
|
|
|
|
|
|
|
|
|
|
|
For each allowed expression, take the root extended public key (ignoring
|
|
|
|
|
|
origin information, the trailing derivation path, the wildcard, and any
|
|
|
|
|
|
multipath specifiers) and extract its x-coordinate. The result is the
|
|
|
|
|
|
32-byte **x-only public key** $p_i$.[^x-only]
|
|
|
|
|
|
|
|
|
|
|
|
[^x-only]: **Why x-only keys?**
|
|
|
|
|
|
X-only public keys are 32 bytes, a natural size for cryptographic operations.
|
|
|
|
|
|
This format is also used in BIP340 (Schnorr signatures) and BIP341 (Taproot).
|
|
|
|
|
|
|
|
|
|
|
|
### Encryption
|
|
|
|
|
|
|
|
|
|
|
|
The format uses CHACHA20_POLY1305 (RFC 8439) as the encryption algorithm,
|
|
|
|
|
|
with a 96-bit random nonce and a 128-bit authentication tag to provide confidentiality
|
|
|
|
|
|
and integrity.
|
|
|
|
|
|
|
|
|
|
|
|
[^chacha-default]: **Why CHACHA20-POLY1305 ?**
|
|
|
|
|
|
ChaCha20-Poly1305 is already used in Bitcoin Core (e.g., BIP324) and is widely
|
|
|
|
|
|
available in cryptographic libraries. It performs well in software without
|
|
|
|
|
|
hardware acceleration, making it suitable for hardware wallets and embedded devices.
|
|
|
|
|
|
|
|
|
|
|
|
* let $nonce$ = random(96 bits)
|
|
|
|
|
|
* let $ciphertext$ = encrypt($payload$, $secret$, $nonce$)
|
|
|
|
|
|
|
2026-06-18 03:02:17 -04:00
|
|
|
|
The $nonce$ MUST NOT be all zero. If the random source yields an all-zero nonce,
|
|
|
|
|
|
the encoder MUST generate a new one.
|
|
|
|
|
|
|
2025-08-22 11:59:20 +02:00
|
|
|
|
### Decryption
|
|
|
|
|
|
|
|
|
|
|
|
In order to decrypt the payload of a backup, the owner of a certain public key p
|
|
|
|
|
|
computes:
|
|
|
|
|
|
|
2026-06-18 00:11:49 -04:00
|
|
|
|
* let $s_i$ = sha256(sha256("BIP138_INDIVIDUAL_SECRET") ‖ sha256("BIP138_INDIVIDUAL_SECRET") ‖ $p$)
|
2025-08-22 11:59:20 +02:00
|
|
|
|
* for each `individual_secret_i` generate `reconstructed_secret_i` =
|
|
|
|
|
|
`individual_secret_i` ⊕ `si`
|
|
|
|
|
|
* for each `reconstructed_secret_i` process $payload$ =
|
|
|
|
|
|
decrypt($ciphertext$, $secret$, $nonce$)
|
|
|
|
|
|
|
|
|
|
|
|
Decryption will succeed if and only if **p** was one of the keys in the
|
|
|
|
|
|
descriptor/wallet policy.
|
|
|
|
|
|
|
|
|
|
|
|
### Encoding
|
|
|
|
|
|
|
|
|
|
|
|
The encrypted backup must be encoded as follows:
|
|
|
|
|
|
|
|
|
|
|
|
`MAGIC` `VERSION` `DERIVATION_PATHS` `INDIVIDUAL_SECRETS` `ENCRYPTION`
|
|
|
|
|
|
`ENCRYPTED_PAYLOAD`
|
|
|
|
|
|
|
|
|
|
|
|
#### Magic
|
|
|
|
|
|
|
2026-06-18 00:11:49 -04:00
|
|
|
|
`MAGIC`: 6 bytes which are ASCII/UTF-8 representation of **BIP138**.
|
2025-08-22 11:59:20 +02:00
|
|
|
|
|
|
|
|
|
|
#### Version
|
|
|
|
|
|
|
|
|
|
|
|
`VERSION`: 1 byte unsigned integer representing the format version. The current
|
|
|
|
|
|
specification defines version `0x01`.
|
|
|
|
|
|
|
|
|
|
|
|
#### Derivation Paths
|
|
|
|
|
|
|
|
|
|
|
|
Note: the derivation-path vector should not contain duplicates.
|
|
|
|
|
|
Derivation paths are optional; they can be useful to simplify the recovery process
|
|
|
|
|
|
if one has used a non-common derivation path to derive his key.[^derivation-optional]
|
|
|
|
|
|
|
2026-07-14 22:49:24 -04:00
|
|
|
|
All implementations that can fetch xpubs from a signing device SHOULD try the following
|
|
|
|
|
|
common paths automatically:
|
|
|
|
|
|
|
|
|
|
|
|
- `m/44h/<coin>h/{0..9}h`
|
|
|
|
|
|
- `m/49h/<coin>h/{0..9}h`
|
|
|
|
|
|
- `m/84h/<coin>h/{0..9}h`
|
|
|
|
|
|
- `m/86h/<coin>h/{0..9}h`
|
|
|
|
|
|
- `m/87h/<coin>h/{0..9}h`
|
|
|
|
|
|
- `m/48h/<coin>h/{0..9}h/1h`
|
|
|
|
|
|
- `m/48h/<coin>h/{0..9}h/2h`
|
|
|
|
|
|
|
|
|
|
|
|
`<coin>` is the BIP44 coin type of the network being recovered: `0` for mainnet
|
|
|
|
|
|
and `1` for test networks. `{0..9}h` covers accounts 0 through 9.
|
|
|
|
|
|
|
|
|
|
|
|
Some signing devices can fail or require confirmation when fetching xpubs for
|
|
|
|
|
|
some of these paths. This list is not exhaustive: an implementation MAY treat a
|
|
|
|
|
|
path as allowed to fail for any device it supports, in particular
|
|
|
|
|
|
`m/87h/<coin>h/{0..9}h`, which is widely under-supported. Implementations SHOULD
|
|
|
|
|
|
at least classify these:[^common-derivation-paths]
|
|
|
|
|
|
|
|
|
|
|
|
- BitBox02: `m/44h/<coin>h/{0..9}h`, `m/87h/<coin>h/{0..9}h`
|
|
|
|
|
|
- Ledger: `m/87h/<coin>h/{0..9}h`
|
|
|
|
|
|
|
|
|
|
|
|
If fetching an xpub for one of these common paths fails or times out,
|
|
|
|
|
|
implementations SHOULD warn the user.
|
|
|
|
|
|
|
2025-08-22 11:59:20 +02:00
|
|
|
|
[^derivation-optional]: **Why are derivation paths optional?**
|
|
|
|
|
|
When standard derivation paths are used, they are easily discoverable, making
|
|
|
|
|
|
them straightforward to brute-force. Omitting them enhances privacy by reducing
|
|
|
|
|
|
the information shared publicly about the descriptor scheme.
|
|
|
|
|
|
|
2026-07-14 22:49:24 -04:00
|
|
|
|
[^common-derivation-paths]: **Why this restricted list?**
|
|
|
|
|
|
The list covers common account paths for the network being recovered, accounts 0
|
|
|
|
|
|
through 9. Some signing devices reject or display some of these paths before
|
|
|
|
|
|
exporting an xpub, so implementations should treat the listed paths for that
|
|
|
|
|
|
device as optional rather than aborting recovery.
|
|
|
|
|
|
|
2025-08-22 11:59:20 +02:00
|
|
|
|
`DERIVATION_PATH` follows this format:
|
|
|
|
|
|
|
|
|
|
|
|
`COUNT`
|
|
|
|
|
|
`CHILD_COUNT` `CHILD` `...` `CHILD`
|
|
|
|
|
|
`...`
|
|
|
|
|
|
`CHILD_COUNT` `CHILD` `...` `CHILD`
|
|
|
|
|
|
|
|
|
|
|
|
`COUNT`: 1-byte unsigned integer (0–255) indicating how many derivation paths are
|
|
|
|
|
|
included.
|
|
|
|
|
|
`CHILD_COUNT`: 1-byte unsigned integer (1–255) indicating how many children are in
|
|
|
|
|
|
the current path.
|
|
|
|
|
|
`CHILD`: 4-byte big-endian unsigned integer representing a child index per BIP-32.
|
|
|
|
|
|
|
|
|
|
|
|
#### Individual Secrets
|
|
|
|
|
|
|
|
|
|
|
|
At least one individual secret must be supplied.[^no-fingerprints]
|
|
|
|
|
|
|
|
|
|
|
|
[^no-fingerprints]: **Why no fingerprints in plaintext encoding?**
|
|
|
|
|
|
Including fingerprints would leak direct information about the descriptor
|
|
|
|
|
|
participants, which compromises privacy.
|
|
|
|
|
|
|
|
|
|
|
|
The `INDIVIDUAL_SECRETS` section follows this format:
|
|
|
|
|
|
|
|
|
|
|
|
`COUNT`
|
|
|
|
|
|
`INDIVIDUAL_SECRET`
|
|
|
|
|
|
`INDIVIDUAL_SECRET`
|
|
|
|
|
|
|
|
|
|
|
|
`COUNT`: 1-byte unsigned integer (1–255) indicating how many secrets are included.
|
|
|
|
|
|
`INDIVIDUAL_SECRET`: 32-byte serialization of the derived individual secret.
|
|
|
|
|
|
|
|
|
|
|
|
Note: the individual secrets vector should not contain duplicates. Implementations
|
|
|
|
|
|
MAY deduplicate secrets during encoding or parsing.
|
|
|
|
|
|
|
|
|
|
|
|
#### Encryption
|
|
|
|
|
|
|
|
|
|
|
|
`ENCRYPTION`: 1-byte unsigned integer identifying the encryption algorithm.
|
|
|
|
|
|
|
|
|
|
|
|
| Value | Definition |
|
|
|
|
|
|
|:-------|:---------------------------------------|
|
|
|
|
|
|
| 0x00 | Reserved |
|
|
|
|
|
|
| 0x01 | CHACHA20_POLY1305 |
|
|
|
|
|
|
|
|
|
|
|
|
#### Payload Size Limits
|
|
|
|
|
|
|
|
|
|
|
|
CHACHA20_POLY1305 (per RFC 8439) supports plaintext up to 2^38 - 64 bytes.
|
|
|
|
|
|
Implementations MAY impose stricter limits based on platform constraints
|
|
|
|
|
|
(e.g., limiting to 2^32 - 1 bytes on 32-bit architectures).
|
|
|
|
|
|
|
|
|
|
|
|
Implementations MUST reject empty payloads.
|
|
|
|
|
|
|
|
|
|
|
|
#### Ciphertext
|
|
|
|
|
|
|
|
|
|
|
|
`CIPHERTEXT` is the encrypted data resulting from encryption of `PAYLOAD` with algorithm
|
|
|
|
|
|
defined in `ENCRYPTION` where `PAYLOAD` is encoded following this format:
|
|
|
|
|
|
|
2026-06-30 10:56:17 -04:00
|
|
|
|
`CONTENT` `LENGTH` `PLAINTEXT` (`CONTENT` `LENGTH` `PLAINTEXT` ...) (`PADDING`)
|
2026-06-17 22:45:43 -04:00
|
|
|
|
|
|
|
|
|
|
`LENGTH`: variable-length integer representing the length of `PLAINTEXT` in bytes. It MUST
|
|
|
|
|
|
be present.
|
|
|
|
|
|
|
|
|
|
|
|
`PLAINTEXT`: the `LENGTH` bytes of payload data.
|
|
|
|
|
|
|
2026-06-30 10:56:17 -04:00
|
|
|
|
`PADDING`: OPTIONAL bytes after the final `PLAINTEXT`, up to the end of the decrypted
|
|
|
|
|
|
`PAYLOAD`. Parsers MUST consume exactly `LENGTH` bytes of each `PLAINTEXT`. The padding
|
|
|
|
|
|
bytes are reserved for size padding (see Padding) and/or vendor-specific data, the same
|
|
|
|
|
|
way trailing bytes after `CIPHERTEXT` are reserved and ignored.
|
2026-06-17 22:45:43 -04:00
|
|
|
|
|
|
|
|
|
|
#### Padding
|
|
|
|
|
|
|
|
|
|
|
|
Backups that are updated over time can leak information through their size even when the
|
|
|
|
|
|
contents are encrypted. A wallet that uploads a new backup after every received transaction
|
|
|
|
|
|
can reveal activity to the storage provider through changing payload sizes. This is relevant
|
|
|
|
|
|
for mutable payloads such as BIP-329 or BIP-0139.[^padding-privacy]
|
|
|
|
|
|
|
|
|
|
|
|
Padding is OPTIONAL and is an encoder concern only: decoders ignore the `PADDING` bytes and
|
|
|
|
|
|
MUST NOT validate any bucket. Because the padding sits inside `CIPHERTEXT`, the
|
|
|
|
|
|
`ENCRYPTED_PAYLOAD` `LENGTH` reveals only the padded size, not the real `PLAINTEXT` size.
|
|
|
|
|
|
|
|
|
|
|
|
Encoders that pad SHOULD zero-fill `PAYLOAD` up to the smallest bucket `S_n` that fits it,
|
|
|
|
|
|
with `S_0 = 10240` (10 KiB) and `S_n = floor(S_0 * (5 / 4)^n)`. Payloads of 10 KiB or less
|
|
|
|
|
|
are padded to 10 KiB. This keeps padding overhead under 25% above the smallest fitting bucket
|
|
|
|
|
|
while coarsening the observable size. Padding is RECOMMENDED for mutable payloads and
|
|
|
|
|
|
unnecessary for static ones such as BIP-380 descriptors and BIP-388 wallet policies.
|
|
|
|
|
|
|
|
|
|
|
|
[^padding-privacy]: **Why pad inside the ciphertext?**
|
|
|
|
|
|
Padding the encrypted payload, rather than appending bytes after the backup, hides the
|
|
|
|
|
|
real payload size from anyone who parses the backup, not just from someone who measures
|
|
|
|
|
|
the file size. The size field a parser sees is the padded `LENGTH`.
|
2025-08-22 11:59:20 +02:00
|
|
|
|
|
|
|
|
|
|
#### Integer Encodings
|
|
|
|
|
|
|
|
|
|
|
|
All variable-length integers are encoded as
|
|
|
|
|
|
[compact size](https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer).
|
|
|
|
|
|
|
|
|
|
|
|
#### Content
|
|
|
|
|
|
|
2026-06-30 10:56:17 -04:00
|
|
|
|
`CONTENT` is a variable length field defining the type of the `PLAINTEXT` that follows
|
|
|
|
|
|
it. It follows this format:
|
2025-08-22 11:59:20 +02:00
|
|
|
|
|
|
|
|
|
|
`TYPE` (`LENGTH`) `DATA`
|
|
|
|
|
|
|
2026-06-30 10:56:17 -04:00
|
|
|
|
A `PAYLOAD` carries one or more `CONTENT LENGTH PLAINTEXT` items, each `CONTENT`
|
|
|
|
|
|
describing the `PLAINTEXT` immediately following it. The sequence ends at the first
|
|
|
|
|
|
`TYPE` byte equal to `0x00`, or at the end of the `PAYLOAD`, whichever comes first; all
|
|
|
|
|
|
remaining bytes are `PADDING`. A `PAYLOAD` MUST contain at least one item (see
|
|
|
|
|
|
[Payload Size Limits](#payload-size-limits)).
|
2026-06-18 03:02:17 -04:00
|
|
|
|
|
2025-08-22 11:59:20 +02:00
|
|
|
|
`TYPE`: 1-byte unsigned integer identifying how to interpret `DATA`.
|
|
|
|
|
|
|
|
|
|
|
|
| Value | Definition |
|
|
|
|
|
|
|:-------|:---------------------------------------|
|
2026-07-14 23:50:57 -04:00
|
|
|
|
| 0x00 | End of content items; padding follows |
|
2025-08-22 11:59:20 +02:00
|
|
|
|
| 0x01 | BIP Number (big-endian uint16) |
|
|
|
|
|
|
| 0x02 | Vendor-Specific Opaque Tag |
|
2026-07-14 22:48:57 -04:00
|
|
|
|
| 0x03 | String |
|
2025-08-22 11:59:20 +02:00
|
|
|
|
|
|
|
|
|
|
`LENGTH`: variable-length integer representing the length of `DATA` in bytes.
|
|
|
|
|
|
|
|
|
|
|
|
For all `TYPE` values except `0x01`, `LENGTH` MUST be present.
|
|
|
|
|
|
|
|
|
|
|
|
`DATA`: variable-length field whose encoding depends on `TYPE`.
|
|
|
|
|
|
|
|
|
|
|
|
For `TYPE` values defined above:
|
2026-07-14 23:50:57 -04:00
|
|
|
|
- 0x00: parsers MUST stop reading content items and treat the remaining bytes as
|
|
|
|
|
|
padding.
|
2025-08-22 11:59:20 +02:00
|
|
|
|
- 0x01: `LENGTH` MUST be omitted and `DATA` is a 2-byte big-endian unsigned integer
|
|
|
|
|
|
representing the BIP number that defines it.
|
|
|
|
|
|
- 0x02: `DATA` MUST be `LENGTH` bytes of opaque, vendor-specific data.
|
2026-07-14 22:48:57 -04:00
|
|
|
|
- 0x03: `DATA` MUST be empty, and the following `PLAINTEXT` is the string itself,
|
|
|
|
|
|
which MUST be valid UTF-8.
|
2025-08-22 11:59:20 +02:00
|
|
|
|
|
|
|
|
|
|
For all `TYPE` values except `0x01`, parsers MUST reject `CONTENT` if `LENGTH` exceeds
|
|
|
|
|
|
the remaining payload bytes.
|
|
|
|
|
|
|
2026-06-18 03:02:17 -04:00
|
|
|
|
For an unknown `TYPE` less than `0x80`, parsers MUST consume its `LENGTH` bytes of
|
2026-06-30 10:56:17 -04:00
|
|
|
|
`DATA`, treat the content type as unknown, consume the following payload `LENGTH` and
|
|
|
|
|
|
`PLAINTEXT`, and continue with the next item.
|
2025-08-22 11:59:20 +02:00
|
|
|
|
|
2026-06-18 03:02:17 -04:00
|
|
|
|
For an unknown `TYPE` greater than or equal to `0x80`, parsers MUST reject the
|
|
|
|
|
|
payload.[^type-upgrade]
|
2025-08-22 11:59:20 +02:00
|
|
|
|
|
|
|
|
|
|
[^type-upgrade]: **Why the 0x80 threshold?**
|
2026-06-18 03:02:17 -04:00
|
|
|
|
The `TYPE >= 0x80` rule keeps the encoding upgradeable. A future version can
|
|
|
|
|
|
define a new content type at or above `0x80` that older parsers reject rather
|
|
|
|
|
|
than misread, while types below `0x80` stay backward compatible.
|
2025-08-22 11:59:20 +02:00
|
|
|
|
|
2026-06-18 03:44:25 -04:00
|
|
|
|
#### BIP380 Descriptor Backup Content
|
|
|
|
|
|
|
2026-06-18 07:07:45 -04:00
|
|
|
|
When `CONTENT` is `TYPE = 0x01` with `DATA = 0x017c` (BIP380), `PLAINTEXT` is a
|
|
|
|
|
|
UTF-8 BIP380 descriptor backup. It is either a single bare descriptor string, or
|
|
|
|
|
|
a JSON descriptor backup document (for several descriptors) when the first
|
|
|
|
|
|
character is `{`.
|
2026-06-18 03:44:25 -04:00
|
|
|
|
|
|
|
|
|
|
Test vectors are in
|
|
|
|
|
|
[`bip380_descriptor_backup.json`](./bip-0138/test_vectors/bip380_descriptor_backup.json).
|
|
|
|
|
|
|
|
|
|
|
|
Descriptor strings MUST NOT contain private key material and SHOULD include a
|
|
|
|
|
|
checksum.
|
|
|
|
|
|
|
|
|
|
|
|
##### JSON Descriptor Backup Documents
|
|
|
|
|
|
|
|
|
|
|
|
The descriptor backup document is a JSON object with the following fields:
|
|
|
|
|
|
|
|
|
|
|
|
- `version`: integer. This specification defines version `1`.
|
|
|
|
|
|
- `descriptor_sets`: array of descriptor set objects.
|
|
|
|
|
|
|
|
|
|
|
|
Each descriptor set describes BIP380 output script descriptors belonging to one
|
|
|
|
|
|
logical account or script family.
|
|
|
|
|
|
|
|
|
|
|
|
##### Descriptor Set Fields
|
|
|
|
|
|
|
|
|
|
|
|
`descriptor` is a required string containing a BIP380 output script descriptor.
|
|
|
|
|
|
|
|
|
|
|
|
For BIP389 multipath descriptors, `/<0;1>` means receive and change,
|
|
|
|
|
|
respectively. The `change_descriptor` field MUST NOT be used.
|
|
|
|
|
|
|
|
|
|
|
|
For descriptors without BIP389 multipath key expressions, `descriptor` is the
|
|
|
|
|
|
receive descriptor and `change_descriptor` is the change descriptor.
|
|
|
|
|
|
|
|
|
|
|
|
If optional boolean `archived` is `true`, importing wallets SHOULD NOT use the
|
|
|
|
|
|
descriptor set for new address generation.
|
|
|
|
|
|
|
|
|
|
|
|
`range` is an optional two-element array `[start, end]`, inclusive, describing
|
|
|
|
|
|
the ranged derivation indexes covered by the descriptor set.
|
|
|
|
|
|
|
|
|
|
|
|
`birth_time` is an optional integer Unix timestamp in seconds, indicating a
|
|
|
|
|
|
lower bound for when the descriptor set may have received funds. Importing
|
|
|
|
|
|
wallets MAY use this value as a scanning hint.
|
|
|
|
|
|
|
2026-06-18 04:38:58 -04:00
|
|
|
|
#### BIP388 Wallet Policy Backup Content
|
|
|
|
|
|
|
|
|
|
|
|
When `CONTENT` is `TYPE = 0x01` with `DATA = 0x0184` (BIP388), `PLAINTEXT` is a
|
|
|
|
|
|
UTF-8 JSON wallet policy backup document. A BIP388 wallet policy is a descriptor
|
|
|
|
|
|
template plus a vector of key information items, as defined in BIP388.
|
|
|
|
|
|
|
|
|
|
|
|
Test vectors are in
|
|
|
|
|
|
[`bip388.json`](./bip-0138/test_vectors/bip388.json).
|
|
|
|
|
|
|
|
|
|
|
|
The document is one of two forms:
|
|
|
|
|
|
|
|
|
|
|
|
- A single policy, a JSON object `{ "keys": [...], "policy": "<template>" }`.
|
|
|
|
|
|
- Multiple policies, a JSON object `{ "version": 1, "policy_sets": [...] }`.
|
|
|
|
|
|
|
|
|
|
|
|
Parsers select the form by structure: an object with a `policy_sets` field is the
|
|
|
|
|
|
multiple form, an object with a `policy` field is the single form.
|
|
|
|
|
|
|
|
|
|
|
|
##### Policy Fields
|
|
|
|
|
|
|
|
|
|
|
|
`keys` is a required array of BIP388 key information items, each a serialized
|
|
|
|
|
|
extended public key with optional key origin (`[fingerprint/path]xpub`). It MUST
|
|
|
|
|
|
contain at least one key. Keys MUST NOT contain private key material.
|
|
|
|
|
|
|
|
|
|
|
|
`policy` is a required non-empty string containing a BIP388 wallet descriptor
|
|
|
|
|
|
template, with `@i` key placeholders referring to `keys`.
|
|
|
|
|
|
|
|
|
|
|
|
##### Policy Set Fields
|
|
|
|
|
|
|
|
|
|
|
|
A policy set has the same `keys` and `policy` fields as the single form, plus the
|
|
|
|
|
|
optional metadata fields:
|
|
|
|
|
|
|
|
|
|
|
|
If optional boolean `archived` is `true`, importing wallets SHOULD NOT use the
|
|
|
|
|
|
policy for new address generation.
|
|
|
|
|
|
|
|
|
|
|
|
`range` is an optional two-element array `[start, end]`, inclusive, describing the
|
|
|
|
|
|
ranged derivation indexes covered by the policy.
|
|
|
|
|
|
|
|
|
|
|
|
`birth_time` is an optional integer Unix timestamp in seconds, indicating a lower
|
|
|
|
|
|
bound for when the policy may have received funds. Importing wallets MAY use this
|
|
|
|
|
|
value as a scanning hint.
|
|
|
|
|
|
|
|
|
|
|
|
##### Multiple Policies Document
|
|
|
|
|
|
|
|
|
|
|
|
The multiple form is a JSON object with the following fields:
|
|
|
|
|
|
|
|
|
|
|
|
- `version`: integer. This specification defines version `1`.
|
|
|
|
|
|
- `policy_sets`: array of policy set objects.
|
|
|
|
|
|
|
2025-08-22 11:59:20 +02:00
|
|
|
|
#### Encrypted Payload
|
|
|
|
|
|
|
|
|
|
|
|
`ENCRYPTED_PAYLOAD` follows this format:
|
|
|
|
|
|
|
|
|
|
|
|
`NONCE` `LENGTH` `CIPHERTEXT`
|
|
|
|
|
|
|
2026-06-18 03:02:17 -04:00
|
|
|
|
`NONCE`: 12-byte (96-bit) nonce. Parsers MUST reject a backup whose `NONCE` is all zero.
|
2025-08-22 11:59:20 +02:00
|
|
|
|
`LENGTH`: variable-length integer representing ciphertext length.
|
|
|
|
|
|
`CIPHERTEXT`: variable-length ciphertext.
|
|
|
|
|
|
|
|
|
|
|
|
Note: `CIPHERTEXT` is followed by the end of the `ENCRYPTED_PAYLOAD` section.
|
|
|
|
|
|
Compliant parsers MUST stop reading after consuming `LENGTH` bytes of ciphertext;
|
|
|
|
|
|
additional trailing bytes are reserved for vendor-specific extensions and MUST
|
|
|
|
|
|
be ignored.
|
|
|
|
|
|
|
|
|
|
|
|
### Text Representation
|
|
|
|
|
|
|
|
|
|
|
|
Implementations SHOULD encode and decode the backup using Base64 (RFC 4648).[^psbt-base64]
|
|
|
|
|
|
|
|
|
|
|
|
[^psbt-base64]: **Why Base64?**
|
|
|
|
|
|
PSBT (BIP174) is commonly exchanged as a Base64 string, so wallet software
|
|
|
|
|
|
likely already supports this representation.
|
|
|
|
|
|
|
|
|
|
|
|
## Rationale
|
|
|
|
|
|
|
|
|
|
|
|
See footnotes throughout the specification for design rationale.
|
|
|
|
|
|
|
|
|
|
|
|
### Future Extensions
|
|
|
|
|
|
|
|
|
|
|
|
The version field enables possible future enhancements:
|
|
|
|
|
|
|
|
|
|
|
|
- Additional encryption algorithms
|
|
|
|
|
|
- Support for threshold-based decryption
|
|
|
|
|
|
- Hiding number of participants
|
|
|
|
|
|
- bech32m export
|
|
|
|
|
|
|
|
|
|
|
|
### Implementation
|
|
|
|
|
|
|
2026-06-30 10:56:17 -04:00
|
|
|
|
- Rust [implementation](https://github.com/pythcoiner/bip138)
|
2025-08-22 11:59:20 +02:00
|
|
|
|
|
|
|
|
|
|
### Test Vectors
|
|
|
|
|
|
|
2026-06-17 22:45:43 -04:00
|
|
|
|
[key_types.json](./bip-0138/test_vectors/keys_types.json) contains test
|
2025-08-22 11:59:20 +02:00
|
|
|
|
vectors for key serialisations.
|
2026-06-17 22:45:43 -04:00
|
|
|
|
[content_type.json](./bip-0138/test_vectors/content_type.json) contains test
|
2025-08-22 11:59:20 +02:00
|
|
|
|
vectors for contents types serialisations.
|
2026-06-17 22:45:43 -04:00
|
|
|
|
[derivation_path.json](./bip-0138/test_vectors/derivation_path.json) contains
|
2025-08-22 11:59:20 +02:00
|
|
|
|
test vectors for derivation paths serialisations.
|
2026-06-17 22:45:43 -04:00
|
|
|
|
[individual_secrets.json](./bip-0138/test_vectors/individual_secrets.json)
|
2025-08-22 11:59:20 +02:00
|
|
|
|
contains test vectors for individual secrets serialization.
|
2026-06-17 22:45:43 -04:00
|
|
|
|
[encryption_secret.json](./bip-0138/test_vectors/encryption_secret.json)
|
2025-08-22 11:59:20 +02:00
|
|
|
|
contains test vectors for generation of encryption secret.
|
2026-06-17 22:45:43 -04:00
|
|
|
|
[chacha20poly1305_encryption.json](./bip-0138/test_vectors/chacha20poly1305_encryption.json)
|
2025-08-22 11:59:20 +02:00
|
|
|
|
contains test vectors for ciphertexts generated using CHACHA20-POLY1305.
|
2026-06-17 22:45:43 -04:00
|
|
|
|
[encrypted_backup.json](./bip-0138/test_vectors/encrypted_backup.json)
|
2026-06-18 04:38:58 -04:00
|
|
|
|
contains test vectors for generation of complete encrypted backups, including
|
2026-06-30 10:56:17 -04:00
|
|
|
|
BIP380 and BIP388 content, a multi-content payload, and an all-zero-nonce backup
|
|
|
|
|
|
that parsers MUST reject.
|
2026-06-18 06:36:38 -04:00
|
|
|
|
[bip380_descriptor_backup.json](./bip-0138/test_vectors/bip380_descriptor_backup.json)
|
|
|
|
|
|
contains test vectors for BIP380 descriptor backup documents.
|
2026-06-18 04:38:58 -04:00
|
|
|
|
[bip388.json](./bip-0138/test_vectors/bip388.json)
|
|
|
|
|
|
contains test vectors for BIP388 wallet policy backup documents.
|
2025-08-22 11:59:20 +02:00
|
|
|
|
|
|
|
|
|
|
## Acknowledgements
|
|
|
|
|
|
|
|
|
|
|
|
// TBD
|