fix(sdk): BRC-104 response preimage encodes an empty body as 0 instead of -1, so every bodyless signed response fails verification - #550
Open
E-Jacko wants to merge 1 commit into
Conversation
BRC-104 §6.7.3 requires an absent or empty body to be encoded as a length of -1, and §6.9 lists the response signature preimage's final field as 'Body length + body bytes (or -1 if none)'. AuthFetch's own request side already implements this (writeRequestBody and writeOptionalText both write -1), but writeGeneralResponsePayload encoded an empty body as 0 with no -1 branch. A conforming counterparty therefore signs a preimage this client can never reproduce, so every signed response with no body — a bare 404, 204 or empty 401/403 — fails signature verification with 'Signature is not valid'. Regression tests pin the terminal varint to exactly -1 with nothing following it for an empty body, and to true length plus bytes otherwise; the empty-body case fails against the previous encoding. AuthFetch's response reader already treats a non-positive length as no body, so verified traffic is unchanged apart from now verifying. Signed-off-by: Elis Jackson <elisjackson@icloud.com>
E-Jacko
requested review from
BraydenLangley,
sirdeggen,
tonesnotes and
ty-everett
as code owners
September 21, 2026 10:12
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
SimplifiedFetchTransport.writeGeneralResponsePayloadencodes an empty HTTP response body as varint0. BRC-104 requires-1. A conforming counterparty therefore signs a preimage this client cannot reproduce, and every signed response with no body fails verification — a bare404,204, or empty401/403all die withSignature is not valid.What the spec requires
BRC-104 §6.7.3: "If the body is empty, specify a length of
-1in the payload."BRC-104 §6.9, on HTTP response messages: "Body length + body bytes (or
-1if none)."This repo's own
specs/auth/brc103-mutual-auth.yamlresponse-payload table already statesVarInt(len) or VarInt(-1); only the code and one summary line (also fixed here) disagreed.What the code did
bodyis always an array (Array.from(new Uint8Array(await response.arrayBuffer()))), so an empty body reached this as length0and there was no-1branch. The absent-value paths on the request side (AuthFetch.writeRequestBody,writeOptionalText) already write-1, and this package's ownauth-express-middlewaresigns empty responses with-1(buildResponsePayload), so the two halves of this repo could not verify each other's bodyless responses.Independent confirmation against a conforming server
A conforming Rust implementation (
bsv-auth-axum-middleware,serialize_response_payload) writes-1for an empty body. Run end to end against such a server, an authenticated caller receiving a correctly signed bodyless404gets:Everything else in the same run verifies — non-empty signed responses round-trip fine — so the failure is isolated to the empty-body encoding. Also reproducible standalone against the published 2.6.1 artifact: the terminal varint of the response preimage for an empty body is
0with no trailing bytes.The fix
Encode
-1for an empty body, mirroring the middleware's response side.AuthFetch.parseAuthenticatedResponsealready treats a non-positive length as "no body", so verified traffic parses identically — it just verifies now.Regression tests pin the terminal varint to exactly
-1with nothing following it (with and without a preceding request id), and to true length plus bytes for a non-empty body. Checked against the pre-fix encoding: the empty-body assertions fail on the unfixed code (0 !== -1), so a reversion cannot stay green.Compatibility note
A server that "fixed" this by signing
0instead would move every conforming implementation off the spec — the correct side to change is this client's response verification preimage, which this PR does. Deployed pairs of this SDK talking toauth-express-middlewareare unaffected for non-empty bodies (unchanged) and for empty bodies were already failing.Sibling defect found while verifying this fix — deliberately NOT fixed here
The present-but-empty request body has the same class of bug on both sides of this repo, and the two currently agree with each other on the non-conforming encoding, so they must be fixed together, not in this PR:
AuthFetch.writeRequestBody'sif (!body)guard is truthiness-based, sobody: new Uint8Array(0)or[]skips the-1branch and signs0;auth-express-middleware'swriteBodyToWriterreconstructs the request preimage the same way (Buffer.alloc(0)from an empty raw body writes0, never reaching its-1fallback).In-repo client↔server traffic matches today because both are wrong the same way; either one fixed alone would break the other, and both together are non-conformant against third parties. Happy to follow up with a coordinated PR for that pair if you want it.