Skip to content

Conversation

@pythcoiner
Copy link

@pythcoiner pythcoiner commented Sep 4, 2025

This is a bip for encrypted backup, an encryption scheme for bitcoin wallet related metadata.

Mailing list post: https://groups.google.com/g/bitcoindev/c/5NgJbpVDgEc

@pythcoiner pythcoiner marked this pull request as draft September 4, 2025 06:47
| 0x01 | BIP-0380 Descriptor (string) |
| 0x02 | BIP-0388 Wallet policy (string) |
| 0x03 | BIP-0329 Labels (JSONL) |
| 0x04 | Wallet backup (JSON) |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe point out that unlisted types should still be decrypted and that new types may be added to the BIP.

That said, a more generic approach would be to allow a bit more space for the type. E.g. a ~4 digit number that must represent a BIP number. Or a short string like "BIP-0329". Both avoid the need to maintain a central register.

Also, if you allow multiple pieces of content in a single encrypted blob, it's useful to have a padding type.

@pythcoiner
Copy link
Author

thanks for the review! will address comments tmr!

@Sjors
Copy link
Member

Sjors commented Sep 4, 2025

Open questions

  • Deterministic nonce: Currently the nonce is generated randomly. Is it safe to produce a deterministic nonce, e.g. hash("NONCE" || plaintext || key_1 || … || key_n), or are there known security concerns with this approach?

In general nonce reuse is unsafe because if you make multiple backups over time, e.g. as you add more transaction labels, you would be reusing the nonce with different message. By including the plaintext in the nonce, you do address that concern.

However it still seems unwise to mess with cryptographic standards. It doesn't seem worth the risk for saving 32 bytes on something that's going to be at least a few hundred bytes for a typical multisig.

@shocknet-justin
Copy link

Concept ACK, seems adjacent to how some lightning tools enable users to recover SCB's with just their seed to identify and decrypt the backup. Makes sense for descriptors to have something similar.

@pythcoiner pythcoiner force-pushed the encrypted_descriptor branch 7 times, most recently from 1e4ca34 to 3b6b6ad Compare September 5, 2025 06:30
@Sjors
Copy link
Member

Sjors commented Sep 5, 2025

Concept ACK

@pythcoiner
Copy link
Author

(not yet finish addressing comments)

@KeysSoze
Copy link

KeysSoze commented Sep 9, 2025

Hi @pythcoiner,

By coincidence, two weeks ago I started working on a proposal for a "Standard Encrypted Wallet Payload" to be placed inside an "Encrypted Envelope". The "Wallet Payload" contains descriptors and metadata but can also act as a full wallet backup including transactions, UTXOs and addresses. The proposal is very much a work in progress.

I only just found this discussion so am reading through it to compare it to my proposal. The descriptor backup in the "Wallet Payload" of my proposal seems to have some overlap with the BIP proposed here. If there is too much overlap I may reconsider progressing with my proposal.

As mentioned, my proposal is very much a work in progress but the wallet payload proposal can be found here:

https://gist.github.com/KeysSoze/7109a7f0455897b1930f851bde6337e3

Maybe jump to the test vector section to see what a basic backup of a descriptor and some meta data would look like prior to encryption.

https://gist.github.com/KeysSoze/7109a7f0455897b1930f851bde6337e3#test-vectors

As my proposal is designed to be modular and extensible the encryption envelopes may be extended to offer Multiparty Encryption and Authentication. See:

https://gist.github.com/KeysSoze/7109a7f0455897b1930f851bde6337e3#user-content-Expanding_the_Security_Model

I have already started documenting an encryption envelope that uses AES-256-GCM and password protection:

https://gist.github.com/KeysSoze/866d009ccd082edf6802df240154b20d

I have not written a reference implementation yet but there are well established python and Rust libraries for CBOR and COSE that should make implementing the BIPs relatively simple.

@pythcoiner
Copy link
Author

pythcoiner commented Sep 13, 2025

Hi @pythcoiner,

By coincidence, two weeks ago I started working on a proposal for a "Standard Encrypted Wallet Payload" to be placed inside an "Encrypted Envelope". The "Wallet Payload" contains descriptors and metadata but can also act as a full wallet backup including transactions, UTXOs and addresses. The proposal is very much a work in progress.

Hi @KeysSoze, this work seems more related/parallel to the wallet_backup specs I've work on few month ago.

But I've adopted a slightly different approach by simply using JSON.

FYI we already implemented this wallet backup format in Liana wallet and I plan to work on a BIP proposal relatively soon.

abrahem79

This comment was marked as spam.


### Secret generation

- Let $p_1, p_2, \dots, p_n$, be the public keys in the descriptor/wallet policy, in increasing lexicographical order
Copy link

Choose a reason for hiding this comment

The 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.
cc @bigspider @darosior

Copy link
Author

Choose a reason for hiding this comment

The 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

Copy link
Contributor

@bigspider bigspider Sep 16, 2025

Choose a reason for hiding this comment

The 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 50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 seems appropriate and should be forward-compatible with variations of this scheme.

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).

Copy link
Author

Choose a reason for hiding this comment

The 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?
@hugohn @ben-kaufman @Rob1Ham

Is there some Lightning constructions using unspendable keys?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the record:

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

I'm quite sure there will be usescases where the user can disable some keys

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If encryption tooling adds an option to you strip specific public keys (in addition to the standard NUMS point), then on the decryption side those keys simply fail to decrypt, which seems fine.

@Sjors
Copy link
Member

Sjors commented Jan 9, 2026

The PR is still draft. Are there things you're still working on?

What's the bip.diff file about? Accidentally committed?

Gathered open issues from the inlines threads:

Nit: there's quite a bit of trailing whitespace (which my editor then automatically tries to fix)


Maybe this could replace VARIANT:

#### Content

`CONTENT` is a variable length field defining the type of `PLAINTEXT` being encrypted,
it follows this format:

`TYPE` (`LENGTH`) `DATA`

`TYPE`: 1-byte unsigned integer identifying how to interpret `DATA`.

| Value  | Definition                             |
|:-------|:---------------------------------------|
| 0x00   | Reserved                               |
| 0x01   | BIP Number (big-endian uint16)         |
| 0x02   | Vendor-Specific Opaque Tag             |

`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:
- 0x00: parsers MUST reject the payload.
- 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.

For all `TYPE` values except `0x01`, parsers MUST reject `CONTENT` if `LENGTH` exceeds the remaining payload bytes.

Parsers MUST skip unknown `TYPE` values less than `0x80`, by consuming `LENGTH` bytes of `DATA`.

For unknown `TYPE` values greater than or equal to `0x80`, it MUST stop parsing `CONTENT`.

It might add one extra byte for the BIP case, but that's negligible compared to the length of a typical descriptor. And it reduces complexity.

The TYPE >= 0x80 TYPE rule means we're not stuck with the current TLV encoding. And it has a nice upgrade property: you can still encode backward compatible stuff at the start.

Let's also add quick section to encourage base64 encoding:

### 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.

(the footnote format is the same as in BIP3, I think we should use it for all rationale bits)

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 encryption doesn't help against subpoenas or provider breaches, and each
Copy link
Member

@Sjors Sjors Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not trigger a debate about cloud encryption schemes, maybe say:

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.

Cloud encryption doesn't help against subpoenas or provider breaches, and each
copy increases attack surface.

These constraints lead to an acute need for an **encrypted**, and
Copy link
Member

@Sjors Sjors Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"acute"? It's been years :-)

This BIP therefore proposes an encrypted, and compact backup format that:

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
Copy link
Member

@Sjors Sjors Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move this to a new paragraph:

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.


### Key Normalization

Before computing the encryption secret, all public keys in the descriptor/wallet policy MUST be normalized to **33-byte compressed public key format** (SEC format with 0x02 or 0x03 prefix).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not normalise to x-only keys? 32 bytes is a nice size.

In any case, it's good to add a footnote to explain this choice.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah if we're gonna "break" things from what we have actually in Liana, I'm happy with x-only also

@Sjors
Copy link
Member

Sjors commented Jan 9, 2026

@murchandamus or other editor: I think this has progressed enough to be BIP number worthy. It would make it easier to generate stable test vectors.

defined in `ENCRYPTION` where `PAYLOAD` is encoded following this format:

`CONTENT` `PLAINTEXT`

Copy link
Member

@Sjors Sjors Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's introduce this here, and not repeat it:

#### Integer Encodings

All variable-length integers are encoded as [compact size](https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer).


See [keys_types.json](./bip-encrypted-backup/test_vectors/keys_types.json) for normalization test vectors.

### AES-GCM Encryption
Copy link
Member

@Sjors Sjors Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Above you write:

The format uses AES-GCM-256 with a 96‑bit random nonce and a 128‑bit
authentication tag to provide confidentiality and integrity.

But this is a better place to specify that. I think it's good that BIP picks one variant, which allows folks to use a stripped down implementation if needed.

@pythcoiner
Copy link
Author

@Sjors thanks for the comments, will address during the week end or next week.

The PR is still draft. Are there things you're still working on?

I'm working on another BIP draft for the payload of a wallet backup, i'd like to undraft them together, as they are quite related: the payload is expected to be encrypted with this spec...

In the meantime, I've "played" with a C implementation of this BIP draft, I wanted to check how hard it should be to implement in bitcoin core and I figure out something: it seems there is actually no dependency in core for AES-GCM-256, while there is already usage of CHACHA20 so i'm wondering if we should not use CHACHA20 as default encryption algo? (I'll cross-post to delving)

@Sjors
Copy link
Member

Sjors commented Jan 9, 2026

@pythcoiner using AEAD_CHACHA20_POLY1305 is an interesting idea! Would make it very easy in Bitcoin Core, and perfectly doable in other projects since these are standard components. Good idea to ask on Delving.

In theory we don't need the Poly1305 part, since we're not worried about man-in-the-middle attacks on the backup, but I suspect any library with ChaCha20 will also have Poly1305.

C implementation of this BIP draft

For Bitcoin Core you can use c++, no need to torture yourself. But if it also works in C that makes it easier for hardware wallets to have low level support - not sure if they needed, but still. You could also try with embedded Rust and MicroPython.

Perhaps an extension of the protocol could have hardware wallet sign off on the descriptor / policy, by appending CONTENT with e.g. an HMAC field.

@pythcoiner pythcoiner force-pushed the encrypted_descriptor branch from bfdf6f7 to ab5d450 Compare January 10, 2026 07:56
@pythcoiner
Copy link
Author

What's the bip.diff file about? Accidentally committed?

yes, I've dropped it

@pythcoiner
Copy link
Author

no need to torture yourself

not felt the torture part 😄

the idea was more, all languages can bind to C, it's not always the case for c++ (or at least less easy) and maybe it worth to have a single implem to maintain

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants