exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

crypto_ecdsa.c (11448B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file util/crypto_ecdsa.c
     18  * @brief ECDSA over NIST P-256
     19  * @author Bohdan Potuzhnyi
     20  * @author Volodymyr Potuzhnyi
     21  *
     22  * GNUnet's ECDSA is over Ed25519, so P-256 is implemented here on top
     23  * of libgcrypt. This module knows nothing about what is being
     24  * signed: callers pass a hash and receive raw fixed-width scalars.
     25  */
     26 #include "platform.h"
     27 #include "taler/taler_util.h"
     28 #include <gcrypt.h>
     29 
     30 
     31 /**
     32  * Curve name libgcrypt uses.
     33  */
     34 #define P256_CURVE "NIST P-256"
     35 
     36 /**
     37  * Size of a scalar: the private key, and each of r and s.
     38  */
     39 #define P256_SCALAR_LEN 32
     40 
     41 /**
     42  * Order of the group, used to canonicalize signatures to low-s.
     43  */
     44 #define P256_ORDER_HEX \
     45   "FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551"
     46 
     47 
     48 /**
     49  * Write @a v into @a buf, zero-padded on the left to exactly @a len
     50  * bytes. Needed because libgcrypt returns MPIs without leading
     51  * zeros, while the wire format uses fixed-width scalars.
     52  *
     53  * @param v value to write
     54  * @param[out] buf where to write the value
     55  * @param len number of bytes to write
     56  * @return #GNUNET_OK on success
     57  */
     58 static enum GNUNET_GenericReturnValue
     59 mpi_to_fixed (gcry_mpi_t v,
     60               unsigned char *buf,
     61               size_t len)
     62 {
     63   size_t nbytes;
     64 
     65   if (0 != gcry_mpi_print (GCRYMPI_FMT_USG,
     66                            NULL,
     67                            0,
     68                            &nbytes,
     69                            v))
     70   {
     71     GNUNET_break (0);
     72     return GNUNET_SYSERR;
     73   }
     74   if (nbytes > len)
     75   {
     76     GNUNET_break (0);
     77     return GNUNET_SYSERR;
     78   }
     79   memset (buf,
     80           0,
     81           len);
     82   if (0 != gcry_mpi_print (GCRYMPI_FMT_USG,
     83                            &buf[len - nbytes],
     84                            nbytes,
     85                            &nbytes,
     86                            v))
     87   {
     88     GNUNET_break (0);
     89     return GNUNET_SYSERR;
     90   }
     91   return GNUNET_OK;
     92 }
     93 
     94 
     95 /**
     96  * Extract the MPI @a token from the s-expression @a sexp.
     97  *
     98  * @param sexp s-expression to search
     99  * @param token name of the token to extract
    100  * @return the value, or NULL on error; caller must release
    101  */
    102 static gcry_mpi_t
    103 sexp_extract_mpi (gcry_sexp_t sexp,
    104                   const char *token)
    105 {
    106   gcry_sexp_t t;
    107   gcry_mpi_t ret;
    108 
    109   t = gcry_sexp_find_token (sexp,
    110                             token,
    111                             0);
    112   if (NULL == t)
    113   {
    114     GNUNET_break (0);
    115     return NULL;
    116   }
    117   ret = gcry_sexp_nth_mpi (t,
    118                            1,
    119                            GCRYMPI_FMT_USG);
    120   gcry_sexp_release (t);
    121   if (NULL == ret)
    122     GNUNET_break (0);
    123   return ret;
    124 }
    125 
    126 
    127 /**
    128  * Is @a s in the upper half of the group order? ECDSA accepts both
    129  * @a s and n-s; only the low variant is canonical.
    130  *
    131  * @param s scalar to check
    132  * @param[out] high set to true if @a s is the high variant
    133  * @return #GNUNET_OK on success
    134  */
    135 static enum GNUNET_GenericReturnValue
    136 s_is_high (gcry_mpi_t s,
    137            bool *high)
    138 {
    139   gcry_mpi_t n;
    140   gcry_mpi_t half;
    141 
    142   if (0 != gcry_mpi_scan (&n,
    143                           GCRYMPI_FMT_HEX,
    144                           P256_ORDER_HEX,
    145                           0,
    146                           NULL))
    147   {
    148     GNUNET_break (0);
    149     return GNUNET_SYSERR;
    150   }
    151   half = gcry_mpi_new (256);
    152   gcry_mpi_rshift (half,
    153                    n,
    154                    1);
    155   *high = (gcry_mpi_cmp (s,
    156                          half) > 0);
    157   gcry_mpi_release (half);
    158   gcry_mpi_release (n);
    159   return GNUNET_OK;
    160 }
    161 
    162 
    163 /**
    164  * Replace @a s by n-s, canonicalizing a signature to low-s.
    165  *
    166  * @param[in,out] s scalar to negate modulo the group order
    167  * @return #GNUNET_OK on success
    168  */
    169 static enum GNUNET_GenericReturnValue
    170 s_to_low (gcry_mpi_t s)
    171 {
    172   gcry_mpi_t n;
    173 
    174   if (0 != gcry_mpi_scan (&n,
    175                           GCRYMPI_FMT_HEX,
    176                           P256_ORDER_HEX,
    177                           0,
    178                           NULL))
    179   {
    180     GNUNET_break (0);
    181     return GNUNET_SYSERR;
    182   }
    183   gcry_mpi_sub (s,
    184                 n,
    185                 s);
    186   gcry_mpi_release (n);
    187   return GNUNET_OK;
    188 }
    189 
    190 
    191 enum GNUNET_GenericReturnValue
    192 TALER_ecdsa_p256_key_create (
    193   struct TALER_EcdsaP256PrivateKeyP *priv,
    194   struct TALER_EcdsaP256PublicKeyP *pub)
    195 {
    196   gcry_sexp_t params = NULL;
    197   gcry_sexp_t keypair = NULL;
    198   gcry_mpi_t d = NULL;
    199   gcry_mpi_t q = NULL;
    200   unsigned char qbuf[2 * P256_SCALAR_LEN + 1];
    201   enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
    202 
    203   if (0 != gcry_sexp_build (&params,
    204                             NULL,
    205                             "(genkey(ecc(curve \"" P256_CURVE "\")))"))
    206   {
    207     GNUNET_break (0);
    208     goto cleanup;
    209   }
    210   if (0 != gcry_pk_genkey (&keypair,
    211                            params))
    212   {
    213     GNUNET_break (0);
    214     goto cleanup;
    215   }
    216   d = sexp_extract_mpi (keypair,
    217                         "d");
    218   q = sexp_extract_mpi (keypair,
    219                         "q");
    220   if ( (NULL == d) ||
    221        (NULL == q) )
    222     goto cleanup;
    223   if (GNUNET_OK !=
    224       mpi_to_fixed (d,
    225                     priv->d,
    226                     sizeof (priv->d)))
    227     goto cleanup;
    228   /* libgcrypt hands us the uncompressed point 0x04|X|Y; we store the
    229      compressed form, whose prefix encodes the parity of Y */
    230   if (GNUNET_OK !=
    231       mpi_to_fixed (q,
    232                     qbuf,
    233                     sizeof (qbuf)))
    234     goto cleanup;
    235   if (0x04 != qbuf[0])
    236   {
    237     GNUNET_break (0);
    238     goto cleanup;
    239   }
    240   pub->q[0] = (qbuf[sizeof (qbuf) - 1] & 1)
    241               ? 0x03
    242               : 0x02;
    243   memcpy (&pub->q[1],
    244           &qbuf[1],
    245           P256_SCALAR_LEN);
    246   ret = GNUNET_OK;
    247 cleanup:
    248   if (NULL != d)
    249     gcry_mpi_release (d);
    250   if (NULL != q)
    251     gcry_mpi_release (q);
    252   if (NULL != keypair)
    253     gcry_sexp_release (keypair);
    254   if (NULL != params)
    255     gcry_sexp_release (params);
    256   return ret;
    257 }
    258 
    259 
    260 enum GNUNET_GenericReturnValue
    261 TALER_ecdsa_p256_sign (
    262   const struct TALER_EcdsaP256PrivateKeyP *priv,
    263   const struct GNUNET_ShortHashCode *hash,
    264   struct TALER_EcdsaP256SignatureP *sig)
    265 {
    266   gcry_sexp_t skey = NULL;
    267   gcry_sexp_t data = NULL;
    268   gcry_sexp_t sigs = NULL;
    269   gcry_mpi_t hm = NULL;
    270   gcry_mpi_t r = NULL;
    271   gcry_mpi_t s = NULL;
    272   enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
    273   bool high;
    274 
    275   if (0 != gcry_sexp_build (&skey,
    276                             NULL,
    277                             "(private-key(ecc(curve \"" P256_CURVE "\")"
    278                             "(d %b)))",
    279                             (int) sizeof (priv->d),
    280                             (const char *) priv->d))
    281   {
    282     GNUNET_break (0);
    283     goto cleanup;
    284   }
    285   if (0 != gcry_mpi_scan (&hm,
    286                           GCRYMPI_FMT_USG,
    287                           hash,
    288                           sizeof (*hash),
    289                           NULL))
    290   {
    291     GNUNET_break (0);
    292     goto cleanup;
    293   }
    294   if (0 != gcry_sexp_build (&data,
    295                             NULL,
    296                             "(data(flags raw)(value %m))",
    297                             hm))
    298   {
    299     GNUNET_break (0);
    300     goto cleanup;
    301   }
    302   if (0 != gcry_pk_sign (&sigs,
    303                          data,
    304                          skey))
    305   {
    306     GNUNET_break (0);
    307     goto cleanup;
    308   }
    309   r = sexp_extract_mpi (sigs,
    310                         "r");
    311   s = sexp_extract_mpi (sigs,
    312                         "s");
    313   if ( (NULL == r) ||
    314        (NULL == s) )
    315     goto cleanup;
    316   if (GNUNET_OK !=
    317       s_is_high (s,
    318                  &high))
    319     goto cleanup;
    320   if (high &&
    321       (GNUNET_OK !=
    322        s_to_low (s)) )
    323     goto cleanup;
    324   if ( (GNUNET_OK !=
    325         mpi_to_fixed (r,
    326                       sig->r_s,
    327                       P256_SCALAR_LEN)) ||
    328        (GNUNET_OK !=
    329         mpi_to_fixed (s,
    330                       &sig->r_s[P256_SCALAR_LEN],
    331                       P256_SCALAR_LEN)) )
    332     goto cleanup;
    333   ret = GNUNET_OK;
    334 cleanup:
    335   if (NULL != r)
    336     gcry_mpi_release (r);
    337   if (NULL != s)
    338     gcry_mpi_release (s);
    339   if (NULL != hm)
    340     gcry_mpi_release (hm);
    341   if (NULL != sigs)
    342     gcry_sexp_release (sigs);
    343   if (NULL != data)
    344     gcry_sexp_release (data);
    345   if (NULL != skey)
    346     gcry_sexp_release (skey);
    347   return ret;
    348 }
    349 
    350 
    351 enum GNUNET_GenericReturnValue
    352 TALER_ecdsa_p256_verify (
    353   const struct TALER_EcdsaP256PublicKeyP *pub,
    354   const struct GNUNET_ShortHashCode *hash,
    355   const struct TALER_EcdsaP256SignatureP *sig)
    356 {
    357   gcry_sexp_t pkey = NULL;
    358   gcry_sexp_t data = NULL;
    359   gcry_sexp_t sigs = NULL;
    360   gcry_mpi_t hm = NULL;
    361   gcry_mpi_t r = NULL;
    362   gcry_mpi_t s = NULL;
    363   enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
    364   bool high;
    365 
    366   if (0 != gcry_mpi_scan (&r,
    367                           GCRYMPI_FMT_USG,
    368                           sig->r_s,
    369                           P256_SCALAR_LEN,
    370                           NULL))
    371   {
    372     GNUNET_break_op (0);
    373     goto cleanup;
    374   }
    375   if (0 != gcry_mpi_scan (&s,
    376                           GCRYMPI_FMT_USG,
    377                           &sig->r_s[P256_SCALAR_LEN],
    378                           P256_SCALAR_LEN,
    379                           NULL))
    380   {
    381     GNUNET_break_op (0);
    382     goto cleanup;
    383   }
    384   /* only the canonical low-s form is accepted, so that a signature
    385      cannot be mauled into a second valid encoding */
    386   if (GNUNET_OK !=
    387       s_is_high (s,
    388                  &high))
    389     goto cleanup;
    390   if (high)
    391   {
    392     GNUNET_break_op (0);
    393     goto cleanup;
    394   }
    395   if (0 != gcry_mpi_scan (&hm,
    396                           GCRYMPI_FMT_USG,
    397                           hash,
    398                           sizeof (*hash),
    399                           NULL))
    400   {
    401     GNUNET_break (0);
    402     goto cleanup;
    403   }
    404   if (0 != gcry_sexp_build (&data,
    405                             NULL,
    406                             "(data(flags raw)(value %m))",
    407                             hm))
    408   {
    409     GNUNET_break (0);
    410     goto cleanup;
    411   }
    412   /* libgcrypt decompresses the point and rejects anything that is not
    413      on the curve, so an invalid public key fails here */
    414   if (0 != gcry_sexp_build (&pkey,
    415                             NULL,
    416                             "(public-key(ecc(curve \"" P256_CURVE "\")"
    417                             "(q %b)))",
    418                             (int) sizeof (pub->q),
    419                             (const char *) pub->q))
    420   {
    421     GNUNET_break_op (0);
    422     goto cleanup;
    423   }
    424   if (0 != gcry_sexp_build (&sigs,
    425                             NULL,
    426                             "(sig-val(ecdsa(r %m)(s %m)))",
    427                             r,
    428                             s))
    429   {
    430     GNUNET_break (0);
    431     goto cleanup;
    432   }
    433   ret = (0 == gcry_pk_verify (sigs,
    434                               data,
    435                               pkey))
    436         ? GNUNET_OK
    437         : GNUNET_SYSERR;
    438 cleanup:
    439   if (NULL != r)
    440     gcry_mpi_release (r);
    441   if (NULL != s)
    442     gcry_mpi_release (s);
    443   if (NULL != hm)
    444     gcry_mpi_release (hm);
    445   if (NULL != sigs)
    446     gcry_sexp_release (sigs);
    447   if (NULL != data)
    448     gcry_sexp_release (data);
    449   if (NULL != pkey)
    450     gcry_sexp_release (pkey);
    451   return ret;
    452 }
    453 
    454 
    455 /* end of crypto_ecdsa.c */