Skip to content

Wip encrypted backup#2

Open
pythcoiner wants to merge 21 commits into
masterfrom
wip-encrypted-backup
Open

Wip encrypted backup#2
pythcoiner wants to merge 21 commits into
masterfrom
wip-encrypted-backup

Conversation

@pythcoiner
Copy link
Copy Markdown
Owner

No description provided.

@pythcoiner pythcoiner force-pushed the wip-encrypted-backup branch 4 times, most recently from ea61634 to dfadaa3 Compare April 16, 2026 15:53
pythcoiner and others added 21 commits April 24, 2026 02:43
Replace magic 0x80000000 literals with named constants for BIP32
hardened/unhardened child derivation.
Extract ParseKeyPathElement() as a shared helper in util/bip32 that
parses a single BIP32 key path element (e.g. "0", "0'", "0h").
Both ParseHDKeypath() and the descriptor parser's ParseKeyPathNum()
now delegate to it, eliminating duplicated parsing logic.
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.
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.

Co-authored-by: Sjors Provoost <sjors@sprovoost.nl>
Introduces WalletDescriptorInfo struct and DescriptorInfoToUniValue() helper
to avoid code duplication when serializing descriptor metadata to UniValue.

Refactors listdescriptors RPC to use the new helper.

Co-authored-by: Sjors Provoost <sjors@sprovoost.nl>
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.

Co-authored-by: Sjors Provoost <sjors@sprovoost.nl>
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.

Co-authored-by: Sjors Provoost <sjors@sprovoost.nl>
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.

Co-authored-by: Sjors Provoost <sjors@sprovoost.nl>
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.

Co-authored-by: Sjors Provoost <sjors@sprovoost.nl>
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.

Co-authored-by: Sjors Provoost <sjors@sprovoost.nl>
Add the AEAD primitives used to encrypt and decrypt the backup payload:
- EncryptChaCha20Poly1305: encrypt with ChaCha20-Poly1305 AEAD
- DecryptChaCha20Poly1305: decrypt and verify the authentication tag

These wrap AEADChaCha20Poly1305 from src/crypto/, exposing a uint8_t
interface keyed on the BIP-xxxx 32-byte secret and 12-byte nonce.
Add the EncryptedBackup struct and the binary/base64 wire encoding:
- EncodeEncryptedBackup: encode to binary format
- EncodeEncryptedBackupBase64: encode to base64 string
- DecodeEncryptedBackup: decode from binary format
- DecodeEncryptedBackupBase64: decode from base64 string

The format uses 6-byte magic "BIPXXX", version byte, derivation paths,
individual secrets (ci values for key recovery), encryption algorithm
identifier, nonce, and CompactSize-prefixed ciphertext.
Add the high-level API tying the primitives together:
- CreateEncryptedBackup: create a backup from a descriptor and plaintext
- DecryptBackupWithKey: attempt decryption using a single public key
- DecryptBackupWithDescriptor: try all keys from a descriptor

Decryption reconstructs the per-key share via si = sha256(tag || pi) and
recovers the decryption secret as s = ci XOR si for each candidate ci in
the backup, then attempts AEAD decryption.

Co-authored-by: Sjors Provoost <sjors@sprovoost.nl>
Stateless RPC that takes a descriptor string and returns a base64-
encoded BIP-XXXX encrypted backup. The encryption key is derived from
the public keys in the descriptor. No wallet is needed.
Stateless RPC that takes a base64-encoded BIP-XXXX encrypted backup
and an extended public key (xpub/tpub), then returns the decrypted
descriptor string. No wallet is needed.
Decrypt a BIP-XXXX encrypted backup and import the descriptor into the
loaded wallet. Requires -rpcwallet. Takes a base64 backup and an
extended public key (xpub/tpub).
Stateless command that takes a descriptor string via -descriptor and
encrypts it using the BIP-XXXX encrypted backup format. The plaintext
is the raw descriptor string bytes.

Output is base64 to stdout by default, or raw binary to -backupfile.
No wallet file is loaded — the encryption key is derived from the
public keys in the descriptor itself.

Co-authored-by: Sjors Provoost <sjors@sprovoost.nl>
Stateless command that takes an encrypted backup (base64 on stdin or
raw binary via -backupfile) and a -pubkey (xpub/tpub), then outputs
the decrypted descriptor string to stdout. No wallet file is loaded.

Co-authored-by: Sjors Provoost <sjors@sprovoost.nl>
Decrypt an encrypted backup and import the descriptor into an existing
wallet file. Requires -wallet, -pubkey, and backup data (base64 on
stdin or raw binary via -backupfile).

Also extracts ReadBackupFromArgsOrStdin() helper to avoid duplicating
the backup-reading logic across decrypt/import/inspect commands.
Co-authored-by: Sjors Provoost <sjors@sprovoost.nl>
@pythcoiner pythcoiner force-pushed the wip-encrypted-backup branch from dfadaa3 to 10fa834 Compare April 24, 2026 06:56
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