A simple backup scheme for wallet accounts

I’m trying to wrap my head around the XOR set of individual secrets Ci (included in backup) as they relate to the shared secret S (to decrypt the ciphertext payload).

[updated] to explain my confusion yesterday about the above statement. They’re not related at all. The shared decryption key is a secret by itself, as are each of the individual Ci pre-images, which are hashed to hide the shared secret until one of the cosigners can remove theirs via XOR to reveal the shared decryption key.

If there are only 2 xpubs in a descriptor, then the XOR result of both Ci values IS the shared secret?

If there are 4 xpubs, or any “even” number of xpubs since each Ci is the whole shared secret minus that individual secret, then the combined XOR result of all Ci values (w/ each individual secret XORed an odd number of times, and revealed), IS the shared secret?

I think I recall a version of similar scheme where each Ci was ciphertext decrypted by its pubkey that revealed the shared secret, rather than XOR-“subtracted” (if that makes sense) from the shared-secret.

I’ll politely ask you to excuse me if I’ve wasted your time or brain cycles to consider my above doubts. I thought that it would be possible to single-out one of the individual secrets and eventually the shared secret by playing with a subset of the Ci secrets, but after trying, I see that the best we can do is an XOR result of at least 2 unknowns.

Thank you for putting thoughts into this important topic.

One idea for an improvement: I do not like that I need now to keep a secret to get an access to descriptor. So, what if I use master xpub chain code as a secret? With that, I would be able to gain access to all descriptors in which keys derived from that master xpub participate.

Also, it is not clear why we need to have a share secret; instead, each multisig participant creates his own backups symmetrically encrypted with just his master xpub chaincode.

I do not completely agree with you on this specific point, for privacy reason you may not want any recovery key to be able to decrypt alone, if the lowest recovery condition is a threshold forinstance.

Let say my Liana policy is or(A, and(thresh(2,B,C,D), older(timelock))) where i’m A and B,C,D are my heirs, I may want to limit the possibility to decrypt the backup only if 2 of my heirs cooperate (or even 2 heirs + a lawyer/ third party).

In this (particular) case SSS (or any mechanism enforcing a threshold at decrypting) may be useful.

But I personally see it more as an optionnal feature or a different format/version rather than default.

In the proposed scheme, indeed you do not need any additional secret (other than your seed/mnenomic). The encryptions of the common secret are part of the backup, not extra secrets to store individually.

There are certainly possible extensions, as explored by @josh ([1] [2]). While I think they are neat and interesting, I would be wary of adding complexity to an otherwise very simple scheme, as I think it is likely to hamper adoption. While a library can encapsulate the implementation complexity, it cannot always encapsulate the interface, which is often made more verbose/difficult by the presence of additional features. Interoperability might also be affected if there are optional features.

In your example, and for most inheritance use cases, the capability of individual heirs to decrypt the backup (even if cooperation is required to actually move the funds) is IMHO unlikely to be problematic in practice.

1 Like

I wrote a rough implementation draft: GitHub - Sjors/descriptor-backup: Encrypt output descriptors and decrypt using any of its public keys

It’s in Ruby, though I might switch to Rust.

Update 2025/08/01: also vibe coded a bech32m encoding. It takes a descriptor as input and finds the xpubs for you.

2 Likes

Nice, I’m working on an implementation based on this scheme in rust, I’ll share there in few days.

1 Like

Looking forward. Perhaps you can also use GitHub - joshdoman/descriptor-codec: Encode and decode Bitcoin wallet descriptors with a 30-40% size reduction from @josh to reduce the encrypted payload size a bit. That library itself could perhaps gain even more size reduction by checking for duplicate xpubs (followed by different derivation paths).

yes, I plan to have a look at it in a second time, its very interesting for those that want to engrave their encrypted backup

Since last week I’ve been working on an Rust implementation based on this scheme & comments that have been made in this thread:

I’ve also started to write draft spec:

tldr : it define a broader scope of what can be encrypted and add few useful metadas to the encrypted payload:

  • version
  • list of derivation path (optional)
  • the type of content
  • the encryption protocol used
  • the nonce used in AES-GCM
1 Like

Related to the topic of this thread, the Bitcoin Core wallet is about to add an RPC to backup a wallet’s descriptors and related information. See https://github.com/bitcoin/bitcoin/pull/32489. I’m unsure how much overlap there is with this effort, but it’s about achieving the same goal: backing up all private-but-not-secret information related to the wallet of a user.

1 Like

I’ve just PR a draft BIP related to this: Bip draft: Bitcoin Encrypted Backup

2 Likes

cross posting from the BIP draft PR to here:

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)

I’d like to get feedback about changing the default encryption algo from AES-GCM => CHACHA20, I’m really thinking having this implemented in core should be a trong plus, and I dant think it worth to add new dependencies only for that purpose.

The secret vs private distinction is a great framing and I think it’s going to change how people think about descriptor backup burden.

However, I still think about two things:

The availability problem feels not addressed. Google deletes inactive accounts. Nostr relays drop data whenever they feel like it. Email providers get acquired. The person who stored 5 copies at wallet creation has no way to know any of them survived without actively checking, which most people won’t do. Confidentiality and durability are separate properties and only one of them is solved here.

The inheritance path also seems to assume the heir holds an xpub, which means they were a cosigner or the owner handed them key material before dying, which is kind of the hard part of inheritance anyway. The dead man switch email workaround is honest about this gap but reintroduces exactly the centralised dependencies the encryption scheme eliminates elsewhere.

Questions I don’t have an answer to are:

  • Is there a way to provide durability guarantees that are enforced by something other than user discipline?
  • What’s the right primitive for heirs who hold no key material at all?

You are correct on both points: this scheme makes no attempt to solve the availability problem, nor guaranteeing the correct key discipline for inheritance and other use cases. I think those have to be addressed on other layers.

I would disagree that the availability problem is hard to solve, though. Computers are really good at storing and copying things. While it’s true they aren’t very good at holding on to things for a very long time, that problem is solved with enough redundancy.
People seem to disagree and proposed solutions like OP_RETURNs or inscriptions as a guaranteed backup – but i don’t think that’s necessary, and ultimately it will likely become too expensive anyway.

Ensuring correct key management from the involved cosigners (especially heirs) is definitely the hardest nut to crack for adoption.

For non-technical people, I tend to think that ‘assisted self-custody with custodial fallback’ is the most reasonable and technologically approachable solution. That is, they are in self-custody (and during that time, a non-custodial service can provide reminders about coin refresh and key management); but if all else fails, after a long enough time-frame, some moderately-trusted third party gets access to the funds. At least trust is only required in case of complete failure, rather than in the normal scenario.

Fair point. Tools already exist to automate replication across multiple providers, and if that gets built into standard software it probably handles most of the availability problem without needing dedicated infrastructure.

Where I keep landing is one step further though. Even with perfect replication, the heir still has to find and access those copies under stress, possibly years after the owner’s accounts went inactive. I think that’s a coordination problem on the recovery side that feels separate from the storage side.

Is the custodial fallback your answer to that too, or do you see that as a different layer entirely?