{
 "number": 36420,
 "input_hash": "fd7e61eae58e9f8e",
 "model": "ledger",
 "prompt_hash": null,
 "created": "2026-11-02T01:11:18+00:00",
 "result": {
  "summary": "Compares public keys as 32-byte x-only public keys instead of 33-byte compressed keys during Miniscript duplicate-key checks in Tapscript descriptors. In a Tapscript context, keys sharing an x-coordinate but differing in parity byte are now detected as identical, causing DuplicateKeyCheck() to fail and mark the descriptor as not sane. Segwit v0 descriptors continue to distinguish full 33-byte keys.",
  "problem": "When a tr() Miniscript descriptor contains keys that differ only in parity prefix (such as pk(02X) and pk(03X)), KeyParser::KeyCompare previously treated them as distinct 33-byte keys and passed sanity checks. Because Tapscript drops key parity on-chain, both branches serialize to identical scripts, violating BIP-379 invariants against duplicate keys and allowing third parties to malleate transactions by switching execution branches without possessing private keys.",
  "card": "This pull request updates Tapscript Miniscript duplicate-key validation to compare public keys as 32-byte x-only keys rather than full 33-byte compressed keys. Previously, tr() descriptors containing keys that differed only by parity passed sanity checks even though they serialize to identical scripts on-chain. This violated BIP-379 duplicate key guarantees and permitted third-party transaction malleability through branch flipping. P2WSH descriptor parsing remains unaffected and continues to distinguish 33-byte compressed keys.",
  "evidence": [
   "Commit 0956ace45d and PR description cite BIP-379: 'BIP-379 malleability analysis relies on the invariant that no public key is repeated. Because one signature satisfies both branches, a third party can malleate transactions by flipping the branch selector without possessing the private key.'",
   "Linked issue #36414 demonstrates that tr() descriptors previously marked scripts with identical x-coordinates and different parities as sane."
  ],
  "scope_notes": "",
  "changed_since_previous": "The implementation now provides complete diffs and unit test additions verifying both tr() descriptor rejection and continued wsh() parsing of distinct 33-byte compressed keys.",
  "thread": {
   "last_event_at": "2026-11-01T00:57:22Z",
   "events": 9,
   "head_sha": "28121591bb60e62d46f566c4775776d5cc37d9ec",
   "schema_floor": 2,
   "schema": 2
  },
  "waiting_on": [
   {
    "at": "2026-11-01",
    "id": "c:6551460341",
    "on": "reviewer",
    "url": "https://github.com/bitcoin/bitcoin/pull/36420#issuecomment-6551460341",
    "what": "re-review or confirmation of clean rebase"
   }
  ],
  "risk": {
   "level": "low",
   "touches": [
    "wallet",
    "tests"
   ],
   "why": "A mistake in descriptor key comparison could falsely reject valid descriptors or fail to detect duplicate keys, impacting descriptor-based wallet imports and script sanity checks."
  },
  "discussion": {
   "author_status": "last statement processed 2026-11-01",
   "open_concerns": [],
   "resolved_concerns": [
    "fametrano: descriptor_tests fails on the multi_a check because multi_a directly in tr() is not Miniscript and has no duplicate-key check (accepted by fametrano 2026-10-03)",
    "fametrano: Drop the failing multi_a test check from descriptor_tests.cpp (accepted by fametrano 2026-10-03)",
    "fametrano: Add a test case with the second key as a WIF whose public key is 03X (accepted by fametrano 2026-10-03)"
   ]
  },
  "reviewability": {
   "state": "Ready",
   "label": "Ready",
   "reason": "Nothing known that would invalidate a review now."
  },
  "agreement": {
   "state": "Strong",
   "summary": "Strong: substantive support, no open objection (fametrano, fametrano)",
   "reason": "Author rebased cleanly on master (c:6551460341).",
   "evidence": [
    "fametrano: ACK \u2014 descriptor_tests and miniscript_tests pass, and both new tr() checks fail without the descriptor.cpp change.",
    "fametrano: ACK \u2014 Only the WIF test case was added since a1c65a77. descriptor_tests and miniscript_tests pass; without the descriptor.cpp change, the two tr() cases and the WIF case fail, the WIF one because its privat"
   ],
   "objections": [
    {
     "reviewer": "fametrano",
     "association": "NONE",
     "type": "objection",
     "kind": "correctness",
     "harm": "descriptor_tests fails on the multi_a check because multi_a directly in tr() is not Miniscript and has no duplicate-key check",
     "request": "",
     "question": "",
     "text": "descriptor_tests fails on the multi_a check because multi_a directly in tr() is not Miniscript and has no duplicate-key check",
     "clears_with": "change",
     "blocking": true,
     "status": "accepted",
     "status_note": "author dropped the test and reviewer ACKed",
     "cleared": true,
     "author_replied": true,
     "others_replied": false,
     "fix_pushed": true,
     "replies": [
      "c:5966355751"
     ],
     "evidence": "2026-10-02: 'descriptor_tests fails on the multi_a check because that descriptor parses: a multi_a directly in tr() is not Miniscript and has no duplicate-key check'",
     "url": "https://github.com/bitcoin/bitcoin/pull/36420#pullrequestreview-5394296896",
     "id": "r:5394296896",
     "at": "2026-10-02",
     "status_at": "2026-10-03",
     "status_by": "fametrano",
     "resolution_evidence": "2026-10-03: fametrano: 'ACK a1c65a776c252ce253972d1712936f315cbc5abc'",
     "pin": null,
     "from_schema": 2
    },
    {
     "reviewer": "fametrano",
     "association": "NONE",
     "type": "suggestion",
     "kind": "correctness",
     "harm": "",
     "request": "Drop the failing multi_a test check from descriptor_tests.cpp",
     "question": "",
     "text": "Drop the failing multi_a test check from descriptor_tests.cpp",
     "clears_with": "change",
     "blocking": true,
     "status": "accepted",
     "status_note": "author dropped the test and reviewer ACKed",
     "cleared": true,
     "author_replied": true,
     "others_replied": false,
     "fix_pushed": true,
     "replies": [
      "c:5966355751"
     ],
     "evidence": "2026-10-02: 'This descriptor parses, so this check fails'",
     "url": "https://github.com/bitcoin/bitcoin/pull/36420#discussion_r4167758786",
     "id": "rc:4167758786",
     "at": "2026-10-02",
     "status_at": "2026-10-03",
     "status_by": "fametrano",
     "resolution_evidence": "2026-10-03: fametrano: 'ACK a1c65a776c252ce253972d1712936f315cbc5abc'",
     "pin": null,
     "from_schema": 2
    },
    {
     "reviewer": "fametrano",
     "association": "NONE",
     "type": "suggestion",
     "kind": "correctness",
     "harm": "",
     "request": "Add a test case with the second key as a WIF whose public key is 03X",
     "question": "",
     "text": "Add a test case with the second key as a WIF whose public key is 03X",
     "clears_with": "answer",
     "blocking": false,
     "status": "accepted",
     "status_note": "author added the test case and reviewer reACKed",
     "cleared": true,
     "author_replied": true,
     "others_replied": false,
     "fix_pushed": true,
     "replies": [
      "c:5969196350",
      "rc:4173179191"
     ],
     "evidence": "2026-10-02: 'Optional: the same case with the second key as a WIF whose public key is `03X`.'",
     "url": "https://github.com/bitcoin/bitcoin/pull/36420#discussion_r4167758806",
     "id": "rc:4167758806",
     "at": "2026-10-02",
     "status_at": "2026-10-03",
     "status_by": "fametrano",
     "resolution_evidence": "2026-10-03: fametrano: 'reACK 0956ace45dac99bc11256f4db1ca59f53016af6b'",
     "pin": null,
     "from_schema": 2
    }
   ],
   "support": [
    {
     "reviewer": "fametrano",
     "reason": "descriptor_tests and miniscript_tests pass, and both new tr() checks fail without the descriptor.cpp change.",
     "substantive": true,
     "verdict": "ACK",
     "id": "c:5968773857",
     "url": "https://github.com/bitcoin/bitcoin/pull/36420#issuecomment-5968773857",
     "at": "2026-10-03",
     "association": "NONE",
     "evidence": "both",
     "areas": [
      "descriptor.cpp",
      "descriptor_tests",
      "miniscript_tests",
      "tr()"
     ]
    },
    {
     "reviewer": "fametrano",
     "reason": "Only the WIF test case was added since a1c65a77. descriptor_tests and miniscript_tests pass; without the descriptor.cpp change, the two tr() cases and the WIF case fail, the WIF one because its privat",
     "substantive": true,
     "verdict": "ACK",
     "id": "c:5972208982",
     "url": "https://github.com/bitcoin/bitcoin/pull/36420#issuecomment-5972208982",
     "at": "2026-10-03",
     "association": "NONE",
     "evidence": "both",
     "areas": [
      "descriptor.cpp",
      "descriptor_tests",
      "miniscript_tests",
      "tr()"
     ]
    }
   ],
   "participants": [
    {
     "login": "fametrano",
     "stance": "support",
     "note": "tested and reACKed after failing multi_a check was dropped and suggested WIF test case was added",
     "association": "NONE",
     "comments": 5,
     "first": "2026-10-02",
     "last": "2026-10-03"
    }
   ],
   "derivation": "substantive support, no open objection (fametrano, fametrano)",
   "model_state": null,
   "corrections": [],
   "notes": "Author rebased cleanly on master (c:6551460341)."
  },
  "dependencies": {
   "depends_on": [],
   "enables": []
  },
  "categories": [
   {
    "name": "wallet",
    "member": true,
    "evidence": "Touches descriptor management logic in src/script/descriptor.cpp, which governs descriptor validation and parsing used by descriptor wallets.",
    "band": "P2",
    "reason_tag": "bug fix",
    "score": 0.6,
    "factors": {
     "bug_severity": 2,
     "leverage": 0,
     "performance": 0,
     "security_stability": 1,
     "user_value": 1
    },
    "rationale": "Fixes a validation bug where Tapscript Miniscript descriptors with keys sharing an x-coordinate pass sanity checks, breaking BIP-379 non-malleability guarantees. The PR description notes that 'BIP-379 malleability analysis relies on the invariant that no public key is repeated' and that failing to detect identical x-only keys allows 'a third party [to] malleate transactions by flipping the branch selector without possessing the private key'."
   }
  ],
  "confidence": "high",
  "uncertainties": [],
  "needs": []
 },
 "cost_usd": 0.0,
 "source": {
  "record": "2026-11-02T01:11:18+00:00",
  "code": "2026-10-04T01:20:39+00:00",
  "judgments": 1
 }
}