Skip to content
133 changes: 108 additions & 25 deletions examples/silentpayments.c
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ int main(void) {

/*** Receiving ***/
{
unsigned char light_client_data33[33];
{
/*** Scanning as a full node (Bob) ***
*
Expand All @@ -332,6 +333,16 @@ int main(void) {
* 1. Collect the relevant prevouts from the transaction and call
* `secp256k1_silentpayments_recipient_prevouts_summary_create`
* 2. Call `secp256k1_silentpayments_recipient_scan_outputs`
* Bob collects the prevouts data from the transaction inputs and
* creates a `secp256k1_silentpayments_prevouts_summary` object. He uses
* this for his own scanning and also serializes the `prevouts_summary`
* object to send to light clients. We will use this later for
* Carol, who is scanning as a light client. Note, anyone can create
* and provide these `prevouts_summary` objects, i.e. you don't need to be
* a Silent Payments wallet, just someone interested in providing this
* data to light clients, e.g. a wallet service provider. In our
* example, Bob is scanning for himself but also sharing this data
* with light clients.
*/
ret = secp256k1_silentpayments_recipient_prevouts_summary_create(ctx,
&prevouts_summary,
Expand All @@ -346,6 +357,12 @@ int main(void) {
printf("This transaction is not valid for Silent Payments, skipping.\n");
return EXIT_SUCCESS;
}
/* Serialize the prevouts_summary data object for later use. */
ret = secp256k1_silentpayments_recipient_prevouts_summary_serialize(ctx,
light_client_data33, 33,
&prevouts_summary
);
assert(ret);

/* Scan the transaction */
n_found_outputs = 0;
Expand Down Expand Up @@ -414,8 +431,23 @@ int main(void) {
}
}
{
/*** Scanning as a full node (Carol) ***/
/* TODO: switch this part to light client scanning once it is supported */
/*** Scanning as a light client (Carol) ***
* Being a light client, Carol likely does not have access to the
* transaction inputs and prevout information, so she uses the
* `prevouts_summary` object created by Bob's full node earlier. This
* serialized `prevouts_summary` object contains everything she needs for
* generating the shared secret, i.e., `input_hash * prevouts_pubkey_sum`.
*
* Additionally, she likely does not have access to the transaction outputs.
* This means she will need to first generate outputs, check if they exist
* in the UTXO set (e.g. BIP158 or some other means of querying), and
* proceed to download the full transaction if there is a match.
*
* Once she has the full transaction, she scans with
* `secp256k1_silentpayments_recipient_scan_outputs` to find all of
* the outputs and extract the tweaks needed for spending later.
*/
int found;

/* Load Carol's spend public key. */
ret = secp256k1_ec_pubkey_parse(ctx,
Expand All @@ -424,37 +456,88 @@ int main(void) {
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,
/* Parse the serialized prevouts_summary object. */
ret = secp256k1_silentpayments_recipient_prevouts_summary_parse(ctx,
&prevouts_summary,
&unlabeled_spend_pubkey,
NULL, NULL /* NULL, NULL for no labels */
light_client_data33, 33
);
if (!ret) {
printf("\n");
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));

{
secp256k1_xonly_pubkey *potential_outputs_ptrs[1];
secp256k1_xonly_pubkey potential_outputs[1];
const secp256k1_pubkey *spend_pubkeys_ptrs[1];

potential_outputs_ptrs[0] = &potential_outputs[0];
spend_pubkeys_ptrs[0] = &unlabeled_spend_pubkey;
ret = secp256k1_silentpayments_recipient_create_output_pubkeys(ctx,
potential_outputs_ptrs,
carol_scan_key,
&prevouts_summary,
spend_pubkeys_ptrs, 1
);
if (!ret) {
printf("This transaction is not valid for Silent Payments, skipping.\n");
return EXIT_SUCCESS;
}
/* At this point, we check that the UTXO exists with a light
* client protocol. For this example, we'll just iterate
* through the list of transaction outputs.
*
* If we generate an output and it does not exist in the
* UTXO set, we are done scanning this transaction. It is
* sufficient to stop after the first match, since we will be
* doing a full scan of the transaction once we have access to
* all of the outputs.
*/
found = 0;
for (i = 0; i < N_OUTPUTS; i++) {
if (secp256k1_xonly_pubkey_cmp(ctx, &potential_outputs[0], &tx_outputs[i]) == 0) {
found = 1;
break;
}
}
}

if (found) {
/* Carol now needs to request the full transaction to do a complete scan. */
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");
}
} else {
printf("Carol did not find any outputs in this transaction.\n");
printf("Carol did not find any outputs in this transaction (light client prefilter).\n");
}
}
}
Expand Down
85 changes: 84 additions & 1 deletion include/secp256k1_silentpayments.h
Original file line number Diff line number Diff line change
Expand Up @@ -235,12 +235,58 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipien
* guaranteed to be portable between different platforms or versions. It is
* however guaranteed to be 101 bytes in size, and can be safely copied/moved.
* This structure does not contain secret data. It can be created with
* `secp256k1_silentpayments_recipient_prevouts_summary_create`.
* `secp256k1_silentpayments_recipient_prevouts_summary_create`. Serialized and
* parsed with `secp256k1_silentpayments_recipient_prevouts_summary_serialize`
* and `secp256k1_silentpayments_recipient_prevouts_summary_parse`.
*/
typedef struct secp256k1_silentpayments_prevouts_summary {
unsigned char data[101];
} secp256k1_silentpayments_prevouts_summary;

/** Parse a 33-byte or 65-byte sequence into a silentpayments_prevouts_summary object.
*
* Both sizes are accepted; see `secp256k1_silentpayments_recipient_prevouts_summary_serialize`
* for the size-vs-parse-speed tradeoff.
*
* Returns: 1 when the prevouts_summary could be parsed, 0 otherwise.
*
* Args: ctx: pointer to a context object.
* Out: prevouts_summary: pointer to a silentpayments_prevouts_summary object.
* In: input: pointer to a serialized silentpayments_prevouts_summary.
* inputlen: size of the serialized input. Must be either 33 or 65.
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_prevouts_summary_parse(
const secp256k1_context *ctx,
secp256k1_silentpayments_prevouts_summary *prevouts_summary,
const unsigned char *input,
size_t inputlen
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);

/** Serialize a silentpayments_prevouts_summary object into a 33-byte or 65-byte sequence.
*
* The 33-byte variant saves bandwidth and is preferred in general. The 65-byte variant
* is slightly faster to parse, at the cost of about double the size.
*
* Serializing a prevouts_summary object created with `_recipient_prevouts_summary_create`
* will result in an EC multiplication. This allows for a more compact serialization, but
* also means a serialized prevouts_summary will not parse back to the same
* prevouts_summary object (due to the EC multiplication).
*
* Returns: 1 always.
*
* Args: ctx: pointer to a context object
* Out: output: pointer to a byte array to store the serialized
* `silentpayments_prevouts_summary`.
* In: outputlen: size of the byte array. Must be either 33 or 65.
* prevouts_summary: pointer to an initialized `silentpayments_prevouts_summary` object
*/
SECP256K1_API int secp256k1_silentpayments_recipient_prevouts_summary_serialize(
const secp256k1_context *ctx,
unsigned char *output,
size_t outputlen,
const secp256k1_silentpayments_prevouts_summary *prevouts_summary
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4);

/** Compute Silent Payments prevouts summary from prevout public keys and transaction
* inputs.
*
Expand Down Expand Up @@ -389,6 +435,43 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipien
const void *label_context
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(8);

/** Create Silent Payments output public keys.
*
* Given a scan key, a prevouts_summary object, and an array of recipient
* spend public keys, create the Silent Payments output public keys.
*
* This function is used by the recipient when scanning for outputs without
* access to the transaction outputs (e.g., using BIP158 block filters). It will
* create the first output (i.e. with k=0) for each of the spend public keys provided.
* It is the caller's responsibility to determine if the created outputs exist.
*
* If a match is found, the caller must download the full transaction and call
* `secp256k1_silentpayments_recipient_scan_outputs` to check if there are additional
* outputs for the recipient and get the full output tweak needed to spend the outputs.
*
* Returns: 1 if output creation was successful, 0 otherwise.
*
* Args: ctx: pointer to a context object
* Out: outputs_xonly: pointer to an array of pointers to the resulting
* output x-only public keys. The outputs_xonly array
* MUST have the same size as the spend_pubkeys array.
* In: scan_key32: pointer to the recipient's 32 byte scan key.
* The scan key is valid if it passes secp256k1_ec_seckey_verify.
* prevouts_summary: pointer to the transaction prevouts summary data
* (see `secp256k1_silentpayments_recipient_prevouts_summary_create`).
* spend_pubkeys: pointer to an array of pointers to the recipient's spend public keys
* (labeled or unlabeled).
* n_spend_pubkeys: the size of the spend_pubkeys array.
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_create_output_pubkeys(
const secp256k1_context *ctx,
secp256k1_xonly_pubkey **outputs_xonly,
const unsigned char *scan_key32,
const secp256k1_silentpayments_prevouts_summary *prevouts_summary,
const secp256k1_pubkey * const *spend_pubkeys,
size_t n_spend_pubkeys
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);

#ifdef __cplusplus
}
#endif
Expand Down
10 changes: 6 additions & 4 deletions src/bench.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ static void help(const char *executable_path, int default_iters) {
#endif

#ifdef ENABLE_MODULE_SILENTPAYMENTS
printf(" silentpayments : all Silent payments benchmarks (scan_nomatch, scan_worstcase)\n");
printf(" silentpayments : all Silent payments benchmarks (create_pubkeys, scan_nomatch, scan_worstcase)\n");
printf(" silentpayments_create_pubkeys : Silent payments k=0 output pubkey creation (light client filter scenario)\n");
printf(" silentpayments_scan_nomatch : Silent payments scanning common case (no match)\n");
printf(" silentpayments_scan_worstcase : Silent payments scanning worst case (block-sized tx, all match)\n");
#endif
Expand Down Expand Up @@ -197,7 +198,8 @@ int main(int argc, char** argv) {
"ecdsa_recover", "schnorrsig", "schnorrsig_verify", "schnorrsig_sign", "ec",
"keygen", "ec_keygen", "ellswift", "encode", "ellswift_encode", "decode",
"ellswift_decode", "ellswift_keygen", "ellswift_ecdh", "silentpayments",
"silentpayments_scan_nomatch", "silentpayments_scan_worstcase"};
"silentpayments_create_pubkeys", "silentpayments_scan_nomatch",
"silentpayments_scan_worstcase"};
int invalid_args = have_invalid_args(argc, argv, valid_args, ARRAY_SIZE(valid_args));

int default_iters = 20000;
Expand Down Expand Up @@ -256,8 +258,8 @@ int main(int argc, char** argv) {
#endif

#ifndef ENABLE_MODULE_SILENTPAYMENTS
if (have_flag(argc, argv, "silentpayments") || have_flag(argc, argv, "silentpayments_scan_nomatch") ||
have_flag(argc, argv, "silentpayments_scan_worstcase")) {
if (have_flag(argc, argv, "silentpayments") || have_flag(argc, argv, "silentpayments_create_pubkeys") ||
have_flag(argc, argv, "silentpayments_scan_nomatch") || have_flag(argc, argv, "silentpayments_scan_worstcase")) {
fprintf(stderr, "./bench: silentpayments module not enabled.\n");
fprintf(stderr, "See README.md for configuration instructions.\n\n");
return EXIT_FAILURE;
Expand Down
30 changes: 30 additions & 0 deletions src/ctime_tests.c
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) {
const secp256k1_xonly_pubkey *sp_xonly_pubkeys[1];
secp256k1_pubkey sp_pubkey;
const secp256k1_pubkey *sp_pubkeys[1];
secp256k1_silentpayments_prevouts_summary parsed_prevouts_summary;
unsigned char prevouts_summary_ser33[33];
secp256k1_xonly_pubkey lc_outputs[2];
secp256k1_xonly_pubkey *lc_outputs_ptrs[2];
const secp256k1_pubkey *lc_spend_pubkeys[2];
#endif

for (i = 0; i < 32; i++) {
Expand Down Expand Up @@ -346,6 +351,31 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) {
*/
CHECK(secp256k1_silentpayments_recipient_scan_outputs(ctx, found_outputs_ptrs, &n_found_outputs, tx_outputs, 1, key, &prevouts_summary, &recipient.spend_pubkey, NULL, NULL));

/* Test the light client scanning API.
*
* The prevouts_summary (de)serialization functions only ever touch public data (the
* summed prevout public keys and the input hash), so there is no secret input to mark
* as such. They are still run here, since the parsed object is what a light client
* feeds into _recipient_create_output_pubkeys below.
*/
CHECK(secp256k1_silentpayments_recipient_prevouts_summary_serialize(ctx, prevouts_summary_ser33, sizeof(prevouts_summary_ser33), &prevouts_summary) == 1);
CHECK(secp256k1_silentpayments_recipient_prevouts_summary_parse(ctx, &parsed_prevouts_summary, prevouts_summary_ser33, sizeof(prevouts_summary_ser33)) == 1);

lc_outputs_ptrs[0] = &lc_outputs[0];
lc_outputs_ptrs[1] = &lc_outputs[1];
lc_spend_pubkeys[0] = &recipient.spend_pubkey;
lc_spend_pubkeys[1] = &sp_pubkey;
/* `key` is still secret at this point and is used as the scan key. Both prevouts_summary
* variants are checked, since the scan key is treated differently in each: for an object
* created from transaction data (combined = 0) it is multiplied with the input hash,
* whereas for one parsed from a serialization (combined = 1) it is used as is. More than
* one spend public key is passed to also cover the loop over the spend public keys.
*/
ret = secp256k1_silentpayments_recipient_create_output_pubkeys(ctx, lc_outputs_ptrs, key, &prevouts_summary, lc_spend_pubkeys, 2);
CHECK(ret == 1);
ret = secp256k1_silentpayments_recipient_create_output_pubkeys(ctx, lc_outputs_ptrs, key, &parsed_prevouts_summary, lc_spend_pubkeys, 2);
CHECK(ret == 1);

#endif
}

Expand Down
Loading
Loading