Skip to content

Commit d297c8e

Browse files
committed
feat(silentpayments): add experimental silent-payments sending support
- Adds CreateSpTx command to create transactions with silent payment outputs: this command creates signed transactions directly rather than PSBTs due to current limitations in secure shared derivation. It supports mixed recipients: regular addresses + silent payments. It DOES NOT support RBF for the created transactions. It generates signed transactions ready for broadcasting. - Adds SilentPaymentCode command to create silent payment codes from public keys and network: the silent payment code generated is independent from any of the other stateful features of bdk-cli. This command is mainly intended for experimental use, do not lock any funds to the generated code if you don't know what you are doing and don't have the keys matching the public keys used. - Adds bdk_sp dependency with "silent-payments" feature flag. - Adds silent payment recipient parsing utility. - Add README section for new silent payment commands. Note: This is experimental functionality for testing only, not recommended for mainnet use.
1 parent a501434 commit d297c8e

File tree

7 files changed

+366
-4
lines changed

7 files changed

+366
-4
lines changed

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
@@ -32,6 +32,7 @@ bdk_electrum = { version = "0.23.0", optional = true }
3232
bdk_esplora = { version = "0.22.1", features = ["async-https", "tokio"], optional = true }
3333
bdk_kyoto = { version = "0.15.1", optional = true }
3434
bdk_redb = { version = "0.1.0", optional = true }
35+
bdk_sp = { version = "0.1.0", optional = true, git = "https://github.com/bitcoindevkit/bdk-sp", tag = "v0.1.0" }
3536
shlex = { version = "1.3.0", optional = true }
3637
payjoin = { version = "=1.0.0-rc.1", features = ["v1", "v2", "io", "_test-utils"], optional = true}
3738
reqwest = { version = "0.12.23", default-features = false, optional = true }
@@ -62,3 +63,6 @@ verify = []
6263
# Extra utility tools
6364
# Compile policies
6465
compiler = []
66+
67+
# Experimental silent payment sending capabilities
68+
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},
@@ -127,6 +130,19 @@ pub enum CliSubCommand {
127130
},
128131
/// List all saved wallet configurations.
129132
Wallets,
133+
/// Silent payment code generation tool.
134+
///
135+
/// Allows the encoding of two public keys into a silent payment code.
136+
/// Useful to create silent payment transactions using fake silent payment codes.
137+
#[cfg(feature = "silent-payments")]
138+
SilentPaymentCode {
139+
/// The scan public key to use on the silent payment code.
140+
#[arg(long = "scan_public_key")]
141+
scan: bdk_sp::bitcoin::secp256k1::PublicKey,
142+
/// The spend public key to use on the silent payment code.
143+
#[arg(long = "spend_public_key")]
144+
spend: bdk_sp::bitcoin::secp256k1::PublicKey,
145+
},
130146
}
131147
/// Wallet operation subcommands.
132148
#[derive(Debug, Subcommand, Clone, PartialEq)]
@@ -339,6 +355,62 @@ pub enum OfflineWalletSubCommand {
339355
)]
340356
add_data: Option<String>, //base 64 econding
341357
},
358+
/// Creates a silent payment transaction
359+
///
360+
/// This sub-command is **EXPERIMENTAL** and should only be used for testing. Do not use this
361+
/// feature to create transactions that spend actual funds on the Bitcoin mainnet.
362+
363+
// This command DOES NOT return a PSBT. Instead, it directly returns a signed transaction
364+
// ready for broadcast, as it is not yet possible to perform a shared derivation of a silent
365+
// payment script pubkey in a secure and trustless manner.
366+
#[cfg(feature = "silent-payments")]
367+
CreateSpTx {
368+
/// Adds a recipient to the transaction.
369+
// Clap Doesn't support complex vector parsing https://github.com/clap-rs/clap/issues/1704.
370+
// Address and amount parsing is done at run time in handler function.
371+
#[arg(env = "ADDRESS:SAT", long = "to", required = false, value_parser = parse_recipient)]
372+
recipients: Option<Vec<(ScriptBuf, u64)>>,
373+
/// Parse silent payment recipients
374+
#[arg(long = "to-sp", required = true, value_parser = parse_sp_code_value_pairs)]
375+
silent_payment_recipients: Vec<(SilentPaymentCode, u64)>,
376+
/// Sends all the funds (or all the selected utxos). Requires only one recipient with value 0.
377+
#[arg(long = "send_all", short = 'a')]
378+
send_all: bool,
379+
/// 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.
380+
#[arg(long = "offline_signer")]
381+
offline_signer: bool,
382+
/// Selects which utxos *must* be spent.
383+
#[arg(env = "MUST_SPEND_TXID:VOUT", long = "utxos", value_parser = parse_outpoint)]
384+
utxos: Option<Vec<OutPoint>>,
385+
/// Marks a utxo as unspendable.
386+
#[arg(env = "CANT_SPEND_TXID:VOUT", long = "unspendable", value_parser = parse_outpoint)]
387+
unspendable: Option<Vec<OutPoint>>,
388+
/// Fee rate to use in sat/vbyte.
389+
#[arg(env = "SATS_VBYTE", short = 'f', long = "fee_rate")]
390+
fee_rate: Option<f32>,
391+
/// Selects which policy should be used to satisfy the external descriptor.
392+
#[arg(env = "EXT_POLICY", long = "external_policy")]
393+
external_policy: Option<String>,
394+
/// Selects which policy should be used to satisfy the internal descriptor.
395+
#[arg(env = "INT_POLICY", long = "internal_policy")]
396+
internal_policy: Option<String>,
397+
/// Optionally create an OP_RETURN output containing given String in utf8 encoding (max 80 bytes)
398+
#[arg(
399+
env = "ADD_STRING",
400+
long = "add_string",
401+
short = 's',
402+
conflicts_with = "add_data"
403+
)]
404+
add_string: Option<String>,
405+
/// Optionally create an OP_RETURN output containing given base64 encoded String. (max 80 bytes)
406+
#[arg(
407+
env = "ADD_DATA",
408+
long = "add_data",
409+
short = 'o',
410+
conflicts_with = "add_string"
411+
)]
412+
add_data: Option<String>, //base 64 econding
413+
},
342414
/// Bumps the fees of an RBF transaction.
343415
BumpFee {
344416
/// 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)