diff --git a/.gitignore b/.gitignore
index 7fe05ef..d81d275 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
/waku_vibe_template
/waku_vibe_template.dSYM
-/nim_chat_poc
+/logos_chat
*.dSYM
nimble.develop
nimble.paths
@@ -21,7 +21,7 @@ nimble.paths
/tags
# a symlink that can't be added to the repo because of Windows
-/nim_chat_poc.nims
+/logos_chat.nims
# Ignore dynamic, static libs and libtool archive files
*.so
diff --git a/.gitmodules b/.gitmodules
index b35d845..34eaae6 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -6,11 +6,6 @@
url = https://github.com/waku-org/nwaku.git
ignore = untracked
branch = master
-[submodule "vendor/nim-sds"]
- path = vendor/nim-sds
- url = https://github.com/jazzz/nim-sds.git
- ignore = untracked
- branch = master
[submodule "vendor/nim-protobuf-serialization"]
path = vendor/nim-protobuf-serialization
url = https://github.com/status-im/nim-protobuf-serialization.git
@@ -26,23 +21,10 @@
url = https://github.com/narimiran/blake2.git
ignore = untracked
branch = master
-[submodule "vendor/illwill"]
- path = vendor/illwill
- url = https://github.com/johnnovak/illwill.git
- ignore = untracked
- branch = master
-[submodule "vendor/nim_chacha20_poly1305"]
- path = vendor/nim_chacha20_poly1305
- url = https://github.com/lantos-lgtm/nim_chacha20_poly1305.git
- ignore = untracked
- branch = master
-[submodule "vendor/constantine"]
- path = vendor/constantine
- url = https://github.com/mratsim/constantine.git
- ignore = untracked
- branch = master
+[submodule "vendor/libchat"]
+ path = vendor/libchat
+ url = https://github.com/logos-messaging/libchat.git
[submodule "vendor/nim-ffi"]
path = vendor/nim-ffi
url = https://github.com/logos-messaging/nim-ffi.git
- ignore = untracked
branch = master
diff --git a/DEVELOPING.md b/DEVELOPING.md
index 32fe621..e707d70 100644
--- a/DEVELOPING.md
+++ b/DEVELOPING.md
@@ -5,9 +5,9 @@
```mermaid
flowchart TD
- NIM("nim-chat-poc
c-bingings, process control, networking")
+ NIM("Logos-Chat
c-bingings, process control, networking")
LIB("libchat
encryption, encoding")
- PROTO("chat_proto
protobufs, language specific types")
+ PROTO("chat-proto
protobufs, language specific types")
LMN("logos-messaging-nim
p2p networking")
@@ -22,13 +22,13 @@ style LMN fill:#fff
## Repositories
-### nim-chat-poc
+### logos-chat
Root of the Logos chat SDK, written in Nim.
Handles async operations and bridges network operations with the cryptographic backend.
**Responsibilities:**
-- C bindings to libchat
+- C bindings to Libchat
- Async execution (Chronos)
- Network integration
@@ -42,12 +42,12 @@ Operates as a pipeline: accepts either encrypted payloads or plaintext content,
- Encoding/decoding
-### logos-messaging-nim
+### logos-message-delivery
P2P networking layer using logos-messaging protocol.
Provides decentralized message transport. This is an external dependency.
-### chat_proto
+### chat-proto
Protobuf definitions.
Defines the protobufs used in the logos-chat protocol and provides generated types for various languages.
\ No newline at end of file
diff --git a/Makefile b/Makefile
index d243276..db259d3 100644
--- a/Makefile
+++ b/Makefile
@@ -41,15 +41,16 @@ define test_name
$(shell echo '$(MAKECMDGOALS)' | cut -d' ' -f3-)
endef
-nim_chat_poc.nims:
- ln -s nim_chat_poc.nimble $@
+logos_chat.nims:
+ ln -s logos_chat.nimble $@
update: | update-common
- rm -rf nim_chat_poc.nims && \
- $(MAKE) nim_chat_poc.nims $(HANDLE_OUTPUT)
+ rm -rf logos_chat.nims && \
+ $(MAKE) logos_chat.nims $(HANDLE_OUTPUT)
clean:
rm -rf build
+ cd vendor/libchat && cargo clean
# must be included after the default target
-include $(BUILD_SYSTEM_DIR)/makefiles/targets.mk
@@ -86,11 +87,17 @@ build-waku-nat:
@echo "Start building waku nat-libs"
$(MAKE) -C vendor/nwaku nat-libs
@echo "Completed building nat-libs"
-
+
+.PHONY: build-libchat
+build-libchat:
+ @echo "Start building libchat"
+ cd vendor/libchat && cargo build --release
+ @echo "Completed building libchat"
+
.PHONY: tests
-tests: | build-waku-librln build-waku-nat nim_chat_poc.nims
+tests: | build-waku-librln build-waku-nat build-libchat logos_chat.nims
echo -e $(BUILD_MSG) "build/$@" && \
- $(ENV_SCRIPT) nim tests $(NIM_PARAMS) nim_chat_poc.nims
+ $(ENV_SCRIPT) nim tests $(NIM_PARAMS) logos_chat.nims
##########
@@ -98,9 +105,9 @@ tests: | build-waku-librln build-waku-nat nim_chat_poc.nims
##########
# Ensure there is a nimble task with a name that matches the target
-tui bot_echo pingpong: | build-waku-librln build-waku-nat nim_chat_poc.nims
+tui bot_echo pingpong: | build-waku-librln build-waku-nat build-libchat logos_chat.nims
echo -e $(BUILD_MSG) "build/$@" && \
- $(ENV_SCRIPT) nim $@ $(NIM_PARAMS) --path:src nim_chat_poc.nims
+ $(ENV_SCRIPT) nim $@ $(NIM_PARAMS) --path:src logos_chat.nims
###########
## Library ##
@@ -118,9 +125,9 @@ endif
LIBLOGOSCHAT := build/liblogoschat.$(LIBLOGOSCHAT_EXT)
.PHONY: liblogoschat
-liblogoschat: | build-waku-librln build-waku-nat nim_chat_poc.nims
+liblogoschat: | build-waku-librln build-waku-nat build-libchat logos_chat.nims
echo -e $(BUILD_MSG) "$(LIBLOGOSCHAT)" && \
- $(ENV_SCRIPT) nim liblogoschat $(NIM_PARAMS) --path:src nim_chat_poc.nims && \
+ $(ENV_SCRIPT) nim liblogoschat $(NIM_PARAMS) --path:src logos_chat.nims && \
echo -e "\n\x1B[92mLibrary built successfully:\x1B[39m" && \
echo " $(shell pwd)/$(LIBLOGOSCHAT)"
diff --git a/README.md b/README.md
index 746e9b2..0129dc9 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Nim Chat POC
+# Logos Chat
This is the technical proof of a modular e2ee chat protocol using Waku. You can find discussion and details [here](https://github.com/waku-org/specs/pull/73)
diff --git a/config.nims b/config.nims
index 27a5d0b..c0b0096 100644
--- a/config.nims
+++ b/config.nims
@@ -5,3 +5,6 @@ for dir in walkDir(thisDir() / "vendor"):
if dir.kind == pcDir:
switch("path", dir.path)
switch("path", dir.path / "src")
+
+switch("path", thisDir() / "vendor/libchat/nim-bindings")
+switch("path", thisDir() / "vendor/libchat/nim-bindings/src")
\ No newline at end of file
diff --git a/examples/bot_echo.nim b/examples/bot_echo.nim
index 0e78481..fd2a5d2 100644
--- a/examples/bot_echo.nim
+++ b/examples/bot_echo.nim
@@ -24,7 +24,7 @@ proc main() {.async.} =
await chatClient.start()
info "EchoBot started"
- info "Invite", link=chatClient.createIntroBundle().toLink()
+ info "Invite", link=chatClient.createIntroBundle()
when isMainModule:
waitFor main()
diff --git a/examples/cbindings/Makefile b/examples/cbindings/Makefile
index aa26a01..ed2d923 100644
--- a/examples/cbindings/Makefile
+++ b/examples/cbindings/Makefile
@@ -2,7 +2,7 @@
CC = gcc
CFLAGS = -Wall -Wextra -I../../library -pthread
-LDFLAGS = -L../../build -lchat -lncurses -Wl,-rpath,../../build
+LDFLAGS = -L../../build -llogoschat -lncurses -Wl,-rpath,../../build
BUILD_DIR = ../../build
TARGET = $(BUILD_DIR)/cbindings_chat_tui
diff --git a/examples/cbindings/cbindings_chat_tui.c b/examples/cbindings/cbindings_chat_tui.c
index 16c4a9e..5bc3faf 100644
--- a/examples/cbindings/cbindings_chat_tui.c
+++ b/examples/cbindings/cbindings_chat_tui.c
@@ -30,9 +30,7 @@ static const size_t MAX_INPUT_LEN = 2048;
// Application state structures
typedef struct {
char current_convo[128];
- char inbox_id[128];
char my_name[64];
- char my_address[128];
void *ctx;
} ChatState;
@@ -442,23 +440,13 @@ static void bundle_callback(int ret, const char *msg, size_t len, void *userData
static void identity_callback(int ret, const char *msg, size_t len, void *userData) {
(void)userData; (void)len;
if (ret == RET_OK) {
- const char *keys[] = {"name", "address"};
- char *values[] = {g_app.chat.my_name, g_app.chat.my_address};
- size_t sizes[] = {sizeof(g_app.chat.my_name), sizeof(g_app.chat.my_address)};
- json_extract(msg, keys, values, sizes, 2);
-
- char buf[256];
- snprintf(buf, sizeof(buf), "Identity: %s (%.24s...)", g_app.chat.my_name, g_app.chat.my_address);
- add_log(buf);
- }
-}
+ const char *keys[] = {"name"};
+ char *values[] = {g_app.chat.my_name};
+ size_t sizes[] = {sizeof(g_app.chat.my_name)};
+ json_extract(msg, keys, values, sizes, 1);
-static void inbox_callback(int ret, const char *msg, size_t len, void *userData) {
- (void)userData;
- if (ret == RET_OK && len > 0) {
- snprintf(g_app.chat.inbox_id, sizeof(g_app.chat.inbox_id), "%.*s", (int)len, msg);
char buf[256];
- snprintf(buf, sizeof(buf), "Inbox: %.24s...", g_app.chat.inbox_id);
+ snprintf(buf, sizeof(buf), "Identity: %s", g_app.chat.my_name);
add_log(buf);
}
}
@@ -713,7 +701,6 @@ int main(int argc, char *argv[]) {
add_log("Starting client...");
chat_start(g_app.chat.ctx, general_callback, NULL);
chat_get_identity(g_app.chat.ctx, identity_callback, NULL);
- chat_get_default_inbox_id(g_app.chat.ctx, inbox_callback, NULL);
add_message("Welcome to Chat TUI!");
add_message("Type /help for commands, /quit to exit");
diff --git a/examples/pingpong.nim b/examples/pingpong.nim
index 8f07f19..5dbe00d 100644
--- a/examples/pingpong.nim
+++ b/examples/pingpong.nim
@@ -5,8 +5,6 @@ import strformat
import chat
import content_types
-# TEsting
-import ../src/chat/crypto
proc getContent(content: ContentFrame): string =
@@ -35,39 +33,38 @@ proc main() {.async.} =
var saro = newClient(waku_saro, Identity(name: "saro", privateKey: sKey))
var raya = newClient(waku_raya, Identity(name: "raya", privateKey: rKey))
- var ri = 0
- # Wire Callbacks
- saro.onNewMessage(proc(convo: Conversation, msg: ReceivedMessage) {.async.} =
+ # Wire Saro Callbacks
+ saro.onNewMessage(proc(convo: Conversation, msg: ReceivedMessage) {.async, closure.} =
let contentFrame = msg.content.fromBytes()
- echo " Saro <------ :: " & getContent(contentFrame)
- await sleepAsync(5000.milliseconds)
+ notice " Saro <------ ", content = getContent(contentFrame)
+ await sleepAsync(1000.milliseconds)
discard await convo.sendMessage(initTextFrame("Ping").toContentFrame().toBytes())
-
- )
+ )
saro.onDeliveryAck(proc(convo: Conversation, msgId: string) {.async.} =
- echo " Saro -- Read Receipt for " & msgId
+ notice " Saro -- Read Receipt for ", msgId= msgId
)
+ # Wire Raya Callbacks
+ var i = 0
+ raya.onNewConversation(proc(convo: Conversation) {.async.} =
+ notice " ------> Raya :: New Conversation: ", id = convo.id()
+ discard await convo.sendMessage(initTextFrame("Hello").toContentFrame().toBytes())
+ )
raya.onNewMessage(proc(convo: Conversation,msg: ReceivedMessage) {.async.} =
let contentFrame = msg.content.fromBytes()
- echo fmt" ------> Raya :: from:{msg.sender} " & getContent(contentFrame)
+ notice " ------> Raya :: from: ", content= getContent(contentFrame)
await sleepAsync(500.milliseconds)
- discard await convo.sendMessage(initTextFrame("Pong" & $ri).toContentFrame().toBytes())
+ discard await convo.sendMessage(initTextFrame("Pong" & $i).toContentFrame().toBytes())
await sleepAsync(800.milliseconds)
- discard await convo.sendMessage(initTextFrame("Pong" & $ri).toContentFrame().toBytes())
- await sleepAsync(500.milliseconds)
- discard await convo.sendMessage(initTextFrame("Pong" & $ri).toContentFrame().toBytes())
- inc ri
+ discard await convo.sendMessage(initTextFrame("Pang" & $i).toContentFrame().toBytes())
+ inc i
)
- raya.onNewConversation(proc(convo: Conversation) {.async.} =
- echo " ------> Raya :: New Conversation: " & convo.id()
- discard await convo.sendMessage(initTextFrame("Hello").toContentFrame().toBytes())
- )
+
raya.onDeliveryAck(proc(convo: Conversation, msgId: string) {.async.} =
echo " raya -- Read Receipt for " & msgId
)
diff --git a/examples/tui.nim b/examples/tui.nim
deleted file mode 100644
index 6120b20..0000000
--- a/examples/tui.nim
+++ /dev/null
@@ -1,7 +0,0 @@
-import chronos
-
-import tui/tui
-
-
-when isMainModule:
- waitFor main()
diff --git a/examples/tui/layout.nim b/examples/tui/layout.nim
deleted file mode 100644
index b72381a..0000000
--- a/examples/tui/layout.nim
+++ /dev/null
@@ -1,78 +0,0 @@
-import illwill
-
-
-type
- Pane* = object
- xStart*: int
- yStart*: int
- width*: int
- height*: int
-
-proc getPanes*(): seq[Pane] =
-
- let statusBarHeight = 6
- let convoWidth = 40
- let inboxHeight = 5
- let footerHeight = 14
- let modalXOffset = 10
- let modalHeight = 10
-
- result = @[]
- let statusBar = Pane(
- xStart: 0,
- yStart: 0,
- width: terminalWidth(),
- height: statusBarHeight
- )
- result.add(statusBar)
-
- let convoPane = Pane(
- xStart: 0,
- yStart: statusBar.yStart + statusBar.height,
- width: convoWidth,
- height: terminalHeight() - statusBar.height - footerHeight - 1
- )
- result.add(convoPane)
-
-
- let msgPane = Pane(
- xStart: convoPane.width,
- yStart: statusBar.yStart + statusBar.height,
- width: terminalWidth() - convoPane.width,
- height: convoPane.height - inboxHeight
- )
- result.add(msgPane)
-
- let msgInputPane = Pane(
- xStart: convoPane.width,
- yStart: msgPane.yStart + msgPane.height,
- width: msgPane.width,
- height: inboxHeight
- )
- result.add(msgInputPane)
-
- let footerPane = Pane(
- xStart: 0,
- yStart: convoPane.yStart + convoPane.height,
- width: int(terminalWidth()),
- height: footerHeight
- )
- result.add(footerPane)
-
-
- let modalPane = Pane(
- xStart: modalXOffset,
- yStart: int(terminalHeight()/2 - modalHeight/2),
- width: int(terminalWidth() - 2*modalXOffset),
- height: int(modalHeight)
- )
- result.add(modalPane)
-
-
-proc offsetPane(pane: Pane): Pane =
- result = Pane(
- xStart: pane.xStart ,
- yStart: pane.yStart + 1,
- width: pane.width ,
- height: pane.height - 2
- )
diff --git a/examples/tui/persistence.nim b/examples/tui/persistence.nim
deleted file mode 100644
index cfcfe46..0000000
--- a/examples/tui/persistence.nim
+++ /dev/null
@@ -1,135 +0,0 @@
-import chronicles
-import chronos
-import libp2p/crypto/crypto
-import sequtils
-import std/json
-import std/jsonutils except distinctBase
-import std/marshal
-import std/options
-import std/os
-import std/streams
-import strformat
-import strutils
-import tables
-
-import chat/crypto/ecdh
-import chat/delivery/waku_client
-import chat/identity
-
-
-const REGISTRATION_DIR = ".registry"
-const KEY_DIR = ".savedkeys"
-
-
-type Config* = object
- ident*: Identity
- waku*: WakuConfig
-
-type SavedConfig* = object
- name*: string
- idkey*: seq[byte]
- nodekey*: seq[byte]
- port*: uint16
- clusterId*: uint16
- shardId*: seq[uint16]
- pubsubTopic*: string
- staticPeers*: seq[string]
-
-
-proc toWakuConfig(s: SavedConfig): WakuConfig =
- result = WakuConfig(
- nodekey: crypto.PrivateKey.init(s.nodekey).get(),
- port: s.port,
- clusterId: s.clusterId,
- shardId: s.shardId,
- pubsubTopic: s.pubsubTopic,
- staticPeers: s.staticPeers
- )
-
-
-proc toIdent(s: SavedConfig): Identity =
- result = Identity(
- name: s.name,
- privateKey: loadPrivateKeyFromBytes(s.idkey).get()
- )
-
-
-proc register(name: string, multiAddr: string) {.async.} =
-
- notice "Registering Account", name=name, maddr=multiAddr
-
- if not dirExists(REGISTRATION_DIR):
- createDir(REGISTRATION_DIR)
-
- try:
- writeFile(joinPath(REGISTRATION_DIR, fmt"{name.toLower()}.maddr"), multiAddr)
- except IOError as e:
- echo "Failed to write registration file: ", e.msg
- raise e
-
-
-proc fetchRegistrations*(): Table[string, string] =
-
- let allFiles = toSeq(walkFiles(fmt"{REGISTRATION_DIR}/*"))
- result = allFiles.mapIt((splitFile(it)[1], readFile(it).strip())).toTable()
-
-
-proc loadCfg(file: string): Option[Config] =
- let data = parseFile(file)
-
- let cfg = Config(
- ident: toIdent(data.to(SavedConfig)),
- waku: toWakuConfig(data.to(SavedConfig))
- )
-
- result = some(cfg)
-
-
-proc fetchCfg(name: string): Option[Config ] =
- let allFiles = toSeq(walkFiles(fmt"{KEY_DIR}/*"))
-
- for file in allFiles:
- if name == splitFile(file)[1]:
- return loadCfg(file)
- return none(Config)
-
-
-proc saveCfg(name:string, cfg: Config) =
-
- let s = SavedConfig(
- name: name,
- idkey: cfg.ident.privatekey.bytes().toSeq(),
- nodekey: cfg.waku.nodekey.getBytes().get(),
- port: cfg.waku.port,
- clusterId: cfg.waku.clusterId,
- shardId: cfg.waku.shardId,
- pubsubTopic: cfg.waku.pubsubTopic,
- staticPeers: cfg.waku.staticPeers
- )
-
- let json = jsonutils.toJson(s)
-
-
- if not dirExists(KEY_DIR):
- createDir(KEY_DIR)
-
- try:
- writeFile(joinPath(KEY_DIR, fmt"{name.toLower()}.cfg"), $json)
- except IOError as e:
- echo "Failed to write cfg file: ", e.msg
- raise e
-
-
-proc getCfg*(name: string): Future[Config] {.async.} =
- let cfgOpt = fetchCfg(name)
- if cfgOpt.isSome:
- result = cfgOpt.get()
- else:
- let newCfg = Config(
- ident: createIdentity(name),
- waku: DefaultConfig()
- )
- saveCfg(name, newCfg)
- await register(name, newCfg.waku.getMultiAddr())
-
- result = newCfg
diff --git a/examples/tui/tui.nim b/examples/tui/tui.nim
deleted file mode 100644
index 010f17d..0000000
--- a/examples/tui/tui.nim
+++ /dev/null
@@ -1,584 +0,0 @@
-
-import algorithm
-import chronicles
-import chronos
-import illwill
-import libp2p/crypto/crypto
-import times
-import strformat
-import strutils
-import sugar
-import tables
-
-import chat
-import content_types/all
-
-import layout
-import persistence
-import utils
-
-const charVert = "│"
-const charHoriz = "─"
-const charTopLeft = "┌"
-const charTopRight = "┐"
-const charBottomLeft = "└"
-const charBottomRight = "┘"
-
-type
-
- LogEntry = object
- level: string
- ts: DateTime
- msg: string
-
- Message = object
- sender: string
- content: string
- timestamp: DateTime
- id: string
- isAcknowledged: bool
-
- ConvoInfo = object
- name: string
- convo: Conversation
- messages: seq[Message]
- lastMsgTime*: DateTime
- isTooLong*: bool
-
- ChatApp = ref object
- client: Client
- tb: TerminalBuffer
- conversations: Table[string, ConvoInfo]
- selectedConv: string
- inputBuffer: string
- inputInviteBuffer: string
- scrollOffset: int
- messageScrollOffset: int
- inviteModal: bool
-
- currentInviteLink: string
- isInviteReady: bool
- peerCount: int
-
- logMsgs: seq[LogEntry]
-
-proc `==`(a,b: ConvoInfo):bool =
- if a.name==b.name:
- true
- else:
- false
-
-
-#################################################
-# Data Management
-#################################################
-
-proc addMessage(conv: var ConvoInfo, messageId: MessageId, sender: string, content: string) =
-
- let now = now()
- conv.messages.add(Message(
- sender: sender,
- id: messageId,
- content: content,
- timestamp: now,
- isAcknowledged: false
- ))
- conv.lastMsgTime = now
-
-
-proc mostRecentConvos(app: ChatApp): seq[ConvoInfo] =
- var convos = collect(for v in app.conversations.values: v)
- convos.sort(proc(a, b: ConvoInfo): int = -cmp(a.lastMsgTime, b.lastMsgTime))
-
- return convos
-
-proc getSelectedConvo(app: ChatApp): ptr ConvoInfo =
- if app.conversations.hasKey(app.selectedConv):
- return addr app.conversations[app.selectedConv]
-
- return addr app.conversations[app.mostRecentConvos()[0].name]
-
-
-
-#################################################
-# ChatSDK Setup
-#################################################
-
-proc createChatClient(name: string): Future[Client] {.async.} =
- var cfg = await getCfg(name)
- result = newClient(cfg.waku, cfg.ident)
-
-
-proc createInviteLink(app: var ChatApp): string =
- app.client.createIntroBundle().toLink()
-
-
-proc createConvo(app: ChatApp) {.async.} =
- discard await app.client.newPrivateConversation(toBundle(app.inputInviteBuffer.strip()).get())
-
-proc sendMessage(app: ChatApp, convoInfo: ptr ConvoInfo, msg: string) {.async.} =
-
- var msgId = ""
- if convoInfo.convo != nil:
- msgId = await convoInfo.convo.sendMessage(initTextFrame(msg).toContentFrame())
-
- convoInfo[].addMessage(msgId, "You", app.inputBuffer)
-
-
-proc setupChatSdk(app: ChatApp) =
-
- let client = app.client
-
- app.client.onNewMessage(proc(convo: Conversation, msg: ReceivedMessage) {.async.} =
- info "New Message: ", convoId = convo.id(), msg= msg
- app.logMsgs.add(LogEntry(level: "info",ts: now(), msg: "NewMsg"))
-
- var contentStr = case msg.content.contentType
- of text:
- decode(msg.content.bytes, TextFrame).get().text
- of unknown:
- ""
-
- app.conversations[convo.id()].messages.add(Message(sender: msg.sender.toHex(), content: contentStr, timestamp: now()))
- )
-
- app.client.onNewConversation(proc(convo: Conversation) {.async.} =
- app.logMsgs.add(LogEntry(level: "info",ts: now(), msg: fmt"Adding Convo: {convo.id()}"))
- info "New Conversation: ", convoId = convo.id()
-
- app.conversations[convo.id()] = ConvoInfo(name: convo.id(), convo: convo, messages: @[], lastMsgTime: now(), isTooLong: false)
- )
-
- app.client.onDeliveryAck(proc(convo: Conversation, msgId: string) {.async.} =
- info "DeliveryAck", msgId=msgId
- app.logMsgs.add(LogEntry(level: "info",ts: now(), msg: fmt"Ack:{msgId}"))
-
- var s = ""
- var msgs = addr app.conversations[convo.id()].messages
- for i in countdown(msgs[].high, 0):
- s = fmt"{s},{msgs[i].id}"
- var m = addr msgs[i]
-
- if m.id == msgId:
- m.isAcknowledged = true
- break # Stop after
- )
-
-
-#################################################
-# Draw Funcs
-#################################################
-
-proc resetTuiCursor(tb: var TerminalBuffer) =
- tb.setForegroundColor(fgWhite)
- tb.setBackgroundColor(bgBlack)
-
-
-proc drawOutline(tb: var TerminalBuffer, layout: Pane, color: ForegroundColor,
- bg: BackgroundColor = bgBlack) =
-
- for x in layout.xStart+1.. yEnd:
- convo.isTooLong = true
- app.logMsgs.add(LogEntry(level: "info",ts: now(), msg: fmt" TOO LONG: {convo.name}"))
-
- return
- tb.write(x, y, fmt"[{timeStr}] {deliveryIcon} {m.sender}")
- y = y + 1
-
- while remainingText.len > 0:
- if y > yEnd:
- convo.isTooLong = true
- app.logMsgs.add(LogEntry(level: "info",ts: now(), msg: fmt" TOO LON2: {convo.name}"))
-
- return
-
- let (line, remain) = remainingText.splitAt(maxContentWidth)
- remainingText = remain
-
- tb.write(x+3, y, line)
- y = y + 1
- y = y + 1
- else:
- var y = yEnd
- for i in countdown(convo.messages.len-1, 0):
-
- let m = convo.messages[i]
- let timeStr = m.timestamp.format("HH:mm:ss")
- var deliveryIcon = " "
- var remainingText = m.content
-
- if m.sender == "You":
- tb.setForegroundColor(fgYellow)
- deliveryIcon = if m.isAcknowledged: "✔" else: "◯"
- else:
- tb.setForegroundColor(fgGreen)
-
- # Print lines in reverse order
- while remainingText.len > 0:
- if (y <= yStart + 1):
- return
- var lineLen = remainingText.len mod maxContentWidth
- if lineLen == 0:
- lineLen = maxContentWidth
-
- let line = remainingText[^lineLen..^1]
- remainingText = remainingText[0..^lineLen+1]
-
- tb.write(x+3, y, line)
- y = y - 1
-
- tb.write(x, y, fmt"[{timeStr}] {deliveryIcon} {m.sender}")
- y = y - 2
-
-proc drawMsgInput( app: ChatApp, layout: Pane) =
- var tb = app.tb
- drawOutline(tb, layout, fgCyan)
-
-
- let inputY = layout.yStart + 2
- let paneStart = layout.xStart
-
- # Draw input prompt
- tb.write(paneStart + 1, inputY, " > " & app.inputBuffer, fgWhite)
-
- # Draw cursor
- let cursorX = paneStart + 3 + app.inputBuffer.len + 1
- if cursorX < paneStart + layout.width - 1:
- tb.write(cursorX, inputY, "_", fgYellow)
-
- discard
-
-proc drawFooter( app: ChatApp, layout: Pane) =
- var tb = app.tb
- drawOutline(tb, layout, fgBlue)
-
- let xStart = layout.xStart + 3
- let yStart = layout.yStart + 2
-
- for i in countdown(app.logMsgs.len - 1, 0):
- let o = app.logMsgs[i]
- let timeStr = o.ts.format("HH:mm:ss")
- let s = fmt"[{timeStr}] {o.level} - {o.msg}"
- app.tb.write( xStart, yStart+i*2, s )
- discard
-
-
-proc drawModal(app: ChatApp, layout: Pane,
- color: ForegroundColor, bg: BackgroundColor = bgBlack) =
-
- var tb = app.tb
- tb.setForegroundColor(color)
- tb.setBackgroundColor(bg)
- for x in layout.xStart.. " & app.inputInviteBuffer)
-
- # Draw cursor
- let cursorX = layout.xStart+5+1 + app.inputInviteBuffer.len
- if cursorX < terminalWidth() - 1:
- tb.write(cursorX, layout.yStart+inputLine, "_", fgYellow)
-
- tb.setForegroundColor(fgBlack)
- tb.setBackgroundColor(bgGreen)
- tb.write(layout.xStart+5, layout.yStart+inputLine+3, "InviteLink: " & app.currentInviteLink)
-
- resetTuiCursor(tb)
-
-#################################################
-# Input Handling
-#################################################
-
-proc gePreviousConv(app: ChatApp): string =
- let convos = app.mostRecentConvos()
- let i = convos.find(app.getSelectedConvo()[])
-
- return convos[max(i-1, 0)].name
-
-
-proc getNextConv(app: ChatApp): string =
- let convos = app.mostRecentConvos()
- var s = ""
- for c in convos:
- s = s & c.name
-
- let i = convos.find(app.getSelectedConvo()[])
- app.logMsgs.add(LogEntry(level: "info",ts: now(), msg: fmt"i:{convos[min(i+1, convos.len-1)].isTooLong}"))
-
- return convos[min(i+1, convos.len-1)].name
-
-
-proc handleInput(app: ChatApp, key: Key) {.async.} =
- case key
- of Key.Up:
- app.selectedConv = app.gePreviousConv()
- of Key.Down:
- app.selectedConv = app.getNextConv()
-
- of Key.PageUp:
- app.messageScrollOffset = min(app.messageScrollOffset + 5, 0)
- of Key.PageDown:
- app.messageScrollOffset = max(app.messageScrollOffset - 5,
- -(max(0, app.getSelectedConvo().messages.len - 10)))
- of Key.Enter:
-
- if app.inviteModal:
- notice "Enter Invite", link= app.inputInviteBuffer
- app.inviteModal = false
- app.isInviteReady = true
- else:
- if app.inputBuffer.len > 0 and app.conversations.len > 0:
-
- let sc = app.getSelectedConvo()
- await app.sendMessage(sc, app.inputBuffer)
-
- app.inputBuffer = ""
- app.messageScrollOffset = 0 # Auto-scroll to bottom
- of Key.Backspace:
- if app.inputBuffer.len > 0:
- app.inputBuffer.setLen(app.inputBuffer.len - 1)
- of Key.Tab:
- app.inviteModal = not app.inviteModal
-
- if app.inviteModal:
- app.currentInviteLink = app.client.createIntroBundle().toLink()
- of Key.Escape, Key.CtrlC:
- quit(0)
- else:
- # Handle regular character input
- let ch = char(key)
- if ch.isAlphaNumeric() or ch in " !@#$%^&*()_+-=[]{}|;':\",./<>?":
- if app.inviteModal:
- app.inputInviteBuffer.add(ch)
- else:
- app.inputBuffer.add(ch)
-
-
-#################################################
-# Tasks
-#################################################
-
-proc appLoop(app: ChatApp, panes: seq[Pane]) : Future[void] {.async.} =
- illwillInit(fullscreen = false)
- # Clear buffer
- while true:
- await sleepAsync(chronos.milliseconds(5))
- app.tb.clear()
-
- drawStatusBar(app, panes[0], fgBlack, getIdColor(app.client.getId()))
- drawConversationPane(app, panes[1])
- drawMsgPane(app, panes[2])
-
- if app.inviteModal:
- drawModal(app, panes[5], fgYellow, bgGreen)
- else:
- drawMsgInput(app, panes[3])
-
- drawFooter(app, panes[4])
-
- # Draw help text
- app.tb.write(1, terminalHeight()-1, "Tab: Invite Modal | ↑/↓: Select conversation | PgUp/PgDn: Scroll messages | Enter: Send | Esc: Quit", fgGreen)
-
- # Display buffer
- app.tb.display()
-
- # Handle input
- let key = getKey()
- await handleInput(app, key)
-
- if app.isInviteReady:
-
- try:
- let sanitized = app.inputInviteBuffer.replace(" ", "").replace("\r","")
- discard await app.client.newPrivateConversation(toBundle(app.inputInviteBuffer.strip()).get())
-
- except Exception as e:
- info "bad invite", invite = app.inputInviteBuffer
- app.inputInviteBuffer = ""
- app.isInviteReady = false
-
-proc peerWatch(app: ChatApp): Future[void] {.async.} =
- while true:
- await sleepAsync(chronos.seconds(1))
- app.peerCount = app.client.ds.getConnectedPeerCount()
-
-
-#################################################
-# Main
-#################################################
-
-proc initChatApp(client: Client): Future[ChatApp] {.async.} =
-
- var app = ChatApp(
- client: client,
- tb: newTerminalBuffer(terminalWidth(), terminalHeight()),
- conversations: initTable[string, ConvoInfo](),
- selectedConv: "Bob",
- inputBuffer: "",
- scrollOffset: 0,
- messageScrollOffset: 0,
- isInviteReady: false,
- peerCount: -1,
- logMsgs: @[]
- )
-
- app.setupChatSdk()
- await app.client.start()
-
-
- # Add some sample conversations with messages
- var sender = "Nobody"
- var conv1 = ConvoInfo(name: "ReadMe", messages: @[])
- conv1.addMessage("",sender, "First start multiple clients and ensure, that he PeerCount is correct (it's listed in the top left corner)")
- conv1.addMessage("",sender, "Once connected, The sender needs to get the recipients introduction link. The links contains the key material and information required to initialize a conversation. Press `Tab` to generate a link")
- conv1.addMessage("",sender, "Paste the link from one client into another. This will start the initialization protocol, which will send an invite to the recipient and negotiate a conversation")
- conv1.addMessage("",sender, "Once established, Applications are notified by a callback that a new conversation has been established, and participants can send messages")
-
-
-
- app.conversations[conv1.name] = conv1
-
- return app
-
-
-proc main*() {.async.} =
-
- let args = getCmdArgs()
- let client = await createChatClient(args.username)
- var app = await initChatApp(client)
-
- let tasks: seq[Future[void]] = @[
- appLoop(app, getPanes()),
- peerWatch(app)
- ]
-
- discard await allFinished(tasks)
-
-when isMainModule:
- waitFor main()
-
-# this is not nim code
\ No newline at end of file
diff --git a/examples/tui/utils.nim b/examples/tui/utils.nim
deleted file mode 100644
index 8b56ca4..0000000
--- a/examples/tui/utils.nim
+++ /dev/null
@@ -1,53 +0,0 @@
-import std/parseopt
-import illwill
-import times
-
-#################################################
-# Command Line Args
-#################################################
-
-type CmdArgs* = object
- username*: string
- invite*: string
-
-proc getCmdArgs*(): CmdArgs =
- var username = ""
- var invite = ""
- for kind, key, val in getopt():
- case kind
- of cmdArgument:
- discard
- of cmdLongOption, cmdShortOption:
- case key
- of "name", "n":
- username = val
- of "invite", "i":
- invite = val
- of cmdEnd:
- break
- if username == "":
- username = ""
-
- result = CmdArgs(username: username, invite:invite)
-
-
-#################################################
-# Utils
-#################################################
-
-proc getIdColor*(id: string): BackgroundColor =
- var i = ord(id[0])
-
-
- let colors = @[bgCyan,
- bgGreen,
- bgMagenta,
- bgRed,
- bgYellow,
- bgBlue
- ]
-
- return colors[i mod colors.len]
-
-proc toStr*(ts: DateTime): string =
- ts.format("HH:mm:ss")
\ No newline at end of file
diff --git a/library/api/client_api.nim b/library/api/client_api.nim
index 0edb798..10c8d76 100644
--- a/library/api/client_api.nim
+++ b/library/api/client_api.nim
@@ -7,7 +7,6 @@ import chronos
import ffi
import src/chat
-import src/chat/proto_types
import src/chat/delivery/waku_client
import src/chat/identity
import library/utils
@@ -119,15 +118,6 @@ proc chat_get_id(
let clientId = ctx.myLib[].getId()
return ok(clientId)
-proc chat_get_default_inbox_id(
- ctx: ptr FFIContext[ChatClient],
- callback: FFICallBack,
- userData: pointer
-) {.ffi.} =
- ## Get the default inbox conversation ID
- let inboxId = ctx.myLib[].defaultInboxConversationId()
- return ok(inboxId)
-
#################################################
# Conversation List Operations
#################################################
diff --git a/library/api/conversation_api.nim b/library/api/conversation_api.nim
index 97d7919..faab6fd 100644
--- a/library/api/conversation_api.nim
+++ b/library/api/conversation_api.nim
@@ -1,14 +1,13 @@
## Conversation API - FFI bindings for conversation operations
## Uses the {.ffi.} pragma for async request handling
-import std/[json, options]
+import std/options
import chronicles
import chronos
import ffi
import stew/byteutils
import src/chat
-import src/chat/proto_types
import library/utils
logScope:
@@ -22,32 +21,24 @@ proc chat_new_private_conversation(
ctx: ptr FFIContext[ChatClient],
callback: FFICallBack,
userData: pointer,
- introBundleJson: cstring,
+ introBundleStr: cstring,
contentHex: cstring
) {.ffi.} =
## Create a new private conversation with the given IntroBundle
- ## introBundleJson: JSON string with {"ident": "hex...", "ephemeral": "hex..."}
+ ## introBundleStr: Intro bundle ASCII string as returned by chat_create_intro_bundle
## contentHex: Initial message content as hex-encoded string
try:
- let bundleJson = parseJson($introBundleJson)
-
- # Parse IntroBundle from JSON
- let identBytes = hexToSeqByte(bundleJson["ident"].getStr())
- let ephemeralBytes = hexToSeqByte(bundleJson["ephemeral"].getStr())
-
- let introBundle = IntroBundle(
- ident: identBytes,
- ephemeral: ephemeralBytes
- )
-
+ # Convert bundle string to seq[byte]
+ let bundle = toBytes($introBundleStr)
+
# Convert hex content to bytes
let content = hexToSeqByte($contentHex)
-
+
# Create the conversation
- let errOpt = await ctx.myLib[].newPrivateConversation(introBundle, content)
+ let errOpt = await ctx.myLib[].newPrivateConversation(bundle, content)
if errOpt.isSome():
return err("failed to create conversation: " & $errOpt.get())
-
+
return ok("")
except CatchableError as e:
error "chat_new_private_conversation failed", error = e.msg
diff --git a/library/api/identity_api.nim b/library/api/identity_api.nim
index 2503242..439a72a 100644
--- a/library/api/identity_api.nim
+++ b/library/api/identity_api.nim
@@ -8,8 +8,6 @@ import ffi
import stew/byteutils
import src/chat
-import src/chat/crypto
-import src/chat/proto_types
import library/utils
logScope:
@@ -25,12 +23,9 @@ proc chat_get_identity(
userData: pointer
) {.ffi.} =
## Get the client identity
- ## Returns JSON string: {"name": "...", "address": "...", "pubkey": "hex..."}
- let ident = ctx.myLib[].identity()
+ ## Returns JSON string: {"name": "..."}
let identJson = %*{
- "name": ident.getName(),
- "address": ident.getAddr(),
- "pubkey": ident.getPubkey().toHex()
+ "name": ctx.myLib[].getId()
}
return ok($identJson)
@@ -44,11 +39,7 @@ proc chat_create_intro_bundle(
userData: pointer
) {.ffi.} =
## Create an IntroBundle for initiating private conversations
- ## Returns JSON string: {"ident": "hex...", "ephemeral": "hex..."}
+ ## Returns the intro bundle as an ASCII string (format: logos_chatintro__)
let bundle = ctx.myLib[].createIntroBundle()
- let bundleJson = %*{
- "ident": bundle.ident.toHex(),
- "ephemeral": bundle.ephemeral.toHex()
- }
- return ok($bundleJson)
+ return ok(string.fromBytes(bundle))
diff --git a/library/liblogoschat.h b/library/liblogoschat.h
index d0aac93..487a903 100644
--- a/library/liblogoschat.h
+++ b/library/liblogoschat.h
@@ -58,9 +58,6 @@ void set_event_callback(void *ctx, FFICallBack callback, void *userData);
// Get the client's identifier
int chat_get_id(void *ctx, FFICallBack callback, void *userData);
-// Get the default inbox conversation ID
-int chat_get_default_inbox_id(void *ctx, FFICallBack callback, void *userData);
-
//////////////////////////////////////////////////////////////////////////////
// Conversation Operations
//////////////////////////////////////////////////////////////////////////////
@@ -75,10 +72,10 @@ int chat_get_conversation(void *ctx, FFICallBack callback, void *userData,
const char *convoId);
// Create a new private conversation with the given IntroBundle
-// introBundleJson: JSON string with {"ident": "hex...", "ephemeral": "hex..."}
+// introBundleStr: Intro bundle ASCII string as returned by chat_create_intro_bundle
// contentHex: Initial message content as hex-encoded string
int chat_new_private_conversation(void *ctx, FFICallBack callback,
- void *userData, const char *introBundleJson,
+ void *userData, const char *introBundleStr,
const char *contentHex);
// Send a message to a conversation
@@ -93,11 +90,11 @@ int chat_send_message(void *ctx, FFICallBack callback, void *userData,
//////////////////////////////////////////////////////////////////////////////
// Get the client identity
-// Returns JSON: {"name": "...", "address": "...", "pubkey": "hex..."}
+// Returns JSON: {"name": "..."}
int chat_get_identity(void *ctx, FFICallBack callback, void *userData);
// Create an IntroBundle for initiating private conversations
-// Returns JSON: {"ident": "hex...", "ephemeral": "hex..."}
+// Returns the intro bundle as an ASCII string (format: logos_chatintro__)
int chat_create_intro_bundle(void *ctx, FFICallBack callback, void *userData);
#ifdef __cplusplus
diff --git a/library/liblogoschat.nim b/library/liblogoschat.nim
index 5d86a6e..87f8de0 100644
--- a/library/liblogoschat.nim
+++ b/library/liblogoschat.nim
@@ -1,7 +1,7 @@
-## liblogoschat - C bindings for the Chat SDK
+## liblogoschat - C bindings for Logos-Chat
## Main entry point for the shared library
##
-## This library exposes the Chat SDK functionality through a C-compatible FFI interface.
+## This library exposes the chat functionality through a C-compatible FFI interface.
## It uses nim-ffi for thread-safe async request handling.
import std/[json, options]
@@ -10,10 +10,8 @@ import stew/byteutils
import
src/chat/client,
- src/chat/conversations,
src/chat/identity,
src/chat/delivery/waku_client,
- src/chat/proto_types,
library/declare_lib,
library/utils
diff --git a/nim_chat_poc.nimble b/logos_chat.nimble
similarity index 90%
rename from nim_chat_poc.nimble
rename to logos_chat.nimble
index 04eaf01..bcf896c 100644
--- a/nim_chat_poc.nimble
+++ b/logos_chat.nimble
@@ -1,11 +1,11 @@
# Package
version = "0.1.0"
-author = "jazzz"
-description = "An example of the chat sdk in Nim"
+author = "Logos.co"
+description = "LogosChat is a decentralized permissionless messaging protocol."
license = "MIT"
srcDir = "src"
-bin = @["nim_chat_poc"]
+bin = @["logos_chat"]
# Dependencies
@@ -15,12 +15,11 @@ requires "nim >= 2.2.4",
"blake2",
"chronicles",
"libp2p",
- "nimchacha20poly1305", # TODO: remove
"confutils",
"eth",
"regex",
"web3",
- "https://github.com/jazzz/nim-sds#exports",
+ "libchat",
"waku",
"ffi"
@@ -77,6 +76,6 @@ task pingpong, "Build the Pingpong example":
let name = "pingpong"
buildBinary name, "examples/", "-d:chronicles_log_level='INFO' -d:chronicles_disabled_topics='waku node' "
-task liblogoschat, "Build the Chat SDK shared library (C bindings)":
+task liblogoschat, "Build the Logos-Chat shared library (C bindings)":
buildLibrary "logoschat", "library/",
"-d:chronicles_log_level='INFO' -d:chronicles_enabled=on --path:src --path:vendor/nim-ffi"
diff --git a/protos/conversations/group_v1.proto b/protos/conversations/group_v1.proto
deleted file mode 100644
index fde7013..0000000
--- a/protos/conversations/group_v1.proto
+++ /dev/null
@@ -1,28 +0,0 @@
-syntax = "proto3";
-
-package wap.convos.group_v1;
-
-import "base.proto";
-import "common_frames.proto";
-
-
-
-message ConversationInvite_GroupV1 {
- repeated string participants = 1;
-}
-
-
-
-message GroupV1Frame {
- // SDS like information: Message ID and channel_id extracted for utility
- string message_id = 2;
- string channel_id = 3; // Channel_id is associated with a set of participants
- // This conflicts with conversation based encryption,
- // need to ensure the derived sender is a valid participant
- base.ReliabilityInfo reliability_info = 10;
-
- oneof frame_type {
- common_frames.ContentFrame content = 100;
- // ...
- }
-}
diff --git a/protos/encryption.proto b/protos/encryption.proto
deleted file mode 100644
index cda0fb0..0000000
--- a/protos/encryption.proto
+++ /dev/null
@@ -1,26 +0,0 @@
-syntax = "proto3";
-
-package wap.encryption;
-
-
-// TODO: This also encompasses plaintexts, is there a better name?
-// Alternatives: ???
-message EncryptedPayload {
-
- oneof encryption {
- encryption.Plaintext plaintext = 1;
- encryption.Doubleratchet doubleratchet = 2;
- }
-}
-
-message Plaintext {
- bytes payload=1;
-}
-
-message Doubleratchet {
- bytes dh = 1; // 32 byte array
- uint32 msgNum = 2;
- uint32 prevChainLen = 3;
- bytes ciphertext = 4;
- string aux = 5;
-}
diff --git a/protos/envelope.proto b/protos/envelope.proto
deleted file mode 100644
index 6a961d4..0000000
--- a/protos/envelope.proto
+++ /dev/null
@@ -1,16 +0,0 @@
-syntax = "proto3";
-
-package wap.envelope;
-
-
-///////////////////////////////////////////////////////////////////////////////
-// Payload Framing Messages
-///////////////////////////////////////////////////////////////////////////////
-
-message WapEnvelopeV1 {
-
- string conversation_hint = 1;
- uint64 salt = 2;
-
- bytes payload = 5;
-}
diff --git a/protos/inbox.proto b/protos/inbox.proto
deleted file mode 100644
index 438b977..0000000
--- a/protos/inbox.proto
+++ /dev/null
@@ -1,17 +0,0 @@
-syntax = "proto3";
-
-package wap.inbox;
-
-import "invite.proto";
-
-message Note{
- string text = 1;
-}
-
-message InboxV1Frame {
- string recipient = 1;
- oneof frame_type {
- invite.InvitePrivateV1 invite_private_v1 = 10;
- Note note = 11;
- }
-}
diff --git a/protos/invite.proto b/protos/invite.proto
deleted file mode 100644
index da9f560..0000000
--- a/protos/invite.proto
+++ /dev/null
@@ -1,14 +0,0 @@
-syntax = "proto3";
-
-package wap.invite;
-
-import "encryption.proto";
-
-message InvitePrivateV1 {
- bytes initiator = 1;
- bytes initiator_ephemeral = 2;
- bytes participant = 3;
- int32 participant_ephemeral_id= 4;
- string discriminator = 5;
- encryption.EncryptedPayload initial_message = 6;
-}
diff --git a/protos/private_v1.proto b/protos/private_v1.proto
deleted file mode 100644
index 7dd69ff..0000000
--- a/protos/private_v1.proto
+++ /dev/null
@@ -1,19 +0,0 @@
-syntax = "proto3";
-
-package wap.convos.private_v1;
-
-
-message Placeholder {
- uint32 counter = 1;
-}
-
-message PrivateV1Frame {
- string conversation_id = 1;
- bytes sender = 2;
- int64 timestamp = 3; // Sender reported timestamp
- oneof frame_type {
- bytes content = 10;
- Placeholder placeholder = 11;
- // ....
- }
-}
diff --git a/protos/reliability.proto b/protos/reliability.proto
deleted file mode 100644
index f71f9b2..0000000
--- a/protos/reliability.proto
+++ /dev/null
@@ -1,23 +0,0 @@
-syntax = "proto3";
-
-package wap.reliability;
-
-///////////////////////////////////////////////////////////////////////////////
-// SDS Payloads
-///////////////////////////////////////////////////////////////////////////////
-
-message HistoryEntry {
- string message_id = 1; // Unique identifier of the SDS message, as defined in `Message`
- bytes retrieval_hint = 2; // Optional information to help remote parties retrieve this SDS
- // message; For example, A Waku deterministic message hash or routing payload hash
- }
-
- message ReliablePayload {
- string message_id = 2;
- string channel_id = 3;
- int32 lamport_timestamp = 10;
- repeated HistoryEntry causal_history = 11;
- bytes bloom_filter = 12;
- // Optional field causes errors in nim protobuf generation. Removing for now as optional is implied anways.
- bytes content = 20;
- }
diff --git a/src/chat.nim b/src/chat.nim
index 754d8da..8cabde4 100644
--- a/src/chat.nim
+++ b/src/chat.nim
@@ -1,17 +1,13 @@
import chat/[
client,
- crypto,
- conversations,
delivery/waku_client,
identity,
- links,
types
]
-export client, conversations, identity, links, waku_client
+export client, identity, waku_client
+export identity.`$`
#export specific frames need by applications
export MessageId
-export toHex
-export crypto.`$`
diff --git a/src/chat/client.nim b/src/chat/client.nim
index ef3d6c4..3717311 100644
--- a/src/chat/client.nim
+++ b/src/chat/client.nim
@@ -6,24 +6,15 @@
import # Foreign
chronicles,
chronos,
- sds,
- sequtils,
- std/tables,
- std/sequtils,
+ libchat,
+ std/options,
strformat,
- strutils,
- tables,
types
import #local
- conversations,
- conversations/convo_impl,
- crypto,
delivery/waku_client,
errors,
identity,
- inbox,
- proto_types,
types,
utils
@@ -35,6 +26,27 @@ logScope:
# Definitions
#################################################
+# Type used to return message data via callback
+type ReceivedMessage* = ref object of RootObj
+ sender*: PublicKey
+ timestamp*: int64
+ content*: seq[byte]
+
+
+type ConvoType = enum
+ PrivateV1
+
+type Conversation* = object
+ ctx: LibChat
+ convoId: string
+ ds: WakuClient
+ convo_type: ConvoType
+
+
+proc id*(self: Conversation): string =
+ return self.convoId
+
+
type
MessageCallback* = proc(conversation: Conversation, msg: ReceivedMessage): Future[void] {.async.}
NewConvoCallback* = proc(conversation: Conversation): Future[void] {.async.}
@@ -48,13 +60,11 @@ type KeyEntry* = object
timestamp: int64
type ChatClient* = ref object
- ident: Identity
+ libchatCtx: LibChat
ds*: WakuClient
- keyStore: Table[string, KeyEntry] # Keyed by HexEncoded Public Key
- conversations: Table[string, Conversation] # Keyed by conversation ID
+ id: string
inboundQueue: QueueRef
isRunning: bool
- inbox: Inbox
newMessageCallbacks: seq[MessageCallback]
newConvoCallbacks: seq[NewConvoCallback]
@@ -64,30 +74,24 @@ type ChatClient* = ref object
# Constructors
#################################################
-proc newClient*(ds: WakuClient, ident: Identity): ChatClient {.raises: [IOError,
- ValueError, SerializationError].} =
+proc newClient*(ds: WakuClient, ident: Identity): ChatClient {.raises: [IOError, ValueError].} =
## Creates new instance of a `ChatClient` with a given `WakuConfig`
+ ## TODO: (P1) Currently the passed in Identity is not used. Libchat Generates one for every invocation.
try:
- let rm = newReliabilityManager().valueOr:
- raise newException(ValueError, fmt"SDS InitializationError")
-
- let defaultInbox = initInbox(ident)
var q = QueueRef(queue: newAsyncQueue[ChatPayload](10))
- var c = ChatClient(ident: ident,
+ var c = ChatClient(
+ libchatCtx: newConversationsContext(),
ds: ds,
- keyStore: initTable[string, KeyEntry](),
- conversations: initTable[string, Conversation](),
+ id: ident.getName(),
inboundQueue: q,
isRunning: false,
- inbox: defaultInbox,
newMessageCallbacks: @[],
newConvoCallbacks: @[])
- c.conversations[defaultInbox.id()] = defaultInbox
- notice "Client started", client = c.ident.getName(),
- defaultInbox = defaultInbox, inTopic= topic_inbox(c.ident.get_addr())
+ notice "Client started", client = c.id
+
result = c
except Exception as e:
error "newCLient", err = e.msg
@@ -97,27 +101,12 @@ proc newClient*(ds: WakuClient, ident: Identity): ChatClient {.raises: [IOError,
#################################################
proc getId*(client: ChatClient): string =
- result = client.ident.getName()
-
-proc identity*(client: ChatClient): Identity =
- result = client.ident
-
-proc defaultInboxConversationId*(self: ChatClient): string =
- ## Returns the default inbox address for the client.
- result = conversationIdFor(self.ident.getPubkey())
-
-proc getConversationFromHint(self: ChatClient,
- conversationHint: string): Result[Option[Conversation], string] =
-
- # TODO: Implementing Hinting
- if not self.conversations.hasKey(conversationHint):
- ok(none(Conversation))
- else:
- ok(some(self.conversations[conversationHint]))
+ result = client.id
proc listConversations*(client: ChatClient): seq[Conversation] =
- result = toSeq(client.conversations.values())
+ # TODO: (P1) Implement list conversations
+ result = @[]
#################################################
# Callback Handling
@@ -135,6 +124,7 @@ proc onNewConversation*(client: ChatClient, callback: NewConvoCallback) =
proc notifyNewConversation(client: ChatClient, convo: Conversation) =
for cb in client.newConvoCallbacks:
+ debug "calling OnConvo CB", client=client.getId(), len = client.newConvoCallbacks.len()
discard cb(convo)
proc onDeliveryAck*(client: ChatClient, callback: DeliveryAckCallback) =
@@ -149,51 +139,47 @@ proc notifyDeliveryAck(client: ChatClient, convo: Conversation,
# Functional
#################################################
-proc createIntroBundle*(self: var ChatClient): IntroBundle =
+proc createIntroBundle*(self: ChatClient): seq[byte] =
## Generates an IntroBundle for the client, which includes
## the required information to send a message.
-
- # Create Ephemeral keypair, save it in the key store
- let ephemeralKey = generateKey()
-
- self.keyStore[ephemeralKey.getPublicKey().bytes().bytesToHex()] = KeyEntry(
- keyType: "ephemeral",
- privateKey: ephemeralKey,
- timestamp: getCurrentTimestamp()
- )
-
- result = IntroBundle(
- ident: @(self.ident.getPubkey().bytes()),
- ephemeral: @(ephemeralKey.getPublicKey().bytes()),
- )
-
+ result = self.libchatCtx.createIntroductionBundle().valueOr:
+ error "could not create bundle",error=error, client = self.getId()
+ return
+
notice "IntroBundleCreated", client = self.getId(),
- pubBytes = result.ident
+ bundle = result
+
+proc sendPayloads(ds: WakuClient, payloads: seq[PayloadResult]) =
+ for payload in payloads:
+ # TODO: (P2) surface errors
+ discard ds.sendBytes(payload.address, payload.data)
#################################################
# Conversation Initiation
#################################################
-proc addConversation*(client: ChatClient, convo: Conversation) =
- notice "Creating conversation", client = client.getId(), convoId = convo.id()
- client.conversations[convo.id()] = convo
- client.notifyNewConversation(convo)
proc getConversation*(client: ChatClient, convoId: string): Conversation =
- notice "Get conversation", client = client.getId(), convoId = convoId
- result = client.conversations[convoId]
+ result = Conversation(ctx:client.libchatCtx, convoId:convoId, ds: client.ds, convo_type: PrivateV1)
proc newPrivateConversation*(client: ChatClient,
- introBundle: IntroBundle, content: Content): Future[Option[ChatError]] {.async.} =
- ## Creates a private conversation with the given `IntroBundle`.
- ## `IntroBundles` are provided out-of-band.
- let remote_pubkey = loadPublicKeyFromBytes(introBundle.ident).get()
- let remote_ephemeralkey = loadPublicKeyFromBytes(introBundle.ephemeral).get()
+ introBundle: seq[byte], content: Content): Future[Option[ChatError]] {.async.} =
- let convo = await client.inbox.inviteToPrivateConversation(client.ds,remote_pubkey, remote_ephemeralkey, content )
- client.addConversation(convo) # TODO: Fix re-entrantancy bug. Convo needs to be saved before payload is sent.
+ let res = client.libchatCtx.createNewPrivateConvo(introBundle, content)
+ let (convoId, payloads) = res.valueOr:
+ error "could not create bundle",error=error, client = client.getId()
+ return some(ChatError(code: errLibChat, context:fmt"got: {error}" ))
+
+ client.ds.sendPayloads(payloads);
+
+
+ client.notifyNewConversation(Conversation(ctx: client.libchatCtx,
+ convoId : convoId, ds: client.ds, convo_type: ConvoType.PrivateV1
+ ))
+
+ notice "CREATED", client=client.getId(), convoId=convoId
return none(ChatError)
@@ -202,31 +188,37 @@ proc newPrivateConversation*(client: ChatClient,
# Receives a incoming payload, decodes it, and processes it.
#################################################
-proc parseMessage(client: ChatClient, msg: ChatPayload) {.raises: [ValueError,
- SerializationError].} =
- let envelopeRes = decode(msg.bytes, WapEnvelopeV1)
- if envelopeRes.isErr:
- debug "Failed to decode WapEnvelopeV1", client = client.getId(), err = envelopeRes.error
- return
- let envelope = envelopeRes.get()
-
- let convo = block:
- let opt = client.getConversationFromHint(envelope.conversationHint).valueOr:
- raise newException(ValueError, "Failed to get conversation: " & error)
-
- if opt.isSome():
- opt.get()
- else:
- let k = toSeq(client.conversations.keys()).join(", ")
- warn "No conversation found", client = client.getId(),
- hint = envelope.conversationHint, knownIds = k
- return
+proc parseMessage(client: ChatClient, msg: ChatPayload) {.raises: [ValueError].} =
try:
- convo.handleFrame(client, envelope.payload)
+ let opt_content = client.libchatCtx.handlePayload(msg.bytes).valueOr:
+ error "handlePayload" , error=error, client=client.getId()
+ return
+
+ if opt_content.isSome():
+ let content = opt_content.get()
+ let convo = client.getConversation(content.conversationId)
+
+ if content.isNewConvo:
+ client.notifyNewConversation(convo)
+
+ # TODO: (P1) Add sender information from LibChat.
+ let msg = ReceivedMessage(timestamp:getCurrentTimestamp(),content: content.data )
+ client.notifyNewMessage(convo, msg)
+ else:
+ debug "Parsed message generated no content", client=client.getId()
+
except Exception as e:
error "HandleFrame Failed", error = e.msg
+proc sendMessage*(convo: Conversation, content: Content) : Future[MessageId] {.async, gcsafe.} =
+ let payloads = convo.ctx.sendContent(convo.convoId, content).valueOr:
+ error "SendMessage", e=error
+ return "error"
+
+ convo.ds.sendPayloads(payloads);
+
+
#################################################
# Async Tasks
#################################################
@@ -237,20 +229,9 @@ proc messageQueueConsumer(client: ChatClient) {.async.} =
while client.isRunning:
let message = await client.inboundQueue.queue.get()
+ debug "Got WakuMessage", client = client.getId() , topic= message.content_topic, len=message.bytes.len()
- let topicRes = inbox.parseTopic(message.contentTopic).or(private_v1.parseTopic(message.contentTopic))
- if topicRes.isErr:
- debug "Invalid content topic", client = client.getId(), err = topicRes.error, contentTopic = message.contentTopic
- continue
-
- notice "Inbound Message Received", client = client.getId(),
- contentTopic = message.contentTopic, len = message.bytes.len()
- try:
- client.parseMessage(message)
-
- except CatchableError as e:
- error "Error in message listener", err = e.msg,
- pubsub = message.pubsubTopic, contentTopic = message.contentTopic
+ client.parseMessage(message)
#################################################
@@ -271,5 +252,6 @@ proc start*(client: ChatClient) {.async.} =
proc stop*(client: ChatClient) {.async.} =
## Stop the client.
await client.ds.stop()
+ client.libchatCtx.destroy()
client.isRunning = false
notice "Client stopped", client = client.getId()
diff --git a/src/chat/conversation_store.nim b/src/chat/conversation_store.nim
deleted file mode 100644
index e629ea7..0000000
--- a/src/chat/conversation_store.nim
+++ /dev/null
@@ -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)
diff --git a/src/chat/conversations.nim b/src/chat/conversations.nim
deleted file mode 100644
index eb426a7..0000000
--- a/src/chat/conversations.nim
+++ /dev/null
@@ -1,5 +0,0 @@
-import
- ./conversations/[convo_type, private_v1, message]
-
-
-export private_v1, convo_type, message
diff --git a/src/chat/conversations/convo_impl.nim b/src/chat/conversations/convo_impl.nim
deleted file mode 100644
index 60fb9e5..0000000
--- a/src/chat/conversations/convo_impl.nim
+++ /dev/null
@@ -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)
-
-
diff --git a/src/chat/conversations/convo_type.nim b/src/chat/conversations/convo_type.nim
deleted file mode 100644
index b810eed..0000000
--- a/src/chat/conversations/convo_type.nim
+++ /dev/null
@@ -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")
diff --git a/src/chat/conversations/message.nim b/src/chat/conversations/message.nim
deleted file mode 100644
index 6f82edf..0000000
--- a/src/chat/conversations/message.nim
+++ /dev/null
@@ -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]
-
-
diff --git a/src/chat/conversations/private_v1.nim b/src/chat/conversations/private_v1.nim
deleted file mode 100644
index 9e8b264..0000000
--- a/src/chat/conversations/private_v1.nim
+++ /dev/null
@@ -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)
diff --git a/src/chat/crypto.nim b/src/chat/crypto.nim
deleted file mode 100644
index 1aa4cc5..0000000
--- a/src/chat/crypto.nim
+++ /dev/null
@@ -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]}"
diff --git a/src/chat/delivery/waku_client.nim b/src/chat/delivery/waku_client.nim
index 4662120..70f59a2 100644
--- a/src/chat/delivery/waku_client.nim
+++ b/src/chat/delivery/waku_client.nim
@@ -20,7 +20,6 @@ import
waku_filter_v2/client,
]
-import ../proto_types
logScope:
topics = "chat waku"
@@ -72,9 +71,9 @@ proc DefaultConfig*(): WakuConfig =
shardId: @[shardId], pubsubTopic: &"/waku/2/rs/{clusterId}/{shardId}",
staticPeers: @[])
-proc sendPayload*(client: WakuClient, contentTopic: string,
- env: WapEnvelopeV1) {.async.} =
- let bytes = encode(env)
+
+proc sendBytes*(client: WakuClient, contentTopic: string,
+ bytes: seq[byte]) {.async.} =
let msg = WakuMessage(contentTopic: contentTopic, payload: bytes)
let res = await client.node.publish(some(PubsubTopic(client.cfg.pubsubTopic)), msg)
diff --git a/src/chat/errors.nim b/src/chat/errors.nim
index c74b5b2..f5b93e3 100644
--- a/src/chat/errors.nim
+++ b/src/chat/errors.nim
@@ -9,6 +9,7 @@ type
errTypeError
errWrapped
errTopic
+ errLibChat
proc `$`*(x: ChatError): string =
fmt"ChatError(code={$x.code}, context: {x.context})"
diff --git a/src/chat/identity.nim b/src/chat/identity.nim
index 37b4267..794fcc1 100644
--- a/src/chat/identity.nim
+++ b/src/chat/identity.nim
@@ -1,6 +1,10 @@
-import crypto
+import crypto/ecdh
import results
+import strformat
+import utils
+
+export PublicKey, PrivateKey, loadPrivateKeyFromBytes, loadPublicKeyFromBytes
type
@@ -28,6 +32,13 @@ proc getPubkey*(self: Identity): PublicKey =
proc getAddr*(self: Identity): string =
result = get_addr(self.getPubKey())
-
proc getName*(self: Identity): string =
result = self.name
+
+proc toHex(key: PublicKey): string =
+ bytesToHex(key.bytes())
+
+proc `$`*(key: PublicKey): string =
+ let byteStr = toHex(key)
+ fmt"{byteStr[0..3]}..{byteStr[^4 .. ^1]}"
+
diff --git a/src/chat/inbox.nim b/src/chat/inbox.nim
deleted file mode 100644
index 7af319a..0000000
--- a/src/chat/inbox.nim
+++ /dev/null
@@ -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"
diff --git a/src/chat/links.nim b/src/chat/links.nim
deleted file mode 100644
index e877235..0000000
--- a/src/chat/links.nim
+++ /dev/null
@@ -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}"
diff --git a/src/chat/proto_types.nim b/src/chat/proto_types.nim
deleted file mode 100644
index a608046..0000000
--- a/src/chat/proto_types.nim
+++ /dev/null
@@ -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")
diff --git a/src/chat/utils.nim b/src/chat/utils.nim
index d6b3a26..9be2dcf 100644
--- a/src/chat/utils.nim
+++ b/src/chat/utils.nim
@@ -6,7 +6,6 @@ import strutils
proc getCurrentTimestamp*(): Timestamp =
result = waku_core.getNanosecondTime(getTime().toUnix())
-
proc hash_func*(s: string | seq[byte]): string =
# This should be Blake2s but it does not exist so substituting with Blake2b
result = getBlake2b(s, 4, "")
@@ -17,17 +16,3 @@ proc bytesToHex*[T](bytes: openarray[T], lowercase: bool = false): string =
for b in bytes:
let hex = b.toHex(2)
result.add(if lowercase: hex.toLower() else: hex)
-
-proc toBytes*(s: string): seq[byte] =
- result = cast[seq[byte]](s)
-
-proc toUtfString*(b: seq[byte]): string =
- result = cast[string](b)
-
-macro panic*(reason: string): untyped =
- result = quote do:
- let pos = instantiationInfo()
- echo `reason` & " ($1:$2)" % [
- pos.filename, $pos.line]
- echo "traceback:\n", getStackTrace()
- quit(1)
diff --git a/src/content_types/all.nim b/src/content_types/all.nim
index f4b6425..0ced84a 100644
--- a/src/content_types/all.nim
+++ b/src/content_types/all.nim
@@ -6,8 +6,6 @@ import protobuf_serialization/proto_parser
import results
import strformat
-import ../chat/proto_types
-
export protobuf_serialization
diff --git a/src/naxolotl.nim b/src/naxolotl.nim
deleted file mode 100644
index f19bbaf..0000000
--- a/src/naxolotl.nim
+++ /dev/null
@@ -1,7 +0,0 @@
-import naxolotl/[
- naxolotl,
- curve25519,
- errors
-]
-
-export naxolotl, curve25519, NaxolotlError
\ No newline at end of file
diff --git a/src/naxolotl/chacha.nim b/src/naxolotl/chacha.nim
deleted file mode 100644
index a20bab9..0000000
--- a/src/naxolotl/chacha.nim
+++ /dev/null
@@ -1,63 +0,0 @@
-import nim_chacha20_poly1305/[common, chacha20_poly1305, poly1305]
-import std/[sysrand]
-import results
-import strformat
-
-import types
-import errors
-
-
-proc encryptWithChaCha20Poly1305*(msgKey: MessageKey, plaintext: var openArray[byte], associatedData: openArray[byte]) : (Nonce, CipherText) =
-
- var nonce : Nonce
- discard urandom(nonce)
-
- var tag: Tag
- var ciphertext = newSeq[byte](plaintext.len + tag.len)
-
- var counter : Counter = 0
-
- # TODO: check plaintext mutability requirement
- chacha20_aead_poly1305_encrypt(
- Key(msgKey),
- nonce,
- counter,
- associatedData,
- plaintext,
- ciphertext.toOpenArray(0, plaintext.high),
- tag
- )
-
- # Combine tag with cipherkey for ease of transport and consistency with other implementations
- copyMem(addr ciphertext[plaintext.len], unsafeAddr tag[0], tag.len)
- (nonce, ciphertext)
-
-
-proc decryptWithChaCha20Poly1305*(msgKey: MessageKey, nonce: Nonce, ciphertext: var openArray[byte], associatedData: openArray[byte]) : Result[seq[byte], NaxolotlError] =
- var tag : Tag
- if ciphertext.len <= tag.len:
- return err(NaxolotlError(code: errInvalidInput, context: fmt"ciphertext is less than {tag.len} bytes. Expected `ciphertext || tag`" ))
-
- copyMem(addr tag[0], unsafeAddr ciphertext[^tag.len], tag.len)
-
- var plaintext = newSeq[byte](ciphertext.len - tag.len)
-
- var computedTag: Tag
- var counter : Counter = 0
-
- chacha20_aead_poly1305_decrypt(
- Key(msgKey),
- nonce,
- counter,
- associatedData,
- plaintext,
- ciphertext.toOpenArray(0,ciphertext.high - tag.len),
- computedTag
- )
-
- if not poly1305_verify(tag, computedTag):
- return err(NaxolotlError(code: errMessageAuthentication, context: fmt"Got Tag: {tag} expected: {computedTag}"))
-
-
- ok(plaintext)
-
\ No newline at end of file
diff --git a/src/naxolotl/curve25519.nim b/src/naxolotl/curve25519.nim
deleted file mode 100644
index 140b928..0000000
--- a/src/naxolotl/curve25519.nim
+++ /dev/null
@@ -1,123 +0,0 @@
-# See https://github.com/vacp2p/nim-libp2p/blob/master/libp2p/crypto/curve25519.nim
-
-import bearssl/[ec, rand]
-import results
-from stew/assign2 import assign
-export results
-
-const Curve25519KeySize* = 32
-
-type
- Curve25519* = object
- Curve25519Key* = array[Curve25519KeySize, byte]
- Curve25519Error* = enum
- Curver25519GenError
-
-proc intoCurve25519Key*(s: openArray[byte]): Curve25519Key =
- assert s.len == Curve25519KeySize
- assign(result, s)
-
-proc getBytes*(key: Curve25519Key): seq[byte] =
- @key
-
-proc byteswap(buf: var Curve25519Key) {.inline.} =
- for i in 0 ..< 16:
- let x = buf[i]
- buf[i] = buf[31 - i]
- buf[31 - i] = x
-
-proc mul*(_: type[Curve25519], point: var Curve25519Key, multiplier: Curve25519Key) =
- let defaultBrEc = ecGetDefault()
-
- # multiplier needs to be big-endian
- var multiplierBs = multiplier
- multiplierBs.byteswap()
- let res = defaultBrEc.mul(
- addr point[0],
- Curve25519KeySize,
- addr multiplierBs[0],
- Curve25519KeySize,
- EC_curve25519,
- )
- assert res == 1
-
-proc mulgen(_: type[Curve25519], dst: var Curve25519Key, point: Curve25519Key) =
- let defaultBrEc = ecGetDefault()
-
- var rpoint = point
- rpoint.byteswap()
-
- let size =
- defaultBrEc.mulgen(addr dst[0], addr rpoint[0], Curve25519KeySize, EC_curve25519)
-
- assert size == Curve25519KeySize
-
-proc public*(private: Curve25519Key): Curve25519Key =
- Curve25519.mulgen(result, private)
-
-proc random*(_: type[Curve25519Key], rng: var HmacDrbgContext): Curve25519Key =
- var res: Curve25519Key
- let defaultBrEc = ecGetDefault()
- let len = ecKeygen(
- PrngClassPointerConst(addr rng.vtable), defaultBrEc, nil, addr res[0], EC_curve25519
- )
- # Per bearssl documentation, the keygen only fails if the curve is
- # unrecognised -
- doAssert len == Curve25519KeySize, "Could not generate curve"
-
- res
-
-const FieldElementSize* = Curve25519KeySize
-
-type FieldElement* = Curve25519Key
-
-# Convert bytes to FieldElement
-proc bytesToFieldElement*(bytes: openArray[byte]): Result[FieldElement, string] =
- if bytes.len != FieldElementSize:
- return err("Field element size must be 32 bytes")
- ok(intoCurve25519Key(bytes))
-
-# Convert FieldElement to bytes
-proc fieldElementToBytes*(fe: FieldElement): seq[byte] =
- fe.getBytes()
-
-# Generate a random FieldElement
-proc generateRandomFieldElement*(): Result[FieldElement, string] =
- let rng = HmacDrbgContext.new()
- if rng.isNil:
- return err("Failed to creat HmacDrbgContext with system randomness")
- ok(Curve25519Key.random(rng[]))
-
-# Generate a key pair (private key and public key are both FieldElements)
-proc generateKeyPair*(): Result[tuple[privateKey, publicKey: FieldElement], string] =
- let privateKeyRes = generateRandomFieldElement()
- if privateKeyRes.isErr:
- return err(privateKeyRes.error)
- let privateKey = privateKeyRes.get()
-
- let publicKey = public(privateKey)
- ok((privateKey, publicKey))
-
-# # Multiply a given Curve25519 point with a set of scalars
-# proc multiplyPointWithScalars*(
-# point: FieldElement, scalars: openArray[FieldElement]
-# ): FieldElement =
-# var res = point
-# for scalar in scalars:
-# Curve25519.mul(res, scalar)
-# res
-
-# # Multiply the Curve25519 base point with a set of scalars
-# proc multiplyBasePointWithScalars*(
-# scalars: openArray[FieldElement]
-# ): Result[FieldElement, string] =
-# if scalars.len <= 0:
-# return err("Atleast one scalar must be provided")
-# var res: FieldElement = public(scalars[0]) # Use the predefined base point
-# for i in 1 ..< scalars.len:
-# Curve25519.mul(res, scalars[i]) # Multiply with each scalar
-# ok(res)
-
-# # Compare two FieldElements
-# proc compareFieldElements*(a, b: FieldElement): bool =
-# a == b
diff --git a/src/naxolotl/errors.nim b/src/naxolotl/errors.nim
deleted file mode 100644
index 57ac784..0000000
--- a/src/naxolotl/errors.nim
+++ /dev/null
@@ -1,11 +0,0 @@
-
-type
- NaxolotlError* = object of CatchableError
- code*: ErrorCode
- context*: string
-
- ErrorCode* = enum
- errDecryption
- errMessageAuthentication
- errInvalidInput
- errProgram
\ No newline at end of file
diff --git a/src/naxolotl/naxolotl.nim b/src/naxolotl/naxolotl.nim
deleted file mode 100644
index d9058fd..0000000
--- a/src/naxolotl/naxolotl.nim
+++ /dev/null
@@ -1,210 +0,0 @@
-import curve25519
-import results
-import chronicles
-import nim_chacha20_poly1305/common
-import strformat
-import strutils
-import sequtils
-import tables
-
-import chacha
-import types
-import utils
-import errors
-
-const maxSkip = 10
-
-
-type Doubleratchet* = object
- dhSelf: PrivateKey
- dhRemote: PublicKey
-
- rootKey: RootKey
- chainKeySend: ChainKey
- chainKeyRecv: ChainKey
-
- msgCountSend: MsgCount
- msgCountRecv: MsgCount
- prevChainLen: MsgCount
-
- # TODO: SkippedKeys
- skippedMessageKeys: Table[(PublicKey,MsgCount), MessageKey]
-
-const DomainSepKdfRoot = "DoubleRatchetRootKey"
-
-
-type DrHeader* = object
- dhPublic*: PublicKey
- msgNumber*: uint32
- prevChainLen*: uint32
-
-
-
-func keyId(dh:PublicKey, recvCount: MsgCount ): KeyId =
- (dh, recvCount)
-
-func hex(a: openArray[byte]) : string =
- a.mapIt(&"{it:02X}").join("")
-
-proc `$`*(x: DrHeader): string =
- "DrHeader(pubKey=" & hex(x.dhPublic) & ", msgNum=" & $x.msgNumber & ", msgNum=" & $x.prevChainLen & ")"
-
-
-proc `$`*(key: array[32, byte]): string =
- let byteStr = hex(key)
- fmt"{byteStr[0..5]}..{byteStr[^6 .. ^1]}"
-
-
-proc generateDhKey() : PrivateKey =
- result = generateKeypair().get()[0]
-
-#################################################
-# Kdf
-#################################################
-
-
-func kdfRoot(rootKey: RootKey, dhOutput:DhDerivedKey): (RootKey, ChainKey) =
-
- var salt = rootKey
- var ikm = dhOutput
- let info = cast[seq[byte]](DomainSepKdfRoot)
-
- hkdfSplit(salt, ikm, info)
-
-func kdfChain(chainKey: ChainKey): (MessageKey, ChainKey) =
-
- let msgKey = hkdfExtract(chainKey, [0x01u8])
- let chainKey = hkdfExtract(chainKey, [0x02u8])
-
- return(msgKey, chainKey)
-
-func dhRatchetSend(self: var Doubleratchet) =
- # Perform DH Ratchet step when receiving a new peer key.
- let dhOutput : DhDerivedKey = dhExchange(self.dhSelf, self.dhRemote).get()
- let (newRootKey, newChainKeySend) = kdfRoot(self.rootKey, dhOutput)
- self.rootKey = newRootKey
- self.chainKeySend = newChainKeySend
- self.msgCountSend = 0
-
-proc dhRatchetRecv(self: var Doubleratchet, remotePublickey: PublicKey ) =
- self.prevChainLen = self.msgCountSend
- self.msgCountSend = 0
- self.msgCountRecv = 0
-
- self.dhRemote = remotePublickey
-
- let dhOutputPre = self.dhSelf.dhExchange(self.dhRemote).get()
- let (newRootKey, newChainKeyRecv) = kdfRoot(self.rootKey, dhOutputPre)
- self.rootKey = newRootKey
- self.chainKeyRecv = newChainKeyRecv
-
- self.dhSelf = generateDhKey()
-
- let dhOutputPost = self.dhSelf.dhExchange(self.dhRemote).get()
- (self.rootKey, self.chainKeySend) = kdfRoot(self.rootKey, dhOutputPost)
-
-
-proc skipMessageKeys(self: var Doubleratchet, until: MsgCount): Result[(), string] =
-
- if self.msgCountRecv + maxSkip < until:
- return err("Too many skipped messages")
-
- while self.msgCountRecv < until:
- let (msgKey, chainKey) = kdfChain(self.chainKeyRecv)
- self.chainKeyRecv = chainKey
-
- let keyId = keyId(self.dhRemote, self.msgCountRecv)
- self.skippedMessageKeys[keyId] = msgKey
- inc self.msgCountRecv
-
- ok(())
-
-proc encrypt(self: var Doubleratchet, plaintext: var seq[byte], associatedData: openArray[byte]): (DrHeader, CipherText) =
-
- let (msgKey, chainKey) = kdfChain(self.chainKeySend)
- self.chainKeySend = chainKey
- let header = DrHeader(
- dhPublic: self.dhSelf.public, #TODO Serialize
- msgNumber: self.msgCountSend,
- prevChainLen: self.prevChainLen)
-
- self.msgCountSend = self.msgCountSend + 1
-
-
- var (nonce, ciphertext) = encryptWithChaCha20Poly1305(msgKey, plaintext, associatedData)
-
- # TODO: optimize copies
- var output : seq[byte]
- output.add(nonce)
- output.add(ciphertext)
-
- (header, output)
-
-
-proc decrypt*(self: var Doubleratchet, header: DrHeader, ciphertext: CipherText, associatedData: openArray[byte] ) : Result[seq[byte], NaxolotlError] =
-
- let peerPublic = header.dhPublic
- var msgKey : MessageKey
-
- # Check Skipped Keys
- let keyId = keyId(header.dhPublic, header.msgNumber)
- if self.skippedMessageKeys.hasKey(keyId):
- debug "detected skipped message", keyId = keyId
- msgKey = self.skippedMessageKeys[keyId]
- else:
- if (peerPublic != self.dhRemote):
- let r = self.skipMessageKeys(header.prevChainLen)
- if r.isErr:
- error "skipMessages", error = r.error()
- self.dhRatchetRecv(peerPublic)
- let r = self.skipMessageKeys(header.msgNumber)
- if r.isErr:
- error "skipMessages", error = r.error()
-
- (msgKey, self.chainKeyRecv) = kdfChain(self.chainKeyRecv)
- inc self.msgCountRecv
-
- var nonce : Nonce
- copyMem(addr nonce[0], unsafeAddr ciphertext[0], Nonce.len)
- var cipherTag = ciphertext[Nonce.len .. ^1]
-
- result = decryptWithChaCha20Poly1305(msgKey,nonce, cipherTag, associatedData )
-
- if result.isOk:
- # TODO: persist chainKey state changes
- self.skippedMessageKeys.del(keyId)
-
-
-proc encrypt*(self: var Doubleratchet, plaintext: var seq[byte]) : (DrHeader, CipherText) =
- encrypt(self, plaintext,@[])
-
-
-proc initDoubleratchetSender*(sharedSecret: array[32, byte], dhRemote: PublicKey): Doubleratchet =
-
- result = Doubleratchet(
- dhSelf: generateDhKey(),
- dhRemote: dhRemote,
- rootKey: RootKey(sharedSecret),
- msgCountSend: 0,
- msgCountRecv: 0,
- prevChainLen: 0,
- skippedMessageKeys: initTable[(PublicKey, MsgCount), MessageKey]()
- )
-
- # Update RK, CKs
- result.dhRatchetSend()
-
-proc initDoubleratchetRecipient*(sharedSecret: array[32, byte], dhSelf: PrivateKey): Doubleratchet =
-
- result = Doubleratchet(
- dhSelf: dhSelf,
- #dhRemote: None,
- rootKey: RootKey(sharedSecret),
- msgCountSend: 0,
- msgCountRecv: 0,
- prevChainLen: 0,
- skippedMessageKeys: initTable[(PublicKey, MsgCount), MessageKey]()
- )
-
-func dhSelfPublic*(self: Doubleratchet): PublicKey =
- self.dhSelf.public
\ No newline at end of file
diff --git a/src/naxolotl/types.nim b/src/naxolotl/types.nim
deleted file mode 100644
index a4504f4..0000000
--- a/src/naxolotl/types.nim
+++ /dev/null
@@ -1,18 +0,0 @@
-import curve25519
-
-type PrivateKey* = Curve25519Key
-type PublicKey* = Curve25519Key
-
-type RootKey* = array[32, byte]
-type ChainKey* = array[32, byte]
-type MessageKey* = array[32, byte]
-type DhDerivedKey* = array[32, byte]
-
-type GenericArray* = array[32, byte]
-
-type CipherText* = seq[byte]
-
-type MsgCount* = uint32
-type KeyId* = (PublicKey, MsgCount)
-
-const KeyLen* = 32
\ No newline at end of file
diff --git a/src/naxolotl/utils.nim b/src/naxolotl/utils.nim
deleted file mode 100644
index 4ec9964..0000000
--- a/src/naxolotl/utils.nim
+++ /dev/null
@@ -1,55 +0,0 @@
-import constantine/hashes
-import constantine/kdf/kdf_hkdf
-import curve25519
-import results
-
-import errors
-import types
-
-
-func hkdfExtract*(key: openArray[byte], seed: openArray[byte]) : GenericArray =
-
- assert GenericArray.len == sha256.digestSize()
-
- var ctx{.noInit.}: HKDF[sha256]
- var prk{.noInit.}: array[sha256.digestSize(), byte]
- ctx.hkdfExtract(prk, key, seed)
-
- return prk
-
-
-
-func hkdfExtractExpand*(output: var openArray[byte], salt: openArray[byte], ikm: openArray[byte], info: openArray[byte] ) =
- var ctx{.noInit.}: HKDF[sha256]
- var prk{.noInit.}: array[sha256.digestSize(), byte]
- ctx.hkdfExtract(prk, salt, ikm)
- ctx.hkdfExpand(output, prk, info, true)
-
-
-func hkdfSplit*(salt: GenericArray, ikm: GenericArray, info: openArray[byte] ) : (RootKey, ChainKey) =
-
- var output : array[KeyLen*2 , byte]
-
- hkdfExtractExpand(output, salt, ikm, info)
-
- var out1 : array[KeyLen, byte]
- var out2 : array[KeyLen, byte]
-
- # Unsafe memcopy
- copyMem(addr out1[0], addr output[0], KeyLen)
- copyMem(addr out2[0], addr output[32], KeyLen)
-
- result = (out1,out2)
-
-
-
-func dhExchange*(a: PrivateKey, b: PublicKey): Result[DhDerivedKey, NaxolotlError] =
- var dhOuput = b
-
- try:
- Curve25519.mul(dhOuput, a)
- except CatchableError as e:
- return err(NaxolotlError( code: errProgram, context: e.msg))
- ok(DhDerivedKey(dhOuput))
-
-
diff --git a/tests/all_tests.nim b/tests/all_tests.nim
index cb136ba..c75d9cc 100644
--- a/tests/all_tests.nim
+++ b/tests/all_tests.nim
@@ -1,4 +1,3 @@
# import individual test suites
import ./test_curve25519
-import ./test_naxolotl
diff --git a/tests/test_naxolotl.nim b/tests/test_naxolotl.nim
deleted file mode 100644
index 9ce0c41..0000000
--- a/tests/test_naxolotl.nim
+++ /dev/null
@@ -1,188 +0,0 @@
-
-import unittest
-import results
-import random
-import sequtils
-import std/md5
-import strformat
-import strutils
-
-import ../src/naxolotl
-import ../src/naxolotl/utils
-import ../src/naxolotl/types
-
-
-
-# Key share test from RFC-7748:
-const ks7748_a_priv = "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a"
-const ks7748_a_pub = "8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a" # Public key point (x co-ord)
-
-const ks7748_b_priv = "5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb"
-const ks7748_b_pub = "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f" # Public key point (x co-ord)s
-
-const ks7748_shared_key = "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742"
-
-# import parseutils
-
-proc hexToArray*[N: static[int]](hexStr: string): array[N, byte] =
- ## Converts hex string to fixed-size byte array
- if hexStr.len != N * 2:
- raise newException(ValueError,
- "Hex string length (" & $hexStr.len & ") doesn't match array size (" & $(
- N*2) & ")")
-
- for i in 0..