{
 "number": 36628,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36628",
 "title": "net: reimplement private broadcast as jobs isolated from the node",
 "author": "instagibbs",
 "author_association": "MEMBER",
 "created_at": "2026-10-23T16:45:24Z",
 "updated_at": "2026-11-01T15:58:23Z",
 "description_hash": "50a5a16320c4aa98",
 "age_days": 9,
 "draft": false,
 "labels": [
  "P2P",
  "Needs rebase"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "8e34f760586c927883bdefae6eac1dce4aea6e3d",
 "head_ref": "2026-10-pb_reimpl",
 "head_repo": "instagibbs/bitcoin",
 "head_history": [
  {
   "t": "2026-10-23T16:54:16Z",
   "sha": "1e77d8636551b6793f8e22f90e294f236a141ca7"
  },
  {
   "t": "2026-10-23T17:21:11Z",
   "sha": "d55fafe253aefff18b784df066f6a977fa2a8e76"
  },
  {
   "t": "2026-10-23T19:00:06Z",
   "sha": "03fbc468cc76163d902deb44c2e8f2b6cea0238c"
  },
  {
   "t": "2026-10-23T20:59:05Z",
   "sha": "a63525b3908f71041910e5ab985965d25a0117d4"
  },
  {
   "t": "2026-10-24T00:41:32Z",
   "sha": "19f9cd7c4d3a5e7a0d07499c60c267c262769466"
  },
  {
   "t": "2026-10-26T03:00:02Z",
   "sha": "22590cf644c2362e0da592ea7f6d3b4f91183a27"
  },
  {
   "t": "2026-10-26T17:03:39Z",
   "sha": "8816dd5c46daa6babe12d6b3343c7cfe81f85536"
  },
  {
   "t": "2026-10-26T17:39:42Z",
   "sha": "26ef29f20d1b02c6cc138c9afcf25d14ce955418"
  },
  {
   "t": "2026-10-26T23:29:59Z",
   "sha": "f547e5eb576f0b86d45118e5425bce27be066266"
  },
  {
   "t": "2026-10-27T16:02:31Z",
   "sha": "2c3d714c1a539e69670e8d0b10fd3e13f20ac1fb"
  },
  {
   "t": "2026-10-27T16:54:42Z",
   "sha": "49a052a9f8a31768ba94b233f4311a6cdc53a894"
  },
  {
   "t": "2026-10-27T18:35:18Z",
   "sha": "ada88b0c0ad07a054ae621abe9ae436e3165c9c1"
  },
  {
   "t": "2026-10-27T21:44:30Z",
   "sha": "00537527384d30d96763adae678e5c500f44885f"
  },
  {
   "t": "2026-10-28T16:15:35Z",
   "sha": "8e34f760586c927883bdefae6eac1dce4aea6e3d"
  }
 ],
 "additions": 6341,
 "deletions": 3046,
 "changed_files": 78,
 "commit_count": 16,
 "size_bucket": "XL",
 "mergeable_state": "dirty",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "darosior",
      "url": "https://github.com/bitcoin/bitcoin/pull/36628#issuecomment-6445520943"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "darosior": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-10-27T15:05:45Z",
   "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": [
   "ajtowns",
   "andrewtoth",
   "darosior",
   "fametrano",
   "spacexm406-web",
   "vasild"
  ]
 },
 "signals": {
  "needs_rebase": true,
  "ci_failed": false,
  "mergeable_state": "dirty",
  "last_author_activity": "2026-10-29T18:21:02Z",
  "last_reviewer_activity": "2026-11-01T15:58:23Z",
  "last_reviewer": "ajtowns",
  "author_silent_days": 3,
  "waiting_on_author_days": 0,
  "days_since_update": 0
 },
 "refs": {
  "mentioned": [
   35032,
   35410,
   35858,
   36277,
   36312,
   36608
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 36608,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "Reimplement private broadcast as node state-less jobs + package relay support"
   },
   {
    "number": 35032,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-04-09",
    "title": "net_processing: don't modify addrman for private broadcast connections"
   },
   {
    "number": 35410,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-06-04",
    "title": "net: use the proxy if overriden when doing v2->v1 reconnections"
   },
   {
    "number": 35858,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "doc: Add privacy.md consolidating network privacy recommendations"
   },
   {
    "number": 36277,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-10-06",
    "title": "net: always complete all initial private broadcast connections"
   },
   {
    "number": 36312,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-09-25",
    "title": "net: don't discourage private broadcast peers"
   }
  ],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "doc/design/private-broadcast.md",
  "src/privbcast/params.h"
 ],
 "body": "Split out of #36608, the overall private broadcast rework to support ease of review for like-for-like feature.\n\nDifferences from #36608 :\n1. No tool support or prep refactors for the tool\n2. No package relay\n3. Slimmed down test profile focusing on protecting aginst IP address linkage and not crashing the node on adversarial inputs (which is a leak in itself)\n\nWith `-privatebroadcast`, `sendrawtransaction` queues a job instead of handing the transaction to\nthe connection manager. Each job runs on its own thread, shares no state with the node, and\nreaches the network only through the Tor proxy: it resolves the chain's DNS seeds, then announces\nover BIP324 to recipients on a schedule drawn up front, each connection with fresh isolation\ncredentials. Nothing a recipient does, and nothing the node sees of the transaction coming back,\nchanges the plan, and the transaction enters the node's mempool only when it returns from the\nnetwork. The old private broadcast connections and relay are removed.\n\nThe invariants are specified in `doc/design/private-broadcast.md`.\n\nThe rest of the tests are in a follow-up to be considered later:\nhttps://github.com/instagibbs/bitcoin/tree/2026-10-pb_reimpl_followup",
 "commits": [
  {
   "sha": "88f960fd83784e918b4b2c47dc06e36eeb1d31f3",
   "date": "2026-10-28T16:14:16Z",
   "message": "doc: add the private broadcast specification\n\nThis is the design the following commits implement: what a private\nbroadcast job guarantees, the values peers and observers see, and the\nnode's interface. Later commits cite its items by number."
  },
  {
   "sha": "5905856abac8e972c953b397a3e5914d3c271772",
   "date": "2026-10-28T16:14:16Z",
   "message": "privbcast: add the parameters and the schedule\n\nStart src/privbcast, the code that will run the node's private\nbroadcast jobs, built into bitcoin_node. params.h holds the values of\nthe specification. DrawPlan draws a job's plan from the job's own\nrandomness: the schedule, the order of the DNS seeds and the seed of\nthe order in which each DNS seed hands out its candidates.\nMIN_DNS_SEEDS is the floor R3 sets for the mainnet seed list, and a\nunit test holds the list to it, so a release that drops seeds below\nwhat fills a job fails the test rather than leaving opportunities\nempty.\n\nImplements C1 and R3; the values of Parameters."
  },
  {
   "sha": "f2dc2e2997173e0d152adbd1c6adff744814b199",
   "date": "2026-10-28T16:14:16Z",
   "message": "privbcast: add candidate assignment\n\nAssign fixes every opportunity's candidate once discovery ends, from\nthe plan and the discovery result alone. An endpoint that several\nseeds returned is credited to the seed that comes first in the plan's\nseed order, through the lowest query set that returned it. Each seed\nhands out its candidates in an order drawn at random by a generator of\nits own, seeded from the plan and the seed's place in that order. Slot\nj's first attempt comes from query set j mod 4 whenever a seed it may\ndraw from has one, so a single exit supplies at most two first\nattempts. Neither which seed answers first nor the order of a seed's\nanswers changes the assignment.\n\nImplements C2, R2, R4, R5a, R5b and R5c."
  },
  {
   "sha": "7b03d4229acca862c42a94feccaaccfb5ce99e45",
   "date": "2026-10-28T16:14:16Z",
   "message": "privbcast: add a byte-driven SOCKS5 client\n\nA job reaches recipients and resolves DNS seed names through the proxy,\non streams its event loop drives without blocking. Socks5Client holds\nno socket: it offers the bytes to write, consumes the proxy's replies,\nand leaves bytes past the final reply to the P2P transport. It offers\nonly username and password authentication, with the credentials it is\ngiven or fresh ones, and fails before sending anything if they are\noutside RFC 1929's lengths or the username would select Tor's extended\nparameters. It sends every destination as a name, numeric addresses\nincluded, and resolves nothing: the node's own connections do the\nsame, and Tor's SafeSocks refuses a request that carries only an\naddress. Its errors name the proxy's reply with netbase's\nSocks5ErrorString, now exported.\n\nImplements B2 and the Proxy interface section."
  },
  {
   "sha": "5d5707f05f88525aa8610a40ccc4d3a0378ec4c0",
   "date": "2026-10-28T16:14:16Z",
   "message": "privbcast: add the attempt state machine\n\nAn attempt is one connection to a recipient once the proxy connected\nit: a BIP324 session as initiator, the handshake with the job's\nVERSION, one INV, the transaction served at most once, and the PING.\nIt holds no socket and reads no clock: the job's loop passes the bytes\nread and the time; each call records what happened (a completed write,\nthe connection, bytes received) and then enforces the attempt's\ndeadlines, so that nothing is acted on at or after one.\n\nThe attempt draws its VERSION and PING nonces from the random context\nit is given, and takes its BIP324 key, ellswift entropy and garbage\nfrom a key source. Without one, as in production, V2Transport draws\nthem from the strong RNG. The fuzz target injects a source\nthat draws from its seeded context, so that an attempt's bytes are a\nfunction of that context and of what the peer sent.\n\nImplements E1-E8, A2, B4, D3 and the attempt part of D2."
  },
  {
   "sha": "cac5ffe15f17e0db70169ebae6b948c4eaf5c4da",
   "date": "2026-10-28T16:14:16Z",
   "message": "privbcast: add proxy streams and discovery\n\nA job reaches the network only through streams to the SOCKS5 proxy,\nwhich its event loop drives without blocking. ProxyStream connects to\nthe proxy by address or unix socket path, resolving nothing, and runs\nthe exchange of its Socks5Client; each stage, the connect and each\nstep of the exchange, times out on the steady clock. Once the proxy\nhas connected the stream, its socket carries the destination's bytes.\n\nDiscovery launches every RESOLVE query at job start, each on a stream\nof its own, in four query sets: each set has credentials of its own\nand one query of every seed, so Tor builds four circuits rather than\none per query, and each seed is still asked through four exits.\nDiscovery ends when every query has ended or when the discovery window\ndoes, whatever is still pending. It keeps the answers that are public\nroutable IPv4 or IPv6 addresses, with the chain's default port, and\ncounts the others; its result gives each seed's answers by the query\nset they came through, whatever order they arrived in. An exception\nthrown while it acts on one query ends only that query, as a failed\nstream would.\n\nImplements B1, B2, C3, D1, R1, R2 and C7 for the queries."
  },
  {
   "sha": "03435a45ef7f41819ab79fc35524fe8524dc0717",
   "date": "2026-10-28T16:14:16Z",
   "message": "privbcast: add the job\n\nA job is one event loop on its caller's thread. Step() runs the\ndeadlines and the dials that are due, waits on every open stream until\nthe next deadline, and does the I/O; Run() steps to the end. The plan\nis drawn when the job is made, before any socket exists, and discovery\nstarts with the first step. When discovery ends the assignment is\nfixed, and each slot dials its opportunities at their scheduled times,\none attempt at a time, until one reaches the announcement point or\nnone is left; a job whose discovery found no candidate ends there,\nfailed with \"no candidates\". The slots share only the loop, so a stalled stream holds\nup no other slot, and the cancel flag ends everything at the next step.\n\nAn exception thrown while the loop acts on one attempt ends only that\nattempt, as a failed stream would. One thrown anywhere else in a step\nfails the job: it stops as a cancelled job does, and its report gives\nthe error.\n\nThe report mirrors Interface/Report, every time an offset from job\nstart on the plan clock. Snapshot() gives the progress the node is to\nshow, from any thread.\n\nImplements A1, A3, A4, C1-C8, D1, D2, H1-H3 and the report interface."
  },
  {
   "sha": "91144bdd5a9e8c8b674615b52913706c4e812d1f",
   "date": "2026-10-28T16:14:16Z",
   "message": "test: extend the harness for private broadcast jobs\n\nThe SOCKS5 server gains Tor's RESOLVE command, reply delays for a slow\nor stalled proxy, a mode that offers only unauthenticated access and a\nmode that behaves as an ordinary non-Tor proxy. A client that hangs up\nbefore its greeting, as a cancelled job's streams can, and a connection\nthat Windows reports as aborted count as closed, not as errors. The P2P\nnetwork thread judges a failed protocol by its own reconnect flag, so\nthat several listeners can share one loop. test_framework/privbcast.py\nholds the release's parameters, a scripted recipient, and the checks of\na job's wire profile, of fresh keys and nonces in each attempt, and of\nits schedule, report summary and proxy credentials.\n\nPrepares the functional tests of the node's jobs."
  },
  {
   "sha": "4f78532ddfdd07af94b139ed4c98b4b6eacd96bb",
   "date": "2026-10-28T16:14:16Z",
   "message": "node: add the private broadcast job queue\n\nThe node runs each private broadcast as a privbcast::Job on a thread of\nits own. PrivbcastQueue queues the submitted transactions, starts and\ncancels the jobs from one scheduler thread, and keeps in memory the\nrecords that getprivatebroadcastinfo and abortprivatebroadcast are to\nshow. It reaches the node only through callbacks for networking, the\nonion proxy and the mempool, and takes the job runner as a parameter,\nso that tests can drive it with stub jobs and mocked clocks. A job that\nfailed, or whose runner threw, ends aborted with the error in its\nrecord. Whether a cancel cut a job short is read from its report, so\none that lands after the job ended leaves it done. At shutdown the\ndefault log gives the number of jobs stopped, and nothing more.\n\nThe scheduler's start rule is a pure function, StartPolicy::Decide(),\nwhich each pass of the scheduler applies.\n\nImplements N3-N7, N9, N10, the node's D2 and D3, U3. Not yet wired:\nthe next commit switches -privatebroadcast to it."
  },
  {
   "sha": "0742c8a8825e08c0cebcf8fcb89ee0c6686ada30",
   "date": "2026-10-28T16:14:16Z",
   "message": "node: run private broadcast as jobs\n\nWith -privatebroadcast, sendrawtransaction now queues a job on the\nnode's PrivbcastQueue instead of handing the transaction to the\nconnection manager, getprivatebroadcastinfo lists the jobs with their\nprogress and reports, and abortprivatebroadcast aborts them. Startup\nrequires onion to be able to become reachable, refuses the regtest\nseed and time divisor overrides on other chains, on signet\n-signetseednode and -signetchallenge, and off regtest a non-zero\n-mocktime, requires -privatebroadcastseed on regtest, which has no DNS\nseeds, and refuses to start rather than trim connections when the\njobs' file descriptors are not available. -connect and -proxyrandomize\nno longer matter.\n\n-privatebroadcasttimedivisor, regtest only, divides every duration of\na job's plan, so that functional tests can run jobs in real time.\n\nThe old private broadcast connections and relay stay in the tree, no\nlonger fed, until the commits that remove them.\n\nImplements N1, N2, N8, N10, N11, U1, U2 and Interface/Node; replaces\nthe old implementation's tests with the specification's."
  },
  {
   "sha": "82cc75b32693a0a318f2123a967678d5c080b175",
   "date": "2026-10-28T16:14:16Z",
   "message": "test: run private broadcast jobs end to end through the node\n\np2p_private_broadcast.py drives jobs on a mocked clock, which moves a\njob's schedule but not the steady clock that times its proxy exchanges\nand connections. These tests run jobs in real time instead, shortened\nby -privatebroadcasttimedivisor: a recipient that floods a job is cut\noff at the receive cap, and a proxy that refuses authentication or is\nnot Tor gets nothing to carry.\n\ntest_framework/privbcast.py gains the harness: it restarts the node\nbehind each scenario's SOCKS5 server with its seeds and time divisor,\nand reads the job's report from getprivatebroadcastinfo."
  },
  {
   "sha": "e452ebc06e5a12ad780d303a7a1114bd7ec04261",
   "date": "2026-10-28T16:14:16Z",
   "message": "net: remove the old private broadcast relay\n\nSince -privatebroadcast runs jobs, nothing hands PeerManager a\ntransaction for private broadcast any more. Remove its queue\n(src/private_broadcast.{h,cpp}) with the methods, the retry task and\nthe private_broadcast option that served it, and its handling of\nprivate broadcast connections: the fixed VERSION, the INV after\nVERACK, the GETDATA and PONG handling, the incoming message filter,\nthe lifetime limit and the exemption from discouragement. The unit and\nfuzz tests of that code go with it, including the net_tests case for\nthe addrman exception and the denialofservice_tests cases for the\ndiscouragement exemption.\n\nDelete-only: besides whole lines, conditions and arguments are cut out\nof lines, and the lines of a removed branch's alternative move out one\nindentation level. git show -w --word-diff-regex='[A-Za-z0-9_]+|[^[:space:]]'\nshows deletions only, apart from the size of the node array in\ndenialofservice_tests, which goes back from 5 to 3."
  },
  {
   "sha": "4acf24a802d5c89c830bc63be8e837567bdeb766",
   "date": "2026-10-28T16:14:16Z",
   "message": "net: remove the old private broadcast connections\n\nWith the relay gone, nothing asks CConnman for private broadcast\nconnections. Remove the connection type, the thread that opened such\nconnections with its counters and choice of network, and the places\nthat treated them specially: the incoming nonce check, the fresh I2P\nsession, the guard against direct connections, the outbound message\nfilter, the fixed protocol version, the exception in disconnection by\nsubnet and their term in the orphanage's static_assert. The type's\nnames go from getpeerinfo's help, bitcoin-cli -netinfo and the GUI. The\nproxy override of ConnectNode() and OpenNetworkConnection(), which only\nthat thread set, goes too, with its copy in CNode and in the\nreconnection record and the fuzz input for it.\n\nDelete-only: besides whole lines, conditions, arguments and list\nseparators are cut out of lines, what followed a cut argument joins\nits line, and one line moves out an indentation level with the if\naround it. git show -w --word-diff-regex='[A-Za-z0-9_]+|[^[:space:]]'\nshows deletions only."
  },
  {
   "sha": "f8c5e64add6922d0653500bfa26496c72ed1d920",
   "date": "2026-10-28T16:14:16Z",
   "message": "refactor: drop the trivial AdvertisedVersion()\n\nWithout private broadcast connections, a node advertises\nPROTOCOL_VERSION on every connection, and CNode::AdvertisedVersion()\nonly returns it. Use the constant again where the method was called."
  },
  {
   "sha": "4bd50b805eea0aca2ed80f0a81570a707cab65de",
   "date": "2026-10-28T16:14:16Z",
   "message": "doc: describe bitcoind -privatebroadcast\n\nAdd the user guide to private broadcast: which Tor proxy the jobs use,\nhow sendrawtransaction queues them and when they start, what is not\nqueued again, how to follow and abort jobs, what disabling networking\nand shutdown do, which node settings do not apply, what the default\nlog shows, the file descriptors the jobs need, and the limits of what\na job protects."
  },
  {
   "sha": "8e34f760586c927883bdefae6eac1dce4aea6e3d",
   "date": "2026-10-28T16:14:16Z",
   "message": "doc: release notes for private broadcast jobs"
  }
 ],
 "timeline": [
  {
   "t": "2026-10-23T16:54:16Z",
   "kind": "force_push",
   "who": "instagibbs",
   "commit": "1e77d8636551b6793f8e22f90e294f236a141ca7",
   "id": "push:1e77d8636551b6793f8e22f90e294f236a141ca7"
  },
  {
   "t": "2026-10-23T17:21:11Z",
   "kind": "force_push",
   "who": "instagibbs",
   "commit": "d55fafe253aefff18b784df066f6a977fa2a8e76",
   "id": "push:d55fafe253aefff18b784df066f6a977fa2a8e76"
  },
  {
   "t": "2026-10-23T19:00:06Z",
   "kind": "force_push",
   "who": "instagibbs",
   "commit": "03fbc468cc76163d902deb44c2e8f2b6cea0238c",
   "id": "push:03fbc468cc76163d902deb44c2e8f2b6cea0238c"
  },
  {
   "t": "2026-10-23T20:59:05Z",
   "kind": "force_push",
   "who": "instagibbs",
   "commit": "a63525b3908f71041910e5ab985965d25a0117d4",
   "id": "push:a63525b3908f71041910e5ab985965d25a0117d4"
  },
  {
   "t": "2026-10-24T00:41:32Z",
   "kind": "force_push",
   "who": "instagibbs",
   "commit": "19f9cd7c4d3a5e7a0d07499c60c267c262769466",
   "id": "push:19f9cd7c4d3a5e7a0d07499c60c267c262769466"
  },
  {
   "t": "2026-10-25T11:42:56Z",
   "kind": "comment",
   "who": "fametrano",
   "assoc": "NONE",
   "text": "This PR's tests do not check that each attempt gets its own VERSION nonce, PING nonce and BIP324 key (A4 in `doc/design/private-broadcast.md`), so a regression there would pass.\n\nOn 19f9cd7c4d I made each of these values a constant for the whole process, one line in `src/privbcast/attempt.cpp` at a time. All four `p2p_private_broadcast*.py` tests passed on each of the three variants. Constant proxy credentials, by contrast, make `p2p_private_broadcast.py` fail.\n\nYour [follow-up branch](https://github.com/instagibbs/bitcoin/tree/2026-10-pb_reimpl_followup) already checks all three, in `p2p_private_broadcast_job.py`; I ran it on the PING-nonce variant and it fails there. A reused nonce or key lets recipients link a node's broadcasts to each other, the same kind of leak as the IP linkage the kept tests guard against. Could that check move into this PR?",
   "id": "c:6397196009",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#issuecomment-6397196009",
   "hash": "20b49229512328a0",
   "edited": null
  },
  {
   "t": "2026-10-26T00:04:36Z",
   "kind": "comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "text": "@fametrano willing to be convinced to move particular items to this PR, but I'm really focusing on IP address level leaks for the first PR. Preventing cross-attempt or cross-tx fingerprinting is a secondary goal, and hard to achieve arbitrary level of assurances, hence its deferral.\n\nedit: changed my mind and brought it forward",
   "id": "c:6405247174",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#issuecomment-6405247174",
   "hash": "0a6f6f22bf49e64b",
   "edited": "2026-10-26T03:01:02Z"
  },
  {
   "t": "2026-10-26T03:00:02Z",
   "kind": "force_push",
   "who": "instagibbs",
   "commit": "22590cf644c2362e0da592ea7f6d3b4f91183a27",
   "id": "push:22590cf644c2362e0da592ea7f6d3b4f91183a27"
  },
  {
   "t": "2026-10-26T09:50:09Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "MEMBER",
   "text": "I find the following not acceptable:\n\nRelying on DNS seeds\n--------------------\n\nCurrently DNS seeds are used only to bootstrap a new node. That happens once in its lifetime and there is a fallback mechanism if the DNS seeds do not work. We never rely solely only on DNS seeds for something. They are centralized services.\n\n`doc/dnsseed-policy.md` mentions:\n\n[quoted text omitted]\nI do not think that it is reasonable to start relying on a few servers permanently (not just once in a lifetime, but every time a node wants to privately broadcast a transaction). This means that if a few operators decide to switch off (or misbehave) their services then everybody on the network is obstructed from privately broadcasting their transactions. That resembles more a client-server architecture than a decentralized P2P network.\n\nNot broadcasting to Tor or I2P peers\n------------------------------------\n\nOnly broadcasting to IP peers via the Tor network would make the feature too fragile - it would rely on the Tor network and the Tor exit nodes working properly. The Tor network has been down in the past. I think it is important to have a fallback mechanism which currently is the I2P network. Also, right now we do not rely on the Tor exit nodes. We could broadcast a transaction within the Tor network to an `.onion` peer even if the exit nodes are off or misbehaving.\n\nEven moderate adoption of private broadcast in its current form improves the privacy of everybody because everybody could be a recipient. Privacy is improved even for nodes that broadcast-to-all. That would be spoiled if we only send to IP peers. Note that a private broadcast recipient would further broadcast-to-all the newly received transaction in a way that is indistinguishable from them being the transaction originator. So an observer can no longer tell whether a node is the original sender or was a recipient of a private broadcast. For this to work everybody must be a potential private broadcast recipient.",
   "id": "c:6413061893",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#issuecomment-6413061893",
   "hash": "00f670d09334ae6e",
   "edited": null
  },
  {
   "t": "2026-10-26T17:03:39Z",
   "kind": "force_push",
   "who": "instagibbs",
   "commit": "8816dd5c46daa6babe12d6b3343c7cfe81f85536",
   "id": "push:8816dd5c46daa6babe12d6b3343c7cfe81f85536"
  },
  {
   "t": "2026-10-26T17:39:42Z",
   "kind": "force_push",
   "who": "instagibbs",
   "commit": "26ef29f20d1b02c6cc138c9afcf25d14ce955418",
   "id": "push:26ef29f20d1b02c6cc138c9afcf25d14ce955418"
  },
  {
   "t": "2026-10-26T18:50:03Z",
   "kind": "comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nIf the seeds go offline, private broadcast stops; it doesn't leak. If they misbehave, the most they can do is pick recipients that drop the transaction. They can't learn who is sending.\n\nThe alternative of picking Tor/i2p peers from addrman as master does, is not an answer anyone should be happy with.\n\nToday we give attackers significant influence over choices being made by victim. Peers can be influenced, by design, into dialing to peers that are unique fingerprints, with the attacker writing to their addrman.\n\nTor isn't decentralized either, but we accept utility in terms of privacy.\n\n[quoted text omitted]\nCurrently in master, it makes sybiling victims much easier, which results in an IP address leak, which is an unfortunate side-effect of the current architecture. Assuming we adopted something like this PR, then we would have to introduce a \"just in time\" gathering of addresses, which would *still* be going through seeder nodes, or again, reintroducing fundamental leaks via local addrman usage. No free lunch here.\n\n[quoted text omitted]\nThe attacks it resists are quite limited today, and therefore it's sort of in purgatory where we can't make any promises about it. That's quite sad, and completely avoidable.",
   "id": "c:6424580079",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#issuecomment-6424580079",
   "hash": "0661402fd7dc4718",
   "edited": null
  },
  {
   "t": "2026-10-26T23:29:59Z",
   "kind": "force_push",
   "who": "instagibbs",
   "commit": "f547e5eb576f0b86d45118e5425bce27be066266",
   "id": "push:f547e5eb576f0b86d45118e5425bce27be066266"
  },
  {
   "t": "2026-10-27T04:02:17Z",
   "kind": "review_comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "path": "src/privbcast/params.h",
   "commit": "49a052a9f8a31768ba94b233f4311a6cdc53a894",
   "in_reply_to": null,
   "text": "70016 is what current private broadcast versions track, and avoiding varying these things across releases is exactly why we have a special profile.",
   "id": "rc:4384957876",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#discussion_r4384957876",
   "hash": "934570a6c64ff323",
   "edited": "2026-10-27T04:05:44Z"
  },
  {
   "t": "2026-10-27T04:05:44Z",
   "kind": "review",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "f547e5eb576f0b86d45118e5425bce27be066266",
   "text": "The design doc looks AI written; is it meant for human or agentic consumption? Shouldn't it be a BIP, or at least posted to the mailing list for broader discussion on exactly the sort of tradeoffs vasild raises [above](https://github.com/bitcoin/bitcoin/pull/36628#issuecomment-6413061893)?\n\n+6k lines (+3.5k -1k under src excluding test) seems like a lot. Presumably also AI written with light human review?\n\nIf the idea is to separate this from the node, it seems like having a separate binary interacting lightly with the node might be a better structure. Something like: a \"bitcoin-privbcast\" binary, connects only over i2p/tor, keeps it own addrman independent from the node, initially populated from dns seeds, perhaps updated in some way (again, independent of the node), bitcoind wallet sends private broadcast txs via bitcoin-privbcast's stdin, and then just monitors for them to appear in the mempool (and sends a cancel message to privbcast's stdin when that occurs), and bitcoin-privbcast rate limits, retries, expires them itself to avoid send bursts correlating with the node restarting.\n\nThat approach would allow people with higher or different tx privacy goals to replace the privbcast binary with something custom if desired (eg to make different tradeoffs about addr handling, or using tor exit nodes, etc), without needing to modify bitcoind source.",
   "id": "r:5662068682",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#pullrequestreview-5662068682",
   "hash": "90e3be42d8751795",
   "edited": null
  },
  {
   "t": "2026-10-27T12:47:21Z",
   "kind": "comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nThe PR text is AI written, curated a number of times under my direction as the primary document. I think there's great value for both humans and AI. It doesn't have to be in the merged branch.\n\n[quoted text omitted]\nI think there's definitely value increasing anonymity set if nothing else. By the same token, the current implementation should have gone through a BIP process, for exactly the same reasons.\n\n[quoted text omitted]\nOriginally I designed it as a separate tool, but I was hoping for a like-for-like replacement. However the architecture lends itself just fine to it, as the only coupled code in this PR to the node is the queue portion. An \"addrman but not node's addrman\" is another design quest, because resisting cross-tx correlation, while a secondary goal, is worth trying to preserve under adversaries.\n\n[quoted text omitted]\nThis is a leak. I prefer bounded tries. We could have it do retries based on block non-inclusion or some other criteria that isn't a clear fingerprint, e.g., not enough peers asking for the tx itself.",
   "id": "c:6442248434",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#issuecomment-6442248434",
   "hash": "0ca97ee05297f4cf",
   "edited": null
  },
  {
   "t": "2026-10-27T15:05:45Z",
   "kind": "comment",
   "who": "darosior",
   "assoc": "MEMBER",
   "text": "Concept ACK.",
   "id": "c:6445520943",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#issuecomment-6445520943",
   "hash": "39bcd188b11b7a83",
   "edited": null
  },
  {
   "t": "2026-10-27T15:27:32Z",
   "kind": "comment",
   "who": "andrewtoth",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nCan this design be expanded on about how they can be influenced, and why that is by design? Would this mean the problem is with the design that allows peers to be influenced into dialing to peers that are unique fingerprints, and not the actual private broadcast? Perhaps this can be solved by a better upstream design in addrman?\n\n[quoted text omitted]\nWould that be a problem with i2p in general, and not private broadcast? I'm not sure I understand how a sybil attack in i2p could leak a clearnet IP address.\n\nHow would sybiling the node affect sending private broadcast transactions through ephemeral i2p tunnels? These connections should not be connected in any way to the broadcasting node's long-lived i2p address (if it has one) or their tor onion address or clearnet addresses.\n\n[quoted text omitted]\nSince we no longer make any promises, and this is marked experimental, can we elaborate on the attacks it cannot resist? I find it difficult to reason about the tradeoffs presented here with respect to attacks that I do not understand or know about. Similarly with the above claims about peer influence and i2p sybiling. Wasn't part of the benefit of marking experimental to allow these attacks and weaknesses to be discussed openly?",
   "id": "c:6446074543",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#issuecomment-6446074543",
   "hash": "3872f639317cea5b",
   "edited": null
  },
  {
   "t": "2026-10-27T16:02:31Z",
   "kind": "force_push",
   "who": "instagibbs",
   "commit": "2c3d714c1a539e69670e8d0b10fd3e13f20ac1fb",
   "id": "push:2c3d714c1a539e69670e8d0b10fd3e13f20ac1fb"
  },
  {
   "t": "2026-10-27T16:54:42Z",
   "kind": "force_push",
   "who": "instagibbs",
   "commit": "49a052a9f8a31768ba94b233f4311a6cdc53a894",
   "id": "push:49a052a9f8a31768ba94b233f4311a6cdc53a894"
  },
  {
   "t": "2026-10-27T18:02:22Z",
   "kind": "comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nOk I was a bit sloppy in my wording: the design *permits* influencing, as an accepted residual.\n\nAddrman is built for partition resistance with the goal of finding a single honest peer that will give us the heaviest chain. As long as the attackers can't take all slots, the node is protected.\n\nIn the private broadcast scenario, our goals are different: the choice of recipient must not say anything about who made it. Each node's table is different, and anyone can write to it through addr gossip, directly as a peer or relayed through one. Addrman needs one honest peer to stay safe; private broadcast loses privacy to one dishonest peer that can \"read\" these differences.\n\nA \"fixed\" addrman would be one that doesn't allow peers to write to it, which would remove its whole purpose. A separate table (addrman lite?) would have to be filled with entries, and that source needs to be as similar to everyone else as possible. In Tor, that's the centralized directory to avoid fingerprinting. For this PR, the answer is \"DNS seeds\". Extending it to \"seeds + gossip with seed provenance per job\" would also work. If reused across jobs, then the per-tx tracking can continue via similar fingerprinting vectors.\n\n[quoted text omitted]\nIt's specific to the private broadcast implementation, not the i2p protocol itself. The population of i2p is small, thus making your i2p-supporting victim node more predisposed to connect to an attacker.\n\nOnce the attacker is a recipient, it can act on a suspect over clearnet before, during or after its private connection. The crux is whether it still gets an INV from the victim, and whether that INV depends on what it did. In master it can (#36277 fixed one case). In this PR the job announces on a schedule drawn at job start, and nothing the job receives or sees of the transaction changes that, so the attacker gets the same INV whatever it does.\n\nThis entire line of attack collapses by construction and we can be a little less \"careful\" on which endpoint we connect to, falling back to reasoning about liveness rather than privacy.\n\n[quoted text omitted]\nTo answer directly: I don't think experimental makes publishing working attacks ok. I think talking about the serial issues the feature has had publicly patched and extrapolating beyond that is more than sufficient to draw lessons.\n\nThe fixes so far: #35032 (addrman updated from private connections), #35410 (v1 retry went over clearnet), #36277 (what recipients saw depended on receipt timing) and #36312 (discouragement of a private peer was observable). Each is a separate place where private broadcast shares state or code with the node, and each was found after merge. Private broadcast connections still run through the node's connection manager, message processing and peer state. The useful question is how we'd know we've found the last one. This PR removes the sharing.\n\nI think marking it as experimental has two considerations:\n\n1. API breaks. They happen even in the case where something is technically perfect. Should have been the case from the get-go.\n2. Failure to achieve stated aims. If our stated aims were to mildly frustrate known adversaries like a strawman-passive LinkingLion (connect and stay quiet), I would say it's unnecessary to mark it experimental. (We also had a bug that broke even this, #35410, fixed in v31.1. Assume for the sake of argument there are no others like it.)\n\nThe issue is that this is not true. It currently fails to achieve what I believe the stated aim is, at a fundamental level. I think this is close: it should resist an attacker that \"runs many bitcoin nodes and then is able to correlate incoming (decrypted) traffic to originating IP address\" (https://github.com/bitcoin/bitcoin/pull/29415#issuecomment-2295439986). That description covers the active version too: an attacker that holds recipient slots and probes suspects over clearnet. That's where master falls short. Finding and building attacks like this is cheap now, so we should defend against the stated model unless that's impossible. The aim of the specification (and thus implementation, if correct) is to directly address this security model.\n\nThe spec in this PR states its goals and invariants against that adversary. The tests here check the IP-linkage invariants, with more on the follow-up branch. I think anyone doubting that a fresh attempt at the problem is needed should be looking at what is in master, and figuring out which invariants it holds, and how they are validated.",
   "id": "c:6449752844",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#issuecomment-6449752844",
   "hash": "c01ffc2609c0413d",
   "edited": null
  },
  {
   "t": "2026-10-27T18:07:42Z",
   "kind": "review_comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "path": "src/privbcast/params.h",
   "commit": "49a052a9f8a31768ba94b233f4311a6cdc53a894",
   "in_reply_to": 4384957876,
   "text": "yeah this is a bad comment and probably not needed, it should *not* update as a profile break which is what the static assert is for.... will change. Tests would have failed on change.",
   "id": "rc:4392392587",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#discussion_r4392392587",
   "hash": "ac919bb3ac6513da",
   "edited": "2026-10-27T18:13:33Z"
  },
  {
   "t": "2026-10-27T18:35:18Z",
   "kind": "force_push",
   "who": "instagibbs",
   "commit": "ada88b0c0ad07a054ae621abe9ae436e3165c9c1",
   "id": "push:ada88b0c0ad07a054ae621abe9ae436e3165c9c1"
  },
  {
   "t": "2026-10-27T21:44:30Z",
   "kind": "force_push",
   "who": "instagibbs",
   "commit": "00537527384d30d96763adae678e5c500f44885f",
   "id": "push:00537527384d30d96763adae678e5c500f44885f"
  },
  {
   "t": "2026-10-28T02:43:43Z",
   "kind": "comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nFWIW, it feels overly long, and poorly focused to me; same as most AI spec work. It doesn't really give you a TLDR high-level summary, mixes high level and low level stuff in the same sentence, and makes up its own unnecessary jargon (eg: \"Attempt: an opportunity that is dialled: one proxy stream, one BIP324 connection, one session.\") Which is fine for an implementation artifact from while you're exploring a feature, and also fine for AI's who like that sort of thing anyway, just seems more negative than positive as a published design doc.\n\n[quoted text omitted]\nBetter late than never? *shrug*\n\n[quoted text omitted]\nI mostly think this PR is way too much work for the value it gets. I seem to be thinking that about a lot of things though.\n\nLooking through my node's logs (long lived ipv4 ip and tor address) over the past few months, I saw 0 private broadcast connections in July, 9 in August (4 didn't send an INV at all, 3 accepted, 1 rejected for insufficient fee, 1 already have), 12 in September (3 didn't send an INV, 2 accepted, 1 rejected for fee, 6 already have), 9 in October so far (4 no INV, 1 accepted, 4 already have). Multiply 3 accepted by 18k listening nodes gives 55k txs/month vs ~20M txs in October so far, so perhaps 0.28% of transaction volume. That usage level doesn't feel like it warrants huge PRs to me.\n\nCould also plausibly just add the hook, and let the implementation live entirely out of tree, with any privacy issues dealt with externally. Then re-merge it, and add wallet support, only once the feature has proven itself robust.\n\n[quoted text omitted]\nI think there's three ways you could consider a feature:\n\n * purely research -- nobody's implemented or using it, people are exploring the idea to see if it works, no real reason not to discuss attacks openly\n * best effort -- this approach gives an improvement on alternatives (just broadcast on clearnet) in the average case, but doesn't really  provide any guarantees against the worst case\n * fully supported -- this is intended to provide guarantees, many people are relying and would be harmed if attacks become more widely known\n\nPersonally, I don't think private broadcast should be considered fully supported, and I think the amount of re-thinking it seems to need along with fairly low adoption suggests it could even be more \"purely research\" than \"best effort\". I think not talking about the potential attacks probably makes it harder to move towards fully supported rather than easier, as well.\n\n[quoted text omitted]\nI don't think enabling privatebroadcast makes things worse for any of these privacy concerns compared to running the same node in the same adversarial environment with privatebroadcast disabled, and in many cases makes them better. I'd call that meeting the aims (\"improves the privacy of the transaction originator\"), even if it's far from perfect or was overhyped (\"If the originator sends two otherwise unrelated transactions, they will not be linkable\").",
   "id": "c:6459225489",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#issuecomment-6459225489",
   "hash": "fea58faabdd80269",
   "edited": null
  },
  {
   "t": "2026-10-28T05:20:23Z",
   "kind": "review_comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "path": "doc/design/private-broadcast.md",
   "commit": "8e34f760586c927883bdefae6eac1dce4aea6e3d",
   "in_reply_to": null,
   "text": "Using `MSG_TX` and not sending wtxidrelay (matching the current code) seems likely to have a larger anonymity set, without losing any meaningful performance afaics.",
   "id": "rc:4397140258",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#discussion_r4397140258",
   "hash": "701d04439232db03",
   "edited": "2026-10-28T05:20:24Z"
  },
  {
   "t": "2026-10-28T13:02:42Z",
   "kind": "review_comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "path": "doc/design/private-broadcast.md",
   "commit": "8e34f760586c927883bdefae6eac1dce4aea6e3d",
   "in_reply_to": 4397140258,
   "text": "1. There's massive profile breaks everywhere in this PR. If we're going to break it, now is the time, especially if usage is low.\n2. Aside from simply being widely supported, WTXID relay allows for slightly more robust opportunistic package relay, since moredern Core nodes will only count wtxid relay peers as additional announcers of orphans. There avoids a potential future profile break (or logic that switches flags depending on what is being sent).",
   "id": "rc:4400855928",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#discussion_r4400855928",
   "hash": "5a01e673791f4e78",
   "edited": "2026-10-28T13:19:51Z"
  },
  {
   "t": "2026-10-28T13:15:13Z",
   "kind": "comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nThat's sort of an argument for ripping it out entirely. Which granted you sort of follow up with immediately afte.\n\nMy argument for keeping it, in replaced form: Yes it's a lot of code, relatively speaking, but it's far easier to reason about than what is in master. There's no interference with consensus critical code, was built for testing in mind, and contains future profile breaks to intentional design decisions. That's not even broaching the subject of the IP address leaks.\n\nIf that's \"too much work\", we should remove it entirely, replaced with maybe a hook like you say.\n\n[quoted text omitted]\nI think there is very little we can say about today's implementation to the user, and incremental whackamole improvements doesn't look like it will change that. \"best effort with no described privacy model forever\" <-- is this something we should be supporting",
   "id": "c:6469961979",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#issuecomment-6469961979",
   "hash": "ecb7cef771f69db1",
   "edited": null
  },
  {
   "t": "2026-10-28T16:15:35Z",
   "kind": "force_push",
   "who": "instagibbs",
   "commit": "8e34f760586c927883bdefae6eac1dce4aea6e3d",
   "id": "push:8e34f760586c927883bdefae6eac1dce4aea6e3d"
  },
  {
   "t": "2026-10-28T16:24:44Z",
   "kind": "comment",
   "who": "darosior",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nThat we mark it experimental in new releases does not automatically notify users that may depend on it because of the misleading claims we made in previous releases.\n\nIt's also unnecessary to put these users at risk, as we can discuss the architectural defects without the details of the node state leaks that allow to exploit those.\n\n[quoted text omitted]\nThe general pattern is pretty simple. The victim node connects to the attacker both on clearnet and through the private broadcast connection. The attacker correlates the node state on both connections, giving away the privatebroadcasting node's IP address.\n\nHaving a non-trivial chance of getting selected for outbound connections costs very little to an attacker. Preventing that a single entity does not occupy all our outbound slots is a design goal of Addrman, but preventing that we don't make a single connection to an attacker with a lot of reachable nodes is not one, nor something we can achieve. Stated differently: it is trivial for today's spies to have the vast majority of Bitcoin nodes connect to them, and then have a non-trivial chance of getting connected to for a private broadcast connection.\n\n_(I'll pass on the terrible incentive for spies to Sybil the network created by the current implementation of this feature.)_\n\nTherefore the only place where we can give meaningful guarantees, is in preventing a private broadcast connection from being correlated with the victim node's connection. This is not something we can guarantee for 2 connections established by the same node, due to the design of our p2p stack, and as i hope the numerous historical correlation vectors and [private broadcast defects](https://github.com/bitcoin/bitcoin/pull/36628#issuecomment-6449752844) make clear.\n\nI want to emphasize guaranteeing the absence of leaks here. In these discussions i've seen advocates of the current private broadcast architecture challenge critics to lay out the details of specific leaks, so they can be (dismissed or) patched. **This is backwards.** The onus is on advocates of a feature claiming to offer privacy or security, to convince others that it does actually achieve the stated goals under an acceptable threat model. Accepting the feature as secure by default and asking skeptics to keep coming up with end to end exploits to prove otherwise until they get tired of it, is not a serious way to approach development of a privacy feature.\n\nIn this instance, this means giving reasonable assurance that state can't be leaked between a regular connection and a private broadcast connection. I'm always happy to consider arguments to the contrary, but as far as i know the only way to get any such assurance currently, is to isolate the private broadcast connections' from the node's state.\n\n[quoted text omitted]\nI think the design document is useful and correct as is, but i agree it's fair to ask the author of a +6000,-3000 PR that directly follows from that document to put in the effort of organically writing such document.\n\n[quoted text omitted]\nI think this applies to the private broadcast version currently in master. It provides categorically weaker privacy than existing solutions, and can't provide any guarantee in adversarial conditions (which are the norm: there *are* entities attempting to deanonymize Bitcoin users today).\n\nThe stated goals are valuable, and I think would make for a useful feature to have in Core. But the current implementation does not achieve them. So we are stuck in this (now familiar) position of having taken on all the technical debt without achieving anything meaningful. The default position in this situation should be to correct the mistake by reverting the merge, but i like Greg's more positive approach to try and fix it instead.\n\nIt's a big PR, but once the specs are pinned down, the implementation is mostly straightforward. It is also not complicated by having to reason about the interaction with existing (and future) P2P machinery, and implements a valuable feature.\n\n[quoted text omitted]\nI strongly disagree. @andrewtoth @vasild as far as i understand you share this opinion? I think this view underpins much of our disagreement on how to approach private broadcast, so it's important to get some common ground here to be able to make progress.\n\nThe status quo before opting into the private broadcast option was to opt into using one of the available tools making a one shot Tor connection to a public broadcast API to obfuscate source+links, or opt into `-onlynet=tor` to obfuscate the source only.\n\nToday, you can opt into the private broadcast option, which sounds like it may protect your source IP, but actually may not if you also run your node on clearnet. And despite marking it as experimental, some users may consider it relatively more reliable than an external tool maintained by a single person, because it was released by Bitcoin Core :tm:. Others may switch to using it with *some* expectation of privacy (\"surely the Core developers wouldn't release a privacy feature which can be defeated *that* easily, they are just being overly conservative in marking it as experimental\").\n\nIn both cases, this gives users a false sense of security, which i think ranks pretty high in the list of things Bitcoin Core should be very careful not to do.\n\nNow you may argue there exists some users with a desire for privacy that would neither use the one-shot Tor broadcast tools, nor the `onlynet=tor` option, but would use `-privatebroadcast` AND would not change their behaviour due to thinking they are now private (which falls under the footgun discussed just above). And you may say these users were previously susceptible to existing spies on clearnet, so `-privatebroadcast` is an improvement for them.\n\nThat may be true before the spies adapt (which is a tautology in itself: users are secure if nobody cares to attack them), but actually makes things even worse once they do. Now the spy can still deanonymize transactions, but they also Sybil the network and we give them for free a \"transactions made by users with a particular desire for privacy\" filter!\n\nTherefore, i think we should stop describe the current implementation of this feature a \"best effort\". It\u2019s an innocuous footgun at best, until people start using it and spies adapt, at which point it becomes actively harmful. I understand this would not be true if we fixed all possible leaks, but i think this PR is the only way to get reasonable assurance about this.",
   "id": "c:6474536731",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#issuecomment-6474536731",
   "hash": "ad64bfb9dc9ad634",
   "edited": null
  },
  {
   "t": "2026-10-28T17:42:52Z",
   "kind": "comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nDefinitely worth putting in a hand-written design doc that's not targeting reimplementation and I had one before (that suffered rot due to churn). But I think the crux of the current arguments currently don't rest on that document,",
   "id": "c:6476284152",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#issuecomment-6476284152",
   "hash": "33bf8cabc702dc7d",
   "edited": null
  },
  {
   "t": "2026-10-28T18:06:20Z",
   "kind": "comment",
   "who": "andrewtoth",
   "assoc": "MEMBER",
   "text": "To be perfectly clear I believe separating the node state as much as possible from private broadcast is the correct goal. However, I shared some concerns about this approach in an earlier draft here https://github.com/instagibbs/bitcoin/pull/7#issuecomment-6201251786, and the same concerns were raised independently by vasild here https://github.com/bitcoin/bitcoin/pull/36628#issuecomment-6413061893. I don't think using the built-in DNS seeds to source addresses for private broadcast is the right approach, and I don't think we should drop i2p or direct tor onion connections.\n\nThe latter concern doesn't seem to continue to make sense if we successfully execute separating the rest of the state.\n\nRegarding the former concern: as of today that would require us to continue using addrman. Since this is an experimental feature, I don't think we need to rush to a solution. I don't think this needs to be done by v33, and we can brainstorm for other ways to better source addresses. IMO supplying addresses is also a better interface for a tool, since other consumers could source addresses how they wish (say just give the tool mempool.space's node IPs) without dependence on the built-in seeds.\n\nAre these two tradeoffs acceptable, while taking the rest of the patch? I'm not sure. I understand the security team needs to use discretion when discussing possible vulnerabilities, but the status quo here is quite frustrating I'm sure for both sides. How can we know if the issue with only addrman or tor onions or i2p are realistically exploitable?\n\n[quoted text omitted]\nFrom the attacks I've seen, the only ones that have not been patched require an attacker to eclipse the node and swallow all addr messages to be effective. If that's the case, this would be very expensive and the user would have bigger problems. Again, is this tradeoff acceptable, or are there now other attacks that have been discovered?\n\n[quoted text omitted]\nI don't think this is an acceptable stance if we want to collaborate on improving this feature. AI will continue to insist there are bugs and leaks that are not realistically possible to exploit.\n\n[quoted text omitted]\nI think the status quo for most users is just using default broadcast, and everyone desires privacy (but they don't know they desire it until someone reveals to them they've violated it). I think making even the current version default behavior if we detect access to a privacy network (and warning if we can't) would be a vast improvement over the status quo. So I think improving the UX of private broadcast (by adding it to wallet, package relay, synchronous, etc.) on the way to making it default behavior is the correct path forward. That way a sybil attacker will spend a lot more resources to find out transactions that are just the default.\n\nOf course we should continue to harden the privacy against these attacks as well, but I don't think the status quo makes this feature less valuable or somehow dangerous to users.\n\n[quoted text omitted]\nIndeed but this is an optional feature that has only been available since last release. I wouldn't expect it to have lots of usage until node-in-a-box implementations start turning it on by default at least.",
   "id": "c:6476783810",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#issuecomment-6476783810",
   "hash": "14ec6ce34f64d96c",
   "edited": null
  },
  {
   "t": "2026-10-29T05:17:02Z",
   "kind": "comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nI think what we have is better than nothing, but I believe the argument you're making is that the relevant alternative isn't nothing, but rather people using an independent tool like https://github.com/alfred-hodler/pushtx and that doing so is better than what we have now (because, being independent, it doesn't risk leaks due to node state contamination).\n\nA quick AI analysis suggests that pushtx has its own failings: if tor isn't available it will just drop to clearnet, it gets the peers to connect to via clearnet DNS, and it allows its connections to be correlated to each other by listening for and reacting to the INV for its own transaction. The alternative approach of using tor to connect to a block explorer's push-tx api endpoint works for privacy, but is obviously pretty terrible for censorship resistance. So I think I'm not really convinced that what we have, even with its flaws, isn't better than the actual alternatives. (I think I am convinced that improving those alternatives is a better path than trying to have an in-node solution that's not contaminated by in-node state though)\n\n[quoted text omitted]\nI think it's a mistake in both theory and practice for people to assume Core developers are infallible with regard to crypto or privacy concerns. Getting things right is a matter of correcting mistakes when they occur, not never making mistakes. Trying to satisfy people who expect us never to make mistakes isn't going to work -- the best we can possibly do is satisfy people who expect that we'll tell them when we make mistakes and that we'll do our best to fix those mistakes.\n\nI think you might want to review #35858 as the recommendations there seem stronger than is justified (\"The transaction reaches the wider network only once a recipient relays it, which prevents linking the submission to the node's IP address.\")\n\nIn any event, it sounds to me like a better approach to this feature would be:\n\n * abandon the approach of in-node support for private broadcast -- there's too much risk of state contamination that defeats the entire purpose of the feature for realistic adversarial scenarios\n * deprecate the existing feature in point updates to existing releases -- what we have isn't something we can really recommend that people use (change it to `-privatebroadcast=allow-deprecated` to continue enabling it perhaps)\n * provide a BIP/spec for private broadcast behaviour, with discussion of the tradeoffs and risks. if different people strongly prefer different approaches, have multiple BIPs as appropriate\n * provide external tools implementing the approach documented in the spec(s)\n * add a hook so that `sendrawtransaction` always uses a specified external tool\n * add a hook so that wallet broadcast (and wallet rebroadcast) always uses a specified external tool rather than adding to the mempool or relaying directly (goal here would be to reduce linkage between txs in different wallets, I think)\n * include an in-repo implementation of a spec whose tradeoffs are reasonable/broadly acceptable and bundle it with bitcoind\n\nI don't have a strong preference as to order; but deprecating the feature shouldn't be delayed pending other steps if our feature either is not something we can recommend as fit for purpose and/or is worse than using alternative third party tools.\n\n(Even in this scenario, there's a potential linkage between node and tx: the wallet will likely only rebroadcast once the tx in question exits your mempool)\n\nApart from \"include an in-repo implementation of a spec\" I think all of those should be relatively small or negative-LOC PRs as far as the bitcoin core repo is concerned, so relatively easy to review.\n\n(To clarify maybe: this is my opinion on an approach would most easily meet everyone's goals here; it's not meant to be an ultimatum, or a \"your ideas are bad and should be thrown away\" against anyone)",
   "id": "c:6488628925",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#issuecomment-6488628925",
   "hash": "297f489d621187e5",
   "edited": "2026-10-29T06:32:31Z"
  },
  {
   "t": "2026-10-29T06:30:49Z",
   "kind": "comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nI think an extreme attack scenario worth thinking about here is:\n\n * about 50% of the reachable addresses in your addrman that also function as a working node (ie, will give you a good headers chain, respond to pings etc) are honest nodes. this is sufficient for addrman's goal of \"preventing a single entity from occupying all outbound slots\" with 99% chance\n * the remaining 50% of reachable addresses in your addrman (or more, given the additional constraints above) are attacker controlled\n * not only that, most of them (say 42% of addrman entries) are roughly unique to your node -- they were given to only you by the attacker, and you didn't successfully relay them to an honest outbound, because addr relay is probabilistic not flood fill).\n * when you make three outbound connections to do private broadcast, you have ~80% chance of one of those connections being to an attacker controlled address that has not been shared with anyone else, linking that transaction with your node (because the attacker only gave that address to your node), and hence with your other transactions\n\nHopefully that exact attack is unrealistic, given the expectation that the attacker has to provide perhaps ~650M unique addresses in total (18k addresses for each of 18k public nodes to be 50% of reachable addresses, but unique for each node?) all with a reachable bitcoin node answering them, ready to spy on relayed transactions. It's meant to be a thought-experiment, not an attack recipe, after all.\n\nIn that scenario, addrman, block relay and transaction relay all continue to work as specced and the node is not eclipsed, because you do have at least one honest outbound connection, but a pervasive group attempting to do tx linking still immediately succeeds in identifying 4-out-of-5 private broadcast txs.\n\n[quoted text omitted]\nI don't think you can reasonably say \"this is an experimental feature\" and \"it should be default and enabled by node-in-a-box implementations\" at the same time.",
   "id": "c:6489688006",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#issuecomment-6489688006",
   "hash": "ab1e000551f35310",
   "edited": null
  },
  {
   "t": "2026-10-29T08:30:22Z",
   "kind": "review_comment",
   "who": "spacexm406-web",
   "assoc": "NONE",
   "path": "src/privbcast/params.h",
   "commit": "49a052a9f8a31768ba94b233f4311a6cdc53a894",
   "in_reply_to": 4384957876,
   "text": "### Breakdown of the Code and Discussion\n 1. **The Code Change (src/privbcast/params.h)**:\n   * **inline constexpr int PROFILE_VERSION{70017};**: Defines the current profile version as 70017.\n   * **static_assert(PROFILE_VERSION == PROTOCOL_VERSION, ...);**: A compile-time assertion checking that the profile version matches the core protocol version, with the comment explaining: *\"the profile's version tracks the release\"*.\n 2. **The Feedback (ajtowns - Anthony Towns)**:\n   * Points out that 70016 is what current private broadcast versions track. He notes that *avoiding* varying these things across standard releases is precisely why a special profile exists, meaning the profile version shouldn't necessarily be tied directly to changing protocol or release versions haphazardly.\n\n   * Agrees that the comment/assertion logic is flawed (\"yeah this is a bad comment and probably not needed\").\n   * Clarifies that changes to this should *not* act as a profile break (which is what the static_assert enforces), and notes that he will change it.",
   "id": "rc:4410152151",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#discussion_r4410152151",
   "hash": "22739d07477e2d05",
   "edited": "2026-10-29T09:53:41Z"
  },
  {
   "t": "2026-10-29T08:36:14Z",
   "kind": "review",
   "who": "spacexm406-web",
   "assoc": "NONE",
   "state": "COMMENTED",
   "commit": "8e34f760586c927883bdefae6eac1dce4aea6e3d",
   "text": "Improve",
   "id": "r:5694875843",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#pullrequestreview-5694875843",
   "hash": "8651b1c537ef7f9a",
   "edited": "2026-10-29T10:36:25Z"
  },
  {
   "t": "2026-10-29T15:18:19Z",
   "kind": "comment",
   "who": "andrewtoth",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nBut I think it's important to note that the attacker does not know which addresses were not successfully relayed to an honest outbound. The attacker cannot know that the address has not been shared with anyone else, so the attack falls apart since the connections could come from anyone.\n\n[quoted text omitted]\nI am not saying it \"should be default and enabled\" today while it is still experimental. I was describing the vision I have for this feature, which I hope we can get to at some point in the future.",
   "id": "c:6500674138",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#issuecomment-6500674138",
   "hash": "d225a24ab075eb2a",
   "edited": "2026-10-29T15:18:28Z"
  },
  {
   "t": "2026-10-29T16:06:42Z",
   "kind": "comment",
   "who": "vasild",
   "assoc": "MEMBER",
   "text": "Thank you, @ajtowns for actually trying to analyze an attack scenario in details.\n\nI think all this talking that private broadcast is broken, flawed, foot gun, a failure, worse than send-to-all or harmful, without explaining why _in details_ have not been constructive.\n\nNow about this:\n\n[quoted text omitted]\nRight, further note that whenever we receive a new inbound connection we would send 1000 addresses from our addrman to the peer. So those 42% unique addresses in our addrman will soon stop to be unique and the attacker cannot make any reliable conclusions. There is unpredictable spill out from our addrman into the addrmans of other nodes.\n\n[quoted text omitted]\nI agree, for more than one reason.",
   "id": "c:6501877169",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#issuecomment-6501877169",
   "hash": "f254b4d1978b30ff",
   "edited": null
  },
  {
   "t": "2026-10-29T17:27:07Z",
   "kind": "comment",
   "who": "darosior",
   "assoc": "MEMBER",
   "text": "@ajtowns\n\n[quoted text omitted]\nCould you explain why? I disagree, and i tried to explain why in my previous message.\n\n[quoted text omitted]\nYes but that's only part of my argument. Shipping something that is worse (privacy wise, which i think is the relevant dimension here) than existing alternatives, certainly isn't better. But also, shipping a privacy branded feature with known architectural defects which permit breaching the promised privacy under a very reasonable threat model, is worse than shipping nothing.\n\n[quoted text omitted]\nHowever for that part, no, this is not the argument i was making. What i had in mind was simply broadcasting the transaction through Tor to one of the many available broadcast API out there. A simple `curl` using your Tor proxy is sufficient here, and i know of multiple people who have been doing just that in the past. Some popular explorers like Mempool's or Blockstream's offer hidden services and even a webpage for transaction submission ([1](http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/tx/push), [2](http://explorerzydxu5ecjrkwceayqybizmjjznk5izmitf2modhcusuqlid.onion/tx/push)), so less technical users can use the Tor browser instead of the command line.\n\nIt is indeed less reliable as it's limited to a few single points of failures, and if we can offer a more decentralized option i'm all in, but i don't think this should compromise on privacy, which is the desired property in the first place.\n\nThat said i am skeptical we could realistically achieve this. I think that would merely shift the single point of failures from explorer APIs towards DNS seeds, and while there may be reasons why the latter may be reasonable, that does not seem like a particularly interesting goal.\n\nInstead, the value i see in private broadcast is to make this limited tool, which allows one to trade-off reliability for substantially improved privacy, available to more people. If we can implement it properly, i think it could have some pretty interesting properties at scale: some turnkey node solutions could start defaulting to using it by default for broadcasting the transactions from wallet commonly associated with these boxes, and if a few % of transactions on the network start being broadcast this way it might create enough false positives to defeat spies' transaction origin analysis.\n\nThis perspective is why i'm supporting this attempt to salvage the feature rather than stripping it off entirely.\n\n[quoted text omitted]\nFair enough for those alternatives, but i don't think this should be our baseline for comparison.\n\n[quoted text omitted]\nOk, i can agree with that list. I would rather we don't deprecate the feature and instead fix it with this PR, but i may be too optimistic. It also seems to me that the threat model / design document for that feature can be discussed internally to Bitcoin Core, and does not need to go through the BIP process, but that's of secondary importance.\n\n@andrewtoth\n\n[quoted text omitted]\nTo be clear: this is unrelated to the security team. As far as i'm aware, no embargo on any of the remaining leaks was requested. Not discussing those in details publicly while users may be affected is just a matter of judgement on the part of the discoverer.\n\n[quoted text omitted]\nAgain, this is backward. How can we be reasonably convinced this feature is robust enough that it is reasonable to ship?\n\n[quoted text omitted]\nI'm sorry, but this is a deeply unserious approach to developing security-critical software. By that standard, should reviewers identifying a memory safety vulnerability be expected to demonstrate a working RCE before the issue is taken seriously? And even then, should we merely address the specific exploit demonstrated, which has been the approach for private broadcast leaks, rather than the underlying vulnerability? This would turn security review into an endless game of whack-a-mole, with the burden falling on reviewers to keep finding new ways to exploit the same fundamental flaws.\n\nThe bit about AI is also concerning. It reads as if you suggested AI tools are hallucinating unreasonable privacy breaches against an otherwise sound implementation of the feature. Yet you've not given a single threat model by which we could judge whether a breach can be considered reasonable, the implementation has  repeatedly been shown to be unsound, and AI tools have been pretty effective at poking holes in it.",
   "id": "c:6503707596",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#issuecomment-6503707596",
   "hash": "d94b3cff0d7ef95e",
   "edited": "2026-10-29T17:28:13Z"
  },
  {
   "t": "2026-10-29T18:21:02Z",
   "kind": "comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nAgain, onus is backwards here. Where's this invariant promised in the code or docs? How did you verify this? Is there existing test for this supposed invariant?\n\nBlind assertions forcing me to do more work is not appropriate.\n\n---\n\nTo actually circle back to the PR at hand: I'm currently putting DNS seeds as a liveness requirement for a privacy tool. This is something I have to defend, but given that, neatly slots into what I consider a reasonable security model, from which flows invariants that can be stated clearly, tested, and audited as heavily as we'd like.\n\nThe alternative is to continue arguing about what is what is \"unrealistic\" under no defined model at all.",
   "id": "c:6504882857",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#issuecomment-6504882857",
   "hash": "1fbec8d61565d8e8",
   "edited": null
  },
  {
   "t": "2026-10-30T01:03:25Z",
   "kind": "comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nThat is captured in the \"80% success rate\" -- they have up to a 20% false-positive rate in this scenario, but that's hardly meeting the \"private\" goal of this feature.\n\n[quoted text omitted]\nThe addresses we send are cached in order to avoid an attacker being able to poll all the addresses in our addrman.\n\n[quoted text omitted]\nAnd yes, I realise that (hopefully) wasn't a sufficient recipe to form a practical attack -- if it were, I wouldn't have posted it publicly. You should be able to conceive of the possibility of a theoretical attack being made both more more impactful and more practical and work to defend against that possibility, not dismiss it out of hand.",
   "id": "c:6512327367",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#issuecomment-6512327367",
   "hash": "ad71dfe57ffdb8bf",
   "edited": null
  },
  {
   "t": "2026-11-01T15:58:23Z",
   "kind": "comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nDon't really think it matters enough to be worth arguing about?\n\n[quoted text omitted]\nReplacing the DNS seed with your own custom variant is perhaps possible to do in a way that avoids addrman's problems (ie offers additional/different guarantees/properties/invariants than today's addrman) so that individuals can avoid the single point of failure, but \"setting up your own tx announcing API that only you use\" just gives you the exact same problem. If it's not realistically achievable, that doesn't matter, but if it is, that's a useful reduction of failure points, I think.\n\nAlso, that's three different preferred approaches to private broadcast just from people in this thread though, so that seems like a good reason for providing a hook rather than picking a winner, so that different people can have their node adopt the approach they prefer.\n\n[quoted text omitted]\nI think there's a fairly strong argument that the feature as implemented and released (in 31.x and 32.x) doesn't do the job it's meant to or that people would reasonably expect it to do; if so, I think that's a good reason to deprecate a feature. Deprecation doesn't mean giving up on the goal -- we can still implement it properly and do it right in a future release. It also doesn't take the feature away from anyone, just gives them what seems to be an appropriate warning that it's not as good an idea to use as it might have seemed at first glance. I don't think this PR as-is is really back-portable (and it doesn't sound like you fully support its exact approach anyway), and I think both deprecating the existing feature and backporting a \"hook and external tool\" approach would be fairly reasonable.\n\nAnyway, that's my view. Mostly sum it up as \"i don't want to review 9k lines of changes for this, and hope there's alternatives\".",
   "id": "c:6560319962",
   "url": "https://github.com/bitcoin/bitcoin/pull/36628#issuecomment-6560319962",
   "hash": "899ed3fb167106a8",
   "edited": null
  }
 ],
 "labels_log": [
  {
   "t": "2026-10-23T16:45:29Z",
   "action": "labeled",
   "label": "P2P",
   "who": "DrahtBot"
  },
  {
   "t": "2026-10-23T16:56:04Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-10-23T18:29:52Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-10-26T17:41:10Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-10-27T00:29:43Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-10-27T16:56:14Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-10-27T18:23:54Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-10-27T22:54:49Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-10-28T17:11:14Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-10-29T12:14:25Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 56869,
 "text_tokens_estimate": 14217,
 "changed_paths": [
  "doc/README.md",
  "doc/design/private-broadcast.md",
  "doc/private-broadcast.md",
  "doc/release-notes-privbcast.md",
  "src/CMakeLists.txt",
  "src/bitcoin-cli.cpp",
  "src/common/messages.cpp",
  "src/init.cpp",
  "src/kernel/chainparams.cpp",
  "src/net.cpp",
  "src/net.h",
  "src/net_processing.cpp",
  "src/net_processing.h",
  "src/netbase.cpp",
  "src/netbase.h",
  "src/node/connection_types.cpp",
  "src/node/connection_types.h",
  "src/node/context.cpp",
  "src/node/context.h",
  "src/node/peerman_args.cpp",
  "src/node/privbcast.cpp",
  "src/node/privbcast.h",
  "src/node/transaction.cpp",
  "src/node/txorphanage.h",
  "src/node/types.h",
  "src/primitives/transaction.h",
  "src/private_broadcast.cpp",
  "src/private_broadcast.h",
  "src/privbcast/assign.cpp",
  "src/privbcast/assign.h",
  "src/privbcast/attempt.cpp",
  "src/privbcast/attempt.h",
  "src/privbcast/discovery.cpp",
  "src/privbcast/discovery.h",
  "src/privbcast/job.cpp",
  "src/privbcast/job.h",
  "src/privbcast/params.h",
  "src/privbcast/plan.cpp",
  "src/privbcast/plan.h",
  "src/privbcast/report.cpp",
  "src/privbcast/report.h",
  "src/privbcast/socks5.cpp",
  "src/privbcast/socks5.h",
  "src/privbcast/stream.cpp",
  "src/privbcast/stream.h",
  "src/qt/guiutil.cpp",
  "src/qt/rpcconsole.cpp",
  "src/rpc/mempool.cpp",
  "src/rpc/net.cpp",
  "src/rpc/util.cpp",
  "src/test/CMakeLists.txt",
  "src/test/denialofservice_tests.cpp",
  "src/test/fuzz/CMakeLists.txt",
  "src/test/fuzz/connman.cpp",
  "src/test/fuzz/p2p_private_broadcast.cpp",
  "src/test/fuzz/private_broadcast.cpp",
  "src/test/fuzz/privbcast_attempt.cpp",
  "src/test/net_tests.cpp",
  "src/test/private_broadcast_tests.cpp",
  "src/test/privbcast_plan_tests.cpp",
  "src/test/privbcast_queue_tests.cpp",
  "src/test/util/net.cpp",
  "src/test/util/net.h",
  "src/test/util/privbcast.h",
  "test/functional/feature_config_args.py",
  "test/functional/feature_init.py",
  "test/functional/feature_signet.py",
  "test/functional/p2p_private_broadcast.py",
  "test/functional/p2p_private_broadcast_cap.py",
  "test/functional/p2p_private_broadcast_completion.py",
  "test/functional/p2p_private_broadcast_peers.py",
  "test/functional/p2p_private_broadcast_proxy.py",
  "test/functional/p2p_private_broadcast_retry_v1.py",
  "test/functional/test_framework/p2p.py",
  "test/functional/test_framework/privbcast.py",
  "test/functional/test_framework/socks5.py",
  "test/functional/test_framework/test_framework.py",
  "test/functional/test_runner.py"
 ],
 "files": [
  {
   "path": "doc/README.md",
   "add": 1,
   "del": 0
  },
  {
   "path": "doc/design/private-broadcast.md",
   "add": 505,
   "del": 0
  },
  {
   "path": "doc/private-broadcast.md",
   "add": 98,
   "del": 0
  },
  {
   "path": "doc/release-notes-privbcast.md",
   "add": 63,
   "del": 0
  },
  {
   "path": "src/CMakeLists.txt",
   "add": 9,
   "del": 1
  },
  {
   "path": "src/bitcoin-cli.cpp",
   "add": 0,
   "del": 2
  },
  {
   "path": "src/common/messages.cpp",
   "add": 4,
   "del": 0
  },
  {
   "path": "src/init.cpp",
   "add": 61,
   "del": 30
  },
  {
   "path": "src/kernel/chainparams.cpp",
   "add": 2,
   "del": 0
  },
  {
   "path": "src/net.cpp",
   "add": 16,
   "del": 218
  },
  {
   "path": "src/net.h",
   "add": 3,
   "del": 99
  },
  {
   "path": "src/net_processing.cpp",
   "add": 15,
   "del": 251
  },
  {
   "path": "src/net_processing.h",
   "add": 0,
   "del": 29
  },
  {
   "path": "src/netbase.cpp",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/netbase.h",
   "add": 3,
   "del": 0
  },
  {
   "path": "src/node/connection_types.cpp",
   "add": 0,
   "del": 2
  },
  {
   "path": "src/node/connection_types.h",
   "add": 0,
   "del": 7
  },
  {
   "path": "src/node/context.cpp",
   "add": 1,
   "del": 0
  },
  {
   "path": "src/node/context.h",
   "add": 3,
   "del": 0
  },
  {
   "path": "src/node/peerman_args.cpp",
   "add": 0,
   "del": 2
  },
  {
   "path": "src/node/privbcast.cpp",
   "add": 501,
   "del": 0
  },
  {
   "path": "src/node/privbcast.h",
   "add": 320,
   "del": 0
  },
  {
   "path": "src/node/transaction.cpp",
   "add": 21,
   "del": 1
  },
  {
   "path": "src/node/txorphanage.h",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/node/types.h",
   "add": 4,
   "del": 2
  },
  {
   "path": "src/primitives/transaction.h",
   "add": 1,
   "del": 2
  },
  {
   "path": "src/private_broadcast.cpp",
   "add": 0,
   "del": 209
  },
  {
   "path": "src/private_broadcast.h",
   "add": 0,
   "del": 271
  },
  {
   "path": "src/privbcast/assign.cpp",
   "add": 121,
   "del": 0
  },
  {
   "path": "src/privbcast/assign.h",
   "add": 104,
   "del": 0
  },
  {
   "path": "src/privbcast/attempt.cpp",
   "add": 325,
   "del": 0
  },
  {
   "path": "src/privbcast/attempt.h",
   "add": 189,
   "del": 0
  },
  {
   "path": "src/privbcast/discovery.cpp",
   "add": 171,
   "del": 0
  },
  {
   "path": "src/privbcast/discovery.h",
   "add": 101,
   "del": 0
  },
  {
   "path": "src/privbcast/job.cpp",
   "add": 505,
   "del": 0
  },
  {
   "path": "src/privbcast/job.h",
   "add": 210,
   "del": 0
  },
  {
   "path": "src/privbcast/params.h",
   "add": 151,
   "del": 0
  },
  {
   "path": "src/privbcast/plan.cpp",
   "add": 75,
   "del": 0
  },
  {
   "path": "src/privbcast/plan.h",
   "add": 53,
   "del": 0
  },
  {
   "path": "src/privbcast/report.cpp",
   "add": 143,
   "del": 0
  },
  {
   "path": "src/privbcast/report.h",
   "add": 100,
   "del": 0
  },
  {
   "path": "src/privbcast/socks5.cpp",
   "add": 207,
   "del": 0
  },
  {
   "path": "src/privbcast/socks5.h",
   "add": 121,
   "del": 0
  },
  {
   "path": "src/privbcast/stream.cpp",
   "add": 234,
   "del": 0
  },
  {
   "path": "src/privbcast/stream.h",
   "add": 120,
   "del": 0
  },
  {
   "path": "src/qt/guiutil.cpp",
   "add": 0,
   "del": 2
  },
  {
   "path": "src/qt/rpcconsole.cpp",
   "add": 1,
   "del": 4
  },
  {
   "path": "src/rpc/mempool.cpp",
   "add": 160,
   "del": 73
  },
  {
   "path": "src/rpc/net.cpp",
   "add": 2,
   "del": 4
  },
  {
   "path": "src/rpc/util.cpp",
   "add": 2,
   "del": 0
  },
  {
   "path": "src/test/CMakeLists.txt",
   "add": 2,
   "del": 1
  },
  {
   "path": "src/test/denialofservice_tests.cpp",
   "add": 1,
   "del": 34
  },
  {
   "path": "src/test/fuzz/CMakeLists.txt",
   "add": 1,
   "del": 2
  },
  {
   "path": "src/test/fuzz/connman.cpp",
   "add": 1,
   "del": 7
  },
  {
   "path": "src/test/fuzz/p2p_private_broadcast.cpp",
   "add": 0,
   "del": 258
  },
  {
   "path": "src/test/fuzz/private_broadcast.cpp",
   "add": 0,
   "del": 250
  },
  {
   "path": "src/test/fuzz/privbcast_attempt.cpp",
   "add": 365,
   "del": 0
  },
  {
   "path": "src/test/net_tests.cpp",
   "add": 0,
   "del": 30
  },
  {
   "path": "src/test/private_broadcast_tests.cpp",
   "add": 0,
   "del": 334
  },
  {
   "path": "src/test/privbcast_plan_tests.cpp",
   "add": 28,
   "del": 0
  },
  {
   "path": "src/test/privbcast_queue_tests.cpp",
   "add": 30,
   "del": 0
  },
  {
   "path": "src/test/util/net.cpp",
   "add": 2,
   "del": 4
  },
  {
   "path": "src/test/util/net.h",
   "add": 0,
   "del": 1
  },
  {
   "path": "src/test/util/privbcast.h",
   "add": 68,
   "del": 0
  },
  {
   "path": "test/functional/feature_config_args.py",
   "add": 21,
   "del": 17
  },
  {
   "path": "test/functional/feature_init.py",
   "add": 6,
   "del": 0
  },
  {
   "path": "test/functional/feature_signet.py",
   "add": 9,
   "del": 0
  },
  {
   "path": "test/functional/p2p_private_broadcast.py",
   "add": 466,
   "del": 390
  },
  {
   "path": "test/functional/p2p_private_broadcast_cap.py",
   "add": 50,
   "del": 73
  },
  {
   "path": "test/functional/p2p_private_broadcast_completion.py",
   "add": 0,
   "del": 99
  },
  {
   "path": "test/functional/p2p_private_broadcast_peers.py",
   "add": 79,
   "del": 0
  },
  {
   "path": "test/functional/p2p_private_broadcast_proxy.py",
   "add": 60,
   "del": 0
  },
  {
   "path": "test/functional/p2p_private_broadcast_retry_v1.py",
   "add": 0,
   "del": 199
  },
  {
   "path": "test/functional/test_framework/p2p.py",
   "add": 3,
   "del": 1
  },
  {
   "path": "test/functional/test_framework/privbcast.py",
   "add": 318,
   "del": 0
  },
  {
   "path": "test/functional/test_framework/socks5.py",
   "add": 101,
   "del": 13
  },
  {
   "path": "test/functional/test_framework/test_framework.py",
   "add": 0,
   "del": 120
  },
  {
   "path": "test/functional/test_runner.py",
   "add": 2,
   "del": 2
  }
 ],
 "test_lines": 3448,
 "git": {
  "head": "8e34f760586c927883bdefae6eac1dce4aea6e3d",
  "head_matches_backup": true,
  "base": "7efbdb6437a840217127bd7dea0b963c67613860",
  "patch_id": "40d7030041c2d0b6",
  "commits": [
   {
    "sha": "88f960fd83",
    "subject": "doc: add the private broadcast specification",
    "files": 1,
    "add": 505,
    "del": 0
   },
   {
    "sha": "5905856aba",
    "subject": "privbcast: add the parameters and the schedule",
    "files": 7,
    "add": 311,
    "del": 0
   },
   {
    "sha": "f2dc2e2997",
    "subject": "privbcast: add candidate assignment",
    "files": 3,
    "add": 226,
    "del": 0
   },
   {
    "sha": "7b03d4229a",
    "subject": "privbcast: add a byte-driven SOCKS5 client",
    "files": 5,
    "add": 333,
    "del": 1
   },
   {
    "sha": "5d5707f05f",
    "subject": "privbcast: add the attempt state machine",
    "files": 6,
    "add": 949,
    "del": 0
   },
   {
    "sha": "cac5ffe15f",
    "subject": "privbcast: add proxy streams and discovery",
    "files": 5,
    "add": 628,
    "del": 0
   },
   {
    "sha": "03435a45ef",
    "subject": "privbcast: add the job",
    "files": 5,
    "add": 960,
    "del": 0
   },
   {
    "sha": "91144bdd5a",
    "subject": "test: extend the harness for private broadcast jobs",
    "files": 3,
    "add": 294,
    "del": 14
   },
   {
    "sha": "4f78532ddf",
    "subject": "node: add the private broadcast job queue",
    "files": 5,
    "add": 766,
    "del": 0
   },
   {
    "sha": "0742c8a882",
    "subject": "node: run private broadcast as jobs",
    "files": 19,
    "add": 893,
    "del": 1006
   },
   {
    "sha": "82cc75b326",
    "subject": "test: run private broadcast jobs end to end through the node",
    "files": 4,
    "add": 271,
    "del": 2
   },
   {
    "sha": "e452ebc06e",
    "subject": "net: remove the old private broadcast relay",
    "files": 14,
    "add": 14,
    "del": 1664
   },
   {
    "sha": "4acf24a802",
    "subject": "net: remove the old private broadcast connections",
    "files": 14,
    "add": 30,
    "del": 347
   },
   {
    "sha": "f8c5e64add",
    "subject": "refactor: drop the trivial AdvertisedVersion()",
    "files": 3,
    "add": 11,
    "del": 24
   },
   {
    "sha": "4bd50b805e",
    "subject": "doc: describe bitcoind -privatebroadcast",
    "files": 2,
    "add": 99,
    "del": 0
   },
   {
    "sha": "8e34f76058",
    "subject": "doc: release notes for private broadcast jobs",
    "files": 1,
    "add": 63,
    "del": 0
   }
  ],
  "patch_truncated": true
 },
 "patch_id": "40d7030041c2d0b6",
 "input_hash": "4ec4fa57e6bfac63",
 "extracted_at": "2026-11-02T01:09:49+00:00"
}