Skip to content

feat: add p2pk_signing_keys to SendOptions#1750

Open
vnprc wants to merge 2 commits intocashubtc:mainfrom
vnprc:send-p2pk-signing-keys-plan
Open

feat: add p2pk_signing_keys to SendOptions#1750
vnprc wants to merge 2 commits intocashubtc:mainfrom
vnprc:send-p2pk-signing-keys-plan

Conversation

@vnprc
Copy link
Contributor

@vnprc vnprc commented Mar 20, 2026

Summary

Wallets that hold P2PK-locked proofs (received from another party, or minted with spending conditions) currently have no way to spend them through the standard prepare_send / confirm flow — they must manually call swap() and handle signing themselves. This PR closes that gap.

Changes:

  • Add p2pk_signing_keys: Vec<SecretKey> to SendOptions (defaults to empty, fully backward-compatible)
  • Extract P2PK/HTLC signing logic from receive/saga/mod.rs into a new sign_proofs() utility in wallet/util.rs, and call it from both the receive and send sagas
  • Call sign_proofs(&mut proofs_to_swap, &opts.p2pk_signing_keys) in PreparedSend::confirm() before the swap, so the mint accepts the spend
  • Mirror the field in cdk-ffi's SendOptions struct and both From conversions (matching the existing ReceiveOptions pattern)

Usage

// Wallet holds P2PK-locked proofs locked to `secret`
let prepared = wallet
    .prepare_send(
        Amount::from(10),
        SendOptions {
            p2pk_signing_keys: vec![secret],
            ..Default::default()
        },
    )
    .await?;

let token = prepared.confirm(None).await?;

Test plan

  • 4 unit tests in wallet/util.rs: correct key signs, wrong key is a no-op, plain proof is a no-op, empty key vec is a no-op
  • Integration test test_p2pk_send_options_signing_keys: funds a wallet, swaps clean proofs for P2PK-locked proofs via the raw mint API, then exercises the full prepare_send / confirm flow with signing keys — verifies the recipient receives the correct amount

Add `p2pk_signing_keys: Vec<SecretKey>` to `SendOptions` so wallets
holding P2PK-locked proofs can spend them through the standard
`prepare_send` / `confirm` flow without manually calling `swap()`.

- Extract `sign_proofs()` into `wallet/util.rs` with four unit tests
- Refactor `receive/saga/mod.rs` to call `sign_proofs()` (no behaviour change)
- Sign `proofs_to_swap` in `send/saga/mod.rs::confirm()` before swap
- Add `p2pk_signing_keys` to cdk-ffi `SendOptions` struct and both `From`
  conversions (was previously hardcoded to `Vec::new()`)
- Fix CHANGELOG `## Added` heading level to `### Added`
- Add integration test `test_p2pk_send_options_signing_keys` covering the
  full prepare_send / confirm flow with P2PK-locked input proofs
@github-project-automation github-project-automation bot moved this to Backlog in CDK Mar 20, 2026
… conditions

When a wallet holds P2PK-locked proofs whose total exactly equals the
requested send amount, prepare_send short-circuits: all proofs go to
proofs_to_send and proofs_to_swap is empty. Since confirm only signs
proofs_to_swap, signing is skipped and the locked proofs flow out in
the token unchanged, causing the recipient to get NUT11 errors.

Fix by shadowing force_swap to true in internal_prepare when
p2pk_signing_keys is non-empty, ensuring all proofs go through a real
swap that signs and unlocks them before building the token.

Add a regression test covering the exact-denomination case.
@lescuer97
Copy link
Contributor

lescuer97 commented Mar 20, 2026

I think this is partly getting fixed here: #1466

This would not be a complete fix but I think it's more or less where you want to take it.

@vnprc
Copy link
Contributor Author

vnprc commented Mar 20, 2026

oh cool!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

2 participants