Merge ElementsProject/secp256k1-zkp#151: MuSig: Add Minimal Compatibility with BIP32 Tweaking
8088eddc534cbbb89dd5f892828c4013416c4f2b musig: add test vector for ordinary (non xonly) tweaking (Elliott Jin) 57a17929fc0056efb5436a6001597d656591e1ad musig: add ordinary and xonly tweaking to the example (Jonas Nick) 37107361a0ff3b8764903e3b384cfc12ed484e7a musig: allow ordinary, non-xonly tweaking (Jonas Nick) c519b468791670654f8b66368a675655cd337ae8 musig: add pubkey_get to obtain a full pubkey from a keyagg_cache (Jonas Nick) Pull request description: In short, `musig_pubkey_tweak_add` now allows for xonly _and_ "ordinary" tweaking. Also, in order to allow using `ec_pubkey_tweak_add` on the non-xonly aggregate public key, there's a new function `musig_pubkey_get` that allows obtaining it from the `keyagg_cache`. One alternative would be that instead of adding `musig_pubkey_get`, we could change `pubkey_agg` to output an ordinary (non-xonly) pubkey. Then users of the API who do not need ordinary (BIP32) tweaking would be forced to call `xonly_pubkey_from_pubkey`. And we'd probably want to change the spec. And it would be a bit weird to output a pubkey that can't be directly schnorrsig_verify'd. Based on #131 ACKs for top commit: robot-dreams: ACK 8088eddc534cbbb89dd5f892828c4013416c4f2b based on https://github.com/ElementsProject/secp256k1-zkp/pull/151#issuecomment-1005198409 and the following `range-diff`: Tree-SHA512: a4a0100f0470c870f88a8da27dbcc4684fcc2caabb368d4340e962e08d5ee04634e6289bafa3448dbfd0b5793a3e70de5bd6ddca7a619cc3220ff762d518a8fe
This commit is contained in:
commit
772df3694e
@ -52,14 +52,51 @@ int create_keypair(const secp256k1_context* ctx, struct signer_secrets *signer_s
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Tweak the pubkey corresponding to the provided keyagg cache, update the cache
|
||||
* and return the tweaked aggregate pk. */
|
||||
int tweak(const secp256k1_context* ctx, secp256k1_xonly_pubkey *agg_pk, secp256k1_musig_keyagg_cache *cache) {
|
||||
secp256k1_pubkey output_pk;
|
||||
unsigned char ordinary_tweak[32] = "this could be a BIP32 tweak....";
|
||||
unsigned char xonly_tweak[32] = "this could be a taproot tweak..";
|
||||
|
||||
|
||||
/* Ordinary tweaking which, for example, allows deriving multiple child
|
||||
* public keys from a single aggregate key using BIP32 */
|
||||
if (!secp256k1_musig_pubkey_ec_tweak_add(ctx, NULL, cache, ordinary_tweak)) {
|
||||
return 0;
|
||||
}
|
||||
/* Note that we did not provided an output_pk argument, because the
|
||||
* resulting pk is also saved in the cache and so if one is just interested
|
||||
* in signing the output_pk argument is unnecessary. On the other hand, if
|
||||
* one is not interested in signing, the same output_pk can be obtained by
|
||||
* calling `secp256k1_musig_pubkey_get` right after key aggregation to get
|
||||
* the full pubkey and then call `secp256k1_ec_pubkey_tweak_add`. */
|
||||
|
||||
/* Xonly tweaking which, for example, allows creating taproot commitments */
|
||||
if (!secp256k1_musig_pubkey_xonly_tweak_add(ctx, &output_pk, cache, xonly_tweak)) {
|
||||
return 0;
|
||||
}
|
||||
/* Note that if we wouldn't care about signing, we can arrive at the same
|
||||
* output_pk by providing the untweaked public key to
|
||||
* `secp256k1_xonly_pubkey_tweak_add` (after converting it to an xonly pubkey
|
||||
* if necessary with `secp256k1_xonly_pubkey_from_pubkey`). */
|
||||
|
||||
/* Now we convert the output_pk to an xonly pubkey to allow to later verify
|
||||
* the Schnorr signature against it. For this purpose we can ignore the
|
||||
* `pk_parity` output argument; we would need it if we would have to open
|
||||
* the taproot commitment. */
|
||||
if (!secp256k1_xonly_pubkey_from_pubkey(ctx, agg_pk, NULL, &output_pk)) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Sign a message hash with the given key pairs and store the result in sig */
|
||||
int sign(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signer, const unsigned char* msg32, unsigned char *sig64) {
|
||||
int sign(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, struct signer *signer, const secp256k1_musig_keyagg_cache *cache, const unsigned char *msg32, unsigned char *sig64) {
|
||||
int i;
|
||||
const secp256k1_xonly_pubkey *pubkeys[N_SIGNERS];
|
||||
const secp256k1_musig_pubnonce *pubnonces[N_SIGNERS];
|
||||
const secp256k1_musig_partial_sig *partial_sigs[N_SIGNERS];
|
||||
/* The same for all signers */
|
||||
secp256k1_musig_keyagg_cache cache;
|
||||
secp256k1_musig_session session;
|
||||
|
||||
for (i = 0; i < N_SIGNERS; i++) {
|
||||
@ -86,7 +123,6 @@ int sign(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, st
|
||||
if (!secp256k1_musig_nonce_gen(ctx, &signer_secrets[i].secnonce, &signer[i].pubnonce, session_id, seckey, msg32, NULL, NULL)) {
|
||||
return 0;
|
||||
}
|
||||
pubkeys[i] = &signer[i].pubkey;
|
||||
pubnonces[i] = &signer[i].pubnonce;
|
||||
}
|
||||
/* Communication round 1: A production system would exchange public nonces
|
||||
@ -94,21 +130,18 @@ int sign(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, st
|
||||
for (i = 0; i < N_SIGNERS; i++) {
|
||||
secp256k1_musig_aggnonce agg_pubnonce;
|
||||
|
||||
/* Create aggregate pubkey, aggregate nonce and initialize signer data */
|
||||
if (!secp256k1_musig_pubkey_agg(ctx, NULL, NULL, &cache, pubkeys, N_SIGNERS)) {
|
||||
return 0;
|
||||
}
|
||||
/* Create aggregate nonce and initialize the session */
|
||||
if (!secp256k1_musig_nonce_agg(ctx, &agg_pubnonce, pubnonces, N_SIGNERS)) {
|
||||
return 0;
|
||||
}
|
||||
if (!secp256k1_musig_nonce_process(ctx, &session, &agg_pubnonce, msg32, &cache, NULL)) {
|
||||
if (!secp256k1_musig_nonce_process(ctx, &session, &agg_pubnonce, msg32, cache, NULL)) {
|
||||
return 0;
|
||||
}
|
||||
/* partial_sign will clear the secnonce by setting it to 0. That's because
|
||||
* you must _never_ reuse the secnonce (or use the same session_id to
|
||||
* create a secnonce). If you do, you effectively reuse the nonce and
|
||||
* leak the secret key. */
|
||||
if (!secp256k1_musig_partial_sign(ctx, &signer[i].partial_sig, &signer_secrets[i].secnonce, &signer_secrets[i].keypair, &cache, &session)) {
|
||||
if (!secp256k1_musig_partial_sign(ctx, &signer[i].partial_sig, &signer_secrets[i].secnonce, &signer_secrets[i].keypair, cache, &session)) {
|
||||
return 0;
|
||||
}
|
||||
partial_sigs[i] = &signer[i].partial_sig;
|
||||
@ -127,7 +160,7 @@ int sign(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, st
|
||||
* fine to first verify the aggregate sig, and only verify the individual
|
||||
* sigs if it does not work.
|
||||
*/
|
||||
if (!secp256k1_musig_partial_sig_verify(ctx, &signer[i].partial_sig, &signer[i].pubnonce, &signer[i].pubkey, &cache, &session)) {
|
||||
if (!secp256k1_musig_partial_sig_verify(ctx, &signer[i].partial_sig, &signer[i].pubnonce, &signer[i].pubkey, cache, &session)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@ -141,6 +174,7 @@ int sign(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, st
|
||||
struct signer signers[N_SIGNERS];
|
||||
const secp256k1_xonly_pubkey *pubkeys_ptr[N_SIGNERS];
|
||||
secp256k1_xonly_pubkey agg_pk;
|
||||
secp256k1_musig_keyagg_cache cache;
|
||||
unsigned char msg[32] = "this_could_be_the_hash_of_a_msg!";
|
||||
unsigned char sig[64];
|
||||
|
||||
@ -156,13 +190,21 @@ int sign(const secp256k1_context* ctx, struct signer_secrets *signer_secrets, st
|
||||
}
|
||||
printf("ok\n");
|
||||
printf("Combining public keys...");
|
||||
if (!secp256k1_musig_pubkey_agg(ctx, NULL, &agg_pk, NULL, pubkeys_ptr, N_SIGNERS)) {
|
||||
/* If you just want to aggregate and not sign the cache can be NULL */
|
||||
if (!secp256k1_musig_pubkey_agg(ctx, NULL, &agg_pk, &cache, pubkeys_ptr, N_SIGNERS)) {
|
||||
printf("FAILED\n");
|
||||
return 1;
|
||||
}
|
||||
printf("ok\n");
|
||||
printf("Tweaking................");
|
||||
/* Optionally tweak the aggregate key */
|
||||
if (!tweak(ctx, &agg_pk, &cache)) {
|
||||
printf("FAILED\n");
|
||||
return 1;
|
||||
}
|
||||
printf("ok\n");
|
||||
printf("Signing message.........");
|
||||
if (!sign(ctx, signer_secrets, signers, msg, sig)) {
|
||||
if (!sign(ctx, signer_secrets, signers, &cache, msg, sig)) {
|
||||
printf("FAILED\n");
|
||||
return 1;
|
||||
}
|
||||
|
@ -223,16 +223,77 @@ SECP256K1_API int secp256k1_musig_pubkey_agg(
|
||||
size_t n_pubkeys
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(5);
|
||||
|
||||
/** Tweak an x-only public key in a given keyagg_cache by adding
|
||||
* the generator multiplied with `tweak32` to it.
|
||||
/** Obtain the aggregate public key from a keyagg_cache.
|
||||
*
|
||||
* This is only useful if you need the non-xonly public key, in particular for
|
||||
* ordinary (non-xonly) tweaking or batch-verifying multiple key aggregations
|
||||
* (not implemented).
|
||||
*
|
||||
* Returns: 0 if the arguments are invalid, 1 otherwise
|
||||
* Args: ctx: pointer to a context object
|
||||
* Out: agg_pk: the MuSig-aggregated public key.
|
||||
* In: keyagg_cache: pointer to a `musig_keyagg_cache` struct initialized by
|
||||
* `musig_pubkey_agg`
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_musig_pubkey_get(
|
||||
const secp256k1_context* ctx,
|
||||
secp256k1_pubkey *agg_pk,
|
||||
secp256k1_musig_keyagg_cache *keyagg_cache
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Apply ordinary "EC" tweaking to a public key in a given keyagg_cache by
|
||||
* adding the generator multiplied with `tweak32` to it. This is useful for
|
||||
* deriving child keys from an aggregate public key via BIP32.
|
||||
*
|
||||
* The tweaking method is the same as `secp256k1_ec_pubkey_tweak_add`. So after
|
||||
* the following pseudocode buf and buf2 have identical contents (absent
|
||||
* earlier failures).
|
||||
*
|
||||
* secp256k1_musig_pubkey_agg(..., keyagg_cache, pubkeys, ...)
|
||||
* secp256k1_musig_pubkey_get(..., agg_pk, keyagg_cache)
|
||||
* secp256k1_musig_pubkey_ec_tweak_add(..., output_pk, tweak32, keyagg_cache)
|
||||
* secp256k1_ec_pubkey_serialize(..., buf, output_pk)
|
||||
* secp256k1_ec_pubkey_tweak_add(..., agg_pk, tweak32)
|
||||
* secp256k1_ec_pubkey_serialize(..., buf2, agg_pk)
|
||||
*
|
||||
* This function is required if you want to _sign_ for a tweaked aggregate key.
|
||||
* On the other hand, if you are only computing a public key, but not intending
|
||||
* to create a signature for it, you can just use
|
||||
* `secp256k1_ec_pubkey_tweak_add`.
|
||||
*
|
||||
* Returns: 0 if the arguments are invalid or the resulting public key would be
|
||||
* invalid (only when the tweak is the negation of the corresponding
|
||||
* secret key). 1 otherwise.
|
||||
* Args: ctx: pointer to a context object initialized for verification
|
||||
* Out: output_pubkey: pointer to a public key to store the result. Will be set
|
||||
* to an invalid value if this function returns 0. If you
|
||||
* do not need it, this arg can be NULL.
|
||||
* In/Out: keyagg_cache: pointer to a `musig_keyagg_cache` struct initialized by
|
||||
* `musig_pubkey_agg`
|
||||
* In: tweak32: pointer to a 32-byte tweak. If the tweak is invalid
|
||||
* according to `secp256k1_ec_seckey_verify`, this function
|
||||
* returns 0. For uniformly random 32-byte arrays the
|
||||
* chance of being invalid is negligible (around 1 in
|
||||
* 2^128).
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_musig_pubkey_ec_tweak_add(
|
||||
const secp256k1_context* ctx,
|
||||
secp256k1_pubkey *output_pubkey,
|
||||
secp256k1_musig_keyagg_cache *keyagg_cache,
|
||||
const unsigned char *tweak32
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
|
||||
|
||||
/** Apply x-only tweaking to a public key in a given keyagg_cache by adding the
|
||||
* generator multiplied with `tweak32` to it. This is useful for creating
|
||||
* Taproot outputs.
|
||||
*
|
||||
* The tweaking method is the same as `secp256k1_xonly_pubkey_tweak_add`. So in
|
||||
* the following pseudocode xonly_pubkey_tweak_add_check (absent earlier
|
||||
* failures) returns 1.
|
||||
*
|
||||
* secp256k1_musig_pubkey_agg(..., agg_pk, keyagg_cache, pubkeys, ...)
|
||||
* secp256k1_musig_pubkey_tweak_add(..., output_pubkey, tweak32, keyagg_cache)
|
||||
* secp256k1_xonly_pubkey_serialize(..., buf, output_pubkey)
|
||||
* secp256k1_musig_pubkey_xonly_tweak_add(..., output_pk, tweak32, keyagg_cache)
|
||||
* secp256k1_xonly_pubkey_serialize(..., buf, output_pk)
|
||||
* secp256k1_xonly_pubkey_tweak_add_check(..., buf, ..., agg_pk, tweak32)
|
||||
*
|
||||
* This function is required if you want to _sign_ for a tweaked aggregate key.
|
||||
@ -255,7 +316,7 @@ SECP256K1_API int secp256k1_musig_pubkey_agg(
|
||||
* chance of being invalid is negligible (around 1 in
|
||||
* 2^128).
|
||||
*/
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_musig_pubkey_tweak_add(
|
||||
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_musig_pubkey_xonly_tweak_add(
|
||||
const secp256k1_context* ctx,
|
||||
secp256k1_pubkey *output_pubkey,
|
||||
secp256k1_musig_keyagg_cache *keyagg_cache,
|
||||
|
@ -244,7 +244,21 @@ int secp256k1_musig_pubkey_agg(const secp256k1_context* ctx, secp256k1_scratch_s
|
||||
return 1;
|
||||
}
|
||||
|
||||
int secp256k1_musig_pubkey_tweak_add(const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_musig_keyagg_cache *keyagg_cache, const unsigned char *tweak32) {
|
||||
int secp256k1_musig_pubkey_get(const secp256k1_context* ctx, secp256k1_pubkey *agg_pk, secp256k1_musig_keyagg_cache *keyagg_cache) {
|
||||
secp256k1_keyagg_cache_internal cache_i;
|
||||
VERIFY_CHECK(ctx != NULL);
|
||||
ARG_CHECK(agg_pk != NULL);
|
||||
memset(agg_pk, 0, sizeof(*agg_pk));
|
||||
ARG_CHECK(keyagg_cache != NULL);
|
||||
|
||||
if(!secp256k1_keyagg_cache_load(ctx, &cache_i, keyagg_cache)) {
|
||||
return 0;
|
||||
}
|
||||
secp256k1_pubkey_save(agg_pk, &cache_i.pk);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int secp256k1_musig_pubkey_tweak_add_internal(const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_musig_keyagg_cache *keyagg_cache, const unsigned char *tweak32, int xonly) {
|
||||
secp256k1_keyagg_cache_internal cache_i;
|
||||
int overflow = 0;
|
||||
secp256k1_scalar tweak;
|
||||
@ -263,7 +277,7 @@ int secp256k1_musig_pubkey_tweak_add(const secp256k1_context* ctx, secp256k1_pub
|
||||
if (overflow) {
|
||||
return 0;
|
||||
}
|
||||
if (secp256k1_extrakeys_ge_even_y(&cache_i.pk)) {
|
||||
if (xonly && secp256k1_extrakeys_ge_even_y(&cache_i.pk)) {
|
||||
cache_i.internal_key_parity ^= 1;
|
||||
secp256k1_scalar_negate(&cache_i.tweak, &cache_i.tweak);
|
||||
}
|
||||
@ -280,4 +294,12 @@ int secp256k1_musig_pubkey_tweak_add(const secp256k1_context* ctx, secp256k1_pub
|
||||
return 1;
|
||||
}
|
||||
|
||||
int secp256k1_musig_pubkey_ec_tweak_add(const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_musig_keyagg_cache *keyagg_cache, const unsigned char *tweak32) {
|
||||
return secp256k1_musig_pubkey_tweak_add_internal(ctx, output_pubkey, keyagg_cache, tweak32, 0);
|
||||
}
|
||||
|
||||
int secp256k1_musig_pubkey_xonly_tweak_add(const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_musig_keyagg_cache *keyagg_cache, const unsigned char *tweak32) {
|
||||
return secp256k1_musig_pubkey_tweak_add_internal(ctx, output_pubkey, keyagg_cache, tweak32, 1);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -538,56 +538,65 @@ int secp256k1_musig_partial_sign(const secp256k1_context* ctx, secp256k1_musig_p
|
||||
* The following public keys arise as intermediate steps:
|
||||
* - P[i] is the i-th public key with corresponding secret key x[i]
|
||||
* P[i] := x[i]*G
|
||||
* - P_agg is the aggregate public key
|
||||
* P_agg := mu[0]*|P[0]| + ... + mu[n-1]*|P[n-1]|
|
||||
* - P_tweak[i] is the tweaked public key after the i-th tweaking operation
|
||||
* P_tweak[0] := P_agg
|
||||
* P_tweak[i] := |P_tweak[i-1]| + t[i]*G for i = 1, ..., m
|
||||
* - P_agg[0] is the aggregate public key
|
||||
* P_agg[0] := mu[0]*|P[0]| + ... + mu[n-1]*|P[n-1]|
|
||||
* - P_agg[i] for 1 <= i <= m is the tweaked public key after the i-th
|
||||
* tweaking operation. There are two types of tweaking: x-only and ordinary
|
||||
* "EC" tweaking. We define a boolean predicate xonly(i) that is true if
|
||||
* the i-th tweaking operation is x-only tweaking and false otherwise
|
||||
* (ordinary tweaking).
|
||||
* Let
|
||||
* P_agg[i] := f(i, P_agg[i-1]) + t[i]*G for i = 1, ..., m
|
||||
* where f(i, X) := |X| if xonly(i)
|
||||
* f(i, X) := X otherwise
|
||||
*
|
||||
* Note that our goal is to produce a partial signature corresponding to
|
||||
* the final public key after m tweaking operations P_final = |P_tweak[m]|.
|
||||
* the final public key after m tweaking operations P_final = |P_agg[m]|.
|
||||
*
|
||||
* Define d[i], d_agg, and d_tweak[i] so that:
|
||||
* Define d[i] for 0 <= i <= n-1 and d_agg[i] for 0 <= i <= m so that:
|
||||
* - |P[i]| = d[i]*P[i]
|
||||
* - |P_agg| = d_agg*P_agg
|
||||
* - |P_tweak[i]| = d_tweak[i]*P_tweak[i]
|
||||
* - f(i+1, P_agg[i]) = d_agg[i]*P_agg[i] for 0 <= i <= m - 1
|
||||
* - |P_agg[m]| = d_agg[m]*P_agg[m]
|
||||
*
|
||||
* In other words, d[i] = 1 if P[i] has even y coordinate, -1 otherwise;
|
||||
* similarly for d_agg and d_tweak[i].
|
||||
* In other words, d[i] = 1 if P[i] has even y coordinate, -1 otherwise.
|
||||
* For 0 <= i <= m-1, d_agg[i] is -1 if and only if xonly(i+1) is true and
|
||||
* P_agg[i] has an odd Y coordinate.
|
||||
*
|
||||
* The (xonly) final public key is P_final = |P_tweak[m]|
|
||||
* = d_tweak[m]*P_tweak[m]
|
||||
* = d_tweak[m]*(|P_tweak[m-1]| + t[m]*G)
|
||||
* = d_tweak[m]*(d_tweak[m-1]*(|P_tweak[m-2]| + t[m-1]*G) + t[m]*G)
|
||||
* = d_tweak[m]*...*d_tweak[1]*|P_agg| + (d_tweak[m]*t[m]+...+*d_tweak[1]*t[1])*G.
|
||||
* The (x-only) final public key is P_final = |P_agg[m]|
|
||||
* = d_agg[m]*P_agg[m]
|
||||
* = d_agg[m]*(f(m, P_agg[m-1]) + t[m]*G)
|
||||
* = d_agg[m]*(d_agg[m-1]*(f(m-1, P_agg[m-2]) + t[m-1]*G) + t[m]*G)
|
||||
* = d_agg[m]*...*d_agg[0]*P_agg[0] + (d_agg[m]*t[m]+...+*d_agg[1]*t[1])*G.
|
||||
* To simplify the equation let us define
|
||||
* t := d_tweak[m]*t[m]+...+*d_tweak[1]*t[1]
|
||||
* d_tweak := d_tweak[m]*...*d_tweak[1].
|
||||
* d_agg := d_agg[m]*...*d_agg[0].
|
||||
* t := d_agg[m]*t[m]+...+*d_agg[1]*t[1] if m > 0, otherwise t := 0
|
||||
* Then we have
|
||||
* P_final - t*G
|
||||
* = d_tweak*|P_agg|
|
||||
* = d_tweak*d_agg*P_agg
|
||||
* = d_tweak*d_agg*(mu[0]*|P[0]| + ... + mu[n-1]*|P[n-1]|)
|
||||
* = d_tweak*d_agg*(d[0]*mu[0]*P[0] + ... + d[n-1]*mu[n-1]*P[n-1])
|
||||
* = sum((d_tweak*d_agg*d[i])*mu[i]*x[i])*G.
|
||||
* = d_agg*P_agg[0]
|
||||
* = d_agg*(mu[0]*|P[0]| + ... + mu[n-1]*|P[n-1]|)
|
||||
* = d_agg*(d[0]*mu[0]*P[0] + ... + d[n-1]*mu[n-1]*P[n-1])
|
||||
* = sum((d_agg*d[i])*mu[i]*x[i])*G.
|
||||
*
|
||||
* Thus whether signer i should use the negated x[i] depends on the product
|
||||
* d_tweak[m]*...*d_tweak[1]*d_agg*d[i]. In other words, negate if and only
|
||||
* d_agg[m]*...*d_agg[1]*d_agg[0]*d[i]. In other words, negate if and only
|
||||
* if the following holds:
|
||||
* (P[i] has odd y) XOR (P_agg has odd y)
|
||||
* XOR (P_tweak[1] has odd y) XOR ... XOR (P_tweak[m] has odd y)
|
||||
* (P[i] has odd y) XOR (xonly(1) and P_agg[0] has odd y)
|
||||
* XOR (xonly(2) and P_agg[1] has odd y)
|
||||
* XOR ... XOR (xonly(m) and P_agg[m-1] has odd y)
|
||||
* XOR (P_agg[m] has odd y)
|
||||
*
|
||||
* Let us now look at how the terms in the equation correspond to the if
|
||||
* condition below for some values of m:
|
||||
* m = 0: P_i has odd y = secp256k1_fe_is_odd(&pk.y)
|
||||
* P_agg has odd y = secp256k1_fe_is_odd(&cache_i.pk.y)
|
||||
* m = 0: P[i] has odd y = secp256k1_fe_is_odd(&pk.y)
|
||||
* P_agg[0] has odd y = secp256k1_fe_is_odd(&cache_i.pk.y)
|
||||
* cache_i.internal_key_parity = 0
|
||||
* m = 1: P_i has odd y = secp256k1_fe_is_odd(&pk.y)
|
||||
* P_agg has odd y = cache_i.internal_key_parity
|
||||
* P_tweak[1] has odd y = secp256k1_fe_is_odd(&cache_i.pk.y)
|
||||
* m = 2: P_i has odd y = secp256k1_fe_is_odd(&pk.y)
|
||||
* P_agg has odd y XOR P_tweak[1] has odd y = cache_i.internal_key_parity
|
||||
* P_tweak[2] has odd y = secp256k1_fe_is_odd(&cache_i.pk.y)
|
||||
* m = 1: P[i] has odd y = secp256k1_fe_is_odd(&pk.y)
|
||||
* xonly(1) and P_agg[0] has odd y = cache_i.internal_key_parity
|
||||
* P_agg[1] has odd y = secp256k1_fe_is_odd(&cache_i.pk.y)
|
||||
* m = 2: P[i] has odd y = secp256k1_fe_is_odd(&pk.y)
|
||||
* (xonly(1) and P_agg[0] has odd y)
|
||||
XOR (xonly(2) and P_agg[1] has odd y) = cache_i.internal_key_parity
|
||||
* P_agg[2] has odd y = secp256k1_fe_is_odd(&cache_i.pk.y)
|
||||
* etc.
|
||||
*/
|
||||
if ((secp256k1_fe_is_odd(&pk.y)
|
||||
@ -674,7 +683,7 @@ int secp256k1_musig_partial_sig_verify(const secp256k1_context* ctx, const secp2
|
||||
/* When producing a partial signature, signer i uses a possibly
|
||||
* negated secret key:
|
||||
*
|
||||
* sk[i] = (d_tweak*d_agg*d[i])*x[i]
|
||||
* sk[i] = (d_agg*d[i])*x[i]
|
||||
*
|
||||
* to ensure that the aggregate signature will correspond to
|
||||
* an aggregate public key with even Y coordinate (see the
|
||||
@ -698,14 +707,14 @@ int secp256k1_musig_partial_sig_verify(const secp256k1_context* ctx, const secp2
|
||||
* The verifier doesn't have access to sk[i]*G, but can construct
|
||||
* it using the xonly public key |P[i]| as follows:
|
||||
*
|
||||
* sk[i]*G = d_tweak*d_agg*d[i]*x[i]*G
|
||||
* = d_tweak*d_agg*d[i]*P[i]
|
||||
* = d_tweak*d_agg*|P[i]|
|
||||
* sk[i]*G = d_agg*d[i]*x[i]*G
|
||||
* = d_agg*d[i]*P[i]
|
||||
* = d_agg*|P[i]|
|
||||
*
|
||||
* The if condition below is true whenever d_tweak*d_agg is
|
||||
* negative (again, see the explanation in musig_partial_sign). In
|
||||
* this case, the verifier negates e which will have the same end
|
||||
* result as negating |P[i]|, since they are multiplied later anyway.
|
||||
* The if condition below is true whenever d_agg is negative (again, see the
|
||||
* explanation in musig_partial_sign). In this case, the verifier negates e
|
||||
* which will have the same end result as negating |P[i]|, since they are
|
||||
* multiplied later anyway.
|
||||
*/
|
||||
if (secp256k1_fe_is_odd(&cache_i.pk.y)
|
||||
!= cache_i.internal_key_parity) {
|
||||
|
@ -140,6 +140,7 @@ void musig_api_tests(secp256k1_scratch_space *scratch) {
|
||||
unsigned char aggnonce_ser[66];
|
||||
unsigned char msg[32];
|
||||
secp256k1_xonly_pubkey agg_pk;
|
||||
secp256k1_pubkey full_agg_pk;
|
||||
secp256k1_musig_keyagg_cache keyagg_cache;
|
||||
secp256k1_musig_keyagg_cache invalid_keyagg_cache;
|
||||
secp256k1_musig_session session;
|
||||
@ -243,39 +244,53 @@ void musig_api_tests(secp256k1_scratch_space *scratch) {
|
||||
CHECK(secp256k1_musig_pubkey_agg(sign, scratch, &agg_pk, &keyagg_cache, pk_ptr, 2) == 1);
|
||||
CHECK(secp256k1_musig_pubkey_agg(vrfy, scratch, &agg_pk, &keyagg_cache, pk_ptr, 2) == 1);
|
||||
|
||||
/** Tweaking **/
|
||||
/* pubkey_get */
|
||||
ecount = 0;
|
||||
CHECK(secp256k1_musig_pubkey_get(none, &full_agg_pk, &keyagg_cache) == 1);
|
||||
CHECK(secp256k1_musig_pubkey_get(none, NULL, &keyagg_cache) == 0);
|
||||
CHECK(ecount == 1);
|
||||
CHECK(secp256k1_musig_pubkey_get(none, &full_agg_pk, NULL) == 0);
|
||||
CHECK(ecount == 2);
|
||||
CHECK(secp256k1_memcmp_var(&full_agg_pk, zeros68, sizeof(full_agg_pk)) == 0);
|
||||
|
||||
/** Tweaking **/
|
||||
{
|
||||
int (*tweak_func[2]) (const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, secp256k1_musig_keyagg_cache *keyagg_cache, const unsigned char *tweak32);
|
||||
tweak_func[0] = secp256k1_musig_pubkey_ec_tweak_add;
|
||||
tweak_func[1] = secp256k1_musig_pubkey_xonly_tweak_add;
|
||||
for (i = 0; i < 2; i++) {
|
||||
secp256k1_pubkey tmp_output_pk;
|
||||
secp256k1_musig_keyagg_cache tmp_keyagg_cache = keyagg_cache;
|
||||
CHECK(secp256k1_musig_pubkey_tweak_add(ctx, &tmp_output_pk, &tmp_keyagg_cache, tweak) == 1);
|
||||
ecount = 0;
|
||||
CHECK((*tweak_func[i])(ctx, &tmp_output_pk, &tmp_keyagg_cache, tweak) == 1);
|
||||
/* Reset keyagg_cache */
|
||||
tmp_keyagg_cache = keyagg_cache;
|
||||
CHECK(secp256k1_musig_pubkey_tweak_add(none, &tmp_output_pk, &tmp_keyagg_cache, tweak) == 1);
|
||||
CHECK((*tweak_func[i])(none, &tmp_output_pk, &tmp_keyagg_cache, tweak) == 1);
|
||||
tmp_keyagg_cache = keyagg_cache;
|
||||
CHECK(secp256k1_musig_pubkey_tweak_add(sign, &tmp_output_pk, &tmp_keyagg_cache, tweak) == 1);
|
||||
CHECK((*tweak_func[i])(sign, &tmp_output_pk, &tmp_keyagg_cache, tweak) == 1);
|
||||
tmp_keyagg_cache = keyagg_cache;
|
||||
CHECK(secp256k1_musig_pubkey_tweak_add(vrfy, &tmp_output_pk, &tmp_keyagg_cache, tweak) == 1);
|
||||
CHECK((*tweak_func[i])(vrfy, &tmp_output_pk, &tmp_keyagg_cache, tweak) == 1);
|
||||
tmp_keyagg_cache = keyagg_cache;
|
||||
CHECK(secp256k1_musig_pubkey_tweak_add(vrfy, NULL, &tmp_keyagg_cache, tweak) == 1);
|
||||
CHECK((*tweak_func[i])(vrfy, NULL, &tmp_keyagg_cache, tweak) == 1);
|
||||
tmp_keyagg_cache = keyagg_cache;
|
||||
CHECK(secp256k1_musig_pubkey_tweak_add(vrfy, &tmp_output_pk, NULL, tweak) == 0);
|
||||
CHECK((*tweak_func[i])(vrfy, &tmp_output_pk, NULL, tweak) == 0);
|
||||
CHECK(ecount == 1);
|
||||
CHECK(memcmp_and_randomize(tmp_output_pk.data, zeros68, sizeof(tmp_output_pk.data)) == 0);
|
||||
tmp_keyagg_cache = keyagg_cache;
|
||||
CHECK(secp256k1_musig_pubkey_tweak_add(vrfy, &tmp_output_pk, &tmp_keyagg_cache, NULL) == 0);
|
||||
CHECK((*tweak_func[i])(vrfy, &tmp_output_pk, &tmp_keyagg_cache, NULL) == 0);
|
||||
CHECK(ecount == 2);
|
||||
CHECK(memcmp_and_randomize(tmp_output_pk.data, zeros68, sizeof(tmp_output_pk.data)) == 0);
|
||||
tmp_keyagg_cache = keyagg_cache;
|
||||
CHECK(secp256k1_musig_pubkey_tweak_add(vrfy, &tmp_output_pk, &tmp_keyagg_cache, max64) == 0);
|
||||
CHECK((*tweak_func[i])(vrfy, &tmp_output_pk, &tmp_keyagg_cache, max64) == 0);
|
||||
CHECK(ecount == 2);
|
||||
CHECK(memcmp_and_randomize(tmp_output_pk.data, zeros68, sizeof(tmp_output_pk.data)) == 0);
|
||||
tmp_keyagg_cache = keyagg_cache;
|
||||
/* Uninitialized keyagg_cache */
|
||||
CHECK(secp256k1_musig_pubkey_tweak_add(vrfy, &tmp_output_pk, &invalid_keyagg_cache, tweak) == 0);
|
||||
CHECK((*tweak_func[i])(vrfy, &tmp_output_pk, &invalid_keyagg_cache, tweak) == 0);
|
||||
CHECK(ecount == 3);
|
||||
CHECK(memcmp_and_randomize(tmp_output_pk.data, zeros68, sizeof(tmp_output_pk.data)) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
/** Session creation **/
|
||||
ecount = 0;
|
||||
@ -841,7 +856,8 @@ void musig_tweak_test_helper(const secp256k1_xonly_pubkey* agg_pk, const unsigne
|
||||
CHECK(secp256k1_schnorrsig_verify(ctx, final_sig, msg, sizeof(msg), agg_pk) == 1);
|
||||
}
|
||||
|
||||
/* Create aggregate public key P[0], tweak multiple times and test signing. */
|
||||
/* Create aggregate public key P[0], tweak multiple times (using xonly and
|
||||
* ordinary tweaking) and test signing. */
|
||||
void musig_tweak_test(secp256k1_scratch_space *scratch) {
|
||||
unsigned char sk[2][32];
|
||||
secp256k1_xonly_pubkey pk[2];
|
||||
@ -861,22 +877,35 @@ void musig_tweak_test(secp256k1_scratch_space *scratch) {
|
||||
/* Compute P0 = keyagg(pk0, pk1) and test signing for it */
|
||||
CHECK(secp256k1_musig_pubkey_agg(ctx, scratch, &P_xonly[0], &keyagg_cache, pk_ptr, 2) == 1);
|
||||
musig_tweak_test_helper(&P_xonly[0], sk[0], sk[1], &keyagg_cache);
|
||||
CHECK(secp256k1_musig_pubkey_get(ctx, &P[0], &keyagg_cache));
|
||||
|
||||
/* Compute Pi = |Pj| + tweaki*G where where j = i-1 and try signing for
|
||||
* that key. The function |.| normalizes the point to have an even
|
||||
* X-coordinate. This results in ordinary "xonly-tweaking". */
|
||||
/* Compute Pi = f(Pj) + tweaki*G where where j = i-1 and try signing for
|
||||
* that key. If xonly is set to true, the function f is normalizes the input
|
||||
* point to have an even X-coordinate ("xonly-tweaking").
|
||||
* Otherwise, the function f is the identity function. */
|
||||
for (i = 1; i < N_TWEAKS; i++) {
|
||||
unsigned char tweak[32];
|
||||
int P_parity;
|
||||
unsigned char P_serialized[32];
|
||||
int xonly = secp256k1_testrand_bits(1);
|
||||
|
||||
secp256k1_testrand256(tweak);
|
||||
CHECK(secp256k1_musig_pubkey_tweak_add(ctx, &P[i], &keyagg_cache, tweak) == 1);
|
||||
if (xonly) {
|
||||
CHECK(secp256k1_musig_pubkey_xonly_tweak_add(ctx, &P[i], &keyagg_cache, tweak) == 1);
|
||||
} else {
|
||||
CHECK(secp256k1_musig_pubkey_ec_tweak_add(ctx, &P[i], &keyagg_cache, tweak) == 1);
|
||||
}
|
||||
CHECK(secp256k1_xonly_pubkey_from_pubkey(ctx, &P_xonly[i], &P_parity, &P[i]));
|
||||
CHECK(secp256k1_xonly_pubkey_serialize(ctx, P_serialized, &P_xonly[i]));
|
||||
/* Check that musig_pubkey_tweak_add produces same result as
|
||||
* xonly_pubkey_tweak_add. */
|
||||
* xonly_pubkey_tweak_add or ec_pubkey_tweak_add. */
|
||||
if (xonly) {
|
||||
unsigned char P_serialized[32];
|
||||
CHECK(secp256k1_xonly_pubkey_serialize(ctx, P_serialized, &P_xonly[i]));
|
||||
CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, P_serialized, P_parity, &P_xonly[i-1], tweak) == 1);
|
||||
} else {
|
||||
secp256k1_pubkey tmp_key = P[i-1];
|
||||
CHECK(secp256k1_ec_pubkey_tweak_add(ctx, &tmp_key, tweak));
|
||||
CHECK(memcmp(&tmp_key, &P[i], sizeof(tmp_key)) == 0);
|
||||
}
|
||||
/* Test signing for P[i] */
|
||||
musig_tweak_test_helper(&P_xonly[i], sk[0], sk[1], &keyagg_cache);
|
||||
}
|
||||
@ -1114,7 +1143,7 @@ void musig_test_vectors_noncegen(void) {
|
||||
}
|
||||
}
|
||||
|
||||
void musig_test_vectors_sign_helper(secp256k1_musig_keyagg_cache *keyagg_cache, int *fin_nonce_parity, unsigned char *sig, const unsigned char *secnonce_bytes, const unsigned char *agg_pubnonce_ser, const unsigned char *sk, const unsigned char *msg, const unsigned char *tweak, const secp256k1_pubkey *adaptor, const unsigned char **pk_ser, int signer_pos) {
|
||||
void musig_test_vectors_sign_helper(secp256k1_musig_keyagg_cache *keyagg_cache, int *fin_nonce_parity, unsigned char *sig, const unsigned char *secnonce_bytes, const unsigned char *agg_pubnonce_ser, const unsigned char *sk, const unsigned char *msg, const unsigned char *tweak, int xonly_tweak, const secp256k1_pubkey *adaptor, const unsigned char **pk_ser, int signer_pos) {
|
||||
secp256k1_keypair signer_keypair;
|
||||
secp256k1_musig_secnonce secnonce;
|
||||
secp256k1_xonly_pubkey pk[3];
|
||||
@ -1135,7 +1164,11 @@ void musig_test_vectors_sign_helper(secp256k1_musig_keyagg_cache *keyagg_cache,
|
||||
}
|
||||
CHECK(secp256k1_musig_pubkey_agg(ctx, NULL, &agg_pk, keyagg_cache, pk_ptr, 3) == 1);
|
||||
if (tweak != NULL) {
|
||||
CHECK(secp256k1_musig_pubkey_tweak_add(ctx, NULL, keyagg_cache, tweak) == 1);
|
||||
if (xonly_tweak) {
|
||||
CHECK(secp256k1_musig_pubkey_xonly_tweak_add(ctx, NULL, keyagg_cache, tweak) == 1);
|
||||
} else {
|
||||
CHECK(secp256k1_musig_pubkey_ec_tweak_add(ctx, NULL, keyagg_cache, tweak) == 1);
|
||||
}
|
||||
}
|
||||
memcpy(&secnonce.data[0], secp256k1_musig_secnonce_magic, 4);
|
||||
memcpy(&secnonce.data[4], secnonce_bytes, sizeof(secnonce.data) - 4);
|
||||
@ -1214,7 +1247,7 @@ void musig_test_vectors_sign(void) {
|
||||
0x20, 0xA1, 0x81, 0x85, 0x5F, 0xD8, 0xBD, 0xB7,
|
||||
0xF1, 0x27, 0xBB, 0x12, 0x40, 0x3B, 0x4D, 0x3B,
|
||||
};
|
||||
musig_test_vectors_sign_helper(&keyagg_cache, &fin_nonce_parity, sig, secnonce, agg_pubnonce, sk, msg, NULL, NULL, pk, 0);
|
||||
musig_test_vectors_sign_helper(&keyagg_cache, &fin_nonce_parity, sig, secnonce, agg_pubnonce, sk, msg, NULL, 0, NULL, pk, 0);
|
||||
/* TODO: remove when test vectors are not expected to change anymore */
|
||||
/* int k, l; */
|
||||
/* printf("const unsigned char sig_expected[32] = {\n"); */
|
||||
@ -1243,7 +1276,7 @@ void musig_test_vectors_sign(void) {
|
||||
0x81, 0x38, 0xDA, 0xEC, 0x5C, 0xB2, 0x0A, 0x35,
|
||||
0x7C, 0xEC, 0xA7, 0xC8, 0x42, 0x42, 0x95, 0xEA,
|
||||
};
|
||||
musig_test_vectors_sign_helper(&keyagg_cache, &fin_nonce_parity, sig, secnonce, agg_pubnonce, sk, msg, NULL, NULL, pk, 1);
|
||||
musig_test_vectors_sign_helper(&keyagg_cache, &fin_nonce_parity, sig, secnonce, agg_pubnonce, sk, msg, NULL, 0, NULL, pk, 1);
|
||||
/* Check that the description of the test vector is correct */
|
||||
CHECK(musig_test_pk_parity(&keyagg_cache) == 0);
|
||||
CHECK(musig_test_is_second_pk(&keyagg_cache, sk));
|
||||
@ -1259,7 +1292,7 @@ void musig_test_vectors_sign(void) {
|
||||
0xE6, 0xA7, 0xF7, 0xFB, 0xE1, 0x5C, 0xDC, 0xAF,
|
||||
0xA4, 0xA3, 0xD1, 0xBC, 0xAA, 0xBC, 0x75, 0x17,
|
||||
};
|
||||
musig_test_vectors_sign_helper(&keyagg_cache, &fin_nonce_parity, sig, secnonce, agg_pubnonce, sk, msg, NULL, NULL, pk, 2);
|
||||
musig_test_vectors_sign_helper(&keyagg_cache, &fin_nonce_parity, sig, secnonce, agg_pubnonce, sk, msg, NULL, 0, NULL, pk, 2);
|
||||
/* Check that the description of the test vector is correct */
|
||||
CHECK(musig_test_pk_parity(&keyagg_cache) == 1);
|
||||
CHECK(fin_nonce_parity == 0);
|
||||
@ -1267,7 +1300,7 @@ void musig_test_vectors_sign(void) {
|
||||
CHECK(memcmp(sig, sig_expected, 32) == 0);
|
||||
}
|
||||
{
|
||||
/* This is a test that includes a public key tweak. */
|
||||
/* This is a test that includes an xonly public key tweak. */
|
||||
const unsigned char sig_expected[32] = {
|
||||
0x5E, 0x24, 0xC7, 0x49, 0x6B, 0x56, 0x5D, 0xEB,
|
||||
0xC3, 0xB9, 0x63, 0x9E, 0x6F, 0x13, 0x04, 0xA2,
|
||||
@ -1280,13 +1313,34 @@ void musig_test_vectors_sign(void) {
|
||||
0x96, 0x12, 0xA6, 0x82, 0xA2, 0x5E, 0xBE, 0x79,
|
||||
0x80, 0x2B, 0x26, 0x3C, 0xDF, 0xCD, 0x83, 0xBB,
|
||||
};
|
||||
musig_test_vectors_sign_helper(&keyagg_cache, &fin_nonce_parity, sig, secnonce, agg_pubnonce, sk, msg, tweak, NULL, pk, 2);
|
||||
musig_test_vectors_sign_helper(&keyagg_cache, &fin_nonce_parity, sig, secnonce, agg_pubnonce, sk, msg, tweak, 1, NULL, pk, 2);
|
||||
|
||||
CHECK(musig_test_pk_parity(&keyagg_cache) == 1);
|
||||
CHECK(!musig_test_is_second_pk(&keyagg_cache, sk));
|
||||
CHECK(fin_nonce_parity == 1);
|
||||
CHECK(memcmp(sig, sig_expected, 32) == 0);
|
||||
}
|
||||
{
|
||||
/* This is a test that includes an ordinary public key tweak. */
|
||||
const unsigned char sig_expected[32] = {
|
||||
0x78, 0x40, 0x8D, 0xDC, 0xAB, 0x48, 0x13, 0xD1,
|
||||
0x39, 0x4C, 0x97, 0xD4, 0x93, 0xEF, 0x10, 0x84,
|
||||
0x19, 0x5C, 0x1D, 0x4B, 0x52, 0xE6, 0x3E, 0xCD,
|
||||
0x7B, 0xC5, 0x99, 0x16, 0x44, 0xE4, 0x4D, 0xDD,
|
||||
};
|
||||
const unsigned char tweak[32] = {
|
||||
0xE8, 0xF7, 0x91, 0xFF, 0x92, 0x25, 0xA2, 0xAF,
|
||||
0x01, 0x02, 0xAF, 0xFF, 0x4A, 0x9A, 0x72, 0x3D,
|
||||
0x96, 0x12, 0xA6, 0x82, 0xA2, 0x5E, 0xBE, 0x79,
|
||||
0x80, 0x2B, 0x26, 0x3C, 0xDF, 0xCD, 0x83, 0xBB,
|
||||
};
|
||||
musig_test_vectors_sign_helper(&keyagg_cache, &fin_nonce_parity, sig, secnonce, agg_pubnonce, sk, msg, tweak, 0, NULL, pk, 2);
|
||||
|
||||
CHECK(musig_test_pk_parity(&keyagg_cache) == 1);
|
||||
CHECK(!musig_test_is_second_pk(&keyagg_cache, sk));
|
||||
CHECK(fin_nonce_parity == 0);
|
||||
CHECK(memcmp(sig, sig_expected, 32) == 0);
|
||||
}
|
||||
{
|
||||
/* This is a test that includes an adaptor. */
|
||||
const unsigned char sig_expected[32] = {
|
||||
@ -1303,7 +1357,7 @@ void musig_test_vectors_sign(void) {
|
||||
};
|
||||
secp256k1_pubkey pub_adaptor;
|
||||
CHECK(secp256k1_ec_pubkey_create(ctx, &pub_adaptor, sec_adaptor) == 1);
|
||||
musig_test_vectors_sign_helper(&keyagg_cache, &fin_nonce_parity, sig, secnonce, agg_pubnonce, sk, msg, NULL, &pub_adaptor, pk, 2);
|
||||
musig_test_vectors_sign_helper(&keyagg_cache, &fin_nonce_parity, sig, secnonce, agg_pubnonce, sk, msg, NULL, 0, &pub_adaptor, pk, 2);
|
||||
|
||||
CHECK(musig_test_pk_parity(&keyagg_cache) == 1);
|
||||
CHECK(!musig_test_is_second_pk(&keyagg_cache, sk));
|
||||
|
Loading…
x
Reference in New Issue
Block a user