Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/pingpong.nim
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,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())

await sleepAsync(20.seconds) # Run for some time

Expand Down
2 changes: 1 addition & 1 deletion examples/tui/tui.nim
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,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
Expand Down
3 changes: 3 additions & 0 deletions protos/invite.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
55 changes: 13 additions & 42 deletions src/chat/client.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -71,21 +72,23 @@ 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,
keyStore: initTable[string, KeyEntry](),
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
Expand All @@ -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
Expand Down Expand Up @@ -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)

Expand All @@ -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: ContentFrame): 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 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
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.

await client.ds.sendPayload(destConvoTopic, env)
return none(ChatError)


Expand Down
106 changes: 74 additions & 32 deletions src/chat/conversations/private_v1.nim
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,21 @@ type
ds: WakuClient
sdsClient: ReliabilityManager
owner: Identity
topic: string
participant: PublicKey
discriminator: string
doubleratchet: naxolotl.Doubleratchet

proc getTopic*(self: PrivateV1): string =
## Returns the topic for the PrivateV1 conversation.
return self.topic
proc derive_topic(participant: PublicKey): string =
## Derives a topic from the participants' public keys.
return "/convo/private/" & 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)

proc allParticipants(self: PrivateV1): seq[PublicKey] =
return @[self.owner.getPubkey(), self.participant]
Expand All @@ -61,9 +68,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 "/convo/private/" & getConvoIdRaw(participants, discriminator)

proc calcMsgId(self: PrivateV1, msgBytes: seq[byte]): string =
let s = fmt"{self.getConvoId()}|{msgBytes}"
Expand Down Expand Up @@ -93,6 +97,7 @@ proc decrypt*(convo: PrivateV1, enc: EncryptedPayload): Result[seq[byte], ChatEr
prevChainLen: dr.prevChainLen
)
copyMem(addr header.dhPublic[0], unsafeAddr dr.dh[0], dr.dh.len) # TODO: Avoid this copy
debug "DECR WRAP", who=convo.owner.getName(), cipher = shrink(dr.ciphertext[0..8])

convo.doubleratchet.decrypt(header, dr.ciphertext, @[]).mapErr(proc(e: NaxolotlError): ChatError = ChatError(code: errWrapped, context: repr(e) ))

Expand All @@ -110,7 +115,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)
Expand All @@ -132,48 +137,43 @@ 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)

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)
var sdsPayload = self.sdsClient.wrapOutgoingMessage(frameBytes, msgId,
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
Expand All @@ -182,19 +182,19 @@ proc sendFrame(self: PrivateV1, ds: WakuClient,
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

Expand All @@ -220,6 +220,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: ContentFrame) : Future[MessageId] {.async.} =

Expand All @@ -231,3 +240,36 @@ method sendMessage*(convo: PrivateV1, content_frame: ContentFrame) : Future[Mess
except Exception as e:
error "Unknown error in PrivateV1:SendMessage"


## Encrypts content without sending it.
proc encryptMessage*(self: PrivateV1, content_frame: ContentFrame) : (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:SendMessage"

proc initPrivateV1Sender*(sender:Identity,
ds: WakuClient,
participant: PublicKey,
seedKey: array[32, byte],
content: ContentFrame,
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)
2 changes: 1 addition & 1 deletion src/chat/identity.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading
Loading