Skip to content

Commit 88d9b6d

Browse files
committed
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
1 parent 57781c7 commit 88d9b6d

File tree

6 files changed

+450
-141
lines changed

6 files changed

+450
-141
lines changed

doc/crypt.tex

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5549,6 +5549,22 @@ \subsection{Signature Generation}
55495549
This function creates the same ECDSA signature as \textit{ecc\_sign\_hash} only the output format is different.
55505550
The format follows \url{https://tools.ietf.org/html/rfc7518#section-3.4}, sometimes it is also called plain signature.
55515551

5552+
\index{ecc\_sign\_hash_ex()}
5553+
\begin{verbatim}
5554+
int ecc_sign_hash_ex(const unsigned char *in,
5555+
unsigned long inlen,
5556+
unsigned char *out,
5557+
unsigned long *outlen,
5558+
prng_state *prng,
5559+
int wprng,
5560+
ecc_signature_type sigformat,
5561+
int *recid,
5562+
ecc_key *key);
5563+
\end{verbatim}
5564+
5565+
This function is an extended version of the ECDSA signature in \textit{ecc\_sign\_hash}, but with a choice of output formats
5566+
and an optional output of the recovery ID for use with \textit{ecc\_recover\_key}.
5567+
55525568
\subsection{Signature Verification}
55535569
\index{ecc\_verify\_hash()}
55545570
\begin{verbatim}
@@ -5579,6 +5595,19 @@ \subsection{Signature Verification}
55795595
This function validate the ECDSA signature as \textit{ecc\_verify\_hash} only the signature input format
55805596
follows \url{https://tools.ietf.org/html/rfc7518#section-3.4}.
55815597

5598+
\index{ecc\_verify\_hash_ex()}
5599+
\begin{verbatim}
5600+
int ecc_verify_hash_ex(const unsigned char *sig,
5601+
unsigned long siglen,
5602+
const unsigned char *hash,
5603+
unsigned long hashlen,
5604+
ecc_signature_type sigformat,
5605+
int *stat,
5606+
ecc_key *key);
5607+
\end{verbatim}
5608+
5609+
This function validates an ECDSA signature as \textit{ecc\_verify\_hash} but with a choice of signature formats.
5610+
55825611
{\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
55835612
will still work, and in effect the signature will still work. With ECC keys the strength of the signature is limited
55845613
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 @@ \subsection{Public Key Recovery}
55935622
const unsigned char *hash,
55945623
unsigned long hashlen,
55955624
int recid,
5625+
ecc_signature_type sigformat,
55965626
ecc_key *key);
55975627
\end{verbatim}
55985628

55995629
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
56005630
pointed to by the array \textit{hash} of length \textit{hashlen}, and the recovery id \textit{recid}. It will store the recovered
5601-
key into \textit{key} and return CRYPT_OK if recovery succeeds, or an error if recovery fails.
5602-
This is for compatibility with the (v,r,s) signatures used in Bitcoin/Ethereum, where public keys are not explicitly
5603-
shared, only the parity of the public key. For curves like secp256k1, recid will take values of 0 or 1, corresponding to
5604-
the parity of the public key's y coordinate. For curves like secp112r2, with a cofactor of 4, values 0..7 are possible,
5605-
with the low bit corresponding to the parity and the higher bits specifying the public key's x coordinate's multiple
5631+
key into \textit{key} and return CRYPT\_OK if recovery succeeds, or an error if recovery fails.
5632+
This is for compatibility with the (v,r,s) signatures used in Ethereum, where public keys are not explicitly shared,
5633+
only the parity of the public key. For curves like secp256k1, recid will take values of 0 or 1, corresponding to the
5634+
parity of the public key's y coordinate. For curves like secp112r2, with a cofactor of 4, values 0..7 are possible,
5635+
with the low bit corresponding to the parity and the higher bits specifying the public key's x coordinate's multiple
56065636
of the curve's order.
5607-
The function \textit{ecc\_recover\_key} implements signature format according to X9.62 ECDSA, and the output is compliant for GF(p) curves.
5637+
If the signature format contains the recovery id (currently only \textit{LTC\_ECCSIG\_ETH27}), \textit{recid} can be -1
5638+
which signals that the recovery id from the signature blob should be used. This means an application does not need to
5639+
extract the recovery id from such a signature in order to use this function.
5640+
The function \textit{ecc\_recover\_key} implements multiple signature formats, and the output is compliant for GF(p) curves.
5641+
5642+
\subsection{Signature Formats}
5643+
The following signature formats are suported:
5644+
5645+
\begin{figure}[hpbt]
5646+
\index{Signature Formats}
5647+
\begin{small}
5648+
\begin{center}
5649+
\begin{tabular}{|l|l|}
5650+
\hline \textbf{sigformat} & \textbf{description} \\
5651+
\hline LTC\_ECCSIG\_ANSIX962 & ASN.1 encoded, ANSI X9.62 \\
5652+
\hline LTC\_ECCSIG\_RFC7518 & raw R, S values as defined in RFC7518 \\
5653+
\hline LTC\_ECCSIG\_ETH27 & raw R, S, V values (V has 27 added) \\
5654+
\hline
5655+
\end{tabular}
5656+
\end{center}
5657+
\end{small}
5658+
\caption{Signature Formats}
5659+
\label{fig:sigformat}
5660+
\end{figure}
56085661

5662+
The \textit{LTC\_ECCSIG\_ETH27} format is based on the Ethereum Yellow Paper, see \url{https://github.com/ethereum/yellowpaper}
5663+
(Appendix F). However, convention allows the use of v=0,1 as equivalent to v=27,28 and both are accepted by
5664+
\textit{ecc\_recover\_key}.
56095665

56105666
\mysection{Shared Secret (ECDH)}
56115667
To construct a Diffie-Hellman shared secret with a private and public ECC key, use the following function:

src/headers/tomcrypt_pk.h

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,16 @@ typedef struct {
244244
void *k;
245245
} ecc_key;
246246

247+
/** Formats of ECC signatures */
248+
typedef enum ecc_signature_type_ {
249+
/* ASN.1 encoded, ANSI X9.62 */
250+
LTC_ECCSIG_ANSIX962 = 0x0,
251+
/* raw R, S values */
252+
LTC_ECCSIG_RFC7518 = 0x1,
253+
/* raw R, S, V (+27) values */
254+
LTC_ECCSIG_ETH27 = 0x2
255+
} ecc_signature_type;
256+
247257
/** the ECC params provided */
248258
extern const ltc_ecc_curve ltc_ecc_curves[];
249259

@@ -286,25 +296,30 @@ int ecc_decrypt_key(const unsigned char *in, unsigned long inlen,
286296
unsigned char *out, unsigned long *outlen,
287297
const ecc_key *key);
288298

289-
int ecc_sign_hash_rfc7518(const unsigned char *in, unsigned long inlen,
290-
unsigned char *out, unsigned long *outlen,
291-
prng_state *prng, int wprng, const ecc_key *key);
299+
#define ecc_sign_hash_rfc7518(in_, inlen_, out_, outlen_, prng_, wprng_, key_) \
300+
ecc_sign_hash_ex(in_, inlen_, out_, outlen_, prng_, wprng_, LTC_ECCSIG_RFC7518, NULL, key_)
292301

293-
int ecc_sign_hash(const unsigned char *in, unsigned long inlen,
294-
unsigned char *out, unsigned long *outlen,
295-
prng_state *prng, int wprng, const ecc_key *key);
302+
#define ecc_sign_hash(in_, inlen_, out_, outlen_, prng_, wprng_, key_) \
303+
ecc_sign_hash_ex(in_, inlen_, out_, outlen_, prng_, wprng_, LTC_ECCSIG_ANSIX962, NULL, key_)
296304

297-
int ecc_verify_hash_rfc7518(const unsigned char *sig, unsigned long siglen,
298-
const unsigned char *hash, unsigned long hashlen,
299-
int *stat, const ecc_key *key);
305+
#define ecc_verify_hash_rfc7518(sig_, siglen_, hash_, hashlen_, stat_, key_) \
306+
ecc_verify_hash_ex(sig_, siglen_, hash_, hashlen_, LTC_ECCSIG_RFC7518, stat_, key_)
300307

301-
int ecc_verify_hash(const unsigned char *sig, unsigned long siglen,
302-
const unsigned char *hash, unsigned long hashlen,
303-
int *stat, const ecc_key *key);
308+
#define ecc_verify_hash(sig_, siglen_, hash_, hashlen_, stat_, key_) \
309+
ecc_verify_hash_ex(sig_, siglen_, hash_, hashlen_, LTC_ECCSIG_ANSIX962, stat_, key_)
310+
311+
int ecc_sign_hash_ex(const unsigned char *in, unsigned long inlen,
312+
unsigned char *out, unsigned long *outlen,
313+
prng_state *prng, int wprng, ecc_signature_type sigformat,
314+
int *recid, const ecc_key *key);
315+
316+
int ecc_verify_hash_ex(const unsigned char *sig, unsigned long siglen,
317+
const unsigned char *hash, unsigned long hashlen,
318+
ecc_signature_type sigformat, int *stat, const ecc_key *key);
304319

305320
int ecc_recover_key(const unsigned char *sig, unsigned long siglen,
306321
const unsigned char *hash, unsigned long hashlen,
307-
int recid, ecc_key *key);
322+
int recid, ecc_signature_type sigformat, ecc_key *key);
308323

309324
#endif
310325

src/pk/ecc/ecc_recover_key.c

Lines changed: 62 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,20 @@
1818
ECC Crypto, Russ Williams
1919
*/
2020

21-
static int _ecc_recover_key(const unsigned char *sig, unsigned long siglen,
22-
const unsigned char *hash, unsigned long hashlen,
23-
int recid, ecc_key *key)
21+
/**
22+
Recover ECC public key from signature and hash
23+
@param sig The signature to verify
24+
@param siglen The length of the signature (octets)
25+
@param hash The hash (message digest) that was signed
26+
@param hashlen The length of the hash (octets)
27+
@param recid The recovery ID ("v"), can be -1 if signature contains it
28+
@param sigformat The format of the signature (ecc_signature_type)
29+
@param key The recovered public ECC key
30+
@return CRYPT_OK if successful (even if the signature is not valid)
31+
*/
32+
int ecc_recover_key(const unsigned char *sig, unsigned long siglen,
33+
const unsigned char *hash, unsigned long hashlen,
34+
int recid, ecc_signature_type sigformat, ecc_key *key)
2435
{
2536
ecc_point *mG = NULL, *mQ = NULL, *mR = NULL;
2637
void *p, *m, *a, *b;
@@ -62,11 +73,56 @@ static int _ecc_recover_key(const unsigned char *sig, unsigned long siglen,
6273
goto error;
6374
}
6475

65-
/* Only ASN.1 format signatures supported for now */
66-
if ((err = der_decode_sequence_multi_ex(sig, siglen, LTC_DER_SEQ_SEQUENCE | LTC_DER_SEQ_STRICT,
76+
if (sigformat == LTC_ECCSIG_ANSIX962) {
77+
/* ANSI X9.62 format - ASN.1 encoded SEQUENCE{ INTEGER(r), INTEGER(s) } */
78+
if ((err = der_decode_sequence_multi_ex(sig, siglen, LTC_DER_SEQ_SEQUENCE | LTC_DER_SEQ_STRICT,
6779
LTC_ASN1_INTEGER, 1UL, r,
6880
LTC_ASN1_INTEGER, 1UL, s,
6981
LTC_ASN1_EOL, 0UL, NULL)) != CRYPT_OK) { goto error; }
82+
}
83+
else if (sigformat == LTC_ECCSIG_RFC7518) {
84+
/* RFC7518 format - raw (r,s) */
85+
i = mp_unsigned_bin_size(key->dp.order);
86+
if (siglen != (2*i)) {
87+
err = CRYPT_INVALID_PACKET;
88+
goto error;
89+
}
90+
if ((err = mp_read_unsigned_bin(r, (unsigned char *)sig, i)) != CRYPT_OK) { goto error; }
91+
if ((err = mp_read_unsigned_bin(s, (unsigned char *)sig+i, i)) != CRYPT_OK) { goto error; }
92+
}
93+
else if (sigformat == LTC_ECCSIG_ETH27) {
94+
/* Ethereum (v,r,s) format */
95+
if (key->dp.oidlen != 5 || key->dp.oid[0] != 1 || key->dp.oid[1] != 3 ||
96+
key->dp.oid[2] != 132 || key->dp.oid[3] != 0 || key->dp.oid[4] != 10) {
97+
/* Only valid for secp256k1 - OID 1.3.132.0.10 */
98+
err = CRYPT_ERROR; goto error;
99+
}
100+
if (siglen != 65) { /* Only secp256k1 curves use this format, so must be 65 bytes long */
101+
err = CRYPT_INVALID_PACKET;
102+
goto error;
103+
}
104+
i = (unsigned long)sig[64];
105+
if ((i>=27) && (i<31)) i -= 27; /* Ethereum adds 27 to recovery ID */
106+
if (recid >= 0 && ((unsigned long)recid != i)) {
107+
/* Recovery ID specified, but doesn't match signature */
108+
err = CRYPT_INVALID_PACKET;
109+
goto error;
110+
}
111+
recid = i;
112+
if ((err = mp_read_unsigned_bin(r, (unsigned char *)sig, 32)) != CRYPT_OK) { goto error; }
113+
if ((err = mp_read_unsigned_bin(s, (unsigned char *)sig+32, 32)) != CRYPT_OK) { goto error; }
114+
}
115+
else {
116+
/* Unknown signature format */
117+
err = CRYPT_ERROR;
118+
goto error;
119+
}
120+
121+
if (recid < 0 || (unsigned long)recid >= 2*(key->dp.cofactor+1)) {
122+
/* Recovery ID is out of range, reject it */
123+
err = CRYPT_INVALID_ARG;
124+
goto error;
125+
}
70126

71127
/* check for zero */
72128
if (mp_cmp_d(r, 0) != LTC_MP_GT || mp_cmp_d(s, 0) != LTC_MP_GT ||
@@ -181,27 +237,10 @@ static int _ecc_recover_key(const unsigned char *sig, unsigned long siglen,
181237
if (mR != NULL) ltc_ecc_del_point(mR);
182238
if (mQ != NULL) ltc_ecc_del_point(mQ);
183239
if (mG != NULL) ltc_ecc_del_point(mG);
184-
mp_clear_multi(r, s, v, w, t1, t2, u1, u2, v1, v2, e, x, y, a_plus3, NULL);
240+
mp_clear_multi(a_plus3, y, x, e, v2, v1, u2, u1, t2, t1, w, v, s, r, NULL);
185241
return err;
186242
}
187243

188-
/**
189-
Recover ECC public key from signature and hash
190-
@param sig The signature to verify
191-
@param siglen The length of the signature (octets)
192-
@param hash The hash (message digest) that was signed
193-
@param hashlen The length of the hash (octets)
194-
@param recid 0 or 1 to select parity ("v")
195-
@param key The recovered public ECC key
196-
@return CRYPT_OK if successful (even if the signature is not valid)
197-
*/
198-
int ecc_recover_key(const unsigned char *sig, unsigned long siglen,
199-
const unsigned char *hash, unsigned long hashlen,
200-
int recid, ecc_key *key)
201-
{
202-
return _ecc_recover_key(sig, siglen, hash, hashlen, recid, key);
203-
}
204-
205244
#endif
206245
#endif
207246

0 commit comments

Comments
 (0)