Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add silentpayments (BIP352) module #1471

Closed
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
silentpayments: add private tweak data creation routine
theStack committed Feb 10, 2024
commit 81d13038d51fbab400cc247512fbaf939db08d49
39 changes: 38 additions & 1 deletion include/secp256k1_silentpayments.h
Original file line number Diff line number Diff line change
@@ -28,7 +28,44 @@ extern "C" {
* operations.
*/

/* TODO: add function API for sender side. */
/** Create Silent Payment tweak data from input private keys.
*
* Given a list of n private keys a_1...a_n (one for each silent payment
* eligible input to spend) and a serialized outpoint_smallest, compute
* the corresponding input private keys tweak data:
*
* a_sum = a_1 + a_2 + ... + a_n
* input_hash = hash(outpoint_smallest || (a_sum * G))
*
* If necessary, the private keys are negated to enforce the right y-parity.
* For that reason, the private keys have to be passed in via two different parameter
* pairs, depending on whether they were used for creating taproot outputs or not.
* The resulting data is needed to create a shared secret for the sender side.
*
* Returns: 1 if shared secret creation was successful. 0 if an error occured.
* Args: ctx: pointer to a context object
* Out: a_sum: pointer to the resulting 32-byte private key sum
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in "silentpayments: add private tweak data creation routine" (81d1303):

Thinking about this more, is there a usecase where the sender might need a_sum and not need the shared secret? If not, seems like it would be better to have the API accept private keys as an input and return the final shared secret. Otherwise, callers need to be really careful with a_sum as it could be a single private key.

What about renaming this function to something like secp256k1_silentpayments_create_shared_secret_from_private_data and it returns unsigned char shared_secret33. This way, we can pass pointers to the privkey data and get back the shared secret without needing to worry about handling a potentially dangerous a_sum in between.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about this more, is there a usecase where the sender might need a_sum and not need the shared secret? If not, seems like it would be better to have the API accept private keys as an input and return the final shared secret. Otherwise, callers need to be really careful with a_sum as it could be a single private key.

The only a_sum/input_hash reuse scenario I could think of is one where the sender wants to create a transaction with more than one silent payments recipient (and with that, different scan pubkeys, i.e. the same recipient with different labels but same scan pubkey wouldn't count as "different" in that sense). Given that sending is an infrequent scenario and calculating a_sum and input_hash are comparably cheap operations, that's probably not a big deal though. The receiving/scanning part is potentially more of a problem, see comment below.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great point. This could be a fairly common usecase (e.g. an exchange processing withdrawals to n silent payment addresses), but it is still a bounded problem in that you can't send to more than N silent payment addresses in a single transaction, where N is the number of outputs you can fit in a single block.

I'm leaning toward making this so the caller doesn't need to handle intermediary secret data. The other option is we expose both routines in the API?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great point. This could be a fairly common usecase (e.g. an exchange processing withdrawals to n silent payment addresses), but it is still a bounded problem in that you can't send to more than N silent payment addresses in a single transaction, where N is the number of outputs you can fit in a single block.

I'm leaning toward making this so the caller doesn't need to handle intermediary secret data. The other option is we expose both routines in the API?

Oops, I missed this comment last week. I agree that a secp256k1_silentpayments_create_shared_secret_from_private_data routine would be useful for the reasons you mentioned (i.e. avoid having to handle intermediate secret key data), will add one in a bit. I tend to think that exposing both routines still makes sense, in cases where performance for sending to multiple receivers is really a concern? Will keep the current one around for discussion, if it's not considered useful it can still be removed later.

* input_hash: pointer to the resulting 32-byte input hash
* In: plain_seckeys: pointer to an array of pointers to 32-byte private keys
* of non-taproot inputs (can be NULL if no private keys of
* non-taproot inputs are used)
* n_plain_seckeys: the number of sender's non-taproot input private keys
* taproot_seckeys: pointer to an array of pointers to 32-byte private keys
* of taproot inputs (can be NULL if no private keys of
* taproot inputs are used)
* n_taproot_seckeys: the number of sender's taproot input private keys
* outpoint_smallest36: serialized smallest outpoint
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_create_private_tweak_data(
const secp256k1_context *ctx,
unsigned char *a_sum,
unsigned char *input_hash,
const unsigned char * const *plain_seckeys,
size_t n_plain_seckeys,
const unsigned char * const *taproot_seckeys,
size_t n_taproot_seckeys,
const unsigned char *outpoint_smallest36
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(8);

/* TODO: add function API for receiver side. */

89 changes: 88 additions & 1 deletion src/modules/silentpayments/main_impl.h
Original file line number Diff line number Diff line change
@@ -9,7 +9,94 @@
#include "../../../include/secp256k1.h"
#include "../../../include/secp256k1_silentpayments.h"

/* TODO: implement functions for sender side. */
/** Set hash state to the BIP340 tagged hash midstate for "BIP0352/Inputs". */
static void secp256k1_silentpayments_sha256_init_inputs(secp256k1_sha256* hash) {
secp256k1_sha256_initialize(hash);
hash->s[0] = 0xd4143ffcul;
hash->s[1] = 0x012ea4b5ul;
hash->s[2] = 0x36e21c8ful;
hash->s[3] = 0xf7ec7b54ul;
hash->s[4] = 0x4dd4e2acul;
hash->s[5] = 0x9bcaa0a4ul;
hash->s[6] = 0xe244899bul;
hash->s[7] = 0xcd06903eul;

hash->bytes = 64;
}

static void secp256k1_silentpayments_calculate_input_hash(unsigned char *input_hash, const unsigned char *outpoint_smallest36, secp256k1_ge *pubkey_sum) {
secp256k1_sha256 hash;
unsigned char pubkey_sum_ser[33];
size_t ser_size;
int ser_ret;

secp256k1_silentpayments_sha256_init_inputs(&hash);
secp256k1_sha256_write(&hash, outpoint_smallest36, 36);
ser_ret = secp256k1_eckey_pubkey_serialize(pubkey_sum, pubkey_sum_ser, &ser_size, 1);
VERIFY_CHECK(ser_ret && ser_size == sizeof(pubkey_sum_ser));
(void)ser_ret;
secp256k1_sha256_write(&hash, pubkey_sum_ser, sizeof(pubkey_sum_ser));
secp256k1_sha256_finalize(&hash, input_hash);
}

int secp256k1_silentpayments_create_private_tweak_data(const secp256k1_context *ctx, unsigned char *a_sum, unsigned char *input_hash, const unsigned char * const *plain_seckeys, size_t n_plain_seckeys, const unsigned char * const *taproot_seckeys, size_t n_taproot_seckeys, const unsigned char *outpoint_smallest36) {
size_t i;
secp256k1_scalar a_sum_scalar, addend;
secp256k1_ge A_sum_ge;
secp256k1_gej A_sum_gej;

/* Sanity check inputs. */
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(a_sum != NULL);
memset(a_sum, 0, 32);
ARG_CHECK(input_hash != NULL);
memset(input_hash, 0, 32);
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
ARG_CHECK(plain_seckeys == NULL || n_plain_seckeys >= 1);
ARG_CHECK(taproot_seckeys == NULL || n_taproot_seckeys >= 1);
ARG_CHECK((plain_seckeys != NULL) || (taproot_seckeys != NULL));
ARG_CHECK((n_plain_seckeys + n_taproot_seckeys) >= 1);
ARG_CHECK(outpoint_smallest36 != NULL);

/* Compute input private keys sum: a_sum = a_1 + a_2 + ... + a_n */
a_sum_scalar = secp256k1_scalar_zero;
for (i = 0; i < n_plain_seckeys; i++) {
int ret = secp256k1_scalar_set_b32_seckey(&addend, plain_seckeys[i]);
VERIFY_CHECK(ret);
(void)ret;

secp256k1_scalar_add(&a_sum_scalar, &a_sum_scalar, &addend);
VERIFY_CHECK(!secp256k1_scalar_is_zero(&a_sum_scalar));
}
/* private keys used for taproot outputs have to be negated if they resulted in an odd point */
for (i = 0; i < n_taproot_seckeys; i++) {
secp256k1_ge addend_point;
int ret = secp256k1_ec_pubkey_create_helper(&ctx->ecmult_gen_ctx, &addend, &addend_point, taproot_seckeys[i]);
VERIFY_CHECK(ret);
(void)ret;
/* declassify addend_point to allow using it as a branch point (this is fine because addend_point is not a secret) */
secp256k1_declassify(ctx, &addend_point, sizeof(addend_point));
secp256k1_fe_normalize_var(&addend_point.y);
if (secp256k1_fe_is_odd(&addend_point.y)) {
secp256k1_scalar_negate(&addend, &addend);
}

secp256k1_scalar_add(&a_sum_scalar, &a_sum_scalar, &addend);
VERIFY_CHECK(!secp256k1_scalar_is_zero(&a_sum_scalar));
}
if (secp256k1_scalar_is_zero(&a_sum_scalar)) {
/* TODO: do we need a special error return code for this case? */
return 0;
}
secp256k1_scalar_get_b32(a_sum, &a_sum_scalar);

/* Compute input_hash = hash(outpoint_L || (a_sum * G)) */
secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &A_sum_gej, &a_sum_scalar);
secp256k1_ge_set_gej(&A_sum_ge, &A_sum_gej);
secp256k1_silentpayments_calculate_input_hash(input_hash, outpoint_smallest36, &A_sum_ge);

return 1;
}

/* TODO: implement functions for receiver side. */