electrum icon indicating copy to clipboard operation
electrum copied to clipboard

supporting silent payments (bip352)

Open ecdsa opened this issue 9 months ago • 5 comments

Description

@MorenoProg asked about how to implement silent payments in https://github.com/spesmilo/electrum/issues/7408#issuecomment-2796696251

Some preliminary remarks:

  • the "send to" logic does not require taproot, but the "spend from" logic does.
  • actually, even if we do not expose it to users, we already have limited support for taproot key-spend (see #9082)
  • electrum-ecc does not support musig yet, but recent libsecp256k1 does, so I guess musig could be added to electrum-ecc "easily".

Note that for a feature to be merged in Electrum, it must not create a situation where user funds are not recoverable from seed. AFAICS, receiving silent payments would not only need a taproot wallet, but they would also break that assumption. Thus, if you want to implement the full "spend from" logic, I think it should be a plugin.

Also, the receiver will need to scan the blockchain in a way that is currently not indexed by Electrum servers. So you need an extra channel to request that information. That's another reason to implement this as a plugin (maybe talking to a local bitcoind)

ecdsa avatar Apr 11 '25 13:04 ecdsa

ref https://github.com/spesmilo/electrum/issues/8847

SomberNight avatar Apr 11 '25 13:04 SomberNight

Planning Sender Silent Payment Support in Electrum

I'm currently working on implementing sender-side silent payments in Electrum (as described in BIP-352). In doing so, I've come across several points that need clarification regarding Electrum's wallet types, supported sighash flags, and how script types are handled.

Please correct me if I've misunderstood anything or if you spot inaccuracies in the assumptions below.

Silent Payment Requirements from BIP-352

  • At least one input MUST be from the (IFSSD) Inputs For Shared Secret Derivation list. TL;DR: IFSSD = [p2tr, p2pkh, p2wpkh, p2sh-p2wpkh]
  • No Input with SegWit version > 1 can be used
  • The sender should sign with one of the sighash flags DEFAULT, ALL, SINGLE, NONE (ANYONECANPAY is unsafe). It is strongly recommended implementations use SIGHASH_ALL (SIGHASH_DEFAULT for taproot inputs) when possible

Determining Wallet Compatibility with Silent Payments

To narrow the scope of implementation, I want to enable sending to silent payment addresses only when it's practically and safely possible, based on the wallet type and its UTXO script types. For this, I examined the different wallet types a user can create (either via QT or commands) and decided, if the 3 criterias above can be fulfilled:

Standard Wallet (deterministic single-keystore): As confirmed here, Electrum never mixes script types unless the wallet is of type “imported”. Therefore, for standard wallets, it's safe to determine whether silent payment is supported solely based on txin_type:

enable_silent_payment = wallet.wallet_type == "standard" and wallet.txin_type in IFSSD

(I know that electrum can't spend p2tr yet, I leave it anyway to be consistent with bip-352.)

My open questions here:

  • Standard wallets can be created in various ways. Are there cases where a standard wallet might use a script type not in IFSSD? For example, when initialized from an extended master private key?

  • In other words: Would a simpler check like wallet.wallet_type == "standard" already be sufficient?

Hardware-backed Standard Wallets

I couldn’t test this due to lack of hardware. But having a watch-only wallet doesn't work, of course, because the private keys are needed to compute the shared secret. So building the transaction would also have to take place on the hardware device, in which I have no experience. So, silent payments would have to be disabled when dealing with hardware devices?

Imported Wallets: Imported wallets can have mixed script types, since users can manually import keys with prefixes that define the script type. Deciding if silent payments should be enabled in imported wallets comes down to the same question as for standard wallets: Can a user import a private key that unlocks an input of a type other than in IFSSD? In pseudo code, could

wallet.wallet_type == "imported" and all([utxo.script_type in ["p2tr", "p2pkh", "p2wpkh", "p2sh-p2wpkh"] for utxo in wallet.get_utxos()]

ever evaluate to false? If this is the case, I think imported wallets should be excluded for silent payments, because it would be quite confusing for a user if sending to a silent payment address suddenly wasn't an option anymore, just because the user ran out of eligible inputs, but still has a high enough balance.

Multisig and 2fa-wallets: Honestly, I'm not sure whether the authors of BIP-352 intended silent payments to be used with these wallet types. As I understand it, Electrum uses p2wsh for multisig, while BIP-352 requires at least one input from an IFSSD. So, for a multisig wallet to make a silent payment, it would need to coordinate with a wallet that can provide such an input — which seems complex and likely out of scope for this issue, but I'm going out on a limb here.

Segwit version > 1

BIP-352 forbids inputs with SegWit version > 1. Since Electrum doesn’t yet support taproot, I believe this won’t be an issue in the foreseeable future.

Sighash Flags

Using SIGHASH_ANYONECANPAY is explicitly unsafe for sender silent payments. This is because adding inputs after the sender has signed the transaction would break shared secret derivation.

From what I’ve seen in transaction.py, Electrum does support ANYONECANPAY. A few open questions:

  • What are Electrum’s current use cases for ANYONECANPAY?

  • Can users explicitly choose or control the sighash type in a standard wallet?

Replaceby fee (RBF)

As noted by @SomberNight in #8847 RBF could lead to issues if the input set changes, which breaks the shared secret. This edge case needs to be handled carefully — either by disallowing RBF entirely when silent payment outputs are involved, or by ensuring that the shared secret is recomputed consistently after any input modification.

Conclusion

To keep things practical and intuitive, implementation should focus on only enabling sender silent payments for:

  • Standard wallets, where the script type is guaranteed to be in the IFSSD set

  • (Maybe) Imported wallets, but only if we can ensure user clarity. Also, as stated here it is recommended to sweep private keys anyway, rather than import it.

Multisig and 2FA wallets are likely too complex or out of scope for this feature right now.

Thanks for any help!

MorenoProg avatar Apr 14 '25 16:04 MorenoProg

Good questions and reasoning, my thoughts are included below. 👏

Planning Sender Silent Payment Support in Electrum

I'm currently working on implementing sender-side silent payments in Electrum (as described in BIP-352). In doing so, I've come across several points that need clarification regarding Electrum's wallet types, supported sighash flags, and how script types are handled.

Please correct me if I've misunderstood anything or if you spot inaccuracies in the assumptions below.

Silent Payment Requirements from BIP-352

  • At least one input MUST be from the (IFSSD) Inputs For Shared Secret Derivation list. TL;DR: IFSSD = [p2tr, p2pkh, p2wpkh, p2sh-p2wpkh]
  • No Input with SegWit version > 1 can be used
  • The sender should sign with one of the sighash flags DEFAULT, ALL, SINGLE, NONE (ANYONECANPAY is unsafe). It is strongly recommended implementations use SIGHASH_ALL (SIGHASH_DEFAULT for taproot inputs) when possible

Determining Wallet Compatibility with Silent Payments

To narrow the scope of implementation, I want to enable sending to silent payment addresses only when it's practically and safely possible, based on the wallet type and its UTXO script types. For this, I examined the different wallet types a user can create (either via QT or commands) and decided, if the 3 criterias above can be fulfilled:

Standard Wallet (deterministic single-keystore): As confirmed here, Electrum never mixes script types unless the wallet is of type “imported”. Therefore, for standard wallets, it's safe to determine whether silent payment is supported solely based on txin_type:

enable_silent_payment = wallet.wallet_type == "standard" and wallet.txin_type in IFSSD

(I know that electrum can't spend p2tr yet, I leave it anyway to be consistent with bip-352.)

My open questions here:

  • Standard wallets can be created in various ways. Are there cases where a standard wallet might use a script type not in IFSSD? For example, when initialized from an extended master private key?

Not likely, probably safe to assume no and provide proper error if a txin_type does not match expected list: p2tr, p2wpkh, p2sh-p2wpkh, p2pkh

  • In other words: Would a simpler check like wallet.wallet_type == "standard" already be sufficient?

Possibly, no need to optimize though.

Hardware-backed Standard Wallets

I couldn’t test this due to lack of hardware. But having a watch-only wallet doesn't work, of course, because the private keys are needed to compute the shared secret. So building the transaction would also have to take place on the hardware device, in which I have no experience. So, silent payments would have to be disabled when dealing with hardware devices?

Spend key pair can be derived and stored on hardware wallet for security. Scan private key will need to be known to scan for payments (although scan private key is named private key it functions more like a xpub in terms of use and privacy)

Imported Wallets: Imported wallets can have mixed script types, since users can manually import keys with prefixes that define the script type. Deciding if silent payments should be enabled in imported wallets comes down to the same question as for standard wallets: Can a user import a private key that unlocks an input of a type other than in IFSSD? In pseudo code, could

wallet.wallet_type == "imported" and all([utxo.script_type in ["p2tr", "p2pkh", "p2wpkh", "p2sh-p2wpkh"] for utxo in wallet.get_utxos()]

ever evaluate to false? If this is the case, I think imported wallets should be excluded for silent payments, because it would be quite confusing for a user if sending to a silent payment address suddenly wasn't an option anymore, just because the user ran out of eligible inputs, but still has a high enough balance.

agree it complicates coin selection UX but could be supported - if at least 1 input has pub key to give sender the needed key information to derive SS

Multisig and 2fa-wallets: Honestly, I'm not sure whether the authors of BIP-352 intended silent payments to be used with these wallet types. As I understand it, Electrum uses p2wsh for multisig, while BIP-352 requires at least one input from an IFSSD. So, for a multisig wallet to make a silent payment, it would need to coordinate with a wallet that can provide such an input — which seems complex and likely out of scope for this issue, but I'm going out on a limb here.

Multisig is out of scope, Musig2 and Frost should accommodate in the future @josibake (Anything else to include here?) https://bips.dev/375/

Segwit version > 1

BIP-352 forbids inputs with SegWit version > 1. Since Electrum doesn’t yet support taproot, I believe this won’t be an issue in the foreseeable future.

Sighash Flags

Using SIGHASH_ANYONECANPAY is explicitly unsafe for sender silent payments. This is because adding inputs after the sender has signed the transaction would break shared secret derivation.

From what I’ve seen in transaction.py, Electrum does support ANYONECANPAY. A few open questions:

  • What are Electrum’s current use cases for ANYONECANPAY?
  • Can users explicitly choose or control the sighash type in a standard wallet?

Replaceby fee (RBF)

As noted by @SomberNight in #8847 RBF could lead to issues if the input set changes, which breaks the shared secret. This edge case needs to be handled carefully — either by disallowing RBF entirely when silent payment outputs are involved, or by ensuring that the shared secret is recomputed consistently after any input modification.

Not allowing the user to use RBF seems sensible at this time

Conclusion

To keep things practical and intuitive, implementation should focus on only enabling sender silent payments for:

  • Standard wallets, where the script type is guaranteed to be in the IFSSD set

Yes, great start

  • (Maybe) Imported wallets, but only if we can ensure user clarity. Also, as stated here it is recommended to sweep private keys anyway, rather than import it.

Multisig and 2FA wallets are likely too complex or out of scope for this feature right now.

Thanks for any help!

macgyver13 avatar May 17 '25 00:05 macgyver13

I have been working hard on this issue and came to conclusive answers in the meantime:

Scope: Silent Payments are currently limited to single-sig standard wallets with a BIP32 keystore. This guarantees that the script type will be one of "p2wpkh", "p2sh-p2wpkh", or "p2pkh"—all of which are compatible with shared secret derivation. The "old" keystore type is not supported, as it generates p2pkh scripts with uncompressed public keys, which are incompatible with Silent Payments. Similarly, imported keystores are excluded, since they may contain p2pkh addresses backed by uncompressed keys as well. Users can always sweep such coins into a supported standard wallet if needed.

RBF: Replace-By-Fee will be disabled for transactions containing Silent Payments. This is critical because the output scripts depend on the input set. When Electrum bumps a fee, it first tries to reduce the change amount; if that fails, it adds new inputs—altering the input set and invalidating the Silent Payment outputs unless they are recalculated. While output recalculation is feasible, it relies on one key assumption: the fee bump must occur on the same device that created the original transaction. Otherwise, there's no way to detect that a transaction includes Silent Payments, as that context isn't shared across devices. I know this may be a rare case, but one that could lead to real issues if overlooked—users running the same wallet on multiple devices could unknowingly bump fees and create invalid outputs, leading to potential fund loss. Proper support would likely require syncing Silent Payment metadata through Electrum servers. (which somewhat bites the privacy model of Silent Payments). In any case, users can still use Child Pays for Parent (CPFP) if a transaction gets stuck in the mempool...

Progress of implementation:

So far, I’ve implemented sending to Silent Payment addresses using the following approach:

  • Silent Payments can be used just like regular payments from the Send tab. This includes single-line, multiline, BIP21 URIs, or contacts. You can mix and match SP and non-SP outputs in a single transaction.
  • SP outputs are added with the intended amount and a 34-byte dummy scriptPubKey, since the actual script can’t be known until the transaction inputs are selected.
  • Once the payment is confirmed, the rest of the transaction (input selection, fee estimation, etc.) is handled by Electrum’s existing logic.
  • After inputs are finalized, the correct SP scriptPubKeys are derived and used to replace the dummy values in the corresponding outputs.
  • Below is a screenshot showing a finalized and broadcasted transaction that includes a Silent Payment.
Image

Problem: There is an ongoing problem, for which I haven't found a smooth solution yet. Regarding the way electrum handles invoices. In the send tab, electrum lists invoices that are not confirmed yet. Electrum lets users save invoices before building the transaction. This works for standard addresses, where the scriptPubKey is known in advance. But Silent Payments generate their scriptPubKey only at transaction creation, based on input keys. In short: Electrum’s current invoice model, invoice tracking and status updates assumes known scripts, which Silent Payments break. I tried to support invoice persistence for Silent Payments by introducing a new field to store the sp_addr and modifying the display logic to show this instead of the placeholder or the final scriptPubKey. However, this required keeping invoices in sync with the finalized transaction outputs, which became increasingly complex—especially when multiple invoices are batched into a single transaction. After investing significant time into this, I'm now considering disabling invoice persistence for Silent Payments altogether. The transaction will still appear in the history tab, where its status can be tracked, and users can perform follow-up actions like CPFP from there. I'm not sure how critical it is to persist Silent Payment invoices, but given the added complexity and edge cases, omitting persistence may be the more robust and maintainable approach for now.

MorenoProg avatar May 19 '25 20:05 MorenoProg

Submitted PR #9900, looking forward to feedback!

MorenoProg avatar Jun 05 '25 15:06 MorenoProg

Regarding receiving silent payments, which apparently cake-wallet implemented as an undocumented modification of the electrum protocol, I wrote a lot of comments (regarding pitfalls of their implementation and how we would have to do it differently) here: https://github.com/cake-tech/cake_wallet/issues/2395

SomberNight avatar Jul 18 '25 17:07 SomberNight