diff --git a/ethp2p.nim b/ethp2p.nim index 36cba26..9acb1d6 100644 --- a/ethp2p.nim +++ b/ethp2p.nim @@ -3,9 +3,10 @@ # (c) Copyright 2018 # Status Research & Development GmbH # -# See the file "LICENSE", included in this -# distribution, for details about the copyright. +# Licensed under either of +# Apache License, version 2.0, (LICENSE-APACHEv2) +# MIT license (LICENSE-MIT) # -import ethp2p/ecc, ethp2p/ecies, ethp2p/auth, ethp2p/hexdump -export ecc, ecies, auth, hexdump +import ethp2p/ecies, ethp2p/auth, ethp2p/hexdump, ethp2p/enode +export ecies, auth, enode, hexdump diff --git a/ethp2p.nimble b/ethp2p.nimble index 48f2426..e41bec7 100644 --- a/ethp2p.nimble +++ b/ethp2p.nimble @@ -8,13 +8,13 @@ license = "MIT" skipDirs = @["tests", "Nim"] requires "nim > 0.18.0", - "rlp >= 1.0.1", - "nimcrypto >= 0.3.0", - "secp256k1 >= 0.1.0", - "eth_keys", - "ranges", - "ttmath", - "https://github.com/status-im/byteutils" + "rlp >= 1.0.1", + "https://github.com/cheatfate/nimcrypto", + "secp256k1 >= 0.1.0", + "eth_keys", + "ranges", + "ttmath", + "https://github.com/status-im/byteutils" proc runTest(name: string, lang = "c") = exec "nim " & lang & " -r tests/" & name @@ -22,4 +22,5 @@ task test, "Runs the test suite": runTest "testecies" runTest "testauth" runTest "testcrypt" + runTest "testenode" runTest("tdiscovery", "cpp") diff --git a/ethp2p/auth.nim b/ethp2p/auth.nim index 66036fe..5b4fe3f 100644 --- a/ethp2p/auth.nim +++ b/ethp2p/auth.nim @@ -3,8 +3,9 @@ # (c) Copyright 2018 # Status Research & Development GmbH # -# See the file "LICENSE", included in this -# distribution, for details about the copyright. +# Licensed under either of +# Apache License, version 2.0, (LICENSE-APACHEv2) +# MIT license (LICENSE-MIT) # ## This module implements Ethereum authentication diff --git a/ethp2p/ecies.nim b/ethp2p/ecies.nim index 73456aa..6346ad2 100644 --- a/ethp2p/ecies.nim +++ b/ethp2p/ecies.nim @@ -3,8 +3,9 @@ # (c) Copyright 2018 # Status Research & Development GmbH # -# See the file "LICENSE", included in this -# distribution, for details about the copyright. +# Licensed under either of +# Apache License, version 2.0, (LICENSE-APACHEv2) +# MIT license (LICENSE-MIT) # ## This module implements ECIES method encryption/decryption. diff --git a/ethp2p/enode.nim b/ethp2p/enode.nim new file mode 100644 index 0000000..a57975c --- /dev/null +++ b/ethp2p/enode.nim @@ -0,0 +1,161 @@ +# +# Ethereum P2P +# (c) Copyright 2018 +# Status Research & Development GmbH +# +# Licensed under either of +# Apache License, version 2.0, (LICENSE-APACHEv2) +# MIT license (LICENSE-MIT) +# + +import uri, eth_keys, strutils, net + +type + ENodeStatus* = enum + ## ENode status codes + Success, ## Conversion operation succeed + IncorrectNodeId, ## Incorrect public key supplied + IncorrectScheme, ## Incorrect URI scheme supplied + IncorrectIP, ## Incorrect IP address supplied + IncorrectPort, ## Incorrect TCP port supplied + IncorrectDiscPort, ## Incorrect UDP discovery port supplied + IncorrectUri, ## Incorrect URI supplied + IncompleteENode ## Incomplete ENODE object + + Address* = object + ## Network address object + ip*: IpAddress ## IPv4/IPv6 address + udpPort*: Port ## UDP discovery port number + tcpPort*: Port ## TCP port number + + ENode* = object + ## ENode object + pubkey*: PublicKey ## Node public key + address*: Address ## Node address + + ENodeException* = object of Exception + +proc raiseENodeError(status: ENodeStatus) = + if status == IncorrectIP: + raise newException(ENodeException, "Incorrect IP address") + elif status == IncorrectPort: + raise newException(ENodeException, "Incorrect port number") + elif status == IncorrectDiscPort: + raise newException(ENodeException, "Incorrect discovery port number") + elif status == IncorrectUri: + raise newException(ENodeException, "Incorrect URI") + elif status == IncorrectScheme: + raise newException(ENodeException, "Incorrect scheme") + elif status == IncorrectNodeId: + raise newException(ENodeException, "Incorrect node id") + elif status == IncompleteENode: + raise newException(ENodeException, "Incomplete enode") + +proc initENode*(e: string, node: var ENode): ENodeStatus = + ## Initialize ENode ``node`` from URI string ``uri``. + var + uport: int = 0 + tport: int = 0 + uri: Uri = initUri() + data: string + + if len(e) == 0: + return IncorrectUri + + parseUri(e, uri) + + if len(uri.scheme) == 0 or uri.scheme.toLowerAscii() != "enode": + return IncorrectScheme + + if len(uri.username) != 128: + return IncorrectNodeId + + for i in uri.username: + if i notin {'A'..'F', 'a'..'f', '0'..'9'}: + return IncorrectNodeId + + if len(uri.password) != 0 or len(uri.path) != 0 or len(uri.anchor) != 0: + return IncorrectUri + + if len(uri.hostname) == 0: + return IncorrectIP + + try: + if len(uri.port) == 0: + return IncorrectPort + tport = parseInt(uri.port) + if tport <= 0 or tport > 65535: + return IncorrectPort + except: + return IncorrectPort + + if len(uri.query) > 0: + if not uri.query.toLowerAscii().startsWith("discport="): + return IncorrectDiscPort + try: + uport = parseInt(uri.query[9..^1]) + if uport <= 0 or uport > 65535: + return IncorrectDiscPort + except: + return IncorrectDiscPort + else: + uport = tport + + try: + data = parseHexStr(uri.username) + if recoverPublicKey(cast[seq[byte]](data), + node.pubkey) != EthKeysStatus.Success: + return IncorrectNodeId + except: + return IncorrectNodeId + + try: + node.address.ip = parseIpAddress(uri.hostname) + except: + zeroMem(addr node.pubkey, KeyLength * 2) + return IncorrectIP + + node.address.tcpPort = Port(tport) + node.address.udpPort = Port(uport) + result = Success + +proc initENode*(uri: string): ENode {.inline.} = + ## Returns ENode object from URI string ``uri``. + let res = initENode(uri, result) + if res != Success: + raiseENodeError(res) + +proc isCorrect*(n: ENode): bool = + ## Returns ``true`` if ENode ``n`` is properly filled. + if n.address.ip.family notin {IpAddressFamily.IPv4, IpAddressFamily.IPv6}: + return false + if n.address.tcpPort == Port(0): + return false + if n.address.udpPort == Port(0): + return false + result = false + for i in n.pubkey.data: + if i != 0x00'u8: + result = true + break + +proc `$`*(n: ENode): string = + ## Returns string representation of ENode. + var ipaddr: string + if not isCorrect(n): + raiseENodeError(IncompleteENode) + if n.address.ip.family == IpAddressFamily.IPv4: + ipaddr = $(n.address.ip) + else: + ipaddr = "[" & $(n.address.ip) & "]" + result = newString(0) + result.add("enode://") + result.add($n.pubkey) + result.add("@") + result.add(ipaddr) + result.add(":") + result.add($int(n.address.tcpPort)) + if uint16(n.address.udpPort) != uint16(n.address.tcpPort): + result.add("?") + result.add("discport=") + result.add($int(n.address.udpPort)) diff --git a/ethp2p/rlpxcrypt.nim b/ethp2p/rlpxcrypt.nim index 94de685..03baae4 100644 --- a/ethp2p/rlpxcrypt.nim +++ b/ethp2p/rlpxcrypt.nim @@ -3,8 +3,9 @@ # (c) Copyright 2018 # Status Research & Development GmbH # -# See the file "LICENSE", included in this -# distribution, for details about the copyright. +# Licensed under either of +# Apache License, version 2.0, (LICENSE-APACHEv2) +# MIT license (LICENSE-MIT) # ## This module implements RLPx cryptography diff --git a/tests/testenode.nim b/tests/testenode.nim new file mode 100644 index 0000000..e6db0e3 --- /dev/null +++ b/tests/testenode.nim @@ -0,0 +1,99 @@ +# +# Ethereum P2P +# (c) Copyright 2018 +# Status Research & Development GmbH +# +# Licensed under either of +# Apache License, version 2.0, (LICENSE-APACHEv2) +# MIT license (LICENSE-MIT) + +import unittest, net, ethp2p/enode + +suite "ENode": + test "Go-Ethereum tests": + const enodes = [ + "http://foobar", + "enode://01010101@123.124.125.126:3", + "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@hostname:3", + "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:foo", + "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:3?discport=foo", + "01010101", + "enode://01010101", + "://foo", + "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150", + "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[::]:52150", + "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[2001:db8:3c4d:15::abcd:ef12]:52150", + "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150?discport=22334", + ] + + const results = [ + IncorrectScheme, + IncorrectNodeId, + IncorrectIP, + IncorrectPort, + IncorrectDiscPort, + IncorrectScheme, + IncorrectNodeId, + IncorrectScheme, + ENodeStatus.Success, + ENodeStatus.Success, + ENodeStatus.Success, + ENodeStatus.Success + ] + + for index in 0..