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

View File

@ -21,6 +21,7 @@ import #local
conversations/convo_impl,
crypto,
delivery/waku_client,
errors,
identity,
inbox,
proto_types,
@ -215,7 +216,11 @@ proc newPrivateConversation*(client: Client,
msgId: string): Future[void] {.async.} =
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)
# TODO: Subscribe to new content topic

View File

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

View File

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