gun-eth 1.2.2 → 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. package/README.md +101 -21
  2. package/index.js +269 -1
  3. package/package.json +1 -1
  4. package/gun-eth.js +0 -270
package/README.md CHANGED
@@ -4,43 +4,123 @@
4
4
 
5
5
  gun-eth is a plugin for GunDB that integrates Ethereum and Web3 functionality. This plugin extends GunDB's capabilities by allowing interaction with the Ethereum blockchain and providing cryptographic and signature management features.
6
6
 
7
- ## Key Features
7
+ SHINE Smart Contract deployed on Optimism Sepolia: [0x43D838b683F772F08f321E5FA265ad3e333BE9C2](https://sepolia-optimism.etherscan.io/address/0x43D838b683F772F08f321E5FA265ad3e333BE9C2)
8
8
 
9
- - Ethereum signature verification
10
- - Signature-based password generation
11
- - Signature creation using Ethereum providers (e.g., MetaMask)
9
+ Currently, the contract is deployed only on Optimism Sepolia. In the future, it will be deployed on multiple chains.
12
10
 
13
- ## Example
11
+ ### Key Features
14
12
 
15
- [gun-eth dapp](https://gun-eth.vercel.app/)
13
+ - **Ethereum Signature Verification**: Verify Ethereum signatures for messages.
14
+ - **Password Generation**: Generate secure passwords from Ethereum signatures.
15
+ - **Signature Creation**: Create Ethereum signatures for messages.
16
+ - **Encrypted Key Pair Management**: Create, store, and retrieve encrypted key pairs.
17
+ - **SHINE Implementation**: Implement the SHINE for data verification on the blockchain.
16
18
 
17
- ## Installation
19
+ ### How to install
18
20
 
19
21
  ```bash
20
22
  npm install gun-eth
21
23
  ```
22
24
 
23
- ## Usage
24
-
25
- ```javascript
26
- const Gun = require("gun");
27
- require("gun-eth");
25
+ ```bash
26
+ import gun from "gun";
27
+ import "gun-eth";
28
28
 
29
- const gun = Gun();
29
+ const gun = gun();
30
30
 
31
- // Now you can use the new features
31
+ await gun.generatePassword("YOUR_SIGNATURE");
32
32
  ```
33
33
 
34
- ## Main Functions
34
+ ### How to use
35
+
36
+ Learn more about Gun.js [here](https://gun.eco/docs/Getting-Started).
37
+
38
+ Learn more about plugin implementation [here](https://github.com/amark/gun/wiki/Adding-Methods-to-the-Gun-Chain#abstraction-layers).
39
+
40
+ ### Core Functions
41
+
42
+ - `verifySignature(message, signature)`: Verifies an Ethereum signature for a given message.
43
+
44
+ ```js
45
+ const recoveredAddress = await gun.verifySignature(message, signature);
46
+ ```
47
+
48
+ - `generatePassword(signature)`: Generates a password from an Ethereum signature.
49
+
50
+ ```js
51
+ const password = gun.generatePassword(signature);
52
+ ```
53
+
54
+ - `createSignature(message)`: Creates an Ethereum signature for a message.
55
+
56
+ ```js
57
+ const signature = await gun.createSignature(message);
58
+ ```
59
+
60
+ - `createAndStoreEncryptedPair(address, signature)`: Creates and stores an encrypted key pair.
35
61
 
36
- - `gun.verifySignature(message, signature)`: Verifies an Ethereum signature
37
- - `gun.generatePassword(signature)`: Generates a password based on a signature
38
- - `gun.createSignature(message)`: Creates a signature using an Ethereum provider
62
+ ```js
63
+ await gun.createAndStoreEncryptedPair(address, signature);
64
+ ```
65
+
66
+ - `getAndDecryptPair(address, signature)`: Retrieves and decrypts a stored key pair.
67
+
68
+ ```js
69
+ const decryptedPair = await gun.getAndDecryptPair(address, signature);
70
+ ```
71
+
72
+ - `shine(chain, nodeId, data, callback)`: Implements SHINE for data verification and storage on the blockchain.
73
+ ```js
74
+ gun.shine("optimismSepolia", nodeId, data, callback);
75
+ ```
76
+
77
+ ### SHINE
78
+
79
+ SHINE (Secure Hash Integrity Network Ethereum) provides a mechanism for verifying data integrity using Ethereum and Gun.js.
80
+
81
+ 1. **Data Storage**: When saving data, a content hash is generated and stored in both Gun.js and on the Ethereum blockchain.
82
+ 2. **Data Verification**: To verify data, the stored hash is compared with a hash generated from the data retrieved from Gun.js.
83
+ 3. **Blockchain Interaction**: The plugin interacts with an Ethereum smart contract to store and verify data hashes.
84
+
85
+ ### Usage Examples
86
+
87
+ #### Verifying Data by NodeId
88
+
89
+ ```js
90
+ const nodeId = "your-node-id-here";
91
+ gun.shine("optimismSepolia", nodeId, null, (ack) => {
92
+ if (ack.ok) {
93
+ console.log("Data verified on blockchain", ack);
94
+ console.log("Timestamp:", ack.timestamp);
95
+ console.log("Updater:", ack.updater);
96
+ console.log("Latest Record:", ack.latestRecord);
97
+ } else {
98
+ console.log("Data not verified or not found", ack);
99
+ }
100
+ });
101
+ ```
102
+
103
+ #### Storing New Data
104
+
105
+ ```js
106
+ const data = { message: "Hello, blockchain!" };
107
+ gun.shine("optimismSepolia", null, data, (ack) => {
108
+ if (ack.ok) {
109
+ console.log("Data stored on Gun.js and blockchain", ack);
110
+ console.log("New Node ID:", ack.nodeId);
111
+ console.log("Transaction Hash:", ack.txHash);
112
+ } else {
113
+ console.log("Error storing data", ack);
114
+ }
115
+ });
116
+ ```
39
117
 
40
- ## Dependencies
118
+ ### Security Considerations
41
119
 
42
- - gun: ^0.2020.1239
43
- - ethers: ^6.0.0
120
+ - Use a secure Ethereum provider (e.g., MetaMask) when interacting with functions that require signatures.
121
+ - Generated passwords and key pairs are sensitive. Handle them carefully and avoid exposing them.
122
+ - Keep Gun.js and Ethereum dependencies up to date for security.
123
+ - Be aware of gas costs associated with blockchain interactions when using SHINE.
44
124
 
45
125
  ## Contributing
46
126
 
package/index.js CHANGED
@@ -1 +1,269 @@
1
- import "./gun-eth.js";
1
+ const Gun = require("gun/gun");
2
+ const SEA = require("gun/sea");
3
+ const ethers = require("ethers");
4
+ const SHINE = require("./SHINE.json");
5
+
6
+ /* import Gun from "gun";
7
+ import SEA from "gun/sea";
8
+ import { ethers } from "ethers";
9
+ import SHINE from "./SHINE.json"; */
10
+
11
+ const SHINE_ABI = SHINE.abi;
12
+ const SHINE_OPTIMISM_SEPOLIA = SHINE.address;
13
+
14
+ let SHINE_CONTRACT_ADDRESS;
15
+
16
+ // Aggiungi il metodo alla catena di Gun
17
+ Gun.chain.verifySignature = async function (message, signature) {
18
+ try {
19
+ const recoveredAddress = ethers.verifyMessage(message, signature);
20
+ return recoveredAddress;
21
+ } catch (error) {
22
+ console.error("Errore durante la verifica della firma:", error);
23
+ return null;
24
+ }
25
+ };
26
+
27
+ Gun.chain.generatePassword = function (signature) {
28
+ try {
29
+ // Usa SHA-256 per derivare una password dalla firma
30
+ const hexSignature = ethers.hexlify(signature);
31
+ const hash = ethers.keccak256(hexSignature);
32
+
33
+ console.log("Password generata:", hash);
34
+ return hash;
35
+ } catch (error) {
36
+ console.error("Errore nella generazione della password:", error);
37
+ return null;
38
+ }
39
+ };
40
+
41
+ Gun.chain.createSignature = async function (message) {
42
+ try {
43
+ // Controlla se window.ethereum è disponibile (metamask o altro provider)
44
+ if (typeof window.ethereum !== "undefined") {
45
+ await window.ethereum.request({ method: "eth_requestAccounts" });
46
+ const provider = new ethers.BrowserProvider(window.ethereum);
47
+ const signer = await provider.getSigner();
48
+ const signature = await signer.signMessage(message);
49
+ console.log("Firma creata:", signature);
50
+ return signature;
51
+ } else {
52
+ throw new Error("Provider Ethereum non trovato");
53
+ }
54
+ } catch (error) {
55
+ console.error("Errore durante la creazione della firma:", error);
56
+ return null;
57
+ }
58
+ };
59
+
60
+ Gun.chain.createAndStoreEncryptedPair = async function (address, signature) {
61
+ try {
62
+ const gun = this;
63
+ const pair = await SEA.pair();
64
+ const encryptedPair = await SEA.encrypt(JSON.stringify(pair), signature);
65
+
66
+ await gun.get("users").get(address).put({ encryptedPair });
67
+ console.log("Pair crittografato e archiviato per:", address);
68
+ } catch (error) {
69
+ console.error(
70
+ "Errore durante la creazione e l'archiviazione del pair crittografato:",
71
+ error
72
+ );
73
+ }
74
+ };
75
+
76
+ Gun.chain.getAndDecryptPair = async function (address, signature) {
77
+ try {
78
+ const gun = this;
79
+ const encryptedData = await gun
80
+ .get("users")
81
+ .get(address)
82
+ .get("encryptedPair")
83
+ .then();
84
+ if (!encryptedData) {
85
+ throw new Error("Nessun dato crittografato trovato per questo indirizzo");
86
+ }
87
+
88
+ const decryptedPair = await SEA.decrypt(encryptedData, signature);
89
+
90
+ console.log(decryptedPair);
91
+ return decryptedPair;
92
+ } catch (error) {
93
+ console.error(
94
+ "Errore durante il recupero e la decrittazione del pair:",
95
+ error
96
+ );
97
+ return null;
98
+ }
99
+ };
100
+
101
+ Gun.chain.shine = function (chain, nodeId, data, callback) {
102
+ console.log("SHINE plugin called with:", { chain, nodeId, data });
103
+
104
+ if (typeof callback !== "function") {
105
+ console.error("Callback must be a function");
106
+ return this;
107
+ }
108
+
109
+ const gun = this;
110
+
111
+ // Seleziona l'indirizzo basato sulla catena
112
+ if (chain === "optimismSepolia") {
113
+ SHINE_CONTRACT_ADDRESS = SHINE_OPTIMISM_SEPOLIA;
114
+ } else {
115
+ throw new Error("Chain not supported");
116
+ }
117
+
118
+ // Funzione per ottenere il signer
119
+ const getSigner = async () => {
120
+ if (typeof window.ethereum !== "undefined") {
121
+ await window.ethereum.request({ method: "eth_requestAccounts" });
122
+ const provider = new ethers.BrowserProvider(window.ethereum);
123
+ return provider.getSigner();
124
+ } else {
125
+ throw new Error("Ethereum provider not found");
126
+ }
127
+ };
128
+
129
+ // Funzione per verificare on-chain
130
+ const verifyOnChain = async (nodeId, contentHash) => {
131
+ console.log("Verifying on chain:", { nodeId, contentHash });
132
+ const signer = await getSigner();
133
+ const contract = new ethers.Contract(
134
+ SHINE_CONTRACT_ADDRESS,
135
+ SHINE_ABI,
136
+ signer
137
+ );
138
+ const [isValid, timestamp, updater] = await contract.verifyData(
139
+ ethers.toUtf8Bytes(nodeId),
140
+ contentHash
141
+ );
142
+ console.log("Verification result:", { isValid, timestamp, updater });
143
+ return { isValid, timestamp, updater };
144
+ };
145
+
146
+ // Funzione per scrivere on-chain
147
+ const writeOnChain = async (nodeId, contentHash) => {
148
+ console.log("Writing on chain:", { nodeId, contentHash });
149
+ const signer = await getSigner();
150
+ const contract = new ethers.Contract(
151
+ SHINE_CONTRACT_ADDRESS,
152
+ SHINE_ABI,
153
+ signer
154
+ );
155
+ const tx = await contract.updateData(
156
+ ethers.toUtf8Bytes(nodeId),
157
+ contentHash
158
+ );
159
+ console.log("Transaction sent:", tx.hash);
160
+ const receipt = await tx.wait();
161
+ console.log("Transaction confirmed:", receipt);
162
+ return tx;
163
+ };
164
+
165
+ // Nuova funzione per ottenere l'ultimo record dalla blockchain
166
+ const getLatestRecord = async (nodeId) => {
167
+ const signer = await getSigner();
168
+ const contract = new ethers.Contract(
169
+ SHINE_CONTRACT_ADDRESS,
170
+ SHINE_ABI,
171
+ signer
172
+ );
173
+ const [contentHash, timestamp, updater] = await contract.getLatestRecord(
174
+ ethers.toUtf8Bytes(nodeId)
175
+ );
176
+ console.log("Latest record from blockchain:", {
177
+ nodeId,
178
+ contentHash,
179
+ timestamp,
180
+ updater,
181
+ });
182
+ return { contentHash, timestamp, updater };
183
+ };
184
+
185
+ // Processo SHINE
186
+ if (nodeId && !data) {
187
+ // Caso 1: Utente passa solo il nodo
188
+ gun.get(nodeId).once(async (existingData) => {
189
+ if (!existingData) {
190
+ if (callback) callback({ err: "Node not found in GunDB" });
191
+ return;
192
+ }
193
+
194
+ console.log("existingData", existingData);
195
+
196
+ // Usa il contentHash memorizzato invece di ricalcolarlo
197
+ const contentHash = existingData._contentHash;
198
+ console.log("contentHash", contentHash);
199
+
200
+ if (!contentHash) {
201
+ if (callback) callback({ err: "No content hash found for this node" });
202
+ return;
203
+ }
204
+
205
+ try {
206
+ const { isValid, timestamp, updater } = await verifyOnChain(
207
+ nodeId,
208
+ contentHash
209
+ );
210
+ const latestRecord = await getLatestRecord(nodeId);
211
+
212
+ if (isValid) {
213
+ if (callback)
214
+ callback({
215
+ ok: true,
216
+ message: "Data verified on blockchain",
217
+ timestamp,
218
+ updater,
219
+ latestRecord,
220
+ });
221
+ } else {
222
+ if (callback)
223
+ callback({
224
+ ok: false,
225
+ message: "Data not verified on blockchain",
226
+ latestRecord,
227
+ });
228
+ }
229
+ } catch (error) {
230
+ if (callback) callback({ err: error.message });
231
+ }
232
+ });
233
+ } else if (data && !nodeId) {
234
+ // Caso 2: Utente passa solo il testo (data)
235
+ const newNodeId = Gun.text.random();
236
+ const dataString = JSON.stringify(data);
237
+ const contentHash = ethers.keccak256(ethers.toUtf8Bytes(dataString));
238
+
239
+ gun
240
+ .get(newNodeId)
241
+ .put({ ...data, _contentHash: contentHash }, async (ack) => {
242
+ console.log("ack", ack);
243
+ if (ack.err) {
244
+ if (callback) callback({ err: "Error saving data to GunDB" });
245
+ return;
246
+ }
247
+
248
+ try {
249
+ const tx = await writeOnChain(newNodeId, contentHash);
250
+ if (callback)
251
+ callback({
252
+ ok: true,
253
+ message: "Data written to GunDB and blockchain",
254
+ nodeId: newNodeId,
255
+ txHash: tx.hash,
256
+ });
257
+ } catch (error) {
258
+ if (callback) callback({ err: error.message });
259
+ }
260
+ });
261
+ } else {
262
+ if (callback)
263
+ callback({
264
+ err: "Invalid input. Provide either nodeId or data, not both.",
265
+ });
266
+ }
267
+
268
+ return gun;
269
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gun-eth",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "description": "A GunDB plugin for Ethereum, and Web3",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/gun-eth.js DELETED
@@ -1,270 +0,0 @@
1
- /* const Gun = require("gun/gun");
2
- const SEA = require("gun/sea");
3
- const ethers = require("ethers");
4
- const SHINE = require("./SHINE.json"); */
5
-
6
- import Gun from "gun";
7
- import SEA from "gun/sea";
8
- import { ethers } from "ethers";
9
-
10
- import SHINE from "./SHINE.json";
11
-
12
- const SHINE_ABI = SHINE.abi;
13
- const SHINE_OPTIMISM_SEPOLIA = SHINE.address;
14
-
15
- let SHINE_CONTRACT_ADDRESS;
16
-
17
- // Aggiungi il metodo alla catena di Gun
18
- Gun.chain.verifySignature = async function (message, signature) {
19
- try {
20
- const recoveredAddress = ethers.verifyMessage(message, signature);
21
- return recoveredAddress;
22
- } catch (error) {
23
- console.error("Errore durante la verifica della firma:", error);
24
- return null;
25
- }
26
- };
27
-
28
- Gun.chain.generatePassword = function (signature) {
29
- try {
30
- // Usa SHA-256 per derivare una password dalla firma
31
- const hexSignature = ethers.hexlify(signature);
32
- const hash = ethers.keccak256(hexSignature);
33
-
34
- console.log("Password generata:", hash);
35
- return hash;
36
- } catch (error) {
37
- console.error("Errore nella generazione della password:", error);
38
- return null;
39
- }
40
- };
41
-
42
- Gun.chain.createSignature = async function (message) {
43
- try {
44
- // Controlla se window.ethereum è disponibile (metamask o altro provider)
45
- if (typeof window.ethereum !== "undefined") {
46
- await window.ethereum.request({ method: "eth_requestAccounts" });
47
- const provider = new ethers.BrowserProvider(window.ethereum);
48
- const signer = await provider.getSigner();
49
- const signature = await signer.signMessage(message);
50
- console.log("Firma creata:", signature);
51
- return signature;
52
- } else {
53
- throw new Error("Provider Ethereum non trovato");
54
- }
55
- } catch (error) {
56
- console.error("Errore durante la creazione della firma:", error);
57
- return null;
58
- }
59
- };
60
-
61
- Gun.chain.createAndStoreEncryptedPair = async function (address, signature) {
62
- try {
63
- const gun = this;
64
- const pair = await SEA.pair();
65
- const encryptedPair = await SEA.encrypt(JSON.stringify(pair), signature);
66
-
67
- await gun.get("users").get(address).put({ encryptedPair });
68
- console.log("Pair crittografato e archiviato per:", address);
69
- } catch (error) {
70
- console.error(
71
- "Errore durante la creazione e l'archiviazione del pair crittografato:",
72
- error
73
- );
74
- }
75
- };
76
-
77
- Gun.chain.getAndDecryptPair = async function (address, signature) {
78
- try {
79
- const gun = this;
80
- const encryptedData = await gun
81
- .get("users")
82
- .get(address)
83
- .get("encryptedPair")
84
- .then();
85
- if (!encryptedData) {
86
- throw new Error("Nessun dato crittografato trovato per questo indirizzo");
87
- }
88
-
89
- const decryptedPair = await SEA.decrypt(encryptedData, signature);
90
-
91
- console.log(decryptedPair);
92
- return decryptedPair;
93
- } catch (error) {
94
- console.error(
95
- "Errore durante il recupero e la decrittazione del pair:",
96
- error
97
- );
98
- return null;
99
- }
100
- };
101
-
102
- Gun.chain.shine = function (chain, nodeId, data, callback) {
103
- console.log("SHINE plugin called with:", { chain, nodeId, data });
104
-
105
- if (typeof callback !== "function") {
106
- console.error("Callback must be a function");
107
- return this;
108
- }
109
-
110
- const gun = this;
111
-
112
- // Seleziona l'indirizzo basato sulla catena
113
- if (chain === "optimismSepolia") {
114
- SHINE_CONTRACT_ADDRESS = SHINE_OPTIMISM_SEPOLIA;
115
- } else {
116
- throw new Error("Chain not supported");
117
- }
118
-
119
- // Funzione per ottenere il signer
120
- const getSigner = async () => {
121
- if (typeof window.ethereum !== "undefined") {
122
- await window.ethereum.request({ method: "eth_requestAccounts" });
123
- const provider = new ethers.BrowserProvider(window.ethereum);
124
- return provider.getSigner();
125
- } else {
126
- throw new Error("Ethereum provider not found");
127
- }
128
- };
129
-
130
- // Funzione per verificare on-chain
131
- const verifyOnChain = async (nodeId, contentHash) => {
132
- console.log("Verifying on chain:", { nodeId, contentHash });
133
- const signer = await getSigner();
134
- const contract = new ethers.Contract(
135
- SHINE_CONTRACT_ADDRESS,
136
- SHINE_ABI,
137
- signer
138
- );
139
- const [isValid, timestamp, updater] = await contract.verifyData(
140
- ethers.toUtf8Bytes(nodeId),
141
- contentHash
142
- );
143
- console.log("Verification result:", { isValid, timestamp, updater });
144
- return { isValid, timestamp, updater };
145
- };
146
-
147
- // Funzione per scrivere on-chain
148
- const writeOnChain = async (nodeId, contentHash) => {
149
- console.log("Writing on chain:", { nodeId, contentHash });
150
- const signer = await getSigner();
151
- const contract = new ethers.Contract(
152
- SHINE_CONTRACT_ADDRESS,
153
- SHINE_ABI,
154
- signer
155
- );
156
- const tx = await contract.updateData(
157
- ethers.toUtf8Bytes(nodeId),
158
- contentHash
159
- );
160
- console.log("Transaction sent:", tx.hash);
161
- const receipt = await tx.wait();
162
- console.log("Transaction confirmed:", receipt);
163
- return tx;
164
- };
165
-
166
- // Nuova funzione per ottenere l'ultimo record dalla blockchain
167
- const getLatestRecord = async (nodeId) => {
168
- const signer = await getSigner();
169
- const contract = new ethers.Contract(
170
- SHINE_CONTRACT_ADDRESS,
171
- SHINE_ABI,
172
- signer
173
- );
174
- const [contentHash, timestamp, updater] = await contract.getLatestRecord(
175
- ethers.toUtf8Bytes(nodeId)
176
- );
177
- console.log("Latest record from blockchain:", {
178
- nodeId,
179
- contentHash,
180
- timestamp,
181
- updater,
182
- });
183
- return { contentHash, timestamp, updater };
184
- };
185
-
186
- // Processo SHINE
187
- if (nodeId && !data) {
188
- // Caso 1: Utente passa solo il nodo
189
- gun.get(nodeId).once(async (existingData) => {
190
- if (!existingData) {
191
- if (callback) callback({ err: "Node not found in GunDB" });
192
- return;
193
- }
194
-
195
- console.log("existingData", existingData);
196
-
197
- // Usa il contentHash memorizzato invece di ricalcolarlo
198
- const contentHash = existingData._contentHash;
199
- console.log("contentHash", contentHash);
200
-
201
- if (!contentHash) {
202
- if (callback) callback({ err: "No content hash found for this node" });
203
- return;
204
- }
205
-
206
- try {
207
- const { isValid, timestamp, updater } = await verifyOnChain(
208
- nodeId,
209
- contentHash
210
- );
211
- const latestRecord = await getLatestRecord(nodeId);
212
-
213
- if (isValid) {
214
- if (callback)
215
- callback({
216
- ok: true,
217
- message: "Data verified on blockchain",
218
- timestamp,
219
- updater,
220
- latestRecord,
221
- });
222
- } else {
223
- if (callback)
224
- callback({
225
- ok: false,
226
- message: "Data not verified on blockchain",
227
- latestRecord,
228
- });
229
- }
230
- } catch (error) {
231
- if (callback) callback({ err: error.message });
232
- }
233
- });
234
- } else if (data && !nodeId) {
235
- // Caso 2: Utente passa solo il testo (data)
236
- const newNodeId = Gun.text.random();
237
- const dataString = JSON.stringify(data);
238
- const contentHash = ethers.keccak256(ethers.toUtf8Bytes(dataString));
239
-
240
- gun
241
- .get(newNodeId)
242
- .put({ ...data, _contentHash: contentHash }, async (ack) => {
243
- console.log("ack", ack);
244
- if (ack.err) {
245
- if (callback) callback({ err: "Error saving data to GunDB" });
246
- return;
247
- }
248
-
249
- try {
250
- const tx = await writeOnChain(newNodeId, contentHash);
251
- if (callback)
252
- callback({
253
- ok: true,
254
- message: "Data written to GunDB and blockchain",
255
- nodeId: newNodeId,
256
- txHash: tx.hash,
257
- });
258
- } catch (error) {
259
- if (callback) callback({ err: error.message });
260
- }
261
- });
262
- } else {
263
- if (callback)
264
- callback({
265
- err: "Invalid input. Provide either nodeId or data, not both.",
266
- });
267
- }
268
-
269
- return gun;
270
- };