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

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.