Remove stale files

This commit is contained in:
Jazz Turner-Baggs 2026-02-11 09:42:25 -08:00
parent e55518c3e5
commit e7a938a7c2
No known key found for this signature in database
10 changed files with 0 additions and 739 deletions

View File

@ -1,16 +0,0 @@
import ./conversations/[convo_type, message]
import identity
import types
type ConvoId = string
type
ConversationStore* = concept
proc addConversation(self: Self, convo: Conversation)
proc getConversation(self: Self, convoId: string): Conversation
proc identity(self: Self): Identity
proc getId(self: Self): string
proc notifyNewMessage(self: Self, convo: Conversation, msg: ReceivedMessage)
proc notifyDeliveryAck(self: Self, convo: Conversation,
msgId: MessageId)

View File

@ -1,5 +0,0 @@
import
./conversations/[convo_type, private_v1, message]
export private_v1, convo_type, message

View File

@ -1,28 +0,0 @@
import ../conversation_store
import ../conversations
import ../inbox
proc getType(convo: Conversation): ConvoTypes =
if convo of Inbox:
return InboxV1Type
elif convo of PrivateV1:
return PrivateV1Type
else:
raise newException(Defect, "Conversation Type not processed")
proc handleFrame*[T: ConversationStore](convo: Conversation, client: T,
bytes: seq[byte]) =
case convo.getType():
of InboxV1Type:
let inbox = Inbox(convo)
inbox.handleFrame(client, bytes)
of PrivateV1Type:
let priv = PrivateV1(convo)
priv.handleFrame(client, bytes)

View File

@ -1,29 +0,0 @@
import chronos
import strformat
import strutils
import ../proto_types
import ../utils
import ../types
type
ConvoTypes* = enum
InboxV1Type, PrivateV1Type
type
Conversation* = ref object of RootObj
name: string
proc `$`(conv: Conversation): string =
fmt"Convo: {conv.name}"
# TODO: Removing the raises clause and the exception raise causes this
# error --> ...src/chat_sdk/client.nim(166, 9) Error: addConversation(client, convo) can raise an unlisted exception: Exception
# Need better understanding of NIMs Exception model
method id*(self: Conversation): string {.raises: [Defect, ValueError].} =
# TODO: make this a compile time check
panic("ProgramError: Missing concrete implementation")
method sendMessage*(convo: Conversation, content_frame: Content) : Future[MessageId] {.async, base, gcsafe.} =
# TODO: make this a compile time check
panic("ProgramError: Missing concrete implementation")

View File

@ -1,11 +0,0 @@
import ../crypto
# How to surface different verifability of properties across conversation types
type ReceivedMessage* = ref object of RootObj
sender*: PublicKey
timestamp*: int64
content*: seq[byte]

View File

@ -1,282 +0,0 @@
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
const TopicPrefixPrivateV1 = "/convo/private/"
type
ReceivedPrivateV1Message* = ref object of ReceivedMessage
proc initReceivedMessage(sender: PublicKey, timestamp: int64, content: Content) : ReceivedPrivateV1Message =
ReceivedPrivateV1Message(sender:sender, timestamp:timestamp, content:content)
type
PrivateV1* = ref object of Conversation
ds: WakuClient
sdsClient: ReliabilityManager
owner: Identity
participant: PublicKey
discriminator: string
doubleratchet: naxolotl.Doubleratchet
proc derive_topic(participant: PublicKey): string =
## Derives a topic from the participants' public keys.
return TopicPrefixPrivateV1 & participant.get_addr()
proc getTopicInbound*(self: PrivateV1): string =
## Returns the topic where the local client is listening for messages
return derive_topic(self.owner.getPubkey())
proc getTopicOutbound*(self: PrivateV1): string =
## Returns the topic where the remote recipient is listening for messages
return derive_topic(self.participant)
## Parses the topic to extract the conversation ID.
proc parseTopic*(topic: string): Result[string, ChatError] =
if not topic.startsWith(TopicPrefixPrivateV1):
return err(ChatError(code: errTopic, context: "Invalid topic prefix"))
let id = topic.split('/')[^1]
if id == "":
return err(ChatError(code: errTopic, context: "Empty conversation ID"))
return ok(id)
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 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
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
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 rm = newReliabilityManager().valueOr:
raise newException(ValueError, fmt"sds initialization: {repr(error)}")
let dr = if isSender:
initDoubleratchetSender(seedKey, participant.bytes)
else:
initDoubleratchetRecipient(seedKey, owner.privateKey.bytes)
result = PrivateV1(
ds: ds,
sdsClient: rm,
owner: owner,
participant: participant,
discriminator: discriminator,
doubleratchet: dr
)
result.wireCallbacks(deliveryAckCb)
result.sdsClient.ensureChannel(result.getConvoId()).isOkOr:
raise newException(ValueError, "bad sds channel")
proc encodeFrame*(self: PrivateV1, msg: PrivateV1Frame): (MessageId, EncryptedPayload) =
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)}")
result = (msgId, self.encrypt(sdsPayload))
proc sendFrame(self: PrivateV1, ds: WakuClient,
msg: PrivateV1Frame): Future[MessageId]{.async.} =
let (msgId, encryptedPayload) = self.encodeFrame(msg)
discard ds.sendPayload(self.getTopicOutbound(), 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,
encPayload: EncryptedPayload) =
## Dispatcher for Incoming `PrivateV1Frames`.
## Calls further processing depending on the kind of frame.
if convo.doubleratchet.dhSelfPublic() == encPayload.doubleratchet.dh:
info "outgoing message, no need to handle", convo = convo.id()
return
let plaintext = convo.decrypt(encPayload).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 typeContent:
# 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
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 encPayload = decode(bytes, EncryptedPayload).valueOr:
raise newException(ValueError, fmt"Failed to decode EncryptedPayload: {repr(error)}")
convo.handleFrame(client,encPayload)
method sendMessage*(convo: PrivateV1, content_frame: Content) : 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"
## Encrypts content without sending it.
proc encryptMessage*(self: PrivateV1, content_frame: Content) : (MessageId, EncryptedPayload) =
try:
let frame = PrivateV1Frame(
sender: @(self.owner.getPubkey().bytes()),
timestamp: getCurrentTimestamp(),
content: content_frame
)
result = self.encodeFrame(frame)
except Exception as e:
error "Unknown error in PrivateV1:EncryptMessage"
proc initPrivateV1Sender*(sender:Identity,
ds: WakuClient,
participant: PublicKey,
seedKey: array[32, byte],
content: Content,
deliveryAckCb: proc(conversation: Conversation, msgId: string): Future[void] {.async.} = nil): (PrivateV1, EncryptedPayload) =
let convo = initPrivateV1(sender, ds, participant, seedKey, "default", true, deliveryAckCb)
# Encrypt Content with Convo
let contentFrame = PrivateV1Frame(sender: @(sender.getPubkey().bytes()), timestamp: getCurrentTimestamp(), content: content)
let (msg_id, encPayload) = convo.encryptMessage(content)
result = (convo, encPayload)
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)

View File

@ -1,35 +0,0 @@
import proto_types
import strformat
import crypto/ecdh
import std/[sysrand]
import results
import utils
export PublicKey, PrivateKey, bytes, createRandomKey, loadPrivateKeyFromBytes, loadPublicKeyFromBytes,
getPublicKey, Dh, Result, get_addr, `$`
proc encrypt_plain*[T: EncryptableTypes](frame: T): EncryptedPayload =
return EncryptedPayload(
plaintext: Plaintext(payload: encode(frame)),
)
proc decrypt_plain*[T: EncryptableTypes](ciphertext: Plaintext, t: typedesc[
T]): Result[T, string] =
let obj = decode(ciphertext.payload, T)
if obj.isErr:
return err("Protobuf decode failed: " & obj.error)
result = ok(obj.get())
proc generate_key*(): PrivateKey =
createRandomKey().get()
proc toHex*(key: PublicKey): string =
bytesToHex(key.bytes())
proc `$`*(key: PublicKey): string =
let byteStr = toHex(key)
fmt"{byteStr[0..3]}..{byteStr[^4 .. ^1]}"

View File

@ -1,171 +0,0 @@
import std/[strutils]
import
chronicles,
chronos,
results,
strformat
import
conversations/convo_type,
conversations,
conversation_store,
crypto,
delivery/waku_client,
errors,
identity,
proto_types,
types
logScope:
topics = "chat inbox"
type
Inbox* = ref object of Conversation
identity: Identity
inbox_addr: string
const
TopicPrefixInbox = "/inbox/"
proc `$`*(conv: Inbox): string =
fmt"Inbox: addr->{conv.inbox_addr}"
proc initInbox*(ident: Identity): Inbox =
## Initializes an Inbox object with the given address and invite callback.
return Inbox(identity: ident)
proc encrypt*(frame: InboxV1Frame): EncryptedPayload =
return encrypt_plain(frame)
proc decrypt*(inbox: Inbox, encbytes: EncryptedPayload): Result[InboxV1Frame, string] =
let res_frame = decrypt_plain(encbytes.plaintext, InboxV1Frame)
if res_frame.isErr:
error "Failed to decrypt frame: ", err = res_frame.error
return err("Failed to decrypt frame: " & res_frame.error)
result = res_frame
proc wrap_env*(payload: EncryptedPayload, convo_id: string): WapEnvelopeV1 =
let bytes = encode(payload)
let salt = generateSalt()
return WapEnvelopeV1(
payload: bytes,
salt: salt,
conversation_hint: convo_id,
)
proc conversation_id_for*(pubkey: PublicKey): string =
## Generates a conversation ID based on the public key.
return "/convo/inbox/v1/" & pubkey.get_addr()
# TODO derive this from instance of Inbox
proc topic_inbox*(client_addr: string): string =
return TopicPrefixInbox & client_addr
proc parseTopic*(topic: string): Result[string, ChatError] =
if not topic.startsWith(TopicPrefixInbox):
return err(ChatError(code: errTopic, context: "Invalid inbox topic prefix"))
let id = topic.split('/')[^1]
if id == "":
return err(ChatError(code: errTopic, context: "Empty inbox id"))
return ok(id)
method id*(convo: Inbox): string =
return conversation_id_for(convo.identity.getPubkey())
## Encrypt and Send a frame to the remote account
proc sendFrame(ds: WakuClient, remote: PublicKey, frame: InboxV1Frame ): Future[void] {.async.} =
let env = wrapEnv(encrypt(frame),conversation_id_for(remote) )
await ds.sendPayload(topic_inbox(remote.get_addr()), env)
proc newPrivateInvite(initator_static: PublicKey,
initator_ephemeral: PublicKey,
recipient_static: PublicKey,
recipient_ephemeral: uint32,
payload: EncryptedPayload) : InboxV1Frame =
let invite = InvitePrivateV1(
initiator: @(initator_static.bytes()),
initiatorEphemeral: @(initator_ephemeral.bytes()),
participant: @(recipient_static.bytes()),
participantEphemeralId: 0,
discriminator: "",
initial_message: payload
)
result = InboxV1Frame(invitePrivateV1: invite, recipient: "")
#################################################
# Conversation Creation
#################################################
## Establish a PrivateConversation with a remote client
proc inviteToPrivateConversation*(self: Inbox, ds: Wakuclient, remote_static: PublicKey, remote_ephemeral: PublicKey, content: Content ) : Future[PrivateV1] {.async.} =
# Create SeedKey
# TODO: Update key derivations when noise is integrated
var local_ephemeral = generateKey()
var sk{.noInit.} : array[32, byte] = default(array[32, byte])
# Initialize PrivateConversation
let (convo, encPayload) = initPrivateV1Sender(self.identity, ds, remote_static, sk, content, nil)
result = convo
# # Build Invite
let frame = newPrivateInvite(self.identity.getPubkey(), local_ephemeral.getPublicKey(), remote_static, 0, encPayload)
# Send
await sendFrame(ds, remote_static, frame)
## Receive am Invitation to create a new private conversation
proc createPrivateV1FromInvite*[T: ConversationStore](client: T,
invite: InvitePrivateV1) =
let destPubkey = loadPublicKeyFromBytes(invite.initiator).valueOr:
raise newException(ValueError, "Invalid public key in intro bundle.")
let deliveryAckCb = proc(
conversation: Conversation,
msgId: string): Future[void] {.async.} =
client.notifyDeliveryAck(conversation, msgId)
# TODO: remove placeholder key
var key : array[32, byte] = default(array[32,byte])
let convo = initPrivateV1Recipient(client.identity(), client.ds, destPubkey, key, deliveryAckCb)
notice "Creating PrivateV1 conversation", client = client.getId(),
convoId = convo.getConvoId()
convo.handleFrame(client, invite.initial_message)
# Calling `addConversation` must only occur after the conversation is completely configured.
# The client calls the OnNewConversation callback, which returns execution to the application.
client.addConversation(convo)
proc handleFrame*[T: ConversationStore](convo: Inbox, client: T, bytes: seq[
byte]) =
## Dispatcher for Incoming `InboxV1Frames`.
## Calls further processing depending on the kind of frame.
let enc = decode(bytes, EncryptedPayload).valueOr:
raise newException(ValueError, "Failed to decode payload")
let frame = convo.decrypt(enc).valueOr:
error "Decrypt failed", client = client.getId(), error = error
raise newException(ValueError, "Failed to Decrypt MEssage: " &
error)
case getKind(frame):
of typeInvitePrivateV1:
createPrivateV1FromInvite(client, frame.invitePrivateV1)
of typeNote:
notice "Receive Note", client = client.getId(), text = frame.note.text
method sendMessage*(convo: Inbox, content_frame: Content) : Future[MessageId] {.async.} =
warn "Cannot send message to Inbox"
result = "program_error"

View File

@ -1,58 +0,0 @@
import base64
import chronos
import strformat
import strutils
import libp2p/crypto/crypto
import ../content_types/all
import proto_types
import utils
#################################################
# Link Generation
#################################################
proc toBundle*(link: string): Result[IntroBundle, string] =
# Check scheme
if not link.startsWith("wap://"):
return err("InvalidScheme")
# Remove scheme
let path = link[6..^1]
# Split by '/'
let parts = path.split('/')
# Expected format: ident/{ident}/ephemeral/{ephemeral}/eid/{eid}
if parts.len != 6:
return err("InvalidFormat")
# Validate structure
if parts[0] != "ident" or parts[2] != "ephemeral" or parts[4] != "eid":
return err("InvalidFormat")
# Extract values
let ident = decode(parts[1]).toBytes()
let ephemeral = decode(parts[3]).toBytes()
let eid = int32(parseInt(parts[5])) # TODO: catch parse error
# Validate non-empty
if ident.len == 0:
return err("MissingIdent")
if ephemeral.len == 0:
return err("MissingEphemeral")
return ok(IntroBundle(
ident: ident,
ephemeral: ephemeral,
ephemeral_id: eid
))
proc toLink*(intro: IntroBundle): string =
let ident = encode(intro.ident, safe = true)
let ephemeral = intro.ephemeral.toHex()
result = fmt"wap://ident/{ident}/ephemeral/{ephemeral}/eid/{intro.ephemeral_id}"

View File

@ -1,104 +0,0 @@
# Can this be an external package? It would be preferable to have these types
# easy to import and use.
import protobuf_serialization # This import is needed or th macro will not work
import protobuf_serialization/proto_parser
import results
import std/random
export protobuf_serialization
import_proto3 "../../protos/inbox.proto"
# import_proto3 "../protos/invite.proto" // Import3 follows protobuf includes so this will result in a redefinition error
import_proto3 "../../protos/envelope.proto"
import_proto3 "../../protos/private_v1.proto"
type EncryptableTypes = InboxV1Frame | EncryptedPayload
export EncryptedPayload
export InboxV1Frame
export PrivateV1Frame
export EncryptableTypes
proc encode*(frame: object): seq[byte] =
## Encodes the frame into a byte sequence using Protobuf serialization.
result = Protobuf.encode(frame)
proc decode*[T: object] (bytes: seq[byte], proto: typedesc[
T]): Result[T, string] =
## Encodes the frame into a byte sequence using Protobuf serialization.
try:
result = ok(Protobuf.decode(bytes, proto))
except ProtobufError as e:
result = err("Failed to decode payload: " & e.msg)
type
IntroBundle {.proto3.} = object
ident* {.fieldNumber: 1.}: seq[byte]
ephemeral* {.fieldNumber: 2.}: seq[byte]
ephemeral_id* {.fieldNumber: 3.}: int32
export IntroBundle
proc generateSalt*(): uint64 =
randomize()
result = 0
for i in 0 ..< 8:
result = result or (uint64(rand(255)) shl (i * 8))
proc toEnvelope*(payload: EncryptedPayload, convo_id: string): WapEnvelopeV1 =
let bytes = encode(payload)
let salt = generateSalt()
# TODO: Implement hinting
return WapEnvelopeV1(
payload: bytes,
salt: salt,
conversation_hint: convo_id,
)
###########################################################
# nim-serialize-protobuf does not support oneof fields.
# As a stop gap each object using oneof fields, needs
# a implementation to look up the type.
#
# The valid field is determined by the fields which
# is not set to the default value
###########################################################
type
InboxV1FrameType* = enum
type_InvitePrivateV1, type_Note
proc getKind*(obj: InboxV1Frame): InboxV1FrameType =
if obj.invite_private_v1 != InvitePrivateV1():
return type_InvitePrivateV1
if obj.note != Note():
return type_Note
raise newException(ValueError, "Un handled one of type")
type
PrivateV1FrameType* = enum
type_Content, type_Placeholder
proc getKind*(obj: PrivateV1Frame): PrivateV1FrameType =
if obj.content != @[]:
return type_Content
if obj.placeholder != Placeholder():
return type_Placeholder
raise newException(ValueError, "Un handled one of type")