Add validated hex string type

This commit is contained in:
coffeepots 2018-06-18 18:31:11 +01:00
parent a5467063b7
commit 81d52cb3cb
4 changed files with 156 additions and 51 deletions

151
tests/ethhexstrings.nim Normal file
View File

@ -0,0 +1,151 @@
type
HexQuantityStr* = distinct string
HexDataStr* = distinct string
# Hex validation
template stripLeadingZeros(value: string): string =
var cidx = 0
# ignore the last character so we retain '0' on zero value
while cidx < value.len - 1 and value[cidx] == '0':
cidx.inc
value[cidx .. ^1]
proc encodeQuantity*(value: SomeUnsignedInt): string =
var hValue = value.toHex.stripLeadingZeros
result = "0x" & hValue
template hasHexHeader*(value: string | HexDataStr | HexQuantityStr): bool =
template strVal: untyped = value.string
if strVal[0] == '0' and strVal[1] in {'x', 'X'} and strVal.len > 2: true
else: false
template isHexChar*(c: char): bool =
if c notin {'0'..'9'} and
c notin {'a'..'f'} and
c notin {'A'..'F'}: false
else: true
proc validate*(value: HexQuantityStr): bool =
template strVal: untyped = value.string
if not value.hasHexHeader:
return false
# No leading zeros
if strVal[2] == '0': return false
for i in 2..<strVal.len:
let c = strVal[i]
if not c.isHexChar:
return false
return true
proc validate*(value: HexDataStr): bool =
template strVal: untyped = value.string
if not value.hasHexHeader:
return false
# Leading zeros are allowed
for i in 2..<strVal.len:
let c = strVal[i]
if not c.isHexChar:
return false
# Must be even number of digits
if strVal.len mod 2 != 0: return false
return true
# Initialisation
template hexDataStr*(value: string): HexDataStr = value.HexDataStr
template hexQuantityStr*(value: string): HexQuantityStr = value.HexQuantityStr
# Converters
import json
from ../rpcserver import expect
proc `%`*(value: HexDataStr): JsonNode =
if not value.validate:
raise newException(ValueError, "HexDataStr: Invalid hex for Ethereum: " & value.string)
else:
result = %(value.string)
proc `%`*(value: HexQuantityStr): JsonNode =
if not value.validate:
raise newException(ValueError, "HexQuantityStr: Invalid hex for Ethereum: " & value.string)
else:
result = %(value.string)
proc fromJson*(n: JsonNode, argName: string, result: var HexDataStr) =
# Note that '0x' is stripped after validation
n.kind.expect(JString, argName)
let hexStr = n.getStr()
if not hexStr.hexDataStr.validate:
raise newException(ValueError, "Parameter \"" & argName & "\" value is not valid as a Ethereum data \"" & hexStr & "\"")
result = hexStr[2..hexStr.high].hexDataStr
proc fromJson*(n: JsonNode, argName: string, result: var HexQuantityStr) =
# Note that '0x' is stripped after validation
n.kind.expect(JString, argName)
let hexStr = n.getStr()
if not hexStr.hexQuantityStr.validate:
raise newException(ValueError, "Parameter \"" & argName & "\" value is not valid as an Ethereum hex quantity \"" & hexStr & "\"")
result = hexStr[2..hexStr.high].hexQuantityStr
# testing
when isMainModule:
import unittest
suite "Hex quantity":
test "Even length":
let
source = "0x123"
x = hexQuantityStr source
check %x == %source
test "Odd length":
let
source = "0x123"
x = hexQuantityStr"0x123"
check %x == %source
test "Missing header":
expect ValueError:
let
source = "1234"
x = hexQuantityStr source
check %x != %source
expect ValueError:
let
source = "01234"
x = hexQuantityStr source
check %x != %source
expect ValueError:
let
source = "x1234"
x = hexQuantityStr source
check %x != %source
suite "Hex data":
test "Even length":
let
source = "0x1234"
x = hexDataStr source
check %x == %source
test "Odd length":
expect ValueError:
let
source = "0x123"
x = hexDataStr source
check %x != %source
test "Missing header":
expect ValueError:
let
source = "1234"
x = hexDataStr source
check %x != %source
expect ValueError:
let
source = "01234"
x = hexDataStr source
check %x != %source
expect ValueError:
let
source = "x1234"
x = hexDataStr source
check %x != %source

View File

@ -1,4 +1,4 @@
import ../rpcserver, nimcrypto, json, stint, strutils, ethtypes, stintjson, ethutils import ../rpcserver, nimcrypto, json, stint, strutils, ethtypes, stintjson, ethhexstrings
#[ #[
For details on available RPC calls, see: https://github.com/ethereum/wiki/wiki/JSON-RPC For details on available RPC calls, see: https://github.com/ethereum/wiki/wiki/JSON-RPC
@ -31,19 +31,16 @@ proc addEthRpcs*(server: RpcServer) =
## Returns the current client version. ## Returns the current client version.
result = "Nimbus-RPC-Test" result = "Nimbus-RPC-Test"
server.rpc("web3_sha3") do(data: string) -> string: server.rpc("web3_sha3") do(data: HexDataStr) -> HexDataStr:
## Returns Keccak-256 (not the standardized SHA3-256) of the given data. ## Returns Keccak-256 (not the standardized SHA3-256) of the given data.
## ##
## data: the data to convert into a SHA3 hash. ## data: the data to convert into a SHA3 hash.
## Returns the SHA3 result of the given string. ## Returns the SHA3 result of the given string.
# TODO: Capture error on malformed input # TODO: Capture error on malformed input
var rawData: seq[byte] var rawData: seq[byte]
if data.validateHexData: rawData = data.string.fromHex
rawData = data[2..data.high].fromHex
else:
raise newException(ValueError, "Invalid hex format")
# data will have 0x prefix # data will have 0x prefix
result = "0x" & $keccak_256.digest(rawData) result = hexDataStr "0x" & $keccak_256.digest(rawData)
server.rpc("net_version") do() -> string: server.rpc("net_version") do() -> string:
## Returns string of the current network id: ## Returns string of the current network id:

View File

@ -1,43 +0,0 @@
template stripLeadingZeros(value: string): string =
var cidx = 0
# ignore the last character so we retain '0' on zero value
while cidx < value.len - 1 and value[cidx] == '0':
cidx.inc
value[cidx .. ^1]
proc encodeQuantity*(value: SomeUnsignedInt): string =
var hValue = value.toHex.stripLeadingZeros
result = "0x" & hValue
template hasHexHeader*(value: string): bool =
if value[0] == '0' and value[1] in {'x', 'X'} and value.len > 2: true
else: false
template isHexChar*(c: char): bool =
if c notin {'0'..'9'} and
c notin {'a'..'f'} and
c notin {'A'..'F'}: false
else: true
proc validateHexQuantity*(value: string): bool =
if not value.hasHexHeader:
return false
# No leading zeros
if value[2] == '0': return false
for i in 2..<value.len:
let c = value[i]
if not c.isHexChar:
return false
return true
proc validateHexData*(value: string): bool =
if not value.hasHexHeader:
return false
# Leading zeros are allowed
for i in 2..<value.len:
let c = value[i]
if not c.isHexChar:
return false
# Must be even number of digits
if value.len mod 2 != 0: return false
return true

View File

@ -1,6 +1,6 @@
import unittest, json, tables import unittest, json, tables
import ../rpcclient, ../rpcstreamservers import ../rpcclient, ../rpcstreamservers
import stint, ethtypes, ethprocs, stintjson, nimcrypto import stint, ethtypes, ethprocs, stintjson, nimcrypto, ethhexstrings
from os import getCurrentDir, DirSep from os import getCurrentDir, DirSep
from strutils import rsplit from strutils import rsplit