@@ -425,4 +425,135 @@ int secp256k1_silentpayments_recipient_public_data_parse(const secp256k1_context
425425 return 1 ;
426426}
427427
428+ int secp256k1_silentpayments_recipient_scan_outputs (
429+ const secp256k1_context * ctx ,
430+ secp256k1_silentpayments_found_output * * found_outputs , size_t * n_found_outputs ,
431+ const secp256k1_xonly_pubkey * const * tx_outputs , size_t n_tx_outputs ,
432+ const unsigned char * recipient_scan_key ,
433+ const secp256k1_silentpayments_public_data * public_data ,
434+ const secp256k1_pubkey * recipient_spend_pubkey ,
435+ const secp256k1_silentpayments_label_lookup label_lookup ,
436+ const void * label_context
437+ ) {
438+ secp256k1_scalar t_k_scalar ;
439+ secp256k1_ge label_ge , recipient_spend_pubkey_ge ;
440+ secp256k1_pubkey A_sum ;
441+ secp256k1_xonly_pubkey P_output_xonly ;
442+ unsigned char shared_secret [33 ];
443+ unsigned char label_tweak32 [32 ];
444+ const unsigned char * label_tweak = label_tweak32 ;
445+ size_t i , k , n_found , found_idx ;
446+ int found , combined ;
447+
448+ /* Sanity check inputs */
449+ VERIFY_CHECK (ctx != NULL );
450+ ARG_CHECK (found_outputs != NULL );
451+ ARG_CHECK (n_found_outputs != NULL );
452+ ARG_CHECK (tx_outputs != NULL );
453+ ARG_CHECK (n_tx_outputs >= 1 );
454+ ARG_CHECK (recipient_scan_key != NULL );
455+ ARG_CHECK (public_data != NULL );
456+ combined = (int )public_data -> data [0 ];
457+ {
458+ unsigned char input_hash [32 ];
459+ unsigned char * input_hash_ptr ;
460+ if (combined ) {
461+ input_hash_ptr = NULL ;
462+ } else {
463+ memset (input_hash , 0 , 32 );
464+ input_hash_ptr = input_hash ;
465+ }
466+ if (!secp256k1_silentpayments_recipient_public_data_load (ctx , & A_sum , input_hash_ptr , public_data )) {
467+ return 0 ;
468+ }
469+ secp256k1_pubkey_load (ctx , & recipient_spend_pubkey_ge , recipient_spend_pubkey );
470+ if (!secp256k1_silentpayments_create_shared_secret (ctx , shared_secret , recipient_scan_key , & A_sum , input_hash_ptr )) {
471+ return 0 ;
472+ }
473+ }
474+
475+ found_idx = 0 ;
476+ n_found = 0 ;
477+ k = 0 ;
478+ while (1 ) {
479+ secp256k1_ge P_output_ge = recipient_spend_pubkey_ge ;
480+ /* Calculate t_k = hash(shared_secret || ser_32(k)) */
481+ secp256k1_silentpayments_create_t_k (& t_k_scalar , shared_secret , k );
482+
483+ /* Calculate P_output = B_spend + t_k * G */
484+ if (!secp256k1_eckey_pubkey_tweak_add (& P_output_ge , & t_k_scalar )) {
485+ return 0 ;
486+ }
487+
488+ found = 0 ;
489+ secp256k1_xonly_pubkey_save (& P_output_xonly , & P_output_ge );
490+ for (i = 0 ; i < n_tx_outputs ; i ++ ) {
491+ if (secp256k1_xonly_pubkey_cmp (ctx , & P_output_xonly , tx_outputs [i ]) == 0 ) {
492+ label_tweak = NULL ;
493+ found = 1 ;
494+ found_idx = i ;
495+ break ;
496+ }
497+
498+ /* If not found, proceed to check for labels (if the labels cache is present) */
499+ if (label_lookup != NULL ) {
500+ secp256k1_pubkey label_pubkey ;
501+ secp256k1_ge P_output_negated_ge , tx_output_ge ;
502+ secp256k1_gej tx_output_gej , label_gej ;
503+
504+ secp256k1_xonly_pubkey_load (ctx , & tx_output_ge , tx_outputs [i ]);
505+ secp256k1_gej_set_ge (& tx_output_gej , & tx_output_ge );
506+ secp256k1_ge_neg (& P_output_negated_ge , & P_output_ge );
507+ /* Negate the generated output and calculate first scan label candidate:
508+ * label1 = tx_output - P_output */
509+ secp256k1_gej_add_ge_var (& label_gej , & tx_output_gej , & P_output_negated_ge , NULL );
510+ secp256k1_ge_set_gej (& label_ge , & label_gej );
511+ secp256k1_pubkey_save (& label_pubkey , & label_ge );
512+ label_tweak = label_lookup (& label_pubkey , label_context );
513+ if (label_tweak != NULL ) {
514+ found = 1 ;
515+ found_idx = i ;
516+ break ;
517+ }
518+
519+ secp256k1_gej_neg (& label_gej , & tx_output_gej );
520+ /* If not found, negate the tx_output and calculate second scan label candidate:
521+ * label2 = -tx_output - P_output */
522+ secp256k1_gej_add_ge_var (& label_gej , & label_gej , & P_output_negated_ge , NULL );
523+ secp256k1_ge_set_gej (& label_ge , & label_gej );
524+ secp256k1_pubkey_save (& label_pubkey , & label_ge );
525+ label_tweak = label_lookup (& label_pubkey , label_context );
526+ if (label_tweak != NULL ) {
527+ found = 1 ;
528+ found_idx = i ;
529+ break ;
530+ }
531+ }
532+ }
533+ if (found ) {
534+ found_outputs [n_found ]-> output = * tx_outputs [found_idx ];
535+ secp256k1_scalar_get_b32 (found_outputs [n_found ]-> tweak , & t_k_scalar );
536+ if (label_lookup != NULL && label_tweak != NULL ) {
537+ found_outputs [n_found ]-> found_with_label = 1 ;
538+ if (!secp256k1_ec_seckey_tweak_add (ctx , found_outputs [n_found ]-> tweak , label_tweak )) {
539+ return 0 ;
540+ }
541+ secp256k1_pubkey_save (& found_outputs [n_found ]-> label , & label_ge );
542+ } else {
543+ found_outputs [n_found ]-> found_with_label = 0 ;
544+ /* TODO: instead of using the tx_output, set the label with a properly invalid pubkey */
545+ secp256k1_pubkey_save (& found_outputs [n_found ]-> label , & P_output_ge );
546+ }
547+ /* Set everything for the next round of scanning */
548+ label_tweak = label_tweak32 ;
549+ n_found ++ ;
550+ k ++ ;
551+ } else {
552+ break ;
553+ }
554+ }
555+ * n_found_outputs = n_found ;
556+ return 1 ;
557+ }
558+
428559#endif
0 commit comments