2 #define _POSIX_C_SOURCE 200112L
11 #include <sodium/randombytes.h>
12 #include <sodium/utils.h>
20 #include "ed25519/ed25519.h"
31 // additional 0 terminator is added by C
32 static const char * const pkprefix
= "== ed25519v1-public: type0 ==\0\0";
33 static const char * const skprefix
= "== ed25519v1-secret: type0 ==\0\0";
35 static const char checksumstr
[] = ".onion checksum";
36 #define checksumstrlen (sizeof(checksumstr) - 1) // 15
39 static char *workdir
= 0;
40 static size_t workdirlen
= 0;
42 static int quietflag
= 0;
43 //static int wantdedup = 0;
46 // 0, direndpos, onionendpos
47 // printstartpos = either 0 or direndpos
48 // printlen = either onionendpos + 1 or ONION_LEN + 1 (additional 1 is for newline)
49 size_t onionendpos
; // end of .onion within string
50 size_t direndpos
; // end of dir before .onion within string
51 size_t printstartpos
; // where to start printing from
52 size_t printlen
; // precalculated, related to printstartpos
54 static int yamloutput
= 0;
55 static int numwords
= 1;
56 static size_t numneedgenerate
= 0;
58 static pthread_mutex_t keysgenerated_mutex
;
59 static volatile size_t keysgenerated
= 0;
60 static volatile int endwork
= 0;
62 pthread_mutex_t fout_mutex
;
65 static void termhandler(int sig
)
78 #define ADDNUMSUCCESS ++st->numsuccess.v
80 #define ADDNUMSUCCESS do ; while (0)
83 // statistics, if enabled
99 VEC_STRUCT(statsvec
,struct statstruct
);
109 VEC_STRUCT(tstatsvec
,struct tstatstruct
);
112 static void onionready(char *sname
,const u8
*secret
,const u8
*pubonion
)
117 if (numneedgenerate
) {
118 pthread_mutex_lock(&keysgenerated_mutex
);
119 if (keysgenerated
>= numneedgenerate
) {
120 pthread_mutex_unlock(&keysgenerated_mutex
);
124 if (keysgenerated
== numneedgenerate
)
126 pthread_mutex_unlock(&keysgenerated_mutex
);
130 if (createdir(sname
,1) != 0) {
131 pthread_mutex_lock(&fout_mutex
);
132 fprintf(stderr
,"ERROR: could not create directory for key output\n");
133 pthread_mutex_unlock(&fout_mutex
);
137 strcpy(&sname
[onionendpos
],"/hs_ed25519_secret_key");
138 writetofile(sname
,secret
,FORMATTED_SECRET_LEN
,1);
140 strcpy(&sname
[onionendpos
],"/hs_ed25519_public_key");
141 writetofile(sname
,pubonion
,FORMATTED_PUBLIC_LEN
,0);
143 strcpy(&sname
[onionendpos
],"/hostname");
144 FILE *hfile
= fopen(sname
,"w");
145 sname
[onionendpos
] = '\n';
147 fwrite(&sname
[direndpos
],ONION_LEN
+ 1,1,hfile
);
151 pthread_mutex_lock(&fout_mutex
);
152 fwrite(&sname
[printstartpos
],printlen
,1,fout
);
154 pthread_mutex_unlock(&fout_mutex
);
157 yamlout_writekeys(&sname
[direndpos
],pubonion
,secret
);
160 union pubonionunion
{
161 u8 raw
[PKPREFIX_SIZE
+ PUBLIC_LEN
+ 32];
169 static char *makesname()
171 char *sname
= (char *) malloc(workdirlen
+ ONION_LEN
+ 63 + 1);
175 memcpy(sname
,workdir
,workdirlen
);
180 static void addsk32(u8
*sk
)
182 register unsigned int c
= 8;
183 for (size_t i
= 0;i
< 32;++i
) {
184 c
= (unsigned int)sk
[i
] + c
; sk
[i
] = c
& 0xFF; c
>>= 8;
190 // 0123 4567 xxxx --3--> 3456 7xxx
191 // 0123 4567 xxxx --1--> 1234 567x
192 static inline void shiftpk(u8
*dst
,const u8
*src
,size_t sbits
)
194 size_t i
,sbytes
= sbits
/ 8;
196 for (i
= 0;i
+ sbytes
< PUBLIC_LEN
;++i
) {
197 dst
[i
] = (u8
) ((src
[i
+sbytes
] << sbits
) |
198 (src
[i
+sbytes
+1] >> (8 - sbits
)));
200 for(;i
< PUBLIC_LEN
;++i
)
204 static void *dowork(void *task
)
206 union pubonionunion pubonion
;
207 u8
* const pk
= &pubonion
.raw
[PKPREFIX_SIZE
];
208 u8 secret
[SKPREFIX_SIZE
+ SECRET_LEN
];
209 u8
* const sk
= &secret
[SKPREFIX_SIZE
];
211 u8 hashsrc
[checksumstrlen
+ PUBLIC_LEN
+ 1];
212 u8 wpk
[PUBLIC_LEN
+ 1];
216 struct statstruct
*st
= (struct statstruct
*)task
;
220 memcpy(secret
,skprefix
,SKPREFIX_SIZE
);
222 memset(&pubonion
,0,sizeof(pubonion
));
223 memcpy(pubonion
.raw
,pkprefix
,PKPREFIX_SIZE
);
224 // write version later as it will be overwritten by hash
225 memcpy(hashsrc
,checksumstr
,checksumstrlen
);
226 hashsrc
[checksumstrlen
+ PUBLIC_LEN
] = 0x03; // version
231 randombytes(seed
,sizeof(seed
));
232 ed25519_seckey_expand(sk
,seed
);
238 if (unlikely(endwork
))
241 ed25519_pubkey(pk
,sk
);
249 shiftpk(wpk
,pk
,filter_len(i
));
252 DOFILTER(j
,wpk
,goto secondfind
);
257 shiftpk(wpk
,wpk
,filter_len(j
));
261 if ((sk
[0] & 248) != sk
[0] || ((sk
[31] & 63) | 64) != sk
[31])
267 memcpy(&hashsrc
[checksumstrlen
],pk
,PUBLIC_LEN
);
268 FIPS202_SHA3_256(hashsrc
,sizeof(hashsrc
),&pk
[PUBLIC_LEN
]);
270 pk
[PUBLIC_LEN
+ 2] = 0x03;
272 strcpy(base32_to(&sname
[direndpos
],pk
,PUBONION_LEN
),".onion");
273 onionready(sname
,secret
,pubonion
.raw
);
284 sodium_memzero(secret
,sizeof(secret
));
285 sodium_memzero(seed
,sizeof(seed
));
289 static void addsztoscalar32(u8
*dst
,size_t v
)
293 for (i
= 0;i
< 32;++i
) {
294 c
+= *dst
+ (v
& 0xFF); *dst
= c
& 0xFF; c
>>= 8;
300 static void *dofastwork(void *task
)
302 union pubonionunion pubonion
;
303 u8
* const pk
= &pubonion
.raw
[PKPREFIX_SIZE
];
304 u8 secret
[SKPREFIX_SIZE
+ SECRET_LEN
];
305 u8
* const sk
= &secret
[SKPREFIX_SIZE
];
307 u8 hashsrc
[checksumstrlen
+ PUBLIC_LEN
+ 1];
308 u8 wpk
[PUBLIC_LEN
+ 1];
314 struct statstruct
*st
= (struct statstruct
*)task
;
318 memcpy(secret
,skprefix
,SKPREFIX_SIZE
);
320 memset(&pubonion
,0,sizeof(pubonion
));
321 memcpy(pubonion
.raw
,pkprefix
,PKPREFIX_SIZE
);
322 // write version later as it will be overwritten by hash
323 memcpy(hashsrc
,checksumstr
,checksumstrlen
);
324 hashsrc
[checksumstrlen
+ PUBLIC_LEN
] = 0x03; // version
332 randombytes(seed
,sizeof(seed
));
333 ed25519_seckey_expand(sk
,seed
);
335 ge_scalarmult_base(&ge_public
,sk
);
336 ge_p3_tobytes(pk
,&ge_public
);
338 for (counter
= 0;counter
< SIZE_MAX
-8;counter
+= 8) {
341 if (unlikely(endwork
))
346 shiftpk(wpk
,pk
,filter_len(i
));
349 DOFILTER(j
,wpk
,goto secondfind
);
354 shiftpk(wpk
,wpk
,filter_len(j
));
358 // update secret key with counter
359 addsztoscalar32(sk
,counter
);
361 if ((sk
[0] & 248) != sk
[0] || ((sk
[31] & 63) | 64) != sk
[31])
367 memcpy(&hashsrc
[checksumstrlen
],pk
,PUBLIC_LEN
);
368 FIPS202_SHA3_256(hashsrc
,sizeof(hashsrc
),&pk
[PUBLIC_LEN
]);
370 pk
[PUBLIC_LEN
+ 2] = 0x03;
372 strcpy(base32_to(&sname
[direndpos
],pk
,PUBONION_LEN
),".onion");
373 onionready(sname
,secret
,pubonion
.raw
);
375 // don't reuse same seed
379 ge_add(&sum
, &ge_public
,&ge_eightpoint
);
380 ge_p1p1_to_p3(&ge_public
,&sum
);
381 ge_p3_tobytes(pk
,&ge_public
);
391 sodium_memzero(secret
,sizeof(secret
));
392 sodium_memzero(seed
,sizeof(seed
));
396 static void printhelp(FILE *out
,const char *progname
)
399 "Usage: %s filter [filter...] [options]\n"
400 " %s -f filterfile [options]\n"
402 "\t-h - print help to stdout and quit\n"
403 "\t-f - instead of specifying filter(s) via commandline, specify filter file which contains filters separated by newlines\n"
404 "\t-q - do not print diagnostic output to stderr\n"
405 "\t-x - do not print onion names\n"
406 "\t-o filename - output onion names to specified file (append)\n"
407 "\t-O filename - output onion names to specified file (overwrite)\n"
408 "\t-F - include directory names in onion names output\n"
409 "\t-d dirname - output directory\n"
410 "\t-t numthreads - specify number of threads (default - auto)\n"
411 "\t-j numthreads - same as -t\n"
412 "\t-n numkeys - specify number of keys (default - 0 - unlimited)\n"
413 "\t-N numwords - specify number of words per key (default - 1)\n"
414 "\t-z - use faster key generation method. this is now default\n"
415 "\t-Z - use slower key generation method\n"
416 "\t-s - print statistics each 10 seconds\n"
417 "\t-S t - print statistics every specified ammount of seconds\n"
418 "\t-T - do not reset statistics counters when printing\n"
419 "\t-y - output generated keys in yaml format instead of dumping them to filesystem\n"
420 "\t-Y [filename [host.onion]] - parse yaml encoded input and extract key(s) to filesystem\n"
435 static void e_additional()
437 fprintf(stderr
,"additional argument required\n");
442 static void e_nostatistics()
444 fprintf(stderr
,"statistics support not compiled in\n");
445 exit(Q_NOSTATISTICS
);
449 static void setworkdir(const char *wd
)
452 size_t l
= strlen(wd
);
457 fprintf(stderr
,"unset workdir\n");
460 unsigned needslash
= 0;
463 char *s
= (char *) malloc(l
+ needslash
+ 1);
474 fprintf(stderr
,"set workdir: %s\n",workdir
);
477 VEC_STRUCT(threadvec
, pthread_t
);
479 int main(int argc
,char **argv
)
481 const char *outfile
= 0;
482 const char *infile
= 0;
483 const char *hostname
= 0;
490 int outfileoverwrite
= 0;
491 struct threadvec threads
;
493 struct statsvec stats
;
494 struct tstatsvec tstats
;
496 int realtimestats
= 1;
503 setvbuf(stderr
,0,_IONBF
,0);
506 const char *progname
= argv
[0];
508 printhelp(stderr
,progname
);
515 if (!ignoreargs
&& *arg
== '-') {
522 fprintf(stderr
,"unrecognised argument: -\n");
523 exit(Q_UNRECOGNISED
);
528 else if (!strcmp(arg
,"help") || !strcmp(arg
,"usage")) {
529 printhelp(stdout
,progname
);
533 fprintf(stderr
,"unrecognised argument: --%s\n",arg
);
534 exit(Q_UNRECOGNISED
);
538 else if (*arg
== 0) {
543 else if (*arg
== 'h') {
544 printhelp(stdout
,progname
);
547 else if (*arg
== 'f') {
549 loadfilterfile(*argv
++);
553 else if (*arg
== 'q')
555 else if (*arg
== 'x')
557 else if (*arg
== 'o') {
558 outfileoverwrite
= 0;
564 else if (*arg
== 'O') {
565 outfileoverwrite
= 1;
571 else if (*arg
== 'F')
573 else if (*arg
== 'd') {
579 else if (*arg
== 't' || *arg
== 'j') {
581 numthreads
= atoi(*argv
++);
585 else if (*arg
== 'n') {
587 numneedgenerate
= (size_t)atoll(*argv
++);
591 else if (*arg
== 'N') {
593 numwords
= atoi(*argv
++);
597 else if (*arg
== 'Z')
599 else if (*arg
== 'z')
601 else if (*arg
== 's') {
603 reportdelay
= 10000000;
608 else if (*arg
== 'S') {
611 reportdelay
= (u64
)atoll(*argv
++) * 1000000;
618 else if (*arg
== 'T') {
625 else if (*arg
== 'y')
627 else if (*arg
== 'Y') {
639 if (hostname
&& strlen(hostname
) != ONION_LEN
) {
640 fprintf(stderr
,"bad onion argument length\n");
641 exit(Q_UNRECOGNISED
);
647 fprintf(stderr
,"unrecognised argument: -%c\n",*arg
);
648 exit(Q_UNRECOGNISED
);
658 fout
= fopen(outfile
,!outfileoverwrite
? "a" : "w");
660 perror("failed to open output file");
661 exit(Q_FAILOPENOUTPUT
);
665 if (!fout
&& yamloutput
) {
666 fprintf(stderr
,"nil output with yaml mode does not make sense\n");
667 exit(Q_FAILOPENOUTPUT
); // define new err code?
671 createdir(workdir
,1);
673 direndpos
= workdirlen
;
674 onionendpos
= workdirlen
+ ONION_LEN
;
677 printstartpos
= direndpos
;
678 printlen
= ONION_LEN
+ 1; // + '\n'
681 printlen
= onionendpos
+ 1; // + '\n'
685 char *sname
= makesname();
688 fin
= fopen(infile
,"r");
690 fprintf(stderr
,"failed to open input file\n");
691 return Q_FAILOPENINPUT
;
694 tret
= yamlin_parseandcreate(fin
,sname
,hostname
);
712 if (!filters_count() && !reportdelay
)
714 if (!filters_count())
719 if (numwords
> 1 && flattened
)
720 fprintf(stderr
,"WARNING: -N switch will produce bogus results because we can't know filter width. reconfigure with --enable-besort and recompile.\n");
726 pthread_mutex_init(&keysgenerated_mutex
,0);
727 pthread_mutex_init(&fout_mutex
,0);
729 if (numthreads
<= 0) {
730 numthreads
= cpucount();
734 fprintf(stderr
,"using %d %s\n",
735 numthreads
,numthreads
== 1 ? "thread" : "threads");
738 signal(SIGTERM
,termhandler
);
739 signal(SIGINT
,termhandler
);
742 VEC_ADDN(threads
,numthreads
);
745 VEC_ADDN(stats
,numthreads
);
748 VEC_ADDN(tstats
,numthreads
);
752 pthread_attr_t tattr
,*tattrp
= &tattr
;
753 tret
= pthread_attr_init(tattrp
);
755 perror("pthread_attr_init");
759 tret
= pthread_attr_setstacksize(tattrp
,80<<10);
761 perror("pthread_attr_setstacksize");
764 for (size_t i
= 0;i
< VEC_LENGTH(threads
);++i
) {
767 tp
= &VEC_BUF(stats
,i
);
769 tret
= pthread_create(&VEC_BUF(threads
,i
),tattrp
,fastkeygen
? dofastwork
: dowork
,tp
);
771 fprintf(stderr
,"error while making " FSZ
"th thread: %s\n",i
,strerror(tret
));
777 tret
= pthread_attr_destroy(tattrp
);
779 perror("pthread_attr_destroy");
783 struct timespec nowtime
;
784 u64 istarttime
,inowtime
,ireporttime
= 0,elapsedoffset
= 0;
785 if (clock_gettime(CLOCK_MONOTONIC
,&nowtime
) < 0) {
786 perror("failed to get time");
789 istarttime
= (1000000 * (u64
)nowtime
.tv_sec
) + ((u64
)nowtime
.tv_nsec
/ 1000);
792 memset(&ts
,0,sizeof(ts
));
793 ts
.tv_nsec
= 100000000;
795 if (numneedgenerate
&& keysgenerated
>= numneedgenerate
) {
802 clock_gettime(CLOCK_MONOTONIC
,&nowtime
);
803 inowtime
= (1000000 * (u64
)nowtime
.tv_sec
) + ((u64
)nowtime
.tv_nsec
/ 1000);
804 u64 sumcalc
= 0,sumsuccess
= 0,sumrestart
= 0;
805 for (int i
= 0;i
< numthreads
;++i
) {
808 newt
= VEC_BUF(stats
,i
).numcalc
.v
;
809 tdiff
= newt
- VEC_BUF(tstats
,i
).oldnumcalc
;
810 VEC_BUF(tstats
,i
).oldnumcalc
= newt
;
811 VEC_BUF(tstats
,i
).numcalc
+= (u64
)tdiff
;
812 sumcalc
+= VEC_BUF(tstats
,i
).numcalc
;
814 newt
= VEC_BUF(stats
,i
).numsuccess
.v
;
815 tdiff
= newt
- VEC_BUF(tstats
,i
).oldnumsuccess
;
816 VEC_BUF(tstats
,i
).oldnumsuccess
= newt
;
817 VEC_BUF(tstats
,i
).numsuccess
+= (u64
)tdiff
;
818 sumsuccess
+= VEC_BUF(tstats
,i
).numsuccess
;
820 newt
= VEC_BUF(stats
,i
).numrestart
.v
;
821 tdiff
= newt
- VEC_BUF(tstats
,i
).oldnumrestart
;
822 VEC_BUF(tstats
,i
).oldnumrestart
= newt
;
823 VEC_BUF(tstats
,i
).numrestart
+= (u64
)tdiff
;
824 sumrestart
+= VEC_BUF(tstats
,i
).numrestart
;
826 if (reportdelay
&& (!ireporttime
|| inowtime
- ireporttime
>= reportdelay
)) {
828 ireporttime
+= reportdelay
;
830 ireporttime
= inowtime
;
834 double calcpersec
= (1000000.0 * sumcalc
) / (inowtime
- istarttime
);
835 double succpersec
= (1000000.0 * sumsuccess
) / (inowtime
- istarttime
);
836 double restpersec
= (1000000.0 * sumrestart
) / (inowtime
- istarttime
);
837 fprintf(stderr
,">calc/sec:%8lf, succ/sec:%8lf, rest/sec:%8lf, elapsed:%5.6lfsec\n",
838 calcpersec
,succpersec
,restpersec
,
839 (inowtime
- istarttime
+ elapsedoffset
) / 1000000.0);
842 for (int i
= 0;i
< numthreads
;++i
) {
843 VEC_BUF(tstats
,i
).numcalc
= 0;
844 VEC_BUF(tstats
,i
).numsuccess
= 0;
845 VEC_BUF(tstats
,i
).numrestart
= 0;
847 elapsedoffset
+= inowtime
- istarttime
;
848 istarttime
= inowtime
;
851 if (sumcalc
> U64_MAX
/ 2) {
852 for (int i
= 0;i
< numthreads
;++i
) {
853 VEC_BUF(tstats
,i
).numcalc
/= 2;
854 VEC_BUF(tstats
,i
).numsuccess
/= 2;
855 VEC_BUF(tstats
,i
).numrestart
/= 2;
857 u64 timediff
= (inowtime
- istarttime
+ 1) / 2;
858 elapsedoffset
+= timediff
;
859 istarttime
+= timediff
;
865 fprintf(stderr
,"waiting for threads to finish...");
866 for (size_t i
= 0;i
< VEC_LENGTH(threads
);++i
)
867 pthread_join(VEC_BUF(threads
,i
),0);
869 fprintf(stderr
," done.\n");
874 pthread_mutex_destroy(&keysgenerated_mutex
);
875 pthread_mutex_destroy(&fout_mutex
);