Skip to content
This repository was archived by the owner on Jan 30, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
86ef9c7
Use entire multiaddress with "mix"
AkshayaMani Nov 15, 2024
932784c
Listen and Dial on full address
AkshayaMani Nov 15, 2024
b008c16
Removed anonmuxer, muxer and upgrade
AkshayaMani Nov 25, 2024
f58f38a
Change to logical connection
AkshayaMani Nov 25, 2024
1d54557
Add unpad message function and tests
AkshayaMani Nov 25, 2024
ff650d7
Changed to generic upgrade
AkshayaMani Nov 25, 2024
b31a99c
Added error handling to multiAddrToBytes
AkshayaMani Nov 25, 2024
27a7c9c
Enhanced MixnetTransportAdapter
AkshayaMani Nov 25, 2024
0abbf57
Fixed Key error and .base error
AkshayaMani Nov 27, 2024
b4dca1b
Added physical connection
AkshayaMani Nov 27, 2024
9fda25d
Fixed await error
AkshayaMani Nov 27, 2024
1c90286
Fixed initialization of mixDialer
AkshayaMani Nov 27, 2024
10b7d98
Formatted with nph
AkshayaMani Nov 27, 2024
e50214b
refactor: use proc as callback
richard-ramos Nov 27, 2024
6afa6ee
fix: return instance
richard-ramos Nov 27, 2024
5778db0
fix: async pragma
richard-ramos Nov 28, 2024
a86b641
Improved error handling
AkshayaMani Nov 28, 2024
add477a
Adapted to generic no. of nodes
AkshayaMani Nov 28, 2024
d77a9b8
Create switches after all files are written
AkshayaMani Nov 28, 2024
faafd8e
Stop switch and delete node info folders
AkshayaMani Nov 28, 2024
1f55c05
Add mix message with protocol
AkshayaMani Nov 29, 2024
6898915
Added mix message type to send/receive
AkshayaMani Nov 29, 2024
5a8c4d7
Moved protocol type to `protocol.nim`
AkshayaMani Dec 5, 2024
f9c369c
Added write method and initialized proto
AkshayaMani Dec 5, 2024
1d274f3
Added `isNodeMultiaddress`
AkshayaMani Dec 5, 2024
32bb6ba
Add separate connection type for exit
AkshayaMani Dec 5, 2024
b05ea81
Removed `poc.nim`
AkshayaMani Dec 5, 2024
28cb0ab
Added write method and updated initialization
AkshayaMani Dec 5, 2024
0293a90
Refactor: bypass multistream negotiation
AkshayaMani Dec 5, 2024
e071ced
Migrated protocol type and related functions
AkshayaMani Dec 5, 2024
665e318
Refactored transport: removed multistream negotiation
AkshayaMani Dec 5, 2024
0902151
Fix RangeDefect in `readExactly`
AkshayaMani Dec 5, 2024
87133aa
Fix error in call back
AkshayaMani Dec 5, 2024
27b6ba8
Removed unnecessary files
AkshayaMani Dec 6, 2024
c0ca792
Created new ping without response protocol
AkshayaMani Dec 6, 2024
e71470b
Added ToDo
AkshayaMani Dec 6, 2024
e933132
Renamed to `MixEntryConnection`
AkshayaMani Dec 6, 2024
77d442a
Renamed to `MixMiddleConnection`
AkshayaMani Dec 6, 2024
d0fd0e1
Added ping without response PoC
AkshayaMani Dec 6, 2024
b310560
Added `NoRespPing` related changes
AkshayaMani Dec 6, 2024
bebadfe
Improved logging
AkshayaMani Dec 6, 2024
6f598dd
nph formatted
AkshayaMani Dec 6, 2024
af999b1
Merge pull request #10 from vacp2p/feature/mix-msg
AkshayaMani Dec 6, 2024
173b6ca
Update `isNodeMultiaddress`
AkshayaMani Dec 9, 2024
e757549
Add break
AkshayaMani Dec 9, 2024
1a5016b
Update src/examples/poc_noresp_ping.nim
AkshayaMani Dec 9, 2024
c561e74
Update src/mixnet_transport_adapter/transport.nim
AkshayaMani Dec 9, 2024
53900c0
Changed to enum that uses the protocol codecs directly
AkshayaMani Dec 9, 2024
3ab8257
Updated to use new protocol enum definition
AkshayaMani Dec 9, 2024
68fb8ac
Added NoRespPing test
AkshayaMani Dec 9, 2024
8496a68
Updated to use new protocol enum definition
AkshayaMani Dec 9, 2024
e93b0b9
Added logging using chronicles
AkshayaMani Dec 9, 2024
ddc90e2
Merge branch 'refactor/mix-transport' of https://github.com/vacp2p/mi…
AkshayaMani Dec 9, 2024
4a99c03
Moved `createSwitch` into `transport`
AkshayaMani Dec 9, 2024
2356e94
Added default value to prevent get fail
AkshayaMani Dec 10, 2024
eefb3e9
nph formatted
AkshayaMani Dec 10, 2024
6e7e903
Return result in `MixnetTransportAdapter` constructor
AkshayaMani Dec 10, 2024
c75bda3
Removed asynCheck
AkshayaMani Dec 10, 2024
3b4b8f1
Merge pull request #11 from vacp2p/refactor/mix-transport
AkshayaMani Dec 10, 2024
a39eddb
Improved error handling
AkshayaMani Dec 14, 2024
cc84443
Added mix message test
AkshayaMani Dec 23, 2024
13768d2
Improved error handling
AkshayaMani Dec 23, 2024
4e222d7
Merge pull request #13 from vacp2p/public-ready
AkshayaMani Dec 23, 2024
8472fbb
Merge pull request #14 from vacp2p/public-ready
AkshayaMani Dec 23, 2024
3f9454e
Removed submodule
AkshayaMani Jan 18, 2025
5b1b358
Update libp2p dependency
AkshayaMani Jan 21, 2025
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
4 changes: 2 additions & 2 deletions mix.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ license = "MIT"

# Dependencies
requires "chronos >= 4.0.3"
requires "libp2p#3fbfb8e19c9b54d46e5d3e3f26364ef84f905e43"
requires "https://github.com/vacp2p/nim-libp2p#poc/mix-transport"
requires "nim >= 2.0.8"
requires "nimcrypto >= 0.6.0"
requires "serialization >= 0.2.2"
Expand All @@ -27,8 +27,8 @@ task test, "Run the test suite":
runTest("test_crypto")
runTest("test_curve25519")
runTest("test_fragmentation")
runTest("test_mix_message")
runTest("test_mix_node")
runTest("test_mix_protocol")
runTest("test_pow")
runTest("test_seqno_generator")
runTest("test_serialization")
Expand Down
20 changes: 10 additions & 10 deletions src/config.nim
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
const
k* = 16 # Security parameter
r* = 5 # Maximum path length
k* = 16 # Security parameter
r* = 5 # Maximum path length
t* = 3 # t.k - combined length of next hop address and delay
L* = 3 # Path length
powSize* = 12 # 4 byte nonce + 8 byte timestamp
alphaSize* = 32 # Group element
betaSize* = ((r * (t + 1)) + 1) * k # (r(t+1)+1)k bytes
L* = 3 # Path length
powSize* = 12 # 4 byte nonce + 8 byte timestamp
alphaSize* = 32 # Group element
betaSize* = ((r * (t + 1)) + 1) * k # (r(t+1)+1)k bytes
gammaSize* = 16 # Output of HMAC-SHA-256, truncated to 16 bytes
headerSize* = alphaSize + betaSize + gammaSize # Total header size
delaySize* = 2 # Delay size
addrSize* = (t * k) - delaySize # Address size
delaySize* = 2 # Delay size
addrSize* = (t * k) - delaySize # Address size
messageSize* = 2413 - headerSize - k - powSize # Size of the message itself
payloadSize* = messageSize + powSize + k # Total payload size
packetSize* = headerSize + payloadSize # Total packet size
payloadSize* = messageSize + powSize + k # Total payload size
packetSize* = headerSize + payloadSize # Total packet size
65 changes: 36 additions & 29 deletions src/crypto.nim
Original file line number Diff line number Diff line change
@@ -1,53 +1,60 @@
import nimcrypto
import endians
import endians, nimcrypto, results

# This function processes 'data' using AES in CTR mode.
# For CTR mode, the same function handles both encryption and decryption.
proc aes_ctr*(key, iv, data: openArray[byte]): seq[byte] =
assert key.len == 16, "Key must be 16 bytes for AES-128"
assert iv.len == 16, "IV must be 16 bytes for AES-128"
proc aes_ctr*(key, iv, data: openArray[byte]): Result[seq[byte], string] =
if key.len != 16:
return err("Key must be 16 bytes for AES-128")
if iv.len != 16:
return err("IV must be 16 bytes for AES-128")

var ctx: CTR[aes128]
ctx.init(key, iv)
var
ctx: CTR[aes128]
output = newSeq[byte](data.len)

var output = newSeq[byte](data.len)
ctx.encrypt(data, output)
ctx.init(key, iv)
ctx.encrypt(data, output)
ctx.clear()

ctx.clear()
return output
return ok(output)

# This function advances the counter in the AES-CTR IV by a specified number of blocks.
proc advance_ctr*(iv: var openArray[byte], blocks: int) =
var counter: uint64
bigEndian64(addr counter, addr iv[8])
counter += uint64(blocks)
bigEndian64(addr iv[8], addr counter)
var counter: uint64
bigEndian64(addr counter, addr iv[8])
counter += uint64(blocks)
bigEndian64(addr iv[8], addr counter)

# This function encrypting processes 'data' using AES in CTR mode from startIndex, without processing all preceding data.
# For CTR mode, the same function handles both encryption and decryption.
proc aes_ctr_start_index*(key, iv, data: openArray[byte], startIndex: int): seq[byte] =
assert key.len == 16, "Key must be 16 bytes for AES-128"
assert iv.len == 16, "IV must be 16 bytes for AES-128"
assert startIndex mod 16 == 0, "Start index must be a multiple of 16"
proc aes_ctr_start_index*(
key, iv, data: openArray[byte], startIndex: int
): Result[seq[byte], string] =
if key.len != 16:
return err("Key must be 16 bytes for AES-128")
if iv.len != 16:
return err("IV must be 16 bytes for AES-128")
if startIndex mod 16 != 0:
return err("Start index must be a multiple of 16")

var advIV = @iv
var advIV = @iv

# Advance the counter to the start index
let blocksToAdvance = startIndex div 16
advance_ctr(advIV, blocksToAdvance)
# Advance the counter to the start index
let blocksToAdvance = startIndex div 16
advance_ctr(advIV, blocksToAdvance)

return aes_ctr(key, advIV, data)
return aes_ctr(key, advIV, data)

# This function hashes 'data' using SHA-256.
proc sha256_hash*(data: openArray[byte]): array[32, byte] =
return sha256.digest(data).data
return sha256.digest(data).data

# This function returns the hash of 'key' truncated to 16 bytes.
proc kdf*(key: openArray[byte]): seq[byte] =
let hash = sha256_hash(key)
result = hash[0..15]
let hash = sha256_hash(key)
return hash[0 .. 15]

# This function computes a HMAC for 'data' using given 'key'.
proc hmac*(key, data: openArray[byte]): seq[byte] =
let hmac = sha256.hmac(key, data).data
result = hmac[0..15]
let hmac = sha256.hmac(key, data).data
return hmac[0 .. 15]
44 changes: 27 additions & 17 deletions src/curve25519.nim
Original file line number Diff line number Diff line change
@@ -1,47 +1,57 @@
import libp2p/crypto/curve25519
import results
import bearssl/rand
import libp2p/crypto/curve25519

const FieldElementSize* = Curve25519KeySize

type FieldElement* = Curve25519Key

# Convert bytes to FieldElement
proc bytesToFieldElement*(bytes: openArray[byte]): FieldElement =
assert bytes.len == FieldElementSize, "Field element size must be 32 bytes"
intoCurve25519Key(bytes)
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*(): FieldElement =
proc generateRandomFieldElement*(): Result[FieldElement, string] =
let rng = HmacDrbgContext.new()
assert not rng.isNil, "Failed to creat HmacDrbgContext with system randomness"
Curve25519Key.random(rng[])
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*(): tuple[privateKey, publicKey: FieldElement] =
let privateKey = generateRandomFieldElement()
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)
(privateKey, publicKey)
ok((privateKey, publicKey))

# Multiply a given Curve25519 point with a set of scalars
proc multiplyPointWithScalars*(point: FieldElement, scalars: openArray[
FieldElement]): FieldElement =
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]): FieldElement =
assert scalars.len > 0, "Atleast one scalar must be provided"
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:
for i in 1 ..< scalars.len:
Curve25519.mul(res, scalars[i]) # Multiply with each scalar
res
ok(res)

# Compare two FieldElements
proc compareFieldElements*(a, b: FieldElement): bool =
Expand Down
112 changes: 112 additions & 0 deletions src/examples/poc_noresp_ping.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import chronicles, std/enumerate, chronos, std/sysrand
import ../mixnet_transport_adapter/[switch, transport]
import ../protocols/[noresp_ping]
import
libp2p/[crypto/secp, multiaddress, builders, protocols/ping, transports/tcptransport]
import ../[mix_node]

proc cryptoRandomInt(max: int): Result[int, string] =
if max == 0:
return err("Max cannot be zero.")
var bytes: array[8, byte]
discard urandom(bytes)
let value = cast[uint64](bytes)
return ok(int(value mod uint64(max)))

proc setUpNodes(numberOfNodes: int): (seq[SkPrivateKey], seq[MultiAddress]) =
# This is not actually GC-safe
{.gcsafe.}:
discard initializeMixNodes(numberOfNodes)

var libp2pPrivKeys: seq[SkPrivateKey] = @[]
var multiAddrs: seq[MultiAddress] = @[]

for index, node in enumerate(mixNodes):
let nodePubInfoRes = getMixPubInfoByIndex(index)
if nodePubInfoRes.isErr:
error "Get mix pub info by index error", err = nodePubInfoRes.error
continue
let nodePubInfo = nodePubInfoRes.get()

let writePubRes = writePubInfoToFile(nodePubInfo, index)
if writePubRes.isErr:
error "Failed to write pub info to file", nodeIndex = index
continue

let writeNodeRes = writeMixNodeInfoToFile(node, index)
if writeNodeRes.isErr:
error "Failed to write mix info to file", nodeIndex = index
continue

let (multiAddrStr, _, _, _, libp2pPrivKey) = getMixNodeInfo(node)
multiAddrs.add(MultiAddress.init(multiAddrStr).value())
libp2pPrivKeys.add(libp2pPrivKey)

return (libp2pPrivKeys, multiAddrs)

proc mixnet_with_transport_adapter_poc() {.async.} =
let
numberOfNodes = 10
(libp2pPrivKeys, multiAddrs) = setUpNodes(numberOfNodes)

# Start nodes
let rng = newRng()
var noRespPingProto: seq[NoRespPing] = @[]
var nodes: seq[Switch] = @[]
for index, _ in enumerate(multiAddrs):
let switch =
createSwitch(libp2pPrivKeys[index], multiAddrs[index], index, numberOfNodes)
if not switch.isNil:
nodes.add(switch)
else:
warn "Failed to set up node", nodeIndex = index

noRespPingProto.add(noresp_ping.NoRespPing.new(rng = rng))
nodes[index].mount(noRespPingProto[index])
await nodes[index].start()
await sleepAsync(1.seconds)

let cryptoRandomIntResult = cryptoRandomInt(numberOfNodes)
if cryptoRandomIntResult.isErr:
error "Failed to generate random number", err = cryptoRandomIntResult.error
return
let senderIndex = cryptoRandomIntResult.value
var receiverIndex = 0
if senderIndex < numberOfNodes - 1:
receiverIndex = senderIndex + 1

let transports = nodes[senderIndex].transports
var transportIndex = -1
for index, transport in enumerate(transports):
if transport of MixnetTransportAdapter:
transportIndex = index
break
if transportIndex == -1:
raise newException(ValueError, "Custom transport not found")

let
mixTransport = nodes[senderIndex].transports[transportIndex]
peerId = nodes[receiverIndex].peerInfo.peerId
peerIdOpt = Opt[PeerId].some(peerId)

let pingFuture = newFuture[void]()

try:
var conn = await MixnetTransportAdapter(mixTransport).dialWithProto(
"", multiAddrs[receiverIndex], peerIdOpt, Opt.some(NoRespPingCodec)
)

let ping = await noRespPingProto[senderIndex].noRespPing(conn)
info "Received ping: ", ping = ping
await sleepAsync(1.seconds)
except Exception as e:
error "An error occurred during dialing: ", err = e.msg

for index, node in enumerate(nodes):
await node.stop()

deleteNodeInfoFolder()
deletePubInfoFolder()

when isMainModule:
waitFor(mixnet_with_transport_adapter_poc())
Loading