diff --git a/config.nims b/config.nims new file mode 100644 index 0000000..de8ad70 --- /dev/null +++ b/config.nims @@ -0,0 +1 @@ +switch("define", "libp2p_pki_schemes=secp256k1") \ No newline at end of file diff --git a/libp2pdht/discv5.nim b/libp2pdht/discv5.nim index a7f0f50..8ee437b 100644 --- a/libp2pdht/discv5.nim +++ b/libp2pdht/discv5.nim @@ -1,4 +1,4 @@ import - ./discv5/[spr, encoding, messages, messages_encoding, node, nodes_verification, protocol, routing_table, sessions, transport] + ./discv5/[crypto, spr, encoding, messages, messages_encoding, node, nodes_verification, protocol, routing_table, sessions, transport] -export spr, encoding, messages, messages_encoding, node, nodes_verification, protocol, routing_table, sessions, transport \ No newline at end of file +export crypto, spr, encoding, messages, messages_encoding, node, nodes_verification, protocol, routing_table, sessions, transport diff --git a/libp2pdht/discv5/crypto.nim b/libp2pdht/discv5/crypto.nim new file mode 100644 index 0000000..846055c --- /dev/null +++ b/libp2pdht/discv5/crypto.nim @@ -0,0 +1,4 @@ +import + ../private/eth/p2p/discoveryv5/crypto + +export crypto diff --git a/libp2pdht/private/eth/p2p/discoveryv5/crypto.nim b/libp2pdht/private/eth/p2p/discoveryv5/crypto.nim new file mode 100644 index 0000000..f0bc7ba --- /dev/null +++ b/libp2pdht/private/eth/p2p/discoveryv5/crypto.nim @@ -0,0 +1,30 @@ +import + std/sugar, + libp2p/crypto/[crypto, secp] + +from secp256k1 import ecdhRaw, SkEcdhRawSecret, toRaw + +proc fromHex*(T: type PrivateKey, data: string): Result[PrivateKey, cstring] = + let skKey = ? SkPrivateKey.init(data).mapErr(e => + ("Failed to init private key from hex string: " & $e).cstring) + ok PrivateKey.init(skKey) + +proc fromHex*(T: type PublicKey, data: string): Result[PublicKey, cstring] = + let skKey = ? SkPublicKey.init(data).mapErr(e => + ("Failed to init public key from hex string: " & $e).cstring) + ok PublicKey.init(skKey) + +func ecdhRaw*(seckey: SkPrivateKey, pubkey: SkPublicKey): SkEcdhRawSecret {.borrow.} + +proc ecdhRaw*( + priv: PrivateKey, + pub: PublicKey): Result[SkEcdhRawSecret, cstring] = + + # TODO: Do we need to support non-secp256k1 schemes? + if priv.scheme != Secp256k1 or pub.scheme != Secp256k1: + return err "Must use secp256k1 scheme".cstring + + ok ecdhRaw(priv.skkey, pub.skkey) + +proc toRaw*(pubkey: PublicKey): seq[byte] = + secp256k1.SkPublicKey(pubkey.skkey).toRaw()[1..^1] diff --git a/libp2pdht/private/eth/p2p/discoveryv5/encoding.nim b/libp2pdht/private/eth/p2p/discoveryv5/encoding.nim index 06807c3..265aaeb 100644 --- a/libp2pdht/private/eth/p2p/discoveryv5/encoding.nim +++ b/libp2pdht/private/eth/p2p/discoveryv5/encoding.nim @@ -14,15 +14,24 @@ {.push raises: [Defect].} import - std/[tables, options, hashes, net], - nimcrypto, stint, chronicles, bearssl, stew/[results, byteutils], metrics, - eth/[rlp, keys], + std/[hashes, net, options, sugar, tables], + eth/rlp, + bearssl, + chronicles, + stew/[results, byteutils], + stint, + libp2p/crypto/crypto as libp2p_crypto, + libp2p/crypto/secp, libp2p/signed_envelope, - "."/[messages, messages_encoding, node, spr, hkdf, sessions] + metrics, + nimcrypto, + "."/[messages, messages_encoding, node, spr, hkdf, sessions], + "."/crypto +from nimcrypto/utils import toHex from stew/objects import checkedEnumAssign -export keys +export crypto declareCounter discovery_session_lru_cache_hits, "Session LRU cache hits" declareCounter discovery_session_lru_cache_misses, "Session LRU cache misses" @@ -57,7 +66,7 @@ type Challenge* = object whoareyouData*: WhoareyouData - pubkey*: Option[keys.PublicKey] + pubkey*: Option[PublicKey] StaticHeader* = object flag: Flag @@ -93,12 +102,16 @@ type Codec* = object localNode*: Node - privKey*: keys.PrivateKey + privKey*: PrivateKey handshakes*: Table[HandshakeKey, Challenge] sessions*: Sessions + EncodeResult*[T] = Result[T, cstring] + DecodeResult*[T] = Result[T, cstring] + CryptoResult*[T] = Result[T, CryptoError] + func `==`*(a, b: HandshakeKey): bool = (a.nodeId == b.nodeId) and (a.address == b.address) @@ -117,18 +130,23 @@ proc idHash(challengeData, ephkey: openArray[byte], nodeId: NodeId): result = ctx.finish() ctx.clear() -proc createIdSignature*(privKey: keys.PrivateKey, challengeData, - ephKey: openArray[byte], nodeId: NodeId): SignatureNR = - signNR(privKey, SkMessage(idHash(challengeData, ephKey, nodeId).data)) +proc createIdSignature*(privKey: PrivateKey, challengeData, + ephKey: openArray[byte], nodeId: NodeId): EncodeResult[Signature] = -proc verifyIdSignature*(sig: SignatureNR, challengeData, ephKey: openArray[byte], - nodeId: NodeId, pubkey: keys.PublicKey): bool = let h = idHash(challengeData, ephKey, nodeId) - verify(sig, SkMessage(h.data), pubkey) + sign(privKey, h.data).mapErr(e => + ("Failed to sign challegene data: " & $e).cstring) -proc deriveKeys*(n1, n2: NodeId, priv: keys.PrivateKey, pub: keys.PublicKey, - challengeData: openArray[byte]): HandshakeSecrets = - let eph = ecdhRawFull(priv, pub) +proc verifyIdSignature*(sig: Signature, challengeData, ephKey: openArray[byte], + nodeId: NodeId, pubkey: PublicKey): bool = + + let h = idHash(challengeData, ephKey, nodeId) + verify(sig, h.data, pubkey) + +proc deriveKeys*(n1, n2: NodeId, priv: PrivateKey, pub: PublicKey, + challengeData: openArray[byte]): EncodeResult[HandshakeSecrets] = + + let eph = ? ecdhRaw(priv, pub) var info = newSeqOfCap[byte](keyAgreementPrefix.len + 32 * 2) for i, c in keyAgreementPrefix: info.add(byte(c)) @@ -141,7 +159,7 @@ proc deriveKeys*(n1, n2: NodeId, priv: keys.PrivateKey, pub: keys.PublicKey, hkdf(sha256, eph.data, challengeData, info, toOpenArray(res, 0, sizeof(secrets) - 1)) - secrets + ok secrets proc encryptGCM*(key: AesKey, nonce, pt, authData: openArray[byte]): seq[byte] = var ectx: GCM[aes128] @@ -236,7 +254,7 @@ proc encodeMessagePacket*(rng: var BrHmacDrbgContext, c: var Codec, proc encodeWhoareyouPacket*(rng: var BrHmacDrbgContext, c: var Codec, toId: NodeId, toAddr: Address, requestNonce: AESGCMNonce, recordSeq: uint64, - pubkey: Option[keys.PublicKey]): seq[byte] = + pubkey: Option[PublicKey]): seq[byte] = var idNonce: IdNonce brHmacDrbgGenerate(rng, idNonce) @@ -278,7 +296,7 @@ proc encodeWhoareyouPacket*(rng: var BrHmacDrbgContext, c: var Codec, proc encodeHandshakePacket*(rng: var BrHmacDrbgContext, c: var Codec, toId: NodeId, toAddr: Address, message: openArray[byte], - whoareyouData: WhoareyouData, pubkey: keys.PublicKey): seq[byte] = + whoareyouData: WhoareyouData, pubkey: PublicKey): EncodeResult[seq[byte]] = var header: seq[byte] var nonce: AESGCMNonce brHmacDrbgGenerate(rng, nonce) @@ -289,37 +307,52 @@ proc encodeHandshakePacket*(rng: var BrHmacDrbgContext, c: var Codec, var authdataHead: seq[byte] authdataHead.add(c.localNode.id.toByteArrayBE()) - authdataHead.add(64'u8) # sig-size: 64 - authdataHead.add(33'u8) # eph-key-size: 33 + + let ephKeys = ? KeyPair.random(rng) + .mapErr((e: CryptoError) => + ("Failed to create random key pair: " & $e).cstring) + + # TODO: Do we need to support non-secp256k1 schemes? + if ephKeys.pubkey.scheme != Secp256k1: + return err "Crypto scheme must be secp256k1".cstring + + let + pubKeyRaw = ? ephKeys.pubkey.getBytes().mapErr((e: CryptoError) => + ("Failed to serialize public key: " & $e).cstring) + signature = ? createIdSignature( + c.privKey, + whoareyouData.challengeData, + pubKeyRaw, + toId) + + let sigBytes = signature.getBytes() + authdataHead.add(sigBytes.len.uint8) # DER variable, less than 72 bytes + authdataHead.add(pubKeyRaw.len.uint8) # eph-key-size: 33 authdata.add(authdataHead) + authdata.add(sigBytes) - let ephKeys = keys.KeyPair.random(rng) - let signature = createIdSignature(c.privKey, whoareyouData.challengeData, - ephKeys.pubkey.toRawCompressed(), toId) - - authdata.add(signature.toRaw()) # compressed pub key format (33 bytes) - authdata.add(ephKeys.pubkey.toRawCompressed()) + authdata.add(pubKeyRaw) # Add SPR of sequence number is newer if whoareyouData.recordSeq < c.localNode.record.seqNum: - let encoded = c.localNode.record.encode - if encoded.isOk: - trace "Encoded local node's SignedPeerRecord", bytes = encoded.get - authdata.add(encoded.get) - else: - error "Failed to encode local node's SignedPeerRecord", error = encoded.error - authdata.add(@[]) + let encoded = ? c.localNode.record.encode.mapErr((e: CryptoError) => + ("Failed to encode local node's SignedPeerRecord: " & + $e).cstring) + authdata.add(encoded) - let secrets = deriveKeys(c.localNode.id, toId, ephKeys.seckey, pubkey, - whoareyouData.challengeData) + let secrets = ? deriveKeys( + c.localNode.id, + toId, + ephKeys.seckey, + pubkey, + whoareyouData.challengeData) # Header let staticHeader = encodeStaticHeader(Flag.HandshakeMessage, nonce, authdata.len()) header.add(staticHeader) - trace "Handshake packet's authdata", authdata header.add(authdata) c.sessions.store(toId, toAddr, secrets.recipientKey, secrets.initiatorKey) @@ -333,7 +366,7 @@ proc encodeHandshakePacket*(rng: var BrHmacDrbgContext, c: var Codec, packet.add(maskedHeader) packet.add(messageEncrypted) - return packet + return ok packet proc decodeHeader*(id: NodeId, iv, maskedHeader: openArray[byte]): DecodeResult[(StaticHeader, seq[byte])] = @@ -437,6 +470,7 @@ proc decodeWhoareyouPacket(c: var Codec, nonce: AESGCMNonce, proc decodeHandshakePacket(c: var Codec, fromAddr: Address, nonce: AESGCMNonce, iv, header, ct: openArray[byte]): DecodeResult[Packet] = + # Checking if there is enough data to decode authdata-head if header.len <= staticHeaderSize + authdataHeadSize: return err("Invalid header for handshake message packet: no authdata-head") @@ -469,24 +503,26 @@ proc decodeHandshakePacket(c: var Codec, fromAddr: Address, nonce: AESGCMNonce, let ephKeyPos = authdataHeadSize + int(sigSize) ephKeyRaw = authdata[ephKeyPos.. + ("Failed to deserialize PublicKey: " & $e).cstring) var record: Option[SignedPeerRecord] let recordPos = ephKeyPos + int(ephKeySize) if authdata.len() > recordPos: # There is possibly an SPR still try: - trace "Decoding handshake packet's authdata", authdata, recordPos, decodeBytes = authdata.toOpenArray(recordPos, authdata.high) # Signature check of record happens in decode. let prBytes = @(authdata.toOpenArray(recordPos, authdata.high)) - decoded = SignedPeerRecord.decode(prBytes) - .expect("Should be valid bytes for SignedPeerRecord") + decoded = ? SignedPeerRecord.decode(prBytes).mapErr( + (e: EnvelopeError) => + ("Invalid bytes for SignedPeerRecord: " & $e).cstring + ) record = some(decoded) except RlpError, ValueError: return err("Invalid encoded SPR") - var pubkey: keys.PublicKey + var pubkey: PublicKey var newNode: Option[Node] # TODO: Shall we return Node or SignedPeerRecord? SignedPeerRecord makes # more sense, but we do need the pubkey and the nodeid @@ -509,16 +545,27 @@ proc decodeHandshakePacket(c: var Codec, fromAddr: Address, nonce: AESGCMNonce, return err("Missing SPR in handshake packet") # Verify the id-signature - let sig = ? SignatureNR.fromRaw( - authdata.toOpenArray(authdataHeadSize, authdataHeadSize + int(sigSize) - 1)) + let + sigBytes = @(authdata.toOpenArray( + authdataHeadSize, + authdataHeadSize + int(sigSize) - 1 + )) + + sig = ? Signature.init(sigBytes).mapErr((e: CryptoError) => + ("Failed to deserialize signature from bytes: " & $e).cstring) + if not verifyIdSignature(sig, challenge.whoareyouData.challengeData, ephKeyRaw, c.localNode.id, pubkey): return err("Invalid id-signature") # Do the key derivation step only after id-signature is verified as this is # costly. - var secrets = deriveKeys(srcId, c.localNode.id, c.privKey, - ephKey, challenge.whoareyouData.challengeData) + var secrets = ? deriveKeys( + srcId, + c.localNode.id, + c.privKey, + ephKey, + challenge.whoareyouData.challengeData) swap(secrets.recipientKey, secrets.initiatorKey) diff --git a/libp2pdht/private/eth/p2p/discoveryv5/node.nim b/libp2pdht/private/eth/p2p/discoveryv5/node.nim index 4e7a854..aa9654c 100644 --- a/libp2pdht/private/eth/p2p/discoveryv5/node.nim +++ b/libp2pdht/private/eth/p2p/discoveryv5/node.nim @@ -9,8 +9,14 @@ import std/hashes, - nimcrypto, stint, chronos, stew/shims/net, chronicles, - eth/keys, eth/net/utils, + bearssl, + chronicles, + chronos, + nimcrypto, + stew/shims/net, + stint, + eth/net/utils, + ./crypto, ./spr export stint @@ -24,24 +30,28 @@ type Node* = ref object id*: NodeId - pubkey*: keys.PublicKey + pubkey*: PublicKey address*: Option[Address] record*: SignedPeerRecord seen*: bool ## Indicates if there was at least one successful ## request-response with this node, or if the nde was verified ## through the underlying transport mechanisms. -func toNodeId*(pk: keys.PublicKey): NodeId = +func toNodeId*(pid: PeerId): NodeId = ## Convert public key to a node identifier. # Keccak256 hash is used as defined in SPR spec for scheme v4: # https://github.com/ethereum/devp2p/blob/master/enr.md#v4-identity-scheme - readUintBE[256](keccak256.digest(pk.toRaw()).data) + readUintBE[256](keccak256.digest(pid.data).data) + +proc toNodeId*(pk: PublicKey): Result[NodeId, cstring] = + let pid = ? PeerId.init(pk) + ok pid.toNodeId func newNode*(r: SignedPeerRecord): Result[Node, cstring] = ## Create a new `Node` from a `SignedPeerRecord`. # TODO: Handle IPv6 - let pk = r.get(keys.PublicKey) + let pk = r.get(PublicKey) # This check is redundant for a properly created record as the deserialization # of a record will fail at `verifySignature` if there is no public key. if pk.isNone(): @@ -49,17 +59,20 @@ func newNode*(r: SignedPeerRecord): Result[Node, cstring] = # Also this can not fail for a properly created record as id is checked upon # deserialization. - let tr = ? r.toTypedRecord() + let + tr = ? r.toTypedRecord() + nodeId = ? pk.get().toNodeId() + if tr.ip.isSome() and tr.udp.isSome(): let a = Address(ip: ipv4(tr.ip.get()), port: Port(tr.udp.get())) - ok(Node(id: pk.get().toNodeId(), pubkey: pk.get() , record: r, + ok(Node(id: nodeId, pubkey: pk.get() , record: r, address: some(a))) else: - ok(Node(id: pk.get().toNodeId(), pubkey: pk.get(), record: r, + ok(Node(id: nodeId, pubkey: pk.get(), record: r, address: none(Address))) -proc update*(n: Node, pk: keys.PrivateKey, ip: Option[ValidIpAddress], +proc update*(n: Node, pk: PrivateKey, ip: Option[ValidIpAddress], tcpPort, udpPort: Option[Port] = none[Port]()): Result[void, cstring] = ? n.record.update(pk, ip, tcpPort, udpPort) @@ -77,7 +90,8 @@ proc update*(n: Node, pk: keys.PrivateKey, ip: Option[ValidIpAddress], ok() -func hash*(n: Node): hashes.Hash = hash(n.pubkey.toRaw) +func hash*(n: Node): hashes.Hash = + hash(n.pubkey.getRawBytes.expect("Public key has correct structure")) func `==`*(a, b: Node): bool = (a.isNil and b.isNil) or diff --git a/libp2pdht/private/eth/p2p/discoveryv5/protocol.nim b/libp2pdht/private/eth/p2p/discoveryv5/protocol.nim index 85518ea..2852766 100644 --- a/libp2pdht/private/eth/p2p/discoveryv5/protocol.nim +++ b/libp2pdht/private/eth/p2p/discoveryv5/protocol.nim @@ -77,7 +77,8 @@ import std/[tables, sets, options, math, sequtils, algorithm], stew/shims/net as stewNet, json_serialization/std/net, stew/[base64, endians2, results], chronicles, chronos, chronos/timer, stint, bearssl, - metrics, eth/[rlp, keys, async_utils], libp2p/routing_record, + metrics, eth/[rlp, async_utils], + libp2p/[crypto/crypto, routing_record], "."/[transport, messages, messages_encoding, node, routing_table, spr, random2, ip_vote, nodes_verification] import nimcrypto except toHex @@ -120,7 +121,7 @@ type Protocol* = ref object localNode*: Node - privateKey: keys.PrivateKey + privateKey: PrivateKey transport*: Transport[Protocol] # exported for tests routingTable*: RoutingTable awaitedMessages: Table[(NodeId, RequestId), Future[Option[Message]]] @@ -214,7 +215,7 @@ proc neighboursAtDistances*(d: Protocol, distances: seq[uint16], proc nodesDiscovered*(d: Protocol): int = d.routingTable.len -func privKey*(d: Protocol): lent keys.PrivateKey = +func privKey*(d: Protocol): lent PrivateKey = d.privateKey func getRecord*(d: Protocol): SignedPeerRecord = @@ -948,7 +949,7 @@ func init*( ) proc newProtocol*( - privKey: keys.PrivateKey, + privKey: PrivateKey, enrIp: Option[ValidIpAddress], enrTcpPort, enrUdpPort: Option[Port], localEnrFields: openArray[(string, seq[byte])] = [], @@ -958,7 +959,7 @@ proc newProtocol*( bindIp = IPv4_any(), enrAutoUpdate = false, config = defaultDiscoveryConfig, - rng = keys.newRng()): + rng = newRng()): Protocol = # TODO: Tried adding bindPort = udpPort as parameter but that gave # "Error: internal error: environment misses: udpPort" in nim-beacon-chain. diff --git a/libp2pdht/private/eth/p2p/discoveryv5/spr.nim b/libp2pdht/private/eth/p2p/discoveryv5/spr.nim index 2f31158..d019f51 100644 --- a/libp2pdht/private/eth/p2p/discoveryv5/spr.nim +++ b/libp2pdht/private/eth/p2p/discoveryv5/spr.nim @@ -12,7 +12,6 @@ import stew/shims/net, stew/base64, eth/rlp, - eth/keys, libp2p/crypto/crypto, libp2p/crypto/secp, libp2p/routing_record, @@ -36,13 +35,11 @@ proc seqNum*(r: SignedPeerRecord): uint64 = proc append*(rlpWriter: var RlpWriter, value: SignedPeerRecord) = # echo "encoding to:" & $value.signedPeerRecord.encode.get var encoded = value.encode - trace "Encoding SignedPeerRecord for RLP", bytes = encoded.get(@[]) if encoded.isErr: error "Error encoding SignedPeerRecord for RLP", error = encoded.error rlpWriter.append encoded.get(@[]) proc fromBytes(r: var SignedPeerRecord, s: openArray[byte]): bool = - trace "Decoding SignedPeerRecord for RLP", bytes = s let decoded = SignedPeerRecord.decode(@s) if decoded.isErr: @@ -56,46 +53,24 @@ proc read*(rlp: var Rlp, T: typedesc[SignedPeerRecord]): T {.raises: [RlpError, ValueError, Defect].} = # echo "read:" & $rlp.rawData ## code directly borrowed from spr.nim - trace "Reading RLP SignedPeerRecord", rawData = rlp.rawData, toBytes = rlp.toBytes if not rlp.hasData() or not result.fromBytes(rlp.toBytes): # TODO: This could also just be an invalid signature, would be cleaner to # split of RLP deserialisation errors from this. raise newException(ValueError, "Could not deserialize") rlp.skipElem() -proc get*(r: SignedPeerRecord, T: type crypto.PublicKey): Option[T] = - ## Get the `PublicKey` from provided `Record`. Return `none` when there is - ## no `PublicKey` in the record. - some(r.envelope.publicKey) - -func pkToPk(pk: crypto.PublicKey) : Option[keys.PublicKey] = - some((keys.PublicKey)(pk.skkey)) - -func pkToPk(pk: keys.PublicKey) : Option[crypto.PublicKey] = - some(crypto.PublicKey.init((secp.SkPublicKey)(pk))) - -func pkToPk(pk: crypto.PrivateKey) : Option[keys.PrivateKey] = - some((keys.PrivateKey)(pk.skkey)) - -func pkToPk(pk: keys.PrivateKey) : Option[crypto.PrivateKey] = - some(crypto.PrivateKey.init((secp.SkPrivateKey)(pk))) - -proc get*(r: SignedPeerRecord, T: type keys.PublicKey): Option[T] = +proc get*(r: SignedPeerRecord, T: type PublicKey): Option[T] = ## Get the `PublicKey` from provided `Record`. Return `none` when there is ## no `PublicKey` in the record. ## PublicKey* = distinct SkPublicKey - let - pk = r.envelope.publicKey - pkToPk(pk) + r.envelope.publicKey.some proc incSeqNo*( r: var SignedPeerRecord, - pk: keys.PrivateKey): RecordResult[void] = - - let cryptoPk = pk.pkToPk.get() # TODO: remove when eth/keys removed + pk: PrivateKey): RecordResult[void] = r.data.seqNo.inc() - r = ? SignedPeerRecord.init(cryptoPk, r.data).mapErr( + r = ? SignedPeerRecord.init(pk, r.data).mapErr( (e: CryptoError) => ("Error initialising SignedPeerRecord with incremented seqNo: " & $e).cstring @@ -123,10 +98,11 @@ proc update*(r: var SignedPeerRecord, pk: crypto.PrivateKey, # ip/tcpPort/udpPort/extraFields let - pubkey = r.get(crypto.PublicKey) - keysPubKey = pubkey.get.pkToPk.get # remove when move away from eth/keys - keysPrivKey = pk.pkToPk.get - if pubkey.isNone() or keysPubKey != keysPrivKey.toPublicKey: + sprPubKey = r.get(PublicKey) + pubKey = pk.getPublicKey + # keysPubKey = pubkey.get.pkToPk.get # remove when move away from eth/keys + # keysPrivKey = pk.pkToPk.get + if sprPubKey.isNone or pubKey.isErr or sprPubKey.get != pubKey.get: return err("Public key does not correspond with given private key") var @@ -208,13 +184,6 @@ proc update*(r: var SignedPeerRecord, pk: crypto.PrivateKey, return ok() -proc update*(r: var SignedPeerRecord, pk: keys.PrivateKey, - ip: Option[ValidIpAddress], - tcpPort, udpPort: Option[Port] = none[Port]()): - RecordResult[void] = - let cPk = pkToPk(pk).get - r.update(cPk, ip, tcpPort, udpPort) - proc toTypedRecord*(r: SignedPeerRecord) : RecordResult[SignedPeerRecord] = ok(r) proc ip*(r: SignedPeerRecord): Option[array[4, byte]] = @@ -275,7 +244,7 @@ proc toBase64*(r: SignedPeerRecord): string = proc toURI*(r: SignedPeerRecord): string = "spr:" & r.toBase64 proc init*(T: type SignedPeerRecord, seqNum: uint64, - pk: crypto.PrivateKey, + pk: PrivateKey, ip: Option[ValidIpAddress], tcpPort, udpPort: Option[Port]): RecordResult[T] = @@ -317,35 +286,10 @@ proc init*(T: type SignedPeerRecord, seqNum: uint64, let ma = MultiAddress.init(ipAddr, proto, protoPort) - # if ip.isSome: - # let - # ipAddr = ip.get - # proto = ipAddr.family - # address = if proto == IPv4: ipAddr.address_v4 - # else: ipAddr.address_v6 - # u and udpPort.isSome - # # let ta = initTAddress(ip.get, udpPort.get) - # # echo ta - # # ma = MultiAddress.init(ta).get - # #let ma1 = MultiAddress.init("/ip4/127.0.0.1").get() #TODO - # #let ma2 = MultiAddress.init(multiCodec("udp"), udpPort.get.int).get - # #ma = ma1 & ma2 - # ma = MultiAddress.init("/ip4/127.0.0.1/udp/" & $udpPort.get.int).get #TODO - # else: - # ma = MultiAddress.init() - # # echo "not implemented" let pr = PeerRecord.init(peerId, @[ma], seqNum) SignedPeerRecord.init(pk, pr).mapErr((e: CryptoError) => ("Failed to init SignedPeerRecord: " & $e).cstring) -proc init*(T: type SignedPeerRecord, seqNum: uint64, - pk: keys.PrivateKey, - ip: Option[ValidIpAddress], - tcpPort, udpPort: Option[Port]): - RecordResult[T] = - let kPk = pkToPk(pk).get - SignedPeerRecord.init(seqNum, kPk, ip, tcpPort, udpPort) - proc contains*(r: SignedPeerRecord, fp: (string, seq[byte])): bool = # TODO: use FieldPair for this, but that is a bit cumbersome. Perhaps the # `get` call can be improved to make this easier. diff --git a/libp2pdht/private/eth/p2p/discoveryv5/transport.nim b/libp2pdht/private/eth/p2p/discoveryv5/transport.nim index adff276..b6d3d6b 100644 --- a/libp2pdht/private/eth/p2p/discoveryv5/transport.nim +++ b/libp2pdht/private/eth/p2p/discoveryv5/transport.nim @@ -7,8 +7,10 @@ # Everything below the handling of ordinary messages import std/[tables, options], + bearssl, chronos, chronicles, + libp2p/crypto/crypto, stew/shims/net, "."/[node, encoding, sessions] @@ -124,8 +126,15 @@ proc receive*(t: Transport, a: Address, packet: openArray[byte]) = # This is a node we previously contacted and thus must have an address. doAssert(toNode.address.isSome()) let address = toNode.address.get() - let data = encodeHandshakePacket(t.rng[], t.codec, toNode.id, - address, pr.message, packet.whoareyou, toNode.pubkey) + let data = encodeHandshakePacket( + t.rng[], + t.codec, + toNode.id, + address, + pr.message, + packet.whoareyou, + toNode.pubkey + ).expect("Valid handshake packet to encode") trace "Send handshake message packet", dstId = toNode.id, address t.send(toNode, data) diff --git a/tests/dht/test_helper.nim b/tests/dht/test_helper.nim index dd8687b..f6d4957 100644 --- a/tests/dht/test_helper.nim +++ b/tests/dht/test_helper.nim @@ -1,19 +1,30 @@ import - stew/shims/net, bearssl, chronos, - eth/keys, - libp2pdht/discv5/[spr, node, routing_table], + bearssl, + chronos, + libp2p/crypto/[crypto, secp], + libp2p/multiaddress, + libp2pdht/discv5/[node, routing_table, spr], + libp2pdht/discv5/crypto as dhtcrypto, libp2pdht/discv5/protocol as discv5_protocol, - libp2p/crypto/crypto, - libp2p/multiaddress + stew/shims/net export net proc localAddress*(port: int): Address = Address(ip: ValidIpAddress.init("127.0.0.1"), port: Port(port)) +proc example*(T: type PrivateKey, rng: ref HmacDrbgContext): PrivateKey = + PrivateKey.random(rng[]).expect("Valid rng for private key") + +proc example*(T: type NodeId, rng: ref HmacDrbgContext): NodeId = + let + privKey = PrivateKey.example(rng) + pubKey = privKey.getPublicKey.expect("Valid private key for public key") + pubKey.toNodeId().expect("Public key valid for node id") + proc initDiscoveryNode*( rng: ref BrHmacDrbgContext, - privKey: keys.PrivateKey, + privKey: PrivateKey, address: Address, bootstrapRecords: openArray[SignedPeerRecord] = [], localEnrFields: openArray[(string, seq[byte])] = [], @@ -41,7 +52,7 @@ proc nodeIdInNodes*(id: NodeId, nodes: openArray[Node]): bool = for n in nodes: if id == n.id: return true -proc generateNode*(privKey: keys.PrivateKey, port: int = 20302, +proc generateNode*(privKey: PrivateKey, port: int = 20302, ip: ValidIpAddress = ValidIpAddress.init("127.0.0.1")): Node = let port = Port(port) @@ -52,17 +63,20 @@ proc generateNode*(privKey: keys.PrivateKey, port: int = 20302, proc generateNRandomNodes*(rng: ref BrHmacDrbgContext, n: int): seq[Node] = var res = newSeq[Node]() for i in 1..n: - let node = generateNode(keys.PrivateKey.random(rng[])) + let + privKey = PrivateKey.example(rng) + node = privKey.generateNode() res.add(node) res proc nodeAndPrivKeyAtDistance*(n: Node, rng: var BrHmacDrbgContext, d: uint32, - ip: ValidIpAddress = ValidIpAddress.init("127.0.0.1")): (Node, keys.PrivateKey) = + ip: ValidIpAddress = ValidIpAddress.init("127.0.0.1")): (Node, PrivateKey) = while true: - let pk = keys.PrivateKey.random(rng) - let node = generateNode(pk, ip = ip) + let + privKey = PrivateKey.random(rng).expect("Valid rng for private key") + node = privKey.generateNode(ip = ip) if logDistance(n.id, node.id) == d: - return (node, pk) + return (node, privKey) proc nodeAtDistance*(n: Node, rng: var BrHmacDrbgContext, d: uint32, ip: ValidIpAddress = ValidIpAddress.init("127.0.0.1")): Node = @@ -98,7 +112,7 @@ func udpExamples*(_: type MultiAddress, count: int): seq[MultiAddress] = res.add Multiaddress.init("/ip4/0.0.0.0/udp/" & $i).get return res -proc toSignedPeerRecord*(privKey: crypto.PrivateKey) : SignedPeerRecord = +proc toSignedPeerRecord*(privKey: PrivateKey) : SignedPeerRecord = ## handle conversion between the two worlds let pr = PeerRecord.init( @@ -109,7 +123,7 @@ proc toSignedPeerRecord*(privKey: crypto.PrivateKey) : SignedPeerRecord = proc example*(T: type SignedPeerRecord): T = let - rng = crypto.newRng() - privKey = crypto.PrivateKey.random(rng[]).expect("Valid rng") + rng = newRng() + privKey = PrivateKey.example(rng) privKey.toSignedPeerRecord diff --git a/tests/dht/test_providers.nim b/tests/dht/test_providers.nim index 27bff65..09d3f03 100644 --- a/tests/dht/test_providers.nim +++ b/tests/dht/test_providers.nim @@ -10,32 +10,30 @@ {.used.} import - std/options, - std/sequtils, - chronos, stew/byteutils, nimcrypto, asynctest, - eth/keys, - libp2pdht/dht, + std/[options, sequtils], + asynctest, + bearssl, chronicles, + chronos, + nimcrypto, + libp2p/crypto/[crypto, secp], + libp2p/[multiaddress, multicodec, multihash, routing_record, signed_envelope], + libp2pdht/dht, + libp2pdht/discv5/crypto as dhtcrypto, libp2pdht/discv5/protocol as discv5_protocol, - test_helper, - libp2p/crypto/crypto, - libp2p/crypto/secp, - libp2p/routing_record, - libp2p/multiaddress, - libp2p/multihash, - libp2p/multicodec, - libp2p/signed_envelope + stew/byteutils, + test_helper proc bootstrapNodes( nodecount: int, bootnodes: seq[SignedPeerRecord], - rng = keys.newRng(), + rng = newRng(), delay: int = 0 - ) : Future[seq[(discv5_protocol.Protocol, keys.PrivateKey)]] {.async.} = + ) : Future[seq[(discv5_protocol.Protocol, PrivateKey)]] {.async.} = debug "---- STARTING BOOSTRAPS ---" for i in 0..>> key: ", key check logDistance(targetId, id) == d test "Distance to id check": @@ -160,17 +171,19 @@ suite "Discovery v5 Tests": test "Distance to id check with keys": const - targetKey = "5d485bdcbe9bc89314a10ae9231e429d33853e3a8fa2af39f5f827370a2e4185e344ace5d16237491dad41f278f1d3785210d29ace76cd627b9147ee340b1125" + targetKey = "045d485bdcbe9bc89314a10ae9231e429d33853e3a8fa2af39f5f827370a2e4185e344ace5d16237491dad41f278f1d3785210d29ace76cd627b9147ee340b1125" testValues = [ # possible id in that distance range - ("9e5b34809116e3790b2258a45e7ef03b11af786503fb1a6d4b4a8ca021ad653c", 251'u16), - ("925b34809116e3790b2258a45e7ef03b11af786503fb1a6d4b4a8ca021ad653c", 252'u16), - ("8a5b34809116e3790b2258a45e7ef03b11af786503fb1a6d4b4a8ca021ad653c", 253'u16), - ("ba5b34809116e3790b2258a45e7ef03b11af786503fb1a6d4b4a8ca021ad653c", 254'u16), - ("da5b34809116e3790b2258a45e7ef03b11af786503fb1a6d4b4a8ca021ad653c", 255'u16), - ("1a5b34809116e3790b2258a45e7ef03b11af786503fb1a6d4b4a8ca021ad653c", 256'u16) + ("cd2c707bdcbdf5109c66de68e8f26adec1527d075e0f93df0ad21c72e98b7a4d", 251'u16), + ("c12c707bdcbdf5109c66de68e8f26adec1527d075e0f93df0ad21c72e98b7a4d", 252'u16), + ("d92c707bdcbdf5109c66de68e8f26adec1527d075e0f93df0ad21c72e98b7a4d", 253'u16), + ("e92c707bdcbdf5109c66de68e8f26adec1527d075e0f93df0ad21c72e98b7a4d", 254'u16), + ("892c707bdcbdf5109c66de68e8f26adec1527d075e0f93df0ad21c72e98b7a4d", 255'u16), + ("492c707bdcbdf5109c66de68e8f26adec1527d075e0f93df0ad21c72e98b7a4d", 256'u16) ] - let targetId = toNodeId(keys.PublicKey.fromHex(targetKey)[]) + let + targetPubKey = PublicKey.fromHex(targetKey).expect("Valid public key hex") + targetId = targetPubKey.toNodeId().expect("Public key valid for node id") for (id, d) in testValues: check idAtDistance(targetId, d) == parse(id, UInt256, 16) @@ -178,10 +191,12 @@ suite "Discovery v5 Tests": test "FindNode Test": const dist = 253'u16 let - mainNodeKey = keys.PrivateKey.fromHex( - "a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a617")[] - testNodeKey = keys.PrivateKey.fromHex( - "a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a618")[] + mainNodeKey = PrivateKey.fromHex( + "a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a617") + .expect("Valid private key hex") + testNodeKey = PrivateKey.fromHex( + "a2b50376a79b1a8c8a3296485572bdfbf54708bb46d3c25d73d2723aaaf6a618") + .expect("Valid private key hex") mainNode = initDiscoveryNode(rng, mainNodeKey, localAddress(20301)) testNode = initDiscoveryNode(rng, testNodeKey, localAddress(20302)) # logarithmic distance between mainNode and testNode is 256 @@ -246,11 +261,11 @@ suite "Discovery v5 Tests": test "FindNode with test table": let mainNode = - initDiscoveryNode(rng, keys.PrivateKey.random(rng[]), localAddress(20301)) + initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20301)) # Generate 1000 random nodes and add to our main node's routing table for i in 0..<1000: - discard mainNode.addSeenNode(generateNode(keys.PrivateKey.random(rng[]))) # for testing only! + discard mainNode.addSeenNode(generateNode(PrivateKey.example(rng))) # for testing only! let neighbours = mainNode.neighbours(mainNode.localNode.id) @@ -261,7 +276,7 @@ suite "Discovery v5 Tests": let testNode = initDiscoveryNode( - rng, keys.PrivateKey.random(rng[]), localAddress(20302), + rng, PrivateKey.example(rng), localAddress(20302), @[mainNode.localNode.record]) discovered = await findNode(testNode, mainNode.localNode, @[closestDistance]) @@ -277,13 +292,13 @@ suite "Discovery v5 Tests": nodeCount = 17 let bootNode = - initDiscoveryNode(rng, keys.PrivateKey.random(rng[]), localAddress(20301)) + initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20301)) bootNode.start() var nodes = newSeqOfCap[discv5_protocol.Protocol](nodeCount) nodes.add(bootNode) for i in 1 ..< nodeCount: - nodes.add(initDiscoveryNode(rng, keys.PrivateKey.random(rng[]), localAddress(20301 + i), + nodes.add(initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20301 + i), @[bootNode.localNode.record])) # Make sure all nodes have "seen" each other by forcing pings @@ -317,10 +332,10 @@ suite "Discovery v5 Tests": test "Resolve target": let mainNode = - initDiscoveryNode(rng, keys.PrivateKey.random(rng[]), localAddress(20301)) + initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20301)) lookupNode = - initDiscoveryNode(rng, keys.PrivateKey.random(rng[]), localAddress(20302)) - targetKey = keys.PrivateKey.random(rng[]) + initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20302)) + targetKey = PrivateKey.example(rng) targetAddress = localAddress(20303) targetNode = initDiscoveryNode(rng, targetKey, targetAddress) targetId = targetNode.localNode.id @@ -400,10 +415,10 @@ suite "Discovery v5 Tests": # We no longer support field filtering # test "Random nodes with spr field filter": # let - # lookupNode = initDiscoveryNode(rng, keys.PrivateKey.random(rng[]), localAddress(20301)) - # targetNode = generateNode(keys.PrivateKey.random(rng[])) - # otherNode = generateNode(keys.PrivateKey.random(rng[])) - # anotherNode = generateNode(keys.PrivateKey.random(rng[])) + # lookupNode = initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20301)) + # targetNode = generateNode(PrivateKey.example(rng)) + # otherNode = generateNode(PrivateKey.example(rng)) + # anotherNode = generateNode(PrivateKey.example(rng)) # check: # lookupNode.addNode(targetNode) @@ -420,7 +435,7 @@ suite "Discovery v5 Tests": test "New protocol with spr": let - privKey = keys.PrivateKey.random(rng[]) + privKey = PrivateKey.example(rng) ip = some(ValidIpAddress.init("127.0.0.1")) port = Port(20301) node = newProtocol(privKey, ip, some(port), some(port), bindPort = port, @@ -441,16 +456,16 @@ suite "Discovery v5 Tests": # Defect (for now?) on incorrect key use expect ResultDefect: - let incorrectKeyUpdates = newProtocol(keys.PrivateKey.random(rng[]), + let incorrectKeyUpdates = newProtocol(PrivateKey.example(rng), ip, some(port), some(port), bindPort = port, rng = rng, previousRecord = some(updatesNode.getRecord())) test "Update node record with revalidate": let mainNode = - initDiscoveryNode(rng, keys.PrivateKey.random(rng[]), localAddress(20301)) + initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20301)) testNode = - initDiscoveryNode(rng, keys.PrivateKey.random(rng[]), localAddress(20302)) + initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20302)) testNodeId = testNode.localNode.id check: @@ -482,9 +497,9 @@ suite "Discovery v5 Tests": test "Update node record with handshake": let mainNode = - initDiscoveryNode(rng, keys.PrivateKey.random(rng[]), localAddress(20301)) + initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20301)) testNode = - initDiscoveryNode(rng, keys.PrivateKey.random(rng[]), localAddress(20302)) + initDiscoveryNode(rng, PrivateKey.example(rng), localAddress(20302)) testNodeId = testNode.localNode.id # Add the node (from the record, so new node!) so no handshake is done yet. @@ -515,18 +530,20 @@ suite "Discovery v5 Tests": test "Verify records of nodes message": let port = Port(9000) - fromNoderecord = SignedPeerRecord.init(1, keys.PrivateKey.random(rng[]), + fromNoderecord = SignedPeerRecord.init(1, PrivateKey.example(rng), some(ValidIpAddress.init("11.12.13.14")), some(port), some(port))[] fromNode = newNode(fromNoderecord)[] - pk = keys.PrivateKey.random(rng[]) - targetDistance = @[logDistance(fromNode.id, pk.toPublicKey().toNodeId())] + privKey = PrivateKey.example(rng) + pubKey = privKey.getPublicKey.expect("Valid private key for public key") + nodeId = pubKey.toNodeId().expect("Public key valid for node id") + targetDistance = @[logDistance(fromNode.id, nodeId)] limit = 16 block: # Duplicates let record = SignedPeerRecord.init( - 1, pk, some(ValidIpAddress.init("12.13.14.15")), + 1, privKey, some(ValidIpAddress.init("12.13.14.15")), some(port), some(port))[] # Exact duplicates @@ -536,7 +553,7 @@ suite "Discovery v5 Tests": # Node id duplicates let recordSameId = SignedPeerRecord.init( - 1, pk, some(ValidIpAddress.init("212.13.14.15")), + 1, privKey, some(ValidIpAddress.init("212.13.14.15")), some(port), some(port))[] records.add(recordSameId) nodes = verifyNodesRecords(records, fromNode, limit, targetDistance) @@ -545,7 +562,7 @@ suite "Discovery v5 Tests": block: # No address let recordNoAddress = SignedPeerRecord.init( - 1, pk, none(ValidIpAddress), some(port), some(port))[] + 1, privKey, none(ValidIpAddress), some(port), some(port))[] records = [recordNoAddress] test = verifyNodesRecords(records, fromNode, limit, targetDistance) check test.len == 0 @@ -553,7 +570,7 @@ suite "Discovery v5 Tests": block: # Invalid address - site local let recordInvalidAddress = SignedPeerRecord.init( - 1, pk, some(ValidIpAddress.init("10.1.2.3")), + 1, privKey, some(ValidIpAddress.init("10.1.2.3")), some(port), some(port))[] records = [recordInvalidAddress] test = verifyNodesRecords(records, fromNode, limit, targetDistance) @@ -562,7 +579,7 @@ suite "Discovery v5 Tests": block: # Invalid address - loopback let recordInvalidAddress = SignedPeerRecord.init( - 1, pk, some(ValidIpAddress.init("127.0.0.1")), + 1, privKey, some(ValidIpAddress.init("127.0.0.1")), some(port), some(port))[] records = [recordInvalidAddress] test = verifyNodesRecords(records, fromNode, limit, targetDistance) @@ -571,7 +588,7 @@ suite "Discovery v5 Tests": block: # Invalid distance let recordInvalidDistance = SignedPeerRecord.init( - 1, pk, some(ValidIpAddress.init("12.13.14.15")), + 1, privKey, some(ValidIpAddress.init("12.13.14.15")), some(port), some(port))[] records = [recordInvalidDistance] test = verifyNodesRecords(records, fromNode, limit, @[0'u16]) @@ -580,7 +597,7 @@ suite "Discovery v5 Tests": block: # Invalid distance but distance validation is disabled let recordInvalidDistance = SignedPeerRecord.init( - 1, pk, some(ValidIpAddress.init("12.13.14.15")), + 1, privKey, some(ValidIpAddress.init("12.13.14.15")), some(port), some(port))[] records = [recordInvalidDistance] test = verifyNodesRecords(records, fromNode, limit) @@ -598,14 +615,14 @@ suite "Discovery v5 Tests": test "Handshake cleanup: different ids": # Node to test the handshakes on. let receiveNode = initDiscoveryNode( - rng, keys.PrivateKey.random(rng[]), localAddress(20302)) + rng, PrivateKey.example(rng), localAddress(20302)) # Create random packets with same ip but different node ids # and "receive" them on receiveNode let a = localAddress(20303) for i in 0 ..< 5: let - privKey = keys.PrivateKey.random(rng[]) + privKey = PrivateKey.example(rng) enrRec = SignedPeerRecord.init(1, privKey, some(ValidIpAddress.init("127.0.0.1")), some(Port(9000)), some(Port(9000))).expect("Properly intialized private key") @@ -629,12 +646,12 @@ suite "Discovery v5 Tests": test "Handshake cleanup: different ips": # Node to test the handshakes on. let receiveNode = initDiscoveryNode( - rng, keys.PrivateKey.random(rng[]), localAddress(20302)) + rng, PrivateKey.example(rng), localAddress(20302)) # Create random packets with same node ids but different ips # and "receive" them on receiveNode let - privKey = keys.PrivateKey.random(rng[]) + privKey = PrivateKey.example(rng) enrRec = SignedPeerRecord.init(1, privKey, some(ValidIpAddress.init("127.0.0.1")), some(Port(9000)), some(Port(9000))).expect("Properly intialized private key") @@ -659,13 +676,13 @@ suite "Discovery v5 Tests": test "Handshake duplicates": # Node to test the handshakes on. let receiveNode = initDiscoveryNode( - rng, keys.PrivateKey.random(rng[]), localAddress(20302)) + rng, PrivateKey.example(rng), localAddress(20302)) # Create random packets with same node ids and same ips # and "receive" them on receiveNode let a = localAddress(20303) - privKey = keys.PrivateKey.random(rng[]) + privKey = PrivateKey.example(rng) enrRec = SignedPeerRecord.init(1, privKey, some(ValidIpAddress.init("127.0.0.1")), some(Port(9000)), some(Port(9000))).expect("Properly intialized private key") @@ -692,9 +709,9 @@ suite "Discovery v5 Tests": test "Talkreq no protocol": let node1 = initDiscoveryNode( - rng, keys.PrivateKey.random(rng[]), localAddress(20302)) + rng, PrivateKey.example(rng), localAddress(20302)) node2 = initDiscoveryNode( - rng, keys.PrivateKey.random(rng[]), localAddress(20303)) + rng, PrivateKey.example(rng), localAddress(20303)) talkresp = await discv5_protocol.talkReq(node1, node2.localNode, @[byte 0x01], @[]) @@ -708,9 +725,9 @@ suite "Discovery v5 Tests": test "Talkreq echo protocol": let node1 = initDiscoveryNode( - rng, keys.PrivateKey.random(rng[]), localAddress(20302)) + rng, PrivateKey.example(rng), localAddress(20302)) node2 = initDiscoveryNode( - rng, keys.PrivateKey.random(rng[]), localAddress(20303)) + rng, PrivateKey.example(rng), localAddress(20303)) talkProtocol = "echo".toBytes() proc handler(protocol: TalkProtocol, request: seq[byte], fromId: NodeId, fromUdpAddress: Address): seq[byte] @@ -733,9 +750,9 @@ suite "Discovery v5 Tests": test "Talkreq register protocols": let node1 = initDiscoveryNode( - rng, keys.PrivateKey.random(rng[]), localAddress(20302)) + rng, PrivateKey.example(rng), localAddress(20302)) node2 = initDiscoveryNode( - rng, keys.PrivateKey.random(rng[]), localAddress(20303)) + rng, PrivateKey.example(rng), localAddress(20303)) talkProtocol = "echo".toBytes() proc handler(protocol: TalkProtocol, request: seq[byte], fromId: NodeId, fromUdpAddress: Address): seq[byte] diff --git a/tests/discv5/test_discoveryv5_encoding.nim b/tests/discv5/test_discoveryv5_encoding.nim index 77d7684..95b7c9c 100644 --- a/tests/discv5/test_discoveryv5_encoding.nim +++ b/tests/discv5/test_discoveryv5_encoding.nim @@ -2,13 +2,20 @@ import std/[options, sequtils, tables], - chronos, asynctest/unittest2, - stint, stew/byteutils, stew/shims/net, - eth/[keys,rlp], + bearssl, + chronos, + eth/rlp, + libp2p/crypto/secp, libp2pdht/discv5/[messages, messages_encoding, encoding, spr, node, sessions], + libp2pdht/discv5/crypto, + stew/byteutils, + stew/shims/net, + stint, ../dht/test_helper +from secp256k1 import toRaw + suite "Discovery v5.1 Protocol Message Encodings": test "Ping Request": let @@ -89,15 +96,15 @@ suite "Discovery v5.1 Protocol Message Encodings": test "Nodes Response (multiple)": var s1, s2: SignedPeerRecord - check s1.fromURI("spr:CiQIARIgWu2YZ5TQVW1gWEfvQijVHqSBtjCbwDt9VppJvYpHX9wSAgMBGlUKJgAkCAESIFrtmGeU0FVtYFhH70Io1R6kgbYwm8A7fVaaSb2KR1_cEKz1xZEGGgsKCQQAAAAAkQIAARoLCgkEAAAAAJECAAIaCwoJBAAAAACRAgADKkAjkK9DeWc82uzd1AEjRr-ksQyRiQ7vYGV4Af3FAEi0JgHvMC8RCQdqn2wBYxvBcyO8o1XMEEKCG01AUZrJlCkD") - check s2.fromURI("spr:CiQIARIguW3cNKnlvRsJVmV0ddgFMmvfAQLi0zf4tlt_6WGA03YSAgMBGlUKJgAkCAESILlt3DSp5b0bCVZldHXYBTJr3wEC4tM3-LZbf-lhgNN2EKz1xZEGGgsKCQQAAAAAkQIAARoLCgkEAAAAAJECAAIaCwoJBAAAAACRAgADKkC4Y9NkDHf-71LOvZon0NjmyzQnkm4IlAJGMDPS0cbSgIF3-2cECC5mRiXHjcHWlI5hPpxUURxFyIgSp7XX1jIL") + check s1.fromURI("spr:CiUIAhIhAjOdSH7SNzktg3kZUNyJHwY23mmMH6BR6gGuP6WL14WAEgIDARpWCicAJQgCEiECM51IftI3OS2DeRlQ3IkfBjbeaYwfoFHqAa4_pYvXhYAQnP2JkgYaCwoJBAAAAACRAgABGgsKCQQAAAAAkQIAAhoLCgkEAAAAAJECAAMqRzBFAiEAjMd_0mXjPJVRdLn0ligEiy1ypjlayzDwup2QU2-hpdUCIH-o5bq46N3umISo4kSwmQIo41RrWptoSGMqvZJHluV2") + check s2.fromURI("spr:CiUIAhIhAmvtpc_d8c2JEw57W7YJK6wj20oES_hHMoqgMQ3RI6RFEgIDARpWCicAJQgCEiECa-2lz93xzYkTDntbtgkrrCPbSgRL-EcyiqAxDdEjpEUQnP2JkgYaCwoJBAAAAACRAgABGgsKCQQAAAAAkQIAAhoLCgkEAAAAAJECAAMqRjBEAiA9QbGnjF5tmMm08_yyE9wWrk3lChyHFaspxRav5kiLTgIgWEHQnpKz0vGtcse8Bm5WHatXMgiG8_u_Jy0s8XMsolk") let total = 0x1'u32 n = NodesMessage(total: total, sprs: @[s1, s2]) reqId = RequestId(id: @[1.byte]) let encoded = encodeMessage(n, reqId) - check byteutils.toHex(encoded) == "04f9018f0101f9018ab8c30a24080112205aed986794d0556d605847ef4228d51ea481b6309bc03b7d569a49bd8a475fdc120203011a550a260024080112205aed986794d0556d605847ef4228d51ea481b6309bc03b7d569a49bd8a475fdc10acf5c591061a0b0a090400000000910200011a0b0a090400000000910200021a0b0a090400000000910200032a402390af4379673cdaecddd4012346bfa4b10c91890eef60657801fdc50048b42601ef302f1109076a9f6c01631bc17323bca355cc1042821b4d40519ac9942903b8c30a2408011220b96ddc34a9e5bd1b0956657475d805326bdf0102e2d337f8b65b7fe96180d376120203011a550a26002408011220b96ddc34a9e5bd1b0956657475d805326bdf0102e2d337f8b65b7fe96180d37610acf5c591061a0b0a090400000000910200011a0b0a090400000000910200021a0b0a090400000000910200032a40b863d3640c77feef52cebd9a27d0d8e6cb3427926e089402463033d2d1c6d2808177fb6704082e664625c78dc1d6948e613e9c54511c45c88812a7b5d7d6320b" + check byteutils.toHex(encoded) == "04f901a00101f9019bb8cc0a250802122102339d487ed237392d83791950dc891f0636de698c1fa051ea01ae3fa58bd78580120203011a560a2700250802122102339d487ed237392d83791950dc891f0636de698c1fa051ea01ae3fa58bd78580109cfd8992061a0b0a090400000000910200011a0b0a090400000000910200021a0b0a090400000000910200032a4730450221008cc77fd265e33c955174b9f49628048b2d72a6395acb30f0ba9d90536fa1a5d502207fa8e5bab8e8ddee9884a8e244b0990228e3546b5a9b6848632abd924796e576b8cb0a2508021221026beda5cfddf1cd89130e7b5bb6092bac23db4a044bf847328aa0310dd123a445120203011a560a27002508021221026beda5cfddf1cd89130e7b5bb6092bac23db4a044bf847328aa0310dd123a445109cfd8992061a0b0a090400000000910200011a0b0a090400000000910200021a0b0a090400000000910200032a46304402203d41b1a78c5e6d98c9b4f3fcb213dc16ae4de50a1c8715ab29c516afe6488b4e02205841d09e92b3d2f1ad72c7bc066e561dab57320886f3fbbf272d2cf1732ca259" let decoded = decodeMessage(encoded) check decoded.isOk() @@ -177,9 +184,9 @@ suite "Discovery v5.1 Cryptographic Primitives Test Vectors": sharedSecret = "0x033b11a2a1f214567e1537ce5e509ffd9b21373247f2a3ff6841f4976f53165e7e" let - pub = keys.PublicKey.fromHex(publicKey)[] - priv = keys.PrivateKey.fromHex(secretKey)[] - eph = ecdhRawFull(priv, pub) + pub = PublicKey.fromHex(publicKey).expect("Valid public key hex") + priv = PrivateKey.fromHex(secretKey).expect("Valid private key hex") + eph = ecdhRaw(priv, pub).expect("Valid public and private keys") check: eph.data == hexToSeqByte(sharedSecret) @@ -198,9 +205,10 @@ suite "Discovery v5.1 Cryptographic Primitives Test Vectors": let secrets = deriveKeys( NodeId.fromHex(nodeIdA), NodeId.fromHex(nodeIdB), - keys.PrivateKey.fromHex(ephemeralKey)[], - keys.PublicKey.fromHex(destPubkey)[], - hexToSeqByte(challengeData)) + PrivateKey.fromHex(ephemeralKey).expect("Valid private key hex"), + PublicKey.fromHex(destPubkey).expect("Valid public key hex"), + hexToSeqByte(challengeData) + ).expect("Valid key structure") check: secrets.initiatorKey == hexToByteArray[aesKeySize](initiatorKey) @@ -214,20 +222,23 @@ suite "Discovery v5.1 Cryptographic Primitives Test Vectors": ephemeralPubkey = "0x039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231" nodeIdB = "0xbbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9" # expected output - idSignature = "0x94852a1e2318c4e5e9d422c98eaf19d1d90d876b29cd06ca7cb7546d0fff7b484fe86c09a064fe72bdbef73ba8e9c34df0cd2b53e9d65528c2c7f336d5dfc6e6" + idSignature = "0xdb0ae930a460fd767cb26a519221e6be5edc3501865406d8af6d215f87ebf35b07563d891082d97147d9499f49bb86ee399f57367af1b866674f9e54760e3a21" let - privKey = keys.PrivateKey.fromHex(staticKey)[] + privKey = PrivateKey.fromHex(staticKey).expect("Valid private key hex") signature = createIdSignature( privKey, hexToSeqByte(challengeData), hexToSeqByte(ephemeralPubkey), - NodeId.fromHex(nodeIdB)) + NodeId.fromHex(nodeIdB) + ).expect("Valid signature data") + libp2pSig = SkSignature.init(signature.data).expect("Valid sig data") + skSig = secp256k1.SkSignature(libp2pSig) check: - signature.toRaw() == hexToByteArray[64](idSignature) + skSig.toRaw() == hexToByteArray[64](idSignature) verifyIdSignature(signature, hexToSeqByte(challengeData), hexToSeqByte(ephemeralPubkey), NodeId.fromHex(nodeIdB), - privKey.toPublicKey()) + privKey.getPublicKey.expect("Valid private key for public key")) test "Encryption/Decryption": const @@ -249,17 +260,19 @@ suite "Discovery v5.1 Cryptographic Primitives Test Vectors": # https://github.com/ethereum/devp2p/blob/master/discv5/discv5-wire-test-vectors.md#packet-encodings suite "Discovery v5.1 Packet Encodings Test Vectors": const - nodeAKey = "0xeef77acb6c6a6eebc5b363a475ac583ec7eccdb42b6481424c60f59aa326547f" - nodeBKey = "0x66fb62bfbd66b9177a138c1e5cddbe4f7c30c343e94e68df8769459cb1cde628" + nodeAKey = "0xfe5f08c842aa946659b266ce68faa5d2fd982634594dccdf7f916e3fcf0541a3" + nodeBKey = "0x00064765abe9a4e63b068b5af99c26c61c8ade9bfdae6494873b137ec8152578" var codecA, codecB: Codec nodeA, nodeB: Node - privKeyA, privKeyB: keys.PrivateKey + privKeyA, privKeyB: PrivateKey setup: - privKeyA = keys.PrivateKey.fromHex(nodeAKey)[] # sender -> encode - privKeyB = keys.PrivateKey.fromHex(nodeBKey)[] # receive -> decode + # sender -> encode + privKeyA = PrivateKey.fromHex(nodeAKey).expect("Valid private key hex") + # receive -> decode + privKeyB = PrivateKey.fromHex(nodeBKey).expect("Valid private key hex") let enrRecA = SignedPeerRecord.init(1, privKeyA, @@ -284,9 +297,9 @@ suite "Discovery v5.1 Packet Encodings Test Vectors": pingSprSeq = 2'u64 encodedPacket = - "00000000000000000000000000000000088b3d4342774649325f313964a39e55" & - "ea96c005ad52be8c7560413a7008f16c9e6d2f43bbea8814a546b7409ce783d3" & - "4c4f53245d08dab84102ed931f66d1492acb308fa1c6715b9d139b81acbdcc" + "000000000000000000000000000000003788c1e1079e89374c4beac74d76364d" & + "bd9e8cd1847adc2f49fbacc6862425583586c023b19b6fdd1d836777ee39fee8" & + "7afd279a5fe4ffdded6d1a6d388217da82d38761b60b0c6e9dd94a8713bc5d" let dummyKey = "0x00000000000000000000000000000001" # of no importance codecA.sessions.store(nodeB.id, nodeB.address.get(), @@ -311,8 +324,8 @@ suite "Discovery v5.1 Packet Encodings Test Vectors": whoareyouSprSeq = 0 encodedPacket = - "00000000000000000000000000000000088b3d434277464933a1ccc59f5967ad" & - "1d6035f15e528627dde75cd68292f9e6c27d6b66c8100a873fcbaed4e16b8d" + "000000000000000000000000000000003788c1e1079e89374d4beac74d76364d" & + "bd9e8cd1847ae48d2f96e595c7a904454033dd25eaefc076a4537f17e8a43a" let decoded = codecB.decodePacket(nodeA.address.get(), hexToSeqByte(encodedPacket)) @@ -341,13 +354,13 @@ suite "Discovery v5.1 Packet Encodings Test Vectors": whoareyouSprSeq = 1'u64 encodedPacket = - "00000000000000000000000000000000088b3d4342774649305f313964a39e55" & - "ea96c005ad521d8c7560413a7008f16c9e6d2f43bbea8814a546b7409ce783d3" & - "4c4f53245d08da4bb252012b2cba3f4f374a90a75cff91f142fa9be3e0a5f3ef" & - "268ccb9065aeecfd67a999e7fdc137e062b2ec4a0eb92947f0d9a74bfbf44dfb" & - "a776b21301f8b65efd5796706adff216ab862a9186875f9494150c4ae06fa4d1" & - "f0396c93f215fa4ef524f1eadf5f0f4126b79336671cbcf7a885b1f8bd2a5d83" & - "9cf8" + "000000000000000000000000000000003788c1e1079e89374e4beac74d76364d" & + "bd9e8cd1847a712f49fbacc6862425583586c023b19b6fdd1d836777ee39fee8" & + "7afd279a5fe4ff441af3b17ec968350f37edbda9e0ba8ac0fd2617ef67a1e362" & + "5ea8eb284a3ca85f7ef976ccf2e87932ffeada775849d7aca378033b7a75dbe8" & + "7cc1767123bb7d7e5d96b5d6ad7c26cb55f6160b250d042ef1b9e6000191ce4e" & + "a93234ca3de051518684902e70e6a47eb8f0c2efeca8e42d2ea7f5bd1f27c12d" & + "ae3c579ddcef630659089c99" let whoareyouData = WhoareyouData( @@ -355,7 +368,9 @@ suite "Discovery v5.1 Packet Encodings Test Vectors": idNonce: hexToByteArray[idNonceSize](whoareyouIdNonce), recordSeq: whoareyouSprSeq, challengeData: hexToSeqByte(whoareyouChallengeData)) - pubkey = some(privKeyA.toPublicKey()) + pubkey = privKeyA.getPublicKey + .expect("Valid private key for public key") + .some challenge = Challenge(whoareyouData: whoareyouData, pubkey: pubkey) key = HandshakeKey(nodeId: nodeA.id, address: nodeA.address.get()) @@ -387,18 +402,18 @@ suite "Discovery v5.1 Packet Encodings Test Vectors": whoareyouSprSeq = 0'u64 encodedPacket = - "2746cce362989b5d7e2496490b25f952e9198c524b06c7e9e069c5f7c8d2c84b" & - "943322ac741826023cb35086eee94baaf98f81217c3dbcb022afb1464555b144" & - "69b49cb19fe1f3459b4bbb03a52fc588bcc69d7ff50842ee6c3fc3ffd58d425f" & - "e8c7bec9777fcb15d9c9e37c4aa3b226274f6631526d6d2127f39e1daff277fd" & - "e867a8222ae509922d9e94456f7cbde14c1788894708713789b28b307ac983c8" & - "31ebc00113ded4011af2bfa06078c8f0a3401e8c034b3ae5506fb002a0355bf1" & - "48b19022bae8b088a0c0bdc22dc3d5ce4a6c5ad700a3f8a82be214c2bef98afe" & - "2dbf4ffaaf816602d470dcfe8184b1db8d873d8813984f86b6350ff5d00d466c" & - "06de59f1797ad01a68bb9c07b9cb56e6989ab0e94d32c60e435a48aa7c89d602" & - "3863bd1605a33f895903657fe72f79ded24b366486a1c02a893702ec7d299ea8" & - "7afe0bb771fad244b8d4d0bd7bf4dc833a17c4db2f926eb7614788308a6f98af" & - "9a0e20bd75af75175645058702122b15" + "000000000000000000000000000000003788c1e1079e89374e4beac74d76364d" & + "bd9e8cd1847bc02f49fbacc6862425583586c023b19b6fdd1d836777ee39fee8" & + "7afd279a5fe4ff451af3b07ec8407cedec19c57a8460e08d3d8a908f78261170" & + "68196e7df56279e7493fbb2076025b395dde6ffeecc45daa59def06c9be97b1f" & + "95636fb8f16887cf13b4a8cca0bcaf805fe62529ad86c59204e73917cf183d19" & + "847617448722cc8c0eea80b68653e858eff5d250abbd55315db21fac1485db8f" & + "deaadba582d43c88f0b25512a5fd8395bd2f9519362d29cceb29028de04e0076" & + "4f6aece318e26e2d123888e484cb1c0ce37ecfee42ced9a811966bae40f40e9d" & + "4b46e27c388330304409a405b6455547661361d2129aa7bed4ff26f68d53532d" & + "cb6bae00506a7c5161b0652afcbf2416e97116bdcf9a7a548d6d8b5b0ab2ed0e" & + "b7a737afc0dbf65f32fd22c27cb17ebfe3c0d43e9bf45cfd24170c9fea348b10" & + "1207010ad51e28040b46770c1e96e22e7c552a6f1a62b4e29f8c99" let whoareyouData = WhoareyouData( @@ -406,7 +421,7 @@ suite "Discovery v5.1 Packet Encodings Test Vectors": idNonce: hexToByteArray[idNonceSize](whoareyouIdNonce), recordSeq: whoareyouSprSeq, challengeData: hexToSeqByte(whoareyouChallengeData)) - pubkey = none(keys.PublicKey) + pubkey = none(PublicKey) challenge = Challenge(whoareyouData: whoareyouData, pubkey: pubkey) key = HandshakeKey(nodeId: nodeA.id, address: nodeA.address.get()) @@ -426,7 +441,7 @@ suite "Discovery v5.1 Packet Encodings Test Vectors": hexToSeqByte(encodedPacket & "00")).isErr() suite "Discovery v5.1 Additional Encode/Decode": - var rng = keys.newRng() + var rng = newRng() test "Encryption/Decryption": let @@ -467,8 +482,7 @@ suite "Discovery v5.1 Additional Encode/Decode": var nonce: AESGCMNonce brHmacDrbgGenerate(rng[], nonce) let - privKey = keys.PrivateKey.random(rng[]) - nodeId = privKey.toPublicKey().toNodeId() + nodeId = NodeId.example(rng) authdata = newSeq[byte](32) staticHeader = encodeStaticHeader(Flag.OrdinaryMessage, nonce, authdata.len()) @@ -486,11 +500,11 @@ suite "Discovery v5.1 Additional Encode/Decode": var codecA, codecB: Codec nodeA, nodeB: Node - privKeyA, privKeyB: keys.PrivateKey + privKeyA, privKeyB: PrivateKey setup: - privKeyA = keys.PrivateKey.random(rng[]) # sender -> encode - privKeyB = keys.PrivateKey.random(rng[]) # receiver -> decode + privKeyA = PrivateKey.example(rng) # sender -> encode + privKeyB = PrivateKey.example(rng) # receiver -> decode let enrRecA = SignedPeerRecord.init(1, privKeyA, @@ -528,7 +542,7 @@ suite "Discovery v5.1 Additional Encode/Decode": let recordSeq = 0'u64 let data = encodeWhoareyouPacket(rng[], codecA, nodeB.id, - nodeB.address.get(), requestNonce, recordSeq, none(keys.PublicKey)) + nodeB.address.get(), requestNonce, recordSeq, none(PublicKey)) let decoded = codecB.decodePacket(nodeA.address.get(), data) @@ -551,7 +565,9 @@ suite "Discovery v5.1 Additional Encode/Decode": m = PingMessage(sprSeq: 0) reqId = RequestId.init(rng[]) message = encodeMessage(m, reqId) - pubkey = some(privKeyA.toPublicKey()) + pubkey = privKeyA.getPublicKey + .expect("Valid private key for public key") + .some # Encode/decode whoareyou packet to get the handshake stored and the # whoareyou data returned. It's either that or construct the header for the @@ -561,9 +577,12 @@ suite "Discovery v5.1 Additional Encode/Decode": nodeA.address.get(), requestNonce, recordSeq, pubkey) decodedDummy = codecA.decodePacket(nodeB.address.get(), encodedDummy) - let data = encodeHandshakePacket(rng[], codecA, nodeB.id, - nodeB.address.get(), message, decodedDummy[].whoareyou, - privKeyB.toPublicKey()) + let + pubKeyB = privKeyB.getPublicKey.expect("Valid private key for public key") + data = encodeHandshakePacket(rng[], codecA, nodeB.id, + nodeB.address.get(), message, decodedDummy[].whoareyou, + pubKeyB + ).expect("Valid handshake packet data") let decoded = codecB.decodePacket(nodeA.address.get(), data) @@ -582,7 +601,7 @@ suite "Discovery v5.1 Additional Encode/Decode": m = PingMessage(sprSeq: 0) reqId = RequestId.init(rng[]) message = encodeMessage(m, reqId) - pubkey = none(keys.PublicKey) + pubkey = none(PublicKey) # Encode/decode whoareyou packet to get the handshake stored and the # whoareyou data returned. It's either that or construct the header for the @@ -592,9 +611,12 @@ suite "Discovery v5.1 Additional Encode/Decode": nodeA.address.get(), requestNonce, recordSeq, pubkey) decodedDummy = codecA.decodePacket(nodeB.address.get(), encodedDummy) - let encoded = encodeHandshakePacket(rng[], codecA, nodeB.id, - nodeB.address.get(), message, decodedDummy[].whoareyou, - privKeyB.toPublicKey()) + let + pubKeyB = privKeyB.getPublicKey.expect("Valid private key for public key") + encoded = encodeHandshakePacket(rng[], codecA, nodeB.id, + nodeB.address.get(), message, decodedDummy[].whoareyou, + pubKeyB + ).expect("Valid handshake packet data") let decoded = codecB.decodePacket(nodeA.address.get(), encoded)