Add ECDH functions

This commit is contained in:
Jazz Turner-Baggs 2025-07-14 19:39:21 -07:00
parent 9a455cc4d2
commit d0046eabf3
4 changed files with 116 additions and 1 deletions

43
src/crypto/ecdh.nim Normal file
View File

@ -0,0 +1,43 @@
import results
import libp2p/crypto/curve25519
import bearssl/rand
type PrivateKey* = object
bytes: Curve25519Key
type PublicKey* = object
bytes: Curve25519Key
proc bytes*(key: PrivateKey): Curve25519Key =
return key.bytes
proc bytes*(key: PublicKey): 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 loadKeyFromBytes*(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 getPublicKey*(privateKey: PrivateKey): PublicKey =
PublicKey(bytes: 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())

View File

@ -2,6 +2,7 @@ import waku/waku_core
import std/[random, times]
import crypto
import blake2
import strutils
proc getTimestamp*(): Timestamp =
result = waku_core.getNanosecondTime(getTime().toUnix())
@ -21,4 +22,9 @@ proc get_addr*(pubkey: SkPublicKey): string =
result = hash_func(pubkey.toHexCompressed())
proc bytesToHex*(bytes: openarray[byte], lowercase: bool = false): string =
## Convert bytes to hex string with case option
result = ""
for b in bytes:
let hex = b.toHex(2)
result.add(if lowercase: hex.toLower() else: hex)

0
tests/config.nims Normal file
View File

66
tests/test_curve25519.nim Normal file
View File

@ -0,0 +1,66 @@
# test_example.nim
import unittest
import ../src/crypto/ecdh # TODO use config.nims
import results
import ../src/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])
# Usage
suite "X25519":
test "Key Loading":
let a_priv = loadKeyFromBytes(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 = loadKeyFromBytes(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 = loadKeyFromBytes(hexToArray[32](ks7748_a_priv)).get()
let a_pub = a_priv.getPublicKey()
let b_priv = loadKeyFromBytes(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