mirror of https://github.com/status-im/nim-eth.git
Discv5 small adjustments (#307)
* Use checkedEnumAssign in discv5.1 * Some renaming according to discv5 spec nomenclature
This commit is contained in:
parent
3626755529
commit
71d6dbd1b4
|
@ -1,8 +1,10 @@
|
||||||
import
|
import
|
||||||
std/[tables, options],
|
std/[tables, options],
|
||||||
nimcrypto, stint, chronicles, stew/results, bearssl, stew/byteutils,
|
nimcrypto, stint, chronicles, bearssl, stew/[results, byteutils],
|
||||||
eth/[rlp, keys], typesv1, node, enr, hkdf, sessions
|
eth/[rlp, keys], typesv1, node, enr, hkdf, sessions
|
||||||
|
|
||||||
|
from stew/objects import checkedEnumAssign
|
||||||
|
|
||||||
export keys
|
export keys
|
||||||
|
|
||||||
{.push raises: [Defect].}
|
{.push raises: [Defect].}
|
||||||
|
@ -44,8 +46,8 @@ type
|
||||||
authdataSize: uint16
|
authdataSize: uint16
|
||||||
|
|
||||||
HandshakeSecrets* = object
|
HandshakeSecrets* = object
|
||||||
writeKey*: AesKey
|
initiatorKey*: AesKey
|
||||||
readKey*: AesKey
|
recipientKey*: AesKey
|
||||||
|
|
||||||
Flag* = enum
|
Flag* = enum
|
||||||
OrdinaryMessage = 0x00
|
OrdinaryMessage = 0x00
|
||||||
|
@ -74,7 +76,7 @@ type
|
||||||
|
|
||||||
DecodeResult*[T] = Result[T, cstring]
|
DecodeResult*[T] = Result[T, cstring]
|
||||||
|
|
||||||
proc idNonceHash*(challengeData, ephkey: openarray[byte], nodeId: NodeId):
|
proc idHash(challengeData, ephkey: openarray[byte], nodeId: NodeId):
|
||||||
MDigest[256] =
|
MDigest[256] =
|
||||||
var ctx: sha256
|
var ctx: sha256
|
||||||
ctx.init()
|
ctx.init()
|
||||||
|
@ -85,9 +87,14 @@ proc idNonceHash*(challengeData, ephkey: openarray[byte], nodeId: NodeId):
|
||||||
result = ctx.finish()
|
result = ctx.finish()
|
||||||
ctx.clear()
|
ctx.clear()
|
||||||
|
|
||||||
proc signIDNonce*(privKey: PrivateKey, challengeData,
|
proc createIdSignature*(privKey: PrivateKey, challengeData,
|
||||||
ephKey: openarray[byte], nodeId: NodeId): SignatureNR =
|
ephKey: openarray[byte], nodeId: NodeId): SignatureNR =
|
||||||
signNR(privKey, SkMessage(idNonceHash(challengeData, ephKey, nodeId).data))
|
signNR(privKey, SkMessage(idHash(challengeData, ephKey, nodeId).data))
|
||||||
|
|
||||||
|
proc verifyIdSignature*(sig: SignatureNR, challengeData, ephKey: openarray[byte],
|
||||||
|
nodeId: NodeId, pubKey: PublicKey): bool =
|
||||||
|
let h = idHash(challengeData, ephKey, nodeId)
|
||||||
|
verify(sig, SkMessage(h.data), pubKey)
|
||||||
|
|
||||||
proc deriveKeys*(n1, n2: NodeID, priv: PrivateKey, pub: PublicKey,
|
proc deriveKeys*(n1, n2: NodeID, priv: PrivateKey, pub: PublicKey,
|
||||||
challengeData: openarray[byte]): HandshakeSecrets =
|
challengeData: openarray[byte]): HandshakeSecrets =
|
||||||
|
@ -171,9 +178,9 @@ proc encodeMessagePacket*(rng: var BrHmacDrbgContext, c: var Codec,
|
||||||
|
|
||||||
# message
|
# message
|
||||||
var messageEncrypted: seq[byte]
|
var messageEncrypted: seq[byte]
|
||||||
var writeKey, readKey: AesKey
|
var initiatorKey, recipientKey: AesKey
|
||||||
if c.sessions.load(toId, toAddr, readKey, writeKey):
|
if c.sessions.load(toId, toAddr, recipientKey, initiatorKey):
|
||||||
messageEncrypted = encryptGCM(writeKey, nonce, message, @iv & header)
|
messageEncrypted = encryptGCM(initiatorKey, nonce, message, @iv & header)
|
||||||
else:
|
else:
|
||||||
# We might not have the node's keys if the handshake hasn't been performed
|
# We might not have the node's keys if the handshake hasn't been performed
|
||||||
# yet. That's fine, we send a random-packet and we will be responded with
|
# yet. That's fine, we send a random-packet and we will be responded with
|
||||||
|
@ -255,7 +262,7 @@ proc encodeHandshakePacket*(rng: var BrHmacDrbgContext, c: var Codec,
|
||||||
authdata.add(authdataHead)
|
authdata.add(authdataHead)
|
||||||
|
|
||||||
let ephKeys = KeyPair.random(rng)
|
let ephKeys = KeyPair.random(rng)
|
||||||
let signature = signIDNonce(c.privKey, whoareyouData.challengeData,
|
let signature = createIdSignature(c.privKey, whoareyouData.challengeData,
|
||||||
ephKeys.pubkey.toRawCompressed(), toId)
|
ephKeys.pubkey.toRawCompressed(), toId)
|
||||||
|
|
||||||
authdata.add(signature.toRaw())
|
authdata.add(signature.toRaw())
|
||||||
|
@ -276,8 +283,9 @@ proc encodeHandshakePacket*(rng: var BrHmacDrbgContext, c: var Codec,
|
||||||
header.add(staticHeader)
|
header.add(staticHeader)
|
||||||
header.add(authdata)
|
header.add(authdata)
|
||||||
|
|
||||||
c.sessions.store(toId, toAddr, secrets.readKey, secrets.writeKey)
|
c.sessions.store(toId, toAddr, secrets.recipientKey, secrets.initiatorKey)
|
||||||
let messageEncrypted = encryptGCM(secrets.writeKey, nonce, message, @iv & header)
|
let messageEncrypted = encryptGCM(secrets.initiatorKey, nonce, message,
|
||||||
|
@iv & header)
|
||||||
|
|
||||||
let maskedHeader = encryptHeader(toId, iv, header)
|
let maskedHeader = encryptHeader(toId, iv, header)
|
||||||
|
|
||||||
|
@ -305,9 +313,9 @@ proc decodeHeader*(id: NodeId, iv, maskedHeader: openarray[byte]):
|
||||||
if uint16.fromBytesBE(staticHeader.toOpenArray(6, 7)) != version:
|
if uint16.fromBytesBE(staticHeader.toOpenArray(6, 7)) != version:
|
||||||
return err("Invalid protocol version")
|
return err("Invalid protocol version")
|
||||||
|
|
||||||
if staticHeader[8] < Flag.low.byte or staticHeader[8] > Flag.high.byte:
|
var flag: Flag
|
||||||
|
if not checkedEnumAssign(flag, staticHeader[8]):
|
||||||
return err("Invalid packet flag")
|
return err("Invalid packet flag")
|
||||||
let flag = cast[Flag](staticHeader[8])
|
|
||||||
|
|
||||||
var nonce: AESGCMNonce
|
var nonce: AESGCMNonce
|
||||||
copyMem(addr nonce[0], unsafeAddr staticHeader[9], gcmNonceSize)
|
copyMem(addr nonce[0], unsafeAddr staticHeader[9], gcmNonceSize)
|
||||||
|
@ -333,12 +341,10 @@ proc decodeMessage*(body: openarray[byte]): DecodeResult[Message] =
|
||||||
if body.len < 1:
|
if body.len < 1:
|
||||||
return err("No message data")
|
return err("No message data")
|
||||||
|
|
||||||
if body[0] < MessageKind.low.byte or body[0] > MessageKind.high.byte:
|
var kind: MessageKind
|
||||||
|
if not checkedEnumAssign(kind, body[0]):
|
||||||
return err("Invalid message type")
|
return err("Invalid message type")
|
||||||
|
|
||||||
# This cast is covered by the above check (else we could get enum with invalid
|
|
||||||
# data!). However, can't we do this in a cleaner way?
|
|
||||||
let kind = cast[MessageKind](body[0])
|
|
||||||
var message = Message(kind: kind)
|
var message = Message(kind: kind)
|
||||||
var rlp = rlpFromBytes(body.toOpenArray(1, body.high))
|
var rlp = rlpFromBytes(body.toOpenArray(1, body.high))
|
||||||
if rlp.enterList:
|
if rlp.enterList:
|
||||||
|
@ -387,15 +393,15 @@ proc decodeMessagePacket(c: var Codec, fromAddr: Address, nonce: AESGCMNonce,
|
||||||
let srcId = NodeId.fromBytesBE(header.toOpenArray(staticHeaderSize,
|
let srcId = NodeId.fromBytesBE(header.toOpenArray(staticHeaderSize,
|
||||||
header.high))
|
header.high))
|
||||||
|
|
||||||
var writeKey, readKey: AesKey
|
var initiatorKey, recipientKey: AesKey
|
||||||
if not c.sessions.load(srcId, fromAddr, readKey, writeKey):
|
if not c.sessions.load(srcId, fromAddr, recipientKey, initiatorKey):
|
||||||
# Don't consider this an error, simply haven't done a handshake yet or
|
# Don't consider this an error, simply haven't done a handshake yet or
|
||||||
# the session got removed.
|
# the session got removed.
|
||||||
trace "Decrypting failed (no keys)"
|
trace "Decrypting failed (no keys)"
|
||||||
return ok(Packet(flag: Flag.OrdinaryMessage, requestNonce: nonce,
|
return ok(Packet(flag: Flag.OrdinaryMessage, requestNonce: nonce,
|
||||||
srcId: srcId))
|
srcId: srcId))
|
||||||
|
|
||||||
let pt = decryptGCM(readKey, nonce, ct, @iv & @header)
|
let pt = decryptGCM(recipientKey, nonce, ct, @iv & @header)
|
||||||
if pt.isNone():
|
if pt.isNone():
|
||||||
# Don't consider this an error, the session got probably removed at the
|
# Don't consider this an error, the session got probably removed at the
|
||||||
# peer's side and a random message is send.
|
# peer's side and a random message is send.
|
||||||
|
@ -495,22 +501,21 @@ proc decodeHandshakePacket(c: var Codec, fromAddr: Address, nonce: AESGCMNonce,
|
||||||
# We should have received a Record in this case.
|
# We should have received a Record in this case.
|
||||||
return err("Missing ENR in handshake packet")
|
return err("Missing ENR in handshake packet")
|
||||||
|
|
||||||
# Verify the id-nonce-sig
|
# Verify the id-signature
|
||||||
let sig = ? SignatureNR.fromRaw(
|
let sig = ? SignatureNR.fromRaw(
|
||||||
authdata.toOpenArray(authdataHeadSize, authdataHeadSize + int(sigSize) - 1))
|
authdata.toOpenArray(authdataHeadSize, authdataHeadSize + int(sigSize) - 1))
|
||||||
|
if not verifyIdSignature(sig, challenge.whoareyouData.challengeData,
|
||||||
let h = idNonceHash(challenge.whoareyouData.challengeData, ephKeyRaw,
|
ephKeyRaw, c.localNode.id, pubkey):
|
||||||
c.localNode.id)
|
|
||||||
if not verify(sig, SkMessage(h.data), pubkey):
|
|
||||||
return err("Invalid id-signature")
|
return err("Invalid id-signature")
|
||||||
|
|
||||||
# Do the key derivation step only after id-nonce-sig is verified!
|
# Do the key derivation step only after id-signature is verified as this is
|
||||||
|
# costly.
|
||||||
var secrets = deriveKeys(srcId, c.localNode.id, c.privKey,
|
var secrets = deriveKeys(srcId, c.localNode.id, c.privKey,
|
||||||
ephKey, challenge.whoareyouData.challengeData)
|
ephKey, challenge.whoareyouData.challengeData)
|
||||||
|
|
||||||
swap(secrets.readKey, secrets.writeKey)
|
swap(secrets.recipientKey, secrets.initiatorKey)
|
||||||
|
|
||||||
let pt = decryptGCM(secrets.readKey, nonce, ct, @iv & @header)
|
let pt = decryptGCM(secrets.recipientKey, nonce, ct, @iv & @header)
|
||||||
if pt.isNone():
|
if pt.isNone():
|
||||||
c.sessions.del(srcId, fromAddr)
|
c.sessions.del(srcId, fromAddr)
|
||||||
# Differently from an ordinary message, this is seen as an error as the
|
# Differently from an ordinary message, this is seen as an error as the
|
||||||
|
@ -524,7 +529,7 @@ proc decodeHandshakePacket(c: var Codec, fromAddr: Address, nonce: AESGCMNonce,
|
||||||
|
|
||||||
# Only store the session secrets in case decryption was successful and also
|
# Only store the session secrets in case decryption was successful and also
|
||||||
# in case the message can get decoded.
|
# in case the message can get decoded.
|
||||||
c.sessions.store(srcId, fromAddr, secrets.readKey, secrets.writeKey)
|
c.sessions.store(srcId, fromAddr, secrets.recipientKey, secrets.initiatorKey)
|
||||||
|
|
||||||
return ok(Packet(flag: Flag.HandshakeMessage, message: message,
|
return ok(Packet(flag: Flag.HandshakeMessage, message: message,
|
||||||
srcIdHs: srcId, node: newNode))
|
srcIdHs: srcId, node: newNode))
|
||||||
|
|
|
@ -121,7 +121,7 @@ suite "Discovery v5.1 Protocol Message Encodings":
|
||||||
check decoded.isErr()
|
check decoded.isErr()
|
||||||
|
|
||||||
# According to test vectors:
|
# According to test vectors:
|
||||||
# https://github.com/fjl/devp2p/blob/discv5-v1-update/discv5/discv5-wire-test-vectors.md#cryptographic-primitives
|
# https://github.com/ethereum/devp2p/blob/master/discv5/discv5-wire-test-vectors.md#cryptographic-primitives
|
||||||
suite "Discovery v5.1 Cryptographic Primitives Test Vectors":
|
suite "Discovery v5.1 Cryptographic Primitives Test Vectors":
|
||||||
test "ECDH":
|
test "ECDH":
|
||||||
const
|
const
|
||||||
|
@ -158,8 +158,8 @@ suite "Discovery v5.1 Cryptographic Primitives Test Vectors":
|
||||||
hexToSeqByte(challengeData))
|
hexToSeqByte(challengeData))
|
||||||
|
|
||||||
check:
|
check:
|
||||||
secrets.writeKey == hexToByteArray[aesKeySize](initiatorKey)
|
secrets.initiatorKey == hexToByteArray[aesKeySize](initiatorKey)
|
||||||
secrets.readKey == hexToByteArray[aesKeySize](recipientKey)
|
secrets.recipientKey == hexToByteArray[aesKeySize](recipientKey)
|
||||||
|
|
||||||
test "Nonce Signing":
|
test "Nonce Signing":
|
||||||
const
|
const
|
||||||
|
@ -173,16 +173,16 @@ suite "Discovery v5.1 Cryptographic Primitives Test Vectors":
|
||||||
|
|
||||||
let
|
let
|
||||||
privKey = PrivateKey.fromHex(staticKey)[]
|
privKey = PrivateKey.fromHex(staticKey)[]
|
||||||
signature = signIDNonce(
|
signature = createIdSignature(
|
||||||
privKey,
|
privKey,
|
||||||
hexToSeqByte(challengeData),
|
hexToSeqByte(challengeData),
|
||||||
hexToSeqByte(ephemeralPubkey),
|
hexToSeqByte(ephemeralPubkey),
|
||||||
NodeId.fromHex(nodeIdB))
|
NodeId.fromHex(nodeIdB))
|
||||||
check signature.toRaw() == hexToByteArray[64](idSignature)
|
check:
|
||||||
|
signature.toRaw() == hexToByteArray[64](idSignature)
|
||||||
let h = idNonceHash(hexToSeqByte(challengeData), hexToSeqByte(ephemeralPubkey),
|
verifyIdSignature(signature, hexToSeqByte(challengeData),
|
||||||
NodeId.fromHex(nodeIdB))
|
hexToSeqByte(ephemeralPubkey), NodeId.fromHex(nodeIdB),
|
||||||
check verify(signature, SkMessage(h.data), privKey.toPublicKey())
|
privKey.toPublicKey())
|
||||||
|
|
||||||
test "Encryption/Decryption":
|
test "Encryption/Decryption":
|
||||||
const
|
const
|
||||||
|
@ -201,7 +201,7 @@ suite "Discovery v5.1 Cryptographic Primitives Test Vectors":
|
||||||
check encrypted == hexToSeqByte(messageCiphertext)
|
check encrypted == hexToSeqByte(messageCiphertext)
|
||||||
|
|
||||||
# According to test vectors:
|
# According to test vectors:
|
||||||
# https://github.com/fjl/devp2p/blob/discv5-v1-update/discv5/discv5-wire-test-vectors.md#packet-encodings
|
# https://github.com/ethereum/devp2p/blob/master/discv5/discv5-wire-test-vectors.md#packet-encodings
|
||||||
suite "Discovery v5.1 Packet Encodings Test Vectors":
|
suite "Discovery v5.1 Packet Encodings Test Vectors":
|
||||||
const
|
const
|
||||||
nodeAKey = "0xeef77acb6c6a6eebc5b363a475ac583ec7eccdb42b6481424c60f59aa326547f"
|
nodeAKey = "0xeef77acb6c6a6eebc5b363a475ac583ec7eccdb42b6481424c60f59aa326547f"
|
||||||
|
@ -546,8 +546,10 @@ suite "Discovery v5.1 Additional Encode/Decode":
|
||||||
# Need to manually add the secrets that normally get negotiated in the
|
# Need to manually add the secrets that normally get negotiated in the
|
||||||
# handshake packet.
|
# handshake packet.
|
||||||
var secrets: HandshakeSecrets
|
var secrets: HandshakeSecrets
|
||||||
codecA.sessions.store(nodeB.id, nodeB.address.get(), secrets.readKey, secrets.writeKey)
|
codecA.sessions.store(nodeB.id, nodeB.address.get(), secrets.recipientKey,
|
||||||
codecB.sessions.store(nodeA.id, nodeA.address.get(), secrets.writeKey, secrets.readKey)
|
secrets.initiatorKey)
|
||||||
|
codecB.sessions.store(nodeA.id, nodeA.address.get(), secrets.initiatorKey,
|
||||||
|
secrets.recipientKey)
|
||||||
|
|
||||||
let (data, nonce) = encodeMessagePacket(rng[], codecA, nodeB.id,
|
let (data, nonce) = encodeMessagePacket(rng[], codecA, nodeB.id,
|
||||||
nodeB.address.get(), message)
|
nodeB.address.get(), message)
|
||||||
|
|
Loading…
Reference in New Issue