-
Notifications
You must be signed in to change notification settings - Fork 5.8k
Bip draft: Bitcoin Encrypted Backup #1951
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,255 @@ | ||
``` | ||
BIP: ? | ||
Layer: Applications | ||
Title: Compact encryption scheme for non-seed wallet data | ||
Author: // TBD | ||
Comments-Summary: No comments yet. | ||
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-???? | ||
Status: Draft | ||
Type: Informational | ||
Created: 2025-08-22 | ||
License: BSD-2-Clause | ||
``` | ||
|
||
## Introduction | ||
|
||
### Abstract | ||
|
||
This BIP defines a compact encryption scheme for **wallet descriptors** (BIP-0380), | ||
**wallet policies** (BIP-0388), **labels** (BIP-0329), and | ||
**wallet backup metadata** (json). The payload must not contain any private key material. | ||
This scheme enables users to outsource long‑term storage to untrusted media or cloud | ||
services without revealing which addresses, scripts, or number of cosigners are involved. | ||
Encryption keys are derived from the lexicographically‑sorted public keys inside the | ||
descriptor or policy, so any party who already holds one of those keys can later decrypt | ||
the backup without extra secrets or round‑trips. The format uses AES-GCM-256 with a 96‑bit | ||
random nonce and a 128‑bit authentication tag to provide confidentiality and integrity. | ||
While initially designed for descriptors and policies, the same scheme encrypts labels | ||
and backup metadata, allowing a uniform, vendor‑neutral, and future‑extensible backup format. | ||
|
||
### 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 | ||
pythcoiner marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
In practice, losing the **wallet descriptor** (or **wallet policy**) is often **as | ||
catastrophic as losing the wallet’s seed** itself. While the seed grants the | ||
ability to sign, the descriptor grants a map to the coins. In multisig or | ||
miniscript contexts, keys alone are **not sufficient** for recovery: without the | ||
original descriptor the wallet cannot reconstruct the script. | ||
|
||
Offline storage of descriptors has two practical obstacles: | ||
|
||
1. **Descriptors are hard to store offline.** | ||
Descriptor string representation can be far longer than a 12/24-word seed phrase. | ||
Paper, steel, and other long-term analog media quickly become impractical for such | ||
lengths, or error-prone to transcribe. | ||
|
||
2. **Online redundancy carries privacy risk.** | ||
Keeping backups on USB thumb-drives, computers, phones, or (worst) cloud drives | ||
avoids the first problem but amplifies surveillance risk: anyone who gains these | ||
**plaintext descriptors** learns the wallet’s public keys, script structure, | ||
etc... Even with encryption at the cloud provider, an attacker or a subpoena can | ||
compel access, and each extra copy multiplies the attack surface. | ||
|
||
These constraints lead to an acute need for an **encrypted**, and | ||
ideally compact backup format that: | ||
|
||
* can be **safely stored in multiple places**, including untrusted on-line 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**: this allows users to outsource its storage to untrusted parties, | ||
for example, cloud providers, specialized services, etc. | ||
* **Has access control**: decrypting it should only be available to the desired | ||
parties (typically, a subset of the cosigners). | ||
* **Easy to implement**: it should not require any sophisticated tools. | ||
* **Vendor-independent**: it should be easy to implement using any hardware signing | ||
device. | ||
|
||
### Scope | ||
|
||
The primary motivation of this proposal is to store a wallet descriptor(BIP-0380) or a | ||
wallet policy(BIP-0388), but it seems valuable enough to also use this scheme to encrypt | ||
payload containing others wallet-related metadata, like Labels(BIP-0329) or | ||
[wallet backup](https://github.com/pythcoiner/wallet_backup). | ||
|
||
Note: For any kind of payload intented to be encrypted with this scheme, private key | ||
material MUST be removed before encryption. | ||
|
||
## 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We might like to use a subset of the public keys in some cases, e.g. if a Taproot descriptor contains an unspendable internal key that is commonly used across multiple descriptors. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. right, we also need to specify that BIP0341 NUMS MUST be sorted out There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideally, for wallet policies, in the long term we might want to introduce a placeholder for a deterministically derived NUMS key, as discussed for example here. So there wouldn't be any xpub at all. But that's not currently specified in BIP-388. Explicitly excluding all the pubkeys with x coordinate It might be worth mentioning that one could choose just a subset of it is not intended for some of these keys to be able to recover from the backup. The caveat is that it becomes application-specific which of those keys should be able to recover. So if that's mentioned, this should be discussed a bit (for example by adding a recommendation to clearly specify the details in the documentation of the application). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what i'm actually wondering: is there any constructions that have an unspendable key NOT using the BIP341 NUMS? Is there some Lightning constructions using unspendable keys? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the record:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'm quite sure there will be usescases where the user can disable some keys |
||
- Let $s$ = sha256("BEB_DECRYPTION_SECRET" | $p_1$ | $p_2$ | ... | $p_n$) | ||
- Let $s_i$ = sha256("BEB_INDIVIDUAL_SECRET" | $p_i$) | ||
- Let $c_i$ = $s$ ⊕ $s_i$ | ||
|
||
**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. In some cases, it may be possible to exclude certain keys | ||
from this process for customs applications or user needs, it is recommended to document | ||
such decision. | ||
|
||
|
||
|
||
### AES-GCM Encryption | ||
|
||
* let $nonce$ = random() | ||
* let $ciphertext$ = aes_gcm_256_encrypt($payload$, $secret$, $nonce$) | ||
|
||
### AES-GCM Decryption | ||
|
||
In order to decrypt the payload of a backup, the owner of a certain public key p | ||
computes: | ||
|
||
* let $s_i$ = sha256("BEB_INDIVIDUAL_SECRET" ‖ $p$) | ||
* for each `individual_secret_i` generate `reconstructed_secret_i` = | ||
`individual_secret_i` ⊕ `si` | ||
* for each `reconstructed_secret_i` process $payload$ = | ||
aes_gcm_256_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 | ||
|
||
`MAGIC`: 3 bytes which are ASCII/UTF-8 representation of **BEB** (`0x42, 0x45, | ||
0x42`). | ||
|
||
#### 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_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. | ||
|
||
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. | ||
|
||
#### Ciphertext | ||
|
||
`CIPHERTEXT` is the encrypted data resulting encryption of `PAYLOAD` with algorithm | ||
defined in `TYPE` where `PAYLOAD` is encoded following this format: | ||
|
||
`CONTENT` `PLAINTEXT` | ||
|
||
#### Content | ||
|
||
`CONTENT` is a variable length field defining the type of `PLAINTEXT` being encrypted, | ||
it follows this format: | ||
|
||
`LENGTH` `VARIANT` | ||
|
||
`LENGTH`: 1-byte unsigned integer representing the length of `CONTENT` content. | ||
`VARIANT`: there is 3 variants: | ||
- if `LENGTH` == 0, it represent undefined content, no `VARIANT` follow. | ||
- if `LENGTH` == 2, `VARIANT` is 2-byte big-endian signed integer representing | ||
the related BIP number that defines the exact content category. | ||
- if 2 < `LENGTH` < 0xFF, `VARIANT` is `LENGTH` additional bytes carrying opaque, | ||
vendor-specific data. | ||
|
||
Note: `LENGTH` = 0xFF is reserved for futures extensions. | ||
|
||
#### Encrypted Payload | ||
|
||
`ENCRYPTED_PAYLOAD` follows this format: | ||
|
||
`TYPE` `NONCE` `LENGTH` `CIPHERTEXT` | ||
|
||
`TYPE`: 1-byte unsigned integer identifying the encryption algorithm. | ||
|
||
| Value | Definition | | ||
|:-------|:---------------------------------------| | ||
| 0x00 | Undefined | | ||
| 0x01 | AES-GCM-256 | | ||
|
||
`NONCE`: 12-byte nonce for AES-GCM-256. | ||
`LENGTH`: [compact | ||
size](https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer) | ||
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. | ||
|
||
## Rationale | ||
|
||
- Why derivation paths are 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. | ||
|
||
- Why avoid including fingerprints in plaintext encoding: Including fingerprints leaks | ||
direct information about the descriptor participants, which compromises privacy. | ||
|
||
|
||
### Future Extensions | ||
|
||
The version field enables possible future enhancements: | ||
|
||
- Additional encryption algorithms | ||
- Support for threshold-based decryption | ||
pythcoiner marked this conversation as resolved.
Show resolved
Hide resolved
|
||
- Hiding number of participants | ||
- bech32m export | ||
|
||
### Implementation | ||
|
||
- rust [implementation](https://github.com/pythcoiner/encrypted_backup) | ||
|
||
### Test Vectors | ||
|
||
See rust implementation [tests](https://github.com/pythcoiner/encrypted_backup/blob/3280f6f9706497671f08d9365414315159080a84/src/ll.rs#L511) | ||
|
||
## Acknowledgements | ||
|
||
// TBD |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
diff --git a/bip-encrypted-backup.md b/bip-encrypted-backup.md | ||
index 1b347de..af96d2f 100644 | ||
--- a/bip-encrypted-backup.md | ||
+++ b/bip-encrypted-backup.md | ||
@@ -119,7 +119,7 @@ descriptor/wallet policy. | ||
|
||
The encrypted backup must be encoded as follows: | ||
|
||
-`MAGIC` `VERSION` `DERIVATION_PATHS` `INDIVIDUAL_SECRETS` `CONTENT` `ENCRYPTION` | ||
+`MAGIC` `VERSION` `DERIVATION_PATHS` `INDIVIDUAL_SECRETS` `ENCRYPTION` | ||
`ENCRYPTED_PAYLOAD` | ||
|
||
#### Magic | ||
@@ -176,6 +176,29 @@ The `INDIVIDUAL_SECRETS` section follows this format: | ||
| 0x03 | BIP-0329 Labels (JSONL) | | ||
| 0x04 | Wallet backup (JSON) | | ||
|
||
+#### Ciphertext | ||
+ | ||
+`CIPHERTEXT` is the encrypted data resulting encryption of `PAYLOAD` with algorithm | ||
+defined in `TYPE` where `PAYLOAD` is encoded following this format: | ||
+ | ||
+`CONTENT` `PLAINTEXT` | ||
+ | ||
+#### Content | ||
+ | ||
+`CONTENT` define the type of `PLAINTEXT` being encrypted, it follows this format: | ||
+ | ||
+`LENGTH` `VARIANT` | ||
+ | ||
+`LENGTH`: 1-byte unsigned integer representing the length of `CONTENT` content. | ||
+`VARIANT`: there is 3 variants: | ||
+ - if `LENGTH` == 0, it represent undefined content, there is no `VARIANT`. | ||
+ - if `LENGTH` == 2, `VARIANT` is 2-byte signed integer (big-endian) representing | ||
+ a BIP number defining the content type. | ||
+ - if 2 < `LENGTH` < 0xFF, `VARIANT` is of length `LENGTH` bytes and the format is | ||
+ let undefined in this specification in order to let room for proprietary usages. | ||
+ | ||
+Note: `LENGTH` = 0xFF is reserved for futures extensions. | ||
+ | ||
#### Encrypted Payload | ||
|
||
`ENCRYPTED_PAYLOAD` follows this format: |
Uh oh!
There was an error while loading. Please reload this page.