Browse Source

Make ECC signature format explicit

Optionally return recovery ID from ecc_sign_hash()

Update documentation

Update tests for ECC recovery

Fix (v,r,s) signature format, regenerate recovery test

Fix over-freeing of private key

Code review fixes to docs

Rename LTC_ECCSIG_BLOCKCHAIN to LTC_ECCSIG_ETH to reflect original definition

Rename to LTC_ECCSIG_ETH27 to make clear it's using the Ethereum +27 convention

Code review changes - calculate recovery ID only if needed, type safety on signature format enum

Use enum for sigformat in docs, and add explanatory note for recid<0

Range checks on v, check RFC7518 signatures' length based on size of key. Fix for when order>prime.

Limit LET_ECCSIG_ETH27 to secp256k1 curve only
Russ Williams 7 years ago
parent
commit
88d9b6db26
6 changed files with 450 additions and 141 deletions
  1. 62 6
      doc/crypt.tex
  2. 28 13
      src/headers/tomcrypt_pk.h
  3. 62 23
      src/pk/ecc/ecc_recover_key.c
  4. 62 46
      src/pk/ecc/ecc_sign_hash.c
  5. 46 51
      src/pk/ecc/ecc_verify_hash.c
  6. 190 2
      tests/ecc_test.c

+ 62 - 6
doc/crypt.tex

@@ -5549,6 +5549,22 @@ int ecc_sign_hash_rfc7518(const unsigned char *in,
 This function creates the same ECDSA signature as \textit{ecc\_sign\_hash} only the output format is different.
 The format follows \url{https://tools.ietf.org/html/rfc7518#section-3.4}, sometimes it is also called plain signature.
 
+\index{ecc\_sign\_hash_ex()}
+\begin{verbatim}
+int ecc_sign_hash_ex(const unsigned char *in,
+                           unsigned long  inlen,
+                           unsigned char *out,
+                           unsigned long *outlen,
+                              prng_state *prng,
+                                     int  wprng,
+                      ecc_signature_type  sigformat,
+                                     int *recid,
+                                 ecc_key *key);
+\end{verbatim}
+
+This function is an extended version of the ECDSA signature in \textit{ecc\_sign\_hash}, but with a choice of output formats
+and an optional output of the recovery ID for use with \textit{ecc\_recover\_key}.
+
 \subsection{Signature Verification}
 \index{ecc\_verify\_hash()}
 \begin{verbatim}
@@ -5579,6 +5595,19 @@ int ecc_verify_hash_rfc7518(const unsigned char *sig,
 This function validate the ECDSA signature as \textit{ecc\_verify\_hash} only the signature input format
 follows \url{https://tools.ietf.org/html/rfc7518#section-3.4}.
 
+\index{ecc\_verify\_hash_ex()}
+\begin{verbatim}
+int ecc_verify_hash_ex(const unsigned char *sig,
+                             unsigned long  siglen,
+                       const unsigned char *hash,
+                             unsigned long  hashlen,
+                        ecc_signature_type  sigformat,
+                                       int *stat,
+                                   ecc_key *key);
+\end{verbatim}
+
+This function validates an ECDSA signature as \textit{ecc\_verify\_hash} but with a choice of signature formats.
+
 {\bf BEWARE:} With ECC if you try to sign a hash that is bigger than your ECC key you can run into problems. The math
 will still work, and in effect the signature will still work.  With ECC keys the strength of the signature is limited
 by the size of the hash, or the size of the key, whichever is smaller.  For example, if you sign with SHA256 and a
@@ -5593,19 +5622,46 @@ int ecc_recover_key(const unsigned char *sig,
                     const unsigned char *hash,
                           unsigned long  hashlen,
                                     int  recid,
+                     ecc_signature_type  sigformat,
                                 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
+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 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.
+If the signature format contains the recovery id (currently only \textit{LTC\_ECCSIG\_ETH27}), \textit{recid} can be -1
+which signals that the recovery id from the signature blob should be used. This means an application does not need to
+extract the recovery id from such a signature in order to use this function.
+The function \textit{ecc\_recover\_key} implements multiple signature formats, and the output is compliant for GF(p) curves.
+
+\subsection{Signature Formats}
+The following signature formats are suported:
+
+\begin{figure}[hpbt]
+\index{Signature Formats}
+\begin{small}
+\begin{center}
+\begin{tabular}{|l|l|}
+\hline \textbf{sigformat} & \textbf{description} \\
+\hline LTC\_ECCSIG\_ANSIX962 & ASN.1 encoded, ANSI X9.62 \\
+\hline LTC\_ECCSIG\_RFC7518 & raw R, S values as defined in RFC7518 \\
+\hline LTC\_ECCSIG\_ETH27 & raw R, S, V values (V has 27 added) \\
+\hline
+\end{tabular}
+\end{center}
+\end{small}
+\caption{Signature Formats}
+\label{fig:sigformat}
+\end{figure}
 
+The \textit{LTC\_ECCSIG\_ETH27} format is based on the Ethereum Yellow Paper, see \url{https://github.com/ethereum/yellowpaper}
+(Appendix F). However, convention allows the use of v=0,1 as equivalent to v=27,28 and both are accepted by
+\textit{ecc\_recover\_key}.
 
 \mysection{Shared Secret (ECDH)}
 To construct a Diffie-Hellman shared secret with a private and public ECC key, use the following function:

+ 28 - 13
src/headers/tomcrypt_pk.h

@@ -244,6 +244,16 @@ typedef struct {
     void *k;
 } ecc_key;
 
+/** Formats of ECC signatures */
+typedef enum ecc_signature_type_ {
+   /* ASN.1 encoded, ANSI X9.62 */
+   LTC_ECCSIG_ANSIX962   = 0x0,
+   /* raw R, S values */
+   LTC_ECCSIG_RFC7518    = 0x1,
+   /* raw R, S, V (+27) values */
+   LTC_ECCSIG_ETH27      = 0x2
+} ecc_signature_type;
+
 /** the ECC params provided */
 extern const ltc_ecc_curve ltc_ecc_curves[];
 
@@ -286,25 +296,30 @@ int  ecc_decrypt_key(const unsigned char *in,  unsigned long  inlen,
                            unsigned char *out, unsigned long *outlen,
                            const ecc_key *key);
 
-int ecc_sign_hash_rfc7518(const unsigned char *in,  unsigned long inlen,
-                                unsigned char *out, unsigned long *outlen,
-                                prng_state *prng, int wprng, const ecc_key *key);
+#define ecc_sign_hash_rfc7518(in_, inlen_, out_, outlen_, prng_, wprng_, key_) \
+   ecc_sign_hash_ex(in_, inlen_, out_, outlen_, prng_, wprng_, LTC_ECCSIG_RFC7518, NULL, key_)
 
-int  ecc_sign_hash(const unsigned char *in,  unsigned long inlen,
-                         unsigned char *out, unsigned long *outlen,
-                         prng_state *prng, int wprng, const ecc_key *key);
+#define ecc_sign_hash(in_, inlen_, out_, outlen_, prng_, wprng_, key_) \
+   ecc_sign_hash_ex(in_, inlen_, out_, outlen_, prng_, wprng_, LTC_ECCSIG_ANSIX962, NULL, key_)
 
-int ecc_verify_hash_rfc7518(const unsigned char *sig,  unsigned long siglen,
-                            const unsigned char *hash, unsigned long hashlen,
-                            int *stat, const ecc_key *key);
+#define ecc_verify_hash_rfc7518(sig_, siglen_, hash_, hashlen_, stat_, key_) \
+   ecc_verify_hash_ex(sig_, siglen_, hash_, hashlen_, LTC_ECCSIG_RFC7518, stat_, key_)
 
-int  ecc_verify_hash(const unsigned char *sig,  unsigned long siglen,
-                     const unsigned char *hash, unsigned long hashlen,
-                     int *stat, const ecc_key *key);
+#define ecc_verify_hash(sig_, siglen_, hash_, hashlen_, stat_, key_) \
+   ecc_verify_hash_ex(sig_, siglen_, hash_, hashlen_, LTC_ECCSIG_ANSIX962, stat_, key_)
+
+int  ecc_sign_hash_ex(const unsigned char *in,  unsigned long inlen,
+                            unsigned char *out, unsigned long *outlen,
+                            prng_state *prng, int wprng, ecc_signature_type sigformat,
+                            int *recid, const ecc_key *key);
+
+int  ecc_verify_hash_ex(const unsigned char *sig,  unsigned long siglen,
+                        const unsigned char *hash, unsigned long hashlen,
+                        ecc_signature_type sigformat, 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);
+                     int recid, ecc_signature_type sigformat, ecc_key *key);
 
 #endif
 

+ 62 - 23
src/pk/ecc/ecc_recover_key.c

@@ -18,9 +18,20 @@
   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)
+/**
+   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       The recovery ID ("v"), can be -1 if signature contains it
+   @param sigformat   The format of the signature (ecc_signature_type)
+   @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_signature_type sigformat, ecc_key *key)
 {
    ecc_point     *mG = NULL, *mQ = NULL, *mR = NULL;
    void          *p, *m, *a, *b;
@@ -62,11 +73,56 @@ static int _ecc_recover_key(const unsigned char *sig,  unsigned long siglen,
       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,
+   if (sigformat == LTC_ECCSIG_ANSIX962) {
+      /* ANSI X9.62 format - ASN.1 encoded SEQUENCE{ INTEGER(r), INTEGER(s) }  */
+      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; }
+   }
+   else if (sigformat == LTC_ECCSIG_RFC7518) {
+      /* RFC7518 format - raw (r,s) */
+      i = mp_unsigned_bin_size(key->dp.order);
+      if (siglen != (2*i)) {
+         err = CRYPT_INVALID_PACKET;
+         goto error;
+      }
+      if ((err = mp_read_unsigned_bin(r, (unsigned char *)sig,   i)) != CRYPT_OK)                       { goto error; }
+      if ((err = mp_read_unsigned_bin(s, (unsigned char *)sig+i, i)) != CRYPT_OK)                       { goto error; }
+   }
+   else if (sigformat == LTC_ECCSIG_ETH27) {
+      /* Ethereum (v,r,s) format */
+      if (key->dp.oidlen != 5   || key->dp.oid[0] != 1 || key->dp.oid[1] != 3 ||
+          key->dp.oid[2] != 132 || key->dp.oid[3] != 0 || key->dp.oid[4] != 10) {
+         /* Only valid for secp256k1 - OID 1.3.132.0.10 */
+         err = CRYPT_ERROR; goto error;
+      }
+      if (siglen != 65) { /* Only secp256k1 curves use this format, so must be 65 bytes long */
+         err = CRYPT_INVALID_PACKET;
+         goto error;
+      }
+      i = (unsigned long)sig[64];
+      if ((i>=27) && (i<31)) i -= 27; /* Ethereum adds 27 to recovery ID */
+      if (recid >= 0 && ((unsigned long)recid != i)) {
+         /* Recovery ID specified, but doesn't match signature */
+         err = CRYPT_INVALID_PACKET;
+         goto error;
+      }
+      recid = i;
+      if ((err = mp_read_unsigned_bin(r, (unsigned char *)sig,  32)) != CRYPT_OK)                       { goto error; }
+      if ((err = mp_read_unsigned_bin(s, (unsigned char *)sig+32, 32)) != CRYPT_OK)                     { goto error; }
+   }
+   else {
+      /* Unknown signature format */
+      err = CRYPT_ERROR;
+      goto error;
+   }
+
+   if (recid < 0 || (unsigned long)recid >= 2*(key->dp.cofactor+1)) {
+      /* Recovery ID is out of range, reject it */
+      err = CRYPT_INVALID_ARG;
+      goto error;
+   }
 
    /* check for zero */
    if (mp_cmp_d(r, 0) != LTC_MP_GT || mp_cmp_d(s, 0) != LTC_MP_GT ||
@@ -181,27 +237,10 @@ error:
    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);
+   mp_clear_multi(a_plus3, y, x, e, v2, v1, u2, u1, t2, t1, w, v, s, r, 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
 

+ 62 - 46
src/pk/ecc/ecc_sign_hash.c

@@ -16,12 +16,27 @@
   ECC Crypto, Tom St Denis
 */
 
-static int _ecc_sign_hash(const unsigned char *in,  unsigned long inlen,
-                                unsigned char *out, unsigned long *outlen,
-                                prng_state *prng, int wprng, const ecc_key *key, int sigformat)
+/**
+  Sign a message digest
+  @param in        The message digest to sign
+  @param inlen     The length of the digest
+  @param out       [out] The destination for the signature
+  @param outlen    [in/out] The max size and resulting size of the signature
+  @param prng      An active PRNG state
+  @param wprng     The index of the PRNG you wish to use
+  @param sigformat The format of the signature to generate (ecc_signature_type)
+  @param recid     [out] The recovery ID for this signature (optional)
+  @param key       A private ECC key
+  @return CRYPT_OK if successful
+*/
+int ecc_sign_hash_ex(const unsigned char *in,  unsigned long inlen,
+                     unsigned char *out, unsigned long *outlen,
+                     prng_state *prng, int wprng, ecc_signature_type sigformat,
+                     int *recid, const ecc_key *key)
 {
    ecc_key       pubkey;
    void          *r, *s, *e, *p, *b;
+   int           v = 0;
    int           err, max_iterations = LTC_PK_MAX_RETRIES;
    unsigned long pbits, pbytes, i, shift_right;
    unsigned char ch, buf[MAXBLOCKSIZE];
@@ -69,6 +84,18 @@ static int _ecc_sign_hash(const unsigned char *in,  unsigned long inlen,
       /* find r = x1 mod n */
       if ((err = mp_mod(pubkey.pubkey.x, p, r)) != CRYPT_OK)               { goto error; }
 
+      if (recid || sigformat==LTC_ECCSIG_ETH27) {
+         /* find recovery ID (if needed) */
+         v = 0;
+         if (mp_copy(pubkey.pubkey.x, s) != CRYPT_OK)                      { goto error; }
+         while (mp_cmp_d(s, 0) == LTC_MP_GT && mp_cmp(s, p) != LTC_MP_LT) {
+            /* Compute x1 div n... this will almost never be reached for curves with order 1 */
+            v += 2;
+            if ((err = mp_sub(s, p, s)) != CRYPT_OK)                       { goto error; }
+         }
+         if (mp_isodd(pubkey.pubkey.y)) v += 1;
+      }
+
       if (mp_iszero(r) == LTC_MP_YES) {
          ecc_free(&pubkey);
       } else {
@@ -92,8 +119,17 @@ static int _ecc_sign_hash(const unsigned char *in,  unsigned long inlen,
       goto errnokey;
    }
 
-   if (sigformat == 1) {
-      /* RFC7518 format */
+   if (recid) *recid = v;
+
+   if (sigformat == LTC_ECCSIG_ANSIX962) {
+      /* store as ASN.1 SEQUENCE { r, s -- integer } */
+      err = der_encode_sequence_multi(out, outlen,
+                               LTC_ASN1_INTEGER, 1UL, r,
+                               LTC_ASN1_INTEGER, 1UL, s,
+                               LTC_ASN1_EOL, 0UL, NULL);
+   }
+   else if (sigformat == LTC_ECCSIG_RFC7518) {
+      /* RFC7518 format - raw (r,s) */
       if (*outlen < 2*pbytes) { err = CRYPT_MEM; goto errnokey; }
       zeromem(out, 2*pbytes);
       i = mp_unsigned_bin_size(r);
@@ -103,13 +139,29 @@ static int _ecc_sign_hash(const unsigned char *in,  unsigned long inlen,
       *outlen = 2*pbytes;
       err = CRYPT_OK;
    }
+   else if (sigformat == LTC_ECCSIG_ETH27) {
+      /* Ethereum (v,r,s) format */
+      if (key->dp.oidlen != 5   || key->dp.oid[0] != 1 || key->dp.oid[1] != 3 ||
+          key->dp.oid[2] != 132 || key->dp.oid[3] != 0 || key->dp.oid[4] != 10) {
+         /* Only valid for secp256k1 - OID 1.3.132.0.10 */
+         err = CRYPT_ERROR; goto errnokey;
+      }
+      if (*outlen < 65) { err = CRYPT_MEM; goto errnokey; }
+      zeromem(out, 65);
+      i = mp_unsigned_bin_size(r);
+      if ((err = mp_to_unsigned_bin(r, out + 32 - i)) != CRYPT_OK) { goto errnokey; }
+      i = mp_unsigned_bin_size(s);
+      if ((err = mp_to_unsigned_bin(s, out + 64 - i)) != CRYPT_OK) { goto errnokey; }
+      out[64] = (unsigned char)(v + 27); /* Recovery ID is 27/28 for Ethereum */
+      *outlen = 65;
+      err = CRYPT_OK;
+   }
    else {
-      /* store as ASN.1 SEQUENCE { r, s -- integer } */
-      err = der_encode_sequence_multi(out, outlen,
-                               LTC_ASN1_INTEGER, 1UL, r,
-                               LTC_ASN1_INTEGER, 1UL, s,
-                               LTC_ASN1_EOL, 0UL, NULL);
+      /* Unknown signature format */
+      err = CRYPT_ERROR;
+      goto error;
    }
+
    goto errnokey;
 error:
    ecc_free(&pubkey);
@@ -118,42 +170,6 @@ errnokey:
    return err;
 }
 
-/**
-  Sign a message digest
-  @param in        The message digest to sign
-  @param inlen     The length of the digest
-  @param out       [out] The destination for the signature
-  @param outlen    [in/out] The max size and resulting size of the signature
-  @param prng      An active PRNG state
-  @param wprng     The index of the PRNG you wish to use
-  @param key       A private ECC key
-  @return CRYPT_OK if successful
-*/
-int ecc_sign_hash(const unsigned char *in,  unsigned long inlen,
-                        unsigned char *out, unsigned long *outlen,
-                        prng_state *prng, int wprng, const ecc_key *key)
-{
-   return _ecc_sign_hash(in, inlen, out, outlen, prng, wprng, key, 0);
-}
-
-/**
-  Sign a message digest in RFC7518 format
-  @param in        The message digest to sign
-  @param inlen     The length of the digest
-  @param out       [out] The destination for the signature
-  @param outlen    [in/out] The max size and resulting size of the signature
-  @param prng      An active PRNG state
-  @param wprng     The index of the PRNG you wish to use
-  @param key       A private ECC key
-  @return CRYPT_OK if successful
-*/
-int ecc_sign_hash_rfc7518(const unsigned char *in,  unsigned long inlen,
-                                unsigned char *out, unsigned long *outlen,
-                                prng_state *prng, int wprng, const ecc_key *key)
-{
-   return _ecc_sign_hash(in, inlen, out, outlen, prng, wprng, key, 1);
-}
-
 #endif
 
 /* ref:         $Format:%D$ */

+ 46 - 51
src/pk/ecc/ecc_verify_hash.c

@@ -16,12 +16,24 @@
   ECC Crypto, Tom St Denis
 */
 
-static 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 sigformat)
+/**
+   Verify an ECC signature in RFC7518 format
+   @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 sigformat   The format of the signature (ecc_signature_type)
+   @param stat        Result of signature, 1==valid, 0==invalid
+   @param key         The corresponding public ECC key
+   @return CRYPT_OK if successful (even if the signature is not valid)
+*/
+int ecc_verify_hash_ex(const unsigned char *sig,  unsigned long siglen,
+                       const unsigned char *hash, unsigned long hashlen,
+                       ecc_signature_type sigformat, int *stat, const ecc_key *key)
 {
-   ecc_point    *mG = NULL, *mQ = NULL;
-   void          *r, *s, *v, *w, *u1, *u2, *e, *p, *m, *a, *a_plus3 = NULL, *mu = NULL, *ma = NULL;
+   ecc_point     *mG = NULL, *mQ = NULL;
+   void          *r, *s, *v, *w, *u1, *u2, *e, *p, *m, *a, *a_plus3;
+   void          *mu = NULL, *ma = NULL;
    void          *mp = NULL;
    int           err;
    unsigned long pbits, pbytes, i, shift_right;
@@ -55,22 +67,41 @@ static int _ecc_verify_hash(const unsigned char *sig,  unsigned long siglen,
       goto error;
    }
 
-   if (sigformat == 1) {
-      /* RFC7518 format */
-      if ((siglen % 2) == 1) {
+   if (sigformat == LTC_ECCSIG_ANSIX962) {
+      /* ANSI X9.62 format - ASN.1 encoded SEQUENCE{ INTEGER(r), INTEGER(s) }  */
+      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; }
+   }
+   else if (sigformat == LTC_ECCSIG_RFC7518) {
+      /* RFC7518 format - raw (r,s) */
+      i = mp_unsigned_bin_size(key->dp.order);
+      if (siglen != (2*i)) {
          err = CRYPT_INVALID_PACKET;
          goto error;
       }
-      i = siglen / 2;
       if ((err = mp_read_unsigned_bin(r, (unsigned char *)sig,   i)) != CRYPT_OK)                       { goto error; }
       if ((err = mp_read_unsigned_bin(s, (unsigned char *)sig+i, i)) != CRYPT_OK)                       { goto error; }
    }
+   else if (sigformat == LTC_ECCSIG_ETH27) {
+      /* Ethereum (v,r,s) format */
+      if (key->dp.oidlen != 5   || key->dp.oid[0] != 1 || key->dp.oid[1] != 3 ||
+          key->dp.oid[2] != 132 || key->dp.oid[3] != 0 || key->dp.oid[4] != 10) {
+         /* Only valid for secp256k1 - OID 1.3.132.0.10 */
+         err = CRYPT_ERROR; goto error;
+      }
+      if (siglen != 65) { /* Only secp256k1 curves use this format, so must be 65 bytes long */
+         err = CRYPT_INVALID_PACKET;
+         goto error;
+      }
+      if ((err = mp_read_unsigned_bin(r, (unsigned char *)sig,  32)) != CRYPT_OK)                       { goto error; }
+      if ((err = mp_read_unsigned_bin(s, (unsigned char *)sig+32, 32)) != CRYPT_OK)                     { goto error; }
+   }
    else {
-      /* ASN.1 format */
-      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; }
+      /* Unknown signature format */
+      err = CRYPT_ERROR;
+      goto error;
    }
 
    /* check for zero */
@@ -153,46 +184,10 @@ error:
    if (mu != NULL) mp_clear(mu);
    if (ma != NULL) mp_clear(ma);
    mp_clear_multi(r, s, v, w, u1, u2, e, a_plus3, NULL);
-   if (mp != NULL) {
-      mp_montgomery_free(mp);
-   }
+   if (mp != NULL) mp_montgomery_free(mp);
    return err;
 }
 
-/**
-   Verify an ECC signature
-   @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 stat        Result of signature, 1==valid, 0==invalid
-   @param key         The corresponding public ECC key
-   @return CRYPT_OK if successful (even if the signature is not valid)
-*/
-int ecc_verify_hash(const unsigned char *sig,  unsigned long siglen,
-                    const unsigned char *hash, unsigned long hashlen,
-                    int *stat, const ecc_key *key)
-{
-   return _ecc_verify_hash(sig, siglen, hash, hashlen, stat, key, 0);
-}
-
-/**
-   Verify an ECC signature in RFC7518 format
-   @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 stat        Result of signature, 1==valid, 0==invalid
-   @param key         The corresponding public ECC key
-   @return CRYPT_OK if successful (even if the signature is not valid)
-*/
-int ecc_verify_hash_rfc7518(const unsigned char *sig,  unsigned long siglen,
-                            const unsigned char *hash, unsigned long hashlen,
-                            int *stat, const ecc_key *key)
-{
-   return _ecc_verify_hash(sig, siglen, hash, hashlen, stat, key, 1);
-}
-
 #endif
 
 /* ref:         $Format:%D$ */

+ 190 - 2
tests/ecc_test.c

@@ -521,7 +521,6 @@ static int _ecc_new_api(void)
       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;
@@ -531,7 +530,7 @@ static int _ecc_new_api(void)
          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);
+            stat = ecc_recover_key(buf, len, data16, 16, j, LTC_ECCSIG_ANSIX962, &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++;
@@ -917,6 +916,194 @@ static int _ecc_import_export(void) {
    return CRYPT_OK;
 }
 
+#ifdef LTC_ECC_SHAMIR
+static int _ecc_test_recovery(void)
+{
+   const char* names[] = {
+#ifdef LTC_ECC_SECP112R1
+      "SECP112R1", "ECC-112",
+      "secp112r1",              /* name is case-insensitive */
+      "S E C-P-1_1_2r1",        /* should pass fuzzy matching */
+#endif
+#ifdef LTC_ECC_SECP112R2
+      "SECP112R2",
+#endif
+#ifdef LTC_ECC_SECP128R1
+      "SECP128R1", "ECC-128",
+#endif
+#ifdef LTC_ECC_SECP128R2
+      "SECP128R2",
+#endif
+#ifdef LTC_ECC_SECP160R1
+      "SECP160R1", "ECC-160",
+#endif
+#ifdef LTC_ECC_SECP160R2
+      "SECP160R2",
+#endif
+#ifdef LTC_ECC_SECP160K1
+      "SECP160K1",
+#endif
+#ifdef LTC_ECC_BRAINPOOLP160R1
+      "BRAINPOOLP160R1",
+#endif
+#ifdef LTC_ECC_SECP192R1
+      "SECP192R1", "NISTP192", "PRIME192V1", "ECC-192", "P-192",
+#endif
+#ifdef LTC_ECC_PRIME192V2
+      "PRIME192V2",
+#endif
+#ifdef LTC_ECC_PRIME192V3
+      "PRIME192V3",
+#endif
+#ifdef LTC_ECC_SECP192K1
+      "SECP192K1",
+#endif
+#ifdef LTC_ECC_BRAINPOOLP192R1
+      "BRAINPOOLP192R1",
+#endif
+#ifdef LTC_ECC_SECP224R1
+      "SECP224R1", "NISTP224", "ECC-224", "P-224",
+#endif
+#ifdef LTC_ECC_SECP224K1
+      "SECP224K1",
+#endif
+#ifdef LTC_ECC_BRAINPOOLP224R1
+      "BRAINPOOLP224R1",
+#endif
+#ifdef LTC_ECC_PRIME239V1
+      "PRIME239V1",
+#endif
+#ifdef LTC_ECC_PRIME239V2
+      "PRIME239V2",
+#endif
+#ifdef LTC_ECC_PRIME239V3
+      "PRIME239V3",
+#endif
+#ifdef LTC_ECC_SECP256R1
+      "SECP256R1", "NISTP256", "PRIME256V1", "ECC-256", "P-256",
+#endif
+#ifdef LTC_ECC_SECP256K1
+      "SECP256K1",
+#endif
+#ifdef LTC_ECC_BRAINPOOLP256R1
+      "BRAINPOOLP256R1",
+#endif
+#ifdef LTC_ECC_BRAINPOOLP320R1
+      "BRAINPOOLP320R1",
+#endif
+#ifdef LTC_ECC_SECP384R1
+      "SECP384R1", "NISTP384", "ECC-384", "P-384",
+#endif
+#ifdef LTC_ECC_BRAINPOOLP384R1
+      "BRAINPOOLP384R1",
+#endif
+#ifdef LTC_ECC_BRAINPOOLP512R1
+      "BRAINPOOLP512R1",
+#endif
+#ifdef LTC_ECC_SECP521R1
+      "SECP521R1", "NISTP521", "ECC-521", "P-521",
+#endif
+   };
+   int i, recid, stat;
+   const ltc_ecc_curve* dp;
+   ecc_key key, privkey, pubkey, reckey;
+   unsigned char buf[1000];
+   unsigned long len;
+   unsigned char data16[16] = { 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1 };
+   unsigned char eth_hash[] = { /* Keccak-256 hash of "Hello World" */
+      0x59, 0x2f, 0xa7, 0x43, 0x88, 0x9f, 0xc7, 0xf9, 0x2a, 0xc2, 0xa3, 0x7b, 0xb1, 0xf5, 0xba, 0x1d,
+      0xaf, 0x2a, 0x5c, 0x84, 0x74, 0x1c, 0xa0, 0xe0, 0x06, 0x1d, 0x24, 0x3a, 0x2e, 0x67, 0x07, 0xba
+   };
+   unsigned char eth_pubkey[] = { /* Public part of randomly-generated key pair */
+      0x04,
+      0xc6, 0x99, 0x5f, 0xdc, 0xf4, 0xf2, 0xda, 0x6e, 0x79, 0xe0, 0x47, 0x12, 0xd3, 0xbe, 0x22, 0xe7,
+      0x65, 0xc6, 0xa3, 0x32, 0x89, 0x1b, 0x34, 0xba, 0xc1, 0xb7, 0x01, 0x83, 0xed, 0xdd, 0xf1, 0xcc,
+      0xbf, 0x20, 0xdd, 0xcd, 0x05, 0x4e, 0x49, 0xc8, 0xcb, 0x66, 0x6c, 0xb7, 0x71, 0x2f, 0x7e, 0xc1,
+      0xd6, 0x1a, 0x4a, 0x42, 0x3d, 0xe5, 0xc2, 0x8d, 0x74, 0x03, 0x81, 0xe7, 0xea, 0xc5, 0x3c, 0x10
+   };
+   unsigned char eth_sig[] = { /* Signature of eth_hash to be verified against eth_pubkey */
+      0xbd, 0x6d, 0xbb, 0xbe, 0x2d, 0xe7, 0x1d, 0x00, 0xae, 0x18, 0x57, 0x12, 0x1d, 0x63, 0xa5, 0x1b,
+      0x0b, 0x42, 0x71, 0xa2, 0x80, 0x49, 0xe0, 0x5c, 0xfa, 0xc8, 0x1a, 0x0d, 0x8a, 0x88, 0x67, 0x56,
+      0xf6, 0x67, 0x1b, 0x41, 0x46, 0x09, 0x4e, 0xd0, 0x44, 0x25, 0x18, 0xfd, 0xf4, 0xcd, 0x62, 0xa3,
+      0xb7, 0x3c, 0x97, 0x55, 0xfa, 0x69, 0xf8, 0xef, 0xe9, 0xcf, 0x12, 0xaf, 0x48, 0x25, 0xe3, 0xe0,
+      0x1b
+   };
+
+   /* XXX-FIXME: TFM does not support sqrtmod_prime */
+   if (strcmp(ltc_mp.name, "TomsFastMath") == 0) return CRYPT_NOP;
+
+#ifdef LTC_ECC_SECP256K1
+   DO(ecc_find_curve("SECP256K1", &dp));
+
+   DO(ecc_set_curve(dp, &pubkey));
+   DO(ecc_set_key(eth_pubkey, sizeof(eth_pubkey), PK_PUBLIC, &pubkey));
+
+   DO(ecc_set_curve(dp, &reckey));
+   stat = ecc_recover_key(eth_sig, sizeof(eth_sig)-1, eth_hash, sizeof(eth_hash), 0, LTC_ECCSIG_RFC7518, &reckey);
+   if (stat != CRYPT_OK) return CRYPT_FAIL_TESTVECTOR;
+   DO(_ecc_key_cmp(PK_PUBLIC, &pubkey, &reckey));
+   ecc_free(&reckey);
+
+   DO(ecc_set_curve(dp, &reckey));
+   stat = ecc_recover_key(eth_sig, sizeof(eth_sig), eth_hash, sizeof(eth_hash), -1, LTC_ECCSIG_ETH27, &reckey);
+   if (stat != CRYPT_OK) return CRYPT_FAIL_TESTVECTOR;
+   DO(_ecc_key_cmp(PK_PUBLIC, &pubkey, &reckey));
+   ecc_free(&reckey);
+
+   ecc_free(&pubkey);
+#endif
+
+   for (i = 0; i < (int)(sizeof(names)/sizeof(names[0])); i++) {
+      DO(ecc_find_curve(names[i], &dp));
+
+      /* generate new key */
+      DO(ecc_set_curve(dp, &key));
+      DO(ecc_generate_key(&yarrow_prng, find_prng ("yarrow"), &key));
+
+      /* export private key */
+      len = sizeof(buf);
+      DO(ecc_get_key(buf, &len, PK_PRIVATE, &key));
+      ecc_free(&key);
+
+      /* load exported private key */
+      DO(ecc_set_curve(dp, &privkey));
+      DO(ecc_set_key(buf, len, PK_PRIVATE, &privkey));
+
+      /* export long public key */
+      len = sizeof(buf);
+      DO(ecc_get_key(buf, &len, PK_PUBLIC, &privkey));
+      if (len != 1 + 2 * (unsigned)ecc_get_size(&privkey)) return CRYPT_FAIL_TESTVECTOR;
+
+      /* load exported public key */
+      DO(ecc_set_curve(dp, &pubkey));
+      DO(ecc_set_key(buf, len, PK_PUBLIC, &pubkey));
+
+      /* test signature */
+      len = sizeof(buf);
+      recid = 0;
+      DO(ecc_sign_hash_ex(data16, 16, buf, &len, &yarrow_prng, find_prng ("yarrow"), LTC_ECCSIG_RFC7518, &recid, &privkey));
+
+      /* test verification */
+      stat = 0;
+      DO(ecc_verify_hash_ex(buf, len, data16, 16, LTC_ECCSIG_RFC7518, &stat, &pubkey));
+      if (stat != 1) return CRYPT_FAIL_TESTVECTOR;
+
+      /* test recovery */
+      DO(ecc_set_curve(dp, &reckey));
+      stat = ecc_recover_key(buf, len, data16, 16, recid, LTC_ECCSIG_RFC7518, &reckey);
+      if (stat != CRYPT_OK) return CRYPT_FAIL_TESTVECTOR;
+      DO(_ecc_key_cmp(PK_PUBLIC, &pubkey, &reckey));
+
+      /* cleanup */
+      ecc_free(&reckey);
+      ecc_free(&privkey);
+      ecc_free(&pubkey);
+   }
+
+  return CRYPT_OK;
+}
+#endif
+
 int ecc_tests(void)
 {
    if (ltc_mp.name == NULL) return CRYPT_NOP;
@@ -928,6 +1115,7 @@ int ecc_tests(void)
    DO(_ecc_issue108());
 #ifdef LTC_ECC_SHAMIR
    DO(_ecc_test_shamir());
+   DO(_ecc_test_recovery());
 #endif
    return CRYPT_OK;
 }