{
 "number": 36444,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36444",
 "title": "wallet: Store information about created and imported multipath descriptors",
 "author": "achow101",
 "author_association": "MEMBER",
 "created_at": "2026-10-05T22:25:11Z",
 "updated_at": "2026-10-08T18:06:54Z",
 "description_hash": "65b11c9cb7a040e3",
 "age_days": 27,
 "draft": true,
 "labels": [
  "Wallet",
  "Needs rebase",
  "CI failed"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "a49e42b8bb704f1e7a3ea2f0b1a1d34ee7c182df",
 "head_ref": "wallet-store-multipath",
 "head_repo": "achow101/bitcoin",
 "head_history": [
  {
   "t": "2026-10-05T22:33:39Z",
   "sha": "a49e42b8bb704f1e7a3ea2f0b1a1d34ee7c182df"
  }
 ],
 "additions": 1436,
 "deletions": 740,
 "changed_files": 45,
 "commit_count": 18,
 "size_bucket": "XL",
 "mergeable_state": "dirty",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "w0xlt",
      "url": "https://github.com/bitcoin/bitcoin/pull/36444#issuecomment-6066074390"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "w0xlt": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-10-08T18:06:50Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 1,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "w0xlt"
  ]
 },
 "signals": {
  "needs_rebase": true,
  "ci_failed": true,
  "mergeable_state": "dirty",
  "last_author_activity": "2026-10-05T22:33:39Z",
  "last_reviewer_activity": "2026-10-08T18:06:50Z",
  "last_reviewer": "w0xlt",
  "author_silent_days": 27,
  "waiting_on_author_days": 24,
  "days_since_update": 24
 },
 "refs": {
  "mentioned": [
   35445,
   36133,
   36442,
   36443
  ],
  "depends_on": [
   36442
  ],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 36442,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "descriptor: Represent multipath descriptors in a single Descriptor object"
   },
   {
    "number": 35445,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-09-09",
    "title": "wallet, descriptor: Revert `StringType::COMPAT` for Miniscript expressions and drop the concept of a Descriptor ID that can be validated"
   },
   {
    "number": 36133,
    "type": "pull",
    "state": "closed",
    "merged": false,
    "merged_at": null,
    "title": "wallet: store multipath descriptor"
   },
   {
    "number": 36443,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "descriptor: Reconstruct a multipath descriptor from its expansion"
   }
  ],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [],
 "body": "The wallet does not store any information about multipath descriptors. When a multipath descriptor is imported, it is first expanded, then the expansion descriptors are imported into the wallet.\n\nThis PR adds a field to `WalletDescriptor` which stores the IDs of the ScriptPubKeyMans of the descriptors that are part of the expansion, in the order that they are expanded. This allows us to use `ReconstructMultipath` to produce the multipath descriptor when necessary.\n\nThis is useful for working with hardware wallets that require policy registration, particularly those that require resending the descriptor that was registered.\n\n`listdescriptors` is updated to include a `multipath_descriptor` field which contains the multipath descriptor for each individual descriptor if it originally came from a multipath descriptor.\n\nLastly, the automatically generated descriptors are now produced and stored as multipath descriptors, so `listdescriptors` will provide a `multipath_descriptor` for everything that the wallet automatically generates.\n\nNote that the IDs stored in the wallet are ScriptPubKeyMan IDs, which we cannot compute from a descriptor as we assume to be opaque blobs since #35445.\n\nAlternative to #36133, depends on #36442 and #36443",
 "commits": [
  {
   "sha": "bcb79a319095300d09d6c95ad27351d480723263",
   "date": "2026-10-05T22:07:34Z",
   "message": "util: Make KeyPathElement class with private members"
  },
  {
   "sha": "4a4101e66a331cb4d901b567f00e5f85706c1f11",
   "date": "2026-10-05T22:07:34Z",
   "message": "util: Change all key path operations to operate with std::vector<KeyPathElement>"
  },
  {
   "sha": "d76009b9661ce8c07f5ad0c8b34717e8548618b5",
   "date": "2026-10-05T22:07:34Z",
   "message": "util: Make KeyPath a subclass of std::vector and not only a typedef\n\nWe will want to have member functions on KeyPath later, so make it a\nclass now."
  },
  {
   "sha": "3c432dd7878d9ff0159d599269b5e1e3381dd2df",
   "date": "2026-10-05T22:07:34Z",
   "message": "util: Store hardened character in KeyPathElement"
  },
  {
   "sha": "3ef7ee8558434a25559456f9b99418b946b2718d",
   "date": "2026-10-05T22:07:34Z",
   "message": "util: Make KeyPathElement handle multipaths\n\nMoves all of the multipath parsing into ParseHDKeypath, and let\nKeyPathElement be a multipath element."
  },
  {
   "sha": "907d103d95efd73ea3b4f3d0c7f93f6b356a94f5",
   "date": "2026-10-05T22:07:34Z",
   "message": "descriptor: Get the multipath expansion from a multipath descriptor"
  },
  {
   "sha": "ee1cc660633b3845d6a054d1601abcd86a2f1093",
   "date": "2026-10-05T22:07:34Z",
   "message": "descriptor: Produce multipath descriptor in ParseScript\n\nInternally, descriptor parsing will produce multipath descriptor objects\nrather than std::vector<DescriptorImpl>. This single multipath\ndescriptor becomes std::vector<Descriptor> within Parse. Future commits\nwill return the single multipath descriptor object for all descriptor parsing."
  },
  {
   "sha": "3752814f45e40751628629fd7cf50c7be3635c2f",
   "date": "2026-10-05T22:30:08Z",
   "message": "descriptor: Return multipath descriptor from Parse\n\nInstead of a std::vector<std::unique_ptr<Descriptor>>, Parse can return\na single std::unique_ptr<Descriptor> that contains the multipath\ninformation if the descriptor was multipath. Callers that\nrequire the multipath expansion use GetMultipathExpansion() to get the\nexpanded descriptors."
  },
  {
   "sha": "2cda830d1b77fb89787b60219e19abfc7e3321b9",
   "date": "2026-10-05T22:33:17Z",
   "message": "descriptor: Implement multipath reconstruction"
  },
  {
   "sha": "bcbf29aa1d9004f57bab191b008bad1b84ac4d15",
   "date": "2026-10-05T22:33:17Z",
   "message": "test: Add multipath reconstruction round trip checks"
  },
  {
   "sha": "cb6ccca033f549153af896d0ed494f58a54af45c",
   "date": "2026-10-05T22:33:17Z",
   "message": "fuzz: Check multipath descriptor reconstruction"
  },
  {
   "sha": "9422834d210fa5e0e5b6d699a1cef28941aac86a",
   "date": "2026-10-05T22:33:17Z",
   "message": "test: Invalid multipath reconstruction tests"
  },
  {
   "sha": "39a2443538f7ad60ac862ff304462fce17def091",
   "date": "2026-10-05T22:33:33Z",
   "message": "wallet: Store multipath relative spkm ids in WalletDescriptor"
  },
  {
   "sha": "61543e0f79c31db93f9fbc7d08ff9e3161031a57",
   "date": "2026-10-05T22:33:33Z",
   "message": "wallet, rpc: Store multipath relative ids on import"
  },
  {
   "sha": "d5df3a17b8764c6f895122c7a0d6aa40bc2eb032",
   "date": "2026-10-05T22:33:33Z",
   "message": "wallet, export: Export multipath relative ids for exportwatchonly"
  },
  {
   "sha": "c70397503d84d99c525db459666f45c3fbbd6077",
   "date": "2026-10-05T22:33:33Z",
   "message": "wallet, export, rpc: also list original multipath descriptors"
  },
  {
   "sha": "65fd869daf7b8ef54fae2e8f056d1fe8069367a5",
   "date": "2026-10-05T22:33:33Z",
   "message": "wallet: Store automatically created descriptors as multipath"
  },
  {
   "sha": "a49e42b8bb704f1e7a3ea2f0b1a1d34ee7c182df",
   "date": "2026-10-05T22:33:33Z",
   "message": "test: Check for multipath descriptors in listdescriptors\n\nCheck that the correct multipath descriptor appears in listdescriptors\nfor a typical single key wallet. Also check that the correct\nnon-multipath descriptors appears, and also do for private\nlistdescriptors."
  }
 ],
 "timeline": [
  {
   "t": "2026-10-05T22:33:39Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "a49e42b8bb704f1e7a3ea2f0b1a1d34ee7c182df",
   "id": "push:a49e42b8bb704f1e7a3ea2f0b1a1d34ee7c182df"
  },
  {
   "t": "2026-10-08T18:06:50Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK",
   "id": "c:6066074390",
   "url": "https://github.com/bitcoin/bitcoin/pull/36444#issuecomment-6066074390",
   "hash": "e8334cc43879b73c",
   "edited": null
  }
 ],
 "labels_log": [
  {
   "t": "2026-10-05T22:25:15Z",
   "action": "labeled",
   "label": "Wallet",
   "who": "DrahtBot"
  },
  {
   "t": "2026-10-05T22:36:28Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-10-07T13:58:19Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 3329,
 "text_tokens_estimate": 832,
 "changed_paths": [
  "src/bench/descriptors.cpp",
  "src/bench/wallet_encrypt.cpp",
  "src/bench/wallet_ismine.cpp",
  "src/key.cpp",
  "src/key.h",
  "src/psbt.h",
  "src/qt/test/wallettests.cpp",
  "src/rpc/mining.cpp",
  "src/rpc/output_script.cpp",
  "src/rpc/util.cpp",
  "src/rpc/util.h",
  "src/script/descriptor.cpp",
  "src/script/descriptor.h",
  "src/script/keyorigin.h",
  "src/script/sign.cpp",
  "src/test/bip32_tests.cpp",
  "src/test/descriptor_tests.cpp",
  "src/test/fuzz/descriptor_parse.cpp",
  "src/test/fuzz/parse_hd_keypath.cpp",
  "src/test/fuzz/util/wallet.h",
  "src/test/psbt_tests.cpp",
  "src/util/bip32.cpp",
  "src/util/bip32.h",
  "src/wallet/export.cpp",
  "src/wallet/export.h",
  "src/wallet/imports.cpp",
  "src/wallet/rpc/addresses.cpp",
  "src/wallet/rpc/backup.cpp",
  "src/wallet/rpc/spend.cpp",
  "src/wallet/rpc/wallet.cpp",
  "src/wallet/scriptpubkeyman.cpp",
  "src/wallet/scriptpubkeyman.h",
  "src/wallet/test/fuzz/scriptpubkeyman.cpp",
  "src/wallet/test/psbt_wallet_tests.cpp",
  "src/wallet/test/util.cpp",
  "src/wallet/test/wallet_tests.cpp",
  "src/wallet/test/walletload_tests.cpp",
  "src/wallet/wallet.cpp",
  "src/wallet/wallet.h",
  "src/wallet/walletdb.cpp",
  "src/wallet/walletutil.cpp",
  "src/wallet/walletutil.h",
  "test/functional/rpc_getdescriptorinfo.py",
  "test/functional/wallet_importdescriptors.py",
  "test/functional/wallet_listdescriptors.py"
 ],
 "files": [
  {
   "path": "src/bench/descriptors.cpp",
   "add": 2,
   "del": 2
  },
  {
   "path": "src/bench/wallet_encrypt.cpp",
   "add": 2,
   "del": 2
  },
  {
   "path": "src/bench/wallet_ismine.cpp",
   "add": 2,
   "del": 2
  },
  {
   "path": "src/key.cpp",
   "add": 4,
   "del": 3
  },
  {
   "path": "src/key.h",
   "add": 2,
   "del": 1
  },
  {
   "path": "src/psbt.h",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/qt/test/wallettests.cpp",
   "add": 3,
   "del": 4
  },
  {
   "path": "src/rpc/mining.cpp",
   "add": 3,
   "del": 4
  },
  {
   "path": "src/rpc/output_script.cpp",
   "add": 14,
   "del": 19
  },
  {
   "path": "src/rpc/util.cpp",
   "add": 8,
   "del": 8
  },
  {
   "path": "src/rpc/util.h",
   "add": 2,
   "del": 1
  },
  {
   "path": "src/script/descriptor.cpp",
   "add": 517,
   "del": 408
  },
  {
   "path": "src/script/descriptor.h",
   "add": 9,
   "del": 1
  },
  {
   "path": "src/script/keyorigin.h",
   "add": 2,
   "del": 1
  },
  {
   "path": "src/script/sign.cpp",
   "add": 3,
   "del": 3
  },
  {
   "path": "src/test/bip32_tests.cpp",
   "add": 77,
   "del": 79
  },
  {
   "path": "src/test/descriptor_tests.cpp",
   "add": 194,
   "del": 37
  },
  {
   "path": "src/test/fuzz/descriptor_parse.cpp",
   "add": 20,
   "del": 2
  },
  {
   "path": "src/test/fuzz/parse_hd_keypath.cpp",
   "add": 8,
   "del": 6
  },
  {
   "path": "src/test/fuzz/util/wallet.h",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/test/psbt_tests.cpp",
   "add": 5,
   "del": 5
  },
  {
   "path": "src/util/bip32.cpp",
   "add": 149,
   "del": 27
  },
  {
   "path": "src/util/bip32.h",
   "add": 113,
   "del": 10
  },
  {
   "path": "src/wallet/export.cpp",
   "add": 30,
   "del": 5
  },
  {
   "path": "src/wallet/export.h",
   "add": 3,
   "del": 0
  },
  {
   "path": "src/wallet/imports.cpp",
   "add": 23,
   "del": 12
  },
  {
   "path": "src/wallet/rpc/addresses.cpp",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/wallet/rpc/backup.cpp",
   "add": 4,
   "del": 0
  },
  {
   "path": "src/wallet/rpc/spend.cpp",
   "add": 4,
   "del": 4
  },
  {
   "path": "src/wallet/rpc/wallet.cpp",
   "add": 2,
   "del": 2
  },
  {
   "path": "src/wallet/scriptpubkeyman.cpp",
   "add": 63,
   "del": 11
  },
  {
   "path": "src/wallet/scriptpubkeyman.h",
   "add": 4,
   "del": 0
  },
  {
   "path": "src/wallet/test/fuzz/scriptpubkeyman.cpp",
   "add": 4,
   "del": 4
  },
  {
   "path": "src/wallet/test/psbt_wallet_tests.cpp",
   "add": 2,
   "del": 3
  },
  {
   "path": "src/wallet/test/util.cpp",
   "add": 4,
   "del": 6
  },
  {
   "path": "src/wallet/test/wallet_tests.cpp",
   "add": 3,
   "del": 5
  },
  {
   "path": "src/wallet/test/walletload_tests.cpp",
   "add": 5,
   "del": 3
  },
  {
   "path": "src/wallet/wallet.cpp",
   "add": 40,
   "del": 18
  },
  {
   "path": "src/wallet/wallet.h",
   "add": 2,
   "del": 2
  },
  {
   "path": "src/wallet/walletdb.cpp",
   "add": 12,
   "del": 10
  },
  {
   "path": "src/wallet/walletutil.cpp",
   "add": 2,
   "del": 2
  },
  {
   "path": "src/wallet/walletutil.h",
   "add": 30,
   "del": 10
  },
  {
   "path": "test/functional/rpc_getdescriptorinfo.py",
   "add": 1,
   "del": 1
  },
  {
   "path": "test/functional/wallet_importdescriptors.py",
   "add": 16,
   "del": 4
  },
  {
   "path": "test/functional/wallet_listdescriptors.py",
   "add": 40,
   "del": 10
  }
 ],
 "test_lines": 565,
 "git": {
  "head": "a49e42b8bb704f1e7a3ea2f0b1a1d34ee7c182df",
  "head_matches_backup": true,
  "base": "cf80493e2367786ce9946c522eb8313a52c90557",
  "patch_id": "fd3d4e1eb0675f25",
  "commits": [
   {
    "sha": "bcb79a3190",
    "subject": "util: Make KeyPathElement class with private members",
    "files": 2,
    "add": 11,
    "del": 5
   },
   {
    "sha": "4a4101e66a",
    "subject": "util: Change all key path operations to operate with std::vector<KeyPathElement>",
    "files": 15,
    "add": 183,
    "del": 138
   },
   {
    "sha": "d76009b966",
    "subject": "util: Make KeyPath a subclass of std::vector and not only a typedef",
    "files": 5,
    "add": 16,
    "del": 12
   },
   {
    "sha": "3c432dd787",
    "subject": "util: Store hardened character in KeyPathElement",
    "files": 7,
    "add": 99,
    "del": 71
   },
   {
    "sha": "3ef7ee8558",
    "subject": "util: Make KeyPathElement handle multipaths",
    "files": 3,
    "add": 171,
    "del": 75
   },
   {
    "sha": "907d103d95",
    "subject": "descriptor: Get the multipath expansion from a multipath descriptor",
    "files": 5,
    "add": 81,
    "del": 1
   },
   {
    "sha": "ee1cc66063",
    "subject": "descriptor: Produce multipath descriptor in ParseScript",
    "files": 4,
    "add": 181,
    "del": 343
   },
   {
    "sha": "3752814f45",
    "subject": "descriptor: Return multipath descriptor from Parse",
    "files": 26,
    "add": 149,
    "del": 154
   },
   {
    "sha": "2cda830d1b",
    "subject": "descriptor: Implement multipath reconstruction",
    "files": 4,
    "add": 260,
    "del": 0
   },
   {
    "sha": "bcbf29aa1d",
    "subject": "test: Add multipath reconstruction round trip checks",
    "files": 1,
    "add": 54,
    "del": 0
   },
   {
    "sha": "cb6ccca033",
    "subject": "fuzz: Check multipath descriptor reconstruction",
    "files": 1,
    "add": 20,
    "del": 2
   },
   {
    "sha": "9422834d21",
    "subject": "test: Invalid multipath reconstruction tests",
    "files": 2,
    "add": 98,
    "del": 1
   },
   {
    "sha": "39a2443538",
    "subject": "wallet: Store multipath relative spkm ids in WalletDescriptor",
    "files": 1,
    "add": 21,
    "del": 7
   },
   {
    "sha": "61543e0f79",
    "subject": "wallet, rpc: Store multipath relative ids on import",
    "files": 4,
    "add": 21,
    "del": 0
   },
   {
    "sha": "d5df3a17b8",
    "subject": "wallet, export: Export multipath relative ids for exportwatchonly",
    "files": 2,
    "add": 26,
    "del": 2
   },
   {
    "sha": "c70397503d",
    "subject": "wallet, export, rpc: also list original multipath descriptors",
    "files": 7,
    "add": 63,
    "del": 5
   },
   {
    "sha": "65fd869daf",
    "subject": "wallet: Store automatically created descriptors as multipath",
    "files": 5,
    "add": 33,
    "del": 5
   },
   {
    "sha": "a49e42b8bb",
    "subject": "test: Check for multipath descriptors in listdescriptors",
    "files": 1,
    "add": 40,
    "del": 10
   }
  ],
  "patch_truncated": true
 },
 "patch_id": "fd3d4e1eb0675f25",
 "input_hash": "284399fa899bf244",
 "extracted_at": "2026-11-02T01:09:49+00:00"
}