[POC] wallet: Enable non-electronic (paper-based) wallet backup with codex32 #33043

pull w0xlt wants to merge 5 commits into bitcoin:master from w0xlt:codex32 changing 31 files +1437 −46
  1. w0xlt commented at 7:36 am on July 23, 2025: contributor

    This PR introduces support for exporting and restoring wallet seeds using the codex32 format, enabling non-electronic (paper-based) wallet backups.

    To accomplish this, the patch ports the codex32.{c,h} implementation from Core Lightning to C++, integrating it with Bitcoin Core’s libraries. Corresponding unit tests for codex32 encoding and decoding are also included.

    Because Bitcoin Core wallets currently do not store the seed material by default, this PR adds support for doing so, along with a new wallet flag to explicitly indicate when this feature is enabled.

    Two new RPCs are introduced:

    exposesecret: exports the wallet seed in codex32 format.

    recoverwalletfromseed: restores a wallet from a codex32-encoded seed.

    A functional test included in the last commit demonstrates the full backup and restore flow. Currently, only the default derivation path is supported.

    This PR is intended as a proposal for feedback—to assess whether this functionality is desirable, and to explore how it might evolve further.

  2. DrahtBot commented at 7:36 am on July 23, 2025: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/33043.

    Reviews

    See the guideline for information on the review process. A summary of reviews will appear here.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #33112 (wallet: relax external_signer flag constraints by Sjors)
    • #33034 (wallet: Store transactions in a separate sqlite table by achow101)
    • #32977 (wallet: Remove wallet version and several legacy related functions by w0xlt)
    • #32895 (wallet: Prepare for future upgrades by recording versions of last client to open and decrypt by achow101)
    • #32652 (wallet: add codex32 argument to addhdkey by roconnor-blockstream)
    • #32489 (wallet: Add exportwatchonlywallet RPC to export a watchonly version of a wallet by achow101)
    • #28333 (wallet: Construct ScriptPubKeyMans with all data rather than loaded progressively by achow101)
    • #27865 (wallet: Track no-longer-spendable TXOs separately by achow101)
    • #26022 (Add util::ResultPtr class by ryanofsky)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

    LLM Linter (✨ experimental)

    Possible typos and grammar issues:

    • IDENTIFER -> IDENTIFIER [correct spelling in “IDENTIFER := BECH32*4”]
    • in stored -> are stored [grammar fix: “wallet seeds in stored in database”]

    drahtbot_id_4_m

  3. DrahtBot added the label CI failed on Jul 23, 2025
  4. DrahtBot commented at 9:36 am on July 23, 2025: contributor

    🚧 At least one of the CI tasks failed. Task lint: https://github.com/bitcoin/bitcoin/runs/46538554195 LLM reason (✨ experimental): The CI failure is caused by lint errors, including trailing whitespace, locale-dependent functions, spelling errors, incorrect file permissions, and missing include guards.

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  5. apoelstra commented at 2:35 pm on July 23, 2025: contributor

    For encrypted wallets, recovery requires both the codex32 seed and the original passphrase, enhancing the security.

    If I understand you correctly, the intent is that an attacker needs the wallet passphrase in addition to the seed. But the seed is the seed – once an attacker has it, it’s game over. He doesn’t need the passphrase or any other part of the wallet to sweep coins.

    codex32 itself does not have any notion of passphrases or encryption.

  6. w0xlt commented at 3:57 pm on July 23, 2025: contributor

    Yes, you’re right. I thought the passphrase deterministically modified the seed to create the BIP 32 Extended Key, but that’s not the case. https://github.com/bitcoin/bitcoin/blob/73e754bd01b0653d1fda2d947fcaed0742da81c3/src/wallet/wallet.cpp#L3542

    So the passphrase doesn’t provide any additional security in this case.

  7. w0xlt marked this as a draft on Jul 23, 2025
  8. w0xlt commented at 4:25 pm on July 23, 2025: contributor

    This could still be implemented—even as part of this PR.

    The wallet generates the seed, stores it, and then derives the BIP32 master key by deterministically combining the seed with the user-provided passphrase. This approach allows users to back up the raw seed while still requiring the passphrase for wallet recovery.

    Or am I missing something ?

  9. wallet: Add codex32 files 8b80a59893
  10. wallet: Store seeds in database 2d6b910820
  11. wallet: Add exposesecret RPC 25b0878cff
  12. Add recoverwalletfromseed RPC 27bef167f0
  13. w0xlt force-pushed on Jul 23, 2025
  14. Add codex32 functional test 14f3342262
  15. w0xlt force-pushed on Jul 23, 2025
  16. Sjors commented at 7:20 pm on July 24, 2025: member
    How does this relate to #27351 / #32652? It’s probably easier to support import before export.
  17. w0xlt commented at 8:12 pm on July 24, 2025: contributor

    The emphasis of this PR is on backup functionality: a backup feature isn’t very useful unless you can also restore the wallet reliably.

    By contrast, the other PRs aim only to import a Codex32 secret—presumably from Core Lightning into Bitcoin Core—and each uses a different method. The approach here creates a fresh wallet and rescans the chain, whereas PR  #32652 appears to convert a Codex32 secret into an xpub via the proposed addhdkey RPC.

  18. Sjors commented at 6:39 am on July 25, 2025: member

    It might be useful to open an issue to discuss paper backups in general.

    I think conceptually the are three different things to backup, and each has different requirements and frequencies:

    1. Private key material: backed up once at wallet creation time (or never, in some multisig setups where recovery consists of using the other keys and moving to a fresh wallet). Needs to be kept secure against theft.
    2. Output descriptors: for anything more complicated than a BIP48 multisig setup you have to keep track of this, or you’ll never find your coins. New descriptors may be added later in the life of a wallet (e.g. for our wallet it happened when taproot support was added, albeit in a deterministic way). Backup happens at wallet creation time and each time a new descriptor is added. Needs to be kept secure for privacy reasons, but not for theft
    3. Transaction meta data (labels, etc), see https://github.com/bitcoin/bips/blob/master/bip-0329.mediawiki. Backups happens frequently. Needs to be kept secure for privacy reasons, but also may need to be imported into bookkeeping software. Printing it may be useful so you can still look things up 20 years from now when who knows what the world looks like and what software people use.

    This division borrows heavily from the motivation section of: https://delvingbitcoin.org/t/a-simple-backup-scheme-for-wallet-accounts/1607

    It seems to me that codex32 only plays a role in (1). And so importing it should be done with addhdkey. Exporting could be done with gethdkeys.

    It may be nice to have a more user friendly way to do all or some of (1), (2) and (3). But that I would probably write a markdown document or a Python script. Outside of this project I could imagine a nice tool that calls various RPC methods and generates a pretty PDF, though I would not recommend that for (1).

  19. DrahtBot commented at 0:36 am on August 16, 2025: contributor
    🐙 This pull request conflicts with the target branch and needs rebase.
  20. DrahtBot added the label Needs rebase on Aug 16, 2025
  21. BenWestgate commented at 3:54 pm on August 21, 2025: contributor

    This could still be implemented—even as part of this PR.

    The wallet generates the seed, stores it, and then derives the BIP32 master key by deterministically combining the seed with the user-provided passphrase. This approach allows users to back up the raw seed while still requiring the passphrase for wallet recovery.

    Or am I missing something ?

    Not part of the BIP-0093 spec.

    If a passphrase is used it becomes a nested 2 of 2(passphrase, codex32 secret) causing funds loss if the passphrase is forgotten. Which is more likely than lost shares if the passphrase offers enough entropy to be secure (4+ EFF_large words w/ 4 rounds of 1GB argon2id KDF.

    In my project BAILS: I derive one codex32 share from a memorized passphrase using Argon2id to create a hybrid brain wallet shamir secret sharing scheme that is resilient against memory loss if n > t + 1.

    A passphrase on the secret is how SLIP-39 does it, similar to what you proposed.

  22. BenWestgate commented at 4:05 pm on August 21, 2025: contributor

    It seems to me that codex32 only plays a role in (1). And so importing it should be done with addhdkey. Exporting could be done with gethdkeys.

    It would be a usability and compatibility improvement to begin exporting hdseed in the codex32 secret format; as:

    1. Easier to type, write and speak than Base58.
    2. Error correcting, hand-verifiable checksum
    3. Easier to extract the payload seed bytes without needing base58 conversions.

    The 4 character identifier when exporting hdseed as a codex32 secret should be the BIP-0032 fingerprint of hdseed.

    Rationale: This default assists users in locating the correct codex32 backup for their wallets, should be distinct for every master seed users may need to disambiguate and, as it is widely stored, improves overall error correction.


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2025-09-02 12:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me