/**
    key management, including audio entropy collection and conversion of seed between binary and ASCII

    Copyright (c) 2020-2022 The Creators of Simphone

    See the file COPYING.LESSER.txt for copying permission.
**/

#include "config.h"
#include "spth.h"

#include "const.h"
#include "logger.h"
#include "table.h"
#include "error.h"
#include "utils.h"
#include "system.h"
#include "crypto.h"
#include "sssl.h"
#include "file.h"
#include "keygen.h"
#include "contact.h"
#include "param.h"
#include "proxy.h"
#include "api.h"

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>

#define SIM_MODULE "keygen"

#define CONTACT_ADDRESS_SYSTEM "SMPHNEHLPEY5A7GFTMLVPNK29V2C8YH9SFS6Y"

#define KEY_SEED_CHECK_BITS 31 /* number of seed checksum bytes */
#define KEY_SEED_WORD_BITS 6   /* number of bits per country name */
#define KEY_MAX_WORD_LENGTH 17 /* maximal country name length */
#define KEY_MAX_SEED_WORDS ((SIM_MAX_SEED_BITS + KEY_SEED_CHECK_BITS + KEY_SEED_WORD_BITS - 1) / KEY_SEED_WORD_BITS)

#define KEY_EC "ec"
#define KEY_RSA "rsa"
#define KEY_SEED "seed"
#define KEY_NO_SEED "SEED"
#define KEY_REVOKED "revoked"

struct key_time {
  simnumber cputime;
  simnumber realtime;
};

static simtype key_file;  /* keyed by KEY_EC / KEY_RSA, value is private key */
static simtype key_table; /* keyed by KEY_EC / KEY_RSA, value is public key */

static const simbyte *key_pointers[] = { key_2048, key_4096, key_8192, key_16384 };
static const unsigned *key_lengths[] = { &key_2048_size, &key_4096_size, &key_8192_size, &key_16384_size };

static simtype key_country_table; /* keyed by country string, value is index into key_countries (seed word value) */
static const char ***key_countries;
static int key_countries_size;

static simtype random_buffers[4];
static unsigned random_bytes[4], random_bits[4];

struct keygen_arg {
  unsigned *state; /* pointer to api_init_state */
  unsigned type;   /* SIM_KEY_BP512R1_xxx (as given to sim_key_generate_, but not SIM_KEY_NONE */
  simtype key;     /* copy of *seed as given to sim_key_generate_ or nil if nil was given */
  simtype seed;    /* *seed given to sim_key_generate_ converted to binary or nil if called with SIM_KEY_NONE */
};

static void key_load_countries (void) {
  const char **p = key_country_strings;
  int n = 0, m = 0;
  while (*p) {
    while (*p++)
      ++m;
    ++n;
  }
  key_countries = sim_new (key_countries_size = n * sizeof (**key_countries));
  key_country_table = table_new (m * 3);
  p = key_country_strings - 1;
  n = 0;
  while (*++p) {
    key_countries[n++] = p;
    while (*p)
      table_add_number (key_country_table, *p++, n);
  }
  if (n != 1 << KEY_SEED_WORD_BITS)
    LOG_FATAL_ (SIM_OK, "countries %d != %d\n", n, 1 << KEY_SEED_WORD_BITS);
}

static void key_unload_countries (void) {
  table_free (key_country_table);
  sim_free ((void *) key_countries, key_countries_size);
}

#define KEY_LOOKUP_DISTANCE(i, j) dd[(i) * (length2 + 2) + (j)]
#define KEY_MIN2(a, b) ((a) < (b) ? (a) : (b))
#define KEY_MIN3(a, b, c) ((a) < (b) ? KEY_MIN2 ((a), (c)) : KEY_MIN2 ((b), (c)))
#define KEY_MIN4(a, b, c, d) ((a) < (b) ? KEY_MIN3 ((a), (c), (d)) : KEY_MIN3 ((b), (c), (d)))

static int sim_convert_strings_to_distance (const simbyte *string1, int length1,
                                            const simbyte *string2, int length2) {
  int i, j, dist, i1, j1, db, infinity = length1 + length2, len;
  int *dd = sim_new (len = (length1 + 2) * (length2 + 2) * sizeof (*dd)), da[256];
  KEY_LOOKUP_DISTANCE (0, 0) = infinity;
  for (i = 0; i < length1 + 1; i++) {
    KEY_LOOKUP_DISTANCE (i + 1, 1) = i;
    KEY_LOOKUP_DISTANCE (i + 1, 0) = infinity;
  }
  for (j = 0; j < length2 + 1; j++) {
    KEY_LOOKUP_DISTANCE (1, j + 1) = j;
    KEY_LOOKUP_DISTANCE (0, j + 1) = infinity;
  }
  memset (da, 0, sizeof (da));
  for (i = 1; i < length1 + 1; i++) {
    db = 0;
    for (j = 1; j < length2 + 1; j++) {
      i1 = da[string2[j - 1]];
      j1 = db;
      if ((dist = string1[i - 1] != string2[j - 1]) == 0)
        db = j;
      KEY_LOOKUP_DISTANCE (i + 1, j + 1) = KEY_MIN4 (KEY_LOOKUP_DISTANCE (i, j) + dist,
                                                     KEY_LOOKUP_DISTANCE (i + 1, j) + 1,
                                                     KEY_LOOKUP_DISTANCE (i, j + 1) + 1,
                                                     KEY_LOOKUP_DISTANCE (i1, j1) + (i - i1 - 1) + 1 + (j - j1 - 1));
    }
    da[string1[i - 1]] = i;
  }
  dist = KEY_LOOKUP_DISTANCE (length1 + 1, length2 + 1);
  sim_free (dd, len);
  return dist;
}

#define KEY_CALC_LAST_DIGIT(digits) (((digits) / 8 * 2 + (digits) % 8) % 9 + 1)

simtype convert_seed_to_string (const simtype seed, unsigned type, simbool numeric) {
  simtype hash, buffer = string_buffer_new ((seed.len * 8 + KEY_SEED_CHECK_BITS + KEY_SEED_WORD_BITS - 1 + 7) / 8);
  simbyte *p = buffer.str, t = (simbyte) type;
  unsigned l = 0;
  int a, b = 0, count = (seed.len * 8 + KEY_SEED_CHECK_BITS + KEY_SEED_WORD_BITS - 1) / KEY_SEED_WORD_BITS;
  simtype buf = t == type ? string_buffer_new ((KEY_MAX_WORD_LENGTH + 1) * count) : nil ();
  void *md = sim_crypt_md_new (CRYPT_MD_SHA);
  memcpy (buffer.str, seed.str, seed.len);
  hash = sim_crypt_md_hash (md, nil (), pointer_new_len (seed.str, seed.len), pointer_new_len (&t, sizeof (t)));
  memcpy (buffer.str + seed.len, hash.str, buffer.len - seed.len);
  key_load_countries ();
  for (a = *p++; count; count--) {
    int n = 0, c = KEY_SEED_WORD_BITS;
    while (c--) {
      n <<= 1;
      if (a & 128)
        ++n;
      a <<= 1;
      if (! (++b & 7))
        a = *p++;
    }
    if (! numeric) {
      strcpy ((char *) buf.str + l, *key_countries[n]);
    } else
      sprintf ((char *) buf.str + l, "%02o%d", n, KEY_CALC_LAST_DIGIT (n));
    l = strlen (buf.ptr);
    buf.str[l++] = '-';
  }
  key_unload_countries ();
  string_buffer_free (buffer);
  string_free (hash);
  return string_buffer_truncate (buf, l ? l - 1 : 0);
}

static int sim_convert_seed_type (const int *values, int count, simbyte type, simbyte *seed) {
  simbyte *p = seed;
  int a = 0, b = 0, i, l = (count * KEY_SEED_WORD_BITS - KEY_SEED_CHECK_BITS) / 8;
  simtype hash;
  if (l > 0) {
    void *md = sim_crypt_md_new (CRYPT_MD_SHA);
    for (i = 0; i < count; i++) {
      int v = values[i], c = KEY_SEED_WORD_BITS;
      while (c--) {
        a <<= 1;
        if (v & 1 << (KEY_SEED_WORD_BITS - 1))
          a++;
        v <<= 1;
        if (! (++b & 7)) {
          *p++ = (simbyte) a;
          a = 0;
        }
      }
    }
    if (b & 7)
      *p++ = (simbyte) (a << (8 - (b & 7)));
    hash = sim_crypt_md_hash (md, nil (), pointer_new_len (seed, l), pointer_new_len (&type, sizeof (type)));
    i = b - l * 8 - KEY_SEED_CHECK_BITS / 8 * 8 - 1; /* number of rest high bits (7 to 13) to compare - 1 */
    a = (hash.str[KEY_SEED_CHECK_BITS / 8] << 8 | hash.str[KEY_SEED_CHECK_BITS / 8 + 1]) & -(1 << (15 - i));
    b = (seed[l + KEY_SEED_CHECK_BITS / 8] << 8 | seed[l + KEY_SEED_CHECK_BITS / 8 + 1]) & -(1 << (15 - i));
    if (a != b || memcmp (seed + l, hash.ptr, KEY_SEED_CHECK_BITS / 8))
      l = 0;
    string_free (hash);
  }
  return l > 0 ? l : 0;
}

static unsigned sim_convert_seed (const int *values, int count, simbool exact, unsigned *type, simbyte *seed) {
  unsigned len, mintype = exact ? 0 : SIM_KEY_BP512R1_RSA2K, maxtype = 255;
  if (! exact)
    maxtype = count < 32 ? SIM_KEY_BP512R1_RSA4K : count < 41 ? SIM_KEY_BP512R1_RSA8K : SIM_KEY_BP512R1_RSA16K;
  for (*type = mintype; *type <= maxtype; ++*type)
    if ((len = sim_convert_seed_type (values, count, (simbyte) *type, seed)) != 0)
      return len;
  *type = SIM_KEY_NONE;
  return 0;
}

simtype convert_string_to_seed (const char *string, unsigned *type) {
  int i, j, count = 0, distance = 0, maxdist = 0, retry = 0, maxretry = ((unsigned) 1 << KEY_SEED_CHECK_BITS) / 1000;
  simtype seed, ascii;
  const char *s = string;
  char *t, *words[KEY_MAX_SEED_WORDS];
  int values[KEY_MAX_SEED_WORDS + 2], *distances[KEY_MAX_SEED_WORDS], tmp[KEY_MAX_SEED_WORDS];
  unsigned seedlen;
  *type = SIM_KEY_NONE;
  if (! s)
    return nil ();
  /* convert UTF-8 string to lowercase ASCII */
  for (t = (ascii = string_buffer_new (strlen (s) + 1)).ptr; *s; s++)
    if ((simbyte) *s < 128) {
      *t++ = (char) tolower (*s);
    } else if ((simbyte) *s == 195 && (simbyte) s[1] >= 128 && (simbyte) s[1] <= 158) {
      *t++ = *s;
      *t++ = *++s + 32;
    }
  *t = 0;
  /* split ASCII string to words */
  t = ascii.ptr;
  while (*t) {
    if (count == KEY_MAX_SEED_WORDS) {
      string_buffer_free (ascii);
      return nil ();
    }
    while (*t == '-' || isspace (*t))
      ++t;
    words[count++] = t;
    while (*t && *t != '-' && ! isspace (*t))
      ++t;
    if (*t) {
      *t++ = 0;
    } else if (! *words[count - 1])
      --count;
  }
  /* convert words to values or to distances to each possible value, if exact value not found */
  key_load_countries ();
  memset (distances, 0, sizeof (distances));
  for (i = 0; i < count; i++) {
    values[i + 1] = (int) table_get_number (key_country_table, words[i]) - 1;
    if (isdigit (*words[i])) {
      j = atoi (words[i]);
      while (j >= 1000)
        j /= 10;
      values[i + 1] = j / 100 * 8 + j / 10 % 10;
      if (strlen (words[i]) < 3 && j < 80 && j % 10 < 8) {
        values[i + 1] = j / 10 * 8 + j % 10;
      } else if (j >= 800 || j / 10 % 10 >= 8 || KEY_CALC_LAST_DIGIT (values[i + 1]) != j % 10) {
        int *dist = distances[i] = sim_new ((1 << KEY_SEED_WORD_BITS) * sizeof (*dist));
        for (j = 0; j < 1 << KEY_SEED_WORD_BITS; j++) {
          char word[5];
          sprintf (word, "%02o%d", j, KEY_CALC_LAST_DIGIT (j));
          dist[j] = sim_convert_strings_to_distance ((simbyte *) words[i], strlen (words[i]),
                                                     (simbyte *) word, strlen (word));
          if (dist[j] > maxdist)
            maxdist = dist[j];
        }
        values[i + 1] = -1;
      }
    } else if (values[i + 1] < 0) {
      int *dist = distances[i] = sim_new ((1 << KEY_SEED_WORD_BITS) * sizeof (*dist));
      const char **names = key_country_strings;
      while (*names) {
        int mindist = 1 << 30;
        while (*names) {
          int d = sim_convert_strings_to_distance ((simbyte *) words[i], strlen (words[i]),
                                                   (const simbyte *) *names, strlen (*names));
          if (d < mindist)
            mindist = d;
          names++;
        }
        *dist++ = mindist;
        if (mindist > maxdist)
          maxdist = mindist;
        names++;
      }
    }
  }
  /* start trying to match with all possible values in order of likelihood up to the retry limit */
  maxretry = count < 32 ? maxretry / 2 : count < 41 ? maxretry * 3 / 8 : maxretry / 4;
  seed = string_buffer_new (2 + (count * KEY_SEED_WORD_BITS + 7) / 8);
  seedlen = 0;
restart:
  while (retry < maxretry && ++distance <= maxdist) {
    for (i = 0; i < count; i++)
      if (distances[i]) {
        tmp[i] = -1;
        for (j = 0; j < 1 << KEY_SEED_WORD_BITS; j++)
          if (distances[i][j] <= distance) {
            tmp[i] = j;
            break;
          }
        if (tmp[i] < 0)
          goto restart;
      }
    do {
      for (i = 0; i < count; i++)
        if (distances[i])
          values[i + 1] = tmp[i];
      if ((seedlen = sim_convert_seed (values + 1, count, false, type, seed.str)) != 0)
        goto done; /* got a match */
      if ((retry += count) >= maxretry)
        break;
      for (i = 0; i < count; i++)
        if (distances[i]) {
          j = tmp[i];
          tmp[i] = -1;
          while (++j < 1 << KEY_SEED_WORD_BITS)
            if (distances[i][j] <= distance) {
              tmp[i] = j;
              break;
            }
          if (tmp[i] >= 0)
            break;
          for (j = 0; j < 1 << KEY_SEED_WORD_BITS; j++)
            if (distances[i][j] <= distance) {
              tmp[i] = j;
              break;
            }
        }
    } while (i < count);
  }
  if (maxdist || ! count) { /* report words that were not matched */
    string_buffer_free (seed);
    seed = string_buffer_new (strlen (string) + 2);
    seed.str[0] = 0;
    for (i = 0; i < count; i++)
      if (distances[i])
        strcat (strcat (seed.ptr, words[i]), " ");
    seedlen = strlen (seed.ptr);
  } else if ((seedlen = sim_convert_seed (values + 1, count, true, type, seed.str)) == 0) {
    for (i = 1; i <= count; i++) { /* try to correct a replaced word */
      int n = values[i];
      for (j = 0; j < 1 << KEY_SEED_WORD_BITS; j++) {
        values[i] = j;
        if ((seedlen = sim_convert_seed (values + 1, count, false, type, seed.str)) != 0)
          goto done;
      }
      values[i] = n;
    }
    for (i = 0; i <= count; i++) /* try to insert a deleted word */
      for (j = 0; j < 1 << KEY_SEED_WORD_BITS; j++) {
        values[i] = j;
        if ((seedlen = sim_convert_seed (values, count + 1, false, type, seed.str)) != 0)
          goto done;
        values[i] = values[i + 1];
      }
    for (i = 0; i < count; i++) { /* try to delete an inserted word */
      for (j = 0; j < count - 1; j++)
        tmp[j] = values[j < i ? j : j + 1];
      if ((seedlen = sim_convert_seed (tmp, count - 1, false, type, seed.str)) != 0)
        goto done;
    }
    for (i = 0; i < count - 1; i++) { /* try to correct a swapped word */
      j = values[i];
      values[i] = values[i + 1];
      values[i + 1] = j;
      if ((seedlen = sim_convert_seed (values, count, false, type, seed.str)) != 0)
        goto done;
      values[i + 1] = values[i];
      values[i] = j;
    }
    memset (seed.str, 0, (count * KEY_SEED_WORD_BITS + 7) / 8 + 2);
  }
done:
  memset (tmp, 0, sizeof (tmp));
  memset (values, 0, sizeof (values));
  memset (words, 0, sizeof (words));
  while (count--)
    sim_free (distances[count], (1 << KEY_SEED_WORD_BITS) * sizeof (*distances[count]));
  string_buffer_free (ascii);
  key_unload_countries ();
  return string_buffer_truncate (seed, seedlen);
}

void random_open_seed (simrandom generator, const simtype seed, const simtype entropy, int part, int parts) {
  unsigned len = seed.len / parts + (part == parts ? seed.len % parts : 0), keysize;
  simbyte *buf = seed.str + (part - 1) * len;
  int i = -3;
  const char *cipher;
  simtype seeds = table_new (2), hash;
  /*LOG_XTRA_SIMTYPE_ (pointer_new_len (buf, len), LOG_BIT_BIN, "seed ");*/
  while ((cipher = sim_crypt_cipher_get (++i, &keysize, NULL)) != NULL)
    if (i != 1 && keysize >= RANDOM_SIZE_BLOCK * 2) {
      unsigned blocksize = i < 0 ? RANDOM_SIZE_HASH : RANDOM_SIZE_BLOCK;
      simtype padded = string_buffer_new (i < 0 ? RANDOM_SIZE_HASH + len : RANDOM_SIZE_BLOCK + (len + 7) / 8 * 8);
      void *md = sim_crypt_md_new (CRYPT_MD_SHA2);
      if (entropy.typ != SIMNIL) {
        hash = sim_crypt_md_hash (md, nil (), nil (), pointer_new_len (buf + RANDOM_SIZE_HASH, len - RANDOM_SIZE_HASH));
      } else if (len >= keysize + blocksize) {
        hash = sim_crypt_md_hash (md, nil (), nil (), pointer_new_len (buf + keysize, len - keysize));
      } else
        hash = sim_crypt_md_hash (md, nil (), nil (), pointer_new_len (buf, len));
      if (hash.len < blocksize) { /* impossible */
        LOG_FATAL_ (SIM_OK, "random %s %u < %u\n", cipher, hash.len, blocksize);
        return;
      }
      memset (padded.str, 0, padded.len);
      memcpy (padded.str, hash.str, blocksize);
      memcpy (padded.str + blocksize, buf, len);
      string_free (hash);
      keysize += blocksize;
      /*LOG_XTRA_SIMTYPES_ (pointer_new_len (padded.str, blocksize),
                            pointer_new_len (padded.str + blocksize,
                                             (keysize < padded.len ? keysize : padded.len) - blocksize),
                            LOG_BIT_BIN, "seed %s ", cipher);*/
      table_add (seeds, cipher, string_buffer_truncate (padded, keysize < padded.len ? keysize : padded.len));
    }
  random_open (generator, seeds, entropy.str, entropy.len);
  table_free (seeds);
}

static unsigned sim_random_buffer_debias (const simbyte *input, unsigned length, simbyte *output) {
  int bits = 8;
  simbyte *buf = output;
  while (length--) { /* de-bias bits like Neumann Janos */
    int byt = *input++;
    while (byt) {
      int bit = byt & 3;
      if (bit == 1 || bit == 2) {
        if (bits == 8)
          bits = *output++ = 0;
        output[-1] |= (simbyte) ((bit & 1) << bits++);
      }
      byt >>= 2;
    }
  }
  return output - buf;
}

void random_buffer_init (simrandom generator) {
  random_buffers[RANDOM_CASE_INDEX (generator)] = nil ();
}

static simbool random_buffer_check_entropy (simrandom generator, unsigned parts) {
  int i = RANDOM_CASE_INDEX (generator);
  if (random_bytes[i] >= RANDOM_SIZE_ENTROPY * RANDOM_ENTROPY_FACTOR * parts)
    if (random_bits[i] >= RANDOM_SIZE_ENTROPY * 8 * RANDOM_ENTROPY_FACTOR * parts)
      return true;
#ifdef DONOT_DEFINE /* fill entropy buffer with zeros - do not ever enable except for testing */
  string_buffer_append (&random_buffers[i], random_bytes[i],
                        RANDOM_SIZE_ENTROPY * RANDOM_ENTROPY_FACTOR * parts - random_bytes[i]);
  random_bits[i] = (random_bytes[i] = RANDOM_SIZE_ENTROPY * RANDOM_ENTROPY_FACTOR * parts) * 8;
  return true;
#else
  return false;
#endif
}

unsigned random_buffer_add_bits (simrandom generator, int bits, unsigned *bytes) {
  int i = RANDOM_CASE_INDEX (generator);
  if (bytes)
    *bytes = random_bytes[i];
  return random_bits[i] += bits;
}

void random_buffer_add (simrandom generator, const void *input, unsigned length, unsigned maxlen) {
  int i = RANDOM_CASE_INDEX (generator);
  string_buffer_append (&random_buffers[i], random_bytes[i], maxlen);
  random_bytes[i] += sim_random_buffer_debias (input, length, random_buffers[i].str + random_bytes[i]);
}

void random_buffer_xor (simrandom generator, const simbyte *input, unsigned length) {
  int i = RANDOM_CASE_INDEX (generator);
  unsigned l = length, len = random_bytes[i], done = l && len ? 0 : 3;
  while (done < 3) {
    random_buffers[i].str[--len] ^= *input++;
    if (! --l) {
      done |= 1;
      input -= (l = length);
    }
    if (! len) {
      done |= 2;
      len = random_bytes[i];
    }
  }
}

simbool random_buffer_open (simrandom generator, unsigned part, unsigned parts) {
  void **rng = generator == random_seeded ? random_private : generator;
  if (! random_buffer_check_entropy (rng, parts))
    return false;
  if (part) {
    unsigned size;
    int i = RANDOM_CASE_INDEX (rng);
    simbyte *buf = random_buffers[i].str + (part - 1) * (size = random_bytes[i] / parts);
    simtype saved = nil ();
    if (part == parts)
      size += random_bytes[i] % parts;
    if (generator == random_session)
      saved = random_get_state (generator);
    random_open (generator, saved, buf, size);
    if (generator == random_session)
      table_free (saved);
    memset (buf, 0, size);
    if (part == parts)
      random_buffer_close (rng);
  }
  return true;
}

void random_buffer_close (simrandom generator) {
  int i = RANDOM_CASE_INDEX (generator);
  random_bits[i] = random_bytes[i] = 0;
  if (random_buffers[i].ptr)
    string_buffer_free (random_buffers[i]);
  random_buffers[i] = nil ();
}

static void key_time_start (struct key_time *timer) {
  timer->cputime = sim_system_cpu_get (SYSTEM_CPU_TIME_THREAD, NULL);
  timer->realtime = system_get_tick ();
}

static int key_time_stop (struct key_time *timer, int factor) {
  int max = param_get_min ("socket.recv", 0);
  struct key_time now;
  key_time_start (&now);
  timer->cputime = (now.cputime - timer->cputime) / 1000;
  timer->realtime = (now.realtime - timer->realtime) * 1000;
  if (timer->cputime >= max * factor * 1000000)
    return SIM_KEY_TOO_LARGE;
  if (timer->realtime >= max * factor * 1000000)
    return SIM_KEY_TOO_SLOW;
  return SIM_OK;
}

simtype key_get (simbool myself, simtype *ec, simtype *rsa) {
  if (! myself) {
    *ec = pointer_new_len (key_system_ec, key_system_ec_size);
    *rsa = pointer_new_len (key_system_rsa, key_system_rsa_size);
  } else {
    if (! key_table.len)
      return *rsa = *ec = nil ();
    *ec = table_get_string (key_table, KEY_EC);
    *rsa = table_get_string (key_table, KEY_RSA);
  }
  return key_file.len ? table_get_string (key_file, KEY_SEED) : nil ();
}

static int sim_key_set__ (const simtype eckey, const simtype rsakey, simtype *key, simtype *address) {
  int err = SIM_SSL_ERROR;
  void *keys[2] = { NULL, NULL };
  if ((keys[SSL_KEY_EC] = sim_ssl_new_key__ (eckey, SSL_KEY_EC, false)) == NULL) {
    LOG_ERROR_ ("failed to load EC key\n");
  } else if ((keys[SSL_KEY_RSA] = sim_ssl_new_key__ (rsakey, SSL_KEY_RSA, false)) == NULL) {
    LOG_ERROR_ ("failed to load RSA key\n");
  } else if (address) {
    simtype ec = sim_ssl_convert_key__ (keys[SSL_KEY_EC], SSL_KEY_EC);
    simtype rsa = sim_ssl_convert_key__ (keys[SSL_KEY_RSA], SSL_KEY_RSA);
    if (ec.typ != SIMNIL && rsa.typ != SIMNIL) {
      table_add (key_table = table_new (2), KEY_EC, ec);
      table_add (key_table, KEY_RSA, rsa);
      *key = sim_crypt_md_hash_address (sim_crypt_md_new (CRYPT_MD_SHA), sim_crypt_md_new (CRYPT_MD_RIPEMD), ec, rsa);
      *address = sim_contact_convert_to_address (*key, 'S');
      LOG_XTRA_ ("address = %s\n", address->str);
      LOG_INFO_ ("id = @%020llu\n", sim_contact_convert_to_id (address->ptr, *key));
      string_free (*key);
      *key = string_new (eckey.len + rsakey.len);
      memcpy (key->str, eckey.str, eckey.len);
      memcpy (key->str + eckey.len, rsakey.str, rsakey.len);
      err = SIM_OK;
    } else
      LOG_ERROR_ ("%s public key is missing\n", ec.typ == SIMNIL ? "EC" : "RSA");
  } else
    err = sim_ssl_set_keys__ (keys[SSL_KEY_EC], keys[SSL_KEY_RSA]);
  sim_ssl_free_key__ (keys[SSL_KEY_EC]);
  sim_ssl_free_key__ (keys[SSL_KEY_RSA]);
  return err;
}

static int key_set_ (const simtype key, simtype *address) {
  int err = SIM_KEY_NO_PRIVATE;
  simtype eckey = table_get_string (key, KEY_EC), rsakey = table_get_string (key, KEY_RSA);
  if (eckey.typ != SIMNIL && rsakey.typ != SIMNIL) {
    simtype tmp = nil ();
    if (address)
      random_open_seed (random_session, rsakey, nil (), 1, 1);
    PTH_UNPROTECT_PROTECT_ (err = sim_key_set__ (eckey, rsakey, &tmp, address));
    if (address)
      random_close (random_session);
    if (tmp.typ != SIMNIL)
      file_set_password (tmp, false);
  } else
    LOG_ERROR_ ("%s private key is missing\n", eckey.typ == SIMNIL ? "EC" : "RSA");
  return err;
}

simtype key_generate_seed (int size) {
  simtype seed = string_new ((size + 7) / 8);
  int parts = (seed.len - 1) / RANDOM_SIZE_BLOCK + 1, part;
  for (part = 1; part <= parts; part++) {
    unsigned len = part == parts ? seed.len - (part - 1) * RANDOM_SIZE_BLOCK : RANDOM_SIZE_BLOCK;
    if (! random_buffer_open (random_private, part, parts)) {
      string_free (seed);
      return nil ();
    }
    random_get (random_private, pointer_new_len (seed.str + (part - 1) * RANDOM_SIZE_BLOCK, len));
    random_close (random_private);
  }
  return seed;
}

static int key_generate_ (simnumber *mseconds, const simtype seed, int size, simtype *address) {
  int err = SIM_KEY_NO_ENTROPY;
  key_uninit ();
  key_file = table_new (2);
  if (seed.len) {
    unsigned minbytes = size > 8192 ? 32 : size > 4096 ? 24 : 16;
    simbool triple = seed.len >= minbytes * 3;
    if (seed.len >= minbytes * 2) {
      random_open_seed (random_private, seed, nil (), 1, triple ? 3 : 2);
      random_open_seed (random_seeded, seed, nil (), 2, triple ? 3 : 2);
      table_add (key_file, KEY_RSA, random_new_rsa_ (random_seeded, mseconds, size));
      random_close (random_seeded);
      random_open_seed (random_private, seed, nil (), triple ? 3 : 1, triple ? 3 : 1);
    } else {
      random_open_seed (random_private, seed, nil (), 1, 1);
      table_add (key_file, KEY_RSA, random_new_rsa_ (random_private, mseconds, size));
    }
    goto ec;
  } else if (random_buffer_open (random_private, 1, 3) && random_buffer_open (random_seeded, 2, 3)) {
    table_add (key_file, KEY_RSA, random_new_rsa_ (random_seeded, mseconds, size));
    random_close (random_seeded);
    if (random_buffer_open (random_private, 3, 3)) {
    ec:
      table_add (key_file, KEY_EC, random_new_ec_ (random_private, false));
      err = SIM_OK;
    }
    random_close (random_private);
  }
  if (err == SIM_OK)
    err = key_set_ (key_file, address);
  if (err != SIM_OK)
    key_uninit ();
  return err;
}

static int event_send_keygen_ (int error, unsigned type, simnumber *seconds) {
  struct key_time timer;
  if (type != SIM_KEY_NONE) {
    simbyte zero[RANDOM_SIZE_BLOCK];
    memset (zero, 0, sizeof (zero));
    random_open_seed (random_public, pointer_new_len (&zero, sizeof (zero)), nil (), 1, 1);
    key_time_start (&timer);
    random_test_key_ (random_public, pointer_new_len (key_pointers[type], *key_lengths[type]), 2, SSL_KEY_RSA);
    random_close (random_public);
  }
  if (type != SIM_KEY_NONE && (error = key_time_stop (&timer, 2)) != SIM_KEY_TOO_LARGE) {
    int factor = (11 - type) << 4; /* adjust validation time for generation of two random primes */
    *seconds = timer.realtime * ((SSL_RSA_BITS_MIN << type) + 3 * factor) / (factor * 1000000) + 1;
    event_send_value (NULL, SIM_EVENT_KEYGEN, SIM_EVENT_KEYGEN_TIME, number_new (*seconds));
    error = SIM_OK;
  } else
    event_send_value (NULL, SIM_EVENT_KEYGEN, SIM_EVENT_KEYGEN, number_new (error));
  return error;
}

static void *thread_keygen_ (void *arg) {
  int err;
  unsigned *decrement = ((struct keygen_arg *) arg)->state, type = ((struct keygen_arg *) arg)->type;
  simtype key = ((struct keygen_arg *) arg)->key, seed = ((struct keygen_arg *) arg)->seed, addr = nil ();
  simnumber sec = 0, start = system_get_tick ();
  LOG_API_DEBUG_ ("%d %s\n", type, key.len ? "SEED" : (char *) key.str);
  if ((err = event_send_keygen_ (SIM_OK, type, &sec)) == SIM_OK) {
    simnumber tick = system_get_tick (), estimated = sec * 1000 / 2 - (tick - start) * 3;
    if ((err = key_generate_ (&estimated, seed, SSL_RSA_BITS_MIN << type, &addr)) == SIM_OK) {
      simtype myaddr = sim_contact_convert_address (addr.ptr, 'S'), event = table_new_name (2, SIM_EVENT_KEYGEN);
      if ((sec = (simnumber) (system_get_tick () - tick + (tick - start) * 6 - estimated) / 1000 - sec + 1) < 0) {
        event_send_value (NULL, SIM_EVENT_KEYGEN, SIM_EVENT_KEYGEN_TIME, number_new (sec));
      } else
        LOG_WARN_ ("generate time exceeded by %lld seconds\n", sec);
      if (seed.typ == SIMNIL && key.typ != SIMNIL) {
        file_set_password (key.len ? key : nil (), true);
        random_init_entropy (myaddr);
        random_init (random_public); /* cannot fail */
        err = key_save_ (KEY_MODE_MAKE | KEY_MODE_SAVE);
        key_uninit ();
        file_set_password (key = nil (), true);
      } else {
        simtype hash = sim_crypt_md_hash (sim_crypt_md_new (CRYPT_MD_RIPEMD), nil (), nil (), seed);
        table_add (key_file, seed.typ != SIMNIL ? KEY_SEED : KEY_NO_SEED, hash);
        random_open_seed (random_public, myaddr, nil (), 1, 1);
        err = key_save_ (KEY_MODE_MAKE);
      }
      random_close (random_public);
      table_add_number (event, SIM_EVENT_KEYGEN, err);
      table_add (event, SIM_EVENT_KEYGEN_ADDRESS, addr);
      event_send_id (sim_contact_convert_to_id (addr.ptr, myaddr), event);
      table_free (event);
      string_free (myaddr);
    } else
      event_send_keygen_ (err, SIM_KEY_NONE, NULL);
  }
  param_close ();
  string_free (seed);
  string_free (key);
  sim_free (arg, sizeof (struct keygen_arg));
  LOG_API_WARN_ (err);
  if (err == SIM_OK)
    LOG_API_DEBUG_ ("\n");
  (*decrement)--; /* api_init_state = INITIALIZED; */
  return pth_thread_exit_ (false);
}

static simnumber sim_key_test_cipher (int cipher, const simbyte *input, unsigned length, simbool encrypt,
                                      simbyte *output) {
  unsigned i;
  void *crypt;
  simnumber identifier = 0;
  simtype iv;
  struct _ssl_master master;
  memset (&master, 0, sizeof (master));
  master.length = sizeof (master.premaster) / 2;
  master.to = pointer_new (PROXY_ADDRESS_CONTROL);
  iv = string_buffer_new (sim_crypt_auth_size (crypt = sim_crypt_open (cipher, &master, 0, encrypt)));
  memset (iv.str, 0, iv.len);
  sim_crypt_auth_start (crypt, length, iv.str, iv.len, 0);
  if (! encrypt) {
    sim_crypt_auth_update (crypt, input, length);
    sim_crypt_decrypt (crypt, pointer_new_len (input, length), output);
  } else
    sim_crypt_encrypt (crypt, pointer_new_len (input, length), output);
  sim_crypt_auth_stop (crypt, iv.str);
  sim_crypt_free (crypt, encrypt);
  for (i = 0; i < sizeof (identifier); i++)
    identifier = identifier << 8 | iv.str[i];
  string_buffer_free (iv);
  return identifier;
}

static int key_test (void) {
  static const simbyte key_test_data[2][RANDOM_SIZE_BLOCK] = {
    { 0xF2, 0x76, 0x0C, 0x89, 0x48, 0x7A, 0x4B, 0xF0, 0xD4, 0x7F, 0x6C, 0xCC, 0xA8, 0xD6, 0x89, 0x15 },
    { 0x71, 0xB2, 0x93, 0xBB, 0x98, 0xFE, 0x94, 0xC8, 0xDA, 0xB9, 0x4B, 0x45, 0xC4, 0xF9, 0x5A, 0x53 }
  };
  int err = SIM_OK, i = -1;
  const char *cipher;
  simnumber identifier, identifier1, identifier2;
  simbyte seed[RANDOM_SIZE_BLOCK], tmp[RANDOM_SIZE_BLOCK];
  simtype seedptr;
  while ((cipher = sim_crypt_cipher_get (++i, NULL, &identifier)) != NULL) {
    memset (tmp, 0, sizeof (tmp));
    memset (seed, 0, sizeof (seed));
    identifier1 = sim_key_test_cipher (i, seed, sizeof (seed), true, tmp) & 0x3FFFFFFFFFFFFFFFLL;
    memset (seed, 0, sizeof (seed));
    identifier2 = sim_key_test_cipher (i, seed, sizeof (seed), true, seed) & 0x3FFFFFFFFFFFFFFFLL;
    if (identifier1 != identifier) {
      LOG_ERROR_ ("test-%s: %lld\n", cipher, identifier1);
      err = SIM_CRYPT_BAD_CIPHER;
    } else if (identifier2 != identifier || memcmp (seed, tmp, sizeof (seed))) {
      LOG_ERROR_ ("TEST-%s: %lld\n", cipher, identifier2);
      err = SIM_CRYPT_BAD_CIPHER;
    } else {
      identifier1 = sim_key_test_cipher (i, seed, sizeof (seed), false, seed) & 0x3FFFFFFFFFFFFFFFLL;
      memset (tmp, 0, sizeof (tmp));
      if (identifier1 != identifier || memcmp (seed, tmp, sizeof (tmp))) {
        LOG_ERROR_ ("Test-%s: %lld\n", cipher, identifier1);
        err = SIM_CRYPT_BAD_CIPHER;
      }
    }
  }
  for (i = 0; i <= 1; i++) {
    memset ((seedptr = pointer_new_len (seed, sizeof (seed))).ptr, 0, sizeof (seed));
    if (i) {
      random_open_seed (random_private, seedptr, nil (), 1, 1);
      random_get (random_private, pointer_new_len (seed, sizeof (seed)));
      random_close (random_private);
    } else {
      simtype hash = sim_crypt_md_hash (sim_crypt_md_new (CRYPT_MD_RIPEMD), nil (), nil (), seedptr);
      memcpy (seed, hash.str, sizeof (seed));
      string_free (hash);
    }
    if (memcmp (seed, key_test_data[i], sizeof (seed))) {
      LOG_ERROR_SIMTYPE_ (seedptr, LOG_BIT_BIN, "test-%s: ", i ? "rng" : "ripemd");
      err = SIM_CRYPT_BAD_CIPHER;
    }
  }
  return err;
}

int key_generate_test_ (void) {
  simtype addr, key, seedptr;
  simbyte seed[RANDOM_SIZE_BLOCK];
  int err = key_test (), i;
  memset (seed, 0, sizeof (seed));
  seedptr = pointer_new_len (seed, sizeof (seed));
  if (err == SIM_OK)
    for (i = 0; (unsigned) i < SIM_ARRAY_SIZE (key_pointers); i++) {
      simnumber zero = 0;
      if ((err = key_generate_ (&zero, seedptr, SSL_RSA_BITS_MIN << i, &addr)) != SIM_OK)
        break;
      string_free (addr);
      if (! i && string_check_diff_len (key = table_get_string (key_file, KEY_EC), key_ec, key_ec_size)) {
        LOG_ERROR_SIMTYPE_ (key, LOG_BIT_BIN, "test-ec: ");
        err = SIM_CRYPT_BAD_CIPHER;
      }
      if (string_check_diff_len (key = table_get_string (key_file, KEY_RSA), key_pointers[i], *key_lengths[i])) {
        LOG_ERROR_SIMTYPE_ (key, LOG_BIT_BIN, "test-%d: ", SSL_RSA_BITS_MIN << i);
        err = SIM_CRYPT_BAD_CIPHER;
      }
      key_uninit ();
    }
  return err;
}

int key_generate_key (unsigned *decrement, simtype password, simtype seed, unsigned type) {
  int err = SIM_KEY_NO_ENTROPY;
  struct keygen_arg *arg = sim_new (sizeof (*arg));
  arg->state = decrement;
  arg->type = type;
  arg->key = password;
  arg->seed = seed;
  if (seed.len || random_buffer_check_entropy (random_private, 3))
    err = pth_thread_spawn (thread_keygen_, arg, NULL, -1);
  if (err != SIM_OK) {
    string_free (arg->seed);
    string_free (arg->key);
    sim_free (arg, sizeof (*arg));
  }
  return err;
}

static int key_validate_ (int level) {
  int err = SIM_KEY_BAD_KEY;
  struct key_time timer;
  simtype saved = random_get_state (random_public);
  key_time_start (&timer);
  if (! random_test_key_ (random_public, table_get_string (key_file, KEY_EC), level, SSL_KEY_EC)) {
    LOG_ERROR_ ("failed to validate EC key\n");
  } else if (! random_test_key_ (random_public, table_get_string (key_file, KEY_RSA), level, SSL_KEY_RSA)) {
    LOG_ERROR_ ("failed to validate RSA key\n");
  } else if ((err = key_time_stop (&timer, level >= 3 ? 9 : 3)) == SIM_KEY_TOO_SLOW) {
    LOG_ERROR_ ("Your computer is too busy to use a %d-bit RSA key\n", CONTACT_GET_KEY_SIZE (contact_list.me));
    err = SIM_OK;
  }
  random_open (random_public, saved, (simbyte *) "", 0);
  table_free (saved);
  return err;
}

int key_save_ (int mode) {
  int err = SIM_OK, err2 = SIM_KEY_NO_SAVE, level = 3;
  simtype revoked = table_get_string (key_file, KEY_REVOKED);
  if (mode == KEY_MODE_KILL) {
    simtype name = sim_file_name_new (FILE_KEY, FILE_DIRECTORY_USER);
    simtype bak = sim_file_name_new (FILE_KEY SIM_FILE_BACKUP, FILE_DIRECTORY_USER);
    if (name.typ != SIMNIL) {
      err = file_wipe (NULL, name.ptr, sim_file_write);
      err2 = file_wipe (NULL, bak.ptr, sim_file_write);
    }
    string_free (bak);
    string_free (name);
    return err2 == SIM_OK ? err : err2;
  }
  if (revoked.typ != SIMNIL) {
    mode = KEY_MODE_LOAD;
  } else if ((mode == KEY_MODE_LOAD && (level = param_get_number ("crypto.validate")) >= 0) || mode & KEY_MODE_MAKE) {
    if (mode == KEY_MODE_LOAD && level > 1)
      event_send_tick_type (SIM_EVENT_KEYGEN);
    if ((err = key_validate_ (level)) != SIM_OK && level > 1 && key_validate_ (1) != SIM_OK && mode == KEY_MODE_LOAD)
      param_set_number ("crypto.validate", 1, SIM_PARAM_TEMPORARY);
  }
  if (mode & KEY_MODE_SAVE && err == SIM_OK) {
    if (key_file.len) {
      if (mode & KEY_MODE_LOAD)
        if (sim_file_check_exists (FILE_KEY) || sim_file_check_exists (FILE_KEY SIM_FILE_BACKUP))
          err = SIM_KEY_EXISTS;
      if (err == SIM_OK)
        if ((err = file_save (key_file, FILE_KEY, FILE_TYPE_ENCRYPTED | FILE_TYPE_KEY | FILE_TYPE_TEMPORARY)) == SIM_OK)
          file_copy (FILE_KEY, FILE_KEY SIM_FILE_BACKUP);
    } else
      err = SIM_KEY_NO_PRIVATE;
  }
  if (revoked.typ != SIMNIL) {
    err = SIM_KEY_NOT_SECURE;
  } else if (err == SIM_OK && mode == KEY_MODE_LOAD) {
    simtype ciphers = string_copy_string (param_get_string ("ssl.ciphers"));
    simtype curves = array_copy_strings (param_get_strings ("ssl.curves"));
    simtype sigalgs = array_copy_strings (param_get_strings ("ssl.sigalgs"));
    PTH_UNPROTECT (ssl_uninit_ (true));
    err = ssl_init_ (ciphers.ptr, curves, sigalgs);
    if (PTH_PROTECT_ (err) == SIM_OK && (err = key_set_ (key_file, NULL)) == SIM_OK)
      err = ssl_reinit_ ();
    if (err != SIM_OK)
      LOG_ERROR_ ("ssl init: %s\n", convert_error (err, true));
    array_free (sigalgs);
    array_free (curves);
    string_free (ciphers);
  }
  return err;
}

int key_init_ (const char *password) {
  int err, err2 = SIM_OK;
  simtype key = nil (), key2, addr = nil ();
#ifndef DONOT_DEFINE
  if ((err = key_test ()) != SIM_OK)
#else /* generate keytest.h */
  if ((err = key_generate_test_ ()) != SIM_OK)
#endif
    return err;
  if (password) {
    simtype seed = file_set_password (*password ? string_copy (password) : nil (), true);
    key_uninit ();
    err2 = file_load (FILE_KEY SIM_FILE_BACKUP, FILE_TYPE_ENCRYPTED | FILE_TYPE_KEY | FILE_TYPE_TEMPORARY, &key2);
    if ((err = file_load (FILE_KEY, FILE_TYPE_ENCRYPTED | FILE_TYPE_KEY | FILE_TYPE_TEMPORARY, &key)) != SIM_OK) {
      if (err2 != SIM_OK) {
        if (err != SIM_KEY_NO_KEY)
          return err;
        if (err2 != SIM_KEY_NO_KEY) {
          sim_error_set_text (" [", FILE_KEY, "]", err2);
        } else if (sim_file_check_exists (FILE_CONTACTS) || sim_file_check_exists (FILE_CONTACTS SIM_FILE_BACKUP))
          return SIM_KEY_NO_SAVED;
        return err2;
      }
      file_copy (FILE_KEY SIM_FILE_BACKUP, FILE_KEY);
      key = key2;
    } else if (err2 != SIM_OK) {
      file_copy (FILE_KEY, FILE_KEY SIM_FILE_BACKUP);
    } else
      table_free (key2);
    if ((err = key_set_ (key, &addr)) == SIM_OK) {
      simtype hash = sim_crypt_md_hash (sim_crypt_md_new (CRYPT_MD_RIPEMD), nil (), nil (), seed);
      table_add (key, seed.typ != SIMNIL ? KEY_SEED : KEY_NO_SEED, hash);
      key_file = key;
    }
    err2 = SIM_OK;
  } else if (key_file.len) {
    table_free (key_table), key_table = nil ();
    if ((err = key_set_ (key_file, &addr)) == SIM_OK && file_load (FILE_KEY, FILE_TYPE_KEY, &key2) == SIM_OK) {
      simtype seed = table_get_string (key_file, KEY_SEED);
      if (string_check_diff_len (table_get_string (key2, KEY_SEED), seed.str, seed.len))
        err = SIM_KEY_EXISTS;
      table_free (key2);
    }
    err2 = SIM_KEY_EXISTS;
  } else
    err = SIM_KEY_NO_PRIVATE;
  if (err == SIM_OK) {
    if ((err = contact_list_load (key = sim_contact_convert_address (addr.ptr, 'S'))) != SIM_OK) {
      if (err2 != SIM_OK)
        err = err2;
    } else if ((err = sim_const_init ()) == SIM_OK) {
      if (! contact_list.array.len) {
        contact_new_ (addr.ptr, CONTACT_AUTH_SELF, &contact_list.me);
        if (contact_list.me)
          contact_list.me->flags |= CONTACT_FLAG_SOUND_ON_Y | CONTACT_FLAG_SOUND_OFF_Y;
      } else if (! string_check_diff (addr, ((simcontact) contact_list.array.arr[1].ptr)->addr)) {
        contact_list.me = contact_list.array.arr[1].ptr;
        contact_list.auth = contact_list.me->auth;
        contact_list.me->auth = CONTACT_AUTH_ACCEPTED;
      } else
        err = SIM_KEY_NO_CONTACT;
      if (contact_list.me) {
        char *os = sim_system_get_version (NULL, NULL);
        contact_list.me->flags |= CONTACT_FLAG_VERIFY | CONTACT_FLAG_VERIFIED |
                                  CONTACT_FLAG_UTF | CONTACT_FLAG_XFER_N | CONTACT_FLAG_AUDIO_N;
        contact_list.me->flags &= ~(CONTACT_FLAG_XFER | CONTACT_FLAG_AUDIO | CONTACT_FLAG_EDIT_N |
                                    CONTACT_FLAG_XFER_Y | CONTACT_FLAG_AUDIO_Y | CONTACT_FLAG_EDIT_Y);
        if (os)
          event_send_name (contact_list.me, SIM_EVENT_ERROR, SIM_EVENT_ERROR_SYSTEM, pointer_new (os));
      } else if (err == SIM_OK)
        err = SIM_CONTACT_BAD_ID;
      if (err == SIM_OK && contact_new_ (CONTACT_ADDRESS_SYSTEM, CONTACT_AUTH_SYSTEM, &contact_list.system) == SIM_OK) {
        contact_list.system->flags &= ~CONTACT_FLAG_AUDIO;
        string_free (contact_list.system->nick);
        contact_list.system->own = contact_list.system->nick = pointer_new (CONTACT_VIP_SYSTEM);
      }
    }
    string_free (key);
  } else
    table_free (key);
  string_free (addr);
  return err;
}

void key_uninit (void) {
  if (key_file.len)
    table_free (key_file);
  if (key_table.len)
    table_free (key_table);
  key_table = key_file = nil ();
}

void key_log_private (const char *module, int level) {
  if (key_file.len)
    log_simtype_ (module, level, key_file, LOG_BIT_BIN, "private keys: ");
}

void key_print_speed_ (unsigned seconds) {
  const char *cipher;
  unsigned len;
  int i, j;
  simnumber cputime, count;
  struct key_time timer;
  simtype ec, rng = string_cat ("r-", random_get_cipher ());
  static simbyte buf[16384];
  for (i = -3; (cipher = sim_crypt_cipher_get (++i, &len, NULL)) != NULL;) {
    void *crypt = NULL;
    simtype iv = nil (), key = string_buffer_new (len);
    memset (key.str, 0, len);
    memset (buf, 0, sizeof (buf));
    if (i >= 0) {
      iv = string_buffer_new (sim_crypt_auth_size (crypt = sim_crypt_new (i, key, true)));
      memset (iv.str, 0, iv.len);
    } else
      cipher = i != -1 ? "random-init" : string_check_diff (rng, "r-") ? rng.ptr : "random-gen";
    printf ("%s... ", cipher);
    fflush (stdout);
    for (cputime = count = 0; cputime < seconds * 1000000; cputime += timer.cputime) {
      key_time_start (&timer);
      for (j = 64; j; j--)
        if (i >= 0) {
          sim_crypt_auth_encrypt (crypt, pointer_new_len (buf, sizeof (buf)), iv, 0, iv.str);
        } else if (i == -1) {
          random_get (random_public, pointer_new_len (buf, sizeof (buf)));
        } else
          random_open (random_public, nil (), buf, sizeof (buf));
      key_time_stop (&timer, 1);
      count += 1000000;
    }
    sim_crypt_free (crypt, true);
    string_buffer_free (iv);
    string_buffer_free (key);
    printf ("%s\t%g MB/sec\n", strlen (cipher) > 3 ? "" : "\t", cputime ? (double) count / cputime : 0);
    fflush (stdout);
  }
  random_open_seed (random_session, pointer_new_len (buf, RANDOM_SIZE_BLOCK), nil (), 1, 1);
  ec = random_new_ec_ (random_public, true);
  for (i = -2; i < (int) SIM_ARRAY_SIZE (key_pointers); i++) {
    pth_unprotect ();
    if (i >= 0) {
      void *rsa;
      simtype encrypted, pub = nil (), buffer;
      printf ("rsa-%d... ", SSL_RSA_BITS_MIN << i);
      fflush (stdout);
      rsa = sim_ssl_new_key__ (pointer_new_len (key_pointers[i], *key_lengths[i]), SSL_KEY_RSA, false);
      if (sim_ssl_set_keys__ (NULL, rsa) == SIM_OK)
        pub = sim_ssl_convert_key__ (rsa, SSL_KEY_RSA);
      sim_ssl_free_key__ (rsa);
      pth_protect_ ();
      random_get (random_public, buffer = string_buffer_new (ssl_rsa_size_ (pub)));
      encrypted = ssl_rsa_crypt_public_ (buffer, pub, nil ());
      string_buffer_free (buffer);
      string_free (pub);
      for (cputime = count = 0; cputime < seconds * 1000000; cputime += timer.cputime) {
        key_time_start (&timer);
        string_free (ssl_rsa_crypt_private_ (encrypted, true));
        key_time_stop (&timer, 1);
        count += 2000000;
      }
      string_free (encrypted);
    } else {
      printf ("ec-%d... ", i == -1 ? 512 : 256);
      fflush (stdout);
      for (cputime = count = 0; cputime < seconds * 1000000; cputime += timer.cputime) {
        key_time_start (&timer);
        sim_ssl_free_key__ (sim_ssl_new_key__ (ec, i == -1 ? SSL_KEY_EC : SSL_KEY_ANONYMOUS, true));
        key_time_stop (&timer, 1);
        count += 2000000;
      }
      pth_protect_ ();
    }
    printf ("\t%g modexp/sec\n", cputime ? (double) count / cputime : 0);
    fflush (stdout);
    string_free (ec);
    ec = i == -2 ? pointer_new_len (key_ec, key_ec_size) : nil ();
  }
  random_close (random_session);
  random_close (random_public);
  string_free (rng);
}
