mirror of https://github.com/status-im/nim-eth.git
More spec compliant ENR
* Don't use signed integers in RLP * Don't store IP addresses as var-sized ints (use fixed-sized blobs instead) * Allow constructing ENR from ENode.Address
This commit is contained in:
parent
b89874f6cc
commit
a1a6862c7c
|
@ -81,7 +81,7 @@ proc makeAuthHeader(c: Codec, toNode: Node, nonce: array[gcmNonceSize, byte],
|
||||||
var resp = AuthResponse(version: 5)
|
var resp = AuthResponse(version: 5)
|
||||||
let ln = c.localNode
|
let ln = c.localNode
|
||||||
|
|
||||||
if challenge.recordSeq < ln.record.sequenceNumber:
|
if challenge.recordSeq < ln.record.seqNum:
|
||||||
resp.record = ln.record
|
resp.record = ln.record
|
||||||
|
|
||||||
var remotePubkey: PublicKey
|
var remotePubkey: PublicKey
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
# ENR implemetation according to spec:
|
# ENR implemetation according to spec:
|
||||||
# https://github.com/ethereum/EIPs/blob/master/EIPS/eip-778.md
|
# https://github.com/ethereum/EIPs/blob/master/EIPS/eip-778.md
|
||||||
|
|
||||||
import strutils, macros, algorithm, options
|
import
|
||||||
import eth/[rlp, keys], nimcrypto, stew/base64
|
net, strutils, macros, algorithm, options,
|
||||||
|
nimcrypto, stew/base64,
|
||||||
|
eth/[rlp, keys], ../enode
|
||||||
|
|
||||||
const
|
const
|
||||||
maxEnrSize = 300
|
maxEnrSize = 300
|
||||||
|
@ -10,7 +12,7 @@ const
|
||||||
|
|
||||||
type
|
type
|
||||||
Record* = object
|
Record* = object
|
||||||
sequenceNumber*: uint64
|
seqNum*: uint64
|
||||||
# signature: seq[byte]
|
# signature: seq[byte]
|
||||||
raw*: seq[byte] # RLP encoded record
|
raw*: seq[byte] # RLP encoded record
|
||||||
pairs: seq[(string, Field)] # sorted list of all key/value pairs
|
pairs: seq[(string, Field)] # sorted list of all key/value pairs
|
||||||
|
@ -37,23 +39,25 @@ type
|
||||||
of kString:
|
of kString:
|
||||||
str: string
|
str: string
|
||||||
of kNum:
|
of kNum:
|
||||||
num: int
|
num: BiggestUInt
|
||||||
of kBytes:
|
of kBytes:
|
||||||
bytes: seq[byte]
|
bytes: seq[byte]
|
||||||
|
|
||||||
template toField[T](v: T): Field =
|
template toField[T](v: T): Field =
|
||||||
when T is string:
|
when T is string:
|
||||||
Field(kind: kString, str: v)
|
Field(kind: kString, str: v)
|
||||||
|
elif T is array:
|
||||||
|
Field(kind: kBytes, bytes: @v)
|
||||||
elif T is seq[byte]:
|
elif T is seq[byte]:
|
||||||
Field(kind: kBytes, bytes: v)
|
Field(kind: kBytes, bytes: v)
|
||||||
elif T is SomeInteger:
|
elif T is SomeUnsignedInt:
|
||||||
Field(kind: kNum, num: v.int)
|
Field(kind: kNum, num: BiggestUInt(v))
|
||||||
else:
|
else:
|
||||||
{.error: "Unsupported field type".}
|
{.error: "Unsupported field type".}
|
||||||
|
|
||||||
proc makeEnrAux(sequenceNumber: uint64, pk: PrivateKey, pairs: openarray[(string, Field)]): Record =
|
proc makeEnrAux(seqNum: uint64, pk: PrivateKey, pairs: openarray[(string, Field)]): Record =
|
||||||
result.pairs = @pairs
|
result.pairs = @pairs
|
||||||
result.sequenceNumber = sequenceNumber
|
result.seqNum = seqNum
|
||||||
|
|
||||||
let pubkey = pk.getPublicKey()
|
let pubkey = pk.getPublicKey()
|
||||||
|
|
||||||
|
@ -64,8 +68,8 @@ proc makeEnrAux(sequenceNumber: uint64, pk: PrivateKey, pairs: openarray[(string
|
||||||
result.pairs.sort() do(a, b: (string, Field)) -> int:
|
result.pairs.sort() do(a, b: (string, Field)) -> int:
|
||||||
cmp(a[0], b[0])
|
cmp(a[0], b[0])
|
||||||
|
|
||||||
proc append(w: var RlpWriter, sequenceNumber: uint64, pairs: openarray[(string, Field)]): seq[byte] =
|
proc append(w: var RlpWriter, seqNum: uint64, pairs: openarray[(string, Field)]): seq[byte] =
|
||||||
w.append(sequenceNumber)
|
w.append(seqNum)
|
||||||
for (k, v) in pairs:
|
for (k, v) in pairs:
|
||||||
w.append(k)
|
w.append(k)
|
||||||
case v.kind
|
case v.kind
|
||||||
|
@ -76,7 +80,7 @@ proc makeEnrAux(sequenceNumber: uint64, pk: PrivateKey, pairs: openarray[(string
|
||||||
|
|
||||||
let toSign = block:
|
let toSign = block:
|
||||||
var w = initRlpList(result.pairs.len * 2 + 1)
|
var w = initRlpList(result.pairs.len * 2 + 1)
|
||||||
w.append(sequenceNumber, result.pairs)
|
w.append(seqNum, result.pairs)
|
||||||
|
|
||||||
var sig: SignatureNR
|
var sig: SignatureNR
|
||||||
if signRawMessage(keccak256.digest(toSign).data, pk, sig) != EthKeysStatus.Success:
|
if signRawMessage(keccak256.digest(toSign).data, pk, sig) != EthKeysStatus.Success:
|
||||||
|
@ -85,15 +89,27 @@ proc makeEnrAux(sequenceNumber: uint64, pk: PrivateKey, pairs: openarray[(string
|
||||||
result.raw = block:
|
result.raw = block:
|
||||||
var w = initRlpList(result.pairs.len * 2 + 2)
|
var w = initRlpList(result.pairs.len * 2 + 2)
|
||||||
w.append(sig.getRaw())
|
w.append(sig.getRaw())
|
||||||
w.append(sequenceNumber, result.pairs)
|
w.append(seqNum, result.pairs)
|
||||||
|
|
||||||
macro initRecord*(sequenceNumber: uint64, pk: PrivateKey, pairs: untyped{nkTableConstr}): untyped =
|
macro initRecord*(seqNum: uint64, pk: PrivateKey, pairs: untyped{nkTableConstr}): untyped =
|
||||||
for c in pairs:
|
for c in pairs:
|
||||||
c.expectKind(nnkExprColonExpr)
|
c.expectKind(nnkExprColonExpr)
|
||||||
c[1] = newCall(bindSym"toField", c[1])
|
c[1] = newCall(bindSym"toField", c[1])
|
||||||
|
|
||||||
result = quote do:
|
result = quote do:
|
||||||
makeEnrAux(`sequenceNumber`, `pk`, `pairs`)
|
makeEnrAux(`seqNum`, `pk`, `pairs`)
|
||||||
|
|
||||||
|
proc init*(T: type Record, seqNum: uint64,
|
||||||
|
pk: PrivateKey,
|
||||||
|
address: enode.Address): T =
|
||||||
|
let
|
||||||
|
isV6 = address.ip.family == IPv6
|
||||||
|
ipField = if isV6: ("ip6", address.ip.address_v6.toField)
|
||||||
|
else: ("ip", address.ip.address_v4.toField)
|
||||||
|
tcpField = ((if isV6: "tcp6" else: "tcp"), address.udpPort.uint16.toField)
|
||||||
|
udpField = ((if isV6: "udp6" else: "udp"), address.tcpPort.uint16.toField)
|
||||||
|
|
||||||
|
makeEnrAux(seqNum, pk, [ipField, tcpField, udpField])
|
||||||
|
|
||||||
proc getField(r: Record, name: string, field: var Field): bool =
|
proc getField(r: Record, name: string, field: var Field): bool =
|
||||||
# It might be more correct to do binary search,
|
# It might be more correct to do binary search,
|
||||||
|
@ -176,7 +192,7 @@ proc verifySignature(r: Record): bool =
|
||||||
var rlp = rlpFromBytes(r.raw.toRange)
|
var rlp = rlpFromBytes(r.raw.toRange)
|
||||||
let sz = rlp.listLen
|
let sz = rlp.listLen
|
||||||
rlp.enterList()
|
rlp.enterList()
|
||||||
let sigData = rlp.read(seq[byte])
|
let sigData = rlp.read(Bytes)
|
||||||
let content = block:
|
let content = block:
|
||||||
var writer = initRlpList(sz - 1)
|
var writer = initRlpList(sz - 1)
|
||||||
var reader = rlp
|
var reader = rlp
|
||||||
|
@ -195,7 +211,8 @@ proc verifySignature(r: Record): bool =
|
||||||
discard
|
discard
|
||||||
|
|
||||||
proc fromBytesAux(r: var Record): bool =
|
proc fromBytesAux(r: var Record): bool =
|
||||||
if r.raw.len > maxEnrSize: return false
|
if r.raw.len > maxEnrSize:
|
||||||
|
return false
|
||||||
|
|
||||||
var rlp = rlpFromBytes(r.raw.toRange)
|
var rlp = rlpFromBytes(r.raw.toRange)
|
||||||
let sz = rlp.listLen
|
let sz = rlp.listLen
|
||||||
|
@ -206,7 +223,7 @@ proc fromBytesAux(r: var Record): bool =
|
||||||
rlp.enterList()
|
rlp.enterList()
|
||||||
rlp.skipElem() # Skip signature
|
rlp.skipElem() # Skip signature
|
||||||
|
|
||||||
r.sequenceNumber = rlp.read(uint64)
|
r.seqNum = rlp.read(uint64)
|
||||||
|
|
||||||
let numPairs = (sz - 2) div 2
|
let numPairs = (sz - 2) div 2
|
||||||
|
|
||||||
|
@ -219,12 +236,11 @@ proc fromBytesAux(r: var Record): bool =
|
||||||
of "secp256k1":
|
of "secp256k1":
|
||||||
let pubkeyData = rlp.read(seq[byte])
|
let pubkeyData = rlp.read(seq[byte])
|
||||||
r.pairs.add((k, Field(kind: kBytes, bytes: pubkeyData)))
|
r.pairs.add((k, Field(kind: kBytes, bytes: pubkeyData)))
|
||||||
of "tcp", "udp", "tcp6", "udp6", "ip":
|
of "tcp", "udp", "tcp6", "udp6":
|
||||||
let v = rlp.read(int)
|
let v = rlp.read(uint16)
|
||||||
r.pairs.add((k, Field(kind: kNum, num: v)))
|
r.pairs.add((k, Field(kind: kNum, num: v)))
|
||||||
else:
|
else:
|
||||||
r.pairs.add((k, Field(kind: kBytes, bytes: rlp.rawData.toSeq)))
|
r.pairs.add((k, Field(kind: kBytes, bytes: rlp.read(Bytes))))
|
||||||
rlp.skipElem
|
|
||||||
|
|
||||||
verifySignature(r)
|
verifySignature(r)
|
||||||
|
|
||||||
|
|
|
@ -297,7 +297,7 @@ proc processClient(transp: DatagramTransport,
|
||||||
proc revalidateNode(p: Protocol, n: Node) {.async.} =
|
proc revalidateNode(p: Protocol, n: Node) {.async.} =
|
||||||
let reqId = newRequestId()
|
let reqId = newRequestId()
|
||||||
var ping: PingPacket
|
var ping: PingPacket
|
||||||
ping.enrSeq = p.localNode.record.sequenceNumber
|
ping.enrSeq = p.localNode.record.seqNum
|
||||||
let (data, nonce) = p.codec.encodeEncrypted(n, encodePacket(ping, reqId), challenge = nil)
|
let (data, nonce) = p.codec.encodeEncrypted(n, encodePacket(ping, reqId), challenge = nil)
|
||||||
p.pendingRequests[nonce] = PendingRequest(node: n, packet: data)
|
p.pendingRequests[nonce] = PendingRequest(node: n, packet: data)
|
||||||
p.send(n, data)
|
p.send(n, data)
|
||||||
|
@ -305,7 +305,7 @@ proc revalidateNode(p: Protocol, n: Node) {.async.} =
|
||||||
let resp = await p.waitPacket(n, reqId)
|
let resp = await p.waitPacket(n, reqId)
|
||||||
if resp.isSome and resp.get.kind == pong:
|
if resp.isSome and resp.get.kind == pong:
|
||||||
let pong = resp.get.pong
|
let pong = resp.get.pong
|
||||||
if pong.enrSeq > n.record.sequenceNumber:
|
if pong.enrSeq > n.record.seqNum:
|
||||||
# TODO: Request new ENR
|
# TODO: Request new ENR
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import unittest
|
import
|
||||||
import eth/p2p/discoveryv5/enr, eth/keys
|
net, unittest, options,
|
||||||
|
nimcrypto/utils,
|
||||||
|
eth/p2p/enode, eth/p2p/discoveryv5/enr, eth/keys, eth/rlp
|
||||||
|
|
||||||
suite "ENR":
|
suite "ENR":
|
||||||
test "Serialization":
|
test "Serialization":
|
||||||
var pk = initPrivateKey("5d2908f3f09ea1ff2e327c3f623159639b00af406e9009de5fd4b910fc34049d")
|
var pk = initPrivateKey("5d2908f3f09ea1ff2e327c3f623159639b00af406e9009de5fd4b910fc34049d")
|
||||||
var r = initRecord(123, pk, {"udp": 1234, "ip": 12345})
|
var r = initRecord(123, pk, {"udp": 1234'u, "ip": [byte 5, 6, 7, 8]})
|
||||||
doAssert($r == """(id: "v4", ip: 12345, secp256k1: 0x02E51EFA66628CE09F689BC2B82F165A75A9DDECBB6A804BE15AC3FDF41F3B34E7, udp: 1234)""")
|
doAssert($r == """(id: "v4", ip: 0x05060708, secp256k1: 0x02E51EFA66628CE09F689BC2B82F165A75A9DDECBB6A804BE15AC3FDF41F3B34E7, udp: 1234)""")
|
||||||
let uri = r.toURI()
|
let uri = r.toURI()
|
||||||
var r2: Record
|
var r2: Record
|
||||||
let sigValid = r2.fromURI(uri)
|
let sigValid = r2.fromURI(uri)
|
||||||
|
@ -16,7 +18,7 @@ suite "ENR":
|
||||||
var r: Record
|
var r: Record
|
||||||
let sigValid = r.fromBase64("-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8")
|
let sigValid = r.fromBase64("-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8")
|
||||||
doAssert(sigValid)
|
doAssert(sigValid)
|
||||||
doAssert($r == """(id: "v4", ip: 2130706433, secp256k1: 0x03CA634CAE0D49ACB401D8A4C6B6FE8C55B70D115BF400769CC1400F3258CD3138, udp: 30303)""")
|
doAssert($r == """(id: "v4", ip: 0x7F000001, secp256k1: 0x03CA634CAE0D49ACB401D8A4C6B6FE8C55B70D115BF400769CC1400F3258CD3138, udp: 30303)""")
|
||||||
|
|
||||||
test "Bad base64":
|
test "Bad base64":
|
||||||
var r: Record
|
var r: Record
|
||||||
|
@ -27,3 +29,25 @@ suite "ENR":
|
||||||
var r: Record
|
var r: Record
|
||||||
let sigValid = r.fromBase64("-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOOnrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8")
|
let sigValid = r.fromBase64("-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOOnrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8")
|
||||||
doAssert(not sigValid)
|
doAssert(not sigValid)
|
||||||
|
|
||||||
|
test "Create from ENode address":
|
||||||
|
let
|
||||||
|
keys = newKeyPair()
|
||||||
|
ip = parseIpAddress("10.20.30.40")
|
||||||
|
enodeAddress = Address(ip: ip, tcpPort: Port 9000, udpPort: Port 9000)
|
||||||
|
enr = Record.init(100, keys.seckey, enodeAddress)
|
||||||
|
typedEnr = get enr.toTypedRecord
|
||||||
|
|
||||||
|
check:
|
||||||
|
typedEnr.secp256k1.isSome
|
||||||
|
typedEnr.secp256k1.get == keys.pubkey.getRawCompressed
|
||||||
|
|
||||||
|
typedEnr.ip.isSome
|
||||||
|
typedEnr.ip.get == [byte 10, 20, 30, 40]
|
||||||
|
|
||||||
|
typedEnr.tcp.isSome
|
||||||
|
typedEnr.tcp.get == 9000
|
||||||
|
|
||||||
|
typedEnr.udp.isSome
|
||||||
|
typedEnr.udp.get == 9000
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue