status-node-manager/libs/waku_utils/waku_handshake_utils.nim

249 lines
10 KiB
Nim

import
# Nimble packages
stew/[byteutils], chronicles, chronos, confutils,
# Nimble packages - Waku
waku/waku_noise/[noise_types, noise_utils, noise_handshake_processing],
waku/utils/noise,
waku/[waku_core, waku_node],
waku/common/[logging, protobuf],
waku/node/peer_manager
type
AgentKeysAndCommitment* = object
staticKey*: noise_types.KeyPair
ephemeralKey*: noise_types.KeyPair
commitment*: seq[byte]
committedStaticKey*: MDigest[256]
ContentTopicInfo* = object
applicationName*: string
applicationVersion*: string
shardId*: string
proc initAgentKeysAndCommitment*(rng: ref HmacDrbgContext): AgentKeysAndCommitment =
let staticKey = genKeyPair(rng[])
let commitment = randomSeqByte(rng[], 32)
AgentKeysAndCommitment(staticKey: staticKey,
ephemeralKey: genKeyPair(rng[]),
commitment: commitment,
committedStaticKey:
commitPublicKey(getPublicKey(staticKey), commitment))
proc initQr*(rng: ref HmacDrbgContext, contentTopicInfo: ContentTopicInfo,
agentInfo: AgentKeysAndCommitment
): tuple[qr: string, qrMessageNametag: seq[byte]] =
let qr = toQr(contentTopicInfo.applicationName,
contentTopicInfo.applicationVersion,
contentTopicInfo.shardId,
getPublicKey(agentInfo.ephemeralKey),
agentInfo.committedStaticKey)
let qrMessageNametag = randomSeqByte(rng[], MessageNametagLength)
(qr, qrMessageNametag)
proc initContentTopicFromQr*(qr: string): ContentTopic =
let (readApplicationName, readApplicationVersion, readShardId, _, _) = fromQr(qr)
let contentTopic = "/" & readApplicationName & "/" &
readApplicationVersion & "/" &
readShardId & "/proto"
return contentTopic
proc initHS*(agentInfo: AgentKeysAndCommitment, qr: string,
isInitiator: bool = false): HandshakeState =
let
hsPattern = NoiseHandshakePatterns["WakuPairing"]
(_, _, _, readEphemeralKey, _) = fromQr(qr)
preMessagePKs: seq[NoisePublicKey] = @[toNoisePublicKey(readEphemeralKey)]
initialize(hsPattern = hsPattern,
ephemeralKey = agentInfo.ephemeralKey,
staticKey = agentInfo.staticKey,
prologue = qr.toBytes,
preMessagePKs = preMessagePKs,
initiator = isInitiator)
proc prepareHandShakeInitiatorMsg*(rng: ref HmacDrbgContext,
contentTopic: string,
agentInfo: AgentKeysAndCommitment,
qrMessageNametag: seq[byte],
agentMessageNametag: var MessageNametag,
agentHS: var HandshakeState,
initiatorStep: var HandshakeStepResult
): Result[WakuMessage, cstring] =
##############################
# 1st step #
# #
# -> eA, eAeB {H(sA||s)}] #
##############################
# The messageNametag for the first handshake message is randomly generated
# and exchanged out-of-band and corresponds to qrMessageNametag
let transportMessage = digestToSeq(agentInfo.committedStaticKey)
# By being the handshake initiator, this agent writes a Waku2 payload v2
# containing handshake message and the (encrypted) transport message
# The message is sent with a messageNametag equal to the one received through
# the QR code
initiatorStep = stepHandshake(rng[], agentHS,
transportMessage = transportMessage,
messageNametag = qrMessageNametag).get()
# We prepare a Waku message from the initiators's payload2
let wakuMsg = encodePayloadV2(initiatorStep.payload2, contentTopic)
assert wakuMsg.isOk()
assert wakuMsg.get().contentTopic == contentTopic
agentMessageNametag = toMessageNametag(agentHS)
wakuMsg
proc publishHandShakeInitiatorMsg*(node: WakuNode,
pubSubTopic: PubsubTopic,
contentTopic: ContentTopic,
message: WakuMessage) {.async.} =
notice "Publishing handshake initiator message", step = 1
discard await node.publish(some(pubSubTopic), message)
notice "Published handshake initiator message",
step = 1,
psTopic = pubSubTopic,
contentTopic = contentTopic,
payload = message.payload
proc handleHandShakeInitiatorMsg*(rng: ref HmacDrbgContext,
pubSubTopic: PubsubTopic,
contentTopic: ContentTopic,
payload: PayloadV2,
receiverStep: var HandshakeStepResult,
receiverHS: var HandshakeState,
receiverMessageNametag: var MessageNametag,
qrMessageNametag: seq[byte]) =
notice "Received handshake initiator message", step = 1,
psTopic = pubSubTopic,
contentTopic = contentTopic,
payload = payload
notice "Handling handshake initiator message", step = 1
# The Receiver reads the Initiator's payloads, and returns the (decrypted) transport
# message the Initiator sent to him
# Note that the Receiver verifies if the received payloadv2 has the expected messageNametag set
receiverStep = stepHandshake(rng[], receiverHS,
readPayloadV2 = payload,
messageNametag = qrMessageNametag).get()
receiverMessageNametag = toMessageNametag(receiverHS)
proc prepareHandShakeMsg*(rng: ref HmacDrbgContext,
contentTopic: string,
agentInfo: AgentKeysAndCommitment,
agentMessageNametag: var MessageNametag,
agentHS: var HandshakeState,
agentStep: var HandshakeStepResult,
step: int
): Result[WakuMessage, cstring] =
###################### ##########################
# 2nd step # # 3rd step #
# # or # #
# <- sB, eAsB {r} # # -> sA, sAeB, sAsB {s} #
###################### ##########################
notice "Preparing handshake message for step:", step = step
let transportMessage = agentInfo.commitment
agentStep = stepHandshake(rng[], agentHS,
transportMessage = transportMessage,
messageNametag = agentMessageNametag).get()
let wakuMsg = encodePayloadV2(agentStep.payload2, contentTopic)
assert wakuMsg.isOk()
assert wakuMsg.get().contentTopic == contentTopic
agentMessageNametag = toMessageNametag(agentHS)
wakuMsg
proc publishHandShakeMsg*(node: WakuNode,
pubSubTopic: PubsubTopic,
contentTopic: ContentTopic,
message: WakuMessage,
step: int) {.async.} =
notice "Publishing handshake message for step:", step = step
discard await node.publish(some(pubSubTopic), message)
notice "Published handshake message for step:", step = step,
psTopic = pubSubTopic,
contentTopic = contentTopic,
message = message
proc handleHandShakeMsg*(rng: ref HmacDrbgContext,
pubSubTopic: PubsubTopic,
contentTopic: ContentTopic,
step: int,
payload: PayloadV2,
initiatorStep: var HandshakeStepResult,
initiatorHS: var HandshakeState,
initiatorMessageNametag: var MessageNametag) =
notice "Received handshake message for step:", step = step,
psTopic = pubSubTopic, contentTopic = contentTopic, payload = payload
notice "Handling handshake message for step:", step = step
initiatorStep = stepHandshake(rng[], initiatorHS,
readPayloadV2 = payload,
messageNametag = initiatorMessageNametag).get()
initiatorMessageNametag = toMessageNametag(initiatorHS)
proc initiatorHandshake*(rng: ref HmacDrbgContext, node: WakuNode,
pubSubTopic: PubsubTopic,
contentTopic: ContentTopic,
qr: string, qrMessageNameTag: seq[byte],
initiatorInfo: AgentKeysAndCommitment
): Future[HandshakeResult] {.async.} =
var
readyForFinalization = false
initiatorMessageNametag: MessageNametag
initiatorStep: HandshakeStepResult
initiatorHS: HandshakeState = initHS(initiatorInfo, qr, true)
initiatorHSResult: HandshakeResult
let initialMessage =
prepareHandShakeInitiatorMsg(rng, contentTopic, initiatorInfo,
qrMessageNameTag, initiatorMessageNametag,
initiatorHS, initiatorStep)
proc initiatorHandshakeHandler(topic: PubsubTopic, msg: WakuMessage): Future[
void] {.async.} =
if msg.contentTopic == contentTopic:
let readPayloadV2 = decodePayloadV2(msg).get()
if readPayloadV2.messageNametag == initiatorMessageNametag:
handleHandShakeMsg(rng, pubSubTopic, contentTopic, step = 2,
readPayloadV2, initiatorStep, initiatorHS,
initiatorMessageNametag)
let handShakeMsgStep3 = prepareHandShakeMsg(rng, contentTopic,
initiatorInfo,
initiatorMessageNametag,
initiatorHS,
initiatorStep, step = 3)
await publishHandShakeMsg(node, pubSubTopic, contentTopic,
handShakeMsgStep3.get(), 3)
readyForFinalization = true
node.subscribe((kind: PubsubSub, topic: pubsubTopic),
some(initiatorHandshakeHandler))
await publishHandShakeInitiatorMsg(node, pubSubTopic, contentTopic,
initialMessage.get())
while true:
if readyForFinalization:
node.unsubscribe((kind: PubsubSub, topic: pubsubTopic))
notice "Finalizing handshake"
initiatorHSResult = finalizeHandshake(initiatorHS)
notice "Handshake finalized successfully"
break
await sleepAsync(2000)
return initiatorHSResult