Skip to content

Commit 7619052

Browse files
committed
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
1 parent b44155f commit 7619052

File tree

4 files changed

+287
-30
lines changed

4 files changed

+287
-30
lines changed

doc/crypt.tex

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5585,6 +5585,28 @@ \subsection{Signature Verification}
55855585
P--192 key, you have in effect 96--bits of security. The library will not warn you if you make this mistake, so it
55865586
is important to check yourself before using the signatures.
55875587

5588+
\subsection{Public Key Recovery}
5589+
\index{ecc\_recover\_key()}
5590+
\begin{verbatim}
5591+
int ecc_recover_key(const unsigned char *sig,
5592+
unsigned long siglen,
5593+
const unsigned char *hash,
5594+
unsigned long hashlen,
5595+
int recid,
5596+
ecc_key *key);
5597+
\end{verbatim}
5598+
5599+
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
5600+
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
5606+
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.
5608+
5609+
55885610
\mysection{Shared Secret (ECDH)}
55895611
To construct a Diffie-Hellman shared secret with a private and public ECC key, use the following function:
55905612
\index{ecc\_shared\_secret()}

src/headers/tomcrypt_pk.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,10 @@ int ecc_verify_hash(const unsigned char *sig, unsigned long siglen,
302302
const unsigned char *hash, unsigned long hashlen,
303303
int *stat, const ecc_key *key);
304304

305+
int ecc_recover_key(const unsigned char *sig, unsigned long siglen,
306+
const unsigned char *hash, unsigned long hashlen,
307+
int recid, ecc_key *key);
308+
305309
#endif
306310

307311
#ifdef LTC_MDSA

src/pk/ecc/ecc_recover_key.c

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/* LibTomCrypt, modular cryptographic library -- Tom St Denis
2+
*
3+
* LibTomCrypt is a library that provides various cryptographic
4+
* algorithms in a highly modular and flexible manner.
5+
*
6+
* The library is free for all purposes without any express
7+
* guarantee it works.
8+
*/
9+
10+
#include "tomcrypt_private.h"
11+
12+
#ifdef LTC_MECC
13+
14+
#ifdef LTC_ECC_SHAMIR
15+
16+
/**
17+
@file ecc_recover_key.c
18+
ECC Crypto, Russ Williams
19+
*/
20+
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)
24+
{
25+
ecc_point *mG = NULL, *mQ = NULL, *mR = NULL;
26+
void *p, *m, *a, *b;
27+
void *r, *s, *v, *w, *t1, *t2, *u1, *u2, *v1, *v2, *e, *x, *y, *a_plus3;
28+
void *mu = NULL, *ma = NULL;
29+
void *mp = NULL;
30+
int err;
31+
unsigned long pbits, pbytes, i, shift_right;
32+
unsigned char ch, buf[MAXBLOCKSIZE];
33+
34+
LTC_ARGCHK(sig != NULL);
35+
LTC_ARGCHK(hash != NULL);
36+
LTC_ARGCHK(key != NULL);
37+
38+
/* BEWARE: requires sqrtmod_prime */
39+
if (ltc_mp.sqrtmod_prime == NULL) {
40+
return CRYPT_ERROR;
41+
}
42+
43+
/* allocate ints */
44+
if ((err = mp_init_multi(&r, &s, &v, &w, &t1, &t2, &u1, &u2, &v1, &v2, &e, &x, &y, &a_plus3, NULL)) != CRYPT_OK) {
45+
return err;
46+
}
47+
48+
p = key->dp.order;
49+
m = key->dp.prime;
50+
a = key->dp.A;
51+
b = key->dp.B;
52+
if ((err = mp_add_d(a, 3, a_plus3)) != CRYPT_OK) {
53+
goto error;
54+
}
55+
56+
/* allocate points */
57+
mG = ltc_ecc_new_point();
58+
mQ = ltc_ecc_new_point();
59+
mR = ltc_ecc_new_point();
60+
if (mR == NULL || mQ == NULL || mG == NULL) {
61+
err = CRYPT_MEM;
62+
goto error;
63+
}
64+
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,
67+
LTC_ASN1_INTEGER, 1UL, r,
68+
LTC_ASN1_INTEGER, 1UL, s,
69+
LTC_ASN1_EOL, 0UL, NULL)) != CRYPT_OK) { goto error; }
70+
71+
/* check for zero */
72+
if (mp_cmp_d(r, 0) != LTC_MP_GT || mp_cmp_d(s, 0) != LTC_MP_GT ||
73+
mp_cmp(r, p) != LTC_MP_LT || mp_cmp(s, p) != LTC_MP_LT) {
74+
err = CRYPT_INVALID_PACKET;
75+
goto error;
76+
}
77+
78+
/* read hash - truncate if needed */
79+
pbits = mp_count_bits(p);
80+
pbytes = (pbits+7) >> 3;
81+
if (pbits > hashlen*8) {
82+
if ((err = mp_read_unsigned_bin(e, (unsigned char *)hash, hashlen)) != CRYPT_OK) { goto error; }
83+
}
84+
else if (pbits % 8 == 0) {
85+
if ((err = mp_read_unsigned_bin(e, (unsigned char *)hash, pbytes)) != CRYPT_OK) { goto error; }
86+
}
87+
else {
88+
shift_right = 8 - pbits % 8;
89+
for (i=0, ch=0; i<pbytes; i++) {
90+
buf[i] = ch;
91+
ch = (hash[i] << (8-shift_right));
92+
buf[i] = buf[i] ^ (hash[i] >> shift_right);
93+
}
94+
if ((err = mp_read_unsigned_bin(e, (unsigned char *)buf, pbytes)) != CRYPT_OK) { goto error; }
95+
}
96+
97+
/* decompress point from r=(x mod p) - BEWARE: requires sqrtmod_prime */
98+
/* x = r + p*(recid/2) */
99+
if ((err = mp_set(x, recid/2)) != CRYPT_OK) { goto error; }
100+
if ((err = mp_mulmod(p, x, m, x)) != CRYPT_OK) { goto error; }
101+
if ((err = mp_add(x, r, x)) != CRYPT_OK) { goto error; }
102+
/* compute x^3 */
103+
if ((err = mp_sqr(x, t1)) != CRYPT_OK) { goto error; }
104+
if ((err = mp_mulmod(t1, x, m, t1)) != CRYPT_OK) { goto error; }
105+
/* compute x^3 + a*x */
106+
if ((err = mp_mulmod(a, x, m, t2)) != CRYPT_OK) { goto error; }
107+
if ((err = mp_add(t1, t2, t1)) != CRYPT_OK) { goto error; }
108+
/* compute x^3 + a*x + b */
109+
if ((err = mp_add(t1, b, t1)) != CRYPT_OK) { goto error; }
110+
/* compute sqrt(x^3 + a*x + b) */
111+
if ((err = mp_sqrtmod_prime(t1, m, t2)) != CRYPT_OK) { goto error; }
112+
113+
/* fill in mR */
114+
if ((err = mp_copy(x, mR->x)) != CRYPT_OK) { goto error; }
115+
if ((mp_isodd(t2) && (recid%2)) || (!mp_isodd(t2) && !(recid%2))) {
116+
if ((err = mp_mod(t2, m, mR->y)) != CRYPT_OK) { goto error; }
117+
}
118+
else {
119+
if ((err = mp_submod(m, t2, m, mR->y)) != CRYPT_OK) { goto error; }
120+
}
121+
if ((err = mp_set(mR->z, 1)) != CRYPT_OK) { goto error; }
122+
123+
/* w = r^-1 mod n */
124+
if ((err = mp_invmod(r, p, w)) != CRYPT_OK) { goto error; }
125+
/* v1 = sw */
126+
if ((err = mp_mulmod(s, w, p, v1)) != CRYPT_OK) { goto error; }
127+
/* v2 = -ew */
128+
if ((err = mp_mulmod(e, w, p, v2)) != CRYPT_OK) { goto error; }
129+
if ((err = mp_submod(p, v2, p, v2)) != CRYPT_OK) { goto error; }
130+
131+
/* w = s^-1 mod n */
132+
if ((err = mp_invmod(s, p, w)) != CRYPT_OK) { goto error; }
133+
/* u1 = ew */
134+
if ((err = mp_mulmod(e, w, p, u1)) != CRYPT_OK) { goto error; }
135+
/* u2 = rw */
136+
if ((err = mp_mulmod(r, w, p, u2)) != CRYPT_OK) { goto error; }
137+
138+
/* find mG */
139+
if ((err = ltc_ecc_copy_point(&key->dp.base, mG)) != CRYPT_OK) { goto error; }
140+
141+
/* find the montgomery mp */
142+
if ((err = mp_montgomery_setup(m, &mp)) != CRYPT_OK) { goto error; }
143+
144+
/* for curves with a == -3 keep ma == NULL */
145+
if (mp_cmp(a_plus3, m) != LTC_MP_EQ) {
146+
if ((err = mp_init_multi(&mu, &ma, NULL)) != CRYPT_OK) { goto error; }
147+
if ((err = mp_montgomery_normalization(mu, m)) != CRYPT_OK) { goto error; }
148+
if ((err = mp_mulmod(a, mu, m, ma)) != CRYPT_OK) { goto error; }
149+
}
150+
151+
/* recover mQ from mR */
152+
/* compute v1*mR + v2*mG = mQ using Shamir's trick */
153+
if ((err = ltc_mp.ecc_mul2add(mR, v1, mG, v2, mQ, ma, m)) != CRYPT_OK) { goto error; }
154+
155+
/* compute u1*mG + u2*mQ = mG using Shamir's trick */
156+
if ((err = ltc_mp.ecc_mul2add(mG, u1, mQ, u2, mG, ma, m)) != CRYPT_OK) { goto error; }
157+
158+
/* v = X_x1 mod n */
159+
if ((err = mp_mod(mG->x, p, v)) != CRYPT_OK) { goto error; }
160+
161+
/* does v == r */
162+
if (mp_cmp(v, r) == LTC_MP_EQ) {
163+
/* found public key which verifies signature */
164+
if ((err = ltc_ecc_copy_point(mQ, &key->pubkey)) != CRYPT_OK) { goto error; }
165+
/* point on the curve + other checks */
166+
if ((err = ltc_ecc_verify_key(key)) != CRYPT_OK) { goto error; }
167+
168+
key->type = PK_PUBLIC;
169+
170+
err = CRYPT_OK;
171+
}
172+
else {
173+
/* not found - recid is wrong or we're unable to calculate public key for some other reason */
174+
err = CRYPT_INVALID_ARG;
175+
}
176+
177+
error:
178+
if (ma != NULL) mp_clear(ma);
179+
if (mu != NULL) mp_clear(mu);
180+
if (mp != NULL) mp_montgomery_free(mp);
181+
if (mR != NULL) ltc_ecc_del_point(mR);
182+
if (mQ != NULL) ltc_ecc_del_point(mQ);
183+
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);
185+
return err;
186+
}
187+
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+
205+
#endif
206+
#endif
207+
208+
/* ref: $Format:%D$ */
209+
/* git commit: $Format:%H$ */
210+
/* commit time: $Format:%ai$ */

tests/ecc_test.c

Lines changed: 51 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,25 @@ static int _ecc_old_api(void)
350350
return CRYPT_OK;
351351
}
352352

353+
static int _ecc_key_cmp(const int should_type, const ecc_key *should, const ecc_key *is)
354+
{
355+
if (should_type != is->type) return CRYPT_ERROR;
356+
if (should_type == PK_PRIVATE) {
357+
if (mp_cmp(should->k, is->k) != LTC_MP_EQ) return CRYPT_ERROR;
358+
}
359+
if (mp_cmp(should->dp.prime, is->dp.prime) != LTC_MP_EQ) return CRYPT_ERROR;
360+
if (mp_cmp(should->dp.A, is->dp.A) != LTC_MP_EQ) return CRYPT_ERROR;
361+
if (mp_cmp(should->dp.B, is->dp.B) != LTC_MP_EQ) return CRYPT_ERROR;
362+
if (mp_cmp(should->dp.order, is->dp.order) != LTC_MP_EQ) return CRYPT_ERROR;
363+
if (mp_cmp(should->dp.base.x, is->dp.base.x) != LTC_MP_EQ) return CRYPT_ERROR;
364+
if (mp_cmp(should->dp.base.y, is->dp.base.y) != LTC_MP_EQ) return CRYPT_ERROR;
365+
if (mp_cmp(should->pubkey.x, is->pubkey.x) != LTC_MP_EQ) return CRYPT_ERROR;
366+
if (mp_cmp(should->pubkey.y, is->pubkey.y) != LTC_MP_EQ) return CRYPT_ERROR;
367+
if (should->dp.size != is->dp.size) return CRYPT_ERROR;
368+
if (should->dp.cofactor != is->dp.cofactor) return CRYPT_ERROR;
369+
return CRYPT_OK;
370+
}
371+
353372
static int _ecc_new_api(void)
354373
{
355374
const char* names[] = {
@@ -474,17 +493,17 @@ static int _ecc_new_api(void)
474493
DO(ecc_set_curve(dp, &privkey));
475494
DO(ecc_set_key(buf, len, PK_PRIVATE, &privkey));
476495

477-
#ifndef USE_TFM
478-
/* XXX-FIXME: TFM does not support sqrtmod_prime */
479-
/* export compressed public key */
480-
len = sizeof(buf);
481-
DO(ecc_get_key(buf, &len, PK_PUBLIC|PK_COMPRESSED, &privkey));
482-
if (len != 1 + (unsigned)ecc_get_size(&privkey)) return CRYPT_FAIL_TESTVECTOR;
483-
/* load exported public+compressed key */
484-
DO(ecc_set_curve(dp, &pubkey));
485-
DO(ecc_set_key(buf, len, PK_PUBLIC, &pubkey));
486-
ecc_free(&pubkey);
487-
#endif
496+
if (strcmp(ltc_mp.name, "TomsFastMath") != 0) {
497+
/* XXX-FIXME: TFM does not support sqrtmod_prime */
498+
/* export compressed public key */
499+
len = sizeof(buf);
500+
DO(ecc_get_key(buf, &len, PK_PUBLIC|PK_COMPRESSED, &privkey));
501+
if (len != 1 + (unsigned)ecc_get_size(&privkey)) return CRYPT_FAIL_TESTVECTOR;
502+
/* load exported public+compressed key */
503+
DO(ecc_set_curve(dp, &pubkey));
504+
DO(ecc_set_key(buf, len, PK_PUBLIC, &pubkey));
505+
ecc_free(&pubkey);
506+
}
488507

489508
/* export long public key */
490509
len = sizeof(buf);
@@ -501,6 +520,27 @@ static int _ecc_new_api(void)
501520
DO(ecc_verify_hash(buf, len, data16, 16, &stat, &pubkey));
502521
if (stat != 1) return CRYPT_FAIL_TESTVECTOR;
503522

523+
#ifdef LTC_ECC_SHAMIR
524+
/* XXX-FIXME: ecc_recover_key currently requires mul2add */
525+
if (strcmp(ltc_mp.name, "TomsFastMath") != 0) {
526+
/* XXX-FIXME: TFM does not support sqrtmod_prime */
527+
int found = 0;
528+
ecc_key reckey;
529+
/* test recovery */
530+
len = sizeof(buf);
531+
DO(ecc_sign_hash(data16, 16, buf, &len, &yarrow_prng, find_prng ("yarrow"), &privkey));
532+
DO(ecc_set_curve(dp, &reckey));
533+
for (j = 0; j < 2*(1+(int)privkey.dp.cofactor); j++) {
534+
stat = ecc_recover_key(buf, len, data16, 16, j, &reckey);
535+
if (stat != CRYPT_OK) continue; /* last two will almost always fail, only possible if x<(prime mod order) */
536+
stat = _ecc_key_cmp(PK_PUBLIC, &pubkey, &reckey);
537+
if (stat == CRYPT_OK) found++;
538+
}
539+
if (found != 1) return CRYPT_FAIL_TESTVECTOR; /* unique match */
540+
ecc_free(&reckey);
541+
}
542+
#endif
543+
504544
/* test encryption */
505545
len = sizeof(buf);
506546
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)
517557
return CRYPT_OK;
518558
}
519559

520-
static int _ecc_key_cmp(const int should_type, const ecc_key *should, const ecc_key *is)
521-
{
522-
if (should_type != is->type) return CRYPT_ERROR;
523-
if (should_type == PK_PRIVATE) {
524-
if (mp_cmp(should->k, is->k) != LTC_MP_EQ) return CRYPT_ERROR;
525-
}
526-
if (mp_cmp(should->dp.prime, is->dp.prime) != LTC_MP_EQ) return CRYPT_ERROR;
527-
if (mp_cmp(should->dp.A, is->dp.A) != LTC_MP_EQ) return CRYPT_ERROR;
528-
if (mp_cmp(should->dp.B, is->dp.B) != LTC_MP_EQ) return CRYPT_ERROR;
529-
if (mp_cmp(should->dp.order, is->dp.order) != LTC_MP_EQ) return CRYPT_ERROR;
530-
if (mp_cmp(should->dp.base.x, is->dp.base.x) != LTC_MP_EQ) return CRYPT_ERROR;
531-
if (mp_cmp(should->dp.base.y, is->dp.base.y) != LTC_MP_EQ) return CRYPT_ERROR;
532-
if (mp_cmp(should->pubkey.x, is->pubkey.x) != LTC_MP_EQ) return CRYPT_ERROR;
533-
if (mp_cmp(should->pubkey.y, is->pubkey.y) != LTC_MP_EQ) return CRYPT_ERROR;
534-
if (should->dp.size != is->dp.size) return CRYPT_ERROR;
535-
if (should->dp.cofactor != is->dp.cofactor) return CRYPT_ERROR;
536-
return CRYPT_OK;
537-
}
538-
539560
static int _ecc_import_export(void) {
540561
const ltc_ecc_curve *cu;
541562
ecc_key key, pri, pub;

0 commit comments

Comments
 (0)