diff --git a/nimbus/config.nim b/nimbus/config.nim index 07a1af331..5b41b2c7b 100644 --- a/nimbus/config.nim +++ b/nimbus/config.nim @@ -8,7 +8,7 @@ # those terms. import - parseopt, strutils, macros, os, times, json, stew/[byteutils], + parseopt, strutils, macros, os, times, json, tables, stew/[byteutils], chronos, eth/[keys, common, p2p, net/nat], chronicles, nimcrypto/hash, eth/p2p/bootnodes, eth/p2p/rlpx_protocols/whisper_protocol, ./db/select_backend, eth/keys, @@ -121,6 +121,11 @@ type Full Archive + NimbusAccount* = object + privateKey*: PrivateKey + keystore*: JsonNode + unlocked*: bool + ChainConfig* = object chainId*: uint homesteadBlock*: BlockNumber @@ -143,7 +148,7 @@ type NimbusConfiguration* = ref object ## Main Nimbus configuration object dataDir*: string - keyFile*: string + keyStore*: string prune*: PruneMode rpc*: RpcConfiguration ## JSON-RPC configuration net*: NetConfiguration ## Network configuration @@ -153,6 +158,7 @@ type # You should only create one instance of the RNG per application / library # Ref is used so that it can be shared between components rng*: ref BrHmacDrbgContext + accounts*: Table[EthAddress, NimbusAccount] CustomGenesisConfig = object chainId*: uint @@ -557,11 +563,8 @@ proc processEthArguments(key, value: string): ConfigStatus = result = Success let config = getConfiguration() case key.toLowerAscii() - of "keyfile": - if fileExists(value): - config.keyFile = value - else: - result = ErrorIncorrectOption + of "keystore": + config.keyStore = value of "datadir": config.dataDir = value of "prune": @@ -825,16 +828,20 @@ template processArgument(processor, key, value, msg: untyped) = proc getDefaultDataDir*(): string = when defined(windows): - "AppData" / "Roaming" / "Nimbus" / "DB" + "AppData" / "Roaming" / "Nimbus" elif defined(macosx): - "Library" / "Application Support" / "Nimbus" / "DB" + "Library" / "Application Support" / "Nimbus" else: - ".cache" / "nimbus" / "db" + ".cache" / "nimbus" + +proc getDefaultKeystoreDir*(): string = + getDefaultDataDir() / "keystore" proc initConfiguration(): NimbusConfiguration = ## Allocates and initializes `NimbusConfiguration` with default values result = new NimbusConfiguration result.rng = newRng() + result.accounts = initTable[EthAddress, NimbusAccount]() ## RPC defaults result.rpc.flags = {} @@ -853,9 +860,12 @@ proc initConfiguration(): NimbusConfiguration = result.net.protocols = defaultProtocols result.net.nodekey = random(PrivateKey, result.rng[]) - const dataDir = getDefaultDataDir() + const + dataDir = getDefaultDataDir() + keystore = getDefaultKeystoreDir() result.dataDir = getHomeDir() / dataDir + result.keystore = getHomeDir() / keystore result.prune = PruneMode.Full ## Whisper defaults @@ -897,7 +907,7 @@ USAGE: nimbus [options] ETHEREUM OPTIONS: - --keyfile: Use keyfile storage file + --keystore: Directory for the keystore (default = inside the datadir) --datadir: Base directory for all blockchain-related data --prune: Blockchain prune mode(full or archive) diff --git a/nimbus/db/db_chain.nim b/nimbus/db/db_chain.nim index 6cd0fa97a..d72eca457 100644 --- a/nimbus/db/db_chain.nim +++ b/nimbus/db/db_chain.nim @@ -159,16 +159,21 @@ iterator getBlockTransactionHashes(self: BaseChainDB, blockHeader: BlockHeader): for encodedTx in self.getBlockTransactionData(blockHeader.txRoot): yield keccakHash(encodedTx) -proc getTransactionCount*(chain: BaseChainDB, blockHash: Hash256): int = - var header: BlockHeader - if chain.getBlockHeader(blockHash, header): - var trie = initHexaryTrie(chain.db, header.txRoot) - var txCount = 0 - while true: - let txKey = rlp.encode(txCount) - if txKey notin trie: - break - inc txCount +proc getTransactionCount*(chain: BaseChainDB, txRoot: Hash256): int = + var trie = initHexaryTrie(chain.db, txRoot) + var txCount = 0 + while true: + let txKey = rlp.encode(txCount) + if txKey notin trie: + break + inc txCount + +proc getUnclesCount*(self: BaseChainDB, ommersHash: Hash256): int = + if ommersHash != EMPTY_UNCLE_HASH: + let encodedUncles = self.db.get(genericHashKey(ommersHash).toOpenArray) + if encodedUncles.len != 0: + let r = rlpFromBytes(encodedUncles) + result = r.listLen proc getBlockBody*(self: BaseChainDB, blockHash: Hash256, output: var BlockBody): bool = var header: BlockHeader diff --git a/nimbus/nimbus.nim b/nimbus/nimbus.nim index 9813856d7..a9ad11b2f 100644 --- a/nimbus/nimbus.nim +++ b/nimbus/nimbus.nim @@ -15,7 +15,7 @@ import eth/p2p/rlpx_protocols/[eth_protocol, les_protocol, whisper_protocol], eth/p2p/blockchain_sync, eth/net/nat, eth/p2p/peer_pool, config, genesis, rpc/[common, p2p, debug, whisper, key_storage], p2p/chain, - eth/trie/db, metrics, metrics/chronicles_support + eth/trie/db, metrics, metrics/chronicles_support, utils ## TODO: ## * No IPv6 support @@ -36,6 +36,7 @@ type proc start(nimbus: NimbusNode) = var conf = getConfiguration() + conf.loadKeystoreFiles() ## logging setLogLevel(conf.debug.logLevel) diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index de52f90e1..c876345d9 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -96,15 +96,13 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) = proc accountDbFromTag(tag: string, readOnly = true): ReadOnlyStateDB = result = getAccountDb(chain.headerFromTag(tag)) - #[proc getBlockBody(hash: KeccakHash): BlockBody = - if not chain.getBlockBody(hash, result): - raise newException(ValueError, "Cannot find hash")]# - server.rpc("eth_protocolVersion") do() -> string: result = $eth_protocol.protocolVersion server.rpc("eth_syncing") do() -> JsonNode: ## Returns SyncObject or false when not syncing. + # TODO: make sure we are not syncing + # when we reach the recent block let numPeers = node.peerPool.connectedNodes.len if numPeers > 0: var sync = SyncState( @@ -137,7 +135,10 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) = server.rpc("eth_accounts") do() -> seq[EthAddressStr]: ## Returns a list of addresses owned by client. - result = @[] + let conf = getConfiguration() + result = newSeqOfCap[EthAddressStr](conf.accounts.len) + for k in keys(conf.accounts): + result.add ethAddressStr(k) server.rpc("eth_blockNumber") do() -> HexQuantityStr: ## Returns integer of the current block number the client is on. @@ -150,9 +151,9 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) = ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns integer of the current balance in wei. let - accountDb = accountDbFromTag(quantityTag) + accDB = accountDbFromTag(quantityTag) address = data.toAddress - balance = accountDb.getBalance(address) + balance = accDB.getBalance(address) result = encodeQuantity(balance) server.rpc("eth_getStorageAt") do(data: EthAddressStr, quantity: HexQuantityStr, quantityTag: string) -> HexDataStr: @@ -163,10 +164,10 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) = ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns: the value at this storage position. let - accountDb = accountDbFromTag(quantityTag) + accDB = accountDbFromTag(quantityTag) address = data.toAddress - key = fromHex(Uint256, quantity.string) - value = accountDb.getStorage(address, key)[0] + key = fromHex(Uint256, quantity.string) + value = accDB.getStorage(address, key)[0] result = hexDataStr(value) server.rpc("eth_getTransactionCount") do(data: EthAddressStr, quantityTag: string) -> HexQuantityStr: @@ -177,8 +178,8 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) = ## Returns integer of the number of transactions send from this address. let address = data.toAddress - accountDb = accountDbFromTag(quantityTag) - result = encodeQuantity(accountDb.getNonce(address)) + accDB = accountDbFromTag(quantityTag) + result = encodeQuantity(accDB.getNonce(address)) server.rpc("eth_getBlockTransactionCountByHash") do(data: EthHashStr) -> HexQuantityStr: ## Returns the number of transactions in a block from a block matching the given block hash. @@ -186,33 +187,41 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) = ## data: hash of a block ## Returns integer of the number of transactions in this block. let - hashData = data.toHash - txCount = chain.getTransactionCount(hashData) + blockHash = data.toHash + header = chain.getBlockHeader(blockHash) + txCount = chain.getTransactionCount(header.txRoot) result = encodeQuantity(txCount.uint) -#[ - server.rpc("eth_getBlockTransactionCountByNumber") do(quantityTag: string) -> int: + + server.rpc("eth_getBlockTransactionCountByNumber") do(quantityTag: string) -> HexQuantityStr: ## Returns the number of transactions in a block matching the given block number. ## ## data: integer of a block number, or the string "earliest", "latest" or "pending", as in the default block parameter. ## Returns integer of the number of transactions in this block. - let header = chain.headerFromTag(quantityTag) - result = getBlockBody(header.hash).transactions.len + let + header = chain.headerFromTag(quantityTag) + txCount = chain.getTransactionCount(header.txRoot) + result = encodeQuantity(txCount.uint) - server.rpc("eth_getUncleCountByBlockHash") do(data: EthHashStr) -> int: + server.rpc("eth_getUncleCountByBlockHash") do(data: EthHashStr) -> HexQuantityStr: ## Returns the number of uncles in a block from a block matching the given block hash. ## ## data: hash of a block. ## Returns integer of the number of uncles in this block. - var hashData = data.toHash - result = getBlockBody(hashData).uncles.len + let + blockHash = data.toHash + header = chain.getBlockHeader(blockHash) + unclesCount = chain.getUnclesCount(header.ommersHash) + result = encodeQuantity(unclesCount.uint) - server.rpc("eth_getUncleCountByBlockNumber") do(quantityTag: string) -> int: + server.rpc("eth_getUncleCountByBlockNumber") do(quantityTag: string) -> HexQuantityStr: ## Returns the number of uncles in a block from a block matching the given block number. ## ## quantityTag: integer of a block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns integer of uncles in this block. - let header = chain.headerFromTag(quantityTag) - result = getBlockBody(header.hash).uncles.len + let + header = chain.headerFromTag(quantityTag) + unclesCount = chain.getUnclesCount(header.ommersHash) + result = encodeQuantity(unclesCount.uint) server.rpc("eth_getCode") do(data: EthAddressStr, quantityTag: string) -> HexDataStr: ## Returns code at a given address. @@ -221,14 +230,13 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) = ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns the code from the given address. let - accountDb = accountDbFromTag(quantityTag) - address = toAddress(data) - storage = accountDb.getCode(address) - # Easier to return the string manually here rather than expect ByteRange to be marshalled - result = byteutils.toHex(storage).HexDataStr + accDB = accountDbFromTag(quantityTag) + address = data.toAddress + storage = accDB.getCode(address) + result = hexDataStr(storage) template sign(privateKey: PrivateKey, message: string): string = - # TODO: Is message length encoded as bytes or characters? + # message length encoded as ASCII representation of decimal let msgData = "\x19Ethereum Signed Message:\n" & $message.len & message $sign(privateKey, msgData.toBytes()) @@ -241,9 +249,16 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) = ## data: address. ## message: message to sign. ## Returns signature. - var privateKey: PrivateKey # TODO: Get from key store - result = ("0x" & sign(privateKey, message.string)).HexDataStr + let + address = data.toAddress + conf = getConfiguration() + acc = conf.getAccount(address).tryGet() + msg = hexToSeqByte(message.string) + if not acc.unlocked: + raise newException(ValueError, "Account locked, please unlock it first") + result = ("0x" & sign(acc.privateKey, cast[string](msg))).HexDataStr +#[ # proc setupTransaction(send: EthSend): Transaction = # let # source = send.source.toAddress diff --git a/nimbus/utils.nim b/nimbus/utils.nim index 0d91ec0a6..050a9bfa2 100644 --- a/nimbus/utils.nim +++ b/nimbus/utils.nim @@ -1,4 +1,6 @@ -import eth/trie/db, eth/[trie, rlp, common], nimcrypto +import + os, tables, json, ./config, stew/[results, byteutils], + eth/trie/db, eth/[trie, rlp, common, keyfile], nimcrypto export nimcrypto.`$` @@ -50,3 +52,40 @@ proc crc32*(crc: uint32, buf: openArray[byte]): uint32 = crcu32 = (crcu32 shr 4) xor kcrc32[int((crcu32 and 0xF) xor (uint32(b) shr 4'u32))] result = not crcu32 + +proc loadKeystoreFiles*(conf: NimbusConfiguration): Result[void, string] = + try: + createDir(conf.keyStore) + except OSError, IOError: + return err("keystore: cannot create directory") + + for filename in walkDirRec(conf.keyStore): + try: + var data = json.parseFile(filename) + let address: EthAddress = hexToByteArray[20](data["address"].getStr()) + conf.accounts[address] = NimbusAccount(keystore: data, unlocked: false) + except JsonParsingError: + return err("keystore: json parsing error " & filename) + except ValueError: + return err("keystore: data parsing error") + except Exception: # json raises Exception + return err("keystore: " & getCurrentExceptionMsg()) + + result = ok() + +proc getAccount*(conf: NimbusConfiguration, address: EthAddress): Result[NimbusAccount, string] = + conf.accounts.withValue(address, val) do: + result = ok(val[]) + do: + result = err("getAccount: not available " & address.toHex) + +proc unlockAccount*(conf: NimbusConfiguration, address: EthAddress, password: string): Result[void, string] = + var acc = conf.getAccount(address).tryGet() + let res = decodeKeyFileJson(acc.keystore, password) + if res.isOk: + acc.privateKey = res.get() + acc.unlocked = true + conf.accounts[address] = acc + result = ok() + else: + result = err($res.error) diff --git a/premix/parser.nim b/premix/parser.nim index 4c489de14..7238187ff 100644 --- a/premix/parser.nim +++ b/premix/parser.nim @@ -19,7 +19,7 @@ proc prefixHex*(x: Hash256): string = "0x" & toLowerAscii($x) proc prefixHex*(x: int64 | uint64 | byte | int): string = - encodeQuantity(x.uint64).toLowerAscii + toLowerAscii(encodeQuantity(x.uint64).string) proc prefixHex*(x: openArray[byte]): string = "0x" & toHex(x, true) diff --git a/tests/keystore/applebanana b/tests/keystore/applebanana new file mode 100644 index 000000000..916939913 --- /dev/null +++ b/tests/keystore/applebanana @@ -0,0 +1 @@ +{"password":"applebanana","address":"0e69cde81b1aa07a45c32c6cd85d67229d36bb1b","crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"7ead94c4f16f3bf31e1cdffd44227403"},"ciphertext":"f023cbdd0ebb26bea0342e2c5719da6a0499e9f675fff3d3c4beb84e1071174f","kdf":"pbkdf2","kdfparams":{"dklen":32,"c":1000000,"prf":"hmac-sha256","salt":"91edacb4f74698e6516062b230064385"},"mac":"f37290005290f56c71cce329f61945d9b2a1c719bc84e966d30c8c395477d625"},"id":"29014ab9-38c0-42fa-b5cf-f049d8568a41","version":3} \ No newline at end of file diff --git a/tests/keystore/bananamonkey b/tests/keystore/bananamonkey new file mode 100644 index 000000000..a5bba497c --- /dev/null +++ b/tests/keystore/bananamonkey @@ -0,0 +1 @@ +{"password":"bananamonkey","address":"597176e9a64aad0845d83afdaf698fbeff77703b","crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"5ad7a73aa1b46b336bd9640a8cf19436"},"ciphertext":"068c556204f86b9e0d6670e1e80f8d34dfb55416b605c0b9f9d833ba962cc64c","kdf":"pbkdf2","kdfparams":{"dklen":32,"c":1000000,"prf":"hmac-sha256","salt":"aea9c4e3dd598d41926268ed2358965e"},"mac":"30cd4c38da212a85aa277c60ee8be7ae4e9cceea75cedeae516827d339d90550"},"id":"7667da26-1e5b-4281-98a3-c9aad03e8491","version":3} \ No newline at end of file diff --git a/tests/keystore/monkeyelephant b/tests/keystore/monkeyelephant new file mode 100644 index 000000000..aa454091c --- /dev/null +++ b/tests/keystore/monkeyelephant @@ -0,0 +1 @@ +{"password":"monkeyelephant","address":"a3b2222afa5c987da6ef773fde8d01b9f23d481f","crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"3ca1d5c5151fba8281f8880ece920740"},"ciphertext":"ec8b7626f3494605e4b66a889c3dbf9bae4d8fe249718d25f010441af3cf6c61","kdf":"pbkdf2","kdfparams":{"dklen":32,"c":1000000,"prf":"hmac-sha256","salt":"364a67e8bc0a782d715832946662850c"},"mac":"7001a9fb0f63db2b7538bab2a34d2103c67035f4754a3bcf4760359f530d5c21"},"id":"5098841b-9060-4ce7-baf7-c748b12d79c9","version":3} \ No newline at end of file diff --git a/tests/rpcclient/ethcallsigs.nim b/tests/rpcclient/ethcallsigs.nim index 10469f49b..a37ae59e7 100644 --- a/tests/rpcclient/ethcallsigs.nim +++ b/tests/rpcclient/ethcallsigs.nim @@ -14,7 +14,7 @@ import ../../nimbus/rpc/hexstrings, ../../nimbus/rpc/rpc_types proc web3_clientVersion(): string -proc web3_sha3(data: string): string +proc web3_sha3(data: HexDataStr): string proc net_version(): string proc net_peerCount(): HexQuantityStr proc net_listening(): bool @@ -30,9 +30,9 @@ proc eth_getBalance(data: EthAddressStr, quantityTag: string): HexQuantityStr proc eth_getStorageAt(data: EthAddressStr, quantity: HexQuantityStr, quantityTag: string): seq[byte] proc eth_getTransactionCount(data: EthAddressStr, quantityTag: string): HexQuantityStr proc eth_getBlockTransactionCountByHash(data: Hash256): HexQuantityStr -proc eth_getBlockTransactionCountByNumber(quantityTag: string) -proc eth_getUncleCountByBlockHash(data: array[32, byte]) -proc eth_getUncleCountByBlockNumber(quantityTag: string) +proc eth_getBlockTransactionCountByNumber(quantityTag: string): HexQuantityStr +proc eth_getUncleCountByBlockHash(data: Hash256): HexQuantityStr +proc eth_getUncleCountByBlockNumber(quantityTag: string): HexQuantityStr proc eth_getCode(data: EthAddressStr, quantityTag: string): HexDataStr proc eth_sign(data:EthAddressStr, message: HexDataStr): HexDataStr #proc eth_sendRawTransaction(data: string, quantityTag: int): UInt256 diff --git a/tests/test_rpc.nim b/tests/test_rpc.nim index ecf695946..070ebcfbd 100644 --- a/tests/test_rpc.nim +++ b/tests/test_rpc.nim @@ -6,11 +6,12 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - unittest, json, strformat, strutils, options, tables, nimcrypto, stew/byteutils, + unittest, json, strformat, strutils, options, tables, os, + nimcrypto, stew/byteutils, json_rpc/[rpcserver, rpcclient], eth/common as eth_common, eth/[rlp, keys], eth/trie/db, eth/p2p/rlpx_protocols/eth_protocol, ../nimbus/rpc/[common, p2p, hexstrings, rpc_types], - ../nimbus/[constants, vm_state, config, genesis], + ../nimbus/[constants, vm_state, config, genesis, utils], ../nimbus/db/[accounts_cache, db_chain, storage_types], ../nimbus/p2p/chain, ./rpcclient/test_hexstrings, ./test_helpers @@ -29,10 +30,6 @@ template sourceDir: string = currentSourcePath.rsplit(DirSep, 1)[0] const sigPath = &"{sourceDir}{DirSep}rpcclient{DirSep}ethcallsigs.nim" createRpcSigs(RpcSocketClient, sigPath) -proc toEthAddressStr(address: EthAddress): EthAddressStr = - result = ("0x" & address.toHex).ethAddressStr - - proc doTests {.async.} = # TODO: Include other transports such as Http var ethNode = setupEthNode(eth) @@ -47,7 +44,25 @@ proc doTests {.async.} = let balance = 100.u256 address: EthAddress = hexToByteArray[20]("0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6") + + signer: EthAddress = hexToByteArray[20]("0x0e69cde81b1aa07a45c32c6cd85d67229d36bb1b") + ks2: EthAddress = hexToByteArray[20]("0xa3b2222afa5c987da6ef773fde8d01b9f23d481f") + ks3: EthAddress = hexToByteArray[20]("0x597176e9a64aad0845d83afdaf698fbeff77703b") + conf = getConfiguration() + + conf.keyStore = "tests" / "keystore" + let res = conf.loadKeystoreFiles() + if res.isErr: + debugEcho res.error + doAssert(res.isOk) + + let acc1 = conf.getAccount(signer).tryGet() + let unlock = conf.unlockAccount(signer, acc1.keystore["password"].getStr()) + if unlock.isErr: + debugEcho unlock.error + doAssert(unlock.isOk) + defaultGenesisBlockForNetwork(conf.net.networkId.toPublicNetwork()).commit(chain) state.mutateStateDB: db.setBalance(address, balance) @@ -73,10 +88,10 @@ proc doTests {.async.} = test "web3_sha3": expect ValueError: - discard await client.web3_sha3(NimbusName) + discard await client.web3_sha3(NimbusName.HexDataStr) let data = "0x" & byteutils.toHex(NimbusName.toOpenArrayByte(0, NimbusName.len-1)) - let res = await client.web3_sha3(data) + let res = await client.web3_sha3(data.hexDataStr) let rawdata = nimcrypto.fromHex(data[2 .. ^1]) let hash = "0x" & $keccak_256.digest(rawdata) check hash == res @@ -135,8 +150,9 @@ proc doTests {.async.} = test "eth_accounts": let res = await client.eth_accounts() - # we do not own any accounts, yet - check res.len == 0 + check signer.ethAddressStr in res + check ks2.ethAddressStr in res + check ks3.ethAddressStr in res test "eth_blockNumber": let res = await client.eth_blockNumber() @@ -163,6 +179,41 @@ proc doTests {.async.} = let res = await client.eth_getBlockTransactionCountByHash(hash) check res.string == "0x0" + test "eth_getBlockTransactionCountByNumber": + let res = await client.eth_getBlockTransactionCountByNumber("0x0") + check res.string == "0x0" + + test "eth_getUncleCountByBlockHash": + let hash = chain.getBlockHash(0.toBlockNumber) + let res = await client.eth_getUncleCountByBlockHash(hash) + check res.string == "0x0" + + test "eth_getUncleCountByBlockNumber": + let res = await client.eth_getUncleCountByBlockNumber("0x0") + check res.string == "0x0" + + test "eth_getCode": + let res = await client.eth_getCode(ethAddressStr("0xfff7ac99c8e4feb60c9750054bdc14ce1857f181"), "0x0") + check res.string == "0x" + + test "eth_sign": + let msg = "hello world" + let msgHex = hexDataStr(msg.toOpenArrayByte(0, msg.len-1)) + + expect ValueError: + discard await client.eth_sign(ethAddressStr(ks2), msgHex) + + let res = await client.eth_sign(ethAddressStr(signer), msgHex) + let sig = Signature.fromHex(res.string).tryGet() + + # now let us try to verify signature + let msgData = "\x19Ethereum Signed Message:\n" & $msg.len & msg + let msgDataHex = hexDataStr(msgData.toOpenArrayByte(0, msgData.len-1)) + let sha3Data = await client.web3_sha3(msgDataHex) + let msgHash = hexToByteArray[32](sha3Data) + let pubkey = recover(sig, SkMessage(msgHash)).tryGet() + let recoveredAddr = pubkey.toCanonicalAddress() + check recoveredAddr == signer # verified #test "eth_call": # let