remove small redundancy
[mkp224o.git] / main.c
blob8ae47ce1a72ef517406afd6cc5c1aeef7c3defef
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <stdint.h>
4 #include <string.h>
5 #include <assert.h>
6 #include <time.h>
7 #include <pthread.h>
8 #include <signal.h>
9 #include <sys/stat.h>
10 #include <sodium/randombytes.h>
12 #include "types.h"
13 #include "likely.h"
14 #include "vec.h"
15 #include "base32.h"
16 #include "ed25519/ed25519.h"
17 #include "keccak.h"
19 // additional leading zero is added by C
20 static const char * const pkprefix = "== ed25519v1-public: type0 ==\0\0";
21 #define pkprefixlen (29 + 3)
22 static const char * const skprefix = "== ed25519v1-secret: type0 ==\0\0";
23 #define skprefixlen (29 + 3)
24 static const char * const checksumstr = ".onion checksum";
25 #define checksumstrlen 15
27 // output directory
28 static char *workdir = 0;
29 static size_t workdirlen = 0;
31 static int quietflag = 0;
33 #define SECRET_LEN 64
34 #define PUBLIC_LEN 32
35 #define SEED_LEN 32
36 // with checksum + version num
37 #define PUBONION_LEN (PUBLIC_LEN + 3)
38 // with newline included
39 #define ONIONLEN 62
41 static size_t onionendpos; // end of .onion within string
42 static size_t direndpos; // end of dir before .onion within string
43 static size_t printstartpos; // where to start printing from
44 static size_t printlen; // precalculated, related to printstartpos
46 static pthread_mutex_t fout_mutex;
47 static FILE *fout;
48 static size_t numneedgenerate = 0;
49 static pthread_mutex_t keysgenerated_mutex;
50 static volatile size_t keysgenerated = 0;
51 static volatile int endwork = 0;
53 static void termhandler(int sig)
55 switch (sig) {
56 case SIGTERM:
57 case SIGINT:
58 endwork = 1;
59 break;
63 struct binfilter {
64 u8 f[PUBLIC_LEN];
65 size_t len; // real len minus one
66 u8 mask;
67 } ;
69 #ifdef INTFILTER
70 struct intfilter {
71 u64 f,m;
72 } ;
73 VEC_STRUCT(ifiltervec,struct intfilter) ifilters;
74 #else
75 VEC_STRUCT(bfiltervec,struct binfilter) bfilters;
76 #endif
78 static void filters_init()
80 #ifdef INTFILTER
81 VEC_INIT(ifilters);
82 #else
83 VEC_INIT(bfilters);
84 #endif
87 static void filters_add(const char *filter)
89 struct binfilter bf;
90 size_t ret, ret2;
91 #ifdef INTFILTER
92 union intconv {
93 u64 i;
94 u8 b[8];
95 } fc,mc;
96 #endif
98 if (!base32_valid(filter,&ret)) {
99 fprintf(stderr, "filter \"%s\" is invalid\n", filter);
100 return;
102 ret = BASE32_FROM_LEN(ret);
103 if (!ret)
104 return;
105 #ifdef INTFILTER
106 if (ret > 8)
107 #else
108 if (ret > PUBLIC_LEN)
109 #endif
111 fprintf(stderr, "filter \"%s\" is too long\n", filter);
112 return;
114 ret2 = base32_from(bf.f,&bf.mask,filter);
115 assert(ret == ret2);
116 //printf("--m:%02X\n", bf.mask);
117 bf.len = ret - 1;
118 #ifdef INTFILTER
119 mc.i = 0;
120 for (size_t i = 0;i < bf.len;++i)
121 mc.b[i] = 0xFF;
122 mc.b[bf.len] = bf.mask;
123 memcpy(fc.b,bf.f,8);
124 fc.i &= mc.i;
125 struct intfilter ifltr = {fc.i,mc.i};
126 VEC_ADD(ifilters,ifltr);
127 #else
128 VEC_ADD(bfilters,bf);
129 #endif
132 static void filters_clean()
134 #ifdef INTFILTER
135 VEC_FREE(ifilters);
136 #else
137 VEC_FREE(bfilters);
138 #endif
141 static size_t filters_count()
143 #ifdef INTFILTER
144 return VEC_LENGTH(ifilters);
145 #else
146 return VEC_LENGTH(bfilters);
147 #endif
150 #ifdef INTFILTER
152 #define FILTERFOR(it) for (it = 0;it < VEC_LENGTH(ifilters);++it)
153 #define MATCHFILTER(it,pk) ((*(u64 *)(pk) & VEC_BUF(ifilters,it).m) == VEC_BUF(ifilters,it).f)
155 #else
157 #define FILTERFOR(it) for (it = 0;it < VEC_LENGTH(bfilters);++it)
158 #define MATCHFILTER(it,pk) ( \
159 memcmp(pk,VEC_BUF(bfilters,it).f,VEC_BUF(bfilters,it).len) == 0 && \
160 (pk[VEC_BUF(bfilters,it).len] & VEC_BUF(bfilters,it).mask) == VEC_BUF(bfilters,it).f[VEC_BUF(bfilters,it).len])
162 #endif
164 static void loadfilterfile(const char *fname)
166 char buf[128];
167 FILE *f = fopen(fname, "r");
168 while(fgets(buf, sizeof(buf), f)) {
169 char *p = buf;
170 while(*p++)
171 if(*p == '\n')
172 *p = 0;
173 if (*buf && *buf != '#' && memcmp(buf, "//", 2) != 0)
174 filters_add(buf);
178 static void printfilters()
180 size_t i,l;
181 #ifdef INTFILTER
182 l = VEC_LENGTH(ifilters);
183 #else
184 l = VEC_LENGTH(bfilters);
185 #endif
186 if (l)
187 fprintf(stderr, "filters:\n");
188 else
189 fprintf(stderr, "no filters defined\n");
191 for (i = 0;i < l;++i) {
192 char buf0[256],buf1[256];
193 u8 bufx[128];
194 #ifdef INTFILTER
195 size_t len = 0;
196 u8 *imraw = (u8 *)&VEC_BUF(ifilters,i).m;
197 while (len < 8 && imraw[len] != 0x00) ++len;
198 u8 mask = imraw[len-1];
199 u8 *ifraw = (u8 *)&VEC_BUF(ifilters,i).f;
200 #else
201 size_t len = VEC_BUF(bfilters,i).len + 1;
202 u8 mask = VEC_BUF(bfilters,i).mask;
203 u8 *ifraw = VEC_BUF(bfilters,i).f;
204 #endif
205 base32_to(buf0,ifraw,len);
206 memcpy(bufx,ifraw,len);
207 bufx[len - 1] |= ~mask;
208 base32_to(buf1,bufx,len);
209 char *a = buf0,*b = buf1;
210 while (*a && *a == *b)
211 ++a, ++b;
212 *a = 0;
213 fprintf(stderr, "\t%s\n",buf0);
218 static void onionready(char *sname, const u8 *secret, const u8 *pubonion)
220 FILE *fh;
222 if (endwork)
223 return;
225 if (numneedgenerate) {
226 pthread_mutex_lock(&keysgenerated_mutex);
227 if (keysgenerated >= numneedgenerate) {
228 pthread_mutex_unlock(&keysgenerated_mutex);
229 return;
233 if (mkdir(sname, 0700) != 0) {
234 if (numneedgenerate)
235 pthread_mutex_unlock(&keysgenerated_mutex);
236 return;
239 if (numneedgenerate) {
240 ++keysgenerated;
241 if (keysgenerated >= numneedgenerate)
242 endwork = 1;
243 pthread_mutex_unlock(&keysgenerated_mutex);
246 strcpy(&sname[onionendpos], "/hs_ed25519_secret_key");
247 fh = fopen(sname, "wb");
248 if (fh) {
249 fwrite(secret, skprefixlen + SECRET_LEN, 1, fh);
250 fclose(fh);
253 strcpy(&sname[onionendpos], "/hostname");
254 fh = fopen(sname, "w");
255 if (fh) {
256 sname[onionendpos] = '\n';
257 fwrite(&sname[direndpos], ONIONLEN+1, 1, fh);
258 fclose(fh);
261 strcpy(&sname[onionendpos], "/hs_ed25519_public_key");
262 fh = fopen(sname, "wb");
263 if (fh) {
264 fwrite(pubonion, pkprefixlen + PUBLIC_LEN, 1, fh);
265 fclose(fh);
268 sname[onionendpos] = '\n';
269 if (fout) {
270 pthread_mutex_lock(&fout_mutex);
271 fwrite(&sname[printstartpos], printlen, 1, fout);
272 fflush(fout);
273 pthread_mutex_unlock(&fout_mutex);
277 // little endian inc
278 static void addseed(u8 *seed)
280 register unsigned int c = 1;
281 for (size_t i = 0; i < SEED_LEN; ++i) {
282 c = (unsigned int)seed[i] + c; seed[i] = c & 0xFF; c >>= 8;
283 // unsure if needed
284 if (!c) break;
288 static void *dowork(void *task)
290 union pubonionunion {
291 u8 raw[pkprefixlen + PUBLIC_LEN + 32];
292 struct {
293 u64 prefix[4];
294 u64 key[4];
295 u64 hash[4];
297 } pubonion;
298 u8 * const pk = &pubonion.raw[pkprefixlen];
299 u8 secret[skprefixlen + SECRET_LEN];
300 u8 * const sk = &secret[skprefixlen];
301 u8 seed[SEED_LEN];
302 u8 hashsrc[checksumstrlen + PUBLIC_LEN + 1];
303 size_t i;
304 char *sname;
306 memcpy(secret,skprefix,skprefixlen);
307 memcpy(pubonion.raw,pkprefix,pkprefixlen);
308 pubonion.raw[pkprefixlen + PUBLIC_LEN + 2] = 0x03; // version
309 memcpy(hashsrc,checksumstr,checksumstrlen);
310 hashsrc[checksumstrlen + PUBLIC_LEN] = 0x03; // version
312 sname = malloc(workdirlen + ONIONLEN + 63 + 1);
313 if (workdir)
314 memcpy(sname,workdir,workdirlen);
316 initseed:
317 randombytes(seed,sizeof(seed));
319 again:
320 if (unlikely(endwork))
321 goto end;
323 ed25519_seckey_expand(sk,seed);
324 ed25519_pubkey(pk,sk);
325 FILTERFOR(i) {
326 if (unlikely(MATCHFILTER(i,pk))) {
327 memcpy(&hashsrc[checksumstrlen],pk,PUBLIC_LEN);
328 FIPS202_SHA3_256(hashsrc,sizeof(hashsrc),&pk[PUBLIC_LEN]);
329 pk[PUBLIC_LEN + 2] = 0x03; // version
330 strcpy(base32_to(&sname[direndpos],pk,PUBONION_LEN), ".onion");
331 onionready(sname, secret, pubonion.raw);
332 goto initseed;
335 addseed(seed);
336 goto again;
338 end:
339 free(sname);
340 return 0;
343 static void addu64toscalar32(u8 *dst, u64 v)
345 int i;
346 u32 c = 0;
347 for (i = 0;i < 8;++i) {
348 c += *dst + (v & 0xFF);
349 *dst = c & 0xFF;
350 c >>= 8;
351 v >>= 8;
352 dst++;
356 static void *dofastwork(void *task)
358 union pubonionunion {
359 u8 raw[pkprefixlen + PUBLIC_LEN + 32];
360 struct {
361 u64 prefix[4];
362 u64 key[4];
363 u64 hash[4];
365 } pubonion;
366 u8 * const pk = &pubonion.raw[pkprefixlen];
367 u8 secret[skprefixlen + SECRET_LEN];
368 u8 * const sk = &secret[skprefixlen];
369 u8 seed[SEED_LEN];
370 u8 hashsrc[checksumstrlen + PUBLIC_LEN + 1];
371 ge_p3 ge_public;
372 u64 counter;
373 size_t i;
374 char *sname;
376 memcpy(secret, skprefix, skprefixlen);
377 memcpy(pubonion.raw, pkprefix, pkprefixlen);
378 pubonion.raw[pkprefixlen + PUBLIC_LEN + 2] = 0x03; // version
379 memcpy(hashsrc, checksumstr, checksumstrlen);
380 hashsrc[checksumstrlen + PUBLIC_LEN] = 0x03; // version
382 sname = malloc(workdirlen + ONIONLEN + 63 + 1);
383 if (workdir)
384 memcpy(sname, workdir, workdirlen);
386 initseed:
387 randombytes(seed,sizeof(seed));
388 ed25519_seckey_expand(sk,seed);
390 ge_scalarmult_base(&ge_public,sk);
391 ge_p3_tobytes(pk,&ge_public);
393 for (counter = 0;counter < U64_MAX-8;counter += 8) {
394 ge_p1p1 sum;
396 if (unlikely(endwork))
397 goto end;
399 FILTERFOR(i) {
400 if (unlikely(MATCHFILTER(i,pk))) {
401 // found!
402 // update secret key with counter
403 addu64toscalar32(sk,counter);
404 // sanity check
405 if (((sk[0] & 248) == sk[0]) && (((sk[31] & 63) | 64) == sk[31])) {
406 /* These operations should be a no-op. */
407 sk[0] &= 248;
408 sk[31] &= 63;
409 sk[31] |= 64;
411 else goto initseed;
413 // calc checksum
414 memcpy(&hashsrc[checksumstrlen],pk,PUBLIC_LEN);
415 FIPS202_SHA3_256(hashsrc,sizeof(hashsrc),&pk[PUBLIC_LEN]);
416 // full name
417 strcpy(base32_to(&sname[direndpos],pk,PUBONION_LEN),".onion");
418 onionready(sname,secret,pubonion.raw);
419 // don't reuse same seed
420 goto initseed;
424 // next
425 ge_add(&sum, &ge_public,&ge_eightpoint);
426 ge_p1p1_to_p3(&ge_public,&sum);
427 ge_p3_tobytes(pk,&ge_public);
429 goto initseed;
431 end:
432 free(sname);
433 return 0;
436 void printhelp(const char *progname)
438 fprintf(stderr,
439 "Usage: %s filter [filter...] [options]\n"
440 " %s -f filterfile [options]\n"
441 "Options:\n"
442 "\t-h - print help\n"
443 "\t-f - instead of specifying filter(s) via commandline, specify filter file which contains filters separated by newlines\n"
444 "\t-q - do not print diagnostic output to stderr\n"
445 "\t-x - do not print onion names\n"
446 "\t-o filename - output onion names to specified file\n"
447 "\t-F - include directory names in onion names output\n"
448 "\t-d dirname - output directory\n"
449 "\t-t numthreads - specify number of threads (default - auto)\n"
450 "\t-n numkeys - specify number of keys (default - 0 - unlimited)\n"
451 "\t-z - use faster, experimental key generation method\n"
452 ,progname,progname);
453 exit(1);
456 void setworkdir(const char *wd)
458 free(workdir);
459 size_t l = strlen(wd);
460 if (!l) {
461 workdir = 0;
462 workdirlen = 0;
463 if (!quietflag)
464 fprintf(stderr, "unset workdir\n");
465 return;
467 int needslash = 0;
468 if (wd[l-1] != '/')
469 needslash = 1;
470 char *s = malloc(l + needslash + 1);
471 memcpy(s, wd, l);
472 if (needslash)
473 s[l++] = '/';
474 s[l] = 0;
476 workdir = s;
477 workdirlen = l;
478 if (!quietflag)
479 fprintf(stderr, "set workdir: %s\n", workdir);
482 VEC_STRUCT(threadvec, pthread_t);
484 int main(int argc, char **argv)
486 char *outfile = 0;
487 const char *arg;
488 int ignoreargs = 0;
489 int dirnameflag = 0;
490 int numthreads = 0;
491 int fastkeygen = 0;
492 struct threadvec threads;
493 int tret;
495 filters_init();
497 fout = stdout;
498 pthread_mutex_init(&keysgenerated_mutex, 0);
499 pthread_mutex_init(&fout_mutex, 0);
501 const char *progname = argv[0];
502 if (argc <= 1)
503 printhelp(progname);
504 argc--, argv++;
506 while (argc--) {
507 arg = *argv++;
508 if (!ignoreargs && *arg == '-') {
509 int numargit = 0;
510 nextarg:
511 ++arg;
512 ++numargit;
513 if (*arg == '-') {
514 if (numargit > 1) {
515 fprintf(stderr, "unrecognised argument: -\n");
516 exit(1);
518 ++arg;
519 if (!*arg)
520 ignoreargs = 1;
521 else if (!strcmp(arg, "help"))
522 printhelp(progname);
523 else {
524 fprintf(stderr, "unrecognised argument: --%s\n", arg);
525 exit(1);
527 numargit = 0;
529 else if (*arg == 0) {
530 if (numargit == 1)
531 ignoreargs = 1;
532 continue;
534 else if (*arg == 'h')
535 printhelp(progname);
536 else if (*arg == 'f') {
537 if (argc--)
538 loadfilterfile(*argv++);
539 else {
540 fprintf(stderr, "additional argument required\n");
541 exit(1);
544 else if (*arg == 'q')
545 ++quietflag;
546 else if (*arg == 'x')
547 fout = 0;
548 else if (*arg == 'o') {
549 if (argc--)
550 outfile = *argv++;
551 else {
552 fprintf(stderr, "additional argument required\n");
553 exit(1);
556 else if (*arg == 'F')
557 dirnameflag = 1;
558 else if (*arg == 'd') {
559 if (argc--) {
560 setworkdir(*argv++);
562 else {
563 fprintf(stderr, "additional argument required\n");
566 else if (*arg == 't') {
567 if (argc--)
568 numthreads = atoi(*argv++);
569 else {
570 fprintf(stderr, "additional argument required\n");
571 exit(1);
574 else if (*arg == 'n') {
575 if (argc--)
576 numneedgenerate = (size_t)atoll(*argv++);
577 else {
578 fprintf(stderr, "additional argument required\n");
579 exit(1);
582 else if (*arg == 'z')
583 fastkeygen = 1;
584 else {
585 fprintf(stderr, "unrecognised argument: -%c\n", *arg);
586 exit(1);
588 if (numargit)
589 goto nextarg;
591 else filters_add(arg);
594 if (outfile)
595 fout = fopen(outfile, "w");
597 if (!quietflag)
598 printfilters();
600 if (!filters_count())
601 return 0;
603 if (workdir)
604 mkdir(workdir, 0700);
606 direndpos = workdirlen;
607 onionendpos = workdirlen + ONIONLEN;
609 if (!dirnameflag) {
610 printstartpos = direndpos;
611 printlen = ONIONLEN + 1;
612 } else {
613 printstartpos = 0;
614 printlen = onionendpos + 1;
617 if (numthreads <= 0) {
618 // TODO: autodetect
619 numthreads = 1;
622 signal(SIGTERM, termhandler);
623 signal(SIGINT, termhandler);
625 VEC_INIT(threads);
626 VEC_ADDN(threads, pthread_t, numthreads);
628 for (size_t i = 0; i < VEC_LENGTH(threads); ++i) {
629 tret = pthread_create(&VEC_BUF(threads, i), 0, fastkeygen ? dofastwork : dowork, 0);
630 if (tret) {
631 fprintf(stderr, "error while making %dth thread: %d\n", (int)i, tret);
632 exit(1);
636 struct timespec ts;
637 memset(&ts,0,sizeof(ts));
638 ts.tv_nsec = 100000000;
639 while (!endwork) {
640 if (numneedgenerate && keysgenerated >= numneedgenerate) {
641 endwork = 1;
642 break;
644 nanosleep(&ts,0);
647 fprintf(stderr, "waiting for threads to finish...\n");
648 for (size_t i = 0; i < VEC_LENGTH(threads); ++i) {
649 pthread_join(VEC_BUF(threads, i), 0);
651 fprintf(stderr, "done, quitting\n");
653 pthread_mutex_destroy(&keysgenerated_mutex);
654 pthread_mutex_destroy(&fout_mutex);
655 filters_clean();
657 if (outfile)
658 fclose(fout);
660 return 0;