diff --git a/src/backend_libsecp256k1/libsecp256k1.nim b/src/backend_libsecp256k1/libsecp256k1.nim index 2f72407..84320e6 100644 --- a/src/backend_libsecp256k1/libsecp256k1.nim +++ b/src/backend_libsecp256k1/libsecp256k1.nim @@ -20,13 +20,13 @@ proc `=destroy`(ctx: ptr secp256k1_context) = ctx.secp256k1_context_destroy type - Serialized_PubKey = ByteArrayBE[65] + Serialized_PubKey = array[65, byte] proc asPtrPubKey(key: PublicKey): ptr secp256k1_pubkey = - cast[ptr secp256k1_pubkey](unsafeAddr key.raw_key) + cast[ptr secp256k1_pubkey](unsafeAddr key) proc asPtrCuchar(key: PrivateKey): ptr cuchar = - cast[ptr cuchar](unsafeAddr key.raw_key) + cast[ptr cuchar](unsafeAddr key) proc asPtrCuchar(key: Serialized_PubKey): ptr cuchar = cast[ptr cuchar](unsafeAddr key) diff --git a/src/datatypes.nim b/src/datatypes.nim index 1c69cbc..f885a21 100644 --- a/src/datatypes.nim +++ b/src/datatypes.nim @@ -7,22 +7,43 @@ # # at your option. This file may not be copied, modified, or distributed except according to those terms. -import ./private/lowlevel_types -import ttmath +import ./private/conversion_bytes +export toHex, hexToByteArrayBE, hexToSeqByteBE -export lowlevel_types, ttmath +# Note: Fields are intentionally kept private type PublicKey* = object - raw_key*: ByteArrayBE[64] + Fraw_key: array[64, byte] PrivateKey* = object - raw_key*: ByteArrayBE[32] - public_key*: PublicKey - - BaseKey* = PrivateKey|PublicKey + Fraw_key: array[32, byte] + Fpublic_key: PublicKey Signature* {.packed.}= object - r*: UInt256 - s*: UInt256 - v*: range[0.byte .. 1.byte] + Fr: array[32, byte] + Fs: array[32, byte] + Fv: range[0.byte .. 1.byte] + + +# "Public" accessors, only exposed to internal modules + +template genAccessors(name: untyped, fieldType, objType: typedesc): untyped = + # Access + proc name*(obj: objType): fieldType {.noSideEffect, inline, noInit.} = + obj.`F name` + + # Assignement + proc `name=`*(obj: var objType, value: fieldType): fieldType {.noSideEffect, inline.} = + obj.`F name` = value + + # Mutable + proc `name`*(obj: var objType): var fieldType {.noSideEffect, inline.} = + obj.`F name` + +genAccessors(raw_key, array[64, byte], PublicKey) +genAccessors(raw_key, array[32, byte], PrivateKey) +genAccessors(public_key, PublicKey, PrivateKey) +genAccessors(s, array[32, byte], Signature) +genAccessors(r, array[32, byte], Signature) +genAccessors(v, range[0.byte .. 1.byte], Signature) diff --git a/src/eth_keys.nim b/src/eth_keys.nim index 19b57f5..417d787 100644 --- a/src/eth_keys.nim +++ b/src/eth_keys.nim @@ -7,13 +7,12 @@ # # at your option. This file may not be copied, modified, or distributed except according to those terms. -import ./datatypes, - ./datatypes_interface +import ./datatypes +export PublicKey, PrivateKey, Signature +import ./datatypes_interface +export datatypes_interface -export datatypes, - datatypes_interface - - -import ttmath -export ttmath +when defined(backend_native): + import ttmath + export ttmath diff --git a/src/private/lowlevel_types.nim b/src/private/conversion_bytes.nim similarity index 70% rename from src/private/lowlevel_types.nim rename to src/private/conversion_bytes.nim index 2ab5d60..941a117 100644 --- a/src/private/lowlevel_types.nim +++ b/src/private/conversion_bytes.nim @@ -7,7 +7,7 @@ # # at your option. This file may not be copied, modified, or distributed except according to those terms. -import ttmath, strutils +import strutils # Note on endianness: # - UInt256 uses host endianness @@ -19,24 +19,7 @@ import ttmath, strutils # https://www.reddit.com/r/crypto/comments/6287my/explanations_on_the_keccaksha3_paddingbyte/ # Note: Since Nim's Keccak-Tiny only accepts string as input, endianness does not matter. -type ByteArrayBE*[N: static[int]] = array[N, byte] - ## A byte array that stores bytes in big-endian order - -proc readUint256BE*(ba: ByteArrayBE[32]): UInt256 {.noSideEffect, inline.}= - ## Convert a big-endian array of Bytes to an UInt256 (in native host endianness) - const N = 32 - for i in 0 ..< N: - {.unroll: 4.} - result = result shl 8 or ba[i].u256 - -proc toByteArrayBE*(num: UInt256): ByteArrayBE[32] {.noSideEffect, noInit, inline.}= - ## Convert an UInt256 (in native host endianness) to a big-endian byte array - const N = 32 - for i in 0 ..< N: - {.unroll: 4.} - result[i] = byte getUInt(num shr uint((N-1-i) * 8)) - -proc readHexChar(c: char): byte {.noSideEffect.}= +proc readHexChar*(c: char): byte {.noSideEffect.}= ## Converts an hex char to a byte case c of '0'..'9': result = byte(ord(c) - ord('0')) @@ -45,7 +28,7 @@ proc readHexChar(c: char): byte {.noSideEffect.}= else: raise newException(ValueError, $c & "is not a hexademical character") -proc skip0xPrefix(hexStr: string): int {.inline.} = +proc skip0xPrefix*(hexStr: string): int {.inline.} = ## Returns the index of the first meaningful char in `hexStr` by skipping ## "0x" prefix if hexStr[0] == '0' and hexStr[1] in {'x', 'X'}: @@ -68,7 +51,7 @@ proc hexToByteArrayBE*(hexStr: string, output: var openArray[byte]) {.inline.} = ## Read a hex string and store it in a Byte Array `output` in Big-Endian order hexToByteArrayBE(hexStr, output, 0, output.high) -proc hexToByteArrayBE*[N: static[int]](hexStr: string): ByteArrayBE[N] {.noSideEffect, noInit, inline.}= +proc hexToByteArrayBE*[N: static[int]](hexStr: string): array[N, byte] {.noSideEffect, noInit, inline.}= ## Read an hex string and store it in a Byte Array in Big-Endian order hexToByteArrayBE(hexStr, result) @@ -83,33 +66,6 @@ proc hexToSeqByteBE*(hexStr: string): seq[byte] {.noSideEffect.}= result[i] = hexStr[2*i].readHexChar shl 4 or hexStr[2*i+1].readHexChar inc(i) -proc hexToUInt256*(hexStr: string): UInt256 {.noSideEffect.}= - ## Read an hex string and store it in a UInt256 - const N = 32 - - var i = skip0xPrefix(hexStr) - - assert hexStr.len - i == 2*N - - while i < 2*N: - result = result shl 4 or hexStr[i].readHexChar.uint.u256 - inc(i) - -proc toHex*(n: UInt256): string {.noSideEffect.}= - ## Convert uint256 to its hex representation - ## Output is in lowercase - - var rem = n # reminder to encode - - const - N = 32 # nb of bytes in n - hexChars = "0123456789abcdef" - - result = newString(2*N) - for i in countdown(2*N - 1, 0): - result[i] = hexChars[(rem and 0xF.u256).getUInt.int] - rem = rem shr 4 - proc toHexAux(ba: openarray[byte]): string {.noSideEffect.} = ## Convert a byte-array to its hex representation ## Output is in lowercase @@ -132,7 +88,7 @@ proc toHex*(ba: openarray[byte]): string {.noSideEffect, inline.} = ## - It is resistant against timing attack toHexAux(ba) -proc toHex*(ba: ByteArrayBE): string {.noSideEffect, inline.} = +proc toHex*[N: static[int]](ba: array[N, byte]): string {.noSideEffect, inline.} = ## Convert a byte-array to its hex representation ## Output is in lowercase ## diff --git a/src/private/conversion_ttmath.nim b/src/private/conversion_ttmath.nim new file mode 100644 index 0000000..2090f92 --- /dev/null +++ b/src/private/conversion_ttmath.nim @@ -0,0 +1,63 @@ +# Nim Eth-keys +# Copyright (c) 2018 Status Research & Development GmbH +# Licensed under either of +# +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) +# +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +import ttmath, strutils, + conversion_bytes + + +# Note on endianness: +# - UInt256 uses host endianness +# - Libsecp256k1, Ethereum EVM expect Big Endian +# https://github.com/ethereum/evmjit/issues/91 +# - Keccak expects least-significant byte first: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf +# Appendix B.1 p37 and outputs a hash with the same endianness as input +# http://www.dianacoman.com/2018/02/08/eucrypt-chapter-9-byte-order-and-bit-disorder-in-keccak/ +# https://www.reddit.com/r/crypto/comments/6287my/explanations_on_the_keccaksha3_paddingbyte/ +# Note: Since Nim's Keccak-Tiny only accepts string as input, endianness does not matter. + +proc toByteArrayBE*(num: UInt256): array[32, byte] {.noSideEffect, noInit, inline.}= + ## Convert an UInt256 (in native host endianness) to a big-endian byte array + const N = 32 + for i in 0 ..< N: + {.unroll: 4.} + result[i] = byte getUInt(num shr uint((N-1-i) * 8)) + +proc readUint256BE*(ba: array[32, byte]): UInt256 {.noSideEffect, inline.}= + ## Convert a big-endian array of Bytes to an UInt256 (in native host endianness) + const N = 32 + for i in 0 ..< N: + {.unroll: 4.} + result = result shl 8 or ba[i].u256 + +proc hexToUInt256*(hexStr: string): UInt256 {.noSideEffect.}= + ## Read an hex string and store it in a UInt256 + const N = 32 + + var i = skip0xPrefix(hexStr) + + assert hexStr.len - i == 2*N + + while i < 2*N: + result = result shl 4 or hexStr[i].readHexChar.uint.u256 + inc(i) + +proc toHex*(n: UInt256): string {.noSideEffect.}= + ## Convert uint256 to its hex representation + ## Output is in lowercase + + var rem = n # reminder to encode + + const + N = 32 # nb of bytes in n + hexChars = "0123456789abcdef" + + result = newString(2*N) + for i in countdown(2*N - 1, 0): + result[i] = hexChars[(rem and 0xF.u256).getUInt.int] + rem = rem shr 4 diff --git a/tests/test_hex_bytes_conversion.nim b/tests/test_hex_bytes_conversion.nim index e33a691..ead4234 100644 --- a/tests/test_hex_bytes_conversion.nim +++ b/tests/test_hex_bytes_conversion.nim @@ -7,8 +7,8 @@ # # at your option. This file may not be copied, modified, or distributed except according to those terms. -import ../src/private/lowlevel_types -import unittest, ttmath, strutils +import ../src/private/[conversion_bytes, conversion_ttmath] +import unittest, ttmath, strutils # TODO remove ttmath needs if backend libsecp256k1 suite "Testing conversion functions: Hex, Bytes, Endianness":