refactor(bounty): drop bodyHash, sign body fields directly#868
Merged
Conversation
Aligns Create + Submit with the rest of the codebase. Every other
signed-action endpoint (/api/outbox, /api/heartbeat, /api/vouch,
/api/inbox mark-read, /api/challenge) signs a plain-text message with
the full content inlined. Bounty was the only place using a
sha256-of-canonical-JSON bodyHash, which forced every client (and any
future MCP tool) to do an extra hashing step that nothing else needs.
New signed messages:
AIBTC Bounty Create | {posterBtc} | {title} | {description} | {rewardSats} | {expiresAt} | {tagsCommaJoined} | {signedAt}
AIBTC Bounty Submit | {bountyId} | {submitterBtc} | {message} | {contentUrl} | {signedAt}
tagsCommaJoined = tags.join(",") or "" when no tags.
contentUrl = "" when omitted.
Accept / Paid / Cancel signed messages unchanged (already plain).
Trust model is preserved: all body fields are part of the signed
message, so any tampering with title / description / reward / expiry
/ tags / submission text breaks the signature. Same precedent as
/api/outbox signing the full reply body.
Drops:
- canonicalJSON, bodyHash from lib/bounty/signatures.ts
- bodyHash import in /api/bounties POST + /api/bounties/[id]/submit POST
- bodyHash references from openapi.json, docs/bounties.txt, /bounty/new
Tests:
- signatures.test.ts: drop canonicalJSON + bodyHash suites; add
coverage for new Create + Submit formats (including empty tags and
empty contentUrl)
- bounty suite: 41 tests pass, no other changes needed
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
Aligns bounty Create + Submit signing with the rest of the codebase. Every other signed-action endpoint (
/api/outbox,/api/heartbeat,/api/vouch,/api/inboxmark-read,/api/challenge) signs a plain-text message with the full content inlined. Bounty was the only place using asha256(canonicalJSON(...))bodyHash, which forced every client (and any future MCP tool) to do an extra hashing step that nothing else in the platform needs.New signed messages
tagsCommaJoined=tags.join(",")or""when no tagscontentUrl=""when omittedAccept / Paid / Cancel messages are unchanged (they were already plain — Accept/Paid/Cancel did not use bodyHash even before this PR).
Trust model preserved
All body fields are part of the signed message, so any tampering with
title/description/rewardSats/expiresAt/tags/ submissionmessage/contentUrlbreaks the signature. Same precedent as/api/outboxsigning the full reply body up to 1000 chars; bounty descriptions are up to 4000 chars but agents sign programmatically via MCPbtc_sign_message, not a human-readable wallet popup, so message length isn't a UX concern.Why now
The bounty system merged a few hours ago in #843. The
bountiestable on prod is empty (no posted bounties), so changing the signing contract is safe with zero migration burden. After this lands, the MCP server can ship a one-callbounty_create_nativetool: build the message string →btc_sign_message→POST /api/bounties. No hash. Same pattern as every other signed action.Files touched
lib/bounty/signatures.tscanonicalJSON+bodyHash. UpdatebuildCreateMessage+buildSubmitMessagesignatures.lib/bounty/constants.tsSIGNATURE_MESSAGE_FORMATS.CREATE+SUBMITtemplates.lib/bounty/index.tscanonicalJSON+bodyHash.lib/bounty/validation.tsbodyHashwas never a request field, only a hint).app/api/bounties/route.tsbodyHashimport. Update self-doc envelope.app/api/bounties/[id]/submit/route.tsapp/api/openapi.json/route.tsapp/docs/[topic]/route.tsapp/bounty/new/page.tsxlib/bounty/__tests__/signatures.test.tscanonicalJSON+bodyHashsuites. Add 4 new assertions for Create + Submit formats, including empty-tags and empty-contentUrl segments.Verification
Plus repo-wide
grep -rn "bodyHash\|canonicalJSON"returns zero hits.Test plan
btc_sign_message→curl POST /api/bounties— to verify prod accepts the new formataibtc-mcp-serverto addbounty_create_native/bounty_submit_nativetools pointing at aibtc.com (replacing the drx4-targeted ones)