2021-12-06 19:51:37 +00:00
|
|
|
|
## 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],
|
2022-11-10 09:29:34 +00:00
|
|
|
|
stew/shims/net,
|
|
|
|
|
std/bitops
|
2021-12-06 19:51:37 +00:00
|
|
|
|
|
|
|
|
|
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/).
|
2022-11-10 09:29:34 +00:00
|
|
|
|
WakuEnrBitfield* = uint8
|
|
|
|
|
|
|
|
|
|
## See: https://rfc.vac.dev/spec/31/#waku2-enr-key
|
|
|
|
|
## each enum numbers maps to a bit (where 0 is the LSB)
|
|
|
|
|
Capabilities* = enum
|
|
|
|
|
Relay = 0,
|
|
|
|
|
Store = 1,
|
|
|
|
|
Filter = 2,
|
|
|
|
|
Lightpush = 3,
|
2021-12-06 19:51:37 +00:00
|
|
|
|
|
2023-02-07 13:06:50 +00:00
|
|
|
|
func getRawField*(multiaddrs: seq[MultiAddress]): seq[byte] =
|
2021-12-06 19:51:37 +00:00
|
|
|
|
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
|
2023-02-07 13:06:50 +00:00
|
|
|
|
|
2021-12-06 19:51:37 +00:00
|
|
|
|
assert maSize.len == 2
|
|
|
|
|
|
|
|
|
|
fieldRaw.add(concat(@maSize, maRaw))
|
2023-02-07 13:06:50 +00:00
|
|
|
|
|
|
|
|
|
return fieldRaw
|
|
|
|
|
|
|
|
|
|
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/
|
|
|
|
|
let fieldRaw = multiaddrs.getRawField()
|
|
|
|
|
|
2021-12-06 19:51:37 +00:00
|
|
|
|
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[])
|
2023-02-07 13:06:50 +00:00
|
|
|
|
|
2021-12-06 19:51:37 +00:00
|
|
|
|
return cleanAddr
|
|
|
|
|
|
2023-02-07 13:06:50 +00:00
|
|
|
|
func stripPeerIds*(multiaddrs: seq[MultiAddress]): seq[MultiAddress] =
|
2021-12-06 19:51:37 +00:00
|
|
|
|
var cleanAddrs: seq[MultiAddress]
|
|
|
|
|
|
|
|
|
|
for multiaddr in multiaddrs:
|
|
|
|
|
if multiaddr.contains(multiCodec("p2p"))[]:
|
|
|
|
|
cleanAddrs.add(multiaddr.stripPeerId())
|
|
|
|
|
else:
|
|
|
|
|
cleanAddrs.add(multiaddr)
|
2023-02-07 13:06:50 +00:00
|
|
|
|
|
2021-12-06 19:51:37 +00:00
|
|
|
|
return cleanAddrs
|
|
|
|
|
|
|
|
|
|
func readBytes(rawBytes: seq[byte], numBytes: int, pos: var int = 0): Result[seq[byte], cstring] =
|
2023-02-07 13:06:50 +00:00
|
|
|
|
## Attempts to read `numBytes` from a sequence, from
|
2021-12-06 19:51:37 +00:00
|
|
|
|
## position `pos`. Returns the requested slice or
|
|
|
|
|
## an error if `rawBytes` boundary is exceeded.
|
2023-02-07 13:06:50 +00:00
|
|
|
|
##
|
2021-12-06 19:51:37 +00:00
|
|
|
|
## If successful, `pos` is advanced by `numBytes`
|
|
|
|
|
|
|
|
|
|
if rawBytes[pos..^1].len() < numBytes:
|
|
|
|
|
return err("Exceeds maximum available bytes")
|
2023-02-07 13:06:50 +00:00
|
|
|
|
|
2021-12-06 19:51:37 +00:00
|
|
|
|
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)
|
|
|
|
|
|
2022-11-10 09:29:34 +00:00
|
|
|
|
# TODO: With the changes in this PR, this can be refactored? Using the enum?
|
|
|
|
|
# Perhaps refactor to:
|
2023-02-07 13:06:50 +00:00
|
|
|
|
# WaKuEnr.enr.Record.init(..., capabilities=[Store, Lightpush])
|
|
|
|
|
# WaKuEnr.enr.Record.init(..., capabilities=[Store, Lightpush, Relay, Filter])
|
2022-11-10 09:29:34 +00:00
|
|
|
|
|
|
|
|
|
# Safer also since we dont inject WakuEnrBitfield, and we let this package
|
|
|
|
|
# handle the bits according to the capabilities
|
|
|
|
|
|
2021-12-06 19:51:37 +00:00
|
|
|
|
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
|
|
|
|
|
|
2023-02-07 13:06:50 +00:00
|
|
|
|
func init*(T: type enr.Record,
|
|
|
|
|
privateKey: crypto.PrivateKey,
|
|
|
|
|
enrIp: Option[ValidIpAddress],
|
|
|
|
|
enrTcpPort, enrUdpPort: Option[Port],
|
|
|
|
|
wakuFlags = none(WakuEnrBitfield),
|
|
|
|
|
multiaddrs: seq[MultiAddress] = @[]): T =
|
|
|
|
|
|
2021-12-06 19:51:37 +00:00
|
|
|
|
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")
|
2023-02-07 13:06:50 +00:00
|
|
|
|
|
2021-12-06 19:51:37 +00:00
|
|
|
|
return enr
|
2022-11-10 09:29:34 +00:00
|
|
|
|
|
2023-02-07 13:06:50 +00:00
|
|
|
|
proc supportsCapability*(r: Record, capability: Capabilities): bool =
|
2022-11-10 09:29:34 +00:00
|
|
|
|
let enrCapabilities = r.get(WAKU_ENR_FIELD, seq[byte])
|
|
|
|
|
if enrCapabilities.isOk():
|
|
|
|
|
return testBit(enrCapabilities.get()[0], capability.ord)
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
proc getCapabilities*(r: Record): seq[Capabilities] =
|
2023-02-07 13:06:50 +00:00
|
|
|
|
return toSeq(Capabilities.low..Capabilities.high).filterIt(r.supportsCapability(it))
|