LCOV - code coverage report
Current view: top level - ballet/zksdk/rangeproofs - fd_rangeproofs.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 177 205 86.3 %
Date: 2026-03-19 18:19:27 Functions: 2 2 100.0 %

          Line data    Source code
       1             : #include "fd_rangeproofs.h"
       2             : 
       3             : /* https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.1/zk-sdk/src/range_proof/mod.rs#L509 */
       4             : void
       5             : fd_rangeproofs_delta(
       6             :   uchar       delta[ 32 ],
       7             :   ulong const nm,
       8             :   uchar const y[ 32 ],
       9             :   uchar const z[ 32 ],
      10             :   uchar const zz[ 32 ],
      11             :   uchar const bit_lengths[ 1 ],
      12             :   uchar const batch_len
      13          30 : ) {
      14          30 :   uchar exp_y[ 32 ];
      15          30 :   uchar sum_of_powers_y[ 32 ];
      16          30 :   fd_memcpy( exp_y, y, 32 );
      17          30 :   fd_curve25519_scalar_add( sum_of_powers_y, y, fd_curve25519_scalar_one );
      18         210 :   for( ulong i=nm; i>2; i/=2 ) {
      19         180 :     fd_curve25519_scalar_mul   ( exp_y, exp_y, exp_y );
      20         180 :     fd_curve25519_scalar_muladd( sum_of_powers_y, exp_y, sum_of_powers_y, sum_of_powers_y );
      21         180 :   }
      22          30 :   fd_curve25519_scalar_sub( delta, z, zz );
      23          30 :   fd_curve25519_scalar_mul( delta, delta, sum_of_powers_y );
      24             : 
      25          30 :   uchar neg_exp_z[ 32 ];
      26          30 :   uchar sum_2[ 32 ];
      27          30 :   fd_curve25519_scalar_neg( neg_exp_z, zz );
      28         270 :   for( ulong i=0; i<batch_len; i++ ) {
      29         240 :     fd_memset( sum_2, 0, 32 );
      30             :     //TODO currently assuming that bit_length[i] is multiple of 8 - need to fix cases: 1, 2, 4
      31         240 :     fd_memset( sum_2, 0xFF, bit_lengths[i] / 8 );
      32         240 :     fd_curve25519_scalar_mul   ( neg_exp_z, neg_exp_z, z );
      33         240 :     fd_curve25519_scalar_muladd( delta, neg_exp_z, sum_2, delta );
      34         240 :   }
      35          30 : }
      36             : 
      37             : /* https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.1/zk-sdk/src/range_proof/mod.rs#L324 */
      38             : int
      39             : fd_rangeproofs_verify(
      40             :   fd_rangeproofs_range_proof_t const * range_proof,
      41             :   fd_rangeproofs_ipp_proof_t const *   ipp_proof,
      42             :   uchar const                          commitments [ 32 ],
      43             :   uchar const                          bit_lengths [ 1 ],
      44             :   uchar const                          batch_len,
      45          36 :   fd_merlin_transcript_t *             transcript ) {
      46             : 
      47             :   /*
      48             :     We need to verify a range proof, by computing a large MSM.
      49             : 
      50             :     We store points in the following array.
      51             :     Indexes are the common example of u128 batch range proof with batch_len==4,
      52             :     used in SPL confidential transfers.
      53             : 
      54             :            points
      55             :       0    G
      56             :       1    H
      57             :       2    S
      58             :       3    T_1
      59             :       4    T_2
      60             :       5    commitments[ 0 ]
      61             :            ...
      62             :       8    commitments[ 3 ]    // 4 == batch_len (example)
      63             :       9    L_vec[ 0 ]
      64             :            ...
      65             :      15    L_vec[ 6 ]          // 7 == log2( 128 )
      66             :      16    R_vec[ 0 ]
      67             :            ...
      68             :      22    R_vec[ 6 ]          // 7 == log2( 128 )
      69             :      23    generators_H[ 0 ]
      70             :            ...
      71             :     150    generators_H[ 127 ] // 128 generators
      72             :     151    generators_G[ 0 ]
      73             :            ...
      74             :     278    generators_G[ 127 ] // 128 generators
      75             :     ------------------------------------------------------ MSM
      76             :            A
      77             : 
      78             :     As final check we test that the result of the MSM == -A.
      79             :     We could negate all scalars, but that'd make it more complex to debug
      80             :     against Rust rangeproofs / Solana, in case of issues, and the marginal
      81             :     cost of negating A is negligible.
      82             : 
      83             :     This implementation has a few differences compared to the Rust implementation.
      84             : 
      85             :     - We need to support batched range proofs for u64, u128 and u256.
      86             :       Rust does dynamic allocations. This implementation statically allocates
      87             :       for u256 (a total of <64kB) and dynamically handles u64 and u128.
      88             : 
      89             :     - This implementation limits memory copies.
      90             :       Input data arrives from the Solana tx in a certain order and essentially
      91             :       includes compressed points and scalars.
      92             :       We allocate enough scalars and (uncompressed) points for the MSM.
      93             :       As we parse input data, we compute scalars and decompress points
      94             :       directly into the memory region used by MSM (layout shown above).
      95             : 
      96             :     - Points and scalars are in a different order compared to Rust,
      97             :       but their value is the same. The order has no particular meaning,
      98             :       it just seemed more convenient.
      99             : 
     100             :     - Range proof depends interally on innerproduct proof (ipp).
     101             :       ipp needs to invert logn elements (called u_i).
     102             :       range proof, in addition, needs to invert y.
     103             :       Rust uses batch inversion to invert all u_i more efficiently.
     104             :       We also include y in the batch, to save 1 inversion (~300 mul).
     105             : 
     106             :     - ipp generates n scalars s_i, from which range proof derives 2n scalars
     107             :       for generators_G and generators_H.
     108             :       The scalars for generators_G are just a rescaling of s_i,
     109             :       while the scalars for generators_H are a bit more complex.
     110             :       We store s_i in the same memory region of generators_G scalars,
     111             :       then use them to compute generators_H scalars, and finally we do
     112             :       the rescaling. This saves 8kB of stack.
     113             :   */
     114             : 
     115             :   /* Capital LOGN, N are used to allocate memory.
     116             :      Lowercase logn, n are used at runtime.
     117             :      This implementation allocates memory to support u256, and
     118             :      at runtime can verify u64, u128 and u256 range proofs. */
     119          36 : #define LOGN 8
     120          36 : #define N (1 << LOGN)
     121          36 : #define MAX (2*N + 2*LOGN + 5 + FD_RANGEPROOFS_MAX_COMMITMENTS)
     122             : 
     123          36 :   const ulong logn = ipp_proof->logn;
     124          36 :   const ulong n = 1UL << logn;
     125             : 
     126             :   /* https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.1/zk-sdk/src/range_proof/mod.rs#L330-L338
     127             :      These checks are already done in batched_range_proof_context_try_into() */
     128             : 
     129             :   /* https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.1/zk-sdk/src/range_proof/mod.rs#L340-L344
     130             :      total bit length (nm) should be a power of 2, and equal to the sz of the range proof.
     131             :      since n by definition is a power of 2, we can just check nm == n. */
     132          36 :   ulong nm = 0;
     133         324 :   for( uchar i=0; i<batch_len; i++ ) {
     134         288 :     nm += bit_lengths[i];
     135         288 :   }
     136          36 :   if( FD_UNLIKELY( nm != n ) ) {
     137           0 :     return FD_RANGEPROOFS_ERROR;
     138           0 :   }
     139             : 
     140             :   /* Validate all inputs */
     141          36 :   uchar scalars[ MAX*32 ];
     142          36 :   fd_ristretto255_point_t points[ MAX ];
     143          36 :   fd_ristretto255_point_t a_res[ 1 ];
     144          36 :   fd_ristretto255_point_t res[ 1 ];
     145             : 
     146          36 :   if( FD_UNLIKELY( fd_curve25519_scalar_validate( range_proof->tx )==NULL ) ) {
     147           0 :     return FD_RANGEPROOFS_ERROR;
     148           0 :   }
     149          36 :   if( FD_UNLIKELY( fd_curve25519_scalar_validate( range_proof->tx_blinding )==NULL ) ) {
     150           0 :     return FD_RANGEPROOFS_ERROR;
     151           0 :   }
     152          36 :   if( FD_UNLIKELY( fd_curve25519_scalar_validate( range_proof->e_blinding )==NULL ) ) {
     153           0 :     return FD_RANGEPROOFS_ERROR;
     154           0 :   }
     155          36 :   if( FD_UNLIKELY( fd_curve25519_scalar_validate( ipp_proof->a )==NULL ) ) {
     156           0 :     return FD_RANGEPROOFS_ERROR;
     157           0 :   }
     158          36 :   if( FD_UNLIKELY( fd_curve25519_scalar_validate( ipp_proof->b )==NULL ) ) {
     159           0 :     return FD_RANGEPROOFS_ERROR;
     160           0 :   }
     161             : 
     162          36 :   fd_ristretto255_point_set( &points[0], fd_rangeproofs_basepoint_G );
     163          36 :   fd_ristretto255_point_set( &points[1], fd_rangeproofs_basepoint_H );
     164          36 :   if( FD_UNLIKELY( fd_ristretto255_point_decompress( a_res, range_proof->a )==NULL ) ) {
     165           0 :     return FD_RANGEPROOFS_ERROR;
     166           0 :   }
     167          36 :   if( FD_UNLIKELY( fd_ristretto255_point_decompress( &points[2], range_proof->s )==NULL ) ) {
     168           0 :     return FD_RANGEPROOFS_ERROR;
     169           0 :   }
     170          36 :   if( FD_UNLIKELY( fd_ristretto255_point_decompress( &points[3], range_proof->t1 )==NULL ) ) {
     171           0 :     return FD_RANGEPROOFS_ERROR;
     172           0 :   }
     173          36 :   if( FD_UNLIKELY( fd_ristretto255_point_decompress( &points[4], range_proof->t2 )==NULL ) ) {
     174           0 :     return FD_RANGEPROOFS_ERROR;
     175           0 :   }
     176          36 :   ulong idx = 5;
     177         276 :   for( ulong i=0; i<batch_len; i++, idx++ ) {
     178         246 :     if( FD_UNLIKELY( fd_ristretto255_point_decompress( &points[ idx ], &commitments[ i*32 ] )==NULL ) ) {
     179           6 :       return FD_RANGEPROOFS_ERROR;
     180           6 :     }
     181         246 :   }
     182         240 :   for( ulong i=0; i<logn; i++, idx++ ) {
     183         210 :     if( FD_UNLIKELY( fd_ristretto255_point_decompress( &points[ idx ], ipp_proof->vecs[ i ].l )==NULL ) ) {
     184           0 :       return FD_RANGEPROOFS_ERROR;
     185           0 :     }
     186         210 :   }
     187         240 :   for( ulong i=0; i<logn; i++, idx++ ) {
     188         210 :     if( FD_UNLIKELY( fd_ristretto255_point_decompress( &points[ idx ], ipp_proof->vecs[ i ].r )==NULL ) ) {
     189           0 :       return FD_RANGEPROOFS_ERROR;
     190           0 :     }
     191         210 :   }
     192          30 :   fd_memcpy( &points[ idx ],   fd_rangeproofs_generators_H, n*sizeof(fd_ristretto255_point_t) );
     193          30 :   fd_memcpy( &points[ idx+n ], fd_rangeproofs_generators_G, n*sizeof(fd_ristretto255_point_t) );
     194             : 
     195             :   /* Finalize transcript and extract challenges */
     196             : 
     197             :   /* https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.1/zk-sdk/src/range_proof/mod.rs#L349 */
     198          30 :   fd_rangeproofs_transcript_domsep_range_proof( transcript, nm );
     199             : 
     200             :   /* https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.1/zk-sdk/src/range_proof/mod.rs#L351-L387 */
     201          30 :   int val = FD_TRANSCRIPT_SUCCESS;
     202          30 :   val |= fd_rangeproofs_transcript_validate_and_append_point( transcript, FD_TRANSCRIPT_LITERAL("A"), range_proof->a);
     203          30 :   val |= fd_rangeproofs_transcript_validate_and_append_point( transcript, FD_TRANSCRIPT_LITERAL("S"), range_proof->s);
     204             : 
     205             :   /* We need to invert a bunch of values, we collect them all in a single buffer to use batch inversion. */
     206          30 :   uchar batchinv_in [ 32*(1+LOGN) ];
     207          30 :   uchar batchinv_out[ 32*(1+LOGN) ];
     208          30 :   uchar allinv[ 32 ];
     209             : 
     210          30 :   uchar *y_inv = batchinv_out;
     211          30 :   uchar *y = batchinv_in;
     212          30 :   uchar z[ 32 ];
     213          30 :   fd_rangeproofs_transcript_challenge_scalar( y, transcript, FD_TRANSCRIPT_LITERAL("y") );
     214          30 :   fd_rangeproofs_transcript_challenge_scalar( z, transcript, FD_TRANSCRIPT_LITERAL("z") );
     215             : 
     216          30 :   val |= fd_rangeproofs_transcript_validate_and_append_point( transcript, FD_TRANSCRIPT_LITERAL("T_1"), range_proof->t1);
     217          30 :   val |= fd_rangeproofs_transcript_validate_and_append_point( transcript, FD_TRANSCRIPT_LITERAL("T_2"), range_proof->t2);
     218          30 :   if( FD_UNLIKELY( val != FD_TRANSCRIPT_SUCCESS ) ) {
     219           0 :     return FD_RANGEPROOFS_ERROR;
     220           0 :   }
     221             : 
     222          30 :   uchar x[ 32 ];
     223          30 :   fd_rangeproofs_transcript_challenge_scalar( x, transcript, FD_TRANSCRIPT_LITERAL("x") );
     224             : 
     225          30 :   fd_rangeproofs_transcript_append_scalar( transcript, FD_TRANSCRIPT_LITERAL("t_x"), range_proof->tx);
     226          30 :   fd_rangeproofs_transcript_append_scalar( transcript, FD_TRANSCRIPT_LITERAL("t_x_blinding"), range_proof->tx_blinding);
     227          30 :   fd_rangeproofs_transcript_append_scalar( transcript, FD_TRANSCRIPT_LITERAL("e_blinding"), range_proof->e_blinding);
     228             : 
     229          30 :   uchar w[ 32 ];
     230          30 :   uchar _c[ 32 ];
     231          30 :   uchar d[ 32 ];
     232          30 :   fd_rangeproofs_transcript_challenge_scalar( w, transcript, FD_TRANSCRIPT_LITERAL("w") );
     233             :   /* The challenge `c` is a legacy component from an older implementation.
     234             :      It is now unused, but is kept here for backward compatibility. */
     235          30 :   fd_rangeproofs_transcript_challenge_scalar( _c, transcript, FD_TRANSCRIPT_LITERAL("c") );
     236             : 
     237             :   /* Inner Product (sub)Proof */
     238          30 :   uchar *u =     &batchinv_in [ 32 ]; // skip y
     239          30 :   uchar *u_inv = &batchinv_out[ 32 ]; // skip y_inv
     240             :   /* https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.1/zk-sdk/src/range_proof/mod.rs#L377 */
     241          30 :   {
     242             :     /* https://github.com/solana-program/zk-elgamal-proof/blob/main/zk-sdk/src/range_proof/inner_product.rs#L246 */
     243          30 :     fd_rangeproofs_transcript_domsep_inner_product( transcript, nm );
     244             : 
     245         240 :     for( ulong i=0; i<logn; i++ ) {
     246         210 :       val |= fd_rangeproofs_transcript_validate_and_append_point( transcript, FD_TRANSCRIPT_LITERAL("L"), ipp_proof->vecs[ i ].l);
     247         210 :       val |= fd_rangeproofs_transcript_validate_and_append_point( transcript, FD_TRANSCRIPT_LITERAL("R"), ipp_proof->vecs[ i ].r);
     248         210 :       if( FD_UNLIKELY( val != FD_TRANSCRIPT_SUCCESS ) ) {
     249           0 :         return FD_RANGEPROOFS_ERROR;
     250           0 :       }
     251         210 :       fd_rangeproofs_transcript_challenge_scalar( &u[ i*32 ], transcript, FD_TRANSCRIPT_LITERAL("u") );
     252         210 :     }
     253          30 :     fd_curve25519_scalar_batch_inv( batchinv_out, allinv, batchinv_in, logn+1 );
     254          30 :   }
     255             : 
     256          30 :   fd_rangeproofs_transcript_append_scalar( transcript, FD_TRANSCRIPT_LITERAL("ipp_a"), ipp_proof->a );
     257          30 :   fd_rangeproofs_transcript_append_scalar( transcript, FD_TRANSCRIPT_LITERAL("ipp_b"), ipp_proof->b );
     258             : 
     259             :   /* https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.1/zk-sdk/src/range_proof/mod.rs#L387 */
     260          30 :   fd_rangeproofs_transcript_challenge_scalar( d, transcript, FD_TRANSCRIPT_LITERAL("d") );
     261             : 
     262             :   /* Compute scalars */
     263             : 
     264             :   /* https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.1/zk-sdk/src/range_proof/mod.rs#L391-L411 */
     265             : 
     266             :   // H: - ( eb + c t_xb )
     267          30 :   uchar const *eb = range_proof->e_blinding;
     268          30 :   uchar const *txb = range_proof->tx_blinding;
     269          30 :   fd_curve25519_scalar_muladd( &scalars[ 1*32 ], d, txb, eb );
     270          30 :   fd_curve25519_scalar_neg(    &scalars[ 1*32 ], &scalars[ 1*32 ] );
     271             : 
     272             :   // S:   x
     273             :   // T_1: c x
     274             :   // T_2: c x^2
     275          30 :   fd_curve25519_scalar_set(    &scalars[ 2*32 ], x );
     276          30 :   fd_curve25519_scalar_mul(    &scalars[ 3*32 ], d, x );
     277          30 :   fd_curve25519_scalar_mul(    &scalars[ 4*32 ], &scalars[ 3*32 ], x );
     278             : 
     279             :   // commitments: c z^2, c z^3 ...
     280          30 :   uchar zz[ 32 ];
     281          30 :   fd_curve25519_scalar_mul(    zz, z, z );
     282          30 :   fd_curve25519_scalar_mul(    &scalars[ 5*32 ], zz, d );
     283          30 :   idx = 6;
     284         240 :   for( ulong i=1; i<batch_len; i++, idx++ ) {
     285         210 :     fd_curve25519_scalar_mul(  &scalars[ idx*32 ], &scalars[ (idx-1)*32 ], z );
     286         210 :   }
     287             : 
     288             :   // L_vec: u0^2, u1^2...
     289             :   // R_vec: 1/u0^2, 1/u1^2...
     290          30 :   uchar *u_sq = &scalars[ idx*32 ];
     291         240 :   for( ulong i=0; i<logn; i++, idx++ ) {
     292         210 :     fd_curve25519_scalar_mul(  &scalars[ idx*32 ], &u[ i*32 ], &u[ i*32 ] );
     293         210 :   }
     294         240 :   for( ulong i=0; i<logn; i++, idx++ ) {
     295         210 :     fd_curve25519_scalar_mul(  &scalars[ idx*32 ], &u_inv[ i*32 ], &u_inv[ i*32 ] );
     296         210 :   }
     297             : 
     298             :   // s_i for generators_G, generators_H
     299          30 :   uchar *s = &scalars[ (idx+n)*32 ];
     300          30 :   fd_curve25519_scalar_mul( &s[ 0*32 ], allinv, y ); // allinv also contains 1/y
     301             :   // s[i] = s[ i-k ] * u[ k+1 ]^2   (k the "next power of 2" wrt i)
     302         239 :   for( ulong k=0; k<logn; k++ ) {
     303         209 :     ulong powk = (1UL << k);
     304        4657 :     for( ulong j=0; j<powk; j++ ) {
     305        4448 :       ulong i = powk + j;
     306        4448 :       fd_curve25519_scalar_mul( &s[ i*32 ], &s[ j*32 ], &u_sq[ (logn-1-k)*32 ] );
     307        4448 :     }
     308         209 :   }
     309             : 
     310             :   // generators_H: (-a * s_i) + (-z)
     311          30 :   uchar const *a = ipp_proof->a;
     312          30 :   uchar const *b = ipp_proof->b;
     313          30 :   uchar minus_b[ 32 ];
     314          30 :   uchar exp_z[ 32 ];
     315          30 :   uchar exp_y_inv[ 32 ];
     316          30 :   uchar z_and_2[ 32 ];
     317          30 :   fd_curve25519_scalar_neg( minus_b, b );
     318          30 :   fd_memcpy( exp_z, zz, 32 );
     319          30 :   fd_memcpy( z_and_2, exp_z, 32 );
     320          30 :   fd_memcpy( exp_y_inv, y, 32 ); //TODO: remove 2 unnecessary muls
     321        4508 :   for( ulong i=0, j=0, m=0; i<n; i++, j++, idx++ ) {
     322        4478 :     if( j == bit_lengths[m] ) {
     323         210 :       j = 0;
     324         210 :       m++;
     325         210 :       fd_curve25519_scalar_mul ( exp_z, exp_z, z );
     326         210 :       fd_memcpy( z_and_2, exp_z, 32 );
     327         210 :     }
     328        4478 :     if( j != 0 ) {
     329        4238 :       fd_curve25519_scalar_add ( z_and_2, z_and_2, z_and_2 );
     330        4238 :     }
     331        4478 :     fd_curve25519_scalar_mul   ( exp_y_inv, exp_y_inv, y_inv );
     332        4478 :     fd_curve25519_scalar_muladd( &scalars[ idx*32 ], &s[ (n-1-i)*32 ], minus_b, z_and_2 );
     333        4478 :     fd_curve25519_scalar_muladd( &scalars[ idx*32 ], &scalars[ idx*32 ], exp_y_inv, z );
     334        4478 :   }
     335             : 
     336             :   // generators_G: (-a * s_i) + (-z)
     337          30 :   uchar minus_z[ 32 ];
     338          30 :   uchar minus_a[ 32 ];
     339          30 :   fd_curve25519_scalar_neg( minus_z, z );
     340          30 :   fd_curve25519_scalar_neg( minus_a, a );
     341        4509 :   for( ulong i=0; i<n; i++, idx++ ) {
     342        4479 :     fd_curve25519_scalar_muladd( &scalars[ idx*32 ], &s[ i*32 ], minus_a, minus_z );
     343        4479 :   }
     344             : 
     345             :   // G
     346             :   // w * (self.t_x - a * b) + c * (delta(&bit_lengths, &y, &z) - self.t_x)
     347          30 :   uchar delta[ 32 ];
     348          30 :   fd_rangeproofs_delta( delta, nm, y, z, zz, bit_lengths, batch_len );
     349          30 :   fd_curve25519_scalar_muladd(  &scalars[ 0 ], minus_a, b, range_proof->tx );
     350          30 :   fd_curve25519_scalar_sub(     delta, delta, range_proof->tx );
     351          30 :   fd_curve25519_scalar_mul(     delta, delta, d );
     352          30 :   fd_curve25519_scalar_muladd(  &scalars[ 0 ], &scalars[ 0 ], w, delta );
     353             : 
     354             :   /* Compute the final MSM */
     355             : 
     356             :   /* https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.1/zk-sdk/src/range_proof/mod.rs#L415-L439 */
     357          30 :   fd_ristretto255_multi_scalar_mul( res, scalars, points, idx );
     358             : 
     359             :   /* https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.1/zk-sdk/src/range_proof/mod.rs#L441-L445 */
     360          30 :   if( FD_LIKELY( fd_ristretto255_point_eq_neg( res, a_res ) ) ) {
     361          12 :     return FD_RANGEPROOFS_SUCCESS;
     362          12 :   }
     363          18 :   return FD_RANGEPROOFS_ERROR;
     364          30 : #undef LOGN
     365          30 : #undef N
     366          30 : #undef MAX
     367          30 : }

Generated by: LCOV version 1.14