commit afb3912f51359595109e77e91ce151e20abaa99b
Author: tevador <tevador@gmail.com>
Date:   Sat Jun 13 21:00:39 2020 +0200

    initial commit

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ec94c2c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+bin/
+obj/
+*.user
+*.suo
+.vs
+x64/
+Release/
+Debug/
+build/
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..5add30f
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,26 @@
+# Copyright (c) 2020, tevador <tevador@gmail.com>
+
+cmake_minimum_required(VERSION 2.8.7)
+
+if(NOT CMAKE_BUILD_TYPE)
+  set(CMAKE_BUILD_TYPE Release)
+  message(STATUS "Setting default build type: ${CMAKE_BUILD_TYPE}")
+endif()
+
+project(monero-seed)
+
+add_executable(${PROJECT_NAME}
+src/argon2/blake2/blake2b.c
+src/argon2/argon2.c
+src/argon2/core.c
+src/argon2/ref.c
+src/galois_field.cpp
+src/gf_elem.cpp
+src/gf_poly.cpp
+src/main.cpp
+src/monero_seed.cpp
+src/reed_solomon_code.cpp
+src/secure_random.cpp
+src/wordlist.cpp)
+
+set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 11)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d37e64f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,70 @@
+## Build
+```
+git clone https://github.com/tevador/monero-seed.git
+cd monero-seed
+cmake ..
+make
+```
+
+## Usage
+
+### Create a new seed
+
+The sole argument is the wallet creation date in `yyyy/MM/dd` format.
+```
+> ./monero-seed --create 2100/03/14
+Mnemonic phrase: pumpkin alter spice lend position sentence surface snow atom lobster exotic robot profit chase
+- version: 1
+- private key: a147f59c9b80e77824ba2e44241598b3b9ddf2e6458e9970352894216b9cbbba
+- created on or after: 02/Mar/2100
+```
+
+### Restore seed
+
+```
+> ./monero-seed --restore "pumpkin alter spice lend position sentence surface snow atom lobster exotic robot profit chase"
+- version: 1
+- private key: a147f59c9b80e77824ba2e44241598b3b9ddf2e6458e9970352894216b9cbbba
+- created on or after: 02/Mar/2100
+```
+
+Restore has limited error correction capability, namely it can correct a single erasure (illegible symbol with a known location).
+This can be tested by replacing a word with `xxxx`:
+
+```
+> ./monero-seed --restore "pumpkin alter xxxx lend position sentence surface snow atom lobster exotic robot profit chase"
+Warning: corrected erasure: xxxx -> spice
+- version: 1
+- private key: a147f59c9b80e77824ba2e44241598b3b9ddf2e6458e9970352894216b9cbbba
+- created on or after: 02/Mar/2100
+```
+
+## Implementation details
+
+The mnemonic phrase contains 154 bits of data, which are used as follows:
+
+* 3 bits for version (this allows the format to be updated up to 7 times)
+* 2 bits reserved for future use
+* 10 bits for approximate wallet creation date
+* 128 bits for the private key seed
+* 11 bits for error detection/correction
+
+### Wordlist
+
+Uses the wordlist from BIP-39. It has 2048 words, allowing 11 bits to be stored in each word. It has some additional useful properties,
+for example each word can be uniquly identified by its first 4 characters.
+
+### Wallet creation date
+
+The mnemonic phrase doesn't store block height, but the time when the wallet was created. This allows the seed to be generated
+offline without access to the blockchain. Wallet software can easily convert a date to the corresponding block height when restoring a seed.
+The wallet creation date has a resolution of 2629746 seconds (1/12 of the average Gregorian year). All dates between June 2020
+and September 2105 can be represented.
+
+### Private key seed
+
+The private key is generated by wallet software from the 128-bit seed included in the mnemonic phrase. Argon2id is used as KDF (memory = 256 MB). The wallet creation date is used as a salt. 128-bit seed provides the same level of security as the elliptic curve used by Monero.
+
+### Error detection/correction
+
+The mnemonic phrase can be treated as a polynomial over GF(2048), which allows us to use an efficient Reed-Solomon ECC with one check word. All single-word errors can be detected and all single-word erasures can be corrected.
diff --git a/src/argon2/argon2.c b/src/argon2/argon2.c
new file mode 100644
index 0000000..e9882b7
--- /dev/null
+++ b/src/argon2/argon2.c
@@ -0,0 +1,354 @@
+/*
+ * Argon2 reference source code package - reference C implementations
+ *
+ * Copyright 2015
+ * Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
+ *
+ * You may use this work under the terms of a Creative Commons CC0 1.0
+ * License/Waiver or the Apache Public License 2.0, at your option. The terms of
+ * these licenses can be found at:
+ *
+ * - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0
+ * - Apache 2.0        : http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * You should have received a copy of both of these licenses along with this
+ * software. If not, they may be obtained at the above URLs.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "argon2.h"
+#include "core.h"
+
+const char *argon2_type2string(argon2_type type, int uppercase) {
+    switch (type) {
+        case Argon2_d:
+            return uppercase ? "Argon2d" : "argon2d";
+        case Argon2_i:
+            return uppercase ? "Argon2i" : "argon2i";
+        case Argon2_id:
+            return uppercase ? "Argon2id" : "argon2id";
+    }
+
+    return NULL;
+}
+
+int argon2_ctx(argon2_context *context, argon2_type type) {
+    /* 1. Validate all inputs */
+    int result = validate_inputs(context);
+    uint32_t memory_blocks, segment_length;
+    argon2_instance_t instance;
+
+    if (ARGON2_OK != result) {
+        return result;
+    }
+
+    if (Argon2_d != type && Argon2_i != type && Argon2_id != type) {
+        return ARGON2_INCORRECT_TYPE;
+    }
+
+    /* 2. Align memory size */
+    /* Minimum memory_blocks = 8L blocks, where L is the number of lanes */
+    memory_blocks = context->m_cost;
+
+    if (memory_blocks < 2 * ARGON2_SYNC_POINTS * context->lanes) {
+        memory_blocks = 2 * ARGON2_SYNC_POINTS * context->lanes;
+    }
+
+    segment_length = memory_blocks / (context->lanes * ARGON2_SYNC_POINTS);
+    /* Ensure that all segments have equal length */
+    memory_blocks = segment_length * (context->lanes * ARGON2_SYNC_POINTS);
+
+    instance.version = context->version;
+    instance.memory = NULL;
+    instance.passes = context->t_cost;
+    instance.memory_blocks = memory_blocks;
+    instance.segment_length = segment_length;
+    instance.lane_length = segment_length * ARGON2_SYNC_POINTS;
+    instance.lanes = context->lanes;
+    instance.threads = context->threads;
+    instance.type = type;
+
+    if (instance.threads > instance.lanes) {
+        instance.threads = instance.lanes;
+    }
+
+    /* 3. Initialization: Hashing inputs, allocating memory, filling first
+     * blocks
+     */
+    result = initialize(&instance, context);
+
+    if (ARGON2_OK != result) {
+        return result;
+    }
+
+    /* 4. Filling memory */
+    result = fill_memory_blocks(&instance);
+
+    if (ARGON2_OK != result) {
+        return result;
+    }
+    /* 5. Finalization */
+    finalize(context, &instance);
+
+    return ARGON2_OK;
+}
+
+int argon2_hash(const uint32_t t_cost, const uint32_t m_cost,
+                const uint32_t parallelism, const void *pwd,
+                const size_t pwdlen, const void *salt, const size_t saltlen,
+                void *hash, const size_t hashlen, char *encoded,
+                const size_t encodedlen, argon2_type type,
+                const uint32_t version){
+
+    argon2_context context;
+    int result;
+    uint8_t *out;
+
+    if (pwdlen > ARGON2_MAX_PWD_LENGTH) {
+        return ARGON2_PWD_TOO_LONG;
+    }
+
+    if (saltlen > ARGON2_MAX_SALT_LENGTH) {
+        return ARGON2_SALT_TOO_LONG;
+    }
+
+    if (hashlen > ARGON2_MAX_OUTLEN) {
+        return ARGON2_OUTPUT_TOO_LONG;
+    }
+
+    if (hashlen < ARGON2_MIN_OUTLEN) {
+        return ARGON2_OUTPUT_TOO_SHORT;
+    }
+
+    out = malloc(hashlen);
+    if (!out) {
+        return ARGON2_MEMORY_ALLOCATION_ERROR;
+    }
+
+    context.out = (uint8_t *)out;
+    context.outlen = (uint32_t)hashlen;
+    context.pwd = CONST_CAST(uint8_t *)pwd;
+    context.pwdlen = (uint32_t)pwdlen;
+    context.salt = CONST_CAST(uint8_t *)salt;
+    context.saltlen = (uint32_t)saltlen;
+    context.secret = NULL;
+    context.secretlen = 0;
+    context.ad = NULL;
+    context.adlen = 0;
+    context.t_cost = t_cost;
+    context.m_cost = m_cost;
+    context.lanes = parallelism;
+    context.threads = parallelism;
+    context.allocate_cbk = NULL;
+    context.free_cbk = NULL;
+    context.flags = ARGON2_DEFAULT_FLAGS;
+    context.version = version;
+
+    result = argon2_ctx(&context, type);
+
+    if (result != ARGON2_OK) {
+        clear_internal_memory(out, hashlen);
+        free(out);
+        return result;
+    }
+
+    /* if raw hash requested, write it */
+    if (hash) {
+        memcpy(hash, out, hashlen);
+    }
+
+    clear_internal_memory(out, hashlen);
+    free(out);
+
+    return ARGON2_OK;
+}
+
+int argon2i_hash_encoded(const uint32_t t_cost, const uint32_t m_cost,
+                         const uint32_t parallelism, const void *pwd,
+                         const size_t pwdlen, const void *salt,
+                         const size_t saltlen, const size_t hashlen,
+                         char *encoded, const size_t encodedlen) {
+
+    return argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen,
+                       NULL, hashlen, encoded, encodedlen, Argon2_i,
+                       ARGON2_VERSION_NUMBER);
+}
+
+int argon2i_hash_raw(const uint32_t t_cost, const uint32_t m_cost,
+                     const uint32_t parallelism, const void *pwd,
+                     const size_t pwdlen, const void *salt,
+                     const size_t saltlen, void *hash, const size_t hashlen) {
+
+    return argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen,
+                       hash, hashlen, NULL, 0, Argon2_i, ARGON2_VERSION_NUMBER);
+}
+
+int argon2d_hash_encoded(const uint32_t t_cost, const uint32_t m_cost,
+                         const uint32_t parallelism, const void *pwd,
+                         const size_t pwdlen, const void *salt,
+                         const size_t saltlen, const size_t hashlen,
+                         char *encoded, const size_t encodedlen) {
+
+    return argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen,
+                       NULL, hashlen, encoded, encodedlen, Argon2_d,
+                       ARGON2_VERSION_NUMBER);
+}
+
+int argon2d_hash_raw(const uint32_t t_cost, const uint32_t m_cost,
+                     const uint32_t parallelism, const void *pwd,
+                     const size_t pwdlen, const void *salt,
+                     const size_t saltlen, void *hash, const size_t hashlen) {
+
+    return argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen,
+                       hash, hashlen, NULL, 0, Argon2_d, ARGON2_VERSION_NUMBER);
+}
+
+int argon2id_hash_encoded(const uint32_t t_cost, const uint32_t m_cost,
+                          const uint32_t parallelism, const void *pwd,
+                          const size_t pwdlen, const void *salt,
+                          const size_t saltlen, const size_t hashlen,
+                          char *encoded, const size_t encodedlen) {
+
+    return argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen,
+                       NULL, hashlen, encoded, encodedlen, Argon2_id,
+                       ARGON2_VERSION_NUMBER);
+}
+
+int argon2id_hash_raw(const uint32_t t_cost, const uint32_t m_cost,
+                      const uint32_t parallelism, const void *pwd,
+                      const size_t pwdlen, const void *salt,
+                      const size_t saltlen, void *hash, const size_t hashlen) {
+    return argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen,
+                       hash, hashlen, NULL, 0, Argon2_id,
+                       ARGON2_VERSION_NUMBER);
+}
+
+static int argon2_compare(const uint8_t *b1, const uint8_t *b2, size_t len) {
+    size_t i;
+    uint8_t d = 0U;
+
+    for (i = 0U; i < len; i++) {
+        d |= b1[i] ^ b2[i];
+    }
+    return (int)((1 & ((d - 1) >> 8)) - 1);
+}
+
+int argon2d_ctx(argon2_context *context) {
+    return argon2_ctx(context, Argon2_d);
+}
+
+int argon2i_ctx(argon2_context *context) {
+    return argon2_ctx(context, Argon2_i);
+}
+
+int argon2id_ctx(argon2_context *context) {
+    return argon2_ctx(context, Argon2_id);
+}
+
+int argon2_verify_ctx(argon2_context *context, const char *hash,
+                      argon2_type type) {
+    int ret = argon2_ctx(context, type);
+    if (ret != ARGON2_OK) {
+        return ret;
+    }
+
+    if (argon2_compare((uint8_t *)hash, context->out, context->outlen)) {
+        return ARGON2_VERIFY_MISMATCH;
+    }
+
+    return ARGON2_OK;
+}
+
+int argon2d_verify_ctx(argon2_context *context, const char *hash) {
+    return argon2_verify_ctx(context, hash, Argon2_d);
+}
+
+int argon2i_verify_ctx(argon2_context *context, const char *hash) {
+    return argon2_verify_ctx(context, hash, Argon2_i);
+}
+
+int argon2id_verify_ctx(argon2_context *context, const char *hash) {
+    return argon2_verify_ctx(context, hash, Argon2_id);
+}
+
+const char *argon2_error_message(int error_code) {
+    switch (error_code) {
+    case ARGON2_OK:
+        return "OK";
+    case ARGON2_OUTPUT_PTR_NULL:
+        return "Output pointer is NULL";
+    case ARGON2_OUTPUT_TOO_SHORT:
+        return "Output is too short";
+    case ARGON2_OUTPUT_TOO_LONG:
+        return "Output is too long";
+    case ARGON2_PWD_TOO_SHORT:
+        return "Password is too short";
+    case ARGON2_PWD_TOO_LONG:
+        return "Password is too long";
+    case ARGON2_SALT_TOO_SHORT:
+        return "Salt is too short";
+    case ARGON2_SALT_TOO_LONG:
+        return "Salt is too long";
+    case ARGON2_AD_TOO_SHORT:
+        return "Associated data is too short";
+    case ARGON2_AD_TOO_LONG:
+        return "Associated data is too long";
+    case ARGON2_SECRET_TOO_SHORT:
+        return "Secret is too short";
+    case ARGON2_SECRET_TOO_LONG:
+        return "Secret is too long";
+    case ARGON2_TIME_TOO_SMALL:
+        return "Time cost is too small";
+    case ARGON2_TIME_TOO_LARGE:
+        return "Time cost is too large";
+    case ARGON2_MEMORY_TOO_LITTLE:
+        return "Memory cost is too small";
+    case ARGON2_MEMORY_TOO_MUCH:
+        return "Memory cost is too large";
+    case ARGON2_LANES_TOO_FEW:
+        return "Too few lanes";
+    case ARGON2_LANES_TOO_MANY:
+        return "Too many lanes";
+    case ARGON2_PWD_PTR_MISMATCH:
+        return "Password pointer is NULL, but password length is not 0";
+    case ARGON2_SALT_PTR_MISMATCH:
+        return "Salt pointer is NULL, but salt length is not 0";
+    case ARGON2_SECRET_PTR_MISMATCH:
+        return "Secret pointer is NULL, but secret length is not 0";
+    case ARGON2_AD_PTR_MISMATCH:
+        return "Associated data pointer is NULL, but ad length is not 0";
+    case ARGON2_MEMORY_ALLOCATION_ERROR:
+        return "Memory allocation error";
+    case ARGON2_FREE_MEMORY_CBK_NULL:
+        return "The free memory callback is NULL";
+    case ARGON2_ALLOCATE_MEMORY_CBK_NULL:
+        return "The allocate memory callback is NULL";
+    case ARGON2_INCORRECT_PARAMETER:
+        return "Argon2_Context context is NULL";
+    case ARGON2_INCORRECT_TYPE:
+        return "There is no such version of Argon2";
+    case ARGON2_OUT_PTR_MISMATCH:
+        return "Output pointer mismatch";
+    case ARGON2_THREADS_TOO_FEW:
+        return "Not enough threads";
+    case ARGON2_THREADS_TOO_MANY:
+        return "Too many threads";
+    case ARGON2_MISSING_ARGS:
+        return "Missing arguments";
+    case ARGON2_ENCODING_FAIL:
+        return "Encoding failed";
+    case ARGON2_DECODING_FAIL:
+        return "Decoding failed";
+    case ARGON2_THREAD_FAIL:
+        return "Threading failure";
+    case ARGON2_DECODING_LENGTH_FAIL:
+        return "Some of encoded parameters are too long or too short";
+    case ARGON2_VERIFY_MISMATCH:
+        return "The password does not match the supplied hash";
+    default:
+        return "Unknown error code";
+    }
+}
diff --git a/src/argon2/argon2.h b/src/argon2/argon2.h
new file mode 100644
index 0000000..1b471f6
--- /dev/null
+++ b/src/argon2/argon2.h
@@ -0,0 +1,437 @@
+/*
+ * Argon2 reference source code package - reference C implementations
+ *
+ * Copyright 2015
+ * Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
+ *
+ * You may use this work under the terms of a Creative Commons CC0 1.0
+ * License/Waiver or the Apache Public License 2.0, at your option. The terms of
+ * these licenses can be found at:
+ *
+ * - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0
+ * - Apache 2.0        : http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * You should have received a copy of both of these licenses along with this
+ * software. If not, they may be obtained at the above URLs.
+ */
+
+#ifndef ARGON2_H
+#define ARGON2_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include <limits.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/* Symbols visibility control */
+#ifdef A2_VISCTL
+#define ARGON2_PUBLIC __attribute__((visibility("default")))
+#define ARGON2_LOCAL __attribute__ ((visibility ("hidden")))
+#elif defined(_MSC_VER)
+#define ARGON2_PUBLIC __declspec(dllexport)
+#define ARGON2_LOCAL
+#else
+#define ARGON2_PUBLIC
+#define ARGON2_LOCAL
+#endif
+
+/*
+ * Argon2 input parameter restrictions
+ */
+
+/* Minimum and maximum number of lanes (degree of parallelism) */
+#define ARGON2_MIN_LANES UINT32_C(1)
+#define ARGON2_MAX_LANES UINT32_C(0xFFFFFF)
+
+/* Minimum and maximum number of threads */
+#define ARGON2_MIN_THREADS UINT32_C(1)
+#define ARGON2_MAX_THREADS UINT32_C(0xFFFFFF)
+
+/* Number of synchronization points between lanes per pass */
+#define ARGON2_SYNC_POINTS UINT32_C(4)
+
+/* Minimum and maximum digest size in bytes */
+#define ARGON2_MIN_OUTLEN UINT32_C(4)
+#define ARGON2_MAX_OUTLEN UINT32_C(0xFFFFFFFF)
+
+/* Minimum and maximum number of memory blocks (each of BLOCK_SIZE bytes) */
+#define ARGON2_MIN_MEMORY (2 * ARGON2_SYNC_POINTS) /* 2 blocks per slice */
+
+#define ARGON2_MIN(a, b) ((a) < (b) ? (a) : (b))
+/* Max memory size is addressing-space/2, topping at 2^32 blocks (4 TB) */
+#define ARGON2_MAX_MEMORY_BITS                                                 \
+    ARGON2_MIN(UINT32_C(32), (sizeof(void *) * CHAR_BIT - 10 - 1))
+#define ARGON2_MAX_MEMORY                                                      \
+    ARGON2_MIN(UINT32_C(0xFFFFFFFF), UINT64_C(1) << ARGON2_MAX_MEMORY_BITS)
+
+/* Minimum and maximum number of passes */
+#define ARGON2_MIN_TIME UINT32_C(1)
+#define ARGON2_MAX_TIME UINT32_C(0xFFFFFFFF)
+
+/* Minimum and maximum password length in bytes */
+#define ARGON2_MIN_PWD_LENGTH UINT32_C(0)
+#define ARGON2_MAX_PWD_LENGTH UINT32_C(0xFFFFFFFF)
+
+/* Minimum and maximum associated data length in bytes */
+#define ARGON2_MIN_AD_LENGTH UINT32_C(0)
+#define ARGON2_MAX_AD_LENGTH UINT32_C(0xFFFFFFFF)
+
+/* Minimum and maximum salt length in bytes */
+#define ARGON2_MIN_SALT_LENGTH UINT32_C(8)
+#define ARGON2_MAX_SALT_LENGTH UINT32_C(0xFFFFFFFF)
+
+/* Minimum and maximum key length in bytes */
+#define ARGON2_MIN_SECRET UINT32_C(0)
+#define ARGON2_MAX_SECRET UINT32_C(0xFFFFFFFF)
+
+/* Flags to determine which fields are securely wiped (default = no wipe). */
+#define ARGON2_DEFAULT_FLAGS UINT32_C(0)
+#define ARGON2_FLAG_CLEAR_PASSWORD (UINT32_C(1) << 0)
+#define ARGON2_FLAG_CLEAR_SECRET (UINT32_C(1) << 1)
+
+/* Global flag to determine if we are wiping internal memory buffers. This flag
+ * is defined in core.c and defaults to 1 (wipe internal memory). */
+extern int FLAG_clear_internal_memory;
+
+/* Error codes */
+typedef enum Argon2_ErrorCodes {
+    ARGON2_OK = 0,
+
+    ARGON2_OUTPUT_PTR_NULL = -1,
+
+    ARGON2_OUTPUT_TOO_SHORT = -2,
+    ARGON2_OUTPUT_TOO_LONG = -3,
+
+    ARGON2_PWD_TOO_SHORT = -4,
+    ARGON2_PWD_TOO_LONG = -5,
+
+    ARGON2_SALT_TOO_SHORT = -6,
+    ARGON2_SALT_TOO_LONG = -7,
+
+    ARGON2_AD_TOO_SHORT = -8,
+    ARGON2_AD_TOO_LONG = -9,
+
+    ARGON2_SECRET_TOO_SHORT = -10,
+    ARGON2_SECRET_TOO_LONG = -11,
+
+    ARGON2_TIME_TOO_SMALL = -12,
+    ARGON2_TIME_TOO_LARGE = -13,
+
+    ARGON2_MEMORY_TOO_LITTLE = -14,
+    ARGON2_MEMORY_TOO_MUCH = -15,
+
+    ARGON2_LANES_TOO_FEW = -16,
+    ARGON2_LANES_TOO_MANY = -17,
+
+    ARGON2_PWD_PTR_MISMATCH = -18,    /* NULL ptr with non-zero length */
+    ARGON2_SALT_PTR_MISMATCH = -19,   /* NULL ptr with non-zero length */
+    ARGON2_SECRET_PTR_MISMATCH = -20, /* NULL ptr with non-zero length */
+    ARGON2_AD_PTR_MISMATCH = -21,     /* NULL ptr with non-zero length */
+
+    ARGON2_MEMORY_ALLOCATION_ERROR = -22,
+
+    ARGON2_FREE_MEMORY_CBK_NULL = -23,
+    ARGON2_ALLOCATE_MEMORY_CBK_NULL = -24,
+
+    ARGON2_INCORRECT_PARAMETER = -25,
+    ARGON2_INCORRECT_TYPE = -26,
+
+    ARGON2_OUT_PTR_MISMATCH = -27,
+
+    ARGON2_THREADS_TOO_FEW = -28,
+    ARGON2_THREADS_TOO_MANY = -29,
+
+    ARGON2_MISSING_ARGS = -30,
+
+    ARGON2_ENCODING_FAIL = -31,
+
+    ARGON2_DECODING_FAIL = -32,
+
+    ARGON2_THREAD_FAIL = -33,
+
+    ARGON2_DECODING_LENGTH_FAIL = -34,
+
+    ARGON2_VERIFY_MISMATCH = -35
+} argon2_error_codes;
+
+/* Memory allocator types --- for external allocation */
+typedef int (*allocate_fptr)(uint8_t **memory, size_t bytes_to_allocate);
+typedef void (*deallocate_fptr)(uint8_t *memory, size_t bytes_to_allocate);
+
+/* Argon2 external data structures */
+
+/*
+ *****
+ * Context: structure to hold Argon2 inputs:
+ *  output array and its length,
+ *  password and its length,
+ *  salt and its length,
+ *  secret and its length,
+ *  associated data and its length,
+ *  number of passes, amount of used memory (in KBytes, can be rounded up a bit)
+ *  number of parallel threads that will be run.
+ * All the parameters above affect the output hash value.
+ * Additionally, two function pointers can be provided to allocate and
+ * deallocate the memory (if NULL, memory will be allocated internally).
+ * Also, three flags indicate whether to erase password, secret as soon as they
+ * are pre-hashed (and thus not needed anymore), and the entire memory
+ *****
+ * Simplest situation: you have output array out[8], password is stored in
+ * pwd[32], salt is stored in salt[16], you do not have keys nor associated
+ * data. You need to spend 1 GB of RAM and you run 5 passes of Argon2d with
+ * 4 parallel lanes.
+ * You want to erase the password, but you're OK with last pass not being
+ * erased. You want to use the default memory allocator.
+ * Then you initialize:
+ Argon2_Context(out,8,pwd,32,salt,16,NULL,0,NULL,0,5,1<<20,4,4,NULL,NULL,true,false,false,false)
+ */
+typedef struct Argon2_Context {
+    uint8_t *out;    /* output array */
+    uint32_t outlen; /* digest length */
+
+    uint8_t *pwd;    /* password array */
+    uint32_t pwdlen; /* password length */
+
+    uint8_t *salt;    /* salt array */
+    uint32_t saltlen; /* salt length */
+
+    uint8_t *secret;    /* key array */
+    uint32_t secretlen; /* key length */
+
+    uint8_t *ad;    /* associated data array */
+    uint32_t adlen; /* associated data length */
+
+    uint32_t t_cost;  /* number of passes */
+    uint32_t m_cost;  /* amount of memory requested (KB) */
+    uint32_t lanes;   /* number of lanes */
+    uint32_t threads; /* maximum number of threads */
+
+    uint32_t version; /* version number */
+
+    allocate_fptr allocate_cbk; /* pointer to memory allocator */
+    deallocate_fptr free_cbk;   /* pointer to memory deallocator */
+
+    uint32_t flags; /* array of bool options */
+} argon2_context;
+
+/* Argon2 primitive type */
+typedef enum Argon2_type {
+  Argon2_d = 0,
+  Argon2_i = 1,
+  Argon2_id = 2
+} argon2_type;
+
+/* Version of the algorithm */
+typedef enum Argon2_version {
+    ARGON2_VERSION_10 = 0x10,
+    ARGON2_VERSION_13 = 0x13,
+    ARGON2_VERSION_NUMBER = ARGON2_VERSION_13
+} argon2_version;
+
+/*
+ * Function that gives the string representation of an argon2_type.
+ * @param type The argon2_type that we want the string for
+ * @param uppercase Whether the string should have the first letter uppercase
+ * @return NULL if invalid type, otherwise the string representation.
+ */
+ARGON2_PUBLIC const char *argon2_type2string(argon2_type type, int uppercase);
+
+/*
+ * Function that performs memory-hard hashing with certain degree of parallelism
+ * @param  context  Pointer to the Argon2 internal structure
+ * @return Error code if smth is wrong, ARGON2_OK otherwise
+ */
+ARGON2_PUBLIC int argon2_ctx(argon2_context *context, argon2_type type);
+
+/**
+ * Hashes a password with Argon2i, producing an encoded hash
+ * @param t_cost Number of iterations
+ * @param m_cost Sets memory usage to m_cost kibibytes
+ * @param parallelism Number of threads and compute lanes
+ * @param pwd Pointer to password
+ * @param pwdlen Password size in bytes
+ * @param salt Pointer to salt
+ * @param saltlen Salt size in bytes
+ * @param hashlen Desired length of the hash in bytes
+ * @param encoded Buffer where to write the encoded hash
+ * @param encodedlen Size of the buffer (thus max size of the encoded hash)
+ * @pre   Different parallelism levels will give different results
+ * @pre   Returns ARGON2_OK if successful
+ */
+ARGON2_PUBLIC int argon2i_hash_encoded(const uint32_t t_cost,
+                                       const uint32_t m_cost,
+                                       const uint32_t parallelism,
+                                       const void *pwd, const size_t pwdlen,
+                                       const void *salt, const size_t saltlen,
+                                       const size_t hashlen, char *encoded,
+                                       const size_t encodedlen);
+
+/**
+ * Hashes a password with Argon2i, producing a raw hash at @hash
+ * @param t_cost Number of iterations
+ * @param m_cost Sets memory usage to m_cost kibibytes
+ * @param parallelism Number of threads and compute lanes
+ * @param pwd Pointer to password
+ * @param pwdlen Password size in bytes
+ * @param salt Pointer to salt
+ * @param saltlen Salt size in bytes
+ * @param hash Buffer where to write the raw hash - updated by the function
+ * @param hashlen Desired length of the hash in bytes
+ * @pre   Different parallelism levels will give different results
+ * @pre   Returns ARGON2_OK if successful
+ */
+ARGON2_PUBLIC int argon2i_hash_raw(const uint32_t t_cost, const uint32_t m_cost,
+                                   const uint32_t parallelism, const void *pwd,
+                                   const size_t pwdlen, const void *salt,
+                                   const size_t saltlen, void *hash,
+                                   const size_t hashlen);
+
+ARGON2_PUBLIC int argon2d_hash_encoded(const uint32_t t_cost,
+                                       const uint32_t m_cost,
+                                       const uint32_t parallelism,
+                                       const void *pwd, const size_t pwdlen,
+                                       const void *salt, const size_t saltlen,
+                                       const size_t hashlen, char *encoded,
+                                       const size_t encodedlen);
+
+ARGON2_PUBLIC int argon2d_hash_raw(const uint32_t t_cost, const uint32_t m_cost,
+                                   const uint32_t parallelism, const void *pwd,
+                                   const size_t pwdlen, const void *salt,
+                                   const size_t saltlen, void *hash,
+                                   const size_t hashlen);
+
+ARGON2_PUBLIC int argon2id_hash_encoded(const uint32_t t_cost,
+                                        const uint32_t m_cost,
+                                        const uint32_t parallelism,
+                                        const void *pwd, const size_t pwdlen,
+                                        const void *salt, const size_t saltlen,
+                                        const size_t hashlen, char *encoded,
+                                        const size_t encodedlen);
+
+ARGON2_PUBLIC int argon2id_hash_raw(const uint32_t t_cost,
+                                    const uint32_t m_cost,
+                                    const uint32_t parallelism, const void *pwd,
+                                    const size_t pwdlen, const void *salt,
+                                    const size_t saltlen, void *hash,
+                                    const size_t hashlen);
+
+/* generic function underlying the above ones */
+ARGON2_PUBLIC int argon2_hash(const uint32_t t_cost, const uint32_t m_cost,
+                              const uint32_t parallelism, const void *pwd,
+                              const size_t pwdlen, const void *salt,
+                              const size_t saltlen, void *hash,
+                              const size_t hashlen, char *encoded,
+                              const size_t encodedlen, argon2_type type,
+                              const uint32_t version);
+
+/**
+ * Verifies a password against an encoded string
+ * Encoded string is restricted as in validate_inputs()
+ * @param encoded String encoding parameters, salt, hash
+ * @param pwd Pointer to password
+ * @pre   Returns ARGON2_OK if successful
+ */
+ARGON2_PUBLIC int argon2i_verify(const char *encoded, const void *pwd,
+                                 const size_t pwdlen);
+
+ARGON2_PUBLIC int argon2d_verify(const char *encoded, const void *pwd,
+                                 const size_t pwdlen);
+
+ARGON2_PUBLIC int argon2id_verify(const char *encoded, const void *pwd,
+                                  const size_t pwdlen);
+
+/* generic function underlying the above ones */
+ARGON2_PUBLIC int argon2_verify(const char *encoded, const void *pwd,
+                                const size_t pwdlen, argon2_type type);
+
+/**
+ * Argon2d: Version of Argon2 that picks memory blocks depending
+ * on the password and salt. Only for side-channel-free
+ * environment!!
+ *****
+ * @param  context  Pointer to current Argon2 context
+ * @return  Zero if successful, a non zero error code otherwise
+ */
+ARGON2_PUBLIC int argon2d_ctx(argon2_context *context);
+
+/**
+ * Argon2i: Version of Argon2 that picks memory blocks
+ * independent on the password and salt. Good for side-channels,
+ * but worse w.r.t. tradeoff attacks if only one pass is used.
+ *****
+ * @param  context  Pointer to current Argon2 context
+ * @return  Zero if successful, a non zero error code otherwise
+ */
+ARGON2_PUBLIC int argon2i_ctx(argon2_context *context);
+
+/**
+ * Argon2id: Version of Argon2 where the first half-pass over memory is
+ * password-independent, the rest are password-dependent (on the password and
+ * salt). OK against side channels (they reduce to 1/2-pass Argon2i), and
+ * better with w.r.t. tradeoff attacks (similar to Argon2d).
+ *****
+ * @param  context  Pointer to current Argon2 context
+ * @return  Zero if successful, a non zero error code otherwise
+ */
+ARGON2_PUBLIC int argon2id_ctx(argon2_context *context);
+
+/**
+ * Verify if a given password is correct for Argon2d hashing
+ * @param  context  Pointer to current Argon2 context
+ * @param  hash  The password hash to verify. The length of the hash is
+ * specified by the context outlen member
+ * @return  Zero if successful, a non zero error code otherwise
+ */
+ARGON2_PUBLIC int argon2d_verify_ctx(argon2_context *context, const char *hash);
+
+/**
+ * Verify if a given password is correct for Argon2i hashing
+ * @param  context  Pointer to current Argon2 context
+ * @param  hash  The password hash to verify. The length of the hash is
+ * specified by the context outlen member
+ * @return  Zero if successful, a non zero error code otherwise
+ */
+ARGON2_PUBLIC int argon2i_verify_ctx(argon2_context *context, const char *hash);
+
+/**
+ * Verify if a given password is correct for Argon2id hashing
+ * @param  context  Pointer to current Argon2 context
+ * @param  hash  The password hash to verify. The length of the hash is
+ * specified by the context outlen member
+ * @return  Zero if successful, a non zero error code otherwise
+ */
+ARGON2_PUBLIC int argon2id_verify_ctx(argon2_context *context,
+                                      const char *hash);
+
+/* generic function underlying the above ones */
+ARGON2_PUBLIC int argon2_verify_ctx(argon2_context *context, const char *hash,
+                                    argon2_type type);
+
+/**
+ * Get the associated error message for given error code
+ * @return  The error message associated with the given error code
+ */
+ARGON2_PUBLIC const char *argon2_error_message(int error_code);
+
+/**
+ * Returns the encoded hash length for the given input parameters
+ * @param t_cost  Number of iterations
+ * @param m_cost  Memory usage in kibibytes
+ * @param parallelism  Number of threads; used to compute lanes
+ * @param saltlen  Salt size in bytes
+ * @param hashlen  Hash size in bytes
+ * @param type The argon2_type that we want the encoded length for
+ * @return  The encoded hash length in bytes
+ */
+ARGON2_PUBLIC size_t argon2_encodedlen(uint32_t t_cost, uint32_t m_cost,
+                                       uint32_t parallelism, uint32_t saltlen,
+                                       uint32_t hashlen, argon2_type type);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
diff --git a/src/argon2/blake2/blake2-impl.h b/src/argon2/blake2/blake2-impl.h
new file mode 100644
index 0000000..241f0be
--- /dev/null
+++ b/src/argon2/blake2/blake2-impl.h
@@ -0,0 +1,156 @@
+/*
+ * Argon2 reference source code package - reference C implementations
+ *
+ * Copyright 2015
+ * Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
+ *
+ * You may use this work under the terms of a Creative Commons CC0 1.0
+ * License/Waiver or the Apache Public License 2.0, at your option. The terms of
+ * these licenses can be found at:
+ *
+ * - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0
+ * - Apache 2.0        : http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * You should have received a copy of both of these licenses along with this
+ * software. If not, they may be obtained at the above URLs.
+ */
+
+#ifndef PORTABLE_BLAKE2_IMPL_H
+#define PORTABLE_BLAKE2_IMPL_H
+
+#include <stdint.h>
+#include <string.h>
+
+#if defined(_MSC_VER)
+#define BLAKE2_INLINE __inline
+#elif defined(__GNUC__) || defined(__clang__)
+#define BLAKE2_INLINE __inline__
+#else
+#define BLAKE2_INLINE
+#endif
+
+/* Argon2 Team - Begin Code */
+/*
+   Not an exhaustive list, but should cover the majority of modern platforms
+   Additionally, the code will always be correct---this is only a performance
+   tweak.
+*/
+#if (defined(__BYTE_ORDER__) &&                                                \
+     (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) ||                           \
+    defined(__LITTLE_ENDIAN__) || defined(__ARMEL__) || defined(__MIPSEL__) || \
+    defined(__AARCH64EL__) || defined(__amd64__) || defined(__i386__) ||       \
+    defined(_M_IX86) || defined(_M_X64) || defined(_M_AMD64) ||                \
+    defined(_M_ARM)
+#define NATIVE_LITTLE_ENDIAN
+#endif
+/* Argon2 Team - End Code */
+
+static BLAKE2_INLINE uint32_t load32(const void *src) {
+#if defined(NATIVE_LITTLE_ENDIAN)
+    uint32_t w;
+    memcpy(&w, src, sizeof w);
+    return w;
+#else
+    const uint8_t *p = (const uint8_t *)src;
+    uint32_t w = *p++;
+    w |= (uint32_t)(*p++) << 8;
+    w |= (uint32_t)(*p++) << 16;
+    w |= (uint32_t)(*p++) << 24;
+    return w;
+#endif
+}
+
+static BLAKE2_INLINE uint64_t load64(const void *src) {
+#if defined(NATIVE_LITTLE_ENDIAN)
+    uint64_t w;
+    memcpy(&w, src, sizeof w);
+    return w;
+#else
+    const uint8_t *p = (const uint8_t *)src;
+    uint64_t w = *p++;
+    w |= (uint64_t)(*p++) << 8;
+    w |= (uint64_t)(*p++) << 16;
+    w |= (uint64_t)(*p++) << 24;
+    w |= (uint64_t)(*p++) << 32;
+    w |= (uint64_t)(*p++) << 40;
+    w |= (uint64_t)(*p++) << 48;
+    w |= (uint64_t)(*p++) << 56;
+    return w;
+#endif
+}
+
+static BLAKE2_INLINE void store32(void *dst, uint32_t w) {
+#if defined(NATIVE_LITTLE_ENDIAN)
+    memcpy(dst, &w, sizeof w);
+#else
+    uint8_t *p = (uint8_t *)dst;
+    *p++ = (uint8_t)w;
+    w >>= 8;
+    *p++ = (uint8_t)w;
+    w >>= 8;
+    *p++ = (uint8_t)w;
+    w >>= 8;
+    *p++ = (uint8_t)w;
+#endif
+}
+
+static BLAKE2_INLINE void store64(void *dst, uint64_t w) {
+#if defined(NATIVE_LITTLE_ENDIAN)
+    memcpy(dst, &w, sizeof w);
+#else
+    uint8_t *p = (uint8_t *)dst;
+    *p++ = (uint8_t)w;
+    w >>= 8;
+    *p++ = (uint8_t)w;
+    w >>= 8;
+    *p++ = (uint8_t)w;
+    w >>= 8;
+    *p++ = (uint8_t)w;
+    w >>= 8;
+    *p++ = (uint8_t)w;
+    w >>= 8;
+    *p++ = (uint8_t)w;
+    w >>= 8;
+    *p++ = (uint8_t)w;
+    w >>= 8;
+    *p++ = (uint8_t)w;
+#endif
+}
+
+static BLAKE2_INLINE uint64_t load48(const void *src) {
+    const uint8_t *p = (const uint8_t *)src;
+    uint64_t w = *p++;
+    w |= (uint64_t)(*p++) << 8;
+    w |= (uint64_t)(*p++) << 16;
+    w |= (uint64_t)(*p++) << 24;
+    w |= (uint64_t)(*p++) << 32;
+    w |= (uint64_t)(*p++) << 40;
+    return w;
+}
+
+static BLAKE2_INLINE void store48(void *dst, uint64_t w) {
+    uint8_t *p = (uint8_t *)dst;
+    *p++ = (uint8_t)w;
+    w >>= 8;
+    *p++ = (uint8_t)w;
+    w >>= 8;
+    *p++ = (uint8_t)w;
+    w >>= 8;
+    *p++ = (uint8_t)w;
+    w >>= 8;
+    *p++ = (uint8_t)w;
+    w >>= 8;
+    *p++ = (uint8_t)w;
+}
+
+static BLAKE2_INLINE uint32_t rotr32(const uint32_t w, const unsigned c) {
+    return (w >> c) | (w << (32 - c));
+}
+
+static BLAKE2_INLINE uint64_t rotr64(const uint64_t w, const unsigned c) {
+    return (w >> c) | (w << (64 - c));
+}
+
+void clear_internal_memory(void *v, size_t n);
+
+#endif
diff --git a/src/argon2/blake2/blake2.h b/src/argon2/blake2/blake2.h
new file mode 100644
index 0000000..9f97e1c
--- /dev/null
+++ b/src/argon2/blake2/blake2.h
@@ -0,0 +1,89 @@
+/*
+ * Argon2 reference source code package - reference C implementations
+ *
+ * Copyright 2015
+ * Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
+ *
+ * You may use this work under the terms of a Creative Commons CC0 1.0
+ * License/Waiver or the Apache Public License 2.0, at your option. The terms of
+ * these licenses can be found at:
+ *
+ * - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0
+ * - Apache 2.0        : http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * You should have received a copy of both of these licenses along with this
+ * software. If not, they may be obtained at the above URLs.
+ */
+
+#ifndef PORTABLE_BLAKE2_H
+#define PORTABLE_BLAKE2_H
+
+#include "../argon2.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+enum blake2b_constant {
+    BLAKE2B_BLOCKBYTES = 128,
+    BLAKE2B_OUTBYTES = 64,
+    BLAKE2B_KEYBYTES = 64,
+    BLAKE2B_SALTBYTES = 16,
+    BLAKE2B_PERSONALBYTES = 16
+};
+
+#pragma pack(push, 1)
+typedef struct __blake2b_param {
+    uint8_t digest_length;                   /* 1 */
+    uint8_t key_length;                      /* 2 */
+    uint8_t fanout;                          /* 3 */
+    uint8_t depth;                           /* 4 */
+    uint32_t leaf_length;                    /* 8 */
+    uint64_t node_offset;                    /* 16 */
+    uint8_t node_depth;                      /* 17 */
+    uint8_t inner_length;                    /* 18 */
+    uint8_t reserved[14];                    /* 32 */
+    uint8_t salt[BLAKE2B_SALTBYTES];         /* 48 */
+    uint8_t personal[BLAKE2B_PERSONALBYTES]; /* 64 */
+} blake2b_param;
+#pragma pack(pop)
+
+typedef struct __blake2b_state {
+    uint64_t h[8];
+    uint64_t t[2];
+    uint64_t f[2];
+    uint8_t buf[BLAKE2B_BLOCKBYTES];
+    unsigned buflen;
+    unsigned outlen;
+    uint8_t last_node;
+} blake2b_state;
+
+/* Ensure param structs have not been wrongly padded */
+/* Poor man's static_assert */
+enum {
+    blake2_size_check_0 = 1 / !!(CHAR_BIT == 8),
+    blake2_size_check_2 =
+        1 / !!(sizeof(blake2b_param) == sizeof(uint64_t) * CHAR_BIT)
+};
+
+/* Streaming API */
+ARGON2_LOCAL int blake2b_init(blake2b_state *S, size_t outlen);
+ARGON2_LOCAL int blake2b_init_key(blake2b_state *S, size_t outlen, const void *key,
+                     size_t keylen);
+ARGON2_LOCAL int blake2b_init_param(blake2b_state *S, const blake2b_param *P);
+ARGON2_LOCAL int blake2b_update(blake2b_state *S, const void *in, size_t inlen);
+ARGON2_LOCAL int blake2b_final(blake2b_state *S, void *out, size_t outlen);
+
+/* Simple API */
+ARGON2_LOCAL int blake2b(void *out, size_t outlen, const void *in, size_t inlen,
+                         const void *key, size_t keylen);
+
+/* Argon2 Team - Begin Code */
+ARGON2_LOCAL int blake2b_long(void *out, size_t outlen, const void *in, size_t inlen);
+/* Argon2 Team - End Code */
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
diff --git a/src/argon2/blake2/blake2b.c b/src/argon2/blake2/blake2b.c
new file mode 100644
index 0000000..ca05df5
--- /dev/null
+++ b/src/argon2/blake2/blake2b.c
@@ -0,0 +1,390 @@
+/*
+ * Argon2 reference source code package - reference C implementations
+ *
+ * Copyright 2015
+ * Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
+ *
+ * You may use this work under the terms of a Creative Commons CC0 1.0
+ * License/Waiver or the Apache Public License 2.0, at your option. The terms of
+ * these licenses can be found at:
+ *
+ * - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0
+ * - Apache 2.0        : http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * You should have received a copy of both of these licenses along with this
+ * software. If not, they may be obtained at the above URLs.
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "blake2.h"
+#include "blake2-impl.h"
+
+static const uint64_t blake2b_IV[8] = {
+    UINT64_C(0x6a09e667f3bcc908), UINT64_C(0xbb67ae8584caa73b),
+    UINT64_C(0x3c6ef372fe94f82b), UINT64_C(0xa54ff53a5f1d36f1),
+    UINT64_C(0x510e527fade682d1), UINT64_C(0x9b05688c2b3e6c1f),
+    UINT64_C(0x1f83d9abfb41bd6b), UINT64_C(0x5be0cd19137e2179)};
+
+static const unsigned int blake2b_sigma[12][16] = {
+    {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
+    {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3},
+    {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4},
+    {7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8},
+    {9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13},
+    {2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9},
+    {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11},
+    {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10},
+    {6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5},
+    {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0},
+    {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
+    {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3},
+};
+
+static BLAKE2_INLINE void blake2b_set_lastnode(blake2b_state *S) {
+    S->f[1] = (uint64_t)-1;
+}
+
+static BLAKE2_INLINE void blake2b_set_lastblock(blake2b_state *S) {
+    if (S->last_node) {
+        blake2b_set_lastnode(S);
+    }
+    S->f[0] = (uint64_t)-1;
+}
+
+static BLAKE2_INLINE void blake2b_increment_counter(blake2b_state *S,
+                                                    uint64_t inc) {
+    S->t[0] += inc;
+    S->t[1] += (S->t[0] < inc);
+}
+
+static BLAKE2_INLINE void blake2b_invalidate_state(blake2b_state *S) {
+    clear_internal_memory(S, sizeof(*S));      /* wipe */
+    blake2b_set_lastblock(S); /* invalidate for further use */
+}
+
+static BLAKE2_INLINE void blake2b_init0(blake2b_state *S) {
+    memset(S, 0, sizeof(*S));
+    memcpy(S->h, blake2b_IV, sizeof(S->h));
+}
+
+int blake2b_init_param(blake2b_state *S, const blake2b_param *P) {
+    const unsigned char *p = (const unsigned char *)P;
+    unsigned int i;
+
+    if (NULL == P || NULL == S) {
+        return -1;
+    }
+
+    blake2b_init0(S);
+    /* IV XOR Parameter Block */
+    for (i = 0; i < 8; ++i) {
+        S->h[i] ^= load64(&p[i * sizeof(S->h[i])]);
+    }
+    S->outlen = P->digest_length;
+    return 0;
+}
+
+/* Sequential blake2b initialization */
+int blake2b_init(blake2b_state *S, size_t outlen) {
+    blake2b_param P;
+
+    if (S == NULL) {
+        return -1;
+    }
+
+    if ((outlen == 0) || (outlen > BLAKE2B_OUTBYTES)) {
+        blake2b_invalidate_state(S);
+        return -1;
+    }
+
+    /* Setup Parameter Block for unkeyed BLAKE2 */
+    P.digest_length = (uint8_t)outlen;
+    P.key_length = 0;
+    P.fanout = 1;
+    P.depth = 1;
+    P.leaf_length = 0;
+    P.node_offset = 0;
+    P.node_depth = 0;
+    P.inner_length = 0;
+    memset(P.reserved, 0, sizeof(P.reserved));
+    memset(P.salt, 0, sizeof(P.salt));
+    memset(P.personal, 0, sizeof(P.personal));
+
+    return blake2b_init_param(S, &P);
+}
+
+int blake2b_init_key(blake2b_state *S, size_t outlen, const void *key,
+                     size_t keylen) {
+    blake2b_param P;
+
+    if (S == NULL) {
+        return -1;
+    }
+
+    if ((outlen == 0) || (outlen > BLAKE2B_OUTBYTES)) {
+        blake2b_invalidate_state(S);
+        return -1;
+    }
+
+    if ((key == 0) || (keylen == 0) || (keylen > BLAKE2B_KEYBYTES)) {
+        blake2b_invalidate_state(S);
+        return -1;
+    }
+
+    /* Setup Parameter Block for keyed BLAKE2 */
+    P.digest_length = (uint8_t)outlen;
+    P.key_length = (uint8_t)keylen;
+    P.fanout = 1;
+    P.depth = 1;
+    P.leaf_length = 0;
+    P.node_offset = 0;
+    P.node_depth = 0;
+    P.inner_length = 0;
+    memset(P.reserved, 0, sizeof(P.reserved));
+    memset(P.salt, 0, sizeof(P.salt));
+    memset(P.personal, 0, sizeof(P.personal));
+
+    if (blake2b_init_param(S, &P) < 0) {
+        blake2b_invalidate_state(S);
+        return -1;
+    }
+
+    {
+        uint8_t block[BLAKE2B_BLOCKBYTES];
+        memset(block, 0, BLAKE2B_BLOCKBYTES);
+        memcpy(block, key, keylen);
+        blake2b_update(S, block, BLAKE2B_BLOCKBYTES);
+        /* Burn the key from stack */
+        clear_internal_memory(block, BLAKE2B_BLOCKBYTES);
+    }
+    return 0;
+}
+
+static void blake2b_compress(blake2b_state *S, const uint8_t *block) {
+    uint64_t m[16];
+    uint64_t v[16];
+    unsigned int i, r;
+
+    for (i = 0; i < 16; ++i) {
+        m[i] = load64(block + i * sizeof(m[i]));
+    }
+
+    for (i = 0; i < 8; ++i) {
+        v[i] = S->h[i];
+    }
+
+    v[8] = blake2b_IV[0];
+    v[9] = blake2b_IV[1];
+    v[10] = blake2b_IV[2];
+    v[11] = blake2b_IV[3];
+    v[12] = blake2b_IV[4] ^ S->t[0];
+    v[13] = blake2b_IV[5] ^ S->t[1];
+    v[14] = blake2b_IV[6] ^ S->f[0];
+    v[15] = blake2b_IV[7] ^ S->f[1];
+
+#define G(r, i, a, b, c, d)                                                    \
+    do {                                                                       \
+        a = a + b + m[blake2b_sigma[r][2 * i + 0]];                            \
+        d = rotr64(d ^ a, 32);                                                 \
+        c = c + d;                                                             \
+        b = rotr64(b ^ c, 24);                                                 \
+        a = a + b + m[blake2b_sigma[r][2 * i + 1]];                            \
+        d = rotr64(d ^ a, 16);                                                 \
+        c = c + d;                                                             \
+        b = rotr64(b ^ c, 63);                                                 \
+    } while ((void)0, 0)
+
+#define ROUND(r)                                                               \
+    do {                                                                       \
+        G(r, 0, v[0], v[4], v[8], v[12]);                                      \
+        G(r, 1, v[1], v[5], v[9], v[13]);                                      \
+        G(r, 2, v[2], v[6], v[10], v[14]);                                     \
+        G(r, 3, v[3], v[7], v[11], v[15]);                                     \
+        G(r, 4, v[0], v[5], v[10], v[15]);                                     \
+        G(r, 5, v[1], v[6], v[11], v[12]);                                     \
+        G(r, 6, v[2], v[7], v[8], v[13]);                                      \
+        G(r, 7, v[3], v[4], v[9], v[14]);                                      \
+    } while ((void)0, 0)
+
+    for (r = 0; r < 12; ++r) {
+        ROUND(r);
+    }
+
+    for (i = 0; i < 8; ++i) {
+        S->h[i] = S->h[i] ^ v[i] ^ v[i + 8];
+    }
+
+#undef G
+#undef ROUND
+}
+
+int blake2b_update(blake2b_state *S, const void *in, size_t inlen) {
+    const uint8_t *pin = (const uint8_t *)in;
+
+    if (inlen == 0) {
+        return 0;
+    }
+
+    /* Sanity check */
+    if (S == NULL || in == NULL) {
+        return -1;
+    }
+
+    /* Is this a reused state? */
+    if (S->f[0] != 0) {
+        return -1;
+    }
+
+    if (S->buflen + inlen > BLAKE2B_BLOCKBYTES) {
+        /* Complete current block */
+        size_t left = S->buflen;
+        size_t fill = BLAKE2B_BLOCKBYTES - left;
+        memcpy(&S->buf[left], pin, fill);
+        blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES);
+        blake2b_compress(S, S->buf);
+        S->buflen = 0;
+        inlen -= fill;
+        pin += fill;
+        /* Avoid buffer copies when possible */
+        while (inlen > BLAKE2B_BLOCKBYTES) {
+            blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES);
+            blake2b_compress(S, pin);
+            inlen -= BLAKE2B_BLOCKBYTES;
+            pin += BLAKE2B_BLOCKBYTES;
+        }
+    }
+    memcpy(&S->buf[S->buflen], pin, inlen);
+    S->buflen += (unsigned int)inlen;
+    return 0;
+}
+
+int blake2b_final(blake2b_state *S, void *out, size_t outlen) {
+    uint8_t buffer[BLAKE2B_OUTBYTES] = {0};
+    unsigned int i;
+
+    /* Sanity checks */
+    if (S == NULL || out == NULL || outlen < S->outlen) {
+        return -1;
+    }
+
+    /* Is this a reused state? */
+    if (S->f[0] != 0) {
+        return -1;
+    }
+
+    blake2b_increment_counter(S, S->buflen);
+    blake2b_set_lastblock(S);
+    memset(&S->buf[S->buflen], 0, BLAKE2B_BLOCKBYTES - S->buflen); /* Padding */
+    blake2b_compress(S, S->buf);
+
+    for (i = 0; i < 8; ++i) { /* Output full hash to temp buffer */
+        store64(buffer + sizeof(S->h[i]) * i, S->h[i]);
+    }
+
+    memcpy(out, buffer, S->outlen);
+    clear_internal_memory(buffer, sizeof(buffer));
+    clear_internal_memory(S->buf, sizeof(S->buf));
+    clear_internal_memory(S->h, sizeof(S->h));
+    return 0;
+}
+
+int blake2b(void *out, size_t outlen, const void *in, size_t inlen,
+            const void *key, size_t keylen) {
+    blake2b_state S;
+    int ret = -1;
+
+    /* Verify parameters */
+    if (NULL == in && inlen > 0) {
+        goto fail;
+    }
+
+    if (NULL == out || outlen == 0 || outlen > BLAKE2B_OUTBYTES) {
+        goto fail;
+    }
+
+    if ((NULL == key && keylen > 0) || keylen > BLAKE2B_KEYBYTES) {
+        goto fail;
+    }
+
+    if (keylen > 0) {
+        if (blake2b_init_key(&S, outlen, key, keylen) < 0) {
+            goto fail;
+        }
+    } else {
+        if (blake2b_init(&S, outlen) < 0) {
+            goto fail;
+        }
+    }
+
+    if (blake2b_update(&S, in, inlen) < 0) {
+        goto fail;
+    }
+    ret = blake2b_final(&S, out, outlen);
+
+fail:
+    clear_internal_memory(&S, sizeof(S));
+    return ret;
+}
+
+/* Argon2 Team - Begin Code */
+int blake2b_long(void *pout, size_t outlen, const void *in, size_t inlen) {
+    uint8_t *out = (uint8_t *)pout;
+    blake2b_state blake_state;
+    uint8_t outlen_bytes[sizeof(uint32_t)] = {0};
+    int ret = -1;
+
+    if (outlen > UINT32_MAX) {
+        goto fail;
+    }
+
+    /* Ensure little-endian byte order! */
+    store32(outlen_bytes, (uint32_t)outlen);
+
+#define TRY(statement)                                                         \
+    do {                                                                       \
+        ret = statement;                                                       \
+        if (ret < 0) {                                                         \
+            goto fail;                                                         \
+        }                                                                      \
+    } while ((void)0, 0)
+
+    if (outlen <= BLAKE2B_OUTBYTES) {
+        TRY(blake2b_init(&blake_state, outlen));
+        TRY(blake2b_update(&blake_state, outlen_bytes, sizeof(outlen_bytes)));
+        TRY(blake2b_update(&blake_state, in, inlen));
+        TRY(blake2b_final(&blake_state, out, outlen));
+    } else {
+        uint32_t toproduce;
+        uint8_t out_buffer[BLAKE2B_OUTBYTES];
+        uint8_t in_buffer[BLAKE2B_OUTBYTES];
+        TRY(blake2b_init(&blake_state, BLAKE2B_OUTBYTES));
+        TRY(blake2b_update(&blake_state, outlen_bytes, sizeof(outlen_bytes)));
+        TRY(blake2b_update(&blake_state, in, inlen));
+        TRY(blake2b_final(&blake_state, out_buffer, BLAKE2B_OUTBYTES));
+        memcpy(out, out_buffer, BLAKE2B_OUTBYTES / 2);
+        out += BLAKE2B_OUTBYTES / 2;
+        toproduce = (uint32_t)outlen - BLAKE2B_OUTBYTES / 2;
+
+        while (toproduce > BLAKE2B_OUTBYTES) {
+            memcpy(in_buffer, out_buffer, BLAKE2B_OUTBYTES);
+            TRY(blake2b(out_buffer, BLAKE2B_OUTBYTES, in_buffer,
+                        BLAKE2B_OUTBYTES, NULL, 0));
+            memcpy(out, out_buffer, BLAKE2B_OUTBYTES / 2);
+            out += BLAKE2B_OUTBYTES / 2;
+            toproduce -= BLAKE2B_OUTBYTES / 2;
+        }
+
+        memcpy(in_buffer, out_buffer, BLAKE2B_OUTBYTES);
+        TRY(blake2b(out_buffer, toproduce, in_buffer, BLAKE2B_OUTBYTES, NULL,
+                    0));
+        memcpy(out, out_buffer, toproduce);
+    }
+fail:
+    clear_internal_memory(&blake_state, sizeof(blake_state));
+    return ret;
+#undef TRY
+}
+/* Argon2 Team - End Code */
diff --git a/src/argon2/blake2/blamka-round-ref.h b/src/argon2/blake2/blamka-round-ref.h
new file mode 100644
index 0000000..b8f2cf4
--- /dev/null
+++ b/src/argon2/blake2/blamka-round-ref.h
@@ -0,0 +1,56 @@
+/*
+ * Argon2 reference source code package - reference C implementations
+ *
+ * Copyright 2015
+ * Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
+ *
+ * You may use this work under the terms of a Creative Commons CC0 1.0
+ * License/Waiver or the Apache Public License 2.0, at your option. The terms of
+ * these licenses can be found at:
+ *
+ * - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0
+ * - Apache 2.0        : http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * You should have received a copy of both of these licenses along with this
+ * software. If not, they may be obtained at the above URLs.
+ */
+
+#ifndef BLAKE_ROUND_MKA_H
+#define BLAKE_ROUND_MKA_H
+
+#include "blake2.h"
+#include "blake2-impl.h"
+
+/* designed by the Lyra PHC team */
+static BLAKE2_INLINE uint64_t fBlaMka(uint64_t x, uint64_t y) {
+    const uint64_t m = UINT64_C(0xFFFFFFFF);
+    const uint64_t xy = (x & m) * (y & m);
+    return x + y + 2 * xy;
+}
+
+#define G(a, b, c, d)                                                          \
+    do {                                                                       \
+        a = fBlaMka(a, b);                                                     \
+        d = rotr64(d ^ a, 32);                                                 \
+        c = fBlaMka(c, d);                                                     \
+        b = rotr64(b ^ c, 24);                                                 \
+        a = fBlaMka(a, b);                                                     \
+        d = rotr64(d ^ a, 16);                                                 \
+        c = fBlaMka(c, d);                                                     \
+        b = rotr64(b ^ c, 63);                                                 \
+    } while ((void)0, 0)
+
+#define BLAKE2_ROUND_NOMSG(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11,   \
+                           v12, v13, v14, v15)                                 \
+    do {                                                                       \
+        G(v0, v4, v8, v12);                                                    \
+        G(v1, v5, v9, v13);                                                    \
+        G(v2, v6, v10, v14);                                                   \
+        G(v3, v7, v11, v15);                                                   \
+        G(v0, v5, v10, v15);                                                   \
+        G(v1, v6, v11, v12);                                                   \
+        G(v2, v7, v8, v13);                                                    \
+        G(v3, v4, v9, v14);                                                    \
+    } while ((void)0, 0)
+
+#endif
diff --git a/src/argon2/core.c b/src/argon2/core.c
new file mode 100644
index 0000000..5eafe08
--- /dev/null
+++ b/src/argon2/core.c
@@ -0,0 +1,543 @@
+/*
+ * Argon2 reference source code package - reference C implementations
+ *
+ * Copyright 2015
+ * Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
+ *
+ * You may use this work under the terms of a Creative Commons CC0 1.0
+ * License/Waiver or the Apache Public License 2.0, at your option. The terms of
+ * these licenses can be found at:
+ *
+ * - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0
+ * - Apache 2.0        : http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * You should have received a copy of both of these licenses along with this
+ * software. If not, they may be obtained at the above URLs.
+ */
+
+/*For memory wiping*/
+#ifdef _MSC_VER
+#include <windows.h>
+#include <winbase.h> /* For SecureZeroMemory */
+#endif
+#if defined __STDC_LIB_EXT1__
+#define __STDC_WANT_LIB_EXT1__ 1
+#endif
+#define VC_GE_2005(version) (version >= 1400)
+
+/* for explicit_bzero() on glibc */
+#define _DEFAULT_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "core.h"
+#include "blake2/blake2.h"
+#include "blake2/blake2-impl.h"
+
+#ifdef GENKAT
+#include "genkat.h"
+#endif
+
+#if defined(__clang__)
+#if __has_attribute(optnone)
+#define NOT_OPTIMIZED __attribute__((optnone))
+#endif
+#elif defined(__GNUC__)
+#define GCC_VERSION                                                            \
+    (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
+#if GCC_VERSION >= 40400
+#define NOT_OPTIMIZED __attribute__((optimize("O0")))
+#endif
+#endif
+#ifndef NOT_OPTIMIZED
+#define NOT_OPTIMIZED
+#endif
+
+/***************Instance and Position constructors**********/
+void init_block_value(block *b, uint8_t in) { memset(b->v, in, sizeof(b->v)); }
+
+void copy_block(block *dst, const block *src) {
+    memcpy(dst->v, src->v, sizeof(uint64_t) * ARGON2_QWORDS_IN_BLOCK);
+}
+
+void xor_block(block *dst, const block *src) {
+    int i;
+    for (i = 0; i < ARGON2_QWORDS_IN_BLOCK; ++i) {
+        dst->v[i] ^= src->v[i];
+    }
+}
+
+static void load_block(block *dst, const void *input) {
+    unsigned i;
+    for (i = 0; i < ARGON2_QWORDS_IN_BLOCK; ++i) {
+        dst->v[i] = load64((const uint8_t *)input + i * sizeof(dst->v[i]));
+    }
+}
+
+static void store_block(void *output, const block *src) {
+    unsigned i;
+    for (i = 0; i < ARGON2_QWORDS_IN_BLOCK; ++i) {
+        store64((uint8_t *)output + i * sizeof(src->v[i]), src->v[i]);
+    }
+}
+
+/***************Memory functions*****************/
+
+int allocate_memory(const argon2_context *context, uint8_t **memory,
+                    size_t num, size_t size) {
+    size_t memory_size = num*size;
+    if (memory == NULL) {
+        return ARGON2_MEMORY_ALLOCATION_ERROR;
+    }
+
+    /* 1. Check for multiplication overflow */
+    if (size != 0 && memory_size / size != num) {
+        return ARGON2_MEMORY_ALLOCATION_ERROR;
+    }
+
+    /* 2. Try to allocate with appropriate allocator */
+    if (context->allocate_cbk) {
+        (context->allocate_cbk)(memory, memory_size);
+    } else {
+        *memory = malloc(memory_size);
+    }
+
+    if (*memory == NULL) {
+        return ARGON2_MEMORY_ALLOCATION_ERROR;
+    }
+
+    return ARGON2_OK;
+}
+
+void free_memory(const argon2_context *context, uint8_t *memory,
+                 size_t num, size_t size) {
+    size_t memory_size = num*size;
+    clear_internal_memory(memory, memory_size);
+    if (context->free_cbk) {
+        (context->free_cbk)(memory, memory_size);
+    } else {
+        free(memory);
+    }
+}
+
+#if defined(__OpenBSD__)
+#define HAVE_EXPLICIT_BZERO 1
+#elif defined(__GLIBC__) && defined(__GLIBC_PREREQ)
+#if __GLIBC_PREREQ(2,25)
+#define HAVE_EXPLICIT_BZERO 1
+#endif
+#endif
+
+void NOT_OPTIMIZED secure_wipe_memory(void *v, size_t n) {
+#if defined(_MSC_VER) && VC_GE_2005(_MSC_VER)
+    SecureZeroMemory(v, n);
+#elif defined memset_s
+    memset_s(v, n, 0, n);
+#elif defined(HAVE_EXPLICIT_BZERO)
+    explicit_bzero(v, n);
+#else
+    static void *(*const volatile memset_sec)(void *, int, size_t) = &memset;
+    memset_sec(v, 0, n);
+#endif
+}
+
+/* Memory clear flag defaults to true. */
+int FLAG_clear_internal_memory = 1;
+void clear_internal_memory(void *v, size_t n) {
+  if (FLAG_clear_internal_memory && v) {
+    secure_wipe_memory(v, n);
+  }
+}
+
+void finalize(const argon2_context *context, argon2_instance_t *instance) {
+    if (context != NULL && instance != NULL) {
+        block blockhash;
+        uint32_t l;
+
+        copy_block(&blockhash, instance->memory + instance->lane_length - 1);
+
+        /* XOR the last blocks */
+        for (l = 1; l < instance->lanes; ++l) {
+            uint32_t last_block_in_lane =
+                l * instance->lane_length + (instance->lane_length - 1);
+            xor_block(&blockhash, instance->memory + last_block_in_lane);
+        }
+
+        /* Hash the result */
+        {
+            uint8_t blockhash_bytes[ARGON2_BLOCK_SIZE];
+            store_block(blockhash_bytes, &blockhash);
+            blake2b_long(context->out, context->outlen, blockhash_bytes,
+                         ARGON2_BLOCK_SIZE);
+            /* clear blockhash and blockhash_bytes */
+            clear_internal_memory(blockhash.v, ARGON2_BLOCK_SIZE);
+            clear_internal_memory(blockhash_bytes, ARGON2_BLOCK_SIZE);
+        }
+
+#ifdef GENKAT
+        print_tag(context->out, context->outlen);
+#endif
+
+        free_memory(context, (uint8_t *)instance->memory,
+                    instance->memory_blocks, sizeof(block));
+    }
+}
+
+uint32_t index_alpha(const argon2_instance_t *instance,
+                     const argon2_position_t *position, uint32_t pseudo_rand,
+                     int same_lane) {
+    /*
+     * Pass 0:
+     *      This lane : all already finished segments plus already constructed
+     * blocks in this segment
+     *      Other lanes : all already finished segments
+     * Pass 1+:
+     *      This lane : (SYNC_POINTS - 1) last segments plus already constructed
+     * blocks in this segment
+     *      Other lanes : (SYNC_POINTS - 1) last segments
+     */
+    uint32_t reference_area_size;
+    uint64_t relative_position;
+    uint32_t start_position, absolute_position;
+
+    if (0 == position->pass) {
+        /* First pass */
+        if (0 == position->slice) {
+            /* First slice */
+            reference_area_size =
+                position->index - 1; /* all but the previous */
+        } else {
+            if (same_lane) {
+                /* The same lane => add current segment */
+                reference_area_size =
+                    position->slice * instance->segment_length +
+                    position->index - 1;
+            } else {
+                reference_area_size =
+                    position->slice * instance->segment_length +
+                    ((position->index == 0) ? (-1) : 0);
+            }
+        }
+    } else {
+        /* Second pass */
+        if (same_lane) {
+            reference_area_size = instance->lane_length -
+                                  instance->segment_length + position->index -
+                                  1;
+        } else {
+            reference_area_size = instance->lane_length -
+                                  instance->segment_length +
+                                  ((position->index == 0) ? (-1) : 0);
+        }
+    }
+
+    /* 1.2.4. Mapping pseudo_rand to 0..<reference_area_size-1> and produce
+     * relative position */
+    relative_position = pseudo_rand;
+    relative_position = relative_position * relative_position >> 32;
+    relative_position = reference_area_size - 1 -
+                        (reference_area_size * relative_position >> 32);
+
+    /* 1.2.5 Computing starting position */
+    start_position = 0;
+
+    if (0 != position->pass) {
+        start_position = (position->slice == ARGON2_SYNC_POINTS - 1)
+                             ? 0
+                             : (position->slice + 1) * instance->segment_length;
+    }
+
+    /* 1.2.6. Computing absolute position */
+    absolute_position = (start_position + relative_position) %
+                        instance->lane_length; /* absolute position */
+    return absolute_position;
+}
+
+/* Single-threaded version for p=1 case */
+static int fill_memory_blocks_st(argon2_instance_t *instance) {
+    uint32_t r, s, l;
+
+    for (r = 0; r < instance->passes; ++r) {
+        for (s = 0; s < ARGON2_SYNC_POINTS; ++s) {
+            for (l = 0; l < instance->lanes; ++l) {
+                argon2_position_t position = {r, l, (uint8_t)s, 0};
+                fill_segment(instance, position);
+            }
+        }
+#ifdef GENKAT
+        internal_kat(instance, r); /* Print all memory blocks */
+#endif
+    }
+    return ARGON2_OK;
+}
+
+int fill_memory_blocks(argon2_instance_t *instance) {
+	if (instance == NULL || instance->lanes == 0) {
+	    return ARGON2_INCORRECT_PARAMETER;
+    }
+    return fill_memory_blocks_st(instance);
+}
+
+int validate_inputs(const argon2_context *context) {
+    if (NULL == context) {
+        return ARGON2_INCORRECT_PARAMETER;
+    }
+
+    if (NULL == context->out) {
+        return ARGON2_OUTPUT_PTR_NULL;
+    }
+
+    /* Validate output length */
+    if (ARGON2_MIN_OUTLEN > context->outlen) {
+        return ARGON2_OUTPUT_TOO_SHORT;
+    }
+
+    if (ARGON2_MAX_OUTLEN < context->outlen) {
+        return ARGON2_OUTPUT_TOO_LONG;
+    }
+
+    /* Validate password (required param) */
+    if (NULL == context->pwd) {
+        if (0 != context->pwdlen) {
+            return ARGON2_PWD_PTR_MISMATCH;
+        }
+    }
+
+    if (ARGON2_MIN_PWD_LENGTH > context->pwdlen) {
+      return ARGON2_PWD_TOO_SHORT;
+    }
+
+    if (ARGON2_MAX_PWD_LENGTH < context->pwdlen) {
+        return ARGON2_PWD_TOO_LONG;
+    }
+
+    /* Validate salt (required param) */
+    if (NULL == context->salt) {
+        if (0 != context->saltlen) {
+            return ARGON2_SALT_PTR_MISMATCH;
+        }
+    }
+
+    if (ARGON2_MIN_SALT_LENGTH > context->saltlen) {
+        return ARGON2_SALT_TOO_SHORT;
+    }
+
+    if (ARGON2_MAX_SALT_LENGTH < context->saltlen) {
+        return ARGON2_SALT_TOO_LONG;
+    }
+
+    /* Validate secret (optional param) */
+    if (NULL == context->secret) {
+        if (0 != context->secretlen) {
+            return ARGON2_SECRET_PTR_MISMATCH;
+        }
+    } else {
+        if (ARGON2_MIN_SECRET > context->secretlen) {
+            return ARGON2_SECRET_TOO_SHORT;
+        }
+        if (ARGON2_MAX_SECRET < context->secretlen) {
+            return ARGON2_SECRET_TOO_LONG;
+        }
+    }
+
+    /* Validate associated data (optional param) */
+    if (NULL == context->ad) {
+        if (0 != context->adlen) {
+            return ARGON2_AD_PTR_MISMATCH;
+        }
+    } else {
+        if (ARGON2_MIN_AD_LENGTH > context->adlen) {
+            return ARGON2_AD_TOO_SHORT;
+        }
+        if (ARGON2_MAX_AD_LENGTH < context->adlen) {
+            return ARGON2_AD_TOO_LONG;
+        }
+    }
+
+    /* Validate memory cost */
+    if (ARGON2_MIN_MEMORY > context->m_cost) {
+        return ARGON2_MEMORY_TOO_LITTLE;
+    }
+
+    if (ARGON2_MAX_MEMORY < context->m_cost) {
+        return ARGON2_MEMORY_TOO_MUCH;
+    }
+
+    if (context->m_cost < 8 * context->lanes) {
+        return ARGON2_MEMORY_TOO_LITTLE;
+    }
+
+    /* Validate time cost */
+    if (ARGON2_MIN_TIME > context->t_cost) {
+        return ARGON2_TIME_TOO_SMALL;
+    }
+
+    if (ARGON2_MAX_TIME < context->t_cost) {
+        return ARGON2_TIME_TOO_LARGE;
+    }
+
+    /* Validate lanes */
+    if (ARGON2_MIN_LANES > context->lanes) {
+        return ARGON2_LANES_TOO_FEW;
+    }
+
+    if (ARGON2_MAX_LANES < context->lanes) {
+        return ARGON2_LANES_TOO_MANY;
+    }
+
+    /* Validate threads */
+    if (ARGON2_MIN_THREADS > context->threads) {
+        return ARGON2_THREADS_TOO_FEW;
+    }
+
+    if (ARGON2_MAX_THREADS < context->threads) {
+        return ARGON2_THREADS_TOO_MANY;
+    }
+
+    if (NULL != context->allocate_cbk && NULL == context->free_cbk) {
+        return ARGON2_FREE_MEMORY_CBK_NULL;
+    }
+
+    if (NULL == context->allocate_cbk && NULL != context->free_cbk) {
+        return ARGON2_ALLOCATE_MEMORY_CBK_NULL;
+    }
+
+    return ARGON2_OK;
+}
+
+void fill_first_blocks(uint8_t *blockhash, const argon2_instance_t *instance) {
+    uint32_t l;
+    /* Make the first and second block in each lane as G(H0||0||i) or
+       G(H0||1||i) */
+    uint8_t blockhash_bytes[ARGON2_BLOCK_SIZE];
+    for (l = 0; l < instance->lanes; ++l) {
+
+        store32(blockhash + ARGON2_PREHASH_DIGEST_LENGTH, 0);
+        store32(blockhash + ARGON2_PREHASH_DIGEST_LENGTH + 4, l);
+        blake2b_long(blockhash_bytes, ARGON2_BLOCK_SIZE, blockhash,
+                     ARGON2_PREHASH_SEED_LENGTH);
+        load_block(&instance->memory[l * instance->lane_length + 0],
+                   blockhash_bytes);
+
+        store32(blockhash + ARGON2_PREHASH_DIGEST_LENGTH, 1);
+        blake2b_long(blockhash_bytes, ARGON2_BLOCK_SIZE, blockhash,
+                     ARGON2_PREHASH_SEED_LENGTH);
+        load_block(&instance->memory[l * instance->lane_length + 1],
+                   blockhash_bytes);
+    }
+    clear_internal_memory(blockhash_bytes, ARGON2_BLOCK_SIZE);
+}
+
+void initial_hash(uint8_t *blockhash, argon2_context *context,
+                  argon2_type type) {
+    blake2b_state BlakeHash;
+    uint8_t value[sizeof(uint32_t)];
+
+    if (NULL == context || NULL == blockhash) {
+        return;
+    }
+
+    blake2b_init(&BlakeHash, ARGON2_PREHASH_DIGEST_LENGTH);
+
+    store32(&value, context->lanes);
+    blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
+
+    store32(&value, context->outlen);
+    blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
+
+    store32(&value, context->m_cost);
+    blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
+
+    store32(&value, context->t_cost);
+    blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
+
+    store32(&value, context->version);
+    blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
+
+    store32(&value, (uint32_t)type);
+    blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
+
+    store32(&value, context->pwdlen);
+    blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
+
+    if (context->pwd != NULL) {
+        blake2b_update(&BlakeHash, (const uint8_t *)context->pwd,
+                       context->pwdlen);
+
+        if (context->flags & ARGON2_FLAG_CLEAR_PASSWORD) {
+            secure_wipe_memory(context->pwd, context->pwdlen);
+            context->pwdlen = 0;
+        }
+    }
+
+    store32(&value, context->saltlen);
+    blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
+
+    if (context->salt != NULL) {
+        blake2b_update(&BlakeHash, (const uint8_t *)context->salt,
+                       context->saltlen);
+    }
+
+    store32(&value, context->secretlen);
+    blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
+
+    if (context->secret != NULL) {
+        blake2b_update(&BlakeHash, (const uint8_t *)context->secret,
+                       context->secretlen);
+
+        if (context->flags & ARGON2_FLAG_CLEAR_SECRET) {
+            secure_wipe_memory(context->secret, context->secretlen);
+            context->secretlen = 0;
+        }
+    }
+
+    store32(&value, context->adlen);
+    blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value));
+
+    if (context->ad != NULL) {
+        blake2b_update(&BlakeHash, (const uint8_t *)context->ad,
+                       context->adlen);
+    }
+
+    blake2b_final(&BlakeHash, blockhash, ARGON2_PREHASH_DIGEST_LENGTH);
+}
+
+int initialize(argon2_instance_t *instance, argon2_context *context) {
+    uint8_t blockhash[ARGON2_PREHASH_SEED_LENGTH];
+    int result = ARGON2_OK;
+
+    if (instance == NULL || context == NULL)
+        return ARGON2_INCORRECT_PARAMETER;
+    instance->context_ptr = context;
+
+    /* 1. Memory allocation */
+    result = allocate_memory(context, (uint8_t **)&(instance->memory),
+                             instance->memory_blocks, sizeof(block));
+    if (result != ARGON2_OK) {
+        return result;
+    }
+
+    /* 2. Initial hashing */
+    /* H_0 + 8 extra bytes to produce the first blocks */
+    /* uint8_t blockhash[ARGON2_PREHASH_SEED_LENGTH]; */
+    /* Hashing all inputs */
+    initial_hash(blockhash, context, instance->type);
+    /* Zeroing 8 extra bytes */
+    clear_internal_memory(blockhash + ARGON2_PREHASH_DIGEST_LENGTH,
+                          ARGON2_PREHASH_SEED_LENGTH -
+                              ARGON2_PREHASH_DIGEST_LENGTH);
+
+#ifdef GENKAT
+    initial_kat(blockhash, context, instance->type);
+#endif
+
+    /* 3. Creating first blocks, we always have at least two blocks in a slice
+     */
+    fill_first_blocks(blockhash, instance);
+    /* Clearing the hash */
+    clear_internal_memory(blockhash, ARGON2_PREHASH_SEED_LENGTH);
+
+    return ARGON2_OK;
+}
diff --git a/src/argon2/core.h b/src/argon2/core.h
new file mode 100644
index 0000000..78000ba
--- /dev/null
+++ b/src/argon2/core.h
@@ -0,0 +1,228 @@
+/*
+ * Argon2 reference source code package - reference C implementations
+ *
+ * Copyright 2015
+ * Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
+ *
+ * You may use this work under the terms of a Creative Commons CC0 1.0
+ * License/Waiver or the Apache Public License 2.0, at your option. The terms of
+ * these licenses can be found at:
+ *
+ * - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0
+ * - Apache 2.0        : http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * You should have received a copy of both of these licenses along with this
+ * software. If not, they may be obtained at the above URLs.
+ */
+
+#ifndef ARGON2_CORE_H
+#define ARGON2_CORE_H
+
+#include "argon2.h"
+
+#define CONST_CAST(x) (x)(uintptr_t)
+
+/**********************Argon2 internal constants*******************************/
+
+enum argon2_core_constants {
+    /* Memory block size in bytes */
+    ARGON2_BLOCK_SIZE = 1024,
+    ARGON2_QWORDS_IN_BLOCK = ARGON2_BLOCK_SIZE / 8,
+    ARGON2_OWORDS_IN_BLOCK = ARGON2_BLOCK_SIZE / 16,
+    ARGON2_HWORDS_IN_BLOCK = ARGON2_BLOCK_SIZE / 32,
+    ARGON2_512BIT_WORDS_IN_BLOCK = ARGON2_BLOCK_SIZE / 64,
+
+    /* Number of pseudo-random values generated by one call to Blake in Argon2i
+       to
+       generate reference block positions */
+    ARGON2_ADDRESSES_IN_BLOCK = 128,
+
+    /* Pre-hashing digest length and its extension*/
+    ARGON2_PREHASH_DIGEST_LENGTH = 64,
+    ARGON2_PREHASH_SEED_LENGTH = 72
+};
+
+/*************************Argon2 internal data types***********************/
+
+/*
+ * Structure for the (1KB) memory block implemented as 128 64-bit words.
+ * Memory blocks can be copied, XORed. Internal words can be accessed by [] (no
+ * bounds checking).
+ */
+typedef struct block_ { uint64_t v[ARGON2_QWORDS_IN_BLOCK]; } block;
+
+/*****************Functions that work with the block******************/
+
+/* Initialize each byte of the block with @in */
+void init_block_value(block *b, uint8_t in);
+
+/* Copy block @src to block @dst */
+void copy_block(block *dst, const block *src);
+
+/* XOR @src onto @dst bytewise */
+void xor_block(block *dst, const block *src);
+
+/*
+ * Argon2 instance: memory pointer, number of passes, amount of memory, type,
+ * and derived values.
+ * Used to evaluate the number and location of blocks to construct in each
+ * thread
+ */
+typedef struct Argon2_instance_t {
+    block *memory;          /* Memory pointer */
+    uint32_t version;
+    uint32_t passes;        /* Number of passes */
+    uint32_t memory_blocks; /* Number of blocks in memory */
+    uint32_t segment_length;
+    uint32_t lane_length;
+    uint32_t lanes;
+    uint32_t threads;
+    argon2_type type;
+    int print_internals; /* whether to print the memory blocks */
+    argon2_context *context_ptr; /* points back to original context */
+} argon2_instance_t;
+
+/*
+ * Argon2 position: where we construct the block right now. Used to distribute
+ * work between threads.
+ */
+typedef struct Argon2_position_t {
+    uint32_t pass;
+    uint32_t lane;
+    uint8_t slice;
+    uint32_t index;
+} argon2_position_t;
+
+/*Struct that holds the inputs for thread handling FillSegment*/
+typedef struct Argon2_thread_data {
+    argon2_instance_t *instance_ptr;
+    argon2_position_t pos;
+} argon2_thread_data;
+
+/*************************Argon2 core functions********************************/
+
+/* Allocates memory to the given pointer, uses the appropriate allocator as
+ * specified in the context. Total allocated memory is num*size.
+ * @param context argon2_context which specifies the allocator
+ * @param memory pointer to the pointer to the memory
+ * @param size the size in bytes for each element to be allocated
+ * @param num the number of elements to be allocated
+ * @return ARGON2_OK if @memory is a valid pointer and memory is allocated
+ */
+int allocate_memory(const argon2_context *context, uint8_t **memory,
+                    size_t num, size_t size);
+
+/*
+ * Frees memory at the given pointer, uses the appropriate deallocator as
+ * specified in the context. Also cleans the memory using clear_internal_memory.
+ * @param context argon2_context which specifies the deallocator
+ * @param memory pointer to buffer to be freed
+ * @param size the size in bytes for each element to be deallocated
+ * @param num the number of elements to be deallocated
+ */
+void free_memory(const argon2_context *context, uint8_t *memory,
+                 size_t num, size_t size);
+
+/* Function that securely cleans the memory. This ignores any flags set
+ * regarding clearing memory. Usually one just calls clear_internal_memory.
+ * @param mem Pointer to the memory
+ * @param s Memory size in bytes
+ */
+void secure_wipe_memory(void *v, size_t n);
+
+/* Function that securely clears the memory if FLAG_clear_internal_memory is
+ * set. If the flag isn't set, this function does nothing.
+ * @param mem Pointer to the memory
+ * @param s Memory size in bytes
+ */
+void clear_internal_memory(void *v, size_t n);
+
+/*
+ * Computes absolute position of reference block in the lane following a skewed
+ * distribution and using a pseudo-random value as input
+ * @param instance Pointer to the current instance
+ * @param position Pointer to the current position
+ * @param pseudo_rand 32-bit pseudo-random value used to determine the position
+ * @param same_lane Indicates if the block will be taken from the current lane.
+ * If so we can reference the current segment
+ * @pre All pointers must be valid
+ */
+uint32_t index_alpha(const argon2_instance_t *instance,
+                     const argon2_position_t *position, uint32_t pseudo_rand,
+                     int same_lane);
+
+/*
+ * Function that validates all inputs against predefined restrictions and return
+ * an error code
+ * @param context Pointer to current Argon2 context
+ * @return ARGON2_OK if everything is all right, otherwise one of error codes
+ * (all defined in <argon2.h>
+ */
+int validate_inputs(const argon2_context *context);
+
+/*
+ * Hashes all the inputs into @a blockhash[PREHASH_DIGEST_LENGTH], clears
+ * password and secret if needed
+ * @param  context  Pointer to the Argon2 internal structure containing memory
+ * pointer, and parameters for time and space requirements.
+ * @param  blockhash Buffer for pre-hashing digest
+ * @param  type Argon2 type
+ * @pre    @a blockhash must have at least @a PREHASH_DIGEST_LENGTH bytes
+ * allocated
+ */
+void initial_hash(uint8_t *blockhash, argon2_context *context,
+                  argon2_type type);
+
+/*
+ * Function creates first 2 blocks per lane
+ * @param instance Pointer to the current instance
+ * @param blockhash Pointer to the pre-hashing digest
+ * @pre blockhash must point to @a PREHASH_SEED_LENGTH allocated values
+ */
+void fill_first_blocks(uint8_t *blockhash, const argon2_instance_t *instance);
+
+/*
+ * Function allocates memory, hashes the inputs with Blake,  and creates first
+ * two blocks. Returns the pointer to the main memory with 2 blocks per lane
+ * initialized
+ * @param  context  Pointer to the Argon2 internal structure containing memory
+ * pointer, and parameters for time and space requirements.
+ * @param  instance Current Argon2 instance
+ * @return Zero if successful, -1 if memory failed to allocate. @context->state
+ * will be modified if successful.
+ */
+int initialize(argon2_instance_t *instance, argon2_context *context);
+
+/*
+ * XORing the last block of each lane, hashing it, making the tag. Deallocates
+ * the memory.
+ * @param context Pointer to current Argon2 context (use only the out parameters
+ * from it)
+ * @param instance Pointer to current instance of Argon2
+ * @pre instance->state must point to necessary amount of memory
+ * @pre context->out must point to outlen bytes of memory
+ * @pre if context->free_cbk is not NULL, it should point to a function that
+ * deallocates memory
+ */
+void finalize(const argon2_context *context, argon2_instance_t *instance);
+
+/*
+ * Function that fills the segment using previous segments also from other
+ * threads
+ * @param context current context
+ * @param instance Pointer to the current instance
+ * @param position Current position
+ * @pre all block pointers must be valid
+ */
+void fill_segment(const argon2_instance_t *instance,
+                  argon2_position_t position);
+
+/*
+ * Function that fills the entire memory t_cost times based on the first two
+ * blocks in each lane
+ * @param instance Pointer to the current instance
+ * @return ARGON2_OK if successful, @context->state
+ */
+int fill_memory_blocks(argon2_instance_t *instance);
+
+#endif
diff --git a/src/argon2/ref.c b/src/argon2/ref.c
new file mode 100644
index 0000000..ad1cf46
--- /dev/null
+++ b/src/argon2/ref.c
@@ -0,0 +1,194 @@
+/*
+ * Argon2 reference source code package - reference C implementations
+ *
+ * Copyright 2015
+ * Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves
+ *
+ * You may use this work under the terms of a Creative Commons CC0 1.0
+ * License/Waiver or the Apache Public License 2.0, at your option. The terms of
+ * these licenses can be found at:
+ *
+ * - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0
+ * - Apache 2.0        : http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * You should have received a copy of both of these licenses along with this
+ * software. If not, they may be obtained at the above URLs.
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "argon2.h"
+#include "core.h"
+
+#include "blake2/blamka-round-ref.h"
+#include "blake2/blake2-impl.h"
+#include "blake2/blake2.h"
+
+
+/*
+ * Function fills a new memory block and optionally XORs the old block over the new one.
+ * @next_block must be initialized.
+ * @param prev_block Pointer to the previous block
+ * @param ref_block Pointer to the reference block
+ * @param next_block Pointer to the block to be constructed
+ * @param with_xor Whether to XOR into the new block (1) or just overwrite (0)
+ * @pre all block pointers must be valid
+ */
+static void fill_block(const block *prev_block, const block *ref_block,
+                       block *next_block, int with_xor) {
+    block blockR, block_tmp;
+    unsigned i;
+
+    copy_block(&blockR, ref_block);
+    xor_block(&blockR, prev_block);
+    copy_block(&block_tmp, &blockR);
+    /* Now blockR = ref_block + prev_block and block_tmp = ref_block + prev_block */
+    if (with_xor) {
+        /* Saving the next block contents for XOR over: */
+        xor_block(&block_tmp, next_block);
+        /* Now blockR = ref_block + prev_block and
+           block_tmp = ref_block + prev_block + next_block */
+    }
+
+    /* Apply Blake2 on columns of 64-bit words: (0,1,...,15) , then
+       (16,17,..31)... finally (112,113,...127) */
+    for (i = 0; i < 8; ++i) {
+        BLAKE2_ROUND_NOMSG(
+            blockR.v[16 * i], blockR.v[16 * i + 1], blockR.v[16 * i + 2],
+            blockR.v[16 * i + 3], blockR.v[16 * i + 4], blockR.v[16 * i + 5],
+            blockR.v[16 * i + 6], blockR.v[16 * i + 7], blockR.v[16 * i + 8],
+            blockR.v[16 * i + 9], blockR.v[16 * i + 10], blockR.v[16 * i + 11],
+            blockR.v[16 * i + 12], blockR.v[16 * i + 13], blockR.v[16 * i + 14],
+            blockR.v[16 * i + 15]);
+    }
+
+    /* Apply Blake2 on rows of 64-bit words: (0,1,16,17,...112,113), then
+       (2,3,18,19,...,114,115).. finally (14,15,30,31,...,126,127) */
+    for (i = 0; i < 8; i++) {
+        BLAKE2_ROUND_NOMSG(
+            blockR.v[2 * i], blockR.v[2 * i + 1], blockR.v[2 * i + 16],
+            blockR.v[2 * i + 17], blockR.v[2 * i + 32], blockR.v[2 * i + 33],
+            blockR.v[2 * i + 48], blockR.v[2 * i + 49], blockR.v[2 * i + 64],
+            blockR.v[2 * i + 65], blockR.v[2 * i + 80], blockR.v[2 * i + 81],
+            blockR.v[2 * i + 96], blockR.v[2 * i + 97], blockR.v[2 * i + 112],
+            blockR.v[2 * i + 113]);
+    }
+
+    copy_block(next_block, &block_tmp);
+    xor_block(next_block, &blockR);
+}
+
+static void next_addresses(block *address_block, block *input_block,
+                           const block *zero_block) {
+    input_block->v[6]++;
+    fill_block(zero_block, input_block, address_block, 0);
+    fill_block(zero_block, address_block, address_block, 0);
+}
+
+void fill_segment(const argon2_instance_t *instance,
+                  argon2_position_t position) {
+    block *ref_block = NULL, *curr_block = NULL;
+    block address_block, input_block, zero_block;
+    uint64_t pseudo_rand, ref_index, ref_lane;
+    uint32_t prev_offset, curr_offset;
+    uint32_t starting_index;
+    uint32_t i;
+    int data_independent_addressing;
+
+    if (instance == NULL) {
+        return;
+    }
+
+    data_independent_addressing =
+        (instance->type == Argon2_i) ||
+        (instance->type == Argon2_id && (position.pass == 0) &&
+         (position.slice < ARGON2_SYNC_POINTS / 2));
+
+    if (data_independent_addressing) {
+        init_block_value(&zero_block, 0);
+        init_block_value(&input_block, 0);
+
+        input_block.v[0] = position.pass;
+        input_block.v[1] = position.lane;
+        input_block.v[2] = position.slice;
+        input_block.v[3] = instance->memory_blocks;
+        input_block.v[4] = instance->passes;
+        input_block.v[5] = instance->type;
+    }
+
+    starting_index = 0;
+
+    if ((0 == position.pass) && (0 == position.slice)) {
+        starting_index = 2; /* we have already generated the first two blocks */
+
+        /* Don't forget to generate the first block of addresses: */
+        if (data_independent_addressing) {
+            next_addresses(&address_block, &input_block, &zero_block);
+        }
+    }
+
+    /* Offset of the current block */
+    curr_offset = position.lane * instance->lane_length +
+                  position.slice * instance->segment_length + starting_index;
+
+    if (0 == curr_offset % instance->lane_length) {
+        /* Last block in this lane */
+        prev_offset = curr_offset + instance->lane_length - 1;
+    } else {
+        /* Previous block */
+        prev_offset = curr_offset - 1;
+    }
+
+    for (i = starting_index; i < instance->segment_length;
+         ++i, ++curr_offset, ++prev_offset) {
+        /*1.1 Rotating prev_offset if needed */
+        if (curr_offset % instance->lane_length == 1) {
+            prev_offset = curr_offset - 1;
+        }
+
+        /* 1.2 Computing the index of the reference block */
+        /* 1.2.1 Taking pseudo-random value from the previous block */
+        if (data_independent_addressing) {
+            if (i % ARGON2_ADDRESSES_IN_BLOCK == 0) {
+                next_addresses(&address_block, &input_block, &zero_block);
+            }
+            pseudo_rand = address_block.v[i % ARGON2_ADDRESSES_IN_BLOCK];
+        } else {
+            pseudo_rand = instance->memory[prev_offset].v[0];
+        }
+
+        /* 1.2.2 Computing the lane of the reference block */
+        ref_lane = ((pseudo_rand >> 32)) % instance->lanes;
+
+        if ((position.pass == 0) && (position.slice == 0)) {
+            /* Can not reference other lanes yet */
+            ref_lane = position.lane;
+        }
+
+        /* 1.2.3 Computing the number of possible reference block within the
+         * lane.
+         */
+        position.index = i;
+        ref_index = index_alpha(instance, &position, pseudo_rand & 0xFFFFFFFF,
+                                ref_lane == position.lane);
+
+        /* 2 Creating a new block */
+        ref_block =
+            instance->memory + instance->lane_length * ref_lane + ref_index;
+        curr_block = instance->memory + curr_offset;
+        if (ARGON2_VERSION_10 == instance->version) {
+            /* version 1.2.1 and earlier: overwrite, not XOR */
+            fill_block(instance->memory + prev_offset, ref_block, curr_block, 0);
+        } else {
+            if(0 == position.pass) {
+                fill_block(instance->memory + prev_offset, ref_block,
+                           curr_block, 0);
+            } else {
+                fill_block(instance->memory + prev_offset, ref_block,
+                           curr_block, 1);
+            }
+        }
+    }
+}
diff --git a/src/galois_field.cpp b/src/galois_field.cpp
new file mode 100644
index 0000000..88cb838
--- /dev/null
+++ b/src/galois_field.cpp
@@ -0,0 +1,32 @@
+/*
+	Copyright (c) 2020 tevador <tevador@gmail.com>
+	All rights reserved.
+*/
+
+#include "galois_field.hpp"
+
+template<unsigned bits, gf_item primitive>
+galois_field<bits, primitive>::galois_field() {
+	gf_item b = 1;
+	for (gf_item i = 0; i < size_; ++i) {
+		log_table[b] = i;
+		exp_table[i] = b;
+		b <<= 1;
+		if ((b & size_) != 0)
+			b ^= primitive;
+	}
+	for (auto i = size_ - 1; i < (2 * size_); ++i) {
+		exp_table[i] = exp_table[i - (size_ - 1)];
+	}
+	for (gf_item i = 2; i < size_; i++) {
+		for (gf_item j = 2; j < size_; j++) {
+			auto p = mult(i, j);
+			if (p == 1) {
+				inv_table[i] = j;
+				break;
+			}
+		}
+	}
+}
+
+template class galois_field<11, 2053>;
diff --git a/src/galois_field.hpp b/src/galois_field.hpp
new file mode 100644
index 0000000..20d9850
--- /dev/null
+++ b/src/galois_field.hpp
@@ -0,0 +1,47 @@
+/*
+	Copyright (c) 2020 tevador <tevador@gmail.com>
+	All rights reserved.
+*/
+
+#pragma once
+#include <cstdint>
+#include <cassert>
+
+typedef uint_least16_t gf_storage;
+typedef uint_fast16_t gf_item;
+
+template<unsigned bits, gf_item primitive>
+class galois_field {
+public:
+	galois_field();
+	gf_item inverse(gf_item i) const {
+		assert(i != 0);
+		return i > 1 ? inv_table[i] : 1;
+	}
+	gf_item mult(gf_item a, gf_item b) const {
+		if (b == 0 || a == 0)
+			return 0;
+		if (b == 1)
+			return a;
+		if (a == 1)
+			return b;
+		return exp_table[log_table[a] + log_table[b]];
+	}
+	gf_item exp(gf_item i) const {
+		return exp_table[i];
+	}
+	static constexpr unsigned size() {
+		return bits;
+	}
+	static constexpr unsigned elements() {
+		return size_;
+	}
+private:
+	static_assert(bits <= 14, "field is too large");
+	static constexpr gf_item size_ = 1u << bits;
+	gf_storage log_table[size_];
+	gf_storage exp_table[2 * size_];
+	gf_storage inv_table[size_];
+};
+
+using gf_2048 = galois_field<11, 2053>;
diff --git a/src/gf_elem.cpp b/src/gf_elem.cpp
new file mode 100644
index 0000000..26924fc
--- /dev/null
+++ b/src/gf_elem.cpp
@@ -0,0 +1,8 @@
+/*
+	Copyright (c) 2020 tevador <tevador@gmail.com>
+	All rights reserved.
+*/
+
+#include "gf_elem.hpp"
+
+const gf_2048 gf_elem::field;
diff --git a/src/gf_elem.hpp b/src/gf_elem.hpp
new file mode 100644
index 0000000..56bea43
--- /dev/null
+++ b/src/gf_elem.hpp
@@ -0,0 +1,68 @@
+/*
+    Copyright (c) 2020 tevador <tevador@gmail.com>
+    All rights reserved.
+*/
+
+#pragma once
+#include "galois_field.hpp"
+
+class gf_elem {
+public:
+    static constexpr gf_item size() {
+        return gf_2048::size();
+    }
+    gf_elem() : value_(0)
+    {
+    }
+    gf_elem(gf_item value) : value_(value)
+    {
+    }
+    gf_elem& operator+=(gf_elem x) {
+        value_ ^= x.value_;
+        return *this;
+    }
+    gf_elem& operator|=(gf_elem x) {
+        value_ |= x.value_;
+        return *this;
+    }
+    gf_elem& operator-=(gf_elem x) {
+        value_ ^= x.value_;
+        return *this;
+    }
+    gf_elem& operator*=(gf_elem x) {
+        value_ = field.mult(value_, x.value_);
+        return *this;
+    }
+    friend gf_elem operator+(gf_elem left, gf_elem right) {
+        left += right;
+        return left;
+    }
+    friend gf_elem operator-(gf_elem left, gf_elem right) {
+        left -= right;
+        return left;
+    }
+    friend gf_elem operator*(gf_elem left, gf_elem right) {
+        left *= right;
+        return left;
+    }
+    friend bool operator==(gf_elem lhs, gf_elem rhs) {
+        return lhs.value_ == rhs.value_;
+    }
+    friend bool operator!=(gf_elem lhs, gf_elem rhs) {
+        return !(lhs == rhs);
+    }
+    gf_elem& inverse() {
+        value_ = field.inverse(value_);
+        return *this;
+    }
+    gf_elem& exp() {
+        value_ = field.exp(value_);
+        return *this;
+    }
+    gf_item value() const {
+        return value_;
+    }
+private:
+	static const gf_2048 field;
+	gf_item value_;
+};
diff --git a/src/gf_poly.cpp b/src/gf_poly.cpp
new file mode 100644
index 0000000..83d9380
--- /dev/null
+++ b/src/gf_poly.cpp
@@ -0,0 +1,135 @@
+/*
+	Copyright (c) 2020 tevador <tevador@gmail.com>
+	All rights reserved.
+*/
+
+#include "gf_poly.hpp"
+
+gf_poly::gf_poly(gf_elem coeff, unsigned degree) : degree_(0) {
+	coeff_[degree] = coeff;
+	if (coeff != 0) {
+		degree_ = degree;
+	}
+}
+
+gf_poly::gf_poly(gf_elem coeff[], unsigned degree) : degree_(0) {
+	assert(degree <= max_degree);
+	for (unsigned i = 0; i <= degree; ++i) {
+		coeff_[i] = coeff[i];
+		if (coeff_[i] != 0) {
+			degree_ = i;
+		}
+	}
+}
+
+void gf_poly::set_degree() {
+	degree_ = 0;
+	for (unsigned i = 0; i <= max_degree; ++i) {
+		if (coeff_[i] != 0) {
+			degree_ = i;
+		}
+	}
+}
+
+gf_poly& gf_poly::operator+=(const gf_poly& x) {
+	auto degree = std::max(degree_, x.degree_);
+	degree_ = 0;
+	for (unsigned i = 0; i <= degree; ++i) {
+		coeff_[i] += x[i];
+		if (coeff_[i] != 0) {
+			degree_ = i;
+		}
+	}
+	return *this;
+}
+
+gf_poly& gf_poly::operator-=(const gf_poly& x) {
+	auto degree = std::max(degree_, x.degree_);
+	degree_ = 0;
+	for (unsigned i = 0; i <= degree; ++i) {
+		coeff_[i] -= x[i];
+		if (coeff_[i] != 0) {
+			degree_ = i;
+		}
+	}
+	return *this;
+}
+
+gf_poly& gf_poly::operator*=(gf_elem x) {
+	auto degree = degree_;
+	for (unsigned i = 0; i <= degree; ++i) {
+		coeff_[i] *= x;
+		if (coeff_[i] != 0) {
+			degree_ = i;
+		}
+	}
+	return *this;
+}
+
+gf_poly& gf_poly::operator*=(const gf_poly& x) {
+	gf_poly result;
+	for (unsigned i = 0; i <= degree_; ++i) {
+		for (unsigned j = 0; j <= x.degree_; ++j) {
+			result.coeff_[i + j] += coeff_[i] * x[j];
+		}
+	}
+	for (unsigned i = 0; i <= degree_ + x.degree_; ++i) {
+		if (result.coeff_[i] != 0) {
+			result.degree_ = i;
+		}
+	}
+	return *this = result;
+}
+
+gf_elem gf_poly::operator()(gf_elem x) const {
+	if (x == 0) {
+		return coeff_[0];
+	}
+	//Horner's method
+	auto result = coeff_[degree_];
+	for (unsigned i = degree_ - 1; i < degree_; --i) {
+		result = result * x + coeff_[i];
+	}
+	return result;
+}
+
+gf_poly gf_poly::div_rem(const gf_poly& nom, const gf_poly& x, gf_poly& rem) {
+	assert(!x.is_zero());
+	gf_poly quotient;
+	rem = gf_poly(nom);
+	gf_elem divisor_term = x[x.degree_];
+	divisor_term.inverse();
+	while (rem.degree_ >= x.degree_ && !rem.is_zero()) {
+		auto degree_diff = rem.degree_ - x.degree_;
+		auto digit = rem[rem.degree_] * divisor_term;
+		gf_poly mono(digit, degree_diff);
+		gf_poly term = x * mono;
+		quotient += mono;
+		rem -= term;
+	}
+
+	return quotient;
+}
+
+
+
+std::ostream& operator<<(std::ostream& os, const gf_poly& poly) {
+	bool term = false;
+	for (unsigned i = poly.degree_; i <= poly.degree_; --i) {
+		if (poly[i] != 0 || i == 0) {
+			if (term)
+				std::cout << " + ";
+			if (i == 0 || poly[i] != 1) {
+				std::cout << poly[i].value();
+			}
+			if (i != 0) {
+				if (i == 1)
+					std::cout << "x";
+				else
+					std::cout << "x**" << i;
+			}
+			term = true;
+		}
+	}
+	return os;
+}
diff --git a/src/gf_poly.hpp b/src/gf_poly.hpp
new file mode 100644
index 0000000..42caa9a
--- /dev/null
+++ b/src/gf_poly.hpp
@@ -0,0 +1,57 @@
+/*
+	Copyright (c) 2020 tevador <tevador@gmail.com>
+	All rights reserved.
+*/
+
+#pragma once
+#include "gf_elem.hpp"
+#include <cassert>
+#include <algorithm>
+#include <iostream>
+
+class gf_poly {
+public:
+	static constexpr size_t max_degree = 13;
+	gf_poly() : degree_(0) //zero polynomial
+	{
+	}
+	gf_poly(gf_elem coeff, unsigned degree); //monomial
+	gf_poly(unsigned degree) : gf_poly(1, degree)
+	{
+	}
+	gf_poly(gf_elem coeff[], unsigned degree);
+	unsigned degree() const {
+		return degree_;
+	}
+	void set_degree();
+	void set_degree(unsigned degree) {
+		degree_ = degree;
+	}
+	bool is_zero() const {
+		return degree_ == 0 && coeff_[0] == 0;
+	}
+	gf_elem operator[](unsigned i) const {
+		return coeff_[i];
+	}
+	gf_elem& operator[](unsigned i) {
+		return coeff_[i];
+	}
+	gf_elem operator()(gf_elem x) const; //evaluate at point x
+	gf_poly& operator+=(const gf_poly& x);
+	gf_poly& operator-=(const gf_poly& x);
+	gf_poly& operator*=(gf_elem x);
+	gf_poly& operator*=(const gf_poly& x);
+	friend gf_poly operator*(const gf_poly& lhs, const gf_poly& rhs) {
+		gf_poly result(lhs);
+		return result *= rhs;
+	}
+	friend gf_poly operator+(const gf_poly& lhs, const gf_poly& rhs) {
+		gf_poly result(lhs);
+		return result += rhs;
+	}
+	static gf_poly div_rem(const gf_poly& nom, const gf_poly& x, gf_poly& rem);
+	friend std::ostream& operator<<(std::ostream& os, const gf_poly& poly);
+private:
+	gf_elem coeff_[2 * (max_degree + 1)] = { };
+	unsigned degree_;
+};
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..411111b
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,83 @@
+/*
+	Copyright (c) 2020 tevador <tevador@gmail.com>
+	All rights reserved.
+*/
+
+#include "monero_seed.hpp"
+#include <iostream>
+#include <iomanip>
+#include <sstream>
+#include <stdexcept>
+#include <cstring>
+
+static inline void read_string_option(const char* option, int argc, char** argv, char** out) {
+	for (int i = 0; i < argc - 1; ++i) {
+		if (strcmp(argv[i], option) == 0) {
+			*out = argv[i + 1];
+			return;
+		}
+	}
+	*out = NULL;
+}
+
+static time_t parse_date(const char* s) {
+	std::istringstream iss(s);
+	char delimiter;
+	int day, month, year;
+	if (iss >> year >> delimiter >> month >> delimiter >> day) {
+		struct tm t = { 0 };
+		t.tm_mday = day;
+		t.tm_mon = month - 1;
+		t.tm_year = year - 1900;
+		t.tm_isdst = -1;
+
+		time_t dt = mktime(&t);
+		if (dt != -1) {
+			return dt;
+		}
+	}
+	throw std::runtime_error("invalid date");
+}
+
+void print_seed(const monero_seed& seed, bool phrase) {
+	if (!seed.correction().empty()) {
+		std::cout << "Warning: corrected erasure: " << monero_seed::erasure << " -> " << seed.correction() << std::endl;
+	}
+	if (phrase) {
+		std::cout << "Mnemonic phrase: " << seed << std::endl;
+	}
+	std::cout << "- version: " << seed.version() << std::endl;
+	std::cout << "- private key: " << seed.key() << std::endl;
+	auto created_on = seed.date();
+	std::tm tm = *std::localtime(&created_on);
+	std::cout << "- created on or after: " << std::put_time(&tm, "%d/%b/%Y") << std::endl;
+}
+
+int main(int argc, char** argv) {
+	char* create;
+	char* restore;
+	read_string_option("--create", argc, argv, &create);
+	read_string_option("--restore", argc, argv, &restore);
+
+	try {
+		if (create != NULL) {
+			monero_seed seed(parse_date(create));
+			print_seed(seed, true);
+		}
+		else if (restore != NULL) {
+			monero_seed seed(restore);
+			print_seed(seed, false);
+		}
+		else {
+			std::cout << "Monero 14-word mnemonic seed proof of concept" << std::endl;
+			std::cout << "Usage: " << std::endl;
+			std::cout << argv[0] << " --create <yyyy-MM-dd>" << std::endl;
+			std::cout << argv[0] << " --restore <14-word seed>" << std::endl;
+		}
+	}
+	catch (const std::exception & ex) {
+		std::cout << "ERROR: " << ex.what() << std::endl;
+		return 1;
+	}
+	return 0;
+}
diff --git a/src/monero_seed.cpp b/src/monero_seed.cpp
new file mode 100644
index 0000000..279bfa5
--- /dev/null
+++ b/src/monero_seed.cpp
@@ -0,0 +1,212 @@
+/*
+	Copyright (c) 2020 tevador <tevador@gmail.com>
+	All rights reserved.
+*/
+
+#include "monero_seed.hpp"
+#include "secure_random.hpp"
+#include "wordlist.hpp"
+#include "gf_poly.hpp"
+#include "reed_solomon_code.hpp"
+#include "argon2/argon2.h"
+#include "argon2/blake2/blake2-impl.h"
+#include <chrono>
+#include <cassert>
+#include <stdexcept>
+#include <cstdint>
+#include <climits>
+#include <iomanip>
+#include <sstream>
+#include <algorithm>
+
+const std::string monero_seed::erasure = "xxxx";
+
+class monero_seed_exception : public std::exception {
+public:
+	monero_seed_exception(const std::string& msg)
+		: msg_(msg)
+	{ }
+	~monero_seed_exception() throw() {}
+
+	const char* what() const throw() override {
+		return msg_.c_str();
+	}
+private:
+	std::string msg_;
+};
+
+#define THROW_EXCEPTION(message) do { \
+		std::ostringstream oss; \
+		oss << message; \
+		throw monero_seed_exception(oss.str()); } \
+	while(false)
+
+constexpr std::time_t epoch = 1590969600; //1st June 2020
+constexpr std::time_t time_step = 2629746; //30.436875 days = 1/12 of the Gregorian year
+
+constexpr unsigned date_bits = 10;
+constexpr unsigned date_mask = (1u << date_bits) - 1;
+constexpr unsigned version_bits = 3;
+constexpr unsigned version_mask = (1u << version_bits) - 1;
+constexpr unsigned reserved_bits = 2;
+constexpr unsigned reserved_mask = (1u << reserved_bits) - 1;
+constexpr unsigned check_digits = 1;
+constexpr unsigned checksum_size = gf_elem::size() * check_digits;
+constexpr unsigned phrase_words = gf_poly::max_degree + 1;
+constexpr unsigned total_bits = gf_elem::size() * phrase_words;
+constexpr uint32_t argon_tcost = 3;
+constexpr uint32_t argon_mcost = 256 * 1024;
+
+static_assert(total_bits
+	== version_bits + date_bits + reserved_bits + checksum_size +
+	sizeof(monero_seed::secret_seed) * CHAR_BIT,
+	"Invalid mnemonic seed size");
+
+static void write_data(gf_poly& poly, unsigned& rem_bits, unsigned value, unsigned bits) {
+	if (rem_bits == 0) {
+		poly.set_degree(poly.degree() + 1);
+		rem_bits = gf_elem::size();
+	}
+	unsigned digit_bits = std::min(rem_bits, bits);
+	unsigned rest_bits = bits - digit_bits;
+	rem_bits -= digit_bits;
+	poly[poly.degree()] |= ((value >> rest_bits) & ((1u << digit_bits) - 1)) << rem_bits;
+	if (rest_bits > 0) {
+		write_data(poly, rem_bits, value & ((1u << rest_bits) - 1), rest_bits);
+	}
+}
+
+template<typename T>
+static void read_data(gf_poly& poly, unsigned& used_bits, T& value, unsigned bits) {
+	unsigned coeff_index = used_bits / gf_elem::size();
+	unsigned bit_index = used_bits % gf_elem::size();
+	unsigned digit_bits = std::min((unsigned)gf_elem::size() - bit_index, bits);
+	unsigned rem_bits = gf_elem::size() - bit_index - digit_bits;
+	unsigned rest_bits = bits - digit_bits;
+	value |= ((poly[coeff_index].value() >> rem_bits) & ((1u << bits) - 1)) << rest_bits;
+	used_bits += digit_bits;
+	if (rest_bits > 0) {
+		read_data(poly, used_bits, value, rest_bits);
+	}
+}
+
+static const reed_solomon_code rs(check_digits);
+
+monero_seed::monero_seed(std::time_t date_created) {
+	if (date_created < epoch) {
+		THROW_EXCEPTION("date_created must not be before 1st June 2020");
+	}
+	unsigned quantized_date = ((date_created - epoch) / time_step) & date_mask;
+	date_ = epoch + quantized_date * time_step;
+	version_ = 0;
+	reserved_ = 0;
+	secure_random::gen_bytes(seed_.data(), seed_.size());
+	
+	char salt[25] = "Monero 14-word seed";
+	salt[20] = version_;
+	store32(salt + 21, quantized_date);
+	argon2id_hash_raw(argon_tcost, argon_mcost, 1, seed_.data(), seed_.size(), salt, sizeof(salt), key_.data(), key_.size());
+
+	unsigned rem_bits = gf_elem::size();
+	write_data(message_, rem_bits, version_, version_bits);
+	write_data(message_, rem_bits, reserved_, reserved_bits);
+	write_data(message_, rem_bits, quantized_date, date_bits);
+	for (auto byte : seed_) {
+		write_data(message_, rem_bits, byte, CHAR_BIT);
+	}
+	assert(rem_bits == 0);
+	rs.encode(message_);
+}
+
+monero_seed::monero_seed(const std::string& phrase) {
+	int word_count = 0;
+	size_t offset = 0;
+	int error = -1;
+	std::string words[phrase_words];
+	do {
+		size_t delim = phrase.find(' ', offset);
+		if (delim == std::string::npos) {
+			delim = phrase.size();
+		}
+		words[word_count] = phrase.substr(offset, delim - offset);
+		auto index = wordlist::english.parse(words[word_count]);
+		if (index == -1) {
+			if (words[word_count] != erasure) {
+				THROW_EXCEPTION("unrecognized word: '" << words[word_count] << "'");
+			}
+			if (error >= 0) {
+				THROW_EXCEPTION("teo or more erasures cannot be corrected");
+			}
+			error = word_count;
+		}
+		message_[word_count] = index;
+		word_count++;
+		offset = delim + 1;
+	} while (offset < phrase.size());
+
+	if (word_count != phrase_words) {
+		THROW_EXCEPTION("the mnemonic phrase must consist of " << phrase_words << " words");
+	}
+
+	message_.set_degree();
+
+	if (error >= 0) {
+		for (unsigned i = 0; i < gf_2048::elements(); ++i) {
+			message_[error] = i;
+			if (rs.check(message_)) {
+				correction_ = wordlist::english.get_word(i);
+				break;
+			}
+		}
+		assert(!correction_.empty());
+	}
+	else {
+		if (!rs.check(message_)) {
+			THROW_EXCEPTION("phrase is invalid (checksum mismatch)");
+		}
+	}
+
+	unsigned used_bits = checksum_size;
+	unsigned quantized_date;
+
+	version_ = 0;
+	reserved_ = 0;
+	quantized_date = 0;
+	memset(seed_.data(), 0, seed_.size());
+
+	read_data(message_, used_bits, version_, version_bits);
+	read_data(message_, used_bits, reserved_, reserved_bits);
+	read_data(message_, used_bits, quantized_date, date_bits);
+
+	for (uint8_t& byte : seed_) {
+		read_data(message_, used_bits, byte, CHAR_BIT);
+	}
+
+	assert(used_bits == total_bits);
+
+	date_ = epoch + quantized_date * time_step;
+
+	char salt[25] = "Monero 14-word seed";
+	salt[20] = version_;
+	store32(salt + 21, quantized_date);
+	argon2id_hash_raw(argon_tcost, argon_mcost, 1, seed_.data(), seed_.size(), salt, sizeof(salt), key_.data(), key_.size());
+}
+
+std::ostream& operator<<(std::ostream& os, const monero_seed& seed) {
+	for (int i = 0; i <= seed.message_.degree(); ++i) {
+		if (i > 0) {
+			os << " ";
+		}
+		os << wordlist::english.get_word(seed.message_[i].value());
+	}
+	return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const monero_seed::secret_key& key) {
+	os << std::hex;
+	for (int i = 0; i < key.size(); ++i) {
+		os << std::setw(2) << std::setfill('0') << (unsigned)key[i];
+	}
+	os << std::dec;
+	return os;
+}
diff --git a/src/monero_seed.hpp b/src/monero_seed.hpp
new file mode 100644
index 0000000..fffc66a
--- /dev/null
+++ b/src/monero_seed.hpp
@@ -0,0 +1,46 @@
+/*
+	Copyright (c) 2020 tevador <tevador@gmail.com>
+	All rights reserved.
+*/
+
+#pragma once
+#include <string>
+#include <array>
+#include <cstdint>
+#include <iostream>
+#include <ctime>
+#include "gf_poly.hpp"
+
+class monero_seed {
+public:
+	static const std::string erasure;
+	static constexpr size_t size = 16;
+	static constexpr size_t key_size = 32;
+	using secret_key = std::array<uint8_t, key_size>;
+	using secret_seed = std::array<uint8_t, size>;
+	monero_seed(const std::string& phrase);
+	monero_seed(std::time_t date_created);
+	std::time_t date() const {
+		return date_;
+	}
+	unsigned version() const {
+		return version_ + 1;
+	}
+	const std::string& correction() const {
+		return correction_;
+	}
+	const secret_key& key() const {
+		return key_;
+	}
+	friend std::ostream& operator<<(std::ostream& os, const monero_seed& seed);
+private:
+	secret_seed seed_;
+	secret_key key_;
+	std::time_t date_;
+	unsigned version_;
+	unsigned reserved_;
+	std::string correction_;
+	gf_poly message_;
+};
+
+std::ostream& operator<<(std::ostream& os, const monero_seed::secret_key& key);
diff --git a/src/reed_solomon_code.cpp b/src/reed_solomon_code.cpp
new file mode 100644
index 0000000..a2d2f07
--- /dev/null
+++ b/src/reed_solomon_code.cpp
@@ -0,0 +1,36 @@
+/*
+	Copyright (c) 2020 tevador <tevador@gmail.com>
+	All rights reserved.
+*/
+
+#include "reed_solomon_code.hpp"
+#include <cassert>
+
+reed_solomon_code::reed_solomon_code(unsigned check_digits) : generator(1, 0) {
+	for (unsigned i = 0; i < check_digits; ++i) {
+		gf_poly binom = gf_poly(1, 1);
+		binom[0] = gf_elem(i + 1).exp();
+		generator *= binom;
+	}
+	assert(generator.degree() == check_digits);
+}
+
+void reed_solomon_code::encode(gf_poly& data) const {
+	data *= gf_poly(generator.degree());
+	gf_poly rem;
+	gf_poly::div_rem(data, generator, rem);
+	data -= rem;
+}
+
+bool reed_solomon_code::check(const gf_poly& message) const {
+	auto syndrome = get_syndrome(message);
+	return syndrome.is_zero();
+}
+
+gf_poly reed_solomon_code::get_syndrome(const gf_poly& message) const {
+	gf_poly syndrome;
+	for (unsigned i = 1; i <= generator.degree(); ++i) {
+		syndrome[i - 1] = message(gf_elem(i).exp());
+	}
+	return syndrome;
+}
diff --git a/src/reed_solomon_code.hpp b/src/reed_solomon_code.hpp
new file mode 100644
index 0000000..7ea3a18
--- /dev/null
+++ b/src/reed_solomon_code.hpp
@@ -0,0 +1,17 @@
+/*
+	Copyright (c) 2020 tevador <tevador@gmail.com>
+	All rights reserved.
+*/
+
+#pragma once
+#include "gf_poly.hpp"
+
+class reed_solomon_code {
+public:
+	reed_solomon_code(unsigned check_digits);
+	void encode(gf_poly& data)  const;
+	bool check(const gf_poly& message) const;
+private:
+	gf_poly get_syndrome(const gf_poly& message) const;
+	gf_poly generator;
+};
diff --git a/src/secure_random.cpp b/src/secure_random.cpp
new file mode 100644
index 0000000..da284d4
--- /dev/null
+++ b/src/secure_random.cpp
@@ -0,0 +1,82 @@
+/*
+	Copyright (c) 2020 tevador <tevador@gmail.com>
+	All rights reserved.
+*/
+
+#include "secure_random.hpp"
+#include <stdexcept>
+#include <cstdlib>
+
+#if defined(_WIN32) || defined(__CYGWIN__)
+	#define WINAPI
+	#include <windows.h>
+	#include <ntsecapi.h>
+#elif defined __linux__ && defined __GLIBC__
+	#define STRINGIFY(x) #x
+	#define STR(x) STRINGIFY(x)
+	#if __GLIBC__ > 2 || __GLIBC_MINOR__ > 24
+		#define LINUX_GETENTROPY
+		#include <sys/random.h>
+	#else
+		#pragma message("Warning: getentropy is not available in GLIBC " \
+			STR(__GLIBC__) "." STR(__GLIBC_MINOR__))
+		#define UNIX_FALLBACK
+		#include <sys/syscall.h>
+		#if defined(SYS_getrandom)
+			#define LINUX_TRY_SYSCALL
+			#include <sys/syscall.h>
+			#include <errno.h>
+		#else
+			#pragma message("Warning: Kernel doesn't support SYS_getrandom")
+		#endif
+	#endif
+#else
+	#define UNIX_FALLBACK
+#endif
+#if defined(UNIX_FALLBACK)
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#define RANDOM_FILE "/dev/urandom"
+#endif
+
+void secure_random::gen_bytes(void* output, size_t size) {
+#if defined(WINAPI)
+	if (!RtlGenRandom(output, size)) {
+		throw std::runtime_error("RtlGenRandom failed");
+	}
+#elif defined(LINUX_GETENTROPY)
+	if (-1 == getentropy(output, size)) {
+		throw std::runtime_error("getentropy failed");
+	}
+#else
+#if defined(LINUX_TRY_SYSCALL)
+	if (size <= 256) {
+		if (0 == syscall(SYS_getrandom, output, size, 0)) {
+			return;
+		}
+	}
+#endif
+	int fd = open(RANDOM_FILE, O_RDONLY);
+	if (fd == -1) {
+		throw std::runtime_error("Unable to open " RANDOM_FILE);
+	}
+	char* outptr = (char*)output;
+	while (size) {
+		ssize_t len = read(fd, outptr, size);
+		if (len < 0) {
+			if (errno != EINTR && errno != EAGAIN) {
+				break;
+			}
+			continue;
+		}
+		outptr += len;
+		size -= len;
+	}
+	close(fd);
+	if (size) {
+		throw std::runtime_error("Unable to read " RANDOM_FILE);
+	}
+#endif
+}
diff --git a/src/secure_random.hpp b/src/secure_random.hpp
new file mode 100644
index 0000000..3039202
--- /dev/null
+++ b/src/secure_random.hpp
@@ -0,0 +1,14 @@
+/*
+	Copyright (c) 2020 tevador <tevador@gmail.com>
+	All rights reserved.
+*/
+
+#pragma once
+#include <cstddef>
+#include <cstdint>
+
+class secure_random {
+public:
+	static void gen_bytes(void* output, size_t size);
+};
+
diff --git a/src/wordlist.cpp b/src/wordlist.cpp
new file mode 100644
index 0000000..1b14376
--- /dev/null
+++ b/src/wordlist.cpp
@@ -0,0 +1,2081 @@
+/*
+	Copyright (c) 2020 tevador <tevador@gmail.com>
+	All rights reserved.
+*/
+
+#include "wordlist.hpp"
+#include <algorithm>
+
+const static std::string english_words[] = {
+	"abandon",
+	"ability",
+	"able",
+	"about",
+	"above",
+	"absent",
+	"absorb",
+	"abstract",
+	"absurd",
+	"abuse",
+	"access",
+	"accident",
+	"account",
+	"accuse",
+	"achieve",
+	"acid",
+	"acoustic",
+	"acquire",
+	"across",
+	"act",
+	"action",
+	"actor",
+	"actress",
+	"actual",
+	"adapt",
+	"add",
+	"addict",
+	"address",
+	"adjust",
+	"admit",
+	"adult",
+	"advance",
+	"advice",
+	"aerobic",
+	"affair",
+	"afford",
+	"afraid",
+	"again",
+	"age",
+	"agent",
+	"agree",
+	"ahead",
+	"aim",
+	"air",
+	"airport",
+	"aisle",
+	"alarm",
+	"album",
+	"alcohol",
+	"alert",
+	"alien",
+	"all",
+	"alley",
+	"allow",
+	"almost",
+	"alone",
+	"alpha",
+	"already",
+	"also",
+	"alter",
+	"always",
+	"amateur",
+	"amazing",
+	"among",
+	"amount",
+	"amused",
+	"analyst",
+	"anchor",
+	"ancient",
+	"anger",
+	"angle",
+	"angry",
+	"animal",
+	"ankle",
+	"announce",
+	"annual",
+	"another",
+	"answer",
+	"antenna",
+	"antique",
+	"anxiety",
+	"any",
+	"apart",
+	"apology",
+	"appear",
+	"apple",
+	"approve",
+	"april",
+	"arch",
+	"arctic",
+	"area",
+	"arena",
+	"argue",
+	"arm",
+	"armed",
+	"armor",
+	"army",
+	"around",
+	"arrange",
+	"arrest",
+	"arrive",
+	"arrow",
+	"art",
+	"artefact",
+	"artist",
+	"artwork",
+	"ask",
+	"aspect",
+	"assault",
+	"asset",
+	"assist",
+	"assume",
+	"asthma",
+	"athlete",
+	"atom",
+	"attack",
+	"attend",
+	"attitude",
+	"attract",
+	"auction",
+	"audit",
+	"august",
+	"aunt",
+	"author",
+	"auto",
+	"autumn",
+	"average",
+	"avocado",
+	"avoid",
+	"awake",
+	"aware",
+	"away",
+	"awesome",
+	"awful",
+	"awkward",
+	"axis",
+	"baby",
+	"bachelor",
+	"bacon",
+	"badge",
+	"bag",
+	"balance",
+	"balcony",
+	"ball",
+	"bamboo",
+	"banana",
+	"banner",
+	"bar",
+	"barely",
+	"bargain",
+	"barrel",
+	"base",
+	"basic",
+	"basket",
+	"battle",
+	"beach",
+	"bean",
+	"beauty",
+	"because",
+	"become",
+	"beef",
+	"before",
+	"begin",
+	"behave",
+	"behind",
+	"believe",
+	"below",
+	"belt",
+	"bench",
+	"benefit",
+	"best",
+	"betray",
+	"better",
+	"between",
+	"beyond",
+	"bicycle",
+	"bid",
+	"bike",
+	"bind",
+	"biology",
+	"bird",
+	"birth",
+	"bitter",
+	"black",
+	"blade",
+	"blame",
+	"blanket",
+	"blast",
+	"bleak",
+	"bless",
+	"blind",
+	"blood",
+	"blossom",
+	"blouse",
+	"blue",
+	"blur",
+	"blush",
+	"board",
+	"boat",
+	"body",
+	"boil",
+	"bomb",
+	"bone",
+	"bonus",
+	"book",
+	"boost",
+	"border",
+	"boring",
+	"borrow",
+	"boss",
+	"bottom",
+	"bounce",
+	"box",
+	"boy",
+	"bracket",
+	"brain",
+	"brand",
+	"brass",
+	"brave",
+	"bread",
+	"breeze",
+	"brick",
+	"bridge",
+	"brief",
+	"bright",
+	"bring",
+	"brisk",
+	"broccoli",
+	"broken",
+	"bronze",
+	"broom",
+	"brother",
+	"brown",
+	"brush",
+	"bubble",
+	"buddy",
+	"budget",
+	"buffalo",
+	"build",
+	"bulb",
+	"bulk",
+	"bullet",
+	"bundle",
+	"bunker",
+	"burden",
+	"burger",
+	"burst",
+	"bus",
+	"business",
+	"busy",
+	"butter",
+	"buyer",
+	"buzz",
+	"cabbage",
+	"cabin",
+	"cable",
+	"cactus",
+	"cage",
+	"cake",
+	"call",
+	"calm",
+	"camera",
+	"camp",
+	"can",
+	"canal",
+	"cancel",
+	"candy",
+	"cannon",
+	"canoe",
+	"canvas",
+	"canyon",
+	"capable",
+	"capital",
+	"captain",
+	"car",
+	"carbon",
+	"card",
+	"cargo",
+	"carpet",
+	"carry",
+	"cart",
+	"case",
+	"cash",
+	"casino",
+	"castle",
+	"casual",
+	"cat",
+	"catalog",
+	"catch",
+	"category",
+	"cattle",
+	"caught",
+	"cause",
+	"caution",
+	"cave",
+	"ceiling",
+	"celery",
+	"cement",
+	"census",
+	"century",
+	"cereal",
+	"certain",
+	"chair",
+	"chalk",
+	"champion",
+	"change",
+	"chaos",
+	"chapter",
+	"charge",
+	"chase",
+	"chat",
+	"cheap",
+	"check",
+	"cheese",
+	"chef",
+	"cherry",
+	"chest",
+	"chicken",
+	"chief",
+	"child",
+	"chimney",
+	"choice",
+	"choose",
+	"chronic",
+	"chuckle",
+	"chunk",
+	"churn",
+	"cigar",
+	"cinnamon",
+	"circle",
+	"citizen",
+	"city",
+	"civil",
+	"claim",
+	"clap",
+	"clarify",
+	"claw",
+	"clay",
+	"clean",
+	"clerk",
+	"clever",
+	"click",
+	"client",
+	"cliff",
+	"climb",
+	"clinic",
+	"clip",
+	"clock",
+	"clog",
+	"close",
+	"cloth",
+	"cloud",
+	"clown",
+	"club",
+	"clump",
+	"cluster",
+	"clutch",
+	"coach",
+	"coast",
+	"coconut",
+	"code",
+	"coffee",
+	"coil",
+	"coin",
+	"collect",
+	"color",
+	"column",
+	"combine",
+	"come",
+	"comfort",
+	"comic",
+	"common",
+	"company",
+	"concert",
+	"conduct",
+	"confirm",
+	"congress",
+	"connect",
+	"consider",
+	"control",
+	"convince",
+	"cook",
+	"cool",
+	"copper",
+	"copy",
+	"coral",
+	"core",
+	"corn",
+	"correct",
+	"cost",
+	"cotton",
+	"couch",
+	"country",
+	"couple",
+	"course",
+	"cousin",
+	"cover",
+	"coyote",
+	"crack",
+	"cradle",
+	"craft",
+	"cram",
+	"crane",
+	"crash",
+	"crater",
+	"crawl",
+	"crazy",
+	"cream",
+	"credit",
+	"creek",
+	"crew",
+	"cricket",
+	"crime",
+	"crisp",
+	"critic",
+	"crop",
+	"cross",
+	"crouch",
+	"crowd",
+	"crucial",
+	"cruel",
+	"cruise",
+	"crumble",
+	"crunch",
+	"crush",
+	"cry",
+	"crystal",
+	"cube",
+	"culture",
+	"cup",
+	"cupboard",
+	"curious",
+	"current",
+	"curtain",
+	"curve",
+	"cushion",
+	"custom",
+	"cute",
+	"cycle",
+	"dad",
+	"damage",
+	"damp",
+	"dance",
+	"danger",
+	"daring",
+	"dash",
+	"daughter",
+	"dawn",
+	"day",
+	"deal",
+	"debate",
+	"debris",
+	"decade",
+	"december",
+	"decide",
+	"decline",
+	"decorate",
+	"decrease",
+	"deer",
+	"defense",
+	"define",
+	"defy",
+	"degree",
+	"delay",
+	"deliver",
+	"demand",
+	"demise",
+	"denial",
+	"dentist",
+	"deny",
+	"depart",
+	"depend",
+	"deposit",
+	"depth",
+	"deputy",
+	"derive",
+	"describe",
+	"desert",
+	"design",
+	"desk",
+	"despair",
+	"destroy",
+	"detail",
+	"detect",
+	"develop",
+	"device",
+	"devote",
+	"diagram",
+	"dial",
+	"diamond",
+	"diary",
+	"dice",
+	"diesel",
+	"diet",
+	"differ",
+	"digital",
+	"dignity",
+	"dilemma",
+	"dinner",
+	"dinosaur",
+	"direct",
+	"dirt",
+	"disagree",
+	"discover",
+	"disease",
+	"dish",
+	"dismiss",
+	"disorder",
+	"display",
+	"distance",
+	"divert",
+	"divide",
+	"divorce",
+	"dizzy",
+	"doctor",
+	"document",
+	"dog",
+	"doll",
+	"dolphin",
+	"domain",
+	"donate",
+	"donkey",
+	"donor",
+	"door",
+	"dose",
+	"double",
+	"dove",
+	"draft",
+	"dragon",
+	"drama",
+	"drastic",
+	"draw",
+	"dream",
+	"dress",
+	"drift",
+	"drill",
+	"drink",
+	"drip",
+	"drive",
+	"drop",
+	"drum",
+	"dry",
+	"duck",
+	"dumb",
+	"dune",
+	"during",
+	"dust",
+	"dutch",
+	"duty",
+	"dwarf",
+	"dynamic",
+	"eager",
+	"eagle",
+	"early",
+	"earn",
+	"earth",
+	"easily",
+	"east",
+	"easy",
+	"echo",
+	"ecology",
+	"economy",
+	"edge",
+	"edit",
+	"educate",
+	"effort",
+	"egg",
+	"eight",
+	"either",
+	"elbow",
+	"elder",
+	"electric",
+	"elegant",
+	"element",
+	"elephant",
+	"elevator",
+	"elite",
+	"else",
+	"embark",
+	"embody",
+	"embrace",
+	"emerge",
+	"emotion",
+	"employ",
+	"empower",
+	"empty",
+	"enable",
+	"enact",
+	"end",
+	"endless",
+	"endorse",
+	"enemy",
+	"energy",
+	"enforce",
+	"engage",
+	"engine",
+	"enhance",
+	"enjoy",
+	"enlist",
+	"enough",
+	"enrich",
+	"enroll",
+	"ensure",
+	"enter",
+	"entire",
+	"entry",
+	"envelope",
+	"episode",
+	"equal",
+	"equip",
+	"era",
+	"erase",
+	"erode",
+	"erosion",
+	"error",
+	"erupt",
+	"escape",
+	"essay",
+	"essence",
+	"estate",
+	"eternal",
+	"ethics",
+	"evidence",
+	"evil",
+	"evoke",
+	"evolve",
+	"exact",
+	"example",
+	"excess",
+	"exchange",
+	"excite",
+	"exclude",
+	"excuse",
+	"execute",
+	"exercise",
+	"exhaust",
+	"exhibit",
+	"exile",
+	"exist",
+	"exit",
+	"exotic",
+	"expand",
+	"expect",
+	"expire",
+	"explain",
+	"expose",
+	"express",
+	"extend",
+	"extra",
+	"eye",
+	"eyebrow",
+	"fabric",
+	"face",
+	"faculty",
+	"fade",
+	"faint",
+	"faith",
+	"fall",
+	"false",
+	"fame",
+	"family",
+	"famous",
+	"fan",
+	"fancy",
+	"fantasy",
+	"farm",
+	"fashion",
+	"fat",
+	"fatal",
+	"father",
+	"fatigue",
+	"fault",
+	"favorite",
+	"feature",
+	"february",
+	"federal",
+	"fee",
+	"feed",
+	"feel",
+	"female",
+	"fence",
+	"festival",
+	"fetch",
+	"fever",
+	"few",
+	"fiber",
+	"fiction",
+	"field",
+	"figure",
+	"file",
+	"film",
+	"filter",
+	"final",
+	"find",
+	"fine",
+	"finger",
+	"finish",
+	"fire",
+	"firm",
+	"first",
+	"fiscal",
+	"fish",
+	"fit",
+	"fitness",
+	"fix",
+	"flag",
+	"flame",
+	"flash",
+	"flat",
+	"flavor",
+	"flee",
+	"flight",
+	"flip",
+	"float",
+	"flock",
+	"floor",
+	"flower",
+	"fluid",
+	"flush",
+	"fly",
+	"foam",
+	"focus",
+	"fog",
+	"foil",
+	"fold",
+	"follow",
+	"food",
+	"foot",
+	"force",
+	"forest",
+	"forget",
+	"fork",
+	"fortune",
+	"forum",
+	"forward",
+	"fossil",
+	"foster",
+	"found",
+	"fox",
+	"fragile",
+	"frame",
+	"frequent",
+	"fresh",
+	"friend",
+	"fringe",
+	"frog",
+	"front",
+	"frost",
+	"frown",
+	"frozen",
+	"fruit",
+	"fuel",
+	"fun",
+	"funny",
+	"furnace",
+	"fury",
+	"future",
+	"gadget",
+	"gain",
+	"galaxy",
+	"gallery",
+	"game",
+	"gap",
+	"garage",
+	"garbage",
+	"garden",
+	"garlic",
+	"garment",
+	"gas",
+	"gasp",
+	"gate",
+	"gather",
+	"gauge",
+	"gaze",
+	"general",
+	"genius",
+	"genre",
+	"gentle",
+	"genuine",
+	"gesture",
+	"ghost",
+	"giant",
+	"gift",
+	"giggle",
+	"ginger",
+	"giraffe",
+	"girl",
+	"give",
+	"glad",
+	"glance",
+	"glare",
+	"glass",
+	"glide",
+	"glimpse",
+	"globe",
+	"gloom",
+	"glory",
+	"glove",
+	"glow",
+	"glue",
+	"goat",
+	"goddess",
+	"gold",
+	"good",
+	"goose",
+	"gorilla",
+	"gospel",
+	"gossip",
+	"govern",
+	"gown",
+	"grab",
+	"grace",
+	"grain",
+	"grant",
+	"grape",
+	"grass",
+	"gravity",
+	"great",
+	"green",
+	"grid",
+	"grief",
+	"grit",
+	"grocery",
+	"group",
+	"grow",
+	"grunt",
+	"guard",
+	"guess",
+	"guide",
+	"guilt",
+	"guitar",
+	"gun",
+	"gym",
+	"habit",
+	"hair",
+	"half",
+	"hammer",
+	"hamster",
+	"hand",
+	"happy",
+	"harbor",
+	"hard",
+	"harsh",
+	"harvest",
+	"hat",
+	"have",
+	"hawk",
+	"hazard",
+	"head",
+	"health",
+	"heart",
+	"heavy",
+	"hedgehog",
+	"height",
+	"hello",
+	"helmet",
+	"help",
+	"hen",
+	"hero",
+	"hidden",
+	"high",
+	"hill",
+	"hint",
+	"hip",
+	"hire",
+	"history",
+	"hobby",
+	"hockey",
+	"hold",
+	"hole",
+	"holiday",
+	"hollow",
+	"home",
+	"honey",
+	"hood",
+	"hope",
+	"horn",
+	"horror",
+	"horse",
+	"hospital",
+	"host",
+	"hotel",
+	"hour",
+	"hover",
+	"hub",
+	"huge",
+	"human",
+	"humble",
+	"humor",
+	"hundred",
+	"hungry",
+	"hunt",
+	"hurdle",
+	"hurry",
+	"hurt",
+	"husband",
+	"hybrid",
+	"ice",
+	"icon",
+	"idea",
+	"identify",
+	"idle",
+	"ignore",
+	"ill",
+	"illegal",
+	"illness",
+	"image",
+	"imitate",
+	"immense",
+	"immune",
+	"impact",
+	"impose",
+	"improve",
+	"impulse",
+	"inch",
+	"include",
+	"income",
+	"increase",
+	"index",
+	"indicate",
+	"indoor",
+	"industry",
+	"infant",
+	"inflict",
+	"inform",
+	"inhale",
+	"inherit",
+	"initial",
+	"inject",
+	"injury",
+	"inmate",
+	"inner",
+	"innocent",
+	"input",
+	"inquiry",
+	"insane",
+	"insect",
+	"inside",
+	"inspire",
+	"install",
+	"intact",
+	"interest",
+	"into",
+	"invest",
+	"invite",
+	"involve",
+	"iron",
+	"island",
+	"isolate",
+	"issue",
+	"item",
+	"ivory",
+	"jacket",
+	"jaguar",
+	"jar",
+	"jazz",
+	"jealous",
+	"jeans",
+	"jelly",
+	"jewel",
+	"job",
+	"join",
+	"joke",
+	"journey",
+	"joy",
+	"judge",
+	"juice",
+	"jump",
+	"jungle",
+	"junior",
+	"junk",
+	"just",
+	"kangaroo",
+	"keen",
+	"keep",
+	"ketchup",
+	"key",
+	"kick",
+	"kid",
+	"kidney",
+	"kind",
+	"kingdom",
+	"kiss",
+	"kit",
+	"kitchen",
+	"kite",
+	"kitten",
+	"kiwi",
+	"knee",
+	"knife",
+	"knock",
+	"know",
+	"lab",
+	"label",
+	"labor",
+	"ladder",
+	"lady",
+	"lake",
+	"lamp",
+	"language",
+	"laptop",
+	"large",
+	"later",
+	"latin",
+	"laugh",
+	"laundry",
+	"lava",
+	"law",
+	"lawn",
+	"lawsuit",
+	"layer",
+	"lazy",
+	"leader",
+	"leaf",
+	"learn",
+	"leave",
+	"lecture",
+	"left",
+	"leg",
+	"legal",
+	"legend",
+	"leisure",
+	"lemon",
+	"lend",
+	"length",
+	"lens",
+	"leopard",
+	"lesson",
+	"letter",
+	"level",
+	"liar",
+	"liberty",
+	"library",
+	"license",
+	"life",
+	"lift",
+	"light",
+	"like",
+	"limb",
+	"limit",
+	"link",
+	"lion",
+	"liquid",
+	"list",
+	"little",
+	"live",
+	"lizard",
+	"load",
+	"loan",
+	"lobster",
+	"local",
+	"lock",
+	"logic",
+	"lonely",
+	"long",
+	"loop",
+	"lottery",
+	"loud",
+	"lounge",
+	"love",
+	"loyal",
+	"lucky",
+	"luggage",
+	"lumber",
+	"lunar",
+	"lunch",
+	"luxury",
+	"lyrics",
+	"machine",
+	"mad",
+	"magic",
+	"magnet",
+	"maid",
+	"mail",
+	"main",
+	"major",
+	"make",
+	"mammal",
+	"man",
+	"manage",
+	"mandate",
+	"mango",
+	"mansion",
+	"manual",
+	"maple",
+	"marble",
+	"march",
+	"margin",
+	"marine",
+	"market",
+	"marriage",
+	"mask",
+	"mass",
+	"master",
+	"match",
+	"material",
+	"math",
+	"matrix",
+	"matter",
+	"maximum",
+	"maze",
+	"meadow",
+	"mean",
+	"measure",
+	"meat",
+	"mechanic",
+	"medal",
+	"media",
+	"melody",
+	"melt",
+	"member",
+	"memory",
+	"mention",
+	"menu",
+	"mercy",
+	"merge",
+	"merit",
+	"merry",
+	"mesh",
+	"message",
+	"metal",
+	"method",
+	"middle",
+	"midnight",
+	"milk",
+	"million",
+	"mimic",
+	"mind",
+	"minimum",
+	"minor",
+	"minute",
+	"miracle",
+	"mirror",
+	"misery",
+	"miss",
+	"mistake",
+	"mix",
+	"mixed",
+	"mixture",
+	"mobile",
+	"model",
+	"modify",
+	"mom",
+	"moment",
+	"monitor",
+	"monkey",
+	"monster",
+	"month",
+	"moon",
+	"moral",
+	"more",
+	"morning",
+	"mosquito",
+	"mother",
+	"motion",
+	"motor",
+	"mountain",
+	"mouse",
+	"move",
+	"movie",
+	"much",
+	"muffin",
+	"mule",
+	"multiply",
+	"muscle",
+	"museum",
+	"mushroom",
+	"music",
+	"must",
+	"mutual",
+	"myself",
+	"mystery",
+	"myth",
+	"naive",
+	"name",
+	"napkin",
+	"narrow",
+	"nasty",
+	"nation",
+	"nature",
+	"near",
+	"neck",
+	"need",
+	"negative",
+	"neglect",
+	"neither",
+	"nephew",
+	"nerve",
+	"nest",
+	"net",
+	"network",
+	"neutral",
+	"never",
+	"news",
+	"next",
+	"nice",
+	"night",
+	"noble",
+	"noise",
+	"nominee",
+	"noodle",
+	"normal",
+	"north",
+	"nose",
+	"notable",
+	"note",
+	"nothing",
+	"notice",
+	"novel",
+	"now",
+	"nuclear",
+	"number",
+	"nurse",
+	"nut",
+	"oak",
+	"obey",
+	"object",
+	"oblige",
+	"obscure",
+	"observe",
+	"obtain",
+	"obvious",
+	"occur",
+	"ocean",
+	"october",
+	"odor",
+	"off",
+	"offer",
+	"office",
+	"often",
+	"oil",
+	"okay",
+	"old",
+	"olive",
+	"olympic",
+	"omit",
+	"once",
+	"one",
+	"onion",
+	"online",
+	"only",
+	"open",
+	"opera",
+	"opinion",
+	"oppose",
+	"option",
+	"orange",
+	"orbit",
+	"orchard",
+	"order",
+	"ordinary",
+	"organ",
+	"orient",
+	"original",
+	"orphan",
+	"ostrich",
+	"other",
+	"outdoor",
+	"outer",
+	"output",
+	"outside",
+	"oval",
+	"oven",
+	"over",
+	"own",
+	"owner",
+	"oxygen",
+	"oyster",
+	"ozone",
+	"pact",
+	"paddle",
+	"page",
+	"pair",
+	"palace",
+	"palm",
+	"panda",
+	"panel",
+	"panic",
+	"panther",
+	"paper",
+	"parade",
+	"parent",
+	"park",
+	"parrot",
+	"party",
+	"pass",
+	"patch",
+	"path",
+	"patient",
+	"patrol",
+	"pattern",
+	"pause",
+	"pave",
+	"payment",
+	"peace",
+	"peanut",
+	"pear",
+	"peasant",
+	"pelican",
+	"pen",
+	"penalty",
+	"pencil",
+	"people",
+	"pepper",
+	"perfect",
+	"permit",
+	"person",
+	"pet",
+	"phone",
+	"photo",
+	"phrase",
+	"physical",
+	"piano",
+	"picnic",
+	"picture",
+	"piece",
+	"pig",
+	"pigeon",
+	"pill",
+	"pilot",
+	"pink",
+	"pioneer",
+	"pipe",
+	"pistol",
+	"pitch",
+	"pizza",
+	"place",
+	"planet",
+	"plastic",
+	"plate",
+	"play",
+	"please",
+	"pledge",
+	"pluck",
+	"plug",
+	"plunge",
+	"poem",
+	"poet",
+	"point",
+	"polar",
+	"pole",
+	"police",
+	"pond",
+	"pony",
+	"pool",
+	"popular",
+	"portion",
+	"position",
+	"possible",
+	"post",
+	"potato",
+	"pottery",
+	"poverty",
+	"powder",
+	"power",
+	"practice",
+	"praise",
+	"predict",
+	"prefer",
+	"prepare",
+	"present",
+	"pretty",
+	"prevent",
+	"price",
+	"pride",
+	"primary",
+	"print",
+	"priority",
+	"prison",
+	"private",
+	"prize",
+	"problem",
+	"process",
+	"produce",
+	"profit",
+	"program",
+	"project",
+	"promote",
+	"proof",
+	"property",
+	"prosper",
+	"protect",
+	"proud",
+	"provide",
+	"public",
+	"pudding",
+	"pull",
+	"pulp",
+	"pulse",
+	"pumpkin",
+	"punch",
+	"pupil",
+	"puppy",
+	"purchase",
+	"purity",
+	"purpose",
+	"purse",
+	"push",
+	"put",
+	"puzzle",
+	"pyramid",
+	"quality",
+	"quantum",
+	"quarter",
+	"question",
+	"quick",
+	"quit",
+	"quiz",
+	"quote",
+	"rabbit",
+	"raccoon",
+	"race",
+	"rack",
+	"radar",
+	"radio",
+	"rail",
+	"rain",
+	"raise",
+	"rally",
+	"ramp",
+	"ranch",
+	"random",
+	"range",
+	"rapid",
+	"rare",
+	"rate",
+	"rather",
+	"raven",
+	"raw",
+	"razor",
+	"ready",
+	"real",
+	"reason",
+	"rebel",
+	"rebuild",
+	"recall",
+	"receive",
+	"recipe",
+	"record",
+	"recycle",
+	"reduce",
+	"reflect",
+	"reform",
+	"refuse",
+	"region",
+	"regret",
+	"regular",
+	"reject",
+	"relax",
+	"release",
+	"relief",
+	"rely",
+	"remain",
+	"remember",
+	"remind",
+	"remove",
+	"render",
+	"renew",
+	"rent",
+	"reopen",
+	"repair",
+	"repeat",
+	"replace",
+	"report",
+	"require",
+	"rescue",
+	"resemble",
+	"resist",
+	"resource",
+	"response",
+	"result",
+	"retire",
+	"retreat",
+	"return",
+	"reunion",
+	"reveal",
+	"review",
+	"reward",
+	"rhythm",
+	"rib",
+	"ribbon",
+	"rice",
+	"rich",
+	"ride",
+	"ridge",
+	"rifle",
+	"right",
+	"rigid",
+	"ring",
+	"riot",
+	"ripple",
+	"risk",
+	"ritual",
+	"rival",
+	"river",
+	"road",
+	"roast",
+	"robot",
+	"robust",
+	"rocket",
+	"romance",
+	"roof",
+	"rookie",
+	"room",
+	"rose",
+	"rotate",
+	"rough",
+	"round",
+	"route",
+	"royal",
+	"rubber",
+	"rude",
+	"rug",
+	"rule",
+	"run",
+	"runway",
+	"rural",
+	"sad",
+	"saddle",
+	"sadness",
+	"safe",
+	"sail",
+	"salad",
+	"salmon",
+	"salon",
+	"salt",
+	"salute",
+	"same",
+	"sample",
+	"sand",
+	"satisfy",
+	"satoshi",
+	"sauce",
+	"sausage",
+	"save",
+	"say",
+	"scale",
+	"scan",
+	"scare",
+	"scatter",
+	"scene",
+	"scheme",
+	"school",
+	"science",
+	"scissors",
+	"scorpion",
+	"scout",
+	"scrap",
+	"screen",
+	"script",
+	"scrub",
+	"sea",
+	"search",
+	"season",
+	"seat",
+	"second",
+	"secret",
+	"section",
+	"security",
+	"seed",
+	"seek",
+	"segment",
+	"select",
+	"sell",
+	"seminar",
+	"senior",
+	"sense",
+	"sentence",
+	"series",
+	"service",
+	"session",
+	"settle",
+	"setup",
+	"seven",
+	"shadow",
+	"shaft",
+	"shallow",
+	"share",
+	"shed",
+	"shell",
+	"sheriff",
+	"shield",
+	"shift",
+	"shine",
+	"ship",
+	"shiver",
+	"shock",
+	"shoe",
+	"shoot",
+	"shop",
+	"short",
+	"shoulder",
+	"shove",
+	"shrimp",
+	"shrug",
+	"shuffle",
+	"shy",
+	"sibling",
+	"sick",
+	"side",
+	"siege",
+	"sight",
+	"sign",
+	"silent",
+	"silk",
+	"silly",
+	"silver",
+	"similar",
+	"simple",
+	"since",
+	"sing",
+	"siren",
+	"sister",
+	"situate",
+	"six",
+	"size",
+	"skate",
+	"sketch",
+	"ski",
+	"skill",
+	"skin",
+	"skirt",
+	"skull",
+	"slab",
+	"slam",
+	"sleep",
+	"slender",
+	"slice",
+	"slide",
+	"slight",
+	"slim",
+	"slogan",
+	"slot",
+	"slow",
+	"slush",
+	"small",
+	"smart",
+	"smile",
+	"smoke",
+	"smooth",
+	"snack",
+	"snake",
+	"snap",
+	"sniff",
+	"snow",
+	"soap",
+	"soccer",
+	"social",
+	"sock",
+	"soda",
+	"soft",
+	"solar",
+	"soldier",
+	"solid",
+	"solution",
+	"solve",
+	"someone",
+	"song",
+	"soon",
+	"sorry",
+	"sort",
+	"soul",
+	"sound",
+	"soup",
+	"source",
+	"south",
+	"space",
+	"spare",
+	"spatial",
+	"spawn",
+	"speak",
+	"special",
+	"speed",
+	"spell",
+	"spend",
+	"sphere",
+	"spice",
+	"spider",
+	"spike",
+	"spin",
+	"spirit",
+	"split",
+	"spoil",
+	"sponsor",
+	"spoon",
+	"sport",
+	"spot",
+	"spray",
+	"spread",
+	"spring",
+	"spy",
+	"square",
+	"squeeze",
+	"squirrel",
+	"stable",
+	"stadium",
+	"staff",
+	"stage",
+	"stairs",
+	"stamp",
+	"stand",
+	"start",
+	"state",
+	"stay",
+	"steak",
+	"steel",
+	"stem",
+	"step",
+	"stereo",
+	"stick",
+	"still",
+	"sting",
+	"stock",
+	"stomach",
+	"stone",
+	"stool",
+	"story",
+	"stove",
+	"strategy",
+	"street",
+	"strike",
+	"strong",
+	"struggle",
+	"student",
+	"stuff",
+	"stumble",
+	"style",
+	"subject",
+	"submit",
+	"subway",
+	"success",
+	"such",
+	"sudden",
+	"suffer",
+	"sugar",
+	"suggest",
+	"suit",
+	"summer",
+	"sun",
+	"sunny",
+	"sunset",
+	"super",
+	"supply",
+	"supreme",
+	"sure",
+	"surface",
+	"surge",
+	"surprise",
+	"surround",
+	"survey",
+	"suspect",
+	"sustain",
+	"swallow",
+	"swamp",
+	"swap",
+	"swarm",
+	"swear",
+	"sweet",
+	"swift",
+	"swim",
+	"swing",
+	"switch",
+	"sword",
+	"symbol",
+	"symptom",
+	"syrup",
+	"system",
+	"table",
+	"tackle",
+	"tag",
+	"tail",
+	"talent",
+	"talk",
+	"tank",
+	"tape",
+	"target",
+	"task",
+	"taste",
+	"tattoo",
+	"taxi",
+	"teach",
+	"team",
+	"tell",
+	"ten",
+	"tenant",
+	"tennis",
+	"tent",
+	"term",
+	"test",
+	"text",
+	"thank",
+	"that",
+	"theme",
+	"then",
+	"theory",
+	"there",
+	"they",
+	"thing",
+	"this",
+	"thought",
+	"three",
+	"thrive",
+	"throw",
+	"thumb",
+	"thunder",
+	"ticket",
+	"tide",
+	"tiger",
+	"tilt",
+	"timber",
+	"time",
+	"tiny",
+	"tip",
+	"tired",
+	"tissue",
+	"title",
+	"toast",
+	"tobacco",
+	"today",
+	"toddler",
+	"toe",
+	"together",
+	"toilet",
+	"token",
+	"tomato",
+	"tomorrow",
+	"tone",
+	"tongue",
+	"tonight",
+	"tool",
+	"tooth",
+	"top",
+	"topic",
+	"topple",
+	"torch",
+	"tornado",
+	"tortoise",
+	"toss",
+	"total",
+	"tourist",
+	"toward",
+	"tower",
+	"town",
+	"toy",
+	"track",
+	"trade",
+	"traffic",
+	"tragic",
+	"train",
+	"transfer",
+	"trap",
+	"trash",
+	"travel",
+	"tray",
+	"treat",
+	"tree",
+	"trend",
+	"trial",
+	"tribe",
+	"trick",
+	"trigger",
+	"trim",
+	"trip",
+	"trophy",
+	"trouble",
+	"truck",
+	"true",
+	"truly",
+	"trumpet",
+	"trust",
+	"truth",
+	"try",
+	"tube",
+	"tuition",
+	"tumble",
+	"tuna",
+	"tunnel",
+	"turkey",
+	"turn",
+	"turtle",
+	"twelve",
+	"twenty",
+	"twice",
+	"twin",
+	"twist",
+	"two",
+	"type",
+	"typical",
+	"ugly",
+	"umbrella",
+	"unable",
+	"unaware",
+	"uncle",
+	"uncover",
+	"under",
+	"undo",
+	"unfair",
+	"unfold",
+	"unhappy",
+	"uniform",
+	"unique",
+	"unit",
+	"universe",
+	"unknown",
+	"unlock",
+	"until",
+	"unusual",
+	"unveil",
+	"update",
+	"upgrade",
+	"uphold",
+	"upon",
+	"upper",
+	"upset",
+	"urban",
+	"urge",
+	"usage",
+	"use",
+	"used",
+	"useful",
+	"useless",
+	"usual",
+	"utility",
+	"vacant",
+	"vacuum",
+	"vague",
+	"valid",
+	"valley",
+	"valve",
+	"van",
+	"vanish",
+	"vapor",
+	"various",
+	"vast",
+	"vault",
+	"vehicle",
+	"velvet",
+	"vendor",
+	"venture",
+	"venue",
+	"verb",
+	"verify",
+	"version",
+	"very",
+	"vessel",
+	"veteran",
+	"viable",
+	"vibrant",
+	"vicious",
+	"victory",
+	"video",
+	"view",
+	"village",
+	"vintage",
+	"violin",
+	"virtual",
+	"virus",
+	"visa",
+	"visit",
+	"visual",
+	"vital",
+	"vivid",
+	"vocal",
+	"voice",
+	"void",
+	"volcano",
+	"volume",
+	"vote",
+	"voyage",
+	"wage",
+	"wagon",
+	"wait",
+	"walk",
+	"wall",
+	"walnut",
+	"want",
+	"warfare",
+	"warm",
+	"warrior",
+	"wash",
+	"wasp",
+	"waste",
+	"water",
+	"wave",
+	"way",
+	"wealth",
+	"weapon",
+	"wear",
+	"weasel",
+	"weather",
+	"web",
+	"wedding",
+	"weekend",
+	"weird",
+	"welcome",
+	"west",
+	"wet",
+	"whale",
+	"what",
+	"wheat",
+	"wheel",
+	"when",
+	"where",
+	"whip",
+	"whisper",
+	"wide",
+	"width",
+	"wife",
+	"wild",
+	"will",
+	"win",
+	"window",
+	"wine",
+	"wing",
+	"wink",
+	"winner",
+	"winter",
+	"wire",
+	"wisdom",
+	"wise",
+	"wish",
+	"witness",
+	"wolf",
+	"woman",
+	"wonder",
+	"wood",
+	"wool",
+	"word",
+	"work",
+	"world",
+	"worry",
+	"worth",
+	"wrap",
+	"wreck",
+	"wrestle",
+	"wrist",
+	"write",
+	"wrong",
+	"yard",
+	"year",
+	"yellow",
+	"you",
+	"young",
+	"youth",
+	"zebra",
+	"zero",
+	"zone",
+	"zoo",
+};
+
+const wordlist wordlist::english = wordlist(english_words);
+
+template<class iter, class T>
+iter binary_find(iter begin, iter end, const T& val) {
+	iter i = std::lower_bound(begin, end, val);
+	if (i != end && !(val < *i)) {
+		return i;
+	}
+	else {
+		return end;
+	}
+}
+
+int wordlist::parse(const std::string& word) const {
+	auto begin = std::begin(values_);
+	auto end = std::end(values_);
+	auto iter = binary_find(begin, end, word);
+	if (iter != end) {
+		return iter - begin;
+	}
+	return -1;
+}
diff --git a/src/wordlist.hpp b/src/wordlist.hpp
new file mode 100644
index 0000000..8b622fe
--- /dev/null
+++ b/src/wordlist.hpp
@@ -0,0 +1,25 @@
+/*
+	Copyright (c) 2020 tevador <tevador@gmail.com>
+	All rights reserved.
+*/
+
+#pragma once
+
+#include <string>
+#include <assert.h>
+
+class wordlist {
+public:
+	static constexpr size_t size = 2048;
+	static const wordlist english;
+	const std::string& get_word(unsigned i) const {
+		assert(i < size);
+		return values_[i];
+	}
+	int parse(const std::string& word) const;
+private:
+	wordlist(const std::string(&values)[size]) : values_(values)
+	{
+	}
+	const std::string(&values_)[size];
+};