Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

send: false bad tweak when intermediate key sum hits zero

Open
#55 3 comments 0 reactions 0 assignees View on GitHub

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

bug

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

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.