Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
251}252/*** Create the recipient objects ***/254/* Alice is sending to Bob and Carol in this transaction:255*256* 1. One output to Bob's labeled address257* 2. Two outputs for Carol258*259* To create multiple outputs for Carol, Alice simply passes Carol's260* Silent Payments address multiple times.261*/262sp_addresses[0] = &carol_address;263sp_addresses[1] = &bob_address;264sp_addresses[2] = &carol_address;265for (i = 0; i < N_OUTPUTS; i++) {266ret = secp256k1_ec_pubkey_parse(ctx,267&recipients[i].scan_pubkey,268(*(sp_addresses[i]))[0],26933270);271ret &= secp256k1_ec_pubkey_parse(ctx,272&recipients[i].spend_pubkey,273(*(sp_addresses[i]))[1],27433275);276if (!ret) {277printf("Something went wrong, this is not a valid Silent Payments address.\n");278return EXIT_FAILURE;279}281/* Alice creates the recipient objects and adds the index of the282* original ordering (the ordering of the `sp_addresses` array) to283* each object. This index is used to return the generated outputs284* in the original ordering so that Alice can match up the generated285* outputs with the correct amounts.286*/287recipients[i].index = i;288recipient_ptrs[i] = &recipients[i];289}290ret = secp256k1_silentpayments_sender_create_outputs(ctx,291tx_output_ptrs,292recipient_ptrs, N_OUTPUTS,293smallest_outpoint,294sender_keypair_ptrs, N_INPUTS,295NULL, 0296);297if (!ret) {298printf("Something went wrong, group limit exceeded or input secret keys sum to zero.\n");299return EXIT_FAILURE;300}301printf("Alice created the following outputs for Bob and Carol:\n");302for (i = 0; i < N_OUTPUTS; i++) {303printf(" ");304ret = secp256k1_xonly_pubkey_serialize(ctx,305serialized_xonly,306&tx_outputs[i]307);308assert(ret);309print_hex(serialized_xonly, sizeof(serialized_xonly));310}311/* It's best practice to try to clear secrets from memory after using312* them. This is done because some bugs can allow an attacker to leak313* memory, for example through "out of bounds" array access (see314* Heartbleed), or the OS swapping them to disk. Hence, we overwrite the315* secret key buffer with zeros.316*317* Here we are preventing these writes from being optimized out, as any318* good compiler will remove any writes that aren't used. */319secure_erase(seckey, sizeof(seckey));320for (i = 0; i < N_INPUTS; i++) {321secure_erase(&sender_keypairs[i], sizeof(sender_keypairs[i]));322}323}325/*** Receiving ***/326{327{328/*** Scanning as a full node (Bob) ***329*330* Since Bob has access to the full transaction, scanning is simple:331*332* 1. Collect the relevant prevouts from the transaction and call333* `secp256k1_silentpayments_recipient_prevouts_summary_create`334* 2. Call `secp256k1_silentpayments_recipient_scan_outputs`335*/336ret = secp256k1_silentpayments_recipient_prevouts_summary_create(ctx,337&prevouts_summary,338smallest_outpoint,339tx_input_ptrs, N_INPUTS,340NULL, 0341);342if (!ret) {343/* We need to always check that the prevouts data object is valid344* before proceeding.345*/346printf("This transaction is not valid for Silent Payments, skipping.\n");347return EXIT_SUCCESS;348}350/* Scan the transaction */351n_found_outputs = 0;352ret = secp256k1_silentpayments_recipient_scan_outputs(ctx,353found_output_ptrs, &n_found_outputs,354(const secp256k1_xonly_pubkey**)tx_output_ptrs, N_OUTPUTS,355bob_scan_key,356&prevouts_summary,357&unlabeled_spend_pubkey,358label_lookup, &bob_labels_cache /* NULL, NULL for no labels */359);360if (!ret) {361printf("This transaction is not valid for Silent Payments, skipping.\n");362return EXIT_SUCCESS;363}364if (n_found_outputs > 0) {365secp256k1_keypair kp;366secp256k1_xonly_pubkey xonly_output;367unsigned char full_seckey[32];369printf("\n");370printf("Bob found the following outputs: \n");371for (i = 0; i < n_found_outputs; i++) {372printf(" ");373ret = secp256k1_xonly_pubkey_serialize(ctx,374serialized_xonly,375&found_outputs[i].output376);377assert(ret);378print_hex(serialized_xonly, sizeof(serialized_xonly));380/* Verify that this output is spendable by Bob by reconstructing the full381* secret key for the xonly output.382*383* This is done by adding the tweak from the transaction to Bob's spend key.384* If the output was sent to a labeled address, the label tweak has already385* been added to the tweak in `secp256k1_silentpayments_found_output`.386*387* To verify that we are able to sign for this output, it is sufficient to388* check that the public key generated from `full_seckey` matches the output389* in the transaction. For a full example on signing for a taproot ouput,390* see `examples/schnorr.c`.391*/392memcpy(&full_seckey, &bob_spend_key, 32);393ret = secp256k1_ec_seckey_tweak_add(ctx, full_seckey, found_outputs[i].tweak);394ret &= secp256k1_keypair_create(ctx, &kp, full_seckey);395ret &= secp256k1_keypair_xonly_pub(396ctx,397&xonly_output,398NULL,399&kp400);401/* We assert here because the only way the seckey_tweak_add operation can fail402* is if the tweak is the negation of Bob's spend key.403*404* We also assert that the generated public key matches the transaction output,405* as it should be impossible for a mismatch at this point considering the406* scanning function completed without errors and indicated found outputs.407*/408assert(ret);409assert(secp256k1_xonly_pubkey_cmp(ctx, &xonly_output, &found_outputs[i].output) == 0);410secure_erase(full_seckey, sizeof(full_seckey));411}412} else {413printf("Bob did not find any outputs in this transaction.\n");414}415}416{417/*** Scanning as a full node (Carol) ***/418/* TODO: switch this part to light client scanning once it is supported */420/* Load Carol's spend public key. */421ret = secp256k1_ec_pubkey_parse(ctx,422&unlabeled_spend_pubkey,423carol_address[1],42433425);426assert(ret);428n_found_outputs = 0;429ret = secp256k1_silentpayments_recipient_scan_outputs(ctx,430found_output_ptrs, &n_found_outputs,431(const secp256k1_xonly_pubkey**)tx_output_ptrs, N_OUTPUTS,432carol_scan_key,433&prevouts_summary,434&unlabeled_spend_pubkey,435NULL, NULL /* NULL, NULL for no labels */436);437if (!ret) {438printf("This transaction is not valid for Silent Payments, skipping.\n");439return EXIT_SUCCESS;440}441if (n_found_outputs > 0) {442/* Carol would spend these outputs the same as Bob, by tweaking her443* spend key with the tweak corresponding to the found output. See above444* for an example for Bob's outputs. */445printf("\n");446printf("Carol found the following outputs: \n");447for (i = 0; i < n_found_outputs; i++) {448printf(" ");449ret = secp256k1_xonly_pubkey_serialize(ctx,450serialized_xonly,451&found_outputs[i].output452);453assert(ret);454print_hex(serialized_xonly, sizeof(serialized_xonly));455}456} else {457printf("Carol did not find any outputs in this transaction.\n");458}459}460}462/* This will clear everything from the context and free the memory */463secp256k1_context_destroy(ctx);464return EXIT_SUCCESS;465}
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.