Merge pull request #2 from status-im/disco-new-keys

Use new eth_keys interface, more error handling
This commit is contained in:
Yuriy Glukhov 2018-04-11 18:49:59 +03:00 committed by GitHub
commit 6ee66c3d00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 60 additions and 46 deletions

View File

@ -13,7 +13,8 @@ requires "nim > 0.18.0",
"secp256k1 >= 0.1.0",
"eth_keys",
"ranges",
"ttmath"
"ttmath",
"https://github.com/status-im/byteutils"
proc runTest(name: string, lang = "c") = exec "nim " & lang & " -r tests/" & name

View File

@ -59,23 +59,21 @@ proc append*(w: var RlpWriter, p: Port) {.inline.} = w.append(p.int)
proc append*(w: var RlpWriter, pk: PublicKey) {.inline.} =
var bytes: array[64, byte]
pk.serialize(bytes)
pk.toRaw(bytes)
w.append(toMemRange(bytes))
proc append*(w: var RlpWriter, h: MDigest[256]) {.inline.} =
w.append(toMemRange(h.data))
proc toBytes(s: Signature): Bytes =
result = newSeq[byte](sizeof(s))
s.serialize(result)
proc pack(cmdId: CommandId, payload: BytesRange, pk: PrivateKey): Bytes =
## Create and sign a UDP message to be sent to a remote node.
##
## See https://github.com/ethereum/devp2p/blob/master/rlpx.md#node-discovery for information on
## how UDP packets are structured.
# TODO: There is a lot of unneeded allocations here
let encodedData = @[cmdId.byte] & payload.toSeq()
let signature = toBytes(pk.sign_msg(keccak256.digest(encodedData)))
let signature = @(pk.signMessage(encodedData).getRaw())
let msgHash = keccak256.digest(signature & encodedData)
result = @(msgHash.data) & signature & encodedData
@ -83,10 +81,13 @@ proc validateMsgHash(msg: Bytes, msgHash: var MDigest[256]): bool =
msgHash.data[0 .. ^1] = msg.toOpenArray(0, msgHash.data.high)
result = msgHash == keccak256.digest(msg.toOpenArray(MAC_SIZE, msg.high))
proc unpack(msg: Bytes): tuple[remotePubkey: PublicKey, cmdId: CommandId, payload: Bytes] =
proc recoverMsgPublicKey(msg: Bytes, pk: var PublicKey): bool =
recoverSignatureKey(msg.toOpenArray(MAC_SIZE, MAC_SIZE + 65),
keccak256.digest(msg.toOpenArray(HEAD_SIZE, msg.high)).data,
pk) == EthKeysStatus.Success
proc unpack(msg: Bytes): tuple[cmdId: CommandId, payload: Bytes] =
result.cmdId = msg[HEAD_SIZE].CommandId
let signature = parseSignature(msg, MAC_SIZE)
result.remotePubkey = recover_pubkey_from_msg(keccak256.digest(msg.toOpenArray(HEAD_SIZE, msg.high)), signature)
result.payload = msg[HEAD_SIZE + 1 .. ^1]
proc expiration(): uint32 =
@ -99,9 +100,10 @@ proc sendTo*(socket: AsyncFD, data: seq[byte], ip: IpAddress, port: Port,
var sa: Sockaddr_storage
var ln: Socklen
ip.toSockaddr(port, sa, ln)
GC_ref(data)
try:
await sendTo(socket, unsafeAddr data[0], data.len, cast[ptr Sockaddr](addr sa), ln)
GC_unref(data)
except:
error "sendTo failed: ", getCurrentExceptionMsg()
proc send(d: DiscoveryProtocol, n: Node, data: seq[byte]) =
asyncCheck d.socket.getFd().AsyncFD.sendTo(data, n.address.ip, n.address.udpPort)
@ -153,7 +155,7 @@ proc newDiscoveryProtocol*(privKey: PrivateKey, address: Address, bootstrapNodes
result.address = address
result.bootstrapNodes = newSeqOfCap[Node](bootstrapNodes.len)
for n in bootstrapNodes: result.bootstrapNodes.add(newNode(n))
result.thisNode = newNode(privKey.public_key, address)
result.thisNode = newNode(privKey.getPublicKey(), address)
result.kademlia = newKademliaProtocol(result.thisNode, result) {.explain.}
proc recvPing(d: DiscoveryProtocol, node: Node, msgHash: MDigest[256]) {.inline.} =
@ -187,7 +189,11 @@ proc recvNeighbours(d: DiscoveryProtocol, node: Node, payload: Bytes) {.inline.}
let udpPort = n.listElem(1).toInt(uint16).Port
let tcpPort = n.listElem(2).toInt(uint16).Port
let pk = parsePublicKey(n.listElem(3).toBytes.toOpenArray())
var pk: PublicKey
if recoverPublicKey(n.listElem(3).toBytes.toOpenArray(), pk) != EthKeysStatus.Success:
warn "Could not parse public key"
continue
neighbours.add(newNode(pk, Address(ip: ip, udpPort: udpPort, tcpPort: tcpPort)))
d.kademlia.recvNeighbours(node, neighbours)
@ -206,7 +212,9 @@ proc expirationValid(rlpEncodedPayload: seq[byte]): bool {.inline.} =
proc receive(d: DiscoveryProtocol, a: Address, msg: Bytes) =
var msgHash: MDigest[256]
if validateMsgHash(msg, msgHash):
let (remotePubkey, cmdId, payload) = unpack(msg)
var remotePubkey: PublicKey
if recoverMsgPublicKey(msg, remotePubkey):
let (cmdId, payload) = unpack(msg)
# echo "received cmd: ", cmdId, ", from: ", a
# echo "pubkey: ", remotePubkey.raw_key.toHex()
if expirationValid(payload):
@ -224,6 +232,8 @@ proc receive(d: DiscoveryProtocol, a: Address, msg: Bytes) =
echo "Unknown command: ", cmdId
else:
debug "Received msg ", cmdId, " from ", a, " already expired"
else:
error "Wrong public key from ", a
else:
error "Wrong msg mac from ", a
@ -236,10 +246,13 @@ proc runListeningLoop(d: DiscoveryProtocol) {.async.} =
slen = sizeof(saddr).Socklen
let received = await recvFromInto(d.socket.getFd().AsyncFD, addr buf[0], buf.len, cast[ptr SockAddr](addr saddr), addr slen)
buf.setLen(received)
try:
var port: Port
var ip: IpAddress
fromSockAddr(saddr, slen, ip, port)
d.receive(Address(ip: ip, udpPort: port, tcpPort: port), buf)
except:
error "receive failed: ", getCurrentExceptionMsg()
proc open*(d: DiscoveryProtocol) =
d.socket = newAsyncSocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
@ -254,17 +267,19 @@ proc resolve*(d: DiscoveryProtocol, n: NodeId): Future[Node] =
d.kademlia.resolve(n)
when isMainModule:
import logging
from private.conversion_bytes import hexToSeqByteBE # from eth_keys
import logging, byteutils
addHandler(newConsoleLogger())
block:
let m = hexToSeqByteBE"79664bff52ee17327b4a2d8f97d8fb32c9244d719e5038eb4f6b64da19ca6d271d659c3ad9ad7861a928ca85f8d8debfbe6b7ade26ad778f2ae2ba712567fcbd55bc09eb3e74a893d6b180370b266f6aaf3fe58a0ad95f7435bf3ddf1db940d20102f2cb842edbd4d182944382765da0ab56fb9e64a85a597e6bb27c656b4f1afb7e06b0fd4e41ccde6dba69a3c4a150845aaa4de2"
let m = hexToSeqByte"79664bff52ee17327b4a2d8f97d8fb32c9244d719e5038eb4f6b64da19ca6d271d659c3ad9ad7861a928ca85f8d8debfbe6b7ade26ad778f2ae2ba712567fcbd55bc09eb3e74a893d6b180370b266f6aaf3fe58a0ad95f7435bf3ddf1db940d20102f2cb842edbd4d182944382765da0ab56fb9e64a85a597e6bb27c656b4f1afb7e06b0fd4e41ccde6dba69a3c4a150845aaa4de2"
var msgHash: MDigest[256]
doAssert(validateMsgHash(m, msgHash))
let (remotePubkey, cmdId, payload) = unpack(m)
assert(payload == hexToSeqByteBE"f2cb842edbd4d182944382765da0ab56fb9e64a85a597e6bb27c656b4f1afb7e06b0fd4e41ccde6dba69a3c4a150845aaa4de2")
var remotePubkey: PublicKey
doAssert(recoverMsgPublicKey(m, remotePubkey))
let (cmdId, payload) = unpack(m)
assert(payload == hexToSeqByte"f2cb842edbd4d182944382765da0ab56fb9e64a85a597e6bb27c656b4f1afb7e06b0fd4e41ccde6dba69a3c4a150845aaa4de2")
assert(cmdId == cmdPong)
assert(remotePubkey == initPublicKey("78de8a0916848093c73790ead81d1928bec737d565119932b98c6b100d944b7a95e94f847f689fc723399d2e31129d182f7ef3863f2b4c820abbf3ab2722344d"))

View File

@ -45,9 +45,7 @@ const
ID_SIZE = 256
proc toNodeId(pk: PublicKey): NodeId =
var k: array[64, byte]
pk.serialize(k)
result = readUintBE[256](keccak256.digest(k).data)
readUintBE[256](keccak256.digest(pk.getRaw()).data)
proc newNode*(pk: PublicKey, address: Address): Node =
result.new()
@ -66,7 +64,7 @@ proc distanceTo(n: Node, id: NodeId): UInt256 = n.id xor id
proc `$`*(n: Node): string =
"Node[" & $n.address.ip & ":" & $n.address.udpPort & "]"
proc hash*(n: Node): hashes.Hash = hash(n.pubkey.raw_key)
proc hash*(n: Node): hashes.Hash = hash(n.pubkey.data)
proc `==`*(a, b: Node): bool = a.pubkey == b.pubkey
proc newKBucket(istart, iend: NodeId): KBucket =
@ -246,7 +244,7 @@ template onTimeout(b: untyped) =
b
proc waitPong(k: KademliaProtocol, node: Node, token: seq[byte]): Future[bool] =
let pingid = token & @(node.pubkey.raw_key)
let pingid = token & @(node.pubkey.data)
assert(pingid notin k.pongFutures, "Already waiting for pong from " & $node)
result = newFuture[bool]("waitPong")
let fut = result
@ -402,7 +400,7 @@ proc bootstrap*(k: KademliaProtocol, bootstrapNodes: seq[Node]) {.async.} =
proc recvPong*(k: KademliaProtocol, node: Node, token: seq[byte]) =
debug "<<< pong from ", node
let pingid = token & @(node.pubkey.raw_key)
let pingid = token & @(node.pubkey.data)
var future: Future[bool]
if k.pongFutures.take(pingid, future):
future.complete(true)