Skip to content

Conversation

@yyforyongyu
Copy link
Collaborator

This PR fixes #1035 - the old design has evolved here, detailed below.

Initial State: Fragmented and Rigid Methods

In the original monolithic wallet.Interface, PSBT-related methods (FundPsbt, SignPsbt, FinalizePsbt) were mixed in with general transaction and account management methods. This had several drawbacks:

  1. Hidden Workflow: The PSBT lifecycle (Fund -> Decorate -> Sign -> Combine -> Finalize) is a distinct, multi-step process. Scattering these methods obscured this workflow, making it harder for developers to understand the correct sequence of operations.
  2. Rigid Signatures: The original method signatures relied on long lists of primitive arguments (e.g., FundPsbt taking explicit minConfs, feeRate, account, keyScope, strategy). This made the API brittle; adding a new funding option (like a specific coin selection strategy or a change output policy) would require breaking changes.
  3. Missing Functionality: The wallet lacked a dedicated CombinePsbt method, forcing users to rely on external tools or ad-hoc merging logic for collaborative signing flows (like Multisig or CoinJoin).

The Design Evolution: A Cohesive, Extensible Workflow

The PsbtManager was designed to solve these issues by grouping all PSBT operations into a single, cohesive interface and adopting extensible parameter objects.

Insight 1: Workflow Cohesion and Discovery

By extracting PsbtManager into its own interface, we elevate PSBTs to a first-class citizen in the wallet API.

  • Explicit Workflow: Grouping Fund, Decorate, Sign, Combine, and Finalize together makes the intended workflow self-evident.
  • Separation of Concerns: This prevents "interface bloat" in TxPublisher or Signer. While PSBTs involve signing, the high-level coordination of a PSBT packet is conceptually distinct from the low-level cryptographic operations of the Signer.

Insight 2: Extensibility via Parameter Objects (FundIntent)

Instead of passing numerous arguments to FundPsbt, the new design introduces the FundIntent struct.

  • Declarative Intent: FundIntent allows the caller to describe what they want (inputs, outputs, fee rate, change source) rather than just passing values.
  • Flexible Input Strategy: The Intent allows for a Policy (automatic coin selection) or manual Inputs (coin control), providing flexibility without changing the method signature.
  • Future-Proofing: New funding options can be added to FundIntent (e.g., specific coin selection algorithms, RBF preferences) without breaking existing code. The same pattern is applied to SignPsbt via SignPsbtParams.

Insight 3: The "Combiner" Role

The addition of CombinePsbt fills a critical gap for collaborative workflows.

  • BIP-174 Compliance: It implements the formal "Combiner" role defined in BIP-174, allowing multiple partial signatures for the same inputs to be merged into a single packet.
  • Stateless Merging: The implementation is designed to be safe and robust, validating that all packets refer to the same transaction before merging. It handles the complexity of deduplicating signatures, scripts, and derivation paths, freeing the client from error-prone manual merging.

The Final API: A Complete PSBT Toolkit

The final design provides a robust, extensible, and complete toolkit for managing the PSBT lifecycle.

// PsbtManager provides a cohesive, high-level interface for creating and
// managing Partially Signed Bitcoin Transactions (PSBTs).
type PsbtManager interface {
	// DecorateInputs enriches a PSBT's inputs with UTXO and derivation
	// information known to the wallet.
	DecorateInputs(ctx context.Context, packet *psbt.Packet,
		skipUnknown bool) (*psbt.Packet, error)

	// FundPsbt performs coin selection and adds the selected inputs (and a
	// change output, if necessary) to the PSBT.
	FundPsbt(ctx context.Context, intent *FundIntent) (*psbt.Packet,
		int32, error)

	// SignPsbt adds partial signatures to the PSBT for all inputs
	// controlled by the wallet.
	SignPsbt(ctx context.Context, params *SignPsbtParams) (
		*SignPsbtResult, error)

	// FinalizePsbt attempts to finalize the PSBT, transitioning it from a
	// partially signed state to a complete, network-ready transaction.
	FinalizePsbt(ctx context.Context, packet *psbt.Packet) error

	// CombinePsbt acts as the "Combiner" role in BIP 174, merging multiple
	// Partially Signed Bitcoin Transactions (PSBTs) into a single packet.
	CombinePsbt(ctx context.Context, psbts ...*psbt.Packet) (
		*psbt.Packet, error)
}

Why This Design is Superior:

  1. Workflow-Centric: The interface clearly guides the developer through the lifecycle of a PSBT.
  2. Extensible: Parameter objects like FundIntent ensure the API can evolve to support advanced features (like custom coin selection strategies) without breaking changes.
  3. Collaborative-Ready: The inclusion of CombinePsbt and the support for external input tweakers in SignPsbtParams make this interface fully capable of handling complex, multi-party signing scenarios.

The new FinalizePsbt implementation diverges from the deprecated method
by decoupling signing logic from account scoping. Instead of requiring
an account number and checking for watch-only status explicitly, it
iterates over all inputs and attempts to sign them using the modern
Signer interface (ComputeUnlockingScript).

Advantages:
1. Robustness: It attempts to sign any input for which the wallet possesses
   the private key, supporting mixed-account transactions naturally.
2. Safety: Defensive checks in fetchPsbtUtxo and FinalizePsbt prevent panics
   on malformed PSBTs (missing UTXO info), logging warnings instead.
3. Multi-party Support: Inputs that cannot be signed (e.g., belonging to
   other parties) are skipped gracefully, allowing MaybeFinalizeAll to
   complete the transaction using existing partial signatures if present.

This approach simplifies the API (no account args) and aligns with the
PsbtManager's goal of being a general-purpose PSBT handler.
We now move all helper methods into `psbt_manager.go`.
Add dedicated unit tests for these old methods.
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.

1 participant