# Nim-LibP2P # Copyright (c) 2023 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) # * MIT license ([LICENSE-MIT](LICENSE-MIT)) # at your option. # This file may not be copied, modified, or distributed except according to # those terms. ## This module implementes API for libp2p peer. {.push raises: [].} {.push public.} import std/[hashes, strutils], stew/[base58, results], chronicles, nimcrypto/utils, utility, ./crypto/crypto, ./multicodec, ./multihash, ./vbuffer, ./protobuf/minprotobuf export results, utility const maxInlineKeyLength* = 42 type PeerId* = object data*: seq[byte] func `$`*(pid: PeerId): string = ## Return base58 encoded ``pid`` representation. # This unusual call syntax is used to avoid a strange Nim compilation error base58.encode(Base58, pid.data) func shortLog*(pid: PeerId): string = ## Returns compact string representation of ``pid``. var spid = $pid if len(spid) > 10: spid[3] = '*' spid.delete(4 .. spid.high - 6) spid chronicles.formatIt(PeerId): shortLog(it) func toBytes*(pid: PeerId, data: var openArray[byte]): int = ## Store PeerId ``pid`` to array of bytes ``data``. ## ## Returns number of bytes needed to store ``pid``. result = len(pid.data) if len(data) >= result and result > 0: copyMem(addr data[0], unsafeAddr pid.data[0], result) template getBytes*(pid: PeerId): seq[byte] = ## Return PeerId ``pid`` as array of bytes. pid.data func hex*(pid: PeerId): string = ## Returns hexadecimal string representation of ``pid``. toHex(pid.data) template len*(pid: PeerId): int = ## Returns length of ``pid`` binary representation. len(pid.data) func cmp*(a, b: PeerId): int = ## Compares two peer ids ``a`` and ``b``. ## Returns: ## ## | 0 iff a == b ## | < 0 iff a < b ## | > 0 iff a > b var i = 0 var m = min(len(a.data), len(b.data)) while i < m: result = ord(a.data[i]) - ord(b.data[i]) if result != 0: return inc(i) result = len(a.data) - len(b.data) template `<=`*(a, b: PeerId): bool = (cmp(a, b) <= 0) template `<`*(a, b: PeerId): bool = (cmp(a, b) < 0) template `>=`*(a, b: PeerId): bool = (cmp(a, b) >= 0) template `>`*(a, b: PeerId): bool = (cmp(a, b) > 0) template `==`*(a, b: PeerId): bool = (cmp(a, b) == 0) template hash*(pid: PeerId): Hash = hash(pid.data) func validate*(pid: PeerId): bool = ## Validate check if ``pid`` is empty or not. len(pid.data) > 0 and MultiHash.validate(pid.data) func hasPublicKey*(pid: PeerId): bool = ## Returns ``true`` if ``pid`` is small enough to hold public key inside. if len(pid.data) > 0: var mh: MultiHash if MultiHash.decode(pid.data, mh).isOk: if mh.mcodec == multiCodec("identity"): result = true func extractPublicKey*(pid: PeerId, pubkey: var PublicKey): bool = ## Returns ``true`` if public key was successfully decoded from PeerId ## ``pid``and stored to ``pubkey``. ## ## Returns ``false`` otherwise. if len(pid.data) > 0: var mh: MultiHash if MultiHash.decode(pid.data, mh).isOk: if mh.mcodec == multiCodec("identity"): let length = len(mh.data.buffer) result = pubkey.init(mh.data.buffer.toOpenArray(mh.dpos, length - 1)) func init*(pid: var PeerId, data: openArray[byte]): bool = ## Initialize peer id from raw binary representation ``data``. ## ## Returns ``true`` if peer was successfully initialiazed. var p = PeerId(data: @data) if p.validate(): pid = p result = true func init*(pid: var PeerId, data: string): bool = ## Initialize peer id from base58 encoded string representation. ## ## Returns ``true`` if peer was successfully initialiazed. var p = newSeq[byte](len(data) + 4) var length = 0 if Base58.decode(data, p, length) == Base58Status.Success: p.setLen(length) var opid: PeerId opid.data = p if opid.validate(): pid = opid result = true func init*(t: typedesc[PeerId], data: openArray[byte]): Result[PeerId, cstring] = ## Create new peer id from raw binary representation ``data``. var res: PeerId if not init(res, data): err("peerid: incorrect PeerId binary form") else: ok(res) func init*(t: typedesc[PeerId], data: string): Result[PeerId, cstring] = ## Create new peer id from base58 encoded string representation ``data``. var res: PeerId if not init(res, data): err("peerid: incorrect PeerId string") else: ok(res) func init*(t: typedesc[PeerId], pubkey: PublicKey): Result[PeerId, cstring] = ## Create new peer id from public key ``pubkey``. var pubraw = ? pubkey.getBytes().orError( cstring("peerid: failed to get bytes from given key")) var mh: MultiHash if len(pubraw) <= maxInlineKeyLength: mh = ? MultiHash.digest("identity", pubraw) else: mh = ? MultiHash.digest("sha2-256", pubraw) ok(PeerId(data: mh.data.buffer)) func init*(t: typedesc[PeerId], seckey: PrivateKey): Result[PeerId, cstring] = ## Create new peer id from private key ``seckey``. PeerId.init(? seckey.getPublicKey().orError(cstring("invalid private key"))) proc random*(t: typedesc[PeerId], rng = newRng()): Result[PeerId, cstring] = ## Create new peer id with random public key. let randomKey = PrivateKey.random(Secp256k1, rng[])[] PeerId.init(randomKey).orError(cstring("failed to generate random key")) func match*(pid: PeerId, pubkey: PublicKey): bool = ## Returns ``true`` if ``pid`` matches public key ``pubkey``. let p = PeerId.init(pubkey) if p.isErr: false else: pid == p.get() func match*(pid: PeerId, seckey: PrivateKey): bool = ## Returns ``true`` if ``pid`` matches private key ``seckey``. let p = PeerId.init(seckey) if p.isErr: false else: pid == p.get() ## Serialization/Deserialization helpers func write*(vb: var VBuffer, pid: PeerId) = ## Write PeerId value ``peerid`` to buffer ``vb``. vb.writeSeq(pid.data) func write*(pb: var ProtoBuffer, field: int, pid: PeerId) = ## Write PeerId value ``peerid`` to object ``pb`` using ProtoBuf's encoding. write(pb, field, pid.data) func getField*(pb: ProtoBuffer, field: int, pid: var PeerId): ProtoResult[bool] {.inline.} = ## Read ``PeerId`` from ProtoBuf's message and validate it var buffer: seq[byte] let res = ? pb.getField(field, buffer) if not(res): ok(false) else: var peerId: PeerId if peerId.init(buffer): pid = peerId ok(true) else: err(ProtoError.IncorrectBlob)