Add basic hashing tests and use keccak instead of sha3

This commit is contained in:
mratsim 2018-02-19 16:02:37 +01:00
parent d830f6993f
commit 388fd963f0
6 changed files with 98 additions and 22 deletions

View File

@ -1,4 +1,4 @@
packageName = "eth_keys"
packageName = "ethash"
version = "0.0.1"
author = "Status Research & Development GmbH"
description = "A reimplementation in pure Nim of ethash, the ethereum proof-of-work algorithm"
@ -7,9 +7,9 @@ srcDir = "src"
### Dependencies
requires "nim >= 0.17.2", "number_theory", "keccak_tiny >= 0.1.0"
requires "nim >= 0.17.2", "keccak_tiny >= 0.1.0"
proc test(name: string, lang: string = "cpp") =
proc test(name: string, lang: string = "c") =
if not dirExists "build":
mkDir "build"
if not dirExists "nimcache":

View File

@ -58,10 +58,10 @@ proc mkcache*(cache_size: int, seed: seq[byte]): seq[Hash[512]] {.noSideEffect.}
# Sequentially produce the initial dataset
result = newSeq[Hash[512]](n)
result[0] = sha3_512 seed
result[0] = keccak512 seed
for i in 1 ..< n:
result[i] = sha3_512 result[i-1].toU512
result[i] = keccak512 result[i-1].toU512
# Use a low-round version of randmemohash
for _ in 0 ..< CACHE_ROUNDS:
@ -70,14 +70,14 @@ proc mkcache*(cache_size: int, seed: seq[byte]): seq[Hash[512]] {.noSideEffect.}
v = result[i].toU512[0] mod n.uint
a = result[(i-1+n) mod n].toU512
b = result[v.int].toU512
result[i] = sha3_512 zipMap(a, b, x xor y)
result[i] = keccak512 zipMap(a, b, x xor y)
# ###############################################################################
# Data aggregation function
const FNV_PRIME = 0x01000193
proc fnv[T: SomeUnsignedInt or Natural](v1, v2: T): T {.inline, noSideEffect.}=
proc fnv*[T: SomeUnsignedInt or Natural](v1, v2: T): T {.inline, noSideEffect.}=
# Original formula is ((v1 * FNV_PRIME) xor v2) mod 2^32
# However contrary to Python and depending on the type T,
@ -91,9 +91,11 @@ proc fnv[T: SomeUnsignedInt or Natural](v1, v2: T): T {.inline, noSideEffect.}=
# - for powers of 2: a mod 2^p == a and (2^p - 1)
# - 2^32 - 1 == high(uint32)
const mask: T = T(2^32) - 1
mulmod(v1 and mask, FNV_PRIME.T, (2^32).T) xor (v2 and mask)
# # mulmod(v1 and mask, FNV_PRIME.T, (2^32).T) xor (v2 and mask)
# Casting to uint32 should do the modulo and masking just fine
(v1.uint32 * FNV_PRIME) xor v2.uint32
# ###############################################################################
# Full dataset calculation
@ -109,7 +111,7 @@ proc calc_dataset_item(cache: seq[Hash[512]], i: Natural): Hash[512] {.noSideEff
mix[0] = mix[0] xor i.uint64
else:
mix[high(mix)] = mix[high(0)] xor i.uint64
mix[] = toU512 sha3_512 mix[]
mix[] = toU512 keccak512 mix[]
# FNV with a lots of random cache nodes based on i
# TODO: we use FNV with word size 64 bit while ethash implementation is using 32 bit words
@ -118,7 +120,7 @@ proc calc_dataset_item(cache: seq[Hash[512]], i: Natural): Hash[512] {.noSideEff
let cache_index = fnv(i.uint64 xor j, mix[j mod r])
mix[] = zipMap(mix[], cache[cache_index.int mod n].toU512, fnv(x, y))
result = sha3_512 mix[]
result = keccak512 mix[]
proc calc_dataset(cache: var seq[Hash[512]]) {.noSideEffect.} =
for i, hash in cache.mpairs:
@ -127,7 +129,7 @@ proc calc_dataset(cache: var seq[Hash[512]]) {.noSideEffect.} =
# ###############################################################################
when isMainModule:
echo get_full_size(100000)
let a = sha3_512 1234.toU512
let a = keccak512 1234.toU512
echo a

View File

@ -66,14 +66,22 @@ proc hexToSeqBytesBE*(hexStr: string): seq[byte] {.noSideEffect.}=
result[i] = hexStr[2*i].readHexChar shl 4 or hexStr[2*i+1].readHexChar
inc(i)
proc toHex*(ba: seq[byte]): string {.noSideEffect, noInit.}=
## Convert a big-endian byte-array to its hex representation
proc toHex*[N: static[int]](ba: ByteArrayBE[N]): string {.noSideEffect.}=
## Convert a big-endian byte array to its hex representation
## Output is in lowercase
##
const hexChars = "0123456789abcdef"
result = newString(2*N)
for i in 0 ..< N:
result[2*i] = hexChars[int ba[i] shr 4 and 0xF]
result[2*i+1] = hexChars[int ba[i] and 0xF]
proc toHex*(ba: seq[byte]): string {.noSideEffect, noInit.}=
## Convert a big-endian byte sequence to its hex representation
## Output is in lowercase
##
## Warning ⚠: Do not use toHex for hex representation of Public Keys
## Use the ``serialize`` proc:
## - PublicKey is actually 2 separate numbers corresponding to coordinate on elliptic curve
## - It is resistant against timing attack
let N = ba.len
const hexChars = "0123456789abcdef"

View File

@ -2,11 +2,49 @@
# MIT Licence
# Copyright (c) 2016 Mamy Ratsimbazafy
# ########### Number of bits to represent a number
# Compiler defined const: https://github.com/nim-lang/Nim/wiki/Consts-defined-by-the-compiler
const withBuiltins = defined(gcc) or defined(clang)
when withBuiltins:
proc builtin_clz(n: cuint): cint {.importc: "__builtin_clz", nodecl.}
proc builtin_clz(n: culong): cint {.importc: "__builtin_clzl", nodecl.}
proc builtin_clz(n: culonglong): cint {.importc: "__builtin_clzll", nodecl.}
type TbuiltinSupported = cuint or culong or culonglong
## Count Leading Zero with optimized builtins routines from GCC/Clang
## Warning ⚠: if n = 0, clz is undefined
proc bit_length*[T: SomeInteger](n: T): T =
## Calculates how many bits are necessary to represent the number
when withBuiltins and T is TbuiltinSupported:
result = if n == T(0): 0 # Removing this branch would make divmod 4x faster :/
else: T.sizeof * 8 - builtin_clz(n)
else:
var x = n
while x != T(0):
x = x shr 1
inc(result)
# ########### Integer math
proc isOdd*[T: SomeInteger](i: T): bool {.inline, noSideEffect.} =
(i and 1.T) != 0
# ############
proc isqrt*[T: SomeInteger](n: T): T =
## Integer square root, return the biggest squarable number under n
## Computation via Newton method
result = n
var y = (2.T shl ((n.bit_length() + 1) shr 1)) - 1
while y < result:
result = y
y = (result + n div result) shr 1
# ############ Efficient divmod
type
ldiv_t {.bycopy, importc: "ldiv_t", header:"<stdlib.h>".} = object
@ -34,7 +72,7 @@ proc divmod*[T: SomeUnsignedInt](a, b: T): tuple[quot, rem: T] {.inline.}=
# Hopefully the compiler does its work properly
(a div b, a mod b)
# ############
# ############ Modular arithmetics
proc addmod*[T: SomeInteger](a, b, m: T): T =
## Modular addition

View File

@ -4,8 +4,7 @@
# Primality testing. TODO: a scalable implementation (i.e. Miller-Rabin)
# See https://github.com/mratsim/nim-projecteuler/blob/master/src/lib/primes.nim
import number_theory # Not on Nimble yet: https://github.com/numforge/number-theory
import ./intmath
proc isPrime*(x: SomeUnsignedInt): bool {.noSideEffect.}=
for i in 2 .. isqrt x:

29
tests/all_tests.nim Normal file
View File

@ -0,0 +1,29 @@
# Copyright (c) 2018 Status Research & Development GmbH
# Distributed under the Apache v2 License (license terms are at http://www.apache.org/licenses/LICENSE-2.0).
import ../src/ethash, unittest, strutils,
keccak_tiny
suite "Base hashing algorithm":
test "FNV hashing":
let
x = 1235'u32
y = 9999999'u32
FNV_PRIME = 0x01000193'u32
check: ((FNV_PRIME * x) xor y) == fnv(x, y)
test "Keccak-256 - Note: spec mentions sha3 but it is Keccak":
let
input = "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
expected = "2b5ddf6f4d21c23de216f44d5e4bdc68e044b71897837ea74c83908be7037cd7".toUpperASCII
actual = toUpperASCII($input.keccak_256) # using keccak built-in conversion proc
actual2 = cast[array[256 div 8, byte]](input.keccak_256).toHex.toUpperAscii
check: expected == actual
check: expected == actual2