
#ifdef PASSPHRASE
void *worker_batch_pass(void *task)
{
	union pubonionunion pubonion;
	u8 * const pk = &pubonion.raw[PKPREFIX_SIZE];
	u8 secret[SKPREFIX_SIZE + SECRET_LEN];
	u8 * const sk = &secret[SKPREFIX_SIZE];
	u8 seed[SEED_LEN];
	u8 hashsrc[checksumstrlen + PUBLIC_LEN + 1];
	u8 wpk[PUBLIC_LEN + 1];
	ge_p3 ALIGN(16) ge_public;
	char *sname;

	// state to keep batch data
	ge_p3   ALIGN(16) ge_batch [BATCHNUM];
	fe      ALIGN(16) tmp_batch[BATCHNUM];
	bytes32 ALIGN(16) pk_batch [BATCHNUM];

	size_t counter,oldcounter;
	size_t i;

#ifdef STATISTICS
	struct statstruct *st = (struct statstruct *)task;
#else
	(void) task;
#endif

	PREFILTER

	memcpy(secret,skprefix,SKPREFIX_SIZE);
	wpk[PUBLIC_LEN] = 0;
	memset(&pubonion,0,sizeof(pubonion));
	memcpy(pubonion.raw,pkprefix,PKPREFIX_SIZE);
	// write version later as it will be overwritten by hash
	memcpy(hashsrc,checksumstr,checksumstrlen);
	hashsrc[checksumstrlen + PUBLIC_LEN] = 0x03; // version

	sname = makesname();

initseed:

#ifdef STATISTICS
	++st->numrestart.v;
#endif

	pthread_mutex_lock(&determseed_mutex);
	for (int i = 0; i < SEED_LEN; i++)
		if (++determseed[i])
			break;
	memcpy(seed, determseed, SEED_LEN);
	pthread_mutex_unlock(&determseed_mutex);

	ed25519_seckey_expand(sk,seed);

	ge_scalarmult_base(&ge_public,sk);

	for (counter = oldcounter = 0;counter < DETERMINISTIC_LOOP_COUNT - (BATCHNUM - 1) * 8;counter += BATCHNUM * 8) {
		ge_p1p1 ALIGN(16) sum;

		if (unlikely(endwork))
			goto end;


		for (size_t b = 0;b < BATCHNUM;++b) {
			ge_batch[b] = ge_public;
			ge_add(&sum,&ge_public,&ge_eightpoint);
			ge_p1p1_to_p3(&ge_public,&sum);
		}
		// NOTE: leaves unfinished one bit at the very end
		ge_p3_batchtobytes_destructive_1(pk_batch,ge_batch,tmp_batch,BATCHNUM);

#ifdef STATISTICS
		st->numcalc.v += BATCHNUM;
#endif

		for (size_t b = 0;b < BATCHNUM;++b) {
			DOFILTER(i,pk_batch[b],{
				if (numwords > 1) {
					shiftpk(wpk,pk_batch[b],filter_len(i));
					size_t j;
					for (int w = 1;;) {
						DOFILTER(j,wpk,goto secondfind);
						goto next;
					secondfind:
						if (++w >= numwords)
							break;
						shiftpk(wpk,wpk,filter_len(j));
					}
				}
				// found!
				// finish it up
				ge_p3_batchtobytes_destructive_finish(pk_batch[b],&ge_batch[b]);
				// copy public key
				memcpy(pk,pk_batch[b],PUBLIC_LEN);
				// update secret key with counter
				addsztoscalar32(sk,counter + (b * 8) - oldcounter);
				oldcounter = counter + (b * 8);
				// sanity check
				if ((sk[0] & 248) != sk[0] || ((sk[31] & 63) | 64) != sk[31])
					goto initseed;

				// reseed right half of key to avoid reuse, it won't change public key anyway
				reseedright(sk);

				ADDNUMSUCCESS;

				// calc checksum
				memcpy(&hashsrc[checksumstrlen],pk,PUBLIC_LEN);
				FIPS202_SHA3_256(hashsrc,sizeof(hashsrc),&pk[PUBLIC_LEN]);
				// version byte
				pk[PUBLIC_LEN + 2] = 0x03;
				// full name
				strcpy(base32_to(&sname[direndpos],pk,PUBONION_LEN),".onion");
				onionready(sname,secret,pubonion.raw);
				pk[PUBLIC_LEN] = 0; // what is this for?
			});
		next:
			;
		}
	}
	// continue if have leftovers, DETERMINISTIC_LOOP_COUNT - counter < BATCHNUM * 8
	// can't have leftovers in theory if BATCHNUM was power of 2 and smaller than DETERMINISTIC_LOOP_COUNT bound
#if (BATCHNUM & (BATCHNUM - 1)) || (BATCHNUM * 8) > DETERMINISTIC_LOOP_COUNT
	if (counter < DETERMINISTIC_LOOP_COUNT) {
		ge_p1p1 ALIGN(16) sum;

		if (unlikely(endwork))
			goto end;

		const size_t remaining = (DETERMINISTIC_LOOP_COUNT - counter) / 8;

		for (size_t b = 0;b < remaining;++b) {
			ge_batch[b] = ge_public;
			ge_add(&sum,&ge_public,&ge_eightpoint);
			ge_p1p1_to_p3(&ge_public,&sum);
		}
		// NOTE: leaves unfinished one bit at the very end
		ge_p3_batchtobytes_destructive_1(pk_batch,ge_batch,batchgez,tmp_batch,remaining);

#ifdef STATISTICS
		st->numcalc.v += remaining;
#endif

		for (size_t b = 0;b < remaining;++b) {
			DOFILTER(i,pk_batch[b],{
				if (numwords > 1) {
					shiftpk(wpk,pk_batch[b],filter_len(i));
					size_t j;
					for (int w = 1;;) {
						DOFILTER(j,wpk,goto secondfind2);
						goto next2;
					secondfind2:
						if (++w >= numwords)
							break;
						shiftpk(wpk,wpk,filter_len(j));
					}
				}
				// found!
				// finish it up
				ge_p3_batchtobytes_destructive_finish(pk_batch[b],&ge_batch[b]);
				// copy public key
				memcpy(pk,pk_batch[b],PUBLIC_LEN);
				// update secret key with counter
				addsztoscalar32(sk,counter + (b * 8) - oldcounter);
				oldcounter = counter + (b * 8);
				// sanity check
				if ((sk[0] & 248) != sk[0] || ((sk[31] & 63) | 64) != sk[31])
					goto initseed;

				// reseed right half of key to avoid reuse, it won't change public key anyway
				reseedright(sk);

				ADDNUMSUCCESS;

				// calc checksum
				memcpy(&hashsrc[checksumstrlen],pk,PUBLIC_LEN);
				FIPS202_SHA3_256(hashsrc,sizeof(hashsrc),&pk[PUBLIC_LEN]);
				// version byte
				pk[PUBLIC_LEN + 2] = 0x03;
				// full name
				strcpy(base32_to(&sname[direndpos],pk,PUBONION_LEN),".onion");
				onionready(sname,secret,pubonion.raw);
				pk[PUBLIC_LEN] = 0; // what is this for?
			});
		next2:
			;
		}
	}
#endif // (BATCHNUM & (BATCHNUM - 1)) || (BATCHNUM * 8) > DETERMINISTIC_LOOP_COUNT
	goto initseed;

end:
	free(sname);

	POSTFILTER

	sodium_memzero(secret,sizeof(secret));
	sodium_memzero(seed,sizeof(seed));

	return 0;
}
#endif // PASSPHRASE
