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.
@@ -0,0 +1,347 @@
1
+ const Gun = require("gun/gun");
2
+ const SEA = require("gun/sea");
3
+ const ethers = require("ethers");
4
+ const SHINE = require("../abis/SHINE.json");
5
+
6
+ const SHINE_ABI = SHINE.abi;
7
+ const SHINE_OPTIMISM_SEPOLIA = SHINE.address;
8
+
9
+ let SHINE_CONTRACT_ADDRESS;
10
+
11
+ let rpcUrl = "";
12
+ let privateKey = "";
13
+
14
+ const MESSAGE_TO_SIGN = "Accesso a GunDB con Ethereum";
15
+
16
+ /**
17
+ * Funzione per ottenere il signer
18
+ * @returns {Promise<ethers.Signer>} Il signer.
19
+ */
20
+ const getSigner = async () => {
21
+ if (rpcUrl && privateKey) {
22
+ // Modalità standalone
23
+ const provider = new ethers.JsonRpcProvider(rpcUrl);
24
+ return new ethers.Wallet(privateKey, provider);
25
+ } else if (
26
+ typeof window !== "undefined" &&
27
+ typeof window.ethereum !== "undefined"
28
+ ) {
29
+ // Modalità browser
30
+ await window.ethereum.request({ method: "eth_requestAccounts" });
31
+ const provider = new ethers.BrowserProvider(window.ethereum);
32
+ return provider.getSigner();
33
+ } else {
34
+ throw new Error("No valid Ethereum provider found");
35
+ }
36
+ };
37
+
38
+ /**
39
+ * Sets standalone configuration for Gun.
40
+ * @param {string} newRpcUrl - The new RPC URL.
41
+ * @param {string} newPrivateKey - The new private key.
42
+ * @returns {Gun} The Gun instance for chaining.
43
+ */
44
+ Gun.chain.setStandaloneConfig = function (newRpcUrl, newPrivateKey) {
45
+ rpcUrl = newRpcUrl;
46
+ privateKey = newPrivateKey;
47
+ console.log("Standalone configuration set");
48
+ return this;
49
+ };
50
+
51
+ /**
52
+ * Verifies an Ethereum signature.
53
+ * @param {string} message - The original message that was signed.
54
+ * @param {string} signature - The signature to verify.
55
+ * @returns {Promise<string|null>} The recovered address or null if verification fails.
56
+ */
57
+ Gun.chain.verifySignature = async function (message, signature) {
58
+ try {
59
+ const recoveredAddress = ethers.verifyMessage(message, signature);
60
+ return recoveredAddress;
61
+ } catch (error) {
62
+ console.error("Error verifying signature:", error);
63
+ return null;
64
+ }
65
+ };
66
+
67
+ /**
68
+ * Generates a password from a signature.
69
+ * @param {string} signature - The signature to derive the password from.
70
+ * @returns {string|null} The generated password or null if generation fails.
71
+ */
72
+ Gun.chain.generatePassword = function (signature) {
73
+ try {
74
+ const hexSignature = ethers.hexlify(signature);
75
+ const hash = ethers.keccak256(hexSignature);
76
+ console.log("Generated password:", hash);
77
+ return hash;
78
+ } catch (error) {
79
+ console.error("Error generating password:", error);
80
+ return null;
81
+ }
82
+ };
83
+
84
+ /**
85
+ * Creates an Ethereum signature for a given message.
86
+ * @param {string} message - The message to sign.
87
+ * @returns {Promise<string|null>} The signature or null if signing fails.
88
+ */
89
+ Gun.chain.createSignature = async function (message) {
90
+ try {
91
+ // Verifica se il messaggio è uguale a MESSAGE_TO_SIGN
92
+ if (message !== MESSAGE_TO_SIGN) {
93
+ throw new Error("Invalid message, valid message is: " + MESSAGE_TO_SIGN);
94
+ }
95
+ const signer = await getSigner();
96
+ const signature = await signer.signMessage(message);
97
+ console.log("Signature created:", signature);
98
+ return signature;
99
+ } catch (error) {
100
+ console.error("Error creating signature:", error);
101
+ return null;
102
+ }
103
+ };
104
+
105
+ /**
106
+ * Creates and stores an encrypted key pair for a given address.
107
+ * @param {string} address - The Ethereum address to associate with the key pair.
108
+ * @param {string} signature - The signature to use for encryption.
109
+ * @returns {Promise<void>}
110
+ */
111
+ Gun.chain.createAndStoreEncryptedPair = async function (address, signature) {
112
+ try {
113
+ const gun = this;
114
+ const pair = await SEA.pair();
115
+ const encryptedPair = await SEA.encrypt(JSON.stringify(pair), signature);
116
+ await gun.get("gun-eth").get("users").get(address).put({ encryptedPair });
117
+ console.log("Encrypted pair stored for:", address);
118
+ } catch (error) {
119
+ console.error("Error creating and storing encrypted pair:", error);
120
+ }
121
+ };
122
+
123
+ /**
124
+ * Retrieves and decrypts a stored key pair for a given address.
125
+ * @param {string} address - The Ethereum address associated with the key pair.
126
+ * @param {string} signature - The signature to use for decryption.
127
+ * @returns {Promise<Object|null>} The decrypted key pair or null if retrieval fails.
128
+ */
129
+ Gun.chain.getAndDecryptPair = async function (address, signature) {
130
+ try {
131
+ const gun = this;
132
+ const encryptedData = await gun
133
+ .get("gun-eth")
134
+ .get("users")
135
+ .get(address)
136
+ .get("encryptedPair")
137
+ .then();
138
+ if (!encryptedData) {
139
+ throw new Error("No encrypted data found for this address");
140
+ }
141
+ const decryptedPair = await SEA.decrypt(encryptedData, signature);
142
+ console.log(decryptedPair);
143
+ return decryptedPair;
144
+ } catch (error) {
145
+ console.error("Error retrieving and decrypting pair:", error);
146
+ return null;
147
+ }
148
+ };
149
+
150
+ /**
151
+ * SHINE (Secure Hybrid Information and Network Environment) functionality.
152
+ * @param {string} chain - The blockchain to use (e.g., "optimismSepolia").
153
+ * @param {string} nodeId - The ID of the node to verify or write.
154
+ * @param {Object} data - The data to write (if writing).
155
+ * @param {Function} callback - Callback function to handle the result.
156
+ * @returns {Gun} The Gun instance for chaining.
157
+ */
158
+ Gun.chain.shine = function (chain, nodeId, data, callback) {
159
+ console.log("SHINE plugin called with:", { chain, nodeId, data });
160
+
161
+ if (typeof callback !== "function") {
162
+ console.error("Callback must be a function");
163
+ return this;
164
+ }
165
+
166
+ const gun = this;
167
+
168
+ // Seleziona l'indirizzo basato sulla catena
169
+ if (chain === "optimismSepolia") {
170
+ SHINE_CONTRACT_ADDRESS = SHINE_OPTIMISM_SEPOLIA;
171
+ } else {
172
+ throw new Error("Chain not supported");
173
+ }
174
+ // Funzione per verificare on-chain
175
+ const verifyOnChain = async (nodeId, contentHash) => {
176
+ console.log("Verifying on chain:", { nodeId, contentHash });
177
+ const signer = await getSigner();
178
+ const contract = new ethers.Contract(
179
+ SHINE_CONTRACT_ADDRESS,
180
+ SHINE_ABI,
181
+ signer
182
+ );
183
+ const [isValid, timestamp, updater] = await contract.verifyData(
184
+ ethers.toUtf8Bytes(nodeId),
185
+ contentHash
186
+ );
187
+ console.log("Verification result:", { isValid, timestamp, updater });
188
+ return { isValid, timestamp, updater };
189
+ };
190
+
191
+ // Funzione per scrivere on-chain
192
+ const writeOnChain = async (nodeId, contentHash) => {
193
+ console.log("Writing on chain:", { nodeId, contentHash });
194
+ const signer = await getSigner();
195
+ const contract = new ethers.Contract(
196
+ SHINE_CONTRACT_ADDRESS,
197
+ SHINE_ABI,
198
+ signer
199
+ );
200
+ const tx = await contract.updateData(
201
+ ethers.toUtf8Bytes(nodeId),
202
+ contentHash
203
+ );
204
+ console.log("Transaction sent:", tx.hash);
205
+ const receipt = await tx.wait();
206
+ console.log("Transaction confirmed:", receipt);
207
+ return tx;
208
+ };
209
+
210
+ // Nuova funzione per ottenere l'ultimo record dalla blockchain
211
+ const getLatestRecord = async (nodeId) => {
212
+ const signer = await getSigner();
213
+ const contract = new ethers.Contract(
214
+ SHINE_CONTRACT_ADDRESS,
215
+ SHINE_ABI,
216
+ signer
217
+ );
218
+ const [contentHash, timestamp, updater] = await contract.getLatestRecord(
219
+ ethers.toUtf8Bytes(nodeId)
220
+ );
221
+ console.log("Latest record from blockchain:", {
222
+ nodeId,
223
+ contentHash,
224
+ timestamp,
225
+ updater,
226
+ });
227
+ return { contentHash, timestamp, updater };
228
+ };
229
+
230
+ // Processo SHINE
231
+ if (nodeId && !data) {
232
+ // Caso 1: Utente passa solo il nodo
233
+ gun.get(nodeId).once(async (existingData) => {
234
+ if (!existingData) {
235
+ if (callback) callback({ err: "Node not found in GunDB" });
236
+ return;
237
+ }
238
+
239
+ console.log("existingData", existingData);
240
+
241
+ // Usa il contentHash memorizzato invece di ricalcolarlo
242
+ const contentHash = existingData._contentHash;
243
+ console.log("contentHash", contentHash);
244
+
245
+ if (!contentHash) {
246
+ if (callback) callback({ err: "No content hash found for this node" });
247
+ return;
248
+ }
249
+
250
+ try {
251
+ const { isValid, timestamp, updater } = await verifyOnChain(
252
+ nodeId,
253
+ contentHash
254
+ );
255
+ const latestRecord = await getLatestRecord(nodeId);
256
+
257
+ if (isValid) {
258
+ if (callback)
259
+ callback({
260
+ ok: true,
261
+ message: "Data verified on blockchain",
262
+ timestamp,
263
+ updater,
264
+ latestRecord,
265
+ });
266
+ } else {
267
+ if (callback)
268
+ callback({
269
+ ok: false,
270
+ message: "Data not verified on blockchain",
271
+ latestRecord,
272
+ });
273
+ }
274
+ } catch (error) {
275
+ if (callback) callback({ err: error.message });
276
+ }
277
+ });
278
+ } else if (data && !nodeId) {
279
+ // Caso 2: Utente passa solo il testo (data)
280
+ const newNodeId = Gun.text.random();
281
+ const dataString = JSON.stringify(data);
282
+ const contentHash = ethers.keccak256(ethers.toUtf8Bytes(dataString));
283
+
284
+ gun
285
+ .get(newNodeId)
286
+ .put({ ...data, _contentHash: contentHash }, async (ack) => {
287
+ console.log("ack", ack);
288
+ if (ack.err) {
289
+ if (callback) callback({ err: "Error saving data to GunDB" });
290
+ return;
291
+ }
292
+
293
+ try {
294
+ const tx = await writeOnChain(newNodeId, contentHash);
295
+ if (callback)
296
+ callback({
297
+ ok: true,
298
+ message: "Data written to GunDB and blockchain",
299
+ nodeId: newNodeId,
300
+ txHash: tx.hash,
301
+ });
302
+ } catch (error) {
303
+ if (callback) callback({ err: error.message });
304
+ }
305
+ });
306
+ } else {
307
+ if (callback)
308
+ callback({
309
+ err: "Invalid input. Provide either nodeId or data, not both.",
310
+ });
311
+ }
312
+
313
+ return gun;
314
+ };
315
+
316
+ /**
317
+ * Converts a Gun private key to an Ethereum account.
318
+ * @param {string} gunPrivateKey - The Gun private key in base64url format.
319
+ * @returns {Object} An object containing the Ethereum account and public key.
320
+ */
321
+ Gun.chain.gunToEthAccount = function (gunPrivateKey) {
322
+ // Function to convert base64url to hex
323
+ const base64UrlToHex = (base64url) => {
324
+ const padding = "=".repeat((4 - (base64url.length % 4)) % 4);
325
+ const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/") + padding;
326
+ const binary = atob(base64);
327
+ return Array.from(binary, (char) =>
328
+ char.charCodeAt(0).toString(16).padStart(2, "0")
329
+ ).join("");
330
+ };
331
+
332
+ // Convert Gun private key to hex format
333
+ const hexPrivateKey = "0x" + base64UrlToHex(gunPrivateKey);
334
+
335
+ // Create an Ethereum wallet from the private key
336
+ const wallet = new ethers.Wallet(hexPrivateKey);
337
+
338
+ // Get the public address (public key)
339
+ const publicKey = wallet.address;
340
+
341
+ return {
342
+ account: wallet,
343
+ publicKey: publicKey,
344
+ };
345
+ };
346
+
347
+ module.exports = Gun;