Add schnorrsig module which implements BIP-340 compliant signatures #558

pull jonasnick wants to merge 15 commits into bitcoin-core:master from jonasnick:schnorrsig changing 20 files +2445 −31
  1. jonasnick commented at 3:33 pm on September 25, 2018: contributor

    This PR implements signing, verification and batch verification as described in BIP-340 in an experimental module named schnorrsig. It includes the test vectors and a benchmarking tool. This PR also adds a module extrakeys that allows BIP-341-style key tweaking.

    (Adding ChaCha20 as a CSPRNG and batch verification was moved to PR #760).

    In order to enable the module run ./configure with --enable-experimental --enable-module-schnorrsig.

    Based on apoelstra’s work.

  2. in src/scalar_8x32_impl.h:801 in ff3a22d147 outdated
    784+        x8 += LE32(seed32[4]);
    785+        x9 += LE32(seed32[5]);
    786+        x10 += LE32(seed32[6]);
    787+        x11 += LE32(seed32[7]);
    788+        x12 += idx;
    789+        x13 += idx >> 32;
    


    DesWurstes commented at 4:13 pm on September 25, 2018:

    Travis with 32-bit size_t is failing because:

    0./src/scalar_8x32_impl.h:760:19: warning: shift count >= width of type [-Wshift-count-overflow]
    1        x13 = idx >> 32;
    2                  ^  ~~
    3./src/scalar_8x32_impl.h:789:20: warning: shift count >= width of type [-Wshift-count-overflow]
    4        x13 += idx >> 32;
    

    I’d replace the size_t above with uint32_t or uint64_t.


    apoelstra commented at 4:24 pm on September 25, 2018:

    Good catch. See also the version of this commit in secp-zkp (where Tim suggests making all the size_ts be uint64_ts.)

    https://github.com/ElementsProject/secp256k1-zkp/pull/23/commits/c3794f902b3265a52e904088f652cf2f1a82107c


    sipa commented at 10:42 pm on November 5, 2018:
    Marking as resolved, as idx has been changed to uint64_t.
  3. jonasnick force-pushed on Sep 25, 2018
  4. jonasnick force-pushed on Sep 25, 2018
  5. jonasnick commented at 4:54 pm on September 25, 2018: contributor
    Replaced the chacha20 commit with a similar commit from secp256k1-zkp (ElementsProject/secp256k1-zkp@c3794f9).
  6. in include/secp256k1_schnorrsig.h:75 in 547ad32c79 outdated
    70+    const secp256k1_context* ctx,
    71+    secp256k1_schnorrsig *sig,
    72+    const unsigned char *msg32,
    73+    const unsigned char *seckey,
    74+    secp256k1_nonce_function noncefp,
    75+    const void *ndata
    


    apoelstra commented at 3:07 pm on September 26, 2018:
    I think we should drop the const on ndata. @sipa thoughts?

    gmaxwell commented at 0:18 am on September 30, 2018:
    It shouldn’t be const, as the sign to contract sort of usage needs to write back the certificate.

    jonasnick commented at 4:16 pm on October 16, 2018:
    done
  7. in src/modules/schnorrsig/main_impl.h:341 in 547ad32c79 outdated
    292+
    293+    VERIFY_CHECK(ctx != NULL);
    294+    ARG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx));
    295+    ARG_CHECK(scratch != NULL);
    296+
    297+    secp256k1_sha256_initialize(&sha);
    


    apoelstra commented at 3:53 pm on September 26, 2018:
    Can we pull the sha object into secp256k1_schnorrsig_verify_batch_init_randomizer?

    jonasnick commented at 8:46 am on October 2, 2018:

    An argument against that is that it makes combining multiple init_randomizers less succinct. With a shared sha object it’d look like

    0    secp256k1_sha256_initialize(&sha);
    1    secp256k1_schnorr_verify_init_randomizer(ctx, &ecmult_data.schnorr, &sha, sig, msg32, pk, n_sigs);
    2    secp256k1_taproot_verify_init_randomizer(ctx, &sha, ...);
    3    secp256k1_sha256_finalize(&sha, ecmult_data.schnorr.chacha_seed);
    

    apoelstra commented at 1:32 pm on October 2, 2018:
    Gotcha, thanks! I wasn’t sure if this was done with an eye toward taproot.
  8. apoelstra commented at 3:58 pm on September 26, 2018: contributor
    ACK except nit about sha object, bikeshedding about the module name, and also I did not check that the static test vectors match those in the BIP.
  9. gmaxwell commented at 0:19 am on September 30, 2018: contributor
    Maybe we should consider adopting an anti-covert-channel warden workflow as the standard interface for this function?
  10. in src/bench_schnorrsig.c:85 in 547ad32c79 outdated
    80+    data.msgs = (const unsigned char **)malloc(MAX_SIGS * 32);
    81+    data.sigs = (const secp256k1_schnorrsig **)malloc(MAX_SIGS * sizeof(*data.sigs));
    82+
    83+    for (i = 0; i < MAX_SIGS; i++) {
    84+        unsigned char sk[32];
    85+        unsigned char *msg = malloc(32);
    


    jimpo commented at 5:51 pm on September 30, 2018:
    nit: cast to (unsigned char *)

    jonasnick commented at 8:36 am on October 2, 2018:
    fixed
  11. in src/bench_schnorrsig.c:79 in 547ad32c79 outdated
    74+    size_t i;
    75+    bench_schnorrsig_data data;
    76+
    77+    data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN);
    78+    data.scratch = secp256k1_scratch_space_create(data.ctx, 1024 * 1024 * 1024);
    79+    data.pk = (const unsigned char **)malloc(MAX_SIGS * 33);
    


    jimpo commented at 6:01 pm on September 30, 2018:
    Shouldn’t this be MAX_SIGS * sizeof(unsigned char*)? (Same below)

    jonasnick commented at 8:36 am on October 2, 2018:
    fixed
  12. in src/bench_schnorrsig.c:86 in 547ad32c79 outdated
    81+    data.sigs = (const secp256k1_schnorrsig **)malloc(MAX_SIGS * sizeof(*data.sigs));
    82+
    83+    for (i = 0; i < MAX_SIGS; i++) {
    84+        unsigned char sk[32];
    85+        unsigned char *msg = malloc(32);
    86+        secp256k1_schnorrsig *sig = (secp256k1_schnorrsig *)malloc(sizeof(*sig));
    


    jimpo commented at 6:02 pm on September 30, 2018:
    Maybe faster to do one big malloc for all msgs, pubkeys, and sigs outside the loop, then index into it.

    jonasnick commented at 8:41 am on October 2, 2018:
    I don’t think that’s measurably faster, setup code like this that is not in a bench_* function is not measured by the tool and it would increase code complexity.
  13. in src/bench_schnorrsig.c:29 in 547ad32c79 outdated
    24+} bench_schnorrsig_data;
    25+
    26+void bench_schnorrsig_sign(void* arg) {
    27+    bench_schnorrsig_data *data = (bench_schnorrsig_data *)arg;
    28+    size_t i;
    29+    unsigned char sk[32] = "benchmarkexample secrettemplate";
    


    jimpo commented at 6:07 pm on September 30, 2018:
    Why does this use a this secret key and message instead of data->sk[i] (which could easily be added to the data struct) and data->msgs[i]?

    apoelstra commented at 4:46 pm on October 1, 2018:
    Because this works and malloc is a PITA to use :P

    jimpo commented at 10:29 pm on October 1, 2018:
    Sure, it’s just a bit odd that bench_schnorrsig_verify and bench_schnorrsig_verify_n use the malloc’ed data while this doesn’t. Feel free to ignore though.
  14. in include/secp256k1_schnorrsig.h:90 in 547ad32c79 outdated
    68+ */
    69+SECP256K1_API int secp256k1_schnorrsig_sign(
    70+    const secp256k1_context* ctx,
    71+    secp256k1_schnorrsig *sig,
    72+    const unsigned char *msg32,
    73+    const unsigned char *seckey,
    


    jimpo commented at 6:18 pm on September 30, 2018:
    Why not make this a secp256k1_scalar *, like in the ecdsa_sig_sign interface?

    apoelstra commented at 4:46 pm on October 1, 2018:
    This is a public-facing function, and secp256k1_scalar is not a public type.
  15. in src/modules/schnorrsig/main_impl.h:64 in 547ad32c79 outdated
    59+    }
    60+
    61+    secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &pkj, &x);
    62+    secp256k1_ge_set_gej(&pk, &pkj);
    63+
    64+    if(!noncefp(buf, msg32, seckey, NULL, (void*)ndata, 0)) {
    


    jimpo commented at 6:30 pm on September 30, 2018:
    style nit: spacing after if is inconsistent in this file.

    jonasnick commented at 8:41 am on October 2, 2018:
    fixed in multiple places. I hope I caught them all.
  16. in src/modules/schnorrsig/main_impl.h:348 in 547ad32c79 outdated
    298+    if(!secp256k1_schnorrsig_verify_batch_init_randomizer(ctx, &ecmult_context, &sha, sig, msg32, pk, n_sigs)) {
    299+        return 0;
    300+    }
    301+    secp256k1_sha256_finalize(&sha, ecmult_context.chacha_seed);
    302+
    303+    secp256k1_scalar_clear(&s);
    


    jimpo commented at 7:34 pm on September 30, 2018:
    This line feels like it belongs in schnorrsig_verify_batch_sum_s

    jonasnick commented at 8:45 am on October 2, 2018:

    I’d prefer to have it outside of batch_sum_s to allow combining similar helper functions in a more general batch verification function like

    0    secp256k1_scalar_clear(&s);
    1    secp256k1_schnorrsig_verify_batch_sum_s(&s, ecmult_context.chacha_seed, sig, n_sigs)
    2    secp256k1_taproot_verify_init(&s, ...);
    

    I added better documentation to this helper function instead.

  17. in src/modules/schnorrsig/main_impl.h:203 in 547ad32c79 outdated
    198+    }
    199+
    200+    /* -R */
    201+    if (idx % 2 == 0) {
    202+        secp256k1_fe rx;
    203+        secp256k1_scalar_negate(sc, &ecmult_context->randomizer_cache[(idx / 2) % 2]);
    


    jimpo commented at 7:40 pm on September 30, 2018:
    Time difference is probably negligible, but it seems easier to negate the single sum_s term in verify_batch rather than each one here.

    jonasnick commented at 8:42 am on October 2, 2018:
    agreed
  18. jimpo commented at 7:50 pm on September 30, 2018: none

    Nice! Such hype.

    I skipped the tests, hoping to get back around to them.

  19. apoelstra commented at 3:59 pm on October 1, 2018: contributor

    @gmaxwell By anti-covert channel do you mean essentially sign-to-contracting random data? I would like this. One thing blocking it is that our nonce function does not take a secp context currently, which makes sign-to-contract unergonmic – see in sighacker how the sign-to-contract context needs to contain a pointer to the secp context.

    I think we should fix that but it should probably be in another PR.

  20. jonasnick commented at 8:47 am on October 2, 2018: contributor
    Thanks @jimpo. I added a commit that addresses your comments.
  21. Christewart cross-referenced this on Oct 3, 2018 from issue Add secp256k1jni bindings for the schnorrsig module in libsecp256k1 by Christewart
  22. ghost commented at 11:59 pm on October 8, 2018: none
    how does this relate to #212 ?
  23. apoelstra commented at 0:21 am on October 9, 2018: contributor
    #212 is not secure against rogue-key attacks nor does it commit to the public key being signed for.
  24. jonasnick commented at 0:48 am on October 9, 2018: contributor
    and #212 was removed in #425
  25. real-or-random commented at 9:28 am on October 12, 2018: contributor
    Oh I was not aware of this PR. FYI, I suggested that a CSPRNG is not enough for batch validation and we need a hash function to generate the seed (https://github.com/sipa/bips/pull/15/files) but now I see that this PR is doing that anyway. :)
  26. in src/modules/schnorrsig/main_impl.h:291 in ebfd6e90b1 outdated
    286+
    287+    for (i = 0; i < n_sigs; i++) {
    288+        int overflow;
    289+        secp256k1_scalar term;
    290+        if (i % 2 == 0) {
    291+            secp256k1_schnorrsig_batch_randomizer(randomizer_cache, chacha_seed, i / 2);
    


    real-or-random commented at 9:52 am on October 12, 2018:

    Currently, we discard one random value in secp256k1_schnorrsig_batch_randomizer(). We could instead

    • initialize randomizer_cache[0] with 1
    • change the condition to i % 2 == 1
    • (remove the third argument of secp256k1_schnorrsig_batch_randomizer())
    • (maybe swap the &r[0] and &r[1] in secp256k1_schnorrsig_batch_randomizer()` to make sure we don’t use the scalars in a weird order – but who cares?)

    And similarly for the other side of the verification equation / ecmult. I think that’ll be i % 4 == 2


    jonasnick commented at 1:56 pm on October 15, 2018:
    Yup, I like that. We can remove the batch_randomizer function entirely and save unnecessary PRNG calls.
  27. in .gitignore:7 in ebfd6e90b1 outdated
    0@@ -1,6 +1,7 @@
    1 bench_inv
    2 bench_ecdh
    3 bench_ecmult
    4+bench_schnorrsig
    5 bench_sign
    6 bench_verify
    7 bench_schnorr_verify
    


    real-or-random commented at 10:43 am on October 12, 2018:
    You could remove this line; it was overlooked in #425

    jonasnick commented at 1:56 pm on October 15, 2018:
    done
  28. in include/secp256k1_schnorrsig.h:22 in ebfd6e90b1 outdated
    17+ */
    18+typedef struct {
    19+    unsigned char data[64];
    20+} secp256k1_schnorrsig;
    21+
    22+/** Serialize a Schnorr signature
    


    real-or-random commented at 10:46 am on October 12, 2018:
    nit: . at end of line

    jonasnick commented at 1:56 pm on October 15, 2018:
    done
  29. in include/secp256k1_schnorrsig.h:94 in ebfd6e90b1 outdated
    89+    const secp256k1_schnorrsig *sig,
    90+    const unsigned char *msg32,
    91+    const secp256k1_pubkey *pubkey
    92+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
    93+
    94+/** Verifies a set of Schnorr signatures
    


    real-or-random commented at 10:52 am on October 12, 2018:
    same here

    jonasnick commented at 1:56 pm on October 15, 2018:
    done
  30. in src/modules/schnorrsig/main_impl.h:292 in ebfd6e90b1 outdated
    271+}
    272+
    273+/** Helper function for batch verification. Sums the s part of all signatures multiplied by their
    274+ *  randomizer.
    275+ *
    276+ *  Returns 1 if s is successfully summed.
    


    real-or-random commented at 11:02 am on October 12, 2018:
    Maybe add “which is the case if all of the scalars in the signatures are in range”. (On the other hand, this is well enough for a helper function, so feel free to ignore.)

    jonasnick commented at 9:50 am on January 29, 2019:
    Feels like that would be a bit too specific imho.
  31. in src/modules/schnorrsig/main_impl.h:334 in ebfd6e90b1 outdated
    325+    if (!secp256k1_schnorrsig_verify_batch_sum_s(&s, ecmult_context.chacha_seed, sig, n_sigs)) {
    326+        return 0;
    327+    }
    328+    secp256k1_scalar_negate(&s, &s);
    329+
    330+    return secp256k1_ecmult_multi_var(&ctx->ecmult_ctx, scratch, &rj, &s, secp256k1_schnorrsig_verify_batch_ecmult_callback, (void *) &ecmult_context, 2 * n_sigs)
    


    real-or-random commented at 11:50 am on October 12, 2018:

    Possible integer overflow in 2 * n_sigs.

    Okay, I need to give you at least 2^31 signatures (each one with s = 0) to exploit that, but then all my signatures suddenly verify. :P


    jonasnick commented at 7:00 am on October 15, 2018:
    Good catch! Although you’d have to give me 2^63 sigs. Fixing this.
  32. jonasnick commented at 1:57 pm on October 15, 2018: contributor
    @real-or-random Thanks for the review. I added a commit to address your comments.
  33. in src/modules/schnorrsig/main_impl.h:311 in 40f8f7a44c outdated
    305@@ -314,12 +306,19 @@ int secp256k1_schnorrsig_verify_batch(const secp256k1_context *ctx, secp256k1_sc
    306     VERIFY_CHECK(ctx != NULL);
    307     ARG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx));
    308     ARG_CHECK(scratch != NULL);
    309+    /* Check that n_sigs is less than half of the maximum size_t value. This is necessary because
    310+     * the number of points given to ecmult_multi is 2*n_sigs. */
    311+    ARG_CHECK(n_sigs < (size_t)1 << (sizeof(size_t)*8-1));
    


    real-or-random commented at 9:57 am on October 16, 2018:

    1 << sizeof(size_t)*8-1 is not necessarily half of the maximum value that can be represented by size_t. For example, there could be unused “padding bits” in the integer. This is only a problem on exotic platforms that we don’t support anyway but yeah… Anyway, a simpler expression is MAX_SIZE >> 1 (only in C99) or equivalently (size_t)(-1) >> 1 because we want C89.

    BUT I don’t know if we need to go this far, I guess there are a lot of places where the code anyway assumes that size_t is at least 32 bits. So we probably can just use the check below? @sipa


    apoelstra commented at 1:30 pm on October 16, 2018:
    I would prefer (size_t)(-1) >> 1 because it’s easier to read. Or even use / 2 instead of >> 1, this will be evaluated at compile-time anyway.

    jonasnick commented at 3:08 pm on October 16, 2018:
    fixed
  34. jonasnick force-pushed on Oct 16, 2018
  35. jonasnick commented at 2:29 pm on October 24, 2018: contributor
    Added a test to increase the coverage of schnorrsig_sign. Now coverage in the schnorrsig module is 100% when excluding the lines that can’t be hit. See https://htmlpreview.github.io/?https://raw.githubusercontent.com/jonasnick/secp256k1/schnorrsig-stats/coverage.src_modules_schnorrsig_main_impl.h.html
  36. real-or-random cross-referenced this on Oct 28, 2018 from issue Fix integer overflow in ecmult_multi_var when n is large by jonasnick
  37. jonasnick cross-referenced this on Nov 1, 2018 from issue Add anti nonce-sidechannel protocol to schnorrsigs by jonasnick
  38. jonasnick force-pushed on Nov 1, 2018
  39. jonasnick force-pushed on Nov 1, 2018
  40. jonasnick commented at 5:34 pm on November 1, 2018: contributor
    squashed and rebased on master
  41. in src/tests.c:1093 in 419d48790d outdated
    1013+    secp256k1_scalar_chacha20(&r1, &r2, seed1, 100);
    1014+    secp256k1_scalar_set_b32(&exp_r1, &expected3[0], NULL);
    1015+    secp256k1_scalar_set_b32(&exp_r2, &expected3[32], NULL);
    1016+    CHECK(secp256k1_scalar_eq(&exp_r1, &r1));
    1017+    CHECK(secp256k1_scalar_eq(&exp_r2, &r2));
    1018+}
    


    sipa commented at 10:53 pm on November 5, 2018:

    It’s perhaps useful to add one test case with some arbitrary input and position, to make sure an implementation isn’t swapping some of the first 31 bytes of the input seed for example.

    Here is a suggestion:

     0+    unsigned char input4[32] = {
     1+        0x32, 0x56, 0x56, 0xf4, 0x29, 0x02, 0xc2, 0xf8,
     2+        0xa3, 0x4b, 0x96, 0xf5, 0xa7, 0xf7, 0xe3, 0x6c,
     3+        0x92, 0xad, 0xa5, 0x18, 0x1c, 0xe3, 0x41, 0xae,
     4+        0xc3, 0xf3, 0x18, 0xd0, 0xfa, 0x5b, 0x72, 0x53
     5+    };
     6+    unsigned char expected4[64] = {
     7+        0x28, 0xd3, 0x56, 0xe7, 0x5c, 0x19, 0xc6, 0xe9,
     8+        0x21, 0x8e, 0x17, 0x6f, 0x11, 0x72, 0x1e, 0x8c,
     9+        0x0d, 0x17, 0xbd, 0xe7, 0xe9, 0xad, 0x14, 0xac,
    10+        0x92, 0xb6, 0x9f, 0x3d, 0xfb, 0x20, 0x09, 0xd6,
    11+        0x6d, 0x3b, 0x8e, 0x43, 0xc7, 0xdc, 0x33, 0xe3,
    12+        0xbb, 0x6f, 0x07, 0x6c, 0xb5, 0xc8, 0xb4, 0x1f,
    13+        0x12, 0xe5, 0x6c, 0xe3, 0x0c, 0x64, 0xd7, 0xd9,
    14+        0xab, 0x0d, 0xa7, 0xf5, 0x81, 0xf1, 0x03, 0x79
    15+    };
    16+
    17+    secp256k1_scalar_chacha20(&r1, &r2, input4, 0x6ff8602a7a78e2f2ULL);
    18+    secp256k1_scalar_set_b32(&exp_r1, &expected4[0], NULL);
    19+    secp256k1_scalar_set_b32(&exp_r2, &expected4[32], NULL);
    20+    CHECK(secp256k1_scalar_eq(&exp_r1, &r1));
    21+    CHECK(secp256k1_scalar_eq(&exp_r2, &r2));
    

    jonasnick commented at 9:33 am on January 29, 2019:
    I added that test (+ other tests from the RFC).
  42. in src/scalar_8x32_impl.h:793 in 419d48790d outdated
    788+        x12 += idx;
    789+        x13 += idx >> 32;
    790+        x14 += 0;
    791+        x15 += over_count;
    792+
    793+        r1->d[7] = BE32(x0);
    


    sipa commented at 11:12 pm on November 5, 2018:
    Is it necessary to define our PRNG function as “output bytes of ChaCha20 interpreted as BE-encoded scalars”? Using LE would be (very slightly) faster on almost every architecture, if we’re not bound by any specific standard.

    apoelstra commented at 1:46 pm on November 6, 2018:
    Sounds good. AFAIK we do not use this rng anywhere where a verifier could detect it anyway (it is used in Bulletproof rewinding, but that only requires sender/recipient to agree on a rng, not that it be anything in particular).

    real-or-random commented at 4:54 pm on November 6, 2018:
    Yes, and even if we’re using it somewhere else, it wouldn’t matter. As I understand it, this discussion is about interpreting the output bytes. So we’re anyway using it according to the standard.

    jonasnick commented at 10:51 pm on November 7, 2018:
    RFC even says LE (for keystreams but we’re getting scalars so I agree it doesn’t matter): “At the end of 20 rounds (or 10 iterations of the above list), we add the original input words to the output words, and serialize the result by sequencing the words one-by-one in little-endian order.”

    real-or-random commented at 1:23 pm on November 8, 2018:

    But we don’t care about the concrete PRG instantiation and other implementations can use whatever they like, right? Or do we want to standardize it (in the BIP)?

    I’m just asking to be sure, I don’t think it’s necessary to standardize it. Advantages are 1) implementations can be (consensus-)consistent with each other, but that’s only relevant in cases that should happen with at most negligible probability, and 2) maybe people won’t get the idea to use their home-made PRG. The obvious disadvantage is less flexibility.


    jonasnick commented at 9:34 am on January 29, 2019:
    I switched to LE.

    real-or-random commented at 2:50 pm on May 25, 2019:

    I think this entire discussion is the wrong one. Here we read uint32_ts into uint32_ts (8x32) or into uint64_ts (4x64) (and then of course most natural way is to do this is to copy the integers and not reverse the bytes). Endianness is not relevant here, so the LE32() macro is wrong here. I think this is the cause of the failure on s390x, which is 64-bit BE, so the LE32() call reverses the bytes there.

    (Maybe I’m on the wrong track. I hate to think about byte order, and probably get it more often wrong than right. :D)

  43. in include/secp256k1_schnorrsig.h:15 in f12dd1a859 outdated
    0@@ -0,0 +1,118 @@
    1+#ifndef SECP256K1_SCHNORRSIG_H
    2+#define SECP256K1_SCHNORRSIG_H
    3+
    4+/** This module implements a variant of Schnorr signatures compliant with
    5+ * BIP-schnorr
    6+ * (https://github.com/sipa/bips/blob/bip-schnorr/bip-schnorr.mediawiki).
    7+ */
    8+
    9+/** Opaque data structure that holds a parsed Schnorr signature.
    


    sipa commented at 11:31 pm on November 5, 2018:

    As the Schnorr signature BIP defines the actual byte representation of signatures, there is no distinction between “invalid because incorrect values” and “invalid because incorrect serialization” (as is the case with ECDSA and DER encoding).

    Because of that, there seems to not be any strict need for separately exposed parse/serialize functions. That is unless we expect operations besides sign/verify that operate on signatures, which one is expected to use without intervening serialization step. In that case we may opt to later change the representation inside to be a secp256k1_fe_storage and a secp256k1_scalar.


    apoelstra commented at 1:44 pm on November 6, 2018:

    We cannot use secp256k1_fe_storage or secp256k1_scalar because the size of these objects is not exported, so it would prevent users building them on the stack.

    Eliminating parse/serialize operations will prevent any future changes we might want to do that could use a more efficient format. On the other hand, I can’t imagine what future changes those might be.


    sipa commented at 3:04 pm on November 6, 2018:

    Look at how secp256k1_pubkey_load works :)

    And yes, I can’t imagine what those operations would be either. I’d like to make sure we’re not committing to an unnecessarily complex API without reason.


    jonasnick commented at 9:00 pm on November 7, 2018:
    Makes sense to me to remove the parse/serialize functions. In the event that there’s a better representation it could be its own struct.

    jonasnick commented at 9:48 am on January 29, 2019:

    That is unless we expect operations besides sign/verify that operate on signatures, which one is expected to use without intervening serialization step.

    There are definitely operations besides sign/verify that operate on signatures, for example verifying a sign-to-contract commitment as implemented here https://github.com/bitcoin-core/secp256k1/pull/572/files#diff-b19c5ee427283d4d82bc5beb4e2f4777R202


    gmaxwell commented at 9:14 am on April 17, 2019:
    We shouldn’t speculate on future needs that we’re not sure of too much– if something useful turns up we can change the API. A guessed API might not be right in any case.
  44. jonasnick commented at 11:26 am on November 8, 2018: contributor
    Added commit that will switch to little endian format when interpreting chacha20 output, replace chacha20 tests with test vectors from the RFC, add sipa’s chacha20 test.
  45. in src/modules/schnorrsig/tests_impl.h:633 in 27b9d429f4 outdated
    548+            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE,
    549+            0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B,
    550+            0xBF, 0xD2, 0x5E, 0x8C, 0xD0, 0x36, 0x41, 0x41
    551+        };
    552+        test_schnorrsig_bip_vectors_check_verify(scratch, pk13, msg13, sig13, 0);
    553+    }
    


    deadalnix commented at 11:04 pm on December 1, 2018:

    It would be beneficial to know what theses test vector are actually testing for, especially the negative ones.

    I gather that: Test 6 check what happen when R.y is not a quadratic residue. Test 7 is test 3 with an invalid value for s ? Is that value somehow specific ? Test 8 is test 1 with negated s ? Test 9 is test 9 with a negated pubkey. Test 10 is test 2 with s such as s * G = e * P so R goes to infinity. Test 11 is test 2 with an invalid value for r. Test 12 is test 2 with r >= n. Test 13 is test 2 with s >= p.

    While I can gather why most of them are doing, some of them still are unclear, and, in any case, I do not think it is beneficial that detective work is required to figure this out.


    jonasnick commented at 9:21 am on December 4, 2018:
    The function uses the test vectors from the BIP as indicated by the name of the function (test_schnorrsig_bip_vectors). The BIP has an explanation for the test vectors at https://github.com/sipa/bips/blob/bip-schnorr/bip-schnorr.mediawiki#test-vectors-and-reference-code. It may be better to add a link to the BIP test vectors but I didn’t want to do that while it’s not merged into the BIP repo.

    deadalnix commented at 1:55 pm on December 4, 2018:
    Thanks, I missed the link to the test vectors in the BIP.
  46. jonasnick cross-referenced this on Dec 22, 2018 from issue Add MuSig module by jonasnick
  47. jonasnick force-pushed on Jan 29, 2019
  48. jonasnick force-pushed on Jan 29, 2019
  49. jonasnick commented at 10:19 am on January 29, 2019: contributor
    Adjusted tests to reflect most recent BIP-schnorr and squashed.
  50. jonasnick cross-referenced this on Jan 29, 2019 from issue Allow sign-to-contract commitments in schnorrsigs by jonasnick
  51. sipa commented at 9:17 pm on April 3, 2019: contributor
    The secp256k1_schnorrsig.h file doesn’t have “#ifdef __cplusplus extern { #endif” protections that make the header C++ compatible.
  52. jonasnick commented at 2:29 pm on April 6, 2019: contributor
    fixed
  53. in include/secp256k1_schnorrsig.h:72 in f532b338b2 outdated
    67+ * Returns 1 on success, 0 on failure.
    68+ *  Args:    ctx: pointer to a context object, initialized for signing (cannot be NULL)
    69+ *  Out:     sig: pointer to the returned signature (cannot be NULL)
    70+ *       nonce_is_negated: a pointer to an integer indicates if signing algorithm negated the
    71+ *                nonce (can be NULL)
    72+ *  In:    msg32: the 32-byte message hash being signed (cannot be NULL)
    


    real-or-random commented at 9:55 am on May 7, 2019:
    it should be message, not a message hash (same for verification). Maybe we should explain that in more detail given that this is different from the ECDSA implementation.

    real-or-random commented at 6:19 pm on May 30, 2019:
    this is still open

    jonasnick commented at 6:31 pm on May 30, 2019:
    removed mention of hash. It doesn’t hurt to use the hash, but not using the hash in ecdsa is broken. So I think this should be stressed in secp256k1.h if it isn’t already.
  54. hegjon commented at 8:45 pm on May 23, 2019: none

    This PR failed to build on s390x on Fedora Linux.

    The patch was applied to commit a34bcaadf14442b86a5de120d4afd131f910d73d-

    Log: https://kojipkgs.fedoraproject.org//work/tasks/7518/35017518/build.log Build task overview: https://koji.fedoraproject.org/koji/taskinfo?taskID=35017512

  55. real-or-random commented at 8:50 pm on May 23, 2019: contributor

    To save others from looking through it, the interesting lines are

    0+ ./tests
    1BUILDSTDERR: src/tests.c:1044: test condition failed: secp256k1_scalar_eq(&exp_r1, &r1)
    2BUILDSTDERR: /var/tmp/rpm-tmp.SSskCl: line 31: 56834 Aborted                 (core dumped) ./tests
    
  56. in src/tests.c:1252 in f532b338b2 outdated
    1039+    unsigned char seed0[32] = { 0 };
    1040+
    1041+    secp256k1_scalar_chacha20(&r1, &r2, seed0, 0);
    1042+    secp256k1_scalar_set_b32(&exp_r1, &expected1[0], NULL);
    1043+    secp256k1_scalar_set_b32(&exp_r2, &expected1[32], NULL);
    1044+    CHECK(secp256k1_scalar_eq(&exp_r1, &r1));
    


    real-or-random commented at 8:50 pm on May 23, 2019:
    (here)
  57. real-or-random commented at 8:55 pm on May 23, 2019: contributor

    @hegjon Do you happen to have the stdout too (instead of only the stderr)? It contains a random seed for the entire test run. Is the failure reproducible?

    (Sorry for 3 comments in 5 min…)

  58. hegjon commented at 9:01 pm on May 23, 2019: none

    @hegjon Do you happen to have the stdout too (instead of only the stderr)? It contains a random seed for the entire test run. Is the failure reproducible?

    I can check and see if I am able to find them, I could also try to redirect stdout to stderr while debugging.

    (Sorry for 3 comments in 5 min…)

    No worries, thanks for replying

  59. hegjon commented at 10:12 pm on May 23, 2019: none

    Is the failure reproducible?

    Seems like it fails every time for s390x, will try to get the stdout

  60. hegjon commented at 5:23 pm on May 24, 2019: none
    If someone want to debug this bug on a s390x system, paste your public ssh key, I and will try to grant you access to one
  61. jonasnick commented at 8:51 pm on May 25, 2019: contributor
    I can confirm that I get the same error on a big endian 64 bit mips system.
  62. in src/scalar_4x64_impl.h:962 in f4153a29ab outdated
    948@@ -946,4 +949,94 @@ SECP256K1_INLINE static void secp256k1_scalar_mul_shift_var(secp256k1_scalar *r,
    949     secp256k1_scalar_cadd_bit(r, 0, (l[(shift - 1) >> 6] >> ((shift - 1) & 0x3f)) & 1);
    950 }
    951 
    952+#define ROTL32(x,n) ((x) << (n) | (x) >> (32-(n)))
    


    jb55 commented at 11:52 pm on May 26, 2019:

    the result of this will be invalid if the integer type of x has a storage size larger than 32 bits.

    for instance ROTL32((1UL << 31), 1) will not equal 1. It doesn’t look like it’s relevant for the current PR as it stands, but this is a common gotcha when using 64 bit literals.

    suggest: ((uint32_t)(v) << (n)) | ((uint32_t)(v) >> (32 - (n)))


    jonasnick commented at 9:02 pm on May 27, 2019:
    Integers given to this macro are always 32 bits
  63. in src/scalar_8x32_impl.h:737 in f4153a29ab outdated
    719@@ -718,4 +720,102 @@ SECP256K1_INLINE static void secp256k1_scalar_mul_shift_var(secp256k1_scalar *r,
    720     secp256k1_scalar_cadd_bit(r, 0, (l[(shift - 1) >> 5] >> ((shift - 1) & 0x1f)) & 1);
    721 }
    722 
    723+#define ROTL32(x,n) ((x) << (n) | (x) >> (32-(n)))
    


    jb55 commented at 0:02 am on May 27, 2019:
    same here
  64. jonasnick commented at 11:58 am on May 27, 2019: contributor
    The suggestion by @real-or-random above is correct. Removing the LE32 macro from the integer assignments makes the tests on my big endian machine pass. I’ll add a commit to fix that later.
  65. jonasnick commented at 9:00 pm on May 27, 2019: contributor
    Pushed a commit that removes the LE32 macro when doing the final chacha20 assignments. This didn’t appear in the little endian tests because LE32 is a noop there. I only ran the tests with 32 bit scalars on the big endian machine, because using 64 bit scalars is prohibited by __int128 support not being available.
  66. hegjon commented at 10:42 pm on May 28, 2019: none
  67. hegjon commented at 11:18 pm on May 28, 2019: none

    It builds+all tests passes on all arches for Fedora with the latest changes.

    I also added these configure flags:

    0--enable-experimental --enable-module-schnorrsig
    

    are they required?

  68. jonasnick commented at 11:34 pm on May 28, 2019: contributor

    @hegjon Nice, thank you!

    are they required?

    yes. Will add to OP.

  69. jonasnick force-pushed on May 30, 2019
  70. jonasnick force-pushed on May 30, 2019
  71. jonasnick commented at 8:00 pm on May 30, 2019: contributor
    rebased
  72. jonasnick force-pushed on May 30, 2019
  73. in include/secp256k1_schnorrsig.h:80 in a81efa5d01 outdated
    78+    const secp256k1_context* ctx,
    79+    secp256k1_schnorrsig *sig,
    80+    int *nonce_is_negated,
    81+    const unsigned char *msg32,
    82+    const unsigned char *seckey,
    83+    secp256k1_nonce_function noncefp,
    


    elichai commented at 11:37 pm on June 13, 2019:

    Using @sipa logic from above

    As the Schnorr signature BIP defines the actual byte representation of signatures, there is no distinction between “invalid because incorrect values” and “invalid because incorrect serialization”

    Shouldn’t the deterministic derivation that’s described in BIP Schnorr be part of the function itself? (I guess if you see someone using this directly for Musig or something like that he will want to provide a different source of randomness)


    jonasnick commented at 2:50 pm on June 17, 2019:
    This is an optional argument in the same way as in ecdsa_sign. In almost all the cases users will provide NULL. I don’t know a specific case where you would want to provide your own nonce function (outside of testing).

    sipa commented at 11:06 pm on October 17, 2019:
    I think we should have the ability to pass in an external nonce generation function, as long as the default matches the spec.
  74. in include/secp256k1_schnorrsig.h:70 in a81efa5d01 outdated
    65+/** Create a Schnorr signature.
    66+ *
    67+ * Returns 1 on success, 0 on failure.
    68+ *  Args:    ctx: pointer to a context object, initialized for signing (cannot be NULL)
    69+ *  Out:     sig: pointer to the returned signature (cannot be NULL)
    70+ *       nonce_is_negated: a pointer to an integer indicates if signing algorithm negated the
    


    elichai commented at 0:03 am on June 14, 2019:
    Could you expand on why would a user of the library want to know if the generated nonce was negated or not?

    jonasnick commented at 3:07 pm on June 17, 2019:
    The caller needs to know that if they want to create sign-to-contract commitments because this information is necessary to batch verify these commitments. PR #589 changes this argument to return nonce_is_negated as part of the full opening which is better.
  75. in src/scalar_8x32_impl.h:750 in a81efa5d01 outdated
    731+#define LE32(p) ((((p) & 0xFF) << 24) | (((p) & 0xFF00) << 8) | (((p) & 0xFF0000) >> 8) | (((p) & 0xFF000000) >> 24))
    732+#else
    733+#define LE32(p) (p)
    734+#endif
    735+
    736+static void secp256k1_scalar_chacha20(secp256k1_scalar *r1, secp256k1_scalar *r2, const unsigned char *seed, uint64_t idx) {
    


    elichai commented at 1:01 am on June 14, 2019:

    A thought about multiple implementations of the same cryptography. is this library becoming a general cryptography library? should there be a different general crypto library with sha2/chacha20 etc.?

    Because right now both libsecp and bitcoin implements SHA256 and HMAC, and after this they’ll also both implement Chacha20 (https://github.com/bitcoin/bitcoin/blob/master/src/crypto/chacha20.cpp)

    Any ideas on the matter? (I myself don’t like duplicate implementations especially of sensitive cryptography(and there’s also a performance difference too))


    sipa commented at 1:13 am on June 14, 2019:

    libsecp256k1 is absolutely not a generic low-level cryptographc primitives library; it’s a library implementing high-level cryptographic algorithms efficiently with a safe-to-use interface.

    As far as duplicate implementations goes, these are included for necessity of exposing fully-functional versions of the high-level functions it implements. I personally don’t think code duplication for such low-level primitives is an issue, as this code doesn’t “rot” (it won’t go out of date ever, at worst very rarely needs to be extended, and possibly doesn’t need to be touched ever, at all), so it isn’t subject to the same priorities most software engineering places.

    That said, there are some practical reasons why having a built-in implementation may be undesirable:

    • In very low-memory systems which already have versions of SHA256/… somewhere, the additional code size may be undesirable.
    • The SHA256/Chacha20 code inside libsecp256k1 isn’t very optimized, and probably never will gain high levels of architecture-specific optimization. Generally in these protocols, the performance of these is not very important, but if it is, it’d be great to have libsecp256k1 use more efficient versions.

    Because of that reason, I think we should see the internal primitives as default, naive, private implementations, and if there is ever a reason to use another one, we should add functionality to make libsecp256k1 use external versions (rather than exposing/improving the internal ones).


    elichai commented at 1:19 am on June 14, 2019:

    Yeah, I didn’t consider the first option a real one, just a possibility about how will the library look with more primitives being added.

    I guess your answer is fair, optimized sha256 might already be a nice speedup in a rapid verification scenario(which is already considered in the whole batch verification).

    Maybe making a general low level library that is shared between libsecp and bitcoin is an option? Otherwise your suggestion that if someone really wanted then it’s possible to add a feature to support these functions as extern.

  76. in src/modules/schnorrsig/main_impl.h:155 in a81efa5d01 outdated
    73+    secp256k1_ge_set_gej(&r, &rj);
    74+
    75+    if (nonce_is_negated != NULL) {
    76+        *nonce_is_negated = 0;
    77+    }
    78+    if (!secp256k1_fe_is_quad_var(&r.y)) {
    


    elichai commented at 8:24 pm on June 15, 2019:

    Is this constant time? As far as I could tell this uses https://github.com/bitcoin-core/secp256k1/blob/master/src/num_gmp_impl.h#L147

    Which uses GMP. Am I missing something?


    sipa commented at 8:37 pm on June 15, 2019:

    It isn’t (all functions ending with _var are variable time).

    This isn’t a problem, as R (including its original Y coordinate) is not secret.


    elichai commented at 9:01 pm on June 15, 2019:
    Oh, ok. I assumed the whole “sign” function is constant time. Constant time, constant memory access signing and pubkey generation. Thanks!

    sipa commented at 9:09 pm on June 15, 2019:

    The whole “constant time” naming is confusing really, as we can’t actually guarantee actual constant time (e.g. the div x86 instruction is variable time!).

    Really what we’re aiming for is sidechannel resistance, and one of the criteria is that no execution branches are memory access locations are dependent on secret data.

  77. jonasnick commented at 11:58 am on June 25, 2019: contributor
    done
  78. real-or-random cross-referenced this on Jul 2, 2019 from issue Better output in failed tests by real-or-random
  79. jonasnick force-pushed on Jul 4, 2019
  80. jonasnick force-pushed on Jul 4, 2019
  81. jonasnick commented at 9:27 pm on July 4, 2019: contributor

    Removed nonce_is_negated out argument from schnorrsig_sign because it was only required for a follow-up sign-to-contract PR that I closed in favor of a different sign-to-contract PR which does not use a nonce_is_negated argument but instead adds s2c_opening and s2c_data arguments (PR #589).

    Also squashed in order to allow to cleanly build the sign-to-contract PR on top.

  82. in src/modules/schnorrsig/main_impl.h:64 in a228e2f54d outdated
    59+    }
    60+
    61+    secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &pkj, &x);
    62+    secp256k1_ge_set_gej(&pk, &pkj);
    63+
    64+    if (!noncefp(buf, msg32, seckey, NULL, (void*)ndata, 0)) {
    


    afk11 commented at 11:36 am on July 8, 2019:
    Per the documentation for secp256k1_nonce_function the algo16 parameter should be written to and non null for schemes besides ECDSA - should the NULL be replaced with an algorithm identifier?

    afk11 commented at 2:02 pm on August 23, 2019:

    @jonasnick reason for the request - https://github.com/bitcoin-core/secp256k1/blob/master/include/secp256k1.h#L90 According to here, NULL is passed for ecdsa, others need a 16byte identifier for the signing algorithm. I maintain language bindings to libsecp256k1, and have an implementation of a secp256k1_nonce_function to allow callers to use their own noncefp. The algo16 is currently the same for ecdsa and schnorr (null) so I can’t run this branch in tests: https://github.com/Bit-Wasp/secp256k1-php/blob/ea94d1daba2dcf07a575d294ce2159e73a15d2cc/secp256k1/secp256k1.c#L55

    If there are any hard coded signature fixtures in tests, they’ll likely need tweaking as the bipschnorr noncefp also commits to the algo16: https://github.com/bitcoin-core/secp256k1/pull/558/files#diff-0d77700fa6e05639431c962ffaa5365aR429


    jonasnick commented at 10:33 am on August 26, 2019:

    Thanks @afk11 , good catch! The simplest fix ist to change nonce_function_bipschnorr to write nothing additional into the hash if algo16 is “bip-schnorr sign “, 0 if NULL, and 16 bytes of algo16 otherwise. @sipa We could easily remove the noncefp parameter from normal schnorrsig signing but then we need to provide another a more specific schnorrsig_sign function that allows providing your nonce function.There are already experimental applications of this where the nonce function returns a static nonce. For example, this is part of the discreet log contracts idea where only one outcome is supposed to be signed and otherwise the secret key leaks.

    Perhaps ideally we should use a tagged hash anyway in bip-schnorr nonce derivation. So in the libsecp bip-schnorr nonce function the tag is either according to bip-schnorr if algo16 is “bip-schnorr sign " otherwise it’s empty and algo16 is written somewhere into the hash.

    EDIT: opened PR for tagged hash in bip-schnorr (https://github.com/sipa/bips/pull/61)


    afk11 commented at 4:52 pm on September 5, 2019:
    @jonasnick sounds like a good solution, thanks!
  83. Christewart cross-referenced this on Jul 21, 2019 from issue WIP: Get schnorr signature unit tests passing by Christewart
  84. nkohen cross-referenced this on Jul 23, 2019 from issue Schnorr secp256k1 rebase by nkohen
  85. jonasnick commented at 8:18 pm on August 29, 2019: contributor

    I pushed new commits that do the following:

    • Introduce new secp256k1_schnorrsig_pubkey which is same as the normal pubkey except its y coordinate is always a quadratic residue. Also added creation function, as well as serialization and parsing according to bip-schnorr.
    • Add tagged hashing. This and above point means that the code should be compatible with the latest version of bip-schnorr (https://github.com/sipa/bips/blob/dc6b91c1a913f004ffa306c68295ac0b0f361264/bip-schnorr.mediawiki).
    • Disable the bip-schnorr test vectors for now until it is decided how to proceed with https://github.com/sipa/bips/pull/62
    • Fix the issue with nonce reuse and the algo16 argument that @afk11 discovered
    • Fix the counter argument in the bipschnorr_nonce_function as it’s exposed via secp.h but different values would not result in different nonces (no one compiles with -DVERIFY) which could result in nonce reuse.

    Now I’ll add functions to commit to, verify and sign for a taproot commitments.

  86. real-or-random commented at 6:58 pm on September 5, 2019: contributor
    * Introduce new `secp256k1_schnorrsig_pubkey` which is same as the normal pubkey except its y coordinate is always a quadratic residue.
    

    I wonder if secp256k1_32byte_pubkey or similar is better name for the future. Not sure.

  87. jonasnick force-pushed on Sep 12, 2019
  88. jonasnick force-pushed on Sep 12, 2019
  89. jonasnick commented at 3:02 pm on September 12, 2019: contributor

    I changed the schnorrsig_pubkey commits to rename the pubkey struct to positive_pubkey (alternative names runners-up: “xonly”, “short”). Also, positive_pubkey related code was moved from the schnorrsig module into the main code. In principle it could be its own module, but if we expect more modules to use positive_pubkeys then I think not doing that is the easiest way.

    I added a new commit that introduces positive_privkey_tweak_add, positive_pubkey_tweak_add, positive_pubkey_tweak_verify, positive_pubkey_from_signed and positive_pubkey_to_signed in order to support bip-taproot operations. See https://github.com/bitcoin-core/secp256k1/pull/558/commits/59d10284ec18c74755d486cce6af55e46e25fbae#diff-4ad2dd2b8b6840915f8f0e99d18e1dc8R721 for an example of how to use these functions.

    It’s not particularly elegant to have all this conversion between positive and “signed” pubkeys but I didn’t really find a way around it. One possible optimization of the API would be to have privkey_tweak_add take the corresponding pubkey as an argument which would avoid having to compute it.

  90. elichai commented at 3:20 pm on September 12, 2019: contributor
    Why positive? Shouldn’t it be quad_pubkey or something like that?
  91. jonasnick commented at 4:56 pm on September 12, 2019: contributor

    Why positive?

    So far it’s the best option I’ve heard (h/t to @sipa). quad_pubkey is possible, but sounds overly technical and is not particularly precise (what’s a quadratic pubkey?). Defining positive points as points where the Y coordinate has a quadratic residue makes it easy to talk about them. Look at how often we say “point with a Y coordinate that is a quadratic residue” in BIP-schnorr/taproot, which is irritating. The definition makes it easy to partition points into positive and negative and in that context we can use “signed” for the existing pubkey type where we don’t care. Additionally, it’s similar to the real numbers where positive numbers have a square root and negative numbers don’t.

  92. sipa commented at 5:51 am on September 13, 2019: contributor

    I guess “positive” may be confusing, and the opposite “signed” is probably even more confusing.

    I’m also ok with “x-only”. Arguably, “positive” (if anything) is a property of the point, while having a Y coordinate at all is a property of the type of public key.

  93. jonasnick commented at 9:04 am on September 14, 2019: contributor
    “x-only” would need to be fleshed out more. How do you call existing pubkey types in that context? How do you call the is_y_quad bit which is simply called sign in this PR. is_y_quad also comes up in bip-taproot and would be much nicer if it could also be called sign.
  94. sipa commented at 10:56 pm on September 19, 2019: contributor

    So what about this:

    • We introduce the term “positive” and “negative” as properties of EC points. Positive means Y is a quadratic residue; negative means Y is a quadratic nonresidue.
    • Then we defined x-only public keys as 32 bytes (in addition to compressed public keys of 33 bytes and uncompressed public keys of 65 bytes), which implicitly correspond to the positive point with the given X coordinate

    What do you think?

  95. jonasnick force-pushed on Sep 23, 2019
  96. jonasnick commented at 7:15 pm on September 23, 2019: contributor
    Renamed positive to xonly, removed signed_pks and kept the sign bit. Also rebased on master to remove travis.yml conflict.
  97. real-or-random commented at 8:35 am on September 24, 2019: contributor

    So what about this:

    * We introduce the term "positive" and "negative" as properties of EC points. Positive means Y is a quadratic residue; negative means Y is a quadratic nonresidue.
    
    * Then we defined x-only public keys as 32 bytes (in addition to compressed public keys of 33 bytes and uncompressed public keys of 65 bytes), which implicitly correspond to the positive point with the given X coordinate
    

    What do you think?

    This sounds great.

  98. in src/secp256k1.c:849 in 2e4ed392e1 outdated
    844+
    845+    *output_pubkey = *(secp256k1_pubkey *)internal_pubkey;
    846+    return secp256k1_ec_pubkey_tweak_add(ctx, output_pubkey, tweak32);
    847+}
    848+
    849+int secp256k1_xonly_pubkey_tweak_verify(const secp256k1_context* ctx, const secp256k1_pubkey *output_pubkey, const secp256k1_xonly_pubkey *internal_pubkey, const unsigned char *tweak32) {
    


    elichai commented at 11:22 pm on September 24, 2019:
    is this function really needed? (i.e. the caller can do exactly the same thing by himself)

    elichai commented at 11:23 pm on September 24, 2019:
    And I don’t remember if there’s a promise that two secp256k1_pubkey instances can be compared. remember I had a conversation about that with @sipa on IRC a while ago.

    jonasnick commented at 2:20 pm on September 25, 2019:

    Before the caller can do exactly the same thing they apparently would need to figure out how to compare pubkeys. On the other hand the taproot branch doesn’t use it because it doesn’t have to parse the serialized output pubkey in the first place and therefore compare the serialized versions.

    Comparing via memcmp should be fine as the field elements are normalized when converting from group elements and parsing and it’s used in tests all over the codebase. But this is not documented or immediately obvious and therefore not ideal. Perhaps it’s time to finally add a group element comparison function.


    jonasnick commented at 7:47 pm on October 24, 2019:
    @sipa uses it in the taproot implementaion as he mentions below :)

    elichai commented at 1:24 pm on March 25, 2020:

    FYI it’s documented that you should serialize secp256k1_pubkey before comparing https://github.com/bitcoin-core/secp256k1/blob/master/include/secp256k1.h#L64

    EDIT: Not really related to what you’re doing, just a comment on Comparing via memcmp should be fine. internally it’s obviously fine

  99. in include/secp256k1.h:753 in 7eeb3aa498 outdated
    722+ *  comparison, use secp256k1_xonly_pubkey_serialize and
    723+ *  secp256k1_xonly_pubkey_parse.
    724+ */
    725+typedef struct {
    726+    unsigned char data[64];
    727+} secp256k1_xonly_pubkey;
    


    roconnor-blockstream commented at 9:30 pm on October 10, 2019:
    Would it make more sense to move this typedef up closer to the top of the file with all the other structures that are typedefed?

    jonasnick commented at 8:43 pm on October 24, 2019:
    Hmm, seemed to be sensible to me to put this close to the functions related to xonly keys. The rest of the file is irrelevant to xonly. Other than being close to the other typedefs there doesn’t seem to be a reason to do it with the clear downside of having to jump more when reading the xonly parts. But shrugs
  100. in src/modules/schnorrsig/main_impl.h:131 in 2e4ed392e1 outdated
    126+    secp256k1_ge pkp;
    127+    secp256k1_gej pkj;
    128+
    129+    secp256k1_scalar_negate(&nege, e);
    130+
    131+    if (!secp256k1_pubkey_load(ctx, &pkp, (secp256k1_pubkey *) pk)) {
    


    roconnor-blockstream commented at 9:03 pm on October 11, 2019:
    should be (const secp256k1_pubkey *) pk to avoid discarding the const qualifier.

    roconnor-blockstream commented at 9:03 pm on October 11, 2019:
    Should be (const secp256k1_pubkey *) pk to avoid discarding the const qualifier.

    jonasnick commented at 9:43 pm on October 24, 2019:
    fixed

    jonasnick commented at 9:43 pm on October 24, 2019:
    fixed
  101. in src/secp256k1.c:788 in 2e4ed392e1 outdated
    783+    size_t outputlen = sizeof(buf);
    784+    VERIFY_CHECK(ctx != NULL);
    785+    ARG_CHECK(pubkey != NULL);
    786+    ARG_CHECK(output32 != NULL);
    787+
    788+    if (!secp256k1_ec_pubkey_serialize(ctx, buf, &outputlen, (secp256k1_pubkey *) pubkey, SECP256K1_EC_COMPRESSED)) {
    


    roconnor-blockstream commented at 9:13 pm on October 11, 2019:
    Should be (const secp256k1_pubkey *) pubkey to avoid casting away the const qualifier.

    jonasnick commented at 9:43 pm on October 24, 2019:
    fixed
  102. in include/secp256k1.h:526 in 2e4ed392e1 outdated
    522@@ -523,6 +523,13 @@ SECP256K1_API int secp256k1_ecdsa_signature_normalize(
    523  */
    524 SECP256K1_API extern const secp256k1_nonce_function secp256k1_nonce_function_rfc6979;
    525 
    526+/** An implementation of the nonce generation function as defined in BIP-schnorr.
    


    sipa commented at 10:12 pm on October 17, 2019:

    It may be worth pointing out here that it’s safe to use this function for ECDSA as well (and perhaps document what tag is used in that case).

    Alternatively, the function could just only support schnorr, and fail in any other context.


    jonasnick commented at 7:46 pm on October 24, 2019:
    It’s safe indeed to use it with ECDSA, descpite ecdsa_sign not providing an algo16 parameter. But what do you mean by tag?

    sipa commented at 7:54 pm on October 24, 2019:

    The algo16 parameter isn’t observable to users of this function, so it doesn’t need to be mentioned.

    For the current implementation however, I think you need to document:

    • When this function is used in schnorr_sign, it produces bip-schnorr compliant signatures.
    • When this function is used in ecdsa_sign, it generates a nonce using an analogue of the bip-schnorr nonce generation algorithm, but with tag “BIPSchnorrNULL” instead of “BIPSchnorrDerive”.

    And written like that, I think that’s a weird choice for the tag, as the fact that ecdsa_sign happens to pass an empty algo16 is an implementation detail that perhaps shouldn’t leak into the protocol used - but this is bikeshedding.


    jonasnick commented at 8:06 pm on October 24, 2019:
    Ah, ic what you mean. If you have a better tag in mind let me know (not sure what’s wrong with BIPSchnorrNULL).

    jonasnick commented at 8:35 pm on October 24, 2019:
    added comments
  103. in include/secp256k1.h:713 in 2e4ed392e1 outdated
    707@@ -701,6 +708,188 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_combine(
    708     size_t n
    709 ) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
    710 
    711+/** Opaque data structure that holds a parsed and valid "x-only" public key.
    712+ *  An x-only pubkey encodes a positive point. That is a point whose Y
    713+ *  coordinate is a quadratic residue. It is serialized using only its X
    


    sipa commented at 10:15 pm on October 17, 2019:
    We’ve updated the bip draft to use the term “square”, maybe do the same here?

    jonasnick commented at 8:34 pm on October 24, 2019:
    yup
  104. in include/secp256k1.h:714 in 2e4ed392e1 outdated
    707@@ -701,6 +708,188 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_combine(
    708     size_t n
    709 ) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
    710 
    711+/** Opaque data structure that holds a parsed and valid "x-only" public key.
    712+ *  An x-only pubkey encodes a positive point. That is a point whose Y
    713+ *  coordinate is a quadratic residue. It is serialized using only its X
    714+ *  coordinate (32 bytes). A secp256k1_xonly_pubkey is also a secp256k1_pubkey
    


    sipa commented at 10:33 pm on October 17, 2019:

    This could be misinterpreted as meaning you can cast a pointer from one to another (which is currently true, but not behavior we should commit to).

    Is it really needed to have support for converting xonly_pubkeys to pubkeys? With a few changes (see below), I think it would be easier to describe the behavior simply as “pubkeys can be converted to xonly_pubkeys, using function xonly_pubkey_from_pubkey and will remain consistent with their private keys when doing so.” I may be overlooking something, though.


    jonasnick commented at 7:45 pm on October 24, 2019:

    This could be misinterpreted as meaning you can cast a pointer from one to another (which is currently true, but not behavior we should commit to).

    Yeah, we do that internally, but shouldn’t encourage users to do the same.

    Is it really needed to have support for converting xonly_pubkeys to pubkeys? With a few changes (see below) […]

    I think with the few changes it may still make sense to have the conversion. A user may only have an xonly pubkey but wants to use an existing function. But I have no specific use case in mind. We could add this later if necessary.

  105. in include/secp256k1.h:764 in 2e4ed392e1 outdated
    759+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
    760+
    761+/** Compute the xonly public key for a secret key. Just as ec_pubkey_create this
    762+ *  function computes the point P by multiplying the seckey (interpreted as a scalar)
    763+ *  with the generator. The public key corresponds to P if the Y coordinate of P is a
    764+ *  quadratic residue or -P otherwise.
    


    sipa commented at 10:38 pm on October 17, 2019:
    I don’t think we need to describe the correspondence between xonly pubkeys and pubkeys for the same privkey. As far as the schnorr-with-xonly-pubkeys-cryptosystem is concerned, (nearly) every 32-byte array is a valid privkey, and they all have a corresponding pubkey.

    jonasnick commented at 7:46 pm on October 24, 2019:
    I think this was just nice to know. Will remove

    jonasnick commented at 8:34 pm on October 24, 2019:
    removed
  106. in include/secp256k1.h:791 in 2e4ed392e1 outdated
    786+ *           0 otherwise
    787+ *
    788+ *  Args:         ctx: pointer to a context object
    789+ *  Out: xonly_pubkey: pointer to an x-only public key object for placing the
    790+ *                     converted public key (cannot be NULL)
    791+ *              sign: sign bit of the pubkey. Can be used to reconstruct a
    


    sipa commented at 10:39 pm on October 17, 2019:

    “sign” is probably confusing when talking about signatures.

    Also, is this needed? Ideally the conversion from pubkey to xonly_pubkey was one-way.


    jonasnick commented at 7:55 pm on October 24, 2019:
    If anything should be is_positive.

    jonasnick commented at 8:02 pm on October 24, 2019:
    Even if it’s one way you need to know the sign bit for a script spend to provide everything necessary for verification?
  107. in include/secp256k1.h:869 in 2e4ed392e1 outdated
    860+ *  Out:  output_pubkey: pointer to a public key object (cannot be NULL)
    861+ *  In: internal_pubkey: pointer to an x-only public key object to apply the
    862+ *                       tweak to (cannot be NULL)
    863+ *              tweak32: pointer to a 32-byte tweak (cannot be NULL)
    864+ */
    865+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_tweak_add(
    


    sipa commented at 10:45 pm on October 17, 2019:

    I think this function should input an xonly_pubkey, and output an xonly_pubkey plus a negation/sign/square/negative/… bit, which is sufficient for verifying it efficiently. @real-or-random suggests seeing it as a commitment scheme and having a function that takes as input xonly_pubkey + message (+ tag?), and outputs an xonly_pubkey (the commitment) and a boolean (the opening). And then a verify function that takes the commitment, point, message, opening and returns true or false. The downside would be that the hashing needs to happen inside libsecp256k1, where it’s harder to take advantage of efficient hashing functions.

    I think the best approach right now is having tweak_add/tweak_add_verify functions working on xonly_pubkeys/bools, and then seeing if we can e.g. make the caller pass in a SHA256 implementation etc. At that point we could start thinking about having more high-level operations like taproot_commitment/verify and bip32_derive etc instead of the rather low-level “tweak” style functions.


    jonasnick commented at 7:41 pm on October 24, 2019:

    I’m in favor of high level operations which is why my other open pull requests have similar commitment APIs to what real-or-random suggests. For this PR I had a different mindset, because when I brought up high level functions you mentioned “ec_pubkey_tweak is perfect” which was a misunderstanding apparently.

    Having tweak_add/tweak_add_verify functions working on xonly_pubkeys/bools is what I started out with, but then decided against that because 1) the output of tweak_add is a secp_pubkey, so returning a pubkey is a better separation of layers, 2) the user otherwise has to deal with two pieces of data of data 3) musig_pubkey_combine takes a tweak as well, and would either have to get an additional out argument with the sign or would work differently than tweak_add and return a pubkey.

    However, if we want to add a high level API anyway, I’m happy with changing this again to the more hacky version if it’s more straightforward to use in your taproot implementation.


    sipa commented at 7:58 pm on October 24, 2019:
    I see the burden of forcing the user to deal with two pieces of data in some cases, but I think it’s justified. The BIPs do not mention any conversion from 32byte-pubkeys to point-pubkeys, only the other way around.

    sipa commented at 7:59 pm on October 24, 2019:
    Yeah, I think I changed my mind after seeing the complexity necessary to have both close-to-optimal performance and an intermediate-layer level API.

    jonasnick commented at 12:43 pm on October 29, 2019:
    Just to clarify, what are the opinions you changed your mind from and to?
  108. in include/secp256k1.h:865 in 2e4ed392e1 outdated
    881+ *  In:   output_pubkey: pointer to a public key object (cannot be NULL)
    882+ *      internal_pubkey: pointer to an x-only public key object to apply the
    883+ *                       tweak to (cannot be NULL)
    884+ *              tweak32: pointer to a 32-byte tweak (cannot be NULL)
    885+ */
    886+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_tweak_verify(
    


    sipa commented at 11:02 pm on October 17, 2019:
    I’m currently using this function in the taproot reference branch (see https://github.com/sipa/bitcoin/commit/9715fa5280404812ab3766de4ee23e1bde5a0fb3#diff-2c33d0ff0ed53b0902e4af9a745a152fR185-R186) which is kind of annoying to roundtrip through pubkey. What about instead having it take an input and output xonly_pubkey and a boolean?
  109. in include/secp256k1_schnorrsig.h:87 in 2e4ed392e1 outdated
    82+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
    83+
    84+/** Verify a Schnorr signature.
    85+ *
    86+ *  Returns: 1: correct signature
    87+ *           0: incorrect or unparseable signature
    


    sipa commented at 11:08 pm on October 17, 2019:
    What would unparseable mean in this context, as you’re passing in an already-parsed signature?

    jonasnick commented at 7:57 pm on October 24, 2019:
    You should ask that yourself because this was just copied from ecdsa_sign and you’re shown as the author of that line ;) Perhaps just incorrect is sufficient.

    jonasnick commented at 8:34 pm on October 24, 2019:
    removed “unparseable”
  110. in include/secp256k1_schnorrsig.h:106 in 2e4ed392e1 outdated
    101+ *
    102+ * Returns 1 if all succeeded, 0 otherwise. In particular, returns 1 if n_sigs is 0.
    103+ *
    104+ *  Args:    ctx: a secp256k1 context object, initialized for verification.
    105+ *       scratch: scratch space used for the multiexponentiation
    106+ *  In:      sig: array of signatures, or NULL if there are no signatures
    


    sipa commented at 11:10 pm on October 17, 2019:
    Maybe clarify that these are pointers to arrays of pointers to signatures (and not pointers to arrays of signatures)? Likewise for the other arguments below.

    jonasnick commented at 8:34 pm on October 24, 2019:
    fixed
  111. in include/secp256k1_schnorrsig.h:110 in 2e4ed392e1 outdated
    105+ *       scratch: scratch space used for the multiexponentiation
    106+ *  In:      sig: array of signatures, or NULL if there are no signatures
    107+ *         msg32: array of messages, or NULL if there are no signatures
    108+ *            pk: array of x-only public keys, or NULL if there are no signatures
    109+ *        n_sigs: number of signatures in above arrays. Must be smaller than
    110+ *                2^31 and smaller than half the maximum size_t value. Must be 0
    


    sipa commented at 11:15 pm on October 17, 2019:
    Maybe say “Must be below the minimum of 2^31 and SIZE_MAX/2”?

    jonasnick commented at 8:33 pm on October 24, 2019:
    fixed
  112. in src/hash_impl.h:167 in 2e4ed392e1 outdated
    162@@ -163,6 +163,20 @@ static void secp256k1_sha256_finalize(secp256k1_sha256 *hash, unsigned char *out
    163     memcpy(out32, (const unsigned char*)out, 32);
    164 }
    165 
    166+/* Initializes a sha256 struct and writes the 64 byte string
    167+ * SHA256(tag)||SHA256(tag) into it. The taglen should be less than or equal to
    


    sipa commented at 11:19 pm on October 17, 2019:
    Why should the tag be at most 64 bytes long?

    jonasnick commented at 8:33 pm on October 24, 2019:
    because brainfart, fixed
  113. in src/modules/schnorrsig/main_impl.h:95 in 2e4ed392e1 outdated
    81+    if (!secp256k1_fe_is_quad_var(&pk.y)) {
    82+        secp256k1_scalar_negate(&x, &x);
    83+    }
    84+
    85+    if (!noncefp(buf, msg32, seckey, (unsigned char *) "BIPSchnorrDerive", (void*)ndata, 0)) {
    86+        return 0;
    


    sipa commented at 11:31 pm on October 17, 2019:
    No wiping of sig here?

    jonasnick commented at 8:33 pm on October 24, 2019:
    I guess you mean secret key, fixed

    jonasnick commented at 8:38 pm on October 24, 2019:
    Oh I guess you mean both
  114. in src/modules/schnorrsig/main_impl.h:103 in 2e4ed392e1 outdated
    85+    if (!noncefp(buf, msg32, seckey, (unsigned char *) "BIPSchnorrDerive", (void*)ndata, 0)) {
    86+        return 0;
    87+    }
    88+    secp256k1_scalar_set_b32(&k, buf, NULL);
    89+    if (secp256k1_scalar_is_zero(&k)) {
    90+        return 0;
    


    sipa commented at 11:35 pm on October 17, 2019:

    No wiping of sig here?

    (“this can’t happen” is a good answer)


    jonasnick commented at 8:33 pm on October 24, 2019:
    fixed

    jonasnick commented at 8:33 pm on October 24, 2019:
    fixed
  115. in src/modules/schnorrsig/main_impl.h:206 in 2e4ed392e1 outdated
    205+         * consecutive tuples before we need to call the RNG for new randomizers:
    206+         * (-randomizer_cache[0], R1)
    207+         * (-randomizer_cache[0]*e1, P1)
    208+         * (-randomizer_cache[1], R2)
    209+         * (-randomizer_cache[1]*e2, P2) */
    210+        secp256k1_scalar_chacha20(&ecmult_context->randomizer_cache[0], &ecmult_context->randomizer_cache[1], ecmult_context->chacha_seed, idx / 4);
    


    sipa commented at 11:50 pm on October 17, 2019:
    It seems you’re computing the randomization factors twice (once for s, once for R/P). Do you know how much time is spent on that? Perhaps it is worth caching them instead.

    jonasnick commented at 12:59 pm on October 29, 2019:
    about 0.42% of time spent in secp256k1_schnorrsig_verify_batch is spent in chacha during the schnorrsig batch benchmarks.

    jonasnick commented at 0:40 am on November 2, 2019:
    I think that’s fine for this PR. Thoughts?

    sipa commented at 0:43 am on November 2, 2019:
    Yeah, I think that’s fine.
  116. jonasnick force-pushed on Oct 24, 2019
  117. jonasnick force-pushed on Oct 24, 2019
  118. in src/secp256k1.c:823 in e28b61c290 outdated
    769+    ARG_CHECK(input32 != NULL);
    770+
    771+    buf[0] = SECP256K1_TAG_PUBKEY_EVEN;
    772+    memcpy(&buf[1], input32, 32);
    773+    if (!secp256k1_ec_pubkey_parse(ctx, (secp256k1_pubkey *) pubkey, buf, sizeof(buf))) {
    774+        return 0;
    


    roconnor-blockstream commented at 2:37 am on October 29, 2019:

    As it stands, if secp256k1_xonly_pubkey_parse fails, it will leave the pubkey value in a invalid state. If the user of the this API were to ignore the return value of secp256k1_xonly_pubkey_parse and proceeded to call secp256k1_schnorrsig_verify, the verification could, perhaps, succeed.

    However if we were to add a line such as memset(pubkey, 0, sizeof(secp256k1_xonly_pubkey); here, then pubkey would be initialized to a value that denotes a point with a zero x-coordinate. The current implementation of secp256k1_schnorrsig_verify calls secp256k1_pubkey_load which explicitly fails when passed a pubkey point with a zero x-coordinate, which in turn would cause secp256k1_schnorrsig_verify to unconditionally fail, even if the result of secp256k1_xonly_pubkey_parse were ignored. (Not only does secp256k1_pubkey_load unconditionally fail, but it also calls ctx->illegal_callback).


    real-or-random commented at 8:14 am on October 29, 2019:
    This is a valid concern. But secp256k1_ec_pubkey_parse does that already: https://github.com/bitcoin-core/secp256k1/blob/e541a90ef6461007d9c6a74b9f9a7fb8aa34aaa8/src/secp256k1.c#L256

    jonasnick commented at 8:36 am on October 29, 2019:
    I added a commit with an explicit test that the pubkey is zeroized.

    roconnor-blockstream commented at 3:27 pm on October 29, 2019:
    @real-or-random Thanks. I missed the indirection in secp256k1_ec_pubkey_parse between Q and pubkey.
  119. real-or-random commented at 8:18 am on October 29, 2019: contributor

    Just a note: The existing pubkey functions all have an ec_ pubkey prefix but the xonly_ do not.

    We could add the ec_ there too, just for consistency. However, I think it would be better to remove the ec_ entirely but it’s too late for this now…. So I think just xonly_ is fine exactly because it does not have this annoying prefix.

  120. jonasnick commented at 8:35 am on October 29, 2019: contributor

    The existing pubkey functions all have an ec_ pubkey prefix but the xonly_ do not.

    Yes, this is on purpose and I agree that this is fine.

  121. jonasnick commented at 0:52 am on November 2, 2019: contributor

    I added a few commits addressing the feedback to

    1. change the tweak api from pubkey to xonly_pubkey and is_positive bit (which reduces taproot test loc quite a bit).
    2. remove xonly to pubkey conversion. Not necessary anymore with the new tweak api

    Also, there was a mismatch with the spec where we had fed the non-negated seckey into the nonce function (to be clear, not a security problem). I also updated test vectors.

    I left the pubkey to xonly pubkey conversion. That’s right now the only efficient way to tweak a seckey and get a corresponding xonly pubkey (with is_positive to allow verification):

    0secp256k1_xonly_privkey_tweak_add(ctx, sk, tweak);
    1secp256k1_ec_pubkey_create(ctx, &xy_pk, sk);
    2secp256k1_xonly_pubkey_from_pubkey(ctx, &output_pk, &is_positive, &xy_pk);
    

    Alternatively we could offer an secp256k1_xonly_pubkey_create_tweaked that in addition to the secret key takes a tweak and returns tweaked seckey and pubkey.

  122. jonasnick commented at 7:59 pm on November 2, 2019: contributor
    Travis fails because of a single gcc test (HOST=i686-linux-gnu ENDOMORPHISM=yes). I’ve done a ./tests 100 iterations locally and wasn’t able to reproduce this so I’m guessing it’s just an accident. I opened #685 to improve reproducibility in the future.
  123. jonasnick commented at 10:31 pm on November 3, 2019: contributor
    There was in fact an issue: uninitialized memory in a newly added test. Should have valground. Fixed now.
  124. jonasnick cross-referenced this on Nov 4, 2019 from issue Add memory sanitizer test to travis by jonasnick
  125. jonasnick commented at 12:55 pm on November 5, 2019: contributor
    Replaced positive/negative point terminology to has_square_y.
  126. ChristopherA cross-referenced this on Nov 7, 2019 from issue hal & BIP-Schnorr? by ChristopherA
  127. ChristopherA cross-referenced this on Nov 7, 2019 from issue x-only BIP-Schnorr? by ChristopherA
  128. jonasnick force-pushed on Nov 14, 2019
  129. jonasnick force-pushed on Nov 14, 2019
  130. jonasnick commented at 1:56 pm on November 14, 2019: contributor

    Squashed the fixup commits and made a few more logical commits.

    0Add xonly_pubkeys which are serialized as 32 byte and whose Y coordinate is a quadratic residue
    1Add tweak functions for xonly_pubkeys that allow to add a tweak to a
    2Add chacha20 function
    3Add initialize_tagged to sha256 which initializes and writes the 64 byte string SHA256(tag)||SHA256(tag) into it.
    4Add schnorrsig module which implements BIP-schnorr [0] compatible signing, verification and batch verification
    5Add taproot test case to schnorrsig module
    

    Also rebased on master to fix conflicts and get travis to work again. There were no code changes except two typo fixes.

  131. jonasnick cross-referenced this on Nov 15, 2019 from issue Update Schnorrsig and MuSig by jonasnick
  132. in include/secp256k1.h:870 in 19c144ccca outdated
    865+ *         has_square_y: 1 if output_pubkey has a square Y coordinate and 0 otherwise.
    866+ *      internal_pubkey: pointer to an x-only public key object to apply the
    867+ *                       tweak to (cannot be NULL)
    868+ *              tweak32: pointer to a 32-byte tweak (cannot be NULL)
    869+ */
    870+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_tweak_verify(
    


    elichai commented at 9:28 am on November 17, 2019:

    I don’t know about this name, people might think it’s actually verifying some commitment which it is not. the commitment is a valid one iff it is P + H(P||C)G. otherwise it’s a regular tweak add which there’s nothing to “verify” about.

    right now that it’s only doing memcmp maybe it’s kinda useless and should be done in core? (I’d agree that if you use gej<->gej comparison than maybe the speed benefit is worth it but that’s not what happening here)

    Though I see that I might be overthinking this.


    jonasnick commented at 7:42 pm on November 18, 2019:

    right now that it’s only doing memcmp maybe it’s kinda useless and should be done in core?

    Also comparing has_square_y. I think we should keep that function, but you’re right about the naming. I renamed it from verify to test and added a note in the doc.

    I think we should definitely make use of avoiding the conversion to affine but not in this PR because there doesn’t seem to be a very concise way to do this.

  133. in src/modules/schnorrsig/main_impl.h:109 in 19c144ccca outdated
    40+    sha->s[4] = 0x814b9e66ul;
    41+    sha->s[5] = 0x0469e801ul;
    42+    sha->s[6] = 0x83909280ul;
    43+    sha->s[7] = 0xb329e454ul;
    44+    sha->bytes = 64;
    45+}
    


    elichai commented at 9:46 am on November 17, 2019:

    Tested locally with the following logic:

    0let mut hash = Sha256::new();
    1hash.input(b"BIPSchnorr");
    2let schnorr = hash.finalize();
    3let mut hash = Sha256::new();
    4hash.input(&schnorr);
    5hash.input(&schnorr);
    6hash.process_current_block();
    7for i in &hash.hash {
    8    println!("0x{:08x}", i);
    9}
    

    ACK on the resulting midstate

    00x048d9a59
    10xfe39fb05
    20x28479648
    30xe4a660f9
    40x814b9e66
    50x0469e801
    60x83909280
    70xb329e454
    

    jonasnick commented at 7:43 pm on November 18, 2019:
    Thanks. Also, otherwise the test vectors would fail.
  134. in src/modules/schnorrsig/main_impl.h:93 in 19c144ccca outdated
    88+        memset(sig, 0, sizeof(*sig));
    89+        memset(seckey_tmp, 0, sizeof(seckey_tmp));
    90+        secp256k1_scalar_clear(&x);
    91+        return 0;
    92+    }
    93+    memset(seckey_tmp, 0, sizeof(seckey_tmp));
    


    elichai commented at 10:05 am on November 17, 2019:
    would be nicer if this was at the end, together with the rest of the memory cleaning calls

    elichai commented at 10:10 am on November 17, 2019:
    oh I see that if you move it you’ll need to also duplicate this into if (secp256k1_scalar_is_zero(&k)) {
  135. in src/secp256k1.c:765 in 19c144ccca outdated
    760+    ARG_CHECK(pubkey != NULL);
    761+    ARG_CHECK(input32 != NULL);
    762+
    763+    buf[0] = SECP256K1_TAG_PUBKEY_EVEN;
    764+    memcpy(&buf[1], input32, 32);
    765+    if (!secp256k1_ec_pubkey_parse(ctx, (secp256k1_pubkey *) pubkey, buf, sizeof(buf))) {
    


    elichai commented at 10:44 am on November 17, 2019:

    Isn’t it better to use secp256k1_ge_set_xquad directly? that way there’s no need to use secp256k1_ec_pubkey_absolute because it is already a square root. (right now there’s a bunch of redundant negations and conversations if the quad residue isn’t even)

    and you could use the 32 byte buffer directly as your TODO says :)


    jonasnick commented at 7:44 pm on November 18, 2019:
    You’re right. I wrote that code when the tie breaker was still evenness and forgot that that’s the reason why it uses the normal parse function. I fixed this. Much cleaner now.
  136. elichai commented at 11:41 am on November 17, 2019: contributor

    Reviewed everything except batch logic + tests + benchmarks. will review later.

    one note is can we be a bit consistent on privkey/seckey? #670

  137. in src/secp256k1.c:829 in 41b7be1dac outdated
    773-    secp256k1_ec_pubkey_absolute(ctx, (secp256k1_pubkey *) pubkey, NULL);
    774+    if (!secp256k1_ge_set_xquad(&Q, &x)) {
    775+        return 0;
    776+    }
    777+    secp256k1_pubkey_save((secp256k1_pubkey *) pubkey, &Q);
    778+    secp256k1_ge_clear(&Q);
    


    elichai commented at 9:18 pm on November 18, 2019:
    nit, I don’t think there’s a need to clear it. (isn’t worth another force push though so unless you’re changing more stuff then it doesn’t matter)
  138. in src/secp256k1.c:844 in 41b7be1dac outdated
    795+    if (!secp256k1_pubkey_load(ctx, &Q, (secp256k1_pubkey *) pubkey)) {
    796         return 0;
    797     }
    798-    memcpy(output32, &buf[1], 32);
    799+    secp256k1_fe_normalize_var(&Q.x);
    800+    secp256k1_fe_get_b32(output32, &Q.x);
    


    elichai commented at 9:22 pm on November 18, 2019:
    Nice :)
  139. in src/secp256k1.c:839 in 41b7be1dac outdated
    835@@ -836,7 +836,7 @@ int secp256k1_xonly_pubkey_tweak_add(const secp256k1_context* ctx, secp256k1_xon
    836     return secp256k1_xonly_pubkey_from_pubkey(ctx, output_pubkey, has_square_y, &pubkey_tmp);
    837 }
    838 
    839-int secp256k1_xonly_pubkey_tweak_verify(const secp256k1_context* ctx, const secp256k1_xonly_pubkey *output_pubkey, int has_square_y, const secp256k1_xonly_pubkey *internal_pubkey, const unsigned char *tweak32) {
    840+int secp256k1_xonly_pubkey_tweak_test(const secp256k1_context* ctx, const secp256k1_xonly_pubkey *output_pubkey, int has_square_y, const secp256k1_xonly_pubkey *internal_pubkey, const unsigned char *tweak32) {
    


    sipa commented at 0:11 am on November 19, 2019:
    You should also rename the defintion in secp256k1.h for this function.

    jonasnick commented at 9:42 am on November 19, 2019:
    oh, fixed
  140. in include/secp256k1.h:753 in a5f6b33704 outdated
    748+    secp256k1_xonly_pubkey* pubkey,
    749+    const unsigned char *input32
    750+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
    751+
    752+/** Serialize an xonly_pubkey object into a 32-byte sequence. Use the
    753+ *  SECP256K1_LEN_XONLY_PUBKEY macro if your want to avoid the magic number 32.
    


    afk11 commented at 3:01 pm on November 22, 2019:
    -your +you

    jonasnick commented at 10:32 pm on November 26, 2019:
    fixed
  141. in src/group.h:59 in a87a0576dc outdated
    52@@ -53,6 +53,11 @@ static int secp256k1_ge_set_xquad(secp256k1_ge *r, const secp256k1_fe *x);
    53  *  for Y. Return value indicates whether the result is valid. */
    54 static int secp256k1_ge_set_xo_var(secp256k1_ge *r, const secp256k1_fe *x, int odd);
    55 
    56+/* Converts group element into its "absolute" value. That means it is kept as
    57+ * is if it has a square Y and otherwise negated. has_square_y is set to 1 in
    58+ * the former case and to 0 in the latter case. */
    59+static void secp256k1_ge_absolute(secp256k1_ge *r, int *has_square_y);
    


    real-or-random commented at 4:28 pm on November 25, 2019:
    had_square_y?

    jonasnick commented at 10:28 pm on November 26, 2019:
    is_negated
  142. in src/tests.c:4434 in a8686b9670 outdated
    4374+    CHECK(secp256k1_xonly_pubkey_parse(none, &pk, ones32) == 0);
    4375+    CHECK(ecount == 2);
    4376+    /* There's no point with x-coordinate 0 on secp256k1 */
    4377+    CHECK(secp256k1_xonly_pubkey_parse(none, &pk, zeros32) == 0);
    4378+    CHECK(ecount == 2);
    4379+    CHECK(secp256k1_xonly_pubkey_parse(none, &pk, buf32) == 1);
    


    real-or-random commented at 4:39 pm on November 25, 2019:
    CHECK(ecount == 2); too?

    jonasnick commented at 10:29 pm on November 26, 2019:
    ecount can only be changed through illegal or error callbacks which should always entail return value of 0.
  143. in src/tests.c:4421 in a36d45d597 outdated
    4372+        memset(&pk_tmp, 0, sizeof(pk_tmp));
    4373+        CHECK(secp256k1_xonly_pubkey_serialize(none, buf32, &pk_tmp) == 0);
    4374+    }
    4375+    /* pubkey_load called illegal callback */
    4376+    CHECK(ecount == 3);
    4377+    CHECK(secp256k1_xonly_pubkey_serialize(none, buf32, &pk) == 1);
    


    real-or-random commented at 4:42 pm on November 25, 2019:
    CHECK(ecount == 3);
  144. in include/secp256k1.h:857 in 41b7be1dac outdated
    851@@ -852,8 +852,11 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_tweak_add(
    852     const unsigned char *tweak32
    853 ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
    854 
    855-/** Verifies that output_pubkey and has_square_y is the result of calling
    856- *  secp256k1_xonly_pubkey_tweak_add with internal_pubkey and tweak32.
    857+/** Tests that output_pubkey and has_square_y is the result of calling
    858+ *  secp256k1_xonly_pubkey_tweak_add with internal_pubkey and tweak32. Note
    859+ *  that this alone does _not_ verify anything cryptographically. If the tweak
    


    real-or-random commented at 4:44 pm on November 25, 2019:

    I don’t know that “anything cryptographically” is. This sentence is hard to understand.

    “Note that this does not verify that tweak32 is constructed in a specific way, e.g., as the hash of internal_pubkey and some message.” “Note that this does not verify that output_pubkey is a commitment to some message.”


    jonasnick commented at 10:32 pm on November 26, 2019:
    fixed
  145. real-or-random commented at 4:55 pm on November 25, 2019: contributor
    f’s look good :)
  146. jonasnick commented at 10:33 pm on November 26, 2019: contributor
    @real-or-random or @elichai may I ask you review the new fixup commits? Will squash and rebase then.
  147. in src/modules/schnorrsig/main_impl.h:229 in c1b1bbca75 outdated
    222@@ -223,6 +223,10 @@ static int secp256k1_schnorrsig_verify_batch_ecmult_callback(secp256k1_scalar *s
    223         secp256k1_sha256 sha;
    224 
    225         if (!secp256k1_xonly_pubkey_load(ecmult_context->ctx, pt, ecmult_context->pk[idx / 2])) {
    226+            /* Logically unreachable because verify_batch_init_randomizer calls
    227+             * secp256k1_ec_pubkey_serialize which only works if loading the
    228+             * pubkey into a group element succeeds.*/
    229+            VERIFY_CHECK(0);
    


    real-or-random commented at 11:05 pm on November 26, 2019:
    Would be more natural to VERIFY_CHECK the result of the function call without an if.

    jonasnick commented at 10:46 am on November 27, 2019:
    fixed
  148. in src/secp256k1.c:874 in a8bd8df5f4 outdated
    822@@ -822,9 +823,10 @@ int secp256k1_xonly_privkey_tweak_add(const secp256k1_context* ctx, unsigned cha
    823     }
    824     secp256k1_pubkey_load(ctx, &ge, &pubkey);
    825     if (!secp256k1_fe_is_quad_var(&ge.y)) {
    826-        if (!secp256k1_ec_privkey_negate(ctx, seckey32)) {
    827-            return 0;
    828-        }
    829+        secp256k1_scalar_set_b32(&sec, seckey32, NULL);
    


    real-or-random commented at 11:08 pm on November 26, 2019:
    Maybe explain why it’s okay to ignore the overflow.

    jonasnick commented at 10:46 am on November 27, 2019:
    fixed
  149. in include/secp256k1.h:860 in a6f5245060 outdated
    858@@ -859,9 +859,9 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_tweak_add(
    859 
    860 /** Tests that output_pubkey and is_negated is the result of calling
    


    real-or-random commented at 11:14 pm on November 26, 2019:

    s/is the result/are the results well, maybe better say

    “Tests whether a pair (output_pubkey, is_negated) is the result of calling” or similar. I think that’s more readable even though there are no actual pairs in C.


    jonasnick commented at 10:46 am on November 27, 2019:
    fixed
  150. real-or-random commented at 11:15 pm on November 26, 2019: contributor
    The fixups look good otherwise. Needs rebase.
  151. in src/modules/schnorrsig/main_impl.h:163 in a74a0132e4 outdated
    159+    }
    160+
    161     secp256k1_schnorrsig_sha256_tagged(&sha);
    162     secp256k1_sha256_write(&sha, &sig->data[0], 32);
    163-    secp256k1_xonly_pubkey_serialize(ctx, buf, pk);
    164+    secp256k1_fe_normalize_var(&pk.x);
    


    elichai commented at 4:32 pm on December 2, 2019:
    I think this normalize isn’t needed? (because it was just deserialized, and when serializing you have to normalize it to avoid malleabilities.)

    jonasnick commented at 11:24 pm on December 2, 2019:
    Indeed. Pubkeys are always normalized. Fixed.
  152. in src/modules/schnorrsig/main_impl.h:230 in a74a0132e4 outdated
    226+            return 0;
    227+        }
    228         secp256k1_schnorrsig_sha256_tagged(&sha);
    229         secp256k1_sha256_write(&sha, &ecmult_context->sig[idx / 2]->data[0], 32);
    230-        secp256k1_xonly_pubkey_serialize(ecmult_context->ctx, buf, ecmult_context->pk[idx / 2]);
    231+        secp256k1_fe_normalize_var(&pt->x);
    


    elichai commented at 4:34 pm on December 2, 2019:
    same?
  153. jonasnick force-pushed on Dec 2, 2019
  154. jonasnick commented at 11:27 pm on December 2, 2019: contributor
    Squashed and rebased
  155. jonasnick referenced this in commit 6afc7b23e8 on Dec 3, 2019
  156. jonasnick referenced this in commit 2c3910057d on Dec 3, 2019
  157. jonasnick force-pushed on Dec 3, 2019
  158. jonasnick force-pushed on Dec 3, 2019
  159. jonasnick commented at 9:33 pm on December 3, 2019: contributor
    Had to force push to remove garbage in the commit messages.
  160. jonasnick referenced this in commit 562ccf41c3 on Dec 3, 2019
  161. in include/secp256k1.h:853 in 1901f3bf9c outdated
    848+    const secp256k1_context* ctx,
    849+    secp256k1_xonly_pubkey *output_pubkey,
    850+    int *is_negated,
    851+    const secp256k1_xonly_pubkey *internal_pubkey,
    852+    const unsigned char *tweak32
    853+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
    


    elichai commented at 2:45 pm on December 11, 2019:

    How do people feel about promising that it’s fine for output_pubkey and internal_pubkey to point to the same object? Right now it will work. but a lot of our APIs start by zeroing out the output object. and then this isn’t fine anymore.

    On one hand it’s nice to not return uninitialized objects. on the other a lot of times you want to tweak the key in place.


    jonasnick commented at 9:22 pm on December 11, 2019:

    Right now it will work. but a lot of our APIs start by zeroing out the output object. and then this isn’t fine anymore.

    I don’t see why. secp256k1_ec_pubkey_tweak_add zeroes the key and is in place. Actually we should also zeroize the output key in this function.

    Also, that xonly_pubkey_tweak_add does not work in place (and therefore differs from ec_pubkey_tweak_add) is just for historical reasons (initially output_pubkey was a different type than internal_pubkey, namely secp256k1_pubkey). How do people feel about changing the function to tweak the pubkey in-place? I think I prefer that because otherwise it’s inconsistent with pubkey_tweak_add.


    jonasnick commented at 9:35 am on December 17, 2019:
    Okay, tweak_add changes the pubkey in-place now. And added a test that the pubkey is zeroed on failure.

    gmaxwell commented at 7:34 am on January 22, 2020:

    The reason for zeroizing the outputs is mostly so that if the caller doesn’t know errors need to be handled, that the output is invalid so that hopefully later error handling will do something useful. This is especially a concern because errors should never be seen without crafted inputs, and it’s plausible a caller simply won’t know they’re possible at all. This isn’t incompatible with in-place operation. (though not-in-place operation is arguably riskier to not zero because it might expose the content of random secrets if the caller ignores the error).

    Zeroizing also makes programming errors more likely to get detected faster, e.g. if the function is called with an invalid output pointer but also an invalid input, if it zeroizes it’s more likely to cause a valgrind error and/or program crash rather than silently being broken.

  162. jonasnick referenced this in commit a654ee5ebc on Dec 12, 2019
  163. jonasnick referenced this in commit a76523297b on Dec 12, 2019
  164. landanhu cross-referenced this on Dec 29, 2019 from issue Problem: rust-secp256k1 fork diverged from upstream by landanhu
  165. sipa cross-referenced this on Jan 21, 2020 from issue Implement BIP 340-342 validation (Schnorr/taproot/tapscript) by sipa
  166. in include/secp256k1.h:536 in df8c698793 outdated
    531+ * When this function is used in ecdsa_sign, it generates a nonce using an
    532+ * analogue of the bip-schnorr nonce generation algorithm, but with tag
    533+ * "BIPSchnorrNULL" instead of "BIPSchnorrDerive".
    534+ * The attempt argument must be 0 or the function will fail and return 0.
    535+ */
    536+SECP256K1_API extern const secp256k1_nonce_function secp256k1_nonce_function_bipschnorr;
    


    MaxHillebrand commented at 8:23 pm on January 22, 2020:
    0SECP256K1_API extern const secp256k1_nonce_function secp256k1_nonce_function_bip340;
    
  167. in include/secp256k1_schnorrsig.h:72 in df8c698793 outdated
    67+ * Returns 1 on success, 0 on failure.
    68+ *  Args:    ctx: pointer to a context object, initialized for signing (cannot be NULL)
    69+ *  Out:     sig: pointer to the returned signature (cannot be NULL)
    70+ *  In:    msg32: the 32-byte message being signed (cannot be NULL)
    71+ *        seckey: pointer to a 32-byte secret key (cannot be NULL)
    72+ *       noncefp: pointer to a nonce generation function. If NULL, secp256k1_nonce_function_bipschnorr is used
    


    MaxHillebrand commented at 8:23 pm on January 22, 2020:
    0 *       noncefp: pointer to a nonce generation function. If NULL, secp256k1_nonce_function_bip340 is used
    
  168. in src/modules/schnorrsig/main_impl.h:67 in df8c698793 outdated
    62+    ARG_CHECK(sig != NULL);
    63+    ARG_CHECK(msg32 != NULL);
    64+    ARG_CHECK(seckey != NULL);
    65+
    66+    if (noncefp == NULL) {
    67+        noncefp = secp256k1_nonce_function_bipschnorr;
    


    MaxHillebrand commented at 8:23 pm on January 22, 2020:
    0        noncefp = secp256k1_nonce_function_bip340;
    
  169. in src/secp256k1.c:418 in df8c698793 outdated
    412@@ -413,6 +413,54 @@ static SECP256K1_INLINE void buffer_append(unsigned char *buf, unsigned int *off
    413     *offset += len;
    414 }
    415 
    416+/* Initializes SHA256 with fixed midstate. This midstate was computed by applying
    417+ * SHA256 to SHA256("BIPSchnorrDerive")||SHA256("BIPSchnorrDerive"). */
    418+static void secp256k1_nonce_function_bipschnorr_sha256_tagged(secp256k1_sha256 *sha) {
    


    MaxHillebrand commented at 8:24 pm on January 22, 2020:
    0static void secp256k1_nonce_function_bip340_sha256_tagged(secp256k1_sha256 *sha) {
    
  170. in src/secp256k1.c:433 in df8c698793 outdated
    428+    sha->bytes = 64;
    429+}
    430+
    431+/* This nonce function is described in BIP-schnorr
    432+ * (https://github.com/sipa/bips/blob/bip-schnorr/bip-schnorr.mediawiki) */
    433+static int nonce_function_bipschnorr(unsigned char *nonce32, const unsigned char *msg32, const unsigned char *key32, const unsigned char *algo16, void *data, unsigned int counter) {
    


    MaxHillebrand commented at 8:24 pm on January 22, 2020:
    0static int nonce_function_bip340(unsigned char *nonce32, const unsigned char *msg32, const unsigned char *key32, const unsigned char *algo16, void *data, unsigned int counter) {
    
  171. in src/secp256k1.c:444 in df8c698793 outdated
    439+    /* Tag the hash with algo16 which is important to avoid nonce reuse across
    440+     * algorithms. If the this nonce function is used in BIP-schnorr signing as
    441+     * defined in the spec, an optimized tagging implementation is used. */
    442+    if (algo16 != NULL) {
    443+        if (memcmp(algo16, "BIPSchnorrDerive", 16) == 0) {
    444+            secp256k1_nonce_function_bipschnorr_sha256_tagged(&sha);
    


    MaxHillebrand commented at 8:24 pm on January 22, 2020:
    0            secp256k1_nonce_function_bip340_sha256_tagged(&sha);
    
  172. in src/secp256k1.c:494 in df8c698793 outdated
    490@@ -443,6 +491,7 @@ static int nonce_function_rfc6979(unsigned char *nonce32, const unsigned char *m
    491    return 1;
    492 }
    493 
    494+const secp256k1_nonce_function secp256k1_nonce_function_bipschnorr = nonce_function_bipschnorr;
    


    MaxHillebrand commented at 8:25 pm on January 22, 2020:
    0const secp256k1_nonce_function secp256k1_nonce_function_bip340 = nonce_function_bip340;
    
  173. in include/secp256k1.h:718 in df8c698793 outdated
    712@@ -701,6 +713,170 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_combine(
    713     size_t n
    714 ) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
    715 
    716+/** Opaque data structure that holds a parsed and valid "x-only" public key.
    717+ *  An x-only pubkey encodes a point whose Y coordinate is square. It is
    718+ *  serialized using only its X coordinate (32 bytes). See bip-schnorr for more
    


    MaxHillebrand commented at 8:25 pm on January 22, 2020:
    0 *  serialized using only its X coordinate (32 bytes). See BIP-340 for more
    
  174. in include/secp256k1_schnorrsig.h:11 in df8c698793 outdated
     6+#ifdef __cplusplus
     7+extern "C" {
     8+#endif
     9+
    10+/** This module implements a variant of Schnorr signatures compliant with
    11+ * BIP-schnorr
    


    MaxHillebrand commented at 8:26 pm on January 22, 2020:
    0 * BIP-340
    
  175. in include/secp256k1_schnorrsig.h:12 in df8c698793 outdated
     7+extern "C" {
     8+#endif
     9+
    10+/** This module implements a variant of Schnorr signatures compliant with
    11+ * BIP-schnorr
    12+ * (https://github.com/sipa/bips/blob/bip-schnorr/bip-schnorr.mediawiki).
    


    MaxHillebrand commented at 8:27 pm on January 22, 2020:
    0 * (https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki).
    

    This link is broken until the PR is merged.

  176. in src/modules/schnorrsig/main_impl.h:33 in df8c698793 outdated
    28+    memcpy(sig->data, in64, 64);
    29+    return 1;
    30+}
    31+
    32+/* Initializes SHA256 with fixed midstate. This midstate was computed by applying
    33+ * SHA256 to SHA256("BIPSchnorr")||SHA256("BIPSchnorr"). */
    


    MaxHillebrand commented at 8:29 pm on January 22, 2020:
    I think the tagged hashes must not be renamed to BIP340, so I will not suggest the edit, but please confirm that BIPSchnorr is the proper way.

    sipa commented at 8:30 pm on January 22, 2020:
    Yes, that’s what the spec says.
  177. in src/secp256k1.c:431 in df8c698793 outdated
    426+    sha->s[6] = 0x17119b2eul;
    427+    sha->s[7] = 0x7bd19a16ul;
    428+    sha->bytes = 64;
    429+}
    430+
    431+/* This nonce function is described in BIP-schnorr
    


    MaxHillebrand commented at 8:30 pm on January 22, 2020:
    0/* This nonce function is described in BIP-340
    
  178. in src/secp256k1.c:432 in df8c698793 outdated
    427+    sha->s[7] = 0x7bd19a16ul;
    428+    sha->bytes = 64;
    429+}
    430+
    431+/* This nonce function is described in BIP-schnorr
    432+ * (https://github.com/sipa/bips/blob/bip-schnorr/bip-schnorr.mediawiki) */
    


    MaxHillebrand commented at 8:30 pm on January 22, 2020:
    0 * (https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) */
    

    jonasnick commented at 5:43 pm on January 23, 2020:
    1. I’ll probably do the name change once the PR is merged. In some cases I’d rather write BIP-340 ("Schnorr Signatures for secp256k1").
  179. in src/secp256k1.c:440 in df8c698793 outdated
    435+
    436+    if (counter != 0) {
    437+        return 0;
    438+    }
    439+    /* Tag the hash with algo16 which is important to avoid nonce reuse across
    440+     * algorithms. If the this nonce function is used in BIP-schnorr signing as
    


    MaxHillebrand commented at 8:31 pm on January 22, 2020:
    0     * algorithms. If the this nonce function is used in BIP-340 signing as
    
  180. in include/secp256k1.h:526 in df8c698793 outdated
    522@@ -523,6 +523,18 @@ SECP256K1_API int secp256k1_ecdsa_signature_normalize(
    523  */
    524 SECP256K1_API extern const secp256k1_nonce_function secp256k1_nonce_function_rfc6979;
    525 
    526+/** An implementation of the nonce generation function as defined in BIP-schnorr.
    


    MaxHillebrand commented at 8:33 pm on January 22, 2020:
    0/** An implementation of the nonce generation function as defined in BIP-340.
    
  181. in include/secp256k1.h:530 in df8c698793 outdated
    522@@ -523,6 +523,18 @@ SECP256K1_API int secp256k1_ecdsa_signature_normalize(
    523  */
    524 SECP256K1_API extern const secp256k1_nonce_function secp256k1_nonce_function_rfc6979;
    525 
    526+/** An implementation of the nonce generation function as defined in BIP-schnorr.
    527+ *
    528+ * If a data pointer is passed, it is assumed to be a pointer to 32 bytes of
    529+ * extra entropy. If the data pointer is NULL and this function is used in
    530+ * schnorrsig_sign, it produces BIP-schnorr compliant signatures.
    


    MaxHillebrand commented at 8:33 pm on January 22, 2020:
    0 * schnorrsig_sign, it produces BIP-340 compliant signatures.
    
  182. MaxHillebrand commented at 8:39 pm on January 22, 2020: none

    This changes every occurrence of BIP-Schnorr [or similar writing] to BIP-340.

    I am unsure if this is the proper way of doing this, especially regarding the tagged hashes, thus I will suggest it for every occurrence, and ask you to please commit or close it depending if it is correct in that instance.

    My suggestions started in this PR.

  183. bitcoin-core deleted a comment on Jan 23, 2020
  184. bitcoin-core deleted a comment on Jan 23, 2020
  185. bitcoin-core deleted a comment on Jan 23, 2020
  186. MaxHillebrand commented at 1:47 pm on January 23, 2020: none

    Sorry @sipa, I’ve removed the suggestions to keep BIPSchnorrDerive. What about secp256k1_nonce_function_bipschnorr?

    and yes @real-or-random I opened the multiple suggestions with the single batch commit in mind.

  187. in include/secp256k1_schnorrsig.h:88 in 6a7ee8a862 outdated
    76+    const secp256k1_context* ctx,
    77+    secp256k1_schnorrsig *sig,
    78+    const unsigned char *msg32,
    79+    const unsigned char *seckey,
    80+    secp256k1_nonce_function noncefp,
    81+    void *ndata
    


    gmaxwell commented at 8:29 pm on January 26, 2020:
    uh. shouldn’t this present an interface that takes the pubkey as an argument, rather than halving the signing speed for data the caller already probably has (and if they don’t have it, they can generate it at the same speed with one extra line of code). Or perhaps accept a null pointer for the case where they don’t have it.

    sipa commented at 8:32 pm on January 26, 2020:
    I was thinking about this too. A complication with x-only public keys is that to take advantage of this, you’d also need to pass in whether the Y coordinate of privkey*G is a quadratic residue or not (as that implies negating the private key before signing).

    sipa commented at 8:38 pm on January 26, 2020:

    To solve this, we’ll probably want a data structure that stores both the x-only pubkey and a boolean that indicates its negation-needed flag (which is equal to whether the privkey*G has a non-square Y coordinate).

    That structure starts to look surprisingly much like the old public key type, except it’s square/nonsquare Y rather than even/odd.


    jonasnick commented at 9:05 pm on January 26, 2020:

    Yes it should be possible to use an interface that’s twice as fast if you know the pubkey.

    Note that in that case we’ll need to add the pubkey as extradata to the deterministic nonce function, otherwise calling sign with the wrong public key may leak the secret key through an (invalid) signature. This makes the signing function with pubkey argument not pass the signing test vectors.

    That structure starts to look surprisingly much like the old public key type

    This is what I did originally for xonly_pubkey_create, xonly_tweak_add etc. But people found that confusing, and so I changed the functions to output xonly_pubkey and is_negated (but apparently not for the xonly_pubkey_create function). is_negated is crucial in avoiding similar recomputation in MuSig and the likes, but apparently I missed the normal Schnorr signing use case.


    gmaxwell commented at 9:32 pm on January 26, 2020:
    Any background on why it was confusing? A single object (which you’d then have a seperate serializer for) seems less confusing to me.

    jonasnick commented at 9:54 pm on January 26, 2020:
    The confusing part was probably my explanation for why this is useful because the interface wasn’t as clear at the time. However, I’d be happy to change it back as the is_negated flag is a recurring nuisance.

    sipa commented at 10:22 pm on January 26, 2020:

    I hate to bring this up at this point, but this discussion makes me wonder if we should consider the changing the x-only pubkey tiebreaker from square-y to even-y (but leave the R one at square-y).

    First of all, using square-y strictly adds a Jacobi symbol computation to either the keygen or signing process, for no gain except consistency with R. Using square-y for R itself does not have this problem, as it avoids a conversion to affine coordinates, but we always need P in affine coordinates anyway.

    However, if we’re going to need an application-exposed full pubkey type, using square-y as a tie breaker adds another downside, namely not being able to reuse the existing normal public keys. This matters, because compatibility with existing key generation is a goal, and I believe that interaction between the two may be annoying in some cases.

    Think about the use case of a simple single-user Taproot output, and spending it via PSBT. PSBT already has records that say “this (full) pubkey was derived this way” (from which master key, and which key derivation path), which we’ll want to reuse in a Taproot world. For key path spending, we’ll need a new PSBT record that states “this (xonly) public key is equal to (reference to full pubkey) with tweak X”. With square-y keys, if we want to avoid forcing the Jacobi symbol computation onto PSBT signers, it also needs flags to indicate whether the private key is to be negated before the tweak and after the tweak. With even-y keys, a generic “this full public key is this full public key + this tweak” that isn’t even Taproot specific suffices.


    gmaxwell commented at 6:01 am on January 27, 2020:

    I think user in general should be discouraged from reusing keys in different applications, beyond the general precautions that get given for all key reuse in crypto (surprising bad things can happen)– there is a very specific easily reached route where using keys across different uses will break things: Use of schnorr+ecdsa on the same message with the same nonce leaks the key. I’m confident some implementations are going to screw that up, and reuse their existing rfc6979…

    Being compatible with existing generation routines doesn’t mean reusing the keys in other applications.

    If you take the key reuse off the table I don’t think it matters particularly much. We could, if we wanted, define a new kind of ‘compressed pubkey’ that instead of 2/3 uses different bits to signal square/non-square.

    I agree that it’s weak one way vs another.


    gmaxwell commented at 9:18 am on January 27, 2020:
    thinking about it, one way would be to use the existing pubkey objects. Add an internal flag for the squareness. And have serialize flags that know how to serialize either as xonly OR using a non-standard value in the first byte to signal the squareness?

    real-or-random commented at 3:33 pm on January 27, 2020:

    Well. I agree, there should be a way to sign without the need to recompute a public key that the user knows already. But this here worries me:

    Note that in that case we’ll need to add the pubkey as extradata to the deterministic nonce function, otherwise calling sign with the wrong public key may leak the secret key through an (invalid) signature. This makes the signing function with pubkey argument not pass the signing test vectors.

    Yes, and annoyingly we need to add the extended public key here. Otherwise you can trick people into producing two signatures, one with and one without negated signing key, which leaks your nonce by s1 + s2 = (r + cx) + (r - cx) = 2r. Note that this is a different attack than the ordinary nonce reuse attack: here, you reuse the nonce r but you actually sign for the same hash c = H(pk||R||m). So this is pretty subtle and I bet people will get this wrong. :/

    This brings me to a question of provable security: if in practice there is a signing algorithm that takes a public key, then you should also have an equivalent signing oracle in your security model on paper. I don’t think it’s hard but unfortunately, noone has looked into this.

    It was a deliberate choice to make schnorr pubkeys different from ecdsa pubkeys to discourage reuse across different scheme. And now they are so different that they are even recognizable by their size, which I think is a good thing.

    Let’s call this “extended pubkeys”. I prefer “extended” over “full” because full suggests that for non-full keys, an essential part is missing. But “extended” it’s not great either. Maybe there is a better term.

    I would love to propose that extended pubkeys should have a serialization which is different from 2/3. But if we want to be compatible to BIP32, that’s just not possible. Or do people think it’s a good idea to be compatible to a variant of BIP32 only? That seems somewhat weird to me but one point is that anyway you want different derivation paths for schnorr vs ecdsa…

    I was never too convinced about the advantage of “consistency of the tie-breaker”. If it’s faster, why not use odd/even.


    jonasnick commented at 3:50 pm on January 27, 2020:
    Not a big deal, but using the oddness tiebreaker requires adding another line in the BIP’s verification algo that negates y if odd. We don’t need that for the squareness tie breaker because the square root automatically computes the square y. This was the original rationale for using squareness (not thinking about signing) in addition to the consistency.

    gmaxwell commented at 7:22 pm on January 27, 2020:
    Indeed, squareness is faster to decode but a negation is about as close to free as you can get while still doing something.

    gmaxwell commented at 7:28 pm on January 27, 2020:
    or you make a super-extended public key which also includes a tagged cryptographic hash of the private key. :-/

    sipa commented at 7:56 pm on January 27, 2020:

    Splitting discussions out. This one about the performance impact on signing, without precomputed public key data.

    • With squareness tie-breaker, signing needs to compute (privkey*G), compute its affine X coordinate (=computing inv(Z), squaring it, and multiplying with X), compute the squaredness of its Y coordinate (=computing Y*Z, and jacobi of it), normalize X, and then conditionally negate the privkey.

    • With oddness tie-breaker, signing needs to compute (privkey*G), compute its affine X coordinate (=computing inv(Z), squaring it, and multiplying with X), compute the affine Y coordinate (=computing 1/Z^3, and multiplying with Y), normalize X, normalize Y, and then conditionally negate the privkey.

    The current PR does one unnecessary multiplication, as it’s unnecessarily computing the affine Y coordinate. After fixing that, the speed gain from going from square to even tiebreaker is trading 1 field multiplication and normalization for 1 jacobi computation - which is dominated by the jacobi symbol.

    I don’t know if we care about this ~5us speedup at signing time (because when you do care, perhaps you want signing with precomputed public key data, which avoids this cost in both cases), but there seems to be basically no gain apart from consistency with R (which is arguably very different, as handling of R is generally limited in scope to the signing code, while public keys are obviously exposed to applications (and things like the taproot tweak break the perfect blackboxy ness of them).


    sipa commented at 11:08 pm on January 27, 2020:

    Split out discussion. This one is about avoiding the EC multiplication by using precomputed pubkey data.

    It seems a question is whether we need to protect against the situation where the public key (or data derived from it), passed to the signing function, is actually computed by the signer itself, or untrusted.

    In the case of signing devices with a fixed master BIP32 key, the public key is likely going be computed by the device itself anyway at signing time, so it’s wasteful to have the signing algorithm redo it.

    On the other hand, @real-or-random and @jonasnick correctly point out that if the public key data provided to the signing algorithm is untrusted, this public key data must also be an input to the derivation function. I don’t think we have a proof that even with this modified derivation function, the resulting signature scheme is secure.

    So I think that’s an unfortunate situation to be in. We shouldn’t standardize an (alternative) signing algorithm that takes untrusted pubkey data as input (because we have no proof it is secure), but simultaneously, having an API that takes in trusted pubkey data is scary (because if the API is abused with untrusted pubkey data, we have a known attack).

    FWIW, the ed25519-donna implementation seems to happily take the public key as input to the signer. I wonder if the concern of an untrusted public key has been analysed in that context.


    real-or-random commented at 7:48 am on January 28, 2020:

    @sipa I took the freedom to “move” your two summary posts to https://github.com/sipa/bips/issues/190 and https://github.com/sipa/bips/issues/191 because GitHub’s “comment on code” feature is not good for extended discussions, and these issues are actually issues, and they relate to the BIP itself, not only the implementation.

    Please continue there.


    elichai commented at 11:32 am on February 16, 2020:
    May I come back to the original discussion of should is_negated be part of the pubkey part and serialized out or not? I mean the only need for is_negated is to verify that you tweak correctly, can/should this be part of the struct? if so it will mean that tweak_test(der(ser(add(key, tweak), key, tweak) != tweak_test(add(key,tweak), key, tweak)

    jonasnick commented at 12:43 pm on February 17, 2020:
    @elichai Not exactly sure what your point is but we have a struct that has the is_negated part and is serializable - the plain old secp256k1_pubkey struct. Sounds like you’re arguing for API design option 2 (https://github.com/bitcoin-core/secp256k1/pull/558#issuecomment-582152117). I don’t think compressed serialization is not enough, because taproot users need a single is_negated bit to stuff in the control block and I don’t think we want them to extract it themselves from the 0x02/0x03 tags.

    elichai commented at 12:47 pm on February 17, 2020:
    yeah I guess we must output the oddness byte someone, so we might as well have it as a separate thing instead of compressed because for most sign/verify you don’t need it

    sipa commented at 7:45 pm on May 22, 2020:
    I think this is resolved now the function takes a secp256k1_keypair as input.
  188. in src/secp256k1.c:438 in 6603c32a10 outdated
    433+
    434+    if (counter != 0) {
    435+        return 0;
    436+    }
    437+    /* Tag the hash with algo16 which is important to avoid nonce reuse across
    438+     * algorithms. If the this nonce function is used in BIP-340 signing as
    


    constcast-glitch commented at 9:36 pm on January 26, 2020:
    I believe “If the this nonce function …” should be “If this nonce function …”.
  189. in src/tests.c:456 in 6603c32a10 outdated
    451+    unsigned char buf2[32];
    452+
    453+    /* Is buffer fully consumed? */
    454+    CHECK((sha1->bytes & 0x3F) == 0);
    455+
    456+    /* Compare the struct excluding the the buffer, because it may be
    


    leishman commented at 3:55 am on January 27, 2020:
    the the
  190. chjj cross-referenced this on Jan 27, 2020 from issue schnorr: implement publicKeyTweakTest() by pinheadmz
  191. real-or-random cross-referenced this on Jan 28, 2020 from issue Avoiding the EC multiplication during signing by using precomputed pubkey data by real-or-random
  192. real-or-random cross-referenced this on Jan 28, 2020 from issue Squareness vs oddness tie-breaker for public keys by real-or-random
  193. jonasnick commented at 10:43 pm on February 4, 2020: contributor

    I pushed a few commits to experiment with bringing this PR in sync with the proposed changes to bip-schnorr: switching from squareness to evenness as tie breaker, new challenge and nonce hash tags, new nonce function that takes pubkeys as argument.

    It was mentioned that with the evenness tiebreaker we could maybe remove the xonly_pubkey altogether. I created https://gist.github.com/jonasnick/a122acd8395ac4c6ae7b648450f5ec07 with three different versions of the taproot test (from schnorrsig/tests_impl.h):

    1. Current version
    2. xonly_pubkey_create is removed but the xonly_pubkey type still exists as arguments to schnorrsig_verify and secp256k1_xonly_pubkey_tweak_test. Output of secp256k1_xonly_pubkey_tweak_add is a normal pubkey
    3. There’s no xonly_pubkey anymore. schnorrsig_verify and xonly_pubkey_tweak_test Return 0 if they get a pubkey with an uneven Y.

    With 3 you can’t simply use schnorrsig_verify with your normal pubkey because it will (or should) fail if called with an odd Y pubkey. You’ll need to call a convert function (or do a serialization roundtrip with the XONLY flag). Therefore, I think it’s better to keep the xonly_pubkey type in the schnorrsig_verify() arguments.

    The current sitation (1) isn’t too bad actually. 2 has the advantage to gets rid of the is_negated variable which seems more redundant now that evenness is used as a tie breaker. But it will reappear in the form of conversion functions unless we want people to mess with the compressed pubkey format to make use of the evenness information in the taproot control block bit. This is how I originally proposed doing this, but it was viewed as too complicated. In light of 2 (or 3), it would also make sense to rename is_negated in taproot_tweak_pubkey in bip-taproot (https://github.com/sipa/bips/blob/bip-taproot/bip-0341.mediawiki) to is_even_y.

  194. jonasnick force-pushed on Feb 12, 2020
  195. jonasnick commented at 9:22 pm on February 12, 2020: contributor
    Had to rebase because of a conflict in .travis.yml, and travis failing due to a 404 when fetching java dependencies (hehe).
  196. jonasnick force-pushed on Feb 12, 2020
  197. junderw cross-referenced this on Feb 13, 2020 from issue Consider adding schnorr support by roccomuso
  198. in src/modules/schnorrsig/main_impl.h:221 in 23c3b0050e outdated
    164+    secp256k1_sha256_write(&sha, &sig->data[0], 32);
    165+    secp256k1_fe_get_b32(buf, &pk.x);
    166+    secp256k1_sha256_write(&sha, buf, sizeof(buf));
    167+    secp256k1_sha256_write(&sha, msg32, 32);
    168+    secp256k1_sha256_finalize(&sha, buf);
    169+    secp256k1_scalar_set_b32(&e, buf, NULL);
    


    unknown commented at 9:39 am on February 20, 2020:
    should overflow be checked here as well?

    jonasnick commented at 9:55 am on February 20, 2020:
    No, computing e as hash mod n matches BIP-340

    real-or-random commented at 3:35 pm on February 20, 2020:
    Maybe add a comment as a reminder for the casual reviewer.

    jonasnick commented at 2:17 pm on March 7, 2020:
    done
  199. in src/modules/schnorrsig/main_impl.h:211 in 23c3b0050e outdated
    154+    secp256k1_scalar_set_b32(&s, &sig->data[32], &overflow);
    155+    if (overflow) {
    156+        return 0;
    157+    }
    158+
    159+    if (!secp256k1_xonly_pubkey_load(ctx, &pk, pubkey)) {
    


    unknown commented at 3:50 am on February 25, 2020:
    Is secp256k1_xonly_pubkey_load / secp256k1_pubkey_load an equivalent of point(pk) in BIP-340? maybe it’ll be good to add a reminder comment of what happens if pubkey's x > p - 1 (if it fails here, as in BIP-340, what makes it fail / secp256k1_fe_from_storage or secp256k1_fe_set_b32…? – or if it goes through and fails at the end, why it is fine)

    jonasnick commented at 2:14 pm on March 7, 2020:
    No, the equivalent is secp256k1_xonly_pubkey_load (the pubkey argument to secp256k1_schnorrsig_verify is already an xonly_pubkey while the BIP-340 pk is a byte array,).
  200. real-or-random cross-referenced this on Mar 3, 2020 from issue Add the prime of the secp256k1 field element. by rantan
  201. jonasnick commented at 2:17 pm on March 7, 2020: contributor
    PR should now match the updated BIP in https://github.com/bitcoin/bips/pull/893#pullrequestreview-366405395. I also added the new test vectors. Will rebase and squash once someone had a look at it.
  202. jonasnick commented at 4:33 pm on March 11, 2020: contributor
    To keep this PR consistent with BIP-taproot (latest updates https://github.com/bitcoin/bips/pull/893), I changed xonly_tweak_test to take the 32 bytes serialized output pubkey to prevent an unnecessary decompression. (@elichai you’ll be happy to hear that xonly_tweak_test now has a better reason to exist :)
  203. in src/secp256k1.c:481 in 819c3a6c15 outdated
    455+    }
    456+    if (algo16 == NULL) {
    457+        return 0;
    458+    }
    459+
    460+    if (data != NULL) {
    


    sipa commented at 0:57 am on March 13, 2020:
    Does this mean that if data == NULL, we use an unmasked key?

    jonasnick commented at 8:58 am on March 16, 2020:
    Yes. Alternatively we could mask with some static array, but that’s unnecessary.

    sipa commented at 4:49 pm on March 16, 2020:
    I that case we should update the BIP to not do the masking when there is no aux?

    jonasnick commented at 4:58 pm on March 16, 2020:

    I think either way is fine. Right now, if you don’t provide an aux, you’re not BIP compliant which should be clearly stated in the documentation. If you want to be BIP compliant but have no aux, you can use all 0s as the BIP mentions:

    If randomness is not available at all at signing time, a simple counter wide enough to not repeat in practice (e.g., 64 bits or wider) and padded with null bytes to a 32 byte-array can be used, or even the constant array with 32 null bytes.


    real-or-random commented at 7:55 am on March 23, 2020:
    What if we force people to provide an aux, i.e., abort if they provide NULL?

    elichai commented at 8:08 am on March 23, 2020:
    We could also get it if the user randomized the context, but I think we’ll then need to increase some PRF counter in the context which will make it not thread safe :(

    real-or-random commented at 8:37 am on March 23, 2020:
    Hm, yes, a counter is a good idea (even if the user hasn’t randomized the context). On C11 we could use <stdatomics.h> to implement a portable atomic counter but I don’t think this is possible on C90.

  204. sipa commented at 1:15 am on March 13, 2020: contributor

    Concept ACK, enough to squash. I’ve reviewed the API, and read through the nonce generation, signing, and (non-batched) verification code.

    Can you include for valgrind_ctime_test support after rebase?

  205. in include/secp256k1_schnorrsig.h:52 in 819c3a6c15 outdated
    47+ *  Out:     sig: pointer to a signature object
    48+ *  In:     in64: pointer to the 64-byte signature to be parsed
    49+ *
    50+ * The signature is serialized in the form R||s, where R is a 32-byte public
    51+ * key (X coordinate only; the Y coordinate is considered to be the unique
    52+ * Y coordinate satisfying the curve equation that is square)
    


    jnewbery commented at 6:02 pm on March 17, 2020:
    s/square/even

    jonasnick commented at 8:57 pm on March 20, 2020:
    thanks, fixed
  206. in include/secp256k1.h:738 in 819c3a6c15 outdated
    733@@ -701,6 +734,170 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ec_pubkey_combine(
    734     size_t n
    735 ) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
    736 
    737+/** Opaque data structure that holds a parsed and valid "x-only" public key.
    738+ *  An x-only pubkey encodes a point whose Y coordinate is square. It is
    


    jnewbery commented at 6:02 pm on March 17, 2020:
    s/square/even

    jonasnick commented at 8:57 pm on March 20, 2020:
    fixed
  207. in include/secp256k1.h:827 in 819c3a6c15 outdated
    822+/** Tweak the secret key of an x-only pubkey by adding a tweak to it. The public
    823+ *  key of the resulting secret key will be the same as the output of
    824+ *  secp256k1_xonly_pubkey_tweak_add called with the same tweak and corresponding
    825+ *  input public key.
    826+ *
    827+ *  If the public key corresponds to a point with square Y, tweak32 is added to
    


    jnewbery commented at 6:03 pm on March 17, 2020:
    s/square/even

    jonasnick commented at 8:58 pm on March 20, 2020:
    fixed
  208. in include/secp256k1.h:848 in 819c3a6c15 outdated
    843+    const unsigned char *tweak32
    844+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
    845+
    846+/** Tweak an x-only public key (in place) by adding tweak times the generator to it.
    847+ *
    848+ *  Because the resulting point may have a non-square Y coordinate, it may not
    


    jnewbery commented at 6:03 pm on March 17, 2020:
    s/non-square/odd

    jonasnick commented at 8:58 pm on March 20, 2020:
    fixed
  209. jonasnick force-pushed on Mar 20, 2020
  210. ysangkok cross-referenced this on Mar 22, 2020 from issue BIP-340 (Schnorr) support by ysangkok
  211. in include/secp256k1.h:824 in 372c4555ca outdated
    819+    secp256k1_xonly_pubkey *xonly_pubkey,
    820+    int *is_negated,
    821+    const secp256k1_pubkey *pubkey
    822+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4);
    823+
    824+/** Tweak the secret key of an x-only pubkey by adding a tweak to it. The public
    


    ysangkok commented at 6:26 pm on March 28, 2020:
    which “x-only pubkey”? should it say “seckey”?

    ysangkok commented at 6:34 pm on March 28, 2020:
    It is weird to reference a pubkey, that the user may not even have generated yet. This function takes no pubkey arguments.
  212. ysangkok changes_requested
  213. jonasnick commented at 1:45 pm on March 31, 2020: contributor

    As a follow up to above discussion about changing the API after switching to the y-parity tie-breaker I experimented with option 2 (remove xonly_pubkey_create). This works, but it introduces the additional step of converting your pubkey into an xonly_pubkey before being able to schnorrsig_verify your signature.

    Therefore, I took it a step further and experimented with what we actually want to have: a secp256k1_keypair type which holds both a secret key and a public key. This avoids needlessly recomputing the public key in xonly_seckey_tweak_add and schnorrsig_tweak_add.

    You create a keypair by calling secp256k1_keypair_create(ctx, &keypair, seckey) with your 32 byte secret key. Instead of xonly_seckey_tweak_add there is a keypair_xonly_tweak_add that tweaks both parts of the keypair. I didn’t add functions for (de-)serializing keypairs for now. There’s no function to combine a secret key and a public key into a keypair, because that can easily lead to a vulnerability if the attacker controls the public key. But as far as I know, random bit corruption of keypairs is okay.

    To see this in action, have a look at the taproot test in my branch https://github.com/jonasnick/secp256k1/blob/schnorrsig-keypair/src/modules/schnorrsig/tests_impl.h#L734. What’s missing in the branch is API documentation and API tests. If there’s no opposition, I suggest to go ahead with this approach because it’s simpler than introducing functions that would be superseded later anyway by keypair functions.

  214. real-or-random commented at 4:26 pm on April 26, 2020: contributor
    Do we know how critical the PRG performance is for the overall performance? We might just use SHA256 instead. Don’t get me wrong, we have a working ChaCha implementation here and ChaCha is lightweight, and I think it’s a good choice. But if we’re thinking about #702, sticking with SHA256 may be simpler, smaller binary, and even faster in the end.
  215. sipa commented at 6:16 pm on April 26, 2020: contributor
    The rough numbers I use as rule of thumb (and maybe outdated…) is that pure C (no intrinsics) implementations on x86_64 need 15 cycles/byte for SHA256 (so multiply by input length after padding), and 3 cycles/byte for ChaCha20 (multiply by the number of bytes produced, rounded up to a multiple of 64).
  216. jonasnick force-pushed on May 14, 2020
  217. jonasnick commented at 1:26 pm on May 14, 2020: contributor

    Since I didn’t get much response to the keypair idea mentioned above I went ahead with that and also took the opportunity to refactor a few other things. All in all, this should make this PR easier to review and to merge.

    1. There’s a new experimental “extrakeys” module for xonly_pubkeys and keypairs. This is better than having those functions and structs in secp256k1.h because that way this PR does not touch secp256k1.h at all an therefore does not interfere with our goal of stabilizing all non-experimental parts of the library. The extrakeys module is enabled automatically if the schnorrsig module is enabled.
    2. For a similar reason I moved the bip-schnorr nonce function to schnorrsig module. It couldn’t be used with ECDSA anyway because it has a different nonce function type (also the serialized xonly pubkey as argument).
    3. schnorrsig batch verification is removed. It’s fairly well tested, but still wouldn’t be comfortable with using this in Bitcoin Core for consensus in its current state because it relies on parts of the lib that are otherwise unused. Also there’s been still some discussion on the RNG and synthetic randomness. For now, it’s simpler to just remove it and add it again in a different PR - ideally with comprehensive fuzz tests.
    4. The Schnorr signature struct is removed. Instead, a schnorr sig is just a 64-byte array which matches the definition in the BIP. The schnorrsig struct didn’t really serve a purpose, its parse and serialize functions were just memcpy’ing and I didn’t come up with a future purpose for it.

    If you tweak your secret key before signing (as is recommended by bip-taproot) schnorrsig_sign takes only 28.4us now vs 53.9us before because it does not have to recompute the public key thanks to keypairs. To get an overview how the new API works have a look at the taproot test case at https://github.com/bitcoin-core/secp256k1/pull/558/commits/682a113f3a0a816c0f0391af9eef036133839335.

    The state of this PR before the force push is archived at https://github.com/jonasnick/secp256k1/commits/schnorrsig-backup52.

  218. in include/secp256k1_extrakeys.h:132 in 7a3acdcef9 outdated
    127+ *                       secp256k1_xonly_pubkey_tweak_add to an xonly_pubkey
    128+ *      internal_pubkey: pointer to an x-only public key object to apply the
    129+ *                       tweak to (cannot be NULL)
    130+ *              tweak32: pointer to a 32-byte tweak (cannot be NULL)
    131+ */
    132+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_tweak_test(
    


    sipa commented at 6:02 pm on May 22, 2020:
    Nit: add add in the name here like the commit name suggests.

    jonasnick commented at 8:51 am on June 5, 2020:
    fixed
  219. in src/modules/schnorrsig/main_impl.h:70 in bb888e4f63 outdated
    66+    }
    67+
    68+    /* Tag the hash with algo16 which is important to avoid nonce reuse across
    69+     * algorithms. If this nonce function is used in BIP-340 signing as defined
    70+     * in the spec, an optimized tagging implementation is used. */
    71+    if (memcmp(algo16, "BIP340/nonce0000", 16) == 0) {
    


    sipa commented at 6:52 pm on May 22, 2020:
    Perhaps it’s slightly cleaner to pad with \x00 bytes, and state in secp256k1_nonce_function_bip340_sha256_tagged’s documentation that it uses the algo16 argument (after removing trailing \x00 bytes) as tag? That would avoid special-casing the behaviour for one specific tag (and only having it be an optimization).

    jonasnick commented at 8:52 am on June 5, 2020:
    good idea, done
  220. in .gitignore:6 in ccb23e329d outdated
    0@@ -1,9 +1,9 @@
    1 bench_inv
    2 bench_ecdh
    3 bench_ecmult
    4+bench_schnorrsig
    5 bench_sign
    6 bench_verify
    7-bench_schnorr_verify
    


    sipa commented at 7:13 pm on May 22, 2020:
    Good thing this very dangerous oversight in the existing code is fixed!

    real-or-random commented at 6:00 pm on June 2, 2020:
    To the moon!
  221. in include/secp256k1_extrakeys.h:114 in ccb23e329d outdated
    108+ *                       according to secp256k1_ec_seckey_verify, this function
    109+ *                       returns 0. For uniformly random 32-byte arrays the
    110+ *                       chance of being invalid is negligible (around 1 in
    111+ *                       2^128) (cannot be NULL).
    112+ */
    113+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_tweak_add(
    


    sipa commented at 7:17 pm on May 22, 2020:
    Any particular reason why this returns a secp256k1_pubkey instead of an xonly_pubkey + parity flag directly?

    jonasnick commented at 8:51 am on June 5, 2020:

    The idea was to convert to xonly_pubkeys as late as possible because many operations on xonly_pubkeys would need the parity bit as well and it’s annoying to keep track of both instead of just using a normal pubkey. Moreover, if someone would want to use the result of tweak_add with a function that takes normal pubkeys, that wouldn’t be easy to do because we don’t have a conversion function (xonly_pk, parity) -> pubkey.

    But on the other hand there isn’t really an operation at this point that would be more common than just using the only key after tweak_add, so I’m tending towards changing this back to output an xonly key.

  222. sipa commented at 7:48 pm on May 22, 2020: contributor

    Approach ACK, and reviewed most of the code.

    I really like the state this is in. It makes perfect sense to have the keypair data type in a separate module (for now), and the commit history is easy to follow. I’ve left a few suggestions in the code, but nothing major.

  223. in src/modules/extrakeys/main_impl.h:122 in cecda26efe outdated
    117+    int ret = 1;
    118+    const secp256k1_pubkey *pubkey = (const secp256k1_pubkey *)&keypair->data[32];
    119+
    120+    if (sk != NULL) {
    121+        secp256k1_scalar_set_b32(sk, &keypair->data[0], NULL);
    122+        ret &= !secp256k1_scalar_is_zero(sk);
    


    elichai commented at 6:25 pm on June 6, 2020:

    nit, I’m not sure if we care, just some weird inconsistency, currently if you use a zeroed out secp256k1_pubkey it will call the callback and you’ll get an abort: https://github.com/bitcoin-core/secp256k1/blob/a39c2b09de304b8f24716b59219ae37c2538c242/src/secp256k1.c#L254

    But here if only the seckey part of keypair is zeroed out it will gracefully fail


    jonasnick commented at 10:16 pm on June 7, 2020:
    The thinking was that that both parts are always zeroed together. I added a commit to ARG_CHECK both parts. The commit also declassifies the result of secp256k1_scalar_is_zero(sk) because that can only happen if a keypair function failed (which zeroes the keypair) and its return value is ignored.
  224. in src/modules/extrakeys/main_impl.h:237 in cecda26efe outdated
    194+        secp256k1_ge_neg(&pk, &pk);
    195+    }
    196+
    197+    ret &= secp256k1_ec_seckey_tweak_add_helper(&sk, tweak32);
    198+    secp256k1_scalar_cmov(&sk, &secp256k1_scalar_zero, !ret);
    199+    ret &= secp256k1_ec_pubkey_tweak_add_helper(&ctx->ecmult_ctx, &pk, tweak32);
    


    elichai commented at 6:38 pm on June 6, 2020:

    On one hand seckey_tweak is constant time with regards both to the secret and the tweak. On the other hand pubkey_tweak is not constant time with regards to the tweak.

    Do we want keypair_xonly_tweak_add to be ct with regards to the tweak or not?


    jonasnick commented at 10:16 pm on June 7, 2020:
    It’s not ct because we reuse the existing pubkey_tweak_add machinery atm. However, it’s easy to imagine scenarios where the tweak is secret. I wonder if it makes sense to do this in a separate PR?
  225. in src/modules/schnorrsig/main_impl.h:79 in cecda26efe outdated
    78+        /* Remove terminating null bytes */
    79+        algo16_len = 16;
    80+        while (algo16_len > 0 && !algo16[algo16_len - 1]) {
    81+            algo16_len--;
    82+        }
    83+        secp256k1_sha256_initialize_tagged(&sha, algo16, algo16_len);
    


    elichai commented at 6:58 pm on June 6, 2020:
    In the current RFC6979 we use the full 16 bytes, why here different? and if we do use this, why not just call strnlen(2)?

    jonasnick commented at 10:17 pm on June 7, 2020:
    See #558 (review) and I didn’t want to figure out where strnlen is available vs. just a loop.
  226. in src/modules/schnorrsig/main_impl.h:142 in cecda26efe outdated
    137+
    138+    ret &= secp256k1_keypair_load(ctx, &sk, &pk, keypair);
    139+    /* Because we are signing for a x-only pubkey, the secret key is negated
    140+     * before signing if the point corresponding to the secret key does not
    141+     * have an even Y. */
    142+    secp256k1_fe_normalize(&pk.y);
    


    elichai commented at 7:15 pm on June 6, 2020:
    You can call secp256k1_fe_normalize_var here

    jonasnick commented at 10:17 pm on June 7, 2020:
    Fixed
  227. in src/modules/schnorrsig/main_impl.h:148 in cecda26efe outdated
    143+    if (secp256k1_fe_is_odd(&pk.y)) {
    144+        secp256k1_scalar_negate(&sk, &sk);
    145+    }
    146+
    147+    secp256k1_scalar_get_b32(seckey, &sk);
    148+    secp256k1_fe_normalize(&pk.x);
    


    elichai commented at 7:15 pm on June 6, 2020:
    also here

    jonasnick commented at 10:17 pm on June 7, 2020:
    Fixed
  228. in src/modules/schnorrsig/main_impl.h:151 in cecda26efe outdated
    146+
    147+    secp256k1_scalar_get_b32(seckey, &sk);
    148+    secp256k1_fe_normalize(&pk.x);
    149+    secp256k1_fe_get_b32(pk_buf, &pk.x);
    150+    ret &= !!noncefp(buf, msg32, seckey, pk_buf, bip340_algo16, (void*)ndata, 0);
    151+    memset(seckey, 0, sizeof(seckey));
    


    elichai commented at 7:18 pm on June 6, 2020:
    Maybe move this to the end with the rest of the cleanup?

    jonasnick commented at 10:18 pm on June 7, 2020:
    Okay
  229. in src/modules/schnorrsig/main_impl.h:145 in cecda26efe outdated
    147+    secp256k1_scalar_get_b32(seckey, &sk);
    148+    secp256k1_fe_normalize(&pk.x);
    149+    secp256k1_fe_get_b32(pk_buf, &pk.x);
    150+    ret &= !!noncefp(buf, msg32, seckey, pk_buf, bip340_algo16, (void*)ndata, 0);
    151+    memset(seckey, 0, sizeof(seckey));
    152+    secp256k1_scalar_set_b32(&k, buf, NULL);
    


    elichai commented at 7:21 pm on June 6, 2020:
    I think that’s a problem, if noncefp failed then buf might be uninitialized, either we treat the return value of noncefp as public data(like ecdsa_sign) and we skip the signing process, or we need to cmov into it.

    jonasnick commented at 10:18 pm on June 7, 2020:
    Good catch. Added commit that initializes buf with 0.

    elichai commented at 7:41 am on June 8, 2020:
    That’s better, now realized that “cmov into it” won’t help because of #754 😅
  230. in src/modules/schnorrsig/main_impl.h:165 in cecda26efe outdated
    160+     * because r is not a secret. */
    161+    secp256k1_declassify(ctx, &r, sizeof(r));
    162+    if (!secp256k1_fe_is_quad_var(&r.y)) {
    163+        secp256k1_scalar_negate(&k, &k);
    164+    }
    165+    secp256k1_fe_normalize(&r.x);
    


    elichai commented at 7:25 pm on June 6, 2020:
    You can call secp256k1_fe_normalize_var here too

    jonasnick commented at 10:18 pm on June 7, 2020:
    Fixed
  231. in src/modules/schnorrsig/main_impl.h:209 in cecda26efe outdated
    208+    }
    209+
    210+    secp256k1_scalar_set_b32(&s, &sig64[32], &overflow);
    211+    if (overflow) {
    212+        return 0;
    213+    }
    


    elichai commented at 7:39 pm on June 6, 2020:
    The BIP says that these can’t be 0 either, (fail if r ≥ p and fail if s ≥ n) having rx = 0 won’t help an attacker because you make sure that rj isn’t infinity when returning. but having s = 0 allows you to get rj = -e*pkj but then you have the problem you need to solve -e*pkj to get your r but you need the r to produce e, so this relies on the properties of the hash function

    jonasnick commented at 10:18 pm on June 7, 2020:
    The BIP allows s = 0 and r = 0. Also solving rj = -e*pkj isn’t easier than rj - s*G = -e*pkj without the secret key or am I misunderstanding the attack scenario?

    elichai commented at 7:49 am on June 8, 2020:

    it is, because in rj - s*G = -e*pkj, you want to find the scalar s, which is DLP. but in rj = -e*pkj you want to find rj which is a point. this isn’t DLP, it’s still “hard” though because rj should be hashed inside of e.

    about the bip, doesn’t this say that zeroes should fail? (if r=p or s=n)

    0Let r = int(sig[0:32]); fail if r ≥ p.
    1Let s = int(sig[32:64]); fail if s ≥ n.
    

    (I have deja vu about this discussion but I can’t find it in the review history hehe )


    real-or-random commented at 2:49 pm on June 9, 2020:

    I don’t see how forging is supposed to be easier for s = 0 than for s != 0. If the hash function is “broken”, you can forge without solving DLP but you don’t need s = 0 for this. Fix any s and solve sG=R+H(P,R,m)P for R.

    about the bip, doesn’t this say that zeroes should fail? (if r=p or s=n)

    It holds that s = int(0x00...00) = 0 <= n.


    elichai commented at 2:53 pm on June 9, 2020:

    Ha, you’re right, I don’t know how I missed that.

    It holds that s = int(0x00...00) = 0 <= n.

    so you’re saying that 0 <= n? even though it’s a scalar so it’s mod n (so 0 mod n == n mod n)


    real-or-random commented at 2:58 pm on June 9, 2020:

    s is an unsigned integer here in the pseudocode.

    “The function int(x), where x is a 32-byte array, returns the 256-bit unsigned integer whose most significant byte first encoding is x.”


    real-or-random commented at 3:03 pm on June 9, 2020:

    I don’t see how forging is supposed to be easier for s = 0 than for s != 0. If the hash function is “broken”, you can forge without solving DLP but you don’t need s = 0 for this. Fix any s and solve sG=R+H(P,R,m)P for R.

    Okay strictly speaking, one can think of a failure mode of the hash function where this is easier to solve for s = 0, but then I don’t see that this failure mode is more likely than any corresponding failure mode some other fixed s != 0.


    LLFourn commented at 12:56 pm on June 11, 2020:

    I don’t see how forging is supposed to be easier for s = 0 than for s != 0. If the hash function is “broken”, you can forge without solving DLP but you don’t need s = 0 for this. Fix any s and solve sG=R+H(P,R,m)P for R.

    Okay strictly speaking, one can think of a failure mode of the hash function where this is easier to solve for s = 0, but then I don’t see that this failure mode is more likely than any corresponding failure mode some other fixed s != 0.

    Actually, strictly speaking, one can’t! As it still has not been garbage collected from when I was looking at Taproot hash function requirements, allow me to recount the formal hash function requirements for key-only Schnorr unforgeability from Neven et al[1]: The adversary must be unable to break the random prefix-preimage (rpp) security of the hash. This is necessary and sufficient in the generic group model. In the rpp game, the adversary is allowed to chose the image c and then is given a random challenge prefix R. They win the game if they can output m such that H(R||m) == c.

    To see that rpp is necessary and that there is no value for s for which the scheme is safer against a hash break, consider that with a rrp solver A we can forge a signature with the following algorithm (ignoring key-prefixing for simplicity):

    0Forge(X):
    11. c <--$ A
    22. choose any s you want
    33. R <-- sG - cX
    44. m <--$ A(R)
    55. return (R,s),m
    

    Thus if the hash function breaks in any way that makes signatures forgeable then rpp must be broken as well (as it is sufficient) therefore an efficient A exists and they will be forgeable for any choice of s. They are not necessarily easily forgeable for any value of c. Perhaps if finding a pre-image for 0 was easier than other values then banning c=0 would help (but it would be better to just stop using the hash function in this case).

    [1] http://www.neven.org/papers/schnorr.pdf


    real-or-random commented at 9:09 am on June 21, 2020:
    Thanks, that’s super interesting.
  232. in src/valgrind_ctime_test.c:120 in cecda26efe outdated
    115+    VALGRIND_MAKE_MEM_DEFINED(&ret, sizeof(ret));
    116+    CHECK(ret == 1);
    117+
    118+    /* The tweak is not treated as a secret in keypair_tweak_add */
    119+    VALGRIND_MAKE_MEM_DEFINED(msg, 32);
    120+    ret &= secp256k1_keypair_xonly_tweak_add(ctx, &keypair, msg);
    


    elichai commented at 7:42 pm on June 6, 2020:
    why bitwise AND here?

    jonasnick commented at 10:18 pm on June 7, 2020:
    It’s unnecessary. Fixed
  233. elichai commented at 7:43 pm on June 6, 2020: contributor
    I also verified that the midstates are correct via an unrelated sha256 impl
  234. bitcoin-core deleted a comment on Jun 13, 2020
  235. jonasnick cross-referenced this on Jun 15, 2020 from issue Nonce functions by real-or-random
  236. jonasnick cross-referenced this on Jun 18, 2020 from issue WIP: Add schnorrsig batch verification by jonasnick
  237. in include/secp256k1_extrakeys.h:143 in ac5a0a1174 outdated
    138+ *                       secp256k1_xonly_pubkey_tweak_add to an xonly_pubkey
    139+ *      internal_pubkey: pointer to an x-only public key object to apply the
    140+ *                       tweak to (cannot be NULL)
    141+ *              tweak32: pointer to a 32-byte tweak (cannot be NULL)
    142+ */
    143+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_tweak_add_test(
    


    real-or-random commented at 9:18 am on June 21, 2020:

    Maybe test is not a great name because we only use this to refer functional tests so far, and it’s somewhat confusing. Suggestions: check, verify. Maybe secp256k1_xonly_pubkey_tweak_add_check or even secp256k1_xonly_pubkey_check_tweak_add, which I like slightly more. That’s still our datatype_operation style because tweak is not really a datatype. This is used in a function CheckPayToContract in https://github.com/bitcoin/bitcoin/pull/17977/commits/a9f418aeb0d31acadd0d5114158fb1fcb9094f40#diff-2c33d0ff0ed53b0902e4af9a745a152fR181 by the way.

    Sorry for nitpicking this single thing right now without anything else but I stumbled upon this just now and probably will forget about otherwise.


    elichai commented at 9:23 am on June 21, 2020:
    It was originally verify and I didn’t like it because it could suggest that it actually verifies the commitment, which it isn’t #558 (review)

    real-or-random commented at 11:47 am on June 21, 2020:
    Oh I see. I think check is then really the best.

    jonasnick commented at 8:32 pm on July 17, 2020:
    Renamed to secp256k1_xonly_pubkey_tweak_add_check
  238. in src/modules/schnorrsig/Makefile.am.include:8 in ac5a0a1174 outdated
    0@@ -0,0 +1,8 @@
    1+include_HEADERS += include/secp256k1_schnorrsig.h
    2+noinst_HEADERS += src/modules/schnorrsig/main_impl.h
    3+noinst_HEADERS += src/modules/schnorrsig/tests_impl.h
    4+if USE_BENCHMARK
    5+noinst_PROGRAMS += bench_schnorrsig
    6+bench_schnorrsig_SOURCES = src/bench_schnorrsig.c
    7+bench_schnorrsig_LDADD = libsecp256k1.la $(SECP_LIBS) $(COMMON_LIB)
    8+endif
    


    gmaxwell commented at 10:38 pm on July 13, 2020:
    missing ending newline

    jonasnick commented at 8:33 pm on July 17, 2020:
    fixed
  239. ZmnSCPxj commented at 2:06 am on July 14, 2020: none

    The Schnorr signature struct is removed. Instead, a schnorr sig is just a 64-byte array which matches the definition in the BIP. The schnorrsig struct didn’t really serve a purpose, its parse and serialize functions were just memcpy’ing and I didn’t come up with a future purpose for it.

    A 64-byte array is merely a blob of bytes. I would argue that the art of programming is taking blobs of bytes and imbuing them with meaning. Thus, a schnorrsig struct serves a purpose of imbuing meaning to a particular blob of 64 bytes, even if in the end the library is just memcpying it. If I were to write an interface to libsecp256k1 I would put types on almost anything, possibly with the ability to hack the bytes inside it, but would avoid flat arrays as much as possible.

  240. gmaxwell commented at 2:34 am on July 14, 2020: contributor

    I would put types on almost anything, possibly

    no strong opinion, but this principle is consistent with MISRA-C which mostly forbids primitive types… it’s a little more complicated when you talk about an API boundary, however.

  241. sipa commented at 2:40 am on July 14, 2020: contributor

    I conceptually agree with typing being useful, but I think there is also a pragmatic argument against: if pretty much all expected usage of the API will be parse_into_schnorrsig(&sig, data_ptr); do_something_with_schnorrsig(&sig) or create_schnorrsig(&sig); serialize_schnorrsig(data_ptr, &sig), and those parse/serialize functions don’t do anything but copy bytes - i’d say they just add clutter for the user.

    This is very much specific to how BIP340 is constructed, where a signature is specified as a byte array. There isn’t a distinction between “format invalid” and “signature invalid” like there is with ECDSA and DER signatures (where failing to parse a signature very well may have a different effect than failing to validate one).

  242. ZmnSCPxj commented at 5:27 am on July 14, 2020: none

    Well, in C-Lighning we even have separate types for sha256-hash from sha256-applied-twice-hash (struct sha256 vs struct sha256d). And we can argue that hashes are just arrays of 32 bytes, we just memcpy them around and treat them as arrays of 32 bytes when serializing across our many internal boundaries. But it is very convenient when passing them around between functions to have a distinct name for them, especially if those functions “just” pass them around, because those functions may be passing around other data of similar sizes anyway.

    Anyway, up to you; if you do not give a decent struct wrapper for signatures, we would on our side anyway.

    Also, a good-enough C programmer knows how (and when) to typecast, especially if you admit that the structure is just a convenient tag for a flat array of 32 bytes.

  243. gmaxwell commented at 8:50 am on July 14, 2020: contributor
    Perhaps best considered independent of this PR, regardless, as it would be a widespread API change.
  244. jonasnick commented at 9:05 am on July 14, 2020: contributor

    But it is very convenient when passing them around between functions to have a distinct name for them, especially if those function

    In addition to what Pieter said (simpler api, no unnecessary complexity, matches BIP340), I think there’s value in suggesting to the user to treat the output of sign (or prove more generally) as a blob whose single purpose is to be transported to another program’s verify function. This is a common concept in crypto libraries (see NaCl for example).

    On the other hand BIP340 recommends to verify the signature immediately after signing. And the signature is input to a adaptor_secret_extract function in a musig module (but this is a less common use case).

    Agree that we can revisit this in a separate PR - this is an experimental module wrt the API after all.

  245. real-or-random commented at 9:35 am on July 14, 2020: contributor

    Using a plain byte array is also what we do in the API for secret keys, and it hasn’t lead to trouble so far.

    And yes, this means you can screw up and use a signature as a secret key and the other way around. That’s not nice but I believe if you do this, that’s not a subtle bug and you don’t need to type system to help you…

  246. in include/secp256k1_extrakeys.h:118 in ac5a0a1174 outdated
    113+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_tweak_add(
    114+    const secp256k1_context* ctx,
    115+    secp256k1_pubkey *output_pubkey,
    116+    const secp256k1_xonly_pubkey *internal_pubkey,
    117+    const unsigned char *tweak32
    118+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
    


    instagibbs commented at 6:07 pm on July 14, 2020:
    missing SECP256K1_ARG_NONNULL(3)

    gmaxwell commented at 7:26 pm on July 14, 2020:
    You mean SECP256K1_ARG_NONNULL(4)

    instagibbs commented at 7:28 pm on July 14, 2020:
    err right

    jonasnick commented at 8:33 pm on July 17, 2020:
    fixed
  247. in include/secp256k1_schnorrsig.h:78 in ac5a0a1174 outdated
    59+ *  Out:   sig64: pointer to a 64-byte array to store the serialized signature (cannot be NULL)
    60+ *  In:    msg32: the 32-byte message being signed (cannot be NULL)
    61+ *       keypair: pointer to an initialized keypair (cannot be NULL)
    62+ *       noncefp: pointer to a nonce generation function. If NULL, secp256k1_nonce_function_bip340 is used
    63+ *         ndata: pointer to arbitrary data used by the nonce generation
    64+ *                function (can be NULL). If it is non-NULL and
    


    instagibbs commented at 6:09 pm on July 14, 2020:
    I don’t find (can be Null) helpful since I quickly scan for (cannot be NULL) and these look very close

    jonasnick commented at 8:33 pm on July 17, 2020:
    Hm, do you have a suggestion what to do instead? This is the same as what include/secp256k1.h does.

    instagibbs commented at 8:46 pm on July 17, 2020:
    Well in most places in same files it looks like it’s simply not stated at all. Not a big deal.
  248. in include/secp256k1_extrakeys.h:195 in ac5a0a1174 outdated
    186+ *  Args:   ctx: pointer to a context object (cannot be NULL)
    187+ *  Out: pubkey: pointer to an xonly_pubkey object. If 1 is returned, it is set
    188+ *               to the keypair public key after converting it to an
    189+ *               xonly_pubkey. If not, its value is undefined (cannot be NULL).
    190+ *    pk_parity: pointer to an integer that will be set to the pk_parity
    191+ *               argument of secp256k1_xonly_pubkey_from_pubkey (can be NULL).
    


    instagibbs commented at 6:09 pm on July 14, 2020:
    I don’t find (can be Null) helpful since I quickly scan for (cannot be NULL) and these look very close
  249. in include/secp256k1_extrakeys.h:80 in ac5a0a1174 outdated
    75+ *  Args:         ctx: pointer to a context object (cannot be NULL)
    76+ *  Out: xonly_pubkey: pointer to an x-only public key object for placing the
    77+ *                     converted public key (cannot be NULL)
    78+ *          pk_parity: pointer to an integer that will be set to 1 if the point
    79+ *                     encoded by xonly_pubkey is the negation of pubkey and set
    80+ *                     to 0 otherwise. (can be NULL)
    


    instagibbs commented at 6:09 pm on July 14, 2020:
    I don’t find (can be Null) helpful since I quickly scan for (cannot be NULL) and these look very close

    real-or-random commented at 4:32 pm on August 7, 2020:
    how is this resolved?
  250. in include/secp256k1_schnorrsig.h:53 in ac5a0a1174 outdated
    34+ *  Improvement Proposal 340 "Schnorr Signatures for secp256k1"
    35+ *  (https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki).
    36+ *
    37+ *  If a data pointer is passed, it is assumed to be a pointer to 32 bytes of
    38+ *  auxiliary random data as defined in BIP-340. If the data pointer is NULL,
    39+ *  schnorrsig_sign does not produce BIP-340 compliant signatures. The algo16
    


    instagibbs commented at 6:40 pm on July 14, 2020:
    Internally it always seems to treat it as a 16 byte array, probably should state this since it’s not an obvious length naming aside.

    jonasnick commented at 8:33 pm on July 17, 2020:
    We say that this is the “same as secp256k1_nonce function except […]” and the doc of secp256k1_nonce_function specifies “algo16: pointer to a 16-byte array describing the signature”. However, I noticed that we can drop the unused attempt argument. Then it makes more sense to copy and adapt the doc. Done.
  251. in src/modules/schnorrsig/main_impl.h:79 in ac5a0a1174 outdated
    74+    if (memcmp(algo16, bip340_algo16, 16) == 0) {
    75+        secp256k1_nonce_function_bip340_sha256_tagged(&sha);
    76+    } else {
    77+        int algo16_len;
    78+        /* Remove terminating null bytes */
    79+        algo16_len = 16;
    


    instagibbs commented at 6:47 pm on July 14, 2020:
    just curious if there’s a reason for definition on this line instead of above

    jonasnick commented at 8:34 pm on July 17, 2020:
    There’s none. Fixed.
  252. instagibbs commented at 7:04 pm on July 14, 2020: none
    The _keypair flow is new and confused me a bit until I thought through the x-only pubkey considerations. Not sure what it would look like, but some explanation would probably be useful for “layman” users. Hard to mis-use at least as-is.
  253. in include/secp256k1_extrakeys.h:136 in ac5a0a1174 outdated
    131+ *  Returns: 0 if the arguments are invalid or the output_pubkey is not the
    132+ *           result of tweaking the internal_pubkey with tweak32. 1 otherwise.
    133+ *  Args:           ctx: pointer to a context object initialized for validation
    134+ *                       (cannot be NULL)
    135+ *  In: output_pubkey32: pointer to a serialized xonly_pubkey (cannot be NULL)
    136+ *     output_pk_parity: the pk_parity value that is returned when
    


    instagibbs commented at 7:14 pm on July 14, 2020:
    micro-nitting: Then naming scheme for this arg seems very taproot-specific.

    jonasnick commented at 8:34 pm on July 17, 2020:
    I don’t see how the naming is taproot-specific. You could also call it odd_y or something but that’s unnecessarily technical. The thing I want to convey is that this belongs to the output_pk.

    instagibbs commented at 8:45 pm on July 17, 2020:
    Oh sorry, I meant output_* as a whole.

    jonasnick commented at 9:00 pm on July 17, 2020:
    Oh right! Perhaps that holds even more for internal_pubkey. But given what tweak_add does, I think those names are okay even outside of the taproot context with the benefit that they’re more understandable than alternatives in the taproot context.

    sipa commented at 6:51 pm on July 19, 2020:

    It took me a few reads before I could parse this sentence - the “that is returned” made it sound like the return value of this function was affected by this argument.

    Attempt at improving: “The expected parity of the output public key (whose serialization is passed in output_pubkey32). This must match the pk_parity value returned by secp256k1_xonly_pubkey_tweak_add when the output was created, or verification will fail.”

  254. jonasnick commented at 8:37 pm on July 17, 2020: contributor

    @instagibbs thank you for having a look!

    some explanation would be useful

    You mean an explanation of the rationale behind keypairs type or how to use it? For the latter best resource right now are the tests - in particular the taproot test. EDIT: In the future we’ll have usage examples (see #748).

    The primary reason for keypairs isn’t xonly, but that schnorrsig_sign requires a pubkey to compute the challenge hash. Recomputing the pubkey is expensive and if the function would accept a separate pubkey argument and it’s attacker controlled somehow it can result in nonce reuse.

  255. jonasnick commented at 8:37 pm on July 17, 2020: contributor
    I pushed a few fixes. In particular, tweak_add_test is renamed to tweak_add_check to avoid confusion with tests and the attempt/counter argument of nonce_function_hardened is dropped because it doesn’t have a purpose.
  256. in include/secp256k1_extrakeys.h:104 in 63bf929e60 outdated
     99+ *
    100+ *  Args:           ctx: pointer to a context object initialized for validation
    101+ *                       (cannot be NULL)
    102+ *  Out:  output_pubkey: pointer to a public key to store the result (cannot be
    103+ *                       NULL)
    104+ *  In: internal_pubkey: pointer to an x-only pubkey to apply the tweak to. Will
    


    sipa commented at 6:38 pm on July 19, 2020:
    “Will be set to an invalid value if this function returns 0”: I suspect this comment applies to another argument, as I doubt it’ll overwrite an input-only argument?

    jonasnick commented at 7:40 pm on July 20, 2020:
    it should apply to output_pubkey but it didn’t apply in all cases so I fixed that too
  257. in include/secp256k1_schnorrsig.h:31 in 63bf929e60 outdated
    26+ *  In:      msg32:     the 32-byte message hash being verified (will not be NULL)
    27+ *           key32:     pointer to a 32-byte secret key (will not be NULL)
    28+ *      xonly_pk32:     the 32-byte serialized xonly pubkey corresponding to key32
    29+ *                      (will not be NULL)
    30+ *           algo16:    pointer to a 16-byte array describing the signature
    31+ *                      algorithm (will be NULL for ECDSA for compatibility).
    


    sipa commented at 6:52 pm on July 19, 2020:
    As this style function pointer isn’t compatible with our ECDSA code, is mentioning it relevant?

    jonasnick commented at 7:40 pm on July 20, 2020:
    fixed
  258. in include/secp256k1_schnorrsig.h:95 in 63bf929e60 outdated
    90+/** Verify a Schnorr signature.
    91+ *
    92+ *  Returns: 1: correct signature
    93+ *           0: incorrect signature
    94+ *  Args:    ctx: a secp256k1 context object, initialized for verification.
    95+ *  In:    sig64: pointer to the 64-byte signatur to verify (cannot be NULL)
    


    sipa commented at 6:57 pm on July 19, 2020:
    Typo: signatur

    jonasnick commented at 7:40 pm on July 20, 2020:
    fixed
  259. in src/modules/schnorrsig/main_impl.h:2 in 63bf929e60 outdated
    0@@ -0,0 +1,230 @@
    1+/**********************************************************************
    2+ * Copyright (c) 2018 Andrew Poelstra                                 *
    


    sipa commented at 7:19 pm on July 19, 2020:
    This seems out of date.

    jonasnick commented at 7:41 pm on July 20, 2020:

    No idea how this works. How about

    0 * Copyright (c) 2020 Andrew Poelstra, Jonas Nick                          
    

    sipa commented at 8:12 pm on July 20, 2020:
    Yeah, or 2018-2020 even.
  260. in src/modules/schnorrsig/tests_impl.h:164 in 63bf929e60 outdated
    159+    secp256k1_context_destroy(sign);
    160+    secp256k1_context_destroy(vrfy);
    161+    secp256k1_context_destroy(both);
    162+}
    163+
    164+/* Checks that hash initialized by secp256k1_musig_sha256_tagged has the
    


    sipa commented at 7:31 pm on July 19, 2020:
    musig?

    jonasnick commented at 7:41 pm on July 20, 2020:
    fixed
  261. sipa commented at 7:42 pm on July 19, 2020: contributor

    I did a more thorough review of the entire diff (f39f99be0e6add959f534c03b93044cef066fe09…63bf929e60d913c6828b60ea08c96f37aa87a8ba) - only comment nits.

    Would it make sense to rebase and squash the fixups at this point? The final tree id I reviewed is 5f5227a7d432f5149d305085034b65a917d760c8.

  262. in include/secp256k1_schnorrsig.h:74 in 63bf929e60 outdated
    68+ *  randomness.
    69+ *
    70+ *  Returns 1 on success, 0 on failure.
    71+ *  Args:    ctx: pointer to a context object, initialized for signing (cannot be NULL)
    72+ *  Out:   sig64: pointer to a 64-byte array to store the serialized signature (cannot be NULL)
    73+ *  In:    msg32: the 32-byte message being signed (cannot be NULL)
    


    LLFourn commented at 11:02 am on July 20, 2020:

    Is there a strong reason not to allow signing messages of any length (so I guess passing in pointer + length)? I have been working on DLCs recently and I noticed that suredbits implemented their PoC price oracle by first hashing and then signing see: https://test.suredbits.com/api/#oracle-specification

    Oracle signatures will be Schnorr Signatures of the following hash: SHA256(moonOrCrash ++ exchange ++ pair ++ eventTime.getMillis)

    My guess is that this design is just a side effect of this API. The hash-then-sign scheme depends on collision resistance of the message hash for EUF-CMA i.e. if the adversary can control the set of messages the oracle is signing (which is not out of the question). Schnorr famously doesn’t require collision resistance (BIP340 even mentions this as a motivation). Of course, practically if CR is broken there may be bigger problems but I think it would be nice to avoid the whole Bitcoin ecosystem unnecessarily hashing stuff when they sign some non-transaction data. I don’t know if non-transaction signing is within the scope of this library though.

    cc @nkohen


    sipa commented at 5:04 pm on July 20, 2020:

    I vaguely recall having this discussions about it, and I think we just resorted to pre-hashed messages because in the context of Bitcoin transaction signing, the security inherently reduces to that, as the message being signed itself contains many hashes (at least the txids of previous transaction outputs being spent, but also various precomputed hashes to make transaction validation not quadratic in the number of inputs).

    It seems fairly easy to permit variable-length messages, as they always go at the end of hash input. However, that would require explaining that it’s only meaningful if the message itself doesn’t contain any hashes itself. I’d also need to check if it doesn’t interfere with our side channel resistance analysis.

    Regardless, this PR is following BIP340 as written (which states that messages are 32-byte arrays). We should probably either include a section (perhaps under design) to explain the choice for pre-hashed messages, or modify the document to permit variable-length messages.


    sipa commented at 5:21 pm on July 20, 2020:

    jonasnick commented at 8:19 am on July 21, 2020:

    I can get on board with this.

    One of the arguments previously against variable length messages was to keep a similar API as ecdsa, but we’ve later deviated from that by adding keypairs and changing the nonce function. It seems like a challenge a a challenge to make sure all message lengths work on all supported architectures and not adding hour long tests to our harness for long messages.

    Fwiw, I don’t think supporting variable length messages strictly necessitates BIP changes. The scheme implemented here is already only bip-schnorr compliant if you pass in random ndata. You could say same about msg_len = 32.


    real-or-random commented at 9:58 am on July 21, 2020:
    On the other hand, just because the BIP would allow variable-lengths messages, that does not imply that we need to implement them. In particular because variable-length stuff is a hassle in C. (But of course this doesn’t solve @LLFourn’s problem then.)

    gmaxwell commented at 11:37 am on July 21, 2020:

    variable-length stuff is a hassle in C.

    … you take a length argument.


    real-or-random commented at 1:38 pm on July 21, 2020:

    Sure but it’s easy for the caller to get it wrong and invalidate memory safety.

    I admit it’s not clear if this a great argument because with fixed-length arrays we just push the issue to the caller entirely. At least the caller could be in a safer language.


    gmaxwell commented at 7:46 pm on July 21, 2020:
    And if it were, it would have a .size(). Besides, this function only reads from the buffer and only turns its reads into hashes, of all possible functions that could mishandle something, that would be the least concerning. One could also provide a wrapper function that set the length, so the interface could stay the same, there would just be a _with_extended_message() version of sign and verify that took a length.

    real-or-random commented at 12:44 pm on July 22, 2020:

    A very related API discussion is whether we should export the hash module. In particular if we want to tell users “just use a hash function to obtain a 32 byte value”, then it’s kind of strange to tell them to use a different library, even though hashing is implemented here. (And they may not care about performance.) However, this has been discussed before and I mostly agree with what @sipa said in #447 (comment):

    libsecp256k1 is a library for elliptic curve crypto, not a generic cryptography library. It has a copy of SHA256 for randomization and a few related algorithms, but it is not optimized in any way. I fear that exposing the function will lead to people demanding that it gets optimized for various purposes and/or platforms, which I don’t think should be the library’s focus.

    Now with Schnorr sigs, SHA256 becomes an integral part of the library, and we’re committed to SHA256, it’s not an implementation detail anymore. So we could at least think about this again for users who don’t care about extraordinary performance. If we also provide a compile-time override #702 , the concern that people demand that it gets optimized is resolved, too.


    gmaxwell commented at 8:22 pm on July 22, 2020:

    I agree that with sha256 being a normative part of the library it makes more sense to expose it.

    Funny but I think 702 is in some ways less of a reason to expose it: one reason to expose it is so that users with small flash don’t end up with two copies. With 702 they can get libsecp2561k to use their external copy.

  263. sipa cross-referenced this on Jul 20, 2020 from issue BIP340: clarify impact of pre-hashed messages, or support variable-length messages by sipa
  264. jonasnick commented at 7:44 pm on July 20, 2020: contributor

    @sipa

    Attempt at improving: “The expected parity of the output public key (whose serialization is passed in output_pubkey32). This must match the pk_parity value returned by secp256k1_xonly_pubkey_tweak_add when the output was created, or verification will fail.”

    pk_parity is created is not created by secp256k1_xonly_pubkey_tweak_add but by secp256k1_xonly_pubkey_from_pubkey. There’s also some explanation at the top of the function. Tried to improve it nonetheless according to your suggestion.

    Would it make sense to rebase and squash the fixups at this point?

    yes, will do.

  265. in include/secp256k1_extrakeys.h:93 in 8d185a465b outdated
    88+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4);
    89+
    90+/** Tweak an x-only public key by adding tweak times the generator to it.
    91+ *
    92+ *  Note that because the resulting point may have an odd Y coordinate, it can
    93+ *  not be represented by an x-only pubkey. Instead, the output_pubkey is a
    


    benthecarman commented at 0:56 am on July 22, 2020:

    may have an odd Y coordinate, it can not be represented by an x-only pubkey

    I think it makes more grammatical sense to say

    may have an odd Y coordinate, that can not be represented by an x-only pubkey


    jonasnick commented at 11:56 am on July 22, 2020:

    Hm, this sounds wrong when you take the other part of the sentence into account:

    Note that because the resulting point may have an odd Y coordinate, that can not be represented by an x-only pubkey.

    Changed it to:

    Note that the resulting point can not be represented by an x-only pubkey because it may have an odd Y coordinate.

  266. jonasnick force-pushed on Jul 22, 2020
  267. jonasnick commented at 11:57 am on July 22, 2020: contributor
    Squashed & rebased. Pre-force pushed version of this PR is archived at https://github.com/jonasnick/secp256k1/commit/8d185a465bc63272caabd547b70cafb9968ffb30.
  268. jonasnick commented at 8:50 pm on July 22, 2020: contributor

    I went through the comments on this PR again and everything has been addressed with the exception of

    • consider allowing variable len messages. I experimented with this in https://github.com/jonasnick/secp256k1/commits/schnorrsig-varmsg. There’s not much to experiment though, it just adds a size_t msg_len argument and fixes the tests. (EDIT: this branch totally ignores domain separation)
    • the tweak apis are inconsistent (both legacy and xonly) as the seckey_tweak_add functions are constant time with respect to the tweak and pubkey_tweak_add are not. Similarly, the keypair_tweak_add is not CT w.r.t. the tweak. This PR is consistent with the legacy API and reuses much of it’s code. Therefore, if we consider changing this (I think we should), the easiest way is to change both legacy and xonly and that’s best done in a different PR.
  269. gmaxwell commented at 9:50 pm on July 22, 2020: contributor
    Variable length, if its done, can be done with another PR. Same for tweak constant time stuff for the reason you gave. You should give this PR a better title (e.g. mention Bip340, I had one person I asked to review this respond in surprise that there wasn’t a BIP assigned, I assume after they googled bip-schnorr and found an outdated doc) and also maybe remove mention of the batch stuff from the initial description (or change it to a pointer to the batch PR)
  270. jonasnick renamed this:
    Add schnorrsig module which implements BIP-schnorr compliant signatures
    Add schnorrsig module which implements BIP-340 compliant signatures
    on Jul 23, 2020
  271. jonasnick commented at 7:21 pm on July 23, 2020: contributor

    Done. The link in the description pointed to an outdated bip-schnorr document.

    I agree that variable length can be done in a different PR. It may even be preferable to add a new function for this because it’ll need some mechanism to deal with domain separation.

  272. jonasnick cross-referenced this on Jul 27, 2020 from issue Update to latest Schnorrsig and MuSig by jonasnick
  273. in include/secp256k1_extrakeys.h:13 in 95dd2c73c5 outdated
     6@@ -7,6 +7,75 @@
     7 extern "C" {
     8 #endif
     9 
    10+/** Opaque data structure that holds a parsed and valid "x-only" public key.
    11+ *  An x-only pubkey encodes a point whose Y coordinate is even. It is
    12+ *  serialized using only its X coordinate (32 bytes). See BIP-340 for more
    13+ *  information about x-only pubkeys.
    


    chris-belcher commented at 1:01 pm on July 27, 2020:
    I think this source code is not consistent with the BIP340 document. The BIP talks about going with the option of implicitly choosing the Y coordinate that is a quadratic residue, not choosing the even Y coordinate.
  274. chris-belcher commented at 1:03 pm on July 27, 2020: none
    I’ve read all the code in every commit, especially commit https://github.com/bitcoin-core/secp256k1/pull/558/commits/47dd51f6a16d30b072e6ba2182b6cc65000164f6 which seems to be where all the heavy lifting is done. I’m not an expert in cryptography, but I know how schnorr and ecdsa are meant to work. So hopefully this review is still useful. I only noticed an issue with one of the comments in the source code which I flagged up.
  275. real-or-random commented at 1:22 pm on July 27, 2020: contributor

    I’ve read all the code in every commit, especially commit 47dd51f which seems to be where all the heavy lifting is done. I’m not an expert in cryptography, but I know how schnorr and ecdsa are meant to work.

    This is more than enough for a useful review!

    I only noticed an issue with one of the comments in the source code which I flagged up.

    Can you point us to your comment again? I can’t find it. (Sometimes GitHub seem to not display comments, in particular when you review the PR commit-by-commit and then comment on a single commit. Maybe that’s the issue here.)

  276. jonasnick commented at 1:23 pm on July 27, 2020: contributor
    @chris-belcher Thanks for the review! I can’t fnd your comment here, but I saw it in my email inbox. Implicitly choosing the pubkey based on the evenness of the Y coordinate is consistent with BIP-340. But it’s inconsistent with an older version of the BIP in @sipa’s bips fork which is the first result when I google “bip-schnorr”. @sipa can you remove that branch?
  277. sipa commented at 11:16 pm on July 27, 2020: contributor

    ACK 2c8e321a1b543e1f008a32c0a9091b752ccf8e72

    Reviewed the diff with my previous review (only rebase, addressing a few comments, secp256k1_xonly_pubkey_tweak_add wipes its output slightly earlier, test_xonly_pubkey_tweak_add_check is renamed to test_xonly_pubkey_tweak_add), and re-reviewed 47dd51f6a16d30b072e6ba2182b6cc65000164f6 on its own.

    The variable-length API I think needs some more discussion, and can be done later.

  278. sipa commented at 11:22 pm on July 27, 2020: contributor
    @jonasnick Deleted my outdated bip-schnorr branch (which was also my default branch, changed that as well).
  279. chris-belcher commented at 1:24 pm on July 31, 2020: none
    I forgot to click “Submit review” on my comment, which is why it wasn’t showing up. Fixed now.
  280. chris-belcher commented at 1:27 pm on July 31, 2020: none

    @chris-belcher Thanks for the review! I can’t fnd your comment here, but I saw it in my email inbox. Implicitly choosing the pubkey based on the evenness of the Y coordinate is consistent with BIP-340. But it’s inconsistent with an older version of the BIP in @sipa’s bips fork which is the first result when I google “bip-schnorr”. @sipa can you remove that branch?

    I’m reading the bip document here which is linked from the OP. I’ll quote the part which confuses me (and add my italics):

    Implicit Y coordinates In order to support efficient verification and batch verification, the Y coordinate of P and of R cannot be ambiguous (every valid X coordinate has two possible Y coordinates). We have a choice between several options for symmetry breaking:

    1. Implicitly choosing the Y coordinate that is in the lower half.
    2. Implicitly choosing the Y coordinate that is even[5].
    3. Implicitly choosing the Y coordinate that is a quadratic residue (has a square root modulo the field size, or “is a square” for short)[6].

    In the case of R the third option is slower at signing time but a bit faster to verify, as it is possible to directly compute whether the Y coordinate is a square when the points are represented in Jacobian coordinates (a common optimization to avoid modular inverses for elliptic curve operations). The two other options require a possibly expensive conversion to affine coordinates first. This would even be the case if the sign or oddness were explicitly coded (option 2 in the list above). We therefore choose option 3.

    By my reading, it looks like the bip uses the quadratic residue to determine the Y coordinate (i.e. option 3). and not the evenness (i.e. option 2). What am I missing?

  281. jonasnick commented at 1:36 pm on July 31, 2020: contributor
    @chris-belcher You’re missing the first few words in the paragraph: “In the case of R” (the nonce). That’s what “We therefore choose option 3” refers to. The next paragraph states “For P[…]. We choose the second option”.
  282. in src/modules/schnorrsig/main_impl.h:144 in 2c8e321a1b outdated
    139+        secp256k1_scalar_negate(&sk, &sk);
    140+    }
    141+
    142+    secp256k1_scalar_get_b32(seckey, &sk);
    143+    secp256k1_fe_get_b32(pk_buf, &pk.x);
    144+    ret &= !!noncefp(buf, msg32, seckey, pk_buf, bip340_algo16, (void*)ndata);
    


    roconnor-blockstream commented at 4:31 pm on August 5, 2020:
    Nit: The casting of ndata to void* seems unnecessary.

    jonasnick commented at 4:14 pm on August 18, 2020:
    fixed
  283. real-or-random commented at 5:43 pm on August 5, 2020: contributor
    needs rebase (sorry :/)
  284. in src/group_impl.h:249 in 2c8e321a1b outdated
    240@@ -241,6 +241,18 @@ static int secp256k1_ge_set_xo_var(secp256k1_ge *r, const secp256k1_fe *x, int o
    241 
    242 }
    243 
    244+static void secp256k1_ge_even_y(secp256k1_ge *r, int *y_parity) {
    245+    if (y_parity != NULL) {
    246+        *y_parity = 0;
    247+    }
    248+    if (secp256k1_fe_is_odd(&r->y)) {
    249+        secp256k1_ge_neg(r, r);
    


    roconnor-blockstream commented at 5:52 pm on August 5, 2020:
    I don’t know if you care, but technically it is faster to call secp256k1_fe_negate(&r->y, &r->y, 1) here. fe_is_odd already requires r->y to be fully normalized as a prerequisite.

    jonasnick commented at 7:42 pm on August 7, 2020:
    I don’t care about the 4 nanoseconds, but I care about removing unnecessary stuff. Better to use fe_negate. Also, this function seems to be out of place in group.h, because no other function there assumes that the coordinates are normalized. Perhaps best to move this function into extrakeys/main_impl.h
  285. in src/modules/extrakeys/main_impl.h:37 in 2c8e321a1b outdated
    32+    }
    33+    if (!secp256k1_ge_set_xo_var(&pk, &x, 0)) {
    34+        return 0;
    35+    }
    36+    secp256k1_xonly_pubkey_save(pubkey, &pk);
    37+    secp256k1_ge_clear(&pk);
    


    roconnor-blockstream commented at 2:41 pm on August 6, 2020:
    Why clear pk (but not x)?

    gmaxwell commented at 5:35 pm on August 6, 2020:
    Since this only handles public input ISTM the clear is just a copy and paste from some private key handling function

    jonasnick commented at 1:41 pm on August 7, 2020:
    It’s a copy and paste from a public key handling function, in particular ec_pubkey_parse. Not idea why it’s there, but I’d be happy to remove it.
  286. in src/modules/extrakeys/main_impl.h:124 in 2c8e321a1b outdated
    119+    ret = !secp256k1_scalar_is_zero(sk);
    120+    /* We can declassify ret here because sk is only zero if a keypair function
    121+     * failed (which zeroes the keypair) and its return value is ignored. */
    122+    secp256k1_declassify(ctx, &ret, sizeof(ret));
    123+    ARG_CHECK(ret);
    124+    return 1;
    


    roconnor-blockstream commented at 3:09 pm on August 6, 2020:
    Mentioned this to jonas already: probably better to return ret here.

    gmaxwell commented at 5:37 pm on August 6, 2020:
    Why was this arg_checking it to begin with? Argchecks are for testing if the caller has violated the calling conventions. I just checked the entire PR and this appears to be the only case of arg_check misuse.

    jonasnick commented at 6:01 pm on August 6, 2020:

    pubkey_load uses ARG_CHECK in the same way to fire an illegal callback if a zeroed pubkey is used.

    0/* don't check return value */
    1do_something_with_keypair_that_fails(keypair);
    2/* keypair is zeroed, ARG_CHECK fails */
    3do_something_with_secret_part_of_keypair_that_fails(keypair);
    

    gmaxwell commented at 6:36 pm on August 6, 2020:
    Ah. Ignore me: I didn’t look at the context and thought this was a deserializer. It’s good to argcheck on the validity of a libsecp256k1 constructed object.

    jonasnick commented at 4:10 pm on August 18, 2020:
    fixed
  287. in src/modules/extrakeys/main_impl.h:210 in 2c8e321a1b outdated
    205+    ret = secp256k1_keypair_load(ctx, &sk, &pk, keypair);
    206+    memset(keypair, 0, sizeof(*keypair));
    207+
    208+    if (secp256k1_fe_is_odd(&pk.y)) {
    209+        secp256k1_scalar_negate(&sk, &sk);
    210+        secp256k1_ge_neg(&pk, &pk);
    


    roconnor-blockstream commented at 12:05 pm on August 7, 2020:
    Same comment as secp256k1_ge_even_y about considering using secp256k1_fe_negate(&pk.y, &pk.y, 1). Alternatively, could also call secp256k1_ge_even_y here and negate sk based on the y_parity flag value.

    jonasnick commented at 2:39 pm on August 7, 2020:
    hm yeah better to use secp256k1_ge_even_y here

    jonasnick commented at 4:10 pm on August 18, 2020:
    done
  288. in src/secp256k1.c:637 in 2c8e321a1b outdated
    636-    secp256k1_scalar_set_b32(&term, tweak, &overflow);
    637     ret = secp256k1_scalar_set_b32_seckey(&sec, seckey);
    638-
    639-    ret &= (!overflow) & secp256k1_eckey_privkey_tweak_add(&sec, &term);
    640+    ret &= secp256k1_ec_seckey_tweak_add_helper(&sec, tweak);
    641     secp256k1_scalar_cmov(&sec, &secp256k1_scalar_zero, !ret);
    


    roconnor-blockstream commented at 12:49 pm on August 7, 2020:
    Should the secp256k1_scalar_cmov be part of secp256k1_ec_seckey_tweak_add_helper?

    jonasnick commented at 1:47 pm on August 7, 2020:
    Currently that cmov takes into account whether scalar_set_b32_seckey failed because it’s part of ret. If cmov was in the helper then the function can fail due to the seckey overflowing the scalar, but the seckey wouldn’t be zeroed after the function call.

    roconnor-blockstream commented at 3:22 pm on August 7, 2020:
    Yes of course. You are right.
  289. in src/tests.c:456 in 2c8e321a1b outdated
    451+    /* Compare the struct excluding the buffer, because it may be
    452+     * uninitialized or already included in the state. */
    453+    CHECK(sha1->bytes == sha2->bytes);
    454+    CHECK(memcmp(sha1->s, sha2->s, sizeof(sha1->s)) == 0);
    455+
    456+    /* Compare the output */
    


    roconnor-blockstream commented at 3:45 pm on August 7, 2020:
    I’m fairly certain this section is unnecessary.

    jonasnick commented at 4:06 pm on August 7, 2020:
    It’s used in schnorrsig/tests_impl.h and should be probably moved there. The section is unnecessary indeed. Perhaps this was some kind of sanity check.

    roconnor-blockstream commented at 4:41 pm on August 7, 2020:
    If removed the arguments can be made const.

    jonasnick commented at 4:15 pm on August 18, 2020:
    fixed
  290. in include/secp256k1_extrakeys.h:229 in 2c8e321a1b outdated
    222+ *                   is negligible (around 1 in 2^128) (cannot be NULL).
    223+ */
    224+SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_keypair_xonly_tweak_add(
    225+    const secp256k1_context* ctx,
    226+    secp256k1_keypair *keypair,
    227+    const unsigned char *tweak32
    


    roconnor-blockstream commented at 4:14 pm on August 7, 2020:
    I believe that this is the only function that doesn’t have an parity flip output value.

    jonasnick commented at 8:45 am on August 17, 2020:
    xonly_pubkey_tweak_add doesn’t have that output value either. You get it when extracting the xonly pubkey from the keypair.
  291. in include/secp256k1_extrakeys.h:44 in 2c8e321a1b outdated
    39+ *  Returns: 1 if the public key was fully valid.
    40+ *           0 if the public key could not be parsed or is invalid.
    41+ *
    42+ *  Args:   ctx: a secp256k1 context object (cannot be NULL).
    43+ *  Out: pubkey: pointer to a pubkey object. If 1 is returned, it is set to a
    44+ *               parsed version of input. If not, its value is undefined (cannot
    


    real-or-random commented at 4:33 pm on August 7, 2020:
    Can we say “unspecified”? “Undefined” always smells like UB.

    jonasnick commented at 9:37 am on August 17, 2020:
    We’re already using “invalid value” for tweak_add. I think that’s the clearest phrasing.
  292. in include/secp256k1_extrakeys.h:90 in 2c8e321a1b outdated
    85+    secp256k1_xonly_pubkey *xonly_pubkey,
    86+    int *pk_parity,
    87+    const secp256k1_pubkey *pubkey
    88+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4);
    89+
    90+/** Tweak an x-only public key by adding tweak times the generator to it.
    


    real-or-random commented at 4:35 pm on August 7, 2020:
    0/** Tweak an x-only public key by adding tweak32 times the generator to it.
    

    Still reads a little strange. Maybe add backticks? Or simpler: by “adding a multiple of the generator”


    jonasnick commented at 4:07 pm on August 18, 2020:
    fixed
  293. in include/secp256k1_extrakeys.h:92 in 2c8e321a1b outdated
    87+    const secp256k1_pubkey *pubkey
    88+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4);
    89+
    90+/** Tweak an x-only public key by adding tweak times the generator to it.
    91+ *
    92+ *  Note that the resulting point can not be represented by an x-only pubkey
    


    real-or-random commented at 4:36 pm on August 7, 2020:
    0 *  Note that the resulting point can not in general be represented by an x-only pubkey
    

    jonasnick commented at 4:07 pm on August 18, 2020:
    fixed
  294. in include/secp256k1_extrakeys.h:100 in 2c8e321a1b outdated
     95+ *
     96+ *  Returns: 0 if the arguments are invalid or the resulting public key would be
     97+ *           invalid (only when the tweak is the negation of the corresponding
     98+ *           secret key). 1 otherwise.
     99+ *
    100+ *  Args:           ctx: pointer to a context object initialized for validation
    


    real-or-random commented at 4:37 pm on August 7, 2020:
    0 *  Args:           ctx: pointer to a context object initialized for verification
    

    jonasnick commented at 4:07 pm on August 18, 2020:
    fixed
  295. in include/secp256k1_extrakeys.h:203 in 2c8e321a1b outdated
    198+    secp256k1_xonly_pubkey *pubkey,
    199+    int *pk_parity,
    200+    const secp256k1_keypair *keypair
    201+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4);
    202+
    203+/** Tweak a keypair by adding tweak times the generator to the x-only public key
    


    real-or-random commented at 4:39 pm on August 7, 2020:
    tweak32 as above

    jonasnick commented at 4:08 pm on August 18, 2020:
    fixed
  296. in include/secp256k1_schnorrsig.h:24 in 2c8e321a1b outdated
    19+ *  additional pubkey argument and not requiring an attempt argument. The pubkey
    20+ *  argument can protect signature schemes with key-prefixed challenge hash
    21+ *  inputs against reusing the nonce when signing with the wrong precomputed
    22+ *  pubkey.
    23+ *
    24+ *  Returns: 1 if a nonce was successfully generated. 0 will cause signing to fail.
    


    real-or-random commented at 4:42 pm on August 7, 2020:
    0 *  Returns: 1 if a nonce was successfully generated. 0 will cause signing to abort.
    

    I know it’s subtle but I think “abort” makes clearer that no signature is produced.


    gmaxwell commented at 4:59 pm on August 7, 2020:
    sounds like abort(). Return an error.

    jonasnick commented at 4:08 pm on August 18, 2020:
    fixed
  297. in include/secp256k1_schnorrsig.h:35 in 2c8e321a1b outdated
    30+ *           algo16:    pointer to a 16-byte array describing the signature
    31+ *                      algorithm (will not be NULL).
    32+ *           data:      Arbitrary data pointer that is passed through.
    33+ *
    34+ *  Except for test cases, this function should compute some cryptographic hash of
    35+ *  the message, the key, the pubkey the algorithm and data.
    


    real-or-random commented at 4:42 pm on August 7, 2020:
    0 *  the message, the key, the pubkey, the algorithm description, and data.
    

    jonasnick commented at 4:08 pm on August 18, 2020:
    fixed
  298. in include/secp256k1_schnorrsig.h:33 in 2c8e321a1b outdated
    27+ *           key32:     pointer to a 32-byte secret key (will not be NULL)
    28+ *      xonly_pk32:     the 32-byte serialized xonly pubkey corresponding to key32
    29+ *                      (will not be NULL)
    30+ *           algo16:    pointer to a 16-byte array describing the signature
    31+ *                      algorithm (will not be NULL).
    32+ *           data:      Arbitrary data pointer that is passed through.
    


    real-or-random commented at 4:44 pm on August 7, 2020:
    Maybe we should make it more explicit that this additional entropy? Or would it be better to have two separate arguments, one for additional entropy (maybe fixed length or with length description?) and one for arbitrary user data?

    gmaxwell commented at 5:06 pm on August 7, 2020:

    That is up to the nonce function.

    For example, there is a nonce function that uses the data function to exfiltrate the nonce itself for use in ECDH to generate stealth addresses. For someone that needed very low latency signing, I once wrote a nonce function that took an argument to a datastructure of precomputed nonces which it managed..

    Two arguments would address that– e.g. one always for extra stuff, but OTOH, the singing function might end up with too many arguments that the user is always stuffing NULL into? Maybe less of an issue if synthetic nonces are strongly encouraged. But those could also be accomplished another way– e.g. passing randomness into the context at init time. (requires malleable signing context)


    real-or-random commented at 5:23 pm on August 7, 2020:

    related: #757

    Two arguments would address that– e.g. one always for extra stuff, but OTOH, the singing function might end up with too many arguments that the user is always stuffing NULL into?

    If there are too many NULLs, maybe we should just have a wrapper that does not allow specifying the nonce function. Specifying the nonce function is pretty advanced stuff anyway (“do this only if you know what you’re doing”).


    jonasnick commented at 5:59 pm on August 7, 2020:

    Maybe we should make it more explicit that this additional entropy? Or would it be better to have two separate arguments […] ?

    I don’t think so, as said above this is up to the nonce function. It’s easy to put both randomness and your additional data in the data arg of your custom nonce function. The doc for nonce data in schnorrsig_sign says If it is non-NULL and secp256k1_nonce_function_bip340 is used, then ndata must be a pointer to 32-byte auxiliary randomness as per BIP-340.

    But I’d be fine with making schnorrsig_sign a function that doesn’t take a nonce function argument and the noncedata arg is renamed to randomness (or something). related: #589 which shows that the best way to implement sign-to-contract (and therefore anti-covert-channel mechanisms) is not via the nonce function. Therefore, changing the nonce function is probably rarely used compared to normal signing. Moreover, there’s little benefit to keep this similar to the ecdsa_sign API because it already takes a keypair instead of a secret key byte array.


    real-or-random commented at 6:21 pm on August 7, 2020:

    But I’d be fine with making schnorrsig_sign a function that doesn’t take a nonce function argument and the noncedata arg is renamed to randomness (or something).

    You mean remove the ability to pass custom functions entirely? Or have two sign functions, one with and without the feature?

    Yeah, maybe the user just doesn’t need this at all. Anyway the nonce function is very fast, and if the user wants randomized signing and has some magic other entropy source for any reason, it’s possible to pass entropy. Is there any other reason why the user would want this?

    Removing it would also leave us with more flexibility for keeping an additional counter in the signing context if we want to do something like _sign_and_randomize in the future (https://github.com/bitcoin-core/secp256k1/issues/780#issuecomment-670531851). (Otherwise, where would the counter go for user-defined nonce functions? “Nowhere” is a possible answer but maybe not a great one.).

    edit: One could make the point that committing to a fixed API was not great for ECDSA because things like synthetic nonces or hashing in the pubkey came up only later.


    jonasnick commented at 6:31 pm on August 7, 2020:
    Two functions. The one without the feature gets to have the simplest sign() name.
  299. real-or-random commented at 4:46 pm on August 7, 2020: contributor
    I looked at the API. I’ll have closer look again soon!
  300. in src/group_impl.h:244 in 2c8e321a1b outdated
    240@@ -241,6 +241,18 @@ static int secp256k1_ge_set_xo_var(secp256k1_ge *r, const secp256k1_fe *x, int o
    241 
    242 }
    243 
    244+static void secp256k1_ge_even_y(secp256k1_ge *r, int *y_parity) {
    


    roconnor-blockstream commented at 5:00 pm on August 7, 2020:
    Add VERIFY_CHECK(!r->infinity);

    jonasnick commented at 4:09 pm on August 18, 2020:
    done
  301. in src/modules/extrakeys/main_impl.h:63 in 2c8e321a1b outdated
    58+
    59+    VERIFY_CHECK(ctx != NULL);
    60+    ARG_CHECK(xonly_pubkey != NULL);
    61+    ARG_CHECK(pubkey != NULL);
    62+
    63+    secp256k1_pubkey_load(ctx, &pk, pubkey);
    


    roconnor-blockstream commented at 5:54 pm on August 7, 2020:
    You have failed to check the return value of secp256k1_pubkey_load. While it is true that this load can never fail as implemented, you seem to do a careful job of checking for load failure everywhere else.

    jonasnick commented at 6:07 pm on August 7, 2020:
    Good catch. The corresponding test is also missing. pubkey_load can fail if you give it a zeroed pubkey.

    roconnor-blockstream commented at 6:53 pm on August 7, 2020:
    Oh I didn’t realize that ARG_CHECK can return 0.

    jonasnick commented at 4:09 pm on August 18, 2020:
    fixed
  302. in src/modules/extrakeys/main_impl.h:118 in 2c8e321a1b outdated
    113+
    114+
    115+static int secp256k1_keypair_seckey_load(const secp256k1_context* ctx, secp256k1_scalar *sk, const secp256k1_keypair *keypair) {
    116+    int ret;
    117+
    118+    secp256k1_scalar_set_b32(sk, &keypair->data[0], NULL);
    


    roconnor-blockstream commented at 6:17 pm on August 7, 2020:
    Maybe use secp256k1_scalar_set_b32_seckey instead?

    roconnor-blockstream commented at 6:18 pm on August 7, 2020:
    int ret = secp256k1_scalar_set_b32_seckey(sk, &keypair->data[0]);

    jonasnick commented at 4:10 pm on August 18, 2020:
    Yup, set_b32_seckey didn’t exist when I wrote this. Done.
  303. in src/modules/extrakeys/main_impl.h:214 in 2c8e321a1b outdated
    209+        secp256k1_scalar_negate(&sk, &sk);
    210+        secp256k1_ge_neg(&pk, &pk);
    211+    }
    212+
    213+    ret &= secp256k1_ec_seckey_tweak_add_helper(&sk, tweak32);
    214+    secp256k1_scalar_cmov(&sk, &secp256k1_scalar_zero, !ret);
    


    roconnor-blockstream commented at 6:47 pm on August 7, 2020:
    Does this cmov serve a purpose? It seems to only provide an early clear of sk. But that seems better accomplished by executing secp256k1_ec_pubkey_tweak_add_helper before secp256k1_ec_seckey_tweak_add_helper instead and eliminating the cmov.

    apoelstra commented at 12:20 pm on August 11, 2020:
    Agreed; the only place sk is used below is in secp256k1_keypair_save but this is already gated on if(ret).

    jonasnick commented at 4:14 pm on August 18, 2020:
    Hmmm no idea why I added this line… I removed it again.
  304. in src/modules/schnorrsig/main_impl.h:224 in 2c8e321a1b outdated
    217+    secp256k1_sha256_write(&sha, msg32, 32);
    218+    secp256k1_sha256_finalize(&sha, buf);
    219+    secp256k1_scalar_set_b32(&e, buf, NULL);
    220+
    221+    /* Compute rj =  s*G + (-e)*pkj */
    222+    secp256k1_scalar_negate(&e, &e);
    


    roconnor-blockstream commented at 8:26 pm on August 7, 2020:
    You probably don’t care, but I feel compelled to point out that it is technically faster to load the odd-variant of the xonly public key (which is normally the even variant) rather than negating the scalar e here.

    jonasnick commented at 12:01 pm on August 17, 2020:
    You mean essentially replacing the e negation with secp256k1_fe_negate(&pk.y, &pk.y, 1);?

    sipa commented at 4:21 am on September 8, 2020:
    I think he means doing the negation at secp256k1_xonly_pubkey_parse time, where currently a negation happens 50% of the time in one direction (and instead doing it the other 50% in the other direction). Seems very hard to do in a way that results in a sane API.

    roconnor-blockstream commented at 4:43 pm on September 8, 2020:
    Indeed. Now that I look again, it is even harder to implement (API-wise) than I had realized at the time I wrote my comment.
  305. gmaxwell cross-referenced this on Aug 7, 2020 from issue Add arm8 tests to the testing configuration. by gmaxwell
  306. gmaxwell commented at 11:04 pm on August 9, 2020: contributor
    Anyone written an exhaustive test? I just noticed there isn’t one in this PR.
  307. in src/bench_schnorrsig.c:57 in c024c80af3 outdated
    52+    int i;
    53+    bench_schnorrsig_data data;
    54+    int iters = get_iters(10000);
    55+
    56+    data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN);
    57+    data.scratch = secp256k1_scratch_space_create(data.ctx, 1024 * 1024 * 1024);
    


    gmaxwell commented at 1:00 am on August 10, 2020:
    A gigabyte is a little absurd, especially since this doesn’t currently use scratch space.

    jonasnick commented at 4:08 pm on August 18, 2020:
    removed the scratch space from the benchmark
  308. gmaxwell commented at 1:03 am on August 10, 2020: contributor

    Test short-comings:

    This should be caught both by a real test and probably by a vector (perhaps ideally by a vector that would be a valid signature if reduced mod the order), and ideally the boundary should also be tested exactly (but I think an exact boundary test would require mocking the schnorr hash).

     0diff --git a/src/modules/schnorrsig/main_impl.h b/src/modules/schnorrsig/main_impl.h
     1index 2ec1cea..d8db70a 100644
     2--- a/src/modules/schnorrsig/main_impl.h
     3+++ b/src/modules/schnorrsig/main_impl.h
     4@@ -202,7 +202,7 @@ int secp256k1_schnorrsig_verify(const secp256k1_context* ctx, const unsigned cha
     5     }
     6 
     7     secp256k1_scalar_set_b32(&s, &sig64[32], &overflow);
     8-    if (overflow) {
     9+    if (0&&overflow) {
    10         return 0;
    11     }
    12 
    

    This one appears to be only tested by the BIP340 vectors. I think that ideally all major functionality should be tested by both a static vector and some kind of dynamic test since vectors are extremely brittle. The brittleness is both an asset because they can be very sensitive and a liability because they tend to get blindly replaced when there are major changes (which were expected to break them) and aren’t very broad (a bug might just happen to pass the few things there are vectors for).

    This one can be resolved with a test that randomly signs stuff and negates the s and makes sure the result never passes.

     0 diff --git a/src/modules/schnorrsig/main_impl.h b/src/modules/schnorrsig/main_impl.h
     1index 2ec1cea..5efdee4 100644
     2--- a/src/modules/schnorrsig/main_impl.h
     3+++ b/src/modules/schnorrsig/main_impl.h
     4@@ -223,8 +223,7 @@ int secp256k1_schnorrsig_verify(const secp256k1_context* ctx, const unsigned cha
     5     secp256k1_gej_set_ge(&pkj, &pk);
     6     secp256k1_ecmult(&ctx->ecmult_ctx, &rj, &pkj, &e, &s);
     7 
     8-    return secp256k1_gej_has_quad_y_var(&rj) /* fails if rj is infinity */
     9-            && secp256k1_gej_eq_x_var(&rx, &rj);
    10+    return secp256k1_gej_eq_x_var(&rx, &rj);
    11 }
    12 
    13 #endif
    

    I admit I’m being extremely nitpicky here, but I think it is best to not only have tests on a few constant values, and this passes with the BIP340 vectors off. It should be easy to also test secp256k1_xonly_pubkey_parse on random points on the twist in addition to zero. (though zero is a pretty good and important value too)

     0diff --git a/src/modules/extrakeys/main_impl.h b/src/modules/extrakeys/main_impl.h
     1index a2abc6a..c5573dd 100644
     2--- a/src/modules/extrakeys/main_impl.h
     3+++ b/src/modules/extrakeys/main_impl.h
     4@@ -30,7 +30,8 @@ int secp256k1_xonly_pubkey_parse(const secp256k1_context* ctx, secp256k1_xonly_p
     5     if (!secp256k1_fe_set_b32(&x, input32)) {
     6         return 0;
     7     }
     8-    if (!secp256k1_ge_set_xo_var(&pk, &x, 0)) {
     9+    secp256k1_ge_set_xo_var(&pk, &x, 0);
    10+    if (secp256k1_fe_is_zero(&x)) {
    11         return 0;
    12     }
    13     secp256k1_xonly_pubkey_save(pubkey, &pk);
    

    [offtopic comment: ./tests 2 is a bit weak on some of these tests, I’m guessing that it is also weak on other ones outside of this module. It’s only worth mentioning because some of the CI tests run with 2. I don’t think anything should be changed in this PR with respect to that, though perhaps all the fast random tests should get their base counts multiplied a bit so if they’re 50/50 likely to pass they’ll still get pretty reliably detected even with ./tests 2.]

    I think the tests in this PR are extremely good regardless of the above nitpicks.

  309. in src/modules/extrakeys/tests_impl.h:116 in 95dd2c73c5 outdated
    111+    CHECK(memcmp(&xonly_pk, &xonly_pk_tmp, sizeof(xonly_pk)) == 0);
    112+
    113+    /* Can't parse a byte string that's not a valid X coordinate */
    114+    memset(ones32, 0xFF, sizeof(ones32));
    115+    CHECK(secp256k1_xonly_pubkey_parse(ctx, &xonly_pk_tmp, ones32) == 0);
    116+    CHECK(memcmp(&xonly_pk_tmp, zeros64, sizeof(xonly_pk_tmp)) == 0);
    


    apoelstra commented at 1:02 am on August 11, 2020:
    Can you add a final CHECK(ecount == 2); just to confirm that neither of these failures were ARG_CHECK failures?

    jonasnick commented at 4:16 pm on August 18, 2020:
    done
  310. in include/secp256k1_extrakeys.h:124 in 8409443226 outdated
    119+ *
    120+ *  Returns: 0 if the arguments are invalid or the output pubkey is not the
    121+ *           result of tweaking the internal_pubkey with tweak32. 1 otherwise.
    122+ *  Args:           ctx: pointer to a context object initialized for validation
    123+ *                       (cannot be NULL)
    124+ *  In: output_pubkey32: pointer to a serialized xonly_pubkey (cannot be NULL)
    


    apoelstra commented at 1:23 am on August 11, 2020:
    I think these should be called tweaked_pubkey32 or similar. It’s confusing (to me) to have output in the name of a variable which is not, in fact, an out-pointer.

    jonasnick commented at 4:07 pm on August 18, 2020:
    okay, fixed
  311. in src/modules/extrakeys/tests_impl.h:271 in 8409443226 outdated
    245+    CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, buf32, pk_parity, &internal_xonly_pk, tweak) == 0);
    246+
    247+    /* Overflowing tweak not allowed */
    248+    CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, output_pk32, pk_parity, &internal_xonly_pk, overflows) == 0);
    249+    CHECK(secp256k1_xonly_pubkey_tweak_add(ctx, &output_pk, &internal_xonly_pk, overflows) == 0);
    250+    CHECK(memcmp(&output_pk, zeros64, sizeof(output_pk)) == 0);
    


    apoelstra commented at 1:29 am on August 11, 2020:
    Similar to the other tests, I’d like this function to end with another CHECK(ecount == 5); i.e. check that none of the 0-returns were ARG_CHECK failures

    jonasnick commented at 4:16 pm on August 18, 2020:
    done
  312. in src/tests.c:449 in e8a3d6ce06 outdated
    444+void test_sha256_eq(secp256k1_sha256 *sha1, secp256k1_sha256 *sha2) {
    445+    unsigned char buf[32] = { 0 };
    446+    unsigned char buf2[32];
    447+
    448+    /* Is buffer fully consumed? */
    449+    CHECK((sha1->bytes & 0x3F) == 0);
    


    apoelstra commented at 3:09 pm on August 11, 2020:
    Should we check sha2 as well? Also should this be a VERIFY_CHECK? I guess in tests.c the distinction doesn’t really matter.

    gmaxwell commented at 4:29 pm on August 11, 2020:
    Tests should use CHECK, because its useful to run the tests without -DVERIFY to make sure that the verifys aren’t fixing a bug (e.g. with a side effect in a verify macro)– and you don’t want all the tests to go away when you do that. :) There is currently no VERIFY_CHECK in tests.c

    jonasnick commented at 1:22 pm on August 18, 2020:

    Should we check sha2 as well?

    we check in the next line that sha1->bytes == sha2->bytes

  313. apoelstra commented at 5:22 pm on August 11, 2020: contributor

    ACK 2c8e321a1b543e1f008a32c0a9091b752ccf8e72 aside from nits others have noted. Ran test suite and benchmarks (for those commits which had them) in valgrind for every commit. Would be nice to have an exhaustive test.

    This does need a rebase though :)

  314. jonasnick commented at 4:41 pm on August 18, 2020: contributor

    I pushed a few commits that should addresses all comments with the exception of

    Let me know if the new commits are sufficient. I’ll squash and rebase then. @gmaxwell

    0> -    if (overflow) {
    1> +    if (0&&overflow) {
    

    I don’t think we can fix that with the current testing capabilities (i.e. without mocking the hash function, or low order generator mode). This branch is covered by test vector 13, but vector 13 fails regardless of the branch. I added a randomized test anyway.

    0-    return secp256k1_gej_has_quad_y_var(&rj) /* fails if rj is infinity */
    1-            && secp256k1_gej_eq_x_var(&rx, &rj);
    2+    return secp256k1_gej_eq_x_var(&rx, &rj);
    

    Added randomized test.

    0-    if (!secp256k1_ge_set_xo_var(&pk, &x, 0)) {
    1+    secp256k1_ge_set_xo_var(&pk, &x, 0);
    2+    if (secp256k1_fe_is_zero(&x)) {
    

    That’s quite a sophisticated mutation. Added randomized test.

  315. in src/modules/extrakeys/main_impl.h:67 in cea1ed2b60 outdated
    62+    }
    63+    if (secp256k1_fe_is_odd(&r->y)) {
    64+        secp256k1_fe_negate(&r->y, &r->y, 1);
    65+        if (y_parity != NULL) {
    66+            *y_parity = 1;
    67+        }
    


    gmaxwell commented at 5:19 pm on August 22, 2020:
    Is there a reason that y_parity is an outpointer instead of just a return value? I think this and the calling code would be cleaner to just return it.

    jonasnick commented at 12:39 pm on August 23, 2020:
    I was hoping someone would notice this so I could fix it :) Done.

    roconnor-blockstream commented at 11:03 am on August 24, 2020:
    Darn. I did notice this, but I just assumed that there was some policy that only error codes are returned by functions in libsecp256k1. I should have at least asked.
  316. gmaxwell commented at 5:27 pm on August 22, 2020: contributor
    The above changes all look fine to me and can be squashed whenever people are done looking at them.
  317. gmaxwell commented at 12:36 pm on August 24, 2020: contributor
    Thanks! that looks good!
  318. sipa commented at 0:40 am on August 25, 2020: contributor

    ACK all the fixup changes. I’m happy with them being squashed.

    How are you thinking of making the even-r changes? A separate commit on top that gets squashed in after the BIP change is complete?

  319. Make the secp256k1_declassify argument constant
    This is required to declassify pointers to constant memory. Declassify should
    never modify its argument.
    3e08b02e2a
  320. extrakeys: Init empty experimental module
    This is to prepare for xonly_pubkeys and keypairs.
    47e6618e11
  321. jonasnick force-pushed on Aug 26, 2020
  322. jonasnick commented at 9:02 pm on August 26, 2020: contributor

    Added missing context frees in extrakeys tests, rebased and squashed. Checked that every commit compiles and runs the tests successfully.

    A separate commit on top that gets squashed in after the BIP change is complete?

    Yeah, that would be my plan. Fwiw I already wrote these commits to test the test vectors and pushed them to https://github.com/jonasnick/secp256k1/commits/schnorrsig-even-R.

  323. sipa commented at 4:18 am on August 27, 2020: contributor
  324. in src/valgrind_ctime_test.c:150 in 2ebf20f30b outdated
    143+#endif
    144+
    145+#if ENABLE_MODULE_SCHNORRSIG
    146+    VALGRIND_MAKE_MEM_UNDEFINED(key, 32);
    147+    ret = secp256k1_keypair_create(ctx, &keypair, key);
    148+    ret = secp256k1_schnorrsig_sign(ctx, sig, msg, &keypair, NULL, NULL);
    


    gmaxwell commented at 9:41 pm on August 27, 2020:

    VALGRIND_MAKE_MEM_DEFINED(&ret, sizeof(ret)); CHECK(ret == 1);

    Right after keypair_create, if you change this file again. Otherwise it can go in an after the fact PR.


    jonasnick commented at 7:34 pm on August 31, 2020:
    Fixed this because it’s trivial.
  325. in src/modules/schnorrsig/main_impl.h:226 in 79da28a01d outdated
    224@@ -223,8 +225,14 @@ int secp256k1_schnorrsig_verify(const secp256k1_context* ctx, const unsigned cha
    225     secp256k1_gej_set_ge(&pkj, &pk);
    226     secp256k1_ecmult(&ctx->ecmult_ctx, &rj, &pkj, &e, &s);
    227 
    228-    return secp256k1_gej_has_quad_y_var(&rj) /* fails if rj is infinity */
    


    gmaxwell commented at 10:06 pm on August 27, 2020:
    secp256k1_gej_has_quad_y_var is unused after this (except for the tests). It was added in #402, presumably for the schnorr support. Should it get removed? @sipa ?

    sipa commented at 10:13 pm on August 27, 2020:
    I don’t think we have any immediate use for it in this library anymore really… not just gej_has_quad_y, but also fe_is_quad_var, num_jacobi, and possibly ge_set_xquad?

    gmaxwell commented at 4:10 am on August 28, 2020:
    ge_set_xquad is just the natural behaviour of using a square root to recover y.

    sipa commented at 4:31 am on August 28, 2020:
    Yeah, that’s why I said maybe. The alternative is merging it into ge_set_xo_var (which would be the only call site left, I believe).
  326. gmaxwell commented at 0:06 am on August 28, 2020: contributor
    ACK BIP340 fixups. Also I’d be fine with a commit that removes the unused stuff, or if @jonasnick prefers that could be left for another PR.
  327. jonasnick force-pushed on Aug 31, 2020
  328. jonasnick commented at 7:36 pm on August 31, 2020: contributor
    Squashed and did not remove the unused stuff because I’d prefer to avoid increasing the scope of this PR.
  329. gmaxwell commented at 10:56 pm on August 31, 2020: contributor

    ACK fb442254ef88d6bfcfe76cf25236ebda02bad795

    Thanks for the trivial improvement to the ct tests in your latest update. I reviewed that the squash was just a squash (plus the trivial additional fixup in the ct tests) and that the code otherwise matched exactly the version I had previously reviewed extensively and tested. I also additionally tested the dist tarballs on a couple platforms, valgrinded the tests/benchmarks, and injected faults to make sure the tests failed.

    Stuff I consider TODO for a follow-up PR: exhaustive tests and removal of the dead code.

  330. jrawsthorne cross-referenced this on Aug 31, 2020 from issue Trivial: Add test logs to gitignore by jrawsthorne
  331. in include/secp256k1_extrakeys.h:37 in fb442254ef outdated
    32+ */
    33+typedef struct {
    34+    unsigned char data[96];
    35+} secp256k1_keypair;
    36+
    37+/** Parse a 32-byte public key into a xonly_pubkey object.
    


    ariard commented at 9:55 pm on September 1, 2020:

    Shouldn’t be a 32-byte array instead of a 32-byte public key ?

    • secp256k1_xonly_pubkey_serialize mentions serializing to a 32-byte sequence
    • we can’t qualify input as a valid pubkey before to actually parse it

    Note, I don’t know how much terminology rigor libsecp256k1 bind to, please discard this comment or any other following of the same type if meaningfulness


    jonasnick commented at 11:36 am on September 4, 2020:
    In BIP 340 public keys are simply 32 byte arrays. But agree that in libsecp that’s confusing because public keys already refer to the parsed objects. Pushed fix that replaces “32-byte pubkey” with “32-byte sequence”.
  332. in include/secp256k1_extrakeys.h:129 in fb442254ef outdated
    124+ *  The tweaked pubkey is represented by its 32-byte x-only serialization and
    125+ *  its pk_parity, which can both be obtained by converting the result of
    126+ *  tweak_add to a secp256k1_xonly_pubkey.
    127+ *
    128+ *  Note that this alone does _not_ verify that the tweaked pubkey is a
    129+ *  commitment. If the tweak is not chosen in a specific way, the tweaked pubkey
    


    ariard commented at 10:14 pm on September 1, 2020:
    As this function is part of the exported API, it could be nice to inform user how to effectively pick it up such that internal_pubkey and tweak argument addition are the only solution for tweaked_pubkey32. Or maybe it’s documented elsewhere ?

    jonasnick commented at 9:48 pm on September 3, 2020:
    It’s better we don’t do that because it will either be underspecified or quite long. This is a comparably low-level function and it is not the job its API documentation to provide instructions for how to use it in (various) cryptographic schemes. BIP 341 includes a binding commitment construction that makes use of secp256k1_xonly_pubkey_tweak_add_check. In the longer term we should have a functions that creates BIP 341 commitments instead of the low-level tweak_add functions (see also #702).
  333. in include/secp256k1_extrakeys.h:157 in fb442254ef outdated
    152+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
    153+
    154+/** Compute the keypair for a secret key.
    155+ *
    156+ *  Returns: 1: secret was valid, keypair is ready to use
    157+ *           0: secret was invalid, try again
    


    ariard commented at 10:21 pm on September 1, 2020:
    nit: Rather rotate secret, it’s not expected caller retries with same already-invalid secret ?

    jonasnick commented at 11:36 am on September 4, 2020:
    Fixed
  334. in include/secp256k1_extrakeys.h:206 in fb442254ef outdated
    201+    int *pk_parity,
    202+    const secp256k1_keypair *keypair
    203+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4);
    204+
    205+/** Tweak a keypair by adding the generator multiplied with tweak32 to the
    206+ *  x-only public key and secret key parts of the keypair.
    


    ariard commented at 10:33 pm on September 1, 2020:
    With regards to the secret key, the tweak32 is only added, its product with the generator isn’t distributed on it ?

    gmaxwell commented at 3:50 am on September 2, 2020:
    The text is correct, your comment indicates it could be less confusing. Might I suggest “Tweak a keypair by adding tweak32 to the secret key and updating the public key accordingly”?

    instagibbs commented at 1:10 pm on September 2, 2020:
    I also am reading this the “wrong way”. I like @gmaxwell suggestion.

    jonasnick commented at 11:37 am on September 4, 2020:
    Fixed
  335. in include/secp256k1_extrakeys.h:78 in fb442254ef outdated
    73+ *           0 otherwise
    74+ *
    75+ *  Args:         ctx: pointer to a context object (cannot be NULL)
    76+ *  Out: xonly_pubkey: pointer to an x-only public key object for placing the
    77+ *                     converted public key (cannot be NULL)
    78+ *          pk_parity: pointer to an integer that will be set to 1 if the point
    


    ariard commented at 10:53 pm on September 1, 2020:
    Why this API choice to give liberality to the caller to pass a null pk_parity ?

    gmaxwell commented at 3:46 am on September 2, 2020:
    It’s an outarg that the caller probably never cares about (perhaps it shouldn’t even be exposed).

    jonasnick commented at 8:42 pm on September 3, 2020:
    @ariard you don’t need pk_parity if you don’t need anyone to call secp256k1_xonly_pubkey_tweak_add_check on your pubkey.

    ariard commented at 6:50 pm on September 4, 2020:
    I see. I think a further slight improvement would be to rename this function to secp256k1_xonly_pubkey_tweak_addition_check. As of today, it’s using two verbs instead of only one to designate the checking of a past transformation of a public key.
  336. in include/secp256k1_extrakeys.h:170 in fb442254ef outdated
    165+    const unsigned char *seckey
    166+) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
    167+
    168+/** Get the public key from a keypair.
    169+ *
    170+ *  Returns: 0 if the arguments are invalid. 1 otherwise.
    


    ariard commented at 11:08 pm on September 1, 2020:

    How can secp256k1_keypair_pub returns 0 ? It doesn’t sound to load pubkey through secp256k1_keypair_load as secp256k1_keypair_xonly_pub is doing ?

    I don’t see a test again a zero’ed keypair among coverage.


    gmaxwell commented at 3:48 am on September 2, 2020:

    Bad arguments will make it return zero. There are tests:

    src/secp256k1/src/modules/extrakeys/tests_impl.h: CHECK(secp256k1_keypair_pub(none, NULL, &keypair) == 0); src/secp256k1/src/modules/extrakeys/tests_impl.h: CHECK(secp256k1_keypair_pub(none, &pk, NULL) == 0);

  337. in src/hash_impl.h:169 in fb442254ef outdated
    163@@ -164,6 +164,19 @@ static void secp256k1_sha256_finalize(secp256k1_sha256 *hash, unsigned char *out
    164     memcpy(out32, (const unsigned char*)out, 32);
    165 }
    166 
    167+/* Initializes a sha256 struct and writes the 64 byte string
    168+ * SHA256(tag)||SHA256(tag) into it. */
    169+static void secp256k1_sha256_initialize_tagged(secp256k1_sha256 *hash, const unsigned char *tag, size_t taglen) {
    


    ariard commented at 11:39 pm on September 1, 2020:

    Note, it ’s a bit confusing for newcomers that hash denotes the cryptographic hash function struct instead of the message digest :)

    I spent a bit of time checking tagged hashes, failing to get back hardcoded values (secp256k1_nonce_function_bip_340_sha256_tagged), before realizing it was actually a midstate


    gmaxwell commented at 3:51 am on September 2, 2020:
    How? The argument is typed?

    ariard commented at 6:52 pm on September 4, 2020:
    I didn’t catch at first hash was in fact the hasher and not the message digest. I guess that’s a matter of habits, renaming would to costly for the ones used to.
  338. in src/modules/schnorrsig/main_impl.h:134 in fb442254ef outdated
    129+
    130+    if (noncefp == NULL) {
    131+        noncefp = secp256k1_nonce_function_bip340;
    132+    }
    133+
    134+    ret &= secp256k1_keypair_load(ctx, &sk, &pk, keypair);
    


    ariard commented at 11:50 pm on September 1, 2020:

    This is a beginner question, but the pattern of bit-wising ret instead of returning directly a failure in case of invalid keypair is to lean towards constant-time evaluation and thus hopefully thwart side-channels ?

    In fact that’s a spec deviation as in “Default signing” it requires “Fail if d’ = 0 or d’ >= n” ? Or does the spec only mandates a logical order not an algorithm implementation one ?


    ariard commented at 0:06 am on September 2, 2020:
    Is there a test passing a zero’ed out keypair ? I don’t see one in test_schnorrsig_api neither among test vectors.

    gmaxwell commented at 4:01 am on September 2, 2020:

    The spec specifies only the functional behaviour equivalent to a particular algorithm, otherwise you couldn’t implement it in C (whos compilers don’t necessarirly perform the specified operations only equivalent ones) or run it on modern computers (e.g. superscalar processors). :)

    And yes, there cannot be any kind of branching or early termination which is casually dependant on secret information in constant time code. Using the AND accomplishes that, and the tests validate that your compiler’s result contains no secret dependant branches or memory accesses. If you go stick a conditional return there the ctime_test will fail.

    Is there a test passing a zero’ed out keypair ? I don’t see one in test_schnorrsig_api

    Agreed, I don’t see one. There should be one.


    jonasnick commented at 11:37 am on September 4, 2020:
    Good catch, fixed
  339. in src/modules/schnorrsig/main_impl.h:147 in fb442254ef outdated
    142+    secp256k1_scalar_get_b32(seckey, &sk);
    143+    secp256k1_fe_get_b32(pk_buf, &pk.x);
    144+    ret &= !!noncefp(buf, msg32, seckey, pk_buf, bip340_algo16, ndata);
    145+    secp256k1_scalar_set_b32(&k, buf, NULL);
    146+    ret &= !secp256k1_scalar_is_zero(&k);
    147+    secp256k1_scalar_cmov(&k, &secp256k1_scalar_one, !ret);
    


    ariard commented at 0:13 am on September 2, 2020:
    I guess this is also a side-channel protection to set at least a nonce of 1 in case of a failure during nonce generation ? But I don’t see how it is masking anything as if we assume an attacker can inject fault and this is a hardcoded backup nonce that’s something we assume is known too ?

    gmaxwell commented at 4:10 am on September 2, 2020:

    This has nothing to do with injecting a fault. For correctness the algorithm must check that k is not zero, because 0*G is the point at infinity and cannot be serialized. But the operation of the code must not contain any branches which depend on secret data nor can it otherwise change its execution time conditional on secret data, so it just substitutes in dummy data to keep everything well defined and constant time.

    Down below there is a conditional zero that nukes the output if its invalid:

    memczero(sig64, 64, !ret);

  340. real-or-random referenced this in commit f49c9896b0 on Sep 2, 2020
  341. in src/modules/schnorrsig/main_impl.h:211 in fb442254ef outdated
    206+    secp256k1_scalar_set_b32(&s, &sig64[32], &overflow);
    207+    if (overflow) {
    208+        return 0;
    209+    }
    210+
    211+    if (!secp256k1_xonly_pubkey_load(ctx, &pk, pubkey)) {
    


    ariard commented at 0:35 am on September 2, 2020:

    How does secp256k1_xonly_pubkey_load can ever trigger this failure as secp256k1_pubkey_load always returns success ?

    Does this encode “The function lift_x(x), where x is an integer in range 0..p-1, returns the point P for which x(P) = x[9] and has_even_y(P), or fails if no such point exists.” spec requirement ?


    gmaxwell commented at 4:16 am on September 2, 2020:

    secp256k1_xonly_pubkey_load calls secp256k1_pubkey_load, which returns 0 based on secp256k1_fe_is_zero(&ge->x).

    It is also an API violation to call this function on an input where the parse which created the secp256k1_xonly_pubkey failed.


    ariard commented at 6:59 pm on September 4, 2020:
    I think I misunderstood the call trace with regards to illegal_callback. I though it was aborting outside of testing and thus can’t return 0.

    elichai commented at 8:42 am on September 6, 2020:
    @ariad, the illegal_callback does abort in the default implementation as you noted, but users can override this to a non-aborting callback in runtime (https://github.com/bitcoin-core/secp256k1/blob/master/include/secp256k1.h#L275), Or even in compile time: https://github.com/bitcoin-core/secp256k1/blob/master/configure.ac#L139. Although the required implementation of these callbacks isn’t documented properly. (see https://github.com/rust-bitcoin/rust-secp256k1/issues/228 for example)
  342. in src/modules/schnorrsig/main_impl.h:229 in fb442254ef outdated
    224+    secp256k1_scalar_negate(&e, &e);
    225+    secp256k1_gej_set_ge(&pkj, &pk);
    226+    secp256k1_ecmult(&ctx->ecmult_ctx, &rj, &pkj, &e, &s);
    227+
    228+    secp256k1_ge_set_gej_var(&r, &rj);
    229+    if (secp256k1_ge_is_infinity(&r)) {
    


    ariard commented at 0:48 am on September 2, 2020:
    Is the check to outlaw point at infinity actually explicitly part of the spec ?

    gmaxwell commented at 4:23 am on September 2, 2020:

    “Fail if not has_even_y(R) or x(R) ≠ r.” cannot be true if R is infinity. However, the BIP was more clear on this point until August 18th and should be fixed. @sipa 968096c451158b9d48446703dafe7cb96b97800c made the BIP unclear in this respect.

    Good catch.


    sipa commented at 9:25 pm on September 2, 2020:
  343. ariard commented at 0:50 am on September 2, 2020: none

    Code Review ACK fb44225 pending on #558 (review), #558 (review) and #558 (review). I’m surely missing context, I’m not a cryptographer no more familiar with libsecp256k1.

    The rest of comments reviews are mostly call for better comments/design questions and can be discarded.

  344. in src/modules/extrakeys/main_impl.h:45 in fb442254ef outdated
    40+int secp256k1_xonly_pubkey_serialize(const secp256k1_context* ctx, unsigned char *output32, const secp256k1_xonly_pubkey *pubkey) {
    41+    secp256k1_ge pk;
    42+
    43+    VERIFY_CHECK(ctx != NULL);
    44+    ARG_CHECK(output32 != NULL);
    45+    memset(output32, 0, 32);
    


    jrawsthorne commented at 12:57 pm on September 2, 2020:
    What’s the reason for the memset here

    instagibbs commented at 1:04 pm on September 2, 2020:
    It can fail at https://github.com/bitcoin-core/secp256k1/pull/558/files/fb442254ef88d6bfcfe76cf25236ebda02bad795#diff-4b25274d7806d13aa4c1e7d4ce22340cR48 and I’m guessing you don’t want anyone to try to use results if they forgot to check.

    elichai commented at 2:17 pm on September 2, 2020:
    the policy in libsecp is to zero out the outputs in case of failure and by putting it on top it will happen even if we’ll add more failure cases in the future.
  345. sipa cross-referenced this on Sep 2, 2020 from issue Clarify that R=infinity is invalid in BIP340 by sipa
  346. jonasnick commented at 9:51 pm on September 3, 2020: contributor
    Thanks for your review @ariard.
  347. gmaxwell commented at 2:53 pm on September 4, 2020: contributor
    ACK latest fixups
  348. ariard commented at 7:01 pm on September 4, 2020: none
    ACK 79785f0
  349. sipa commented at 2:21 am on September 5, 2020: contributor

    I’ve written exhaustive tests for the schnorrsig sign/verify (and also for part of the extrakeys functions): https://github.com/sipa/secp256k1/commits/202009_schnorrsig_exhaustive

    Feel free to cherry-pick, but it’s probably better to add them as a follow-up (especially as it includes a few refactors as well).

  350. jonasnick force-pushed on Sep 6, 2020
  351. extrakeys: Add xonly_pubkey with serialize, parse and from_pubkey 4cd2ee474d
  352. Separate helper function for ec_pubkey_tweak_add
    This is in preparation for allowing code reuse by xonly tweak add functions
    176bfb1110
  353. extrakeys: Add xonly_pubkey_tweak_add & xonly_pubkey_tweak_add_test 910d9c284c
  354. Separate helper functions for pubkey_create and seckey_tweak_add
    This is in preparation for allowing code reuse by keypair functions
    f0010349b8
  355. extrakeys: Add keypair struct with create, pub and pub_xonly 58254463f9
  356. extrakeys: Add keypair_xonly_tweak_add 6fcb5b845d
  357. Allow initializing tagged sha256
    This will be used by the schnorrsig module
    eabd9bc46a
  358. schnorrsig: Init empty experimental module 7a703fd97d
  359. schnorrsig: Add BIP-340 nonce function 7332d2db6b
  360. schnorrsig: Add BIP-340 compatible signing and verification 4e43520026
  361. schnorrsig: Add benchmark for sign and verify 8dfd53ee3f
  362. schnorrsig: Add taproot test case 16ffa9d97c
  363. valgrind_ctime_test: Add schnorrsig_sign f431b3f28a
  364. jonasnick commented at 7:01 pm on September 6, 2020: contributor

    Squashed in latest fixups. Updated post-merge todo list:

    • consider allowing variable len messages. I experimented with this in https://github.com/jonasnick/secp256k1/commits/schnorrsig-varmsg. There’s not much to experiment though, it just adds a size_t msg_len argument and fixes the tests. (this branch totally ignores domain separation)
    • the tweak apis are inconsistent (both legacy and xonly) as the seckey_tweak_add functions are constant time with respect to the tweak and pubkey_tweak_add are not. Similarly, the keypair_tweak_add is not CT w.r.t. the tweak. This PR is consistent with the legacy API and reuses much of it’s code. Therefore, if we consider changing this (I think we should), the easiest way is to change both legacy and xonly and that’s best done in a different PR.
    • consider simplifying default schnorrsig_sign function by removing noncefp and clarifying that noncedata refers to aux_rand from BIP 340
    • Add exhaustive tests for schnorrsig and extrakeys (sipa wrote a few, see comment above)
    • Remove unused functions such as secp256k1_gej_has_quad_y_var (see #558#pullrequestreview-477117034).
    • Mention of schnorrsig module in README.
  365. gmaxwell commented at 8:34 pm on September 6, 2020: contributor
    ACK f431b3f28ac95a3645ad5a6dc96b878fa30a1de3 (exactly matches the previous post-fixup version which I have already reviewed and tested)
  366. sipa commented at 4:25 am on September 8, 2020: contributor
    ACK f431b3f28ac95a3645ad5a6dc96b878fa30a1de3
  367. real-or-random commented at 11:20 am on September 8, 2020: contributor
    Sorry for not being helpful in the past week, I’ll try to review/hopefully ACK this week.
  368. real-or-random requested review from real-or-random on Sep 8, 2020
  369. sipa commented at 1:30 am on September 9, 2020: contributor
    I ran my exhaustive tests on top of this, with both orders 13 and 199 (disabling the slowest other tests, as they take several CPU years for 199). All pass.
  370. in include/secp256k1_schnorrsig.h:97 in f431b3f28a
    92+ *
    93+ *  Returns: 1: correct signature
    94+ *           0: incorrect signature
    95+ *  Args:    ctx: a secp256k1 context object, initialized for verification.
    96+ *  In:    sig64: pointer to the 64-byte signature to verify (cannot be NULL)
    97+ *         msg32: the 32-byte message being verified (cannot be NULL)
    


    roconnor-blockstream commented at 4:43 pm on September 10, 2020:
    Are we planning to change the API to allow arbitrary message lengths, or add a new function to the API later to allow arbitrary message lengths?

    jonasnick commented at 4:50 pm on September 10, 2020:
    Yes, see the post-merge to do list #558 (comment)
  371. real-or-random approved
  372. real-or-random commented at 7:06 pm on September 11, 2020: contributor

    ACK f431b3f28ac95a3645ad5a6dc96b878fa30a1de3 careful code review

    I have a few minor comments in my notes but all of those can be addressed later.

  373. real-or-random merged this on Sep 11, 2020
  374. real-or-random closed this on Sep 11, 2020

  375. sipa cross-referenced this on Sep 11, 2020 from issue Update secp256k1 subtree (including BIP340 support) by sipa
  376. jonasnick cross-referenced this on Sep 11, 2020 from issue travis: run bench_schnorrsig by jonasnick
  377. sipa cross-referenced this on Sep 12, 2020 from issue Exhaustive test improvements + exhaustive schnorrsig tests by sipa
  378. fanquake referenced this in commit ba4b3fbcf2 on Sep 14, 2020
  379. sidhujag referenced this in commit 711b81967a on Sep 15, 2020
  380. jasonbcox referenced this in commit 5ab6cb06f7 on Sep 29, 2020
  381. jasonbcox referenced this in commit 3374da576f on Sep 29, 2020
  382. jasonbcox referenced this in commit ee1b32cb0d on Sep 29, 2020
  383. jasonbcox referenced this in commit 6a9d5cc855 on Sep 29, 2020
  384. jasonbcox referenced this in commit a7fc6797d4 on Sep 29, 2020
  385. jasonbcox referenced this in commit ee00af3c8f on Sep 29, 2020
  386. deadalnix referenced this in commit 542c2efe44 on Sep 29, 2020
  387. deadalnix referenced this in commit a0bccf54f9 on Sep 29, 2020
  388. deadalnix referenced this in commit 35ede4bdb6 on Sep 29, 2020
  389. deadalnix referenced this in commit 7cf7dc459e on Sep 29, 2020
  390. deadalnix referenced this in commit 6304e4528e on Sep 29, 2020
  391. jasonbcox referenced this in commit 51407fcd5e on Sep 29, 2020
  392. jasonbcox referenced this in commit 11f99c1b9a on Sep 29, 2020
  393. jasonbcox referenced this in commit 852d5e1989 on Sep 29, 2020
  394. jasonbcox referenced this in commit 10cf62f3c8 on Sep 29, 2020
  395. deadalnix referenced this in commit e3550eb534 on Sep 30, 2020
  396. deadalnix referenced this in commit 358281475b on Sep 30, 2020
  397. deadalnix referenced this in commit e5ffbae082 on Sep 30, 2020
  398. deadalnix referenced this in commit cd45ae75e1 on Sep 30, 2020
  399. deadalnix referenced this in commit 58fd134ed0 on Sep 30, 2020
  400. deadalnix referenced this in commit 1790105162 on Sep 30, 2020
  401. deadalnix referenced this in commit c341d26b48 on Sep 30, 2020
  402. deadalnix referenced this in commit d796ee6d86 on Sep 30, 2020
  403. deadalnix referenced this in commit f995ae3e0e on Sep 30, 2020
  404. deadalnix referenced this in commit b7697cd1f5 on Sep 30, 2020
  405. deadalnix referenced this in commit 50daa9ea41 on Sep 30, 2020
  406. deadalnix referenced this in commit b448c7c530 on Sep 30, 2020
  407. deadalnix referenced this in commit 9da8345313 on Sep 30, 2020
  408. deadalnix referenced this in commit 29f529baed on Sep 30, 2020
  409. deadalnix referenced this in commit 6e6e961d89 on Sep 30, 2020
  410. AdamISZ cross-referenced this on Jul 27, 2021 from issue secp256k1 version bump by kristapsk
  411. UdjinM6 referenced this in commit c2ab8285d8 on Aug 10, 2021
  412. 5tefan referenced this in commit abf9ac837f on Aug 12, 2021

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin-core/secp256k1. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2024-12-23 01:15 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me