[Draft / POC] Silent Payments #24897

pull w0xlt wants to merge 19 commits into bitcoin:master from w0xlt:silent_payment_021 changing 56 files +3538 −141
  1. w0xlt commented at 0:37 am on April 17, 2022: contributor

    This PR proposes an early version of Silent Payment (author:@RubenSomsen). In this scheme, the recipient generates a public address, but the sender tweaks the address and the recipient detects the payment by verifying all transactions on the blockchain. An example use case would be private donations.

    The purpose of this PR is not a final version, but to start the discussion and get benchmarks based on a real implementation.

    This version is built on top of #994 (bitcoin-core/secp256k1) for x-only ECDH support and #23480 (bitcoin/bitcoin) for rawtr(). Each new silent transaction detected is stored in wallet as a rawtr() descriptor.

    In this implementation, the sender can tweak the recipient address by passing the silent_payment option to send RPC. The transaction output will be different from the address entered.

    For example ./src/bitcoin-cli -regtest -named send outputs="[{\"bcrt1pwlh5xuyrpgfunwyww8cfu78yfs2yqyevl7yturavahh5kgxwdd2q5hzgfu\": 1.1}]" fee_rate=1 options="{ \"silent_payment\": true}".

    will generate vout with completely unrelated outputs:

     0"vout": [
     1    {
     2      "value": 1.10000000,
     3      "n": 0,
     4      "scriptPubKey": {
     5        "desc": "rawtr(65b19890c5ca40edb816d26f5f48cd9f3ed51121613b1c2405adc1a6dbbc824a)#8myx9tcu",
     6        "address": "bcrt1pvkce3yx9efqwmwqk6fh47jxdnuld2yfpvya3cfq94hq6dkausf9qrfjkgz",
     7
     8      }
     9    },
    10    {
    11      "value": 2.02499835,
    12      "n": 1,
    13      "scriptPubKey": {
    14        "desc": "rawtr(c45cb3d500bbf8f0c8841e8e011b008781d826c16ee348edb822c0f97419bc4d)#26hcce63",
    15        "address": "bcrt1pc3wt84gqh0u0pjyyr68qzxcqs7qasfkpdm353mdcytq0jaqeh3xsuvlykg",
    16      }
    17    }
    18  ]
    

    Any wallet, as long as it has access to private keys, can send silent payments. Thus, this excludes watch-only wallets or wallets with external signers .

    But the recipient’s wallet needs a new flag called SILENT_PAYMENT. This flag allows an additional scan that verifies that the wallet keys match the silent payment scheme. When it detects a silent payment that belongs to the wallet, it is stored in a rawtr() descriptor.

    ./src/bitcoin-cli -regtest -named createwallet wallet_name="recipient" silent_payment=true

    Therefore, scanning each address for each transaction is potentially prohibitive overhead, so the node can be initialized with keypool=1 or a descriptor with range [0,1] can be imported into a blank wallet. Until there is more benchmark data, it is the safest option. The proposal recommends one static address.

    I’ve been running some silent payments on signet using wallets with default keypool and default range, I haven’t noticed any relevant performance drops on the signet node. Apparently this implementation is working as expected but I can’t guarantee that the scheme is implemented correctly or safely, so I’m opening this PR for reviews, modifications and improvements.

    There is a new functional test (test/functional/wallet_silentpayment.py) that can help to better understand the implementation.

  2. DrahtBot added the label Build system on Apr 17, 2022
  3. DrahtBot added the label Descriptors on Apr 17, 2022
  4. DrahtBot added the label RPC/REST/ZMQ on Apr 17, 2022
  5. DrahtBot added the label Upstream on Apr 17, 2022
  6. DrahtBot added the label Utils/log/libs on Apr 17, 2022
  7. DrahtBot added the label Wallet on Apr 17, 2022
  8. DrahtBot commented at 4:48 am on April 17, 2022: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    Concept ACK ghost, rajarshimaitra, kristapsk

    If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #bitcoin-core/gui/650 (Add Import to Wallet GUI by KolbyML)
    • #27215 (wallet: Turn destdata entries into CAddressBookData fields by achow101)
    • #26951 (wallet: improve rescan performance 640% by pstratem)
    • #26858 (wallet: Use defined purposes instead of inlining by aureleoules)
    • #26840 (refactor: importpubkey, importprivkey, importaddress, importmulti, and importdescriptors rpc by KolbyML)
    • #26836 (wallet: finish addressbook encapsulation by furszy)
    • #26728 (wallet: Have the wallet store the key for automatically generated descriptors by achow101)
    • #26177 (refactor / kernel: Move non-gArgs chainparams functionality to kernel by TheCharlatan)
    • #26129 (wallet, refactor: FundTransaction(): return out-params as util::Result structure by theStack)
    • #26094 (rpc: Return block hash & height in getbalances, gettransaction and getwalletinfo by aureleoules)
    • #26076 (Switch hardened derivation marker to h (in normalized descriptors and new wallets) by Sjors)
    • #26008 (wallet: cache IsMine scriptPubKeys to improve performance of descriptor wallets by achow101)
    • #25991 (Wallet: Add foreign_outputs metadata to support CoinJoin transactions by luke-jr)
    • #25907 (wallet: rpc to add automatically generated descriptors by achow101)
    • #25806 (wallet: group outputs only once, decouple it from Coin Selection by furszy)
    • #25766 (wallet: Include a signature with encrypted keys to mitigate a wallet scam by achow101)
    • #25722 (refactor: Use util::Result class for wallet loading by ryanofsky)
    • #25634 (wallet, tests: Expand and test when the blank wallet flag should be un/set by achow101)
    • #25620 (wallet: Introduce AddressBookManager by furszy)
    • #25297 (wallet: group independent db writes on single batched db transaction by furszy)
    • #25273 (wallet: Pass through transaction locktime and preset input sequences and scripts to CreateTransaction by achow101)
    • #24914 (wallet: Load database records in a particular order by achow101)
    • #23561 (BIP324: Handshake prerequisites by dhruv)
    • #23432 (BIP324: CKey encode/decode to elligator-swift by dhruv)
    • #22838 (descriptors: Be able to specify change and receiving in a single descriptor string by achow101)
    • #22341 (rpc: add getxpub by Sjors)
    • #19690 (util: improve FindByte() performance by LarryRuane)
    • #19461 (multiprocess: Add bitcoin-gui -ipcconnect option by ryanofsky)
    • #19460 (multiprocess: Add bitcoin-wallet -ipcconnect option by ryanofsky)
    • #10102 (Multiprocess bitcoin by ryanofsky)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  9. ghost commented at 1:54 am on April 18, 2022: none
    Concept ACK
  10. in src/wallet/silentpayment.cpp:64 in b97fee2f44 outdated
    59+    XOnlyPubKey xOnlyPubKey = XOnlyPubKey(pubKey);
    60+
    61+    return xOnlyPubKey;
    62+}
    63+
    64+void Recipient::CreateTweakedPubkey(
    


    pinheadmz commented at 2:08 pm on April 18, 2022:
    I’m just trying to understand the protocol better. This function is called “create tweaked pubkey” but what seems more significant about it to me is that it also provides the recipient with the private key they need to spend from the silent payment output. Is that correct?

    w0xlt commented at 3:10 pm on April 18, 2022:

    Yes. I defined this name when using another approach, but that’s the idea.

    secp256k1_keypair_xonly_tweak_add returns both the tweaked private and public keys in the recipient_keypair parameter. So the recipient can spend the output.

  11. DrahtBot added the label Needs rebase on Apr 18, 2022
  12. w0xlt force-pushed on Apr 19, 2022
  13. DrahtBot removed the label Needs rebase on Apr 19, 2022
  14. luke-jr commented at 7:06 pm on April 19, 2022: member
    Need some way to avoid users trying to send a silent payment to a wallet that doesn’t support it… am I missing something?
  15. w0xlt commented at 7:12 am on April 20, 2022: contributor

    @luke-jr Yes, it will need to be further discussed if this concept proves to be valid.

    In this PR, all descriptor wallets support silent transactions as the SILENT_PAYMENT flag can be enabled or disabled at any time. The rescanblockchain RPC can be used to retrieve previous silent payments.

    As only Taproot addresses can be used for silent payments and only descriptor wallets support Taproot, this may suffice, but it certainly needs further discussion.

  16. RubenSomsen commented at 8:45 am on April 20, 2022: none

    Need some way to avoid users trying to send a silent payment to a wallet that doesn’t support it @luke-jr When the recipient publishes their silent payment address, it needs to be clear that it is not a regular taproot address. The address format should be distinct. The tweaked silent payment address that is generated by the sender and is ultimately committed on-chain should be indistinguishable from a regular taproot output.

    only Taproot addresses can be used for silent payments @w0xlt Note that nothing about the silent payment scheme is exclusive to taproot, but regardless I do think it’s good to limit the specifications to creating taproot outputs only as it encourages taproot use and means transactions without taproot outputs (albeit hopefully rare in the eventual future) don’t need to be scanned.

  17. luke-jr commented at 6:00 pm on April 20, 2022: member

    When the recipient publishes their silent payment address, it needs to be clear that it is not a regular taproot address. The address format should be distinct.

    But I don’t see that in this current spec/code?

  18. w0xlt force-pushed on Apr 22, 2022
  19. DrahtBot added the label Needs rebase on Apr 26, 2022
  20. w0xlt force-pushed on Apr 27, 2022
  21. w0xlt force-pushed on Apr 27, 2022
  22. DrahtBot removed the label Needs rebase on Apr 28, 2022
  23. w0xlt force-pushed on Apr 28, 2022
  24. JeremyRubin commented at 4:32 am on April 28, 2022: contributor

    Need some way to avoid users trying to send a silent payment to a wallet that doesn’t support it… am I missing something?

    it’s worse than that, AFAIU the address needs to support it not just the wallet – E.g., what if the Tr key is a NUMS point?

    Worth making a new address type / format so that you never mix the two.

  25. w0xlt force-pushed on May 5, 2022
  26. w0xlt force-pushed on May 8, 2022
  27. w0xlt force-pushed on May 8, 2022
  28. w0xlt commented at 6:54 pm on May 16, 2022: contributor

    @luke-jr @JeremyRubin Thanks for the comments. The current version deliberately does not implement a new address/descriptor format. This requires a separate discussion and changes in wallet behavior. And it doesn’t make sense to start a discussion about it before the scheme proves itself valid in a viable time.

    Currently the main focus is to validate the scheme and verify that the performance is viable.

  29. w0xlt commented at 6:55 pm on May 16, 2022: contributor
    I created a short and simple tutorial on how to make silent payments on signet. https://gist.github.com/w0xlt/72390ded95dd797594f80baba5d2e6ee
  30. DrahtBot added the label Needs rebase on May 17, 2022
  31. w0xlt force-pushed on May 22, 2022
  32. DrahtBot removed the label Needs rebase on May 22, 2022
  33. prusnak commented at 4:47 pm on May 23, 2022: contributor
    @w0xlt I am sorry if this is documented somewhere, but how do you determine which input carries the public key for ECDH?
  34. w0xlt commented at 11:36 pm on May 23, 2022: contributor

    @prusnak It s not well documented as well as being discussed.

    In the current implementation, for simplicity, only the first input is used.

    There is a suggestion by @RubenSomsen to use a hash of all inputs. This would make silent payments compatible with coinjoins.

    This is a possibility of change if the current version proves viable, since there shouldn’t be much difference in performance between the two versions.

    I posted some estimates here.

  35. in src/index/silentpaymentindex.cpp:85 in 8f616acf86 outdated
    80+    else if (whichType == TxoutType::PUBKEYHASH) {
    81+
    82+        int sigSize = static_cast<int>(scriptSig[0]);
    83+        int pubKeySize = static_cast<int>(scriptSig[sigSize + 1]);
    84+        auto serializedPubKey = std::vector<unsigned char>(scriptSig.begin() + sigSize + 2, scriptSig.end());
    85+        assert(serializedPubKey.size() == pubKeySize);
    


    unknown commented at 10:15 am on May 24, 2022:
    warning: comparison of integer expressions of different signedness: ‘std::vector::size_type’ {aka ‘long unsigned int’} and ‘int’ [-Wsign-compare]

  36. DrahtBot added the label Needs rebase on May 24, 2022
  37. unknown approved
  38. rajarshimaitra commented at 5:07 pm on May 24, 2022: contributor

    Huge Concept ACK..

    This is amazing.. 🔥🔥🔥🔥

  39. a5an0 commented at 0:12 am on May 26, 2022: none
    I followed the tutorial and it worked great on signet
  40. kristapsk commented at 9:54 am on May 26, 2022: contributor
    Concept ACK
  41. in src/wallet/silentpayment.cpp:1 in 8f616acf86 outdated
    0@@ -0,0 +1,115 @@
    1+#include "silentpayment.h"
    


    kristapsk commented at 10:01 am on May 26, 2022:

    Linter error:

    0Please use bracket syntax includes ("#include <foo.h>") instead of quote syntax includes:
    1src/wallet/silentpayment.cpp:#include "silentpayment.h"
    2^---- failure generated from lint-includes.py
    

  42. in src/wallet/wallet.cpp:1249 in 8f616acf86 outdated
    1273-            // Block disconnection override an abandoned tx as unconfirmed
    1274-            // which means user may have to call abandontransaction again
    1275-            TxState tx_state = std::visit([](auto&& s) -> TxState { return s; }, state);
    1276-            return AddToWallet(MakeTransactionRef(tx), tx_state, /*update_wtx=*/nullptr, /*fFlushOnClose=*/false, rescanning_old_block);
    1277+    // TODO: can more than one txin.prevout be in the mempool (RBF) ?
    1278+    assert( ++pos == coins.end() );
    


    kristapsk commented at 10:02 am on May 26, 2022:

    Linter error:

    0Assertions should not have side effects:
    1src/wallet/wallet.cpp:    assert( ++pos == coins.end() );
    

  43. in src/wallet/silentpayment.h:2 in 8f616acf86 outdated
    0@@ -0,0 +1,38 @@
    1+#ifndef BITCOIN_SILENT_PAYMENT_H
    2+#define BITCOIN_SILENT_PAYMENT_H
    


    kristapsk commented at 10:06 am on May 26, 2022:
    0#ifndef BITCOIN_WALLET_SILENTPAYMENT_H
    1#define BITCOIN_WALLET_SILENTPAYMENT_H
    

    Linter does not like the name of include guard define:

    0src/wallet/silentpayment.h seems to be missing the expected include guard:
    1  #ifndef BITCOIN_WALLET_SILENTPAYMENT_H
    2  #define BITCOIN_WALLET_SILENTPAYMENT_H
    3  ...
    4  #endif // BITCOIN_WALLET_SILENTPAYMENT_H
    

  44. in src/rpc/blockchain.cpp:1936 in 8f616acf86 outdated
    1933+        throw JSONRPCError(RPC_VERIFY_ERROR, "The silent payment index is either disabled or not yet synced.");
    1934+    }
    1935+
    1936+    ChainstateManager& chainman = EnsureChainman(node);
    1937+    LOCK(cs_main);
    1938+    CChainState& active_chainstate = chainman.ActiveChainstate();
    


    kristapsk commented at 10:12 am on May 26, 2022:
    0rpc/blockchain.cpp: In function bool {anonymous}::CheckSilentPayment(uint256&, const CScript&, std::vector<std::tuple<FlatSigningProvider, CScript> >&, node::NodeContext&, std::vector<uint256>&, std::vector<XOnlyPubKey>&, std::map<CScript, std::__cxx11::basic_string<char> >&):
    1rpc/blockchain.cpp:1936:18: warning: unused variable active_chainstate [-Wunused-variable]
    2 1936 |     CChainState& active_chainstate = chainman.ActiveChainstate();
    3      |                  ^~~~~~~~~~~~~~~~~
    

  45. in src/wallet/wallet.cpp:1292 in 8f616acf86 outdated
    1316+    else if (whichType == TxoutType::PUBKEYHASH) {
    1317+
    1318+        int sigSize = static_cast<int>(scriptSig[0]);
    1319+        int pubKeySize = static_cast<int>(scriptSig[sigSize + 1]);
    1320+        auto serializedPubKey = std::vector<unsigned char>(scriptSig.begin() + sigSize + 2, scriptSig.end());
    1321+        assert(serializedPubKey.size() == pubKeySize);
    


    kristapsk commented at 10:19 am on May 26, 2022:
    0In file included from wallet/wallet.cpp:44:
    1wallet/wallet.cpp: In member function bool wallet::CWallet::ExtractPubkeyFromInput(const CTxIn&, XOnlyPubKey&):
    2wallet/wallet.cpp:1292:40: warning: comparison of integer expressions of different signedness: std::vector<unsigned char>::size_type {aka long unsigned int} and int [-Wsign-compare]
    3 1292 |         assert(serializedPubKey.size() == pubKeySize);
    4      |                ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~
    

  46. in src/wallet/silentpayment.cpp:72 in 8f616acf86 outdated
    67+    secp256k1_keypair& recipient_keypair)
    68+{
    69+    unsigned char recipient_secret_key[32];
    70+    unsigned char shared_secret[32];
    71+
    72+    secp256k1_pubkey output_pubkey;
    


    kristapsk commented at 10:20 am on May 26, 2022:
    0wallet/silentpayment.cpp: In static member function static void silentpayment::Recipient::CreateTweakedKeyPair(const secp256k1_context*, secp256k1_xonly_pubkey, secp256k1_keypair&):
    1wallet/silentpayment.cpp:72:22: warning: unused variable output_pubkey [-Wunused-variable]
    2   72 |     secp256k1_pubkey output_pubkey;
    3      |                      ^~~~~~~~~~~~~
    

  47. in test/functional/wallet_silentpayment.py:25 in 8f616acf86 outdated
    20+        pass
    21+
    22+    def invalid_create_wallet(self):
    23+        self.log.info("Testing the creation of invalid wallets")
    24+
    25+        assert_raises_rpc_error(-4, "Only descriptor wallets support silent payments.",
    


    Sjors commented at 9:22 am on June 2, 2022:
    Skip this test when compiled without legacy wallet support.

  48. in test/functional/wallet_silentpayment.py:32 in 8f616acf86 outdated
    27+
    28+        assert_raises_rpc_error(-4, "Silent payments require the ability to store private keys.",
    29+            self.nodes[1].createwallet, wallet_name='invalid_wallet', descriptors=True, disable_private_keys=True, silent_payment=True)
    30+
    31+        assert_raises_rpc_error(-4, "Silent payments require the ability to store private keys.",
    32+            self.nodes[1].createwallet, wallet_name='invalid_wallet',  descriptors=True, disable_private_keys=True, external_signer=True, silent_payment=True)
    


    Sjors commented at 9:24 am on June 2, 2022:
    Skip this test when compiled without external signer support.

  49. Sjors commented at 2:18 pm on June 2, 2022: member

    Very cool! It’s nice to be able to test this concept with “real” demo software. I’ll share some thoughts about the concept on the mailinglist or gist. Will focus on the implementation here.

    I was able to send to signet coins to myself. Here’s the public key: tb1p0ykcmjxv26utyntns8ryn70at9t38zkdta9fj3zrrnpnczuddxvqf8qwac

    My first thought was that it may be better (initially) to implement silent payments as a standalone tool that uses the Bitcoin Core RPC.

    However, given that this implementation requires a new index, I don’t know how practical that is. Getting a new index added to Bitcoin Core however is also not easy (https://bitcoincore.reviews/14053). The light at the end the tunnel here would be a plugin system for indexes, which should be possible once process separation is complete. See #24230. That will take a while though.

    Another reason for building a separate tool that uses the RPC is that this proposal has a pretty big resource need, something that the project may not want to commit to anytime soon. The sending side is not resource intensive though, so perhaps that is easier to get in.

    Limiting the scope of this implementation to taproot makes sense. Limiting it to the first input seems brittle however, hopefully it’s not too difficult to scan them all. Other implementations might order the input coins differently. Also, if you go for the approach of building an external tool that uses the RPC, then you can could use send with 1 fixed input and have the wallet automatically select the other coins. But in that case the RPC gives no guarantee which position the input will end up (it might be the first in practice atm).

    In case the proposal is modified to consider all inputs, keep in mind that this makes coin selection (slightly) more complicated. The wallet has to decide, before it knows the output address, which inputs to use. That would involve some surgery in the way coin selection is integrated in the wallet. When implemented as an external tool, you would probably make two calls to send: one with a dummy destination and automatic coin selection to get the coins, and then one with manual coin selection and the real destination. But that’s not pretty, and won’t work if the wallet has a combination of taproot and non-taproot coins, because you currently can’t tell coin selection to only use taproot inputs.

    Importing a descriptor with a shorter range than the keypool size has no effect afaik, it’s just treated as a descriptor with range 0-1000. We could change that behavior, so that a descriptor imported with a fixed range is not expanded unless you re-import it. Not sure if that’s ideal, cc @achow101. Another option might be to make the keypool configurable per wallet, though that also feels a bit meh. A user may have multiple wallets, not all of which use this feature, so relying on a global keypool=1 setting is not ideal. And a factor 1000 slower scanning seems suboptimal.

    Another approach could be that a silent_payment wallet only ever generates 1 key, perhaps with a new silent(pk) descriptor type. That might also make it easier for getnewaddress to spit out something that’s not actually an address. See https://gist.github.com/RubenSomsen/c43b79517e7cb701ebf77eec6dbb46b8?permalink_comment_id=4177027#gistcomment-4177027 for discussion around what format the silent payment public key should be communicated in.

    Ideally the scriptPubKeys are stored in the silent(pk) descriptor. This would make getaddressinfo return the right descriptor. In that case getnewaddress should always fail, because the addresses are not known in advance. You’d need another way to communicate the public key. The easiest hack might be to return it as public-key version of the silent(pk) descriptor, so the user can find it with either listdescriptors or getaddressinfo on any received payment.

  50. w0xlt force-pushed on Jun 4, 2022
  51. DrahtBot removed the label Needs rebase on Jun 4, 2022
  52. w0xlt commented at 9:57 pm on June 5, 2022: contributor

    @Sjors @kristapsk @1440000bytes Thanks for reviews and tests. I addressed your suggestions in the last force-push. @Sjors I agree that using a separate wallet/tool ​​for silent payments can be a good solution. I initially considered this option, but it would imply re-implementing the scan logic. The tool must be able to request and de-serialize blocks and the UTXO Set to scan coins. So a new branch appeared to be the simplest way to implement a proof of concept.

    In case the proposal is modified to consider all inputs, keep in mind that this makes coin selection (slightly) more complicated.

    Yes. There are some tradeoffs involving how to use inputs for ECDH. I don’t think there’s a way to accept every type of input, as seen in https://github.com/bitcoin/bitcoin/pull/24897/commits/178e39d804a092016abe9650a1cde085173d6795.

    Example:

    0if (whichType == TxoutType::NONSTANDARD ||
    1    whichType == TxoutType::MULTISIG ||
    2    whichType == TxoutType::WITNESS_UNKNOWN ) {
    3        return false;
    4    }
    

    given that this implementation requires a new index,

    This index just speeds up the transaction verification. This is not necessary for Silent Payments, although in this particular implementation it is required for UX purposes. I’m getting some metrics to see if it can be removed and still have a good user experience.

    relying on a global keypool=1 setting is not ideal.

    Yes. This is just for testing and should not be required in an eventual final version.

    Another approach could be that a silent_payment wallet only ever generates 1 key, perhaps with a new silent(pk) descriptor type.

    I think this is the best solution. This would imply changing the getnewaddress RPC to always return the same address for this silent descriptor and allowing a non-ranged descriptor to be active.

  53. DrahtBot added the label Needs rebase on Jul 12, 2022
  54. w0xlt force-pushed on Jul 15, 2022
  55. w0xlt commented at 11:24 pm on July 15, 2022: contributor
    Rebased.
  56. ghost commented at 0:24 am on July 16, 2022: none

    Rebased.

    why is CI failing?

  57. DrahtBot removed the label Needs rebase on Jul 16, 2022
  58. w0xlt commented at 1:49 am on July 16, 2022: contributor

    @1440000bytes this PR is built on top of #23480 and https://github.com/bitcoin-core/secp256k1/pull/994, which have not yet been merged.

    One of the error is the same as #23480, which occurs in wallet_taproot.py. This is failing almost every CI check, but not on my machine (Ubuntu 21.10). Regarding the error inwallet_taproot.py, I suggested the solution in #23480 (comment), but had forgotten to commit it here. This error has now been fixed.

    The other error is a lint error about manually merging #994. I’m still not sure how to fix this lint error.

    EDIT: The only error now is the lint error mentioned above. From what I understand from the file test/lint/git-subtree-check.sh, this is because the src/secp256k1 subtree is compared with the master branch of https://github.com/bitcoin-core/secp256k1 and as it is different, it results in an error.

    If so, this error will remain until https://github.com/bitcoin-core/secp256k1/pull/994 is merged.

    Anyway, it’s a lint error (formatting basically). Does not affect functionality.

  59. w0xlt force-pushed on Jul 16, 2022
  60. w0xlt force-pushed on Jul 16, 2022
  61. w0xlt force-pushed on Jul 16, 2022
  62. luke-jr commented at 5:29 am on July 16, 2022: member

    Another approach could be that a silent_payment wallet only ever generates 1 key, perhaps with a new silent(pk) descriptor type. That might also make it easier for getnewaddress to spit out something that’s not actually an address.

    How would you apply a label and determine who the sender is?

    Also, being non-reversible from the UTXO doesn’t make it any less of an address…

  63. w0xlt commented at 6:03 am on July 16, 2022: contributor

    @luke-jr Not sure if I understand your question, but this scheme doesn’t change anything regarding the sender/recipient relationship. The sender will know the tweaked address and the recipient will know where the coins are coming from.

    What this proposal prevents is external observers from knowing or tracking how many coins or transactions a public address has. And it also prevents reuse of a public address (as it happens in on-chain donations, for example).

  64. luke-jr commented at 2:46 pm on July 16, 2022: member

    Not sure if I understand your question, but this scheme doesn’t change anything regarding the sender/recipient relationship. The sender will know the tweaked address and the recipient will know where the coins are coming from.

    The recipient knows who the sender is and the purpose of the transaction, based on the address the bitcoins were sent to. If getnewaddress gave the same address every call, it would be impossible to tell the difference between them.

    And it also prevents reuse of a public address (as it happens in on-chain donations, for example).

    I thought the whole point was to make reuse of such an address safe?

  65. w0xlt commented at 3:14 pm on July 16, 2022: contributor

    If getnewaddress gave the same address every call

    You can label the tweaked address. It is part of the wallet. The rawtr(pk) descriptor is normally added to wallet.

    I thought the whole point was to make reuse of such an address safe?

    Yes. That’s the point. The same address will be tweaked on every transaction

    As I understand it, the purpose of SP is primarily to increase privacy in a situation where you need to publicly post an address and anyone can send coins to it.

    If you need more control to categorize each address by payer, an HD wallet is better.

  66. luke-jr commented at 3:31 pm on July 16, 2022: member

    You can label the tweaked address. It is part of the wallet.

    That seems like a bad design… it should show the silent address.

    But more importantly, for the recipient to set a label, he has to know what it’s for first….

  67. w0xlt commented at 3:37 pm on July 16, 2022: contributor

    it should show the silent address.

    This can be done. The wallet knows that a certain transaction / address was found by verifying SP.

  68. unknown approved
  69. in src/wallet/spend.cpp:791 in 1e2c0760a6 outdated
    680+        }
    681+
    682+        auto recipientPubKey = XOnlyPubKey(solutions[0]);
    683+
    684+        auto destScriptPubKey = GetScriptForDestination(WitnessV1Taproot(recipientPubKey));
    685+        CTxDestination address2;
    


    unknown commented at 1:22 am on July 26, 2022:
    address2 is not being used

    w0xlt commented at 8:19 pm on August 1, 2022:
    Removed in d554e067c7715b736062312463fa8a1e36ed48c2. Thanks for review.
  70. w0xlt force-pushed on Aug 1, 2022
  71. w0xlt force-pushed on Aug 1, 2022
  72. DrahtBot added the label Needs rebase on Aug 2, 2022
  73. w0xlt force-pushed on Aug 3, 2022
  74. DrahtBot removed the label Needs rebase on Aug 3, 2022
  75. DrahtBot added the label Needs rebase on Aug 5, 2022
  76. w0xlt force-pushed on Aug 9, 2022
  77. DrahtBot removed the label Needs rebase on Aug 9, 2022
  78. DrahtBot added the label Needs rebase on Aug 9, 2022
  79. w0xlt force-pushed on Aug 10, 2022
  80. DrahtBot removed the label Needs rebase on Aug 10, 2022
  81. w0xlt force-pushed on Aug 16, 2022
  82. w0xlt commented at 0:09 am on August 17, 2022: contributor

    This PR has been updated with a new silent payment version, which eliminates some manual steps from the previous version (such as the need to set the keypool to avoid costly multi-key scan).

    This is achieved by using a new descriptor type (sp()) that has no range and contains exactly one key.

    Example: sp(cQq73sG9....JD51uaRD)#9llg6xjm

    This descriptor introduces a new type of output: silent-payment. This output type returns a standard Taproot script (Segwit V1), but with HRP changed from bc to sp on the mainnet (or tsp on testnet and signet).

    This output type will always generate the same address (unless another sp descriptor is enabled on the same wallet).

    0$ ./src/bitcoin-cli -signet getnewaddress '' 'silent-payment'
    1tsp1pfmjyl7ecpmx8yf8cu6g3ez36jy7s9mzuh5pdnal3k0n588uzgmfs4s4fws
    

    To create a silent transaction, simply use the silent payment address as one of the outputs. The send RPC will automatically identify and tweak it.

    The transaction can contain multiple outputs, combining silent and standard addresses.

    I have written a step by step signet tutorial so reviewers can test this new version easily. https://gist.github.com/w0xlt/a7b498ac1ff14b8c292a22be789bd93f

  83. DrahtBot added the label Needs rebase on Aug 17, 2022
  84. w0xlt force-pushed on Aug 17, 2022
  85. DrahtBot removed the label Needs rebase on Aug 17, 2022
  86. LLFourn commented at 1:37 pm on August 17, 2022: none
    is the idea of a scanning key included in this? I would have expected sp(...) to contain 64 bytes (two xonly keys).
  87. Kixunil commented at 3:24 pm on August 17, 2022: none
    I think there is a use case for multiple SP addresses and this is probably what @luke-jr meant. Let’s say I work on two Open Source projects. I could post the same SP address for both but it’d be nicer to post a separate SP address for each. Then I would be able to determine how much people appreciate each project. Perhaps most donations go only to one of them so I should focus on that one? It may be an advanced use case but a very reasonable one. I’d myself definitely prefer this over having one address.
  88. luke-jr commented at 4:03 pm on August 17, 2022: member
    Many people don’t receive only donations. If you have multiple clients, you need a different address for each so you know who is paying you.
  89. ghost commented at 4:03 pm on August 17, 2022: none
    @Kixunil 2 wallets (different silent payment address) for 2 projects would also make sense
  90. ghost commented at 4:05 pm on August 17, 2022: none

    Many people don’t receive only donations. If you have multiple clients, you need a different address for each so you know who is paying you. @luke-jr client could share txid in which he sent the payment

  91. Kixunil commented at 4:16 pm on August 17, 2022: none
    @luke-jr indeed, though you could already send a different xpub to each client and avoid some scanning costs. SP address may be nicer though, not sure. @1440000bytes yes, those hacks are available but I’d prefer to avoid such hacks because they significantly degrade UX.
  92. ghost commented at 4:28 pm on August 17, 2022: none

    @1440000bytes yes, those hacks are available but I’d prefer to avoid such hacks because they significantly degrade UX.

    Not sure I would consider it hack because it makes things simple. Even BIP 47 has one reusable code per wallet so I would find it confusing to have multiple silent payment address in the same wallet. Although no strong opinion and won’t mind anything as long as silent payment address is available in core.

  93. Kixunil commented at 4:41 pm on August 17, 2022: none
    • May make spending funds together impossible/difficult
    • Managing multiple wallets is PITA
    • Sending txid is just more work for humans that computers could do instead

    I think these are enough reasons to at least support multiple addresses in descriptors and leaving it up to wallets to decide whether to provide support for more than one in GUI/CLI/…

  94. w0xlt force-pushed on Aug 17, 2022
  95. w0xlt force-pushed on Aug 17, 2022
  96. w0xlt force-pushed on Aug 17, 2022
  97. luke-jr commented at 10:55 pm on August 17, 2022: member

    client could share txid in which he sent the payment

    Anyone could claim to have sent it. There is still no proof-of-sender in Bitcoin, and it doesn’t look like anyone is working on it.

    Even if it were possible, it’d still be a pain - this kind of thing should “just work” and label transactions, without extra steps from the users.

    Even BIP 47 has one reusable code per wallet

    BIP 47 was a bad design that shouldn’t be implemented nor imitated…

  98. w0xlt commented at 11:20 pm on August 17, 2022: contributor

    The default behavior for most descriptors is to be ranged and therefore support multiple addresses. If the idea is to have multiple sp addresses, a ranged sp version is sufficient (and is easier to implement than the current version).

    The decision to use a rangeless descriptor is for validation purposes. Verification of silent payments is costly. And if the user inadvertently generates multiple sp addresses, the overhead can be prohibitive. Until there are more reliable statistics on how many sp addresses a node (even on a raspberrey) can process (from mempool and when loading wallets), I think it’s prudent to keep only one active address per wallet.

    The label issue shouldn’t be complicated, I think. UTXO can inherit the same label assigned to the sp address or descriptor. I can work on that in the next iteration.

  99. RubenSomsen commented at 11:26 pm on August 17, 2022: none

    I think there is a use case for multiple SP addresses @Kixunil good point about wanting to identify the reason for the payment. Naively, the issue is that two keys means twice the scanning, but an interesting alternative would be to simply use the same key (assuming you’re OK with using the same identity) but add a public identifier f to it when tweaking. So instead of hash(i*X)*G + X you get hash(i*X)*G + X + f*G . This means every additional “address” only costs one additional ECC addition when scanning (relatively cheap compared to doing ECC multiplications).

    The main downside with this is that f becomes crucial for recovering from backup. If we set f as an index (0, 1, 2, 3…) then you’d only have to remember how many “addresses” you issued (and perhaps overshoot when unsure) to ensure recovery of funds, though of course you’d rather also remember which index is associated with what payment reason.

    Absolute worst case scenario you could even do something similar to the gap limit where you scan the full history (not just the UTXO set so you don’t miss spent outputs) with a default max index of e.g. 100, and then if you find out most of them are in use, you scan the next 100, etc. Costly, but thorough, and only needed as a last resort.

  100. DrahtBot added the label Needs rebase on Aug 19, 2022
  101. w0xlt force-pushed on Aug 20, 2022
  102. DrahtBot removed the label Needs rebase on Aug 20, 2022
  103. DrahtBot added the label Needs rebase on Aug 31, 2022
  104. ghost commented at 6:45 am on September 1, 2022: none

    This PR has been updated with a new silent payment version, which eliminates some manual steps from the previous version (such as the need to set the keypool to avoid costly multi-key scan).

    This is achieved by using a new descriptor type (sp()) that has no range and contains exactly one key.

    Example: sp(cQq73sG9....JD51uaRD)#9llg6xjm

    This descriptor introduces a new type of output: silent-payment. This output type returns a standard Taproot script (Segwit V1), but with HRP changed from bc to sp on the mainnet (or tsp on testnet and signet).

    This output type will always generate the same address (unless another sp descriptor is enabled on the same wallet).

    0$ ./src/bitcoin-cli -signet getnewaddress '' 'silent-payment'
    1tsp1pfmjyl7ecpmx8yf8cu6g3ez36jy7s9mzuh5pdnal3k0n588uzgmfs4s4fws
    

    To create a silent transaction, simply use the silent payment address as one of the outputs. The send RPC will automatically identify and tweak it.

    The transaction can contain multiple outputs, combining silent and standard addresses.

    I have written a step by step signet tutorial so reviewers can test this new version easily. https://gist.github.com/w0xlt/a7b498ac1ff14b8c292a22be789bd93f

    Tested the new version and everything worked fine except scanning UTXO set: https://gist.github.com/w0xlt/a7b498ac1ff14b8c292a22be789bd93f?permalink_comment_id=4286510#gistcomment-4286510

    I have updated the steps I followed here in tutorial section: https://silentbitco.in/

    Updated the new silent payment address in DNS as TXT record: tsp1pkr8e083j7vp0w82ry5s53rz3t26yf0p8dzg7gqs53r6y399kyw4qa8z52y


    Suggestion: We could add a new RPC command to create silent payment address instead of using getnewaddress. So the CLI command would look like this:

    0$ bitcoin-cli getspaddress
    
  105. w0xlt force-pushed on Sep 4, 2022
  106. w0xlt force-pushed on Sep 4, 2022
  107. w0xlt force-pushed on Sep 4, 2022
  108. DrahtBot removed the label Needs rebase on Sep 4, 2022
  109. w0xlt force-pushed on Sep 4, 2022
  110. DrahtBot added the label Needs rebase on Sep 13, 2022
  111. ghost commented at 4:56 pm on September 13, 2022: none

    @w0xlt can you rebase?

    The purpose of this PR is not a final version, but to start the discussion and get benchmarks based on a real implementation.

    T> his version is built on top of https://github.com/bitcoin-core/secp256k1/pull/994 (bitcoin-core/secp256k1) for x-only ECDH support and #23480 (bitcoin/bitcoin) for rawtr(). Each new silent transaction detected is stored in wallet as a rawtr() descriptor.

    Can you confirm this is one of the version that can be used for bitcoin?

  112. w0xlt force-pushed on Sep 15, 2022
  113. DrahtBot removed the label Needs rebase on Sep 15, 2022
  114. w0xlt force-pushed on Sep 15, 2022
  115. w0xlt commented at 3:48 pm on September 18, 2022: contributor

    @1440000bytes Done.

    I am not sure I understand your question. The idea of current implementation is to be used with Bitcoin. I’m evaluating the possibility of including more than one sp address per wallet, but that would be a future change .

  116. ghost commented at 3:55 pm on September 18, 2022: none
    Sorry I forgot to mention ‘mainnet’ after bitcoin. Wanted to know if this version is good enough for mainnet and what else could be done to get more reviews and eventually get this merged before v25.0
  117. w0xlt force-pushed on Sep 29, 2022
  118. w0xlt force-pushed on Sep 29, 2022
  119. w0xlt force-pushed on Sep 29, 2022
  120. w0xlt force-pushed on Sep 29, 2022
  121. w0xlt force-pushed on Sep 29, 2022
  122. w0xlt force-pushed on Sep 29, 2022
  123. w0xlt force-pushed on Sep 29, 2022
  124. luke-jr commented at 9:57 pm on September 29, 2022: member

    @1440000bytes This isn’t even close to ready for review yet… It still needs at least a BIP and support for generating multiple addresses. And the secp256k1 changes also haven’t been merged yet, much less included in the version Bitcoin Core uses.

    Suggestion: We could add a new RPC command to create silent payment address instead of using getnewaddress.

    Why? getnewaddress is the correct thing to use here…

  125. w0xlt commented at 10:03 pm on September 29, 2022: contributor

    This new version addresses most (or all) requests made in PR so far:

    . Implements the new scheme suggested by @RubenSomsen that allows multiple silent addresses per wallet with minimal overhead. . Implements a new RPC to retrieve silent addresses (suggested by @1440000bytes), which allows users to assign different labels to different addresses. That way, the user knows which silent address the UTXO came from (as suggested by @luke-jr).

    Example:

    0./src/bitcoin-cli -signet -rpcwallet="receiver" getspaddress
    1tsp001pjgcwd9p6f2rcgf35dlgvj77h2afylg6lp5cdn0cztrk4k54w99kqxn48tq
    2
    3# This will return the same address as above (both have no label)
    4./src/bitcoin-cli -signet -rpcwallet="receiver" getspaddress
    5tsp001pjgcwd9p6f2rcgf35dlgvj77h2afylg6lp5cdn0cztrk4k54w99kqxn48tq
    6
    7# New label, new address
    8./src/bitcoin-cli -signet -rpcwallet="receiver" getspaddress 'donation'
    9tsp011pjgcwd9p6f2rcgf35dlgvj77h2afylg6lp5cdn0cztrk4k54w99kq80t7lt 
    

    In this new scheme, the address has a new field called identifier, which tells the receiver and sender how to tweak the address correctly.

    If the receiver, for whatever reason, doesn’t know which identifiers have been used, there is no problem. The wallet can scan all identifiers from 0 to 99. Currently, only 100 different identifiers per wallet are allowed. This limit, however, can be increased at any time in the future.

    Unlike address formats so far, sp addresses are not script-related and may eventually include any additional information needed, such as an expiration timestamp (or block height). That way, users don’t have to track the address indefinitely.

    As usual I wrote a step by step tutorial: https://gist.github.com/w0xlt/c81277ae8677b6c0d3dd073893210875

  126. ghost commented at 10:08 pm on September 29, 2022: none

    @1440000bytes This isn’t even close to ready for review yet… It still needs at least a BIP and support for generating multiple addresses. And the secp256k1 changes also haven’t been merged yet, much less included in the version Bitcoin Core uses.

    Suggestion: We could add a new RPC command to create silent payment address instead of using getnewaddress.

    Why? getnewaddress is the correct thing to use here…

    I respect for your review. At least we know whats needed for bitcoin core/knots to use this.

  127. w0xlt commented at 10:12 pm on September 29, 2022: contributor

    @1440000bytes This new push addresses all requests made so far, but as @luke-jr said, there is still a long way to go before this PR is merged (if at all). https://github.com/bitcoin-core/secp256k1/pull/994 needs to be merged first.

    Why? getnewaddress is the correct thing to use here…

    I accepted the suggestion of @1440000bytes because getspaddress will not necessarily return a new address. If the user repeats the label, the address will be the same, so getnewaddress does not exactly represent the expected behavior.

  128. ghost commented at 10:41 pm on September 29, 2022: none

    @1440000bytes This new push addresses all requests made so far, but as @luke-jr said, there is still a long way to go before this PR is merged (if at all). bitcoin-core/secp256k1#994 needs to be merged first.

    Why? getnewaddress is the correct thing to use here…

    I accepted the suggestion of @1440000bytes because getspaddress will not necessarily return a new address. If the user repeats the label, the address will be the same, so getnewaddress does not exactly represent the expected behavior.

    I dont want this as another “proof of concept”

    Users need privacy right now and it will be an inspiration when core wallet does it with something like silent payment. We don’t need to share bitcoin address on social media.

    Ross https://twitter.com/raw_avocado/status/1575110869809143808 got it wrong with opsec by sharing email address on bitcointalk. Lot of users were tracked because they shared bitcoin address on social media. Lets fix this partially by allowing the users to share a silent payment address.

  129. ghost commented at 10:45 pm on September 29, 2022: none
  130. luke-jr commented at 11:07 pm on September 29, 2022: member

    This new version addresses most (or all) requests made in PR so far:

    I just finished going over the BIPs PRs and didn’t see one for this yet.

    In this new scheme, the address has a new field called identifier, which tells the receiver and sender how to set the address correctly.

    Wouldn’t this allow someone who obtains two addresses, to determine they are both for the same wallet? Seems like a major privacy problem.

    Currently, only 100 different identifiers per wallet are allowed. This limit, however, can be increased at any time in the future.

    Any limit seems problematic, but 100 is definitely too few.

    Why? getnewaddress is the correct thing to use here…

    I accepted the suggestion of @1440000bytes because getspaddress will not necessarily return a new address. If the user repeats the label, the address will be the same, so getnewaddress does not exactly represent the expected behavior.

    I see, that makes sense. getaccountaddress used to do something similar. We no longer have accounts, but maybe a more generic name getreusableaddress would be better. (I assume getnewaddress can also be used still?)

  131. in src/wallet/wallet.cpp:1335 in 06037565e9 outdated
    1300-            }
    1301-            return true;
    1302+        if (IsWalletFlagSet(WALLET_FLAG_SILENT_PAYMENT) && VerifySilentPayment(tx, rawTrKeys))
    1303+        {
    1304+            ret = AddSilentScriptKeyMan(tx, state, rescanning_old_block, rawTrKeys) || ret;
    1305         }
    


    aureleoules commented at 3:02 pm on September 30, 2022:

    in 06037565e907c020340e064a5612abfebf347cc9 could simplify code

    0bool ret = (fExisted || IsMine(tx) || IsFromMe(tx)) ? HandleNewTxBelongingToMe(tx, state, rescanning_old_block) : false;
    1if (IsWalletFlagSet(WALLET_FLAG_SILENT_PAYMENT) && VerifySilentPayment(tx, rawTrKeys)) {
    2    ret = AddSilentScriptKeyMan(tx, state, rescanning_old_block, rawTrKeys) || ret;
    3}
    4return ret;
    

    instead of

     0bool ret = false;
     1
     2if (fExisted || IsMine(tx) || IsFromMe(tx))
     3{
     4    ret = HandleNewTxBelongingToMe(tx, state, rescanning_old_block);
     5}
     6
     7if (IsWalletFlagSet(WALLET_FLAG_SILENT_PAYMENT) && VerifySilentPayment(tx, rawTrKeys))
     8{
     9    ret = AddSilentScriptKeyMan(tx, state, rescanning_old_block, rawTrKeys) || ret;
    10}
    11
    12return ret;
    

  132. in src/wallet/wallet.cpp:1238 in 06037565e9 outdated
    1233+
    1234 bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const SyncTxState& state, bool fUpdate, bool rescanning_old_block)
    1235 {
    1236     const CTransaction& tx = *ptx;
    1237     {
    1238         AssertLockHeld(cs_wallet);
    


    aureleoules commented at 3:05 pm on September 30, 2022:
    no need for this scope

  133. in src/wallet/wallet.cpp:1272 in 06037565e9 outdated
    1304+            ret = AddSilentScriptKeyMan(tx, state, rescanning_old_block, rawTrKeys) || ret;
    1305         }
    1306+
    1307+        return ret;
    1308     }
    1309     return false;
    


    aureleoules commented at 3:11 pm on September 30, 2022:
    this will never be reached

  134. in src/index/silentpaymentindex.cpp:172 in 79402460ad outdated
    167+
    168+    if (block.height < consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height) {
    169+        return true;
    170+    }
    171+
    172+    std::vector<std::pair<uint256, uint256>> items;
    


    aureleoules commented at 3:15 pm on September 30, 2022:

    3c05b6baaea9054d000cbce1390463cc150b80b1: explicit what these uint256 correspond to

    0    std::vector<std::pair<uint256, uint256>> items; // <tx_hash, keydata>
    

  135. in src/index/silentpaymentindex.cpp:45 in 79402460ad outdated
    35+bool SilentPaymentIndex::DB::WriteSilentPayments(const std::vector<std::pair<uint256, uint256>>& items)
    36+{
    37+    CDBBatch batch(*this);
    38+    for (const auto& tuple : items) {
    39+        batch.Write(std::make_pair(DB_SILENTPAYMENTINDEX, tuple.first), tuple.second);
    40+    }
    


    aureleoules commented at 3:16 pm on September 30, 2022:

    3c05b6baaea9054d000cbce1390463cc150b80b1

    0    for (const auto& [tx_hash, keydata] : items) {
    1        batch.Write(std::make_pair(DB_SILENTPAYMENTINDEX, tx_hash), keydata);
    2    }
    

  136. in src/index/silentpaymentindex.cpp:50 in 79402460ad outdated
    45+    : BaseIndex(std::move(chain), "silentpaymentindex"), m_db(std::make_unique<SilentPaymentIndex::DB>(n_cache_size, f_memory, f_wipe))
    46+{}
    47+
    48+SilentPaymentIndex::~SilentPaymentIndex() {}
    49+
    50+bool SilentPaymentIndex::ExtractPubkeyFromInput(const Coin& prevCoin, const CTxIn& txin,  XOnlyPubKey& senderPubKey)
    


    aureleoules commented at 3:20 pm on September 30, 2022:

    3c05b6baaea9054d000cbce1390463cc150b80b1

    0bool SilentPaymentIndex::ExtractPubkeyFromInput(const Coin& prevCoin, const CTxIn& txin,  XOnlyPubKey& senderPubKey) const
    

  137. in src/key_io.cpp:106 in 79402460ad outdated
    102     uint160 hash;
    103     error_str = "";
    104+    bool silent_payment{false};
    105+
    106+    const auto& silent_payment_hrp = params.SilentPaymentHRP();
    107+    auto dest_silent_payment_hrp = ToLower(str.substr(0, params.SilentPaymentHRP().size()));
    


    aureleoules commented at 3:25 pm on September 30, 2022:

    8a757e61b94f76f83bbcbe449edda710187af5f3 same below

    0    auto dest_silent_payment_hrp = ToLower(std::string_view(str).substr(0, params.SilentPaymentHRP().size()));
    

  138. in src/rpc/blockchain.cpp:2036 in 79402460ad outdated
    2030@@ -1983,6 +2031,14 @@ bool FindScriptPubKey(std::atomic<int>& scan_progress, const std::atomic<bool>&
    2031         if (needles.count(coin.out.scriptPubKey)) {
    2032             out_results.emplace(key, coin);
    2033         }
    2034+        for(const auto& sp_key_range: sp_keys_range) {
    2035+            CKey sp_key = std::get<0>(sp_key_range);
    2036+            std::pair<int64_t, int64_t> sp_range = std::get<1>(sp_key_range);
    


    aureleoules commented at 3:49 pm on September 30, 2022:

    c53eec1c25c4a94935ae8e775c09e71ef24a3d0b

    0        for(const auto& [sp_key, sp_range]: sp_keys_range) {
    

  139. in src/rpc/blockchain.cpp:2188 in 79402460ad outdated
    2188-            auto scripts = EvalDescriptorStringOrObject(scanobject, provider);
    2189-            for (CScript& script : scripts) {
    2190-                std::string inferred = InferDescriptor(script, provider)->ToString();
    2191-                needles.emplace(script);
    2192-                descriptors.emplace(std::move(script), std::move(inferred));
    2193+            // auto scripts = EvalDescriptorStringOrObject(scanobject, provider);
    


    aureleoules commented at 3:49 pm on September 30, 2022:

    c53eec1c25c4a94935ae8e775c09e71ef24a3d0b


  140. in src/rpc/blockchain.cpp:2191 in 79402460ad outdated
    2191-                needles.emplace(script);
    2192-                descriptors.emplace(std::move(script), std::move(inferred));
    2193+            // auto scripts = EvalDescriptorStringOrObject(scanobject, provider);
    2194+            auto ret = EvalDescriptorStringOrObject(scanobject);
    2195+            auto desc_str = std::get<0>(ret);
    2196+            auto range = std::get<1>(ret);
    


    aureleoules commented at 3:50 pm on September 30, 2022:

    c53eec1c25c4a94935ae8e775c09e71ef24a3d0b

    0            auto [desc_str, range] = EvalDescriptorStringOrObject(scanobject);
    

  141. in src/rpc/blockchain.cpp:2211 in 79402460ad outdated
    2211+                // if (std::find(sp_keys.begin(), sp_keys.end(), privKey) == sp_keys.end()) {
    2212+                //     sp_keys.push_back(privKey);
    2213+                // }
    2214+
    2215+                bool privkey_already_added{false};
    2216+                for (const std::tuple<CKey, std::tuple<int64_t, int64_t>> privkeyrange : sp_keys_range) {
    


    aureleoules commented at 3:53 pm on September 30, 2022:

    c53eec1c25c4a94935ae8e775c09e71ef24a3d0b

    0                for (const auto& [key, range] : sp_keys_range) {
    

  142. in src/rpc/blockchain.cpp:2220 in 79402460ad outdated
    2220+                        break;
    2221+                    }
    2222+                }
    2223+
    2224+                if (!privkey_already_added) {
    2225+                    sp_keys_range.push_back({privKey, range});
    


    aureleoules commented at 3:54 pm on September 30, 2022:

    c53eec1c25c4a94935ae8e775c09e71ef24a3d0b

    0                    sp_keys_range.emplace_back(privKey, range);
    

  143. in src/rpc/rawtransaction_util.cpp:119 in 79402460ad outdated
    115@@ -116,12 +116,17 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
    116             CTxOut out(0, CScript() << OP_RETURN << data);
    117             rawTx.vout.push_back(out);
    118         } else {
    119-            CTxDestination destination = DecodeDestination(name_);
    120+            ;
    


    aureleoules commented at 3:56 pm on September 30, 2022:

    155fcb69cb850953f7778387eab0611bb32c557e


  144. in src/rpc/rawtransaction_util.cpp:123 in 79402460ad outdated
    115@@ -116,12 +116,17 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
    116             CTxOut out(0, CScript() << OP_RETURN << data);
    117             rawTx.vout.push_back(out);
    118         } else {
    119-            CTxDestination destination = DecodeDestination(name_);
    120+            ;
    121+            auto decoded = DecodeDestinationIndicatingSP(name_);
    122+            CTxDestination destination{std::get<0>(decoded)};
    123+            bool silent_payment{std::get<1>(decoded)};
    124+            int32_t identifier{std::get<2>(decoded)};
    


    aureleoules commented at 3:58 pm on September 30, 2022:
    155fcb69cb850953f7778387eab0611bb32c557e use structured binding

  145. in src/rpc/rawtransaction_util.cpp:129 in 79402460ad outdated
    126             if (!IsValidDestination(destination)) {
    127                 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Bitcoin address: ") + name_);
    128             }
    129 
    130-            if (!destinations.insert(destination).second) {
    131+            if (!destinations.insert({destination, identifier}).second) {
    


    aureleoules commented at 3:58 pm on September 30, 2022:

    155fcb69cb850953f7778387eab0611bb32c557e

    0            if (!destinations.emplace(destination, identifier).second) {
    

  146. in src/rpc/util.cpp:1133 in 79402460ad outdated
    1128+    }
    1129+
    1130+    return {desc_str, range};
    1131+}
    1132+
    1133+std::vector<CScript> DescriptorToScripts(std::string desc_str, std::pair<int64_t, int64_t> range, FlatSigningProvider& provider)
    


    aureleoules commented at 3:59 pm on September 30, 2022:

    c53eec1c25c4a94935ae8e775c09e71ef24a3d0b also in header

    0std::vector<CScript> DescriptorToScripts(const std::string& desc_str, std::pair<int64_t, int64_t> range, FlatSigningProvider& provider)
    

  147. in src/rpc/util.cpp:1155 in 79402460ad outdated
    1150+        std::move(scripts.begin(), scripts.end(), std::back_inserter(ret));
    1151+    }
    1152+    return ret;
    1153+}
    1154+
    1155+std::vector<CKey> DescriptorToKeys(std::string desc_str, std::pair<int64_t, int64_t> range, FlatSigningProvider& provider)
    


    aureleoules commented at 4:00 pm on September 30, 2022:

    c53eec1c25c4a94935ae8e775c09e71ef24a3d0b

    0std::vector<CKey> DescriptorToKeys(const std::string& desc_str, std::pair<int64_t, int64_t> range, FlatSigningProvider& provider)
    

  148. in src/rpc/util.cpp:1170 in 79402460ad outdated
    1165+    }
    1166+    std::vector<CKey> ret;
    1167+    for (int i = range.first; i <= range.second; ++i) {
    1168+        FlatSigningProvider out;
    1169+        desc->ExpandPrivate(i, provider, out);
    1170+        //std::move(out.keys.begin(), out.keys.end(), std::back_inserter(ret));
    


    aureleoules commented at 4:00 pm on September 30, 2022:

    c53eec1c25c4a94935ae8e775c09e71ef24a3d0b


  149. in src/rpc/util.cpp:1171 in 79402460ad outdated
    1166+    std::vector<CKey> ret;
    1167+    for (int i = range.first; i <= range.second; ++i) {
    1168+        FlatSigningProvider out;
    1169+        desc->ExpandPrivate(i, provider, out);
    1170+        //std::move(out.keys.begin(), out.keys.end(), std::back_inserter(ret));
    1171+        for (auto& [_, privKey] : out.keys) {
    


    aureleoules commented at 4:01 pm on September 30, 2022:

    c53eec1c25c4a94935ae8e775c09e71ef24a3d0b

    0        for (const auto& [_, privKey] : out.keys) {
    

  150. in src/wallet/rpc/addresses.cpp:208 in 79402460ad outdated
    204+    auto address = request.params[0].get_str();
    205+
    206+    auto decoded = DecodeDestinationIndicatingSP(address);
    207+    CTxDestination dest{std::get<0>(decoded)};
    208+    bool silent_payment{std::get<1>(decoded)};
    209+    int32_t silent_payment_index{std::get<2>(decoded)};
    


    aureleoules commented at 4:02 pm on September 30, 2022:
    a0c5c6cf29835ee8315356e050c01db9252e9dc1 use structured binding

  151. in src/wallet/rpc/addresses.cpp:644 in 79402460ad outdated
    636@@ -553,7 +637,11 @@ RPCHelpMan getaddressinfo()
    637     LOCK(pwallet->cs_wallet);
    638 
    639     std::string error_msg;
    640-    CTxDestination dest = DecodeDestination(request.params[0].get_str(), error_msg);
    641+
    642+    auto decoded = DecodeDestinationIndicatingSP(request.params[0].get_str(), error_msg);
    643+    CTxDestination dest{std::get<0>(decoded)};
    644+    bool silent_payment{std::get<1>(decoded)};
    645+    int32_t silent_payment_index{std::get<2>(decoded)};
    


    aureleoules commented at 4:02 pm on September 30, 2022:
    8a757e61b94f76f83bbcbe449edda710187af5f3 use structured binding

  152. aureleoules commented at 4:15 pm on September 30, 2022: member
    Left some code review nits/minor I found while looking at the code. No need to address them now as I think this still needs conceptual review and benchmarking. Will review and test this PR further later.
  153. aureleoules commented at 12:24 pm on October 3, 2022: member

    Currently, only 100 different identifiers per wallet are allowed. This limit, however, can be increased at any time in the future.

    This identifier is encoded in the sp address, isn’t it sensitive information to leak? Any limit seems too low, especially 100. Can’t the identifier be encoded using a CompactSize?

  154. clarkmoody commented at 10:53 pm on October 3, 2022: none

    This identifier is encoded in the sp address, isn’t it sensitive information to leak?

    A common feature of these stealth address schemes (BIP47, BIP351) is that a static identifier may be safely associated with your identity without loss of privacy.

  155. luke-jr commented at 10:57 pm on October 3, 2022: member
    But that’s not correct. If someone acquires both, they now know they pay to the same wallet.
  156. RubenSomsen commented at 11:26 pm on October 3, 2022: none

    There seems to be some confusion about the function of the identifier. Its function is not to create more than one identity, but to be able to distinguish why someone paid you. For example, when the same entity is raising money on behalf of two different charities and wants to know for which of the two charities the sender intended their donation.

    To be absolutely clear, the payments that appear on-chain are still absolutely unlinkable by a third party observer, nor are they able to tell which identifier was used.

    In cases where you don’t want people to know that you’re the same entity, the identifier is insufficient – you’ll need a completely separate Silent Payment address which (roughly) doubles your scanning efforts, so it’s much more costly in terms of performance.

  157. luke-jr commented at 11:45 pm on October 3, 2022: member
    This is a serious privacy leak that no other invoice address format has right now (maybe Lightning? not sure), and is not generally expected/known.
  158. RubenSomsen commented at 0:26 am on October 4, 2022: none

    @luke-jr I suspect we’re not yet on the same page. Could you be specific about what you think is being leaked? Essentially all that is happening is that the person who is getting paid is publicly saying “you could send me money for purpose A or purpose B, so please tweak the final address a bit so I know for which purpose you sent me money”. The resulting on-chain address is still completely unrecognizable by outside parties.

    So essentially all that is happening is that a few extra secret bits of information are being transmitted to the recipient along with the payment. There is no impact on privacy other than the fact that the recipient voluntarily made a statement about how it would interpret those bits.

    Of course this is not equivalent to having a completely separate identity on which you could get paid, but that is not its purpose. I.e. the difference is A says pay me for X, B says pay me for Y, and A might be the same person as B but nobody knows, or A saying pay me for X or Y so we know the same person will receive the funds no matter if you pay for X or Y. Both are useful. One is more flexible than the other, but also more computationally expensive.

  159. luke-jr commented at 0:43 am on October 4, 2022: member
    In your example earlier, charity A does not necessarily want to leak that it is using the same service as charity B. Throughout Bitcoin’s history, there has never been any leak of such relationships between invoice addresses. It would be very unexpected, especially for it to be introduced with a privacy improvement!
  160. RubenSomsen commented at 0:57 am on October 4, 2022: none

    @luke-jr I agree it is a communication challenge to explain such a feature to users, but I hope you see now that it can be of benefit when used correctly. I also agree that this idea is not adequate in cases where you do not want people to know that the same entity is receiving money for separate purposes.

    Another example which may be more clear: maybe you’re a known open source developer who is openly working on multiple projects and you want to distinguish for which of these projects your supporters are donating.

  161. luke-jr commented at 3:08 am on October 4, 2022: member
    @RubenSomsen I agree it may have use cases, but if the user asks for a new address, they should get a new address, not a variation of an existing one. (Unless they explicitly ask for a variation, which I would suggest doing in a followup PR since it necessarily adds a new API and concept)
  162. RubenSomsen commented at 9:22 am on October 4, 2022: none
    @luke-jr I agree it may not be a good idea to refer to the same pubkey/identity + different identifier as a “new address” if that sets the wrong expectations for users. Thanks for clarifying.
  163. DrahtBot added the label Needs rebase on Oct 4, 2022
  164. w0xlt force-pushed on Oct 5, 2022
  165. w0xlt force-pushed on Oct 5, 2022
  166. DrahtBot removed the label Needs rebase on Oct 5, 2022
  167. w0xlt commented at 11:53 am on October 5, 2022: contributor

    @aureleoules Thanks for the detailed review. I addressed all the suggestions.

    the user asks for a new address, they should get a new address @luke-jr as mentioned earlier, that’s why a new RPC was created instead of using getnewaddress, just so the user doesn’t think a new address will be generated.

  168. w0xlt force-pushed on Oct 11, 2022
  169. w0xlt force-pushed on Oct 11, 2022
  170. w0xlt force-pushed on Oct 11, 2022
  171. w0xlt commented at 7:01 am on October 11, 2022: contributor

    Silent Payment v4 (coinjoin support added)

    Changes:

    . Silent payments now use all inputs to create transactions. Previously, they only used the first input. This change increases privacy and makes silent payments compatible with coinjoin.

    . getspaddress RPC renamed to getsilentaddress for clarity

    . Added support for silent payment in PSBT via walletcreatefundedpsbt RPC.

    . Added a new index scheme (which stores the sum of input public keys for each transaction). The previous index bitcoin/signet/indexes/silentpaymentindex should be removed as it is no longer compatible with this new version.

    For reviewers:

    Now, silent payments use the scheme hash(i1*X + i2*X + i3*X + ...)*G + X == hash(x*(I1+I2+I3+...))*G + X, as described here: https://gist.github.com/RubenSomsen/c43b79517e7cb701ebf77eec6dbb46b8#variant-using-all-inputs

    As inputs can be Taproot, this introduced a new issue as bitcoin-core/secp256k1 does not support x-only public key sum (perhaps due to missing prefix byte).

    I opened a new PR https://github.com/bitcoin-core/secp256k1/pull/1143 to add a function to convert from x-only to compressed public key with even y. This is the solution being used by the current silent payment implementation.

    Tutorial updated: https://gist.github.com/w0xlt/c81277ae8677b6c0d3dd073893210875

  172. aureleoules commented at 12:30 pm on October 11, 2022: member

    How about returning a JSON object with some warnings for new labels to explicit that new labels are not new identities?

     0./src/bitcoin-cli -testnet -rpcwallet=sp1 getsilentaddress client1 
     1{
     2  "address": "tsp071pp809z56vwvyqhwc4fsjq8vhjw6yyguzv2u9hnwt3jvyl2ha49nwqtsc8w9",
     3  "index": 7,
     4  "label": "client1",
     5  "warnings": "This address is not a new identity. It is a re-use of an existing identity with a different label."
     6}
     7./src/bitcoin-cli -testnet -rpcwallet=sp1 getsilentaddress client1
     8{
     9  "address": "tsp071pp809z56vwvyqhwc4fsjq8vhjw6yyguzv2u9hnwt3jvyl2ha49nwqtsc8w9",
    10  "label": "client1",
    11  "index": 7
    12}
    13./src/bitcoin-cli -testnet -rpcwallet=sp1 getsilentaddress client2
    14{
    15  "address": "tsp081pp809z56vwvyqhwc4fsjq8vhjw6yyguzv2u9hnwt3jvyl2ha49nwqyqkdah",
    16  "index": 8,
    17  "label": "client2",
    18  "warnings": "This address is not a new identity. It is a re-use of an existing identity with a different label."
    19}
    20./src/bitcoin-cli -testnet -rpcwallet=sp1 getsilentaddress client2
    21{
    22  "address": "tsp081pp809z56vwvyqhwc4fsjq8vhjw6yyguzv2u9hnwt3jvyl2ha49nwqyqkdah",
    23  "label": "client2",
    24  "index": 8
    25}
    
     0diff --git a/src/wallet/rpc/addresses.cpp b/src/wallet/rpc/addresses.cpp
     1index 1f8621d48..cd6165cef 100644
     2--- a/src/wallet/rpc/addresses.cpp
     3+++ b/src/wallet/rpc/addresses.cpp
     4@@ -110,10 +110,14 @@ RPCHelpMan getsilentaddress()
     5     if (!request.params[0].isNull())
     6         label = LabelFromValue(request.params[0]);
     7 
     8+    UniValue obj(UniValue::VOBJ);
     9     for (auto const& [key, val] : pwallet->m_silent_address_book)
    10     {
    11         if (val.m_label == label) {
    12-            return val.m_address;
    13+            obj.pushKV("address", val.m_address);
    14+            obj.pushKV("label", val.m_label);
    15+            obj.pushKV("index", key);
    16+            return obj;
    17         }
    18     }
    19 
    20@@ -128,7 +132,16 @@ RPCHelpMan getsilentaddress()
    21         throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, util::ErrorString(op_dest).original);
    22     }
    23 
    24-    return EncodeDestination(*op_dest, silent_payment, current_index);
    25+    const auto address{EncodeDestination(*op_dest, silent_payment, current_index)};
    26+
    27+    obj.pushKV("address", address);
    28+    obj.pushKV("index", current_index);
    29+    obj.pushKV("label", label);
    30+    if (current_index > 0) {
    31+        obj.pushKV("warnings", "This address is not a new identity. It is a re-use of an existing identity with a different label.");
    32+    }
    33+
    34+    return obj;
    35 },
    36     };
    37 }
    
  173. in src/index/silentpaymentindex.cpp:51 in 978fbe4a7d outdated
    46+    : BaseIndex(std::move(chain), "silentpaymentindex"), m_db(std::make_unique<SilentPaymentIndex::DB>(n_cache_size, f_memory, f_wipe))
    47+{}
    48+
    49+SilentPaymentIndex::~SilentPaymentIndex() {}
    50+
    51+bool SilentPaymentIndex::ExtractPubkeyFromInput(const Coin& prevCoin, const CTxIn& txin,  XOnlyPubKey& senderPubKey) const
    


    aureleoules commented at 12:37 pm on October 11, 2022:
    in c9b3e312d3579be66043934c3a0c1f13911ab715 This function seems to be largely duplicated from the function CWallet::ExtractPubkeyFromInput in src/wallet/wallet.cpp, is there a way to re-use code?

  174. in src/rpc/util.cpp:1208 in 978fbe4a7d outdated
    1148+            throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys: '%s'", desc_str));
    1149+        }
    1150+        std::move(scripts.begin(), scripts.end(), std::back_inserter(ret));
    1151+    }
    1152+    return ret;
    1153+}
    


    aureleoules commented at 12:47 pm on October 11, 2022:

    in de25bcf6b7d59ad8803748412825a99bdb812cf3 This code is from std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, FlatSigningProvider& provider), you should re-use it.

     0diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp
     1index 084d165da..672d3fd98 100644
     2--- a/src/rpc/util.cpp
     3+++ b/src/rpc/util.cpp
     4@@ -1073,24 +1073,7 @@ std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, Fl
     5         throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan object needs to be either a string or an object");
     6     }
     7 
     8-    std::string error;
     9-    auto desc = Parse(desc_str, provider, error);
    10-    if (!desc) {
    11-        throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
    12-    }
    13-    if (!desc->IsRange()) {
    14-        range.first = 0;
    15-        range.second = 0;
    16-    }
    17-    std::vector<CScript> ret;
    18-    for (int i = range.first; i <= range.second; ++i) {
    19-        std::vector<CScript> scripts;
    20-        if (!desc->Expand(i, provider, scripts, provider)) {
    21-            throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys: '%s'", desc_str));
    22-        }
    23-        std::move(scripts.begin(), scripts.end(), std::back_inserter(ret));
    24-    }
    25-    return ret;
    26+    return DescriptorToScripts(desc_str, range, provider);
    27 }
    28 
    29 std::tuple<std::string, std::pair<int64_t, int64_t>> EvalDescriptorStringOrObject(const UniValue& scanobject)
    

  175. in src/key_io.cpp:109 in 978fbe4a7d outdated
    107+    auto dest_silent_payment_hrp = ToLower(std::string_view(str).substr(0, params.SilentPaymentHRP().size()));
    108 
    109     // Note this will be false if it is a valid Bech32 address for a different network
    110-    bool is_bech32 = (ToLower(str.substr(0, params.Bech32HRP().size())) == params.Bech32HRP());
    111+    bool is_bech32_or_sp = (ToLower(str.substr(0, params.Bech32HRP().size())) == params.Bech32HRP()) ||
    112+        dest_silent_payment_hrp == silent_payment_hrp;
    


    aureleoules commented at 12:52 pm on October 11, 2022:

    in d8a8182d2d37edcff51579225123f18434204f4a

    0    bool is_bech32_or_sp = (ToLower(std::string_view(str).substr(0, params.Bech32HRP().size())) == params.Bech32HRP()) ||
    1        dest_silent_payment_hrp == silent_payment_hrp;
    

  176. in src/wallet/rpc/spend.cpp:1224 in 978fbe4a7d outdated
    1220             int change_position;
    1221             bool rbf{options.exists("replaceable") ? options["replaceable"].get_bool() : pwallet->m_signal_rbf};
    1222-            CMutableTransaction rawTx = ConstructTransaction(options["inputs"], request.params[0], options["locktime"], rbf);
    1223+            CMutableTransaction rawTx = ConstructTransaction(options["inputs"], request.params[0], options["locktime"], rbf, &silent_payment_vouts);
    1224+
    1225+            if (silent_payment_vouts.size() > 0) {
    


    aureleoules commented at 12:53 pm on October 11, 2022:

    in 37bf469db8aead02e8cd8601eb1238e7d810657d

    0            if (!silent_payment_vouts.empty()) {
    

  177. in src/wallet/scriptpubkeyman.cpp:2643 in 978fbe4a7d outdated
    2639@@ -2620,6 +2640,87 @@ bool DescriptorScriptPubKeyMan::AddCryptedKey(const CKeyID& key_id, const CPubKe
    2640     return true;
    2641 }
    2642 
    2643+bool DescriptorScriptPubKeyMan::GetPrivKeyForSilentPayment(const CScript scriptPubKey, CKey& privKey, bool onlyTaproot)
    


    aureleoules commented at 12:54 pm on October 11, 2022:

    in 3618e1635dc53021fcc1373070c9e52ebf4e2477

    0bool DescriptorScriptPubKeyMan::GetPrivKeyForSilentPayment(const CScript& scriptPubKey, CKey& privKey, bool onlyTaproot)
    

  178. in src/wallet/scriptpubkeyman.cpp:2704 in 978fbe4a7d outdated
    2699+    CKey priv_key = priv_keys.at(0);
    2700+
    2701+    for(auto& pub_key_items : tx_output_pub_keys) {
    2702+
    2703+        CScript& outputScriptPubKey = std::get<0>(pub_key_items);
    2704+        XOnlyPubKey& outputPubKey = std::get<1>(pub_key_items);
    


    aureleoules commented at 12:55 pm on October 11, 2022:
    3618e1635dc53021fcc1373070c9e52ebf4e2477 use structured binding instead

  179. in src/wallet/scriptpubkeyman.cpp:2713 in 978fbe4a7d outdated
    2708+        for (int32_t identifier = 0; identifier <= m_wallet_descriptor.next_index; identifier++) {
    2709+            CKey silKey = silent_sender.Tweak(identifier);
    2710+            XOnlyPubKey silPubKey{XOnlyPubKey(silKey.GetPubKey())};
    2711+
    2712+            if (silPubKey == outputPubKey) {
    2713+                raw_tr_keys.push_back({silKey, identifier});
    


    aureleoules commented at 12:56 pm on October 11, 2022:

    3618e1635dc53021fcc1373070c9e52ebf4e2477 this avoids creating a temporary

    0                raw_tr_keys.emplace_back({silKey, identifier});
    

  180. in src/wallet/silentpayment.cpp:11 in 978fbe4a7d outdated
     6+#include <secp256k1_extrakeys.h>
     7+#include <arith_uint256.h>
     8+
     9+namespace silentpayment {
    10+
    11+Sender::Sender(const CKey sender_secret_key, const XOnlyPubKey recipient_x_only_public_key)
    


    aureleoules commented at 12:57 pm on October 11, 2022:

    in 60537b92b0f27cab91fbb61da0d195197146bff6

    0Sender::Sender(const CKey& sender_secret_key, const XOnlyPubKey& recipient_x_only_public_key)
    

  181. in src/wallet/silentpayment.cpp:41 in 978fbe4a7d outdated
    36+
    37+    // Add the identifier to the shared_secret
    38+    arith_uint256 tweak;
    39+    tweak = tweak + identifier;
    40+
    41+    int return_val = secp256k1_ec_seckey_tweak_add(m_context, shared_secret, ArithToUint256(tweak).data());
    


    aureleoules commented at 1:02 pm on October 11, 2022:

    60537b92b0f27cab91fbb61da0d195197146bff6

    return_val should be asserted


  182. in src/wallet/wallet.cpp:1425 in 978fbe4a7d outdated
    1472+        }
    1473+
    1474+        rawTrKeys = desc_spkm->VerifySilentPaymentAddress(outputPubKeys, sum_sender_pubkeys);
    1475+    }
    1476+
    1477+    return rawTrKeys.size() > 0;
    


    aureleoules commented at 1:03 pm on October 11, 2022:

    47cc799b9518c13d96fde5a7de050b8a0cd41e4c

    0    return !rawTrKeys.empty();
    

  183. in src/wallet/silentpayment.cpp:135 in 978fbe4a7d outdated
    130+    secp256k1_context_destroy(m_context);
    131+    memset(m_shared_secret, 0, sizeof(m_shared_secret));
    132+    memset(m_recipient_keypair.data, 0, sizeof(m_recipient_keypair.data));
    133+}
    134+
    135+CKey Recipient::Tweak(const int32_t& identifier)
    


    aureleoules commented at 1:06 pm on October 11, 2022:
    60537b92b0f27cab91fbb61da0d195197146bff6 I believe this can be made const

  184. aureleoules commented at 1:29 pm on October 11, 2022: member

    Left some more code-review comments.

    I also tested the following scenario:

    • Create regular wallet
    • Generate some coins to it
    • Create SP wallet
    • Send payment from regular wallet to SP address
    • SP wallet correctly shows the coins received
    • Send back from SP wallet to regular wallet
    • Regular wallet shows the coins received

    I also tested the same scenario from an SP wallet to another SP wallet and it worked successfully.

  185. w0xlt force-pushed on Oct 11, 2022
  186. w0xlt commented at 0:31 am on October 12, 2022: contributor
    @aureleoules Thanks. Great suggestion to improve the UX regarding getsilentaddress. I added you as co-author in https://github.com/bitcoin/bitcoin/pull/24897/commits/35701eff890f5cff02af3e8c59b20f2948153b5f.
  187. in src/silentpayment.cpp:36 in 62c0c18a69 outdated
    31+
    32+XOnlyPubKey Sender::Tweak(const int32_t& identifier) const
    33+{
    34+    unsigned char shared_secret[32];
    35+    memcpy(shared_secret, m_shared_secret, 32);
    36+    assert(memcmp(shared_secret, m_shared_secret, 32) == 0);
    


    aureleoules commented at 7:19 am on October 12, 2022:

    fb014fefff60d87cf21f220fa756aa449bd6daf4 assertions should not have side effects also seen at src/silentpayment.cpp:148 in same commit fb014fefff60d87cf21f220fa756aa449bd6daf4

    0	int r = memcmp(shared_secret, m_shared_secret, 32)
    1    assert(r == 0);
    

    w0xlt commented at 2:48 am on October 16, 2022:
    Done in 1f48b4c3ea120edaf67b25bb09e4a69421fe45a1.
  188. in src/index/silentpaymentindex.cpp:51 in 62c0c18a69 outdated
    46+    : BaseIndex(std::move(chain), "silentpaymentindex"), m_db(std::make_unique<SilentPaymentIndex::DB>(n_cache_size, f_memory, f_wipe))
    47+{}
    48+
    49+SilentPaymentIndex::~SilentPaymentIndex() {}
    50+
    51+bool SilentPaymentIndex::GetSilentPaymentKey(const CTransactionRef& tx, const CBlockIndex* blockindex, XOnlyPubKey& sum_tx_pubkeys)
    


    aureleoules commented at 7:20 am on October 12, 2022:

    301481648aa20f8a8ebd7d868a1a63300cb6ab3e

    0bool SilentPaymentIndex::GetSilentPaymentKey(const CTransactionRef& tx, const CBlockIndex* blockindex, XOnlyPubKey& sum_tx_pubkeys) const
    

    w0xlt commented at 2:48 am on October 16, 2022:
    Done in 8e49caf4e5dc29b6e275aff6f0a3234bbfd77c41.
  189. in src/silentpayment.cpp:155 in 62c0c18a69 outdated
    155+
    156+    return_val = secp256k1_keypair_xonly_tweak_add(m_context, &recipient_keypair, shared_secret);
    157+    assert(return_val);
    158+
    159+    unsigned char result_secret_key[32];
    160+    return_val = secp256k1_keypair_sec(m_context, result_secret_key, &recipient_keypair);
    


    aureleoules commented at 7:34 am on October 12, 2022:
    fb014fefff60d87cf21f220fa756aa449bd6daf4 return_val should be asserted

  190. in src/wallet/scriptpubkeyman.cpp:2643 in 62c0c18a69 outdated
    2639@@ -2620,6 +2640,86 @@ bool DescriptorScriptPubKeyMan::AddCryptedKey(const CKeyID& key_id, const CPubKe
    2640     return true;
    2641 }
    2642 
    2643+bool DescriptorScriptPubKeyMan::GetPrivKeyForSilentPayment(const CScript& scriptPubKey, CKey& privKey, bool onlyTaproot)
    


    aureleoules commented at 7:41 am on October 12, 2022:

    c3e3002cf9b70f8b54f5e344277ad0864c555f5c

    0bool DescriptorScriptPubKeyMan::GetPrivKeyForSilentPayment(const CScript& scriptPubKey, CKey& privKey, bool onlyTaproot) const
    

    w0xlt commented at 2:49 am on October 16, 2022:
    Done in a97cff0045ae0dcdb29fddb7ff263c2e3b052ce9.
  191. in src/wallet/wallet.cpp:1188 in 62c0c18a69 outdated
    1224-                        }
    1225-                    }
    1226+    for(auto& identifier_key: rawTrKeys) {
    1227+
    1228+        CKey key = std::get<0>(identifier_key);
    1229+        int32_t identifier = std::get<1>(identifier_key);
    


    aureleoules commented at 7:42 am on October 12, 2022:
    43503e2ae6a462472f8ede023d6682cff99dd60d use structured binding

    w0xlt commented at 2:49 am on October 16, 2022:
    Done in 4da115941601e433569c5fd11a1d4a5f4b90107e.
  192. in src/wallet/wallet.cpp:2576 in 62c0c18a69 outdated
    2572@@ -2368,18 +2573,32 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize)
    2573     return res;
    2574 }
    2575 
    2576-util::Result<CTxDestination> CWallet::GetNewDestination(const OutputType type, const std::string label)
    2577+util::Result<CTxDestination> CWallet::GetNewDestination(const OutputType type, const std::string label, const bool silent_payment, const int32_t current_index)
    


    aureleoules commented at 7:43 am on October 12, 2022:

    35701eff890f5cff02af3e8c59b20f2948153b5f

    0util::Result<CTxDestination> CWallet::GetNewDestination(const OutputType type, const std::string& label, const bool silent_payment, const int32_t current_index)
    

    w0xlt commented at 2:49 am on October 16, 2022:
    Done in f8cbdccfdeff1ce584d5ce99544af36814d82785.
  193. in src/wallet/scriptpubkeyman.cpp:2701 in 62c0c18a69 outdated
    2696+
    2697+    assert(priv_keys.size() == 1);
    2698+
    2699+    CKey priv_key = priv_keys.at(0);
    2700+
    2701+    for(auto& pub_key_items : tx_output_pub_keys) {
    


    aureleoules commented at 8:39 am on October 12, 2022:

    c3e3002cf9b70f8b54f5e344277ad0864c555f5c

    0    for(const auto& [outputScriptPubKey, outputPubKey] : tx_output_pub_keys) {
    

    w0xlt commented at 2:50 am on October 16, 2022:
    Done in a97cff0045ae0dcdb29fddb7ff263c2e3b052ce9.
  194. aureleoules commented at 8:45 am on October 12, 2022: member

    Thanks for the co-author! @w0xlt

    I was playing around with the code and tried benchmarking, here’s a benchmark for SumXOnlyPublicKeys if you want to include it in this PR:

     0diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include
     1index e1e206687..91650b621 100644
     2--- a/src/Makefile.bench.include
     3+++ b/src/Makefile.bench.include
     4@@ -45,6 +45,7 @@ bench_bench_bitcoin_SOURCES = \
     5   bench/rollingbloom.cpp \
     6   bench/rpc_blockchain.cpp \
     7   bench/rpc_mempool.cpp \
     8+  bench/silentpayment.cpp \
     9   bench/strencodings.cpp \
    10   bench/util_time.cpp \
    11   bench/verify_script.cpp
    12diff --git a/src/bench/silentpayment.cpp b/src/bench/silentpayment.cpp
    13new file mode 100644
    14index 000000000..1cd5455b9
    15--- /dev/null
    16+++ b/src/bench/silentpayment.cpp
    17@@ -0,0 +1,68 @@
    18+// Copyright (c) 2022 The Bitcoin Core developers
    19+// Distributed under the MIT software license, see the accompanying
    20+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
    21+
    22+#include <bench/bench.h>
    23+
    24+#include <random.h>
    25+#include <secp256k1.h>
    26+#include <secp256k1_ecdh.h>
    27+#include <secp256k1_extrakeys.h>
    28+#include <silentpayment.h>
    29+#include <test/util/setup_common.h>
    30+
    31+static XOnlyPubKey gen_key(secp256k1_context* ctx) {
    32+    unsigned char seckey[32];
    33+    GetRandBytes(seckey);
    34+
    35+    secp256k1_pubkey pubkey;
    36+    int return_val = secp256k1_ec_pubkey_create(ctx, &pubkey, seckey);
    37+    assert(return_val);
    38+
    39+    secp256k1_xonly_pubkey xonly_pubkey;
    40+    return_val = secp256k1_xonly_pubkey_from_pubkey(ctx, &xonly_pubkey, nullptr, &pubkey);
    41+    assert(return_val);
    42+
    43+    unsigned char xonly_pubkey_bytes[32];
    44+    return_val = secp256k1_xonly_pubkey_serialize(ctx, xonly_pubkey_bytes, &xonly_pubkey);
    45+    assert(return_val);
    46+
    47+    return XOnlyPubKey(xonly_pubkey_bytes);
    48+}
    49+
    50+static void SumXOnlyPublicKeys(benchmark::Bench& bench, int key_count)
    51+{
    52+    auto ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
    53+    std::vector<XOnlyPubKey> sender_x_only_public_keys;
    54+    for (int i = 0; i < key_count; i++) {
    55+        sender_x_only_public_keys.push_back(gen_key(ctx));
    56+    }
    57+
    58+    bench.run([&] {
    59+        silentpayment::Recipient::SumXOnlyPublicKeys(sender_x_only_public_keys);
    60+    });
    61+
    62+    secp256k1_context_destroy(ctx);
    63+}
    64+
    65+static void SumXOnlyPublicKeys_1(benchmark::Bench& bench)
    66+{
    67+    SumXOnlyPublicKeys(bench, 1);
    68+}
    69+static void SumXOnlyPublicKeys_10(benchmark::Bench& bench)
    70+{
    71+    SumXOnlyPublicKeys(bench, 10);
    72+}
    73+static void SumXOnlyPublicKeys_100(benchmark::Bench& bench)
    74+{
    75+    SumXOnlyPublicKeys(bench, 100);
    76+}
    77+static void SumXOnlyPublicKeys_1000(benchmark::Bench& bench)
    78+{
    79+    SumXOnlyPublicKeys(bench, 1000);
    80+}
    81+
    82+BENCHMARK(SumXOnlyPublicKeys_1);
    83+BENCHMARK(SumXOnlyPublicKeys_10);
    84+BENCHMARK(SumXOnlyPublicKeys_100);
    85+BENCHMARK(SumXOnlyPublicKeys_1000);
    

    Also, why did you move src/wallet/silentpayment.cpp to src/silentpayment.cpp?

  195. aureleoules commented at 1:17 pm on October 12, 2022: member

    Also, I think it could be nice to have a way to list all generated silent addresses with their respective label, to quickly find the payment address for an existing client for example. Such as:

     0$ ./src/bitcoin-cli -regtest listsilentaddresses
     1{
     2  "wallet_name": "my_sp_wallet",
     3  "silent_addresses": [
     4	{
     5		"address": "sprt041plqhqd9t33wty9l3l86rpx82pmh7sny9qlhq9ypatxvwnq2rxyfgqrj43fq",
     6		"label": "Client 1",
     7		"identifier: 4
     8	},
     9	{
    10		"address": "sprt051plqhqd9t33wty9l3l86rpx82pmh7sny9qlhq9ypatxvwnq2rxyfgqzwtgat",
    11		"label": "Client 2",
    12		"identifier: 5
    13	},
    14	{
    15		"address": "sprt061plqhqd9t33wty9l3l86rpx82pmh7sny9qlhq9ypatxvwnq2rxyfgqprq2gk",
    16		"label": "Donations",
    17		"identifier: 6
    18	},
    19	...
    20  ]
    21}
    

    I hope this feature is not too premature for this PR.

  196. achow101 marked this as a draft on Oct 12, 2022
  197. DrahtBot added the label Needs rebase on Oct 13, 2022
  198. w0xlt force-pushed on Oct 15, 2022
  199. DrahtBot removed the label Needs rebase on Oct 15, 2022
  200. w0xlt force-pushed on Oct 16, 2022
  201. w0xlt force-pushed on Oct 16, 2022
  202. w0xlt force-pushed on Oct 16, 2022
  203. w0xlt force-pushed on Oct 16, 2022
  204. w0xlt commented at 5:05 am on October 16, 2022: contributor

    @aureleoules Thanks for all the suggestions and the benchmark file. I’ve added it (and given you authorship) as it’s crucial to determine the feasibility of this scheme. I also added the ECDHPerformance_10_100 method as ECDH is the most expensive operation (which until this PR was not used by Bitcoin Core - see f66beaa534aa42341a3c3030024a13ae9078c04c).

    I also implemented the listsilentaddresses RPC. Really great suggestion.

    Also, why did you move src/wallet/silentpayment.cpp to src/silentpayment.cpp?

    I removed silentpayment.{cpp,h} from wallet folder because it is also used in index and RPC scantxoutset (which is not related to wallet). So this was introducing a wallet dependency on these classes.

  205. w0xlt commented at 5:08 am on October 16, 2022: contributor

    This new push implements a new cryptographic scheme (also suggested by @RubenSomsen) that eliminates the need for PR https://github.com/bitcoin-core/secp256k1/pull/994 and https://github.com/bitcoin-core/secp256k1/pull/1143.

    The new scheme basically works as follows: . If the keys used for silent payments generate a public key with odd y, then negate them when performing ECDH. . For x-only pubkeys, add 2 in front before running ECDH (so it becomes a public key with even y). . Tweak the original keys, not the negated ones. @1440000bytes This way, now all CI checks are passing successfully as the secp256k1 lib is the same as the master branch.

  206. w0xlt commented at 8:57 pm on October 16, 2022: contributor
    I forgot to mention that any previous index of silentpayment needs to be removed as it is not compatible with the last push.
  207. in src/key_io.cpp:74 in 9fde792246 outdated
    70+
    71+        if (m_silent_payment) {
    72+            hrp = m_params.SilentPaymentHRP();
    73+
    74+            std::ostringstream ss;
    75+            ss << std::setw(2) << std::setfill('0') << m_silent_payment_index;
    


    aureleoules commented at 1:50 pm on October 19, 2022:

    Encoding the identifier in a different base could increase the maximum number of identifiers when stored on 2 bytes. base10 (current) -> 100 identifiers base16 -> 256 identifiers base32 -> 1024 identifiers base58 -> 3364 identifiers

    The issue with a base58 HRP is that it’s incompatible with BIP-173 and BIP-350, maybe the identifier could be encoded in the data part instead of the hrp? Or base32 is enough.


    w0xlt commented at 4:39 pm on October 21, 2022:

    Another excellent suggestion @aureleoules !

    I’ve been working on this. Some examples: sprt1la68kq44e29c0p85rknf59qs5eww6vr3dw08arakura622ce7wcvygce0uqnw3q4 for identifier 8090879. sprt1yvpttj5ts7z0g8dxng2ppfjua5c8z6u7068mdc8m5543nuascg33jlcpe5xc6 for identifier 35.


    aureleoules commented at 5:36 pm on October 21, 2022:
    Great, how exactly is the identifier encoded in your examples?

    w0xlt commented at 7:24 am on October 23, 2022:

    Currently the format is: HRP + 00 (two digits) + V1 Segwit address (bech32m)

    But there is no need to be a segwit address. This is because in the first version the sender could pay for the original segwit address or tweak it to the silent address, but early discussions in this PR indicated a preference on the part of reviewers for a specific address for silent payments.

    So the format in the examples above is different, which is:

    HRP + identifier (variable length) + compressed public key (both identifier and PK are in bech32m format)

    I haven’t pushed it yet as the above format is not compatible with current code and also adds complexity to the code as it is not a valid scriptPubKey.

    However, here is the code used to obtain the format above (in key_io.cpp):

     0std::string EncodeSilentDestination(const CPubKey pubkey, const int32_t silent_payment_index)
     1{
     2    // The data_in is index + public key
     3    std::vector<unsigned char> data_in = {};
     4    std::vector<unsigned char> data_out = {};
     5
     6    std::vector<unsigned char> index_data = {};
     7    unsigned char index_chunk = silent_payment_index & 0xFF;
     8    int8_t shift = 8;
     9
    10    while(index_chunk != 0) {
    11        index_data.push_back(index_chunk);
    12        index_chunk = (silent_payment_index >> shift) & 0xFF;
    13        shift += 8;
    14    }
    15
    16    data_in.insert(data_in.end(), index_data.begin(), index_data.end());
    17    data_in.insert(data_in.end(), pubkey.begin(), pubkey.end());
    18
    19    ConvertBits<8, 5, true>([&](unsigned char c) { data_out.push_back(c); }, data_in.begin(), data_in.end());
    20
    21    std::string hrp = Params().SilentPaymentHRP();
    22
    23    return bech32::Encode(bech32::Encoding::BECH32M, hrp, data_out);
    24}
    25
    26std::tuple<CPubKey, int32_t> DecodeSilentData(const std::vector<unsigned char>& data)
    27{
    28    if (data.size() <= 33) {
    29        return {CPubKey(), 0};
    30    }
    31
    32    std::vector<unsigned char> pubkey_data(data.end() - 33, data.end());
    33    CPubKey pubkey{pubkey_data.begin(), pubkey_data.end()};
    34
    35    std::vector<unsigned char> index_data(data.begin(), data.end() - 33);
    36
    37    int32_t index = 0;
    38    int shift = 0;
    39
    40    for (auto it = index_data.begin(); it != index_data.end(); it++) {
    41        index |=  (*it << shift);
    42        shift += 8;
    43    }
    44
    45    return {pubkey, index};
    46}
    47
    48std::vector<unsigned char> DecodeSilentAddress(const std::string& str)
    49{
    50    auto params{Params()};
    51
    52    const auto& silent_payment_hrp = params.SilentPaymentHRP();
    53    auto dest_silent_payment_hrp = ToLower(std::string_view(str).substr(0, params.SilentPaymentHRP().size()));
    54
    55    bool is_sp = dest_silent_payment_hrp == silent_payment_hrp;
    56
    57    if (!is_sp) {
    58        return {};
    59    }
    60
    61    std::vector<unsigned char> data;
    62    data.clear();
    63    const auto dec = bech32::Decode(str);
    64    auto dec_silent_payment_hrp = dec.hrp.substr(0, params.SilentPaymentHRP().size());
    65
    66    if (dec.encoding != bech32::Encoding::BECH32M || dec.data.empty() || dec.hrp != silent_payment_hrp) {
    67        return {};
    68    }
    69
    70    data.reserve(((dec.data.size() - 1) * 5) / 8);
    71    if (ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, dec.data.begin(), dec.data.end())) {
    72        return data;
    73    }
    74
    75    return {};
    76}
    
  208. in src/key_io.h:16 in 9fde792246 outdated
    12@@ -13,6 +13,8 @@
    13 
    14 #include <string>
    15 
    16+const int16_t SILENT_ADDRESS_MAXIMUM_IDENTIFIER = 99;
    


    aureleoules commented at 1:52 pm on October 19, 2022:
    Type missmatch between int16_t and int32_t of m_silent_payment_index
  209. josibake commented at 6:18 pm on October 19, 2022: member
    thanks for PoC’ing an implementation on this, @w0xlt ! in the comments on https://gist.github.com/RubenSomsen/c43b79517e7cb701ebf77eec6dbb46b8 you mention having a gist with steps for testing (https://gist.github.com/w0xlt/a7b498ac1ff14b8c292a22be789bd93f) but this link appears to be dead. do you have an updated link for testing?
  210. w0xlt commented at 6:28 pm on October 19, 2022: contributor
    Hi @josibake I removed old versions of the tutorial when the implementation and interface changed. The current url is https://gist.github.com/w0xlt/c81277ae8677b6c0d3dd073893210875.
  211. ghost commented at 11:57 pm on October 21, 2022: none

    This new push implements a new cryptographic scheme (also suggested by @RubenSomsen) that eliminates the need for PR bitcoin-core/secp256k1#994 and bitcoin-core/secp256k1#1143.

    The new scheme basically works as follows: . If the keys used for silent payments generate a public key with odd y, then negate them when performing ECDH. . For x-only pubkeys, add 2 in front before running ECDH (so it becomes a public key with even y). . Tweak the original keys, not the negated ones.

    @1440000bytes This way, now all CI checks are passing successfully as the secp256k1 lib is the same as the master branch.

    Still waiting for response particularly about the last question : https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2022-October/021003.html

  212. w0xlt commented at 7:05 am on October 23, 2022: contributor

    @1440000bytes I haven’t accessed ML for a while.

    I replied to the email, but regarding the last question: the warning was suggested by @aureleoules in #24897 (comment) and the reason was a discussion in this PR about users thinking that each address would come from a different key and not the same key.

  213. aureleoules commented at 3:34 pm on October 25, 2022: member

    In cases where you don’t want people to know that you’re the same entity, the identifier is insufficient – you’ll need a completely separate Silent Payment address which (roughly) doubles your scanning efforts, so it’s much more costly in terms of performance.

    If #25957 gets merged, would this still be an issue? If I’m not mistaken, the double-scanning efforts problem mostly apply when importing a backup of an sp descriptor or syncing the wallet when the chain is out-of-date of many blocks. Which is not often done, with #25957 the issue should be mitigated anyway? So, would it be reasonable to implement both the identifier feature and the multi-sp-identity feature per wallet for those who wish/need full privacy?

    Also, rescanning a sp descriptor makes bitcoind crash with the following assert:

    bitcoind: wallet/wallet.cpp:1315: bool wallet::CWallet::VerifySilentPayment(const CTransaction &, std::vector<std::tuple<CKey, int32_t» &): Assertion `xOnlyPubKey.IsFullyValid()’ failed.

    To reproduce:

    0./src/bitcoind -testnet -silentpaymentindex
    1./src/bitcoin-cli -testnet -named createwallet wallet_name=sp_crash silent_payment=true
    2./src/bitcoin-cli -testnet -rpcwallet=sp_crash importdescriptors '[{
    3      "desc": "sp(cTXUgq3oC65nkmWjN8Q1eg1wsMPZBr526Ynfecpy4XmKtztjomrg)#vshe5dpl", 
    4      "timestamp": 1607448174,
    5      "active": true,
    6      "internal": false,
    7      "next": 0
    8}]'
    

    Any sp descriptor seems to lead to the crash.

    I haven’t debugged the issue but my guess is that the scanning process encountered a taproot output with an invalid xOnlyPubKey in the block close to the timestamp 1607448174 (~2020-12-08), which should just be skipped instead of being asserted valid?

  214. w0xlt commented at 2:13 am on October 27, 2022: contributor

    @aureleoules thanks for testing. They are really helpful. The problematic output is https://blockstream.info/testnet/address/tb1pqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqkqqqqqqqqqkkkkkkf5, whose scriptPubKey is 51200000000000000000000000000000000000000000000000000000000000000000.

    I didn’t know it’s a valid Taproot script as the public key is invalid. The code assumes that if TxoutType whichType = Solver(vout.scriptPubKey, solutions); returned a TxoutType::WITNESS_V1_TAPROOT, both the script and the public key are valid.

    But the public key in the above scriptPubKey is evidently false.

    I will change this validation and release a new version with the new address format, described in #24897 (review).

    Regarding #25957, I think it’s incompatible with Silent Payments, which needs to tweak the keys for each transaction. AFAIK, there is no way to combine the silent payment scheme with Golomb-coded sets.

  215. w0xlt commented at 2:58 am on October 27, 2022: contributor

    The following change will allow importing the descriptor.

    wallet/wallet.cpp:1315

    0-        assert(xOnlyPubKey.IsFullyValid());
    1+        if (!xOnlyPubKey.IsFullyValid()) {
    2+            continue;
    3+       }
    

    It might be better to use -rpcclienttimeout=0 when importing or scanning sp descriptors.

  216. w0xlt force-pushed on Oct 28, 2022
  217. w0xlt force-pushed on Oct 28, 2022
  218. w0xlt force-pushed on Oct 28, 2022
  219. w0xlt force-pushed on Oct 28, 2022
  220. w0xlt force-pushed on Oct 28, 2022
  221. w0xlt force-pushed on Oct 29, 2022
  222. w0xlt force-pushed on Oct 29, 2022
  223. w0xlt commented at 6:00 am on October 29, 2022: contributor

    The last push proposes and implements a new address format. The previous format was: HRP + 00 (two digits) + V1 Segwit address (bech32m)

    But there is no need for a segwit script, as detailed in #24897 (review). A silent payment address only needs to expose the public key and identifier. So the new format is:

    HRP + identifier (variable length) + compressed public key (both identifier and pubkey are in bech32m format)

    The reason to use the compressed public key instead of the x-only pubkey is to save an operation and simplify the code. If x-only pubkey were used instead of compressed public key, it would be necessary to check if the private key generates a public key with odd y and negates it if so. Exposing the prefix byte in the public key makes this unnecessary.

  224. in src/index/silentpaymentindex.cpp:102 in 2fe96a4241 outdated
     97+        const CTxIn& txin{tx->vin.at(i)};
     98+
     99+        auto pubkey_variant = silentpayment::ExtractPubkeyFromInput(prev_coin, txin);
    100+
    101+        if (std::holds_alternative<CPubKey>(pubkey_variant)) {
    102+            auto pubkey = std::get<CPubKey>(pubkey_variant);
    


    aureleoules commented at 9:27 am on October 31, 2022:

    ebff8b2d55d9c087c4bf8f8961bd8ec1ee51c04f

    0            const auto& pubkey = std::get<CPubKey>(pubkey_variant);
    

    w0xlt commented at 11:08 pm on November 1, 2022:
    Done in 05ef1b48bfd6cb466bac118a917449b1d26d043d.
  225. in src/index/silentpaymentindex.cpp:107 in 2fe96a4241 outdated
    102+            auto pubkey = std::get<CPubKey>(pubkey_variant);
    103+            if (pubkey.IsFullyValid()) {
    104+                input_pubkeys.push_back(pubkey);
    105+            }
    106+        } else if (std::holds_alternative<XOnlyPubKey>(pubkey_variant)) {
    107+            auto pubkey = std::get<XOnlyPubKey>(pubkey_variant);
    


    aureleoules commented at 9:27 am on October 31, 2022:

    ebff8b2d55d9c087c4bf8f8961bd8ec1ee51c04f

    0            const auto& pubkey = std::get<XOnlyPubKey>(pubkey_variant);
    

    w0xlt commented at 11:09 pm on November 1, 2022:
    Done in 05ef1b48bfd6cb466bac118a917449b1d26d043d.
  226. in src/key_io.cpp:283 in 2fe96a4241 outdated
    279@@ -278,6 +280,39 @@ std::string EncodeDestination(const CTxDestination& dest)
    280     return std::visit(DestinationEncoder(Params()), dest);
    281 }
    282 
    283+std::string EncodeSilentDestination(const CPubKey pubkey, const int32_t silent_payment_index)
    


    aureleoules commented at 9:29 am on October 31, 2022:

    5ee94ec2b7ef5f3affcde75325e795221b8f5122

    0std::string EncodeSilentDestination(const CPubKey& pubkey, const int32_t silent_payment_index)
    

    w0xlt commented at 11:09 pm on November 1, 2022:
    Done in 7f71bc26087227fb0659800eaffa3c4198e6109e.
  227. in src/key_io.cpp:355 in 2fe96a4241 outdated
    350+    int shift = 0;
    351+
    352+    for (auto it = index_data.begin(); it != index_data.end(); it++) {
    353+        index |=  (*it << shift);
    354+        shift += 8;
    355+    }
    


    aureleoules commented at 9:30 am on October 31, 2022:

    5ee94ec2b7ef5f3affcde75325e795221b8f5122

    0    for (const auto& it : index_data) {
    1        index |=  (it << shift);
    2        shift += 8;
    3    }
    

  228. in src/primitives/transaction.cpp:61 in 2fe96a4241 outdated
    61+{
    62+    nValue = nValueIn;
    63+    scriptPubKey = scriptPubKeyIn;
    64+    m_silentpayment = silentpayment;
    65+}
    66+
    


    aureleoules commented at 9:35 am on October 31, 2022:
    a8931257c319e5c6e3e575171667d8f9cd61ace2 use a constructor initializer list, maybe refactor the one above as well?

    w0xlt commented at 11:46 pm on November 1, 2022:
    Done in e833dd60f8bceff2d81acea7b0aadfb1b97baa71.
  229. in src/silentpayment.cpp:88 in 2fe96a4241 outdated
    83+    secp256k1_pubkey recipient_public_key;
    84+    memcpy(recipient_public_key.data, m_recipient_public_key.data, 64);
    85+    assert(memcmp(recipient_public_key.data, m_recipient_public_key.data, 64) == 0);
    86+
    87+    // Tweak the recipient's pubkey with identifier + shared_secret
    88+    return_val = secp256k1_ec_pubkey_tweak_add(m_context, &recipient_public_key, shared_secret);
    


    aureleoules commented at 9:39 am on October 31, 2022:
    4da30d6fd3eb9347081ecdea33470c8d212f56a9 return_val is never asserted

    w0xlt commented at 11:47 pm on November 1, 2022:
    Done in b6b55768c0b0fbc4632a3e4be31583349c637b96.
  230. in src/silentpayment.cpp:173 in 2fe96a4241 outdated
    201+    }
    202+
    203+    std::vector<secp256k1_pubkey *> p_pubkeys;
    204+    for (size_t i = 0; i < v_pubkeys.size(); i++) {
    205+        p_pubkeys.push_back(&v_pubkeys.at(i));
    206+    }
    


    aureleoules commented at 9:41 am on October 31, 2022:

    4da30d6fd3eb9347081ecdea33470c8d212f56a9

    0    for (auto& pubkey : v_pubkeys) {
    1        p_pubkeys.push_back(&pubkey);
    2    }
    

  231. in src/wallet/rpc/spend.cpp:1225 in 2fe96a4241 outdated
    1221             CMutableTransaction rawTx = ConstructTransaction(options["inputs"], request.params[0], options["locktime"], rbf);
    1222+
    1223+            bool is_silent_payment{false};
    1224+
    1225+            for (uint64_t i = 0; i < rawTx.vout.size(); i++) {
    1226+                if (rawTx.vout[i].m_silentpayment) {
    


    aureleoules commented at 9:43 am on October 31, 2022:

    a8931257c319e5c6e3e575171667d8f9cd61ace2

    0            for (const auto& out : rawTx.vout) {
    1                if (out.m_silentpayment) {
    

    w0xlt commented at 11:48 pm on November 1, 2022:
    Done in e833dd60f8bceff2d81acea7b0aadfb1b97baa71.
  232. in src/wallet/rpc/spend.cpp:1323 in 2fe96a4241 outdated
    1236+                if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) || pwallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER) ) {
    1237+                    throw JSONRPCError(RPC_WALLET_ERROR, "Silent payments require access to private keys to build transactions.");
    1238+                }
    1239+                EnsureWalletIsUnlocked(*pwallet);
    1240+            }
    1241+
    


    aureleoules commented at 9:44 am on October 31, 2022:
    This code seems to be duplicated in send and walletcreatefundedpsbt RPCs, maybe wrap this in a static function?

    w0xlt commented at 11:48 pm on November 1, 2022:
    Done in e833dd60f8bceff2d81acea7b0aadfb1b97baa71.
  233. in src/wallet/scriptpubkeyman.cpp:2736 in 2fe96a4241 outdated
    2731+    if (m_silent_recipient == nullptr) {
    2732+        LOCK(cs_desc_man);
    2733+
    2734+        std::vector<CKey> priv_keys;
    2735+
    2736+        for (auto& [_, priv_key] : m_map_keys) {
    


    aureleoules commented at 9:45 am on October 31, 2022:

    268c3fd560964d708fb9718548d216149065112a

    0        for (const auto& [_, priv_key] : m_map_keys) {
    

    w0xlt commented at 11:49 pm on November 1, 2022:
    Done in 94bbc986d176244658a931037dab67de4d9fdb4a.
  234. in src/wallet/scriptpubkeyman.cpp:2747 in 2fe96a4241 outdated
    2742+        m_silent_recipient = std::make_unique<silentpayment::Recipient>(priv_keys.at(0));
    2743+    }
    2744+}
    2745+
    2746+std::vector<std::tuple<CKey, int32_t>> DescriptorScriptPubKeyMan::VerifySilentPaymentAddress(
    2747+    std::vector<std::tuple<CScript, XOnlyPubKey>>& tx_output_pub_keys,
    


    aureleoules commented at 9:46 am on October 31, 2022:

    268c3fd560964d708fb9718548d216149065112a

    0    const std::vector<std::tuple<CScript, XOnlyPubKey>>& tx_output_pub_keys,
    

    w0xlt commented at 11:49 pm on November 1, 2022:
    Done in 94bbc986d176244658a931037dab67de4d9fdb4a.
  235. in src/wallet/scriptpubkeyman.cpp:2748 in 2fe96a4241 outdated
    2743+    }
    2744+}
    2745+
    2746+std::vector<std::tuple<CKey, int32_t>> DescriptorScriptPubKeyMan::VerifySilentPaymentAddress(
    2747+    std::vector<std::tuple<CScript, XOnlyPubKey>>& tx_output_pub_keys,
    2748+    CPubKey& sender_pub_key)
    


    aureleoules commented at 9:46 am on October 31, 2022:

    268c3fd560964d708fb9718548d216149065112a

    0    const CPubKey& sender_pub_key)
    

    w0xlt commented at 11:50 pm on November 1, 2022:
    Done in 94bbc986d176244658a931037dab67de4d9fdb4a.
  236. in src/wallet/spend.cpp:769 in 2fe96a4241 outdated
    764+        if (spk_managers.size() != 1) {
    765+            error = _("Only one ScriptPubKeyManager was expected for the input.");
    766+            return false;
    767+        }
    768+
    769+        DescriptorScriptPubKeyMan* spk_manager = dynamic_cast<DescriptorScriptPubKeyMan*>(*spk_managers.begin());
    


    aureleoules commented at 9:47 am on October 31, 2022:

    a8931257c319e5c6e3e575171667d8f9cd61ace2

    0        const auto* spk_manager = dynamic_cast<DescriptorScriptPubKeyMan*>(*spk_managers.begin());
    

    w0xlt commented at 11:50 pm on November 1, 2022:
    Done in e833dd60f8bceff2d81acea7b0aadfb1b97baa71.
  237. in src/wallet/spend.cpp:778 in 2fe96a4241 outdated
    773+        if (!sender_secret_key.IsValid()) {
    774+            error = _("The private key of one of the inputs was not found.");
    775+            return false;
    776+        }
    777+
    778+        input_private_keys.push_back({sender_secret_key, is_taproot});
    


    aureleoules commented at 9:48 am on October 31, 2022:

    a8931257c319e5c6e3e575171667d8f9cd61ace2

    0        input_private_keys.emplace_back(sender_secret_key, is_taproot);
    

  238. in src/wallet/spend.cpp:981 in 2fe96a4241 outdated
    966+    for (uint64_t i = 0; i < txNew.vout.size(); i++) {
    967+        if (txNew.vout[i].m_silentpayment) {
    968+            is_silent_payment = true;
    969+            break;
    970+        }
    971+    }
    


    aureleoules commented at 9:49 am on October 31, 2022:

    a8931257c319e5c6e3e575171667d8f9cd61ace2

    0    for (const auto& out : txNew.vout) {
    1        if (out.m_silentpayment) {
    2            is_silent_payment = true;
    3            break;
    4        }
    5    }
    

  239. in src/wallet/wallet.cpp:1322 in 2fe96a4241 outdated
    1358+        ret = AddSilentScriptKeyMan(tx, state, rescanning_old_block, rawTrKeys) || ret;
    1359+    }
    1360+    return ret;
    1361+}
    1362+
    1363+Coin CWallet::FindPreviousCoin(const CTxIn& txin)
    


    aureleoules commented at 9:53 am on October 31, 2022:

    268c3fd560964d708fb9718548d216149065112a

    0Coin CWallet::FindPreviousCoin(const CTxIn& txin) const
    

  240. in src/wallet/wallet.cpp:1344 in 2fe96a4241 outdated
    1362+
    1363+Coin CWallet::FindPreviousCoin(const CTxIn& txin)
    1364+{
    1365+    // Find the UTXO being spent in UTXO Set to learn the transaction type
    1366+    std::map<COutPoint, Coin> coins;
    1367+    coins[txin.prevout];
    


    aureleoules commented at 9:55 am on October 31, 2022:
    a56b75609aafbf6ea1a9c1a280ccd3133c28d13b is this intended?

  241. in src/wallet/wallet.cpp:1393 in 2fe96a4241 outdated
    1440+            auto pubkey = std::get<XOnlyPubKey>(pubkey_variant);
    1441+            if (pubkey.IsFullyValid()) {
    1442+                input_xonly_pubkeys.push_back(pubkey);
    1443             }
    1444-            return true;
    1445         }
    


    aureleoules commented at 9:57 am on October 31, 2022:
    This seems to be duplicated code from wallet/wallet.cpp:1384 and index/silentpaymentindex.cpp:101, can you re-use it?
  242. aureleoules commented at 10:24 am on October 31, 2022: member

    Left some additional code review comments.

    I deleted the old silentpaymentindex, re-compiled with the new changes and ran ./src/bitcoind -testnet -silentpaymentindex and got

    02022-10-31T10:22:33Z Syncing silentpaymentindex with block chain from height 2377655
    1bitcoind: silentpayment.cpp:276: std::variant<CPubKey, XOnlyPubKey> silentpayment::ExtractPubkeyFromInput(const Coin &, const CTxIn &): Assertion `scriptWitness.stack.at(1).size() == 33' failed.
    
  243. michaelfolkson commented at 10:49 am on October 31, 2022: contributor

    I think this is very cool work and hopefully will be added to some ecosystem wallets in future but unsure whether it should be included in the scope for the Core wallet (the Core wallet is already attempting to do a lot e.g. complex scripting support and has been subject to a number of significant re-architectures recently). I guess that decision falls to the maintainers and long term contributors of the Core wallet as if this is merged it will need to be maintained by them. Scope creep is hard to push back on especially when the work is genuinely promising and interesting but it has to be done sometimes unfortunately.

    Perhaps the wallet scope discussion can happen (if it needs to happen) in an issue as to not disrupt the review of this PR.

  244. ghost commented at 11:01 am on October 31, 2022: none

    I think this is very cool work and hopefully will be added to some ecosystem wallets in future but unsure whether it should be included in the scope for the Core wallet (the Core wallet is already attempting to do a lot e.g. complex scripting support and has been subject to a number of significant re-architectures recently). I guess that decision falls to the maintainers and long term contributors of the Core wallet as if this is merged it will need to be maintained by them. Scope creep is hard to push back on especially when the work is genuinely promising and interesting but it has to be done sometimes unfortunately.

    Perhaps the wallet scope discussion can happen (if it needs to happen) in an issue as to not disrupt the review of this PR.

    This is a basic feature that should exist in every bitcoin wallet.

    I appreciate the specs by @RubenSomsen, code by @w0xlt and respect all the reviewers.

    Would love to see this merged and available in v25.0 so that every bitcoin core user can share their silent payment address on social media and accept payments with privacy.

  245. w0xlt force-pushed on Nov 1, 2022
  246. w0xlt force-pushed on Nov 1, 2022
  247. w0xlt force-pushed on Nov 1, 2022
  248. aureleoules commented at 2:13 pm on November 7, 2022: member

    I can no longer use sendtoaddress nor generatetoaddress to send coins to an sp address:

    0./src/bitcoin-cli -regtest -named -rpcwallet=test sendtoaddress address=sprt1qgqqygu4ws7ggfa3sewh5zmr9pthrklqvllh7d97z8ufx6eskyw25tp8af8hwk amount=1 fee_rate=25
    1error code: -5
    2error message:
    3Invalid Bitcoin address: sprt1qgqqygu4ws7ggfa3sewh5zmr9pthrklqvllh7d97z8ufx6eskyw25tp8af8hwk
    

    It used to work in 62c0c18a69559270a0a420fc48e88d50a632062c at least.

    I verified that the importdescriptors issue mentioned above is fixed and can also now sync the silentpaymentindex.

  249. w0xlt force-pushed on Nov 10, 2022
  250. w0xlt force-pushed on Nov 10, 2022
  251. w0xlt force-pushed on Nov 10, 2022
  252. w0xlt force-pushed on Nov 10, 2022
  253. w0xlt force-pushed on Nov 10, 2022
  254. w0xlt force-pushed on Nov 10, 2022
  255. w0xlt commented at 9:46 pm on November 10, 2022: contributor
    @aureleoules Thanks for the continuous testing. The reason sendtoaddress was not able to recognize the sp address is that this is no longer a segwit script. Since the last push, the address format is identifier + pubkey but in the last version it has changed again as described in the comment below. I also implemented sp support for sendmany and sendtoaddress in this version.
  256. w0xlt commented at 9:49 pm on November 10, 2022: contributor

    New update:

    . This new version implements a new scheme, which uses two keys (scan and spend keys). This scheme allows watch-only wallets for silent payments.

    . It also implements a new address format HRP + spend x-only pubkey + scan x-only pubkey, as specified in https://gist.github.com/RubenSomsen/c43b79517e7cb701ebf77eec6dbb46b8#scanning-key. Example:

     0$ ./src/bitcoin-cli -regtest getsilentaddress 'donation'
     1{
     2  "address": "sprt1mx0828tmms6v4dzjvwyp8w3aus350k9fthlrj8v8zxmp9w2tf88l3kqh6rr98xx05mv444npzkj9j7wrxzj9tc7v0w7jl0xhpc6wzpc0l5gcq",
     3  "label": "donation",
     4  "identifier": 1
     5}
     6
     7$ ./src/bitcoin-cli -regtest decodesilentaddress "sprt1mx0828tmms6v4dzjvwyp8w3aus350k9fthlrj8v8zxmp9w2tf88l3kqh6rr98xx05mv444npzkj9j7wrxzj9tc7v0w7jl0xhpc6wzpc0l5gcq"
     8{
     9  "address": "sprt1mx0828tmms6v4dzjvwyp8w3aus350k9fthlrj8v8zxmp9w2tf88l3kqh6rr98xx05mv444npzkj9j7wrxzj9tc7v0w7jl0xhpc6wzpc0l5gcq",
    10  "scan_pubkey": "d99e751d7bdc34cab452638813ba3de42347d8a95dfe391d8711b612b94b49cf",
    11  "spend_pubkey": "f8d817d0c65398cfa6d95ad66115a45979c330a455e3cc7bbd2fbcd70e34e107",
    12  "label": "donation",
    13  "identifier": 1
    14}
    

    . Adds support for silent payment in sendmany and sendtoaddress

    . Improvements in silentpayment.{cpp,h}. This class no longer manages secp256k1_context itself and no longer interacts directly with secp256k1 library.

    . Silent watch-only wallets have not yet been implemented. Some architectural improvements are needed first.

    Supported commands (RPC) so far: . send . walletcreatefundedpsbt . sendmany . sendtoaddress . scantxoutset

  257. aureleoules commented at 10:45 pm on November 14, 2022: member

    I was able to fully sync the silentpaymentindex on mainnet and testnet (respective size 23M and 1.2M). I also ran the following steps:

    • Create SP wallet
    • Send coins to it
    • Export SP descriptor in new wallet
    • Check balances match

    And I imported an sp descriptor on mainnet and testnet with timestamp 0 successfully.

    Will test further later.

  258. DrahtBot added the label Needs rebase on Dec 5, 2022
  259. w0xlt force-pushed on Dec 14, 2022
  260. w0xlt force-pushed on Dec 14, 2022
  261. DrahtBot removed the label Needs rebase on Dec 14, 2022
  262. DrahtBot added the label Needs rebase on Dec 19, 2022
  263. w0xlt force-pushed on Jan 6, 2023
  264. w0xlt force-pushed on Jan 6, 2023
  265. DrahtBot removed the label Needs rebase on Jan 6, 2023
  266. DrahtBot added the label Needs rebase on Jan 10, 2023
  267. in src/index/silentpaymentindex.cpp:75 in f7bda4c695 outdated
    70+    CBlockUndo blockUndo;
    71+    CBlock block;
    72+
    73+    if (!(UndoReadFromDisk(blockUndo, blockindex) && ReadBlockFromDisk(block, blockindex, Params().GetConsensus()))) {
    74+        return false;
    75+    }
    


    aureleoules commented at 1:35 pm on January 17, 2023:

    GetSilentPaymentKey is executed for each transaction for each block during the index creation. But it is reading block undo data and block data for every transaction, this can be optimized by reading it only once per block in the parent function (SilentPaymentIndex::CustomAppend).

     0diff --git a/src/index/silentpaymentindex.cpp b/src/index/silentpaymentindex.cpp
     1index 65329a408..601a09d21 100644
     2--- a/src/index/silentpaymentindex.cpp
     3+++ b/src/index/silentpaymentindex.cpp
     4@@ -48,7 +48,7 @@ SilentPaymentIndex::SilentPaymentIndex(std::unique_ptr<interfaces::Chain> chain,
     5 
     6 SilentPaymentIndex::~SilentPaymentIndex() {}
     7 
     8-bool SilentPaymentIndex::GetSilentPaymentKey(const CTransactionRef& tx, const CBlockIndex* blockindex, CPubKey& sum_tx_pubkeys) const
     9+bool SilentPaymentIndex::GetSilentPaymentKey(const CTransactionRef& tx, const CBlock& block, const CBlockUndo& blockUndo, CPubKey& sum_tx_pubkeys) const
    10 {
    11     if (tx->IsCoinBase()) {
    12         return false;
    13@@ -67,32 +67,22 @@ bool SilentPaymentIndex::GetSilentPaymentKey(const CTransactionRef& tx, const CB
    14         return false;
    15     }
    16 
    17-    CBlockUndo blockUndo;
    18-    CBlock block;
    19-
    20-    if (!(UndoReadFromDisk(blockUndo, blockindex) && ReadBlockFromDisk(block, blockindex, Params().GetConsensus()))) {
    21+    auto it = std::find_if(block.vtx.cbegin(), block.vtx.cend(), [tx](CTransactionRef t){ return *t == *tx; });
    22+    if (it == block.vtx.end()) {
    23         return false;
    24     }
    25 
    26-    CTxUndo* undoTX {nullptr};
    27-    auto it = std::find_if(block.vtx.begin(), block.vtx.end(), [tx](CTransactionRef t){ return *t == *tx; });
    28-    if (it != block.vtx.end()) {
    29-        // -1 as blockundo does not have coinbase tx
    30-        undoTX = &blockUndo.vtxundo.at(it - block.vtx.begin() - 1);
    31-    }
    32-
    33-    if (undoTX == nullptr) {
    34-        return false;
    35-    }
    36+    // -1 as blockundo does not have coinbase tx    
    37+    const auto& undoTX = blockUndo.vtxundo.at(it - block.vtx.begin() - 1);
    38 
    39-    assert(tx->vin.size() == undoTX->vprevout.size());
    40+    assert(tx->vin.size() == undoTX.vprevout.size());
    41 
    42     std::vector<XOnlyPubKey> input_xonly_pubkeys;
    43     std::vector<CPubKey> input_pubkeys;
    44 
    45     for (size_t i = 0; i < tx->vin.size(); i++)
    46     {
    47-        const Coin& prev_coin{undoTX->vprevout[i]};
    48+        const Coin& prev_coin{undoTX.vprevout[i]};
    49 
    50         const CTxIn& txin{tx->vin.at(i)};
    51 
    52@@ -140,9 +130,16 @@ bool SilentPaymentIndex::CustomAppend(const interfaces::BlockInfo& block)
    53 
    54     const CBlockIndex* pindex = WITH_LOCK(cs_main, return m_chainstate->m_blockman.LookupBlockIndex(block.hash));
    55 
    56+    CBlockUndo blockundo;
    57+    CBlock blockdata;
    58+
    59+    if (!(UndoReadFromDisk(blockundo, pindex) && ReadBlockFromDisk(blockdata, pindex, Params().GetConsensus()))) {
    60+        return false;
    61+    }
    62+
    63     for (const auto& tx : block.data->vtx) {
    64         CPubKey tweakedKey;
    65-        if (GetSilentPaymentKey(tx, pindex, tweakedKey)) {
    66+        if (GetSilentPaymentKey(tx, blockdata, blockundo, tweakedKey)) {
    67             items.emplace_back(tx->GetHash(), tweakedKey);
    68         }
    69 
    70diff --git a/src/index/silentpaymentindex.h b/src/index/silentpaymentindex.h
    71index 44e601334..ef3942686 100644
    72--- a/src/index/silentpaymentindex.h
    73+++ b/src/index/silentpaymentindex.h
    74@@ -22,7 +22,7 @@ private:
    75 
    76     bool AllowPrune() const override { return false; }
    77 
    78-    bool GetSilentPaymentKey(const CTransactionRef& tx, const CBlockIndex* pindex, CPubKey& sum_tx_pubkeys) const;
    79+    bool GetSilentPaymentKey(const CTransactionRef& tx, const CBlock& block, const CBlockUndo& blockUndo, CPubKey& sum_tx_pubkeys) const;
    80 
    81 protected:
    82     bool CustomAppend(const interfaces::BlockInfo& block) override;
    

    This patch speeds up the scanning by 11x (from 11 block/s to 124 block/s)

    Also note that once #24230 is merged, block data (and undo data) will be available in the BlockInfo struct directly (In SilentPaymentIndex::CustomAppend) if the index is configured with additional options. (Currently, the data and undo_data of the BlockInfo struct is unused.)


    w0xlt commented at 9:27 am on January 29, 2023:
    Really good catch @aureleoules ! Thanks for the all the reviews. This improves the index considerably. Done in 1fe05939916b779c7f9111bcbaeaace03595c014. I added you as co-author.
  268. in src/index/silentpaymentindex.cpp:144 in f7bda4c695 outdated
    144+        CPubKey tweakedKey;
    145+        if (GetSilentPaymentKey(tx, pindex, tweakedKey)) {
    146+            items.emplace_back(tx->GetHash(), tweakedKey);
    147+        }
    148+
    149+    }
    


    aureleoules commented at 1:41 pm on January 17, 2023:
    It may be better to execute this function for the entire block instead of executing it for every transaction, and have the for loop inside the function. This would avoid a lot of function calls.

    w0xlt commented at 9:27 am on January 29, 2023:
    Done in 1fe05939916b779c7f9111bcbaeaace03595c014.
  269. w0xlt force-pushed on Jan 26, 2023
  270. DrahtBot removed the label Needs rebase on Jan 26, 2023
  271. w0xlt force-pushed on Jan 26, 2023
  272. w0xlt force-pushed on Jan 27, 2023
  273. w0xlt force-pushed on Jan 28, 2023
  274. DrahtBot added the label Needs rebase on Jan 28, 2023
  275. w0xlt force-pushed on Jan 29, 2023
  276. DrahtBot removed the label Needs rebase on Jan 29, 2023
  277. w0xlt force-pushed on Jan 29, 2023
  278. w0xlt force-pushed on Jan 29, 2023
  279. w0xlt force-pushed on Jan 29, 2023
  280. w0xlt commented at 9:29 am on January 29, 2023: contributor
    Rebased and all the issues identified by SonarCloud in #26865 (comment) have been fixed. Great tool @aureleoules
  281. w0xlt force-pushed on Feb 1, 2023
  282. w0xlt force-pushed on Feb 1, 2023
  283. w0xlt force-pushed on Feb 2, 2023
  284. w0xlt force-pushed on Feb 7, 2023
  285. w0xlt force-pushed on Feb 7, 2023
  286. w0xlt force-pushed on Feb 14, 2023
  287. w0xlt force-pushed on Feb 14, 2023
  288. w0xlt force-pushed on Feb 14, 2023
  289. w0xlt force-pushed on Feb 14, 2023
  290. w0xlt force-pushed on Feb 14, 2023
  291. w0xlt commented at 5:26 am on February 14, 2023: contributor

    Update: The silentpayment index has been removed from the codebase.

    scantxoutset has been changed to use txindex and CBlockUndo. So the scan works without silentpayment index.

    getsilentpaymentblockdata also works without any indexes, using only CBlockUndo. The code is relatively simple. Therefore, even serving light clients, there is no need for indexing.

    Seems to work fine. Not sure about the performance.

    A new PR, with the index, will be added soon.

  292. DrahtBot added the label Needs rebase on Feb 17, 2023
  293. w0xlt force-pushed on Feb 19, 2023
  294. DrahtBot removed the label Needs rebase on Feb 19, 2023
  295. w0xlt force-pushed on Feb 23, 2023
  296. w0xlt force-pushed on Feb 23, 2023
  297. w0xlt force-pushed on Feb 25, 2023
  298. DrahtBot added the label Needs rebase on Mar 7, 2023
  299. DrahtBot commented at 1:08 am on March 7, 2023: contributor

    🐙 This pull request conflicts with the target branch and needs rebase.

  300. in src/outputtype.h:23 in 3742cc7557 outdated
    19@@ -20,13 +20,15 @@ enum class OutputType {
    20     BECH32,
    21     BECH32M,
    22     UNKNOWN,
    23+    SILENT_PAYMENT,
    


    Sjors commented at 3:40 pm on April 7, 2023:
    3742cc75571a60bda52a41923c8244535af9ff23: maybe move that above UNKNOWN?
  301. Sjors commented at 3:47 pm on April 7, 2023: member

    Don’t forget to rebase. The bech32 hrp stuff moved to kernel/chainparams.{h,cpp}.

    The silentpayment index has been removed from the codebase. … A new PR, with the index, will be added soon.

    That’s nice. It’s more important to prove functionality, can improve performance later.

    The rawtr() functionality has been merged, so the dependency can be removed from the PR description. Either this PR description or a BIP should remind the reader why rawtr() is necessary (you can’t know the untweaked key) and safe (there’s no way for the sender to unilaterally add a hidden script path?).

    Enabling the ECDH module in libsecp256k1 might be a reason to put silent payments behind a configure flag initially. Then again, we also need it for BIP324 P2P encryption #23561 and I’m not seeing any discussion there about enabling the module either. That PR also introduces fuzz tests, so maybe it’s worth combining efforts and getting those merged jointly.

    Most of the commits here are reasonably short. It might be worth splitting f0f9817f44fb8dc43c1f8ca0a456fcd3780350af a bit further.

    I would suggest moving PSBT and light client support to a followup PR. It’s important to prove that it works, but that can be done by just keeping it rebased on this PR with the minimum functionality.

  302. in src/wallet/rpc/backup.cpp:1501 in 9143c7e1f1 outdated
    1494@@ -1495,6 +1495,11 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c
    1495             throw JSONRPCError(RPC_INVALID_PARAMETER, "Active descriptors must be ranged");
    1496         }
    1497 
    1498+        if (isSP && data.exists("next_index"))
    


    Sjors commented at 4:08 pm on April 7, 2023:
    9143c7e1f1710f123072cc272b66ab2cad0eb57a: this commit won’t compile, probably needs something from a later commit
  303. in src/wallet/rpc/addresses.cpp:138 in ee652ca59a outdated
    133+
    134+    ret.pushKV("address", address);
    135+    ret.pushKV("label", label);
    136+    ret.pushKV("identifier", identifier);
    137+    if (identifier > 0) {
    138+        ret.pushKV("warnings", "This address is not a new identity. It is a re-use of an existing identity with a different label.");
    


    Sjors commented at 6:40 pm on April 7, 2023:

    The term “identity” is confusing here. It’s also confusing that you get this error when you generate a new silent payment address, but not when you request an existing one (by reusing a label).

    I guess what you’re trying to say here is that an outside observer can tell that two silent payment addresses belong to the same person? Maybe just say that:

    The silent payment addresses are unique for accounting purposes only. They can be linked to the same wallet. For privacy a new wallet is required.

    That said, I think this feature is a footgun. Also because poor coin control will eventually link funds from all labels.

  304. Sjors commented at 6:57 pm on April 7, 2023: member

    Silent watch-only wallets have not yet been implemented. Some architectural improvements are needed first.

    This would be nice though, maybe in a followup. I was trying to test it with bitcoin-cli -signet -named createwallet wallet_name=SilentWatcher silent_payment=true disable_private_keys=true and then import a descriptor like sp(…).

    I guess one thing you need for that is the ability to dump the watch key. You could add an argument scan_privkey to decodesilentaddress which would return the private, rather the public. Ideally in the form of a sp(…) descriptor ready for import (and a warning that leaking that descriptor hurts your privacy, though coins can’t be stolen)

    Finally I noticed that calling listdescriptors true on the wallet reveals only 1 private key for the sp() descriptor; I guess because they are derived from each other?

  305. conf: add EDCH module b6eacc3899
  306. crypto: Add necessary new functions to the silent payments scheme ebf4e8e591
  307. add Silent Payment Scheme 0a19978486
  308. script: create a new output type `OutputType::SILENT_PAYMENT` d4774d5c01
  309. script: add `sp()` descriptor 3bb18d4dbf
  310. wallet: add Silent Payment flag e886d3abc6
  311. script: add SP address generation 02ae78ee62
  312. wallet: enable SP option in the `createwallet`
    Co-authored-by: Aurèle Oulès <aurele@oules.com>
    a967b2103e
  313. wallet: add function to create silent transaction 271e78b5e6
  314. interface: Add `getUndoBlock` function 09410a8f56
  315. wallet: Add `GetSilentPaymentKeysPerBlock` function 1134fe977c
  316. wallet: add Silent Payment verification
    Co-authored-by: josibake <josibake@protonmail.com>
    ff4dabd224
  317. rpc: change scantxoutset to scan silent payments d499a31b4c
  318. Add silent tx function to `walletcreatefundedpsbt` 8600b767f6
  319. wallet: add silent tx function to `sendmany` and `sendtoaddress` 0980159996
  320. wallet: add `listsilentaddresses()` RPC 5d2562aa67
  321. rpc: Add `getsilentpaymentblockdata` for light client 903eaf2538
  322. test: add functional test for Silent Payments 92d69ee941
  323. bench: add benchmark for silent payments 50a6320e57
  324. w0xlt force-pushed on Apr 14, 2023
  325. josibake commented at 1:15 pm on June 5, 2023: member

    I’ve rebased and created a slimmed-down version of this PR in #27827. Most notably, I took out labels and removed some of the RPC support, and made several updates as the silent payments spec has changed a bit since this PR was last updated. For anyone reviewing this PR, I’d appreciate your feedback on #27827.

    Big thanks to @w0xlt for moving this draft along as far as they did! My plan is to keep cherry-picking commits from this draft for follow-up PRs to add back in label support, RPC coverage, etc

  326. fanquake commented at 1:18 pm on June 5, 2023: member
    Closing this, in favour of #27827.
  327. fanquake closed this on Jun 5, 2023

  328. bitcoin locked this on Jun 26, 2024

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2024-07-01 10:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me