From mboxrd@z Thu Jan 1 00:00:00 1970 Delivery-date: Sat, 14 Nov 2026 04:34:29 -0800 Received: from mail-oa1-f61.google.com ([209.85.160.61]) by mail.fairlystable.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.94.2) (envelope-from ) id 1xSCxh-000T33-2I for bitcoindev@gnusha.org; Sat, 14 Nov 2026 04:34:29 -0800 Received: by mail-oa1-f61.google.com with SMTP id 586e51a60fabf-4943ed99826sf1810802fac.3 for ; Sat, 14 Nov 2026 04:34:28 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlegroups.com; s=20260707; t=1794659663; x=1795264463; darn=gnusha.org; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :list-id:mailing-list:precedence:x-original-sender:content-type :mime-version:subject:message-id:to:from:date:sender:from:to:cc :subject:date:message-id:reply-to:content-type; bh=yR32ao7BeiMSm5WBplgMN8WhwWPDNU1oauyWaMcYNmc=; b=Vo5wbr4r9Ziq3x17XDM4zi8aERtQXyZ5IPExrmwh/zKzgkIT9RXts3JwdgGMtXb/cp vpoTx7qOoV593Wkmb91prSrXZlOzVXIep5NTYVzdK1Xlr6YeinJgU+GDFismOj+PbZBu DYOX9xQDKZ5N34TU4Xn5pADHPit159/QkNcTQdcNbMjf1LqYokbZY22gtCkp6UdGEflk Vdts9l4AVnqiN1yAavq1cnp9faPnBg5Vl+utd6OPOlFfASjfHFc9Qjbqzh+k3OeCUNXa 1jeTrWMbOSXKbEhXOe2kj+FBNFKNjw3lbyHeLjJsJ/WkhVWlUvM/+p2cd1mHP5qo/eA1 NhXw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1794659663; x=1795264463; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :list-id:mailing-list:precedence:x-original-sender:content-type :mime-version:subject:message-id:to:from:date:x-beenthere :x-gm-message-state:sender:from:to:cc:subject:date:message-id :reply-to:content-type; bh=yR32ao7BeiMSm5WBplgMN8WhwWPDNU1oauyWaMcYNmc=; b=NjWsGn2gXIuYvu/jIxZRYEPnmrFdTQlAZv68pCbKEuUVtBkspKkWXa+oDsQ5gZfw3o oflTWjA4nZKqH7M/zt/L8zXd3CK9DTKDta4Ri9q0Ykx5OlWjMhFvwDN/E1jG9APQpYE0 vyx51+1pQ5LLgF6Bpxh63oiE1YqFiIsW9v99gVTvEs7Uh6tdjfl6QCogfDRmmb3klYZM bO6VkWln8gTQ7oJ7cutN34F9WaXztXKv6p22XBnsPQXICBFuKoNAYadjw/waBgY1blo4 Wgi2bgEsyDhC7AOYrvIEtFI2ZCRTminE25F9CxgTxv/uQRqhxIvSKC6ZGVfAWqJDBUyo BZkw== Sender: bitcoindev@googlegroups.com X-Forwarded-Encrypted: i=1; AL+hErFUmJMbAJxvNo7VuuCGeUZIZAy37ryTRdGCO7FzzGEu+ESZjQwztl71U91fSQeHimHBhIldt/ldlswe@gnusha.org X-Gm-Message-State: APuU4Z/ZQsiuTb8QqNpLmKXC7RuzIomGFEOWU/7mCIjf8EOqi7v3CCHP hFjAU+Z6XLUs8NFg6DW/482TE0r2AAwXX5CJEvBs1U7AHM7FXk7fr8bV X-Received: by 2002:a05:6870:26c:b0:48f:e107:3183 with SMTP id 586e51a60fabf-4afdf49e2fbmr5026418fac.54.1794659663005; Sat, 14 Nov 2026 04:34:23 -0800 (PST) X-BeenThere: bitcoindev@googlegroups.com; h="ATskLdefgYwOHoYD9ZfSTFWgsA5eZTiC8sodxeR4POauFsZzow==" Received: by 2002:a05:687c:2f09:b0:49d:fffa:fcd0 with SMTP id 586e51a60fabf-4afdf02fc20ls2134750fac.2.-pod-prod-05-us; Sat, 14 Nov 2026 04:34:17 -0800 (PST) X-Received: by 2002:a05:6808:1804:b0:4e7:8f9b:1191 with SMTP id 5614622812f47-56e64f0c81fmr3919635b6e.52.1794659657652; Sat, 14 Nov 2026 04:34:17 -0800 (PST) Received: by 2002:a05:690c:3003:b0:8a6:990a:8ca5 with SMTP id 00721157ae682-8b4b0f55fa5ms7b3; Sat, 14 Nov 2026 00:40:03 -0800 (PST) X-Received: by 2002:a05:690c:c6c2:b0:8b3:5a91:61e8 with SMTP id 00721157ae682-8b4aa3cdd91mr24370137b3.69.1794645602619; Sat, 14 Nov 2026 00:40:02 -0800 (PST) Date: Sat, 14 Nov 2026 00:40:02 -0800 (PST) From: Ali Sherief To: Bitcoin Development Mailing List Message-Id: <05fbac98-5a91-4224-ad9b-745441252e26n@googlegroups.com> Subject: [bitcoindev] spscand and libspscan: a watch-only BIP352 scanner MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="----=_Part_13545_296530642.1794645602341" X-Original-Sender: ali@notatether.com Precedence: list Mailing-list: list bitcoindev@googlegroups.com; contact bitcoindev+owners@googlegroups.com List-ID: X-Google-Group-Id: 786775582512 List-Post: , List-Help: , List-Archive: , List-Unsubscribe: , X-Spam-Score: -0.7 (/) ------=_Part_13545_296530642.1794645602341 Content-Type: multipart/alternative; boundary="----=_Part_13546_1038456883.1794645602341" ------=_Part_13546_1038456883.1794645602341 Content-Type: text/plain; charset="UTF-8" Hi all, I have published spscand, a watch-only scanner for BIP352 silent payments (version 1.1.2), and libspscan, the C library it scans with: https://github.com/ZenulAbidin/spscand (MIT) What it does spscand holds the scan private key and the spend public key. It finds outputs paid to an sp1 address and its labels, tracks spends and reorgs, and reports them over a local read-only HTTP API and a webhook. For each output it stores the tweak t_k, so a separate signer can derive d = b_spend + t_k. It handles confirmed blocks only. How scanning stays private It never queries a server about an address, output or transaction. Also there are three block sources: 1. Bitcoin Core (25+) over RPC. Tweaks are computed locally from getblock verbosity 3. Nothing leaves the machine. 2. A BlindBit Oracle server. For every block from the wallet birthday to the tip it downloads all tweaks, all taproot outputs and the spent-key prefixes, and matches locally. Every block is requested the same way whether or not it pays the wallet. The server learns the client's IP address (unless Tor is used), the birthday and when the client is online or rescans. It can also withhold data and so hide a payment. 3. Filters, the strategy in BIP352's appendix. Tweaks still come from BlindBit, for every block. The block's BIP158 basic filter comes from P2P peers (BIP157). The client computes its candidate k = 0 output keys, tests them and its unspent outputs against the filter, and downloads the block only on a match. The peer serving that block learns it was of interest, so each block is fetched from a randomly chosen peer over a connection of its own. A peer could hide a payment by serving a false filter. Filter headers are therefore compared across all reachable peers, and the user is told to configure at least two. The P2P transport is v1 (no BIP324 yet). With a SOCKS5 proxy, names are resolved by the proxy, proxy environment variables are ignored, and each process, and in filter mode each P2P connection, uses random SOCKS credentials so that Tor with IsolateSOCKSAuth gives it circuits of its own. libspscan libspscan does the BIP352 receiving side and the BIP158 filter checks. It does no I/O, never takes a spend private key, and depends only on the C standard library, so it can be used under other wallets or indexers. spscand calls it through ctypes. - It bundles libsecp256k1 v0.8.0, unmodified (the README gives the commit and a diff command). Shared secrets, output tweaks and labels come from its silentpayments module. - libsecp256k1 is compiled into the same translation unit so that label matching can use its field arithmetic. Only spscan_* symbols are exported. - A tweak server does not say which transaction a tweak belongs to, so a block's tweaks are tried against all of its taproot outputs. Each label adds a candidate x(P_k + L) per tweak. These are added in affine coordinates with one field inversion per batch of 1024. When labels outnumber twice the outputs, output - P_k is looked up among the labels instead, as BIP352 suggests. - The shared secret b_scan * (input_hash * A) is computed in constant time. Matching after that branches on public output keys and on P_k, as libsecp256k1's own scanner does. - Lookup tables are keyed with SipHash under a key derived from the scan key, so a sender cannot choose output keys that collide. Measurements, mainnet block 833044 (721 eligible transactions, 2105 taproot outputs): - BlindBit: 135 kB downloaded; 0.05 s CPU, 0.16 s with 1000 labels. - Filters: 28 kB of tweaks and a 31 kB filter; 0.05 s CPU. - The filter check tests tweaks * (1 + labels) items. The chance of downloading this block for nothing is 0.2% with no labels, 1% with 10 and 9% with 100. With 1000 labels most blocks match. Tests - The BIP352 receiving test vectors, in both the C and the Python code, with every result compared between the two. - Bitcoin Core's BIP158 vectors. libspscan builds the same filters byte for byte. - Mainnet block 833044 checked against a real BlindBit index. - The filters backend against fake peers, including peers that serve false filters. - An end-to-end run on regtest with Bitcoin Core 27.1: payments from the BIP352 reference sender with P2WPKH, P2SH-P2WPKH, P2PKH and P2TR inputs, then labels, spending, spend detection, reorgs and rescans. In filter mode it checks that only blocks paying or spending the wallet's outputs were downloaded, and with a SOCKS proxy that nothing bypassed it. Corrections and criticism are welcome, on the list or as GitHub issues. Ali Sherief bitmixlist.org -- You received this message because you are subscribed to the Google Groups "Bitcoin Development Mailing List" group. To unsubscribe from this group and stop receiving emails from it, send an email to bitcoindev+unsubscribe@googlegroups.com. To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/05fbac98-5a91-4224-ad9b-745441252e26n%40googlegroups.com. ------=_Part_13546_1038456883.1794645602341 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Hi all,

I have published spscand, a watch-only scanner for BIP35= 2 silent payments (version 1.1.2), and libspscan, the C library it scans wi= th:

https://github.com/ZenulAbidin/spscand =C2=A0(MIT)

What it does

spscand holds the scan private key and the spend = public key. It finds outputs paid to an sp1 address and its labels, tracks = spends and reorgs, and reports them over a local read-only HTTP API and a w= ebhook. For each output it stores the tweak t_k, so a separate signer can d= erive d =3D b_spend + t_k. It handles confirmed blocks only.

How= scanning stays private

It never queries a server about an addre= ss, output or transaction. Also there are three block sources:

1= . Bitcoin Core (25+) over RPC. Tweaks are computed locally from getblock ve= rbosity 3. Nothing leaves the machine.

2. A BlindBit Oracle serv= er. For every block from the wallet birthday to the tip it downloads all tw= eaks, all taproot outputs and the spent-key prefixes, and matches locally. = Every block is requested the same way whether or not it pays the wallet. Th= e server learns the client's IP address (unless Tor is used), the birthday = and when the client is online or rescans. It can also withhold data and so = hide a payment.

3. Filters, the strategy in BIP352's appendix. T= weaks still come from BlindBit, for every block. The block's BIP158 basic f= ilter comes from P2P peers (BIP157). The client computes its candidate k = =3D 0 output keys, tests them and its unspent outputs against the filter, a= nd downloads the block only on a match. The peer serving that block learns = it was of interest, so each block is fetched from a randomly chosen peer ov= er a connection of its own.

A peer could hide a payment by servi= ng a false filter. Filter headers are therefore compared across all reachab= le peers, and the user is told to configure at least two. The P2P transport= is v1 (no BIP324 yet).

With a SOCKS5 proxy, names are resolved = by the proxy, proxy environment variables are ignored, and each process, an= d in filter mode each P2P connection, uses random SOCKS credentials so that= Tor with IsolateSOCKSAuth gives it circuits of its own.

libspsc= an

libspscan does the BIP352 receiving side and the BIP158 filte= r checks. It does no I/O, never takes a spend private key, and depends only= on the C standard library, so it can be used under other wallets or indexe= rs. spscand calls it through ctypes.

- It bundles libsecp256k1 v= 0.8.0, unmodified (the README gives the commit and a diff command). Shared = secrets, output tweaks and labels come from its silentpayments module.
- libsecp256k1 is compiled into the same translation unit so that label ma= tching can use its field arithmetic. Only spscan_* symbols are exported.- A tweak server does not say which transaction a tweak belongs to, so a= block's tweaks are tried against all of its taproot outputs. Each label ad= ds a candidate x(P_k + L) per tweak. These are added in affine coordinates = with one field inversion per batch of 1024. When labels outnumber twice the= outputs, output - P_k is looked up among the labels instead, as BIP352 sug= gests.
- The shared secret b_scan * (input_hash * A) is computed in co= nstant time. Matching after that branches on public output keys and on P_k,= as libsecp256k1's own scanner does.
- Lookup tables are keyed with Si= pHash under a key derived from the scan key, so a sender cannot choose outp= ut keys that collide.

Measurements, mainnet block 833044 (721 el= igible transactions, 2105 taproot outputs):

- BlindBit: 135 kB d= ownloaded; 0.05 s CPU, 0.16 s with 1000 labels.
- Filters: 28 kB of tw= eaks and a 31 kB filter; 0.05 s CPU.
- The filter check tests tweaks *= (1 + labels) items. The chance of downloading this block for nothing is 0.= 2% with no labels, 1% with 10 and 9% with 100. With 1000 labels most blocks= match.

Tests

- The BIP352 receiving test vectors, in= both the C and the Python code, with every result compared between the two= .
- Bitcoin Core's BIP158 vectors. libspscan builds the same filters b= yte for byte.
- Mainnet block 833044 checked against a real BlindBit i= ndex.
- The filters backend against fake peers, including peers that s= erve false filters.
- An end-to-end run on regtest with Bitcoin Core 2= 7.1: payments from the BIP352 reference sender with P2WPKH, P2SH-P2WPKH, P2= PKH and P2TR inputs, then labels, spending, spend detection, reorgs and res= cans. In filter mode it checks that only blocks paying or spending the wall= et's outputs were downloaded, and with a SOCKS proxy that nothing bypassed = it.

Corrections and criticism are welcome, on the list or as Git= Hub issues.

Ali Sherief
bitmixlist.org

--
You received this message because you are subscribed to the Google Groups &= quot;Bitcoin Development Mailing List" group.
To unsubscribe from this group and stop receiving emails from it, send an e= mail to bitcoind= ev+unsubscribe@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/bitcoind= ev/05fbac98-5a91-4224-ad9b-745441252e26n%40googlegroups.com.
------=_Part_13546_1038456883.1794645602341-- ------=_Part_13545_296530642.1794645602341--