Skip to content

Commit afc809e

Browse files
committed
feat(mix): mixnode
1 parent 70b7d61 commit afc809e

3 files changed

Lines changed: 400 additions & 1 deletion

File tree

libp2p/protocols/mix/mix_node.nim

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
import os, results, strformat, sugar
2+
import std/streams
3+
import ../../crypto/[crypto, curve25519, secp]
4+
import ../../[multiaddress, multicodec, peerid]
5+
import ./[serialization, curve25519, multiaddr]
6+
7+
const MixNodeInfoSize* =
8+
AddrSize + (2 * FieldElementSize) + (SkRawPublicKeySize + SkRawPrivateKeySize)
9+
const MixPubInfoSize* = AddrSize + FieldElementSize + SkRawPublicKeySize
10+
11+
type MixNodeInfo* = object
12+
multiAddr*: MultiAddress
13+
mixPubKey*: FieldElement
14+
mixPrivKey*: FieldElement
15+
libp2pPubKey*: SkPublicKey
16+
libp2pPrivKey*: SkPrivateKey
17+
18+
proc initMixNodeInfo*(
19+
multiAddr: MultiAddress,
20+
mixPubKey, mixPrivKey: FieldElement,
21+
libp2pPubKey: SkPublicKey,
22+
libp2pPrivKey: SkPrivateKey,
23+
): MixNodeInfo =
24+
MixNodeInfo(
25+
multiAddr: multiAddr,
26+
mixPubKey: mixPubKey,
27+
mixPrivKey: mixPrivKey,
28+
libp2pPubKey: libp2pPubKey,
29+
libp2pPrivKey: libp2pPrivKey,
30+
)
31+
32+
proc get*(
33+
info: MixNodeInfo
34+
): (MultiAddress, FieldElement, FieldElement, SkPublicKey, SkPrivateKey) =
35+
(
36+
info.multiAddr, info.mixPubKey, info.mixPrivKey, info.libp2pPubKey,
37+
info.libp2pPrivKey,
38+
)
39+
40+
proc serialize*(nodeInfo: MixNodeInfo): Result[seq[byte], string] =
41+
let addrBytes = multiAddrToBytes(nodeInfo.multiAddr).valueOr:
42+
return err("Error in multiaddress conversion to bytes: " & error)
43+
44+
let
45+
mixPubKeyBytes = fieldElementToBytes(nodeInfo.mixPubKey)
46+
mixPrivKeyBytes = fieldElementToBytes(nodeInfo.mixPrivKey)
47+
libp2pPubKeyBytes = nodeInfo.libp2pPubKey.getBytes()
48+
libp2pPrivKeyBytes = nodeInfo.libp2pPrivKey.getBytes()
49+
50+
return ok(
51+
addrBytes & mixPubKeyBytes & mixPrivKeyBytes & libp2pPubKeyBytes & libp2pPrivKeyBytes
52+
)
53+
54+
proc deserialize*(T: typedesc[MixNodeInfo], data: openArray[byte]): Result[T, string] =
55+
if len(data) != MixNodeInfoSize:
56+
return
57+
err("Serialized Mix node info must be exactly " & $MixNodeInfoSize & " bytes")
58+
59+
let multiAddr = bytesToMultiAddr(data[0 .. AddrSize - 1]).valueOr:
60+
return err("Error in multiaddress conversion to bytes: " & error)
61+
62+
let mixPubKey = bytesToFieldElement(
63+
data[AddrSize .. (AddrSize + FieldElementSize - 1)]
64+
).valueOr:
65+
return err("Mix public key deserialize error: " & error)
66+
67+
let mixPrivKey = bytesToFieldElement(
68+
data[(AddrSize + FieldElementSize) .. (AddrSize + (2 * FieldElementSize) - 1)]
69+
).valueOr:
70+
return err("Mix private key deserialize error: " & error)
71+
72+
let libp2pPubKey = SkPublicKey.init(
73+
data[
74+
AddrSize + (2 * FieldElementSize) ..
75+
AddrSize + (2 * FieldElementSize) + SkRawPublicKeySize - 1
76+
]
77+
).valueOr:
78+
return err("Failed to initialize libp2p public key")
79+
80+
let libp2pPrivKey = SkPrivateKey.init(
81+
data[AddrSize + (2 * FieldElementSize) + SkRawPublicKeySize ..^ 1]
82+
).valueOr:
83+
return err("Failed to initialize libp2p private key")
84+
85+
ok(
86+
T(
87+
multiAddr: multiAddr,
88+
mixPubKey: mixPubKey,
89+
mixPrivKey: mixPrivKey,
90+
libp2pPubKey: libp2pPubKey,
91+
libp2pPrivKey: libp2pPrivKey,
92+
)
93+
)
94+
95+
proc isNodeMultiaddress*(mixNodeInfo: MixNodeInfo, multiAddr: MultiAddress): bool =
96+
return mixNodeInfo.multiAddr == multiAddr
97+
98+
proc writeToFile*(
99+
node: MixNodeInfo, index: int, nodeInfoFolderPath: string = "./nodeInfo"
100+
): Result[void, string] =
101+
if not dirExists(nodeInfoFolderPath):
102+
createDir(nodeInfoFolderPath)
103+
let filename = nodeInfoFolderPath / fmt"mixNode_{index}"
104+
var file = newFileStream(filename, fmWrite)
105+
if file == nil:
106+
return err("Failed to create file stream for " & filename)
107+
defer:
108+
file.close()
109+
110+
let serializedData = node.serialize().valueOr:
111+
return err("Failed to serialize mix node info: " & error)
112+
113+
file.writeData(addr serializedData[0], serializedData.len)
114+
return ok()
115+
116+
proc readFromFile*(
117+
T: typedesc[MixNodeInfo], index: int, nodeInfoFolderPath: string = "./nodeInfo"
118+
): Result[T, string] =
119+
try:
120+
let filename = nodeInfoFolderPath / fmt"mixNode_{index}"
121+
if not fileExists(filename):
122+
return err("File does not exist")
123+
var file = newFileStream(filename, fmRead)
124+
if file == nil:
125+
return err(
126+
"Failed to open file: " & filename &
127+
". Check permissions or if the path is correct."
128+
)
129+
defer:
130+
file.close()
131+
let data = file.readAll()
132+
if data.len != MixNodeInfoSize:
133+
return err(
134+
"Invalid data size for MixNodeInfo: expected " & $MixNodeInfoSize &
135+
" bytes, but got " & $(data.len) & " bytes."
136+
)
137+
let dMixNodeInfo = MixNodeInfo.deserialize(cast[seq[byte]](data)).valueOr:
138+
return err("Mix node info deserialize error: " & error)
139+
return ok(dMixNodeInfo)
140+
except IOError as e:
141+
return err("File read error: " & $e.msg)
142+
except OSError as e:
143+
return err("OS error: " & $e.msg)
144+
145+
proc deleteNodeInfoFolder*(nodeInfoFolderPath: string = "./nodeInfo") =
146+
## Deletes the folder that stores serialized mix node info files
147+
## along with all its contents, if the folder exists.
148+
if dirExists(nodeInfoFolderPath):
149+
removeDir(nodeInfoFolderPath)
150+
151+
type MixPubInfo* = object
152+
multiAddr*: MultiAddress
153+
mixPubKey*: FieldElement
154+
libp2pPubKey*: SkPublicKey
155+
156+
proc init*(
157+
T: typedesc[MixPubInfo],
158+
multiAddr: MultiAddress,
159+
mixPubKey: FieldElement,
160+
libp2pPubKey: SkPublicKey,
161+
): T =
162+
T(multiAddr: multiAddr, mixPubKey: mixPubKey, libp2pPubKey: libp2pPubKey)
163+
164+
proc get*(info: MixPubInfo): (MultiAddress, FieldElement, SkPublicKey) =
165+
(info.multiAddr, info.mixPubKey, info.libp2pPubKey)
166+
167+
proc serialize*(nodeInfo: MixPubInfo): Result[seq[byte], string] =
168+
let addrBytes = multiAddrToBytes(nodeInfo.multiAddr).valueOr:
169+
return err("Error in multiaddress conversion to bytes: " & error)
170+
171+
let
172+
mixPubKeyBytes = fieldElementToBytes(nodeInfo.mixPubKey)
173+
libp2pPubKeyBytes = nodeInfo.libp2pPubKey.getBytes()
174+
175+
return ok(addrBytes & mixPubKeyBytes & libp2pPubKeyBytes)
176+
177+
proc deserialize*(T: typedesc[MixPubInfo], data: openArray[byte]): Result[T, string] =
178+
if len(data) != MixPubInfoSize:
179+
return
180+
err("Serialized mix public info must be exactly " & $MixPubInfoSize & " bytes")
181+
182+
let multiAddr = bytesToMultiAddr(data[0 .. AddrSize - 1]).valueOr:
183+
return err("Error in bytes to multiaddress conversion: " & error)
184+
185+
let mixPubKey = bytesToFieldElement(
186+
data[AddrSize .. (AddrSize + FieldElementSize - 1)]
187+
).valueOr:
188+
return err("Mix public key deserialize error: " & error)
189+
190+
let libp2pPubKey = SkPublicKey.init(data[(AddrSize + FieldElementSize) ..^ 1]).valueOr:
191+
return err("Failed to initialize libp2p public key: ")
192+
193+
ok(MixPubInfo(multiAddr: multiAddr, mixPubKey: mixPubKey, libp2pPubKey: libp2pPubKey))
194+
195+
proc writeToFile*(
196+
node: MixPubInfo, index: int, pubInfoFolderPath: string = "./pubInfo"
197+
): Result[void, string] =
198+
if not dirExists(pubInfoFolderPath):
199+
createDir(pubInfoFolderPath)
200+
let filename = pubInfoFolderPath / fmt"mixNode_{index}"
201+
var file = newFileStream(filename, fmWrite)
202+
if file == nil:
203+
return err("Failed to create file stream for " & filename)
204+
defer:
205+
file.close()
206+
207+
let serializedData = node.serialize().valueOr:
208+
return err("Failed to serialize mix pub info: " & error)
209+
210+
file.writeData(unsafeAddr serializedData[0], serializedData.len)
211+
return ok()
212+
213+
proc readFromFile*(
214+
T: typedesc[MixPubInfo], index: int, pubInfoFolderPath: string = "./pubInfo"
215+
): Result[T, string] =
216+
try:
217+
let filename = pubInfoFolderPath / fmt"mixNode_{index}"
218+
if not fileExists(filename):
219+
return err("File does not exist")
220+
var file = newFileStream(filename, fmRead)
221+
if file == nil:
222+
return err(
223+
"Failed to open file: " & filename &
224+
". Check permissions or if the path is correct."
225+
)
226+
defer:
227+
file.close()
228+
let data = file.readAll()
229+
if data.len != MixPubInfoSize:
230+
return err(
231+
"Invalid data size for MixNodeInfo: expected " & $MixNodeInfoSize &
232+
" bytes, but got " & $(data.len) & " bytes."
233+
)
234+
let dMixPubInfo = MixPubInfo.deserialize(cast[seq[byte]](data)).valueOr:
235+
return err("Mix pub info deserialize error: " & error)
236+
return ok(dMixPubInfo)
237+
except IOError as e:
238+
return err("File read error: " & $e.msg)
239+
except OSError as e:
240+
return err("OS error: " & $e.msg)
241+
242+
proc deletePubInfoFolder*(pubInfoFolderPath: string = "./pubInfo") =
243+
## Deletes the folder containing serialized public mix node info
244+
## and all files inside it, if the folder exists.
245+
if dirExists(pubInfoFolderPath):
246+
removeDir(pubInfoFolderPath)
247+
248+
type MixNodes* = seq[MixNodeInfo]
249+
250+
proc getMixPubInfoByIndex*(self: MixNodes, index: int): Result[MixPubInfo, string] =
251+
if index < 0 or index >= self.len:
252+
return err("Index must be between 0 and " & $(self.len))
253+
ok(
254+
MixPubInfo(
255+
multiAddr: self[index].multiAddr,
256+
mixPubKey: self[index].mixPubKey,
257+
libp2pPubKey: self[index].libp2pPubKey,
258+
)
259+
)
260+
261+
proc generateMixNodes(count: int, basePort: int = 4242): Result[MixNodes, string] =
262+
var nodes = newSeq[MixNodeInfo](count)
263+
for i in 0 ..< count:
264+
let keyPairResult = generateKeyPair()
265+
if keyPairResult.isErr:
266+
return err("Generate key pair error: " & $keyPairResult.error)
267+
let (mixPrivKey, mixPubKey) = keyPairResult.get()
268+
269+
let
270+
rng = newRng()
271+
keyPair = SkKeyPair.random(rng[])
272+
libp2pPrivKey = keyPair.seckey
273+
libp2pPubKey = keyPair.pubkey
274+
pubKeyProto = PublicKey(scheme: Secp256k1, skkey: libp2pPubKey)
275+
peerId = PeerId.init(pubKeyProto).get()
276+
multiAddr =
277+
?MultiAddress
278+
.init(fmt"/ip4/0.0.0.0/tcp/{basePort + i}/p2p/{peerId}")
279+
.tryGet()
280+
.catch()
281+
.mapErr(x => x.msg)
282+
283+
nodes[i] = MixNodeInfo(
284+
multiAddr: multiAddr,
285+
mixPubKey: mixPubKey,
286+
mixPrivKey: mixPrivKey,
287+
libp2pPubKey: libp2pPubKey,
288+
libp2pPrivKey: libp2pPrivKey,
289+
)
290+
291+
ok(nodes)
292+
293+
proc initializeMixNodes*(count: int, basePort: int = 4242): Result[MixNodes, string] =
294+
## Creates and initializes a set of mix nodes
295+
let mixNodes = generateMixNodes(count, basePort).valueOr:
296+
return err("Mix node initialization error: " & error)
297+
return ok(mixNodes)
298+
299+
proc findByPeerId*(self: MixNodes, peerId: PeerId): Result[MixNodeInfo, string] =
300+
for node in self:
301+
let p2pPart = ?node.multiAddr.getPart(multiCodec("p2p"))
302+
let nodePeerId = ?PeerId.init(?p2pPart.protoArgument()).mapErr(x => $x)
303+
if nodePeerId == peerId:
304+
return ok(node)
305+
return err("No node with peer id: " & $peerId)
306+
307+
proc initMixMultiAddrByIndex*(
308+
self: var MixNodes, index: int, multiAddr: MultiAddress
309+
): Result[void, string] =
310+
if index < 0 or index >= self.len:
311+
return err("Index must be between 0 and " & $(self.len))
312+
self[index].multiAddr = multiAddr
313+
ok()

0 commit comments

Comments
 (0)