Skip to content

Commit 4a8e3ce

Browse files
committed
Merge #220: Add experimental silent payment transaction creation support
c579854 feat(silentpayments): add experimental silent-payments sending support (nymius) Pull request description: **Description** This PR adds experimental support for creating silent payment transactions through a new `CreateSpTx` command. The implementation integrates the `bdk_sp` crate to enable sending Bitcoin to silent payment addresses. Key changes: - Adds `bdk_sp` dependency as an optional feature - Implements `CreateSpTx` command with support for silent payment recipients - Includes parser for silent payment `address:amount` pairs - Returns signed transactions ready for broadcast (not PSBTs due to silent payment derivation constraints) **Notes to the reviewers** - This feature is marked as **EXPERIMENTAL** and includes warnings against mainnet use - The command returns signed transactions directly rather than PSBTs because silent payment script pubkey derivation cannot be performed securely in a trustless manner with standard PSBT workflows - RBF is disabled for silent payment transactions (sequence set to MAX) - The implementation handles both single key and extended key signers - Error handling uses temporary `.expect()` calls that should be addressed in future iterations **Changelog notice** **Added**: Experimental silent payment transaction creation via `CreateSpTx` command (feature-gated behind `sp` flag) **Checklists** **All Submissions** * [x] I've signed all my commits * [x] I followed the [conventional commit guidelines](https://www.conventionalcommits.org/en/v1.0.0/) * [x] I ran cargo fmt, clippy and test before committing **New Features** ~~* [ ] I've added tests for the new feature~~ * [x] I've added docs for the new feature ACKs for top commit: tvpeter: tACK c579854 Tree-SHA512: 0d5e2064b2eacab4fa2c0161bb1d9fd73c17401900c80a665a8ba3feb2025ddd6896efd0c30f7ceab361a88c397010965442bc9689df85383aaa9a0ec95fec0c
2 parents f80d92a + c579854 commit 4a8e3ce

7 files changed

Lines changed: 366 additions & 4 deletions

File tree

Cargo.lock

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ bdk_electrum = { version = "0.23.2", optional = true }
3333
bdk_esplora = { version = "0.22.1", features = ["async-https", "tokio"], optional = true }
3434
bdk_kyoto = { version = "0.15.4", optional = true }
3535
bdk_redb = { version = "0.1.1", optional = true }
36+
bdk_sp = { version = "0.1.0", optional = true, git = "https://github.com/bitcoindevkit/bdk-sp", tag = "v0.1.0" }
3637
shlex = { version = "1.3.0", optional = true }
3738
payjoin = { version = "=1.0.0-rc.1", features = ["v1", "v2", "io", "_test-utils"], optional = true}
3839
reqwest = { version = "0.13.2", default-features = false, optional = true }
@@ -65,3 +66,6 @@ verify = []
6566
# Extra utility tools
6667
# Compile policies
6768
compiler = []
69+
70+
# Experimental silent payment sending capabilities
71+
silent-payments = ["dep:bdk_sp"]

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,31 @@ cargo run --features rpc -- wallet --wallet payjoin_wallet2 sync
148148
cargo run --features rpc -- wallet --wallet payjoin_wallet2 balance
149149
150150
cargo run --features rpc -- wallet --wallet payjoin_wallet2 send_payjoin --ohttp_relay "https://pj.bobspacebkk.com" --ohttp_relay "https://pj.benalleng.com" --fee_rate 1 --uri "<URI>"
151+
152+
#### Silent payments
153+
154+
> [!WARNING]
155+
> This tool does not support silent payment scanning, nor the `silent_payment_code`
156+
> command has any control on the public keys provided. If you don't have access
157+
> to a silent payment scanner with the keys you provided, you are not going to
158+
> be able to discover any funds, and if you do not control the private keys,
159+
> you are not going to be able to spend the funds. We do not recommend the use
160+
> of any of the silent payment features with real funds.
161+
162+
To experiment with silent payments, you can get two public keys in compressed or uncompressed format, `A1` and `A2`, and produce a silent payment code by calling:
163+
```shell
164+
cargo run --features sp -- --network signet silent_payment_code --scan_public_key '<A1>' --spend_public_key '<A2>'
165+
```
166+
167+
Once you have a silent payment code, `SP_CODE_1` and an amount `AMOUNT_1` to send, you can create a valid transaction locking funds to a silent payment code derived address with the following command:
168+
169+
```shell
170+
cargo run --features electrum,sp -- --network testnet4 wallet --wallet sample_wallet --ext-descriptor "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)" --database-type sqlite --client-type electrum --url "ssl://mempool.space:40002" create_sp_tx --to-sp <SP_CODE_1>:<AMOUNT_1>
171+
```
172+
173+
It's also possible to drain all balance to a silent payment wallet by using the `--send_all` flag:
174+
```shell
175+
cargo run --features electrum,sp -- --network testnet4 wallet --wallet sample_wallet --ext-descriptor "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)" --database-type sqlite --client-type electrum --url "ssl://mempool.space:40002" create_sp_tx --send_all --to-sp <SP_CODE_1>:0
151176
```
152177

153178
## Justfile

src/commands.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
//! All subcommands are defined in the below enums.
1414
1515
#![allow(clippy::large_enum_variant)]
16+
#[cfg(feature = "silent-payments")]
17+
use {crate::utils::parse_sp_code_value_pairs, bdk_sp::encoding::SilentPaymentCode};
18+
1619
use bdk_wallet::bitcoin::{
1720
Address, Network, OutPoint, ScriptBuf,
1821
bip32::{DerivationPath, Xpriv},
@@ -182,6 +185,19 @@ pub enum CliSubCommand {
182185
#[arg(value_enum)]
183186
shell: Shell,
184187
},
188+
/// Silent payment code generation tool.
189+
///
190+
/// Allows the encoding of two public keys into a silent payment code.
191+
/// Useful to create silent payment transactions using fake silent payment codes.
192+
#[cfg(feature = "silent-payments")]
193+
SilentPaymentCode {
194+
/// The scan public key to use on the silent payment code.
195+
#[arg(long = "scan_key")]
196+
scan: bdk_sp::bitcoin::secp256k1::PublicKey,
197+
/// The spend public key to use on the silent payment code.
198+
#[arg(long = "spend_key")]
199+
spend: bdk_sp::bitcoin::secp256k1::PublicKey,
200+
},
185201
}
186202

187203
/// Wallet operation subcommands.
@@ -395,6 +411,62 @@ pub enum OfflineWalletSubCommand {
395411
)]
396412
add_data: Option<String>, //base 64 econding
397413
},
414+
/// Creates a silent payment transaction
415+
///
416+
/// This sub-command is **EXPERIMENTAL** and should only be used for testing. Do not use this
417+
/// feature to create transactions that spend actual funds on the Bitcoin mainnet.
418+
419+
// This command DOES NOT return a PSBT. Instead, it directly returns a signed transaction
420+
// ready for broadcast, as it is not yet possible to perform a shared derivation of a silent
421+
// payment script pubkey in a secure and trustless manner.
422+
#[cfg(feature = "silent-payments")]
423+
CreateSpTx {
424+
/// Adds a recipient to the transaction.
425+
// Clap Doesn't support complex vector parsing https://github.com/clap-rs/clap/issues/1704.
426+
// Address and amount parsing is done at run time in handler function.
427+
#[arg(env = "ADDRESS:SAT", long = "to", required = false, value_parser = parse_recipient)]
428+
recipients: Option<Vec<(ScriptBuf, u64)>>,
429+
/// Parse silent payment recipients
430+
#[arg(long = "to-sp", required = true, value_parser = parse_sp_code_value_pairs)]
431+
silent_payment_recipients: Vec<(SilentPaymentCode, u64)>,
432+
/// Sends all the funds (or all the selected utxos). Requires only one recipient with value 0.
433+
#[arg(long = "send_all", short = 'a')]
434+
send_all: bool,
435+
/// Make a PSBT that can be signed by offline signers and hardware wallets. Forces the addition of `non_witness_utxo` and more details to let the signer identify the change output.
436+
#[arg(long = "offline_signer")]
437+
offline_signer: bool,
438+
/// Selects which utxos *must* be spent.
439+
#[arg(env = "MUST_SPEND_TXID:VOUT", long = "utxos", value_parser = parse_outpoint)]
440+
utxos: Option<Vec<OutPoint>>,
441+
/// Marks a utxo as unspendable.
442+
#[arg(env = "CANT_SPEND_TXID:VOUT", long = "unspendable", value_parser = parse_outpoint)]
443+
unspendable: Option<Vec<OutPoint>>,
444+
/// Fee rate to use in sat/vbyte.
445+
#[arg(env = "SATS_VBYTE", short = 'f', long = "fee_rate")]
446+
fee_rate: Option<f32>,
447+
/// Selects which policy should be used to satisfy the external descriptor.
448+
#[arg(env = "EXT_POLICY", long = "external_policy")]
449+
external_policy: Option<String>,
450+
/// Selects which policy should be used to satisfy the internal descriptor.
451+
#[arg(env = "INT_POLICY", long = "internal_policy")]
452+
internal_policy: Option<String>,
453+
/// Optionally create an OP_RETURN output containing given String in utf8 encoding (max 80 bytes)
454+
#[arg(
455+
env = "ADD_STRING",
456+
long = "add_string",
457+
short = 's',
458+
conflicts_with = "add_data"
459+
)]
460+
add_string: Option<String>,
461+
/// Optionally create an OP_RETURN output containing given base64 encoded String. (max 80 bytes)
462+
#[arg(
463+
env = "ADD_DATA",
464+
long = "add_data",
465+
short = 'o',
466+
conflicts_with = "add_string"
467+
)]
468+
add_data: Option<String>, //base 64 econding
469+
},
398470
/// Bumps the fees of an RBF transaction.
399471
BumpFee {
400472
/// TXID of the transaction to update.

src/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ pub enum BDKCliError {
2121
#[error("Create transaction error: {0}")]
2222
CreateTx(#[from] bdk_wallet::error::CreateTxError),
2323

24+
#[cfg(feature = "silent-payments")]
25+
#[error("Silent payment address decoding error: {0}")]
26+
SilentPaymentParseError(#[from] bdk_sp::encoding::ParseError),
27+
2428
#[error("Descriptor error: {0}")]
2529
DescriptorError(#[from] bdk_wallet::descriptor::error::Error),
2630

0 commit comments

Comments
 (0)