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 (¶ms, 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 */