Attempt to further add the discv5 wire test vectors

This commit is contained in:
kdeme 2020-02-18 17:16:46 +01:00 committed by zah
parent 5ca1a21ecd
commit 3dd26e8526
2 changed files with 92 additions and 14 deletions

View File

@ -49,11 +49,12 @@ proc idNonceHash(nonce, ephkey: openarray[byte]): array[32, byte] =
ctx.update(ephkey) ctx.update(ephkey)
ctx.finish().data ctx.finish().data
proc signIDNonce(c: Codec, idNonce, ephKey: openarray[byte]): SignatureNR = proc signIDNonce*(c: Codec, idNonce, ephKey: openarray[byte]): SignatureNR =
if signRawMessage(idNonceHash(idNonce, ephKey), c.privKey, result) != EthKeysStatus.Success: if signRawMessage(idNonceHash(idNonce, ephKey), c.privKey, result) != EthKeysStatus.Success:
raise newException(EthKeysException, "Could not sign idNonce") raise newException(EthKeysException, "Could not sign idNonce")
proc deriveKeys(n1, n2: NodeID, priv: PrivateKey, pub: PublicKey, challenge: Whoareyou, result: var HandshakeSecrets) = proc deriveKeys(n1, n2: NodeID, priv: PrivateKey, pub: PublicKey,
idNonce: openarray[byte], result: var HandshakeSecrets) =
var eph: SharedSecretFull var eph: SharedSecretFull
if ecdhAgree(priv, pub, eph) != EthKeysStatus.Success: if ecdhAgree(priv, pub, eph) != EthKeysStatus.Success:
raise newException(EthKeysException, "ecdhAgree failed") raise newException(EthKeysException, "ecdhAgree failed")
@ -68,9 +69,9 @@ proc deriveKeys(n1, n2: NodeID, priv: PrivateKey, pub: PublicKey, challenge: Who
static: assert(sizeof(result) == 16 * 3) static: assert(sizeof(result) == 16 * 3)
var res = cast[ptr UncheckedArray[byte]](addr result) var res = cast[ptr UncheckedArray[byte]](addr result)
hkdf(sha256, eph.data, challenge.idNonce, info, toOpenArray(res, 0, sizeof(result) - 1)) hkdf(sha256, eph.data, idNonce, info, toOpenArray(res, 0, sizeof(result) - 1))
proc encryptGCM(key, nonce, pt, authData: openarray[byte]): seq[byte] = proc encryptGCM*(key, nonce, pt, authData: openarray[byte]): seq[byte] =
var ectx: GCM[aes128] var ectx: GCM[aes128]
ectx.init(key, nonce, authData) ectx.init(key, nonce, authData)
result = newSeq[byte](pt.len + gcmTagSize) result = newSeq[byte](pt.len + gcmTagSize)
@ -79,7 +80,8 @@ proc encryptGCM(key, nonce, pt, authData: openarray[byte]): seq[byte] =
ectx.clear() ectx.clear()
proc makeAuthHeader(c: Codec, toNode: Node, nonce: array[gcmNonceSize, byte], proc makeAuthHeader(c: Codec, toNode: Node, nonce: array[gcmNonceSize, byte],
handhsakeSecrets: var HandshakeSecrets, challenge: Whoareyou): seq[byte] = handshakeSecrets: var HandshakeSecrets,
challenge: Whoareyou): seq[byte] =
var resp = AuthResponse(version: 5) var resp = AuthResponse(version: 5)
let ln = c.localNode let ln = c.localNode
@ -91,15 +93,16 @@ proc makeAuthHeader(c: Codec, toNode: Node, nonce: array[gcmNonceSize, byte],
resp.signature = c.signIDNonce(challenge.idNonce, ephPubkey).getRaw resp.signature = c.signIDNonce(challenge.idNonce, ephPubkey).getRaw
deriveKeys(ln.id, toNode.id, ephKey, toNode.node.pubKey, challenge, handhsakeSecrets) deriveKeys(ln.id, toNode.id, ephKey, toNode.node.pubKey, challenge.idNonce,
handshakeSecrets)
let respRlp = rlp.encode(resp) let respRlp = rlp.encode(resp)
var zeroNonce: array[gcmNonceSize, byte] var zeroNonce: array[gcmNonceSize, byte]
let respEnc = encryptGCM(handhsakeSecrets.authRespKey, zeroNonce, respRLP, []) let respEnc = encryptGCM(handshakeSecrets.authRespKey, zeroNonce, respRLP, [])
let header = AuthHeader(auth: nonce, idNonce: challenge.idNonce, scheme: authSchemeName, let header = AuthHeader(auth: nonce, idNonce: challenge.idNonce,
ephemeralKey: ephPubkey, response: respEnc) scheme: authSchemeName, ephemeralKey: ephPubkey, response: respEnc)
rlp.encode(header) rlp.encode(header)
proc `xor`[N: static[int], T](a, b: array[N, T]): array[N, T] = proc `xor`[N: static[int], T](a, b: array[N, T]): array[N, T] =
@ -181,7 +184,8 @@ proc decodePacketBody(typ: byte, body: openarray[byte], res: var Packet): bool =
return true return true
proc decodeAuthResp(c: Codec, fromId: NodeId, head: AuthHeader, challenge: Whoareyou, secrets: var HandshakeSecrets, newNode: var Node): bool = proc decodeAuthResp(c: Codec, fromId: NodeId, head: AuthHeader,
challenge: Whoareyou, secrets: var HandshakeSecrets, newNode: var Node): bool =
if head.scheme != authSchemeName: if head.scheme != authSchemeName:
warn "Unknown auth scheme" warn "Unknown auth scheme"
return false return false
@ -190,7 +194,8 @@ proc decodeAuthResp(c: Codec, fromId: NodeId, head: AuthHeader, challenge: Whoar
if recoverPublicKey(head.ephemeralKey, ephKey) != EthKeysStatus.Success: if recoverPublicKey(head.ephemeralKey, ephKey) != EthKeysStatus.Success:
return false return false
deriveKeys(fromId, c.localNode.id, c.privKey, ephKey, challenge, secrets) deriveKeys(fromId, c.localNode.id, c.privKey, ephKey, challenge.idNonce,
secrets)
var zeroNonce: array[gcmNonceSize, byte] var zeroNonce: array[gcmNonceSize, byte]
let respData = decryptGCM(secrets.authRespKey, zeroNonce, head.response, []) let respData = decryptGCM(secrets.authRespKey, zeroNonce, head.response, [])

View File

@ -1,6 +1,6 @@
import unittest import
import eth/p2p/discoveryv5/[types, encoding, enr] unittest, stew/byteutils, stint,
import eth/rlp, stew/byteutils eth/[rlp, keys] , eth/p2p/discoveryv5/[types, encoding, enr]
# According to test vectors: # According to test vectors:
# https://github.com/ethereum/devp2p/blob/master/discv5/discv5-wire-test-vectors.md # https://github.com/ethereum/devp2p/blob/master/discv5/discv5-wire-test-vectors.md
@ -121,3 +121,76 @@ suite "Discovery v5 Protocol Message Encodings":
p.enrs = @[e1, e2] p.enrs = @[e1, e2]
var reqId: RequestId = 1 var reqId: RequestId = 1
check encodePacket(p, reqId).toHex == "04f8f20101f8eef875b8401ce2991c64993d7c84c29a00bdc871917551c7d330fca2dd0d69c706596dc655448f030b98a77d4001fd46ae0112ce26d613c5a6a02a81a6223cd0c4edaa53280182696482763489736563703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138f875b840d7f1c39e376297f81d7297758c64cb37dcc5c3beea9f57f7ce9695d7d5a67553417d719539d6ae4b445946de4d99e680eb8063f29485b555d45b7df16a1850130182696482763489736563703235366b31a1030e2cb74241c0c4fc8e8166f1a79a05d5b0dd95813a74b094529f317d5c39d235" check encodePacket(p, reqId).toHex == "04f8f20101f8eef875b8401ce2991c64993d7c84c29a00bdc871917551c7d330fca2dd0d69c706596dc655448f030b98a77d4001fd46ae0112ce26d613c5a6a02a81a6223cd0c4edaa53280182696482763489736563703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138f875b840d7f1c39e376297f81d7297758c64cb37dcc5c3beea9f57f7ce9695d7d5a67553417d719539d6ae4b445946de4d99e680eb8063f29485b555d45b7df16a1850130182696482763489736563703235366b31a1030e2cb74241c0c4fc8e8166f1a79a05d5b0dd95813a74b094529f317d5c39d235"
suite "Discovery v5 Cryptographic Primitives":
test "ECDH":
const
# input
publicKey = "0x9961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231503061ac4aaee666073d7e5bc2c80c3f5c5b500c1cb5fd0a76abbb6b675ad157"
secretKey = "0xfb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736"
# expected output
sharedSecret = "0x033b11a2a1f214567e1537ce5e509ffd9b21373247f2a3ff6841f4976f53165e7e"
let
pub = initPublicKey(publicKey)
priv = initPrivateKey(secretKey)
var eph: SharedSecretFull
check:
ecdhAgree(priv, pub, eph) == EthKeysStatus.Success
eph.data == hexToSeqByte(sharedSecret)
test "Key Derivation":
const
# input
secretKey = "0x02a77e3aa0c144ae7c0a3af73692b7d6e5b7a2fdc0eda16e8d5e6cb0d08e88dd04"
nodeIdA = "0xa448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7"
nodeIdB = "0x885bba8dfeddd49855459df852ad5b63d13a3fae593f3f9fa7e317fd43651409"
idNonce = "0x0101010101010101010101010101010101010101010101010101010101010101"
# expected output
initiatorKey = "0x238d8b50e4363cf603a48c6cc3542967"
recipientKey = "0xbebc0183484f7e7ca2ac32e3d72c8891"
authRespKey = "0xe987ad9e414d5b4f9bfe4ff1e52f2fae"
# Code doesn't allow to start from shared `secretKey`, but only from the
# public and private key. Would require pulling `ecdhAgree` out of
# `deriveKeys`
skip()
test "Nonce Signing":
const
# input
idNonce = "0xa77e3aa0c144ae7c0a3af73692b7d6e5b7a2fdc0eda16e8d5e6cb0d08e88dd04"
ephemeralKey = "0x9961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231503061ac4aaee666073d7e5bc2c80c3f5c5b500c1cb5fd0a76abbb6b675ad157"
localSecretKey = "0xfb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736"
# expected output
idNonceSig = "0xc5036e702a79902ad8aa147dabfe3958b523fd6fa36cc78e2889b912d682d8d35fdea142e141f690736d86f50b39746ba2d2fc510b46f82ee08f08fd55d133a4"
let
c = Codec(privKey: initPrivateKey(localSecretKey))
signature = signIDNonce(c, hexToByteArray[32](idNonce),
hexToByteArray[64](ephemeralKey))
check signature.getRaw() == hexToByteArray[64](idNonceSig)
test "Encryption/Decryption":
const
# input
encryptionKey = "0x9f2d77db7004bf8a1a85107ac686990b"
nonce = "0x27b5af763c446acd2749fe8e"
pt = "0x01c20101"
ad = "0x93a7400fa0d6a694ebc24d5cf570f65d04215b6ac00757875e3f3a5f42107903"
# expected output
messageCiphertext = "0xa5d12a2d94b8ccb3ba55558229867dc13bfa3648"
let encrypted = encryptGCM(hexToByteArray[16](encryptionKey),
hexToByteArray[12](nonce),
hexToSeqByte(pt),
hexToByteArray[32](ad))
check encrypted == hexToSeqByte(messageCiphertext)
test "Authentication Header and Encrypted Message Generation":
# Can't work directly with the provided shared secret as keys are derived
# inside makeAuthHeader, and passed on one call up.
# The encryption of the auth-resp-pt uses one of these keys, as does the
# encryption of the message itself. So the whole test depends on this.
skip()