{
 "number": 36443,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36443",
 "title": "descriptor: Reconstruct a multipath descriptor from its expansion",
 "author": "achow101",
 "author_association": "MEMBER",
 "created_at": "2026-10-05T22:16:19Z",
 "updated_at": "2026-10-23T06:18:43Z",
 "description_hash": "65e807242c618b71",
 "age_days": 27,
 "draft": true,
 "labels": [
  "Needs rebase",
  "Descriptors",
  "CI failed"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "62e70eb6f05ba52082ba03af4664c2691a12f270",
 "head_ref": "multipath-reconstruction",
 "head_repo": "achow101/bitcoin",
 "head_history": [
  {
   "t": "2026-10-05T22:33:23Z",
   "sha": "9422834d210fa5e0e5b6d699a1cef28941aac86a"
  },
  {
   "t": "2026-10-07T18:02:07Z",
   "sha": "62e70eb6f05ba52082ba03af4664c2691a12f270"
  }
 ],
 "additions": 1231,
 "deletions": 709,
 "changed_files": 39,
 "commit_count": 12,
 "size_bucket": "XL",
 "mergeable_state": "dirty",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "w0xlt",
      "url": "https://github.com/bitcoin/bitcoin/pull/36443#issuecomment-6066076341"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "w0xlt": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-10-08T18:06:57Z",
   "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": [
   "fametrano",
   "w0xlt"
  ]
 },
 "signals": {
  "needs_rebase": true,
  "ci_failed": true,
  "mergeable_state": "dirty",
  "last_author_activity": "2026-10-07T18:02:07Z",
  "last_reviewer_activity": "2026-10-23T06:18:43Z",
  "last_reviewer": "fametrano",
  "author_silent_days": 25,
  "waiting_on_author_days": 9,
  "days_since_update": 9
 },
 "refs": {
  "mentioned": [
   36442
  ],
  "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"
   }
  ],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [
   36442
  ],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/script/descriptor.cpp",
  "src/test/descriptor_tests.cpp"
 ],
 "body": "#36442 allows multipath descriptors to be represented in a single `Descriptor` object. `GetMultipathExpansion` returns the multiple descriptors represented by a multipath descriptor. This expansion is reversible - given the expansion in the order of the multipath elements, it is possible to determine what the original multipath descriptor was. This PR adds `ReconstructMultipath` to produce a new `Descriptor` representing the multipath descriptor that produced the expansion.\n\nDepends on #36442",
 "commits": [
  {
   "sha": "2ff1009dbe6655e50f02659e45536a1f8377b26a",
   "date": "2026-10-07T17:36:52Z",
   "message": "util: Make KeyPathElement class with private members"
  },
  {
   "sha": "9353f7986c36d7d2e05a70db36cd085d8b107923",
   "date": "2026-10-07T17:36:52Z",
   "message": "util: Change all key path operations to operate with std::vector<KeyPathElement>"
  },
  {
   "sha": "611b2b9f167e64c07914316d41d55cf02b9451ea",
   "date": "2026-10-07T17:36:52Z",
   "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": "192c38bb11d71e5687cb9770cc95ac778e21f555",
   "date": "2026-10-07T17:36:52Z",
   "message": "util: Store hardened character in KeyPathElement"
  },
  {
   "sha": "2f7d95b319f607d0335cd47e1553b32defe39894",
   "date": "2026-10-07T17:36:52Z",
   "message": "util: Make KeyPathElement handle multipaths\n\nMoves all of the multipath parsing into ParseHDKeypath, and let\nKeyPathElement be a multipath element."
  },
  {
   "sha": "010a986ad3c667608a82fca238c03a0da2f4e230",
   "date": "2026-10-07T17:36:52Z",
   "message": "descriptor: Get the multipath expansion from a multipath descriptor"
  },
  {
   "sha": "fa1b3002b4b48c688303e1c784e5efda83788204",
   "date": "2026-10-07T17:36:52Z",
   "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": "b7980165b2b9801851944c6100ec9fd477085686",
   "date": "2026-10-07T17:49:53Z",
   "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": "5c0b1e77a6cc02e52880b81f45578dfb44a5a0f4",
   "date": "2026-10-07T17:55:20Z",
   "message": "descriptor: Implement multipath reconstruction"
  },
  {
   "sha": "e2d78b3edd3ea2d36a7529e680b7c8a844c011d8",
   "date": "2026-10-07T17:55:20Z",
   "message": "test: Add multipath reconstruction round trip checks"
  },
  {
   "sha": "8aba47abf1a609d6bb15da275f967a1555b5bd5d",
   "date": "2026-10-07T17:55:20Z",
   "message": "fuzz: Check multipath descriptor reconstruction"
  },
  {
   "sha": "62e70eb6f05ba52082ba03af4664c2691a12f270",
   "date": "2026-10-07T17:55:20Z",
   "message": "test: Invalid multipath reconstruction tests"
  }
 ],
 "timeline": [
  {
   "t": "2026-10-05T22:33:23Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "9422834d210fa5e0e5b6d699a1cef28941aac86a",
   "id": "push:9422834d210fa5e0e5b6d699a1cef28941aac86a"
  },
  {
   "t": "2026-10-07T18:02:07Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "62e70eb6f05ba52082ba03af4664c2691a12f270",
   "id": "push:62e70eb6f05ba52082ba03af4664c2691a12f270"
  },
  {
   "t": "2026-10-08T18:06:57Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK",
   "id": "c:6066076341",
   "url": "https://github.com/bitcoin/bitcoin/pull/36443#issuecomment-6066076341",
   "hash": "e8334cc43879b73c",
   "edited": null
  },
  {
   "t": "2026-10-23T06:18:42Z",
   "kind": "review",
   "who": "fametrano",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "62e70eb6f05ba52082ba03af4664c2691a12f270",
   "text": "`ReconstructMultipath` accepts relatives that repeat a path value, and then returns a descriptor that is not their multipath form.\n\nI tested it with a small harness against btclib-wallet, a BIP389 implementation I maintain. Its expansions of 24 multipath descriptors each came back as the original. Thirteen mismatched sets (different keys, function, threshold, timelock, key order, tree shape, origin, wildcard or range, two changed steps in one key, musig steps at both levels) return null, as they should.\n\nRepeated values are not caught. `wpkh(X/0/*)`, `wpkh(X/1/*)`, `wpkh(X/0/*)` gives `wpkh(X/<0;1;0>/*)`, which `Parse` refuses as a duplicate. `wpkh(X/0/*)` twice and then `wpkh(X/1/*)` gives `wpkh(X/<0;1>/*)`: two elements for three inputs. Two identical inputs give the single-path descriptor back. In `BIP32PubkeyProvider::ReconstructMultipath`, each relative is compared with `p` after `AddMultipathIndex` has already extended it. So a repeat is skipped while `p` still holds one index, and appended once it holds more. Nothing checks the indexes already in `p`, or that the step ends with one index per input. The inline suggestions add both checks, with a test for the repeated cases, musig included. I can share the harness and its outputs.",
   "id": "r:5624648052",
   "url": "https://github.com/bitcoin/bitcoin/pull/36443#pullrequestreview-5624648052",
   "hash": "01c7904114c3ce05",
   "edited": "2026-10-23T06:18:43Z"
  },
  {
   "t": "2026-10-23T06:18:43Z",
   "kind": "review_comment",
   "who": "fametrano",
   "assoc": "CONTRIBUTOR",
   "path": "src/script/descriptor.cpp",
   "commit": "62e70eb6f05ba52082ba03af4664c2691a12f270",
   "in_reply_to": null,
   "text": "A relative whose index is already in the step is a repeat, so reject it.\n\n```suggestion\n                    if (std::ranges::find(p.Indexes(), rp.Index(0)) != p.Indexes().end()) return false;\n                    p.AddMultipathIndex(rp.Index(0));\n```",
   "id": "rc:4356713203",
   "url": "https://github.com/bitcoin/bitcoin/pull/36443#discussion_r4356713203",
   "hash": "d6d1bdd28ace633f",
   "edited": null
  },
  {
   "t": "2026-10-23T06:18:43Z",
   "kind": "review_comment",
   "who": "fametrano",
   "assoc": "CONTRIBUTOR",
   "path": "src/script/descriptor.cpp",
   "commit": "62e70eb6f05ba52082ba03af4664c2691a12f270",
   "in_reply_to": null,
   "text": "Same check for the musig path.\n\n```suggestion\n                    if (std::ranges::find(p.Indexes(), rp.Index(0)) != p.Indexes().end()) return false;\n                    p.AddMultipathIndex(rp.Index(0));\n```",
   "id": "rc:4356713234",
   "url": "https://github.com/bitcoin/bitcoin/pull/36443#discussion_r4356713234",
   "hash": "8f0def480f9c7597",
   "edited": null
  },
  {
   "t": "2026-10-23T06:18:43Z",
   "kind": "review_comment",
   "who": "fametrano",
   "assoc": "CONTRIBUTOR",
   "path": "src/script/descriptor.cpp",
   "commit": "62e70eb6f05ba52082ba03af4664c2691a12f270",
   "in_reply_to": null,
   "text": "Fail unless the step ends with one index per input. This also rejects identical inputs.\n\n```suggestion\n        // Each relative must add one multipath index\n        if (!relatives.empty() && multipath_len != relatives.size() + 1) return nullptr;\n\n        return out;\n```",
   "id": "rc:4356713246",
   "url": "https://github.com/bitcoin/bitcoin/pull/36443#discussion_r4356713246",
   "hash": "2c21c9648e69539f",
   "edited": null
  },
  {
   "t": "2026-10-23T06:18:43Z",
   "kind": "review_comment",
   "who": "fametrano",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/descriptor_tests.cpp",
   "commit": "62e70eb6f05ba52082ba03af4664c2691a12f270",
   "in_reply_to": null,
   "text": "Add the repeated-value cases, with and without musig.\n\n```suggestion\n    });\n    CheckFailedMultipathReconstruction({\n        \"wpkh(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)\",\n        \"wpkh(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/1)\",\n        \"wpkh(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)\",\n    });\n    CheckFailedMultipathReconstruction({\n        \"wpkh(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)\",\n        \"wpkh(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)\",\n        \"wpkh(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/1)\",\n    });\n    CheckFailedMultipathReconstruction({\n        \"wpkh(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)\",\n        \"wpkh(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)\",\n    });\n    CheckFailedMultipathReconstruction({\n        \"tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/1)\",\n        \"tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/2)\",\n        \"tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/1)\",\n    });\n    CheckFailedMultipathReconstruction({\n        \"tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/1)\",\n        \"tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/1)\",\n        \"tr(musig(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y)/2)\",\n    });\n```",
   "id": "rc:4356713256",
   "url": "https://github.com/bitcoin/bitcoin/pull/36443#discussion_r4356713256",
   "hash": "2af4376f10c4e987",
   "edited": null
  }
 ],
 "labels_log": [
  {
   "t": "2026-10-05T22:16:23Z",
   "action": "labeled",
   "label": "Descriptors",
   "who": "DrahtBot"
  },
  {
   "t": "2026-10-05T22:36:17Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-10-07T13:58:23Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-10-07T19:19:41Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-10-21T14:01:27Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-10-05T22:19:07Z",
   "kind": "convert_to_draft",
   "who": "achow101"
  }
 ],
 "text_chars": 6860,
 "text_tokens_estimate": 1715,
 "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/imports.cpp",
  "src/wallet/rpc/addresses.cpp",
  "src/wallet/rpc/spend.cpp",
  "src/wallet/rpc/wallet.cpp",
  "src/wallet/scriptpubkeyman.cpp",
  "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/walletdb.cpp",
  "src/wallet/walletutil.cpp",
  "src/wallet/walletutil.h",
  "test/functional/rpc_getdescriptorinfo.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": 4,
   "del": 3
  },
  {
   "path": "src/wallet/imports.cpp",
   "add": 13,
   "del": 12
  },
  {
   "path": "src/wallet/rpc/addresses.cpp",
   "add": 1,
   "del": 1
  },
  {
   "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": 13,
   "del": 11
  },
  {
   "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": 1,
   "del": 2
  },
  {
   "path": "src/wallet/test/walletload_tests.cpp",
   "add": 5,
   "del": 3
  },
  {
   "path": "src/wallet/wallet.cpp",
   "add": 18,
   "del": 14
  },
  {
   "path": "src/wallet/walletdb.cpp",
   "add": 12,
   "del": 10
  },
  {
   "path": "src/wallet/walletutil.cpp",
   "add": 2,
   "del": 2
  },
  {
   "path": "src/wallet/walletutil.h",
   "add": 4,
   "del": 4
  },
  {
   "path": "test/functional/rpc_getdescriptorinfo.py",
   "add": 1,
   "del": 1
  }
 ],
 "test_lines": 490,
 "git": {
  "head": "62e70eb6f05ba52082ba03af4664c2691a12f270",
  "head_matches_backup": true,
  "base": "9dfde64cc3262329051fd05fffe40eecc786a99f",
  "patch_id": "9c725704b4b44a49",
  "commits": [
   {
    "sha": "2ff1009dbe",
    "subject": "util: Make KeyPathElement class with private members",
    "files": 2,
    "add": 11,
    "del": 5
   },
   {
    "sha": "9353f7986c",
    "subject": "util: Change all key path operations to operate with std::vector<KeyPathElement>",
    "files": 15,
    "add": 183,
    "del": 138
   },
   {
    "sha": "611b2b9f16",
    "subject": "util: Make KeyPath a subclass of std::vector and not only a typedef",
    "files": 5,
    "add": 16,
    "del": 12
   },
   {
    "sha": "192c38bb11",
    "subject": "util: Store hardened character in KeyPathElement",
    "files": 7,
    "add": 99,
    "del": 71
   },
   {
    "sha": "2f7d95b319",
    "subject": "util: Make KeyPathElement handle multipaths",
    "files": 3,
    "add": 171,
    "del": 75
   },
   {
    "sha": "010a986ad3",
    "subject": "descriptor: Get the multipath expansion from a multipath descriptor",
    "files": 5,
    "add": 81,
    "del": 1
   },
   {
    "sha": "fa1b3002b4",
    "subject": "descriptor: Produce multipath descriptor in ParseScript",
    "files": 4,
    "add": 181,
    "del": 343
   },
   {
    "sha": "b7980165b2",
    "subject": "descriptor: Return multipath descriptor from Parse",
    "files": 26,
    "add": 147,
    "del": 151
   },
   {
    "sha": "5c0b1e77a6",
    "subject": "descriptor: Implement multipath reconstruction",
    "files": 4,
    "add": 260,
    "del": 0
   },
   {
    "sha": "e2d78b3edd",
    "subject": "test: Add multipath reconstruction round trip checks",
    "files": 1,
    "add": 54,
    "del": 0
   },
   {
    "sha": "8aba47abf1",
    "subject": "fuzz: Check multipath descriptor reconstruction",
    "files": 1,
    "add": 20,
    "del": 2
   },
   {
    "sha": "62e70eb6f0",
    "subject": "test: Invalid multipath reconstruction tests",
    "files": 2,
    "add": 98,
    "del": 1
   }
  ],
  "patch_truncated": true
 },
 "patch_id": "9c725704b4b44a49",
 "input_hash": "18c22dd820a88898",
 "extracted_at": "2026-11-02T01:09:49+00:00"
}