bitcoindevkit / bitcoindevkit/bdk-tx
Finalizer is not persistable, blocking external/offline signing workflows
- 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
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