nwaku/waku/v2/utils/wakuenr.nim

154 lines
4.4 KiB
Nim

## Collection of utilities related to Waku's use of EIP-778 ENR
## Implemented according to the specified Waku v2 ENR usage
## More at https://rfc.vac.dev/spec/31/
{.push raises: [Defect]}
import
std/[bitops, sequtils],
eth/keys,
eth/p2p/discoveryv5/enr,
libp2p/[multiaddress, multicodec],
libp2p/crypto/crypto,
stew/[endians2, results],
stew/shims/net
export enr, crypto, multiaddress, net
const
MULTIADDR_ENR_FIELD* = "multiaddrs"
WAKU_ENR_FIELD* = "waku2"
type
## 8-bit flag field to indicate Waku capabilities.
## Only the 4 LSBs are currently defined according
## to RFC31 (https://rfc.vac.dev/spec/31/).
WakuEnrBitfield* = uint8
func toFieldPair(multiaddrs: seq[MultiAddress]): FieldPair =
## Converts a seq of multiaddrs to a `multiaddrs` ENR
## field pair according to https://rfc.vac.dev/spec/31/
var fieldRaw: seq[byte]
for multiaddr in multiaddrs:
let
maRaw = multiaddr.data.buffer # binary encoded multiaddr
maSize = maRaw.len.uint16.toBytes(Endianness.bigEndian) # size as Big Endian unsigned 16-bit integer
assert maSize.len == 2
fieldRaw.add(concat(@maSize, maRaw))
return toFieldPair(MULTIADDR_ENR_FIELD, fieldRaw)
func stripPeerId(multiaddr: MultiAddress): MultiAddress =
var cleanAddr = MultiAddress.init()
for item in multiaddr.items:
if item[].protoName()[] != "p2p":
# Add all parts except p2p peerId
discard cleanAddr.append(item[])
return cleanAddr
func stripPeerIds(multiaddrs: seq[MultiAddress]): seq[MultiAddress] =
var cleanAddrs: seq[MultiAddress]
for multiaddr in multiaddrs:
if multiaddr.contains(multiCodec("p2p"))[]:
cleanAddrs.add(multiaddr.stripPeerId())
else:
cleanAddrs.add(multiaddr)
return cleanAddrs
func readBytes(rawBytes: seq[byte], numBytes: int, pos: var int = 0): Result[seq[byte], cstring] =
## Attempts to read `numBytes` from a sequence, from
## position `pos`. Returns the requested slice or
## an error if `rawBytes` boundary is exceeded.
##
## If successful, `pos` is advanced by `numBytes`
if rawBytes[pos..^1].len() < numBytes:
return err("Exceeds maximum available bytes")
let slicedSeq = rawBytes[pos..<pos+numBytes]
pos += numBytes
return ok(slicedSeq)
################
# Public utils #
################
func initWakuFlags*(lightpush, filter, store, relay: bool): WakuEnrBitfield =
## Creates an waku2 ENR flag bit field according to RFC 31 (https://rfc.vac.dev/spec/31/)
var v = 0b0000_0000'u8
if lightpush: v.setBit(3)
if filter: v.setBit(2)
if store: v.setBit(1)
if relay: v.setBit(0)
return v.WakuEnrBitfield
func toMultiAddresses*(multiaddrsField: seq[byte]): seq[MultiAddress] =
## Parses a `multiaddrs` ENR field according to
## https://rfc.vac.dev/spec/31/
var multiaddrs: seq[MultiAddress]
let totalLen = multiaddrsField.len()
if totalLen < 2:
return multiaddrs
var pos = 0
while pos < totalLen:
let addrLenRes = multiaddrsField.readBytes(2, pos)
if addrLenRes.isErr():
return multiaddrs
let addrLen = uint16.fromBytesBE(addrLenRes.get())
if addrLen == 0.uint16:
# Ensure pos always advances and we don't get stuck in infinite loop
return multiaddrs
let addrRaw = multiaddrsField.readBytes(addrLen.int, pos)
if addrRaw.isErr():
return multiaddrs
let multiaddr = MultiAddress.init(addrRaw.get())
if multiaddr.isErr():
return multiaddrs
multiaddrs.add(multiaddr.get())
return multiaddrs
func initEnr*(privateKey: crypto.PrivateKey,
enrIp: Option[ValidIpAddress],
enrTcpPort, enrUdpPort: Option[Port],
wakuFlags = none(WakuEnrBitfield),
multiaddrs: seq[MultiAddress] = @[]): enr.Record =
assert privateKey.scheme == PKScheme.Secp256k1
## Waku-specific ENR fields (https://rfc.vac.dev/spec/31/)
var wakuEnrFields: seq[FieldPair]
# `waku2` field
if wakuFlags.isSome:
wakuEnrFields.add(toFieldPair(WAKU_ENR_FIELD, @[wakuFlags.get().byte]))
# `multiaddrs` field
if multiaddrs.len > 0:
wakuEnrFields.add(multiaddrs.stripPeerIds().toFieldPair)
let
rawPk = privateKey.getRawBytes().expect("Private key is valid")
pk = keys.PrivateKey.fromRaw(rawPk).expect("Raw private key is of valid length")
enr = enr.Record.init(1, pk,
enrIp, enrTcpPort, enrUdpPort,
wakuEnrFields).expect("Record within size limits")
return enr