send: false bad tweak when intermediate key sum hits zero
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 64/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Quiet
- Tech stack
- rust
- Domain
- cryptography
Research direction
Start by running the standalone Cargo reproduction, then read silentpayments/src/send/mod.rs around the a_sum accumulation and compare it with the receive-side aggregation described in the issue. Add regression coverage for both key orders; done means both non-zero final sums succeed and only a zero final sum produces an error.
Written by the indexing model from the issue text.
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:
A = a6df6a0bb448992a301df4258e06a89fe7cf7146f59ac3bd5ff26083acb22ceb
-A mod n = 592095f44bb766d5cfe20bda71f9575ed2df6b9fb9addc7e5fdffe0923841456
To Reproduce
Create a temporary Cargo project with:
[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:
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:
cargo run --quiet
I get:
[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:
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:
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.
- Dominant language
- Rust
- Stars
- 24
- Forks
- 11
- PR merge metrics
- No merged PRs in 30d
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
rustls/rustls-platform-verifier#262 · 2 comments · 2 reactions ·
-
needs-triage
Difficulty 2/5 1-3 hours Newbie friendliness 73/100
bherbruck/solvecraft#4493 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 77/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
IngvarConsulting/unica#1911 ·
Maintainers usually reply within 1 day