diff --git a/.appveyor.yml b/.appveyor.yml index c678125..033be48 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,37 +1,48 @@ version: '{build}' cache: -- nim-0.17.2_x64.zip - x86_64-4.9.2-release-win32-seh-rt_v4-rev4.7z -- packages -> **\packages.config -- '%LocalAppData%\NuGet\Cache -> **\packages.config' +- i686-4.9.2-release-win32-dwarf-rt_v4-rev4.7z +# We always want 32 and 64-bit compilation matrix: - fast_finish: true + fast_finish: false # set this flag to immediately finish build once one of the jobs fails. environment: + matrix: - - MINGW_ARCHIVE: x86_64-4.9.2-release-win32-seh-rt_v4-rev4.7z - MINGW_DIR: mingw64 - MINGW_URL: https://ayera.dl.sourceforge.net/project/mingw-w64/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/4.9.2/threads-win32/seh/x86_64-4.9.2-release-win32-seh-rt_v4-rev4.7z - NIM_ARCHIVE: nim-0.17.2_x64.zip - NIM_DIR: nim-0.17.2 - NIM_URL: https://nim-lang.org/download/nim-0.17.2_x64.zip + - MINGW_DIR: mingw32 + MINGW_URL: https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/4.9.2/threads-win32/dwarf/i686-4.9.2-release-win32-dwarf-rt_v4-rev4.7z/download + MINGW_ARCHIVE: i686-4.9.2-release-win32-dwarf-rt_v4-rev4.7z + platform: x86 + - MINGW_DIR: mingw64 + MINGW_URL: https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/4.9.2/threads-win32/seh/x86_64-4.9.2-release-win32-seh-rt_v4-rev4.7z/download + MINGW_ARCHIVE: x86_64-4.9.2-release-win32-seh-rt_v4-rev4.7z platform: x64 install: - - MKDIR %CD%\tools_tmp + - setlocal EnableExtensions EnableDelayedExpansion - IF not exist "%MINGW_ARCHIVE%" appveyor DownloadFile "%MINGW_URL%" -FileName "%MINGW_ARCHIVE%" - - 7z x -y "%MINGW_ARCHIVE%" -o"%CD%\tools_tmp"> nul - - IF not exist "%NIM_ARCHIVE%" appveyor DownloadFile "%NIM_URL%" -FileName "%NIM_ARCHIVE%" - - 7z x -y "%NIM_ARCHIVE%" -o"%CD%\tools_tmp"> nul - - SET PATH=%CD%\tools_tmp\%NIM_DIR%\bin;%CD%\tools_tmp\%MINGW_DIR%\bin;%PATH% - - SET PATH=%PATH%;%CD% - + - 7z x -y "%MINGW_ARCHIVE%" > nul + - SET PATH=%CD%\%MINGW_DIR%\bin;%CD%\Nim\bin;%PATH% + - git clone https://github.com/nim-lang/Nim.git + - cd %CD%\Nim + - git remote add statusim https://github.com/status-im/Nim.git + - git fetch statusim + - git config --global user.email "you@example.com" + - git config --global user.name "Your Name" + - for /f "tokens=*" %%G IN ('git branch -a --list ^"statusim/status-autopatch-*^"') DO (git merge %%G) + - git clone --depth 1 https://github.com/nim-lang/csources + - cd csources + - IF "%PLATFORM%" == "x64" ( build64.bat ) else ( build.bat ) + - cd .. + - bin\nim c koch + - koch boot -d:release + - koch nimble build_script: - - nimble.exe refresh - + - cd C:\projects\%APPVEYOR_PROJECT_SLUG% + - nimble install -y test_script: - - nimble.exe test + - nimble test deploy: off diff --git a/.travis.yml b/.travis.yml index 90ec7e4..0cb3877 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,43 +1,42 @@ -language: c - cache: ccache matrix: + # allow_failures: + # - os: osx + include: - # Build and test against the master (stable) and devel branches of Nim - # Build and test using both gcc and clang - os: linux - env: CHANNEL=stable - compiler: gcc + sudo: required + services: + - docker + before_install: + - docker pull statusteam/nim-base + script: + - docker run statusteam/nim-base nim --version + - docker run -v "$(pwd):/project" -w /project statusteam/nim-base sh -c "nimble install -dy && nimble test" - - os: linux - env: CHANNEL=devel - compiler: gcc - - # On OSX we only test against clang (gcc is mapped to clang by default) - os: osx - env: CHANNEL=stable - compiler: clang + before_install: + - brew update + - brew install rocksdb + # - brew install gcc - allow_failures: - # Ignore failures when building against the devel Nim branch - # Also ignore OSX, due to very long build time and Homebrew/curl SSLRead errors - - env: CHANNEL=devel - - os: osx - fast_finish: true + - git clone https://github.com/nim-lang/nim.git + - cd nim + - git remote add statusim https://github.com/status-im/nim.git + - git fetch statusim + - git config --global user.email "you@example.com" + - git config --global user.name "Your Name" + - for b in $(git branch -a --list 'statusim/status-autopatch-*'); do git merge $b; done + - git clone --depth 1 https://github.com/nim-lang/csources.git + - cd csources + - sh build.sh + - cd .. + - export PATH=$PWD/bin:$PATH + - nim c koch + - ./koch boot -d:release + - ./koch nimble + - cd .. -install: - - export CHOOSENIM_NO_ANALYTICS=1 - - curl https://nim-lang.org/choosenim/init.sh -sSf > init.sh - - sh init.sh -y - - export PATH=~/.nimble/bin:$PATH - - echo "export PATH=~/.nimble/bin:$PATH" >> ~/.profile - - choosenim $CHANNEL - -script: - - nimble refresh - - nimble test - -branches: - except: - - gh-pages + script: + - nimble install -dy && nimble test diff --git a/README.md b/README.md index 0ad1f74..bbd6ae4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ **Nim Ethash** -[![Build Status (Travis)](https://img.shields.io/travis/status-im/nim-ethash/master.svg?label=Linux%20/%20macOS "Linux/macOS build status (Travis)")](https://travis-ci.org/status-im/nim-ethash)[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) ![Stability: experimental](https://img.shields.io/badge/stability-experimental-orange.svg) +[![Build Status (Travis)](https://img.shields.io/travis/status-im/nim-ethash/master.svg?label=Linux%20/%20macOS "Linux/macOS build status (Travis)")](https://travis-ci.org/status-im/nim-ethash)[![License: Apache](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) ![Stability: experimental](https://img.shields.io/badge/stability-experimental-orange.svg) # Introduction A pure-Nim implementation of Ethash, the Ethereum proof of work diff --git a/benchmarks/proof_of_work_keccak_tiny.nim b/benchmarks/proof_of_work_keccak_tiny.nim new file mode 100644 index 0000000..f608ed0 --- /dev/null +++ b/benchmarks/proof_of_work_keccak_tiny.nim @@ -0,0 +1,230 @@ +# 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 math, endians, + keccak_tiny + +import ../src/private/[primes, conversion, functional, intmath] +import ../src/data_sizes + +# ############################################################################### +# Definitions + +const + REVISION* = 23 # Based on spec revision 23 + WORD_BYTES = 4 # bytes in word - in Nim we use 64 bits words # TODO check that + DATASET_BYTES_INIT* = 2'u^30 # bytes in dataset at genesis + DATASET_BYTES_GROWTH* = 2'u^23 # dataset growth per epoch + CACHE_BYTES_INIT* = 2'u^24 # bytes in cache at genesis + CACHE_BYTES_GROWTH* = 2'u^17 # cache growth per epoch + CACHE_MULTIPLIER = 1024 # Size of the DAG relative to the cache + EPOCH_LENGTH* = 30000 # blocks per epoch + MIX_BYTES* = 128 # width of mix + HASH_BYTES* = 64 # hash length in bytes + DATASET_PARENTS* = 256 # number of parents of each dataset element + CACHE_ROUNDS* = 3 # number of rounds in cache production + ACCESSES* = 64 # number of accesses in hashimoto loop + +# ############################################################################### +# Parameters + +proc get_cache_size*(block_number: uint): uint {.noSideEffect.}= + result = CACHE_BYTES_INIT + CACHE_BYTES_GROWTH * (block_number div EPOCH_LENGTH) + result -= HASH_BYTES + while (let dm = divmod(result, HASH_BYTES); + dm.rem == 0 and not dm.quot.isPrime): + # In a static lang, checking that the result of a division is prime + # Means checking that reminder == 0 and quotient is prime + result -= 2 * HASH_BYTES + +proc get_data_size*(block_number: uint): uint {.noSideEffect.}= + result = DATASET_BYTES_INIT + DATASET_BYTES_GROWTH * (block_number div EPOCH_LENGTH) + result -= MIX_BYTES + while (let dm = divmod(result, MIX_BYTES); + dm.rem == 0 and not dm.quot.isPrime): + result -= 2 * MIX_BYTES + +# ############################################################################### +# Fetch from lookup tables of 2048 epochs of data sizes and cache sizes + +proc get_datasize_lut*(block_number: Natural): uint64 {.noSideEffect, inline.} = + data_sizes[block_number div EPOCH_LENGTH] + +proc get_cachesize_lut*(block_number: Natural): uint64 {.noSideEffect, inline.} = + cache_sizes[block_number div EPOCH_LENGTH] + +# ############################################################################### +# Cache generation + +proc mkcache*(cache_size: uint64, seed: Hash[256]): seq[Hash[512]] {.noSideEffect.}= + + # Cache size + let n = int(cache_size div HASH_BYTES) + + # Sequentially produce the initial dataset + result = newSeq[Hash[512]](n) + result[0] = keccak512 seed.data + + for i in 1 ..< n: + result[i] = keccak512 result[i-1].data + + # Use a low-round version of randmemohash + for _ in 0 ..< CACHE_ROUNDS: + for i in 0 ..< n: + let + v = result[i].as_u32_words[0] mod n.uint32 + a = result[(i-1+n) mod n].data + b = result[v.int].data + 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): uint32 {.inline, noSideEffect.}= + + # Original formula is ((v1 * FNV_PRIME) xor v2) mod 2^32 + # However contrary to Python and depending on the type T, + # in Nim (v1 * FNV_PRIME) can overflow + # We can't do 2^32 with an int (only 2^32-1) + # and in general (a xor b) mod c != (a mod c) xor (b mod c) + # + # Thankfully + # We know that: + # - (a xor b) and c == (a and c) xor (b and c) + # - for powers of 2: a mod 2^p == a and (2^p - 1) + # - 2^32 - 1 == high(uint32) + + # So casting to uint32 should do the modulo and masking just fine + + (v1.uint32 * FNV_PRIME) xor v2.uint32 + +# ############################################################################### +# Full dataset calculation + +proc calc_dataset_item*(cache: seq[Hash[512]], i: Natural): Hash[512] {.noSideEffect, noInit.} = + let n = cache.len + const r: uint32 = HASH_BYTES div WORD_BYTES + + # Alias for the result value. Interpreted as an array of uint32 words + var mix = cast[ptr array[16, uint32]](addr result) + + mix[] = cache[i mod n].as_u32_words + when system.cpuEndian == littleEndian: + mix[0] = mix[0] xor i.uint32 + else: + mix[high(mix)] = mix[high(mix)] xor i.uint32 + result = keccak512 mix[] + + # FNV with a lots of random cache nodes based on i + for j in 0'u32 ..< DATASET_PARENTS: + let cache_index = fnv(i.uint32 xor j, mix[j mod r]) + mix[] = zipMap(mix[], cache[cache_index.int mod n].as_u32_words, fnv(x, y)) + + result = keccak512 mix[] + +when defined(openmp): + # Remove stacktraces when using OpenMP, heap alloc from strings will crash. + {.push stacktrace: off.} +proc calc_dataset*(full_size: Natural, cache: seq[Hash[512]]): seq[Hash[512]] = + + result = newSeq[Hash[512]](full_size div HASH_BYTES) + for i in `||`(0, result.len - 1, "simd"): + # OpenMP loop + result[i] = calc_dataset_item(cache, i) + +when defined(openmp): + # Remove stacktraces when using OpenMP, heap alloc from strings will crash. + {.pop.} + +# ############################################################################### +# Main loop + +type HashimotoHash = tuple[mix_digest, value: Hash[256]] + +template hashimoto(header: Hash[256], + nonce: uint64, + full_size: Natural, + dataset_lookup_p: untyped, + dataset_lookup_p1: untyped, + result: var HashimotoHash + ) = + let + n = uint32 full_size div HASH_BYTES + w = uint32 MIX_BYTES div WORD_BYTES + mixhashes = uint32 MIX_BYTES div HASH_BYTES + + assert full_size mod HASH_BYTES == 0 + assert MIX_BYTES mod HASH_BYTES == 0 + + # combine header+nonce into a 64 byte seed + var s{.noInit.}: Hash[512] + let s_bytes = cast[ptr array[64, byte]](addr s) # Alias for to interpret s as a byte array + let s_words = cast[ptr array[16, uint32]](addr s) # Alias for to interpret s as an uint32 array + + s_bytes[][0..<32] = header.data # We first populate the first 40 bytes of s with the concatenation + # In template we need to dereference first otherwise it's not considered as var + + var nonceLE{.noInit.}: array[8, byte] # the nonce should be concatenated with its LITTLE ENDIAN representation + littleEndian64(addr nonceLE, unsafeAddr nonce) + s_bytes[][32..<40] = cast[array[8,byte]](nonceLE) + + s = keccak_512 s_bytes[][0..<40] # TODO: Does this allocate a seq? + + # start the mix with replicated s + assert MIX_BYTES div HASH_BYTES == 2 + var mix{.noInit.}: array[32, uint32] + mix[0..<16] = s_words[] + mix[16..<32] = s_words[] + + # mix in random dataset nodes + for i in 0'u32 ..< ACCESSES: + let p{.inject.} = fnv(i xor s_words[0], mix[i mod w]) mod (n div mixhashes) * mixhashes + let p1{.inject.} = p + 1 + + # Unrolled: for j in range(MIX_BYTES / HASH_BYTES): => for j in 0 ..< 2 + var newdata{.noInit.}: type mix + newdata[0..<16] = cast[array[16, uint32]](dataset_lookup_p) + newdata[16..<32] = cast[array[16, uint32]](dataset_lookup_p1) + + mix = zipMap(mix, newdata, fnv(x, y)) + + # compress mix + # ⚠⚠ Warning ⚠⚠: Another bigEndian littleEndian issue? + # It doesn't seem like the uint32 in cmix need to be changed to big endian + # cmix is an alias to the result.mix_digest + let cmix = cast[ptr array[8, uint32]](addr result.mix_digest) + for i in countup(0, mix.len - 1, 4): + cmix[i div 4] = mix[i].fnv(mix[i+1]).fnv(mix[i+2]).fnv(mix[i+3]) + + var concat{.noInit.}: array[64 + 32, byte] + concat[0..<64] = s_bytes[] + concat[64..<96] = cast[array[32, byte]](result.mix_digest) + result.value = keccak_256(concat) + +proc hashimoto_light*(full_size:Natural, cache: seq[Hash[512]], + header: Hash[256], nonce: uint64): HashimotoHash {.noSideEffect.} = + + hashimoto(header, + nonce, + full_size, + calc_data_set_item(cache, p), + calc_data_set_item(cache, p1), + result) + +proc hashimoto_full*(full_size:Natural, dataset: seq[Hash[512]], + header: Hash[256], nonce: uint64): HashimotoHash {.noSideEffect.} = + # TODO spec mentions full_size but I don't think we need it (retrieve it from dataset.len) + hashimoto(header, + nonce, + full_size, + dataset[int(p)], + dataset[int(p1)], + result) +# ############################################################################### +# Defining the seed hash + +proc get_seedhash*(block_number: uint64): Hash[256] {.noSideEffect.} = + for i in 0 ..< int(block_number div EPOCH_LENGTH): + result = keccak256 result.data diff --git a/ethash.nimble b/ethash.nimble index fa54374..0136e2a 100644 --- a/ethash.nimble +++ b/ethash.nimble @@ -7,7 +7,7 @@ srcDir = "src" ### Dependencies -requires "nim >= 0.18.0", "keccak_tiny >= 0.1.0" +requires "nim >= 0.18.0", "nimcrypto >= 0.1.0" proc test(name: string, lang: string = "c") = if not dirExists "build": diff --git a/src/mining.nim b/src/mining.nim index b9e398f..131423d 100644 --- a/src/mining.nim +++ b/src/mining.nim @@ -1,8 +1,8 @@ # 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 ./proof_of_work, ./private/casting -import endians, random, math +import ./proof_of_work, ./private/conversion +import endians, random, math, nimcrypto proc mulCarry(a, b: uint64): tuple[carry, unit: uint64] = ## Multiplication in extended precision @@ -63,8 +63,8 @@ proc mulCarry(a, b: uint64): tuple[carry, unit: uint64] = proc isValid(nonce: uint64, difficulty: uint64, full_size: Natural, - dataset: seq[Hash[512]], - header: Hash[256]): bool {.noSideEffect.}= + dataset: seq[MDigest[512]], + header: MDigest[256]): bool {.noSideEffect.}= # Boundary is 2^256/difficulty # A valid nonce will have: hashimoto < 2^256/difficulty # We can't represent 2^256 as an uint256 so as a workaround we use: @@ -113,14 +113,13 @@ proc isValid(nonce: uint64, result = carry == 0 +# const High_uint64 = not 0'u64 # TODO: Nim random does not work on uint64 range. - -proc mine*(full_size: Natural, dataset: seq[Hash[512]], header: Hash[256], difficulty: uint64): uint64 = +proc mine*(full_size: Natural, dataset: seq[MDigest[512]], header: MDigest[256], difficulty: uint64): uint64 = # Returns a valid nonce randomize() # Start with a completely random seed - result = uint64 random(high(int)) # TODO: Nim random does not work on uint64 range. - # Also random is deprecated in devel and does not include the end of the range. + result = uint64 rand(high(int)) # TODO: Nim rand does not work on uint64 range. while not result.isValid(difficulty, full_size, dataset, header): inc(result) # we rely on uint overflow (mod 2^64) here. diff --git a/src/private/casting.nim b/src/private/conversion.nim similarity index 79% rename from src/private/casting.nim rename to src/private/conversion.nim index 3f46f49..1f4b2ca 100644 --- a/src/private/casting.nim +++ b/src/private/conversion.nim @@ -1,15 +1,12 @@ # 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 keccak_tiny +import nimcrypto -proc as_u32_words*[N: static[int]](x: Hash[N]): array[N div 32, uint32] {.inline, noSideEffect, noInit.}= +proc as_u32_words*[bits: static[int]](x: MDigest[bits]): array[bits div 32, uint32] {.inline, noSideEffect, noInit.}= # Convert an hash to its uint32 representation cast[type result](x) -type ByteArrayBE*[N: static[int]] = array[N, byte] - ## A byte array that stores bytes in big-endian order - proc readHexChar(c: char): byte {.noSideEffect.}= ## Converts an hex char to a byte case c @@ -19,7 +16,7 @@ proc readHexChar(c: char): byte {.noSideEffect.}= else: raise newException(ValueError, $c & "is not a hexademical character") -proc hexToByteArrayBE*[N: static[int]](hexStr: string): ByteArrayBE[N] {.noSideEffect, noInit.}= +proc hexToByteArrayBE*[N: static[int]](hexStr: string): array[N, byte] {.noSideEffect, noInit.}= ## Read an hex string and store it in a Byte Array in Big-Endian order var i = 0 if hexStr[i] == '0' and (hexStr[i+1] == 'x' or hexStr[i+1] == 'X'): @@ -44,7 +41,7 @@ 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*[N: static[int]](ba: ByteArrayBE[N]): string {.noSideEffect.}= +proc toHex*[N: static[int]](ba: array[N, byte]): string {.noSideEffect.}= ## Convert a big-endian byte array to its hex representation ## Output is in lowercase @@ -68,7 +65,7 @@ proc toHex*(ba: seq[byte]): string {.noSideEffect, noInit.}= result[2*i] = hexChars[int ba[i] shr 4 and 0xF] result[2*i+1] = hexChars[int ba[i] and 0xF] -proc toByteArrayBE*[T: SomeInteger](num: T): ByteArrayBE[T.sizeof] {.noSideEffect, noInit, inline.}= +proc toByteArrayBE*[T: SomeInteger](num: T): array[T.sizeof, byte] {.noSideEffect, noInit, inline.}= ## Convert an int (in native host endianness) to a big-endian byte array # Note: only works on devel @@ -81,5 +78,5 @@ proc toByteArrayBE*[T: SomeInteger](num: T): ByteArrayBE[T.sizeof] {.noSideEffec for i in 0 ..< N: result[i] = byte(num shr T((N-1-i) * 8)) -proc toByteArrayBE*[N: static[int]](x: Hash[N]): ByteArrayBE[N div 8] {.inline, noSideEffect, noInit.}= - cast[type result](x.data) \ No newline at end of file +proc toByteArrayBE*[bits: static[int]](x: MDigest[bits]): array[bits div 8, byte] {.inline, noSideEffect, noInit.}= + cast[type result](x.data) diff --git a/src/private/functional.nim b/src/private/functional.nim index 31dbb48..c82b28d 100644 --- a/src/private/functional.nim +++ b/src/private/functional.nim @@ -4,8 +4,6 @@ # Pending https://github.com/alehander42/zero-functional/issues/6 # A zip + map that avoids heap allocation -import ./casting - iterator enumerateZip[N: static[int], T, U]( a: array[N, T], b: array[N, U] @@ -28,10 +26,11 @@ template zipMap*[N: static[int], T, U]( op )) - var result: array[N, outType] + {.pragma: align64, codegenDecl: "$# $# __attribute__((aligned(64)))".} + var result{.noInit, align64.}: array[N, outType] for i, x {.inject.}, y {.inject.} in enumerateZip(a, b): - {.unroll: 4.} + {.unroll: 4.} # This is a no-op at the moment result[i] = op result diff --git a/src/proof_of_work.nim b/src/proof_of_work.nim index ef78e04..ada2989 100644 --- a/src/proof_of_work.nim +++ b/src/proof_of_work.nim @@ -2,11 +2,10 @@ # Distributed under the Apache v2 License (license terms are at http://www.apache.org/licenses/LICENSE-2.0). import math, endians, - keccak_tiny + nimcrypto -import ./private/[primes, casting, functional, intmath] +import ./private/[primes, conversion, functional, intmath] export toHex, hexToByteArrayBE, hexToSeqBytesBE, toByteArrayBE # debug functions -export keccak_tiny # ############################################################################### # Definitions @@ -58,17 +57,17 @@ proc get_cachesize_lut*(block_number: Natural): uint64 {.noSideEffect, inline.} # ############################################################################### # Cache generation -proc mkcache*(cache_size: uint64, seed: Hash[256]): seq[Hash[512]] {.noSideEffect.}= +proc mkcache*(cache_size: uint64, seed: MDigest[256]): seq[MDigest[512]] {.noSideEffect.}= # Cache size let n = int(cache_size div HASH_BYTES) # Sequentially produce the initial dataset - result = newSeq[Hash[512]](n) - result[0] = keccak512 seed.data + result = newSeq[MDigest[512]](n) + result[0] = keccak512.digest seed.data for i in 1 ..< n: - result[i] = keccak512 result[i-1].data + result[i] = keccak512.digest result[i-1].data # Use a low-round version of randmemohash for _ in 0 ..< CACHE_ROUNDS: @@ -77,7 +76,7 @@ proc mkcache*(cache_size: uint64, seed: Hash[256]): seq[Hash[512]] {.noSideEffec v = result[i].as_u32_words[0] mod n.uint32 a = result[(i-1+n) mod n].data b = result[v.int].data - result[i] = keccak512 zipMap(a, b, x xor y) + result[i] = keccak512.digest zipMap(a, b, x xor y) # ############################################################################### # Data aggregation function @@ -105,7 +104,7 @@ proc fnv*[T: SomeUnsignedInt or Natural](v1, v2: T): uint32 {.inline, noSideEffe # ############################################################################### # Full dataset calculation -proc calc_dataset_item*(cache: seq[Hash[512]], i: Natural): Hash[512] {.noSideEffect, noInit.} = +proc calc_dataset_item*(cache: seq[MDigest[512]], i: Natural): MDigest[512] {.noSideEffect, noInit.} = let n = cache.len const r: uint32 = HASH_BYTES div WORD_BYTES @@ -117,21 +116,21 @@ proc calc_dataset_item*(cache: seq[Hash[512]], i: Natural): Hash[512] {.noSideEf mix[0] = mix[0] xor i.uint32 else: mix[high(mix)] = mix[high(mix)] xor i.uint32 - result = keccak512 mix[] + result = keccak512.digest mix[] # FNV with a lots of random cache nodes based on i for j in 0'u32 ..< DATASET_PARENTS: let cache_index = fnv(i.uint32 xor j, mix[j mod r]) mix[] = zipMap(mix[], cache[cache_index.int mod n].as_u32_words, fnv(x, y)) - result = keccak512 mix[] + result = keccak512.digest mix[] when defined(openmp): # Remove stacktraces when using OpenMP, heap alloc from strings will crash. {.push stacktrace: off.} -proc calc_dataset*(full_size: Natural, cache: seq[Hash[512]]): seq[Hash[512]] = +proc calc_dataset*(full_size: Natural, cache: seq[MDigest[512]]): seq[MDigest[512]] = - result = newSeq[Hash[512]](full_size div HASH_BYTES) + result = newSeq[MDigest[512]](full_size div HASH_BYTES) for i in `||`(0, result.len - 1, "simd"): # OpenMP loop result[i] = calc_dataset_item(cache, i) @@ -143,9 +142,9 @@ when defined(openmp): # ############################################################################### # Main loop -type HashimotoHash = tuple[mix_digest, value: Hash[256]] +type HashimotoHash = tuple[mix_digest, value: MDigest[256]] -template hashimoto(header: Hash[256], +template hashimoto(header: MDigest[256], nonce: uint64, full_size: Natural, dataset_lookup_p: untyped, @@ -161,7 +160,8 @@ template hashimoto(header: Hash[256], assert MIX_BYTES mod HASH_BYTES == 0 # combine header+nonce into a 64 byte seed - var s{.noInit.}: Hash[512] + {.pragma: align64, codegenDecl: "$# $# __attribute__((aligned(64)))".} + var s{.align64, noInit.}: MDigest[512] let s_bytes = cast[ptr array[64, byte]](addr s) # Alias for to interpret s as a byte array let s_words = cast[ptr array[16, uint32]](addr s) # Alias for to interpret s as an uint32 array @@ -172,11 +172,11 @@ template hashimoto(header: Hash[256], littleEndian64(addr nonceLE, unsafeAddr nonce) s_bytes[][32..<40] = cast[array[8,byte]](nonceLE) - s = keccak_512 s_bytes[][0..<40] # TODO: Does this allocate a seq? + s = keccak_512.digest s_bytes[][0..<40] # TODO: Does this slicing allocate a seq? # start the mix with replicated s assert MIX_BYTES div HASH_BYTES == 2 - var mix{.noInit.}: array[32, uint32] + var mix{.align64, noInit.}: array[32, uint32] mix[0..<16] = s_words[] mix[16..<32] = s_words[] @@ -203,10 +203,10 @@ template hashimoto(header: Hash[256], var concat{.noInit.}: array[64 + 32, byte] concat[0..<64] = s_bytes[] concat[64..<96] = cast[array[32, byte]](result.mix_digest) - result.value = keccak_256(concat) + result.value = keccak_256.digest concat -proc hashimoto_light*(full_size:Natural, cache: seq[Hash[512]], - header: Hash[256], nonce: uint64): HashimotoHash {.noSideEffect.} = +proc hashimoto_light*(full_size:Natural, cache: seq[MDigest[512]], + header: MDigest[256], nonce: uint64): HashimotoHash {.noSideEffect.} = hashimoto(header, nonce, @@ -215,8 +215,8 @@ proc hashimoto_light*(full_size:Natural, cache: seq[Hash[512]], calc_data_set_item(cache, p1), result) -proc hashimoto_full*(full_size:Natural, dataset: seq[Hash[512]], - header: Hash[256], nonce: uint64): HashimotoHash {.noSideEffect.} = +proc hashimoto_full*(full_size:Natural, dataset: seq[MDigest[512]], + header: MDigest[256], nonce: uint64): HashimotoHash {.noSideEffect.} = # TODO spec mentions full_size but I don't think we need it (retrieve it from dataset.len) hashimoto(header, nonce, @@ -227,6 +227,6 @@ proc hashimoto_full*(full_size:Natural, dataset: seq[Hash[512]], # ############################################################################### # Defining the seed hash -proc get_seedhash*(block_number: uint64): Hash[256] {.noSideEffect.} = +proc get_seedhash*(block_number: uint64): MDigest[256] {.noSideEffect.} = for i in 0 ..< int(block_number div EPOCH_LENGTH): - result = keccak256 result.data + result = keccak256.digest result.data diff --git a/tests/test_mining.nim b/tests/test_mining.nim index 1b5fc6d..34af184 100644 --- a/tests/test_mining.nim +++ b/tests/test_mining.nim @@ -1,7 +1,7 @@ # 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, keccak_tiny, times, strutils +import ../src/ethash, unittest, times, strutils, nimcrypto suite "Test mining": @@ -12,7 +12,7 @@ suite "Test mining": let blck = 22'u # block number cache = mkcache(get_cachesize(blck), get_seedhash(blck)) - header = cast[Hash[256]]( + header = cast[MDigest[256]]( hexToByteArrayBE[32]("372eca2454ead349c3df0ab5d00b0b706b23e49d469387db91811cee0358fc6d") ) difficulty = 132416'u64 diff --git a/tests/test_proof_of_work.nim b/tests/test_proof_of_work.nim index b3c63b7..f2ad57e 100644 --- a/tests/test_proof_of_work.nim +++ b/tests/test_proof_of_work.nim @@ -1,8 +1,7 @@ # 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, algorithm, random, sequtils, - keccak_tiny +import ../src/ethash, unittest, strutils, algorithm, random, sequtils, nimcrypto suite "Base hashing algorithm": @@ -21,8 +20,8 @@ suite "Base hashing algorithm": 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 + actual = toUpperASCII($keccak256.digest(input)) # using keccak built-in conversion proc + actual2 = cast[array[256 div 8, byte]](keccak_256.digest(input)).toHex.toUpperAscii check: expected == actual check: expected == actual2 @@ -32,8 +31,8 @@ suite "Base hashing algorithm": let input = "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" expected = "0be8a1d334b4655fe58c6b38789f984bb13225684e86b20517a55ab2386c7b61c306f25e0627c60064cecd6d80cd67a82b3890bd1289b7ceb473aad56a359405".toUpperASCII - actual = toUpperASCII($input.keccak_512) # using keccak built-in conversion proc - actual2 = cast[array[512 div 8, byte]](input.keccak_512).toHex.toUpperAscii + actual = toUpperASCII($keccak512.digest(input)) # using keccak built-in conversion proc + actual2 = cast[array[512 div 8, byte]](keccak_512.digest(input)).toHex.toUpperAscii check: expected == actual check: expected == actual2 @@ -117,7 +116,7 @@ suite "Cache initialization": # https://github.com/ethereum/ethash/blob/f5f0a8b1962544d2b6f40df8e4b0d9a32faf8f8e/test/python/test_pyethash.py#L31-L36 test "Mkcache": let actual_str = "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - var actual_hash: Hash[256] + var actual_hash: MDigest[256] copyMem(addr actual_hash, unsafeAddr actual_str[0], 256 div 8) let @@ -136,10 +135,10 @@ suite "Seed hash": check: $get_seedhash(0) == zeroHex test "Seed hash of the next 2048 epochs (2048 * 30000 blocks)": - var expected: Hash[256] + var expected: MDigest[256] for i in countup(0'u32, 30000 * 2048, 30000): check: get_seedhash(i) == expected - expected = keccak_256(expected.data) + expected = keccak_256.digest(expected.data) suite "Dagger hashimoto computation": # We can't replicate Python's dynamic typing here @@ -151,11 +150,11 @@ suite "Dagger hashimoto computation": cache_str = "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" header_str = "~~~~~X~~~~~~~~~~~~~~~~~~~~~~~~~~" - var cache_hash: Hash[256] + var cache_hash: MDigest[256] copyMem(addr cache_hash, unsafeAddr cache_str[0], 256 div 8) let cache = mkcache(cache_size, cache_hash) - var header: Hash[256] + var header: MDigest[256] copyMem(addr header, unsafeAddr header_str[0], 256 div 8) let full = calc_dataset(full_size, cache) @@ -168,7 +167,7 @@ suite "Dagger hashimoto computation": test "Real dataset and recomputation from cache matches": # https://github.com/ethereum/ethash/blob/f5f0a8b1962544d2b6f40df8e4b0d9a32faf8f8e/test/c/test.cpp#L360-L374 - for i in 0 ..< full_size div sizeof(Hash[512]): + for i in 0 ..< full_size div sizeof(MDigest[512]): for j in 0 ..< 32: let expected = calc_dataset_item(cache, j) check: full[j] == expected @@ -183,7 +182,7 @@ suite "Dagger hashimoto computation": let full_result = hashimoto_full(full_size, dataset, header, 0) # Check not null - var zero_hash : Hash[256] + var zero_hash : MDigest[256] check: light_result.mix_digest != zero_hash check: light_result.value != zero_hash check: light_result == full_result @@ -194,7 +193,7 @@ suite "Real blocks test": # POC-9 testnet, epoch 0 let blck = 22'u # block number let cache = mkcache(get_cachesize(blck), get_seedhash(blck)) - let header = cast[Hash[256]]( + let header = cast[MDigest[256]]( hexToByteArrayBE[32]("372eca2454ead349c3df0ab5d00b0b706b23e49d469387db91811cee0358fc6d") ) @@ -205,10 +204,10 @@ suite "Real blocks test": 0x495732e0ed7a801c'u ) - check: light.value == cast[Hash[256]]( + check: light.value == cast[MDigest[256]]( hexToByteArrayBE[32]("00000b184f1fdd88bfd94c86c39e65db0c36144d5e43f745f722196e730cb614") ) - check: light.mixDigest == cast[Hash[256]]( + check: light.mixDigest == cast[MDigest[256]]( hexToByteArrayBE[32]("2f74cdeb198af0b9abe65d22d372e22fb2d474371774a9583c1cc427a07939f5") ) @@ -217,7 +216,7 @@ suite "Real blocks test": # POC-9 testnet, epoch 1 let blck = 30001'u # block number let cache = mkcache(get_cachesize(blck), get_seedhash(blck)) - let header = cast[Hash[256]]( + let header = cast[MDigest[256]]( hexToByteArrayBE[32]("7e44356ee3441623bc72a683fd3708fdf75e971bbe294f33e539eedad4b92b34") ) @@ -228,7 +227,7 @@ suite "Real blocks test": 0x318df1c8adef7e5e'u ) - check: light.mixDigest == cast[Hash[256]]( + check: light.mixDigest == cast[MDigest[256]]( hexToByteArrayBE[32]("144b180aad09ae3c81fb07be92c8e6351b5646dda80e6844ae1b697e55ddde84") ) @@ -237,7 +236,7 @@ suite "Real blocks test": # POC-9 testnet, epoch 2 let blck = 60000'u # block number let cache = mkcache(get_cachesize(blck), get_seedhash(blck)) - let header = cast[Hash[256]]( + let header = cast[MDigest[256]]( hexToByteArrayBE[32]("5fc898f16035bf5ac9c6d9077ae1e3d5fc1ecc3c9fd5bee8bb00e810fdacbaa0") ) @@ -248,6 +247,6 @@ suite "Real blocks test": 0x50377003e5d830ca'u ) - check: light.mixDigest == cast[Hash[256]]( + check: light.mixDigest == cast[MDigest[256]]( hexToByteArrayBE[32]("ab546a5b73c452ae86dadd36f0ed83a6745226717d3798832d1b20b489e82063") )