Skip to content

Commit 3b6b6ad

Browse files
committed
bip encrypted_backup
1 parent 2e3dd3f commit 3b6b6ad

File tree

1 file changed

+231
-0
lines changed

1 file changed

+231
-0
lines changed

bip-encrypted-backup.md

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
```
2+
BIP: ?
3+
Layer: Applications
4+
Title: Compact encryption scheme for non-seed wallet data
5+
Author: // TBD
6+
Comments-Summary: No comments yet.
7+
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-????
8+
Status: Draft
9+
Type: Informational
10+
Created: 2025-08-22
11+
License: BSD-2-Clause
12+
```
13+
14+
## Introduction
15+
16+
### Abstract
17+
18+
This BIP defines a compact encryption scheme for **wallet descriptors** (BIP-0380),
19+
**wallet policies** (BIP-0388), **labels** (BIP-0329), and
20+
**wallet backup metadata** (json). The payload must not contains any private key material.
21+
This scheme enables users to outsource long‑term storage to untrusted media or cloud
22+
services without revealing which addresses, scripts, or number of cosigners are involved.
23+
Encryption keys are derived from the lexicographically‑sorted public keys inside the
24+
descriptor or policy, so any party who already holds one of those keys can later decrypt
25+
the backup without extra secrets or round‑trips. The format uses AES-GCM-256 with a 96‑bit
26+
random nonce and a 128‑bit authentication tag to provide confidentiality and integrity.
27+
While initially designed for descriptors and policies, the same scheme encrypts labels
28+
and backup metadata, allowing a uniform, vendor‑neutral, and future‑extensible backup format.
29+
30+
### Copyright
31+
32+
This BIP is licensed under the BSD 2-Clause License.
33+
Redistribution and use in source and binary forms, with or without modification, are
34+
permitted provided that the above copyright notice and this permission notice appear
35+
in all copies.
36+
37+
### Motivation
38+
39+
In practice, losing the **wallet descriptor** (or **wallet policy**) is often **as
40+
catastrophic as losing the wallet’s seed** itself. While the seed grants the
41+
ability to sign, the descriptor grants a map to the coins. In multisig or
42+
miniscript contexts, keys alone are **not sufficient** for recovery: without the
43+
original descriptor the wallet cannot reconstruct the script.
44+
45+
Offline storage of descriptors has two practical obstacles:
46+
47+
1. **Descriptors are hard to store offline.**
48+
Descriptor string representation can be far longer than a 12/24-word seed phrase.
49+
Paper, steel, and other long-term analog media quickly become impractical for such
50+
lengths, or error-prone to transcribe.
51+
52+
2. **Online redundancy carries privacy risk.**
53+
Keeping backups on USB thumb-drives, computers, phones, or (worst) cloud drives
54+
avoids the first problem but amplifies surveillance risk: anyone who gains these
55+
**plaintext descriptors** learns the wallet’s public keys, script structure,
56+
etc... Even with encryption at the cloud provider, an attacker or a subpoena can
57+
compel access, and each extra copy multiplies the attack surface.
58+
59+
These constraints lead to an acute need for an **encrypted**, and
60+
ideally compact backup format that:
61+
62+
* can be **safely stored in multiple places**, including untrusted on-line services,
63+
* can be **decrypted only by intended holders** of specified public keys,
64+
65+
See the original [Delving post](https://delvingbitcoin.org/t/a-simple-backup-scheme-for-wallet-accounts/1607/31)
66+
for more background.
67+
68+
### Expected properties
69+
70+
* **Encrypted**: this allows users to outsource its storage to untrusted parties,
71+
for example, cloud providers, specialized services, etc.
72+
* **Has access control**: decrypting it should only be available to the desired
73+
parties (typically, a subset of the cosigners).
74+
* **Easy to implement**: it should not require any sophisticated tools.
75+
* **Vendor-independent**: it should be easy to implement using any hardware signing
76+
device.
77+
78+
### Scope
79+
80+
The primary motivation of this proposal is to store a wallet descriptor(BIP-0380) or a
81+
wallet policy(BIP-0388), but it seems valuable enough to also use this scheme to encrypt
82+
payload containing others wallet-related metadata, like Labels(BIP-0329) or
83+
[wallet backup](https://github.com/pythcoiner/wallet_backup).
84+
85+
Note: For any kind of payload intented to be encrypted with this scheme, private key
86+
material MUST be removed before encryption.
87+
88+
## Specification
89+
90+
Note: in the followings sections, the operator ⊕ refers to the bitwise XOR operation.
91+
92+
### Secret generation
93+
94+
- Let $p_1, p_2, \dots, p_n$, be the public keys in the descriptor/wallet policy, in increasing lexicographical order
95+
- Let $s = \textrm{sha256("BEB_DECRYPTION_SECRET"} \| p_1 \| p_2 \| \dots \| p_n\textrm{)}$
96+
- Let $s_i = \operatorname{sha256}(``\textrm{BEB_INDIVIDUAL_SECRET}" \| p_i)$
97+
- Let $c_i = s \oplus s_i $
98+
99+
### AES-GCM Encryption
100+
101+
* let `nonce` = random()
102+
* let `ciphertext` = aes_gcm_256_encrypt(`payload`, `secret`, `nonce`)
103+
104+
### AES-GCM Decryption
105+
106+
In order to decrypt the payload of a backup, the owner of a certain public key p
107+
computes:
108+
109+
* let `si` = sha256("BEB_INDIVIDUAL_SECRET" ‖ `p`)
110+
* for each `individual_secret_i` generate `reconstructed_secret_i` =
111+
`individual_secret_i``si`
112+
* for each `reconstructed_secret_i` process `payload` =
113+
aes_gcm_256_decrypt(`ciphertext`, `secret`, `nonce`)
114+
115+
Decryption will succeed if and only if **p** was one of the keys in the
116+
descriptor/wallet policy.
117+
118+
### Encoding
119+
120+
The encrypted backup must be encoded as follows:
121+
122+
`MAGIC` `VERSION` `DERIVATION_PATHS` `INDIVIDUAL_SECRETS` `CONTENT` `ENCRYPTION`
123+
`ENCRYPTED_PAYLOAD`
124+
125+
#### Magic
126+
127+
`MAGIC`: 3 bytes which are ASCII/UTF-8 representation of **BEB** (`0x42, 0x45,
128+
0x42`).
129+
130+
#### Version
131+
132+
`VERSION`: 1 byte unsigned integer representing the format version. The current
133+
specification defines version `0x01`.
134+
135+
#### Derivation Paths
136+
137+
Note: the derivation-path vector should not contain duplicates.
138+
Derivation paths are optional; they can be useful to simplify the recovery process
139+
if one has used a non-common derivation path to derive his key.
140+
141+
`DERIVATION_PATH` follows this format:
142+
143+
`COUNT`
144+
`CHILD_COUNT` `CHILD` `...` `CHILD`
145+
`...`
146+
`CHILD_COUNT` `CHILD` `...` `CHILD`
147+
148+
`COUNT`: 1-byte unsigned integer (0–255) indicating how many derivation paths are
149+
included.
150+
`CHILD_COUNT`: 1-byte unsigned integer (1–255) indicating how many children are in
151+
the current path.
152+
`CHILD`: 4-byte big-endian unsigned integer representing a child index per BIP-32.
153+
154+
#### Individual Secrets
155+
156+
At least one individual secret must be supplied.
157+
158+
The `INDIVIDUAL_SECRETS` section follows this format:
159+
160+
`COUNT`
161+
`INDIVIDUAL_SECRET`
162+
`INDIVIDUAL_SECRET`
163+
164+
`COUNT`: 1-byte unsigned integer (1–255) indicating how many secrets are included.
165+
`INDIVIDUAL_SECRET`: 32-byte serialization of the derived individual secret.
166+
167+
#### Content
168+
169+
`CONTENT`: 1-byte unsigned integer identifying what has been encrypted.
170+
171+
| Value | Definition |
172+
|:-------|:---------------------------------------|
173+
| 0x00 | Undefined |
174+
| 0x01 | BIP-0380 Descriptor (string) |
175+
| 0x02 | BIP-0388 Wallet policy (string) |
176+
| 0x03 | BIP-0329 Labels (JSONL) |
177+
| 0x04 | Wallet backup (JSON) |
178+
179+
#### Encrypted Payload
180+
181+
`ENCRYPTED_PAYLOAD` follows this format:
182+
183+
`TYPE` `NONCE` `LENGTH` `CIPHERTEXT`
184+
185+
`TYPE`: 1-byte unsigned integer identifying the encryption algorithm.
186+
187+
| Value | Definition |
188+
|:-------|:---------------------------------------|
189+
| 0x00 | Undefined |
190+
| 0x01 | AES-GCM-256 |
191+
192+
`NONCE`: 12-byte nonce for AES-GCM-256.
193+
`LENGTH`: [compact
194+
size](https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer)
195+
integer representing ciphertext length.
196+
`CIPHERTEXT`: variable-length ciphertext.
197+
198+
Note: `CIPHERTEXT` is followed by the end of the `ENCRYPTED_PAYLOAD` section.
199+
Compliant parsers MUST stop reading after consuming `LENGTH` bytes of ciphertext;
200+
additional trailing bytes are reserved for vendor-specific extensions and MUST
201+
be ignored.
202+
203+
## Rationale
204+
205+
- Why derivation paths are optional: When standard derivation paths are used, they are
206+
easily discoverable, making them straightforward to brute-force. Omitting them
207+
enhances privacy by reducing the information shared publicly about the descriptor
208+
scheme.
209+
210+
- Why avoid including fingerprints in plaintext encoding: Including fingerprints leaks
211+
direct information about the descriptor participants, which compromises privacy.
212+
213+
214+
### Future Extensions
215+
216+
The version field enables possible future enhancements:
217+
218+
- Additional encryption algorithms
219+
- Support for threshold-based decryption
220+
221+
### Implementation
222+
223+
- rust [implementation](https://github.com/pythcoiner/encrypted_backup)
224+
225+
### Test Vectors
226+
227+
See rust implementation [tests](https://github.com/pythcoiner/encrypted_backup/blob/3280f6f9706497671f08d9365414315159080a84/src/ll.rs#L511)
228+
229+
## Acknowledgements
230+
231+
// TBD

0 commit comments

Comments
 (0)