{
 "number": 35358,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/35358",
 "title": "external signer: verify PSBT is reliable after signing it",
 "author": "brunoerg",
 "author_association": "MEMBER",
 "created_at": "2026-05-22T12:31:49Z",
 "updated_at": "2026-09-15T16:36:34Z",
 "age_days": 118,
 "draft": false,
 "labels": [],
 "milestone": null,
 "base": "master",
 "head_sha": "e5ca77ed5049ec273bb11a441ca86d9389507f66",
 "head_ref": "2026-04-external-signer",
 "head_repo": "brunoerg/bitcoin",
 "head_history": [
  {
   "t": "2026-05-22T13:11:00Z",
   "sha": "e5ca77ed5049ec273bb11a441ca86d9389507f66"
  }
 ],
 "additions": 264,
 "deletions": 16,
 "changed_files": 4,
 "commit_count": 1,
 "size_bucket": "M",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {},
   "conflicts": [
    {
     "number": 36114,
     "title": "wallet: harden external signer psbt processing, revamp mock",
     "author": "Sjors"
    },
    {
     "number": 33112,
     "title": "wallet: relax external_signer flag constraints",
     "author": "Sjors"
    }
   ]
  }
 },
 "acks_parsed": {},
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 0,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "Sjors",
   "achow101",
   "junbyjun1238"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-05-22T13:11:00Z",
  "last_reviewer_activity": "2026-08-28T11:28:00Z",
  "last_reviewer": "Sjors",
  "author_silent_days": 118,
  "waiting_on_author_days": 20,
  "days_since_update": 1
 },
 "refs": {
  "mentioned": [
   36114
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 36114,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "wallet: harden external signer psbt processing, revamp mock"
   }
  ],
  "conflicts": [
   36114,
   33112
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [],
 "body": "Seeking Concept ACK\n\nA compromised or \"rogue\" external signer could return a PSBT with silently altered outputs or use unsafe sighash types that do not commit to all outputs (perhaps unlikely, but a buggy signer could also change it). This PR adds a post-signing validation step in `External::SignTransaction` that compares the signed PSBT returned by the external signer against the original one before accepting it. Without these checks, it is not super friendly (e.g. using RPC) to manually verify the same.\n\nNote: the added tests were partially vibe-coded.",
 "commits": [
  {
   "sha": "e5ca77ed5049ec273bb11a441ca86d9389507f66",
   "date": "2026-05-22T13:10:34Z",
   "message": "external-signer: verify PSBT is reliable after signing it"
  }
 ],
 "timeline": [
  {
   "t": "2026-05-22T13:11:00Z",
   "kind": "force_push",
   "who": "brunoerg",
   "commit": "e5ca77ed5049ec273bb11a441ca86d9389507f66"
  },
  {
   "t": "2026-05-23T08:14:03Z",
   "kind": "comment",
   "who": "junbyjun1238",
   "assoc": "CONTRIBUTOR",
   "text": "Should `SIGHASH_ALL | SIGHASH_ANYONECANPAY` really be considered safe here?\n\nThe PR protects outputs, and `ANYONECANPAY|ALL` does commit to all\noutputs. But `ANYONECANPAY` also means the signed input can be included\nin a larger transaction with additional inputs that the wallet did not\nauthorize. If the expected sighash is `SIGHASH_ALL` (or `SIGHASH_DEFAULT`\nfor taproot), a rogue signer silently downgrading to `ANYONECANPAY|ALL`\nwould pass this check while producing a signature with strictly weaker\ncommitment.\n\nIs allowing `ANYONECANPAY` an intentional design choice here, or should\nthe check require the returned sighash to match the PSBT's per-input\nsighash field when present, and fall back to the default policy otherwise?"
  },
  {
   "t": "2026-05-27T21:45:04Z",
   "kind": "comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nYes.\n\nIt is not a problem for us if someone else adds more inputs. We only care that the inputs we want spend and the outputs we want to send to are all in the transaction. If someone else adds more inputs, the inputs we want to spend are still there, and the outputs we want to create are still committed to. There is no effect to the wallet, hence it is safe.\n\n***\n\nConcept OK I guess\n\nI think we generally assume an external signer to be more trusted/secure, so we don't really do checks on their output. An external signer can always leak private keys or steal the user's coins and there isn't much we can do about it."
  },
  {
   "t": "2026-08-28T11:28:00Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "I absorbed this into #36114, which revamps the external signer mock in a way that makes this stuff easier to test."
  }
 ],
 "labels_log": [
  {
   "t": "2026-05-22T13:11:46Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-22T15:40:45Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 2170,
 "text_tokens_estimate": 542,
 "changed_paths": [
  "src/external_signer.cpp",
  "test/functional/mocks/signer.py",
  "test/functional/rpc_signer.py",
  "test/functional/wallet_signer.py"
 ],
 "files": [
  {
   "path": "src/external_signer.cpp",
   "add": 57,
   "del": 0
  },
  {
   "path": "test/functional/mocks/signer.py",
   "add": 78,
   "del": 5
  },
  {
   "path": "test/functional/rpc_signer.py",
   "add": 72,
   "del": 0
  },
  {
   "path": "test/functional/wallet_signer.py",
   "add": 57,
   "del": 11
  }
 ],
 "test_lines": 223,
 "git": {
  "head": "e5ca77ed5049ec273bb11a441ca86d9389507f66",
  "head_matches_backup": true,
  "base": "b9f0040caf45355c4621e792df09fffabf23495e",
  "commits": [
   {
    "sha": "e5ca77ed50",
    "subject": "external-signer: verify PSBT is reliable after signing it",
    "files": 4,
    "add": 264,
    "del": 16
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "baeb32308b623813",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}