/* Copyright (c) 2015 Ryan Castellucci, All Rights Reserved */
#include <time.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>

#include <openssl/sha.h>

#include <sys/time.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/sysinfo.h>

#include "ripemd160_256.h"

#include "ec_pubkey_fast.h"

#include "hex.h"
#include "bloom.h"
#include "mmapf.h"
#include "hash160.h"
#include "hsearchf.h"

#include "algo/brainv2.h"
#include "algo/warpwallet.h"
#include "algo/brainwalletio.h"
#include "algo/sha3.h"

// raise this if you really want, but quickly diminishing returns
#define BATCH_MAX 4096

static int brainflayer_is_init = 0;

typedef struct pubhashfn_s {
  void (*fn)(hash160_t *, const unsigned char *);
  char id;
} pubhashfn_t;

static unsigned char *mem;

static mmapf_ctx bloom_mmapf;
static unsigned char *bloom = NULL;

static unsigned char *unhexed = NULL;
static size_t unhexed_sz = 4096;

#define bail(code, ...)                         \
  do {                                          \
    fprintf(stderr, __VA_ARGS__);               \
    exit(code);                                 \
  } while (0)

#define chkmalloc(S) _chkmalloc(S, __FILE__, __LINE__)
static void * _chkmalloc(size_t size, unsigned char *file, unsigned int line) {
  void *ptr = malloc(size);
  if (ptr == NULL) {
    bail(1, "malloc(%zu) failed at %s:%u: %s\n", size, file, line, strerror(errno));
  }
  return ptr;
}

#define chkrealloc(P, S) _chkrealloc(P, S, __FILE__, __LINE__);
static void * _chkrealloc(void *ptr, size_t size, unsigned char *file, unsigned int line) {
  void *ptr2 = realloc(ptr, size);
  if (ptr2 == NULL) {
    bail(1, "realloc(%p, %zu) failed at %s:%u: %s\n", ptr, size, file, line, strerror(errno));
  }
  return ptr2;
}

uint64_t getns() {
  uint64_t ns;
  struct timespec ts;
  clock_gettime(CLOCK_MONOTONIC, &ts);
  ns  = ts.tv_nsec;
  ns += ts.tv_sec * 1000000000ULL;
  return ns;
}

static inline void brainflayer_init_globals() {
  /* only initialize stuff once */
  if (!brainflayer_is_init) {
    /* initialize buffers */
    mem = chkmalloc(4096);
    unhexed = chkmalloc(unhexed_sz);

    /* set the flag */
    brainflayer_is_init = 1;
  }
}

// function pointers
static int (*input2priv)(unsigned char *, unsigned char *, size_t);

/* bitcoin uncompressed address */
static void uhash160(hash160_t *h, const unsigned char *upub) {
  SHA256_CTX ctx;
  unsigned char hash[SHA256_DIGEST_LENGTH];

  SHA256_Init(&ctx);
  SHA256_Update(&ctx, upub, 65);
  SHA256_Final(hash, &ctx);
  ripemd160_256(hash, h->uc);
}

/* bitcoin compressed address */
static void chash160(hash160_t *h, const unsigned char *upub) {
  SHA256_CTX ctx;
  unsigned char cpub[33];
  unsigned char hash[SHA256_DIGEST_LENGTH];

  /* quick and dirty public key compression */
  cpub[0] = 0x02 | (upub[64] & 0x01);
  memcpy(cpub + 1, upub + 1, 32);
  SHA256_Init(&ctx);
  SHA256_Update(&ctx, cpub, 33);
  SHA256_Final(hash, &ctx);
  ripemd160_256(hash, h->uc);
}

/* ethereum address */
static void ehash160(hash160_t *h, const unsigned char *upub) {
  SHA3_256_CTX ctx;
  unsigned char hash[SHA256_DIGEST_LENGTH];

  /* compute hash160 for uncompressed public key */
  /* keccak_256_last160(pub) */
  KECCAK_256_Init(&ctx);
  KECCAK_256_Update(&ctx, upub+1, 64);
  KECCAK_256_Final(hash, &ctx);
  memcpy(h->uc, hash+12, 20);
}

/* msb of x coordinate of public key */
static void xhash160(hash160_t *h, const unsigned char *upub) {
  memcpy(h->uc, upub+1, 20);
}



static int pass2priv(unsigned char *priv, unsigned char *pass, size_t pass_sz) {
  SHA256_CTX ctx;

  SHA256_Init(&ctx);
  SHA256_Update(&ctx, pass, pass_sz);
  SHA256_Final(priv, &ctx);

  return 0;
}

static int keccak2priv(unsigned char *priv, unsigned char *pass, size_t pass_sz) {
  SHA3_256_CTX ctx;

  KECCAK_256_Init(&ctx);
  KECCAK_256_Update(&ctx, pass, pass_sz);
  KECCAK_256_Final(priv, &ctx);

  return 0;
}

/* ether.camp "2031 passes of SHA-3 (Keccak)" */
static int camp2priv(unsigned char *priv, unsigned char *pass, size_t pass_sz) {
  SHA3_256_CTX ctx;
  int i;

  KECCAK_256_Init(&ctx);
  KECCAK_256_Update(&ctx, pass, pass_sz);
  KECCAK_256_Final(priv, &ctx);

  for (i = 1; i < 2031; ++i) {
    KECCAK_256_Init(&ctx);
    KECCAK_256_Update(&ctx, priv, 32);
    KECCAK_256_Final(priv, &ctx);
  }

  return 0;
}

static int sha32priv(unsigned char *priv, unsigned char *pass, size_t pass_sz) {
  SHA3_256_CTX ctx;

  SHA3_256_Init(&ctx);
  SHA3_256_Update(&ctx, pass, pass_sz);
  SHA3_256_Final(priv, &ctx);

  return 0;
}

/*
  static int dicap2hash160(unsigned char *pass, size_t pass_sz) {
  SHA3_256_CTX ctx;

  int i, ret;

  KECCAK_256_Init(&ctx);
  KECCAK_256_Update(&ctx, pass, pass_sz);
  KECCAK_256_Final(priv256, &ctx);
  for (i = 0; i < 16384; ++i) {
  KECCAK_256_Init(&ctx);
  KECCAK_256_Update(&ctx, priv256, 32);
  KECCAK_256_Final(priv256, &ctx);
  }

  for (;;) {
  ret = priv2hash160(priv256);
  if (hash160_uncmp.uc[0] == 0) { break; }
  KECCAK_256_Init(&ctx);
  KECCAK_256_Update(&ctx, priv256, 32);
  KECCAK_256_Final(priv256, &ctx);
  }
  return ret;
  }
*/

static int rawpriv2priv(unsigned char *priv, unsigned char *rawpriv, size_t rawpriv_sz) {
  memcpy(priv, rawpriv, rawpriv_sz);
  return 0;
}

static unsigned char *kdfsalt;
static size_t kdfsalt_sz;

static int warppass2priv(unsigned char *priv, unsigned char *pass, size_t pass_sz) {
  int ret;
  if ((ret = warpwallet(pass, pass_sz, kdfsalt, kdfsalt_sz, priv)) != 0) return ret;
  pass[pass_sz] = 0;
  return 0;
}

static int bwiopass2priv(unsigned char *priv, unsigned char *pass, size_t pass_sz) {
  int ret;
  if ((ret = brainwalletio(pass, pass_sz, kdfsalt, kdfsalt_sz, priv)) != 0) return ret;
  pass[pass_sz] = 0;
  return 0;
}

static int brainv2pass2priv(unsigned char *priv, unsigned char *pass, size_t pass_sz) {
  unsigned char hexout[33];
  int ret;
  if ((ret = brainv2(pass, pass_sz, kdfsalt, kdfsalt_sz, hexout)) != 0) return ret;
  pass[pass_sz] = 0;
  return pass2priv(priv, hexout, sizeof(hexout)-1);
}

static unsigned char *kdfpass;
static size_t kdfpass_sz;

static int warpsalt2priv(unsigned char *priv, unsigned char *salt, size_t salt_sz) {
  int ret;
  if ((ret = warpwallet(kdfpass, kdfpass_sz, salt, salt_sz, priv)) != 0) return ret;
  salt[salt_sz] = 0;
  return 0;
}

static int bwiosalt2priv(unsigned char *priv, unsigned char *salt, size_t salt_sz) {
  int ret;
  if ((ret = brainwalletio(kdfpass, kdfpass_sz, salt, salt_sz, priv)) != 0) return ret;
  salt[salt_sz] = 0;
  return 0;
}

static int brainv2salt2priv(unsigned char *priv, unsigned char *salt, size_t salt_sz) {
  unsigned char hexout[33];
  int ret;
  if ((ret = brainv2(kdfpass, kdfpass_sz, salt, salt_sz, hexout)) != 0) return ret;
  salt[salt_sz] = 0;
  return pass2priv(priv, hexout, sizeof(hexout)-1);
}

static unsigned char rushchk[5];
static int rush2priv(unsigned char *priv, unsigned char *pass, size_t pass_sz) {
  SHA256_CTX ctx;
  unsigned char hash[SHA256_DIGEST_LENGTH];
  unsigned char userpasshash[SHA256_DIGEST_LENGTH*2+1];

  SHA256_Init(&ctx);
  SHA256_Update(&ctx, pass, pass_sz);
  SHA256_Final(hash, &ctx);

  hex(hash, sizeof(hash), userpasshash, sizeof(userpasshash));

  SHA256_Init(&ctx);
  // kdfsalt should be the fragment up to the !
  SHA256_Update(&ctx, kdfsalt, kdfsalt_sz);
  SHA256_Update(&ctx, userpasshash, 64);
  SHA256_Final(priv, &ctx);

  // early exit if the checksum doesn't match
  if (memcmp(priv, rushchk, sizeof(rushchk)) != 0) { return -1; }

  return 0;
}

inline static int priv_incr(unsigned char *upub, unsigned char *priv) {
  int sz;

  secp256k1_ec_pubkey_incr(upub, &sz, priv);

  return 0;
}

inline static void priv2pub(unsigned char *upub, const unsigned char *priv) {
  int sz;

  secp256k1_ec_pubkey_create_precomp(upub, &sz, priv);
}


inline static void fprintresult(FILE *f, hash160_t *hash,
                                unsigned char compressed,
                                unsigned char *type,
                                unsigned char *input) {
  unsigned char hexed0[41];

  fprintf(f, "%s:%c:%s:%s\n",
          hex(hash->uc, 20, hexed0, sizeof(hexed0)),
          compressed,
          type,
          input);
}

struct {
  float alpha, ilines_rate, ilines_rate_avg;
  uint64_t report_mask;
  uint64_t time_last, time_curr, time_delta;
  uint64_t time_start, time_elapsed;
  uint64_t ilines_last, ilines_curr, ilines_delta;
  uint64_t olines;
  int batch_stopped;
} stats;

void gen_stats(void) {
  stats.time_curr = getns();
  stats.time_delta = stats.time_curr - stats.time_last;
  stats.time_elapsed = stats.time_curr - stats.time_start;
  stats.time_last = stats.time_curr;
  stats.ilines_delta = stats.ilines_curr - stats.ilines_last;
  stats.ilines_last = stats.ilines_curr;
  stats.ilines_rate = (stats.ilines_delta * 1.0e9) / (stats.time_delta * 1.0);

  if (stats.batch_stopped < BATCH_MAX) {
    /* report overall average on last status update */
    stats.ilines_rate_avg = (stats.ilines_curr * 1.0e9) / (stats.time_elapsed * 1.0);
  } else if (stats.ilines_rate_avg < 0) {
    stats.ilines_rate_avg = stats.ilines_rate;
    /* target reporting frequency to about once every five seconds */
  } else if (stats.time_delta < 2500000000) {
    stats.report_mask = (stats.report_mask << 1) | 1;
    stats.ilines_rate_avg = stats.ilines_rate; /* reset EMA */
  } else if (stats.time_delta > 10000000000) {
    stats.report_mask >>= 1;
    stats.ilines_rate_avg = stats.ilines_rate; /* reset EMA */
  } else {
    /* exponetial moving average */
    stats.ilines_rate_avg = stats.alpha
      * stats.ilines_rate
      + (1 - stats.alpha)
      * stats.ilines_rate_avg;
  }
}

void print_stats(void) {
  fprintf(stderr,
          "rate: %9.2f p/s"
          " found: %5zu/%-10zu"
          " elapsed: %8.3f s",
          stats.ilines_rate_avg,
          stats.olines,
          stats.ilines_curr,
          stats.time_elapsed / 1.0e9
          );

  fflush(stderr);
}

void init_stats(void) {
  stats.time_start = stats.time_last = getns();
  stats.olines = stats.ilines_last = stats.ilines_curr = 0;
  stats.ilines_rate_avg = -1;
  stats.alpha = 0.500;
  stats.report_mask = 0;
  stats.batch_stopped = -1;
}

void sig_stats(int signo){
  if(signo == SIGUSR1) print_stats();
}

void usage(unsigned char *name) {
  printf("Usage: %s [OPTION]...\n\n\
 -a                          open output file in append mode\n\
 -b FILE                     check for matches against bloom filter FILE\n\
 -f FILE                     verify matches against sorted hash160s in FILE\n\
 -i FILE                     read from FILE instead of stdin\n\
 -o FILE                     write to FILE instead of stdout\n\
 -c TYPES                    use TYPES for public key to hash160 computation\n\
                             multiple can be specified, for example the default\n\
                             is 'uc', which will check for both uncompressed\n\
                             and compressed addresses using Bitcoin's algorithm\n\
                             u - uncompressed address\n\
                             c - compressed address\n\
                             e - ethereum address\n\
                             x - most signifigant bits of x coordinate\n\
 -t TYPE                     inputs are TYPE - supported types:\n\
                             sha256 (default) - classic brainwallet\n\
                             sha3   - sha3-256\n\
                             priv   - raw private keys (requires -x)\n\
                             warp   - WarpWallet (supports -s or -p)\n\
                             bwio   - brainwallet.io (supports -s or -p)\n\
                             bv2    - brainv2 (supports -s or -p) VERY SLOW\n\
                             rush   - rushwallet (requires -r) FAST\n\
                             keccak - keccak256 (ethercamp/old ethaddress)\n\
                             camp2  - keccak256 * 2031 (new ethercamp)\n\
 -x                          treat input as hex encoded\n\
 -s SALT                     use SALT for salted input types (default: none)\n\
 -p PASSPHRASE               use PASSPHRASE for salted input types, inputs\n\
                             will be treated as salts\n\
 -r FRAGMENT                 use FRAGMENT for cracking rushwallet passphrase\n\
 -I HEXPRIVKEY               incremental private key cracking mode, starting\n\
                             at HEXPRIVKEY (supports -n) FAST\n\
 -k K                        skip the first K lines of input\n\
 -n K/N                      use only the Kth of every N input lines\n\
 -B                          batch size for affine transformations\n\
                             must be a power of 2 (default/max: %d)\n\
 -w WINDOW_SIZE              window size for ecmult table (default: 16)\n\
                             uses about 3 * 2^w KiB memory on startup, but\n\
                             only about 2^w KiB once the table is built\n\
 -m FILE                     load ecmult table from FILE\n\
                             the ecmtabgen tool can build such a table\n\
 -v                          verbose - display cracking progress\n\
 -h                          show this help\n", name, BATCH_MAX);
  //q, --quiet                 suppress non-error messages
  exit(1);
}

int main(int argc, char **argv) {
  FILE *ifile = stdin;
  FILE *ofile = stdout;
  FILE *ffile = NULL;

  int ret, c, i, j;

  int64_t raw_lines = -1;

  int skipping = 0, tty = 0;

  unsigned char modestr[64];

  int spok = 0, aopt = 0, vopt = 0, wopt = 16, xopt = 0;
  int nopt_mod = 0, nopt_rem = 0, Bopt = 0;
  uint64_t kopt = 0;
  unsigned char *bopt = NULL, *iopt = NULL, *oopt = NULL;
  unsigned char *topt = NULL, *sopt = NULL, *popt = NULL;
  unsigned char *mopt = NULL, *fopt = NULL, *ropt = NULL;
  unsigned char *Iopt = NULL, *copt = NULL;

  unsigned char priv[64];
  hash160_t hash160;
  pubhashfn_t pubhashfn[8];
  memset(pubhashfn, 0, sizeof(pubhashfn));

  int batch_stopped = -1;
  char *batch_line[BATCH_MAX];
  size_t batch_line_sz[BATCH_MAX];
  int batch_line_read[BATCH_MAX];
  unsigned char batch_priv[BATCH_MAX][32];
  unsigned char batch_upub[BATCH_MAX][65];

  while ((c = getopt(argc, argv, "avxb:hi:k:f:m:n:o:p:s:r:c:t:w:I:B:")) != -1) {
    switch (c) {
    case 'a':
      aopt = 1; // open output file in append mode
      break;
    case 'k':
      kopt = strtoull(optarg, NULL, 10); // skip first k lines of input
      skipping = 1;
      break;
    case 'n':
      // only try the rem'th of every mod lines (one indexed)
      nopt_rem = atoi(optarg) - 1;
      optarg = strchr(optarg, '/');
      if (optarg != NULL) { nopt_mod = atoi(optarg+1); }
      skipping = 1;
      break;
    case 'B':
      Bopt = atoi(optarg);
      break;
    case 'w':
      if (wopt > 1) wopt = atoi(optarg);
      break;
    case 'm':
      mopt = optarg; // table file
      wopt = 1; // auto
      break;
    case 'v':
      vopt = 1; // verbose
      break;
    case 'b':
      bopt = optarg; // bloom filter file
      break;
    case 'f':
      fopt = optarg; // full filter file
      break;
    case 'i':
      iopt = optarg; // input file
      break;
    case 'o':
      oopt = optarg; // output file
      break;
    case 'x':
      xopt = 1; // input is hex encoded
      break;
    case 's':
      sopt = optarg; // salt
      break;
    case 'p':
      popt = optarg; // passphrase
      break;
    case 'r':
      ropt = optarg; // rushwallet
      break;
    case 'c':
      copt = optarg; // type of hash160
      break;
    case 't':
      topt = optarg; // type of input
      break;
    case 'I':
      Iopt = optarg; // start key for incremental
      xopt = 1; // input is hex encoded
      break;
    case 'h':
      // show help
      usage(argv[0]);
      return 0;
    case '?':
      // show error
      return 1;
    default:
      // should never be reached...
      printf("got option '%c' (%d)\n", c, c);
      return 1;
    }
  }

  if (optind < argc) {
    if (optind == 1 && argc == 2) {
      // older versions of brainflayer had the bloom filter file as a
      // single optional argument, this keeps compatibility with that
      bopt = argv[1];
    } else {
      fprintf(stderr, "Invalid arguments:\n");
      while (optind < argc) {
        fprintf(stderr, "    '%s'\n", argv[optind++]);
      }
      exit(1);
    }
  }

  if (nopt_rem != 0 || nopt_mod != 0) {
    // note that nopt_rem has had one subtracted at option parsing
    if (nopt_rem >= nopt_mod) {
      bail(1, "Invalid '-n' argument, remainder '%d' must be <= modulus '%d'\n", nopt_rem+1, nopt_mod);
    } else if (nopt_rem < 0) {
      bail(1, "Invalid '-n' argument, remainder '%d' must be > 0\n", nopt_rem+1);
    } else if (nopt_mod < 1) {
      bail(1, "Invalid '-n' argument, modulus '%d' must be > 0\n", nopt_mod);
    }
  }

  if (wopt < 1 || wopt > 28) {
    bail(1, "Invalid window size '%d' - must be >= 1 and <= 28\n", wopt);
  } else {
    // very rough sanity check of window size
    struct sysinfo info;
    sysinfo(&info);
    uint64_t sysram = info.mem_unit * info.totalram;
    if (3584LLU*(1<<wopt) > sysram) {
      bail(1, "Not enough ram for requested window size '%d'\n", wopt);
    }
  }

  if (Bopt) { // if unset, will be set later
    if (Bopt < 1 || Bopt > BATCH_MAX) {
      bail(1, "Invalid '-B' argument, batch size '%d' - must be >= 1 and <= %d\n", Bopt, BATCH_MAX);
    } else if (Bopt & (Bopt - 1)) { // https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2
      bail(1, "Invalid '-B' argument, batch size '%d' is not a power of 2\n", Bopt);
    }
  }

  if (Iopt) {
    if (strlen(Iopt) != 64) {
      bail(1, "The starting key passed to the '-I' must be 64 hex digits exactly\n");
    }
    if (topt) {
      bail(1, "Cannot specify input type in incremental mode\n");
    }
    topt = "priv";
    // normally, getline would allocate the batch_line entries, but we need to
    // do this to give the processing loop somewhere to write to in incr mode
    for (i = 0; i < BATCH_MAX; ++i) {
      batch_line[i] = Iopt;
    }
    unhex(Iopt, sizeof(priv)*2, priv, sizeof(priv));
    skipping = 1;
    if (!nopt_mod) { nopt_mod = 1; };
  }


  /* handle copt */
  if (copt == NULL) { copt = "uc"; }
  i = 0;
  while (copt[i]) {
    switch (copt[i]) {
    case 'u':
      pubhashfn[i].fn = &uhash160;
      break;
    case 'c':
      pubhashfn[i].fn = &chash160;
      break;
    case 'e':
      pubhashfn[i].fn = &ehash160;
      break;
    case 'x':
      pubhashfn[i].fn = &xhash160;
      break;
    default:
      bail(1, "Unknown hash160 type '%c'.\n", copt[i]);
    }
    if (strchr(copt + i + 1, copt[i])) {
      bail(1, "Duplicate hash160 type '%c'.\n", copt[i]);
    }
    pubhashfn[i].id = copt[i];
    ++i;
  }

  /* handle topt */
  if (topt == NULL) { topt = "sha256"; }

  if (strcmp(topt, "sha256") == 0) {
    input2priv = &pass2priv;
  } else if (strcmp(topt, "priv") == 0) {
    if (!xopt) {
      bail(1, "raw private key input requires -x");
    }
    input2priv = &rawpriv2priv;
  } else if (strcmp(topt, "warp") == 0) {
    if (!Bopt) { Bopt = 1; } // don't batch transform for slow input hashes by default
    spok = 1;
    input2priv = popt ? &warpsalt2priv : &warppass2priv;
  } else if (strcmp(topt, "bwio") == 0) {
    if (!Bopt) { Bopt = 1; } // don't batch transform for slow input hashes by default
    spok = 1;
    input2priv = popt ? &bwiosalt2priv : &bwiopass2priv;
  } else if (strcmp(topt, "bv2") == 0) {
    if (!Bopt) { Bopt = 1; } // don't batch transform for slow input hashes by default
    spok = 1;
    input2priv = popt ? &brainv2salt2priv : &brainv2pass2priv;
  } else if (strcmp(topt, "rush") == 0) {
    input2priv = &rush2priv;
  } else if (strcmp(topt, "camp2") == 0) {
    input2priv = &camp2priv;
  } else if (strcmp(topt, "keccak") == 0) {
    input2priv = &keccak2priv;
  } else if (strcmp(topt, "sha3") == 0) {
    input2priv = &sha32priv;
    //  } else if (strcmp(topt, "dicap") == 0) {
    //    input2priv = &dicap2priv;
  } else {
    bail(1, "Unknown input type '%s'.\n", topt);
  }

  if (spok) {
    if (sopt && popt) {
      bail(1, "Cannot specify both a salt and a passphrase\n");
    }
    if (popt) {
      kdfpass = popt;
      kdfpass_sz = strlen(popt);
    } else {
      if (sopt) {
        kdfsalt = sopt;
        kdfsalt_sz = strlen(kdfsalt);
      } else {
        kdfsalt = chkmalloc(0);
        kdfsalt_sz = 0;
      }
    }
  } else {
    if (popt) {
      bail(1, "Specifying a passphrase not supported with input type '%s'\n", topt);
    } else if (sopt) {
      bail(1, "Specifying a salt not supported with this input type '%s'\n", topt);
    }
  }

  if (ropt) {
    if (input2priv != &rush2priv) {
      bail(1, "Specifying a url fragment only supported with input type 'rush'\n");
    }
    kdfsalt = ropt;
    kdfsalt_sz = strlen(kdfsalt) - sizeof(rushchk)*2;
    if (kdfsalt[kdfsalt_sz-1] != '!') {
      bail(1, "Invalid rushwallet url fragment '%s'\n", kdfsalt);
    }
    unhex(kdfsalt+kdfsalt_sz, sizeof(rushchk)*2, rushchk, sizeof(rushchk));
    kdfsalt[kdfsalt_sz] = '\0';
  } else if (input2priv == &rush2priv) {
    bail(1, "The '-r' option is required for rushwallet.\n");
  }

  snprintf(modestr, sizeof(modestr), xopt ? "(hex)%s" : "%s", topt);

  if (bopt) {
    if ((ret = mmapf(&bloom_mmapf, bopt, BLOOM_SIZE, MMAPF_RNDRD)) != MMAPF_OKAY) {
      bail(1, "failed to open bloom filter '%s': %s\n", bopt, mmapf_strerror(ret));
    } else if (bloom_mmapf.mem == NULL) {
      bail(1, "got NULL pointer trying to set up bloom filter\n");
    }
    bloom = bloom_mmapf.mem;
  }

  if (fopt) {
    if (!bopt) {
      bail(1, "The '-f' option must be used with a bloom filter\n");
    }
    if ((ffile = fopen(fopt, "r")) == NULL) {
      bail(1, "failed to open '%s' for reading: %s\n", fopt, strerror(errno));
    }
  }

  if (iopt) {
    if ((ifile = fopen(iopt, "r")) == NULL) {
      bail(1, "failed to open '%s' for reading: %s\n", iopt, strerror(errno));
    }
    // increases readahead window, don't really care if it fails
    posix_fadvise(fileno(ifile), 0, 0, POSIX_FADV_SEQUENTIAL);
  }

  if (oopt && (ofile = fopen(oopt, (aopt ? "a" : "w"))) == NULL) {
    bail(1, "failed to open '%s' for writing: %s\n", oopt, strerror(errno));
  }

  /* line buffer output */
  setvbuf(ofile,  NULL, _IOLBF, 0);
  /* line buffer stderr */
  setvbuf(stderr, NULL, _IOLBF, 0);

  if (vopt && ofile == stdout && isatty(fileno(stdout))) { tty = 1; }

  brainflayer_init_globals();

  if (secp256k1_ec_pubkey_precomp_table(wopt, mopt) != 0) {
    bail(1, "failed to initialize precomputed table\n");
  }

  if (secp256k1_ec_pubkey_batch_init(BATCH_MAX) != 0) {
    bail(1, "failed to initialize batch point conversion structures\n");
  }

  if (vopt) {
    init_stats();
    if(signal(SIGUSR1, sig_stats) == SIG_ERR){
      fprintf(stderr, "Can't set sig_stat");
      fflush(stderr);
    }
  }

  // set default batch size
  if (!Bopt) { Bopt = BATCH_MAX; }

  for (;;) {
    if (Iopt) {
      if (skipping) {
        priv_add_uint32(priv, nopt_rem + kopt);
        skipping = 0;
      }
      secp256k1_ec_pubkey_batch_incr(Bopt, nopt_mod, batch_upub, batch_priv, priv);
      memcpy(priv, batch_priv[Bopt-1], 32);
      priv_add_uint32(priv, nopt_mod);

      batch_stopped = Bopt;
      stats.batch_stopped = batch_stopped;
    } else {
      for (i = 0; i < Bopt; ++i) {
        if ((batch_line_read[i] = getline(&batch_line[i], &batch_line_sz[i], ifile)-1) > -1) {
          if (skipping) {
            ++raw_lines;
            if (kopt && raw_lines < kopt) { --i; continue; }
            if (nopt_mod && raw_lines % nopt_mod != nopt_rem) { --i; continue; }
          }
        } else {
          break;
        }
        batch_line[i][batch_line_read[i]] = 0;
        if (xopt) {
          if (batch_line_read[i] / 2 > unhexed_sz) {
            unhexed_sz = batch_line_read[i];
            unhexed = chkrealloc(unhexed, unhexed_sz);
          }
          // rewrite the input line from hex
          unhex(batch_line[i], batch_line_read[i], unhexed, unhexed_sz);
          if (input2priv(batch_priv[i], unhexed, batch_line_read[i]/2) != 0) {
            fprintf(stderr, "input2priv failed! continuing...\n");
          }
        } else {
          if (input2priv(batch_priv[i], batch_line[i], batch_line_read[i]) != 0) {
            fprintf(stderr, "input2priv failed! continuing...\n");
          }
        }
      }

      // batch compute the public keys
      secp256k1_ec_pubkey_batch_create(Bopt, batch_upub, batch_priv);

      // save ending value from read loop
      batch_stopped = i;
      stats.batch_stopped = i;
    }

    // loop over the public keys
    for (i = 0; i < batch_stopped; ++i) {
      j = 0;
      if (bloom) { /* crack mode */
        // loop over pubkey hash functions
        while (pubhashfn[j].fn != NULL) {
          pubhashfn[j].fn(&hash160, batch_upub[i]);
          if (bloom_chk_hash160(bloom, hash160.ul)) {
            if (!fopt || hsearchf(ffile, &hash160)) {
              if (tty) { fprintf(ofile, "\033[0K"); }
              // reformat/populate the line if required
              if (Iopt) {
                hex(batch_priv[i], 32, batch_line[i], 65);
              }
              fprintresult(ofile, &hash160, pubhashfn[j].id, modestr, batch_line[i]);
              ++stats.olines;
            }
          }
          ++j;
        }
      } else { /* generate mode */
        // reformat/populate the line if required
        if (Iopt) {
          hex(batch_priv[i], 32, batch_line[i], 65);
        }
        while (pubhashfn[j].fn != NULL) {
          pubhashfn[j].fn(&hash160, batch_upub[i]);
          fprintresult(ofile, &hash160, pubhashfn[j].id, modestr, batch_line[i]);
          ++j;
        }
      }
    }
    // end public key processing loop

    // start stats
    if (vopt) {
      stats.ilines_curr += stats.batch_stopped;
      if (stats.batch_stopped < BATCH_MAX || (stats.ilines_curr & stats.report_mask) == 0) {
        gen_stats();
      }
    }
    // end stats

    // main loop exit condition
    if (batch_stopped < Bopt) {
      if (vopt) { fprintf(stderr, "\n"); }
      break;
    }
  }

  return 0;
}

/*  vim: set ts=2 sw=2 et ai si: */
