diodejs 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +2 -0
- package/.github/workflows/main.yml +33 -0
- package/LICENSE +674 -0
- package/README.md +98 -0
- package/bindPort.js +120 -0
- package/connection.js +552 -0
- package/examples/RPCTest.js +29 -0
- package/examples/portForwardTest.js +16 -0
- package/examples/publishPortTest.js +19 -0
- package/index.js +7 -0
- package/package.json +28 -0
- package/publishPort.js +295 -0
- package/rpc.js +178 -0
- package/testServers/udpTest.js +45 -0
- package/utils.js +77 -0
package/connection.js
ADDED
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
// connection.js
|
|
2
|
+
const tls = require('tls');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const { RLP } = require('@ethereumjs/rlp');
|
|
5
|
+
const EventEmitter = require('events');
|
|
6
|
+
const { makeReadable, parseRequestId, parseResponseType, parseReason } = require('./utils');
|
|
7
|
+
const { Buffer } = require('buffer'); // Import Buffer
|
|
8
|
+
const asn1 = require('asn1.js');
|
|
9
|
+
const secp256k1 = require('secp256k1');
|
|
10
|
+
const ethUtil = require('ethereumjs-util');
|
|
11
|
+
const crypto = require('crypto');
|
|
12
|
+
const DiodeRPC = require('./rpc');
|
|
13
|
+
const abi = require('ethereumjs-abi');
|
|
14
|
+
class DiodeConnection extends EventEmitter {
|
|
15
|
+
constructor(host, port, certPath) {
|
|
16
|
+
super();
|
|
17
|
+
this.host = host;
|
|
18
|
+
this.port = port;
|
|
19
|
+
this.certPath = certPath;
|
|
20
|
+
this.socket = null;
|
|
21
|
+
this.requestId = 0; // Initialize request ID counter
|
|
22
|
+
this.pendingRequests = new Map(); // Map to store pending requests
|
|
23
|
+
this.totalConnections = 0;
|
|
24
|
+
this.totalBytes = 128000; // start with 128KB
|
|
25
|
+
// Add buffer to handle partial data
|
|
26
|
+
this.receiveBuffer = Buffer.alloc(0);
|
|
27
|
+
this.RPC = new DiodeRPC(this);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
connect() {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const options = {
|
|
33
|
+
cert: fs.readFileSync(this.certPath),
|
|
34
|
+
key: fs.readFileSync(this.certPath),
|
|
35
|
+
rejectUnauthorized: false,
|
|
36
|
+
ciphers: 'ECDHE-ECDSA-AES256-GCM-SHA384',
|
|
37
|
+
ecdhCurve: 'secp256k1',
|
|
38
|
+
minVersion: 'TLSv1.2',
|
|
39
|
+
maxVersion: 'TLSv1.2',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
this.socket = tls.connect(this.port, this.host, options, async () => {
|
|
43
|
+
console.log('Connected to Diode.io server');
|
|
44
|
+
// Set keep-alive to prevent connection timeout forever
|
|
45
|
+
this.socket.setKeepAlive(true, 0);
|
|
46
|
+
|
|
47
|
+
// Send the ticketv2 command
|
|
48
|
+
try {
|
|
49
|
+
const ticketCommand = await this.createTicketCommand();
|
|
50
|
+
const response = await this.sendCommand(ticketCommand);
|
|
51
|
+
console.log('Ticket accepted:', response);
|
|
52
|
+
resolve();
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error('Error sending ticket:', error);
|
|
55
|
+
reject(error);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
this.socket.on('data', (data) => {
|
|
60
|
+
try {
|
|
61
|
+
this._handleData(data);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error('Error handling data:', error);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
this.socket.on('error', (err) => {
|
|
67
|
+
console.error('Connection error:', err);
|
|
68
|
+
reject(err);
|
|
69
|
+
});
|
|
70
|
+
this.socket.on('end', () => console.log('Disconnected from server'));
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
_handleData(data) {
|
|
75
|
+
// Append new data to the receive buffer
|
|
76
|
+
this.receiveBuffer = Buffer.concat([this.receiveBuffer, data]);
|
|
77
|
+
console.log('Received data:', data.toString('hex'));
|
|
78
|
+
|
|
79
|
+
let offset = 0;
|
|
80
|
+
while (offset + 2 <= this.receiveBuffer.length) {
|
|
81
|
+
// Read the length of the message (2 bytes)
|
|
82
|
+
const lengthBuffer = this.receiveBuffer.slice(offset, offset + 2);
|
|
83
|
+
const length = lengthBuffer.readUInt16BE(0);
|
|
84
|
+
|
|
85
|
+
if (offset + 2 + length > this.receiveBuffer.length) {
|
|
86
|
+
// Not enough data received yet, wait for more
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const messageBuffer = this.receiveBuffer.slice(offset + 2, offset + 2 + length);
|
|
91
|
+
offset += 2 + length;
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const decodedMessage = RLP.decode(Uint8Array.from(messageBuffer));
|
|
95
|
+
console.log('Decoded message:', makeReadable(decodedMessage));
|
|
96
|
+
|
|
97
|
+
if (Array.isArray(decodedMessage) && decodedMessage.length > 1) {
|
|
98
|
+
const requestIdRaw = decodedMessage[0];
|
|
99
|
+
const responseArray = decodedMessage[1];
|
|
100
|
+
|
|
101
|
+
// Parse requestId
|
|
102
|
+
const requestId = parseRequestId(requestIdRaw);
|
|
103
|
+
|
|
104
|
+
// Debug statements
|
|
105
|
+
console.log('requestIdRaw:', requestIdRaw);
|
|
106
|
+
console.log('Parsed requestId:', requestId);
|
|
107
|
+
|
|
108
|
+
if (requestId !== null && this.pendingRequests.has(requestId)) {
|
|
109
|
+
// This is a response to a pending request
|
|
110
|
+
const [responseTypeRaw, ...responseData] = responseArray;
|
|
111
|
+
const responseRaw = responseData[0];
|
|
112
|
+
|
|
113
|
+
// Debug statements
|
|
114
|
+
console.log('responseTypeRaw:', responseTypeRaw);
|
|
115
|
+
console.log('Type of responseTypeRaw:', typeof responseTypeRaw);
|
|
116
|
+
console.log('Instance of responseTypeRaw:', responseTypeRaw instanceof Uint8Array);
|
|
117
|
+
console.log('Is Array:', Array.isArray(responseTypeRaw));
|
|
118
|
+
|
|
119
|
+
// Parse responseType
|
|
120
|
+
const responseType = parseResponseType(responseTypeRaw);
|
|
121
|
+
|
|
122
|
+
console.log(`Received response for requestId: ${requestId}`);
|
|
123
|
+
console.log(`Response Type: '${responseType}'`);
|
|
124
|
+
|
|
125
|
+
const { resolve, reject } = this.pendingRequests.get(requestId);
|
|
126
|
+
try{
|
|
127
|
+
if (responseType === 'response') {
|
|
128
|
+
if (!Array.isArray(responseRaw) && makeReadable(responseRaw) === 'too_low') {
|
|
129
|
+
this.fixResponse(responseData);
|
|
130
|
+
// Re-send the ticket command
|
|
131
|
+
this.createTicketCommand().then((ticketCommand) => {
|
|
132
|
+
this.sendCommand(ticketCommand).then(resolve).catch(reject);
|
|
133
|
+
}).catch(reject);
|
|
134
|
+
resolve(responseData);
|
|
135
|
+
}
|
|
136
|
+
resolve(responseData);
|
|
137
|
+
} else if (responseType === 'error') {
|
|
138
|
+
if (responseData.length > 1) {
|
|
139
|
+
const reason = parseReason(responseData[1]);
|
|
140
|
+
reject(new Error(reason));
|
|
141
|
+
} else {
|
|
142
|
+
const reason = parseReason(responseData[0]);
|
|
143
|
+
reject(new Error(reason));
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
resolve(responseData);
|
|
147
|
+
}
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error('Error handling response:', error);
|
|
150
|
+
}
|
|
151
|
+
this.pendingRequests.delete(requestId);
|
|
152
|
+
} else {
|
|
153
|
+
// This is an unsolicited message
|
|
154
|
+
console.log('Received unsolicited message:', decodedMessage);
|
|
155
|
+
this.emit('unsolicited', decodedMessage);
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
// Invalid message format
|
|
159
|
+
console.error('Invalid message format:', decodedMessage);
|
|
160
|
+
}
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error('Error decoding message:', error);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
// Remove processed data from the buffer
|
|
168
|
+
this.receiveBuffer = this.receiveBuffer.slice(offset);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
fixResponse(response) {
|
|
172
|
+
/* response is :
|
|
173
|
+
[
|
|
174
|
+
'too_low',
|
|
175
|
+
1284,
|
|
176
|
+
666,
|
|
177
|
+
11,
|
|
178
|
+
135591,
|
|
179
|
+
'test',
|
|
180
|
+
'0x01eb1726dd7286d2dab222ea5dfef7c820cd01c30936240f5780a6e468e731f3b55d4c963b3eb768663263b396555aa52be49d7d3ae2a9173732fa410ad46434f3'
|
|
181
|
+
]
|
|
182
|
+
[3] is last totalConnections
|
|
183
|
+
[4] is last totalBytes
|
|
184
|
+
*/
|
|
185
|
+
const totalConnectionsBuffer = Buffer.from(response[3]);
|
|
186
|
+
const totalBytesBuffer = Buffer.from(response[4]);
|
|
187
|
+
this.totalConnections = parseInt(totalConnectionsBuffer.readUIntBE(0, totalConnectionsBuffer.length), 10) +1;
|
|
188
|
+
this.totalBytes = parseInt(totalBytesBuffer.readUIntBE(0, totalBytesBuffer.length), 10) + 128000;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
sendCommand(commandArray) {
|
|
192
|
+
return new Promise((resolve, reject) => {
|
|
193
|
+
//check if connection is alive
|
|
194
|
+
if (!this.socket || this.socket.destroyed) {
|
|
195
|
+
//reconnect
|
|
196
|
+
this.connect().then(() => {
|
|
197
|
+
this.sendCommand(commandArray).then(resolve).catch(reject);
|
|
198
|
+
}).catch(reject);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const requestId = this._getNextRequestId();
|
|
202
|
+
// Build the message as [requestId, [commandArray]]
|
|
203
|
+
const commandWithId = [requestId, commandArray];
|
|
204
|
+
|
|
205
|
+
// Store the promise callbacks to resolve/reject later
|
|
206
|
+
this.pendingRequests.set(requestId, { resolve, reject });
|
|
207
|
+
|
|
208
|
+
const commandBuffer = RLP.encode(commandWithId);
|
|
209
|
+
const byteLength = Buffer.byteLength(commandBuffer);
|
|
210
|
+
|
|
211
|
+
// Create a 2-byte length buffer
|
|
212
|
+
const lengthBuffer = Buffer.alloc(2);
|
|
213
|
+
lengthBuffer.writeUInt16BE(byteLength, 0);
|
|
214
|
+
|
|
215
|
+
const message = Buffer.concat([lengthBuffer, commandBuffer]);
|
|
216
|
+
|
|
217
|
+
console.log(`Sending command with requestId ${requestId}:`, commandArray);
|
|
218
|
+
console.log('Command buffer:', message.toString('hex'));
|
|
219
|
+
|
|
220
|
+
this.socket.write(message);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
sendCommandWithSessionId(commandArray, sessionId) {
|
|
225
|
+
return new Promise((resolve, reject) => {
|
|
226
|
+
//check if connection is alive
|
|
227
|
+
if (!this.socket || this.socket.destroyed) {
|
|
228
|
+
//reconnect
|
|
229
|
+
this.connect().then(() => {
|
|
230
|
+
this.sendCommand(commandArray).then(resolve).catch(reject);
|
|
231
|
+
}).catch(reject);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const requestId = sessionId;
|
|
235
|
+
// Build the message as [requestId, [commandArray]]
|
|
236
|
+
const commandWithId = [requestId, commandArray];
|
|
237
|
+
|
|
238
|
+
// Store the promise callbacks to resolve/reject later
|
|
239
|
+
this.pendingRequests.set(requestId, { resolve, reject });
|
|
240
|
+
|
|
241
|
+
const commandBuffer = RLP.encode(commandWithId);
|
|
242
|
+
const byteLength = Buffer.byteLength(commandBuffer);
|
|
243
|
+
|
|
244
|
+
// Create a 2-byte length buffer
|
|
245
|
+
const lengthBuffer = Buffer.alloc(2);
|
|
246
|
+
lengthBuffer.writeUInt16BE(byteLength, 0);
|
|
247
|
+
|
|
248
|
+
const message = Buffer.concat([lengthBuffer, commandBuffer]);
|
|
249
|
+
|
|
250
|
+
console.log(`Sending command with requestId ${requestId}:`, commandArray);
|
|
251
|
+
console.log('Command buffer:', message.toString('hex'));
|
|
252
|
+
|
|
253
|
+
this.socket.write(message);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
getEthereumAddress() {
|
|
258
|
+
try {
|
|
259
|
+
const pem = fs.readFileSync(this.certPath, 'utf8');
|
|
260
|
+
let privateKeyPem;
|
|
261
|
+
let privateKeyDer;
|
|
262
|
+
let privateKeyBytes;
|
|
263
|
+
|
|
264
|
+
if (pem.includes('-----BEGIN PRIVATE KEY-----')) {
|
|
265
|
+
// Handle PKCS#8 format
|
|
266
|
+
privateKeyPem = pem
|
|
267
|
+
.replace('-----BEGIN PRIVATE KEY-----', '')
|
|
268
|
+
.replace('-----END PRIVATE KEY-----', '')
|
|
269
|
+
.replace(/\r?\n|\r/g, '');
|
|
270
|
+
|
|
271
|
+
privateKeyDer = Buffer.from(privateKeyPem, 'base64');
|
|
272
|
+
|
|
273
|
+
// Define ASN.1 structure for PKCS#8 private key
|
|
274
|
+
const PrivateKeyInfoASN = asn1.define('PrivateKeyInfo', function () {
|
|
275
|
+
this.seq().obj(
|
|
276
|
+
this.key('version').int(),
|
|
277
|
+
this.key('privateKeyAlgorithm').seq().obj(
|
|
278
|
+
this.key('algorithm').objid(),
|
|
279
|
+
this.key('parameters').optional()
|
|
280
|
+
),
|
|
281
|
+
this.key('privateKey').octstr(),
|
|
282
|
+
this.key('attributes').implicit(0).any().optional(),
|
|
283
|
+
this.key('publicKey').implicit(1).bitstr().optional()
|
|
284
|
+
);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Decode the DER-encoded private key
|
|
288
|
+
const privateKeyInfo = PrivateKeyInfoASN.decode(privateKeyDer, 'der');
|
|
289
|
+
const privateKeyOctetString = privateKeyInfo.privateKey;
|
|
290
|
+
|
|
291
|
+
// Now parse the ECPrivateKey structure inside the octet string
|
|
292
|
+
const ECPrivateKeyASN = asn1.define('ECPrivateKey', function () {
|
|
293
|
+
this.seq().obj(
|
|
294
|
+
this.key('version').int(),
|
|
295
|
+
this.key('privateKey').octstr(),
|
|
296
|
+
this.key('parameters').explicit(0).objid().optional(),
|
|
297
|
+
this.key('publicKey').explicit(1).bitstr().optional()
|
|
298
|
+
);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const ecPrivateKey = ECPrivateKeyASN.decode(privateKeyOctetString, 'der');
|
|
302
|
+
privateKeyBytes = ecPrivateKey.privateKey;
|
|
303
|
+
console.log('Private key bytes:', privateKeyBytes.toString('hex'));
|
|
304
|
+
} else {
|
|
305
|
+
throw new Error('Unsupported key format. Expected EC PRIVATE KEY or PRIVATE KEY in PEM format.');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Compute the public key
|
|
309
|
+
const publicKeyUint8Array = secp256k1.publicKeyCreate(privateKeyBytes, false); // uncompressed
|
|
310
|
+
|
|
311
|
+
// Convert publicKey to Buffer if necessary
|
|
312
|
+
const publicKeyBuffer = Buffer.isBuffer(publicKeyUint8Array)
|
|
313
|
+
? publicKeyUint8Array
|
|
314
|
+
: Buffer.from(publicKeyUint8Array);
|
|
315
|
+
|
|
316
|
+
// Derive the Ethereum address
|
|
317
|
+
const addressBuffer = ethUtil.pubToAddress(publicKeyBuffer, true);
|
|
318
|
+
const address = '0x' + addressBuffer.toString('hex');
|
|
319
|
+
|
|
320
|
+
console.log('Ethereum address:', address);
|
|
321
|
+
return address;
|
|
322
|
+
} catch (error) {
|
|
323
|
+
console.error('Error extracting Ethereum address:', error);
|
|
324
|
+
throw error;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
getServerEthereumAddress() {
|
|
329
|
+
try {
|
|
330
|
+
const serverCert = this.socket.getPeerCertificate(true);
|
|
331
|
+
if (!serverCert.raw) {
|
|
332
|
+
throw new Error('Failed to get server certificate.');
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const publicKeyBuffer = Buffer.isBuffer(serverCert.pubkey)
|
|
336
|
+
? serverCert.pubkey
|
|
337
|
+
: Buffer.from(serverCert.pubkey);
|
|
338
|
+
|
|
339
|
+
console.log('Public key Server:', publicKeyBuffer.toString('hex'));
|
|
340
|
+
|
|
341
|
+
const addressBuffer = ethUtil.pubToAddress(publicKeyBuffer, true);
|
|
342
|
+
const address = '0x' + addressBuffer.toString('hex');
|
|
343
|
+
|
|
344
|
+
console.log('Server Ethereum address:', address);
|
|
345
|
+
return address;
|
|
346
|
+
} catch (error) {
|
|
347
|
+
console.error('Error extracting server Ethereum address:', error);
|
|
348
|
+
throw error;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// getServerEthereumAddress() {
|
|
353
|
+
// try {
|
|
354
|
+
// const serverCert = this.socket.getPeerCertificate(true);
|
|
355
|
+
// if (!serverCert.raw) {
|
|
356
|
+
// throw new Error('Failed to get server certificate.');
|
|
357
|
+
// }
|
|
358
|
+
|
|
359
|
+
// // Extract public key from the certificate
|
|
360
|
+
// const publicKey = serverCert.pubkey; // May need to parse ASN.1 structure to get the public key
|
|
361
|
+
// // Assume you have a method to extract the public key buffer from the certificate
|
|
362
|
+
|
|
363
|
+
// // Compute Ethereum address from public key
|
|
364
|
+
// const publicKeyBuffer = Buffer.from(publicKey); // Ensure it's a Buffer
|
|
365
|
+
// const addressBuffer = ethUtil.pubToAddress(publicKeyBuffer, true);
|
|
366
|
+
|
|
367
|
+
// return addressBuffer; // Return as Buffer
|
|
368
|
+
// } catch (error) {
|
|
369
|
+
// console.error('Error extracting server Ethereum address:', error);
|
|
370
|
+
// throw error;
|
|
371
|
+
// }
|
|
372
|
+
// }
|
|
373
|
+
|
|
374
|
+
// Method to extract private key bytes from certPath
|
|
375
|
+
getPrivateKey() {
|
|
376
|
+
// Similar to getEthereumAddress(), but return privateKeyBytes
|
|
377
|
+
// Ensure to handle different key formats (EC PRIVATE KEY and PRIVATE KEY)
|
|
378
|
+
try {
|
|
379
|
+
const pem = fs.readFileSync(this.certPath, 'utf8');
|
|
380
|
+
let privateKeyPem;
|
|
381
|
+
let privateKeyDer;
|
|
382
|
+
let privateKeyBytes;
|
|
383
|
+
|
|
384
|
+
if (pem.includes('-----BEGIN PRIVATE KEY-----')) {
|
|
385
|
+
// Handle PKCS#8 format
|
|
386
|
+
privateKeyPem = pem
|
|
387
|
+
.replace('-----BEGIN PRIVATE KEY-----', '')
|
|
388
|
+
.replace('-----END PRIVATE KEY-----', '')
|
|
389
|
+
.replace(/\r?\n|\r/g, '');
|
|
390
|
+
|
|
391
|
+
privateKeyDer = Buffer.from(privateKeyPem, 'base64');
|
|
392
|
+
|
|
393
|
+
// Define ASN.1 structure for PKCS#8 private key
|
|
394
|
+
const PrivateKeyInfoASN = asn1.define('PrivateKeyInfo', function () {
|
|
395
|
+
this.seq().obj(
|
|
396
|
+
this.key('version').int(),
|
|
397
|
+
this.key('privateKeyAlgorithm').seq().obj(
|
|
398
|
+
this.key('algorithm').objid(),
|
|
399
|
+
this.key('parameters').optional()
|
|
400
|
+
),
|
|
401
|
+
this.key('privateKey').octstr(),
|
|
402
|
+
this.key('attributes').implicit(0).any().optional(),
|
|
403
|
+
this.key('publicKey').implicit(1).bitstr().optional()
|
|
404
|
+
);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// Decode the DER-encoded private key
|
|
408
|
+
const privateKeyInfo = PrivateKeyInfoASN.decode(privateKeyDer, 'der');
|
|
409
|
+
const privateKeyOctetString = privateKeyInfo.privateKey;
|
|
410
|
+
|
|
411
|
+
// Now parse the ECPrivateKey structure inside the octet string
|
|
412
|
+
const ECPrivateKeyASN = asn1.define('ECPrivateKey', function () {
|
|
413
|
+
this.seq().obj(
|
|
414
|
+
this.key('version').int(),
|
|
415
|
+
this.key('privateKey').octstr(),
|
|
416
|
+
this.key('parameters').explicit(0).objid().optional(),
|
|
417
|
+
this.key('publicKey').explicit(1).bitstr().optional()
|
|
418
|
+
);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
const ecPrivateKey = ECPrivateKeyASN.decode(privateKeyOctetString, 'der');
|
|
422
|
+
privateKeyBytes = ecPrivateKey.privateKey;
|
|
423
|
+
} else if (pem.includes('-----BEGIN EC PRIVATE KEY-----')) {
|
|
424
|
+
// Handle EC PRIVATE KEY format
|
|
425
|
+
privateKeyPem = pem
|
|
426
|
+
.replace('-----BEGIN EC PRIVATE KEY-----', '')
|
|
427
|
+
.replace('-----END EC PRIVATE KEY-----', '')
|
|
428
|
+
.replace(/\r?\n|\r/g, '');
|
|
429
|
+
|
|
430
|
+
privateKeyDer = Buffer.from(privateKeyPem, 'base64');
|
|
431
|
+
|
|
432
|
+
// Define ASN.1 structure for EC private key
|
|
433
|
+
const ECPrivateKeyASN = asn1.define('ECPrivateKey', function () {
|
|
434
|
+
this.seq().obj(
|
|
435
|
+
this.key('version').int(),
|
|
436
|
+
this.key('privateKey').octstr(),
|
|
437
|
+
this.key('parameters').explicit(0).objid().optional(),
|
|
438
|
+
this.key('publicKey').explicit(1).bitstr().optional()
|
|
439
|
+
);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// Decode the DER-encoded private key
|
|
443
|
+
const ecPrivateKey = ECPrivateKeyASN.decode(privateKeyDer, 'der');
|
|
444
|
+
privateKeyBytes = ecPrivateKey.privateKey;
|
|
445
|
+
} else {
|
|
446
|
+
throw new Error('Unsupported key format. Expected EC PRIVATE KEY or PRIVATE KEY in PEM format.');
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return privateKeyBytes;
|
|
450
|
+
} catch (error) {
|
|
451
|
+
console.error('Error extracting Ethereum address:', error);
|
|
452
|
+
throw error;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
async createTicketSignature(serverIdBuffer, totalConnections, totalBytes, localAddress, epoch) {
|
|
457
|
+
this.getEthereumAddress()
|
|
458
|
+
const chainId = 1284;
|
|
459
|
+
const fleetContractBuffer = ethUtil.toBuffer('0x6000000000000000000000000000000000000000'); // 20-byte Buffer
|
|
460
|
+
|
|
461
|
+
// Hash of localAddress (empty string)
|
|
462
|
+
const localAddressHash = crypto.createHash('sha256').update(Buffer.from(localAddress, 'utf8')).digest();
|
|
463
|
+
|
|
464
|
+
// Data to sign
|
|
465
|
+
const dataToSign = [
|
|
466
|
+
ethUtil.setLengthLeft(ethUtil.toBuffer(chainId), 32),
|
|
467
|
+
ethUtil.setLengthLeft(ethUtil.toBuffer(epoch), 32),
|
|
468
|
+
ethUtil.setLengthLeft(fleetContractBuffer, 32),
|
|
469
|
+
ethUtil.setLengthLeft(ethUtil.toBuffer(serverIdBuffer), 32),
|
|
470
|
+
ethUtil.setLengthLeft(ethUtil.toBuffer(totalConnections), 32),
|
|
471
|
+
ethUtil.setLengthLeft(ethUtil.toBuffer(totalBytes), 32),
|
|
472
|
+
ethUtil.setLengthLeft(localAddressHash, 32),
|
|
473
|
+
];
|
|
474
|
+
|
|
475
|
+
// Convert each element in dataToSign to bytes32 and concatenate them
|
|
476
|
+
const encodedData = Buffer.concat(dataToSign.map(item => abi.rawEncode(['bytes32'], [item])));
|
|
477
|
+
|
|
478
|
+
console.log('Encoded data:', encodedData.toString('hex'));
|
|
479
|
+
|
|
480
|
+
console.log('Data to sign:', makeReadable(dataToSign));
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
// Sign the data
|
|
484
|
+
const privateKey = this.getPrivateKey();
|
|
485
|
+
const msgHash = ethUtil.keccak256(encodedData);
|
|
486
|
+
console.log('Message hash:', msgHash.toString('hex'));
|
|
487
|
+
const signature = secp256k1.ecdsaSign(msgHash, privateKey);
|
|
488
|
+
console.log('Signature:', signature);
|
|
489
|
+
|
|
490
|
+
const signatureBuffer = Buffer.concat([
|
|
491
|
+
ethUtil.toBuffer([signature.recid]),
|
|
492
|
+
signature.signature
|
|
493
|
+
]);
|
|
494
|
+
|
|
495
|
+
return signatureBuffer;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
async createTicketCommand() {
|
|
499
|
+
const chainId = 1284;
|
|
500
|
+
const fleetContract = ethUtil.toBuffer('0x6000000000000000000000000000000000000000')
|
|
501
|
+
const localAddress = 'test2'; // Always empty string
|
|
502
|
+
|
|
503
|
+
// Increment totalConnections
|
|
504
|
+
this.totalConnections += 1;
|
|
505
|
+
const totalConnections = this.totalConnections;
|
|
506
|
+
|
|
507
|
+
// Assume totalBytes is managed elsewhere
|
|
508
|
+
const totalBytes = this.totalBytes;
|
|
509
|
+
|
|
510
|
+
// Get server Ethereum address as Buffer
|
|
511
|
+
const serverIdBuffer = this.getServerEthereumAddress();
|
|
512
|
+
|
|
513
|
+
// Get epoch
|
|
514
|
+
const epoch = await this.RPC.getEpoch();
|
|
515
|
+
const signature = await this.createTicketSignature(
|
|
516
|
+
serverIdBuffer,
|
|
517
|
+
totalConnections,
|
|
518
|
+
totalBytes,
|
|
519
|
+
localAddress,
|
|
520
|
+
epoch
|
|
521
|
+
);
|
|
522
|
+
console.log('Signature hex:', signature.toString('hex'));
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
// Construct the ticket command
|
|
526
|
+
const ticketCommand = [
|
|
527
|
+
'ticketv2',
|
|
528
|
+
chainId,
|
|
529
|
+
epoch,
|
|
530
|
+
fleetContract,
|
|
531
|
+
totalConnections,
|
|
532
|
+
totalBytes,
|
|
533
|
+
localAddress,
|
|
534
|
+
signature
|
|
535
|
+
];
|
|
536
|
+
|
|
537
|
+
return ticketCommand;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
_getNextRequestId() {
|
|
541
|
+
// Increment the request ID counter, wrap around if necessary
|
|
542
|
+
this.requestId = (this.requestId + 1) % Number.MAX_SAFE_INTEGER;
|
|
543
|
+
return this.requestId;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
close() {
|
|
547
|
+
this.socket.end();
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
module.exports = DiodeConnection;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const { DiodeConnection, DiodeRPC } = require('../index');
|
|
2
|
+
const { makeReadable } = require('../utils');
|
|
3
|
+
|
|
4
|
+
async function main() {
|
|
5
|
+
const host = 'us2.prenet.diode.io';
|
|
6
|
+
const port = 41046;
|
|
7
|
+
const certPath = 'device_certificate.pem';
|
|
8
|
+
|
|
9
|
+
const connection = new DiodeConnection(host, port, certPath);
|
|
10
|
+
await connection.connect();
|
|
11
|
+
const rpc = connection.RPC;
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const address = connection.getEthereumAddress();
|
|
15
|
+
console.log('Address:', address);
|
|
16
|
+
const ping = await rpc.ping();
|
|
17
|
+
console.log('Ping:', ping);
|
|
18
|
+
const blockPeak = await rpc.getBlockPeak();
|
|
19
|
+
console.log('Current Block Peak:', blockPeak);
|
|
20
|
+
const blockHeader = await rpc.getBlockHeader(blockPeak);
|
|
21
|
+
console.log('Block Header:', makeReadable(blockHeader));
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error('RPC Error:', error);
|
|
24
|
+
} finally {
|
|
25
|
+
connection.close();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
main();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const { DiodeConnection, BindPort } = require('../index');
|
|
2
|
+
|
|
3
|
+
async function main() {
|
|
4
|
+
const host = 'us2.prenet.diode.io';
|
|
5
|
+
const port = 41046;
|
|
6
|
+
const certPath = 'device_certificate.pem';
|
|
7
|
+
|
|
8
|
+
const connection = new DiodeConnection(host, port, certPath);
|
|
9
|
+
await connection.connect();
|
|
10
|
+
|
|
11
|
+
const portForward = new BindPort(connection, 3002, 80, "5365baf29cb7ab58de588dfc448913cb609283e2");
|
|
12
|
+
portForward.bind();
|
|
13
|
+
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
main();
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// example.js
|
|
2
|
+
|
|
3
|
+
const DiodeConnection = require('../connection')
|
|
4
|
+
const PublishPort = require('../publishPort')
|
|
5
|
+
|
|
6
|
+
async function startPublishing() {
|
|
7
|
+
const host = 'us2.prenet.diode.io';
|
|
8
|
+
const port = 41046;
|
|
9
|
+
const certPath = 'device_certificate.pem';
|
|
10
|
+
|
|
11
|
+
const connection = new DiodeConnection(host, port, certPath);
|
|
12
|
+
await connection.connect();
|
|
13
|
+
|
|
14
|
+
const publishedPorts = [8080]; // Ports you want to publish
|
|
15
|
+
const publishPort = new PublishPort(connection, publishedPorts, certPath);
|
|
16
|
+
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
startPublishing();
|
package/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// index.js
|
|
2
|
+
const DiodeConnection = require('./connection');
|
|
3
|
+
const DiodeRPC = require('./rpc');
|
|
4
|
+
const BindPort = require('./bindPort');
|
|
5
|
+
const PublishPort = require('./publishPort');
|
|
6
|
+
const makeReadable = require('./utils').makeReadable;
|
|
7
|
+
module.exports = { DiodeConnection, DiodeRPC, BindPort , PublishPort, makeReadable };
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "diodejs",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"description": "A JavaScript client for interacting with the Diode network. It provides functionalities to bind and publish ports, send RPC commands, and handle responses.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"author": "",
|
|
10
|
+
"license": "ISC",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@ethereumjs/rlp": "^5.0.2",
|
|
13
|
+
"asn1.js": "^5.4.1",
|
|
14
|
+
"axios": "^1.6.8",
|
|
15
|
+
"buffer": "^6.0.3",
|
|
16
|
+
"crypto": "^1.0.1",
|
|
17
|
+
"dgram": "^1.0.1",
|
|
18
|
+
"ethereumjs-abi": "^0.6.8",
|
|
19
|
+
"ethereumjs-util": "^7.1.5",
|
|
20
|
+
"ethers": "^6.13.2",
|
|
21
|
+
"fs": "^0.0.1-security",
|
|
22
|
+
"net": "^1.0.2",
|
|
23
|
+
"node-fetch": "^2.7.0",
|
|
24
|
+
"rlp": "^3.0.0",
|
|
25
|
+
"secp256k1": "^5.0.0",
|
|
26
|
+
"tls": "^0.0.1"
|
|
27
|
+
}
|
|
28
|
+
}
|