gun-eth 1.4.0 → 1.4.2

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.
@@ -0,0 +1,2580 @@
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('gun'), require('ethers'), require('fs'), require('url'), require('path')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', 'gun', 'ethers', 'fs', 'url', 'path'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.GunEth = {}, global.Gun, global.ethers, global.fs, global.url, global.path$1));
5
+ })(this, (function (exports, Gun, ethers, fs, url, path$1) { 'use strict';
6
+
7
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
8
+ var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
9
+
10
+ function getDefaultExportFromCjs (x) {
11
+ return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
12
+ }
13
+
14
+ function commonjsRequire(path) {
15
+ throw new Error('Could not dynamically require "' + path + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');
16
+ }
17
+
18
+ var sea = {exports: {}};
19
+
20
+ sea.exports;
21
+
22
+ (function (module) {
23
+ (function(){
24
+
25
+ /* UNBUILD */
26
+ function USE(arg, req){
27
+ return req? commonjsRequire(arg) : arg.slice? USE[R(arg)] : function(mod, path){
28
+ arg(mod = {exports: {}});
29
+ USE[R(path)] = mod.exports;
30
+ }
31
+ function R(p){
32
+ return p.split('/').slice(-1).toString().replace('.js','');
33
+ }
34
+ }
35
+ { var MODULE = module; }
36
+ USE(function(module){
37
+ // Security, Encryption, and Authorization: SEA.js
38
+ // MANDATORY READING: https://gun.eco/explainers/data/security.html
39
+ // IT IS IMPLEMENTED IN A POLYFILL/SHIM APPROACH.
40
+ // THIS IS AN EARLY ALPHA!
41
+
42
+ if(typeof self !== "undefined"){ module.window = self; } // should be safe for at least browser/worker/nodejs, need to check other envs like RN etc.
43
+ if(typeof window !== "undefined"){ module.window = window; }
44
+
45
+ var tmp = module.window || module, u;
46
+ var SEA = tmp.SEA || {};
47
+
48
+ if(SEA.window = module.window){ SEA.window.SEA = SEA; }
49
+
50
+ try{ if(u+'' !== typeof MODULE){ MODULE.exports = SEA; } }catch(e){}
51
+ module.exports = SEA;
52
+ })(USE, './root');
53
+ USE(function(module){
54
+ var SEA = USE('./root');
55
+ try{ if(SEA.window){
56
+ if(location.protocol.indexOf('s') < 0
57
+ && location.host.indexOf('localhost') < 0
58
+ && ! /^127\.\d+\.\d+\.\d+$/.test(location.hostname)
59
+ && location.protocol.indexOf('file:') < 0){
60
+ console.warn('HTTPS needed for WebCrypto in SEA, redirecting...');
61
+ location.protocol = 'https:'; // WebCrypto does NOT work without HTTPS!
62
+ }
63
+ } }catch(e){}
64
+ })(USE, './https');
65
+ USE(function(module){
66
+ var u;
67
+ if(u+''== typeof btoa){
68
+ if(u+'' == typeof Buffer){
69
+ try{ commonjsGlobal.Buffer = USE("buffer", 1).Buffer; }catch(e){ console.log("Please `npm install buffer` or add it to your package.json !"); }
70
+ }
71
+ commonjsGlobal.btoa = function(data){ return Buffer.from(data, "binary").toString("base64") };
72
+ commonjsGlobal.atob = function(data){ return Buffer.from(data, "base64").toString("binary") };
73
+ }
74
+ })(USE, './base64');
75
+ USE(function(module){
76
+ USE('./base64');
77
+ // This is Array extended to have .toString(['utf8'|'hex'|'base64'])
78
+ function SeaArray() {}
79
+ Object.assign(SeaArray, { from: Array.from });
80
+ SeaArray.prototype = Object.create(Array.prototype);
81
+ SeaArray.prototype.toString = function(enc, start, end) { enc = enc || 'utf8'; start = start || 0;
82
+ const length = this.length;
83
+ if (enc === 'hex') {
84
+ const buf = new Uint8Array(this);
85
+ return [ ...Array(((end && (end + 1)) || length) - start).keys()]
86
+ .map((i) => buf[ i + start ].toString(16).padStart(2, '0')).join('')
87
+ }
88
+ if (enc === 'utf8') {
89
+ return Array.from(
90
+ { length: (end || length) - start },
91
+ (_, i) => String.fromCharCode(this[ i + start])
92
+ ).join('')
93
+ }
94
+ if (enc === 'base64') {
95
+ return btoa(this)
96
+ }
97
+ };
98
+ module.exports = SeaArray;
99
+ })(USE, './array');
100
+ USE(function(module){
101
+ USE('./base64');
102
+ // This is Buffer implementation used in SEA. Functionality is mostly
103
+ // compatible with NodeJS 'safe-buffer' and is used for encoding conversions
104
+ // between binary and 'hex' | 'utf8' | 'base64'
105
+ // See documentation and validation for safe implementation in:
106
+ // https://github.com/feross/safe-buffer#update
107
+ var SeaArray = USE('./array');
108
+ function SafeBuffer(...props) {
109
+ console.warn('new SafeBuffer() is depreciated, please use SafeBuffer.from()');
110
+ return SafeBuffer.from(...props)
111
+ }
112
+ SafeBuffer.prototype = Object.create(Array.prototype);
113
+ Object.assign(SafeBuffer, {
114
+ // (data, enc) where typeof data === 'string' then enc === 'utf8'|'hex'|'base64'
115
+ from() {
116
+ if (!Object.keys(arguments).length || arguments[0]==null) {
117
+ throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.')
118
+ }
119
+ const input = arguments[0];
120
+ let buf;
121
+ if (typeof input === 'string') {
122
+ const enc = arguments[1] || 'utf8';
123
+ if (enc === 'hex') {
124
+ const bytes = input.match(/([\da-fA-F]{2})/g)
125
+ .map((byte) => parseInt(byte, 16));
126
+ if (!bytes || !bytes.length) {
127
+ throw new TypeError('Invalid first argument for type \'hex\'.')
128
+ }
129
+ buf = SeaArray.from(bytes);
130
+ } else if (enc === 'utf8' || 'binary' === enc) { // EDIT BY MARK: I think this is safe, tested it against a couple "binary" strings. This lets SafeBuffer match NodeJS Buffer behavior more where it safely btoas regular strings.
131
+ const length = input.length;
132
+ const words = new Uint16Array(length);
133
+ Array.from({ length: length }, (_, i) => words[i] = input.charCodeAt(i));
134
+ buf = SeaArray.from(words);
135
+ } else if (enc === 'base64') {
136
+ const dec = atob(input);
137
+ const length = dec.length;
138
+ const bytes = new Uint8Array(length);
139
+ Array.from({ length: length }, (_, i) => bytes[i] = dec.charCodeAt(i));
140
+ buf = SeaArray.from(bytes);
141
+ } else if (enc === 'binary') { // deprecated by above comment
142
+ buf = SeaArray.from(input); // some btoas were mishandled.
143
+ } else {
144
+ console.info('SafeBuffer.from unknown encoding: '+enc);
145
+ }
146
+ return buf
147
+ }
148
+ input.byteLength; // what is going on here? FOR MARTTI
149
+ const length = input.byteLength ? input.byteLength : input.length;
150
+ if (length) {
151
+ let buf;
152
+ if (input instanceof ArrayBuffer) {
153
+ buf = new Uint8Array(input);
154
+ }
155
+ return SeaArray.from(buf || input)
156
+ }
157
+ },
158
+ // This is 'safe-buffer.alloc' sans encoding support
159
+ alloc(length, fill = 0 /*, enc*/ ) {
160
+ return SeaArray.from(new Uint8Array(Array.from({ length: length }, () => fill)))
161
+ },
162
+ // This is normal UNSAFE 'buffer.alloc' or 'new Buffer(length)' - don't use!
163
+ allocUnsafe(length) {
164
+ return SeaArray.from(new Uint8Array(Array.from({ length : length })))
165
+ },
166
+ // This puts together array of array like members
167
+ concat(arr) { // octet array
168
+ if (!Array.isArray(arr)) {
169
+ throw new TypeError('First argument must be Array containing ArrayBuffer or Uint8Array instances.')
170
+ }
171
+ return SeaArray.from(arr.reduce((ret, item) => ret.concat(Array.from(item)), []))
172
+ }
173
+ });
174
+ SafeBuffer.prototype.from = SafeBuffer.from;
175
+ SafeBuffer.prototype.toString = SeaArray.prototype.toString;
176
+
177
+ module.exports = SafeBuffer;
178
+ })(USE, './buffer');
179
+ USE(function(module){
180
+ const SEA = USE('./root');
181
+ const api = {Buffer: USE('./buffer')};
182
+ var o = {}, u;
183
+
184
+ // ideally we can move away from JSON entirely? unlikely due to compatibility issues... oh well.
185
+ JSON.parseAsync = JSON.parseAsync || function(t,cb,r){ var u; try{ cb(u, JSON.parse(t,r)); }catch(e){ cb(e); } };
186
+ JSON.stringifyAsync = JSON.stringifyAsync || function(v,cb,r,s){ var u; try{ cb(u, JSON.stringify(v,r,s)); }catch(e){ cb(e); } };
187
+
188
+ api.parse = function(t,r){ return new Promise(function(res, rej){
189
+ JSON.parseAsync(t,function(err, raw){ err? rej(err) : res(raw); },r);
190
+ })};
191
+ api.stringify = function(v,r,s){ return new Promise(function(res, rej){
192
+ JSON.stringifyAsync(v,function(err, raw){ err? rej(err) : res(raw); },r,s);
193
+ })};
194
+
195
+ if(SEA.window){
196
+ api.crypto = SEA.window.crypto || SEA.window.msCrypto;
197
+ api.subtle = (api.crypto||o).subtle || (api.crypto||o).webkitSubtle;
198
+ api.TextEncoder = SEA.window.TextEncoder;
199
+ api.TextDecoder = SEA.window.TextDecoder;
200
+ api.random = (len) => api.Buffer.from(api.crypto.getRandomValues(new Uint8Array(api.Buffer.alloc(len))));
201
+ }
202
+ if(!api.TextDecoder)
203
+ {
204
+ const { TextEncoder, TextDecoder } = USE((u+'' == typeof MODULE?'.':'')+'./lib/text-encoding', 1);
205
+ api.TextDecoder = TextDecoder;
206
+ api.TextEncoder = TextEncoder;
207
+ }
208
+ if(!api.crypto)
209
+ {
210
+ try
211
+ {
212
+ var crypto = USE('crypto', 1);
213
+ Object.assign(api, {
214
+ crypto,
215
+ random: (len) => api.Buffer.from(crypto.randomBytes(len))
216
+ });
217
+ const { Crypto: WebCrypto } = USE('@peculiar/webcrypto', 1);
218
+ api.ossl = api.subtle = new WebCrypto({directory: 'ossl'}).subtle; // ECDH
219
+ }
220
+ catch(e){
221
+ console.log("Please `npm install @peculiar/webcrypto` or add it to your package.json !");
222
+ }}
223
+
224
+ module.exports = api;
225
+ })(USE, './shim');
226
+ USE(function(module){
227
+ var SEA = USE('./root');
228
+ var shim = USE('./shim');
229
+ var s = {};
230
+ s.pbkdf2 = {hash: {name : 'SHA-256'}, iter: 100000, ks: 64};
231
+ s.ecdsa = {
232
+ pair: {name: 'ECDSA', namedCurve: 'P-256'},
233
+ sign: {name: 'ECDSA', hash: {name: 'SHA-256'}}
234
+ };
235
+ s.ecdh = {name: 'ECDH', namedCurve: 'P-256'};
236
+
237
+ // This creates Web Cryptography API compliant JWK for sign/verify purposes
238
+ s.jwk = function(pub, d){ // d === priv
239
+ pub = pub.split('.');
240
+ var x = pub[0], y = pub[1];
241
+ var jwk = {kty: "EC", crv: "P-256", x: x, y: y, ext: true};
242
+ jwk.key_ops = d ? ['sign'] : ['verify'];
243
+ if(d){ jwk.d = d; }
244
+ return jwk;
245
+ };
246
+
247
+ s.keyToJwk = function(keyBytes) {
248
+ const keyB64 = keyBytes.toString('base64');
249
+ const k = keyB64.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');
250
+ return { kty: 'oct', k: k, ext: false, alg: 'A256GCM' };
251
+ };
252
+
253
+ s.recall = {
254
+ validity: 12 * 60 * 60, // internally in seconds : 12 hours
255
+ hook: function(props){ return props } // { iat, exp, alias, remember } // or return new Promise((resolve, reject) => resolve(props)
256
+ };
257
+
258
+ s.check = function(t){ return (typeof t == 'string') && ('SEA{' === t.slice(0,4)) };
259
+ s.parse = async function p(t){ try {
260
+ var yes = (typeof t == 'string');
261
+ if(yes && 'SEA{' === t.slice(0,4)){ t = t.slice(3); }
262
+ return yes ? await shim.parse(t) : t;
263
+ } catch (e) {}
264
+ return t;
265
+ };
266
+
267
+ SEA.opt = s;
268
+ module.exports = s;
269
+ })(USE, './settings');
270
+ USE(function(module){
271
+ var shim = USE('./shim');
272
+ module.exports = async function(d, o){
273
+ var t = (typeof d == 'string')? d : await shim.stringify(d);
274
+ var hash = await shim.subtle.digest({name: o||'SHA-256'}, new shim.TextEncoder().encode(t));
275
+ return shim.Buffer.from(hash);
276
+ };
277
+ })(USE, './sha256');
278
+ USE(function(module){
279
+ // This internal func returns SHA-1 hashed data for KeyID generation
280
+ const __shim = USE('./shim');
281
+ const subtle = __shim.subtle;
282
+ const ossl = __shim.ossl ? __shim.ossl : subtle;
283
+ const sha1hash = (b) => ossl.digest({name: 'SHA-1'}, new ArrayBuffer(b));
284
+ module.exports = sha1hash;
285
+ })(USE, './sha1');
286
+ USE(function(module){
287
+ var SEA = USE('./root');
288
+ var shim = USE('./shim');
289
+ var S = USE('./settings');
290
+ var sha = USE('./sha256');
291
+ var u;
292
+
293
+ SEA.work = SEA.work || (async (data, pair, cb, opt) => { try { // used to be named `proof`
294
+ var salt = (pair||{}).epub || pair; // epub not recommended, salt should be random!
295
+ opt = opt || {};
296
+ if(salt instanceof Function){
297
+ cb = salt;
298
+ salt = u;
299
+ }
300
+ data = (typeof data == 'string')? data : await shim.stringify(data);
301
+ if('sha' === (opt.name||'').toLowerCase().slice(0,3)){
302
+ var rsha = shim.Buffer.from(await sha(data, opt.name), 'binary').toString(opt.encode || 'base64');
303
+ if(cb){ try{ cb(rsha); }catch(e){console.log(e);} }
304
+ return rsha;
305
+ }
306
+ salt = salt || shim.random(9);
307
+ var key = await (shim.ossl || shim.subtle).importKey('raw', new shim.TextEncoder().encode(data), {name: opt.name || 'PBKDF2'}, false, ['deriveBits']);
308
+ var work = await (shim.ossl || shim.subtle).deriveBits({
309
+ name: opt.name || 'PBKDF2',
310
+ iterations: opt.iterations || S.pbkdf2.iter,
311
+ salt: new shim.TextEncoder().encode(opt.salt || salt),
312
+ hash: opt.hash || S.pbkdf2.hash,
313
+ }, key, opt.length || (S.pbkdf2.ks * 8));
314
+ data = shim.random(data.length); // Erase data in case of passphrase
315
+ var r = shim.Buffer.from(work, 'binary').toString(opt.encode || 'base64');
316
+ if(cb){ try{ cb(r); }catch(e){console.log(e);} }
317
+ return r;
318
+ } catch(e) {
319
+ console.log(e);
320
+ SEA.err = e;
321
+ if(SEA.throw){ throw e }
322
+ if(cb){ cb(); }
323
+ return;
324
+ }});
325
+
326
+ module.exports = SEA.work;
327
+ })(USE, './work');
328
+ USE(function(module){
329
+ var SEA = USE('./root');
330
+ var shim = USE('./shim');
331
+ USE('./settings');
332
+
333
+ SEA.name = SEA.name || (async (cb, opt) => { try {
334
+ if(cb){ try{ cb(); }catch(e){console.log(e);} }
335
+ return;
336
+ } catch(e) {
337
+ console.log(e);
338
+ SEA.err = e;
339
+ if(SEA.throw){ throw e }
340
+ if(cb){ cb(); }
341
+ return;
342
+ }});
343
+
344
+ //SEA.pair = async (data, proof, cb) => { try {
345
+ SEA.pair = SEA.pair || (async (cb, opt) => { try {
346
+
347
+ var ecdhSubtle = shim.ossl || shim.subtle;
348
+ // First: ECDSA keys for signing/verifying...
349
+ var sa = await shim.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, [ 'sign', 'verify' ])
350
+ .then(async (keys) => {
351
+ // privateKey scope doesn't leak out from here!
352
+ //const { d: priv } = await shim.subtle.exportKey('jwk', keys.privateKey)
353
+ var key = {};
354
+ key.priv = (await shim.subtle.exportKey('jwk', keys.privateKey)).d;
355
+ var pub = await shim.subtle.exportKey('jwk', keys.publicKey);
356
+ //const pub = Buff.from([ x, y ].join(':')).toString('base64') // old
357
+ key.pub = pub.x+'.'+pub.y; // new
358
+ // x and y are already base64
359
+ // pub is UTF8 but filename/URL safe (https://www.ietf.org/rfc/rfc3986.txt)
360
+ // but split on a non-base64 letter.
361
+ return key;
362
+ });
363
+
364
+ // To include PGPv4 kind of keyId:
365
+ // const pubId = await SEA.keyid(keys.pub)
366
+ // Next: ECDH keys for encryption/decryption...
367
+
368
+ try{
369
+ var dh = await ecdhSubtle.generateKey({name: 'ECDH', namedCurve: 'P-256'}, true, ['deriveKey'])
370
+ .then(async (keys) => {
371
+ // privateKey scope doesn't leak out from here!
372
+ var key = {};
373
+ key.epriv = (await ecdhSubtle.exportKey('jwk', keys.privateKey)).d;
374
+ var pub = await ecdhSubtle.exportKey('jwk', keys.publicKey);
375
+ //const epub = Buff.from([ ex, ey ].join(':')).toString('base64') // old
376
+ key.epub = pub.x+'.'+pub.y; // new
377
+ // ex and ey are already base64
378
+ // epub is UTF8 but filename/URL safe (https://www.ietf.org/rfc/rfc3986.txt)
379
+ // but split on a non-base64 letter.
380
+ return key;
381
+ });
382
+ }catch(e){
383
+ if(SEA.window){ throw e }
384
+ if(e == 'Error: ECDH is not a supported algorithm'){ console.log('Ignoring ECDH...'); }
385
+ else { throw e }
386
+ } dh = dh || {};
387
+
388
+ var r = { pub: sa.pub, priv: sa.priv, /* pubId, */ epub: dh.epub, epriv: dh.epriv };
389
+ if(cb){ try{ cb(r); }catch(e){console.log(e);} }
390
+ return r;
391
+ } catch(e) {
392
+ console.log(e);
393
+ SEA.err = e;
394
+ if(SEA.throw){ throw e }
395
+ if(cb){ cb(); }
396
+ return;
397
+ }});
398
+
399
+ module.exports = SEA.pair;
400
+ })(USE, './pair');
401
+ USE(function(module){
402
+ var SEA = USE('./root');
403
+ var shim = USE('./shim');
404
+ var S = USE('./settings');
405
+ var sha = USE('./sha256');
406
+ var u;
407
+
408
+ SEA.sign = SEA.sign || (async (data, pair, cb, opt) => { try {
409
+ opt = opt || {};
410
+ if(!(pair||opt).priv){
411
+ if(!SEA.I){ throw 'No signing key.' }
412
+ pair = await SEA.I(null, {what: data, how: 'sign', why: opt.why});
413
+ }
414
+ if(u === data){ throw '`undefined` not allowed.' }
415
+ var json = await S.parse(data);
416
+ var check = opt.check = opt.check || json;
417
+ if(SEA.verify && (SEA.opt.check(check) || (check && check.s && check.m))
418
+ && u !== await SEA.verify(check, pair)){ // don't sign if we already signed it.
419
+ var r = await S.parse(check);
420
+ if(!opt.raw){ r = 'SEA' + await shim.stringify(r); }
421
+ if(cb){ try{ cb(r); }catch(e){console.log(e);} }
422
+ return r;
423
+ }
424
+ var pub = pair.pub;
425
+ var priv = pair.priv;
426
+ var jwk = S.jwk(pub, priv);
427
+ var hash = await sha(json);
428
+ var sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, {name: 'ECDSA', namedCurve: 'P-256'}, false, ['sign'])
429
+ .then((key) => (shim.ossl || shim.subtle).sign({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, new Uint8Array(hash))); // privateKey scope doesn't leak out from here!
430
+ var r = {m: json, s: shim.Buffer.from(sig, 'binary').toString(opt.encode || 'base64')};
431
+ if(!opt.raw){ r = 'SEA' + await shim.stringify(r); }
432
+
433
+ if(cb){ try{ cb(r); }catch(e){console.log(e);} }
434
+ return r;
435
+ } catch(e) {
436
+ console.log(e);
437
+ SEA.err = e;
438
+ if(SEA.throw){ throw e }
439
+ if(cb){ cb(); }
440
+ return;
441
+ }});
442
+
443
+ module.exports = SEA.sign;
444
+ })(USE, './sign');
445
+ USE(function(module){
446
+ var SEA = USE('./root');
447
+ var shim = USE('./shim');
448
+ var S = USE('./settings');
449
+ var sha = USE('./sha256');
450
+ var u;
451
+
452
+ SEA.verify = SEA.verify || (async (data, pair, cb, opt) => { try {
453
+ var json = await S.parse(data);
454
+ if(false === pair){ // don't verify!
455
+ var raw = await S.parse(json.m);
456
+ if(cb){ try{ cb(raw); }catch(e){console.log(e);} }
457
+ return raw;
458
+ }
459
+ opt = opt || {};
460
+ // SEA.I // verify is free! Requires no user permission.
461
+ var pub = pair.pub || pair;
462
+ var key = SEA.opt.slow_leak? await SEA.opt.slow_leak(pub) : await (shim.ossl || shim.subtle).importKey('jwk', S.jwk(pub), {name: 'ECDSA', namedCurve: 'P-256'}, false, ['verify']);
463
+ var hash = await sha(json.m);
464
+ var buf, sig, check, tmp; try{
465
+ buf = shim.Buffer.from(json.s, opt.encode || 'base64'); // NEW DEFAULT!
466
+ sig = new Uint8Array(buf);
467
+ check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash));
468
+ if(!check){ throw "Signature did not match." }
469
+ }catch(e){
470
+ if(SEA.opt.fallback){
471
+ return await SEA.opt.fall_verify(data, pair, cb, opt);
472
+ }
473
+ }
474
+ var r = check? await S.parse(json.m) : u;
475
+
476
+ if(cb){ try{ cb(r); }catch(e){console.log(e);} }
477
+ return r;
478
+ } catch(e) {
479
+ console.log(e); // mismatched owner FOR MARTTI
480
+ SEA.err = e;
481
+ if(SEA.throw){ throw e }
482
+ if(cb){ cb(); }
483
+ return;
484
+ }});
485
+
486
+ module.exports = SEA.verify;
487
+ // legacy & ossl memory leak mitigation:
488
+
489
+ var knownKeys = {};
490
+ SEA.opt.slow_leak = pair => {
491
+ if (knownKeys[pair]) return knownKeys[pair];
492
+ var jwk = S.jwk(pair);
493
+ knownKeys[pair] = (shim.ossl || shim.subtle).importKey("jwk", jwk, {name: 'ECDSA', namedCurve: 'P-256'}, false, ["verify"]);
494
+ return knownKeys[pair];
495
+ };
496
+
497
+ var O = SEA.opt;
498
+ SEA.opt.fall_verify = async function(data, pair, cb, opt, f){
499
+ if(f === SEA.opt.fallback){ throw "Signature did not match" } f = f || 1;
500
+ var tmp = data||'';
501
+ data = SEA.opt.unpack(data) || data;
502
+ var json = await S.parse(data), pub = pair.pub || pair, key = await SEA.opt.slow_leak(pub);
503
+ var hash = (f <= SEA.opt.fallback)? shim.Buffer.from(await shim.subtle.digest({name: 'SHA-256'}, new shim.TextEncoder().encode(await S.parse(json.m)))) : await sha(json.m); // this line is old bad buggy code but necessary for old compatibility.
504
+ var buf; var sig; var check; try{
505
+ buf = shim.Buffer.from(json.s, opt.encode || 'base64'); // NEW DEFAULT!
506
+ sig = new Uint8Array(buf);
507
+ check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash));
508
+ if(!check){ throw "Signature did not match." }
509
+ }catch(e){ try{
510
+ buf = shim.Buffer.from(json.s, 'utf8'); // AUTO BACKWARD OLD UTF8 DATA!
511
+ sig = new Uint8Array(buf);
512
+ check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash));
513
+ }catch(e){
514
+ if(!check){ throw "Signature did not match." }
515
+ }
516
+ }
517
+ var r = check? await S.parse(json.m) : u;
518
+ O.fall_soul = tmp['#']; O.fall_key = tmp['.']; O.fall_val = data; O.fall_state = tmp['>'];
519
+ if(cb){ try{ cb(r); }catch(e){console.log(e);} }
520
+ return r;
521
+ };
522
+ SEA.opt.fallback = 2;
523
+
524
+ })(USE, './verify');
525
+ USE(function(module){
526
+ var shim = USE('./shim');
527
+ var S = USE('./settings');
528
+ var sha256hash = USE('./sha256');
529
+
530
+ const importGen = async (key, salt, opt) => {
531
+ const combo = key + (salt || shim.random(8)).toString('utf8'); // new
532
+ const hash = shim.Buffer.from(await sha256hash(combo), 'binary');
533
+
534
+ const jwkKey = S.keyToJwk(hash);
535
+ return await shim.subtle.importKey('jwk', jwkKey, {name:'AES-GCM'}, false, ['encrypt', 'decrypt'])
536
+ };
537
+ module.exports = importGen;
538
+ })(USE, './aeskey');
539
+ USE(function(module){
540
+ var SEA = USE('./root');
541
+ var shim = USE('./shim');
542
+ USE('./settings');
543
+ var aeskey = USE('./aeskey');
544
+ var u;
545
+
546
+ SEA.encrypt = SEA.encrypt || (async (data, pair, cb, opt) => { try {
547
+ opt = opt || {};
548
+ var key = (pair||opt).epriv || pair;
549
+ if(u === data){ throw '`undefined` not allowed.' }
550
+ if(!key){
551
+ if(!SEA.I){ throw 'No encryption key.' }
552
+ pair = await SEA.I(null, {what: data, how: 'encrypt', why: opt.why});
553
+ key = pair.epriv || pair;
554
+ }
555
+ var msg = (typeof data == 'string')? data : await shim.stringify(data);
556
+ var rand = {s: shim.random(9), iv: shim.random(15)}; // consider making this 9 and 15 or 18 or 12 to reduce == padding.
557
+ var ct = await aeskey(key, rand.s, opt).then((aes) => (/*shim.ossl ||*/ shim.subtle).encrypt({ // Keeping the AES key scope as private as possible...
558
+ name: opt.name || 'AES-GCM', iv: new Uint8Array(rand.iv)
559
+ }, aes, new shim.TextEncoder().encode(msg)));
560
+ var r = {
561
+ ct: shim.Buffer.from(ct, 'binary').toString(opt.encode || 'base64'),
562
+ iv: rand.iv.toString(opt.encode || 'base64'),
563
+ s: rand.s.toString(opt.encode || 'base64')
564
+ };
565
+ if(!opt.raw){ r = 'SEA' + await shim.stringify(r); }
566
+
567
+ if(cb){ try{ cb(r); }catch(e){console.log(e);} }
568
+ return r;
569
+ } catch(e) {
570
+ console.log(e);
571
+ SEA.err = e;
572
+ if(SEA.throw){ throw e }
573
+ if(cb){ cb(); }
574
+ return;
575
+ }});
576
+
577
+ module.exports = SEA.encrypt;
578
+ })(USE, './encrypt');
579
+ USE(function(module){
580
+ var SEA = USE('./root');
581
+ var shim = USE('./shim');
582
+ var S = USE('./settings');
583
+ var aeskey = USE('./aeskey');
584
+
585
+ SEA.decrypt = SEA.decrypt || (async (data, pair, cb, opt) => { try {
586
+ opt = opt || {};
587
+ var key = (pair||opt).epriv || pair;
588
+ if(!key){
589
+ if(!SEA.I){ throw 'No decryption key.' }
590
+ pair = await SEA.I(null, {what: data, how: 'decrypt', why: opt.why});
591
+ key = pair.epriv || pair;
592
+ }
593
+ var json = await S.parse(data);
594
+ var buf, bufiv, bufct; try{
595
+ buf = shim.Buffer.from(json.s, opt.encode || 'base64');
596
+ bufiv = shim.Buffer.from(json.iv, opt.encode || 'base64');
597
+ bufct = shim.Buffer.from(json.ct, opt.encode || 'base64');
598
+ var ct = await aeskey(key, buf, opt).then((aes) => (/*shim.ossl ||*/ shim.subtle).decrypt({ // Keeping aesKey scope as private as possible...
599
+ name: opt.name || 'AES-GCM', iv: new Uint8Array(bufiv), tagLength: 128
600
+ }, aes, new Uint8Array(bufct)));
601
+ }catch(e){
602
+ if('utf8' === opt.encode){ throw "Could not decrypt" }
603
+ if(SEA.opt.fallback){
604
+ opt.encode = 'utf8';
605
+ return await SEA.decrypt(data, pair, cb, opt);
606
+ }
607
+ }
608
+ var r = await S.parse(new shim.TextDecoder('utf8').decode(ct));
609
+ if(cb){ try{ cb(r); }catch(e){console.log(e);} }
610
+ return r;
611
+ } catch(e) {
612
+ console.log(e);
613
+ SEA.err = e;
614
+ if(SEA.throw){ throw e }
615
+ if(cb){ cb(); }
616
+ return;
617
+ }});
618
+
619
+ module.exports = SEA.decrypt;
620
+ })(USE, './decrypt');
621
+ USE(function(module){
622
+ var SEA = USE('./root');
623
+ var shim = USE('./shim');
624
+ USE('./settings');
625
+ // Derive shared secret from other's pub and my epub/epriv
626
+ SEA.secret = SEA.secret || (async (key, pair, cb, opt) => { try {
627
+ opt = opt || {};
628
+ if(!pair || !pair.epriv || !pair.epub){
629
+ if(!SEA.I){ throw 'No secret mix.' }
630
+ pair = await SEA.I(null, {what: key, how: 'secret', why: opt.why});
631
+ }
632
+ var pub = key.epub || key;
633
+ var epub = pair.epub;
634
+ var epriv = pair.epriv;
635
+ var ecdhSubtle = shim.ossl || shim.subtle;
636
+ var pubKeyData = keysToEcdhJwk(pub);
637
+ var props = Object.assign({ public: await ecdhSubtle.importKey(...pubKeyData, true, []) },{name: 'ECDH', namedCurve: 'P-256'}); // Thanks to @sirpy !
638
+ var privKeyData = keysToEcdhJwk(epub, epriv);
639
+ var derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveBits']).then(async (privKey) => {
640
+ // privateKey scope doesn't leak out from here!
641
+ var derivedBits = await ecdhSubtle.deriveBits(props, privKey, 256);
642
+ var rawBits = new Uint8Array(derivedBits);
643
+ var derivedKey = await ecdhSubtle.importKey('raw', rawBits,{ name: 'AES-GCM', length: 256 }, true, [ 'encrypt', 'decrypt' ]);
644
+ return ecdhSubtle.exportKey('jwk', derivedKey).then(({ k }) => k);
645
+ });
646
+ var r = derived;
647
+ if(cb){ try{ cb(r); }catch(e){console.log(e);} }
648
+ return r;
649
+ } catch(e) {
650
+ console.log(e);
651
+ SEA.err = e;
652
+ if(SEA.throw){ throw e }
653
+ if(cb){ cb(); }
654
+ return;
655
+ }});
656
+
657
+ // can this be replaced with settings.jwk?
658
+ var keysToEcdhJwk = (pub, d) => { // d === priv
659
+ //var [ x, y ] = shim.Buffer.from(pub, 'base64').toString('utf8').split(':') // old
660
+ var [ x, y ] = pub.split('.'); // new
661
+ var jwk = d ? { d: d } : {};
662
+ return [ // Use with spread returned value...
663
+ 'jwk',
664
+ Object.assign(
665
+ jwk,
666
+ { x: x, y: y, kty: 'EC', crv: 'P-256', ext: true }
667
+ ), // ??? refactor
668
+ {name: 'ECDH', namedCurve: 'P-256'}
669
+ ]
670
+ };
671
+
672
+ module.exports = SEA.secret;
673
+ })(USE, './secret');
674
+ USE(function(module){
675
+ var SEA = USE('./root');
676
+ // This is to certify that a group of "certificants" can "put" anything at a group of matched "paths" to the certificate authority's graph
677
+ SEA.certify = SEA.certify || (async (certificants, policy = {}, authority, cb, opt = {}) => { try {
678
+ /*
679
+ The Certify Protocol was made out of love by a Vietnamese code enthusiast. Vietnamese people around the world deserve respect!
680
+ IMPORTANT: A Certificate is like a Signature. No one knows who (authority) created/signed a cert until you put it into their graph.
681
+ "certificants": '*' or a String (Bob.pub) || an Object that contains "pub" as a key || an array of [object || string]. These people will have the rights.
682
+ "policy": A string ('inbox'), or a RAD/LEX object {'*': 'inbox'}, or an Array of RAD/LEX objects or strings. RAD/LEX object can contain key "?" with indexOf("*") > -1 to force key equals certificant pub. This rule is used to check against soul+'/'+key using Gun.text.match or String.match.
683
+ "authority": Key pair or priv of the certificate authority.
684
+ "cb": A callback function after all things are done.
685
+ "opt": If opt.expiry (a timestamp) is set, SEA won't sync data after opt.expiry. If opt.block is set, SEA will look for block before syncing.
686
+ */
687
+ console.log('SEA.certify() is an early experimental community supported method that may change API behavior without warning in any future version.');
688
+
689
+ certificants = (() => {
690
+ var data = [];
691
+ if (certificants) {
692
+ if ((typeof certificants === 'string' || Array.isArray(certificants)) && certificants.indexOf('*') > -1) return '*'
693
+ if (typeof certificants === 'string') return certificants
694
+ if (Array.isArray(certificants)) {
695
+ if (certificants.length === 1 && certificants[0]) return typeof certificants[0] === 'object' && certificants[0].pub ? certificants[0].pub : typeof certificants[0] === 'string' ? certificants[0] : null
696
+ certificants.map(certificant => {
697
+ if (typeof certificant ==='string') data.push(certificant);
698
+ else if (typeof certificant === 'object' && certificant.pub) data.push(certificant.pub);
699
+ });
700
+ }
701
+
702
+ if (typeof certificants === 'object' && certificants.pub) return certificants.pub
703
+ return data.length > 0 ? data : null
704
+ }
705
+ return
706
+ })();
707
+
708
+ if (!certificants) return console.log("No certificant found.")
709
+
710
+ const expiry = opt.expiry && (typeof opt.expiry === 'number' || typeof opt.expiry === 'string') ? parseFloat(opt.expiry) : null;
711
+ const readPolicy = (policy || {}).read ? policy.read : null;
712
+ const writePolicy = (policy || {}).write ? policy.write : typeof policy === 'string' || Array.isArray(policy) || policy["+"] || policy["#"] || policy["."] || policy["="] || policy["*"] || policy[">"] || policy["<"] ? policy : null;
713
+ // The "blacklist" feature is now renamed to "block". Why ? BECAUSE BLACK LIVES MATTER!
714
+ // We can now use 3 keys: block, blacklist, ban
715
+ const block = (opt || {}).block || (opt || {}).blacklist || (opt || {}).ban || {};
716
+ const readBlock = block.read && (typeof block.read === 'string' || (block.read || {})['#']) ? block.read : null;
717
+ const writeBlock = typeof block === 'string' ? block : block.write && (typeof block.write === 'string' || block.write['#']) ? block.write : null;
718
+
719
+ if (!readPolicy && !writePolicy) return console.log("No policy found.")
720
+
721
+ // reserved keys: c, e, r, w, rb, wb
722
+ const data = JSON.stringify({
723
+ c: certificants,
724
+ ...(expiry ? {e: expiry} : {}), // inject expiry if possible
725
+ ...(readPolicy ? {r: readPolicy } : {}), // "r" stands for read, which means read permission.
726
+ ...(writePolicy ? {w: writePolicy} : {}), // "w" stands for write, which means write permission.
727
+ ...(readBlock ? {rb: readBlock} : {}), // inject READ block if possible
728
+ ...(writeBlock ? {wb: writeBlock} : {}), // inject WRITE block if possible
729
+ });
730
+
731
+ const certificate = await SEA.sign(data, authority, null, {raw:1});
732
+
733
+ var r = certificate;
734
+ if(!opt.raw){ r = 'SEA'+JSON.stringify(r); }
735
+ if(cb){ try{ cb(r); }catch(e){console.log(e);} }
736
+ return r;
737
+ } catch(e) {
738
+ SEA.err = e;
739
+ if(SEA.throw){ throw e }
740
+ if(cb){ cb(); }
741
+ return;
742
+ }});
743
+
744
+ module.exports = SEA.certify;
745
+ })(USE, './certify');
746
+ USE(function(module){
747
+ var shim = USE('./shim');
748
+ // Practical examples about usage found in tests.
749
+ var SEA = USE('./root');
750
+ SEA.work = USE('./work');
751
+ SEA.sign = USE('./sign');
752
+ SEA.verify = USE('./verify');
753
+ SEA.encrypt = USE('./encrypt');
754
+ SEA.decrypt = USE('./decrypt');
755
+ SEA.certify = USE('./certify');
756
+ //SEA.opt.aeskey = USE('./aeskey'); // not official! // this causes problems in latest WebCrypto.
757
+
758
+ SEA.random = SEA.random || shim.random;
759
+
760
+ // This is Buffer used in SEA and usable from Gun/SEA application also.
761
+ // For documentation see https://nodejs.org/api/buffer.html
762
+ SEA.Buffer = SEA.Buffer || USE('./buffer');
763
+
764
+ // These SEA functions support now ony Promises or
765
+ // async/await (compatible) code, use those like Promises.
766
+ //
767
+ // Creates a wrapper library around Web Crypto API
768
+ // for various AES, ECDSA, PBKDF2 functions we called above.
769
+ // Calculate public key KeyID aka PGPv4 (result: 8 bytes as hex string)
770
+ SEA.keyid = SEA.keyid || (async (pub) => {
771
+ try {
772
+ // base64('base64(x):base64(y)') => shim.Buffer(xy)
773
+ const pb = shim.Buffer.concat(
774
+ pub.replace(/-/g, '+').replace(/_/g, '/').split('.')
775
+ .map((t) => shim.Buffer.from(t, 'base64'))
776
+ );
777
+ // id is PGPv4 compliant raw key
778
+ const id = shim.Buffer.concat([
779
+ shim.Buffer.from([0x99, pb.length / 0x100, pb.length % 0x100]), pb
780
+ ]);
781
+ const sha1 = await sha1hash(id);
782
+ const hash = shim.Buffer.from(sha1, 'binary');
783
+ return hash.toString('hex', hash.length - 8) // 16-bit ID as hex
784
+ } catch (e) {
785
+ console.log(e);
786
+ throw e
787
+ }
788
+ });
789
+ // all done!
790
+ // Obviously it is missing MANY necessary features. This is only an alpha release.
791
+ // Please experiment with it, audit what I've done so far, and complain about what needs to be added.
792
+ // SEA should be a full suite that is easy and seamless to use.
793
+ // Again, scroll naer the top, where I provide an EXAMPLE of how to create a user and sign in.
794
+ // Once logged in, the rest of the code you just read handled automatically signing/validating data.
795
+ // But all other behavior needs to be equally easy, like opinionated ways of
796
+ // Adding friends (trusted public keys), sending private messages, etc.
797
+ // Cheers! Tell me what you think.
798
+ ((SEA.window||{}).GUN||{}).SEA = SEA;
799
+
800
+ module.exports = SEA;
801
+ // -------------- END SEA MODULES --------------------
802
+ // -- BEGIN SEA+GUN MODULES: BUNDLED BY DEFAULT UNTIL OTHERS USE SEA ON OWN -------
803
+ })(USE, './sea');
804
+ USE(function(module){
805
+ var SEA = USE('./sea'), Gun, u;
806
+ if(SEA.window){
807
+ Gun = SEA.window.GUN || {chain:{}};
808
+ } else {
809
+ Gun = USE((u+'' == typeof MODULE?'.':'')+'./gun', 1);
810
+ }
811
+ SEA.GUN = Gun;
812
+
813
+ function User(root){
814
+ this._ = {$: this};
815
+ }
816
+ User.prototype = (function(){ function F(){} F.prototype = Gun.chain; return new F() }()); // Object.create polyfill
817
+ User.prototype.constructor = User;
818
+
819
+ // let's extend the gun chain with a `user` function.
820
+ // only one user can be logged in at a time, per gun instance.
821
+ Gun.chain.user = function(pub){
822
+ var gun = this, root = gun.back(-1), user;
823
+ if(pub){
824
+ pub = SEA.opt.pub((pub._||'')['#']) || pub;
825
+ return root.get('~'+pub);
826
+ }
827
+ if(user = root.back('user')){ return user }
828
+ var root = (root._), at = root, uuid = at.opt.uuid || lex;
829
+ (at = (user = at.user = gun.chain(new User))._).opt = {};
830
+ at.opt.uuid = function(cb){
831
+ var id = uuid(), pub = root.user;
832
+ if(!pub || !(pub = pub.is) || !(pub = pub.pub)){ return id }
833
+ id = '~' + pub + '/' + id;
834
+ if(cb && cb.call){ cb(null, id); }
835
+ return id;
836
+ };
837
+ return user;
838
+ };
839
+ function lex(){ return Gun.state().toString(36).replace('.','') }
840
+ Gun.User = User;
841
+ User.GUN = Gun;
842
+ User.SEA = Gun.SEA = SEA;
843
+ module.exports = User;
844
+ })(USE, './user');
845
+ USE(function(module){
846
+ var u, Gun = (''+u != typeof GUN)? (GUN||{chain:{}}) : USE((''+u === typeof MODULE?'.':'')+'./gun', 1);
847
+ Gun.chain.then = function(cb, opt){
848
+ var gun = this, p = (new Promise(function(res, rej){
849
+ gun.once(res, opt);
850
+ }));
851
+ return cb? p.then(cb) : p;
852
+ };
853
+ })(USE, './then');
854
+ USE(function(module){
855
+ var User = USE('./user'), SEA = User.SEA, Gun = User.GUN, noop = function(){};
856
+
857
+ // Well first we have to actually create a user. That is what this function does.
858
+ User.prototype.create = function(...args){
859
+ var pair = typeof args[0] === 'object' && (args[0].pub || args[0].epub) ? args[0] : typeof args[1] === 'object' && (args[1].pub || args[1].epub) ? args[1] : null;
860
+ var alias = pair && (pair.pub || pair.epub) ? pair.pub : typeof args[0] === 'string' ? args[0] : null;
861
+ var pass = pair && (pair.pub || pair.epub) ? pair : alias && typeof args[1] === 'string' ? args[1] : null;
862
+ var cb = args.filter(arg => typeof arg === 'function')[0] || null; // cb now can stand anywhere, after alias/pass or pair
863
+ var opt = args && args.length > 1 && typeof args[args.length-1] === 'object' ? args[args.length-1] : {}; // opt is always the last parameter which typeof === 'object' and stands after cb
864
+
865
+ var gun = this, cat = (gun._), root = gun.back(-1);
866
+ cb = cb || noop;
867
+ opt = opt || {};
868
+ if(false !== opt.check){
869
+ var err;
870
+ if(!alias){ err = "No user."; }
871
+ if((pass||'').length < 8){ err = "Password too short!"; }
872
+ if(err){
873
+ cb({err: Gun.log(err)});
874
+ return gun;
875
+ }
876
+ }
877
+ if(cat.ing){
878
+ (cb || noop)({err: Gun.log("User is already being created or authenticated!"), wait: true});
879
+ return gun;
880
+ }
881
+ cat.ing = true;
882
+ var act = {};
883
+ act.a = function(pubs){
884
+ act.pubs = pubs;
885
+ if(pubs && !opt.already){
886
+ // If we can enforce that a user name is already taken, it might be nice to try, but this is not guaranteed.
887
+ var ack = {err: Gun.log('User already created!')};
888
+ cat.ing = false;
889
+ (cb || noop)(ack);
890
+ gun.leave();
891
+ return;
892
+ }
893
+ act.salt = String.random(64); // pseudo-randomly create a salt, then use PBKDF2 function to extend the password with it.
894
+ SEA.work(pass, act.salt, act.b); // this will take some short amount of time to produce a proof, which slows brute force attacks.
895
+ };
896
+ act.b = function(proof){
897
+ act.proof = proof;
898
+ pair ? act.c(pair) : SEA.pair(act.c); // generate a brand new key pair or use the existing.
899
+ };
900
+ act.c = function(pair){
901
+ var tmp;
902
+ act.pair = pair || {};
903
+ if(tmp = cat.root.user){
904
+ tmp._.sea = pair;
905
+ tmp.is = {pub: pair.pub, epub: pair.epub, alias: alias};
906
+ }
907
+ // the user's public key doesn't need to be signed. But everything else needs to be signed with it! // we have now automated it! clean up these extra steps now!
908
+ act.data = {pub: pair.pub};
909
+ act.d();
910
+ };
911
+ act.d = function(){
912
+ act.data.alias = alias;
913
+ act.e();
914
+ };
915
+ act.e = function(){
916
+ act.data.epub = act.pair.epub;
917
+ SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, act.proof, act.f, {raw:1}); // to keep the private key safe, we AES encrypt it with the proof of work!
918
+ };
919
+ act.f = function(auth){
920
+ act.data.auth = JSON.stringify({ek: auth, s: act.salt});
921
+ act.g(act.data.auth);
922
+ };
923
+ act.g = function(auth){ var tmp;
924
+ act.data.auth = act.data.auth || auth;
925
+ root.get(tmp = '~'+act.pair.pub).put(act.data).on(act.h); // awesome, now we can actually save the user with their public key as their ID.
926
+ var link = {}; link[tmp] = {'#': tmp}; root.get('~@'+alias).put(link).get(tmp).on(act.i); // next up, we want to associate the alias with the public key. So we add it to the alias list.
927
+ };
928
+ act.h = function(data, key, msg, eve){
929
+ eve.off(); act.h.ok = 1; act.i();
930
+ };
931
+ act.i = function(data, key, msg, eve){
932
+ if(eve){ act.i.ok = 1; eve.off(); }
933
+ if(!act.h.ok || !act.i.ok){ return }
934
+ cat.ing = false;
935
+ cb({ok: 0, pub: act.pair.pub}); // callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack)
936
+ if(noop === cb){ pair ? gun.auth(pair) : gun.auth(alias, pass); } // if no callback is passed, auto-login after signing up.
937
+ };
938
+ root.get('~@'+alias).once(act.a);
939
+ return gun;
940
+ };
941
+ User.prototype.leave = function(opt, cb){
942
+ var gun = this, user = (gun.back(-1)._).user;
943
+ if(user){
944
+ delete user.is;
945
+ delete user._.is;
946
+ delete user._.sea;
947
+ }
948
+ if(SEA.window){
949
+ try{var sS = {};
950
+ sS = SEA.window.sessionStorage;
951
+ delete sS.recall;
952
+ delete sS.pair;
953
+ }catch(e){} }
954
+ return gun;
955
+ };
956
+ })(USE, './create');
957
+ USE(function(module){
958
+ var User = USE('./user'), SEA = User.SEA, Gun = User.GUN, noop = function(){};
959
+ // now that we have created a user, we want to authenticate them!
960
+ User.prototype.auth = function(...args){ // TODO: this PR with arguments need to be cleaned up / refactored.
961
+ var pair = typeof args[0] === 'object' && (args[0].pub || args[0].epub) ? args[0] : typeof args[1] === 'object' && (args[1].pub || args[1].epub) ? args[1] : null;
962
+ var alias = !pair && typeof args[0] === 'string' ? args[0] : null;
963
+ var pass = (alias || (pair && !(pair.priv && pair.epriv))) && typeof args[1] === 'string' ? args[1] : null;
964
+ var cb = args.filter(arg => typeof arg === 'function')[0] || null; // cb now can stand anywhere, after alias/pass or pair
965
+ var opt = args && args.length > 1 && typeof args[args.length-1] === 'object' ? args[args.length-1] : {}; // opt is always the last parameter which typeof === 'object' and stands after cb
966
+
967
+ var gun = this, cat = (gun._), root = gun.back(-1);
968
+
969
+ if(cat.ing){
970
+ (cb || noop)({err: Gun.log("User is already being created or authenticated!"), wait: true});
971
+ return gun;
972
+ }
973
+ cat.ing = true;
974
+
975
+ var act = {}, u, tries = 9;
976
+ act.a = function(data){
977
+ if(!data){ return act.b() }
978
+ if(!data.pub){
979
+ var tmp = []; Object.keys(data).forEach(function(k){ if('_'==k){ return } tmp.push(data[k]); });
980
+ return act.b(tmp);
981
+ }
982
+ if(act.name){ return act.f(data) }
983
+ act.c((act.data = data).auth);
984
+ };
985
+ act.b = function(list){
986
+ var get = (act.list = (act.list||[]).concat(list||[])).shift();
987
+ if(u === get){
988
+ if(act.name){ return act.err('Your user account is not published for dApps to access, please consider syncing it online, or allowing local access by adding your device as a peer.') }
989
+ if(alias && tries--){
990
+ root.get('~@'+alias).once(act.a);
991
+ return;
992
+ }
993
+ return act.err('Wrong user or password.')
994
+ }
995
+ root.get(get).once(act.a);
996
+ };
997
+ act.c = function(auth){
998
+ if(u === auth){ return act.b() }
999
+ if('string' == typeof auth){ return act.c(obj_ify(auth)) } // in case of legacy
1000
+ SEA.work(pass, (act.auth = auth).s, act.d, act.enc); // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force.
1001
+ };
1002
+ act.d = function(proof){
1003
+ SEA.decrypt(act.auth.ek, proof, act.e, act.enc);
1004
+ };
1005
+ act.e = function(half){
1006
+ if(u === half){
1007
+ if(!act.enc){ // try old format
1008
+ act.enc = {encode: 'utf8'};
1009
+ return act.c(act.auth);
1010
+ } act.enc = null; // end backwards
1011
+ return act.b();
1012
+ }
1013
+ act.half = half;
1014
+ act.f(act.data);
1015
+ };
1016
+ act.f = function(pair){
1017
+ var half = act.half || {}, data = act.data || {};
1018
+ act.g(act.lol = {pub: pair.pub || data.pub, epub: pair.epub || data.epub, priv: pair.priv || half.priv, epriv: pair.epriv || half.epriv});
1019
+ };
1020
+ act.g = function(pair){
1021
+ if(!pair || !pair.pub || !pair.epub){ return act.b() }
1022
+ act.pair = pair;
1023
+ var user = (root._).user, at = (user._);
1024
+ at.tag;
1025
+ var upt = at.opt;
1026
+ at = user._ = root.get('~'+pair.pub)._;
1027
+ at.opt = upt;
1028
+ // add our credentials in-memory only to our root user instance
1029
+ user.is = {pub: pair.pub, epub: pair.epub, alias: alias || pair.pub};
1030
+ at.sea = act.pair;
1031
+ cat.ing = false;
1032
+ try{if(pass && u == (obj_ify(cat.root.graph['~'+pair.pub].auth)||'')[':']){ opt.shuffle = opt.change = pass; } }catch(e){} // migrate UTF8 & Shuffle!
1033
+ opt.change? act.z() : (cb || noop)(at);
1034
+ if(SEA.window && ((gun.back('user')._).opt||opt).remember){
1035
+ // TODO: this needs to be modular.
1036
+ try{var sS = {};
1037
+ sS = SEA.window.sessionStorage; // TODO: FIX BUG putting on `.is`!
1038
+ sS.recall = true;
1039
+ sS.pair = JSON.stringify(pair); // auth using pair is more reliable than alias/pass
1040
+ }catch(e){}
1041
+ }
1042
+ try{
1043
+ if(root._.tag.auth){ // auth handle might not be registered yet
1044
+ (root._).on('auth', at); // TODO: Deprecate this, emit on user instead! Update docs when you do.
1045
+ } else { setTimeout(function(){ (root._).on('auth', at); },1); } // if not, hackily add a timeout.
1046
+ //at.on('auth', at) // Arrgh, this doesn't work without event "merge" code, but "merge" code causes stack overflow and crashes after logging in & trying to write data.
1047
+ }catch(e){
1048
+ Gun.log("Your 'auth' callback crashed with:", e);
1049
+ }
1050
+ };
1051
+ act.h = function(data){
1052
+ if(!data){ return act.b() }
1053
+ alias = data.alias;
1054
+ if(!alias)
1055
+ alias = data.alias = "~" + pair.pub;
1056
+ if(!data.auth){
1057
+ return act.g(pair);
1058
+ }
1059
+ pair = null;
1060
+ act.c((act.data = data).auth);
1061
+ };
1062
+ act.z = function(){
1063
+ // password update so encrypt private key using new pwd + salt
1064
+ act.salt = String.random(64); // pseudo-random
1065
+ SEA.work(opt.change, act.salt, act.y);
1066
+ };
1067
+ act.y = function(proof){
1068
+ SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, proof, act.x, {raw:1});
1069
+ };
1070
+ act.x = function(auth){
1071
+ act.w(JSON.stringify({ek: auth, s: act.salt}));
1072
+ };
1073
+ act.w = function(auth){
1074
+ if(opt.shuffle){ // delete in future!
1075
+ console.log('migrate core account from UTF8 & shuffle');
1076
+ var tmp = {}; Object.keys(act.data).forEach(function(k){ tmp[k] = act.data[k]; });
1077
+ delete tmp._;
1078
+ tmp.auth = auth;
1079
+ root.get('~'+act.pair.pub).put(tmp);
1080
+ } // end delete
1081
+ root.get('~'+act.pair.pub).get('auth').put(auth, cb || noop);
1082
+ };
1083
+ act.err = function(e){
1084
+ var ack = {err: Gun.log(e || 'User cannot be found!')};
1085
+ cat.ing = false;
1086
+ (cb || noop)(ack);
1087
+ };
1088
+ act.plugin = function(name){
1089
+ if(!(act.name = name)){ return act.err() }
1090
+ var tmp = [name];
1091
+ if('~' !== name[0]){
1092
+ tmp[1] = '~'+name;
1093
+ tmp[2] = '~@'+name;
1094
+ }
1095
+ act.b(tmp);
1096
+ };
1097
+ if(pair){
1098
+ if(pair.priv && pair.epriv)
1099
+ act.g(pair);
1100
+ else
1101
+ root.get('~'+pair.pub).once(act.h);
1102
+ } else
1103
+ if(alias){
1104
+ root.get('~@'+alias).once(act.a);
1105
+ } else
1106
+ if(!alias && !pass){
1107
+ SEA.name(act.plugin);
1108
+ }
1109
+ return gun;
1110
+ };
1111
+ function obj_ify(o){
1112
+ if('string' != typeof o){ return o }
1113
+ try{o = JSON.parse(o);
1114
+ }catch(e){o={};} return o;
1115
+ }
1116
+ })(USE, './auth');
1117
+ USE(function(module){
1118
+ var User = USE('./user'), SEA = User.SEA; User.GUN;
1119
+ User.prototype.recall = function(opt, cb){
1120
+ var gun = this, root = gun.back(-1);
1121
+ opt = opt || {};
1122
+ if(opt && opt.sessionStorage){
1123
+ if(SEA.window){
1124
+ try{
1125
+ var sS = {};
1126
+ sS = SEA.window.sessionStorage; // TODO: FIX BUG putting on `.is`!
1127
+ if(sS){
1128
+ (root._).opt.remember = true;
1129
+ ((gun.back('user')._).opt||opt).remember = true;
1130
+ if(sS.recall || sS.pair) root.user().auth(JSON.parse(sS.pair), cb); // pair is more reliable than alias/pass
1131
+ }
1132
+ }catch(e){}
1133
+ }
1134
+ return gun;
1135
+ }
1136
+ /*
1137
+ TODO: copy mhelander's expiry code back in.
1138
+ Although, we should check with community,
1139
+ should expiry be core or a plugin?
1140
+ */
1141
+ return gun;
1142
+ };
1143
+ })(USE, './recall');
1144
+ USE(function(module){
1145
+ var User = USE('./user'), SEA = User.SEA, Gun = User.GUN, noop = function(){};
1146
+ User.prototype.pair = function(){
1147
+ var user = this, proxy; // undeprecated, hiding with proxies.
1148
+ try{ proxy = new Proxy({DANGER:'\u2620'}, {get: function(t,p,r){
1149
+ if(!user.is || !(user._||'').sea){ return }
1150
+ return user._.sea[p];
1151
+ }});}catch(e){}
1152
+ return proxy;
1153
+ };
1154
+ // If authenticated user wants to delete his/her account, let's support it!
1155
+ User.prototype.delete = async function(alias, pass, cb){
1156
+ console.log("user.delete() IS DEPRECATED AND WILL BE MOVED TO A MODULE!!!");
1157
+ var gun = this; gun.back(-1); var user = gun.back('user');
1158
+ try {
1159
+ user.auth(alias, pass, function(ack){
1160
+ var pub = (user.is||{}).pub;
1161
+ // Delete user data
1162
+ user.map().once(function(){ this.put(null); });
1163
+ // Wipe user data from memory
1164
+ user.leave();
1165
+ (cb || noop)({ok: 0});
1166
+ });
1167
+ } catch (e) {
1168
+ Gun.log('User.delete failed! Error:', e);
1169
+ }
1170
+ return gun;
1171
+ };
1172
+ User.prototype.alive = async function(){
1173
+ console.log("user.alive() IS DEPRECATED!!!");
1174
+ const gunRoot = this.back(-1);
1175
+ try {
1176
+ // All is good. Should we do something more with actual recalled data?
1177
+ await authRecall(gunRoot);
1178
+ return gunRoot._.user._
1179
+ } catch (e) {
1180
+ const err = 'No session!';
1181
+ Gun.log(err);
1182
+ throw { err }
1183
+ }
1184
+ };
1185
+ User.prototype.trust = async function(user){
1186
+ console.log("`.trust` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!");
1187
+ // TODO: BUG!!! SEA `node` read listener needs to be async, which means core needs to be async too.
1188
+ //gun.get('alice').get('age').trust(bob);
1189
+ if (Gun.is(user)) {
1190
+ user.get('pub').get((ctx, ev) => {
1191
+ console.log(ctx, ev);
1192
+ });
1193
+ }
1194
+ user.get('trust').get(path).put(theirPubkey);
1195
+
1196
+ // do a lookup on this gun chain directly (that gets bob's copy of the data)
1197
+ // do a lookup on the metadata trust table for this path (that gets all the pubkeys allowed to write on this path)
1198
+ // do a lookup on each of those pubKeys ON the path (to get the collab data "layers")
1199
+ // THEN you perform Jachen's mix operation
1200
+ // and return the result of that to...
1201
+ };
1202
+ User.prototype.grant = function(to, cb){
1203
+ console.log("`.grant` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!");
1204
+ var gun = this, user = gun.back(-1).user(), pair = user._.sea, path = '';
1205
+ gun.back(function(at){ if(at.is){ return } path += (at.get||''); });
1206
+ (async function(){
1207
+ var enc, sec = await user.get('grant').get(pair.pub).get(path).then();
1208
+ sec = await SEA.decrypt(sec, pair);
1209
+ if(!sec){
1210
+ sec = SEA.random(16).toString();
1211
+ enc = await SEA.encrypt(sec, pair);
1212
+ user.get('grant').get(pair.pub).get(path).put(enc);
1213
+ }
1214
+ var pub = to.get('pub').then();
1215
+ var epub = to.get('epub').then();
1216
+ pub = await pub; epub = await epub;
1217
+ var dh = await SEA.secret(epub, pair);
1218
+ enc = await SEA.encrypt(sec, dh);
1219
+ user.get('grant').get(pub).get(path).put(enc, cb);
1220
+ }());
1221
+ return gun;
1222
+ };
1223
+ User.prototype.secret = function(data, cb){
1224
+ console.log("`.secret` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!");
1225
+ var gun = this, user = gun.back(-1).user(), pair = user.pair(), path = '';
1226
+ gun.back(function(at){ if(at.is){ return } path += (at.get||''); });
1227
+ (async function(){
1228
+ var enc, sec = await user.get('trust').get(pair.pub).get(path).then();
1229
+ sec = await SEA.decrypt(sec, pair);
1230
+ if(!sec){
1231
+ sec = SEA.random(16).toString();
1232
+ enc = await SEA.encrypt(sec, pair);
1233
+ user.get('trust').get(pair.pub).get(path).put(enc);
1234
+ }
1235
+ enc = await SEA.encrypt(data, sec);
1236
+ gun.put(enc, cb);
1237
+ }());
1238
+ return gun;
1239
+ };
1240
+
1241
+ /**
1242
+ * returns the decrypted value, encrypted by secret
1243
+ * @returns {Promise<any>}
1244
+ // Mark needs to review 1st before officially supported
1245
+ User.prototype.decrypt = function(cb) {
1246
+ let gun = this,
1247
+ path = ''
1248
+ gun.back(function(at) {
1249
+ if (at.is) {
1250
+ return
1251
+ }
1252
+ path += at.get || ''
1253
+ })
1254
+ return gun
1255
+ .then(async data => {
1256
+ if (data == null) {
1257
+ return
1258
+ }
1259
+ const user = gun.back(-1).user()
1260
+ const pair = user.pair()
1261
+ let sec = await user
1262
+ .get('trust')
1263
+ .get(pair.pub)
1264
+ .get(path)
1265
+ sec = await SEA.decrypt(sec, pair)
1266
+ if (!sec) {
1267
+ return data
1268
+ }
1269
+ let decrypted = await SEA.decrypt(data, sec)
1270
+ return decrypted
1271
+ })
1272
+ .then(res => {
1273
+ cb && cb(res)
1274
+ return res
1275
+ })
1276
+ }
1277
+ */
1278
+ module.exports = User;
1279
+ })(USE, './share');
1280
+ USE(function(module){
1281
+ var SEA = USE('./sea'), S = USE('./settings'), u;
1282
+ var Gun = (SEA.window||'').GUN || USE((''+u === typeof MODULE?'.':'')+'./gun', 1);
1283
+ // After we have a GUN extension to make user registration/login easy, we then need to handle everything else.
1284
+
1285
+ // We do this with a GUN adapter, we first listen to when a gun instance is created (and when its options change)
1286
+ Gun.on('opt', function(at){
1287
+ if(!at.sea){ // only add SEA once per instance, on the "at" context.
1288
+ at.sea = {own: {}};
1289
+ at.on('put', check, at); // SEA now runs its firewall on HAM diffs, not all i/o.
1290
+ }
1291
+ this.to.next(at); // make sure to call the "next" middleware adapter.
1292
+ });
1293
+
1294
+ // Alright, this next adapter gets run at the per node level in the graph database.
1295
+ // correction: 2020 it gets run on each key/value pair in a node upon a HAM diff.
1296
+ // This will let us verify that every property on a node has a value signed by a public key we trust.
1297
+ // If the signature does not match, the data is just `undefined` so it doesn't get passed on.
1298
+ // If it does match, then we transform the in-memory "view" of the data into its plain value (without the signature).
1299
+ // Now NOTE! Some data is "system" data, not user data. Example: List of public keys, aliases, etc.
1300
+ // This data is self-enforced (the value can only match its ID), but that is handled in the `security` function.
1301
+ // From the self-enforced data, we can see all the edges in the graph that belong to a public key.
1302
+ // Example: ~ASDF is the ID of a node with ASDF as its public key, signed alias and salt, and
1303
+ // its encrypted private key, but it might also have other signed values on it like `profile = <ID>` edge.
1304
+ // Using that directed edge's ID, we can then track (in memory) which IDs belong to which keys.
1305
+ // Here is a problem: Multiple public keys can "claim" any node's ID, so this is dangerous!
1306
+ // This means we should ONLY trust our "friends" (our key ring) public keys, not any ones.
1307
+ // I have not yet added that to SEA yet in this alpha release. That is coming soon, but beware in the meanwhile!
1308
+
1309
+ function check(msg){ // REVISE / IMPROVE, NO NEED TO PASS MSG/EVE EACH SUB?
1310
+ var eve = this, at = eve.as, put = msg.put, soul = put['#'], key = put['.'], val = put[':'], state = put['>'], id = msg['#'], tmp;
1311
+ if(!soul || !key){ return }
1312
+ if((msg._||'').faith && (at.opt||'').faith && 'function' == typeof msg._){
1313
+ SEA.opt.pack(put, function(raw){
1314
+ SEA.verify(raw, false, function(data){ // this is synchronous if false
1315
+ put['='] = SEA.opt.unpack(data);
1316
+ eve.to.next(msg);
1317
+ });});
1318
+ return
1319
+ }
1320
+ var no = function(why){ at.on('in', {'@': id, err: msg.err = why}); }; // exploit internal relay stun for now, maybe violates spec, but testing for now. // Note: this may be only the sharded message, not original batch.
1321
+ //var no = function(why){ msg.ack(why) };
1322
+ (msg._||'').DBG && ((msg._||'').DBG.c = +new Date);
1323
+ if(0 <= soul.indexOf('<?')){ // special case for "do not sync data X old" forget
1324
+ // 'a~pub.key/b<?9'
1325
+ tmp = parseFloat(soul.split('<?')[1]||'');
1326
+ if(tmp && (state < (Gun.state() - (tmp * 1000)))){ // sec to ms
1327
+ (tmp = msg._) && (tmp.stun) && (tmp.stun--); // THIS IS BAD CODE! It assumes GUN internals do something that will probably change in future, but hacking in now.
1328
+ return; // omit!
1329
+ }
1330
+ }
1331
+
1332
+ if('~@' === soul){ // special case for shared system data, the list of aliases.
1333
+ check.alias(eve, msg, val, key, soul, at, no); return;
1334
+ }
1335
+ if('~@' === soul.slice(0,2)){ // special case for shared system data, the list of public keys for an alias.
1336
+ check.pubs(eve, msg, val, key, soul, at, no); return;
1337
+ }
1338
+ //if('~' === soul.slice(0,1) && 2 === (tmp = soul.slice(1)).split('.').length){ // special case, account data for a public key.
1339
+ if(tmp = SEA.opt.pub(soul)){ // special case, account data for a public key.
1340
+ check.pub(eve, msg, val, key, soul, at, no, at.user||'', tmp); return;
1341
+ }
1342
+ if(0 <= soul.indexOf('#')){ // special case for content addressing immutable hashed data.
1343
+ check.hash(eve, msg, val, key, soul, at, no); return;
1344
+ }
1345
+ check.any(eve, msg, val, key, soul, at, no, at.user||''); return;
1346
+ }
1347
+ check.hash = function(eve, msg, val, key, soul, at, no){ // mark unbuilt @i001962 's epic hex contrib!
1348
+ SEA.work(val, null, function(data){
1349
+ function hexToBase64(hexStr) {
1350
+ let base64 = "";
1351
+ for(let i = 0; i < hexStr.length; i++) {
1352
+ base64 += !(i - 1 & 1) ? String.fromCharCode(parseInt(hexStr.substring(i - 1, i + 1), 16)) : "";}
1353
+ return btoa(base64);}
1354
+ if(data && data === key.split('#').slice(-1)[0]){ return eve.to.next(msg) }
1355
+ else if (data && data === hexToBase64(key.split('#').slice(-1)[0])){
1356
+ return eve.to.next(msg) }
1357
+ no("Data hash not same as hash!");
1358
+ }, {name: 'SHA-256'});
1359
+ };
1360
+ check.alias = function(eve, msg, val, key, soul, at, no){ // Example: {_:#~@, ~@alice: {#~@alice}}
1361
+ if(!val){ return no("Data must exist!") } // data MUST exist
1362
+ if('~@'+key === link_is(val)){ return eve.to.next(msg) } // in fact, it must be EXACTLY equal to itself
1363
+ no("Alias not same!"); // if it isn't, reject.
1364
+ };
1365
+ check.pubs = function(eve, msg, val, key, soul, at, no){ // Example: {_:#~@alice, ~asdf: {#~asdf}}
1366
+ if(!val){ return no("Alias must exist!") } // data MUST exist
1367
+ if(key === link_is(val)){ return eve.to.next(msg) } // and the ID must be EXACTLY equal to its property
1368
+ no("Alias not same!"); // that way nobody can tamper with the list of public keys.
1369
+ };
1370
+ check.pub = async function(eve, msg, val, key, soul, at, no, user, pub){ var tmp; // Example: {_:#~asdf, hello:'world'~fdsa}}
1371
+ const raw = await S.parse(val) || {};
1372
+ const verify = (certificate, certificant, cb) => {
1373
+ if (certificate.m && certificate.s && certificant && pub)
1374
+ // now verify certificate
1375
+ return SEA.verify(certificate, pub, data => { // check if "pub" (of the graph owner) really issued this cert
1376
+ if (u !== data && u !== data.e && msg.put['>'] && msg.put['>'] > parseFloat(data.e)) return no("Certificate expired.") // certificate expired
1377
+ // "data.c" = a list of certificants/certified users
1378
+ // "data.w" = lex WRITE permission, in the future, there will be "data.r" which means lex READ permission
1379
+ if (u !== data && data.c && data.w && (data.c === certificant || data.c.indexOf('*' ) > -1)) {
1380
+ // ok, now "certificant" is in the "certificants" list, but is "path" allowed? Check path
1381
+ let path = soul.indexOf('/') > -1 ? soul.replace(soul.substring(0, soul.indexOf('/') + 1), '') : '';
1382
+ String.match = String.match || Gun.text.match;
1383
+ const w = Array.isArray(data.w) ? data.w : typeof data.w === 'object' || typeof data.w === 'string' ? [data.w] : [];
1384
+ for (const lex of w) {
1385
+ if ((String.match(path, lex['#']) && String.match(key, lex['.'])) || (!lex['.'] && String.match(path, lex['#'])) || (!lex['#'] && String.match(key, lex['.'])) || String.match((path ? path + '/' + key : key), lex['#'] || lex)) {
1386
+ // is Certificant forced to present in Path
1387
+ if (lex['+'] && lex['+'].indexOf('*') > -1 && path && path.indexOf(certificant) == -1 && key.indexOf(certificant) == -1) return no(`Path "${path}" or key "${key}" must contain string "${certificant}".`)
1388
+ // path is allowed, but is there any WRITE block? Check it out
1389
+ if (data.wb && (typeof data.wb === 'string' || ((data.wb || {})['#']))) { // "data.wb" = path to the WRITE block
1390
+ var root = eve.as.root.$.back(-1);
1391
+ if (typeof data.wb === 'string' && '~' !== data.wb.slice(0, 1)) root = root.get('~' + pub);
1392
+ return root.get(data.wb).get(certificant).once(value => { // TODO: INTENT TO DEPRECATE.
1393
+ if (value && (value === 1 || value === true)) return no(`Certificant ${certificant} blocked.`)
1394
+ return cb(data)
1395
+ })
1396
+ }
1397
+ return cb(data)
1398
+ }
1399
+ }
1400
+ return no("Certificate verification fail.")
1401
+ }
1402
+ })
1403
+ return
1404
+ };
1405
+
1406
+ if ('pub' === key && '~' + pub === soul) {
1407
+ if (val === pub) return eve.to.next(msg) // the account MUST match `pub` property that equals the ID of the public key.
1408
+ return no("Account not same!")
1409
+ }
1410
+
1411
+ if ((tmp = user.is) && tmp.pub && !raw['*'] && !raw['+'] && (pub === tmp.pub || (pub !== tmp.pub && ((msg._.msg || {}).opt || {}).cert))){
1412
+ SEA.opt.pack(msg.put, packed => {
1413
+ SEA.sign(packed, (user._).sea, async function(data) {
1414
+ if (u === data) return no(SEA.err || 'Signature fail.')
1415
+ msg.put[':'] = {':': tmp = SEA.opt.unpack(data.m), '~': data.s};
1416
+ msg.put['='] = tmp;
1417
+
1418
+ // if writing to own graph, just allow it
1419
+ if (pub === user.is.pub) {
1420
+ if (tmp = link_is(val)) (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1;
1421
+ JSON.stringifyAsync(msg.put[':'], function(err,s){
1422
+ if(err){ return no(err || "Stringify error.") }
1423
+ msg.put[':'] = s;
1424
+ return eve.to.next(msg);
1425
+ });
1426
+ return
1427
+ }
1428
+
1429
+ // if writing to other's graph, check if cert exists then try to inject cert into put, also inject self pub so that everyone can verify the put
1430
+ if (pub !== user.is.pub && ((msg._.msg || {}).opt || {}).cert) {
1431
+ const cert = await S.parse(msg._.msg.opt.cert);
1432
+ // even if cert exists, we must verify it
1433
+ if (cert && cert.m && cert.s)
1434
+ verify(cert, user.is.pub, _ => {
1435
+ msg.put[':']['+'] = cert; // '+' is a certificate
1436
+ msg.put[':']['*'] = user.is.pub; // '*' is pub of the user who puts
1437
+ JSON.stringifyAsync(msg.put[':'], function(err,s){
1438
+ if(err){ return no(err || "Stringify error.") }
1439
+ msg.put[':'] = s;
1440
+ return eve.to.next(msg);
1441
+ });
1442
+ return
1443
+ });
1444
+ }
1445
+ }, {raw: 1});
1446
+ });
1447
+ return;
1448
+ }
1449
+
1450
+ SEA.opt.pack(msg.put, packed => {
1451
+ SEA.verify(packed, raw['*'] || pub, function(data){ var tmp;
1452
+ data = SEA.opt.unpack(data);
1453
+ if (u === data) return no("Unverified data.") // make sure the signature matches the account it claims to be on. // reject any updates that are signed with a mismatched account.
1454
+ if ((tmp = link_is(data)) && pub === SEA.opt.pub(tmp)) (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1;
1455
+
1456
+ // check if cert ('+') and putter's pub ('*') exist
1457
+ if (raw['+'] && raw['+']['m'] && raw['+']['s'] && raw['*'])
1458
+ // now verify certificate
1459
+ verify(raw['+'], raw['*'], _ => {
1460
+ msg.put['='] = data;
1461
+ return eve.to.next(msg);
1462
+ });
1463
+ else {
1464
+ msg.put['='] = data;
1465
+ return eve.to.next(msg);
1466
+ }
1467
+ });
1468
+ });
1469
+ return
1470
+ };
1471
+ check.any = function(eve, msg, val, key, soul, at, no, user){ if(at.opt.secure){ return no("Soul missing public key at '" + key + "'.") }
1472
+ // TODO: Ask community if should auto-sign non user-graph data.
1473
+ at.on('secure', function(msg){ this.off();
1474
+ if(!at.opt.secure){ return eve.to.next(msg) }
1475
+ no("Data cannot be changed.");
1476
+ }).on.on('secure', msg);
1477
+ return;
1478
+ };
1479
+
1480
+ var valid = Gun.valid, link_is = function(d,l){ return 'string' == typeof (l = valid(d)) && l }; (Gun.state||'').ify;
1481
+
1482
+ var pubcut = /[^\w_-]/; // anything not alphanumeric or _ -
1483
+ SEA.opt.pub = function(s){
1484
+ if(!s){ return }
1485
+ s = s.split('~');
1486
+ if(!s || !(s = s[1])){ return }
1487
+ s = s.split(pubcut).slice(0,2);
1488
+ if(!s || 2 != s.length){ return }
1489
+ if('@' === (s[0]||'')[0]){ return }
1490
+ s = s.slice(0,2).join('.');
1491
+ return s;
1492
+ };
1493
+ SEA.opt.stringy = function(t){
1494
+ // TODO: encrypt etc. need to check string primitive. Make as breaking change.
1495
+ };
1496
+ SEA.opt.pack = function(d,cb,k, n,s){ var tmp, f; // pack for verifying
1497
+ if(SEA.opt.check(d)){ return cb(d) }
1498
+ if(d && d['#'] && d['.'] && d['>']){ tmp = d[':']; f = 1; }
1499
+ JSON.parseAsync(f? tmp : d, function(err, meta){
1500
+ var sig = ((u !== (meta||'')[':']) && (meta||'')['~']); // or just ~ check?
1501
+ if(!sig){ cb(d); return }
1502
+ cb({m: {'#':s||d['#'],'.':k||d['.'],':':(meta||'')[':'],'>':d['>']||Gun.state.is(n, k)}, s: sig});
1503
+ });
1504
+ };
1505
+ var O = SEA.opt;
1506
+ SEA.opt.unpack = function(d, k, n){ var tmp;
1507
+ if(u === d){ return }
1508
+ if(d && (u !== (tmp = d[':']))){ return tmp }
1509
+ k = k || O.fall_key; if(!n && O.fall_val){ n = {}; n[k] = O.fall_val; }
1510
+ if(!k || !n){ return }
1511
+ if(d === n[k]){ return d }
1512
+ if(!SEA.opt.check(n[k])){ return d }
1513
+ var soul = (n && n._ && n._['#']) || O.fall_soul, s = Gun.state.is(n, k) || O.fall_state;
1514
+ if(d && 4 === d.length && soul === d[0] && k === d[1] && fl(s) === fl(d[3])){
1515
+ return d[2];
1516
+ }
1517
+ if(s < SEA.opt.shuffle_attack){
1518
+ return d;
1519
+ }
1520
+ };
1521
+ SEA.opt.shuffle_attack = 1546329600000; // Jan 1, 2019
1522
+ var fl = Math.floor; // TODO: Still need to fix inconsistent state issue.
1523
+ // TODO: Potential bug? If pub/priv key starts with `-`? IDK how possible.
1524
+
1525
+ })(USE, './index');
1526
+ }());
1527
+ } (sea));
1528
+
1529
+ var seaExports = sea.exports;
1530
+ var SEA = /*@__PURE__*/getDefaultExportFromCjs(seaExports);
1531
+
1532
+ const __filename = url.fileURLToPath((typeof document === 'undefined' && typeof location === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : typeof document === 'undefined' ? location.href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('gun-eth.js', document.baseURI).href)));
1533
+ const __dirname = path$1.dirname(__filename);
1534
+
1535
+ let contractAddresses = {
1536
+ PROOF_OF_INTEGRITY_ADDRESS: null,
1537
+ STEALTH_ANNOUNCER_ADDRESS: null
1538
+ };
1539
+
1540
+ try {
1541
+ const rawdata = fs.readFileSync(path$1.join(__dirname, 'contract-address.json'), 'utf8');
1542
+ contractAddresses = JSON.parse(rawdata);
1543
+ console.log("Loaded contract addresses:", contractAddresses);
1544
+ } catch (error) {
1545
+ console.warn("Warning: contract-address.json not found or invalid");
1546
+ }
1547
+
1548
+ const LOCAL_CONFIG = {
1549
+ PROOF_OF_INTEGRITY_ADDRESS: contractAddresses.PROOF_OF_INTEGRITY_ADDRESS,
1550
+ STEALTH_ANNOUNCER_ADDRESS: contractAddresses.STEALTH_ANNOUNCER_ADDRESS,
1551
+ RPC_URL: "http://127.0.0.1:8545",
1552
+ GUN_PEER: "http://localhost:8765/gun",
1553
+ CHAIN_ID: 31337
1554
+ };
1555
+
1556
+ process.env.NODE_ENV === 'development'
1557
+ ? LOCAL_CONFIG.STEALTH_ANNOUNCER_ADDRESS
1558
+ : "0x..."; // Indirizzo su Optimism Sepolia
1559
+
1560
+ const PROOF_OF_INTEGRITY_ADDRESS = process.env.NODE_ENV === 'development'
1561
+ ? LOCAL_CONFIG.PROOF_OF_INTEGRITY_ADDRESS
1562
+ : "0x..."; // Indirizzo su Optimism Sepolia
1563
+
1564
+ process.env.NODE_ENV === 'development'
1565
+ ? LOCAL_CONFIG.RPC_URL
1566
+ : "https://sepolia.optimism.io";
1567
+
1568
+ const PROOF_OF_INTEGRITY_ABI = [
1569
+ {
1570
+ "inputs": [
1571
+ {
1572
+ "internalType": "bytes[]",
1573
+ "name": "nodeIds",
1574
+ "type": "bytes[]"
1575
+ },
1576
+ {
1577
+ "internalType": "bytes32[]",
1578
+ "name": "contentHashes",
1579
+ "type": "bytes32[]"
1580
+ }
1581
+ ],
1582
+ "name": "batchUpdateData",
1583
+ "outputs": [],
1584
+ "stateMutability": "nonpayable",
1585
+ "type": "function"
1586
+ },
1587
+ {
1588
+ "anonymous": false,
1589
+ "inputs": [
1590
+ {
1591
+ "indexed": true,
1592
+ "internalType": "bytes",
1593
+ "name": "nodeId",
1594
+ "type": "bytes"
1595
+ },
1596
+ {
1597
+ "indexed": false,
1598
+ "internalType": "bytes32",
1599
+ "name": "contentHash",
1600
+ "type": "bytes32"
1601
+ },
1602
+ {
1603
+ "indexed": false,
1604
+ "internalType": "address",
1605
+ "name": "updater",
1606
+ "type": "address"
1607
+ }
1608
+ ],
1609
+ "name": "DataUpdated",
1610
+ "type": "event"
1611
+ },
1612
+ {
1613
+ "inputs": [
1614
+ {
1615
+ "internalType": "bytes",
1616
+ "name": "nodeId",
1617
+ "type": "bytes"
1618
+ }
1619
+ ],
1620
+ "name": "getLatestRecord",
1621
+ "outputs": [
1622
+ {
1623
+ "internalType": "bytes32",
1624
+ "name": "",
1625
+ "type": "bytes32"
1626
+ },
1627
+ {
1628
+ "internalType": "uint256",
1629
+ "name": "",
1630
+ "type": "uint256"
1631
+ },
1632
+ {
1633
+ "internalType": "address",
1634
+ "name": "",
1635
+ "type": "address"
1636
+ }
1637
+ ],
1638
+ "stateMutability": "view",
1639
+ "type": "function"
1640
+ },
1641
+ {
1642
+ "inputs": [
1643
+ {
1644
+ "internalType": "bytes",
1645
+ "name": "nodeId",
1646
+ "type": "bytes"
1647
+ },
1648
+ {
1649
+ "internalType": "bytes32",
1650
+ "name": "contentHash",
1651
+ "type": "bytes32"
1652
+ }
1653
+ ],
1654
+ "name": "updateData",
1655
+ "outputs": [],
1656
+ "stateMutability": "nonpayable",
1657
+ "type": "function"
1658
+ },
1659
+ {
1660
+ "inputs": [
1661
+ {
1662
+ "internalType": "bytes",
1663
+ "name": "nodeId",
1664
+ "type": "bytes"
1665
+ },
1666
+ {
1667
+ "internalType": "bytes32",
1668
+ "name": "contentHash",
1669
+ "type": "bytes32"
1670
+ }
1671
+ ],
1672
+ "name": "verifyData",
1673
+ "outputs": [
1674
+ {
1675
+ "internalType": "bool",
1676
+ "name": "",
1677
+ "type": "bool"
1678
+ },
1679
+ {
1680
+ "internalType": "uint256",
1681
+ "name": "",
1682
+ "type": "uint256"
1683
+ },
1684
+ {
1685
+ "internalType": "address",
1686
+ "name": "",
1687
+ "type": "address"
1688
+ }
1689
+ ],
1690
+ "stateMutability": "view",
1691
+ "type": "function"
1692
+ }
1693
+ ];
1694
+
1695
+ // =============================================
1696
+ // IMPORTS AND GLOBAL VARIABLES
1697
+ // =============================================
1698
+
1699
+ let PROOF_CONTRACT_ADDRESS;
1700
+ let rpcUrl = "";
1701
+ let privateKey = "";
1702
+
1703
+ const MESSAGE_TO_SIGN = "Access GunDB with Ethereum";
1704
+
1705
+ // =============================================
1706
+ // UTILITY FUNCTIONS
1707
+ // =============================================
1708
+ /**
1709
+ * Generates a random node ID for GunDB
1710
+ * @returns {string} A random hexadecimal string
1711
+ */
1712
+ function generateRandomId() {
1713
+ return ethers.ethers.hexlify(ethers.ethers.randomBytes(32)).slice(2);
1714
+ }
1715
+
1716
+ /**
1717
+ * Generates a password from a signature.
1718
+ * @param {string} signature - The signature to derive the password from.
1719
+ * @returns {string|null} The generated password or null if generation fails.
1720
+ */
1721
+ function generatePassword(signature) {
1722
+ try {
1723
+ const hexSignature = ethers.ethers.hexlify(signature);
1724
+ const hash = ethers.ethers.keccak256(hexSignature);
1725
+ console.log("Generated password:", hash);
1726
+ return hash;
1727
+ } catch (error) {
1728
+ console.error("Error generating password:", error);
1729
+ return null;
1730
+ }
1731
+ }
1732
+
1733
+ /**
1734
+ * Converts a Gun private key to an Ethereum account.
1735
+ * @param {string} gunPrivateKey - The Gun private key in base64url format.
1736
+ * @returns {Object} An object containing the Ethereum account and public key.
1737
+ */
1738
+ function gunToEthAccount(gunPrivateKey) {
1739
+ // Function to convert base64url to hex
1740
+ const base64UrlToHex = (base64url) => {
1741
+ const padding = "=".repeat((4 - (base64url.length % 4)) % 4);
1742
+ const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/") + padding;
1743
+ const binary = atob(base64);
1744
+ return Array.from(binary, (char) =>
1745
+ char.charCodeAt(0).toString(16).padStart(2, "0")
1746
+ ).join("");
1747
+ };
1748
+
1749
+ // Convert Gun private key to hex format
1750
+ const hexPrivateKey = "0x" + base64UrlToHex(gunPrivateKey);
1751
+
1752
+ // Create an Ethereum wallet from the private key
1753
+ const wallet = new ethers.ethers.Wallet(hexPrivateKey);
1754
+
1755
+ // Get the public address (public key)
1756
+ const publicKey = wallet.address;
1757
+
1758
+ return {
1759
+ account: wallet,
1760
+ publicKey: publicKey,
1761
+ privateKey: hexPrivateKey,
1762
+ };
1763
+ }
1764
+
1765
+ /**
1766
+ * Gets an Ethereum signer based on current configuration
1767
+ * @returns {Promise<ethers.Signer>} The configured signer
1768
+ * @throws {Error} If no valid provider is found
1769
+ */
1770
+ const getSigner = async () => {
1771
+ if (rpcUrl && privateKey) {
1772
+ // Standalone mode with local provider
1773
+ const provider = new ethers.ethers.JsonRpcProvider(rpcUrl, {
1774
+ chainId: LOCAL_CONFIG.CHAIN_ID,
1775
+ name: "localhost"
1776
+ });
1777
+ return new ethers.ethers.Wallet(privateKey, provider);
1778
+ } else if (
1779
+ typeof window !== "undefined" &&
1780
+ typeof window.ethereum !== "undefined"
1781
+ ) {
1782
+ // Browser mode
1783
+ await window.ethereum.request({ method: "eth_requestAccounts" });
1784
+ const provider = new ethers.ethers.BrowserProvider(window.ethereum);
1785
+ return provider.getSigner();
1786
+ } else {
1787
+ throw new Error("No valid Ethereum provider found");
1788
+ }
1789
+ };
1790
+
1791
+ // =============================================
1792
+ // BASIC GUN-ETH CHAIN METHODS
1793
+ // =============================================
1794
+
1795
+ // Set the message to sign
1796
+ Gun.chain.MESSAGE_TO_SIGN = MESSAGE_TO_SIGN;
1797
+
1798
+ /**
1799
+ * Sets standalone configuration for Gun.
1800
+ * @param {string} newRpcUrl - The new RPC URL.
1801
+ * @param {string} newPrivateKey - The new private key.
1802
+ * @returns {Gun} The Gun instance for chaining.
1803
+ */
1804
+ Gun.chain.setSigner = function (newRpcUrl, newPrivateKey) {
1805
+ rpcUrl = newRpcUrl;
1806
+ privateKey = newPrivateKey;
1807
+ console.log("Standalone configuration set");
1808
+ return this;
1809
+ };
1810
+
1811
+ /**
1812
+ * Verifies an Ethereum signature.
1813
+ * @param {string} message - The original message that was signed.
1814
+ * @param {string} signature - The signature to verify.
1815
+ * @returns {Promise<string|null>} The recovered address or null if verification fails.
1816
+ */
1817
+ Gun.chain.verifySignature = async function (message, signature) {
1818
+ try {
1819
+ const recoveredAddress = ethers.ethers.verifyMessage(message, signature);
1820
+ return recoveredAddress;
1821
+ } catch (error) {
1822
+ console.error("Error verifying signature:", error);
1823
+ return null;
1824
+ }
1825
+ };
1826
+
1827
+ /**
1828
+ * Generates a password from a signature.
1829
+ * @param {string} signature - The signature to derive the password from.
1830
+ * @returns {string|null} The generated password or null if generation fails.
1831
+ */
1832
+ Gun.chain.generatePassword = function (signature) {
1833
+ return generatePassword(signature);
1834
+ };
1835
+
1836
+ /**
1837
+ * Creates an Ethereum signature for a given message.
1838
+ * @param {string} message - The message to sign.
1839
+ * @returns {Promise<string|null>} The signature or null if signing fails.
1840
+ */
1841
+ Gun.chain.createSignature = async function (message) {
1842
+ try {
1843
+ // Check if message matches MESSAGE_TO_SIGN
1844
+ if (message !== MESSAGE_TO_SIGN) {
1845
+ throw new Error("Invalid message, valid message is: " + MESSAGE_TO_SIGN);
1846
+ }
1847
+ const signer = await getSigner();
1848
+ const signature = await signer.signMessage(message);
1849
+ console.log("Signature created:", signature);
1850
+ return signature;
1851
+ } catch (error) {
1852
+ console.error("Error creating signature:", error);
1853
+ return null;
1854
+ }
1855
+ };
1856
+
1857
+ // =============================================
1858
+ // KEY PAIR MANAGEMENT
1859
+ // =============================================
1860
+ /**
1861
+ * Creates and stores an encrypted key pair for a given address.
1862
+ * @param {string} address - The Ethereum address to associate with the key pair.
1863
+ * @param {string} signature - The signature to use for encryption.
1864
+ * @returns {Promise<void>}
1865
+ */
1866
+ Gun.chain.createAndStoreEncryptedPair = async function (address, signature) {
1867
+ try {
1868
+ const gun = this;
1869
+ const pair = await SEA.pair();
1870
+ const v_pair = await SEA.pair();
1871
+ const s_pair = await SEA.pair();
1872
+ const password = generatePassword(signature);
1873
+
1874
+ // Save original SEA pairs
1875
+ const encryptedPair = await SEA.encrypt(JSON.stringify(pair), password);
1876
+ const encryptedV_pair = await SEA.encrypt(JSON.stringify(v_pair), password);
1877
+ const encryptedS_pair = await SEA.encrypt(JSON.stringify(s_pair), password);
1878
+
1879
+ // Convert only to get Ethereum addresses
1880
+ const viewingAccount = gunToEthAccount(v_pair.priv);
1881
+ const spendingAccount = gunToEthAccount(s_pair.priv);
1882
+
1883
+ await gun.get("gun-eth").get("users").get(address).put({
1884
+ pair: encryptedPair,
1885
+ v_pair: encryptedV_pair,
1886
+ s_pair: encryptedS_pair,
1887
+ publicKeys: {
1888
+ viewingPublicKey: v_pair.epub, // Use SEA encryption public key
1889
+ viewingPublicKey: v_pair.epub, // Use SEA encryption public key
1890
+ spendingPublicKey: spendingAccount.publicKey, // Use Ethereum address
1891
+ ethViewingAddress: viewingAccount.publicKey // Also save Ethereum address
1892
+ }
1893
+ });
1894
+
1895
+ console.log("Encrypted pairs and public keys stored for:", address);
1896
+ } catch (error) {
1897
+ console.error("Error creating and storing encrypted pair:", error);
1898
+ throw error;
1899
+ }
1900
+ };
1901
+
1902
+ /**
1903
+ * Retrieves and decrypts a stored key pair for a given address.
1904
+ * @param {string} address - The Ethereum address associated with the key pair.
1905
+ * @param {string} signature - The signature to use for decryption.
1906
+ * @returns {Promise<Object|null>} The decrypted key pair or null if retrieval fails.
1907
+ */
1908
+ Gun.chain.getAndDecryptPair = async function (address, signature) {
1909
+ try {
1910
+ const gun = this;
1911
+ const encryptedData = await gun
1912
+ .get("gun-eth")
1913
+ .get("users")
1914
+ .get(address)
1915
+ .get("pair")
1916
+ .then();
1917
+ if (!encryptedData) {
1918
+ throw new Error("No encrypted data found for this address");
1919
+ }
1920
+ const decryptedPair = await SEA.decrypt(encryptedData, signature);
1921
+ console.log(decryptedPair);
1922
+ return decryptedPair;
1923
+ } catch (error) {
1924
+ console.error("Error retrieving and decrypting pair:", error);
1925
+ return null;
1926
+ }
1927
+ };
1928
+
1929
+ // =============================================
1930
+ // PROOF OF INTEGRITY
1931
+ // =============================================
1932
+ /**
1933
+ * Proof of Integrity
1934
+ * @param {string} chain - The blockchain to use (e.g., "optimismSepolia").
1935
+ * @param {string} nodeId - The ID of the node to verify or write.
1936
+ * @param {Object} data - The data to write (if writing).
1937
+ * @param {Function} callback - Callback function to handle the result.
1938
+ * @returns {Gun} The Gun instance for chaining.
1939
+ */
1940
+ Gun.chain.proof = function (chain, nodeId, data, callback) {
1941
+ console.log("Proof plugin called with:", { chain, nodeId, data });
1942
+
1943
+ if (typeof callback !== "function") {
1944
+ console.error("Callback must be a function");
1945
+ return this;
1946
+ }
1947
+
1948
+ const gun = this;
1949
+
1950
+ // Seleziona l'indirizzo basato sulla catena
1951
+ if (chain === "optimismSepolia" || chain === "localhost") {
1952
+ PROOF_CONTRACT_ADDRESS = process.env.NODE_ENV === 'development'
1953
+ ? LOCAL_CONFIG.PROOF_OF_INTEGRITY_ADDRESS
1954
+ : PROOF_OF_INTEGRITY_ADDRESS;
1955
+
1956
+ console.log("Using contract address:", PROOF_CONTRACT_ADDRESS);
1957
+
1958
+ if (!PROOF_CONTRACT_ADDRESS) {
1959
+ callback({ err: "Contract address not found. Did you deploy the contract?" });
1960
+ return this;
1961
+ }
1962
+ } else {
1963
+ callback({ err: "Chain not supported" });
1964
+ return this;
1965
+ }
1966
+
1967
+ // Funzione per verificare on-chain
1968
+ const verifyOnChain = async (nodeId, contentHash) => {
1969
+ console.log("Verifying on chain:", { nodeId, contentHash });
1970
+ const signer = await getSigner();
1971
+ const contract = new ethers.ethers.Contract(
1972
+ PROOF_CONTRACT_ADDRESS,
1973
+ PROOF_OF_INTEGRITY_ABI,
1974
+ signer
1975
+ );
1976
+ const [isValid, timestamp, updater] = await contract.verifyData(
1977
+ ethers.ethers.toUtf8Bytes(nodeId),
1978
+ contentHash
1979
+ );
1980
+ console.log("Verification result:", { isValid, timestamp, updater });
1981
+ return { isValid, timestamp, updater };
1982
+ };
1983
+
1984
+ // Funzione per scrivere on-chain
1985
+ const writeOnChain = async (nodeId, contentHash) => {
1986
+ console.log("Writing on chain:", { nodeId, contentHash });
1987
+ const signer = await getSigner();
1988
+ const contract = new ethers.ethers.Contract(
1989
+ PROOF_CONTRACT_ADDRESS,
1990
+ PROOF_OF_INTEGRITY_ABI,
1991
+ signer
1992
+ );
1993
+ const tx = await contract.updateData(
1994
+ ethers.ethers.toUtf8Bytes(nodeId),
1995
+ contentHash
1996
+ );
1997
+ console.log("Transaction sent:", tx.hash);
1998
+ const receipt = await tx.wait();
1999
+ console.log("Transaction confirmed:", receipt);
2000
+ return tx;
2001
+ };
2002
+
2003
+ // Funzione per ottenere l'ultimo record
2004
+ const getLatestRecord = async (nodeId) => {
2005
+ const signer = await getSigner();
2006
+ const contract = new ethers.ethers.Contract(
2007
+ PROOF_CONTRACT_ADDRESS,
2008
+ PROOF_OF_INTEGRITY_ABI,
2009
+ signer
2010
+ );
2011
+ const [contentHash, timestamp, updater] = await contract.getLatestRecord(
2012
+ ethers.ethers.toUtf8Bytes(nodeId)
2013
+ );
2014
+ console.log("Latest record from blockchain:", {
2015
+ nodeId,
2016
+ contentHash,
2017
+ timestamp,
2018
+ updater,
2019
+ });
2020
+ return { contentHash, timestamp, updater };
2021
+ };
2022
+
2023
+
2024
+ if (nodeId && !data) {
2025
+ // Case 1: User passes only node
2026
+ gun.get(nodeId).once(async (existingData) => {
2027
+ if (!existingData) {
2028
+ if (callback) callback({ err: "Node not found in GunDB" });
2029
+ return;
2030
+ }
2031
+
2032
+ console.log("existingData", existingData);
2033
+
2034
+ // Use stored contentHash instead of recalculating
2035
+ const contentHash = existingData._contentHash;
2036
+ console.log("contentHash", contentHash);
2037
+
2038
+ if (!contentHash) {
2039
+ if (callback) callback({ err: "No content hash found for this node" });
2040
+ return;
2041
+ }
2042
+
2043
+ try {
2044
+ const { isValid, timestamp, updater } = await verifyOnChain(
2045
+ nodeId,
2046
+ contentHash
2047
+ );
2048
+ const latestRecord = await getLatestRecord(nodeId);
2049
+
2050
+ if (isValid) {
2051
+ if (callback)
2052
+ callback({
2053
+ ok: true,
2054
+ message: "Data verified on blockchain",
2055
+ timestamp,
2056
+ updater,
2057
+ latestRecord,
2058
+ });
2059
+ } else {
2060
+ if (callback)
2061
+ callback({
2062
+ ok: false,
2063
+ message: "Data not verified on blockchain",
2064
+ latestRecord,
2065
+ });
2066
+ }
2067
+ } catch (error) {
2068
+ if (callback) callback({ err: error.message });
2069
+ }
2070
+ });
2071
+ } else if (data && !nodeId) {
2072
+ // Case 2: User passes only text (data)
2073
+ const newNodeId = generateRandomId();
2074
+ const dataString = JSON.stringify(data);
2075
+ const contentHash = ethers.ethers.keccak256(ethers.ethers.toUtf8Bytes(dataString));
2076
+
2077
+ gun
2078
+ .get(newNodeId)
2079
+ .put({ ...data, _contentHash: contentHash }, async (ack) => {
2080
+ console.log("ack", ack);
2081
+ if (ack.err) {
2082
+ if (callback) callback({ err: "Error saving data to GunDB" });
2083
+ return;
2084
+ }
2085
+
2086
+ try {
2087
+ const tx = await writeOnChain(newNodeId, contentHash);
2088
+ if (callback)
2089
+ callback({
2090
+ ok: true,
2091
+ message: "Data written to GunDB and blockchain",
2092
+ nodeId: newNodeId,
2093
+ txHash: tx.hash,
2094
+ });
2095
+ } catch (error) {
2096
+ if (callback) callback({ err: error.message });
2097
+ }
2098
+ });
2099
+ } else {
2100
+ if (callback)
2101
+ callback({
2102
+ err: "Invalid input. Provide either nodeId or data, not both.",
2103
+ });
2104
+ }
2105
+
2106
+ return gun;
2107
+ };
2108
+
2109
+ // =============================================
2110
+ // STEALTH ADDRESS CORE FUNCTIONS
2111
+ // =============================================
2112
+ /**
2113
+ * Converts a Gun private key to an Ethereum account.
2114
+ * @param {string} gunPrivateKey - The Gun private key in base64url format.
2115
+ * @returns {Object} An object containing the Ethereum account and public key.
2116
+ */
2117
+ Gun.chain.gunToEthAccount = function (gunPrivateKey) {
2118
+ return gunToEthAccount(gunPrivateKey);
2119
+ };
2120
+
2121
+ /**
2122
+ * Utility function to generate stealth address
2123
+ * @param {string} sharedSecret - The shared secret
2124
+ * @param {string} spendingPublicKey - The spending public key
2125
+ * @returns {Object} The stealth address and private key
2126
+ */
2127
+ function deriveStealthAddress(sharedSecret, spendingPublicKey) {
2128
+ try {
2129
+ // Convert shared secret to bytes
2130
+ const sharedSecretBytes = Buffer.from(sharedSecret, 'base64');
2131
+
2132
+ // Generate stealth private key using shared secret and spending public key
2133
+ const stealthPrivateKey = ethers.ethers.keccak256(
2134
+ ethers.ethers.concat([
2135
+ sharedSecretBytes,
2136
+ ethers.ethers.getBytes(spendingPublicKey)
2137
+ ])
2138
+ );
2139
+
2140
+ // Create stealth wallet
2141
+ const stealthWallet = new ethers.ethers.Wallet(stealthPrivateKey);
2142
+
2143
+ console.log("Debug deriveStealthAddress:", {
2144
+ sharedSecretHex: ethers.ethers.hexlify(sharedSecretBytes),
2145
+ spendingPublicKey,
2146
+ stealthPrivateKey,
2147
+ stealthAddress: stealthWallet.address
2148
+ });
2149
+
2150
+ return {
2151
+ stealthPrivateKey,
2152
+ stealthAddress: stealthWallet.address,
2153
+ wallet: stealthWallet
2154
+ };
2155
+ } catch (error) {
2156
+ console.error("Error in deriveStealthAddress:", error);
2157
+ throw error;
2158
+ }
2159
+ }
2160
+
2161
+ /**
2162
+ * Generate a stealth key and related key pairs
2163
+ * @param {string} recipientAddress - The recipient's Ethereum address
2164
+ * @param {string} signature - The sender's signature to access their keys
2165
+ * @returns {Promise<Object>} Object containing stealth addresses and keys
2166
+ */
2167
+ Gun.chain.generateStealthAddress = async function (recipientAddress, signature) {
2168
+ try {
2169
+ const gun = this;
2170
+
2171
+ // Get recipient's public keys
2172
+ const recipientData = await gun
2173
+ .get("gun-eth")
2174
+ .get("users")
2175
+ .get(recipientAddress)
2176
+ .get("publicKeys")
2177
+ .then();
2178
+
2179
+ if (!recipientData || !recipientData.viewingPublicKey || !recipientData.spendingPublicKey) {
2180
+ throw new Error("Recipient's public keys not found");
2181
+ }
2182
+
2183
+ // Get sender's keys
2184
+ const senderAddress = await this.verifySignature(MESSAGE_TO_SIGN, signature);
2185
+ const password = generatePassword(signature);
2186
+
2187
+ const senderData = await gun
2188
+ .get("gun-eth")
2189
+ .get("users")
2190
+ .get(senderAddress)
2191
+ .then();
2192
+
2193
+ if (!senderData || !senderData.s_pair) {
2194
+ throw new Error("Sender's keys not found");
2195
+ }
2196
+
2197
+ // Decrypt sender's spending pair
2198
+ let spendingKeyPair;
2199
+ try {
2200
+ const decryptedData = await SEA.decrypt(senderData.s_pair, password);
2201
+ spendingKeyPair = typeof decryptedData === 'string' ?
2202
+ JSON.parse(decryptedData) :
2203
+ decryptedData;
2204
+ } catch (error) {
2205
+ console.error("Error decrypting spending pair:", error);
2206
+ throw new Error("Unable to decrypt spending pair");
2207
+ }
2208
+
2209
+ // Generate shared secret using SEA ECDH with encryption public key
2210
+ const sharedSecret = await SEA.secret(recipientData.viewingPublicKey, spendingKeyPair);
2211
+
2212
+ if (!sharedSecret) {
2213
+ throw new Error("Unable to generate shared secret");
2214
+ }
2215
+
2216
+ console.log("Generate shared secret:", sharedSecret);
2217
+
2218
+ const { stealthAddress } = deriveStealthAddress(
2219
+ sharedSecret,
2220
+ recipientData.spendingPublicKey
2221
+ );
2222
+
2223
+ return {
2224
+ stealthAddress,
2225
+ senderPublicKey: spendingKeyPair.epub, // Use encryption public key
2226
+ spendingPublicKey: recipientData.spendingPublicKey
2227
+ };
2228
+
2229
+ } catch (error) {
2230
+ console.error("Error generating stealth address:", error);
2231
+ throw error;
2232
+ }
2233
+ };
2234
+
2235
+ /**
2236
+ * Publish public keys needed to receive stealth payments
2237
+ * @param {string} signature - The signature to authenticate the user
2238
+ * @returns {Promise<void>}
2239
+ */
2240
+ Gun.chain.publishStealthKeys = async function (signature) {
2241
+ try {
2242
+ const gun = this;
2243
+ const address = await this.verifySignature(MESSAGE_TO_SIGN, signature);
2244
+ const password = generatePassword(signature);
2245
+
2246
+ // Get encrypted key pairs
2247
+ const encryptedData = await gun
2248
+ .get("gun-eth")
2249
+ .get("users")
2250
+ .get(address)
2251
+ .then();
2252
+
2253
+ if (!encryptedData || !encryptedData.v_pair || !encryptedData.s_pair) {
2254
+ throw new Error("Keys not found");
2255
+ }
2256
+
2257
+ // Decrypt viewing and spending pairs
2258
+ const viewingKeyPair = JSON.parse(
2259
+ await SEA.decrypt(encryptedData.v_pair, password)
2260
+ );
2261
+ const spendingKeyPair = JSON.parse(
2262
+ await SEA.decrypt(encryptedData.s_pair, password)
2263
+ );
2264
+
2265
+ const viewingAccount = gunToEthAccount(viewingKeyPair.priv);
2266
+ const spendingAccount = gunToEthAccount(spendingKeyPair.priv);
2267
+
2268
+ // Publish only public keys
2269
+ await gun.get("gun-eth").get("users").get(address).get("publicKeys").put({
2270
+ viewingPublicKey: viewingAccount.publicKey,
2271
+ spendingPublicKey: spendingAccount.publicKey,
2272
+ });
2273
+
2274
+ console.log("Stealth public keys published successfully");
2275
+ } catch (error) {
2276
+ console.error("Error publishing stealth keys:", error);
2277
+ throw error;
2278
+ }
2279
+ };
2280
+
2281
+ // =============================================
2282
+ // STEALTH PAYMENT FUNCTIONS
2283
+ // =============================================
2284
+ /**
2285
+ * Recover funds from a stealth address
2286
+ * @param {string} stealthAddress - The stealth address to recover funds from
2287
+ * @param {string} senderPublicKey - The sender's public key used to generate the address
2288
+ * @param {string} signature - The signature to decrypt private keys
2289
+ * @returns {Promise<Object>} Object containing wallet to access funds
2290
+ */
2291
+ Gun.chain.recoverStealthFunds = async function (
2292
+ stealthAddress,
2293
+ senderPublicKey,
2294
+ signature,
2295
+ spendingPublicKey
2296
+ ) {
2297
+ try {
2298
+ const gun = this;
2299
+ const password = generatePassword(signature);
2300
+
2301
+ // Get own key pairs
2302
+ const myAddress = await this.verifySignature(MESSAGE_TO_SIGN, signature);
2303
+ const encryptedData = await gun
2304
+ .get("gun-eth")
2305
+ .get("users")
2306
+ .get(myAddress)
2307
+ .then();
2308
+
2309
+ if (!encryptedData || !encryptedData.v_pair || !encryptedData.s_pair) {
2310
+ throw new Error("Keys not found");
2311
+ }
2312
+
2313
+ // Decrypt viewing and spending pairs
2314
+ let viewingKeyPair;
2315
+ try {
2316
+ const decryptedViewingData = await SEA.decrypt(encryptedData.v_pair, password);
2317
+ viewingKeyPair = typeof decryptedViewingData === 'string' ?
2318
+ JSON.parse(decryptedViewingData) :
2319
+ decryptedViewingData;
2320
+ } catch (error) {
2321
+ console.error("Error decrypting keys:", error);
2322
+ throw new Error("Unable to decrypt keys");
2323
+ }
2324
+
2325
+ // Generate shared secret using SEA ECDH
2326
+ const sharedSecret = await SEA.secret(senderPublicKey, viewingKeyPair);
2327
+
2328
+ if (!sharedSecret) {
2329
+ throw new Error("Unable to generate shared secret");
2330
+ }
2331
+
2332
+ console.log("Recover shared secret:", sharedSecret);
2333
+
2334
+ const { wallet, stealthAddress: recoveredAddress } = deriveStealthAddress(
2335
+ sharedSecret,
2336
+ spendingPublicKey
2337
+ );
2338
+
2339
+ // Verify address matches
2340
+ if (recoveredAddress.toLowerCase() !== stealthAddress.toLowerCase()) {
2341
+ console.error("Mismatch:", {
2342
+ recovered: recoveredAddress,
2343
+ expected: stealthAddress,
2344
+ sharedSecret
2345
+ });
2346
+ throw new Error("Recovered stealth address does not match");
2347
+ }
2348
+
2349
+ return {
2350
+ wallet,
2351
+ address: recoveredAddress,
2352
+ };
2353
+ } catch (error) {
2354
+ console.error("Error recovering stealth funds:", error);
2355
+ throw error;
2356
+ }
2357
+ };
2358
+
2359
+ /**
2360
+ * Announce a stealth payment
2361
+ * @param {string} stealthAddress - The generated stealth address
2362
+ * @param {string} senderPublicKey - The sender's public key
2363
+ * @param {string} spendingPublicKey - The spending public key
2364
+ * @param {string} signature - The sender's signature
2365
+ * @returns {Promise<void>}
2366
+ */
2367
+ Gun.chain.announceStealthPayment = async function (
2368
+ stealthAddress,
2369
+ senderPublicKey,
2370
+ spendingPublicKey,
2371
+ signature,
2372
+ options = { onChain: false }
2373
+ ) {
2374
+ try {
2375
+ const gun = this;
2376
+ const senderAddress = await this.verifySignature(MESSAGE_TO_SIGN, signature);
2377
+
2378
+ if (options.onChain) {
2379
+ // On-chain announcement
2380
+ const signer = await getSigner();
2381
+ const contractAddress = process.env.NODE_ENV === 'development'
2382
+ ? LOCAL_CONFIG.STEALTH_ANNOUNCER_ADDRESS
2383
+ : STEALTH_ANNOUNCER_ADDRESS;
2384
+
2385
+ console.log("Using contract address:", contractAddress);
2386
+
2387
+ const contract = new ethers.ethers.Contract(
2388
+ contractAddress,
2389
+ STEALTH_ANNOUNCER_ABI,
2390
+ signer
2391
+ );
2392
+
2393
+ // Get dev fee from contract
2394
+ const devFee = await contract.devFee();
2395
+ console.log("Dev fee:", devFee.toString());
2396
+
2397
+ // Call contract
2398
+ const tx = await contract.announcePayment(
2399
+ senderPublicKey,
2400
+ spendingPublicKey,
2401
+ stealthAddress,
2402
+ { value: devFee }
2403
+ );
2404
+
2405
+ console.log("Transaction sent:", tx.hash);
2406
+ const receipt = await tx.wait();
2407
+ console.log("Transaction confirmed:", receipt.hash);
2408
+
2409
+ console.log("Stealth payment announced on-chain (dev fee paid)");
2410
+ } else {
2411
+ // Off-chain announcement (GunDB)
2412
+ await gun
2413
+ .get("gun-eth")
2414
+ .get("stealth-payments")
2415
+ .set({
2416
+ stealthAddress,
2417
+ senderAddress,
2418
+ senderPublicKey,
2419
+ spendingPublicKey,
2420
+ timestamp: Date.now(),
2421
+ });
2422
+ console.log("Stealth payment announced off-chain");
2423
+ }
2424
+ } catch (error) {
2425
+ console.error("Error announcing stealth payment:", error);
2426
+ console.error("Error details:", error.stack);
2427
+ throw error;
2428
+ }
2429
+ };
2430
+
2431
+ /**
2432
+ * Get all stealth payments for an address
2433
+ * @param {string} signature - The signature to authenticate the user
2434
+ * @returns {Promise<Array>} List of stealth payments
2435
+ */
2436
+ Gun.chain.getStealthPayments = async function (signature, options = { source: 'both' }) {
2437
+ try {
2438
+ const payments = [];
2439
+
2440
+ if (options.source === 'onChain' || options.source === 'both') {
2441
+ // Get on-chain payments
2442
+ const signer = await getSigner();
2443
+ const contractAddress = process.env.NODE_ENV === 'development'
2444
+ ? LOCAL_CONFIG.STEALTH_ANNOUNCER_ADDRESS
2445
+ : STEALTH_ANNOUNCER_ADDRESS;
2446
+
2447
+ const contract = new ethers.ethers.Contract(
2448
+ contractAddress,
2449
+ STEALTH_ANNOUNCER_ABI,
2450
+ signer
2451
+ );
2452
+
2453
+ try {
2454
+ // Get total number of announcements
2455
+ const totalAnnouncements = await contract.getAnnouncementsCount();
2456
+ const totalCount = Number(totalAnnouncements.toString());
2457
+ console.log("Total on-chain announcements:", totalCount);
2458
+
2459
+ if (totalCount > 0) {
2460
+ // Get announcements in batches of 100
2461
+ const batchSize = 100;
2462
+ const lastIndex = totalCount - 1;
2463
+
2464
+ for(let i = 0; i <= lastIndex; i += batchSize) {
2465
+ const toIndex = Math.min(i + batchSize - 1, lastIndex);
2466
+ const batch = await contract.getAnnouncementsInRange(i, toIndex);
2467
+
2468
+ // For each announcement, try to decrypt
2469
+ for(const announcement of batch) {
2470
+ try {
2471
+ // Verify announcement is valid
2472
+ if (!announcement || !announcement.stealthAddress ||
2473
+ !announcement.senderPublicKey || !announcement.spendingPublicKey) {
2474
+ console.log("Invalid announcement:", announcement);
2475
+ continue;
2476
+ }
2477
+
2478
+ // Try to recover funds to verify if announcement is for us
2479
+ const recoveredWallet = await this.recoverStealthFunds(
2480
+ announcement.stealthAddress,
2481
+ announcement.senderPublicKey,
2482
+ signature,
2483
+ announcement.spendingPublicKey
2484
+ );
2485
+
2486
+ // If no errors thrown, announcement is for us
2487
+ payments.push({
2488
+ stealthAddress: announcement.stealthAddress,
2489
+ senderPublicKey: announcement.senderPublicKey,
2490
+ spendingPublicKey: announcement.spendingPublicKey,
2491
+ timestamp: Number(announcement.timestamp),
2492
+ source: 'onChain',
2493
+ wallet: recoveredWallet
2494
+ });
2495
+
2496
+ } catch (e) {
2497
+ // Not for us, continue
2498
+ console.log(`Announcement not for us: ${announcement.stealthAddress}`);
2499
+ continue;
2500
+ }
2501
+ }
2502
+ }
2503
+ }
2504
+ } catch (error) {
2505
+ console.error("Error retrieving on-chain announcements:", error);
2506
+ }
2507
+ }
2508
+
2509
+ if (options.source === 'offChain' || options.source === 'both') {
2510
+ // Get off-chain payments
2511
+ const gun = this;
2512
+ const offChainPayments = await new Promise((resolve) => {
2513
+ const p = [];
2514
+ gun
2515
+ .get("gun-eth")
2516
+ .get("stealth-payments")
2517
+ .get(recipientAddress)
2518
+ .map()
2519
+ .once((payment, id) => {
2520
+ if (payment?.stealthAddress) {
2521
+ p.push({ ...payment, id, source: 'offChain' });
2522
+ }
2523
+ });
2524
+ setTimeout(() => resolve(p), 2000);
2525
+ });
2526
+
2527
+ payments.push(...offChainPayments);
2528
+ }
2529
+
2530
+ console.log(`Found ${payments.length} stealth payments`);
2531
+ return payments;
2532
+ } catch (error) {
2533
+ console.error("Error retrieving stealth payments:", error);
2534
+ throw error;
2535
+ }
2536
+ };
2537
+
2538
+ /**
2539
+ * Clean up old stealth payments
2540
+ * @param {string} recipientAddress - The recipient's address
2541
+ * @returns {Promise<void>}
2542
+ */
2543
+ Gun.chain.cleanStealthPayments = async function(recipientAddress) {
2544
+ try {
2545
+ const gun = this;
2546
+ const payments = await gun
2547
+ .get("gun-eth")
2548
+ .get("stealth-payments")
2549
+ .get(recipientAddress)
2550
+ .map()
2551
+ .once()
2552
+ .then();
2553
+
2554
+ // Remove empty or invalid nodes
2555
+ if (payments) {
2556
+ Object.keys(payments).forEach(async (key) => {
2557
+ const payment = payments[key];
2558
+ if (!payment || !payment.stealthAddress || !payment.senderPublicKey || !payment.spendingPublicKey) {
2559
+ await gun
2560
+ .get("gun-eth")
2561
+ .get("stealth-payments")
2562
+ .get(recipientAddress)
2563
+ .get(key)
2564
+ .put(null);
2565
+ }
2566
+ });
2567
+ }
2568
+ } catch (error) {
2569
+ console.error("Error cleaning stealth payments:", error);
2570
+ }
2571
+ };
2572
+
2573
+ exports.default = Gun;
2574
+ exports.MESSAGE_TO_SIGN = MESSAGE_TO_SIGN;
2575
+ exports.generatePassword = generatePassword;
2576
+ exports.gunToEthAccount = gunToEthAccount;
2577
+
2578
+ Object.defineProperty(exports, '__esModule', { value: true });
2579
+
2580
+ }));