Jelajahi Sumber

Implementation of ecc_recover_key to obtain public key from hash+signature

Workaround for TFM missing sqrtmod_prime

Fix unused variable warnings with USE_TFM, make TomsFastMath a runtime check

Disable ecc_recover_key if no ecc_mul2add available

Wrap ecc_recover_key and its test in #ifdef LTC_ECC_SHAMIR

Fix unused variables when built without LTC_ECC_SHAMIR

Code review tweaks

Code review tweaks - remove sigformat, tidy up (de)allocation

Code review tweaks
Russ Williams 7 tahun lalu
induk
melakukan
76190521e3
4 mengubah file dengan 287 tambahan dan 30 penghapusan
  1. 22 0
      doc/crypt.tex
  2. 4 0
      src/headers/tomcrypt_pk.h
  3. 210 0
      src/pk/ecc/ecc_recover_key.c
  4. 51 30
      tests/ecc_test.c

+ 22 - 0
doc/crypt.tex

@@ -5585,6 +5585,28 @@ by the size of the hash, or the size of the key, whichever is smaller.  For exam
 P--192 key, you have in effect 96--bits of security. The library will not warn you if you make this mistake, so it
 is important to check yourself before using the signatures.
 
+\subsection{Public Key Recovery}
+\index{ecc\_recover\_key()}
+\begin{verbatim}
+int ecc_recover_key(const unsigned char *sig,
+                          unsigned long  siglen,
+                    const unsigned char *hash,
+                          unsigned long  hashlen,
+                                    int  recid,
+                                ecc_key *key);
+\end{verbatim}
+
+This function will recover (a) public key from the ECDSA signature in the array pointed to by \textit{sig} of length \textit{siglen} octets, the message digest
+pointed to by the array \textit{hash} of length \textit{hashlen}, and the recovery id \textit{recid}. It will store the recovered
+key into \textit{key} and return CRYPT_OK if recovery succeeds, or an error if recovery fails.
+This is for compatibility with the (v,r,s) signatures used in Bitcoin/Ethereum, where public keys are not explicitly
+shared, only the parity of the public key. For curves like secp256k1, recid will take values of 0 or 1, corresponding to
+the parity of the public key's y coordinate. For curves like secp112r2, with a cofactor of 4, values 0..7 are possible,
+with the low bit corresponding to the parity and the higher bits specifying the public key's x coordinate's  multiple
+of the curve's order.
+The function \textit{ecc\_recover\_key} implements signature format according to X9.62 ECDSA, and the output is compliant for GF(p) curves.
+
+
 \mysection{Shared Secret (ECDH)}
 To construct a Diffie-Hellman shared secret with a private and public ECC key, use the following function:
 \index{ecc\_shared\_secret()}

+ 4 - 0
src/headers/tomcrypt_pk.h

@@ -302,6 +302,10 @@ int  ecc_verify_hash(const unsigned char *sig,  unsigned long siglen,
                      const unsigned char *hash, unsigned long hashlen,
                      int *stat, const ecc_key *key);
 
+int  ecc_recover_key(const unsigned char *sig,  unsigned long siglen,
+                     const unsigned char *hash, unsigned long hashlen,
+                     int recid, ecc_key *key);
+
 #endif
 
 #ifdef LTC_MDSA

+ 210 - 0
src/pk/ecc/ecc_recover_key.c

@@ -0,0 +1,210 @@
+/* LibTomCrypt, modular cryptographic library -- Tom St Denis
+ *
+ * LibTomCrypt is a library that provides various cryptographic
+ * algorithms in a highly modular and flexible manner.
+ *
+ * The library is free for all purposes without any express
+ * guarantee it works.
+ */
+
+#include "tomcrypt_private.h"
+
+#ifdef LTC_MECC
+
+#ifdef LTC_ECC_SHAMIR
+
+/**
+  @file ecc_recover_key.c
+  ECC Crypto, Russ Williams
+*/
+
+static int _ecc_recover_key(const unsigned char *sig,  unsigned long siglen,
+                            const unsigned char *hash, unsigned long hashlen,
+                            int recid, ecc_key *key)
+{
+   ecc_point     *mG = NULL, *mQ = NULL, *mR = NULL;
+   void          *p, *m, *a, *b;
+   void          *r, *s, *v, *w, *t1, *t2, *u1, *u2, *v1, *v2, *e, *x, *y, *a_plus3;
+   void          *mu = NULL, *ma = NULL;
+   void          *mp = NULL;
+   int           err;
+   unsigned long pbits, pbytes, i, shift_right;
+   unsigned char ch, buf[MAXBLOCKSIZE];
+
+   LTC_ARGCHK(sig  != NULL);
+   LTC_ARGCHK(hash != NULL);
+   LTC_ARGCHK(key  != NULL);
+
+   /* BEWARE: requires sqrtmod_prime */
+   if (ltc_mp.sqrtmod_prime == NULL) {
+      return CRYPT_ERROR;
+   }
+
+   /* allocate ints */
+   if ((err = mp_init_multi(&r, &s, &v, &w, &t1, &t2, &u1, &u2, &v1, &v2, &e, &x, &y, &a_plus3, NULL)) != CRYPT_OK) {
+      return err;
+   }
+
+   p = key->dp.order;
+   m = key->dp.prime;
+   a = key->dp.A;
+   b = key->dp.B;
+   if ((err = mp_add_d(a, 3, a_plus3)) != CRYPT_OK) {
+      goto error;
+   }
+
+   /* allocate points */
+   mG = ltc_ecc_new_point();
+   mQ = ltc_ecc_new_point();
+   mR = ltc_ecc_new_point();
+   if (mR == NULL || mQ  == NULL || mG == NULL) {
+      err = CRYPT_MEM;
+      goto error;
+   }
+
+   /* Only ASN.1 format signatures supported for now */
+   if ((err = der_decode_sequence_multi_ex(sig, siglen, LTC_DER_SEQ_SEQUENCE | LTC_DER_SEQ_STRICT,
+                                     LTC_ASN1_INTEGER, 1UL, r,
+                                     LTC_ASN1_INTEGER, 1UL, s,
+                                     LTC_ASN1_EOL, 0UL, NULL)) != CRYPT_OK)                             { goto error; }
+
+   /* check for zero */
+   if (mp_cmp_d(r, 0) != LTC_MP_GT || mp_cmp_d(s, 0) != LTC_MP_GT ||
+       mp_cmp(r, p) != LTC_MP_LT || mp_cmp(s, p) != LTC_MP_LT) {
+      err = CRYPT_INVALID_PACKET;
+      goto error;
+   }
+
+   /* read hash - truncate if needed */
+   pbits = mp_count_bits(p);
+   pbytes = (pbits+7) >> 3;
+   if (pbits > hashlen*8) {
+      if ((err = mp_read_unsigned_bin(e, (unsigned char *)hash, hashlen)) != CRYPT_OK)                  { goto error; }
+   }
+   else if (pbits % 8 == 0) {
+      if ((err = mp_read_unsigned_bin(e, (unsigned char *)hash, pbytes)) != CRYPT_OK)                   { goto error; }
+   }
+   else {
+      shift_right = 8 - pbits % 8;
+      for (i=0, ch=0; i<pbytes; i++) {
+        buf[i] = ch;
+        ch = (hash[i] << (8-shift_right));
+        buf[i] = buf[i] ^ (hash[i] >> shift_right);
+      }
+      if ((err = mp_read_unsigned_bin(e, (unsigned char *)buf, pbytes)) != CRYPT_OK)                    { goto error; }
+   }
+
+   /* decompress point from r=(x mod p) - BEWARE: requires sqrtmod_prime */
+   /* x = r + p*(recid/2) */
+   if ((err = mp_set(x, recid/2)) != CRYPT_OK)                                                          { goto error; }
+   if ((err = mp_mulmod(p, x, m, x)) != CRYPT_OK)                                                       { goto error; }
+   if ((err = mp_add(x, r, x)) != CRYPT_OK)                                                             { goto error; }
+   /* compute x^3 */
+   if ((err = mp_sqr(x, t1)) != CRYPT_OK)                                                               { goto error; }
+   if ((err = mp_mulmod(t1, x, m, t1)) != CRYPT_OK)                                                     { goto error; }
+   /* compute x^3 + a*x */
+   if ((err = mp_mulmod(a, x, m, t2)) != CRYPT_OK)                                                      { goto error; }
+   if ((err = mp_add(t1, t2, t1)) != CRYPT_OK)                                                          { goto error; }
+   /* compute x^3 + a*x + b */
+   if ((err = mp_add(t1, b, t1)) != CRYPT_OK)                                                           { goto error; }
+   /* compute sqrt(x^3 + a*x + b) */
+   if ((err = mp_sqrtmod_prime(t1, m, t2)) != CRYPT_OK)                                                 { goto error; }
+
+   /* fill in mR */
+   if ((err = mp_copy(x, mR->x)) != CRYPT_OK)                                                           { goto error; }
+   if ((mp_isodd(t2) && (recid%2)) || (!mp_isodd(t2) && !(recid%2))) {
+      if ((err = mp_mod(t2, m, mR->y)) != CRYPT_OK)                                                     { goto error; }
+   }
+   else {
+      if ((err = mp_submod(m, t2, m, mR->y)) != CRYPT_OK)                                               { goto error; }
+   }
+   if ((err = mp_set(mR->z, 1)) != CRYPT_OK)                                                            { goto error; }
+
+   /*  w  = r^-1 mod n */
+   if ((err = mp_invmod(r, p, w)) != CRYPT_OK)                                                          { goto error; }
+   /* v1 = sw */
+   if ((err = mp_mulmod(s, w, p, v1)) != CRYPT_OK)                                                      { goto error; }
+   /* v2 = -ew */
+   if ((err = mp_mulmod(e, w, p, v2)) != CRYPT_OK)                                                      { goto error; }
+   if ((err = mp_submod(p, v2, p, v2)) != CRYPT_OK)                                                     { goto error; }
+
+   /*  w  = s^-1 mod n */
+   if ((err = mp_invmod(s, p, w)) != CRYPT_OK)                                                          { goto error; }
+   /* u1 = ew */
+   if ((err = mp_mulmod(e, w, p, u1)) != CRYPT_OK)                                                      { goto error; }
+   /* u2 = rw */
+   if ((err = mp_mulmod(r, w, p, u2)) != CRYPT_OK)                                                      { goto error; }
+
+   /* find mG */
+   if ((err = ltc_ecc_copy_point(&key->dp.base, mG)) != CRYPT_OK)                                       { goto error; }
+
+   /* find the montgomery mp */
+   if ((err = mp_montgomery_setup(m, &mp)) != CRYPT_OK)                                                 { goto error; }
+
+   /* for curves with a == -3 keep ma == NULL */
+   if (mp_cmp(a_plus3, m) != LTC_MP_EQ) {
+      if ((err = mp_init_multi(&mu, &ma, NULL)) != CRYPT_OK)                                            { goto error; }
+      if ((err = mp_montgomery_normalization(mu, m)) != CRYPT_OK)                                       { goto error; }
+      if ((err = mp_mulmod(a, mu, m, ma)) != CRYPT_OK)                                                  { goto error; }
+   }
+
+   /* recover mQ from mR */
+   /* compute v1*mR + v2*mG = mQ using Shamir's trick */
+   if ((err = ltc_mp.ecc_mul2add(mR, v1, mG, v2, mQ, ma, m)) != CRYPT_OK)                               { goto error; }
+
+   /* compute u1*mG + u2*mQ = mG using Shamir's trick */
+   if ((err = ltc_mp.ecc_mul2add(mG, u1, mQ, u2, mG, ma, m)) != CRYPT_OK)                               { goto error; }
+
+   /* v = X_x1 mod n */
+   if ((err = mp_mod(mG->x, p, v)) != CRYPT_OK)                                                         { goto error; }
+
+   /* does v == r */
+   if (mp_cmp(v, r) == LTC_MP_EQ) {
+      /* found public key which verifies signature */
+      if ((err = ltc_ecc_copy_point(mQ, &key->pubkey)) != CRYPT_OK)                                     { goto error; }
+      /* point on the curve + other checks */
+      if ((err = ltc_ecc_verify_key(key)) != CRYPT_OK)                                                  { goto error; }
+
+      key->type = PK_PUBLIC;
+
+      err = CRYPT_OK;
+   }
+   else {
+      /* not found - recid is wrong or we're unable to calculate public key for some other reason */
+      err = CRYPT_INVALID_ARG;
+   }
+
+error:
+   if (ma != NULL) mp_clear(ma);
+   if (mu != NULL) mp_clear(mu);
+   if (mp != NULL) mp_montgomery_free(mp);
+   if (mR != NULL) ltc_ecc_del_point(mR);
+   if (mQ != NULL) ltc_ecc_del_point(mQ);
+   if (mG != NULL) ltc_ecc_del_point(mG);
+   mp_clear_multi(r, s, v, w, t1, t2, u1, u2, v1, v2, e, x, y, a_plus3, NULL);
+   return err;
+}
+
+/**
+   Recover ECC public key from signature and hash
+   @param sig         The signature to verify
+   @param siglen      The length of the signature (octets)
+   @param hash        The hash (message digest) that was signed
+   @param hashlen     The length of the hash (octets)
+   @param recid       0 or 1 to select parity ("v")
+   @param key         The recovered public ECC key
+   @return CRYPT_OK if successful (even if the signature is not valid)
+*/
+int ecc_recover_key(const unsigned char *sig,  unsigned long siglen,
+                    const unsigned char *hash, unsigned long hashlen,
+                    int recid, ecc_key *key)
+{
+   return _ecc_recover_key(sig, siglen, hash, hashlen, recid, key);
+}
+
+#endif
+#endif
+
+/* ref:         $Format:%D$ */
+/* git commit:  $Format:%H$ */
+/* commit time: $Format:%ai$ */

+ 51 - 30
tests/ecc_test.c

@@ -350,6 +350,25 @@ static int _ecc_old_api(void)
    return CRYPT_OK;
 }
 
+static int _ecc_key_cmp(const int should_type, const ecc_key *should, const ecc_key *is)
+{
+   if (should_type != is->type)                               return CRYPT_ERROR;
+   if (should_type == PK_PRIVATE) {
+      if (mp_cmp(should->k, is->k) != LTC_MP_EQ)              return CRYPT_ERROR;
+   }
+   if (mp_cmp(should->dp.prime,  is->dp.prime)  != LTC_MP_EQ) return CRYPT_ERROR;
+   if (mp_cmp(should->dp.A,      is->dp.A)      != LTC_MP_EQ) return CRYPT_ERROR;
+   if (mp_cmp(should->dp.B,      is->dp.B)      != LTC_MP_EQ) return CRYPT_ERROR;
+   if (mp_cmp(should->dp.order,  is->dp.order)  != LTC_MP_EQ) return CRYPT_ERROR;
+   if (mp_cmp(should->dp.base.x, is->dp.base.x) != LTC_MP_EQ) return CRYPT_ERROR;
+   if (mp_cmp(should->dp.base.y, is->dp.base.y) != LTC_MP_EQ) return CRYPT_ERROR;
+   if (mp_cmp(should->pubkey.x,  is->pubkey.x)  != LTC_MP_EQ) return CRYPT_ERROR;
+   if (mp_cmp(should->pubkey.y,  is->pubkey.y)  != LTC_MP_EQ) return CRYPT_ERROR;
+   if (should->dp.size != is->dp.size)                        return CRYPT_ERROR;
+   if (should->dp.cofactor != is->dp.cofactor)                return CRYPT_ERROR;
+   return CRYPT_OK;
+}
+
 static int _ecc_new_api(void)
 {
    const char* names[] = {
@@ -474,17 +493,17 @@ static int _ecc_new_api(void)
       DO(ecc_set_curve(dp, &privkey));
       DO(ecc_set_key(buf, len, PK_PRIVATE, &privkey));
 
-#ifndef USE_TFM
-      /* XXX-FIXME: TFM does not support sqrtmod_prime */
-      /* export compressed public key */
-      len = sizeof(buf);
-      DO(ecc_get_key(buf, &len, PK_PUBLIC|PK_COMPRESSED, &privkey));
-      if (len != 1 + (unsigned)ecc_get_size(&privkey)) return CRYPT_FAIL_TESTVECTOR;
-      /* load exported public+compressed key */
-      DO(ecc_set_curve(dp, &pubkey));
-      DO(ecc_set_key(buf, len, PK_PUBLIC, &pubkey));
-      ecc_free(&pubkey);
-#endif
+      if (strcmp(ltc_mp.name, "TomsFastMath") != 0) {
+         /* XXX-FIXME: TFM does not support sqrtmod_prime */
+         /* export compressed public key */
+         len = sizeof(buf);
+         DO(ecc_get_key(buf, &len, PK_PUBLIC|PK_COMPRESSED, &privkey));
+         if (len != 1 + (unsigned)ecc_get_size(&privkey)) return CRYPT_FAIL_TESTVECTOR;
+         /* load exported public+compressed key */
+         DO(ecc_set_curve(dp, &pubkey));
+         DO(ecc_set_key(buf, len, PK_PUBLIC, &pubkey));
+         ecc_free(&pubkey);
+      }
 
       /* export long public key */
       len = sizeof(buf);
@@ -501,6 +520,27 @@ static int _ecc_new_api(void)
       DO(ecc_verify_hash(buf, len, data16, 16, &stat, &pubkey));
       if (stat != 1) return CRYPT_FAIL_TESTVECTOR;
 
+#ifdef LTC_ECC_SHAMIR
+      /* XXX-FIXME: ecc_recover_key currently requires mul2add */
+      if (strcmp(ltc_mp.name, "TomsFastMath") != 0) {
+         /* XXX-FIXME: TFM does not support sqrtmod_prime */
+         int found = 0;
+         ecc_key reckey;
+         /* test recovery */
+         len = sizeof(buf);
+         DO(ecc_sign_hash(data16, 16, buf, &len, &yarrow_prng, find_prng ("yarrow"), &privkey));
+         DO(ecc_set_curve(dp, &reckey));
+         for (j = 0; j < 2*(1+(int)privkey.dp.cofactor); j++) {
+            stat = ecc_recover_key(buf, len, data16, 16, j, &reckey);
+            if (stat != CRYPT_OK) continue; /* last two will almost always fail, only possible if x<(prime mod order) */
+            stat = _ecc_key_cmp(PK_PUBLIC, &pubkey, &reckey);
+            if (stat == CRYPT_OK) found++;
+         }
+         if (found != 1) return CRYPT_FAIL_TESTVECTOR; /* unique match */
+         ecc_free(&reckey);
+      }
+#endif
+
       /* test encryption */
       len = sizeof(buf);
       DO(ecc_encrypt_key(data16, 16, buf, &len, &yarrow_prng, find_prng("yarrow"), find_hash("sha256"), &pubkey));
@@ -517,25 +557,6 @@ static int _ecc_new_api(void)
    return CRYPT_OK;
 }
 
-static int _ecc_key_cmp(const int should_type, const ecc_key *should, const ecc_key *is)
-{
-   if (should_type != is->type)                               return CRYPT_ERROR;
-   if (should_type == PK_PRIVATE) {
-      if (mp_cmp(should->k, is->k) != LTC_MP_EQ)              return CRYPT_ERROR;
-   }
-   if (mp_cmp(should->dp.prime,  is->dp.prime)  != LTC_MP_EQ) return CRYPT_ERROR;
-   if (mp_cmp(should->dp.A,      is->dp.A)      != LTC_MP_EQ) return CRYPT_ERROR;
-   if (mp_cmp(should->dp.B,      is->dp.B)      != LTC_MP_EQ) return CRYPT_ERROR;
-   if (mp_cmp(should->dp.order,  is->dp.order)  != LTC_MP_EQ) return CRYPT_ERROR;
-   if (mp_cmp(should->dp.base.x, is->dp.base.x) != LTC_MP_EQ) return CRYPT_ERROR;
-   if (mp_cmp(should->dp.base.y, is->dp.base.y) != LTC_MP_EQ) return CRYPT_ERROR;
-   if (mp_cmp(should->pubkey.x,  is->pubkey.x)  != LTC_MP_EQ) return CRYPT_ERROR;
-   if (mp_cmp(should->pubkey.y,  is->pubkey.y)  != LTC_MP_EQ) return CRYPT_ERROR;
-   if (should->dp.size != is->dp.size)                        return CRYPT_ERROR;
-   if (should->dp.cofactor != is->dp.cofactor)                return CRYPT_ERROR;
-   return CRYPT_OK;
-}
-
 static int _ecc_import_export(void) {
    const ltc_ecc_curve *cu;
    ecc_key key, pri, pub;