gun-eth 1.4.0 → 1.4.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ }));