Merge bitcoin-core/secp256k1#1889: field: serialize elements by word

e217ead5c4 field: serialize elements by word (Lőrinc)

Pull request description:

  **Problem:** Both field implementations serialize normalized elements with 32 separate byte assignments in `secp256k1_fe_impl_get_b32`.
  They were introduced in [#437](https://github.com/bitcoin-core/secp256k1/pull/437/changes#diff-6e0cae0111d7c054ba27f22399eb4a2ac6c9788ee97da7f9fc5948c63dcf882cL353) to unroll nested nibble loops for performance.
  At the time, the endian write helpers did not exist, so the unrolling was expressed directly.

  **Fix:** Keep both serializers unrolled by packing the 5x52 limbs into four 64-bit words and the 10x26 limbs into eight 32-bit words, then reuse the corresponding endian write helper.
  This matches the existing [4x64](https://github.com/bitcoin-core/secp256k1/blob/ebf594320dc838b9de1abb54d5ba98cef84f4297/src/scalar_4x64_impl.h#L161-L168) and [8x32](https://github.com/bitcoin-core/secp256k1/blob/ebf594320dc838b9de1abb54d5ba98cef84f4297/src/scalar_8x32_impl.h#L195-L206) scalar serializers.
  The serialized output is unchanged for normalized inputs, and benchmarks indicate no measurable performance regression.

  **Testing:** Existing [field conversion and reduced-boundary tests](https://github.com/bitcoin-core/secp256k1/blob/ebf594320dc838b9de1abb54d5ba98cef84f4297/src/tests.c#L3079-L3173) compare the serialized bytes directly.
  [Compiler Explorer](https://godbolt.org/z/nxaKecdxh) compares the exact before and after serializers with GCC 16.1 and Clang 22.1 at `-O2`, in both native and `-m32` modes.

  <details><summary>Benchmarks</summary>

  ```bash
  for cc in gcc clang; do \
    echo; $cc --version | head -1; \
    CC=$cc cmake -B build-$cc -GNinja -DCMAKE_BUILD_TYPE=Release >/dev/null 2>&1 && \
    CC=$cc cmake -B build-$cc-10x26 -GNinja -DCMAKE_BUILD_TYPE=Release -DSECP256K1_TEST_OVERRIDE_WIDE_MULTIPLY=int64 >/dev/null 2>&1 && \
    for build in build-$cc build-$cc-10x26; do \
      echo; echo $build; \
      for rev in ebf594320d e217ead5c46062ecda964ba858f7518b7e3bb5e7; do \
        git fetch origin -q $rev && git checkout -q $rev && \
        ninja -C $build bench >/dev/null 2>&1 && \
        echo $rev && \
        SECP256K1_BENCH_ITERS=10000 $build/bin/bench; \
      done; \
    done; \
  done
  ```

  ```bash
  gcc (Ubuntu 15.2.0-16ubuntu1) 15.2.0

  build-gcc
  ebf594320d
  Benchmark                     ,    Min(us)    ,    Avg(us)    ,    Max(us)

  ecdsa_verify                  ,    33.9       ,    34.0       ,    34.0
  ecdsa_sign                    ,    21.6       ,    21.6       ,    21.6
  ec_keygen                     ,    15.2       ,    15.2       ,    15.3
  ecdh                          ,    33.4       ,    33.5       ,    33.5
  schnorrsig_sign               ,    16.1       ,    16.1       ,    16.2
  schnorrsig_verify             ,    34.3       ,    34.3       ,    34.4
  ellswift_encode               ,    16.2       ,    16.2       ,    16.2
  ellswift_decode               ,     7.21      ,     7.22      ,     7.24
  ellswift_keygen               ,    31.5       ,    31.5       ,    31.5
  ellswift_ecdh                 ,    36.0       ,    36.1       ,    36.1
  e217ead5c4
  Benchmark                     ,    Min(us)    ,    Avg(us)    ,    Max(us)

  ecdsa_verify                  ,    34.0       ,    34.1       ,    34.4
  ecdsa_sign                    ,    21.6       ,    21.6       ,    21.6
  ec_keygen                     ,    15.3       ,    15.3       ,    15.3
  ecdh                          ,    33.3       ,    33.4       ,    33.6
  schnorrsig_sign               ,    16.1       ,    16.1       ,    16.2
  schnorrsig_verify             ,    34.4       ,    34.4       ,    34.5
  ellswift_encode               ,    16.3       ,    16.3       ,    16.3
  ellswift_decode               ,     7.24      ,     7.24      ,     7.25
  ellswift_keygen               ,    31.5       ,    31.6       ,    31.7
  ellswift_ecdh                 ,    36.0       ,    36.1       ,    36.1

  build-gcc-10x26
  ebf594320d
  Benchmark                     ,    Min(us)    ,    Avg(us)    ,    Max(us)

  ecdsa_verify                  ,    58.4       ,    58.5       ,    58.5
  ecdsa_sign                    ,    29.7       ,    29.8       ,    29.8
  ec_keygen                     ,    22.3       ,    22.4       ,    22.5
  ecdh                          ,    56.0       ,    56.1       ,    56.2
  schnorrsig_sign               ,    23.4       ,    23.4       ,    23.5
  schnorrsig_verify             ,    58.9       ,    59.0       ,    59.1
  ellswift_encode               ,    22.7       ,    22.7       ,    22.7
  ellswift_decode               ,    10.8       ,    10.8       ,    10.8
  ellswift_keygen               ,    45.1       ,    45.2       ,    45.3
  ellswift_ecdh                 ,    59.6       ,    59.7       ,    59.8
  e217ead5c4
  Benchmark                     ,    Min(us)    ,    Avg(us)    ,    Max(us)

  ecdsa_verify                  ,    58.5       ,    58.6       ,    58.7
  ecdsa_sign                    ,    29.8       ,    29.8       ,    29.9
  ec_keygen                     ,    22.3       ,    22.4       ,    22.4
  ecdh                          ,    56.0       ,    56.1       ,    56.4
  schnorrsig_sign               ,    23.5       ,    23.5       ,    23.5
  schnorrsig_verify             ,    58.9       ,    59.0       ,    59.1
  ellswift_encode               ,    22.9       ,    22.9       ,    22.9
  ellswift_decode               ,    10.8       ,    10.8       ,    10.8
  ellswift_keygen               ,    45.2       ,    45.3       ,    45.4
  ellswift_ecdh                 ,    59.6       ,    59.8       ,    59.9

  Ubuntu clang version 21.1.8 (6ubuntu1)

  build-clang
  ebf594320d
  Benchmark                     ,    Min(us)    ,    Avg(us)    ,    Max(us)

  ecdsa_verify                  ,    36.2       ,    36.3       ,    36.7
  ecdsa_sign                    ,    23.1       ,    23.1       ,    23.2
  ec_keygen                     ,    16.6       ,    16.7       ,    16.7
  ecdh                          ,    35.6       ,    35.7       ,    35.7
  schnorrsig_sign               ,    17.6       ,    17.6       ,    17.6
  schnorrsig_verify             ,    36.6       ,    36.7       ,    36.9
  ellswift_encode               ,    16.6       ,    16.6       ,    16.7
  ellswift_decode               ,     7.28      ,     7.29      ,     7.30
  ellswift_keygen               ,    33.2       ,    33.2       ,    33.3
  ellswift_ecdh                 ,    38.5       ,    38.5       ,    38.6
  e217ead5c4
  Benchmark                     ,    Min(us)    ,    Avg(us)    ,    Max(us)

  ecdsa_verify                  ,    35.5       ,    35.6       ,    35.8
  ecdsa_sign                    ,    23.0       ,    23.1       ,    23.2
  ec_keygen                     ,    16.6       ,    16.7       ,    16.8
  ecdh                          ,    35.7       ,    35.7       ,    35.8
  schnorrsig_sign               ,    17.6       ,    17.6       ,    17.7
  schnorrsig_verify             ,    35.9       ,    35.9       ,    36.0
  ellswift_encode               ,    16.4       ,    16.4       ,    16.4
  ellswift_decode               ,     7.22      ,     7.22      ,     7.23
  ellswift_keygen               ,    33.0       ,    33.0       ,    33.1
  ellswift_ecdh                 ,    38.5       ,    38.6       ,    38.8

  build-clang-10x26
  ebf594320d
  Benchmark                     ,    Min(us)    ,    Avg(us)    ,    Max(us)

  ecdsa_verify                  ,    64.2       ,    64.3       ,    64.7
  ecdsa_sign                    ,    33.8       ,    33.9       ,    33.9
  ec_keygen                     ,    26.3       ,    26.3       ,    26.3
  ecdh                          ,    63.4       ,    63.6       ,    63.8
  schnorrsig_sign               ,    27.2       ,    27.3       ,    27.3
  schnorrsig_verify             ,    64.5       ,    64.6       ,    64.7
  ellswift_encode               ,    22.1       ,    22.1       ,    22.1
  ellswift_decode               ,    10.6       ,    10.6       ,    10.7
  ellswift_keygen               ,    48.3       ,    48.3       ,    48.5
  ellswift_ecdh                 ,    67.0       ,    67.0       ,    67.1
  e217ead5c4
  Benchmark                     ,    Min(us)    ,    Avg(us)    ,    Max(us)

  ecdsa_verify                  ,    63.9       ,    64.0       ,    64.3
  ecdsa_sign                    ,    33.8       ,    33.9       ,    34.0
  ec_keygen                     ,    26.3       ,    26.3       ,    26.3
  ecdh                          ,    62.5       ,    62.5       ,    62.6
  schnorrsig_sign               ,    27.2       ,    27.3       ,    27.4
  schnorrsig_verify             ,    64.1       ,    64.2       ,    64.2
  ellswift_encode               ,    22.1       ,    22.2       ,    22.3
  ellswift_decode               ,    10.6       ,    10.7       ,    10.7
  ellswift_keygen               ,    48.4       ,    48.4       ,    48.6
  ellswift_ecdh                 ,    66.2       ,    66.3       ,    66.4
  ```
  </details>

ACKs for top commit:
  real-or-random:
    utACK e217ead5c4
  theStack:
    ACK e217ead5c4

Tree-SHA512: 9a92a8d8682e5fd1ac40cab097770741fb78fc6de7c54edf24bd0796bc06b1ec1c375c996099fdaf228a56788208faa6aae107a53b15d596d19f047a5f1a30e6
This commit is contained in:
merge-script
2026-07-18 00:46:21 +02:00
2 changed files with 12 additions and 64 deletions
+8 -32
View File
@@ -303,38 +303,14 @@ static int secp256k1_fe_impl_set_b32_limit(secp256k1_fe *r, const unsigned char
/** Convert a field element to a 32-byte big endian value. Requires the input to be normalized */ /** Convert a field element to a 32-byte big endian value. Requires the input to be normalized */
static void secp256k1_fe_impl_get_b32(unsigned char *r, const secp256k1_fe *a) { static void secp256k1_fe_impl_get_b32(unsigned char *r, const secp256k1_fe *a) {
r[0] = (a->n[9] >> 14) & 0xff; secp256k1_write_be32(&r[0], (a->n[9] << 10) | (a->n[8] >> 16));
r[1] = (a->n[9] >> 6) & 0xff; secp256k1_write_be32(&r[4], (a->n[8] << 16) | (a->n[7] >> 10));
r[2] = ((a->n[9] & 0x3F) << 2) | ((a->n[8] >> 24) & 0x3); secp256k1_write_be32(&r[8], (a->n[7] << 22) | (a->n[6] >> 4));
r[3] = (a->n[8] >> 16) & 0xff; secp256k1_write_be32(&r[12], (a->n[6] << 28) | (a->n[5] << 2) | (a->n[4] >> 24));
r[4] = (a->n[8] >> 8) & 0xff; secp256k1_write_be32(&r[16], (a->n[4] << 8) | (a->n[3] >> 18));
r[5] = a->n[8] & 0xff; secp256k1_write_be32(&r[20], (a->n[3] << 14) | (a->n[2] >> 12));
r[6] = (a->n[7] >> 18) & 0xff; secp256k1_write_be32(&r[24], (a->n[2] << 20) | (a->n[1] >> 6));
r[7] = (a->n[7] >> 10) & 0xff; secp256k1_write_be32(&r[28], (a->n[1] << 26) | a->n[0]);
r[8] = (a->n[7] >> 2) & 0xff;
r[9] = ((a->n[7] & 0x3) << 6) | ((a->n[6] >> 20) & 0x3f);
r[10] = (a->n[6] >> 12) & 0xff;
r[11] = (a->n[6] >> 4) & 0xff;
r[12] = ((a->n[6] & 0xf) << 4) | ((a->n[5] >> 22) & 0xf);
r[13] = (a->n[5] >> 14) & 0xff;
r[14] = (a->n[5] >> 6) & 0xff;
r[15] = ((a->n[5] & 0x3f) << 2) | ((a->n[4] >> 24) & 0x3);
r[16] = (a->n[4] >> 16) & 0xff;
r[17] = (a->n[4] >> 8) & 0xff;
r[18] = a->n[4] & 0xff;
r[19] = (a->n[3] >> 18) & 0xff;
r[20] = (a->n[3] >> 10) & 0xff;
r[21] = (a->n[3] >> 2) & 0xff;
r[22] = ((a->n[3] & 0x3) << 6) | ((a->n[2] >> 20) & 0x3f);
r[23] = (a->n[2] >> 12) & 0xff;
r[24] = (a->n[2] >> 4) & 0xff;
r[25] = ((a->n[2] & 0xf) << 4) | ((a->n[1] >> 22) & 0xf);
r[26] = (a->n[1] >> 14) & 0xff;
r[27] = (a->n[1] >> 6) & 0xff;
r[28] = ((a->n[1] & 0x3f) << 2) | ((a->n[0] >> 24) & 0x3);
r[29] = (a->n[0] >> 16) & 0xff;
r[30] = (a->n[0] >> 8) & 0xff;
r[31] = a->n[0] & 0xff;
} }
SECP256K1_INLINE static void secp256k1_fe_impl_negate_unchecked(secp256k1_fe *r, const secp256k1_fe *a, int m) { SECP256K1_INLINE static void secp256k1_fe_impl_negate_unchecked(secp256k1_fe *r, const secp256k1_fe *a, int m) {
+4 -32
View File
@@ -269,38 +269,10 @@ static int secp256k1_fe_impl_set_b32_limit(secp256k1_fe *r, const unsigned char
/** Convert a field element to a 32-byte big endian value. Requires the input to be normalized */ /** Convert a field element to a 32-byte big endian value. Requires the input to be normalized */
static void secp256k1_fe_impl_get_b32(unsigned char *r, const secp256k1_fe *a) { static void secp256k1_fe_impl_get_b32(unsigned char *r, const secp256k1_fe *a) {
r[0] = (a->n[4] >> 40) & 0xFF; secp256k1_write_be64(&r[0], (a->n[4] << 16) | (a->n[3] >> 36));
r[1] = (a->n[4] >> 32) & 0xFF; secp256k1_write_be64(&r[8], (a->n[3] << 28) | (a->n[2] >> 24));
r[2] = (a->n[4] >> 24) & 0xFF; secp256k1_write_be64(&r[16], (a->n[2] << 40) | (a->n[1] >> 12));
r[3] = (a->n[4] >> 16) & 0xFF; secp256k1_write_be64(&r[24], (a->n[1] << 52) | a->n[0]);
r[4] = (a->n[4] >> 8) & 0xFF;
r[5] = a->n[4] & 0xFF;
r[6] = (a->n[3] >> 44) & 0xFF;
r[7] = (a->n[3] >> 36) & 0xFF;
r[8] = (a->n[3] >> 28) & 0xFF;
r[9] = (a->n[3] >> 20) & 0xFF;
r[10] = (a->n[3] >> 12) & 0xFF;
r[11] = (a->n[3] >> 4) & 0xFF;
r[12] = ((a->n[2] >> 48) & 0xF) | ((a->n[3] & 0xF) << 4);
r[13] = (a->n[2] >> 40) & 0xFF;
r[14] = (a->n[2] >> 32) & 0xFF;
r[15] = (a->n[2] >> 24) & 0xFF;
r[16] = (a->n[2] >> 16) & 0xFF;
r[17] = (a->n[2] >> 8) & 0xFF;
r[18] = a->n[2] & 0xFF;
r[19] = (a->n[1] >> 44) & 0xFF;
r[20] = (a->n[1] >> 36) & 0xFF;
r[21] = (a->n[1] >> 28) & 0xFF;
r[22] = (a->n[1] >> 20) & 0xFF;
r[23] = (a->n[1] >> 12) & 0xFF;
r[24] = (a->n[1] >> 4) & 0xFF;
r[25] = ((a->n[0] >> 48) & 0xF) | ((a->n[1] & 0xF) << 4);
r[26] = (a->n[0] >> 40) & 0xFF;
r[27] = (a->n[0] >> 32) & 0xFF;
r[28] = (a->n[0] >> 24) & 0xFF;
r[29] = (a->n[0] >> 16) & 0xFF;
r[30] = (a->n[0] >> 8) & 0xFF;
r[31] = a->n[0] & 0xFF;
} }
SECP256K1_INLINE static void secp256k1_fe_impl_negate_unchecked(secp256k1_fe *r, const secp256k1_fe *a, int m) { SECP256K1_INLINE static void secp256k1_fe_impl_negate_unchecked(secp256k1_fe *r, const secp256k1_fe *a, int m) {