LCOV - code coverage report
Current view: top level - ballet/bigint - fd_uint256_mul.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 83 83 100.0 %
Date: 2026-09-13 04:25:35 Functions: 69 286 24.1 %

          Line data    Source code
       1             : #ifndef HEADER_fd_src_ballet_bigint_fd_uint256_h
       2             : #error "Do not include this directly; use fd_uint256.h"
       3             : #endif
       4             : 
       5             : /* Implementation of uint256 Montgomery mul.
       6             : 
       7             :    TODOs:
       8             :    - efficient sqr, Alg. 5 https://eprint.iacr.org/2022/1400
       9             :    - regular CIOS (the current impl *probably* won't work for secp256k1/r1)
      10             : 
      11             :    This is used under the hood to implement:
      12             :    - bn254 field arithmetic
      13             :    - bn254 scalar field arithmetic
      14             :    - (TODO) ed25519 scalar field arithmetic
      15             :    - (TODO) secp256k1 field and scalar arithmetic
      16             :    - (TODO) secp256r1 field and scalar arithmetic
      17             :    - ...
      18             :  */
      19             : 
      20             : #define INLINE static inline __attribute__((always_inline))
      21             : 
      22             : #ifdef FD_USING_GCC
      23             : #define OPTIMIZE __attribute__((optimize("unroll-loops")))
      24             : #else
      25             : #define OPTIMIZE
      26             : #endif
      27             : 
      28             : #if FD_HAS_X86
      29             : #if defined(__GNUC__) && !defined(__clang__)
      30             : #include <x86gprintrin.h>
      31             : #else
      32             : #include <immintrin.h>
      33             : #endif
      34             : #endif
      35             : 
      36             : /* Utility functions for fd_uint256_mul_mod_p.
      37             :    Implementation is based on uint128.
      38             :    The implementations WITHOUT uint128 are just for completeness, we
      39             :    should avoid using them (and, e.g., rely on fiat-crypto mul instead). */
      40             : 
      41             : INLINE void
      42             : fd_ulong_sub_borrow(
      43             :     ulong * r,   /* out (r=a-b) */
      44             :     int *   b,   /* out borrow flag */
      45             :     ulong   a0,
      46             :     ulong   a1,
      47             :     int     bi   /* in borrow flag */
      48   204628191 : ) {
      49   204628191 : # if FD_HAS_X86
      50   204628191 :   *b = (uchar)_subborrow_u64( (uchar)bi, a0, a1, (unsigned long long *)r );
      51             : # else
      52             :   a1 += !!bi;
      53             :   *r = a0 - a1;
      54             :   *b = a0 < a1;
      55             : # endif
      56   204628191 : }
      57             : 
      58             : INLINE void
      59             : fd_ulong_add_carry(
      60             :     ulong * r,   /* out (r=a+b+ci) */
      61             :     int *   c,   /* out carry flag */
      62             :     ulong   a0,
      63             :     ulong   a1,
      64             :     int     ci   /* in carry flag */
      65   215773138 : ) {
      66   215773138 : # if FD_HAS_X86
      67   215773138 :   *c = (uchar)_addcarry_u64( (uchar)ci, a0, a1, (unsigned long long *)r );
      68             : # else
      69             :   ulong r0 = a0 + a1;
      70             :   ulong r1 = r0 + !!ci;
      71             :   *r = r1;
      72             :   *c = (int)((r0 < a0) | (r1 < r0));
      73             : # endif
      74   215773138 : }
      75             : 
      76             : #if FD_HAS_INT128
      77             : 
      78             : INLINE void
      79   566137600 : fd_ulong_vec_mul( ulong l[4], ulong h[4], ulong const a[4], ulong b ) {
      80   566137600 :   uint128 r0 = ((uint128)a[0]) * ((uint128)b);
      81   566137600 :   uint128 r1 = ((uint128)a[1]) * ((uint128)b);
      82   566137600 :   uint128 r2 = ((uint128)a[2]) * ((uint128)b);
      83   566137600 :   uint128 r3 = ((uint128)a[3]) * ((uint128)b);
      84   566137600 :   l[0] = (ulong)r0;  h[0] = (ulong)(r0 >> 64);
      85   566137600 :   l[1] = (ulong)r1;  h[1] = (ulong)(r1 >> 64);
      86   566137600 :   l[2] = (ulong)r2;  h[2] = (ulong)(r2 >> 64);
      87   566137600 :   l[3] = (ulong)r3;  h[3] = (ulong)(r3 >> 64);
      88   566137600 : }
      89             : 
      90             : INLINE void
      91  2265472168 : fd_ulong_add_carry4( ulong *l, uchar *h, ulong a0, ulong a1, ulong a2, uchar a3 ) {
      92  2265472168 :   uint128 r = ((uint128)a0) + ((uint128)a1) + ((uint128)a2) + ((uint128)a3);
      93  2265472168 :   *l = (ulong)r;
      94  2265472168 :   *h = (uchar)(r >> 64);
      95  2265472168 : }
      96             : 
      97             : #else
      98             : 
      99             : INLINE void
     100             : fd_ulong_mul128( ulong * l, ulong * h, ulong const a, ulong const b ) {
     101             :     /* First calculate all of the cross products. */
     102             :     ulong lo_lo = (a & 0xFFFFFFFF) * (b & 0xFFFFFFFF);
     103             :     ulong hi_lo = (a >> 32)        * (b & 0xFFFFFFFF);
     104             :     ulong lo_hi = (a & 0xFFFFFFFF) * (b >> 32);
     105             :     ulong hi_hi = (a >> 32)        * (b >> 32);
     106             : 
     107             :     /* Now add the products together. These will never overflow. */
     108             :     ulong cross = (lo_lo >> 32) + (hi_lo & 0xFFFFFFFF) + lo_hi;
     109             :     ulong upper = (hi_lo >> 32) + (cross >> 32)        + hi_hi;
     110             : 
     111             :     *h = upper;
     112             :     *l = (cross << 32) | (lo_lo & 0xFFFFFFFF);
     113             :   }
     114             : 
     115             : INLINE void
     116             : fd_ulong_vec_mul( ulong l[4], ulong h[4], ulong const a[4], ulong b ) {
     117             :   fd_ulong_mul128( &l[0], &h[0], a[0], b );
     118             :   fd_ulong_mul128( &l[1], &h[1], a[1], b );
     119             :   fd_ulong_mul128( &l[2], &h[2], a[2], b );
     120             :   fd_ulong_mul128( &l[3], &h[3], a[3], b );
     121             : }
     122             : 
     123             : INLINE void
     124             : fd_ulong_add_carry4( ulong *l, uchar *h, ulong a0, ulong a1, ulong a2, uchar a3 ) {
     125             :   ulong r0 = a0 + a1;
     126             :   uchar c0 = r0 < a0;
     127             : 
     128             :   ulong r1 = a2 + a3;
     129             :   uchar c1 = r1 < a2;
     130             : 
     131             :   *l = r0 + r1;
     132             :   *h = (uchar)((*l < r0) + c0 + c1);
     133             : }
     134             : 
     135             : #endif
     136             : 
     137             : INLINE fd_uint256_t *
     138             : fd_uint256_add(fd_uint256_t *       r,
     139             :                fd_uint256_t const * a,
     140      230442 :                fd_uint256_t const * b ) {
     141      230442 :   uchar c0;
     142      230442 :   fd_ulong_add_carry4( &r->limbs[0], &c0, a->limbs[0], b->limbs[0], 0, 0 );
     143      230442 :   fd_ulong_add_carry4( &r->limbs[1], &c0, a->limbs[1], b->limbs[1], 0, c0 );
     144      230442 :   fd_ulong_add_carry4( &r->limbs[2], &c0, a->limbs[2], b->limbs[2], 0, c0 );
     145      230442 :   fd_ulong_add_carry4( &r->limbs[3], &c0, a->limbs[3], b->limbs[3], 0, c0 );
     146      230442 :   return r;
     147      230442 : }
     148             : 
     149             : /* fd_uint256_mul_mod_p computes r = a * b mod p, using the CIOS method.
     150             :    r, a, b are in Montgomery representation (p is not).
     151             : 
     152             :    This is an efficient implementation of CIOS that works when (circa) p < 2^255
     153             :    (precisely, p->limbs[3] < (2^64-1)/2 - 1).
     154             :    Alg. 2, https://eprint.iacr.org/2022/1400
     155             :    Code example, for bn254: https://github.com/Consensys/gnark-crypto/blob/v0.12.1/ecc/bn254/fp/element_ops_purego.go#L66
     156             : 
     157             :    In go lang, bits.Add64 has carry 0, 1.
     158             :    We allow the carry to be a uchar, so we can dp a single add chain after each mul.
     159             : 
     160             :    This function is intended to be wrapped into a fd_<field>_mul( r, a, b ).
     161             :    Experimentally we found that:
     162             :    1. We have to force inlining for this function, otherwise compilers tend to reuse
     163             :       the function, introducing overhead.
     164             :    2. In GCC, we have to force loop unrolling optimization *in the outer fd_<field>_mul()*
     165             :       function, otherwise performance degrades significantly.
     166             :       For this we added the macro FD_UINT256_FP_MUL_IMPL. */
     167             : 
     168             : #if FD_HAS_X86 && defined(__BMI2__) && defined(__ADX__)
     169             : __asm__( ".include \"src/ballet/bigint/fd_uint256_mul.inc\"" );
     170             : #endif
     171             : 
     172             : INLINE fd_uint256_t *
     173             : fd_uint256_mul_mod_p( fd_uint256_t *       r,
     174             :                       fd_uint256_t const * a,
     175             :                       fd_uint256_t const * b,
     176             :                       fd_uint256_t const * p,
     177   106150800 :                       ulong const          p_inv ) {
     178    35383600 : #if FD_HAS_X86 && defined(__BMI2__) && defined(__ADX__)
     179    35383600 :   register fd_uint256_t *       _r    __asm__("rdi") = r;
     180    35383600 :   register fd_uint256_t const * _a    __asm__("rsi") = a;
     181    35383600 :   register fd_uint256_t const * _b    __asm__("rdx") = b;
     182    35383600 :   register fd_uint256_t const * _p    __asm__("rcx") = p;
     183    35383600 :   register ulong                _pinv __asm__("r8")  = p_inv;
     184    35383600 :   __asm__ __volatile__ (
     185    35383600 :     "_fd_uint256_mul_mod_p %[r], %[a], %[b], %[p], %[pinv]"
     186    35383600 :     : [r]"+r"(_r), [a]"+r"(_a), [b]"+r"(_b), [pinv]"+r"(_pinv)
     187    35383600 :     : [p]"r"(_p)
     188    35383600 :     : "rax", "r9", "r10", "r11", "cc", "memory"
     189    35383600 :   );
     190    35383600 :   r = _r;
     191             : #else
     192    70767200 :   ulong FD_ALIGNED t[4] = { 0 };
     193    70767200 :   ulong FD_ALIGNED u[4];
     194    70767200 :   ulong FD_ALIGNED h[4];
     195    70767200 :   ulong FD_ALIGNED l[4];
     196    70767200 :   uchar c0, c1;
     197    70767200 :   ulong tmp;
     198    70767200 :   ulong m;
     199             : 
     200   353836000 :   for( int i=0; i<4; i++ ) {
     201   283068800 :     fd_ulong_vec_mul( l, u, a->limbs, b->limbs[i] );
     202   283068800 :     fd_ulong_add_carry4( &t[0], &c0, t[0], l[0], 0, 0 );
     203   283068800 :     fd_ulong_add_carry4( &t[1], &c0, t[1], l[1], u[0], c0 );
     204   283068800 :     fd_ulong_add_carry4( &t[2], &c0, t[2], l[2], u[1], c0 );
     205   283068800 :     fd_ulong_add_carry4( &t[3], &c0, t[3], l[3], u[2], c0 );
     206             : 
     207   283068800 :     m = t[0] * p_inv;
     208             : 
     209   283068800 :     fd_ulong_vec_mul( l, h, p->limbs, m );
     210   283068800 :     fd_ulong_add_carry4( &tmp,  &c1, t[0], l[0], 0, 0 );
     211   283068800 :     fd_ulong_add_carry4( &t[0], &c1, t[1], l[1], h[0], c1 );
     212   283068800 :     fd_ulong_add_carry4( &t[1], &c1, t[2], l[2], h[1], c1 );
     213   283068800 :     fd_ulong_add_carry4( &t[2], &c1, t[3], l[3], h[2], c1 );
     214   283068800 :     t[3] = u[3] + h[3] + c0 + c1;
     215   283068800 :   }
     216             : 
     217    70767200 :   r->limbs[0] = t[0];
     218    70767200 :   r->limbs[1] = t[1];
     219    70767200 :   r->limbs[2] = t[2];
     220    70767200 :   r->limbs[3] = t[3];
     221             : 
     222    70767200 :   if( fd_uint256_cmp( r, p ) >= 0 ) {
     223     3620114 :     int b = 0;
     224     3620114 :     fd_ulong_sub_borrow( &r->limbs[0], &b, r->limbs[0], p->limbs[0], b );
     225     3620114 :     fd_ulong_sub_borrow( &r->limbs[1], &b, r->limbs[1], p->limbs[1], b );
     226     3620114 :     fd_ulong_sub_borrow( &r->limbs[2], &b, r->limbs[2], p->limbs[2], b );
     227     3620114 :     fd_ulong_sub_borrow( &r->limbs[3], &b, r->limbs[3], p->limbs[3], b );
     228     3620114 :   }
     229    70767200 : #endif
     230   106150800 :   return r;
     231   106150800 : }
     232             : 
     233             : /* FD_UINT256_FP_MUL_IMPL macro to properly implement Fp mul based on
     234             :    fd_uint256_mul_mod_p().
     235             :    In GCC we need to explicitly force loop unroll. */
     236             : #define FD_UINT256_FP_MUL_IMPL(fp, p, p_inv)          \
     237             :   static inline fp ## _t * OPTIMIZE                   \
     238             :   fp ## _mul( fp ## _t * r,                           \
     239             :               fp ## _t const * a,                     \
     240   106150800 :               fp ## _t const * b ) {                  \
     241   106150800 :     return fd_uint256_mul_mod_p( r, a, b, p, p_inv ); \
     242   106150800 :   }

Generated by: LCOV version 1.14