Skip to content

test(htlc): assert the swap tree against the SDK/maker golden vectors - #20

Open
Arshia-r-m wants to merge 1 commit into
fix/swap-signing-guardsfrom
test/htlc-golden-vectors
Open

Arshia-r-m wants to merge 1 commit into
fix/swap-signing-guardsfrom
test/htlc-golden-vectors

Conversation

@Arshia-r-m

Copy link
Copy Markdown
Member

Problem

src/wallet/test/htlc_swap_tree.rs builds the Boltz swap tree by hand. swap_tree_leaves_match_boltz_layout only checks it against our own reading of the Boltz layout. Nothing ties rgb-lib's leaf encoders, MuSig2 key order or recipient-id encoder to what the maker actually returns, which is what the SDK checks against (kaleidoswap-sdk/tests/fixtures/rgb-v1, maker PR #668). If any of the three sides drifted, we would only find out in a live swap.

Closes #16

Changes

  • Vendored fixture: tests/fixtures/kaleidoswap-sdk/rgb-v1/htlc-golden-vectors.json, plus a README.md with its provenance.
    • Source: kaleidoswap/kaleidoswap-sdk, tests/fixtures/rgb-v1/wire-contract.json and rgb-golden-vectors.json.
    • Pinned at 3b034ac7fefe7a3388ba5fefb1d52fe33b1dce0a, the last commit touching both files. trunk was at 660779a3c1ec9f0bb7c32c9d9cdb276b7cbaff86 when fetched on 2026-10-14.
    • The fixture's _source key records the upstream blob ids and SHA-256 hashes.
    • It is a subset with values unchanged: provenance, the preimage, both swaps' keys, swapTree, timeout, lockup address and rgb.{recipientId,scriptPubkey}, and all 12 recipient vectors.
  • New tests: a sdk_golden_vectors module in htlc_swap_tree.rs with three plain #[test]s. None of them calls initialize().
    • htlc_swap_tree_leaves_match_sdk_golden_vectors: claim_leaf_script and refund_leaf_script must reproduce the maker's claim and refund leaves for both swaps. It also checks the leaf version (0xc0) and that the preimage hashes to the reverse request's preimageHash.
    • htlc_swap_tree_output_key_matches_sdk_golden_vectors:
      • The MuSig2 key aggregate in Boltz order (claim-first for submarine, refund-first for reverse), tweaked by the tree from swap_tree_spend_info, must give the vector's output key, scriptPubkey and lockup address.
      • The reversed order must not give that key.
      • Both tapleaf hashes must sit at depth 1, combine to the merkle root, and be committed by the vector output key (checked through the control blocks).
    • htlc_recipient_ids_match_sdk_golden_vectors: production recipient_id_from_script_buf must match all 12 vectors (both HTLC scripts × bc/tb3/tb4/sb/sbc/bcrt) and round-trip through script_buf_from_recipient_id. It must also match the recipient ids in both create responses.
  • Refactor: swap_tree_spend_info() is factored out of SwapTreeHtlc::new without changing behavior. The integration tests and the golden tests now share one tree builder.
  • Dev-dependency: secp256k1_musig = { package = "secp256k1", version = "=0.32.0-beta.2" }, used for libsecp256k1's BIP327 key aggregation. It was already in Cargo.lock through rgb-consensus, so the lockfile gains only one dependency edge and no new crate.

Tests

  • SKIP_INIT=1 cargo test --all-features --lib sdk_golden_vectors: 3 passed, in 0.01s.
  • No services needed: the same 3 tests pass with all network access blocked (sandbox-exec -p '(version 1)(allow default)(deny network*)' <test binary> sdk_golden_vectors).
  • Mutation checks: each one failed the expected test and was then reverted.
    • Fixture byte: submarine refund-leaf timeout 029000 changed to 029100. The leaves test and the output-key test both fail; the output key becomes 16ca7d… instead of a0649c….
    • Fixture byte: one character of the tb4 recipient id changed. The recipient-id test fails.
    • Production encoder: recipient_id_from_script_buf changed to ignore its network argument. The recipient-id test fails (bcrt: returned where bc: was expected).
    • Test-tree encoder: refund_leaf_script changed from OP_CLTV to OP_CSV. The leaves test fails.
  • cargo fmt --check and cargo clippy --lib --workspace --all-features --all-targets -- -D warnings: clean.
  • SKIP_INIT=1 cargo test --all-features htlc: 5 passed, 7 failed.
    • The 5 service-free tests pass: the 3 new ones plus swap_tree_leaves_match_boltz_layout and swap_tree_witnesses_verify.
    • All 7 regtest-backed tests fail at wallet setup (go_online) with InvalidIndexer { details: "not a valid electrum nor esplora server" }, before any HTLC code runs.
    • Electrum on 127.0.0.1:50001/50002 and bitcoind on 18443 were not listening during this run; the shared services lost state when the dev volume was unmounted. These tests need a rerun once the services are back. The only change in their code path is the swap_tree_spend_info refactor, which makes the same TaprootBuilder calls.

Notes

  • Reverse claim leaf mismatch (real finding): the maker's reverse claim leaf starts with Boltz's preimage-size check, OP_SIZE 32 OP_EQUALVERIFY (82 01 20 88). rgb-lib's claim_leaf_script only builds the submarine form, and the integration tests only exercise that form. The golden test asserts that the reverse leaf equals this guard followed by claim_leaf_script(...), so the gap is pinned rather than hidden. Neither the builder nor the vectors were changed to make them agree.
  • No production swap-tree or MuSig2 code: rgb-lib has none. The swap tree is built only in test code, and rgb-lib only colors and signs HTLC spends. The leaf and tree assertions therefore cover the test builders the integration tests rely on. MuSig2 key aggregation comes from libsecp256k1 in the dev-dependency, not from rgb-lib. The recipient-id assertions do exercise production code.
  • Tapleaf hashes: upstream does not list them. They are checked through the vector output key (control blocks and merkle root) rather than pinned as hex.
  • Fields without an rgb-lib counterpart: these were not vendored. They are the pair responses, invoices, amounts, blinding values, the reference taker claim PSBT (maker-layer-rgb::build_taker_claim_psbt) and the fee model.
  • Feature gate: htlc_swap_tree is gated on the electrum feature, a default feature, so the new tests build in default and --all-features runs.

🤖 Generated with Claude Code

htlc_swap_tree.rs builds the Boltz swap tree by hand and only checked it
against its own reading of the Boltz layout. Nothing tied rgb-lib's leaf
encoders, MuSig2 key order and recipient-id encoder to the trees and
recipient ids the maker actually returns, and that the SDK checks against.
Drift on any side would only show up in a live swap.

Vendor the relevant subset of kaleidoswap-sdk's rgb-v1 fixtures
(wire-contract.json and rgb-golden-vectors.json at 3b034ac, trunk 660779a)
and add three service-free tests:

- the claim and refund leaves built by claim_leaf_script and
  refund_leaf_script match the maker's leaves. The reverse claim leaf
  carries Boltz's OP_SIZE 32 OP_EQUALVERIFY guard, which the test builder
  does not emit; the test pins that difference;
- MuSig2 aggregation (libsecp256k1) in Boltz order, claim-first for
  submarine and refund-first for reverse, tweaked by the tree from
  swap_tree_spend_info, gives the vector output key and lockup address.
  The other order does not, and each tapleaf hash is committed by that
  output key;
- recipient_id_from_script_buf matches all 12 recipient vectors (both
  HTLC scripts on all six RGB networks) and the create-response ids.

swap_tree_spend_info is factored out of SwapTreeHtlc::new so the
integration tests and the golden test share one tree builder.

Closes #16

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Arshia-r-m

Copy link
Copy Markdown
Member Author

Regtest results (services restored, 2026-10-15)

  • SKIP_INIT=1 cargo test --all-features htlc: 12 passed, 0 failed. That includes the 7 regtest-backed tests that couldn't run during the outage.

🤖 Generated with Claude Code

@Arshia-r-m
Arshia-r-m marked this pull request as ready for review October 15, 2026 08:43
@Arshia-r-m Arshia-r-m added code-review Run the automated code review on this PR security-review Run the automated security review on this PR labels Oct 15, 2026
@kaleido-security-review

Copy link
Copy Markdown

🔐 Automated security review

No security findings for this revision.

This PR is test-only: it vendors golden-vector fixtures, adds service-free tests asserting the test-built swap tree, MuSig2 key order and the production recipient-id encoder against maker/SDK vectors, and factors out a shared tree builder. No production code paths that move funds, handle keys, or accept untrusted input are changed, and the vendored fixture contains only mock-capture test data (a synthetic preimage and throwaway keys), so the change introduces no exploitable security weakness.

@cf/zai-org/glm-5.3 · 37,188 in / 1,498 out tokens (≈ $0.06) · 4 files · commit 25e755348944 · report gh-1019482487-20-25e755348944
AI-generated: verify before acting. A clean result is not an audit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

code-review Run the automated code review on this PR security-review Run the automated security review on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant