LCOV - code coverage report
Current view: top level - ballet/ed25519 - fd_ed25519_cache.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 167 188 88.8 %
Date: 2026-10-15 04:28:26 Functions: 14 14 100.0 %

          Line data    Source code
       1             : #include "fd_ed25519.h"
       2             : #include "fd_curve25519.h"
       3             : 
       4          99 : #define FD_ED25519_CACHE_MAGIC (0xfd3d25519cac4e00UL) /* fd ed25519 cache ver 0 */
       5             : 
       6             : /* A set holds the keys of its WAY_CNT ways in 2 cache lines.  use[w]
       7             :    is the tick of the last use of way w (0 if way w is invalid). */
       8             : 
       9     1354774 : #define WAY_CNT (4UL)
      10             : 
      11             : struct __attribute__((aligned(128))) fd_ed25519_cache_set {
      12             :   uchar key[ WAY_CNT ][ 32 ];
      13             :   ulong use[ WAY_CNT ];
      14             : };
      15             : typedef struct fd_ed25519_cache_set fd_ed25519_cache_set_t;
      16             : 
      17             : struct __attribute__((aligned(FD_ED25519_CACHE_ALIGN))) fd_ed25519_cache {
      18             :   ulong magic;
      19             :   ulong set_cnt;    /* power of 2 */
      20             :   ulong filter_cnt; /* power of 2 */
      21             :   ulong seed;
      22             : 
      23             :   ulong credit; /* table builds are paid for with credits, see cache_admit */
      24             :   ulong tick;   /* incremented on every lookup */
      25             : 
      26             :   ulong hit_cnt;
      27             :   ulong miss_cnt;
      28             :   ulong insert_cnt;
      29             : 
      30             :   fd_ed25519_cache_set_t * set;    /* indexed [0,set_cnt) */
      31             :   ulong *                  filter; /* indexed [0,filter_cnt), hashes of recent successful misses */
      32             :   fd_ed25519_point_t *     tbl;    /* indexed [0,WAY_CNT*set_cnt*FD_ED25519_SPLIT_A_TBL_CNT), split tables of -A */
      33             : 
      34             :   fd_ed25519_point_t b_tbl[ FD_ED25519_SPLIT_B_TBL_CNT ];
      35             : };
      36             : 
      37             : /* The admission filter has 4 slots per entry */
      38             : 
      39          99 : #define FILTER_RATIO (4UL)
      40             : 
      41             : /* Each evaluated group equation earns a credit and a table build
      42             :    (~0.7 verify) costs BUILD_COST credits, bounding the build overhead
      43             :    to <10% even when every key is new.  Up to CREDIT_MAX credits bank
      44             :    up for bursts of new keys. */
      45             : 
      46      261976 : #define BUILD_COST (8UL)
      47      249632 : #define CREDIT_MAX (64UL*BUILD_COST)
      48             : 
      49             : FD_FN_CONST ulong
      50        1227 : fd_ed25519_cache_align( void ) {
      51        1227 :   return FD_ED25519_CACHE_ALIGN;
      52        1227 : }
      53             : 
      54             : FD_FN_CONST ulong
      55         321 : fd_ed25519_cache_footprint( ulong ent_cnt ) {
      56         321 :   if( FD_UNLIKELY( !fd_ulong_is_pow2( ent_cnt ) || ent_cnt<WAY_CNT || ent_cnt>(1UL<<20) ) ) return 0UL;
      57         321 :   ulong set_cnt = ent_cnt/WAY_CNT;
      58         321 :   ulong l = FD_LAYOUT_INIT;
      59         321 :   l = FD_LAYOUT_APPEND( l, alignof(fd_ed25519_cache_t),     sizeof(fd_ed25519_cache_t)                              );
      60         321 :   l = FD_LAYOUT_APPEND( l, alignof(fd_ed25519_cache_set_t), set_cnt*sizeof(fd_ed25519_cache_set_t)                  );
      61         321 :   l = FD_LAYOUT_APPEND( l, alignof(ulong),                  FILTER_RATIO*ent_cnt*sizeof(ulong)                      );
      62         321 :   l = FD_LAYOUT_APPEND( l, alignof(fd_ed25519_point_t),     ent_cnt*FD_ED25519_SPLIT_A_TBL_CNT*sizeof(fd_ed25519_point_t) );
      63         321 :   return FD_LAYOUT_FINI( l, fd_ed25519_cache_align() );
      64         321 : }
      65             : 
      66             : void *
      67             : fd_ed25519_cache_new( void * mem,
      68             :                       ulong  ent_cnt,
      69          99 :                       ulong  seed ) {
      70          99 :   if( FD_UNLIKELY( !mem ) ) {
      71           0 :     FD_LOG_WARNING(( "NULL mem" ));
      72           0 :     return NULL;
      73           0 :   }
      74          99 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_ed25519_cache_align() ) ) ) {
      75           0 :     FD_LOG_WARNING(( "misaligned mem" ));
      76           0 :     return NULL;
      77           0 :   }
      78          99 :   if( FD_UNLIKELY( !fd_ed25519_cache_footprint( ent_cnt ) ) ) {
      79           0 :     FD_LOG_WARNING(( "bad ent_cnt" ));
      80           0 :     return NULL;
      81           0 :   }
      82             : 
      83          99 :   ulong set_cnt    = ent_cnt/WAY_CNT;
      84          99 :   ulong filter_cnt = FILTER_RATIO*ent_cnt;
      85             : 
      86          99 :   FD_SCRATCH_ALLOC_INIT( l, mem );
      87          99 :   fd_ed25519_cache_t * cache = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_ed25519_cache_t),     sizeof(fd_ed25519_cache_t)                              );
      88          99 :   void *               set   = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_ed25519_cache_set_t), set_cnt*sizeof(fd_ed25519_cache_set_t)                  );
      89          99 :   ulong *              filt  = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong),                  filter_cnt*sizeof(ulong)                                );
      90          99 :   void *               tbl   = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_ed25519_point_t),     ent_cnt*FD_ED25519_SPLIT_A_TBL_CNT*sizeof(fd_ed25519_point_t) );
      91          99 :   FD_SCRATCH_ALLOC_FINI( l, fd_ed25519_cache_align() );
      92             : 
      93          99 :   cache->set_cnt    = set_cnt;
      94          99 :   cache->filter_cnt = filter_cnt;
      95          99 :   cache->seed       = seed;
      96          99 :   cache->credit     = CREDIT_MAX;
      97          99 :   cache->tick       = 0UL;
      98          99 :   cache->hit_cnt    = 0UL;
      99          99 :   cache->miss_cnt   = 0UL;
     100          99 :   cache->insert_cnt = 0UL;
     101          99 :   cache->set        = set;
     102          99 :   cache->filter     = filt;
     103          99 :   cache->tbl        = tbl;
     104             : 
     105          99 :   memset( set,  0, set_cnt*sizeof(fd_ed25519_cache_set_t) );
     106          99 :   memset( filt, 0, filter_cnt*sizeof(ulong)               );
     107             : 
     108          99 :   fd_ed25519_split_table_b( cache->b_tbl );
     109             : 
     110          99 :   FD_COMPILER_MFENCE();
     111          99 :   cache->magic = FD_ED25519_CACHE_MAGIC;
     112          99 :   FD_COMPILER_MFENCE();
     113             : 
     114          99 :   return mem;
     115          99 : }
     116             : 
     117             : fd_ed25519_cache_t *
     118          99 : fd_ed25519_cache_join( void * shcache ) {
     119          99 :   fd_ed25519_cache_t * cache = (fd_ed25519_cache_t *)shcache;
     120          99 :   if( FD_UNLIKELY( !cache ) ) {
     121           0 :     FD_LOG_WARNING(( "NULL shcache" ));
     122           0 :     return NULL;
     123           0 :   }
     124          99 :   if( FD_UNLIKELY( cache->magic!=FD_ED25519_CACHE_MAGIC ) ) {
     125           0 :     FD_LOG_WARNING(( "bad magic" ));
     126           0 :     return NULL;
     127           0 :   }
     128          99 :   return cache;
     129          99 : }
     130             : 
     131             : void *
     132          54 : fd_ed25519_cache_leave( fd_ed25519_cache_t * cache ) {
     133          54 :   return (void *)cache;
     134          54 : }
     135             : 
     136             : void *
     137          54 : fd_ed25519_cache_delete( void * shcache ) {
     138          54 :   fd_ed25519_cache_t * cache = (fd_ed25519_cache_t *)shcache;
     139          54 :   if( FD_UNLIKELY( !cache ) ) {
     140           0 :     FD_LOG_WARNING(( "NULL shcache" ));
     141           0 :     return NULL;
     142           0 :   }
     143          54 :   if( FD_UNLIKELY( cache->magic!=FD_ED25519_CACHE_MAGIC ) ) {
     144           0 :     FD_LOG_WARNING(( "bad magic" ));
     145           0 :     return NULL;
     146           0 :   }
     147          54 :   FD_COMPILER_MFENCE();
     148          54 :   cache->magic = 0UL;
     149          54 :   FD_COMPILER_MFENCE();
     150          54 :   return shcache;
     151          54 : }
     152             : 
     153       12027 : ulong fd_ed25519_cache_hit_cnt   ( fd_ed25519_cache_t const * cache ) { return cache->hit_cnt;    }
     154          15 : ulong fd_ed25519_cache_miss_cnt  ( fd_ed25519_cache_t const * cache ) { return cache->miss_cnt;   }
     155          36 : ulong fd_ed25519_cache_insert_cnt( fd_ed25519_cache_t const * cache ) { return cache->insert_cnt; }
     156             : 
     157             : /* cache_query returns the split table of -A for public_key, or NULL if
     158             :    public_key is not cached.  *_h is set to the hash of public_key. */
     159             : 
     160             : static fd_ed25519_point_t const *
     161             : cache_query( fd_ed25519_cache_t * cache,
     162             :              uchar const          public_key[ 32 ],
     163      353080 :              ulong *              _h ) {
     164      353080 :   ulong h   = fd_hash( cache->seed, public_key, 32UL );
     165      353080 :   ulong idx = h & (cache->set_cnt-1UL);
     166      353080 :   *_h = h;
     167             : 
     168      353080 :   fd_ed25519_cache_set_t * set = cache->set + idx;
     169      353080 :   ulong tick = ++cache->tick;
     170             : 
     171     1085808 :   for( ulong w=0UL; w<WAY_CNT; w++ ) {
     172      939554 :     if( FD_LIKELY( set->use[ w ] && fd_memeq( set->key[ w ], public_key, 32UL ) ) ) {
     173      206826 :       set->use[ w ] = tick;
     174      206826 :       cache->hit_cnt++;
     175      206826 :       fd_ed25519_point_t const * tbl = cache->tbl + (WAY_CNT*idx+w)*FD_ED25519_SPLIT_A_TBL_CNT;
     176    17861290 :       for( ulong off=0UL; off<FD_ED25519_SPLIT_A_TBL_CNT*sizeof(fd_ed25519_point_t); off+=64UL ) {
     177    17654464 :         __builtin_prefetch( (uchar const *)tbl + off, 0, 3 );
     178    17654464 :       }
     179      206826 :       return tbl;
     180      206826 :     }
     181      939554 :   }
     182      146254 :   cache->miss_cnt++;
     183      146254 :   return NULL;
     184      353080 : }
     185             : 
     186             : /* cache_admit is called after a signature by public_key (hash h, not
     187             :    cached) verified successfully, with neg_a the decoded point -A.  The
     188             :    key is inserted if it also verified successfully recently.  Only
     189             :    keys that decode to a valid, not small order point can verify
     190             :    successfully, and building a table requires a valid signature, so
     191             :    junk traffic can't force table builds. */
     192             : 
     193             : static void
     194             : cache_admit( fd_ed25519_cache_t *       cache,
     195             :              uchar const                public_key[ 32 ],
     196             :              ulong                      h,
     197       44127 :              fd_ed25519_point_t const * neg_a ) {
     198             : 
     199       44127 :   ulong * slot = cache->filter + (fd_ulong_hash( h ) & (cache->filter_cnt-1UL));
     200       44127 :   if( FD_LIKELY( *slot!=h ) ) {
     201       21505 :     *slot = h;
     202       21505 :     return;
     203       21505 :   }
     204       22622 :   if( FD_UNLIKELY( cache->credit<BUILD_COST ) ) return;
     205       12344 :   cache->credit -= BUILD_COST;
     206       12344 :   *slot = 0UL;
     207             : 
     208             :   /* Replace the least recently used way (invalid ways have use 0) */
     209             : 
     210       12344 :   ulong idx = h & (cache->set_cnt-1UL);
     211       12344 :   fd_ed25519_cache_set_t * set = cache->set + idx;
     212       12344 :   ulong w = 0UL;
     213       49376 :   for( ulong v=1UL; v<WAY_CNT; v++ ) w = fd_ulong_if( set->use[ v ]<set->use[ w ], v, w );
     214             : 
     215       12344 :   fd_ed25519_split_table_a( cache->tbl + (WAY_CNT*idx+w)*FD_ED25519_SPLIT_A_TBL_CNT, neg_a );
     216       12344 :   memcpy( set->key[ w ], public_key, 32UL );
     217       12344 :   set->use[ w ] = ++cache->tick;
     218       12344 :   cache->insert_cnt++;
     219       12344 : }
     220             : 
     221             : /* verify_one does the checks of fd_ed25519_verify on one signature, in
     222             :    the same order and with the same error codes, and returns the first
     223             :    failing check's error code.  If all checks pass, it returns
     224             :    FD_ED25519_SUCCESS and, if *_ok is non-zero on entry, sets *_ok to
     225             :    whether the group equation holds (if *_ok is zero on entry, the
     226             :    group equation is not evaluated). */
     227             : 
     228             : static int
     229             : verify_one( uchar const          msg[],
     230             :             ulong                msg_sz,
     231             :             uchar const          sig[ 64 ],
     232             :             uchar const          public_key[ 32 ],
     233             :             fd_sha512_t *        sha,
     234             :             fd_ed25519_cache_t * cache,
     235      369499 :             int *                _ok ) {
     236             : 
     237      369499 :   uchar const * r = sig;
     238      369499 :   uchar const * S = sig + 32;
     239             : 
     240      369499 :   if( FD_UNLIKELY( !fd_curve25519_scalar_validate( S ) ) ) return FD_ED25519_ERR_SIG;
     241             : 
     242      353080 :   ulong h;
     243      353080 :   fd_ed25519_point_t const * a_tbl = cache_query( cache, public_key, &h );
     244             : 
     245      353080 :   fd_ed25519_point_t Aprime[1], R[1];
     246      353080 :   if( FD_LIKELY( a_tbl ) ) {
     247             : 
     248             :     /* A is cached, so it decodes and is not small order: the checks of
     249             :        the uncached path reduce to the ones on R.  frombytes_1x gives
     250             :        the same point as frombytes_2x, so the small order checks
     251             :        agree.  When the equation is evaluated, R's decode is
     252             :        interleaved with the scalar mul and its checks run afterwards,
     253             :        in the same order. */
     254             : 
     255      206826 :     if( FD_UNLIKELY( !*_ok ) ) {
     256       29514 :       if( FD_UNLIKELY( fd_ed25519_point_frombytes_1x( R, r ) ) ) return FD_ED25519_ERR_SIG;
     257       28930 :       if( FD_UNLIKELY( fd_ed25519_affine_is_small_order( R ) ) ) return FD_ED25519_ERR_SIG;
     258       28927 :       return FD_ED25519_SUCCESS;
     259       28930 :     }
     260             : 
     261      177312 :     fd_ed25519_point_decode_t dec[1];
     262      177312 :     fd_ed25519_point_decode_init( dec, r );
     263             : 
     264      177312 :     uchar k[ 64 ];
     265      177312 :     fd_sha512_fini( fd_sha512_append( fd_sha512_append( fd_sha512_append( fd_sha512_init( sha ),
     266      177312 :                     r, 32UL ), public_key, 32UL ), msg, msg_sz ), k );
     267      177312 :     fd_curve25519_scalar_reduce( k, k );
     268             : 
     269             :     /* Rcmp = [k](-A') + [S]B */
     270             : 
     271      177312 :     fd_ed25519_point_t Rcmp[1];
     272      177312 :     fd_ed25519_double_scalar_mul_base_split_decode( Rcmp, k, a_tbl, S, cache->b_tbl, dec );
     273             : 
     274      177312 :     if( FD_UNLIKELY( fd_ed25519_point_decode_fini( R, dec ) ) ) return FD_ED25519_ERR_SIG;
     275      163482 :     if( FD_UNLIKELY( fd_ed25519_affine_is_small_order( R ) ) ) return FD_ED25519_ERR_SIG;
     276             : 
     277      155124 :     cache->credit = fd_ulong_min( cache->credit+1UL, CREDIT_MAX );
     278      155124 :     *_ok = fd_ed25519_point_eq_z1( Rcmp, R );
     279      155124 :     return FD_ED25519_SUCCESS;
     280      163482 :   }
     281             : 
     282      146254 :   int res = fd_ed25519_point_frombytes_2x( Aprime, public_key, R, r );
     283      146254 :   if( FD_UNLIKELY( res ) ) return res == -1 ? FD_ED25519_ERR_PUBKEY : FD_ED25519_ERR_SIG;
     284      122452 :   if( FD_UNLIKELY( fd_ed25519_affine_is_small_order( Aprime ) ) ) return FD_ED25519_ERR_PUBKEY;
     285      110473 :   if( FD_UNLIKELY( fd_ed25519_affine_is_small_order( R      ) ) ) return FD_ED25519_ERR_SIG;
     286             : 
     287      108562 :   if( FD_UNLIKELY( !*_ok ) ) return FD_ED25519_SUCCESS;
     288             : 
     289       94409 :   cache->credit = fd_ulong_min( cache->credit+1UL, CREDIT_MAX );
     290             : 
     291       94409 :   uchar k[ 64 ];
     292       94409 :   fd_sha512_fini( fd_sha512_append( fd_sha512_append( fd_sha512_append( fd_sha512_init( sha ),
     293       94409 :                   r, 32UL ), public_key, 32UL ), msg, msg_sz ), k );
     294       94409 :   fd_curve25519_scalar_reduce( k, k );
     295             : 
     296             :   /* Rcmp = [k](-A') + [S]B */
     297             : 
     298       94409 :   fd_ed25519_point_t Rcmp[1];
     299       94409 :   fd_ed25519_point_neg( Aprime, Aprime );
     300       94409 :   fd_ed25519_double_scalar_mul_base( Rcmp, k, Aprime, S );
     301       94409 :   *_ok = fd_ed25519_point_eq_z1( Rcmp, R );
     302       94409 :   if( FD_LIKELY( *_ok ) ) cache_admit( cache, public_key, h, Aprime );
     303       94409 :   return FD_ED25519_SUCCESS;
     304      108562 : }
     305             : 
     306             : int
     307             : fd_ed25519_verify_cached( uchar const          msg[], /* msg_sz */
     308             :                           ulong                msg_sz,
     309             :                           uchar const          sig[ 64 ],
     310             :                           uchar const          public_key[ 32 ],
     311             :                           fd_sha512_t *        sha,
     312      155158 :                           fd_ed25519_cache_t * cache ) {
     313      155158 :   int ok  = 1;
     314      155158 :   int err = verify_one( msg, msg_sz, sig, public_key, sha, cache, &ok );
     315      155158 :   if( FD_UNLIKELY( err ) ) return err;
     316       91337 :   return FD_LIKELY( ok ) ? FD_ED25519_SUCCESS : FD_ED25519_ERR_MSG;
     317      155158 : }
     318             : 
     319             : int
     320             : fd_ed25519_verify_batch_single_msg_cached( uchar const          msg[], /* msg_sz */
     321             :                                            ulong const          msg_sz,
     322             :                                            uchar const          signatures[ 64 ], /* 64 * batch_sz */
     323             :                                            uchar const          pubkeys[ 32 ],    /* 32 * batch_sz */
     324             :                                            fd_sha512_t *        shas[ 1 ],        /* batch_sz */
     325             :                                            uchar const          batch_sz,
     326       41478 :                                            fd_ed25519_cache_t * cache ) {
     327       41478 :   if( FD_UNLIKELY( batch_sz == 0 || batch_sz > 16 ) ) return FD_ED25519_ERR_SIG;
     328             : 
     329             :   /* The uncached version runs all checks first and then the group
     330             :      equations.  A check failure on any signature takes precedence over
     331             :      a group equation failure on an earlier one, so group equation
     332             :      failures are only reported after all checks pass.  Once a group
     333             :      equation fails, the remaining ones are skipped. */
     334             : 
     335       41073 :   int ok = 1;
     336      242349 :   for( ulong j=0UL; j<batch_sz; j++ ) {
     337      214341 :     int err = verify_one( msg, msg_sz, signatures+64UL*j, pubkeys+32UL*j, shas[j], cache, &ok );
     338      214341 :     if( FD_UNLIKELY( err ) ) return err;
     339      214341 :   }
     340       28008 :   return FD_LIKELY( ok ) ? FD_ED25519_SUCCESS : FD_ED25519_ERR_MSG;
     341       41073 : }

Generated by: LCOV version 1.14