Skip to content

Instantly share code, notes, and snippets.

@theStack
Last active December 4, 2025 18:43
Show Gist options
  • Select an option

  • Save theStack/25c77747838610931e8bbeb9d76faf78 to your computer and use it in GitHub Desktop.

Select an option

Save theStack/25c77747838610931e8bbeb9d76faf78 to your computer and use it in GitHub Desktop.
Different approaches for scanning in Silent Payments (BIP-352)
Different approaches for scanning in Silent Payments (BIP-352)
=============================================================================================
===== "calculate label candidates for each tx output, look them up in the labels cache" =====
=============================================================================================
The current approach for scanning in secp #1765 is as follows, following BIP-352:
For each k value, derive the unlabeled output candidate P_k, then loop over the
transaction x-only outputs linearly to check whether there is either a unlabeled or a
labeled match. For the latter, two label candidates are calculated for each transaction
output (necessary as we don't know the correct Y coordinate for x-only outputs), that are
checked against the labels cache each to see if there is a match:
------------------------------------------------------------
for k = 0..:
derive P_k
for tx_output in tx_outputs:
if pk_xonly(P_k) == tx_output:
UNLABELED_MATCH
label_candidate1 = pk_even(tx_output) - P_k
label_candidate2 = pk_odd(tx_output) - P_k
if any of label_candidate{1,2} in labels_cache:
LABELED_MATCH
abort scanning if there was no match
------------------------------------------------------------
For scanning a transaction that doesn't have any matches (the common case), there are
2 * N_OUTPUTS point additions needed for labels scanning, independent on the number of
different labels to scan for.
========================================================================================
===== "calculate output candidates for each label, look them up in the tx outputs" =====
========================================================================================
Alternative approach (as proposed by w0xlt):
For each k value, derive the unlabeled output candidate P_k (that part is still the same).
Instead of looping linearly over all the transaction outputs and calculating label
candidates for each of them, let the user supply all the label public keys to scan for,
and calculate the output candidate from that, then look for each label if there is a match:
------------------------------------------------------------
for k = 0..:
derive P_k
if pk_xonly(P_k) in tx_outputs:
UNLABELED_MATCH
for label_pubkey in user_supplied_labels:
output_candidate = P_k + label_pubkey
if pk_xonly(output_candidate) in tx_outputs:
LABELED_MATCH
abort scanning if there was no match
------------------------------------------------------------
The lookup in tx_outputs can be sped up by using binary search (relevant for worst-case
scanning attack). For scanning a transaction that doesn't have any matches (the common
case), there are N_LABELS point additions needed for labels scanning.
Especially for users with a small number of used labels (i.e. only the label for change
outputs is used, i.e. N_LABELS = 1), the second approach seems significanly faster than
the original one, considering that most Silent Payments eligible transactions will likely have
at least two outputs to scan for. As the number of labels to scan for increases, the first
approach is likely faster.
Any thoughts on this? Did I miss anything?
@theStack
Copy link
Author

theStack commented Dec 4, 2025

Benchmark results from the WIP "silentpayments" secp256k1 module (theStack/secp256k1@8eced64) over a L/N matrix (note that L=1 should represent any higher L values as well for the BIP approach, assuming that the label cache lookup cost can be neglected):

Benchmark                               ,    Min(us)    ,    Avg(us)    ,    Max(us)    

sp_full_scan_BIP-algo_L=0_N=2           ,    41.2       ,    41.2       ,    41.2    
sp_full_scan_BIP-algo_L=0_N=5           ,    41.3       ,    41.3       ,    41.3    
sp_full_scan_BIP-algo_L=0_N=10          ,    41.4       ,    41.4       ,    41.4    
sp_full_scan_BIP-algo_L=0_N=20          ,    41.6       ,    41.6       ,    41.6    
sp_full_scan_BIP-algo_L=0_N=50          ,    42.3       ,    42.4       ,    42.4    
sp_full_scan_BIP-algo_L=0_N=100         ,    43.6       ,    43.6       ,    43.6    
sp_full_scan_BIP-algo_L=0_N=200         ,    46.0       ,    46.0       ,    46.0    
-----
sp_full_scan_BIP-algo_L=1_N=2           ,    45.1       ,    45.1       ,    45.1    
sp_full_scan_BIP-algo_L=1_N=5           ,    51.3       ,    51.3       ,    51.3    
sp_full_scan_BIP-algo_L=1_N=10          ,    62.0       ,    62.0       ,    62.0    
sp_full_scan_BIP-algo_L=1_N=20          ,    84.2       ,    84.2       ,    84.2    
sp_full_scan_BIP-algo_L=1_N=50          ,   151.0       ,   151.0       ,   151.0    
sp_full_scan_BIP-algo_L=1_N=100         ,   263.0       ,   263.0       ,   263.0    
sp_full_scan_BIP-algo_L=1_N=200         ,   488.0       ,   488.0       ,   488.0    
-----

sp_full_scan_LabelSet-algo_L=0_N=2      ,    41.2       ,    41.2       ,    41.2    
sp_full_scan_LabelSet-algo_L=0_N=5      ,    41.5       ,    41.5       ,    41.5    
sp_full_scan_LabelSet-algo_L=0_N=10     ,    42.4       ,    42.4       ,    42.4    
sp_full_scan_LabelSet-algo_L=0_N=20     ,    44.7       ,    44.7       ,    44.7    
sp_full_scan_LabelSet-algo_L=0_N=50     ,    53.8       ,    53.8       ,    53.8    
sp_full_scan_LabelSet-algo_L=0_N=100    ,    72.6       ,    72.6       ,    72.6    
sp_full_scan_LabelSet-algo_L=0_N=200    ,   116.0       ,   116.0       ,   116.0    
-----
sp_full_scan_LabelSet-algo_L=1_N=2      ,    42.3       ,    42.3       ,    42.3    
sp_full_scan_LabelSet-algo_L=1_N=5      ,    42.6       ,    42.6       ,    42.6    
sp_full_scan_LabelSet-algo_L=1_N=10     ,    43.5       ,    43.5       ,    43.5    
sp_full_scan_LabelSet-algo_L=1_N=20     ,    45.8       ,    45.8       ,    45.8    
sp_full_scan_LabelSet-algo_L=1_N=50     ,    55.0       ,    55.0       ,    55.0    
sp_full_scan_LabelSet-algo_L=1_N=100    ,    73.8       ,    73.8       ,    73.8    
sp_full_scan_LabelSet-algo_L=1_N=200    ,   117.0       ,   117.0       ,   117.0    
-----
sp_full_scan_LabelSet-algo_L=2_N=2      ,    43.4       ,    43.4       ,    43.4    
sp_full_scan_LabelSet-algo_L=2_N=5      ,    43.7       ,    43.7       ,    43.7    
sp_full_scan_LabelSet-algo_L=2_N=10     ,    44.6       ,    44.6       ,    44.6    
sp_full_scan_LabelSet-algo_L=2_N=20     ,    47.0       ,    47.0       ,    47.0    
sp_full_scan_LabelSet-algo_L=2_N=50     ,    56.2       ,    56.2       ,    56.2    
sp_full_scan_LabelSet-algo_L=2_N=100    ,    75.0       ,    75.0       ,    75.0    
sp_full_scan_LabelSet-algo_L=2_N=200    ,   119.0       ,   119.0       ,   119.0    
-----
sp_full_scan_LabelSet-algo_L=3_N=2      ,    44.5       ,    44.5       ,    44.5    
sp_full_scan_LabelSet-algo_L=3_N=5      ,    44.9       ,    44.9       ,    44.9    
sp_full_scan_LabelSet-algo_L=3_N=10     ,    45.8       ,    45.8       ,    45.8    
sp_full_scan_LabelSet-algo_L=3_N=20     ,    48.1       ,    48.2       ,    48.2    
sp_full_scan_LabelSet-algo_L=3_N=50     ,    57.4       ,    57.4       ,    57.4    
sp_full_scan_LabelSet-algo_L=3_N=100    ,    76.2       ,    76.2       ,    76.3    
sp_full_scan_LabelSet-algo_L=3_N=200    ,   120.0       ,   120.0       ,   120.0    
-----
sp_full_scan_LabelSet-algo_L=5_N=2      ,    46.7       ,    46.7       ,    46.7    
sp_full_scan_LabelSet-algo_L=5_N=5      ,    47.1       ,    47.1       ,    47.1    
sp_full_scan_LabelSet-algo_L=5_N=10     ,    48.1       ,    48.1       ,    48.1    
sp_full_scan_LabelSet-algo_L=5_N=20     ,    50.4       ,    50.5       ,    50.5    
sp_full_scan_LabelSet-algo_L=5_N=50     ,    59.8       ,    59.8       ,    59.8    
sp_full_scan_LabelSet-algo_L=5_N=100    ,    78.6       ,    78.6       ,    78.7    
sp_full_scan_LabelSet-algo_L=5_N=200    ,   122.0       ,   122.0       ,   122.0    
-----
sp_full_scan_LabelSet-algo_L=10_N=2     ,    52.3       ,    52.4       ,    52.4    
sp_full_scan_LabelSet-algo_L=10_N=5     ,    52.9       ,    52.9       ,    52.9    
sp_full_scan_LabelSet-algo_L=10_N=10    ,    54.0       ,    54.0       ,    54.0    
sp_full_scan_LabelSet-algo_L=10_N=20    ,    56.4       ,    56.5       ,    56.5    
sp_full_scan_LabelSet-algo_L=10_N=50    ,    66.0       ,    66.0       ,    66.0    
sp_full_scan_LabelSet-algo_L=10_N=100   ,    84.9       ,    85.0       ,    85.0    
sp_full_scan_LabelSet-algo_L=10_N=200   ,   129.0       ,   129.0       ,   129.0    
-----
sp_full_scan_LabelSet-algo_L=20_N=2     ,    63.7       ,    63.7       ,    63.8    
sp_full_scan_LabelSet-algo_L=20_N=5     ,    64.5       ,    64.5       ,    64.5    
sp_full_scan_LabelSet-algo_L=20_N=10    ,    65.7       ,    65.8       ,    65.8    
sp_full_scan_LabelSet-algo_L=20_N=20    ,    68.6       ,    68.6       ,    68.6    
sp_full_scan_LabelSet-algo_L=20_N=50    ,    78.5       ,    78.5       ,    78.5    
sp_full_scan_LabelSet-algo_L=20_N=100   ,    97.6       ,    97.6       ,    97.6    
sp_full_scan_LabelSet-algo_L=20_N=200   ,   142.0       ,   142.0       ,   142.0    
-----
sp_full_scan_LabelSet-algo_L=50_N=2     ,    97.9       ,    98.0       ,    98.0    
sp_full_scan_LabelSet-algo_L=50_N=5     ,    99.5       ,    99.5       ,    99.5    
sp_full_scan_LabelSet-algo_L=50_N=10    ,   101.0       ,   101.0       ,   101.0    
sp_full_scan_LabelSet-algo_L=50_N=20    ,   105.0       ,   105.0       ,   105.0    
sp_full_scan_LabelSet-algo_L=50_N=50    ,   116.0       ,   116.0       ,   116.0    
sp_full_scan_LabelSet-algo_L=50_N=100   ,   136.0       ,   136.0       ,   136.0    
sp_full_scan_LabelSet-algo_L=50_N=200   ,   181.0       ,   182.0       ,   182.0    
-----

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment