Files
bitcoin/examples/silentpayments.c
fanquake 09cc345c3e Squashed 'src/secp256k1/' changes from d2d04864ef..687155df6b
687155df6b Merge bitcoin-core/secp256k1#1897: tests: check results before using outputs
8a700a355e Merge bitcoin-core/secp256k1#1907: release cleanup: bump version after 0.8.0
78657bf28b release cleanup: bump version after 0.8.0
6e2c8bc4ec Merge bitcoin-core/secp256k1#1906: release: prepare for 0.8.0
5840c19b4e release: prepare for 0.8.0
3873647bfb Merge bitcoin-core/secp256k1#1904: sha256: cross-check caller supplied compression function
c84ea46561 sha256: cross-check caller supplied compression function
f8c75f89a5 Merge bitcoin-core/secp256k1#1903: changelog: add entry for #1821
2076b06a42 changelog: add entry for #1821
7fecac74ae Merge bitcoin-core/secp256k1#1709: release-process: Add signing of tarball plus minor improvements
12d9cfd86e release-process: Add attaching output of check-abi.sh to PR
b0a0ae8246 release-process: Add "cleaning up"
4a73b1ae27 release-process: Fix nits
fae22e777e release-process: Add signing of tarball
34f00ca9d9 release-process: Refactor
1c2933bb87 Merge bitcoin-core/secp256k1#1902: changelog: add entry for #1859
51fc633e3e changelog: add entry for #1859
d77f44e9cd Merge bitcoin-core/secp256k1#1863: ellswift: don't declassify or leave sk in sha256 buffer
0ae17e304e ellswift: don't declassify or leave sk in sha256 buffer
0a9e788901 Merge bitcoin-core/secp256k1#1900: Use SHA256 override for pointers to known aux functions
300c9bb26d Merge bitcoin-core/secp256k1#1901: changelog: Add entry for #1869
44ba8cd7df changelog: Add entry for #1869
4147f8bdf6 header: Add note on SHA256 override and aux functions
ed091bc49d ecdsa/ecdh: Use SHA256 override if known noncefp/hashfp is passed
209ed1025b Merge bitcoin-core/secp256k1#1886: Remove deprecated `secp256k1_context_no_precomp` pointer
bf435856bb Remove deprecated `secp256k1_context_no_precomp` pointer
528863e61f Merge bitcoin-core/secp256k1#1869: Remove SECP256K1_GNUC_PREREQ macro
3a73d473f2 Merge bitcoin-core/secp256k1#1776: Remove deprecated `secp256k1_schnorrsig_sign` alias
e14756bd25 Remove deprecated `secp256k1_schnorrsig_sign` alias
7151e3b843 Merge bitcoin-core/secp256k1#1899: changelog: add missing entries for #1777 and #1860
f52eb393c4 changelog: add missing entries for #1777 and #1860
0f6baf319f Merge bitcoin-core/secp256k1#1896: silentpayments: address #1765 follow-ups, add changelog entry
a2ad68cd81 ec: check pubkey sort test results
93280c2291 silentpayments: check test serialization
b8de1bc30f musig: check infinity test setup
0618af8131 extrakeys: check test pubkey loads
1d3f72d3fa recovery: check exhaustive API results
564afb0b06 ellswift: check test operation results
658c7edc24 tests: check exhaustive ecmult success
d9ac2ee5e6 Add changelog entry for silentpayments module
0fa38f3d29 silentpayments: API docs and internal comment followups
ae075d7cbd hash: Include secp256k1.h directly
dba4d937a9 include: Remove SECP256K1_GNUC_PREREQ macro
09870e9c54 Use __GNUC__ instead of SECP256K1_GNUC_PREREQ

git-subtree-dir: src/secp256k1
git-subtree-split: 687155df6b76f1da1639a6f0923b69e6beaa3a09
2026-08-12 15:58:59 +01:00

466 lines
19 KiB
C

/*************************************************************************
* To the extent possible under law, the author(s) have dedicated all *
* copyright and related and neighboring rights to the software in this *
* file to the public domain worldwide. This software is distributed *
* without any warranty. For the CC0 Public Domain Dedication, see *
* EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 *
*************************************************************************/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <secp256k1_extrakeys.h>
#include <secp256k1_silentpayments.h>
#include "examples_util.h"
#define N_INPUTS 2
#define N_OUTPUTS 3
/* Static data for Bob and Carol's Silent Payments addresses */
static unsigned char smallest_outpoint[36] = {
0x16, 0x9e, 0x1e, 0x83, 0xe9, 0x30, 0x85, 0x33, 0x91,
0xbc, 0x6f, 0x35, 0xf6, 0x05, 0xc6, 0x75, 0x4c, 0xfe,
0xad, 0x57, 0xcf, 0x83, 0x87, 0x63, 0x9d, 0x3b, 0x40,
0x96, 0xc5, 0x4f, 0x18, 0xf4, 0x00, 0x00, 0x00, 0x00
};
static unsigned char bob_scan_key[32] = {
0xa8, 0x90, 0x54, 0xc9, 0x5b, 0xe3, 0xc3, 0x01,
0x56, 0x65, 0x74, 0xf2, 0xaa, 0x93, 0xad, 0xe0,
0x51, 0x85, 0x09, 0x03, 0xa6, 0x9c, 0xbd, 0xd1,
0xd4, 0x7e, 0xae, 0x26, 0x3d, 0x7b, 0xc0, 0x31
};
static unsigned char bob_spend_key[32] = {
0x9d, 0x6a, 0xd8, 0x55, 0xce, 0x34, 0x17, 0xef,
0x84, 0xe8, 0x36, 0x89, 0x2e, 0x5a, 0x56, 0x39,
0x2b, 0xfb, 0xa0, 0x5f, 0xa5, 0xd9, 0x7c, 0xce,
0xa3, 0x0e, 0x26, 0x6f, 0x54, 0x0e, 0x08, 0xb3
};
static unsigned char bob_scan_and_spend_pubkeys[2][33] = {
{
0x02, 0x15, 0x40, 0xae, 0xa8, 0x97, 0x54, 0x7a,
0xd4, 0x39, 0xb4, 0xe0, 0xf6, 0x09, 0xe5, 0xf0,
0xfa, 0x63, 0xde, 0x89, 0xab, 0x11, 0xed, 0xe3,
0x1e, 0x8c, 0xde, 0x4b, 0xe2, 0x19, 0x42, 0x5f, 0x23
},
{
0x02, 0x5c, 0xc9, 0x85, 0x6d, 0x6f, 0x83, 0x75,
0x35, 0x0e, 0x12, 0x39, 0x78, 0xda, 0xac, 0x20,
0x0c, 0x26, 0x0c, 0xb5, 0xb5, 0xae, 0x83, 0x10,
0x6c, 0xab, 0x90, 0x48, 0x4d, 0xcd, 0x8f, 0xcf, 0x36
}
};
static unsigned char carol_scan_key[32] = {
0x04, 0xb2, 0xa4, 0x11, 0x63, 0x5c, 0x09, 0x77,
0x59, 0xaa, 0xcd, 0x0f, 0x00, 0x5a, 0x4c, 0x82,
0xc8, 0xc9, 0x28, 0x62, 0xc6, 0xfc, 0x28, 0x4b,
0x80, 0xb8, 0xef, 0xeb, 0xc2, 0x0c, 0x3d, 0x17
};
static unsigned char carol_address[2][33] = {
{
0x03, 0xbb, 0xc6, 0x3f, 0x12, 0x74, 0x5d, 0x3b,
0x9e, 0x9d, 0x24, 0xc6, 0xcd, 0x7a, 0x1e, 0xfe,
0xba, 0xd0, 0xa7, 0xf4, 0x69, 0x23, 0x2f, 0xbe,
0xcf, 0x31, 0xfb, 0xa7, 0xb4, 0xf7, 0xdd, 0xed, 0xa8
},
{
0x03, 0x81, 0xeb, 0x9a, 0x9a, 0x9e, 0xc7, 0x39,
0xd5, 0x27, 0xc1, 0x63, 0x1b, 0x31, 0xb4, 0x21,
0x56, 0x6f, 0x5c, 0x2a, 0x47, 0xb4, 0xab, 0x5b,
0x1f, 0x6a, 0x68, 0x6d, 0xfb, 0x68, 0xea, 0xb7, 0x16
}
};
/** Labels
*
* The structs and callback function are implemented here as a demonstration
* of how the label lookup callback is meant to query a label cache and return
* the label tweak when a match is found. This is for demonstration purposes
* only and not optimized. In production, it is expected that the
* caller will be using a much more performant data structure for storing and
* querying labels, like e.g. a hash table with O(1) lookup cost.
*
* Recipients not using labels can ignore these steps and simply pass `NULL`
* for the label_lookup and label_context arguments:
*
* secp256k1_silentpayments_recipient_scan_outputs(..., NULL, NULL);
*/
struct label_cache_entry {
unsigned char label[33];
unsigned char label_tweak[32];
};
struct labels_cache {
size_t entries_used;
struct label_cache_entry entries[5];
};
const unsigned char* label_lookup(
const unsigned char* label33,
const void* cache_ptr
) {
const struct labels_cache* cache = (const struct labels_cache*)cache_ptr;
size_t i;
for (i = 0; i < cache->entries_used; i++) {
if (memcmp(cache->entries[i].label, label33, 33) == 0) {
return cache->entries[i].label_tweak;
}
}
return NULL;
}
int main(void) {
unsigned char randomize[32];
unsigned char serialized_xonly[32];
secp256k1_xonly_pubkey tx_inputs[N_INPUTS];
const secp256k1_xonly_pubkey *tx_input_ptrs[N_INPUTS];
secp256k1_xonly_pubkey tx_outputs[N_OUTPUTS];
secp256k1_xonly_pubkey *tx_output_ptrs[N_OUTPUTS];
secp256k1_silentpayments_found_output found_outputs[N_OUTPUTS];
secp256k1_silentpayments_found_output *found_output_ptrs[N_OUTPUTS];
secp256k1_silentpayments_prevouts_summary prevouts_summary;
secp256k1_pubkey unlabeled_spend_pubkey;
struct labels_cache bob_labels_cache;
unsigned char bob_address[2][33];
int ret;
size_t i;
uint32_t n_found_outputs;
/* Before we can call actual API functions, we need to create a "context" */
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
if (!fill_random(randomize, sizeof(randomize))) {
printf("Failed to generate randomness\n");
return EXIT_FAILURE;
}
/* Randomizing the context is recommended to protect against side-channel
* leakage. See `secp256k1_context_randomize` in secp256k1.h for more
* information about it. This should never fail. */
ret = secp256k1_context_randomize(ctx, randomize);
assert(ret);
/* Set up the pointer arrays. These will be used for sending and scanning. */
for (i = 0; i < N_INPUTS; i++) {
tx_input_ptrs[i] = &tx_inputs[i];
}
for (i = 0; i < N_OUTPUTS; i++) {
tx_output_ptrs[i] = &tx_outputs[i];
found_output_ptrs[i] = &found_outputs[i];
}
/*** Create a labeled Silent Payments address (Bob) ***/
{
size_t len = 33;
secp256k1_silentpayments_label label;
secp256k1_pubkey labeled_spend_pubkey;
/* Per BIP-352, the label integer value m = 0 is reserved for the receiver's own
* change, so only values m >= 1 must be used for publishing addresses. */
uint32_t m = 1;
/* Load Bob's spend public key */
ret = secp256k1_ec_pubkey_parse(ctx,
&unlabeled_spend_pubkey,
bob_scan_and_spend_pubkeys[1],
33
);
assert(ret);
/* Create a (label_tweak, label) pair and add them to the labels cache.
*
* Bob MUST keep track of all of the labels he has used by adding them
* to the labels cache. Otherwise, he will not find outputs sent to his
* labeled addresses when scanning. */
ret = secp256k1_silentpayments_recipient_label_create(ctx,
&label,
bob_labels_cache.entries[0].label_tweak,
bob_scan_key,
m
);
if (!ret) {
printf("Something went wrong, event with negligible probability happened.\n");
return EXIT_FAILURE;
}
ret = secp256k1_silentpayments_recipient_label_serialize(ctx, bob_labels_cache.entries[0].label, &label);
assert(ret);
bob_labels_cache.entries_used = 1;
/* Now that the labels cache has been updated, Bob creates his labeled
* Silent Payments address and publishes it.
*/
ret = secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(ctx, &labeled_spend_pubkey, &unlabeled_spend_pubkey, &label);
if (!ret) {
printf("Something went wrong, event with negligible probability happened.\n");
return EXIT_FAILURE;
}
memcpy(bob_address[0], bob_scan_and_spend_pubkeys[0], 33);
ret = secp256k1_ec_pubkey_serialize(ctx,
bob_address[1],
&len,
&labeled_spend_pubkey,
SECP256K1_EC_COMPRESSED
);
assert(ret);
}
/*** Sending (Alice) ***/
{
secp256k1_keypair sender_keypairs[N_INPUTS];
const secp256k1_keypair *sender_keypair_ptrs[N_INPUTS];
secp256k1_silentpayments_recipient recipients[N_OUTPUTS];
const secp256k1_silentpayments_recipient *recipient_ptrs[N_OUTPUTS];
/* 2D array for holding multiple public key pairs. The second index, i.e., [2],
* is to represent the spend and scan public keys. */
unsigned char (*sp_addresses[N_OUTPUTS])[2][33];
unsigned char seckey[32];
/*** Generate secret keys for the sender ***
*
* In this example, only taproot inputs are used but the function can be
* called with a mix of taproot seckeys and plain seckeys. Taproot
* seckeys are passed as keypairs to allow the sending function to check
* if the secret keys need to be negated without needing to do an
* expensive pubkey generation. This is not needed for plain seckeys
* since there is no need for negation.
*
* The public key from each input keypair is saved in the `tx_inputs`
* array. This array will be used later in the example to represent the
* public keys the recipient will extract from the transaction inputs.
*
* If the secret key is zero or out of range (bigger than secp256k1's
* order), fail. Note that the probability of this happening is
* negligible. */
for (i = 0; i < N_INPUTS; i++) {
if (!fill_random(seckey, sizeof(seckey))) {
printf("Failed to generate randomness\n");
return EXIT_FAILURE;
}
/* Try to create a keypair with a valid context, it should only fail
* if the secret key is zero or out of range. */
if (secp256k1_keypair_create(ctx, &sender_keypairs[i], seckey)) {
sender_keypair_ptrs[i] = &sender_keypairs[i];
ret = secp256k1_keypair_xonly_pub(
ctx,
&tx_inputs[i],
NULL,
&sender_keypairs[i]
);
assert(ret);
} else {
printf("Failed to create keypair\n");
return EXIT_FAILURE;
}
}
/*** Create the recipient objects ***/
/* Alice is sending to Bob and Carol in this transaction:
*
* 1. One output to Bob's labeled address
* 2. Two outputs for Carol
*
* To create multiple outputs for Carol, Alice simply passes Carol's
* Silent Payments address multiple times.
*/
sp_addresses[0] = &carol_address;
sp_addresses[1] = &bob_address;
sp_addresses[2] = &carol_address;
for (i = 0; i < N_OUTPUTS; i++) {
ret = secp256k1_ec_pubkey_parse(ctx,
&recipients[i].scan_pubkey,
(*(sp_addresses[i]))[0],
33
);
ret &= secp256k1_ec_pubkey_parse(ctx,
&recipients[i].spend_pubkey,
(*(sp_addresses[i]))[1],
33
);
if (!ret) {
printf("Something went wrong, this is not a valid Silent Payments address.\n");
return EXIT_FAILURE;
}
/* Alice creates the recipient objects and adds the index of the
* original ordering (the ordering of the `sp_addresses` array) to
* each object. This index is used to return the generated outputs
* in the original ordering so that Alice can match up the generated
* outputs with the correct amounts.
*/
recipients[i].index = i;
recipient_ptrs[i] = &recipients[i];
}
ret = secp256k1_silentpayments_sender_create_outputs(ctx,
tx_output_ptrs,
recipient_ptrs, N_OUTPUTS,
smallest_outpoint,
sender_keypair_ptrs, N_INPUTS,
NULL, 0
);
if (!ret) {
printf("Something went wrong, group limit exceeded or input secret keys sum to zero.\n");
return EXIT_FAILURE;
}
printf("Alice created the following outputs for Bob and Carol:\n");
for (i = 0; i < N_OUTPUTS; i++) {
printf(" ");
ret = secp256k1_xonly_pubkey_serialize(ctx,
serialized_xonly,
&tx_outputs[i]
);
assert(ret);
print_hex(serialized_xonly, sizeof(serialized_xonly));
}
/* It's best practice to try to clear secrets from memory after using
* them. This is done because some bugs can allow an attacker to leak
* memory, for example through "out of bounds" array access (see
* Heartbleed), or the OS swapping them to disk. Hence, we overwrite the
* secret key buffer with zeros.
*
* Here we are preventing these writes from being optimized out, as any
* good compiler will remove any writes that aren't used. */
secure_erase(seckey, sizeof(seckey));
for (i = 0; i < N_INPUTS; i++) {
secure_erase(&sender_keypairs[i], sizeof(sender_keypairs[i]));
}
}
/*** Receiving ***/
{
{
/*** Scanning as a full node (Bob) ***
*
* Since Bob has access to the full transaction, scanning is simple:
*
* 1. Collect the relevant prevouts from the transaction and call
* `secp256k1_silentpayments_recipient_prevouts_summary_create`
* 2. Call `secp256k1_silentpayments_recipient_scan_outputs`
*/
ret = secp256k1_silentpayments_recipient_prevouts_summary_create(ctx,
&prevouts_summary,
smallest_outpoint,
tx_input_ptrs, N_INPUTS,
NULL, 0
);
if (!ret) {
/* We need to always check that the prevouts data object is valid
* before proceeding.
*/
printf("This transaction is not valid for Silent Payments, skipping.\n");
return EXIT_SUCCESS;
}
/* Scan the transaction */
n_found_outputs = 0;
ret = secp256k1_silentpayments_recipient_scan_outputs(ctx,
found_output_ptrs, &n_found_outputs,
(const secp256k1_xonly_pubkey**)tx_output_ptrs, N_OUTPUTS,
bob_scan_key,
&prevouts_summary,
&unlabeled_spend_pubkey,
label_lookup, &bob_labels_cache /* NULL, NULL for no labels */
);
if (!ret) {
printf("This transaction is not valid for Silent Payments, skipping.\n");
return EXIT_SUCCESS;
}
if (n_found_outputs > 0) {
secp256k1_keypair kp;
secp256k1_xonly_pubkey xonly_output;
unsigned char full_seckey[32];
printf("\n");
printf("Bob found the following outputs: \n");
for (i = 0; i < n_found_outputs; i++) {
printf(" ");
ret = secp256k1_xonly_pubkey_serialize(ctx,
serialized_xonly,
&found_outputs[i].output
);
assert(ret);
print_hex(serialized_xonly, sizeof(serialized_xonly));
/* Verify that this output is spendable by Bob by reconstructing the full
* secret key for the xonly output.
*
* This is done by adding the tweak from the transaction to Bob's spend key.
* If the output was sent to a labeled address, the label tweak has already
* been added to the tweak in `secp256k1_silentpayments_found_output`.
*
* To verify that we are able to sign for this output, it is sufficient to
* check that the public key generated from `full_seckey` matches the output
* in the transaction. For a full example on signing for a taproot ouput,
* see `examples/schnorr.c`.
*/
memcpy(&full_seckey, &bob_spend_key, 32);
ret = secp256k1_ec_seckey_tweak_add(ctx, full_seckey, found_outputs[i].tweak);
ret &= secp256k1_keypair_create(ctx, &kp, full_seckey);
ret &= secp256k1_keypair_xonly_pub(
ctx,
&xonly_output,
NULL,
&kp
);
/* We assert here because the only way the seckey_tweak_add operation can fail
* is if the tweak is the negation of Bob's spend key.
*
* We also assert that the generated public key matches the transaction output,
* as it should be impossible for a mismatch at this point considering the
* scanning function completed without errors and indicated found outputs.
*/
assert(ret);
assert(secp256k1_xonly_pubkey_cmp(ctx, &xonly_output, &found_outputs[i].output) == 0);
secure_erase(full_seckey, sizeof(full_seckey));
}
} else {
printf("Bob did not find any outputs in this transaction.\n");
}
}
{
/*** Scanning as a full node (Carol) ***/
/* TODO: switch this part to light client scanning once it is supported */
/* Load Carol's spend public key. */
ret = secp256k1_ec_pubkey_parse(ctx,
&unlabeled_spend_pubkey,
carol_address[1],
33
);
assert(ret);
n_found_outputs = 0;
ret = secp256k1_silentpayments_recipient_scan_outputs(ctx,
found_output_ptrs, &n_found_outputs,
(const secp256k1_xonly_pubkey**)tx_output_ptrs, N_OUTPUTS,
carol_scan_key,
&prevouts_summary,
&unlabeled_spend_pubkey,
NULL, NULL /* NULL, NULL for no labels */
);
if (!ret) {
printf("This transaction is not valid for Silent Payments, skipping.\n");
return EXIT_SUCCESS;
}
if (n_found_outputs > 0) {
/* Carol would spend these outputs the same as Bob, by tweaking her
* spend key with the tweak corresponding to the found output. See above
* for an example for Bob's outputs. */
printf("\n");
printf("Carol found the following outputs: \n");
for (i = 0; i < n_found_outputs; i++) {
printf(" ");
ret = secp256k1_xonly_pubkey_serialize(ctx,
serialized_xonly,
&found_outputs[i].output
);
assert(ret);
print_hex(serialized_xonly, sizeof(serialized_xonly));
}
} else {
printf("Carol did not find any outputs in this transaction.\n");
}
}
}
/* This will clear everything from the context and free the memory */
secp256k1_context_destroy(ctx);
return EXIT_SUCCESS;
}