Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ bdk_redb = { version = "0.1.0", optional = true }
shlex = { version = "1.3.0", optional = true }
tracing = "0.1.41"
tracing-subscriber = "0.3.20"
bdk_sp = { version = "0.1.0", optional = true, git = "https://github.com/bitcoindevkit/bdk-sp", tag = "v0.1.0" }

[features]
default = ["repl", "sqlite"]
Expand All @@ -54,3 +55,6 @@ verify = []
# Extra utility tools
# Compile policies
compiler = []

# Experimental silent payment sending capabilities
sp = ["dep:bdk_sp"]
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,27 @@ To generate a new extended master key, suitable for use in a descriptor:
cargo run -- key generate
```

#### Silent payments

> [!WARNING]
> This tool does not support silent payment scanning, nor the `silent_payment_code`
> command has any control on the public keys provided. If you don't have access
> to a silent payment scanner with the keys you provided, you are not going to
> be able to discover any funds, and if you do not control the private keys,
> you are not going to be able to spend the funds. We do not recommend the use
> of any of the silent payment features with real funds.

To experiment with silent payments, you can get two public keys in compressed format, `A1` and `A2`, and produce a silent payment code by calling:
```shell
cargo run --features sp -- --network signet silent_payment_code --scan_public_key '<A1>' --spend_public_key '<A2>'
```

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:

```shell
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>
```

## Justfile

We have added the `just` command runner to help you with common commands (during development) and running regtest `bitcoind` if you are using the `rpc` feature.
Expand Down
72 changes: 72 additions & 0 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@

#![allow(clippy::large_enum_variant)]

#[cfg(feature = "sp")]
use {crate::utils::parse_sp_code_value_pairs, bdk_sp::encoding::SilentPaymentCode};

use bdk_wallet::bitcoin::{
Address, Network, OutPoint, ScriptBuf,
bip32::{DerivationPath, Xpriv},
Expand Down Expand Up @@ -107,6 +110,19 @@ pub enum CliSubCommand {
#[command(flatten)]
wallet_opts: WalletOpts,
},
/// Silent payment code generation tool.
///
/// Allows the encoding of two public keys into a silent payment code.
/// Useful to create silent payment transactions using fake silent payment codes.
#[cfg(feature = "sp")]
SilentPaymentCode {
/// The scan public key to use on the silent payment code.
#[arg(long = "scan_public_key")]
scan: bdk_sp::bitcoin::secp256k1::PublicKey,
/// The spend public key to use on the silent payment code.
#[arg(long = "spend_public_key")]
spend: bdk_sp::bitcoin::secp256k1::PublicKey,
}
}

/// Wallet operation subcommands.
Expand Down Expand Up @@ -311,6 +327,62 @@ pub enum OfflineWalletSubCommand {
)]
add_data: Option<String>, //base 64 econding
},
/// Creates a silent payment transaction
///
/// This sub-command is **EXPERIMENTAL** and should only be used for testing. Do not use this
/// feature to create transactions that spend actual funds on the Bitcoin mainnet.

// This command DOES NOT return a PSBT. Instead, it directly returns a signed transaction
// ready for broadcast, as it is not yet possible to perform a shared derivation of a silent
// payment script pubkey in a secure and trustless manner.
#[cfg(feature = "sp")]
CreateSpTx {
/// Adds a recipient to the transaction.
// Clap Doesn't support complex vector parsing https://github.com/clap-rs/clap/issues/1704.
// Address and amount parsing is done at run time in handler function.
#[arg(env = "ADDRESS:SAT", long = "to", required = false, value_parser = parse_recipient)]
recipients: Option<Vec<(ScriptBuf, u64)>>,
/// Parse silent payment recipients
#[arg(long = "to-sp", required = true, value_parser = parse_sp_code_value_pairs)]
silent_payment_recipients: Vec<(SilentPaymentCode, u64)>,
/// Sends all the funds (or all the selected utxos). Requires only one recipient with value 0.
#[arg(long = "send_all", short = 'a')]
send_all: bool,
/// 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.
#[arg(long = "offline_signer")]
offline_signer: bool,
/// Selects which utxos *must* be spent.
#[arg(env = "MUST_SPEND_TXID:VOUT", long = "utxos", value_parser = parse_outpoint)]
utxos: Option<Vec<OutPoint>>,
/// Marks a utxo as unspendable.
#[arg(env = "CANT_SPEND_TXID:VOUT", long = "unspendable", value_parser = parse_outpoint)]
unspendable: Option<Vec<OutPoint>>,
/// Fee rate to use in sat/vbyte.
#[arg(env = "SATS_VBYTE", short = 'f', long = "fee_rate")]
fee_rate: Option<f32>,
/// Selects which policy should be used to satisfy the external descriptor.
#[arg(env = "EXT_POLICY", long = "external_policy")]
external_policy: Option<String>,
/// Selects which policy should be used to satisfy the internal descriptor.
#[arg(env = "INT_POLICY", long = "internal_policy")]
internal_policy: Option<String>,
/// Optionally create an OP_RETURN output containing given String in utf8 encoding (max 80 bytes)
#[arg(
env = "ADD_STRING",
long = "add_string",
short = 's',
conflicts_with = "add_data"
)]
add_string: Option<String>,
/// Optionally create an OP_RETURN output containing given base64 encoded String. (max 80 bytes)
#[arg(
env = "ADD_DATA",
long = "add_data",
short = 'o',
conflicts_with = "add_string"
)]
add_data: Option<String>, //base 64 econding
},
/// Bumps the fees of an RBF transaction.
BumpFee {
/// TXID of the transaction to update.
Expand Down
3 changes: 3 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ pub enum BDKCliError {
#[error("Create transaction error: {0}")]
CreateTx(#[from] bdk_wallet::error::CreateTxError),

#[error("Silent payment address decoding error: {0}")]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[error("Silent payment address decoding error: {0}")]
#[cfg(feature = "sp")]
#[error("Silent payment address decoding error: {0}")]

SilentPaymentParseError(#[from] bdk_sp::encoding::ParseError),

#[error("Descriptor error: {0}")]
DescriptorError(#[from] bdk_wallet::descriptor::error::Error),

Expand Down
Loading
Loading