bitcoindevkit / bitcoindevkit/bdk-tx

Finalizer is not persistable, blocking external/offline signing workflows

Open
#83 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
13
Forks
17
Avg merge
17h 22m
Merged PRs (30d)
1

Description

## Problem

The `Finalizer` cannot be serialized and persisted to storage. This prevents a common and important workflow: handing off a transaction for signing to an external party and finalizing it later.

When signing is delegated to an external signer (hardware wallet, air-gapped device, remote co-signer, or another service), an arbitrary amount of time may pass between PSBT creation and the return of the signed PSBT. The process that built the transaction — and the `Finalizer` it produced — may not stay alive across that gap. It could be a short-lived request handler, a CLI invocation that exits, or a wallet that is shut down between sessions.

The PSBT itself is already persistable (BIP174 is a serialization format), so the unsigned/partially-signed transaction can be saved and handed off freely. The `Finalizer`, however, cannot follow it. This means the *only* way to finalize a PSBT with bdk-tx is to keep the original in-memory `Finalizer` alive from creation through finalization, which is incompatible with any persistence boundary between those two steps.

## Why this matters

- **External signers** — the defining use case. The wallet builds and persists the PSBT, sends it off, and exits. When the signed PSBT comes back (minutes, hours, or days later), there is no way to reconstruct the `Finalizer` needed to complete it.
- **Offline / air-gapped flows** — PSBT goes out to cold storage and comes back signed; the online side has no surviving finalization state.
- **Crash recovery / restarts** — a wallet restarted mid-flow loses the ability to finalize PSBTs that were already created and signed.

## Root cause

`Finalizer` holds a `HashMap` (`src/finalizer.rs`), where `Plan` is `miniscript::plan::Plan`. That type:

- does not implement `serde::Serialize` / `Deserialize`,
- has no public constructor (its `template` and `descriptor` fields are `pub(crate)`), and
- cannot be rebuilt from its public accessors.

As a result, there is no supported way to serialize a `Finalizer`, nor to reconstruct an equivalent one from saved data, after the originating process is gone.

## Scope

This issue is intentionally limited to stating the problem. It does **not** propose a solution; design options (and their trade-offs around forward-compatibility, miniscript coupling, and API surface) should be discussed separately.

Contributor guide

Open the contributing guide

Research direction

Start with src/finalizer.rs and inspect Finalizer's HashMap state, then review miniscript::plan::Plan's public API and serialization support. Compare persistence approaches with attention to forward compatibility, miniscript coupling, and API surface; the issue is ready for implementation only after a solution is agreed.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
blockchain
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.