diff --git a/README.md b/README.md index 0d3430d..ac3655a 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,33 @@ Use `-h` switch to obtain all available options. I highly recommend reading [OPTIMISATION.txt][OPTIMISATION] for performance-related tips. +#### Trustless mining +[Trustless mining](https://github.com/cathugger/mkp224o/issues/60) lets someone +else safely mine a vanity .onion for you without letting them get its private +key. Beware that it's an experimental feature - it's not yet completely tested, +audited, nor does it work with all configurations. + +``` +# example usage +$ ./mkp224o --genbase base.priv base.pub +writing private base key to 'base.priv' +writing public base key to 'base.pub' +done. + +$ ./mkp224o --basekey base.pub -d ~/keys -n 1 neko +set workdir: /home/dzwdz/keys/ +sorting filters... done. +filters: + neko +in total, 1 filter +using 1 thread +neko3q2neskgkol2gyg2hia46xnavwt4yfy2nvj2pulmvc7lxk6yfkad.onion +waiting for threads to finish... done. + +$ ./mkp224o --combine ~/keys/neko3q2nes*.onion/halfkey base.priv +saving to ~/keys/neko3q2neskgkol2gyg2hia46xnavwt4yfy2nvj2pulmvc7lxk6yfkad.onion/hs_ed25519_secret_key +``` + ### FAQ and other useful info * How do I generate address? diff --git a/configure.ac b/configure.ac index a2053c8..dde93a2 100644 --- a/configure.ac +++ b/configure.ac @@ -83,6 +83,7 @@ AC_ARG_ENABLE([ref10], [AS_HELP_STRING([--enable-ref10], [use SUPERCOP ref10 ed25519 implementation @<:@default=no@:>@])], [ + AC_MSG_ERROR(ref10 is temporarily unsupported. try using donna instead) AS_IF([test x"$ed25519impl" != x"" -a "$ed25519impl" != "ref10"], [AC_MSG_ERROR(only one ed25519 implementation can be defined)]) ed25519impl="ref10" diff --git a/ed25519/ed25519_impl_pre.h b/ed25519/ed25519_impl_pre.h index bc84ad9..37cae16 100644 --- a/ed25519/ed25519_impl_pre.h +++ b/ed25519/ed25519_impl_pre.h @@ -79,6 +79,7 @@ inline static void ge_initeightpoint(void) {} #define ge_p3 ge25519_p3 #define ge_p1p1_to_p3 ge25519_p1p1_to_p3 #define ge_p3_tobytes ge25519_pack +#define ge_frombytes_negate_vartime ge25519_unpackneg_vartime #define ge_add ge25519_pnielsadd_p1p1 #define ge_p3_batchtobytes_destructive_1 ge25519_batchpack_destructive_1 @@ -185,19 +186,39 @@ static int ed25519_keypair(unsigned char *pk,unsigned char *sk) } #define fe bignum25519 +#define sc25519 bignum256modm #define ge_p1p1 ge25519_p1p1 #define ge_p3 ge25519 #define ge_p1p1_to_p3 ge25519_p1p1_to_full #define ge_p3_tobytes ge25519_pack +#define ge_frombytes_negate_vartime ge25519_unpack_negative_vartime #define ge_p3_batchtobytes_destructive_1 ge25519_batchpack_destructive_1 #define ge_p3_batchtobytes_destructive_finish ge25519_batchpack_destructive_finish - #define ge_add CRYPTO_NAMESPACE(ge_add) #define ge_scalarmult_base CRYPTO_NAMESPACE(ge_scalarmult_base) +static void sc25519_from32bytes(bignum256modm *r, const unsigned char x[32]) +{ + expand256_modm(*r, x, 32); +} + +static void sc25519_to32bytes(unsigned char r[32], const sc25519 *x) +{ + contract256_modm(r, *x); +} + +static void sc25519_add(bignum256modm *r, const bignum256modm *x, const bignum256modm *y) +{ + add256_modm(*r, *x, *y); +} + +static void ge25519_scalarmult_base(ge25519 *r, const bignum256modm *s) +{ + ge25519_scalarmult_base_niels(r,ge25519_niels_base_multiples,*s); +} DONNA_INLINE static void ge_add(ge25519_p1p1 *r,const ge25519 *p,const ge25519_pniels *q) { diff --git a/headers.h b/headers.h new file mode 100644 index 0000000..df1bafc --- /dev/null +++ b/headers.h @@ -0,0 +1,8 @@ +#define HEADER_BASESK "mkp224o base_sk\0" +#define HEADER_BASESKLEN 16 + +#define HEADER_BASEPK "mkp224o base_pk\0" +#define HEADER_BASEPKLEN 16 + +#define HEADER_HALFKEY "mkp224o halfkey\0" +#define HEADER_HALFKEYLEN 16 diff --git a/main.c b/main.c index 5a1fbde..b4e447f 100644 --- a/main.c +++ b/main.c @@ -24,6 +24,7 @@ #include "ioutil.h" #include "common.h" #include "yaml.h" +#include "headers.h" #include "filters.h" @@ -132,6 +133,14 @@ static void printhelp(FILE *out,const char *progname) #endif " --rawyaml raw (unprefixed) public/secret keys for -y/-Y\n" " (may be useful for tor controller API)\n" + " --basekey base.pub\n" + " trustless mining: the private keys found will need\n" + " to be --combine'd with the private parts of all\n" + " basekeys used\n" + " --genbase base.priv base.pub\n" + " generate base keys for trustless mining\n" + " --combine halfkey base.priv..\n" + " combine a mined hs_secret key with base key(s)\n" " -h, --help, --usage print help to stdout and quit\n" " -V, --version print version information to stdout and exit\n" ,progname,progname); @@ -262,6 +271,161 @@ enum worker_type { WT_BATCH, }; +// i'm so sorry for including an implementation header +// i didn't find another way to get access to the functions +#include "ed25519/ed25519_impl_pre.h" +static void genbase(const char *privpath, const char *pubpath) +{ + u8 base_sk[32]; + u8 base_pk[32]; + u8 base_extsk[64]; + ge_p3 ALIGN(16) A; + FILE *fp; + + randombytes(base_sk, sizeof base_sk); + ed25519_seckey_expand(base_extsk, base_sk); + ge_scalarmult_base(&A, base_extsk); + ge25519_pack(base_pk, &A); + + printf("writing private base key to '%s'\n", privpath); + fp = fopen(privpath, "w"); + if (!fp) { + perror("couldn't open"); + exit(1); + } + if (fwrite(HEADER_BASESK, 1, HEADER_BASESKLEN, fp) != HEADER_BASESKLEN) { + perror("write"); + exit(1); + } + if (fwrite(base_sk, 1, 32, fp) != 32) { + perror("write"); + exit(1); + } + fclose(fp); + + printf("writing public base key to '%s'\n", pubpath); + fp = fopen(pubpath, "w"); + if (!fp) { + perror("couldn't open"); + exit(1); + } + if (fwrite(HEADER_BASEPK, 1, HEADER_BASEPKLEN, fp) != HEADER_BASEPKLEN) { + perror("write"); + exit(1); + } + if (fwrite(base_pk, 1, 32, fp) != 32) { + perror("write"); + exit(1); + } + fclose(fp); + + puts("done."); +} + +static void combine(int argc, char **argv) +{ + u8 halfkey[HEADER_HALFKEYLEN + SECRET_LEN + PUBLIC_LEN]; + u8 result[FORMATTED_SECRET_LEN]; + FILE *fp; + const char *minedpath = argv[0]; + + if (argc < 2) { + fprintf(stderr, "--combine requires one or more base private keys after the mined key.\n"); + exit(1); + } + + fp = fopen(minedpath, "r"); + if (fp == NULL) { + perror("failed to open halfkey"); + exit(1); + } + if (fread(halfkey, sizeof halfkey, 1, fp) != 1) { + perror("failed to read hs_secret_key"); + exit(1); + } + if (memcmp(halfkey, HEADER_HALFKEY, HEADER_HALFKEYLEN) != 0) { + fprintf(stderr, "Invalid halfkey format. The halfkey must be the first argument.\n"); + exit(1); + } + fclose(fp); + + sc25519 ALIGN(16) a; + sc25519_from32bytes(&a, &halfkey[HEADER_HALFKEYLEN]); + + for (int i = 1; i < argc; i++) { + u8 base_sk[32], base_extsk[64]; + fp = fopen(argv[i], "r"); + if (fp == NULL) { + perror("couldn't open basekey"); + exit(1); + } + if (fread(base_sk, 1, HEADER_BASESKLEN, fp) != HEADER_BASESKLEN) { + perror("read"); + exit(1); + } + if (memcmp(base_sk, HEADER_BASESK, HEADER_BASESKLEN) != 0) { + fprintf(stderr, "\"%s\" isn't a valid base secret key.\n", argv[i]); + exit(1); + } + if (fread(base_sk, 1, sizeof base_sk, fp) != sizeof base_sk) { + perror("read"); + exit(1); + } + fclose(fp); + + sc25519 ALIGN(16) b; + ed25519_seckey_expand(base_extsk, base_sk); + sc25519_from32bytes(&b, base_extsk); + sc25519_add(&a, &a, &b); + } + + ge_p3 ALIGN(16) A; + ge25519_scalarmult_base(&A, &a); + u8 pk[32]; + ge25519_pack(pk, &A); + + // Save secret scalar. + memcpy(result, "== ed25519v1-secret: type0 ==\0\0\0", SKPREFIX_SIZE); + sc25519_to32bytes(&result[SKPREFIX_SIZE], &a); + + // Compute the key's hash prefix. + // See "Pseudorandom generation of r.", page 8 of https://ed25519.cr.yp.to/ed25519-20110926.pdf + // Usually it's generated together with the secret scalar using a hash + // function, but we can't do that here. As far as I can tell, it just + // needs to be another secret value. + // I'm setting it to a hash of the secret scalar to prevent generating + // multiple keys with the same secret scalar but different hash prefixes, + // which never occurs in normal ed25519. + FIPS202_SHAKE256(&result[SKPREFIX_SIZE], 32, &result[64], 32); + + ge25519_scalarmult_base(&A, &a); + ge25519_pack(pk, &A); + + if (memcmp(pk, &halfkey[HEADER_HALFKEYLEN + SECRET_LEN], PUBLIC_LEN) != 0) { + fprintf(stderr,"Didn't get the expected public key. You probably didn't use the right basekey(s).\n"); + exit(1); + } + + char *newpath = malloc(strlen(minedpath) + strlen("hs_ed25519_secret_key") + 1); + strcpy(newpath, minedpath); + char *slash = strrchr(newpath, '/'); + slash = slash ? slash + 1 : newpath; + strcpy(slash, "hs_ed25519_secret_key"); + printf("saving to %s\n", newpath); + + fp = fopen(newpath, "w"); + if (!fp) { + perror("couldn't open"); + exit(1); + } + if (fwrite(result, sizeof result, 1, fp) != 1) { + perror("failed to write hs_ed25519_secret_key"); + exit(1); + } + fclose(fp); +} +#include "ed25519/ed25519_impl_post.h" + int main(int argc,char **argv) { const char *outfile = 0; @@ -285,6 +449,7 @@ int main(int argc,char **argv) int realtimestats = 1; #endif int tret; + int basekeys = 0; if (sodium_init() < 0) { fprintf(stderr,"sodium_init() failed\n"); @@ -326,6 +491,45 @@ int main(int argc,char **argv) printversion(); exit(0); } + else if (!strcmp(arg,"combine")) { + combine(argc,argv); + exit(0); + } + else if (!strcmp(arg,"genbase")) { + if (argc != 2) { + printhelp(stdout,progname); + exit(0); + } + genbase(argv[0],argv[1]); + exit(0); + } + else if (!strcmp(arg,"basekey")) { + if (argc--) { + u8 base_pk[32]; + FILE *fp = fopen(*argv++, "r"); + if (!fp) { + perror("couldn't open basekey"); + exit(1); + } + if (fread(base_pk, 1, HEADER_BASEPKLEN, fp) != HEADER_BASEPKLEN) { + perror("read"); + exit(1); + } + if (memcmp(base_pk, HEADER_BASEPK, HEADER_BASEPKLEN) != 0) { + fprintf(stderr, "\"%s\" isn't a valid base public key.\n", argv[-1]); + exit(1); + } + if (fread(base_pk, 1, sizeof base_pk, fp) != sizeof base_pk) { + perror("read"); + exit(1); + } + fclose(fp); + ed25519_pubkey_addbase(base_pk); + basekeys++; + } else { + e_additional(); + } + } else if (!strcmp(arg,"rawyaml")) yamlraw = 1; #ifdef PASSPHRASE @@ -507,6 +711,11 @@ int main(int argc,char **argv) exit(1); } + if (yamloutput && 0 < basekeys) { + fprintf(stderr,"-y is incompatible with --basekey\n"); + exit(1); + } + #ifdef PASSPHRASE if (checkpointfile && !deterministic) { fprintf(stderr,"--checkpoint requires passphrase\n"); diff --git a/worker.c b/worker.c index 78376ce..9958be2 100644 --- a/worker.c +++ b/worker.c @@ -20,11 +20,15 @@ #include "ioutil.h" #include "common.h" #include "yaml.h" +#include "headers.h" #include "worker.h" #include "filters.h" +#include "ed25519/ed25519.h" +#include "ed25519/ed25519_impl_pre.h" + #ifndef _WIN32 #define FSZ "%zu" #else @@ -51,6 +55,9 @@ size_t numneedgenerate = 0; char *workdir = 0; size_t workdirlen = 0; +ge_p3 ALIGN(16) PUBKEY_BASE; +int pubkey_base_initialized = 0; + #ifdef PASSPHRASE // How many times we loop before a reseed @@ -60,6 +67,9 @@ pthread_mutex_t determseed_mutex; u8 determseed[SEED_LEN]; #endif +static int ed25519_pubkey_onbase(u8 *pk,const u8 *sk); +static void sanitycheck(const u8 *sk, const u8 *pk); + char *makesname(void) { @@ -88,16 +98,7 @@ static void onionready(char *sname,const u8 *secret,const u8 *pubonion) pthread_mutex_unlock(&keysgenerated_mutex); } - // disabled as this was never ever triggered as far as I'm aware -#if 0 - // Sanity check that the public key matches the private one. - ge_p3 ALIGN(16) point; - u8 testpk[PUBLIC_LEN]; - ge_scalarmult_base(&point,&secret[SKPREFIX_SIZE]); - ge_p3_tobytes(testpk,&point); - if (memcmp(testpk,&pubonion[PKPREFIX_SIZE],PUBLIC_LEN) != 0) - abort(); -#endif + sanitycheck(&secret[SKPREFIX_SIZE], &pubonion[PKPREFIX_SIZE]); if (!yamloutput) { if (createdir(sname,1) != 0) { @@ -107,11 +108,36 @@ static void onionready(char *sname,const u8 *secret,const u8 *pubonion) return; } - strcpy(&sname[onionendpos],"/hs_ed25519_secret_key"); - writetofile(sname,secret,FORMATTED_SECRET_LEN,1); + if (pubkey_base_initialized == 0) { + strcpy(&sname[onionendpos],"/hs_ed25519_secret_key"); + writetofile(sname,secret,FORMATTED_SECRET_LEN,1); - strcpy(&sname[onionendpos],"/hs_ed25519_public_key"); - writetofile(sname,pubonion,FORMATTED_PUBLIC_LEN,0); + strcpy(&sname[onionendpos],"/hs_ed25519_public_key"); + writetofile(sname,pubonion,FORMATTED_PUBLIC_LEN,0); + } else { + strcpy(&sname[onionendpos],"/halfkey"); + FILE *fp = fopen(sname,"w"); + if (!fp) { + perror("couldn't create output file"); + return; + } + if (fwrite(HEADER_HALFKEY,HEADER_HALFKEYLEN,1,fp) != 1) { + perror("couldn't write to output file"); + fclose(fp); + return; + } + if (fwrite(&secret[SKPREFIX_SIZE],SECRET_LEN,1,fp) != 1) { + perror("couldn't write to output file"); + fclose(fp); + return; + } + if (fwrite(&pubonion[PKPREFIX_SIZE],PUBLIC_LEN,1,fp) != 1) { + perror("couldn't write to output file"); + fclose(fp); + return; + } + fclose(fp); + } strcpy(&sname[onionendpos],"/hostname"); FILE *hfile = fopen(sname,"w"); @@ -212,10 +238,49 @@ static void reseedright(u8 sk[SECRET_LEN]) #define BATCHNUM 2048 #endif +#include "worker_impl.inc.h" // uses those globals -#include "ed25519/ed25519.h" +void ed25519_pubkey_addbase(const u8 base_pk[32]) +{ + ge_p3 ALIGN(16) A; + u8 tmp_pk[32]; -#include "worker_impl.inc.h" + ge_frombytes_negate_vartime(&A, base_pk); + // dumb hack: The only available frombytes function flips the point. + // To get the original point back, I can just pack and unpack it again. + ge_p3_tobytes(tmp_pk, &A); + ge_frombytes_negate_vartime(&A, tmp_pk); + + if (!pubkey_base_initialized) { + // note: PUBKEY_BASE could be initialized to the point at infinity + // to remove the need for pubkey_base_initialized. + pubkey_base_initialized = 1; + PUBKEY_BASE = A; + } else { + ge25519_add(&PUBKEY_BASE, &PUBKEY_BASE, &A); + } +} + +static int ed25519_pubkey_onbase(u8 *pk,const u8 *sk) +{ + ge_p3 ALIGN(16) A; + ge_scalarmult_base(&A, sk); + if (pubkey_base_initialized) { + ge25519_add(&A, &A, &PUBKEY_BASE); + } + ge_p3_tobytes(pk,&A); + return 0; +} + + +static void sanitycheck(const u8 *sk, const u8 *pk) { + u8 testpk[PUBLIC_LEN]; + ed25519_pubkey_onbase(testpk, sk); + if (memcmp(testpk,pk,PUBLIC_LEN) != 0) { + fprintf(stderr, "Sanity check failed. Please report this on Github, including the command line parameters you've used.\n"); + abort(); + } +} size_t worker_batch_memuse(void) { diff --git a/worker.h b/worker.h index 36912a0..24e5299 100644 --- a/worker.h +++ b/worker.h @@ -36,6 +36,7 @@ extern u8 determseed[SEED_LEN]; #endif extern void worker_init(void); +extern void ed25519_pubkey_addbase(const u8 base_pk[32]); extern char *makesname(void); extern size_t worker_batch_memuse(void); diff --git a/worker_batch.inc.h b/worker_batch.inc.h index 7eb5cd6..a6870e1 100644 --- a/worker_batch.inc.h +++ b/worker_batch.inc.h @@ -46,8 +46,10 @@ initseed: randombytes(seed,sizeof(seed)); ed25519_seckey_expand(sk,seed); - ge_scalarmult_base(&ge_public,sk); + if (pubkey_base_initialized) { + ge25519_add(&ge_public, &ge_public, &PUBKEY_BASE); + } for (counter = 0;counter < SIZE_MAX-(8*BATCHNUM);counter += 8*BATCHNUM) { ge_p1p1 ALIGN(16) sum; diff --git a/worker_batch_pass.inc.h b/worker_batch_pass.inc.h index 0fa49ff..856f311 100644 --- a/worker_batch_pass.inc.h +++ b/worker_batch_pass.inc.h @@ -54,6 +54,9 @@ initseed: ed25519_seckey_expand(sk,seed); ge_scalarmult_base(&ge_public,sk); + if (pubkey_base_initialized) { + ge25519_add(&ge_public, &ge_public, &PUBKEY_BASE); + } for (counter = oldcounter = 0;counter < DETERMINISTIC_LOOP_COUNT - (BATCHNUM - 1) * 8;counter += BATCHNUM * 8) { ge_p1p1 ALIGN(16) sum; diff --git a/worker_fast.inc.h b/worker_fast.inc.h index b9ec7a0..0c23637 100644 --- a/worker_fast.inc.h +++ b/worker_fast.inc.h @@ -41,8 +41,10 @@ initseed: randombytes(seed,sizeof(seed)); ed25519_seckey_expand(sk,seed); - ge_scalarmult_base(&ge_public,sk); + if (pubkey_base_initialized) { + ge25519_add(&ge_public, &ge_public, &PUBKEY_BASE); + } ge_p3_tobytes(pk,&ge_public); for (counter = 0;counter < SIZE_MAX-8;counter += 8) { diff --git a/worker_fast_pass.inc.h b/worker_fast_pass.inc.h index 793d600..ec90a6e 100644 --- a/worker_fast_pass.inc.h +++ b/worker_fast_pass.inc.h @@ -49,6 +49,9 @@ initseed: ed25519_seckey_expand(sk,seed); ge_scalarmult_base(&ge_public,sk); + if (pubkey_base_initialized) { + ge25519_add(&ge_public, &ge_public, &PUBKEY_BASE); + } ge_p3_tobytes(pk,&ge_public); for (counter = oldcounter = 0;counter < DETERMINISTIC_LOOP_COUNT;counter += 8) { diff --git a/worker_impl.inc.h b/worker_impl.inc.h index fbd7b0f..e886a65 100644 --- a/worker_impl.inc.h +++ b/worker_impl.inc.h @@ -1,6 +1,4 @@ -#include "ed25519/ed25519_impl_pre.h" - static size_t CRYPTO_NAMESPACE(worker_batch_memuse)(void) { return (sizeof(ge_p3) + sizeof(fe) + sizeof(bytes32)) * BATCHNUM; @@ -12,4 +10,3 @@ static size_t CRYPTO_NAMESPACE(worker_batch_memuse)(void) #include "worker_batch.inc.h" #include "worker_batch_pass.inc.h" -#include "ed25519/ed25519_impl_post.h" diff --git a/worker_slow.inc.h b/worker_slow.inc.h index ab75ef2..4ade24c 100644 --- a/worker_slow.inc.h +++ b/worker_slow.inc.h @@ -42,7 +42,7 @@ again: if (unlikely(endwork)) goto end; - ed25519_pubkey(pk,sk); + ed25519_pubkey_onbase(pk,sk); #ifdef STATISTICS ++st->numcalc.v;