Integrate doubleratchet encryption to PrivateV1

This commit is contained in:
Jazz Turner-Baggs 2025-10-09 18:30:56 -07:00
parent 8ff8add31d
commit 16aebac0c7
6 changed files with 83 additions and 23 deletions

View File

@ -9,7 +9,7 @@ message EncryptedPayload {
oneof encryption { oneof encryption {
encryption.Plaintext plaintext = 1; encryption.Plaintext plaintext = 1;
encryption.Ecies ecies = 2; encryption.Doubleratchet doubleratchet = 2;
} }
} }
@ -17,8 +17,10 @@ message Plaintext {
bytes payload=1; bytes payload=1;
} }
message Ecies { message Doubleratchet {
bytes encrypted_bytes=1; bytes dh = 1; // 32 byte array
bytes ephemeral_pubkey = 2; uint32 msgNum = 2;
bytes tag = 3; uint32 prevChainLen = 3;
bytes ciphertext = 4;
string aux = 5;
} }

View File

@ -21,6 +21,7 @@ import #local
conversations/convo_impl, conversations/convo_impl,
crypto, crypto,
delivery/waku_client, delivery/waku_client,
errors,
identity, identity,
inbox, inbox,
proto_types, proto_types,
@ -215,7 +216,11 @@ proc newPrivateConversation*(client: Client,
msgId: string): Future[void] {.async.} = msgId: string): Future[void] {.async.} =
client.notifyDeliveryAck(conversation, msgId) client.notifyDeliveryAck(conversation, msgId)
let convo = initPrivateV1(client.identity(), destPubkey, "default", deliveryAckCb) # TODO: remove placeholder key
var key : array[32, byte]
key[2]=2
var convo = initPrivateV1Sender(client.identity(), destPubkey, key, deliveryAckCb)
client.addConversation(convo) client.addConversation(convo)
# TODO: Subscribe to new content topic # TODO: Subscribe to new content topic

View File

@ -14,12 +14,15 @@ import ../delivery/waku_client
import ../[ import ../[
identity, identity,
errors,
proto_types, proto_types,
types, types,
utils utils
] ]
import convo_type import convo_type
import ../../naxolotl as nax
type type
PrivateV1* = ref object of Conversation PrivateV1* = ref object of Conversation
@ -29,6 +32,7 @@ type
topic: string topic: string
participants: seq[PublicKey] participants: seq[PublicKey]
discriminator: string discriminator: string
doubleratchet: naxolotl.Doubleratchet
proc getTopic*(self: PrivateV1): string = proc getTopic*(self: PrivateV1): string =
## Returns the topic for the PrivateV1 conversation. ## Returns the topic for the PrivateV1 conversation.
@ -56,11 +60,32 @@ proc calcMsgId(self: PrivateV1, msgBytes: seq[byte]): string =
result = getBlake2b(s, 16, "") result = getBlake2b(s, 16, "")
proc encrypt*(convo: PrivateV1, frame: PrivateV1Frame): EncryptedPayload = proc encrypt*(convo: PrivateV1, plaintext: var seq[byte]): EncryptedPayload =
result = EncryptedPayload(plaintext: Plaintext(payload: encode(frame)))
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 decrypt*(convo: PrivateV1, enc: EncryptedPayload): PrivateV1Frame =
result = decode(enc.plaintext.payload, PrivateV1Frame).get()
proc wireCallbacks(convo: PrivateV1, deliveryAckCb: proc( proc wireCallbacks(convo: PrivateV1, deliveryAckCb: proc(
@ -91,8 +116,8 @@ proc wireCallbacks(convo: PrivateV1, deliveryAckCb: proc(
proc initPrivateV1*(owner: Identity, participant: PublicKey, proc initPrivateV1*(owner: Identity, participant: PublicKey, seedKey: array[32, byte],
discriminator: string = "default", deliveryAckCb: proc( discriminator: string = "default", isSender: bool, deliveryAckCb: proc(
conversation: Conversation, conversation: Conversation,
msgId: string): Future[void] {.async.} = nil): msgId: string): Future[void] {.async.} = nil):
PrivateV1 = PrivateV1 =
@ -107,7 +132,8 @@ proc initPrivateV1*(owner: Identity, participant: PublicKey,
owner: owner, owner: owner,
topic: derive_topic(participants, discriminator), topic: derive_topic(participants, discriminator),
participants: participants, participants: participants,
discriminator: discriminator discriminator: discriminator,
doubleratchet: initDoubleratchet(seedKey, owner.privateKey.bytes, participant.bytes, isSender)
) )
result.wireCallbacks(deliveryAckCb) result.wireCallbacks(deliveryAckCb)
@ -115,19 +141,28 @@ proc initPrivateV1*(owner: Identity, participant: PublicKey,
result.sdsClient.ensureChannel(result.getConvoId()).isOkOr: result.sdsClient.ensureChannel(result.getConvoId()).isOkOr:
raise newException(ValueError, "bad sds channel") raise newException(ValueError, "bad sds channel")
proc initPrivateV1Sender*(owner:Identity, participant: PublicKey, seedKey: array[32, byte], deliveryAckCb: proc(
conversation: Conversation, msgId: string): Future[void] {.async.} = nil): PrivateV1 =
initPrivateV1(owner, participant, seedKey, "default", true, deliveryAckCb)
proc initPrivateV1Recipient*(owner:Identity, participant: PublicKey, seedKey: array[32, byte], deliveryAckCb: proc(
conversation: Conversation, msgId: string): Future[void] {.async.} = nil): PrivateV1 =
initPrivateV1(owner, participant, seedKey, "default", false, deliveryAckCb)
proc sendFrame(self: PrivateV1, ds: WakuClient, proc sendFrame(self: PrivateV1, ds: WakuClient,
msg: PrivateV1Frame): Future[MessageId]{.async.} = msg: PrivateV1Frame): Future[MessageId]{.async.} =
let frameBytes = encode(msg) let frameBytes = encode(msg)
let msgId = self.calcMsgId(frameBytes) let msgId = self.calcMsgId(frameBytes)
let sdsPayload = self.sdsClient.wrapOutgoingMessage(frameBytes, msgId, var sdsPayload = self.sdsClient.wrapOutgoingMessage(frameBytes, msgId,
self.getConvoId()).valueOr: self.getConvoId()).valueOr:
raise newException(ValueError, fmt"sds wrapOutgoingMessage failed: {repr(error)}") raise newException(ValueError, fmt"sds wrapOutgoingMessage failed: {repr(error)}")
let encryptedBytes = EncryptedPayload(plaintext: Plaintext( let encryptedPayload = self.encrypt(sdsPayload)
payload: sdsPayload))
discard ds.sendPayload(self.getTopic(), encryptedBytes.toEnvelope( discard ds.sendPayload(self.getTopic(), encryptedPayload.toEnvelope(
self.getConvoId())) self.getConvoId()))
result = msgId result = msgId
@ -144,9 +179,12 @@ proc handleFrame*[T: ConversationStore](convo: PrivateV1, client: T,
let enc = decode(bytes, EncryptedPayload).valueOr: let enc = decode(bytes, EncryptedPayload).valueOr:
raise newException(ValueError, fmt"Failed to decode EncryptedPayload: {repr(error)}") raise newException(ValueError, fmt"Failed to decode EncryptedPayload: {repr(error)}")
# TODO: Decrypt the payload let plaintext = convo.decrypt(enc).valueOr:
error "decryption failed", error = error
return
let (frameData, missingDeps, channelId) = convo.sdsClient.unwrapReceivedMessage( let (frameData, missingDeps, channelId) = convo.sdsClient.unwrapReceivedMessage(
enc.plaintext.payload).valueOr: plaintext).valueOr:
raise newException(ValueError, fmt"Failed to unwrap SDS message:{repr(error)}") raise newException(ValueError, fmt"Failed to unwrap SDS message:{repr(error)}")
debug "sds unwrap", convo = convo.id(), missingDeps = missingDeps, debug "sds unwrap", convo = convo.id(), missingDeps = missingDeps,

14
src/chat_sdk/errors.nim Normal file
View File

@ -0,0 +1,14 @@
import strformat
type
ChatError* = object of CatchableError
code*: ErrorCode
context*: string
ErrorCode* = enum
errTypeError
errWrapped
proc `$`*(x: ChatError): string =
fmt"ChatError(code={$x.code}, context: {x.context})"

View File

@ -78,7 +78,11 @@ proc createPrivateV1FromInvite*[T: ConversationStore](client: T,
msgId: string): Future[void] {.async.} = msgId: string): Future[void] {.async.} =
client.notifyDeliveryAck(conversation, msgId) client.notifyDeliveryAck(conversation, msgId)
let convo = initPrivateV1(client.identity(), destPubkey, "default", deliveryAckCb) # TODO: remove placeholder key
var key : array[32, byte]
key[2]=2
let convo = initPrivateV1Recipient(client.identity(), destPubkey, key, deliveryAckCb)
notice "Creating PrivateV1 conversation", client = client.getId(), notice "Creating PrivateV1 conversation", client = client.getId(),
topic = convo.getConvoId() topic = convo.getConvoId()
client.addConversation(convo) client.addConversation(convo)

View File

@ -1,4 +1 @@
type ChatError* = string
type MessageId* = string type MessageId* = string