Discv5 readme (#278)

* Add discovery v5 readme + test_discv5 task

* Move hkdf tests to test file and add to nimble task
This commit is contained in:
Kim De Mey 2020-07-12 23:53:27 +02:00 committed by GitHub
parent 0888667ac0
commit f3de959261
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 285 additions and 153 deletions

View File

@ -16,6 +16,7 @@ Ethereum-related utilities written in Nim. Includes things like Bloom filters, p
- [keyfile](doc/keyfile.md)
- [trie](doc/trie.md)
- [bloom](doc/bloom.md)
- [discv5](doc/discv5.md)
## Prerequisites

111
doc/discv5.md Normal file
View File

@ -0,0 +1,111 @@
# Discovery v5
## Introduction
This `eth/p2p/discoveryv5` directory holds a Nim implementation of the
discovery v5 protocol specified
[here](https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md).
This specification is still in DRAFT and thus subject to change. In fact, it is
likely that the current packet format will change, see
https://github.com/ethereum/devp2p/issues/152.
This implementation does not support "Topic Advertisement" yet.
The implementation relies on other modules in the `eth` package, namely: `keys`,
`rlp`, `async_utils` and `trie/db`. The latter is likely to change, see
https://github.com/status-im/nim-eth/issues/242
## How to use
```Nim
let
rng = keys.newRng
privKey = PrivateKey.random(rng[])
(ip, tcpPort, udpPort) = setupNat(config) # Or fill in external IP/ports manually
ddb = DiscoveryDB.init(newMemoryDB())
d = newProtocol(privKey, ddb, ip, tcpPort, udpPort, rng = rng)
d.open() # Start listening
```
This will initialize the `Protocol` and start listening. However, as no
bootstrap nodes were passed in the `newProtocol` call, the created ENR will need
to be advertised somehow ("out of band"), so that the node can become known to
other nodes in the network.
To initialize with a bootnode or a set of bootnodes, the ENRs need to be passed
as parameter in `newProtocol`.
```Nim
d = newProtocol(privKey, ddb, ip, tcpPort, udpPort,
bootstrapRecords = bootnodes)
d.open() # Start listening and add bootstrap nodes to the routing table.
```
Now there are two ways to run the protocol.
One can call `d.start()` and two loops will be started:
1. Random lookup loop
2. Revalidation loop
The first loop will at specific interval do a lookup with a random `NodeId`.
This lookup will add discovered nodes to the routing table.
The second loop will at random ranged interval send a ping to the least recently
seen node in a random bucket. This is to keep the routing table cleared of
unreachable/dead nodes.
Or, one can decide to do this manually within its application by using the
available calls:
- `lookupRandom` and `lookup` for discovering more peers.
- `revalidateNode` or directly `ping` for revalidating nodes.
In the future, the random lookup loop might be altered to only run in case no
lookup was done in the last x minutes. This way `d.start()` could still be run
while maintaining control over the lookups.
Of course, in either scenario, lookups can still be done for actually finding a
specific node. There is a `resolve` call that can help with this, it will first
look in the local routing table and if it finds the node it will try to contact
the node directly to check if the ENR is up to date. If any of this fail a
`lookup` will be done.
## Test suite
To run the test suite specifically for discovery v5 related (discovery v5 + its
nim-eth dependencies) tests, one can run following command:
```sh
# Install required modules
nimble install
# Run only discovery v5 related test suite
nimble tests_discv5
```
## dcli
This is a small command line application that allows you to run a discovery
node. It also has the options to do a `ping` or `findNode` request to a specific
node, by providing its ENR.
### Example usage
```sh
# Build dcli
nim c -d:chronicles_log_level:trace -d:release eth/p2p/discoveryv5/dcli
# See all options
./eth/p2p/discoveryv5/dcli --help
# Ping another node
./eth/p2p/discoveryv5/dcli ping enr:<base64 encoding of ENR>
# Run discovery node
./eth/p2p/discoveryv5/dcli --log-level:debug --bootnode:enr:<base64 encoding of ENR>
```
### Metrics
Metrics are available for `routing_table_nodes`, which holds the amount of nodes
in the routing table.
To compile in an HTTP endpoint for accessing the metrics add the `insecure`
compile time flag:
```sh
# Build dcli with metrics
nim c -d:insecure -d:chronicles_log_level:trace -d:release eth/p2p/discoveryv5/dcli
# Run dcli with metrics
./eth/p2p/discoveryv5/dcli --metrics --bootnode:enr:<base64 encoding of ENR>
```
You can now see the metrics at http://localhost:8008/metrics. Or use e.g.
Prometheus to grab the data.

View File

@ -49,6 +49,7 @@ proc runP2pTests() =
"test_shh_connect",
"test_protocol_handlers",
"test_enr",
"test_hkdf",
"test_discoveryv5",
"test_discv5_encoding",
"test_routing_table"
@ -88,3 +89,19 @@ task test, "run tests":
runRlpTests()
runTrieTests()
runDbTests()
proc runDiscv5Tests() =
for filename in [
"test_enr",
"test_hkdf",
"test_discoveryv5",
"test_discv5_encoding",
"test_routing_table"
]:
runTest("tests/p2p/" & filename)
task test_discv5, "run tests of discovery v5 and its dependencies":
runKeysTests()
runRlpTests()
runTrieTests() # This probably tests a bit to much for what we use it for.
runDiscv5Tests()

View File

@ -28,156 +28,3 @@ proc hkdf*(HashType: typedesc, ikm, salt, info: openarray[byte],
copyMem(addr output[iStart], addr t.data, sz)
ctx.clear()
when isMainModule:
import stew/byteutils
proc hextToBytes(s: string): seq[byte] =
if s.len != 0: return hexToSeqByte(s)
template test(constants: untyped) =
block:
constants
let
bikm = hextToBytes(IKM)
bsalt = hextToBytes(salt)
binfo = hextToBytes(info)
bprk {.used.} = hextToBytes(PRK)
bokm = hextToBytes(OKM)
var output = newSeq[byte](L)
hkdf(HashType, bikm, bsalt, binfo, output)
doAssert(output == bokm)
test: # 1
type HashType = sha256
const
IKM = "0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"
salt = "0x000102030405060708090a0b0c"
info = "0xf0f1f2f3f4f5f6f7f8f9"
L = 42
PRK = "0x077709362c2e32df0ddc3f0dc47bba63" &
"90b6c73bb50f9c3122ec844ad7c2b3e5"
OKM = "0x3cb25f25faacd57a90434f64d0362f2a" &
"2d2d0a90cf1a5a4c5db02d56ecc4c5bf" &
"34007208d5b887185865"
test: # 2
type HashType = sha256
const
IKM = "0x000102030405060708090a0b0c0d0e0f" &
"101112131415161718191a1b1c1d1e1f" &
"202122232425262728292a2b2c2d2e2f" &
"303132333435363738393a3b3c3d3e3f" &
"404142434445464748494a4b4c4d4e4f"
salt = "0x606162636465666768696a6b6c6d6e6f" &
"707172737475767778797a7b7c7d7e7f" &
"808182838485868788898a8b8c8d8e8f" &
"909192939495969798999a9b9c9d9e9f" &
"a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
info = "0xb0b1b2b3b4b5b6b7b8b9babbbcbdbebf" &
"c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" &
"d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" &
"e0e1e2e3e4e5e6e7e8e9eaebecedeeef" &
"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
L = 82
PRK = "0x06a6b88c5853361a06104c9ceb35b45c" &
"ef760014904671014a193f40c15fc244"
OKM = "0xb11e398dc80327a1c8e7f78c596a4934" &
"4f012eda2d4efad8a050cc4c19afa97c" &
"59045a99cac7827271cb41c65e590e09" &
"da3275600c2f09b8367793a9aca3db71" &
"cc30c58179ec3e87c14c01d5c1f3434f" &
"1d87"
test: # 3
type HashType = sha256
const
IKM = "0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"
salt = ""
info = ""
L = 42
PRK = "0x19ef24a32c717b167f33a91d6f648bdf" &
"96596776afdb6377ac434c1c293ccb04"
OKM = "0x8da4e775a563c18f715f802a063c5a31" &
"b8a11f5c5ee1879ec3454e5f3c738d2d" &
"9d201395faa4b61a96c8"
test: # 4
type HashType = sha1
const
IKM = "0x0b0b0b0b0b0b0b0b0b0b0b"
salt = "0x000102030405060708090a0b0c"
info = "0xf0f1f2f3f4f5f6f7f8f9"
L = 42
PRK = "0x9b6c18c432a7bf8f0e71c8eb88f4b30baa2ba243"
OKM = "0x085a01ea1b10f36933068b56efa5ad81" &
"a4f14b822f5b091568a9cdd4f155fda2" &
"c22e422478d305f3f896"
test: # 5
type HashType = sha1
const
IKM = "0x000102030405060708090a0b0c0d0e0f" &
"101112131415161718191a1b1c1d1e1f" &
"202122232425262728292a2b2c2d2e2f" &
"303132333435363738393a3b3c3d3e3f" &
"404142434445464748494a4b4c4d4e4f"
salt = "0x606162636465666768696a6b6c6d6e6f" &
"707172737475767778797a7b7c7d7e7f" &
"808182838485868788898a8b8c8d8e8f" &
"909192939495969798999a9b9c9d9e9f" &
"a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
info = "0xb0b1b2b3b4b5b6b7b8b9babbbcbdbebf" &
"c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" &
"d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" &
"e0e1e2e3e4e5e6e7e8e9eaebecedeeef" &
"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
L = 82
PRK = "0x8adae09a2a307059478d309b26c4115a224cfaf6"
OKM = "0x0bd770a74d1160f7c9f12cd5912a06eb" &
"ff6adcae899d92191fe4305673ba2ffe" &
"8fa3f1a4e5ad79f3f334b3b202b2173c" &
"486ea37ce3d397ed034c7f9dfeb15c5e" &
"927336d0441f4c4300e2cff0d0900b52" &
"d3b4"
test: # 6
type HashType = sha1
const
IKM = "0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"
salt = ""
info = ""
L = 42
PRK = "0xda8c8a73c7fa77288ec6f5e7c297786aa0d32d01"
OKM = "0x0ac1af7002b3d761d1e55298da9d0506" &
"b9ae52057220a306e07b6b87e8df21d0" &
"ea00033de03984d34918"
test: # 7
type HashType = sha1
const
IKM = "0x0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c"
salt = ""
info = ""
L = 42
PRK = "0x2adccada18779e7c2077ad2eb19d3f3e731385dd"
OKM = "0x2c91117204d745f3500d636a62f64f0a" &
"b3bae548aa53d423b0d1f27ebba6f5e5" &
"673a081d70cce7acfc48"
block:
var output: array[5, byte]
var secret = [0x01.byte, 0x02, 0x03]
var salt = [0x04.byte, 0x05, 0x06]
var info = [0x07.byte, 0x08, 0x09]
hkdf(sha256, secret, salt, info, output)
doAssert(@output == "D5CF839F63".hextToBytes)

156
tests/p2p/test_hkdf.nim Normal file
View File

@ -0,0 +1,156 @@
import
std/unittest,
nimcrypto, stew/byteutils,
eth/p2p/discoveryv5/hkdf
proc hextToBytes(s: string): seq[byte] =
if s.len != 0: return hexToSeqByte(s)
template runTest(constants: untyped) =
block:
constants
let
bikm = hextToBytes(IKM)
bsalt = hextToBytes(salt)
binfo = hextToBytes(info)
bprk {.used.} = hextToBytes(PRK)
bokm = hextToBytes(OKM)
var output = newSeq[byte](L)
hkdf(HashType, bikm, bsalt, binfo, output)
check(output == bokm)
suite "hkdf":
test "Test Vectors":
runTest: # 1
type HashType = sha256
const
IKM = "0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"
salt = "0x000102030405060708090a0b0c"
info = "0xf0f1f2f3f4f5f6f7f8f9"
L = 42
PRK = "0x077709362c2e32df0ddc3f0dc47bba63" &
"90b6c73bb50f9c3122ec844ad7c2b3e5"
OKM = "0x3cb25f25faacd57a90434f64d0362f2a" &
"2d2d0a90cf1a5a4c5db02d56ecc4c5bf" &
"34007208d5b887185865"
runTest: # 2
type HashType = sha256
const
IKM = "0x000102030405060708090a0b0c0d0e0f" &
"101112131415161718191a1b1c1d1e1f" &
"202122232425262728292a2b2c2d2e2f" &
"303132333435363738393a3b3c3d3e3f" &
"404142434445464748494a4b4c4d4e4f"
salt = "0x606162636465666768696a6b6c6d6e6f" &
"707172737475767778797a7b7c7d7e7f" &
"808182838485868788898a8b8c8d8e8f" &
"909192939495969798999a9b9c9d9e9f" &
"a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
info = "0xb0b1b2b3b4b5b6b7b8b9babbbcbdbebf" &
"c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" &
"d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" &
"e0e1e2e3e4e5e6e7e8e9eaebecedeeef" &
"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
L = 82
PRK = "0x06a6b88c5853361a06104c9ceb35b45c" &
"ef760014904671014a193f40c15fc244"
OKM = "0xb11e398dc80327a1c8e7f78c596a4934" &
"4f012eda2d4efad8a050cc4c19afa97c" &
"59045a99cac7827271cb41c65e590e09" &
"da3275600c2f09b8367793a9aca3db71" &
"cc30c58179ec3e87c14c01d5c1f3434f" &
"1d87"
runTest: # 3
type HashType = sha256
const
IKM = "0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"
salt = ""
info = ""
L = 42
PRK = "0x19ef24a32c717b167f33a91d6f648bdf" &
"96596776afdb6377ac434c1c293ccb04"
OKM = "0x8da4e775a563c18f715f802a063c5a31" &
"b8a11f5c5ee1879ec3454e5f3c738d2d" &
"9d201395faa4b61a96c8"
runTest: # 4
type HashType = sha1
const
IKM = "0x0b0b0b0b0b0b0b0b0b0b0b"
salt = "0x000102030405060708090a0b0c"
info = "0xf0f1f2f3f4f5f6f7f8f9"
L = 42
PRK = "0x9b6c18c432a7bf8f0e71c8eb88f4b30baa2ba243"
OKM = "0x085a01ea1b10f36933068b56efa5ad81" &
"a4f14b822f5b091568a9cdd4f155fda2" &
"c22e422478d305f3f896"
runTest: # 5
type HashType = sha1
const
IKM = "0x000102030405060708090a0b0c0d0e0f" &
"101112131415161718191a1b1c1d1e1f" &
"202122232425262728292a2b2c2d2e2f" &
"303132333435363738393a3b3c3d3e3f" &
"404142434445464748494a4b4c4d4e4f"
salt = "0x606162636465666768696a6b6c6d6e6f" &
"707172737475767778797a7b7c7d7e7f" &
"808182838485868788898a8b8c8d8e8f" &
"909192939495969798999a9b9c9d9e9f" &
"a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
info = "0xb0b1b2b3b4b5b6b7b8b9babbbcbdbebf" &
"c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" &
"d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" &
"e0e1e2e3e4e5e6e7e8e9eaebecedeeef" &
"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
L = 82
PRK = "0x8adae09a2a307059478d309b26c4115a224cfaf6"
OKM = "0x0bd770a74d1160f7c9f12cd5912a06eb" &
"ff6adcae899d92191fe4305673ba2ffe" &
"8fa3f1a4e5ad79f3f334b3b202b2173c" &
"486ea37ce3d397ed034c7f9dfeb15c5e" &
"927336d0441f4c4300e2cff0d0900b52" &
"d3b4"
runTest: # 6
type HashType = sha1
const
IKM = "0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"
salt = ""
info = ""
L = 42
PRK = "0xda8c8a73c7fa77288ec6f5e7c297786aa0d32d01"
OKM = "0x0ac1af7002b3d761d1e55298da9d0506" &
"b9ae52057220a306e07b6b87e8df21d0" &
"ea00033de03984d34918"
runTest: # 7
type HashType = sha1
const
IKM = "0x0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c"
salt = ""
info = ""
L = 42
PRK = "0x2adccada18779e7c2077ad2eb19d3f3e731385dd"
OKM = "0x2c91117204d745f3500d636a62f64f0a" &
"b3bae548aa53d423b0d1f27ebba6f5e5" &
"673a081d70cce7acfc48"
block:
var output: array[5, byte]
var secret = [0x01.byte, 0x02, 0x03]
var salt = [0x04.byte, 0x05, 0x06]
var info = [0x07.byte, 0x08, 0x09]
hkdf(sha256, secret, salt, info, output)
check(@output == "D5CF839F63".hextToBytes)