Skip to content

Conversation

@Sjors
Copy link
Owner

@Sjors Sjors commented Jan 14, 2026

Implements bitcoin/bips#1951

DO NOT USE YET

Backups generated with this code are guaranteed to be useless, because the BIP number hasn't been assigned yet - which is part of the encryption scheme.

The spec is also not final yet and will probably be adjusted. I'm not going to implement backward compatibility until the spec is final.

Even if the BIP is final, there's no guarantee this will ever make it into Bitcoin Core, so you may to use an alternative implementation.

It's also entirely vibe coded. Despite much back and forth with Claude Opus 4.5, I have not yet thoroughly reviewed every commit it wrote.

Add helper methods to AEADChaCha20Poly1305 for converting between
the internal Nonce96 type ({uint32_t, uint64_t}) and a 12-byte
array representation (big-endian).

RFC8439 defines the nonce as 96 opaque bits, but our implementation
splits it. These helpers make it convenient to work with byte-based
nonce representations.
Sjors added 2 commits January 15, 2026 09:17
BIP-380 specifies that descriptors can use either ' or h as
the hardened indicator. ParseHDKeypath only supported the former.

This prepares for using ParseHDKeypath with paths extracted
from descriptors which typically use 'h' for shell-escaping convenience.
Introduces WalletDescriptorInfo struct and DescriptorInfoToUniValue() helper
to avoid code duplication when serializing descriptor metadata to UniValue.

Refactors listdescriptors RPC to use the new helper.
@Sjors Sjors force-pushed the wip-encrypted-backup branch from 26aae5f to 695d392 Compare January 15, 2026 08:24
Sjors added 9 commits January 15, 2026 11:50
Add functions for normalizing public keys to x-only format as specified
in BIP-xxxx (Bitcoin Encrypted Backup). These primitives form the foundation
for the encryption scheme.

Functions added:
- NormalizeToXOnly(): Convert CPubKey or CExtPubKey to 32-byte x-only format
- IsNUMSPoint(): Check if a key is the BIP341 unspendable NUMS point
- ExtractKeysFromDescriptor(): Extract and normalize all keys from a descriptor

Includes test vectors from the BIP specification.
Add functions to compute the decryption secret and individual secrets
as specified in BIP-xxxx. The decryption secret is derived from sorted
public keys, and individual secrets allow any keyholder to decrypt.

Functions added:
- ComputeDecryptionSecret(): Hash sorted keys to derive decryption secret
- ComputeIndividualSecret(): Hash a single key to derive its individual secret
- ComputeAllIndividualSecrets(): Compute XOR'd secrets for all keys

Includes test vectors from the BIP specification.
Add functions for encoding/decoding derivation paths as specified in BIP-xxxx:
- ParseDerivationPath: parse m/44'/0'/0' style strings
- EncodeDerivationPaths: encode to binary format (count + path lengths + BE child indices)
- DecodeDerivationPaths: decode from binary format

Includes test vectors from the BIP specification.
Add functions for encoding/decoding individual secrets as specified in BIP-xxxx:
- EncodeIndividualSecrets: encode sorted secrets to binary format
- DecodeIndividualSecrets: decode from binary format

Individual secrets allow any keyholder to derive the decryption secret using:
decryption_secret = ci XOR sha256("BIP_XXXX_INDIVIDUAL_SECRET" || pi)

Includes test vectors from the BIP specification.
Add functions for encoding/decoding content type metadata as specified in BIP-xxxx:
- EncodeContent: encode BIP_NUMBER or VENDOR_SPECIFIC content types
- DecodeContent: decode content type from binary format

Content types define what kind of data is in the encrypted payload:
- BIP_NUMBER: references a BIP specification (e.g., 380 for descriptors)
- VENDOR_SPECIFIC: application-defined content with length prefix

Includes test vectors from the BIP specification.
Add the complete encryption and encoding layer for BIP-xxxx encrypted backups:

Encryption:
- EncryptChaCha20Poly1305: encrypt with ChaCha20-Poly1305 AEAD
- DecryptChaCha20Poly1305: decrypt and verify authentication tag

Backup creation and encoding:
- CreateEncryptedBackup: create backup from descriptor and plaintext
- EncodeEncryptedBackup: encode to binary format
- EncodeEncryptedBackupBase64: encode to base64 string
- DecodeEncryptedBackup: decode from binary format
- DecodeEncryptedBackupBase64: decode from base64 string

Decryption:
- DecryptBackupWithKey: attempt decryption using a single public key
- DecryptBackupWithDescriptor: try all keys from a descriptor

The format uses 6-byte magic "BIPXXX", version byte, derivation paths,
individual secrets (ci values for key recovery), and ChaCha20-Poly1305
encrypted payload containing content type metadata and user data.
Adds two new commands to bitcoin-wallet tool:

encryptbackup:
  Creates an encrypted backup of all wallet descriptors.
  Outputs base64-encoded backup to stdout.
  Requires: -wallet=<name>

decryptbackup:
  Decrypts a backup using a provided extended public key (xpub/tpub).
  Reads base64 backup from stdin, outputs JSON to stdout.
  Requires: -pubkey=<xpub>
  The output is compatible with the importdescriptors RPC.

The decryption only requires an xpub that was used in the original
wallet. In a recovery scenario, the user derives this from their
seed phrase at a known derivation path (e.g., m/84'/0'/0').

Includes functional test demonstrating the full roundtrip.
Display unencrypted metadata from a BIP-xxxx encrypted backup:
- Format version
- Number of recipients
- Encryption algorithm
- Derivation paths (if present)
To include derivation path in backup header.
@Sjors Sjors force-pushed the wip-encrypted-backup branch from 695d392 to 90fe3c7 Compare January 15, 2026 10:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants