Skip to content

feat(intent): set BIP-322 PSBT_GLOBAL_GENERIC_SIGNED_MESSAGE (0x09) - #252

Open
afsheenb wants to merge 2 commits into
arkade-os:masterfrom
afsheenb:feat/intent-proof-bip322-0x09
Open

feat(intent): set BIP-322 PSBT_GLOBAL_GENERIC_SIGNED_MESSAGE (0x09)#252
afsheenb wants to merge 2 commits into
arkade-os:masterfrom
afsheenb:feat/intent-proof-bip322-0x09

Conversation

@afsheenb

@afsheenb afsheenb commented Jun 29, 2026

Copy link
Copy Markdown

Intent-proof PSBTs never set BIP-322's global 0x09 field (the encoded message being signed). Without it a co-signer cannot recompute the to_spend commitment from PSBT-internal data alone, making it impossible to reliably distinguish a genuine ownership proof from an ordinary fund-moving spend before contributing a partial signature.

Insert the 0x09 global field (type_value=0x09, key=empty) into the proof PSBT inside build_proof_psbt(), using the already-encoded message string that is computed there for the to_spend tagged hash. The field round-trips correctly through PSBT serialize/deserialize.

Add two unit tests: one verifying the field is set to the expected bytes, one verifying it survives a PSBT round-trip. Provides feature parity with arkade-os/ts-sdk#578.

Summary by CodeRabbit

  • New Features
    • Proof-of-funds PSBTs now embed signed-message metadata to improve compatibility with message-verification workflows.
  • Bug Fixes
    • Improved PSBT saving/loading so the embedded message data is preserved during serialize/deserialize.
  • Tests
    • Added unit coverage to verify the embedded signed-message metadata is correctly included and round-trips reliably.

Intent-proof PSBTs never set BIP-322's global 0x09 field (the encoded
message being signed). Without it a co-signer cannot recompute the
to_spend commitment from PSBT-internal data alone, making it impossible
to reliably distinguish a genuine ownership proof from an ordinary
fund-moving spend before contributing a partial signature.

Insert the 0x09 global field (type_value=0x09, key=empty) into the
proof PSBT inside build_proof_psbt(), using the already-encoded message
string that is computed there for the to_spend tagged hash. The field
round-trips correctly through PSBT serialize/deserialize.

Add two unit tests: one verifying the field is set to the expected
bytes, one verifying it survives a PSBT round-trip.
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 81571209-13f0-47c7-ab42-ceb2c13be945

📥 Commits

Reviewing files that changed from the base of the PR and between a3f436c and 720e040.

📒 Files selected for processing (1)
  • ark-core/src/intent.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • ark-core/src/intent.rs

Walkthrough

build_proof_psbt now inserts PSBT global unknown field 0x09 with the encoded IntentMessage bytes. Related intent documentation comments were updated, and new tests verify the field value and PSBT round-trip behavior using a Taproot-backed helper input.

Changes

BIP-322 PSBT Global Field

Layer / File(s) Summary
Intent docs and serialization comments
ark-core/src/intent.rs
Adds and adjusts documentation comments for Input, Intent, make_intent, and IntentMessage, including the message serialization description.
PSBT field insertion and tests
ark-core/src/intent.rs
build_proof_psbt writes the encoded intent message into PSBT global unknown field 0x09; the test module adds a Taproot-backed dummy_input() helper and checks the field value and serialize/deserialize round-trip.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding the BIP-322 PSBT global signed message field to intent PSBTs.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ark-core/src/intent.rs`:
- Around line 636-647: The dummy_input fixture in intent::dummy_input is
inconsistent with its contract: the comment says it should be built from an
OP_TRUE leaf, but the leaf is created with ScriptBuf::new_op_return. Update
dummy_input to construct a trivially spendable tapscript that matches the stated
OP_TRUE behavior, or adjust the helper comment to reflect the actual leaf type
so tests don’t rely on an unspendable fixture.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0a97eb06-ca1a-4efc-9cf3-19b97705956f

📥 Commits

Reviewing files that changed from the base of the PR and between 4e8b696 and a3f436c.

📒 Files selected for processing (1)
  • ark-core/src/intent.rs

Comment thread ark-core/src/intent.rs Outdated
- Add /// doc comments to all top-level public items (Input, Intent,
  make_intent, IntentMessage and their key methods) to bring CodeRabbit
  docstring coverage above the 80% threshold.
- Fix dummy_input: leaf was built with ScriptBuf::new_op_return which
  is unspendable and contradicted the "OP_TRUE leaf" comment; replace
  with ScriptBuf::builder().push_opcode(OP_TRUE).into_script() so the
  fixture matches its stated contract.
@afsheenb

Copy link
Copy Markdown
Author

@coderabbitai I've updated the docstrings and fixed the test snafu. All better?

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Outline =="
ast-grep outline ark-core/src/intent.rs --view expanded || true

echo
echo "== Current diff for ark-core/src/intent.rs =="
git diff -- ark-core/src/intent.rs || true

echo
echo "== Relevant file slices =="
nl -ba ark-core/src/intent.rs | sed -n '1,260p'
echo
nl -ba ark-core/src/intent.rs | sed -n '260,520p'

Length of output: 3067


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Public items and surrounding doc comments =="
cat -n ark-core/src/intent.rs | sed -n '30,210p'

echo
echo "== build_proof_psbt and IntentMessage area =="
cat -n ark-core/src/intent.rs | sed -n '300,520p'

echo
echo "== tests / dummy_input =="
cat -n ark-core/src/intent.rs | sed -n '642,780p'

Length of output: 21140


@afsheenb Yes — this looks addressed to me.

The public-facing docs I was worried about are now in place, and the test helper is consistent again: dummy_input() now builds from an OP_TRUE leaf and uses the matching control block/script pubkey path (Line 649, Line 660, Line 666 through Line 677).

No further concerns from my side on those points.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@arkana-ai-bot arkana-ai-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed ark-core/src/intent.rs at 720e040. Small, focused change; behavior is metadata-only (no consensus/signing effect).

Correctness of the 0x09 insertion

  • psbt.unknown.insert(Key { type_value: 0x09, key: vec![] }, message.as_bytes().to_vec()) at ark-core/src/intent.rs:414-417 is placed after the per-input loop, so it runs exactly once. Good.
  • The bytes match the JSON-encoded message string already used to build the to_spend tagged hash (message shadowed from message.encode()? at intent.rs:330-333), so the field and the commitment are consistent.
  • In rust-bitcoin 0.32.7 (per ark-core/Cargo.toml:17), PSBT global key type 0x09 is unused, so it round-trips through unknown. The build_proof_psbt_0x09_survives_psbt_roundtrip test confirms this.

Cross-repo interop check

  • ts-sdk sets the same field: packages/ts-sdk/src/intent/index.ts:160,314-330 (const PSBT_GLOBAL_GENERIC_SIGNED_MESSAGE = 0x09, empty key, UTF-8 message).
  • arkd writes it too: pkg/ark-lib/intent/proof.go:158-163 (Unknowns = append(..., &psbt.Unknown{Key: []byte{0x09}, Value: []byte(message)})). btcsuite Unknown.Key is the raw key bytes (type byte only, no extra key data), which serializes to length-prefix + 0x09 — byte-identical to rust-bitcoin's Key { type_value: 0x09, key: vec![] }. Interop with the Go implementation is intact.

Server-side consumption — none, and that's fine

  • arkd/pkg/ark-lib/intent/proof.go's Verify(proofB64, message, skip) takes the message as an out-of-band parameter (proof.go:57-87) and never reads the 0x09 field. So this is purely informational metadata for verifiers that don't already have the message; the actual security binding remains the to_spend commitment inside unsigned_tx.input[0].previous_output.txid. Not protocol-critical.

Naming nit

  • "BIP-322 PSBT_GLOBAL_GENERIC_SIGNED_MESSAGE (0x09)" is not something BIP-322 actually defines; it's an Ark ecosystem convention shared across the three SDKs. The naming is copied consistently, so no action needed here — but if a future BIP allocates 0x09 as a real global type, older PSBTs will fail to deserialize on newer library versions. Same risk carried by ts-sdk and arkd; worth being aware of.

Tests

  • Danger's "no test changes" warning is stale: two new tests are added (intent.rs:743-767) covering both the initial insertion and the serialize/deserialize round-trip. dummy_input() correctly builds a taproot input using G's x-coordinate as the internal key, a single OP_TRUE leaf at depth 0, and matching control block — sufficient for the metadata check. Only IntentMessage::Delete is exercised, but since 0x09 handling doesn't branch on the variant, that's acceptable.
  • No test asserts that make_intent() (the public wrapper) also propagates the field. It does today because it doesn't mutate psbt.unknown, but a light regression guard there wouldn't hurt if you want defense in depth.

Doc comments

  • The doc additions on Input, Input::new, Intent, Intent::new, Intent::serialize_proof, Intent::serialize_message, make_intent, and IntentMessage/IntentMessage::encode are accurate. No API surface changes.

No blockers. LGTM.

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.

3 participants