Skip to content
54 changes: 54 additions & 0 deletions include/secp256k1_silentpayments.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,60 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_sender_c
size_t n_plain_seckeys
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5);

/** Create Silent Payment label tweak and label.
*
* Given a recipient's 32 byte scan key and a label integer m, calculate the
* corresponding label tweak and label:
*
* label_tweak = hash(scan_key || m)
* label = label_tweak * G
*
* Returns: 1 if label tweak and label creation was successful.
* 0 if hash output label_tweak32 is not valid scalar (negligible
* probability per hash evaluation).
*
* Args: ctx: pointer to a context object
* (not secp256k1_context_static)
* Out: label: pointer to the resulting label public key
* label_tweak32: pointer to the 32 byte label tweak
* In: scan_key32: pointer to the recipient's 32 byte scan key
* m: integer for the m-th label (0 is used for change outputs)
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_create_label(
const secp256k1_context *ctx,
secp256k1_pubkey *label,
unsigned char *label_tweak32,
const unsigned char *scan_key32,
const uint32_t m
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: not saying it's wrong, but it seems unnecessary and at least very unusual to use const for parameters that are passed by value (feel free to ignore if that was discussed before, I suspect there was a reason for introducing it as I can't remember seeing it from earlier review rounds)

) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);

/** Create Silent Payment labeled spend public key.
*
* Given a recipient's spend public key and a label, calculate the
* corresponding labeled spend public key:
*
* labeled_spend_pubkey = unlabeled_spend_pubkey + label
*
* The result is used by the recipient to create a Silent Payment address,
* consisting of the serialized and concatenated scan public key and
* (labeled) spend public key.
*
* Returns: 1 if labeled spend public key creation was successful.
* 0 if spend pubkey and label sum to zero (negligible probability for
* labels created according to BIP352).
*
* Args: ctx: pointer to a context object
* Out: labeled_spend_pubkey: pointer to the resulting labeled spend public key
* In: unlabeled_spend_pubkey: pointer to the recipient's unlabeled spend public key
* label: pointer to the recipient's label public key
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(
const secp256k1_context *ctx,
secp256k1_pubkey *labeled_spend_pubkey,
const secp256k1_pubkey *unlabeled_spend_pubkey,
const secp256k1_pubkey *label
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);

#ifdef __cplusplus
}
#endif
Expand Down
70 changes: 70 additions & 0 deletions src/modules/silentpayments/main_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -333,4 +333,74 @@ int secp256k1_silentpayments_sender_create_outputs(
return 1;
}

/** Set hash state to the BIP340 tagged hash midstate for "BIP0352/Label". */
static void secp256k1_silentpayments_sha256_init_label(secp256k1_sha256* hash) {
secp256k1_sha256_initialize(hash);
hash->s[0] = 0x26b95d63ul;
hash->s[1] = 0x8bf1b740ul;
hash->s[2] = 0x10a5986ful;
hash->s[3] = 0x06a387a5ul;
hash->s[4] = 0x2d1c1c30ul;
hash->s[5] = 0xd035951aul;
hash->s[6] = 0x2d7f0f96ul;
hash->s[7] = 0x29e3e0dbul;

hash->bytes = 64;
}

int secp256k1_silentpayments_recipient_create_label(const secp256k1_context *ctx, secp256k1_pubkey *label, unsigned char *label_tweak32, const unsigned char *scan_key32, const uint32_t m) {
secp256k1_sha256 hash;
unsigned char m_serialized[4];

/* Sanity check inputs. */
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(label != NULL);
ARG_CHECK(label_tweak32 != NULL);
ARG_CHECK(scan_key32 != NULL);
Copy link
Contributor

Choose a reason for hiding this comment

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

in _recipient_create_label: I think it would make sense to also error if the passed scan key is invalid, to prevent the user of creating unspendable labels


/* Compute hash(ser_256(b_scan) || ser_32(m)) [sha256 with tag "BIP0352/Label"] */
secp256k1_silentpayments_sha256_init_label(&hash);
secp256k1_sha256_write(&hash, scan_key32, 32);
secp256k1_write_be32(m_serialized, m);
secp256k1_sha256_write(&hash, m_serialized, sizeof(m_serialized));
secp256k1_sha256_finalize(&hash, label_tweak32);

secp256k1_memclear_explicit(m_serialized, sizeof(m_serialized));
secp256k1_sha256_clear(&hash);
return secp256k1_ec_pubkey_create(ctx, label, label_tweak32);
}

int secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(const secp256k1_context *ctx, secp256k1_pubkey *labeled_spend_pubkey, const secp256k1_pubkey *unlabeled_spend_pubkey, const secp256k1_pubkey *label) {
secp256k1_ge labeled_spend_pubkey_ge, label_addend;
secp256k1_gej result_gej;
secp256k1_ge result_ge;
int ret;

/* Sanity check inputs. */
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(labeled_spend_pubkey != NULL);
ARG_CHECK(unlabeled_spend_pubkey != NULL);
ARG_CHECK(label != NULL);

/* Calculate labeled_spend_pubkey = spend_pubkey + label.
* If either the label or spend public key is an invalid public key,
* return early
*/
ret = secp256k1_pubkey_load(ctx, &labeled_spend_pubkey_ge, unlabeled_spend_pubkey);
ret &= secp256k1_pubkey_load(ctx, &label_addend, label);
if (!ret) {
return 0;
}
secp256k1_gej_set_ge(&result_gej, &labeled_spend_pubkey_ge);
secp256k1_gej_add_ge_var(&result_gej, &result_gej, &label_addend, NULL);
if (secp256k1_gej_is_infinity(&result_gej)) {
return 0;
}

secp256k1_ge_set_gej_var(&result_ge, &result_gej);
secp256k1_pubkey_save(labeled_spend_pubkey, &result_ge);

return 1;
}

#endif
47 changes: 47 additions & 0 deletions src/modules/silentpayments/tests_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,56 @@ static void test_send_api(void) {
}
}

static void test_label_api(void) {
secp256k1_pubkey l, s, ls, e; /* label pk, spend pk, labeled spend pk, expected labeled spend pk */
unsigned char lt[32]; /* label tweak */
const unsigned char expected[33] = {
0x03, 0xdc, 0x7f, 0x09, 0x9a, 0xbe, 0x95, 0x7a,
0x58, 0x43, 0xd2, 0xb6, 0xbb, 0x35, 0x79, 0x61,
0x5c, 0x60, 0x36, 0xa4, 0x9b, 0x86, 0xf4, 0xbe,
0x46, 0x38, 0x60, 0x28, 0xa8, 0x1a, 0x77, 0xd4,
0x91
};

/* Create a label and labeled spend public key, verify we get the expected result */
CHECK(secp256k1_ec_pubkey_parse(CTX, &s, BOB_ADDRESS[1], 33));
CHECK(secp256k1_silentpayments_recipient_create_label(CTX, &l, lt, ALICE_SECKEY, 1));
CHECK(secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(CTX, &ls, &s, &l));
CHECK(secp256k1_ec_pubkey_parse(CTX, &e, expected, 33));
CHECK(secp256k1_ec_pubkey_cmp(CTX, &ls, &e) == 0);

/* Check null values are handled */
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_label(CTX, NULL, lt, ALICE_SECKEY, 1));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_label(CTX, &l, NULL, ALICE_SECKEY, 1));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_label(CTX, &l, lt, NULL, 1));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(CTX, NULL, &s, &l));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(CTX, &ls, NULL, &l));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(CTX, &ls, &s, NULL));
/* Check for malformed spend and label public keys, i.e., any single pubkey is malformed or the public
* keys are valid but sum up to zero.
*/
{
secp256k1_pubkey neg_spend_pubkey = s;
CHECK(secp256k1_ec_pubkey_negate(CTX, &neg_spend_pubkey));
CHECK(secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(CTX, &ls, &s, &neg_spend_pubkey) == 0);
/* Also test with a malformed spend public key. */
memset(&s, 0, sizeof(s));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(CTX, &ls, &s, &neg_spend_pubkey));
/* Reset s back to a valid public key for the next test. */
CHECK(secp256k1_ec_pubkey_parse(CTX, &s, BOB_ADDRESS[1], 33));
memset(&l, 0, sizeof(l));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(CTX, &ls, &s, &l));
/* Reset l back to a valid public key for the next test */
CHECK(secp256k1_silentpayments_recipient_create_label(CTX, &l, lt, ALICE_SECKEY, 1));
memset(&s, 0, sizeof(s));
CHECK_ILLEGAL(CTX, secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(CTX, &ls, &s, &l));
}
}

void run_silentpayments_tests(void) {
test_recipient_sort();
test_send_api();
test_label_api();
}

#endif