Skip to content

feat(sdk): let a 402 declare txids the payer may omit from payment ancestry - #445

Merged
sirdeggen merged 5 commits into
bsv-blockchain:mainfrom
imranterranode:proposal/authfetch-known-txids
Sep 8, 2026
Merged

sirdeggen merged 5 commits into
bsv-blockchain:mainfrom
imranterranode:proposal/authfetch-known-txids

Conversation

@imranterranode

@imranterranode imranterranode commented Aug 8, 2026 •

Copy link
Copy Markdown
Contributor

Program and scope

  • Tracker or issue: feat(sdk): let a 402 declare txids the payer may omit from payment ancestry #445; multipart transport remains in BRC-118 multipart body transport is unimplemented, and header-carried payments fail at 32KB behind Cloudflare #473.
  • Program gate(s) advanced: backward-compatible SDK payment ancestry optimization, refreshed onto current main.
  • Why this change is needed: chained unconfirmed payments resend full ancestry in x-bsv-payment. A recipient can now advertise transactions it already possesses and has validated through the optional x-bsv-payment-known-txids 402 response header. AuthFetch passes up to 256 unique, valid lowercase IDs to wallet createAction, including repriced payments. Missing or invalid-only hints preserve the existing call. Compatible retries reuse the original payment.
  • Explicitly out of scope: multipart transport, server-side hint discovery, BRC amendment, publication and deployment. The header is an optional SDK extension; it is not yet standardized by BRC-105.
  • Exact head SHA reviewed: 515ecf7

Impact

  • No public package source or manifest changed
  • Public package source or manifest changed; affected packages are listed below
  • Infrastructure source, dependency, image, or deployment configuration changed
  • Public API, exports, types, runtime targets, or browser/mobile behavior changed
  • Security-sensitive boundary changed
  • Documentation or examples changed

Affected packages: @bsv/sdk 2.5.0 (minor: optional behavior and exported parseKnownTxidsHeader). Existing SDK peers accept this version; no dependent API or dependency-range migration is required. README, API documentation, changelog, release/migration ledger and governed version facts are current. The existing unpublished @bsv/message-box-client 2.5.0 minor candidate also records its rebuilt SDK-containing UMD bundle; no further version increment is needed. Publication remains a separate protected workflow action.

Verification

  • Local commands and results (Node 24.15.0, pnpm 10.33.2): frozen install with lifecycle scripts disabled; audited esbuild rebuild; pnpm health:check, pnpm lint, pnpm format:check, pnpm typecheck, pnpm build, pnpm audit:security, pnpm docs:examples, pnpm docs:facts:check, and cross-package version checks passed.
  • SDK: pnpm --filter @bsv/sdk test:coverage --runInBand — 5,941 tests across 157 suites, plus one snapshot, passed. The 17 known-txid tests cover parsing, normalization, deduplication, exact 256-ID truncation, malformed-only omission, first payment, repricing, authenticated-response parsing, and byte-identical compatible retries with changed or absent hints.
  • Dependent behavior: auth-express-middleware 120 tests and payment-express-middleware 39 tests passed; 41 focused wallet knownTxids/BEEF/createAction tests across five suites passed.
  • Hosted CI run: CI and merge gate, CodeQL, and Conformance all passed for 515ecf70419d588ac2a3216239c1373b54919cf0. Every emitted CI check passed, and the complete merge gate validates the expected scope-based skips. The repository-owned patch gate passed at 100.00% (17/17 changed line/branch points; target 90%). GitHub still blocks merge because repository ruleset 19713320 requires codecov/patch, while this fork PR intentionally skips external Codecov upload/notification in ci.yml. No Codecov status has been fabricated and no branch-protection setting has been changed.
  • Conformance evidence: TS conformance runner passed 6,479 tests with 211 existing governed skips; all 6,690 structural vectors across 76 files passed.
  • Coverage delta: all 11 changed executable AuthFetch lines covered; AuthFetch line coverage 98.35%, branch coverage 95.58%; total SDK line coverage 95.56%.
  • Lint/typecheck delta: no new warnings or errors.
  • Browser/mobile/packed-consumer evidence: exact SDK tarball passed export, strict declaration, ESM/CommonJS, source-map and package checks; Vite, esbuild and UMD exact-tarball browser contracts passed. The full local sweep passed all 13 dependent browser packages plus the SDK; Message Box Client packed consumers also passed. No React Native contract change.
  • Performance or bundle-size delta: same Node24/rspack comparison against main measured UMD 554,957 → 555,261 raw bytes (+304, 0.055%), gzip 158,185 → 158,309 (+124), Brotli 131,962 → 132,151 (+189). Both bundles contain the same 128 mapped modules; only AuthFetch source content changed. The raw UMD budget moves from 555,000 to 555,500 because the previous build had 43 bytes of headroom. Compressed budgets remain unchanged. Vite raw bytes are 741,743 (+417); esbuild raw bytes are 560,201 (+314), so its raw budget moves from 560,000 to 560,500. Chunk/module composition and all compressed budgets are unchanged. Message Box Client UMD measures 509,963 → 510,247 raw bytes (+284), gzip 145,689 → 145,791 (+102), Brotli 122,096 → 122,173 (+77), with the same 130 mapped modules and only SDK AuthFetch source changed. Its raw budget moves 510,000 → 510,500; compressed limits remain unchanged.
  • I self-reviewed the complete diff for correctness, security, compatibility, public API, artifacts, dependencies, docs, and operations
  • All applicable checks are terminal and successful on the exact head; any scope-based skip is expected and validated by the merge gate

Security and dependencies

  • No dependency or lockfile change
  • Changelog, runtime relevance, peer compatibility, transitive graph, and audit results were reviewed
  • CodeQL/negative tests cover any changed trust boundary
  • The exact-head CodeQL analysis has no new alert
  • The exact-head repository quality gate reports zero new Sonar findings (including accepted or false-positive issue states) and zero unreviewed hotspots; Sonar's aggregate verdict alone is not merge evidence
  • No new override, advisory dismissal, quality suppression, or skipped test
  • Any temporary exception is registered with owner, evidence, review date, and removal condition
  • Workflow permissions and lifecycle-script behavior remain least privilege

The hint comes through the existing authenticated x-bsv-* response envelope. It does not change the amount, payee, signing, or retry authorization. Recipients must advertise only ancestors they already hold and have validated; arbitrary unknown IDs do not inject transactions into wallet BEEF. AuthFetch does not infer recipient knowledge from payer broadcast history. The sdk-auth-http mutation target passed at 84.96% (113 mutants; zero uncovered or invalid mutants). No new exception was introduced. Security audit passed the existing governed policy; this change does not add dependencies or advisory exceptions.

Dependency evidence

No dependency versions or lockfiles change. SDK 2.5.0 fits existing public peer ranges; workspace SDK references in dependents are development dependencies. The additional browser bytes are measured above, with no new modules.

Release and operations

  • No npm publication was performed from a workstation or from this PR
  • Required npm patch bumps are included or intentionally deferred by the controlling program
  • Image/SBOM/provenance/deployment/rollback impact is documented
  • Documentation, changelog, migration, and operational guidance are current

The SDK minor bump covers the additive API. Existing consumers require no migration. Browser services enabling the hint must expose the optional response header under their existing CORS policy. No service deployment, image, persisted schema or wire encoding changes. Pending SDK release notes are preserved; no artifacts were published.

Completion evidence

  • The linked tracker is updated only for work fully proved by merged code, passing checks, resolved alerts, measurements, or an approved exception
  • Review conversations are resolved
  • Documentation, changelog, migration notes, release notes, and operator guidance are current or concretely not applicable
  • No pending, failed, stale, cancelled, or unexpectedly skipped check is being handed to another contributor as complete
  • One qualified maintainer approval is sufficient; no last-pusher restriction is assumed

@imranterranode imranterranode changed the title proposal(sdk): let a 402 declare txids the payer may omit from payment ancestry discussion: payment header size — BRC-118 transport and BRC-96 txid-only ancestry Aug 9, 2026
@ty-everett

Copy link
Copy Markdown
Collaborator

I like this BRC-96 approach a lot, and I like all of the code here. This BRC96 change should land regardless, because it's opt-in, it's sensibly constrained, and it's fail-soft. A very smart approach to this.

I also think the multipart approach deserves more attention, and I'll mess around with some implementations when I get the chance.

@imranterranode

Copy link
Copy Markdown
Contributor Author

Thanks — glad the shape works.

One thing worth flagging before this lands, since it would be easy to let it through by default: the header name x-bsv-payment-known-txids is mine, not from any BRC. It sits in the x-bsv-payment-* namespace BRC-105 defines, but it isn't specified there. If this merges, the SDK makes it the de facto standard — any server wanting to use the mechanism has to emit that exact string — without it having gone through the spec.

I have no attachment to it. Happy for it to be renamed, or folded into BRC-105/118 transport negotiation rather than standing alone as its own header. Worth deciding deliberately now rather than unpicking it later if BRC-118 ends up defining something similar in its negotiation.

Also happy to split the BRC-118 material out into its own issue so this PR is purely the BRC-96 change, and take it out of draft — say the word and I'll do both.

@imranterranode
imranterranode force-pushed the proposal/authfetch-known-txids branch from 7a26e9b to 6a77057 Compare August 10, 2026 12:07
@imranterranode
imranterranode force-pushed the proposal/authfetch-known-txids branch from 6a77057 to b998079 Compare August 13, 2026 06:57
@imranterranode imranterranode changed the title discussion: payment header size — BRC-118 transport and BRC-96 txid-only ancestry feat(sdk): let a 402 declare txids the payer may omit from payment ancestry Aug 13, 2026
@imranterranode
imranterranode marked this pull request as ready for review August 13, 2026 06:57
@imranterranode

Copy link
Copy Markdown
Contributor Author

Done both of the things I offered above, so this is now out of draft and merge-framed.

  • BRC-118 split out into BRC-118 multipart body transport is unimplemented, and header-carried payments fail at 32KB behind Cloudflare #473, so the multipart transport can be designed without this PR waiting on it. Your note about wanting to experiment with implementations is referenced there.
  • The code is unchanged in shape from what you endorsed — same opt-in header, same fail-soft parse, same 256 cap. Two additions: it now also forwards the list on the mid-flight reprice path (the requirementsChanged branch built a second transaction and dropped the option, which is the worst place to drop it), plus a regression test for that; and I verified on the toolbox side that knownTxids needs no trustSelf: 'known' companion, and that a server-declared list replaces rather than merges with autoKnownTxids. Both written up in the body.
  • Rebased onto current main and squashed to one commit. 125/125 across the AuthFetch suites, typecheck and oxlint --deny-warnings clean locally.

The header-name question is still open and now has its own section in the body rather than sitting in a comment. One useful constraint I found while checking: the name has to stay inside the x-bsv- namespace, because auth-express-middleware only serialises x-bsv--and-not-x-bsv-auth response headers into the signed envelope — anything else is silently dropped in transit. So it narrows to which x-bsv-payment-* string. Happy with whatever you pick, including folding it into BRC-105/118 negotiation instead.

One ask: CI, CodeQL and Conformance are all sitting at action_required and have never run on this PR, on any push — they need a maintainer to approve the workflow run. Would be good to have a real CI signal on it before anyone relies on my local run.

@ty-everett

Copy link
Copy Markdown
Collaborator

Approved the CI. Looks like some small CI stuff, but then it's all good after that! For the header, it's not technically specified, but I'm happy to entertain a PR on the BRCs repo to amend 105 with a new, optional header field. If you do that feel free to put yourself in the list of spec authors too :)

Nice work on this, I think the initial header stuff is good. I agree with splitting the other changes out as you've done. I'm not as familiar with the multipart stuff, so I'll leave it for othes to discuss and review. It also sounds reasonable at a high level though.

…cestry

An x-bsv-payment carries the payment transaction plus its ancestry, so the
recipient can verify it without asking anyone. Any ancestor the recipient
already holds is redundant weight, but the payer cannot know which those are,
so it sends all of them. Chained payments therefore grow without bound: each
spends the previous payment's unconfirmed change, so every payment re-ships the
whole unconfirmed run until a block collapses it to a merkle path.

BRC-96 already specifies the shorter encoding (Tx Data Format 02, version
marker 0200BEEF), the SDK already implements it, and wallet-toolbox already
honours createAction's knownTxids end to end. The only missing piece is that
nothing tells the payer which txids the recipient has.

Read an optional x-bsv-payment-known-txids off the 402 and thread it into
createAction. Absent header omits the option entirely, so behaviour is
byte-identical to before. Parsing is fail-soft — malformed entries are dropped
rather than thrown — because a bad header should cost bytes, never a payment.
The list is capped at 256 so a hostile server cannot inflate the createAction
call.

Only the recipient may populate this. Per BRC-96 a txid-only entry "is treated
as implicitly valid", i.e. the recipient verifies nothing about it, so omitting
an ancestor the recipient lacks makes the payment unverifiable. The list must
come from the recipient's own records and must never be inferred by the payer.

Both paths that build a payment forward the list, including the regeneration
branch taken when a server adjusts its price mid-flight — a repriced retry is
already the largest request in the exchange.
@imranterranode
imranterranode force-pushed the proposal/authfetch-known-txids branch from b998079 to 8942d72 Compare August 14, 2026 06:19
@imranterranode

Copy link
Copy Markdown
Contributor Author

Rebased onto current main — could you re-approve the workflow runs when you get a chance? All three are sitting at action_required again on the new SHA.

The failure wasn't in this change: pnpm audit:security was tripping on GHSA-2v37-7h3g-55p8 (nanoid <3.3.18, via .>vite>postcss>nanoid), and dcf5fda10 widened the workspace override from nanoid@<3.3.17 to <3.3.18 about four hours after my run started. The other three red jobs were cascades of it (prepare=failure, everything else skipped).

The commit is unchanged — same two files, byte-identical patch, only the base moved. I reproduced the job locally on Node 24 after a clean --frozen-lockfile install: audit:security, ops:check, check-versions, check-sdk-peer, typecheck and lint --deny-warnings all pass, and the full @bsv/sdk suite is 5878/5878 across 154 suites.

On the header name — will do, thanks, and appreciated. I'll open a BRC-105 amendment adding it as an optional field once this lands.

@imranterranode

Copy link
Copy Markdown
Contributor Author

Gentle nudge on the CI gate, and thanks in advance to whoever picks it up.

CI, Conformance and CodeQL are sitting at action_required on 3ec863e. Fork PRs need a maintainer to approve the runs, and the sync with main I pushed earlier today reset the approval from last time — sorry for the repeat ask.

Whenever one of you has a spare minute: @ty-everett @sirdeggen @BraydenLangley @tonesnotes — no preference at all, first available is perfect. No rush.

For context, I think everything else is settled: ty-everett's approval from the 13th still stands, there are no unresolved threads and no conflicts, and the ungated checks are green (Socket Security ×2, SonarCloud quality gate passed). If you're happy to merge once they go green that would save another round, since any further sync with main parks the runs again — but happy to follow whatever process you prefer.

@mo-jaber

Copy link
Copy Markdown
Contributor

@ty-everett @sirdeggen @BraydenLangley @tonesnotes friendly nudge on this

@sirdeggen
sirdeggen marked this pull request as draft September 8, 2026 15:02
@sirdeggen sirdeggen self-assigned this Sep 8, 2026
@sirdeggen
sirdeggen marked this pull request as ready for review September 8, 2026 15:03
@sirdeggen
sirdeggen marked this pull request as draft September 8, 2026 15:09
@sonarqubecloud

sonarqubecloud Bot commented Sep 8, 2026

Copy link
Copy Markdown

@sirdeggen
sirdeggen marked this pull request as ready for review September 8, 2026 17:17
@sirdeggen
sirdeggen merged commit 714c762 into bsv-blockchain:main Sep 8, 2026
37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants