2025-11-28 16:09:02 +08:00

234 lines
7.8 KiB
Nim

import blake2
import chronicles
import chronos
import sds
import std/[sequtils, strutils, strformat]
import std/algorithm
import sugar
import tables
import ../conversation_store
import ../crypto
import ../delivery/waku_client
import ../[
identity,
errors,
proto_types,
types,
utils
]
import convo_type
import message
import ../../naxolotl as nax
type
ReceivedPrivateV1Message* = ref object of ReceivedMessage
proc initReceivedMessage(sender: PublicKey, timestamp: int64, content: ContentFrame) : ReceivedPrivateV1Message =
ReceivedPrivateV1Message(sender:sender, timestamp:timestamp, content:content)
type
PrivateV1* = ref object of Conversation
ds: WakuClient
sdsClient: ReliabilityManager
owner: Identity
topic: string
participant: PublicKey
discriminator: string
doubleratchet: naxolotl.Doubleratchet
proc getTopic*(self: PrivateV1): string =
## Returns the topic for the PrivateV1 conversation.
return self.topic
proc allParticipants(self: PrivateV1): seq[PublicKey] =
return @[self.owner.getPubkey(), self.participant]
proc getConvoIdRaw(participants: seq[PublicKey],
discriminator: string): string =
# This is a placeholder implementation.
var addrs = participants.map(x => x.get_addr());
addrs.sort()
addrs.add(discriminator)
let raw = addrs.join("|")
return utils.hash_func(raw)
proc getConvoId*(self: PrivateV1): string =
return getConvoIdRaw(@[self.owner.getPubkey(), self.participant], self.discriminator)
proc derive_topic(participants: seq[PublicKey], discriminator: string): string =
## Derives a topic from the participants' public keys.
return "/convo/private/" & getConvoIdRaw(participants, discriminator)
proc calcMsgId(self: PrivateV1, msgBytes: seq[byte]): string =
let s = fmt"{self.getConvoId()}|{msgBytes}"
result = getBlake2b(s, 16, "")
proc encrypt*(convo: PrivateV1, plaintext: var seq[byte]): EncryptedPayload =
let (header, ciphertext) = convo.doubleratchet.encrypt(plaintext) #TODO: Associated Data
result = EncryptedPayload(doubleratchet: proto_types.DoubleRatchet(
dh: toSeq(header.dhPublic),
msgNum: header.msgNumber,
prevChainLen: header.prevChainLen,
ciphertext: ciphertext)
)
proc decrypt*(convo: PrivateV1, enc: EncryptedPayload): Result[seq[byte], ChatError] =
# Ensure correct type as received
if enc.doubleratchet.ciphertext == @[]:
return err(ChatError(code: errTypeError, context: "Expected doubleratchet encrypted payload got ???"))
let dr = enc.doubleratchet
var header = DrHeader(
msgNumber: dr.msgNum,
prevChainLen: dr.prevChainLen
)
copyMem(addr header.dhPublic[0], unsafeAddr dr.dh[0], dr.dh.len) # TODO: Avoid this copy
if convo.doubleratchet.dhSelf.public == header.dhPublic:
info "outgoing message, no need to decrypt"
return err(ChatError(code: errDecryptOutgoing, context: "Attempted to decrypt outgoing message"))
convo.doubleratchet.decrypt(header, dr.ciphertext, @[]).mapErr(proc(e: NaxolotlError): ChatError = ChatError(code: errWrapped, context: repr(e) ))
proc wireCallbacks(convo: PrivateV1, deliveryAckCb: proc(
conversation: Conversation,
msgId: string): Future[void] {.async.} = nil) =
## Accepts lambdas/functions to be called from Reliability Manager callbacks.
let funcMsg = proc(messageId: SdsMessageID,
channelId: SdsChannelID) {.gcsafe.} =
debug "sds message ready", messageId = messageId,
channelId = channelId
let funcDeliveryAck = proc(messageId: SdsMessageID,
channelId: SdsChannelID) {.gcsafe.} =
debug "sds message ack", messageId = messageId,
channelId = channelId, cb = repr(deliveryAckCb)
if deliveryAckCb != nil:
asyncSpawn deliveryAckCb(convo, messageId)
let funcDroppedMsg = proc(messageId: SdsMessageID, missingDeps: seq[
SdsMessageID], channelId: SdsChannelID) {.gcsafe.} =
debug "sds message missing", messageId = messageId,
missingDeps = missingDeps, channelId = channelId
convo.sdsClient.setCallbacks(
funcMsg, funcDeliveryAck, funcDroppedMsg
)
proc initPrivateV1*(owner: Identity, ds:WakuClient, participant: PublicKey, seedKey: array[32, byte],
discriminator: string = "default", isSender: bool, deliveryAckCb: proc(
conversation: Conversation,
msgId: string): Future[void] {.async.} = nil):
PrivateV1 =
var participants = @[owner.getPubkey(), participant];
var rm = newReliabilityManager().valueOr:
raise newException(ValueError, fmt"sds initialization: {repr(error)}")
result = PrivateV1(
ds: ds,
sdsClient: rm,
owner: owner,
topic: derive_topic(participants, discriminator),
participant: participant,
discriminator: discriminator,
doubleratchet: initDoubleratchet(seedKey, owner.privateKey.bytes, participant.bytes, isSender)
)
result.wireCallbacks(deliveryAckCb)
result.sdsClient.ensureChannel(result.getConvoId()).isOkOr:
raise newException(ValueError, "bad sds channel")
proc initPrivateV1Sender*(owner:Identity, ds: WakuClient, participant: PublicKey, seedKey: array[32, byte], deliveryAckCb: proc(
conversation: Conversation, msgId: string): Future[void] {.async.} = nil): PrivateV1 =
initPrivateV1(owner, ds, participant, seedKey, "default", true, deliveryAckCb)
proc initPrivateV1Recipient*(owner:Identity,ds: WakuClient, participant: PublicKey, seedKey: array[32, byte], deliveryAckCb: proc(
conversation: Conversation, msgId: string): Future[void] {.async.} = nil): PrivateV1 =
initPrivateV1(owner,ds, participant, seedKey, "default", false, deliveryAckCb)
proc sendFrame(self: PrivateV1, ds: WakuClient,
msg: PrivateV1Frame): Future[MessageId]{.async.} =
let frameBytes = encode(msg)
let msgId = self.calcMsgId(frameBytes)
var sdsPayload = self.sdsClient.wrapOutgoingMessage(frameBytes, msgId,
self.getConvoId()).valueOr:
raise newException(ValueError, fmt"sds wrapOutgoingMessage failed: {repr(error)}")
let encryptedPayload = self.encrypt(sdsPayload)
discard ds.sendPayload(self.getTopic(), encryptedPayload.toEnvelope(
self.getConvoId()))
result = msgId
method id*(self: PrivateV1): string =
return getConvoIdRaw(self.allParticipants(), self.discriminator)
proc handleFrame*[T: ConversationStore](convo: PrivateV1, client: T,
bytes: seq[byte]) =
## Dispatcher for Incoming `PrivateV1Frames`.
## Calls further processing depending on the kind of frame.
let enc = decode(bytes, EncryptedPayload).valueOr:
raise newException(ValueError, fmt"Failed to decode EncryptedPayload: {repr(error)}")
let plaintext = convo.decrypt(enc).valueOr:
error "decryption failed", error = error
return
let (frameData, missingDeps, channelId) = convo.sdsClient.unwrapReceivedMessage(
plaintext).valueOr:
raise newException(ValueError, fmt"Failed to unwrap SDS message:{repr(error)}")
debug "sds unwrap", convo = convo.id(), missingDeps = missingDeps,
channelId = channelId
let frame = decode(frameData, PrivateV1Frame).valueOr:
raise newException(ValueError, "Failed to decode SdsM: " & error)
if frame.sender == @(convo.owner.getPubkey().bytes()):
notice "Self Message", convo = convo.id()
return
case frame.getKind():
of typeContentFrame:
# TODO: Using client.getId() results in an error in this context
client.notifyNewMessage(convo, initReceivedMessage(convo.participant, frame.timestamp, frame.content))
of typePlaceholder:
notice "Got Placeholder", text = frame.placeholder.counter
method sendMessage*(convo: PrivateV1, content_frame: ContentFrame) : Future[MessageId] {.async.} =
try:
let frame = PrivateV1Frame(sender: @(convo.owner.getPubkey().bytes()),
timestamp: getCurrentTimestamp(), content: content_frame)
result = await convo.sendFrame(convo.ds, frame)
except Exception as e:
error "Unknown error in PrivateV1:SendMessage"