Skip to content
Open
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
21 changes: 13 additions & 8 deletions src/chat/client.nim
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ type KeyEntry* = object
type Client* = ref object
ident: Identity
ds*: WakuClient
keyStore: Table[string, KeyEntry] # Keyed by HexEncoded Public Key
keyStore: Table[RemoteKeyIdentifier, seq[KeyEntry]]
conversations: Table[string, Conversation] # Keyed by conversation ID
inboundQueue: QueueRef
isRunning: bool
Expand All @@ -77,7 +77,7 @@ proc newClient*(cfg: WakuConfig, ident: Identity): Client {.raises: [IOError,
var q = QueueRef(queue: newAsyncQueue[ChatPayload](10))
var c = Client(ident: ident,
ds: waku,
keyStore: initTable[string, KeyEntry](),
keyStore: initTable[RemoteKeyIdentifier, seq[KeyEntry]](),
conversations: initTable[string, Conversation](),
inboundQueue: q,
isRunning: false,
Expand Down Expand Up @@ -150,18 +150,23 @@ proc notifyDeliveryAck(client: Client, convo: Conversation,
# Functional
#################################################

proc cacheInviteKey(self: var Client, key: PrivateKey) =

let rki_ephemeral = generateRemoteKeyIdentifier(key.getPublicKey())

self.keyStore[rki_ephemeral] = @[KeyEntry(
keyType: "ephemeral",
privateKey: key,
timestamp: getCurrentTimestamp()
)]

proc createIntroBundle*(self: var Client): IntroBundle =
## 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()
)
self.cacheInviteKey(ephemeralKey)

result = IntroBundle(
ident: @(self.ident.getPubkey().bytes()),
Expand Down
6 changes: 2 additions & 4 deletions src/chat/conversations/private_v1.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@

import blake2
import chronicles
import chronos
import sds
Expand Down Expand Up @@ -75,15 +73,15 @@ proc getConvoIdRaw(participants: seq[PublicKey],
addrs.sort()
addrs.add(discriminator)
let raw = addrs.join("|")
return utils.hash_func(raw)
return utils.hash_func_str(16, 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, "")
result = utils.hash_func_str(16, s)


proc encrypt*(convo: PrivateV1, plaintext: var seq[byte]): EncryptedPayload =
Expand Down
17 changes: 13 additions & 4 deletions src/chat/crypto.nim
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import proto_types

import strformat
import crypto/ecdh
import std/[sysrand]
import results
import sequtils
import std/[endians,sysrand]
import strformat
import types
import utils

import proto_types

export PublicKey, PrivateKey, bytes, createRandomKey, loadPrivateKeyFromBytes, loadPublicKeyFromBytes,
getPublicKey, Dh, Result, get_addr, `$`

Expand Down Expand Up @@ -33,3 +35,10 @@ proc toHex*(key: PublicKey): string =
proc `$`*(key: PublicKey): string =
let byteStr = toHex(key)
fmt"{byteStr[0..3]}..{byteStr[^4 .. ^1]}"

proc generateRemoteKeyIdentifier*(publicKey: PublicKey): RemoteKeyIdentifier =
let bytes = cast[seq[byte]]("WAP") & publicKey.bytes().toSeq()
let hash = utils.hash_func_bytes(4,bytes)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My AI friend tells me that the collision seems high,

With a 4-byte (32-bit) hash, collisions are extremely easy to find.

There are only 2³² ≈ 4.29 billion possible distinct hash outputs.
By the birthday paradox, you only need about 2¹⁶ ≈ 65,536 different inputs to have a ~50% chance of finding at least one collision.
With around 77,000 to 100,000 random inputs, the probability of a collision becomes virtually certain (>99%).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Collisions are not really a concern here.

  • The identifier is scoped to the ephemeral key set of a single user. The expected number of keys over a clients lifetime is expected to be ~2^11 = 2048 keys - one for each Introduction sent (0.05%).
  • These keys are deleted after they are used, which reduces the number of keys active at a single moment
  • The result of a collision is clients would check all keys in the index
  • The public key is not considered Secret/confidential

Copy link
Contributor

@kaichaosun kaichaosun Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems an increased counter could achieve similar goal as the boundary is clear, not sure if it's a good idea to use it here.
Also, after a quick search in the code, I don't see there is places using keyStore after set, is it not implemented yet?

Copy link
Collaborator Author

@jazzz jazzz Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it not implemented yet?

Requires the Noise Protocol integration into Inbox. This change integrates the keyId into the invite, and the follow up will handle keyManagement

Copy link
Collaborator Author

@jazzz jazzz Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems an increased counter could achieve similar goal as the boundary is clear

I generally like this idea, as it saves some more bytes. However it would leak how many invites a given contact has created. I'd have to check to see how that fits with our privacy model.


var result: uint32
littleEndian32(addr result, unsafeAddr hash[0])
2 changes: 1 addition & 1 deletion src/chat/crypto/ecdh.nim
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ proc bytes*(key: PublicKey): array[Curve25519KeySize, byte] =

proc get_addr*(pubkey: PublicKey): string =
# TODO: Needs Spec
result = hash_func(pubkey.bytes().bytesToHex())
result = hash_func_str(6,pubkey.bytes().bytesToHex())


proc bytes*(key: PrivateKey): Curve25519Key =
Expand Down
6 changes: 3 additions & 3 deletions src/chat/inbox.nim
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,14 @@ proc sendFrame(ds: WakuClient, remote: PublicKey, frame: InboxV1Frame ): Future[
proc newPrivateInvite(initator_static: PublicKey,
initator_ephemeral: PublicKey,
recipient_static: PublicKey,
recipient_ephemeral: uint32,
recipient_ephemeral: PublicKey,
payload: EncryptedPayload) : InboxV1Frame =

let invite = InvitePrivateV1(
initiator: @(initator_static.bytes()),
initiatorEphemeral: @(initator_ephemeral.bytes()),
participant: @(recipient_static.bytes()),
participantEphemeralId: 0,
participantEphemeralId: cast[int32](generateRemoteKeyIdentifier(recipient_ephemeral)),
discriminator: "",
initial_message: payload
)
Expand All @@ -115,7 +115,7 @@ proc inviteToPrivateConversation*(self: Inbox, ds: Wakuclient, remote_static: Pu
result = convo

# # Build Invite
let frame = newPrivateInvite(self.identity.getPubkey(), local_ephemeral.getPublicKey(), remote_static, 0, encPayload)
let frame = newPrivateInvite(self.identity.getPubkey(), local_ephemeral.getPublicKey(), remote_static, remote_ephemeral, encPayload)

# Send
await sendFrame(ds, remote_static, frame)
Expand Down
2 changes: 2 additions & 0 deletions src/chat/types.nim
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
type MessageId* = string
type Content* = seq[byte]

type RemoteKeyIdentifier* = uint32
13 changes: 10 additions & 3 deletions src/chat/utils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,16 @@ 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, "")
proc hash_func_bytes*(n: static range[1..64], s: string | seq[byte]): seq[uint8] =

let key = ""
var b: Blake2b
blake2b_init(b, n, cstring(key), len(key))
blake2b_update(b, s, len(s))
result = blake2b_final(b)

proc hash_func_str*(n: static range[1..64], s: string | seq[byte]): string =
result = $hash_func_bytes(n,s)

proc bytesToHex*[T](bytes: openarray[T], lowercase: bool = false): string =
## Convert bytes to hex string with case option
Expand Down
Loading