gun-eth 1.4.21 → 1.4.23

Sign up to get free protection for your applications and to get access to all the features.
package/src/index.js DELETED
@@ -1,953 +0,0 @@
1
- // =============================================
2
- // IMPORTS AND GLOBAL VARIABLES
3
- // =============================================
4
- import Gun from "gun";
5
- import SEA from "gun/sea.js";
6
- import { ethers } from "ethers";
7
- import { PROOF_OF_INTEGRITY_ABI, STEALTH_ANNOUNCER_ABI, getAddressesForChain } from "./abis/abis.js";
8
- import { LOCAL_CONFIG } from "./config/local.js";
9
-
10
- // Ottieni gli indirizzi corretti per la chain
11
- const chainConfig = getAddressesForChain('optimismSepolia'); // o la chain desiderata
12
- const STEALTH_ANNOUNCER_ADDRESS = chainConfig.STEALTH_ANNOUNCER_ADDRESS;
13
- const PROOF_OF_INTEGRITY_ADDRESS = chainConfig.PROOF_OF_INTEGRITY_ADDRESS;
14
-
15
- let PROOF_CONTRACT_ADDRESS;
16
- let rpcUrl = "";
17
- let privateKey = "";
18
-
19
- export const MESSAGE_TO_SIGN = "Access GunDB with Ethereum";
20
-
21
- let contractAddresses = {
22
- PROOF_OF_INTEGRITY_ADDRESS: null,
23
- STEALTH_ANNOUNCER_ADDRESS: STEALTH_ANNOUNCER_ADDRESS
24
- };
25
-
26
- // Solo per Node.js
27
- if (typeof window === 'undefined') {
28
- const { fileURLToPath } = require('url');
29
- const { dirname } = require('path');
30
- const { readFileSync } = require('fs');
31
- const path = require('path');
32
-
33
- const __filename = fileURLToPath(import.meta.url);
34
- const __dirname = dirname(__filename);
35
-
36
- try {
37
- const rawdata = readFileSync(path.join(__dirname, 'contract-address.json'), 'utf8');
38
- contractAddresses = JSON.parse(rawdata);
39
- console.log('Loaded contract addresses:', contractAddresses);
40
- } catch (err) {
41
- console.warn('Warning: contract-address.json not found or invalid');
42
- }
43
- }
44
-
45
- // =============================================
46
- // UTILITY FUNCTIONS
47
- // =============================================
48
- /**
49
- * Generates a random node ID for GunDB
50
- * @returns {string} A random hexadecimal string
51
- */
52
- export function generateRandomId() {
53
- return ethers.hexlify(ethers.randomBytes(32)).slice(2);
54
- }
55
-
56
- /**
57
- * Generates a password from a signature.
58
- * @param {string} signature - The signature to derive the password from.
59
- * @returns {string|null} The generated password or null if generation fails.
60
- */
61
- export function generatePassword(signature) {
62
- try {
63
- const hexSignature = ethers.hexlify(signature);
64
- const hash = ethers.keccak256(hexSignature);
65
- console.log("Generated password:", hash);
66
- return hash;
67
- } catch (error) {
68
- console.error("Error generating password:", error);
69
- return null;
70
- }
71
- }
72
-
73
- /**
74
- * Converts a Gun private key to an Ethereum account.
75
- * @param {string} gunPrivateKey - The Gun private key in base64url format.
76
- * @returns {Object} An object containing the Ethereum account and public key.
77
- */
78
- export function gunToEthAccount(gunPrivateKey) {
79
- // Function to convert base64url to hex
80
- const base64UrlToHex = (base64url) => {
81
- const padding = "=".repeat((4 - (base64url.length % 4)) % 4);
82
- const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/") + padding;
83
- const binary = atob(base64);
84
- return Array.from(binary, (char) =>
85
- char.charCodeAt(0).toString(16).padStart(2, "0")
86
- ).join("");
87
- };
88
-
89
- // Convert Gun private key to hex format
90
- const hexPrivateKey = "0x" + base64UrlToHex(gunPrivateKey);
91
-
92
- // Create an Ethereum wallet from the private key
93
- const wallet = new ethers.Wallet(hexPrivateKey);
94
-
95
- // Get the public address (public key)
96
- const publicKey = wallet.address;
97
-
98
- return {
99
- account: wallet,
100
- publicKey: publicKey,
101
- privateKey: hexPrivateKey,
102
- };
103
- }
104
-
105
- /**
106
- * Gets an Ethereum signer based on current configuration
107
- * @returns {Promise<ethers.Signer>} The configured signer
108
- * @throws {Error} If no valid provider is found
109
- */
110
- export const getSigner = async () => {
111
- if (rpcUrl && privateKey) {
112
- // Standalone mode with local provider
113
- const provider = new ethers.JsonRpcProvider(rpcUrl, {
114
- chainId: LOCAL_CONFIG.CHAIN_ID,
115
- name: "localhost"
116
- });
117
- return new ethers.Wallet(privateKey, provider);
118
- } else if (
119
- typeof window !== "undefined" &&
120
- typeof window.ethereum !== "undefined"
121
- ) {
122
- // Browser mode
123
- await window.ethereum.request({ method: "eth_requestAccounts" });
124
- const provider = new ethers.BrowserProvider(window.ethereum);
125
- return provider.getSigner();
126
- } else {
127
- throw new Error("No valid Ethereum provider found");
128
- }
129
- };
130
-
131
- /**
132
- * Utility function to generate stealth address
133
- * @param {string} sharedSecret - The shared secret
134
- * @param {string} spendingPublicKey - The spending public key
135
- * @returns {Object} The stealth address and private key
136
- */
137
- export function deriveStealthAddress(sharedSecret, spendingPublicKey) {
138
- try {
139
- // Convert shared secret to bytes
140
- const sharedSecretBytes = Buffer.from(sharedSecret, 'base64');
141
-
142
- // Generate stealth private key using shared secret and spending public key
143
- const stealthPrivateKey = ethers.keccak256(
144
- ethers.concat([
145
- sharedSecretBytes,
146
- ethers.getBytes(spendingPublicKey)
147
- ])
148
- );
149
-
150
- // Create stealth wallet
151
- const stealthWallet = new ethers.Wallet(stealthPrivateKey);
152
-
153
- console.log("Debug deriveStealthAddress:", {
154
- sharedSecretHex: ethers.hexlify(sharedSecretBytes),
155
- spendingPublicKey,
156
- stealthPrivateKey,
157
- stealthAddress: stealthWallet.address
158
- });
159
-
160
- return {
161
- stealthPrivateKey,
162
- stealthAddress: stealthWallet.address,
163
- wallet: stealthWallet
164
- };
165
- } catch (error) {
166
- console.error("Error in deriveStealthAddress:", error);
167
- throw error;
168
- }
169
- }
170
-
171
- // =============================================
172
- // BASIC GUN-ETH CHAIN METHODS
173
- // =============================================
174
-
175
- // Set the message to sign
176
- Gun.chain.MESSAGE_TO_SIGN = MESSAGE_TO_SIGN;
177
-
178
- /**
179
- * Sets standalone configuration for Gun.
180
- * @param {string} newRpcUrl - The new RPC URL.
181
- * @param {string} newPrivateKey - The new private key.
182
- * @returns {Gun} The Gun instance for chaining.
183
- */
184
- Gun.chain.setSigner = function (newRpcUrl, newPrivateKey) {
185
- rpcUrl = newRpcUrl;
186
- privateKey = newPrivateKey;
187
- console.log("Standalone configuration set");
188
- return this;
189
- };
190
-
191
- Gun.chain.getSigner = getSigner();
192
-
193
- /**
194
- * Verifies an Ethereum signature.
195
- * @param {string} message - The original message that was signed.
196
- * @param {string} signature - The signature to verify.
197
- * @returns {Promise<string|null>} The recovered address or null if verification fails.
198
- */
199
- Gun.chain.verifySignature = async function (message, signature) {
200
- try {
201
- const recoveredAddress = ethers.verifyMessage(message, signature);
202
- return recoveredAddress;
203
- } catch (error) {
204
- console.error("Error verifying signature:", error);
205
- return null;
206
- }
207
- };
208
-
209
- /**
210
- * Generates a password from a signature.
211
- * @param {string} signature - The signature to derive the password from.
212
- * @returns {string|null} The generated password or null if generation fails.
213
- */
214
- Gun.chain.generatePassword = function (signature) {
215
- return generatePassword(signature);
216
- };
217
-
218
- /**
219
- * Creates an Ethereum signature for a given message.
220
- * @param {string} message - The message to sign.
221
- * @returns {Promise<string|null>} The signature or null if signing fails.
222
- */
223
- Gun.chain.createSignature = async function (message) {
224
- try {
225
- // Check if message matches MESSAGE_TO_SIGN
226
- if (message !== MESSAGE_TO_SIGN) {
227
- throw new Error("Invalid message, valid message is: " + MESSAGE_TO_SIGN);
228
- }
229
- const signer = await getSigner();
230
- const signature = await signer.signMessage(message);
231
- console.log("Signature created:", signature);
232
- return signature;
233
- } catch (error) {
234
- console.error("Error creating signature:", error);
235
- return null;
236
- }
237
- };
238
-
239
- // =============================================
240
- // KEY PAIR MANAGEMENT
241
- // =============================================
242
- /**
243
- * Creates and stores an encrypted key pair for a given address.
244
- * @param {string} address - The Ethereum address to associate with the key pair.
245
- * @param {string} signature - The signature to use for encryption.
246
- * @returns {Promise<void>}
247
- */
248
- Gun.chain.createAndStoreEncryptedPair = async function (address, signature) {
249
- try {
250
- const gun = this;
251
- const pair = await SEA.pair();
252
- const v_pair = await SEA.pair();
253
- const s_pair = await SEA.pair();
254
- const password = generatePassword(signature);
255
-
256
- // Save original SEA pairs
257
- const encryptedPair = await SEA.encrypt(JSON.stringify(pair), password);
258
- const encryptedV_pair = await SEA.encrypt(JSON.stringify(v_pair), password);
259
- const encryptedS_pair = await SEA.encrypt(JSON.stringify(s_pair), password);
260
-
261
- // Convert only to get Ethereum addresses
262
- const viewingAccount = gunToEthAccount(v_pair.priv);
263
- const spendingAccount = gunToEthAccount(s_pair.priv);
264
-
265
- gun.get("gun-eth").get("users").get(address).put({
266
- pair: encryptedPair,
267
- v_pair: encryptedV_pair,
268
- s_pair: encryptedS_pair,
269
- publicKeys: {
270
- viewingPublicKey: v_pair.epub, // Use SEA encryption public key
271
- viewingPublicKey: v_pair.epub, // Use SEA encryption public key
272
- spendingPublicKey: spendingAccount.publicKey, // Use Ethereum address
273
- ethViewingAddress: viewingAccount.publicKey // Also save Ethereum address
274
- }
275
- });
276
-
277
- console.log("Encrypted pairs and public keys stored for:", address);
278
- } catch (error) {
279
- console.error("Error creating and storing encrypted pair:", error);
280
- throw error;
281
- }
282
- };
283
-
284
- /**
285
- * Retrieves and decrypts a stored key pair for a given address.
286
- * @param {string} address - The Ethereum address associated with the key pair.
287
- * @param {string} signature - The signature to use for decryption.
288
- * @returns {Promise<Object|null>} The decrypted key pair or null if retrieval fails.
289
- */
290
- Gun.chain.getAndDecryptPair = async function (address, signature) {
291
- try {
292
- const gun = this;
293
- const encryptedData = await gun
294
- .get("gun-eth")
295
- .get("users")
296
- .get(address)
297
- .get("pair")
298
- .then();
299
- if (!encryptedData) {
300
- throw new Error("No encrypted data found for this address");
301
- }
302
- const password = generatePassword(signature);
303
- const decryptedPair = await SEA.decrypt(encryptedData, password);
304
- console.log(decryptedPair);
305
- return decryptedPair;
306
- } catch (error) {
307
- console.error("Error retrieving and decrypting pair:", error);
308
- return null;
309
- }
310
- };
311
-
312
- // =============================================
313
- // PROOF OF INTEGRITY
314
- // =============================================
315
- /**
316
- * Proof of Integrity
317
- * @param {string} chain - The blockchain to use (e.g., "optimismSepolia").
318
- * @param {string} nodeId - The ID of the node to verify or write.
319
- * @param {Object} data - The data to write (if writing).
320
- * @param {Function} callback - Callback function to handle the result.
321
- * @returns {Gun} The Gun instance for chaining.
322
- */
323
- Gun.chain.proof = function (chain, nodeId, data, callback) {
324
- console.log("Proof plugin called with:", { chain, nodeId, data });
325
-
326
- if (typeof callback !== "function") {
327
- console.error("Callback must be a function");
328
- return this;
329
- }
330
-
331
- try {
332
- // Se siamo in localhost e in development, usa automaticamente la chain locale
333
- const targetChain = isLocalEnvironment() ? 'localhost' : chain;
334
- const chainConfig = getAddressesForChain(targetChain);
335
-
336
- console.log(`Using ${targetChain} configuration:`, chainConfig);
337
-
338
- // Usa gli indirizzi dalla configurazione
339
- const contract = new ethers.Contract(
340
- chainConfig.PROOF_OF_INTEGRITY_ADDRESS,
341
- PROOF_OF_INTEGRITY_ABI,
342
- signer
343
- );
344
-
345
- // Funzione per verificare on-chain
346
- const verifyOnChain = async (nodeId, contentHash) => {
347
- console.log("Verifying on chain:", { nodeId, contentHash });
348
- const signer = await getSigner();
349
- const contract = new ethers.Contract(
350
- PROOF_CONTRACT_ADDRESS,
351
- PROOF_OF_INTEGRITY_ABI,
352
- signer
353
- );
354
- const [isValid, timestamp, updater] = await contract.verifyData(
355
- ethers.toUtf8Bytes(nodeId),
356
- contentHash
357
- );
358
- console.log("Verification result:", { isValid, timestamp, updater });
359
- return { isValid, timestamp, updater };
360
- };
361
-
362
- // Funzione per scrivere on-chain
363
- const writeOnChain = async (nodeId, contentHash) => {
364
- console.log("Writing on chain:", { nodeId, contentHash });
365
- const signer = await getSigner();
366
- const contract = new ethers.Contract(
367
- PROOF_CONTRACT_ADDRESS,
368
- PROOF_OF_INTEGRITY_ABI,
369
- signer
370
- );
371
- const tx = await contract.updateData(
372
- ethers.toUtf8Bytes(nodeId),
373
- contentHash
374
- );
375
- console.log("Transaction sent:", tx.hash);
376
- const receipt = await tx.wait();
377
- console.log("Transaction confirmed:", receipt);
378
- return tx;
379
- };
380
-
381
- // Funzione per ottenere l'ultimo record
382
- const getLatestRecord = async (nodeId) => {
383
- const signer = await getSigner();
384
- const contract = new ethers.Contract(
385
- PROOF_CONTRACT_ADDRESS,
386
- PROOF_OF_INTEGRITY_ABI,
387
- signer
388
- );
389
- const [contentHash, timestamp, updater] = await contract.getLatestRecord(
390
- ethers.toUtf8Bytes(nodeId)
391
- );
392
- console.log("Latest record from blockchain:", {
393
- nodeId,
394
- contentHash,
395
- timestamp,
396
- updater,
397
- });
398
- return { contentHash, timestamp, updater };
399
- };
400
-
401
-
402
- if (nodeId && !data) {
403
- // Case 1: User passes only node
404
- gun.get(nodeId).once(async (existingData) => {
405
- if (!existingData) {
406
- if (callback) callback({ err: "Node not found in GunDB" });
407
- return;
408
- }
409
-
410
- console.log("existingData", existingData);
411
-
412
- // Use stored contentHash instead of recalculating
413
- const contentHash = existingData._contentHash;
414
- console.log("contentHash", contentHash);
415
-
416
- if (!contentHash) {
417
- if (callback) callback({ err: "No content hash found for this node" });
418
- return;
419
- }
420
-
421
- try {
422
- const { isValid, timestamp, updater } = await verifyOnChain(
423
- nodeId,
424
- contentHash
425
- );
426
- const latestRecord = await getLatestRecord(nodeId);
427
-
428
- if (isValid) {
429
- if (callback)
430
- callback({
431
- ok: true,
432
- message: "Data verified on blockchain",
433
- timestamp,
434
- updater,
435
- latestRecord,
436
- });
437
- } else {
438
- if (callback)
439
- callback({
440
- ok: false,
441
- message: "Data not verified on blockchain",
442
- latestRecord,
443
- });
444
- }
445
- } catch (error) {
446
- if (callback) callback({ err: error.message });
447
- }
448
- });
449
- } else if (data && !nodeId) {
450
- // Case 2: User passes only text (data)
451
- const newNodeId = generateRandomId();
452
- const dataString = JSON.stringify(data);
453
- const contentHash = ethers.keccak256(ethers.toUtf8Bytes(dataString));
454
-
455
- gun
456
- .get(newNodeId)
457
- .put({ ...data, _contentHash: contentHash }, async (ack) => {
458
- console.log("ack", ack);
459
- if (ack.err) {
460
- if (callback) callback({ err: "Error saving data to GunDB" });
461
- return;
462
- }
463
-
464
- try {
465
- const tx = await writeOnChain(newNodeId, contentHash);
466
- if (callback)
467
- callback({
468
- ok: true,
469
- message: "Data written to GunDB and blockchain",
470
- nodeId: newNodeId,
471
- txHash: tx.hash,
472
- });
473
- } catch (error) {
474
- if (callback) callback({ err: error.message });
475
- }
476
- });
477
- } else {
478
- if (callback)
479
- callback({
480
- err: "Invalid input. Provide either nodeId or data, not both.",
481
- });
482
- }
483
-
484
- return gun;
485
- } catch (error) {
486
- callback({ err: error.message });
487
- return this;
488
- }
489
- };
490
-
491
- // =============================================
492
- // STEALTH ADDRESS CORE FUNCTIONS
493
- // =============================================
494
- /**
495
- * Converts a Gun private key to an Ethereum account.
496
- * @param {string} gunPrivateKey - The Gun private key in base64url format.
497
- * @returns {Object} An object containing the Ethereum account and public key.
498
- */
499
- Gun.chain.gunToEthAccount = function (gunPrivateKey) {
500
- return gunToEthAccount(gunPrivateKey);
501
- };
502
-
503
- /**
504
- * Generate a stealth key and related key pairs
505
- * @param {string} recipientAddress - The recipient's Ethereum address
506
- * @param {string} signature - The sender's signature to access their keys
507
- * @returns {Promise<Object>} Object containing stealth addresses and keys
508
- */
509
- Gun.chain.generateStealthAddress = async function (recipientAddress, signature) {
510
- try {
511
- const gun = this;
512
-
513
- // Get recipient's public keys
514
- const recipientData = await gun
515
- .get("gun-eth")
516
- .get("users")
517
- .get(recipientAddress)
518
- .get("publicKeys")
519
- .then();
520
-
521
- if (!recipientData || !recipientData.viewingPublicKey || !recipientData.spendingPublicKey) {
522
- throw new Error("Recipient's public keys not found");
523
- }
524
-
525
- // Get sender's keys
526
- const senderAddress = await this.verifySignature(MESSAGE_TO_SIGN, signature);
527
- const password = generatePassword(signature);
528
-
529
- const senderData = await gun
530
- .get("gun-eth")
531
- .get("users")
532
- .get(senderAddress)
533
- .then();
534
-
535
- if (!senderData || !senderData.s_pair) {
536
- throw new Error("Sender's keys not found");
537
- }
538
-
539
- // Decrypt sender's spending pair
540
- let spendingKeyPair;
541
- try {
542
- const decryptedData = await SEA.decrypt(senderData.s_pair, password);
543
- spendingKeyPair = typeof decryptedData === 'string' ?
544
- JSON.parse(decryptedData) :
545
- decryptedData;
546
- } catch (error) {
547
- console.error("Error decrypting spending pair:", error);
548
- throw new Error("Unable to decrypt spending pair");
549
- }
550
-
551
- // Generate shared secret using SEA ECDH with encryption public key
552
- const sharedSecret = await SEA.secret(recipientData.viewingPublicKey, spendingKeyPair);
553
-
554
- if (!sharedSecret) {
555
- throw new Error("Unable to generate shared secret");
556
- }
557
-
558
- console.log("Generate shared secret:", sharedSecret);
559
-
560
- const { stealthAddress } = deriveStealthAddress(
561
- sharedSecret,
562
- recipientData.spendingPublicKey
563
- );
564
-
565
- return {
566
- stealthAddress,
567
- senderPublicKey: spendingKeyPair.epub, // Use encryption public key
568
- spendingPublicKey: recipientData.spendingPublicKey
569
- };
570
-
571
- } catch (error) {
572
- console.error("Error generating stealth address:", error);
573
- throw error;
574
- }
575
- };
576
-
577
- /**
578
- * Publish public keys needed to receive stealth payments
579
- * @param {string} signature - The signature to authenticate the user
580
- * @returns {Promise<void>}
581
- */
582
- Gun.chain.publishStealthKeys = async function (signature) {
583
- try {
584
- const gun = this;
585
- const address = await this.verifySignature(MESSAGE_TO_SIGN, signature);
586
- const password = generatePassword(signature);
587
-
588
- // Get encrypted key pairs
589
- const encryptedData = await gun
590
- .get("gun-eth")
591
- .get("users")
592
- .get(address)
593
- .then();
594
-
595
- if (!encryptedData || !encryptedData.v_pair || !encryptedData.s_pair) {
596
- throw new Error("Keys not found");
597
- }
598
-
599
- // Decrypt viewing and spending pairs
600
- const viewingKeyPair = JSON.parse(
601
- await SEA.decrypt(encryptedData.v_pair, password)
602
- );
603
- const spendingKeyPair = JSON.parse(
604
- await SEA.decrypt(encryptedData.s_pair, password)
605
- );
606
-
607
- const viewingAccount = gunToEthAccount(viewingKeyPair.priv);
608
- const spendingAccount = gunToEthAccount(spendingKeyPair.priv);
609
-
610
- // Publish only public keys
611
- gun.get("gun-eth").get("users").get(address).get("publicKeys").put({
612
- viewingPublicKey: viewingAccount.publicKey,
613
- spendingPublicKey: spendingAccount.publicKey,
614
- });
615
-
616
- console.log("Stealth public keys published successfully");
617
- } catch (error) {
618
- console.error("Error publishing stealth keys:", error);
619
- throw error;
620
- }
621
- };
622
-
623
- // =============================================
624
- // STEALTH PAYMENT FUNCTIONS
625
- // =============================================
626
- /**
627
- * Recover funds from a stealth address
628
- * @param {string} stealthAddress - The stealth address to recover funds from
629
- * @param {string} senderPublicKey - The sender's public key used to generate the address
630
- * @param {string} signature - The signature to decrypt private keys
631
- * @returns {Promise<Object>} Object containing wallet to access funds
632
- */
633
- Gun.chain.recoverStealthFunds = async function (
634
- stealthAddress,
635
- senderPublicKey,
636
- signature,
637
- spendingPublicKey
638
- ) {
639
- try {
640
- const gun = this;
641
- const password = generatePassword(signature);
642
-
643
- // Get own key pairs
644
- const myAddress = await this.verifySignature(MESSAGE_TO_SIGN, signature);
645
- const encryptedData = await gun
646
- .get("gun-eth")
647
- .get("users")
648
- .get(myAddress)
649
- .then();
650
-
651
- if (!encryptedData || !encryptedData.v_pair || !encryptedData.s_pair) {
652
- throw new Error("Keys not found");
653
- }
654
-
655
- // Decrypt viewing and spending pairs
656
- let viewingKeyPair;
657
- try {
658
- const decryptedViewingData = await SEA.decrypt(encryptedData.v_pair, password);
659
- viewingKeyPair = typeof decryptedViewingData === 'string' ?
660
- JSON.parse(decryptedViewingData) :
661
- decryptedViewingData;
662
- } catch (error) {
663
- console.error("Error decrypting keys:", error);
664
- throw new Error("Unable to decrypt keys");
665
- }
666
-
667
- // Generate shared secret using SEA ECDH
668
- const sharedSecret = await SEA.secret(senderPublicKey, viewingKeyPair);
669
-
670
- if (!sharedSecret) {
671
- throw new Error("Unable to generate shared secret");
672
- }
673
-
674
- console.log("Recover shared secret:", sharedSecret);
675
-
676
- const { wallet, stealthAddress: recoveredAddress } = deriveStealthAddress(
677
- sharedSecret,
678
- spendingPublicKey
679
- );
680
-
681
- // Verify address matches
682
- if (recoveredAddress.toLowerCase() !== stealthAddress.toLowerCase()) {
683
- console.error("Mismatch:", {
684
- recovered: recoveredAddress,
685
- expected: stealthAddress,
686
- sharedSecret
687
- });
688
- throw new Error("Recovered stealth address does not match");
689
- }
690
-
691
- return {
692
- wallet,
693
- address: recoveredAddress,
694
- };
695
- } catch (error) {
696
- console.error("Error recovering stealth funds:", error);
697
- throw error;
698
- }
699
- };
700
-
701
- /**
702
- * Announce a stealth payment
703
- * @param {string} stealthAddress - The generated stealth address
704
- * @param {string} senderPublicKey - The sender's public key
705
- * @param {string} spendingPublicKey - The spending public key
706
- * @param {string} signature - The sender's signature
707
- * @returns {Promise<void>}
708
- */
709
- Gun.chain.announceStealthPayment = async function (
710
- stealthAddress,
711
- senderPublicKey,
712
- spendingPublicKey,
713
- signature,
714
- options = { onChain: false, chain: 'optimismSepolia' }
715
- ) {
716
- try {
717
- const gun = this;
718
- const senderAddress = await this.verifySignature(MESSAGE_TO_SIGN, signature);
719
-
720
- if (options.onChain) {
721
- // On-chain announcement
722
- const signer = await getSigner();
723
- const chainAddresses = getAddressesForChain(options.chain);
724
- const contractAddress = chainAddresses.STEALTH_ANNOUNCER_ADDRESS;
725
-
726
- console.log("Using contract address:", contractAddress);
727
-
728
- const contract = new ethers.Contract(
729
- contractAddress,
730
- STEALTH_ANNOUNCER_ABI,
731
- signer
732
- );
733
-
734
- // Get dev fee from contract
735
- const devFee = await contract.devFee();
736
- console.log("Dev fee:", devFee.toString());
737
-
738
- // Call contract
739
- const tx = await contract.announcePayment(
740
- senderPublicKey,
741
- spendingPublicKey,
742
- stealthAddress,
743
- { value: devFee }
744
- );
745
-
746
- console.log("Transaction sent:", tx.hash);
747
- const receipt = await tx.wait();
748
- console.log("Transaction confirmed:", receipt.hash);
749
-
750
- console.log("Stealth payment announced on-chain (dev fee paid)");
751
- } else {
752
- // Off-chain announcement (GunDB)
753
- gun
754
- .get("gun-eth")
755
- .get("stealth-payments")
756
- .set({
757
- stealthAddress,
758
- senderAddress,
759
- senderPublicKey,
760
- spendingPublicKey,
761
- timestamp: Date.now(),
762
- });
763
- console.log("Stealth payment announced off-chain");
764
- }
765
- } catch (error) {
766
- console.error("Error announcing stealth payment:", error);
767
- console.error("Error details:", error.stack);
768
- throw error;
769
- }
770
- };
771
-
772
- /**
773
- * Get all stealth payments for an address
774
- * @param {string} signature - The signature to authenticate the user
775
- * @returns {Promise<Array>} List of stealth payments
776
- */
777
- Gun.chain.getStealthPayments = async function (signature, options = { source: 'both' }) {
778
- try {
779
- const payments = [];
780
-
781
- if (options.source === 'onChain' || options.source === 'both') {
782
- // Get on-chain payments
783
- const signer = await getSigner();
784
- const contractAddress = process.env.NODE_ENV === 'development'
785
- ? LOCAL_CONFIG.STEALTH_ANNOUNCER_ADDRESS
786
- : STEALTH_ANNOUNCER_ADDRESS;
787
-
788
- const contract = new ethers.Contract(
789
- contractAddress,
790
- STEALTH_ANNOUNCER_ABI,
791
- signer
792
- );
793
-
794
- try {
795
- // Get total number of announcements
796
- const totalAnnouncements = await contract.getAnnouncementsCount();
797
- const totalCount = Number(totalAnnouncements.toString());
798
- console.log("Total on-chain announcements:", totalCount);
799
-
800
- if (totalCount > 0) {
801
- // Get announcements in batches of 100
802
- const batchSize = 100;
803
- const lastIndex = totalCount - 1;
804
-
805
- for(let i = 0; i <= lastIndex; i += batchSize) {
806
- const toIndex = Math.min(i + batchSize - 1, lastIndex);
807
- const batch = await contract.getAnnouncementsInRange(i, toIndex);
808
-
809
- // For each announcement, try to decrypt
810
- for(const announcement of batch) {
811
- try {
812
- // Verify announcement is valid
813
- if (!announcement || !announcement.stealthAddress ||
814
- !announcement.senderPublicKey || !announcement.spendingPublicKey) {
815
- console.log("Invalid announcement:", announcement);
816
- continue;
817
- }
818
-
819
- // Try to recover funds to verify if announcement is for us
820
- const recoveredWallet = await this.recoverStealthFunds(
821
- announcement.stealthAddress,
822
- announcement.senderPublicKey,
823
- signature,
824
- announcement.spendingPublicKey
825
- );
826
-
827
- // If no errors thrown, announcement is for us
828
- payments.push({
829
- stealthAddress: announcement.stealthAddress,
830
- senderPublicKey: announcement.senderPublicKey,
831
- spendingPublicKey: announcement.spendingPublicKey,
832
- timestamp: Number(announcement.timestamp),
833
- source: 'onChain',
834
- wallet: recoveredWallet
835
- });
836
-
837
- } catch (e) {
838
- // Not for us, continue
839
- console.log(`Announcement not for us: ${announcement.stealthAddress}`);
840
- continue;
841
- }
842
- }
843
- }
844
- }
845
- } catch (error) {
846
- console.error("Error retrieving on-chain announcements:", error);
847
- }
848
- }
849
-
850
- if (options.source === 'offChain' || options.source === 'both') {
851
- // Get off-chain payments
852
- const gun = this;
853
- const offChainPayments = await new Promise((resolve) => {
854
- const p = [];
855
- gun
856
- .get("gun-eth")
857
- .get("stealth-payments")
858
- .get(recipientAddress)
859
- .map()
860
- .once((payment, id) => {
861
- if (payment?.stealthAddress) {
862
- p.push({ ...payment, id, source: 'offChain' });
863
- }
864
- });
865
- setTimeout(() => resolve(p), 2000);
866
- });
867
-
868
- payments.push(...offChainPayments);
869
- }
870
-
871
- console.log(`Found ${payments.length} stealth payments`);
872
- return payments;
873
- } catch (error) {
874
- console.error("Error retrieving stealth payments:", error);
875
- throw error;
876
- }
877
- };
878
-
879
- /**
880
- * Clean up old stealth payments
881
- * @param {string} recipientAddress - The recipient's address
882
- * @returns {Promise<void>}
883
- */
884
- Gun.chain.cleanStealthPayments = async function(recipientAddress) {
885
- try {
886
- const gun = this;
887
- const payments = await gun
888
- .get("gun-eth")
889
- .get("stealth-payments")
890
- .get(recipientAddress)
891
- .map()
892
- .once()
893
- .then();
894
-
895
- // Remove empty or invalid nodes
896
- if (payments) {
897
- Object.keys(payments).forEach(async (key) => {
898
- const payment = payments[key];
899
- if (!payment || !payment.stealthAddress || !payment.senderPublicKey || !payment.spendingPublicKey) {
900
- await gun
901
- .get("gun-eth")
902
- .get("stealth-payments")
903
- .get(recipientAddress)
904
- .get(key)
905
- .put(null);
906
- }
907
- });
908
- }
909
- } catch (error) {
910
- console.error("Error cleaning stealth payments:", error);
911
- }
912
- };
913
-
914
- // =============================================
915
- // EXPORTS
916
- // =============================================
917
-
918
- // Crea una classe GunEth che contiene tutti i metodi e le utility
919
- export class GunEth {
920
- // Static utility methods
921
- static generateRandomId = generateRandomId;
922
- static generatePassword = generatePassword;
923
- static gunToEthAccount = gunToEthAccount;
924
- static getSigner = getSigner;
925
- static deriveStealthAddress = deriveStealthAddress;
926
-
927
- // Chain methods
928
- static chainMethods = {
929
- setSigner: Gun.chain.setSigner,
930
- getSigner: Gun.chain.getSigner,
931
- verifySignature: Gun.chain.verifySignature,
932
- generatePassword: Gun.chain.generatePassword,
933
- createSignature: Gun.chain.createSignature,
934
- createAndStoreEncryptedPair: Gun.chain.createAndStoreEncryptedPair,
935
- getAndDecryptPair: Gun.chain.getAndDecryptPair,
936
- proof: Gun.chain.proof,
937
- gunToEthAccount: Gun.chain.gunToEthAccount,
938
- generateStealthAddress: Gun.chain.generateStealthAddress,
939
- publishStealthKeys: Gun.chain.publishStealthKeys,
940
- recoverStealthFunds: Gun.chain.recoverStealthFunds,
941
- announceStealthPayment: Gun.chain.announceStealthPayment,
942
- getStealthPayments: Gun.chain.getStealthPayments,
943
- cleanStealthPayments: Gun.chain.cleanStealthPayments
944
- };
945
-
946
- // Constants
947
- static MESSAGE_TO_SIGN = MESSAGE_TO_SIGN;
948
- static PROOF_CONTRACT_ADDRESS = PROOF_CONTRACT_ADDRESS;
949
- static LOCAL_CONFIG = LOCAL_CONFIG;
950
- }
951
-
952
- // Esporta Gun come default
953
- export default Gun;