Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@louisinger
Copy link
Collaborator

@louisinger louisinger commented Nov 10, 2025

This PR is splitting VerifyAndCombinePartialTx into :

  • VerifyBoardingTapscriptSigs : verify boarding inputs signatures (+ add control block validation)
  • CombineTapscriptSigs : move signed inputs tapscript sigs to the commiment tx

It aims to move signature verification before locking the commitment tx mutex. This way, an invalid tx can't block the batch session.

it renames "VerifyTapscriptPartialSigs" to "VerifyVtxoTapscriptSigs".
it removes CountSignedTaprootInputs : VerifyBoardingTapscriptSigs returns the count of signed inputs

add generic functions VerifyTapscriptSigs and GetPrevOutputFetcher to txutil package in arklib.

@altafan please review

Summary by CodeRabbit

  • New Features

    • Added public utilities for Taproot prevout fetching and signature verification to support enhanced validation.
  • Refactor

    • Overhauled Taproot signature verification and signature-combining flows, introduced boarding-input verification, and centralized prevout handling to improve validation and error reporting.
  • Tests

    • Increased a sleep delay in an exit test and shortened an explorer polling interval to improve test stability.
  • Chores

    • Updated interfaces and test mocks to align with the new verification and combination approach.

@louisinger louisinger requested a review from altafan November 10, 2025 23:48
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 10, 2025

Walkthrough

Replaces per-input PSBT tapscript verification with vtxo-aware verification and boarding-input checks, adds combine-by-index API, delegates prevout fetching to txutils, updates service call sites and mocks, and tweaks two tests/config timings.

Changes

Cohort / File(s) Summary
Service changes
internal/core/application/service.go
Switched calls from VerifyTapscriptPartialSigsVerifyVtxoTapscriptSigs; SignCommitmentTx reads commitmentTx read-only, calls VerifyBoardingTapscriptSigs (returns signed input indices), errors with INVALID_BOARDING_INPUT_SIG when none, and uses CombineTapscriptSigs to produce combined tx.
TxBuilder interface
internal/core/ports/tx_builder.go
Renamed VerifyTapscriptPartialSigsVerifyVtxoTapscriptSigs; removed CountSignedTaprootInputs; replaced VerifyAndCombinePartialTx(dest, src) with CombineTapscriptSigs(dest, src, inputIndexes []int); added VerifyBoardingTapscriptSigs(txToVerify, commitmentTx) ([]int, error).
TxBuilder implementation (covenantless)
internal/infrastructure/tx-builder/covenantless/builder.go
Implemented VerifyVtxoTapscriptSigs, VerifyBoardingTapscriptSigs, and CombineTapscriptSigs; replaced local prevout helper with txutils.GetPrevOutputFetcher; updated combine logic to use explicit indexes and txid checks; removed getPrevOutputFetcher.
txutils / Taproot utilities
pkg/ark-lib/txutils/utils.go
Added VerifyTapscriptSigs(tx *psbt.Packet, prevoutFetcher txscript.PrevOutputFetcher) ([]int, error) and GetPrevOutputFetcher(tx *psbt.Packet) (txscript.PrevOutputFetcher, error) implementing control-block parsing, schnorr verification, and prevout-based lookup.
Intent / proof helper
pkg/ark-lib/intent/proof.go
Removed internal getPrevoutFetcher() and replaced usage with txutils.GetPrevOutputFetcher(ptx).
Mocks / live store tests
internal/infrastructure/live-store/live_store_test.go
Updated mocked TxBuilder methods to VerifyVtxoTapscriptSigs, CombineTapscriptSigs(dest, src, indexes), and added VerifyBoardingTapscriptSigs(txToVerify, commitmentTx) ([]int, error).
E2E tests / test utils
internal/test/e2e/e2e_test.go, internal/test/e2e/utils_test.go
Increased sleep in TestUnilateralExit from 5s → 10s; reduced ExplorerPollInterval in init args from 2s → 1s.

Sequence Diagram(s)

sequenceDiagram
    participant S as Service
    participant B as TxBuilder
    participant U as txutils

    rect rgb(250,250,255)
    Note over S: SignCommitmentTx (read-only commitmentTx)
    S->>B: VerifyBoardingTapscriptSigs(partialTx, commitmentTx)
    B->>U: GetPrevOutputFetcher(commitmentTx)
    B->>U: VerifyTapscriptSigs(partialPSBT, prevoutFetcher)
    U-->>B: []int (signed input indices)
    alt no signed inputs
        B-->>S: error INVALID_BOARDING_INPUT_SIG
    else
        S->>B: CombineTapscriptSigs(dest, src, indexes)
        B-->>S: combinedTx
    end
    end

    rect rgb(245,255,245)
    Note over S: Offchain verification (ark/checkpoint)
    S->>B: VerifyVtxoTapscriptSigs(tx, mustIncludeSignerSig)
    B->>U: GetPrevOutputFetcher(...) / VerifyTapscriptSigs(...)
    U-->>B: ([]int, *psbt.Packet)
    B-->>S: verification result
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Review focus:
    • Cryptographic correctness in VerifyTapscriptSigs (control-block parsing, taproot key derivation, schnorr signature verification).
    • Correctness and error cases in GetPrevOutputFetcher (assumptions about WitnessUtxo presence).
    • Index mapping, bounds checking, and txid validation in CombineTapscriptSigs.
    • Concurrency/locking implications of read-only access to commitmentTx in SignCommitmentTx.
    • Tests and mocks alignment with new method signatures and return shapes.

Possibly related PRs

Suggested reviewers

  • altafan
  • Kukks
  • sekulicd

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'SignCommitmentTx improvements' refers to a real part of the changeset (SignCommitmentTx flow modifications), but it does not capture the main point of the PR—which is splitting VerifyAndCombinePartialTx into two distinct operations and refactoring signature verification across multiple flows.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/ark-lib/txutils/utils.go (1)

31-36: Fix the witness loop to keep the build green

for i := range witCount doesn’t compile because range can’t iterate over a scalar (witCount is a uint64). Use an indexed loop so the code builds.

-	for i := range witCount {
-		witness[i], err = wire.ReadVarBytes(r, 0, txscript.MaxScriptSize, "witness")
+	for i := uint64(0); i < witCount; i++ {
+		witness[i], err = wire.ReadVarBytes(r, 0, txscript.MaxScriptSize, "witness")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e2cb3f5 and 2077f5f.

📒 Files selected for processing (7)
  • internal/core/application/service.go (4 hunks)
  • internal/core/ports/tx_builder.go (1 hunks)
  • internal/infrastructure/live-store/live_store_test.go (2 hunks)
  • internal/infrastructure/tx-builder/covenantless/builder.go (3 hunks)
  • internal/test/e2e/e2e_test.go (1 hunks)
  • internal/test/e2e/utils_test.go (1 hunks)
  • pkg/ark-lib/txutils/utils.go (3 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: louisinger
Repo: arkade-os/arkd PR: 691
File: internal/core/application/service.go:557-562
Timestamp: 2025-08-19T10:58:41.042Z
Learning: In the arkd SubmitOffchainTx method, using the checkpoint PSBT input's tapscript (forfeit path) for the VtxoInput.Tapscript field is the correct behavior, not a bug as initially thought. The system correctly handles the relationship between checkpoint inputs and Ark transaction inputs.
📚 Learning: 2025-08-19T10:58:41.042Z
Learnt from: louisinger
Repo: arkade-os/arkd PR: 691
File: internal/core/application/service.go:557-562
Timestamp: 2025-08-19T10:58:41.042Z
Learning: In the arkd SubmitOffchainTx method, using the checkpoint PSBT input's tapscript (forfeit path) for the VtxoInput.Tapscript field is the correct behavior, not a bug as initially thought. The system correctly handles the relationship between checkpoint inputs and Ark transaction inputs.

Applied to files:

  • internal/core/ports/tx_builder.go
  • pkg/ark-lib/txutils/utils.go
  • internal/core/application/service.go
  • internal/infrastructure/tx-builder/covenantless/builder.go
  • internal/infrastructure/live-store/live_store_test.go
📚 Learning: 2025-09-29T14:33:52.871Z
Learnt from: louisinger
Repo: arkade-os/arkd PR: 722
File: pkg/ark-lib/intent/proof.go:52-58
Timestamp: 2025-09-29T14:33:52.871Z
Learning: In btcsuite/btcd's psbt package, the NewFromRawBytes function's boolean parameter (b64) automatically handles base64 decoding when set to true, so passing a base64 string via strings.NewReader with b64=true is the correct usage pattern.

Applied to files:

  • pkg/ark-lib/txutils/utils.go
📚 Learning: 2025-09-29T14:33:52.871Z
Learnt from: louisinger
Repo: arkade-os/arkd PR: 722
File: pkg/ark-lib/intent/proof.go:52-58
Timestamp: 2025-09-29T14:33:52.871Z
Learning: In btcsuite/btcd's psbt package, the NewFromRawBytes function's boolean parameter (b64) automatically handles base64 decoding when set to true, so passing a base64 string via strings.NewReader with b64=true is the correct usage pattern. A common bug is manually base64-decoding the string and then passing b64=true, which causes a double-decode error.

Applied to files:

  • pkg/ark-lib/txutils/utils.go
🧬 Code graph analysis (3)
pkg/ark-lib/txutils/utils.go (1)
pkg/ark-lib/intent/proof.go (1)
  • Verify (53-128)
internal/core/application/service.go (2)
pkg/errors/errors.go (2)
  • INVALID_BOARDING_INPUT_SIG (316-320)
  • InvalidBoardingInputSigMetadata (186-188)
pkg/ark-lib/intent/proof.go (1)
  • New (136-160)
internal/infrastructure/tx-builder/covenantless/builder.go (1)
pkg/ark-lib/txutils/utils.go (2)
  • GetPrevOutputFetcher (148-161)
  • VerifyTapscriptSigs (43-145)
🔇 Additional comments (4)
internal/test/e2e/e2e_test.go (1)

264-264: Verify the timeout increase is necessary and not masking a performance regression.

The timeout increase from 5 to 10 seconds in commit 6934426 ("Improve SignCommitmentTx") corresponds to a significant refactoring that split signature verification and combination into separate steps. While this change may require additional processing time, confirm whether:

  1. This 2x timeout increase was measured and validated as necessary for the refactored verification logic
  2. The separate verification step (VerifyBoardingTapscriptSigs followed by CombineTapscriptSigs) adds proportional overhead or indicates a performance regression
  3. A polling mechanism (checking balance state until complete or timeout) would be more reliable than a fixed sleep
internal/infrastructure/tx-builder/covenantless/builder.go (3)

52-61: LGTM! Clear rename improves API semantics.

The rename from VerifyTapscriptPartialSigs to VerifyVtxoTapscriptSigs better communicates that this method specifically verifies VTXO tapscript signatures, aligning with the PR's goal to distinguish between different verification contexts.


72-75: LGTM! Good consolidation to shared utility.

Moving to txutils.GetPrevOutputFetcher centralizes prevout fetching logic, improving code reuse and consistency across the codebase.


1017-1039: LGTM! Strong security approach using commitment tx for prevouts.

This method correctly uses the commitmentTx (built by the builder) to derive prevouts for signature verification, preventing attackers from manipulating prevouts in txToVerify to produce valid-looking but fraudulent signatures. The explicit comment at lines 1031-1032 documents this important security property.

Co-authored-by: Pietralberto Mazza <[email protected]>
Signed-off-by: louisinger <[email protected]>
Co-authored-by: Pietralberto Mazza <[email protected]>
Signed-off-by: louisinger <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
pkg/ark-lib/txutils/utils.go (1)

108-117: Fix the function name typo.

The function txscript.CalcTapscriptSignaturehash (lowercase "h") doesn't exist in btcsuite/btcd. The correct function name is CalcTapscriptSignatureHash (capital "H").

Apply this diff:

-			sighash, err = txscript.CalcTapscriptSignaturehash(
+			sighash, err = txscript.CalcTapscriptSignatureHash(
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dccaf06 and f1ef263.

📒 Files selected for processing (1)
  • pkg/ark-lib/txutils/utils.go (3 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: louisinger
Repo: arkade-os/arkd PR: 691
File: internal/core/application/service.go:557-562
Timestamp: 2025-08-19T10:58:41.042Z
Learning: In the arkd SubmitOffchainTx method, using the checkpoint PSBT input's tapscript (forfeit path) for the VtxoInput.Tapscript field is the correct behavior, not a bug as initially thought. The system correctly handles the relationship between checkpoint inputs and Ark transaction inputs.
📚 Learning: 2025-08-19T10:58:41.042Z
Learnt from: louisinger
Repo: arkade-os/arkd PR: 691
File: internal/core/application/service.go:557-562
Timestamp: 2025-08-19T10:58:41.042Z
Learning: In the arkd SubmitOffchainTx method, using the checkpoint PSBT input's tapscript (forfeit path) for the VtxoInput.Tapscript field is the correct behavior, not a bug as initially thought. The system correctly handles the relationship between checkpoint inputs and Ark transaction inputs.

Applied to files:

  • pkg/ark-lib/txutils/utils.go
📚 Learning: 2025-09-29T14:33:52.871Z
Learnt from: louisinger
Repo: arkade-os/arkd PR: 722
File: pkg/ark-lib/intent/proof.go:52-58
Timestamp: 2025-09-29T14:33:52.871Z
Learning: In btcsuite/btcd's psbt package, the NewFromRawBytes function's boolean parameter (b64) automatically handles base64 decoding when set to true, so passing a base64 string via strings.NewReader with b64=true is the correct usage pattern.

Applied to files:

  • pkg/ark-lib/txutils/utils.go
📚 Learning: 2025-09-29T14:33:52.871Z
Learnt from: louisinger
Repo: arkade-os/arkd PR: 722
File: pkg/ark-lib/intent/proof.go:52-58
Timestamp: 2025-09-29T14:33:52.871Z
Learning: In btcsuite/btcd's psbt package, the NewFromRawBytes function's boolean parameter (b64) automatically handles base64 decoding when set to true, so passing a base64 string via strings.NewReader with b64=true is the correct usage pattern. A common bug is manually base64-decoding the string and then passing b64=true, which causes a double-decode error.

Applied to files:

  • pkg/ark-lib/txutils/utils.go
🧬 Code graph analysis (1)
pkg/ark-lib/txutils/utils.go (1)
pkg/ark-lib/intent/proof.go (1)
  • Verify (53-128)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: integration tests
  • GitHub Check: unit tests
🔇 Additional comments (3)
pkg/ark-lib/txutils/utils.go (3)

31-31: LGTM: Idiomatic loop refactor.

The change to a range-based loop is idiomatic and correct.


41-145: Well-structured tapscript verification logic.

The function correctly implements tapscript signature verification:

  • Validates control blocks against expected Taproot keys
  • Skips inputs appropriately (non-taproot, unsigned, or without leaf scripts)
  • Properly computes leaf hashes and signature hashes
  • Verifies Schnorr signatures
  • Provides descriptive error messages

The logic is sound once the function name typo at line 109 is fixed.


147-161: LGTM: Correct prevout fetcher implementation.

The function correctly builds a prevout fetcher from PSBT WitnessUtxo fields with appropriate error handling for missing witness data.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
pkg/ark-lib/txutils/utils.go (2)

14-18: Handle the error from ParsePubKey (already flagged).

This issue was already identified in a previous review: the error from ParsePubKey is silently discarded, and if parsing fails, unspendableKey will be nil, causing a nil-pointer dereference at line 83.


108-117: Call the correct function name (already flagged).

This critical issue was already identified: txscript.CalcTapscriptSignaturehash (lowercase "h") doesn't exist. The correct function is txscript.CalcTaprootSignatureHash (capital "H").

🧹 Nitpick comments (1)
pkg/ark-lib/txutils/utils.go (1)

123-145: Consider consistent error wrapping.

The signature parsing and verification logic is correct. As suggested in previous reviews, you might want to consistently use %w instead of %s for error wrapping (lines 126, 134) to preserve the error chain, though this is a minor stylistic improvement.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 68441c0 and 4d089e0.

📒 Files selected for processing (1)
  • pkg/ark-lib/txutils/utils.go (3 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: louisinger
Repo: arkade-os/arkd PR: 691
File: internal/core/application/service.go:557-562
Timestamp: 2025-08-19T10:58:41.042Z
Learning: In the arkd SubmitOffchainTx method, using the checkpoint PSBT input's tapscript (forfeit path) for the VtxoInput.Tapscript field is the correct behavior, not a bug as initially thought. The system correctly handles the relationship between checkpoint inputs and Ark transaction inputs.
📚 Learning: 2025-08-19T10:58:41.042Z
Learnt from: louisinger
Repo: arkade-os/arkd PR: 691
File: internal/core/application/service.go:557-562
Timestamp: 2025-08-19T10:58:41.042Z
Learning: In the arkd SubmitOffchainTx method, using the checkpoint PSBT input's tapscript (forfeit path) for the VtxoInput.Tapscript field is the correct behavior, not a bug as initially thought. The system correctly handles the relationship between checkpoint inputs and Ark transaction inputs.

Applied to files:

  • pkg/ark-lib/txutils/utils.go
📚 Learning: 2025-09-29T14:33:52.871Z
Learnt from: louisinger
Repo: arkade-os/arkd PR: 722
File: pkg/ark-lib/intent/proof.go:52-58
Timestamp: 2025-09-29T14:33:52.871Z
Learning: In btcsuite/btcd's psbt package, the NewFromRawBytes function's boolean parameter (b64) automatically handles base64 decoding when set to true, so passing a base64 string via strings.NewReader with b64=true is the correct usage pattern.

Applied to files:

  • pkg/ark-lib/txutils/utils.go
📚 Learning: 2025-09-29T14:33:52.871Z
Learnt from: louisinger
Repo: arkade-os/arkd PR: 722
File: pkg/ark-lib/intent/proof.go:52-58
Timestamp: 2025-09-29T14:33:52.871Z
Learning: In btcsuite/btcd's psbt package, the NewFromRawBytes function's boolean parameter (b64) automatically handles base64 decoding when set to true, so passing a base64 string via strings.NewReader with b64=true is the correct usage pattern. A common bug is manually base64-decoding the string and then passing b64=true, which causes a double-decode error.

Applied to files:

  • pkg/ark-lib/txutils/utils.go
🧬 Code graph analysis (1)
pkg/ark-lib/txutils/utils.go (1)
pkg/ark-lib/intent/proof.go (1)
  • Verify (53-128)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build and Scan
  • GitHub Check: unit tests
  • GitHub Check: integration tests
🔇 Additional comments (4)
pkg/ark-lib/txutils/utils.go (4)

31-31: LGTM: Idiomatic range loop.

The change to for i := range witCount is cleaner and more idiomatic Go.


41-96: LGTM: Solid validation logic.

The input validation flow is well-structured:

  • Validates PSBT/tx input count consistency
  • Correctly filters for single taproot leaf script inputs
  • Validates prevout existence and type
  • Properly derives and validates the taproot output key using the control block
  • Provides clear error messages with context

147-151: LGTM: Clean return handling.

The signed input tracking and return logic is correct.


153-167: LGTM: Clean prevout fetcher utility.

The implementation correctly builds a MultiPrevOutFetcher from PSBT WitnessUtxo fields with appropriate error handling for missing witness data. This aligns well with the usage pattern shown in intent/proof.go.

@altafan altafan merged commit b50d6cd into arkade-os:master Nov 11, 2025
5 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Nov 11, 2025
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.

2 participants