Problem
Issue #331 shipped the initiator-side PSBT coordination (propose, session machine, CLI wallet spend --recovery-tier) and a Reject-only responder card on desktop. The responder approve flow, where a signer receives a proposed PSBT and contributes a signature via KfpNode::contribute_psbt_signature, doesn't exist anywhere in the codebase yet:
keep-cli has no responder command; cmd_wallet_spend is initiator-only.
keep-core does not manage recovery-signer private keys (the keys corresponding to announced recovery xpubs live on external hardware/software today).
keep-bitcoin::RecoveryTxBuilder::sign_recovery accepts a raw 32-byte secret, so the signing primitive exists, but nothing is wired to feed it one.
keep-desktop surfaces pending-PSBT cards with Reject-only by design; the comment at keep-desktop/src/screen/wallet.rs calls this out explicitly.
Scope
Land the end-to-end responder path so a recovery-key holder can approve an incoming PSBT:
- keep-core: API to store, unlock, and sign with a recovery-signer private key (scoped by group pubkey + xpub fingerprint). Consider whether storage is keep-core's responsibility or whether this should delegate to an external NIP-46 signer.
- keep-bitcoin / keep-core glue: responder entry point that takes
(session_id, PSBT bytes, descriptor_hash, tier_index, signer_identity), invokes sign_recovery, and returns the merged PSBT ready for contribute_psbt_signature.
- keep-cli: symmetric command, e.g.
keep wallet approve-psbt <session-id> or auto-sign mode, so CI and headless nodes can participate.
- keep-desktop: wire the existing
PsbtPendingDisplay card with an Approve action that loads the signer, invokes the responder entry point, and calls contribute_psbt_signature. Gate on a clear PSBT preview (destinations + amounts, not just the session snapshot).
- Tests: end-to-end integration covering propose → approve → contribute → finalize with a real FROST group fixture.
Notes
- Security considerations: decide whether recovery-key material belongs in the encrypted Keep vault, in an OS keychain, or strictly on external hardware via NIP-46.
- UX: desktop should show the destinations and per-output amounts extracted from the PSBT before exposing Approve, to mitigate misrouted/malicious proposals.
- Mobile (Android) responder UI can follow once the keep-core + UniFFI surface stabilizes; out of scope here.
References
Problem
Issue #331 shipped the initiator-side PSBT coordination (propose, session machine, CLI
wallet spend --recovery-tier) and a Reject-only responder card on desktop. The responder approve flow, where a signer receives a proposed PSBT and contributes a signature viaKfpNode::contribute_psbt_signature, doesn't exist anywhere in the codebase yet:keep-clihas no responder command;cmd_wallet_spendis initiator-only.keep-coredoes not manage recovery-signer private keys (the keys corresponding to announced recovery xpubs live on external hardware/software today).keep-bitcoin::RecoveryTxBuilder::sign_recoveryaccepts a raw 32-byte secret, so the signing primitive exists, but nothing is wired to feed it one.keep-desktopsurfaces pending-PSBT cards with Reject-only by design; the comment atkeep-desktop/src/screen/wallet.rscalls this out explicitly.Scope
Land the end-to-end responder path so a recovery-key holder can approve an incoming PSBT:
(session_id, PSBT bytes, descriptor_hash, tier_index, signer_identity), invokessign_recovery, and returns the merged PSBT ready forcontribute_psbt_signature.keep wallet approve-psbt <session-id>or auto-sign mode, so CI and headless nodes can participate.PsbtPendingDisplaycard with an Approve action that loads the signer, invokes the responder entry point, and callscontribute_psbt_signature. Gate on a clear PSBT preview (destinations + amounts, not just the session snapshot).Notes
References
keep-bitcoin/src/recovery_tx.rs::RecoveryTxBuilder::sign_recoverykeep-frost-net/src/node/psbt.rs::KfpNode::contribute_psbt_signature