bitcoindevkit / bitcoindevkit/bdk-sp

send: false bad tweak when intermediate key sum hits zero

Open
#55 3 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
Rust
Stars
24
Forks
11
Avg merge
6d 9h
Merged PRs (30d)
7

Description

I believe `bdk_sp::send::create_silentpayment_partial_secret` may fail when the running partial sum of eligible input keys hits zero, even though the final sum of all eligible keys is non-zero.

I first noticed this while differential-testing Silent Payments implementations, then reduced it to the standalone repro below against current `bdk-sp` HEAD.

The minimal key set is `[A, -A, A]` modulo secp256k1 order, so the final sum is `A`, not zero.

Concrete scalars:

```text
A = a6df6a0bb448992a301df4258e06a89fe7cf7146f59ac3bd5ff26083acb22ceb
-A mod n = 592095f44bb766d5cfe20bda71f9575ed2df6b9fb9addc7e5fdffe0923841456
```

**To Reproduce**
Create a temporary Cargo project with:

```toml
[package]
name = "upstream_repro_bdk_sp_head"
version = "0.1.0"
edition = "2024"

[dependencies]
bdk_sp = { git = "https://github.com/bitcoindevkit/bdk-sp.git", rev = "2f28d19581202d46fd0b30c35b6ae1cc45e37ce5" }
bitcoin = "0.32.8"
hex = "0.4"
```

and:

```rust
use bdk_sp::send::create_silentpayment_partial_secret;
use bitcoin::secp256k1::SecretKey;
use bitcoin::ScriptBuf;
use hex::encode;

fn smallest_outpoint_bytes(txid_hex: &str, vout: u32) -> [u8; 36] {
let mut txid = hex::decode(txid_hex).unwrap();
txid.reverse();
let mut out = [0u8; 36];
out[..32].copy_from_slice(&txid);
out[32..].copy_from_slice(&vout.to_le_bytes());
out
}

fn run_case(name: &str, keys: Vec<(ScriptBuf, SecretKey)>) {
let txid = "3a286147b25e16ae80aff406f2673c6e565418c40f45c071245cdebc8a94174e";
match create_silentpayment_partial_secret(&smallest_outpoint_bytes(txid, 0), &keys) {
Ok(secret) => println!("{name}: OK {}", encode(secret.secret_bytes())),
Err(err) => println!("{name}: ERR {err}"),
}
}

fn main() {
let spk = ScriptBuf::from_bytes(
hex::decode("00149d9e24f9fab4e35bf1a6df4b46cb533296ac0792").unwrap(),
);
let a = SecretKey::from_slice(
&hex::decode("a6df6a0bb448992a301df4258e06a89fe7cf7146f59ac3bd5ff26083acb22ceb")
.unwrap(),
)
.unwrap();
let minus_a = SecretKey::from_slice(
&hex::decode("592095f44bb766d5cfe20bda71f9575ed2df6b9fb9addc7e5fdffe0923841456")
.unwrap(),
)
.unwrap();

run_case(
"[A,-A,A]",
vec![(spk.clone(), a), (spk.clone(), minus_a), (spk.clone(), a)],
);
run_case(
"[A,A,-A]",
vec![(spk.clone(), a), (spk.clone(), a), (spk, minus_a)],
);
}
```

Run:

```bash
cargo run --quiet
```

I get:

```text
[A,-A,A]: ERR Silent payment sending error: bad tweak
[A,A,-A]: OK 9236293a30e156add5e1303da4c5867e191746e2971bedd8790065e587b08e49
```

**Expected behavior**
I would expect both orders to succeed.

Per BIP352 sender construction, the sender should sum all eligible input private keys first and fail only if the final sum is zero. In this case:

```text
A + (-A) + A = A
A + A + (-A) = A
```

Both are non-zero, so the result should not depend on input order.

This reduced low-level repro came from a valid three-input transaction with three distinct eligible P2WPKH inputs sharing the same smallest outpoint `3a286147b25e16ae80aff406f2673c6e565418c40f45c071245cdebc8a94174e:0`.

**Build environment**
- Tag/commit: `2f28d19581202d46fd0b30c35b6ae1cc45e37ce5`
- OS+version: `macOS 26.4`
- Rust/Cargo version: `cargo 1.92.0`, `rustc 1.92.0`
- Rust/Cargo target: `aarch64-apple-darwin`

**Additional context**
The likely cause seems to be the left-to-right accumulation in `silentpayments/src/send/mod.rs`:

```rust
let mut a_sum = available_keys[0];
for sk in available_keys.iter().skip(1) {
a_sum = a_sum.add_tweak(&Scalar::from(*sk))?;
}
```

If the intermediate sum reaches zero after `A + (-A)`, `add_tweak` errors immediately, even though the remaining `+ A` would make the final sum non-zero again.

The receive side appears to aggregate input public keys as a full set before hashing, so the send/receive asymmetry also seems suspicious.

If repeated key material across distinct eligible inputs is intentionally unsupported here, please ignore this and I’d appreciate a pointer to that restriction.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.