From 7ee12eb25023bbc97f3b071fa195f06e459fb50f Mon Sep 17 00:00:00 2001 From: Jazz Turner-Baggs <473256+jazzz@users.noreply.github.com> Date: Tue, 16 Dec 2025 08:20:53 -0800 Subject: [PATCH] Inbound Path (#38) * refactor invite * Update tests * Cleanups * Remove ContentFrame references --- examples/pingpong.nim | 2 +- examples/tui/tui.nim | 2 +- protos/invite.proto | 3 + src/chat/client.nim | 55 +++-------- src/chat/conversations/private_v1.nim | 127 ++++++++++++++++---------- src/chat/identity.nim | 2 +- src/chat/inbox.nim | 64 +++++++++++-- src/chat/proto_types.nim | 1 - src/naxolotl/naxolotl.nim | 48 +++++++--- tests/test_naxolotl.nim | 27 ++---- 10 files changed, 198 insertions(+), 133 deletions(-) diff --git a/examples/pingpong.nim b/examples/pingpong.nim index bd5f75d..83a457a 100644 --- a/examples/pingpong.nim +++ b/examples/pingpong.nim @@ -82,7 +82,7 @@ proc main() {.async.} = # Perform OOB Introduction: Raya -> Saro let raya_bundle = raya.createIntroBundle() - discard await saro.newPrivateConversation(raya_bundle) + discard await saro.newPrivateConversation(raya_bundle, initTextFrame("Init").toContentFrame().toBytes()) await sleepAsync(20.seconds) # Run for some time diff --git a/examples/tui/tui.nim b/examples/tui/tui.nim index a3b4716..010f17d 100644 --- a/examples/tui/tui.nim +++ b/examples/tui/tui.nim @@ -203,7 +203,7 @@ proc drawStatusBar(app: ChatApp, layout: Pane , fg: ForegroundColor, bg: Backgro var i = layout.yStart + 1 var chunk = layout.width - 9 - tb.write(1, i, "Name: " & app.client.getId()) + tb.write(1, i, "Name: " & app.client.getName()) inc i tb.write(1, i, fmt"PeerCount: {app.peerCount}") inc i diff --git a/protos/invite.proto b/protos/invite.proto index 76e518e..da9f560 100644 --- a/protos/invite.proto +++ b/protos/invite.proto @@ -2,10 +2,13 @@ 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/src/chat/client.nim b/src/chat/client.nim index 3d5b374..02e2c73 100644 --- a/src/chat/client.nim +++ b/src/chat/client.nim @@ -54,6 +54,7 @@ type Client* = ref object conversations: Table[string, Conversation] # Keyed by conversation ID inboundQueue: QueueRef isRunning: bool + inbox: Inbox newMessageCallbacks: seq[MessageCallback] newConvoCallbacks: seq[NewConvoCallback] @@ -71,6 +72,8 @@ proc newClient*(cfg: WakuConfig, ident: Identity): Client {.raises: [IOError, let rm = newReliabilityManager().valueOr: raise newException(ValueError, fmt"SDS InitializationError") + let defaultInbox = initInbox(ident) + var q = QueueRef(queue: newAsyncQueue[ChatPayload](10)) var c = Client(ident: ident, ds: waku, @@ -78,14 +81,14 @@ proc newClient*(cfg: WakuConfig, ident: Identity): Client {.raises: [IOError, conversations: initTable[string, Conversation](), inboundQueue: q, isRunning: false, + inbox: defaultInbox, newMessageCallbacks: @[], newConvoCallbacks: @[]) - let defaultInbox = initInbox(c.ident.getPubkey()) c.conversations[defaultInbox.id()] = defaultInbox - notice "Client started", client = c.ident.getId(), - defaultInbox = defaultInbox + notice "Client started", client = c.ident.getName(), + defaultInbox = defaultInbox, inTopic= topic_inbox(c.ident.get_addr()) result = c except Exception as e: error "newCLient", err = e.msg @@ -95,7 +98,7 @@ proc newClient*(cfg: WakuConfig, ident: Identity): Client {.raises: [IOError, ################################################# proc getId*(client: Client): string = - result = client.ident.getId() + result = client.ident.getName() proc identity*(client: Client): Identity = result = client.ident @@ -174,7 +177,7 @@ proc createIntroBundle*(self: var Client): IntroBundle = ################################################# proc addConversation*(client: Client, convo: Conversation) = - notice "Creating conversation", client = client.getId(), topic = convo.id() + notice "Creating conversation", client = client.getId(), convoId = convo.id() client.conversations[convo.id()] = convo client.notifyNewConversation(convo) @@ -183,47 +186,15 @@ proc getConversation*(client: Client, convoId: string): Conversation = result = client.conversations[convoId] proc newPrivateConversation*(client: Client, - introBundle: IntroBundle): Future[Option[ChatError]] {.async.} = + 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() - notice "New PRIVATE Convo ", client = client.getId(), - fromm = introBundle.ident.mapIt(it.toHex(2)).join("") + 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 destPubkey = loadPublicKeyFromBytes(introBundle.ident).valueOr: - raise newException(ValueError, "Invalid public key in intro bundle.") - - let convoId = conversationIdFor(destPubkey) - let destConvoTopic = topicInbox(destPubkey.getAddr()) - - let invite = InvitePrivateV1( - initiator: @(client.ident.getPubkey().bytes()), - initiatorEphemeral: @[0, 0], # TODO: Add ephemeral - participant: @(destPubkey.bytes()), - participantEphemeralId: introBundle.ephemeralId, - discriminator: "test" - ) - - - - let env = wrapEnv(encrypt(InboxV1Frame(invitePrivateV1: invite, - recipient: "")), convoId) - - let deliveryAckCb = proc( - conversation: Conversation, - msgId: string): Future[void] {.async.} = - client.notifyDeliveryAck(conversation, msgId) - - # TODO: remove placeholder key - var key : array[32, byte] - key[2]=2 - - var convo = initPrivateV1Sender(client.identity(), client.ds, destPubkey, key, deliveryAckCb) - client.addConversation(convo) - - # TODO: Subscribe to new content topic - - await client.ds.sendPayload(destConvoTopic, env) return none(ChatError) diff --git a/src/chat/conversations/private_v1.nim b/src/chat/conversations/private_v1.nim index e578cb9..9e8b264 100644 --- a/src/chat/conversations/private_v1.nim +++ b/src/chat/conversations/private_v1.nim @@ -23,7 +23,8 @@ import convo_type import message import ../../naxolotl as nax - + +const TopicPrefixPrivateV1 = "/convo/private/" type ReceivedPrivateV1Message* = ref object of ReceivedMessage @@ -37,17 +38,32 @@ type ds: WakuClient sdsClient: ReliabilityManager owner: Identity - topic: string participant: PublicKey discriminator: string doubleratchet: naxolotl.Doubleratchet -const - TopicPrefixPrivateV1 = "/convo/private/" +proc derive_topic(participant: PublicKey): string = + ## Derives a topic from the participants' public keys. + return TopicPrefixPrivateV1 & participant.get_addr() -proc getTopic*(self: PrivateV1): string = - ## Returns the topic for the PrivateV1 conversation. - return self.topic +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] @@ -64,20 +80,6 @@ proc getConvoIdRaw(participants: seq[PublicKey], proc getConvoId*(self: PrivateV1): string = return getConvoIdRaw(@[self.owner.getPubkey(), self.participant], self.discriminator) -proc derive_topic(participants: seq[PublicKey], discriminator: string): string = - ## Derives a topic from the participants' public keys. - return TopicPrefixPrivateV1 & getConvoIdRaw(participants, discriminator) - -## 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 calcMsgId(self: PrivateV1, msgBytes: seq[byte]): string = let s = fmt"{self.getConvoId()}|{msgBytes}" @@ -124,7 +126,7 @@ proc wireCallbacks(convo: PrivateV1, deliveryAckCb: proc( let funcDeliveryAck = proc(messageId: SdsMessageID, channelId: SdsChannelID) {.gcsafe.} = debug "sds message ack", messageId = messageId, - channelId = channelId, cb = repr(deliveryAckCb) + channelId = channelId if deliveryAckCb != nil: asyncSpawn deliveryAckCb(convo, messageId) @@ -146,19 +148,21 @@ proc initPrivateV1*(owner: Identity, ds:WakuClient, participant: PublicKey, seed msgId: string): Future[void] {.async.} = nil): PrivateV1 = - var participants = @[owner.getPubkey(), participant]; - 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, - topic: derive_topic(participants, discriminator), participant: participant, discriminator: discriminator, - doubleratchet: initDoubleratchet(seedKey, owner.privateKey.bytes, participant.bytes, isSender) + doubleratchet: dr ) result.wireCallbacks(deliveryAckCb) @@ -166,18 +170,7 @@ proc initPrivateV1*(owner: Identity, ds:WakuClient, participant: PublicKey, seed result.sdsClient.ensureChannel(result.getConvoId()).isOkOr: raise newException(ValueError, "bad sds channel") - -proc initPrivateV1Sender*(owner:Identity, ds: WakuClient, participant: PublicKey, seedKey: array[32, byte], deliveryAckCb: proc( - conversation: Conversation, msgId: string): Future[void] {.async.} = nil): PrivateV1 = - initPrivateV1(owner, ds, participant, seedKey, "default", true, deliveryAckCb) - -proc initPrivateV1Recipient*(owner:Identity,ds: WakuClient, participant: PublicKey, seedKey: array[32, byte], deliveryAckCb: proc( - conversation: Conversation, msgId: string): Future[void] {.async.} = nil): PrivateV1 = - initPrivateV1(owner,ds, participant, seedKey, "default", false, deliveryAckCb) - - -proc sendFrame(self: PrivateV1, ds: WakuClient, - msg: PrivateV1Frame): Future[MessageId]{.async.} = +proc encodeFrame*(self: PrivateV1, msg: PrivateV1Frame): (MessageId, EncryptedPayload) = let frameBytes = encode(msg) let msgId = self.calcMsgId(frameBytes) @@ -185,9 +178,12 @@ proc sendFrame(self: PrivateV1, ds: WakuClient, self.getConvoId()).valueOr: raise newException(ValueError, fmt"sds wrapOutgoingMessage failed: {repr(error)}") - let encryptedPayload = self.encrypt(sdsPayload) + result = (msgId, self.encrypt(sdsPayload)) - discard ds.sendPayload(self.getTopic(), encryptedPayload.toEnvelope( +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 @@ -197,18 +193,15 @@ method id*(self: PrivateV1): string = return getConvoIdRaw(self.allParticipants(), self.discriminator) proc handleFrame*[T: ConversationStore](convo: PrivateV1, client: T, - bytes: seq[byte]) = + encPayload: EncryptedPayload) = ## Dispatcher for Incoming `PrivateV1Frames`. ## Calls further processing depending on the kind of frame. - let enc = decode(bytes, EncryptedPayload).valueOr: - raise newException(ValueError, fmt"Failed to decode EncryptedPayload: {repr(error)}") - - if convo.doubleratchet.dhSelfPublic() == enc.doubleratchet.dh: + if convo.doubleratchet.dhSelfPublic() == encPayload.doubleratchet.dh: info "outgoing message, no need to handle", convo = convo.id() return - let plaintext = convo.decrypt(enc).valueOr: + let plaintext = convo.decrypt(encPayload).valueOr: error "decryption failed", error = error return @@ -234,6 +227,15 @@ proc handleFrame*[T: ConversationStore](convo: PrivateV1, client: T, 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.} = @@ -245,3 +247,36 @@ method sendMessage*(convo: PrivateV1, content_frame: Content) : Future[MessageId 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/identity.nim b/src/chat/identity.nim index cdc8218..37b4267 100644 --- a/src/chat/identity.nim +++ b/src/chat/identity.nim @@ -29,5 +29,5 @@ proc getAddr*(self: Identity): string = result = get_addr(self.getPubKey()) -proc getId*(self: Identity): string = +proc getName*(self: Identity): string = result = self.name diff --git a/src/chat/inbox.nim b/src/chat/inbox.nim index 24df76e..9549ade 100644 --- a/src/chat/inbox.nim +++ b/src/chat/inbox.nim @@ -11,16 +11,19 @@ import conversations, conversation_store, crypto, + delivery/waku_client, errors, + identity, proto_types, - types + types, + utils logScope: topics = "chat inbox" type Inbox* = ref object of Conversation - pubkey: PublicKey + identity: Identity inbox_addr: string const @@ -30,9 +33,9 @@ proc `$`*(conv: Inbox): string = fmt"Inbox: addr->{conv.inbox_addr}" -proc initInbox*(pubkey: PublicKey): Inbox = +proc initInbox*(ident: Identity): Inbox = ## Initializes an Inbox object with the given address and invite callback. - return Inbox(pubkey: pubkey) + return Inbox(identity: ident) proc encrypt*(frame: InboxV1Frame): EncryptedPayload = return encrypt_plain(frame) @@ -73,13 +76,52 @@ proc parseTopic*(topic: string): Result[string, ChatError] = return ok(id) method id*(convo: Inbox): string = - return conversation_id_for(convo.pubkey) + 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) = @@ -92,13 +134,17 @@ proc createPrivateV1FromInvite*[T: ConversationStore](client: T, client.notifyDeliveryAck(conversation, msgId) # TODO: remove placeholder key - var key : array[32, byte] - key[2]=2 + 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(), - topic = convo.getConvoId() - client.addConversation(convo) + 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]) = diff --git a/src/chat/proto_types.nim b/src/chat/proto_types.nim index 2e3a25c..a608046 100644 --- a/src/chat/proto_types.nim +++ b/src/chat/proto_types.nim @@ -10,7 +10,6 @@ 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/encryption.proto" import_proto3 "../../protos/envelope.proto" import_proto3 "../../protos/private_v1.proto" diff --git a/src/naxolotl/naxolotl.nim b/src/naxolotl/naxolotl.nim index ad2efa6..d9058fd 100644 --- a/src/naxolotl/naxolotl.nim +++ b/src/naxolotl/naxolotl.nim @@ -50,12 +50,20 @@ 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(self: var Doubleratchet, rootKey: RootKey, dhOutput:DhDerivedKey): (RootKey, ChainKey) = +func kdfRoot(rootKey: RootKey, dhOutput:DhDerivedKey): (RootKey, ChainKey) = var salt = rootKey var ikm = dhOutput @@ -63,7 +71,7 @@ func kdfRoot(self: var Doubleratchet, rootKey: RootKey, dhOutput:DhDerivedKey): hkdfSplit(salt, ikm, info) -func kdfChain(self: Doubleratchet, chainKey: ChainKey): (MessageKey, ChainKey) = +func kdfChain(chainKey: ChainKey): (MessageKey, ChainKey) = let msgKey = hkdfExtract(chainKey, [0x01u8]) let chainKey = hkdfExtract(chainKey, [0x02u8]) @@ -73,7 +81,7 @@ func kdfChain(self: Doubleratchet, chainKey: ChainKey): (MessageKey, 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, self.rootKey, dhOutput) + let (newRootKey, newChainKeySend) = kdfRoot(self.rootKey, dhOutput) self.rootKey = newRootKey self.chainKeySend = newChainKeySend self.msgCountSend = 0 @@ -86,14 +94,14 @@ proc dhRatchetRecv(self: var Doubleratchet, remotePublickey: PublicKey ) = self.dhRemote = remotePublickey let dhOutputPre = self.dhSelf.dhExchange(self.dhRemote).get() - let (newRootKey, newChainKeyRecv) = kdfRoot(self, self.rootKey, dhOutputPre) + let (newRootKey, newChainKeyRecv) = kdfRoot(self.rootKey, dhOutputPre) self.rootKey = newRootKey self.chainKeyRecv = newChainKeyRecv - self.dhSelf = generateKeypair().get()[0] + self.dhSelf = generateDhKey() let dhOutputPost = self.dhSelf.dhExchange(self.dhRemote).get() - (self.rootKey, self.chainKeySend) = kdfRoot(self, self.rootKey, dhOutputPost) + (self.rootKey, self.chainKeySend) = kdfRoot(self.rootKey, dhOutputPost) proc skipMessageKeys(self: var Doubleratchet, until: MsgCount): Result[(), string] = @@ -102,7 +110,7 @@ proc skipMessageKeys(self: var Doubleratchet, until: MsgCount): Result[(), strin return err("Too many skipped messages") while self.msgCountRecv < until: - let (msgKey, chainKey) = self.kdfChain(self.chainKeyRecv) + let (msgKey, chainKey) = kdfChain(self.chainKeyRecv) self.chainKeyRecv = chainKey let keyId = keyId(self.dhRemote, self.msgCountRecv) @@ -113,7 +121,7 @@ proc skipMessageKeys(self: var Doubleratchet, until: MsgCount): Result[(), strin proc encrypt(self: var Doubleratchet, plaintext: var seq[byte], associatedData: openArray[byte]): (DrHeader, CipherText) = - let (msgKey, chainKey) = self.kdfChain(self.chainKeySend) + let (msgKey, chainKey) = kdfChain(self.chainKeySend) self.chainKeySend = chainKey let header = DrHeader( dhPublic: self.dhSelf.public, #TODO Serialize @@ -130,14 +138,12 @@ proc encrypt(self: var Doubleratchet, plaintext: var seq[byte], associatedData: 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 @@ -155,7 +161,7 @@ proc decrypt*(self: var Doubleratchet, header: DrHeader, ciphertext: CipherText, if r.isErr: error "skipMessages", error = r.error() - (msgKey, self.chainKeyRecv) = self.kdfChain(self.chainKeyRecv) + (msgKey, self.chainKeyRecv) = kdfChain(self.chainKeyRecv) inc self.msgCountRecv var nonce : Nonce @@ -173,10 +179,10 @@ proc encrypt*(self: var Doubleratchet, plaintext: var seq[byte]) : (DrHeader, Ci encrypt(self, plaintext,@[]) -func initDoubleratchet*(sharedSecret: array[32, byte], dhSelf: PrivateKey, dhRemote: PublicKey, isSending: bool = true): Doubleratchet = +proc initDoubleratchetSender*(sharedSecret: array[32, byte], dhRemote: PublicKey): Doubleratchet = result = Doubleratchet( - dhSelf: dhSelf, + dhSelf: generateDhKey(), dhRemote: dhRemote, rootKey: RootKey(sharedSecret), msgCountSend: 0, @@ -185,8 +191,20 @@ func initDoubleratchet*(sharedSecret: array[32, byte], dhSelf: PrivateKey, dhRem skippedMessageKeys: initTable[(PublicKey, MsgCount), MessageKey]() ) - if isSending: - result.dhRatchetSend() + # 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/tests/test_naxolotl.nim b/tests/test_naxolotl.nim index a4edb9b..9ce0c41 100644 --- a/tests/test_naxolotl.nim +++ b/tests/test_naxolotl.nim @@ -31,7 +31,6 @@ proc hexToArray*[N: static[int]](hexStr: string): array[N, byte] = "Hex string length (" & $hexStr.len & ") doesn't match array size (" & $( N*2) & ")") - var result: array[N, byte] for i in 0..