Skip to content

Latest commit

 

History

History
135 lines (103 loc) · 5.24 KB

README.md

File metadata and controls

135 lines (103 loc) · 5.24 KB

HashX

HashX is an algorithm designed for client puzzles and proof-of-work schemes. While traditional cryptographic hash functions use a fixed one-way compression function, each HashX instance represents a unique pseudorandomly generated one-way function.

HashX functions are generated as a carefully crafted sequence of integer operations to fully saturate a 3-way superscalar CPU pipeline (modeled after the Intel Ivy Bridge architecture). Extra care is taken to avoid optimizations and to ensure that each function takes exactly the same number of CPU cycles (currently 512 instructions over 192 cycles).

API

The API consists of 4 functions and is documented in the public header file hashx.h.

Example of usage:

#include <hashx.h>
#include <stdio.h>

int main() {
    char seed[] = "this is a seed that will generate a hash function";
    char hash[HASHX_SIZE];
    hashx_ctx* ctx = hashx_alloc(HASHX_COMPILED);
    if (ctx == HASHX_NOTSUPP)
        ctx = hashx_alloc(HASHX_INTERPRETED);
    if (ctx == NULL)
        return 1;
    if (!hashx_make(ctx, seed, sizeof(seed))) /* generate a hash function */
        return 1;
    hashx_exec(ctx, 123456789, hash); /* calculate the hash of a nonce value */
    hashx_free(ctx);
    for (unsigned i = 0; i < HASHX_SIZE; ++i)
        printf("%02x", hash[i] & 0xff);
    printf("\n");
    return 0;
}

Build

A C99-compatible compiler and cmake are required.

git clone https://github.com/tevador/hashx.git
cd hashx
mkdir build
cd build
cmake .. [-DHASHX_BLOCK_MODE=ON] [-DHASHX_SIZE=<1-32>] [-DHASHX_SALT="my custom hash"]
make

Block mode (default: off)

Because HashX is meant to be used in proof-of-work schemes and client puzzles, the input is a 64-bit counter value. If you need to hash arbitrary data, build with -DHASHX_BLOCK_MODE=ON. This will change the API to accept const void*, size_t instead of uint64_t. However, it is strongly recommended to use the counter mode, which is almost twice faster for short inputs.

Hash size (default: 32)

The default hash output size is 32 bytes (256 bits). If you want to reduce the output size, build with, for example, -DHASHX_SIZE=20. Output sizes in the range of 1-32 bytes are supported. Shorter output sizes are formed by simply truncating the full 256-bit hash.

Generator salt (default: "HashX v1")

An implementation-specific salt value may be specified when building, for example: -DHASHX_SALT="my custom hash". This value is used as a salt when generating hash instances. The maximum supported salt size is 15 characters.

Performance

HashX was designed for fast verification. Generating a hash function from seed takes about 50 μs and a 64-bit nonce can be hashed in under 100 ns (in compiled mode) or in about 1-2 μs (in interpreted mode).

A benchmark executable is included:

./hashx-bench --seeds 500

Security

HashX should provide strong preimage resistance. No other security guarantees are made. About 99% of HashX instances pass SMHasher, but using HashX as a generic hash function is not recommended.

Known vulnerabilities that should not affect intended use cases:

  1. HashX is not collision resistant. Around 0.2% of seeds produce "weak" hash functions for which hash collisions are plentiful.
  2. Secret values should not be used as inputs to HashX because the generated instructions include data-dependent branches by design.

Protocols based on HashX

Here are two examples of how HashX can be used in practice:

Interactive client puzzle

Client puzzles are protocols designed to protect server resources from abuse. A client requesting a resource from a server may be asked to solve a puzzle before the request is accepted.

One of the first proposed client puzzles is Hashcash, which requires the client to find a partial SHA-1 hash inversion. However, because of the static nature of cryptographic hash functions, an attacker can offload hashing to a GPU or FPGA to gain a significant advantage over legitimate clients equipped only with a CPU.

In a HashX-based interactive client puzzle, the server sends each client a 256-bit challenge used to generate a unique HashX function. The client then has to find a 64-bit nonce value such that the resulting hash has a predefined number of leading zeroes. An attacker cannot easily parallelize the workload because each request would require a new GPU kernel or FPGA bistream.

Non-interactive proof-of-work

In the absence of a central authority handing out challenges (for example in a cryptocurrency), the client takes some public information T (for example a block template) and combines it with a chosen 64-bit nonce N1. The resulting string X = T||N1 is then used to generate a HashX function HX. The client then tries to find a 16-bit nonce N2 such that r = HX(N2) meets the difficulty target of the protocol. If no N2 value is successful, the client increments N1 and tries with a different hash function.

In this protocol, each HashX function provides only 65536 attempts before it must be discarded. This limits the parallelization advantage of GPUs and FPGAs. A CPU core will be able to test about 200 different hash functions per second.