gun-eth 1.3.1 → 1.3.4

Sign up to get free protection for your applications and to get access to all the features.
package/README.md CHANGED
@@ -111,19 +111,7 @@ Learn more about plugin implementation [here](https://github.com/amark/gun/wiki/
111
111
  ```javascript
112
112
  gun.shine("optimismSepolia", nodeId, data, callback);
113
113
  ```
114
-
115
- - `setToken(token)`: Sets a custom token for Gun operations.
116
-
117
- ```javascript
118
- gun.setToken("yourCustomToken");
119
- ```
120
-
121
- - `getToken()`: Retrieves the current custom token.
122
-
123
- ```javascript
124
- const currentToken = gun.getToken();
125
- ```
126
-
114
+
127
115
  ## SHINE
128
116
 
129
117
  SHINE (Secure Hash Integrity Network Ethereum) provides a mechanism for verifying data integrity using Ethereum and Gun.js.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gun-eth",
3
- "version": "1.3.1",
3
+ "version": "1.3.4",
4
4
  "description": "A GunDB plugin for Ethereum, and Web3",
5
5
  "main": "./src/index.js",
6
6
  "scripts": {
@@ -0,0 +1,533 @@
1
+ (function (root, factory) {
2
+ if (typeof define === "function" && define.amd) {
3
+ define(["gun", "gun/sea", "ethers"], factory);
4
+ } else if (typeof module === "object" && module.exports) {
5
+ module.exports = factory(
6
+ require("gun/gun"),
7
+ require("gun/sea"),
8
+ require("ethers")
9
+ );
10
+ } else {
11
+ factory(root.Gun, root.SEA, root.ethers);
12
+ }
13
+ })(typeof self !== "undefined" ? self : this, function (Gun, SEA, ethers) {
14
+ console.log("Factory del plugin Gun-Eth chiamata");
15
+
16
+ const MESSAGE_TO_SIGN = "Accesso a GunDB con Ethereum";
17
+
18
+ // Funzione per verificare se ethers è disponibile
19
+ function checkEthers() {
20
+ if (typeof ethers === "undefined") {
21
+ console.error(
22
+ "Ethers.js non è disponibile. Assicurati che sia caricato prima di questo script."
23
+ );
24
+ return false;
25
+ }
26
+ console.log("Ethers version:", ethers.version);
27
+ return true;
28
+ }
29
+
30
+ // Global variables
31
+ let SHINE_ABI = [
32
+ {
33
+ anonymous: false,
34
+ inputs: [
35
+ {
36
+ indexed: true,
37
+ internalType: "bytes",
38
+ name: "nodeId",
39
+ type: "bytes",
40
+ },
41
+ {
42
+ indexed: false,
43
+ internalType: "bytes32",
44
+ name: "contentHash",
45
+ type: "bytes32",
46
+ },
47
+ {
48
+ indexed: false,
49
+ internalType: "address",
50
+ name: "updater",
51
+ type: "address",
52
+ },
53
+ ],
54
+ name: "DataUpdated",
55
+ type: "event",
56
+ },
57
+ {
58
+ inputs: [
59
+ {
60
+ internalType: "bytes[]",
61
+ name: "nodeIds",
62
+ type: "bytes[]",
63
+ },
64
+ {
65
+ internalType: "bytes32[]",
66
+ name: "contentHashes",
67
+ type: "bytes32[]",
68
+ },
69
+ ],
70
+ name: "batchUpdateData",
71
+ outputs: [],
72
+ stateMutability: "nonpayable",
73
+ type: "function",
74
+ },
75
+ {
76
+ inputs: [
77
+ {
78
+ internalType: "bytes",
79
+ name: "nodeId",
80
+ type: "bytes",
81
+ },
82
+ ],
83
+ name: "getLatestRecord",
84
+ outputs: [
85
+ {
86
+ internalType: "bytes32",
87
+ name: "",
88
+ type: "bytes32",
89
+ },
90
+ {
91
+ internalType: "uint256",
92
+ name: "",
93
+ type: "uint256",
94
+ },
95
+ {
96
+ internalType: "address",
97
+ name: "",
98
+ type: "address",
99
+ },
100
+ ],
101
+ stateMutability: "view",
102
+ type: "function",
103
+ },
104
+ {
105
+ inputs: [
106
+ {
107
+ internalType: "bytes",
108
+ name: "",
109
+ type: "bytes",
110
+ },
111
+ ],
112
+ name: "nodeData",
113
+ outputs: [
114
+ {
115
+ internalType: "bytes32",
116
+ name: "contentHash",
117
+ type: "bytes32",
118
+ },
119
+ {
120
+ internalType: "uint256",
121
+ name: "timestamp",
122
+ type: "uint256",
123
+ },
124
+ {
125
+ internalType: "address",
126
+ name: "updater",
127
+ type: "address",
128
+ },
129
+ ],
130
+ stateMutability: "view",
131
+ type: "function",
132
+ },
133
+ {
134
+ inputs: [
135
+ {
136
+ internalType: "bytes",
137
+ name: "nodeId",
138
+ type: "bytes",
139
+ },
140
+ {
141
+ internalType: "bytes32",
142
+ name: "contentHash",
143
+ type: "bytes32",
144
+ },
145
+ ],
146
+ name: "updateData",
147
+ outputs: [],
148
+ stateMutability: "nonpayable",
149
+ type: "function",
150
+ },
151
+ {
152
+ inputs: [
153
+ {
154
+ internalType: "bytes",
155
+ name: "nodeId",
156
+ type: "bytes",
157
+ },
158
+ {
159
+ internalType: "bytes32",
160
+ name: "contentHash",
161
+ type: "bytes32",
162
+ },
163
+ ],
164
+ name: "verifyData",
165
+ outputs: [
166
+ {
167
+ internalType: "bool",
168
+ name: "",
169
+ type: "bool",
170
+ },
171
+ {
172
+ internalType: "uint256",
173
+ name: "",
174
+ type: "uint256",
175
+ },
176
+ {
177
+ internalType: "address",
178
+ name: "",
179
+ type: "address",
180
+ },
181
+ ],
182
+ stateMutability: "view",
183
+ type: "function",
184
+ },
185
+ ];
186
+
187
+ let SHINE_OPTIMISM_SEPOLIA = "0x43D838b683F772F08f321E5FA265ad3e333BE9C2";
188
+ let SHINE_CONTRACT_ADDRESS;
189
+ let rpcUrl = "";
190
+ let privateKey = "";
191
+
192
+ /**
193
+ * Funzione per ottenere il signer
194
+ * @returns {Promise<ethers.Signer>} Il signer.
195
+ */
196
+ const getSigner = async () => {
197
+ if (rpcUrl && privateKey) {
198
+ // Modalità standalone
199
+ const provider = new ethers.JsonRpcProvider(rpcUrl);
200
+ return new ethers.Wallet(privateKey, provider);
201
+ } else if (
202
+ typeof window !== "undefined" &&
203
+ typeof window.ethereum !== "undefined"
204
+ ) {
205
+ // Modalità browser
206
+ await window.ethereum.request({ method: "eth_requestAccounts" });
207
+ const provider = new ethers.BrowserProvider(window.ethereum);
208
+ return provider.getSigner();
209
+ } else {
210
+ throw new Error("No valid Ethereum provider found");
211
+ }
212
+ };
213
+
214
+ /**
215
+ * Sets standalone configuration for Gun.
216
+ * @param {string} newRpcUrl - The new RPC URL.
217
+ * @param {string} newPrivateKey - The new private key.
218
+ * @returns {Gun} The Gun instance for chaining.
219
+ */
220
+ Gun.chain.setStandaloneConfig = function (newRpcUrl, newPrivateKey) {
221
+ rpcUrl = newRpcUrl;
222
+ privateKey = newPrivateKey;
223
+ console.log("Standalone configuration set");
224
+ return this;
225
+ };
226
+
227
+ /**
228
+ * Verifies an Ethereum signature.
229
+ * @param {string} message - The original message that was signed.
230
+ * @param {string} signature - The signature to verify.
231
+ * @returns {Promise<string|null>} The recovered address or null if verification fails.
232
+ */
233
+ Gun.chain.verifySignature = async function (message, signature) {
234
+ try {
235
+ const recoveredAddress = ethers.verifyMessage(message, signature);
236
+ return recoveredAddress;
237
+ } catch (error) {
238
+ console.error("Error verifying signature:", error);
239
+ return null;
240
+ }
241
+ };
242
+
243
+ /**
244
+ * Generates a password from a signature.
245
+ * @param {string} signature - The signature to derive the password from.
246
+ * @returns {string|null} The generated password or null if generation fails.
247
+ */
248
+ Gun.chain.generatePassword = function (signature) {
249
+ try {
250
+ const hexSignature = ethers.hexlify(signature);
251
+ const hash = ethers.keccak256(hexSignature);
252
+ console.log("Generated password:", hash);
253
+ return hash;
254
+ } catch (error) {
255
+ console.error("Error generating password:", error);
256
+ return null;
257
+ }
258
+ };
259
+
260
+ /**
261
+ * Creates an Ethereum signature for a given message.
262
+ * @param {string} message - The message to sign.
263
+ * @returns {Promise<string|null>} The signature or null if signing fails.
264
+ */
265
+ Gun.chain.createSignature = async function (message) {
266
+ try {
267
+ // Verifica se il messaggio è uguale a MESSAGE_TO_SIGN
268
+ if (message !== MESSAGE_TO_SIGN) {
269
+ throw new Error(
270
+ "Invalid message, valid message is: " + MESSAGE_TO_SIGN
271
+ );
272
+ }
273
+ const signer = await getSigner();
274
+ const signature = await signer.signMessage(message);
275
+ console.log("Signature created:", signature);
276
+ return signature;
277
+ } catch (error) {
278
+ console.error("Error creating signature:", error);
279
+ return null;
280
+ }
281
+ };
282
+
283
+ /**
284
+ * Creates and stores an encrypted key pair for a given address.
285
+ * @param {string} address - The Ethereum address to associate with the key pair.
286
+ * @param {string} signature - The signature to use for encryption.
287
+ * @returns {Promise<void>}
288
+ */
289
+ Gun.chain.createAndStoreEncryptedPair = async function (address, signature) {
290
+ try {
291
+ const gun = this;
292
+ const pair = await SEA.pair();
293
+ const encryptedPair = await SEA.encrypt(JSON.stringify(pair), signature);
294
+ await gun.get("gun-eth").get("users").get(address).put({ encryptedPair });
295
+ console.log("Encrypted pair stored for:", address);
296
+ } catch (error) {
297
+ console.error("Error creating and storing encrypted pair:", error);
298
+ }
299
+ };
300
+
301
+ /**
302
+ * Retrieves and decrypts a stored key pair for a given address.
303
+ * @param {string} address - The Ethereum address associated with the key pair.
304
+ * @param {string} signature - The signature to use for decryption.
305
+ * @returns {Promise<Object|null>} The decrypted key pair or null if retrieval fails.
306
+ */
307
+ Gun.chain.getAndDecryptPair = async function (address, signature) {
308
+ try {
309
+ const gun = this;
310
+ const encryptedData = await gun
311
+ .get("gun-eth")
312
+ .get("users")
313
+ .get(address)
314
+ .get("encryptedPair")
315
+ .then();
316
+ if (!encryptedData) {
317
+ throw new Error("No encrypted data found for this address");
318
+ }
319
+ const decryptedPair = await SEA.decrypt(encryptedData, signature);
320
+ console.log(decryptedPair);
321
+ return decryptedPair;
322
+ } catch (error) {
323
+ console.error("Error retrieving and decrypting pair:", error);
324
+ return null;
325
+ }
326
+ };
327
+
328
+ /**
329
+ * SHINE (Secure Hybrid Information and Network Environment) functionality.
330
+ * @param {string} chain - The blockchain to use (e.g., "optimismSepolia").
331
+ * @param {string} nodeId - The ID of the node to verify or write.
332
+ * @param {Object} data - The data to write (if writing).
333
+ * @param {Function} callback - Callback function to handle the result.
334
+ * @returns {Gun} The Gun instance for chaining.
335
+ */
336
+ Gun.chain.shine = function (chain, nodeId, data, callback) {
337
+ console.log("SHINE plugin called with:", { chain, nodeId, data });
338
+
339
+ if (!checkEthers()) {
340
+ if (callback) callback({ err: "Ethers.js non è disponibile" });
341
+ return this;
342
+ }
343
+
344
+ if (typeof callback !== "function") {
345
+ console.error("Callback must be a function");
346
+ return this;
347
+ }
348
+
349
+ const gun = this;
350
+
351
+ // Seleziona l'indirizzo basato sulla catena
352
+ if (chain === "optimismSepolia") {
353
+ SHINE_CONTRACT_ADDRESS = SHINE_OPTIMISM_SEPOLIA;
354
+ } else {
355
+ throw new Error("Chain not supported");
356
+ }
357
+
358
+ // Funzione per verificare on-chain
359
+ const verifyOnChain = async (nodeId, contentHash) => {
360
+ console.log("Verifying on chain:", { nodeId, contentHash });
361
+ const signer = await getSigner();
362
+ const contract = new ethers.Contract(
363
+ SHINE_CONTRACT_ADDRESS,
364
+ SHINE_ABI,
365
+ signer
366
+ );
367
+ const [isValid, timestamp, updater] = await contract.verifyData(
368
+ ethers.toUtf8Bytes(nodeId),
369
+ contentHash
370
+ );
371
+ console.log("Verification result:", { isValid, timestamp, updater });
372
+ return { isValid, timestamp, updater };
373
+ };
374
+
375
+ // Funzione per scrivere on-chain
376
+ const writeOnChain = async (nodeId, contentHash) => {
377
+ console.log("Writing on chain:", { nodeId, contentHash });
378
+ const signer = await getSigner();
379
+ const contract = new ethers.Contract(
380
+ SHINE_CONTRACT_ADDRESS,
381
+ SHINE_ABI,
382
+ signer
383
+ );
384
+ const tx = await contract.updateData(
385
+ ethers.toUtf8Bytes(nodeId),
386
+ contentHash
387
+ );
388
+ console.log("Transaction sent:", tx.hash);
389
+ const receipt = await tx.wait();
390
+ console.log("Transaction confirmed:", receipt);
391
+ return tx;
392
+ };
393
+
394
+ // Nuova funzione per ottenere l'ultimo record dalla blockchain
395
+ const getLatestRecord = async (nodeId) => {
396
+ const signer = await getSigner();
397
+ const contract = new ethers.Contract(
398
+ SHINE_CONTRACT_ADDRESS,
399
+ SHINE_ABI,
400
+ signer
401
+ );
402
+ const [contentHash, timestamp, updater] = await contract.getLatestRecord(
403
+ ethers.toUtf8Bytes(nodeId)
404
+ );
405
+ console.log("Latest record from blockchain:", {
406
+ nodeId,
407
+ contentHash,
408
+ timestamp,
409
+ updater,
410
+ });
411
+ return { contentHash, timestamp, updater };
412
+ };
413
+
414
+ // Processo SHINE
415
+ if (nodeId && !data) {
416
+ // Caso 1: Utente passa solo il nodo
417
+ gun.get(nodeId).once(async (existingData) => {
418
+ if (!existingData) {
419
+ if (callback) callback({ err: "Node not found in GunDB" });
420
+ return;
421
+ }
422
+
423
+ console.log("existingData", existingData);
424
+
425
+ // Usa il contentHash memorizzato invece di ricalcolarlo
426
+ const contentHash = existingData._contentHash;
427
+ console.log("contentHash", contentHash);
428
+
429
+ if (!contentHash) {
430
+ if (callback)
431
+ callback({ err: "No content hash found for this node" });
432
+ return;
433
+ }
434
+
435
+ try {
436
+ const { isValid, timestamp, updater } = await verifyOnChain(
437
+ nodeId,
438
+ contentHash
439
+ );
440
+ const latestRecord = await getLatestRecord(nodeId);
441
+
442
+ if (isValid) {
443
+ if (callback)
444
+ callback({
445
+ ok: true,
446
+ message: "Data verified on blockchain",
447
+ timestamp,
448
+ updater,
449
+ latestRecord,
450
+ });
451
+ } else {
452
+ if (callback)
453
+ callback({
454
+ ok: false,
455
+ message: "Data not verified on blockchain",
456
+ latestRecord,
457
+ });
458
+ }
459
+ } catch (error) {
460
+ if (callback) callback({ err: error.message });
461
+ }
462
+ });
463
+ } else if (data && !nodeId) {
464
+ // Caso 2: Utente passa solo il testo (data)
465
+ const newNodeId = Gun.text.random();
466
+ const dataString = JSON.stringify(data);
467
+ const contentHash = ethers.keccak256(ethers.toUtf8Bytes(dataString));
468
+
469
+ gun
470
+ .get(newNodeId)
471
+ .put({ ...data, _contentHash: contentHash }, async (ack) => {
472
+ console.log("ack", ack);
473
+ if (ack.err) {
474
+ if (callback) callback({ err: "Error saving data to GunDB" });
475
+ return;
476
+ }
477
+
478
+ try {
479
+ const tx = await writeOnChain(newNodeId, contentHash);
480
+ if (callback)
481
+ callback({
482
+ ok: true,
483
+ message: "Data written to GunDB and blockchain",
484
+ nodeId: newNodeId,
485
+ txHash: tx.hash,
486
+ });
487
+ } catch (error) {
488
+ if (callback) callback({ err: error.message });
489
+ }
490
+ });
491
+ } else {
492
+ if (callback)
493
+ callback({
494
+ err: "Invalid input. Provide either nodeId or data, not both.",
495
+ });
496
+ }
497
+
498
+ return gun;
499
+ };
500
+
501
+ /**
502
+ * Converts a Gun private key to an Ethereum account.
503
+ * @param {string} gunPrivateKey - The Gun private key in base64url format.
504
+ * @returns {Object} An object containing the Ethereum account and public key.
505
+ */
506
+ Gun.chain.gunToEthAccount = function (gunPrivateKey) {
507
+ // Function to convert base64url to hex
508
+ const base64UrlToHex = (base64url) => {
509
+ const padding = "=".repeat((4 - (base64url.length % 4)) % 4);
510
+ const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/") + padding;
511
+ const binary = atob(base64);
512
+ return Array.from(binary, (char) =>
513
+ char.charCodeAt(0).toString(16).padStart(2, "0")
514
+ ).join("");
515
+ };
516
+
517
+ // Convert Gun private key to hex format
518
+ const hexPrivateKey = "0x" + base64UrlToHex(gunPrivateKey);
519
+
520
+ // Create an Ethereum wallet from the private key
521
+ const wallet = new ethers.Wallet(hexPrivateKey);
522
+
523
+ // Get the public address (public key)
524
+ const publicKey = wallet.address;
525
+
526
+ return {
527
+ account: wallet,
528
+ publicKey: publicKey,
529
+ };
530
+ };
531
+
532
+ console.log("Plugin Gun-Eth successfully loaded");
533
+ });
@@ -160,4 +160,4 @@
160
160
  });
161
161
  </script>
162
162
  </body>
163
- </html>
163
+ </html>