mirror of
https://github.com/logos-messaging/nim-chat-poc.git
synced 2026-02-26 17:03:08 +00:00
Identity Simplification (#71)
* Contract interface for bindings * Add errorType * Remove Identity files * Update Echo_bot * update cbindings * Add installation name * Update tests * bump libchat dep
This commit is contained in:
parent
714d97029c
commit
c2196c77ee
@ -8,8 +8,7 @@ import content_types
|
||||
|
||||
proc main() {.async.} =
|
||||
let waku = initWakuClient(DefaultConfig())
|
||||
let ident = createIdentity("EchoBot")
|
||||
var chatClient = newClient(waku, ident)
|
||||
var chatClient = newClient(waku).get()
|
||||
|
||||
chatClient.onNewMessage(proc(convo: Conversation, msg: ReceivedMessage) {.async.} =
|
||||
info "New Message: ", convoId = convo.id(), msg= msg
|
||||
|
||||
@ -26,12 +26,9 @@ proc main() {.async.} =
|
||||
var waku_saro = initWakuClient(DefaultConfig())
|
||||
var waku_raya = initWakuClient(DefaultConfig())
|
||||
|
||||
let sKey = loadPrivateKeyFromBytes(@[45u8, 216, 160, 24, 19, 207, 193, 214, 98, 92, 153, 145, 222, 247, 101, 99, 96, 131, 149, 185, 33, 187, 229, 251, 100, 158, 20, 131, 111, 97, 181, 210]).get()
|
||||
let rKey = loadPrivateKeyFromBytes(@[43u8, 12, 160, 51, 212, 90, 199, 160, 154, 164, 129, 229, 147, 69, 151, 17, 239, 51, 190, 33, 86, 164, 50, 105, 39, 250, 182, 116, 138, 132, 114, 234]).get()
|
||||
|
||||
# Create Clients
|
||||
var saro = newClient(waku_saro, Identity(name: "saro", privateKey: sKey))
|
||||
var raya = newClient(waku_raya, Identity(name: "raya", privateKey: rKey))
|
||||
var saro = newClient(waku_saro).get()
|
||||
var raya = newClient(waku_raya).get()
|
||||
|
||||
# Wire Saro Callbacks
|
||||
saro.onNewMessage(proc(convo: Conversation, msg: ReceivedMessage) {.async, closure.} =
|
||||
|
||||
@ -8,7 +8,6 @@ import ffi
|
||||
|
||||
import src/chat
|
||||
import src/chat/delivery/waku_client
|
||||
import src/chat/identity
|
||||
import library/utils
|
||||
|
||||
logScope:
|
||||
@ -52,14 +51,11 @@ proc createChatClient(
|
||||
for peer in config["staticPeers"]:
|
||||
wakuCfg.staticPeers.add(peer.getStr())
|
||||
|
||||
# Create identity
|
||||
let identity = createIdentity(name)
|
||||
|
||||
# Create Waku client
|
||||
let wakuClient = initWakuClient(wakuCfg)
|
||||
|
||||
# Create Chat client
|
||||
let client = newClient(wakuClient, identity)
|
||||
let client = ?newClient(wakuClient, installation_name = name)
|
||||
|
||||
# Register event handlers
|
||||
client.onNewMessage(chatCallbacks.onNewMessage)
|
||||
|
||||
@ -10,7 +10,6 @@ import stew/byteutils
|
||||
|
||||
import
|
||||
src/chat/client,
|
||||
src/chat/identity,
|
||||
src/chat/delivery/waku_client,
|
||||
library/declare_lib,
|
||||
library/utils
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
import chat/[
|
||||
client,
|
||||
delivery/waku_client,
|
||||
identity,
|
||||
types
|
||||
]
|
||||
|
||||
export client, identity, waku_client
|
||||
export identity.`$`
|
||||
export client, waku_client
|
||||
|
||||
#export specific frames need by applications
|
||||
export MessageId
|
||||
|
||||
@ -14,7 +14,6 @@ import # Foreign
|
||||
import #local
|
||||
delivery/waku_client,
|
||||
errors,
|
||||
identity,
|
||||
types,
|
||||
utils
|
||||
|
||||
@ -53,16 +52,9 @@ type
|
||||
DeliveryAckCallback* = proc(conversation: Conversation,
|
||||
msgId: MessageId): Future[void] {.async.}
|
||||
|
||||
|
||||
type KeyEntry* = object
|
||||
keyType: string
|
||||
privateKey: PrivateKey
|
||||
timestamp: int64
|
||||
|
||||
type ChatClient* = ref object
|
||||
libchatCtx: LibChat
|
||||
ds*: WakuClient
|
||||
id: string
|
||||
inboundQueue: QueueRef
|
||||
isRunning: bool
|
||||
|
||||
@ -74,34 +66,38 @@ type ChatClient* = ref object
|
||||
# Constructors
|
||||
#################################################
|
||||
|
||||
proc newClient*(ds: WakuClient, ident: Identity): ChatClient {.raises: [IOError, ValueError].} =
|
||||
## Creates new instance of a `ChatClient` with a given `WakuConfig`
|
||||
## TODO: (P1) Currently the passed in Identity is not used. Libchat Generates one for every invocation.
|
||||
proc newClient*(ds: WakuClient, ephemeral: bool = true, installation_name: string = "default"): Result[ChatClient, ErrorType] =
|
||||
## Creates new instance of a `ChatClient` with a given `WakuConfig`.
|
||||
## A new installation is created if no saved installation with `installation_name` is found
|
||||
|
||||
if not ephemeral:
|
||||
return err("persistence is not currently supported")
|
||||
|
||||
try:
|
||||
|
||||
var q = QueueRef(queue: newAsyncQueue[ChatPayload](10))
|
||||
var c = ChatClient(
|
||||
libchatCtx: newConversationsContext(),
|
||||
libchatCtx: newConversationsContext(installation_name),
|
||||
ds: ds,
|
||||
id: ident.getName(),
|
||||
inboundQueue: q,
|
||||
isRunning: false,
|
||||
newMessageCallbacks: @[],
|
||||
newConvoCallbacks: @[])
|
||||
|
||||
|
||||
notice "Client started", client = c.id
|
||||
notice "Client started"
|
||||
|
||||
result = c
|
||||
result = ok(c)
|
||||
except Exception as e:
|
||||
error "newCLient", err = e.msg
|
||||
result = err(e.msg)
|
||||
|
||||
#################################################
|
||||
# Parameter Access
|
||||
#################################################
|
||||
|
||||
proc getId*(client: ChatClient): string =
|
||||
result = client.id
|
||||
result = client.libchatCtx.getInstallationName()
|
||||
|
||||
|
||||
proc listConversations*(client: ChatClient): seq[Conversation] =
|
||||
|
||||
@ -1,61 +0,0 @@
|
||||
# Reference: https://github.com/vacp2p/mix/blob/main/src/curve25519.nim
|
||||
|
||||
|
||||
import results
|
||||
import bearssl/rand
|
||||
import libp2p/crypto/curve25519
|
||||
|
||||
const FieldElementSize* = Curve25519KeySize
|
||||
|
||||
type FieldElement* = Curve25519Key
|
||||
|
||||
# Convert bytes to FieldElement
|
||||
proc bytesToFieldElement*(bytes: openArray[byte]): Result[FieldElement, string] =
|
||||
if bytes.len != FieldElementSize:
|
||||
return err("Field element size must be 32 bytes")
|
||||
ok(intoCurve25519Key(bytes))
|
||||
|
||||
# Convert FieldElement to bytes
|
||||
proc fieldElementToBytes*(fe: FieldElement): seq[byte] =
|
||||
fe.getBytes()
|
||||
|
||||
# Generate a random FieldElement
|
||||
proc generateRandomFieldElement*(): Result[FieldElement, string] =
|
||||
let rng = HmacDrbgContext.new()
|
||||
if rng.isNil:
|
||||
return err("Failed to creat HmacDrbgContext with system randomness")
|
||||
ok(Curve25519Key.random(rng[]))
|
||||
|
||||
# Generate a key pair (private key and public key are both FieldElements)
|
||||
proc generateKeyPair*(): Result[tuple[privateKey, publicKey: FieldElement], string] =
|
||||
let privateKeyRes = generateRandomFieldElement()
|
||||
if privateKeyRes.isErr:
|
||||
return err(privateKeyRes.error)
|
||||
let privateKey = privateKeyRes.get()
|
||||
|
||||
let publicKey = public(privateKey)
|
||||
ok((privateKey, publicKey))
|
||||
|
||||
# Multiply a given Curve25519 point with a set of scalars
|
||||
proc multiplyPointWithScalars*(
|
||||
point: FieldElement, scalars: openArray[FieldElement]
|
||||
): FieldElement =
|
||||
var res = point
|
||||
for scalar in scalars:
|
||||
Curve25519.mul(res, scalar)
|
||||
res
|
||||
|
||||
# Multiply the Curve25519 base point with a set of scalars
|
||||
proc multiplyBasePointWithScalars*(
|
||||
scalars: openArray[FieldElement]
|
||||
): Result[FieldElement, string] =
|
||||
if scalars.len <= 0:
|
||||
return err("Atleast one scalar must be provided")
|
||||
var res: FieldElement = public(scalars[0]) # Use the predefined base point
|
||||
for i in 1 ..< scalars.len:
|
||||
Curve25519.mul(res, scalars[i]) # Multiply with each scalar
|
||||
ok(res)
|
||||
|
||||
# Compare two FieldElements
|
||||
proc compareFieldElements*(a, b: FieldElement): bool =
|
||||
a == b
|
||||
@ -1,58 +0,0 @@
|
||||
import results
|
||||
import libp2p/crypto/curve25519
|
||||
import bearssl/rand
|
||||
|
||||
import ../utils
|
||||
|
||||
type PrivateKey* = object
|
||||
bytes: Curve25519Key
|
||||
|
||||
type PublicKey* = distinct Curve25519Key # TODO: define outside of ECDH
|
||||
|
||||
|
||||
proc bytes*(key: PublicKey): array[Curve25519KeySize, byte] =
|
||||
cast[array[Curve25519KeySize, byte]](key)
|
||||
|
||||
proc get_addr*(pubkey: PublicKey): string =
|
||||
# TODO: Needs Spec
|
||||
result = hash_func(pubkey.bytes().bytesToHex())
|
||||
|
||||
|
||||
proc bytes*(key: PrivateKey): Curve25519Key =
|
||||
return key.bytes
|
||||
|
||||
|
||||
|
||||
proc createRandomKey*(): Result[PrivateKey, string] =
|
||||
let rng = HmacDrbgContext.new()
|
||||
if rng.isNil:
|
||||
return err("Failed to create HmacDrbgContext with system randomness")
|
||||
ok(PrivateKey(bytes: Curve25519Key.random(rng[])))
|
||||
|
||||
proc loadPrivateKeyFromBytes*(bytes: openArray[byte]): Result[PrivateKey, string] =
|
||||
if bytes.len != Curve25519KeySize:
|
||||
return err("Private key size must be 32 bytes")
|
||||
ok(PrivateKey(bytes: intoCurve25519Key(bytes)))
|
||||
|
||||
proc loadPublicKeyFromBytes*(bytes: openArray[byte]): Result[PublicKey, string] =
|
||||
if bytes.len != Curve25519KeySize:
|
||||
return err("Public key size must be 32 bytes")
|
||||
ok(PublicKey(intoCurve25519Key(bytes)))
|
||||
|
||||
|
||||
proc getPublicKey*(privateKey: PrivateKey): PublicKey =
|
||||
PublicKey( public(privateKey.bytes))
|
||||
|
||||
|
||||
proc Dh*(privateKey: PrivateKey, publicKey: PublicKey): Result[seq[
|
||||
byte], string] =
|
||||
|
||||
var outputKey = publicKey.bytes
|
||||
try:
|
||||
Curve25519.mul(outputKey, privateKey.bytes)
|
||||
except CatchableError as e:
|
||||
return err("Failed to compute shared secret: " & e.msg)
|
||||
|
||||
return ok(outputKey.getBytes())
|
||||
|
||||
|
||||
@ -1,44 +0,0 @@
|
||||
|
||||
import crypto/ecdh
|
||||
import results
|
||||
import strformat
|
||||
import utils
|
||||
|
||||
export PublicKey, PrivateKey, loadPrivateKeyFromBytes, loadPublicKeyFromBytes
|
||||
|
||||
|
||||
type
|
||||
Identity* = object
|
||||
name*: string
|
||||
privateKey*: PrivateKey # TODO: protect key exposure
|
||||
|
||||
|
||||
#################################################
|
||||
# Constructors
|
||||
#################################################
|
||||
|
||||
proc createIdentity*(name: string): Identity =
|
||||
let privKey = createRandomKey().get()
|
||||
result = Identity(name: name, privateKey: privKey)
|
||||
|
||||
|
||||
#################################################
|
||||
# Parameter Access
|
||||
#################################################
|
||||
|
||||
proc getPubkey*(self: Identity): PublicKey =
|
||||
result = self.privateKey.getPublicKey()
|
||||
|
||||
proc getAddr*(self: Identity): string =
|
||||
result = get_addr(self.getPubKey())
|
||||
|
||||
proc getName*(self: Identity): string =
|
||||
result = self.name
|
||||
|
||||
proc toHex(key: PublicKey): string =
|
||||
bytesToHex(key.bytes())
|
||||
|
||||
proc `$`*(key: PublicKey): string =
|
||||
let byteStr = toHex(key)
|
||||
fmt"{byteStr[0..3]}..{byteStr[^4 .. ^1]}"
|
||||
|
||||
@ -1,2 +1,4 @@
|
||||
type MessageId* = string
|
||||
type Content* = seq[byte]
|
||||
type ErrorType* = string
|
||||
type PublicKey* = array[32, byte]
|
||||
|
||||
@ -1,3 +1 @@
|
||||
# import individual test suites
|
||||
|
||||
import ./test_curve25519
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
# Smoke test: validates that the binary links all dependencies at runtime.
|
||||
# No networking, no start(), no message exchange — just instantiation.
|
||||
import results
|
||||
|
||||
import ../src/chat
|
||||
|
||||
proc main() =
|
||||
try:
|
||||
let waku = initWakuClient(DefaultConfig())
|
||||
let ident = createIdentity("SmokeTest")
|
||||
var client = newClient(waku, ident)
|
||||
var client = newClient(waku).get()
|
||||
if client.isNil:
|
||||
raise newException(CatchableError, "newClient returned nil")
|
||||
let id = client.getId()
|
||||
|
||||
@ -1,65 +0,0 @@
|
||||
import results
|
||||
import unittest
|
||||
|
||||
import ../src/chat/crypto/ecdh # TODO use config.nims
|
||||
import ../src/chat/utils
|
||||
|
||||
# Key share test from RFC-7748:
|
||||
const ks7748_a_priv = "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a"
|
||||
const ks7748_a_pub = "8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a" # Public key point (x co-ord)
|
||||
|
||||
const ks7748_b_priv = "5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb"
|
||||
const ks7748_b_pub = "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f" # Public key point (x co-ord)s
|
||||
|
||||
const ks7748_shared_key = "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742"
|
||||
|
||||
import parseutils
|
||||
|
||||
proc hexToArray*[N: static[int]](hexStr: string): array[N, byte] =
|
||||
## Converts hex string to fixed-size byte array
|
||||
if hexStr.len != N * 2:
|
||||
raise newException(ValueError,
|
||||
"Hex string length (" & $hexStr.len & ") doesn't match array size (" & $(
|
||||
N*2) & ")")
|
||||
|
||||
for i in 0..<N:
|
||||
if parseHex(hexStr[i*2..i*2+1], result[i]) == 0:
|
||||
raise newException(ValueError, "Invalid hex pair: " & hexStr[i*2..i*2+1])
|
||||
|
||||
|
||||
suite "X25519":
|
||||
test "Key Loading":
|
||||
|
||||
let a_priv = loadPrivateKeyFromBytes(hexToArray[32](ks7748_a_priv)).get()
|
||||
let a_pub = a_priv.getPublicKey()
|
||||
|
||||
check bytesToHex(a_pub.bytes, lowercase = true) == ks7748_a_pub
|
||||
check bytesToHex(a_pub.bytes, lowercase = true) != ks7748_b_pub
|
||||
|
||||
let b_priv = loadPrivateKeyFromBytes(hexToArray[32](ks7748_b_priv)).get()
|
||||
let b_pub = b_priv.getPublicKey()
|
||||
|
||||
check bytesToHex(b_pub.bytes, lowercase = true) != ks7748_a_pub
|
||||
check bytesToHex(b_pub.bytes, lowercase = true) == ks7748_b_pub
|
||||
|
||||
test "ECDH":
|
||||
|
||||
let a_priv = loadPrivateKeyFromBytes(hexToArray[32](ks7748_a_priv)).get()
|
||||
let a_pub = a_priv.getPublicKey()
|
||||
|
||||
let b_priv = loadPrivateKeyFromBytes(hexToArray[32](ks7748_b_priv)).get()
|
||||
let b_pub = b_priv.getPublicKey()
|
||||
|
||||
|
||||
let sk1 = Dh(a_priv, b_pub)
|
||||
if sk1.isErr:
|
||||
raise newException(ValueError, "ECDH1 failed: " & sk1.error)
|
||||
|
||||
let sk2 = Dh(b_priv, a_pub)
|
||||
if sk2.isErr:
|
||||
raise newException(ValueError, "ECDH2 failed: " & sk2.error)
|
||||
|
||||
check bytesToHex(sk1.get(), lowercase = true) == ks7748_shared_key
|
||||
check bytesToHex(sk2.get(), lowercase = true) == ks7748_shared_key
|
||||
|
||||
# Run with: nim c -r test_example.nim
|
||||
2
vendor/libchat
vendored
2
vendor/libchat
vendored
@ -1 +1 @@
|
||||
Subproject commit a9ca4ffb7de90ea4cd269350c189c19fb78a2589
|
||||
Subproject commit eb941387dfba1e110e55e47c4e1374c6a6239b0c
|
||||
Loading…
x
Reference in New Issue
Block a user