The silentpayments sending path in SPDK appears to 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 ran into this while testing multiple BIP 352 implementations and reduced it to the self-contained case below.
Minimal case
- three distinct P2WPKH inputs
- private keys are
[A, -A, A] modulo secp256k1 order
- so the final sum is
A, not zero
Concrete scalars:
A = a6df6a0bb448992a301df4258e06a89fe7cf7146f59ac3bd5ff26083acb22ceb
-A mod n = 592095f44bb766d5cfe20bda71f9575ed2df6b9fb9addc7e5fdffe0923841456
These are attached to three distinct outpoints:
3a286147b25e16ae80aff406f2673c6e565418c40f45c071245cdebc8a94174e:0
3a286147b25e16ae80aff406f2673c6e565418c40f45c071245cdebc8a94174e:1
3a286147b25e16ae80aff406f2673c6e565418c40f45c071245cdebc8a94174e:2
Expected behavior
This should succeed if the rule is based on the full eligible-input sum:
which is non-zero.
The BIP 352 reference-style full-sum logic sums the full set first and only fails if the final sum is zero. On this case it computes:
a_sum = a6df6a0bb448992a301df4258e06a89fe7cf7146f59ac3bd5ff26083acb22ceb
input_hash = ea876d97952aaee5dd02c892a2ec6631790e7cdbbf84f9923e76a476ec7fa000
output = bf300962adaaf21b58cf043d91ff46661b2688eeaaf753fe6ee8b642de3b715f
Actual behavior
Using the current silentpayments sending utility directly:
use hex::encode;
use silentpayments::secp256k1::SecretKey;
use silentpayments::utils::sending::calculate_partial_secret;
fn main() {
let txid = "3a286147b25e16ae80aff406f2673c6e565418c40f45c071245cdebc8a94174e".to_owned();
let a =
SecretKey::from_slice(&hex::decode("a6df6a0bb448992a301df4258e06a89fe7cf7146f59ac3bd5ff26083acb22ceb").unwrap()).unwrap();
let minus_a =
SecretKey::from_slice(&hex::decode("592095f44bb766d5cfe20bda71f9575ed2df6b9fb9addc7e5fdffe0923841456").unwrap()).unwrap();
let input_keys = vec![(a, false), (minus_a, false), (a, false)];
let outpoints = vec![(txid.clone(), 0u32), (txid.clone(), 1u32), (txid, 2u32)];
match calculate_partial_secret(&input_keys, &outpoints) {
Ok(secret) => println!("OK {}", encode(secret.secret_bytes())),
Err(err) => println!("ERR {err}"),
}
}
This returns:
Likely cause
The implementation in silentpayments/src/utils/sending.rs appears to fold left-to-right with add_tweak:
let result: SecretKey = tail
.iter()
.try_fold(*head, |acc, &item| acc.add_tweak(&item.into()))?;
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.
Question
Is repeated key material across distinct inputs intentionally unsupported for sending, or is this a bug?
I could not find such a restriction in BIP 352, and repeated keys across multiple UTXOs seems valid in general. If this interpretation is wrong, I would appreciate a pointer.
If helpful, I can also share the minimized request JSON I used to derive this case.
The
silentpaymentssending path in SPDK appears to 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 ran into this while testing multiple BIP 352 implementations and reduced it to the self-contained case below.
Minimal case
[A, -A, A]modulo secp256k1 orderA, not zeroConcrete scalars:
These are attached to three distinct outpoints:
Expected behavior
This should succeed if the rule is based on the full eligible-input sum:
which is non-zero.
The BIP 352 reference-style full-sum logic sums the full set first and only fails if the final sum is zero. On this case it computes:
Actual behavior
Using the current
silentpaymentssending utility directly:This returns:
Likely cause
The implementation in
silentpayments/src/utils/sending.rsappears to fold left-to-right withadd_tweak:If the intermediate sum reaches zero after
A + (-A),add_tweakerrors immediately, even though the remaining+ Awould make the final sum non-zero again.Question
Is repeated key material across distinct inputs intentionally unsupported for sending, or is this a bug?
I could not find such a restriction in BIP 352, and repeated keys across multiple UTXOs seems valid in general. If this interpretation is wrong, I would appreciate a pointer.
If helpful, I can also share the minimized request JSON I used to derive this case.