From f0cce073a8b56a15a392b5fa94098c6fb828755f Mon Sep 17 00:00:00 2001 From: akaoio <160459444+akaoio@users.noreply.github.com> Date: Tue, 25 Mar 2025 01:41:36 +0700 Subject: [PATCH] New SEA features! (many new features) (#1400) * feat: create pair with seed, content addressing with shorter hash * feat: create pair using priv/epriv * optimize SEA.pair * feat: globalThis along with window * white labeling * feat: add WebAuthn example and enhance SEA.sign, SEA.verify, SEA check.pub, for WebAuthn support * feat: enhance WebAuthn integration with new put options and improved signature handling * polish SEA.sign and SEA.verify * feat: localize options in SEA.check.pub to enhance security and prevent attacks * fix: correct destructuring of user object to enhance security in SEA * rebuild SEA * feat: support ArrayBuffer as seed for key pair generation in SEA * test: add unit test for hashing ArrayBuffer in SEA * fix: create deterministic key pair from seed * fix: add missing B parameter for ECC curve and implement point validation * feat: add ArrayBuffer support for hashing in SEA and implement corresponding unit test * fix: convert numeric salt to string in PBKDF2 implementation * fix: convert numeric salt option to string in PBKDF2 implementation * improve hashing tests * improve sea.work * rebuild SEA * improve SEA.work and rebuild SEA * enhance SEA encryption handling and improve test coverage for SEA functions --------- Co-authored-by: noname Co-authored-by: x Co-authored-by: x Co-authored-by: noname --- examples/webauthn.html | 20 + examples/webauthn.js | 176 ++++++ gun.js | 5 +- gun.min.js | 2 +- lib/build.js | 138 +++++ lib/serve.js | 1 + package-lock.json | 1189 ++++++++++++++++++++++++++-------------- package.json | 3 +- sea.js | 559 +++++++++++++------ sea/aeskey.js | 2 +- sea/auth.js | 4 +- sea/certify.js | 1 - sea/create.js | 2 +- sea/index.js | 92 ++-- sea/pair.js | 209 +++++-- sea/root.js | 3 +- sea/settings.js | 2 +- sea/sign.js | 71 ++- sea/verify.js | 215 +++++--- sea/work.js | 11 +- src/book.js | 6 +- src/mesh.js | 2 +- src/root.js | 5 +- src/websocket.js | 4 +- test/sea/sea.js | 351 +++++++++++- 25 files changed, 2271 insertions(+), 802 deletions(-) create mode 100644 examples/webauthn.html create mode 100644 examples/webauthn.js create mode 100644 lib/build.js diff --git a/examples/webauthn.html b/examples/webauthn.html new file mode 100644 index 00000000..26b09691 --- /dev/null +++ b/examples/webauthn.html @@ -0,0 +1,20 @@ + + + + + + + + +

WebAuthn Example

+ + + + + + + + \ No newline at end of file diff --git a/examples/webauthn.js b/examples/webauthn.js new file mode 100644 index 00000000..65b25558 --- /dev/null +++ b/examples/webauthn.js @@ -0,0 +1,176 @@ +/* +DISCUSSION WITH AI: +UPGRADE SEA.verify to allow put with signed + +The goal of this dev session is to make the check() function in SEA handle signed puts, but without having the user to be authenticated. It should check if the signature matches the pub, thats it. + +There are files that are related to this mission: sign.js, verify.js and index.js, they are in /sea folder. + +The sign() function in sign.js create signature from given SEA pair. We will modify it to also be able to request WebAuthn signature. We must transform (normalize) the signature of passkey to make it look like SEA signature. But we must keep its current functionalities remain working. + +MUST KEEP IN MIND: webauthn sign doesn't sign the original data alone, instead, it wrap the original data in an object + +The verify() function in verify.js verifies if signature matches pub. We will modify it to also be able to verify new kind of signature created by webauthn passkey. + +The check() function in index.js handles every data packet that flows through the system. It works like a filter to filter out bad (signature not matched) datas. + +We must also modify index.js in sea, the check.pub() function. It handles outgoing and incoming put data. In there we will make it to be able to use SEA.sign with external authenticator which is WebAuthn. + +We must edit slowly. After every edition, we must debug on browser using examples/webauthn.html and examples/webauthn.js to check if it works, then keep editing slowly until it works. + +What should we edit? +The sea.js in the root folder is just a built, it is very heavy and you cannot read it. So we must "blindly" debug in sign.js, verify.js and index.js in /sea folder. + +DO THIS AFTER EVERY EDITION: +npm run buildSea + +We need to re-build sea before testing it. + +BIG UPDATE: +Now after some coding, the sign.js and verify.js work perfectly in test in webauthn.js. Ok. We should now focus in modifying check.pub in index.js. + +How it should work? +At line 147 in index.js, it currently checks: +- if user authenticated (in SEA) and must not have wrapped cert + - if user is writing to his graph + - if he is writing to someone else's graph, must have msg._.msg.opt.cert + +Now what we want is to make it to also allows unauthenticated user to make put, using put(data, null, {opt: {authenticator}}). +It should detect if authenticator exists, then use that in replace for user._.sea. Then the following logic is the same. But we also must keep the current functionalities remain working. + +What I want? +When putting with authenticator (webauthn), the device doesn't provide public key. So user must provide pub via opt.pub if he wants to put data to someone else's graph. If opt.pub doesn't exist, he can only writes to his own graph. +*/ + +console.log("WEB AUTHN EXAMPLE") + +const base64url = { + encode: function(buffer) { + return btoa(String.fromCharCode(...new Uint8Array(buffer))) + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=/g, ''); + }, + decode: function(str) { + str = str.replace(/-/g, '+').replace(/_/g, '/'); + while (str.length % 4) str += '='; + return atob(str); + } +}; + +const data = "Hello, World!" +let credential, pub, signature + +document.getElementById('create').onclick = async () => { + try { + credential = await navigator.credentials.create({ + publicKey: { + challenge: new Uint8Array(16), + rp: { id: "localhost", name: "Example Inc." }, + user: { + id: new TextEncoder().encode("example-user-id"), + name: "Example User", + displayName: "Example User" + }, + // See the list of algos: https://www.iana.org/assignments/cose/cose.xhtml#algorithms + // The 2 algos below are required in order to work with SEA + pubKeyCredParams: [ + { type: "public-key", alg: -7 }, // ECDSA, P-256 curve, for signing + { type: "public-key", alg: -25 }, // ECDH, P-256 curve, for creating shared secrets using SEA.secret + { type: "public-key", alg: -257 } + ], + authenticatorSelection: { + userVerification: "preferred" + }, + timeout: 60000, + attestation: "none" + } + }); + + console.log("Credential:", credential); + + const publicKey = credential.response.getPublicKey(); + const rawKey = new Uint8Array(publicKey); + + console.log("Raw public key bytes:", rawKey); + + const xCoord = rawKey.slice(27, 59); + const yCoord = rawKey.slice(59, 91); + + console.log("X coordinate (32 bytes):", base64url.encode(xCoord)); + console.log("Y coordinate (32 bytes):", base64url.encode(yCoord)); + + pub = `${base64url.encode(xCoord)}.${base64url.encode(yCoord)}`; + console.log("Final pub format:", pub); + + } catch(err) { + console.error('Create credential error:', err); + } +} + +const authenticator = async (data) => { + const challenge = new TextEncoder().encode(data); + const options = { + publicKey: { + challenge, + rpId: window.location.hostname, + userVerification: "preferred", + allowCredentials: [{ + type: "public-key", + id: credential.rawId + }], + timeout: 60000 + } + }; + + const assertion = await navigator.credentials.get(options); + console.log("SIGNED:", {options, assertion}); + return assertion.response; +}; + +document.getElementById('sign').onclick = async () => { + if (!credential) { + console.error("Create credential first"); + return; + } + + try { + signature = await SEA.sign(data, authenticator); + console.log("Signature:", signature); + } catch(err) { + console.error('Signing error:', err); + } +} + +document.getElementById('verify').onclick = async () => { + if (!signature) { + console.error("Sign message first"); + return; + } + + try { + const verified = await SEA.verify(signature, pub); + console.log("Verified:", verified); + } catch(err) { + console.error('Verification error:', err); + } +} + +document.getElementById('put').onclick = async () => { + gun.get(`~${pub}`).get('test').put("hello world", null, { opt: { authenticator }}) + setTimeout(() => { + gun.get(`~${pub}`).get('test').once((data) => { + console.log("Data:", data); + }) + }, 2000) +} + +document.getElementById('put-with-pair').onclick = async () => { + const bob = await SEA.pair() + gun.get(`~${bob.pub}`).get('test').put("this is bob", null, { opt: { authenticator: bob }}) + setTimeout(() => { + gun.get(`~${bob.pub}`).get('test').once((data) => { + console.log("Data:", data); + }) + }, 2000) +} \ No newline at end of file diff --git a/gun.js b/gun.js index 2247c57d..8970984e 100644 --- a/gun.js +++ b/gun.js @@ -758,15 +758,12 @@ Gun.log = function(){ return (!Gun.log.off && C.log.apply(C, arguments)), [].slice.call(arguments).join(' ') }; Gun.log.once = function(w,s,o){ return (o = Gun.log.once)[w] = o[w] || 0, o[w]++ || Gun.log(s) }; - if(typeof window !== "undefined"){ (window.GUN = window.Gun = Gun).window = window } + ((typeof globalThis !== "undefined" && typeof window === "undefined" && typeof WorkerGlobalScope !== "undefined") ? ((globalThis.GUN = globalThis.Gun = Gun).window = globalThis) : (typeof window !== "undefined" ? ((window.GUN = window.Gun = Gun).window = window) : undefined)); try{ if(typeof MODULE !== "undefined"){ MODULE.exports = Gun } }catch(e){} module.exports = Gun; (Gun.window||{}).console = (Gun.window||{}).console || {log: function(){}}; (C = console).only = function(i, s){ return (C.only.i && i === C.only.i && C.only.i++) && (C.log.apply(C, arguments) || s) }; - - ;"Please do not remove welcome log unless you are paying for a monthly sponsorship, thanks!"; - Gun.log.once("welcome", "Hello wonderful person! :) Thanks for using GUN, please ask for help on http://chat.gun.eco if anything takes you longer than 5min to figure out!"); })(USE, './root'); ;USE(function(module){ diff --git a/gun.min.js b/gun.min.js index 83be9594..cf6503aa 100644 --- a/gun.min.js +++ b/gun.min.js @@ -1 +1 @@ -!function(){function T(e,t){return t?require(e):e.slice?T[o(e)]:function(t,n){e(t={exports:{}}),T[o(n)]=t.exports};function o(t){return t.split("/").slice(-1).toString().replace(".js","")}}var n;"undefined"!=typeof module&&(n=module),T(function(t){String.random=function(t,n){var e="";for(t=t||24,n=n||"0123456789ABCDEFGHIJKLMNOPQRSTUVWXZabcdefghijklmnopqrstuvwxyz";0"]||n["<"])||o===n["="]&&(e=n["*"]||n[">"],t.slice(0,(e||"").length)===e||o===n["*"]&&(o!==n[">"]&&o!==n["<"]?t>=n[">"]&&t<=n["<"]:o!==n[">"]&&t>=n[">"]||o!==n["<"]&&t<=n["<"])))},String.hash=function(t,n){if("string"==typeof t){if(n=n||0,!t.length)return n;for(var e=0,o=t.length;e=s.now()-i&&r++<3333?t():a(function(){i=s.now(),t()},r=0)},g=setTimeout,f=(c=g.turn=g.turn||function(t){1==f.push(t)&&l(p)}).s=[],l=g.poll,h=0,p=function(){(u=f[h++])&&u(),h!=f.length&&99!=h||(f=c.s=f.slice(h),h=0),f.length&&l(p)},g=setTimeout,v=g.turn,(g.each=g.each||function(r,a,s,u){u=u||9,function t(n,e,o){if(e=(n=(r||[]).splice(0,u)).length){for(var i=0;i"]||e;if(e)return"number"==typeof(e=e[n])?e:-1/0},n.ify=function(t,n,e,o,i){(t=t||{})._=t._||{},i&&(t._["#"]=i);i=t._[">"]||(t._[">"]={});return void 0!==n&&"_"!==n&&("number"==typeof e&&(i[n]=e),void 0!==o&&(t[n]=o)),t},t.exports=n})(T,"./state"),T(function(t){T("./shim"),t.exports=function(o){var i={s:{}},r=i.s;o=o||{max:999,age:9e3},i.check=function(t){return!!r[t]&&n(t)};var n=i.track=function(t){t=r[t]||(r[t]={});return t.was=i.now=+new Date,i.to||(i.to=setTimeout(i.drop,o.age+9)),t};return i.drop=function(e){i.to=null,i.now=+new Date;var t=Object.keys(r);console.STAT&&console.STAT(i.now,+new Date-i.now,"dup drop keys"),setTimeout.each(t,function(t){var n=r[t];n&&(e||o.age)>i.now-n.was||delete r[t]},0,99)},i}})(T,"./dup"),T(function(t){T("./onto"),t.exports=function(t,n){if(this.on){var e=(this.opt||{}).lack||9e3;if("function"!=typeof t){if(!t)return;var o=t["#"]||t,i=(this.tag||"")[o];return i?(n&&(i=this.on(o,n),clearTimeout(i.err),i.err=setTimeout(function(){i.off()},e)),!0):void 0}o=n&&n["#"]||a(9);if(!t)return o;var r=this.on(o,t,n);return r.err=r.err||setTimeout(function(){r.off(),r.next({err:"Error: No ACK yet.",lack:!0})},e),o}};var a=String.random||function(){return Math.random().toString(36).slice(2)}})(T,"./ask"),T(function(t){function f(t){return t instanceof f?(this._={$:this}).$:this instanceof f?f.create(this._={$:this,opt:t}):new f(t)}function a(t,a){var s=+new Date,n=t._||{},u=n.DBG=t.DBG,c=t["#"],f=g(9),l=Object.keys(a||"").sort(),h=((a||"")._||"")["#"],p=(l.length,t.$._.root),d=a===p.graph[h];console.STAT&&console.STAT(s,((u||n).gk=+new Date)-s,"got keys"),a&&function t(){s=+new Date;for(var n,e,o,i=0,r={};i<9&&(n=l[i++]);)v(r,n,G(a,n),a[n],h);l=l.slice(i),(e={})[h]=r,r=e,d&&((o=function(){}).ram=o.faith=!0),e=l.length,console.STAT&&console.STAT(s,-(s-(s=+new Date)),"got copied some"),u&&(u.ga=+new Date),p.on("in",{"@":c,"#":f,put:r,"%":e?f=g(9):O,$:p.$,_:o,DBG:u}),console.STAT&&console.STAT(s,+new Date-s,"got in"),e&&setTimeout.turn(t)}(),a||p.on("in",{"@":t["#"]})}f.is=function(t){return t instanceof f||t&&t._&&t===t._.$||!1},f.version=.202,(f.chain=f.prototype).toJSON=function(){},T("./shim"),f.valid=T("./valid"),f.state=T("./state"),f.on=T("./onto"),f.dup=T("./dup"),f.ask=T("./ask"),function(){function b(t){if(t)if(t.out!==b){var n,e,o=this.as,i=o.at||o,r=i.$,a=i.dup,s=t.DBG;if((e=t["#"])||(e=t["#"]=g(9)),!a.check(e)){if(a.track(e),e=t._,t._="function"==typeof e?e:function(){},t.$&&t.$===(t.$._||"").$||(t.$=r),t["@"]&&!t.put&&(a=(o=t)["@"]||"",(n=a._)?(n.acks=(n.acks||0)+1,(n.err=o.err)&&(o["@"]=n["#"],_(n)),n.ok=o.ok||n.ok,n.stop||n.crack||(n.crack=n.match&&n.match.push(function(){c(n)})),c(n)):(e=(e=(e=o.$)&&(e=e._)&&(e=e.root)&&e.dup).check(a))&&(o["@"]=e["#"]||o["@"])),!i.ask(t["@"],t)){if(s&&(s.u=+new Date),t.put)return void u(t);t.get&&f.on.get(t,r)}s&&(s.uc=+new Date),this.to.next(t),s&&(s.ua=+new Date),t.nts||t.NTS||(t.out=b,i.on("out",t),s&&(s.ue=+new Date))}}else this.to.next(t)}function u(a){if(a){var s=a._||"",t=s.root=((s.$=a.$||"")._||"").root;if(a["@"]&&s.faith&&!s.miss)return a.out=b,void t.on("out",a);s.latch=t.hatch,s.match=t.hatch=[];var u,c,f,l,h,p,d,g,v,y=a.put,k=s.DBG=a.DBG,m=+new Date;n=n||m,y["#"]&&y["."]||(k&&(k.p=m),s["#"]=a["#"],s.msg=a,s.all=0,s.stun=1,u=Object.keys(y),console.STAT&&console.STAT(m,((k||s).pk=+new Date)-m,"put sort"),c=0,function t(n){if(f!=c){if(!(h=u[f=c]))return console.STAT&&console.STAT(m,((k||s).pd=+new Date)-m,"put"),void _(s);(p=y[h])?(v=p._)?h!==v["#"]?g=w+$(h)+"soul not same.":(d=v[">"])||(g=w+$(h)+"no state."):g=w+$(h)+"no meta.":g=w+$(h)+"no node.",l=Object.keys(p||{})}if(g)return a.err=s.err=g,void _(s);var e,o=0;for(n=n||0;n++<9&&(e=l[o++]);)if("_"!==e){var i=p[e],r=d[e];if(O===r){g=w+$(e)+"on"+$(h)+"no state.";break}if(!A(i)){g=w+$(e)+"on"+$(h)+"bad "+typeof i+$(i);break}!function t(n,e,o,i,r){var a=r._||"",s=a.root,u=s.graph;var c=u[o]||N,f=G(c,e,1),l=c[e];var h=a.DBG;(c=console.STAT)&&(u[o]&&l||(c.has=(c.has||0)+1));u=x();if(uj?j:c),void(console.STAT&&console.STAT((h||a).Hf=+new Date,c,"future"));if(i":i},ok:r.ok,_:a})}(i,e,h,r,a),++S}(l=l.slice(o)).length?D(t):(++c,l=null,t(n))}())}}function e(t){(u=(t._||"").DBG)&&(u.pa=+new Date,u.pm=u.pm||+new Date);var n=this.as,e=n.graph,o=t._,i=t.put,r=i["#"],a=i["."],s=i[":"],u=i[">"];t["#"];(i=o.msg)&&(i=i.put)&&(i=i[r])&&v(i,a,u,s,r),e[r]=v(e[r],a,u,s,r),(i=(n.next||"")[r])&&i.on("in",t),_(o),this.to.next(t)}function _(t,n){var e,o;t.stop||!t.err&&0<--t.stun||(t.stop=1,(e=t.root)&&((o=t.match).end=1,o===e.hatch&&(!(o=t.latch)||o.end?delete e.hatch:e.hatch=o),t.hatch&&t.hatch(),setTimeout.each(t.match,function(t){t&&t()}),!(n=t.msg)||t.err||n.err||(n.out=b,t.root.on("out",n),i())))}function c(t){t&&t.root&&(t.stun||t.acks!==t.all||t.root.on("in",{"@":t["#"],err:t.err,ok:t.err?O:t.ok||{"":1}}))}f.create=function(t){t.root=t.root||t,t.graph=t.graph||{},t.on=t.on||f.on,t.ask=t.ask||f.ask,t.dup=t.dup||f.dup();var n=t.$.opt(t.opt);return t.once||(t.on("in",b,t),t.on("out",b,t),t.on("put",e,t),f.on("create",t),t.on("create",t)),t.once=1,n},f.on.put=u;var n,w="Error: Invalid graph!",$=function(t){return" '"+(""+t).slice(0,9)+"...' "},T=JSON.stringify,j=2147483647,x=f.state,S=0,i=function(){999":v(r.graph[i.soul],n)}}),e))return}else{if(e=i.ask&&i.ask[""],(i.ask||(i.ask={}))[""]=i,p!==i.put&&(i.on("in",i),e))return;t.$=i.$}return r.ask(a,t),r.on("in",t)}if(n["."])return o.get?(t={get:{".":o.get},$:o.$},(i.ask||(i.ask={}))[o.get]=t.$._):t={get:o.lex?t.get:{},$:o.$},i.on("out",t);if(((o.ask||(o.ask={}))[""]=o).get)return n["."]=o.get,(i.ask||(i.ask={}))[o.get]=t.$._,i.on("out",t)}return i.on("out",t)}o.on("in",{put:o.put=p,$:o.$})},r.on.in=function(n,e){var o,t,i=(e=e||this.as).root,r=((o=n.$||(n.$=e.$))||"")._||d,a=n.put||"",s=a["#"],u=a["."],c=p!==a["="]?a["="]:a[":"],f=a[">"]||-1/0;if(p!==n.put&&(p===a["#"]||p===a["."]||p===a[":"]&&p===a["="]||p===a[">"]))return g(a)?void e.on("in",{$:r.back.$,put:{"#":s=r.back.soul,".":u=r.has||r.get,"=":a,">":v(r.back.put,u)},via:n}):(s=((a||"")._||"")["#"])?(o=e.root.$.get(s),setTimeout.each(Object.keys(a).sort(),function(t){"_"!=t&&p!==(f=v(a,t))&&e.on("in",{$:o,put:{"#":s,".":t,"=":a[t],">":f},VIA:n})})):void console.log("chain not yet supported for",a,"...",n,e);(n.seen||"")[e.id]||(((n.seen||(n.seen=function(){}))[e.id]=e)!==r&&(Object.keys(n).forEach(function(t){a[t]=n[t]},a={}),a.get=e.get||a.get,e.soul||e.has?r.soul&&(a.$=e.$,a.$$=a.$$||r.$):a.$$$=a.$$$||e.$,n=a),h(n,e),(e.soul||n.$$)&&f>=v(i.graph[s],u)&&((a=i.$.get(s)._).put=y(a.put,u,f,c,s)),!r.soul&&f>=v(i.graph[s],u)&&(t=(i.$.get(s)._.next||"")[u])&&(t.put=c,"string"==typeof(a=g(c))&&(t.put=i.$.get(a)._.put||c)),this.to&&this.to.next(n),e.any&&setTimeout.each(Object.keys(e.any),function(t){(t=e.any[t])&&t(n)},0,99),e.echo&&setTimeout.each(Object.keys(e.echo),function(t){(t=e.echo[t])&&t.on("in",n)},0,99),((n.$$||"")._||r).soul&&(t=e.next)&&(t=t[u])&&(a={},Object.keys(n).forEach(function(t){a[t]=n[t]}),a.$=(n.$$||n.$).get(a.get=u),delete a.$$,delete a.$$$,t.on("in",a)),l(n,e))},r.on.link=l,r.on.unlink=h;var p,d={},s=String.random,g=r.valid,u=function(t,n){return t&&Object.prototype.hasOwnProperty.call(t,n)},n=r.state,v=n.is,y=n.ify})(T,"./chain"),T(function(t){var g=T("./root");function r(t){var n=this.at||this.on;if(!t||n.soul||n.has)return this.off();if(t=(t=(t=t.$||t)._||t).id){n.map;if((n=this.seen||(this.seen={}))[t])return!0;n[t]=!0}}g.chain.get=function(t,n,e){var o;if("string"==typeof t){if(0==t.length)return(i=this.chain())._.err={err:g.log("0 length key!",t)},n&&n.call(i,i._.err),i;var i=(i=!(i=((f=this._).next||a)[t])?t&&function(t,n){var e=n._,o=e.next,i=n.chain()._;o=o||(e.next={});o[i.get=t]=i,n===e.root.$?i.soul=t:(e.soul||e.has)&&(i.has=t);return i}(t,this):i)&&i.$}else{if("function"==typeof t){if(!0===n)return function(t,n,e){var a,s=t._,u=0;if(a=s.soul||s.link)return n(a,e,s);if(s.jam)return s.jam.push([n,e]);s.jam=[[n,e]],t.get(function(t,n){if(!(v===t.put&&!s.root.opt.super&&(a=Object.keys(s.root.opt.peers).length)&&++u<=a)){n.rid(t);var e=(e=t.$)&&e._||{},o=0;for(a=s.jam,delete s.jam;r=a[o++];){var i=r[0],r=r[1];i&&i(e.link||e.soul||g.valid(t.put)||((t.put||{})._||{})["#"],r,t,n)}}},{out:{get:{".":!0}}})}(this,t,e),this;var c,f=(i=this)._,l=n||{},h=f.root;l.at=f,l.ok=t;var p={};function d(n,t,e){if(!d.stun&&(!(o=h.pass)||o[c])){var o,i=n.$._,r=(n.$$||"")._,a=(r||i).put,s=!i.has&&!i.soul,u={};if(!s&&v!==a||(a=v===((o=n.put)||"")["="]?v===(o||"")[":"]?o:o[":"]:o["="]),"string"==typeof(o=g.valid(a))&&(a=v===(o=h.$.get(o)._.put)?l.not?v:a:o),!l.not||v!==a){if(v===l.stun){if((o=h.stun)&&o.on&&(f.$.back(function(t){if(o.on(""+t.id,u={}),(u.run||0)u.run&&(u.stun&&!u.stun.end||(u.stun=o.on("stun"),u.stun=u.stun&&u.stun.last),u.stun&&!u.stun.end)))return void((u.stun.add||(u.stun.add={}))[c]=function(){d(n,t,1)});if(v===a&&(e=0),(o=h.hatch)&&!o.end&&v===l.hatch&&!e)return p[i.$._.id]?void 0:(p[i.$._.id]=1,void o.push(function(){d(n,t,1)}));p={}}if(h.pass){if(h.pass[c+i.id])return;h.pass[c+i.id]=1}l.on?l.ok.call(i.$,a,i.get,n,t||d):l.v2020?l.ok(n,t||d):(Object.keys(n).forEach(function(t){o[t]=n[t]},o={}),(n=o).put=a,l.ok.call(l.as,n,t||d))}}}return(((d.at=f).any||(f.any={}))[c=String.random(7)]=d).off=function(){d.stun=1,f.any&&delete f.any[c]},d.rid=r,d.id=l.run||++h.once,o=h.pass,(h.pass={})[c]=1,l.out=l.out||{get:{}},f.on("out",l.out),h.pass=o,i}if("number"==typeof t)return this.get(""+t,n,e);if("string"==typeof(o=s(t)))return this.get(o,n,e);(o=this.get.next)&&(i=o(this,t))}return i?n&&"function"==typeof n&&i.get(n,e):((i=this.chain())._.err={err:g.log("Invalid get request!",t)},n&&n.call(i,i._.err)),i};var v,a={},s=g.valid})(T,"./get"),T(function(t){var y=T("./root");function k(n,t){var e,o,i;t&&(t=(t._||"").id||t,e=n.root.stun||(n.root.stun={on:y.on}),o={},n.stun||(n.stun=e.on("stun",function(){})),(i=e.on(""+t))&&i.the.last.next(o),o.run>=n.run||e.on(""+t,function(t){return n.stun.end?(this.off(),void this.to.next(t)):(t.run=t.run||n.run,void(t.stun=t.stun||n.stun))}))}function m(n){var e,t,o,i,r;n.err?m.end(n.stun,n.root):n.todo.length||n.end||!Object.empty(n.wait)||(n.end=1,r=n.$.back(-1)._,e=r.root,t=r.ask(function(t){e.on("ack",t),t.err&&!t.lack&&y.log(t),++o>(n.acks||0)&&this.off(),n.ack&&n.ack(t,this)},n.opt),o=0,i=n.stun,(r=function(){i&&(m.end(i,e),setTimeout.each(Object.keys(i=i.add||""),function(t){(t=i[t])&&t()}))}).hatch=r,n.ack&&!n.ok&&(n.ok=n.acks||9),n.via._.on("out",{put:n.out=n.graph,ok:n.ok&&{"@":n.ok+1},opt:n.opt,"#":t,_:r}))}y.chain.put=function(t,n,g){var e=this,o=e._,i=o.root;(g=g||{}).root=o.root,g.run||(g.run=i.once),k(g,o.id),g.ack=g.ack||n,g.via=g.via||e,g.data=g.data||t,g.soul||(g.soul=o.soul||"string"==typeof n&&n);var v=g.state=g.state||y.state();return"function"==typeof t?t(function(t){g.data=t,e.put(void 0,void 0,g)}):g.soul?(g.$=i.$.get(g.soul),g.todo=[{it:g.data,ref:g.$}],g.turn=g.turn||r,g.ran=g.ran||m,function t(){var n,e,i,r,a,o,s,u=g.todo,c=u.pop(),f=c.it;c.ref&&c.ref._.id;if(k(g,c.ref),(r=c.todo)&&(f=f[e=r.pop()],r.length&&u.push(c)),e&&(u.path||(u.path=[])).push(e),!(n=b(f))&&!(a=y.is(f))){if(!Object.plain(f))return void m.err(g,"Invalid data: "+((o=f)&&(s=o.constructor)&&s.name||typeof o)+" at "+(g.via.back(function(t){t.get&&r.push(t.get)},r=[])||r.join("."))+"."+(u.path||[]).join("."));for(var l=g.seen||(g.seen=[]),h=l.length;h--;)if(f===(r=l[h]).it){n=f=r.link;break}}if(e&&n)c.node=_(c.node,e,v,f);else{if(!g.seen)return void m.err(g,"Data at root of graph must be a node (an object).");g.seen.push(i={it:f,link:{},todo:a?[]:Object.keys(f).sort().reverse(),path:(u.path||[]).slice(),up:c}),c.node=_(c.node,e,v,i.link),!a&&i.todo.length&&u.push(i);var p=g.seen.length;function d(t,n){var e=i.link["#"];n&&(n.off(),n.rid(t));var o=e||t.soul||(r=(t.$$||t.$)._||"").soul||r.link||((r=r.put||"")._||"")["#"]||r["#"]||((r=t.put||"")&&t.$$?r:r["="]||r[":"]||"")["#"];e||k(g,t.$),o||c.link["#"]?(o||(o=[],(t.$$||t.$).back(function(t){return(r=t.soul||t.link)?o.push(r):void o.push(t.get)}),o=o.reverse().join("/")),i.link["#"]=o,a||(((g.graph||(g.graph={}))[o]=i.node||(i.node={_:{}}))._["#"]=o),delete g.wait[p],i.wait&&setTimeout.each(i.wait,function(t){t&&t()}),g.ran(g)):(c.wait||(c.wait=[])).push(function(){d(t,n)})}(g.wait||(g.wait={}))[p]="",r=(i.ref=a?f:e?c.ref.get(e):c.ref)._,(r=f&&(f._||"")["#"]||r.soul||r.link)?d({soul:r}):i.ref.get(d,{run:g.run,v2020:1,out:{get:{".":" "}}})}if(!u.length)return g.ran(g);g.turn(t)}()):function(n){var e,t=n.via._;n.via=n.via.back(function(t){return t.soul||!t.get?t.$:(e=n.data,void((n.data={})[t.get]=e))}),n.via&&n.via._.soul||(n.via=t.root.$.get(((n.data||"")._||"")["#"]||t.$.back("opt.uuid")()));n.via.put(n.data,n.ack,n)}(g),e},m.end=function(t,n){t.end=e,t.the.to===t&&t===t.the.last&&delete n.stun,t.off()},m.err=function(t,n){(t.ack||e).call(t,t.out={err:t.err=y.log(n)}),t.ran(t)};var e=function(){},r=setTimeout.turn,b=y.valid,_=y.state.ify})(T,"./put"),T(function(t){var n=T("./root");T("./chain"),T("./back"),T("./put"),T("./get"),t.exports=n})(T,"./index"),T(function(t){var g=T("./index");g.chain.on=function(t,n,e,o){var i=this._;i.root;if("string"==typeof t)return n?(o=i.on(t,n,e||i,o),e&&e.$&&(e.subs||(e.subs=[])).push(o),this):i.on(t);(n=!0===n?{change:!0}:n||{}).not=1,n.on=1;return this.get(t,n),this},g.chain.once=function(c,f){if(f=f||{},!c)return t=this,g.log.once("valonce","Chainable val is experimental, its behavior and API may change moving forward. Please play with it and report bugs and ideas on how to improve it."),(e=t.chain())._.nix=t.once(function(t,n){e._.on("in",this._)}),e._.lex=t._.lex,e;var t,e,l,h=this._,p=h.root,d=(h.put,String.random(7));return this.get(function(n,e,o,i){var r=this,a=r._,s=a.one||(a.one={});function u(t){a.has||a.soul||(a={put:n,get:e}),void 0===(l=a.put)&&(l=((o.$$||"")._||"").put),"string"!=typeof g.valid(l)||void 0!==(l=p.$.get(l)._.put)||t?i.stun||""!==s[d]&&(s[d]="",(h.soul||h.has)&&i.off(),c.call(r,l,a.get),clearTimeout(s[d])):s[d]=setTimeout(function(){u(1)},f.wait||99)}i.stun||""!==s[d]&&(!0!==(l=g.valid(n))?"string"!=typeof l&&(clearTimeout((h.one||"")[d]),clearTimeout(s[d]),s[d]=setTimeout(u,f.wait||99)):u())},{on:1}),this},g.chain.off=function(){var e,t=this._,o=t.back;if(o)return t.ack=0,(e=o.next)&&e[t.get]&&delete e[t.get],(e=o.ask)&&delete e[t.get],(e=o.put)&&delete e[t.get],(e=t.soul)&&delete o.root.graph[e],(e=t.map)&&Object.keys(e).forEach(function(t,n){(n=e[t]).link&&o.root.$.get(n.link).off()}),(e=t.next)&&Object.keys(e).forEach(function(t,n){e[t].$.off()}),t.on("off",{}),this}})(T,"./on"),T(function(t){var s=T("./index"),o=s.chain.get.next;function u(t){this.to.next(t);var n=this.as,e=t.$._,o=t.put;(e.soul||t.$$)&&((e=n.lex)&&!String.match(t.get||(o||"")["."],e["."]||e["#"]||e)||s.on.link(t,n))}s.chain.get.next=function(t,n){var e;return Object.plain(n)?(e=((e=n["#"])||"")["="]||e)?t.get(e):((e=t.chain()._).lex=n,t.on("in",function(t){String.match(t.get||(t.put||"")["."],n["."]||n["#"]||n)&&e.on("in",t),this.to.next(t)}),e.$):(o||c)(t,n)},s.chain.map=function(r,t,n){var e,a,o=this,i=o._;return Object.plain(r)&&(e=r["."]?r:{".":r},r=void 0),r?(s.log.once("mapfn","Map functions are experimental, their behavior and API may change moving forward. Please play with it and report bugs and ideas on how to improve it."),a=o.chain(),o.map().on(function(t,n,e,o){o=(r||c).call(this,t,n,e,o);if(void 0!==o){if(t===o)return a._.on("in",e);if(s.is(o))return a._.on("in",o._);var i={};Object.keys(e.put).forEach(function(t){i[t]=e.put[t]},i),i["="]=o,a._.on("in",{get:n,put:i})}})):(a=i.each)||((i.each=a=o.chain())._.lex=e||a._.lex||i.lex,a._.nix=o.back("nix"),o.on("in",u,a._)),a};var c=function(){}})(T,"./map"),T(function(t){var s=T("./index");s.chain.set=function(t,i,n){var e,r,a=this,o=a.back(-1);return i=i||function(){},(n=n||{}).item=n.item||t,(e=((t||"")._||"")["#"])&&((t={})["#"]=e),"string"==typeof(r=s.valid(t))?a.get(e=r).put(t,i,n):s.is(t)?(a.put(function(o){t.get(function(t,n,e){return t?((r={})[t]={"#":t},void o(r)):i.call(a,{err:s.log('Only a node can be linked! Not "'+e.put+'"!')})},!0)}),t):(Object.plain(t)&&(t=o.get(e=a.back("opt.uuid")()).put(t)),a.get(e||o.back("opt.uuid")(7)).put(t,i,n))}})(T,"./set"),T(function(t){T("./shim");function n(){}var r=JSON.parseAsync||function(t,n,e){var o=+new Date;try{n(void 0,JSON.parse(t,e),w.sucks(+new Date-o))}catch(t){n(t)}},w=JSON.stringifyAsync||function(t,n,e,o){var i=+new Date;try{n(void 0,JSON.stringify(t,e,o),w.sucks(+new Date-i))}catch(t){n(t)}};function e(u){var d=function(){},g=u.opt||{};g.log=g.log||console.log,g.gap=g.gap||g.wait||0,g.max=g.max||.3*(g.memory?999*g.memory*999:3e8),g.pack=g.pack||.01*g.max*.01,g.puff=g.puff||9;var v=setTimeout.turn||setTimeout,y=u.dup,k=y.check,m=y.track,i=(new Date,d.hear=function(t,a){if(t){if(g.max<=t.length)return d.say({dam:"!",err:"Message too big!"},a);d===this&&(i.d+=t.length||0,++i.c);var n,e=a.SH=+new Date,o=t[0];if("["===o)return r(t,function(t,i){if(t||!i)return d.say({dam:"!",err:"DAM JSON parse error."},a);console.STAT&&console.STAT(+new Date,i.length,"# on hear batch");var r=g.puff;!function t(){for(var n,e=+new Date,o=0;o<"])&&"string"==typeof r&&r.slice(0,99).split(",").forEach(function(t){this[t]=1},t._.yo={}),r=t.dam)return(r=d.hear[r])&&r(t,n,u),void m(o);(r=t.ok)&&(t._.near=r["/"]);e=+new Date;s&&(s.is=e),n.SI=o,u.on("in",d.last=t),s&&(s.hd=+new Date),console.STAT&&console.STAT(e,+new Date-e,t.get?"msg get":t.put?"msg put":"msg"),(r=m(o)).via=n,t.get&&(r.it=t),a&&m(a),d.leap=d.last=null}};function b(t){var n=t.batch,e="string"==typeof n;if(e&&(n+="]"),t.batch=t.tail=null,n&&(e?!(n.length<3):n.length)){if(!e)try{n=1===n.length?n[0]:JSON.stringify(n)}catch(t){return g.log("DAM JSON stringify error",t)}n&&_(n,t)}}function _(n,e){try{var t=e.wire;e.say?e.say(n):t.send&&t.send(n),d.say.d+=n.length||0,++d.say.c}catch(t){(e.queue=e.queue||[]).push(n)}}i.c=i.d=0,function(){var h,p=0;function t(t,n){var e;return n instanceof Object?(Object.keys(n).sort().forEach(o,{to:e={},on:n}),e):n}function o(t){this.to[t]=this.on[t]}d.hash=function(o,i){var r,a,s,u=+new Date;w(o.put,function t(n,e){e=(a=a||(s=e||"")).slice(0,32768);r=String.hash(e,r),(a=a.slice(32768))?v(t,0):(console.STAT&&console.STAT(u,+new Date-u,"say json+hash"),o._.$put=s,o["##"]=r,d.say(o,i),delete o._.$put)},t)};d.say=function(r,a){var t;if((t=this)&&(t=t.to)&&t.next&&t.next(r),!r)return!1;var n,s,u=r["@"],c=r._||(r._=function(){}),e=r.DBG,o=+new Date;if(c.y=c.y||o,a||e&&(e.y=o),(n=r["#"])||(n=r["#"]=String.random(9)),h||m(n),r["##"]||$===r.put||c.via||!u){if(!(a=!a&&u?(t=y.s[u])&&(t.via||(t=t.it)&&(t=t._)&&t.via)||(t=d.last)&&u===t["#"]&&d.leap:a)&&u)return y.s[u]?void 0:(console.STAT&&console.STAT(+new Date,++p,"total no peer to ack to"),!1);if(!a&&d.way)return d.way(r);if(e&&(e.yh=+new Date),s=c.raw){if(e&&(e.yr=+new Date),!a||!a.id){if(!Object.plain(a||g.peers))return!1;var o=+new Date,f=(g.puff,g.peers),l=Object.keys(a||g.peers||{});return console.STAT&&console.STAT(o,+new Date-o,"peer keys"),void function t(){var n=+new Date;h=1;var e=c.raw;c.raw=s;for(var o,i=0;i<9&&(o=(l||"")[i++]);)(o=f[o]||(a||"")[o])&&d.say(r,o);c.raw=e,h=0,l=l.slice(i),console.STAT&&console.STAT(n,+new Date-n,"say loop"),l.length&&(v(t,0),u&&m(u))}()}if(!a.wire&&d.wire&&d.wire(a),n!==a.last){if(a.last=n,a===c.via)return!1;if((t=c.yo)&&(t[a.url]||t[a.pid]||t[a.id]))return!1;if(console.STAT&&console.STAT(o,((e||c).yp=+new Date)-(c.y||o),"say prep"),!h&&u&&m(u),a.batch){if(a.tail=(t=a.tail||0)+s.length,a.tail<=g.pack)return void(a.batch+=(t?",":"")+s);b(a)}a.batch="[";var i=+new Date;setTimeout(function(){console.STAT&&console.STAT(i,+new Date-i,"0ms TO"),b(a)},g.gap),_(s,a),console.STAT&&u===a.SI&&console.STAT(o,+new Date-a.SH,"say ack")}}else d.raw(r,a)}else d.hash(r,a)};d.say.c=d.say.d=0,d.raw=function(e,o){if(!e)return"";var i,r=e._||{};if(s=r.raw)return s;if("string"==typeof e)return e;var t=e["##"],n=e["@"];if(t&&n){if(!r.via&&k(n+t))return!1;if((s=(y.s[n]||"").it)||(s=d.last)&&n===s["#"]){if(t===s["##"])return!1;s["##"]||(s["##"]=t)}}if(!e.dam&&!e["@"]){var a,s,u=0,c=[];for(a in s=g.peers){var f=s[a];if(c.push(f.url||f.pid||f.id),6<++u)break}1<"]=c.join())}if(e.put&&(s=e.ok)&&(e.ok={"@":(s["@"]||1)-1,"/":s["/"]==e._.near?d.near:s["/"]}),i=r.$put)return s={},Object.keys(e).forEach(function(t){s[t]=e[t]}),s.put=":])([:",void w(s,function(t,n){t||(t=+new Date,s=n.indexOf('"put":":])([:"'),l($,n=n.slice(0,s+6)+i+n.slice(s+14)),console.STAT&&console.STAT(t,+new Date-t,"say slice"))});function l(t,n){t||(r.raw=n,d.say(e,o))}w(e,l)}}(),d.near=0,d.hi=function(n){var t;n.wire?(n.id?g.peers[n.url||n.id]=n:(t=n.id=n.id||n.url||String.random(9),d.say({dam:"?",pid:u.opt.pid},g.peers[t]=n),delete y.s[n.last]),n.met||(d.near++,n.met=+new Date,u.on("hi",n)),t=n.queue,n.queue=[],setTimeout.each(t||[],function(t){_(t,n)},0,9)):d.wire(n.length?{url:n,id:n}:n)},d.bye=function(t){t.met&&--d.near,delete t.met,u.on("bye",t);var n=+new Date;n-=t.met||n,d.bye.time=((d.bye.time||n)+n)/2},d.hear["!"]=function(t,n){g.log("Error:",t.err)},d.hear["?"]=function(t,n){t.pid&&(n.pid||(n.pid=t.pid),t["@"])||(d.say({dam:"?",pid:g.pid,"@":t["#"]},n),delete y.s[n.last])},d.hear.mob=function(t,n){!t.peers||(t=(t=Object.keys(t.peers))[Math.random()*t.length>>0])&&(d.bye(n),d.hi(t))},u.on("create",function(t){t.opt.pid=t.opt.pid||String.random(9),this.to.next(t),t.on("out",d.say)}),u.on("bye",function(t,n){t=g.peers[t.id||t]||t,this.to.next(t),t.bye?t.bye():(n=t.wire)&&n.close&&n.close(),delete g.peers[t.id],t.wire=null});var e={};return u.on("bye",function(t,n){this.to.next(t),(n=console.STAT)&&(n.peers=d.near),(n=t.url)&&(e[n]=!0,setTimeout(function(){delete e[n]},g.lack||9e3))}),u.on("hi",function(e,t){this.to.next(e),(t=console.STAT)&&(t.peers=d.near),g.super||(9999<(t=Object.keys(u.next||"")).length&&!console.SUBS&&console.log(console.SUBS="Warning: You have more than 10K live GETs, which might use more bandwidth than your screen can show - consider `.off()`."),setTimeout.each(t,function(n){var t=u.next[n];g.super||(t.ask||"")[""]?d.say({get:{"#":n}},e):setTimeout.each(Object.keys(t.ask||""),function(t){t&&d.say({"##":String.hash((u.graph[n]||"")[t]),get:{"#":n,".":t}},e)})}))}),d}w.sucks=function(t){99"],n[":"],e),f&&4999880"]||n["<"])||o===n["="]&&(e=n["*"]||n[">"]||n["<"],t.slice(0,(e||"").length)===e||o===n["*"]&&(o!==n[">"]&&o!==n["<"]?t>=n[">"]&&t<=n["<"]:o!==n[">"]&&t>=n[">"]||o!==n["<"]&&t<=n["<"])))},h.text.hash=h.text.hash||function(t,n){if(l("text.hash"),"string"==typeof t){if(n=n||0,!t.length)return n;for(var e=0,o=t.length;e"]||n["<"])||o===n["="]&&(e=n["*"]||n[">"],t.slice(0,(e||"").length)===e||o===n["*"]&&(o!==n[">"]&&o!==n["<"]?t>=n[">"]&&t<=n["<"]:o!==n[">"]&&t>=n[">"]||o!==n["<"]&&t<=n["<"])))},String.hash=function(t,n){if("string"==typeof t){if(n=n||0,!t.length)return n;for(var e=0,o=t.length;e=a.now()-i&&r++<3333?t():s(function(){i=a.now(),t()},r=0)},g=setTimeout,f=(c=g.turn=g.turn||function(t){1==f.push(t)&&l(p)}).s=[],l=g.poll,h=0,p=function(){(u=f[h++])&&u(),h!=f.length&&99!=h||(f=c.s=f.slice(h),h=0),f.length&&l(p)},g=setTimeout,v=g.turn,(g.each=g.each||function(r,s,a,u){u=u||9,function t(n,e,o){if(e=(n=(r||[]).splice(0,u)).length){for(var i=0;i>=0])||"").substring())||(e(i[a+1])||"").substring()<=t)&&a!=r;)a+=o<=t?(s-(r=a))/2:-((s=a)-r)/2;return a}function f(t,n){return"string"!=typeof t.from?t.from:t.from=h(n=n||t.from||"")}function l(t){t=t||function(t){return t};for(var n,e=0,o=m(this),i=[],r=this.book.parse||function(){};n=o[e++];)i.push(t(this.get(n=n.word||r(n)||n),n,this));return i}function s(t,n){var e,o=this;if(e=o.all[t])return o(t,n);var i=o.page(t=""+t);return i&&i.from&&(o.get(t),o.all[t])?o(t,n):(e=o.all[t]={word:t,is:n,page:i,substring:d,toString:g},i.first=i.first>0,r=i,s={first:o[r].substring(),size:0,substring:v,toString:y,book:n,get:n,read:l},a=s.from=[];for(;e=o[i++];)a.push(e),s.size+=(e.is||"").length||1,e.page=s;t.from=t.from.slice(0,r),t.size-=s.size,n.list.splice(c(s.first,n.list)+1,0,s),n.split&&n.split(s,t)}(i,o),o)}function h(t){return function t(n,e){var o,i;if((o=n.indexOf(""))<0)return n;if(""==n[0]&&1==n.length)return[];if((i=o+2+parseInt((i=n[o+1]).substring(0,i.indexOf('"'))||i))!=i)return[];n[o]=n.slice(o,i).join(e||"|");return n.slice(0,o+1).concat(t(n.slice(i),e))}((t=t||"").substring(1,t.length-1).split(t[0]),t[0])}function p(t){return(t||"").length||1}function d(t,n){return this.word}function g(){return this.text=this.text||":"+u.encode(this.word)+":"+u.encode(this.is)+":"}function v(t,n){return(this.first||this.word||u.decode((f(this)||"")[0]||"")).substring(t,n)}function y(){return this.text=this.text||function(t){t.limbo&&m(t);return"string"==typeof t.from?t.from:"|"+(t.from||[]).join("|")+"|"}(this)}function m(t,n){var e=t.from="string"==typeof t.from?h(t.from):t.from||[];return(n=n||t.limbo)?function(t,n){n=n||t.limbo||[],t.limbo=null;var e,o=0,i=t.from;for(;e=n[o++];)a(e.word,t)?i[a.i]=e:i.push(e);return i}(t).sort(function(t,n){return(t.word||u.decode(""+t))<(n.word||u.decode(""+n))?-1:1}):e}u.slot=h,u.encode=function(t,n,e){switch(n=n||"|",e=e||String.fromCharCode(32),typeof t){case"string":for(var o=t.indexOf(n),i=0;-1!=o;)i++,o=t.indexOf(n,o+1);return(i?n+i:"")+'"'+t;case"number":return t<0?""+t:"+"+t;case"boolean":return t?"+":"-";case"object":if(!t)return" ";for(var r,s=Object.keys(t).sort(),o=0,a=n;r=s[o++];)a+=e+u.encode(r,n,e)+e+u.encode(t[r],n,e)+e+n;return a}},u.decode=function(t,n){if("string"==typeof t){switch(t){case" ":return null;case"-":return!1;case"+":return!0}switch(t[0]){case"-":case"+":return parseFloat(t);case'"':return t.slice(1)}return t.slice(t.indexOf('"')+1)}},u.hash=function(t,n){if("string"==typeof t){if(n=n||0,!t.length)return n;for(var e=0,o=t.length;e"]||e;if(e)return"number"==typeof(e=e[n])?e:-1/0},n.ify=function(t,n,e,o,i){(t=t||{})._=t._||{},i&&(t._["#"]=i);i=t._[">"]||(t._[">"]={});return void 0!==n&&"_"!==n&&("number"==typeof e&&(i[n]=e),void 0!==o&&(t[n]=o)),t},t.exports=n})(x,"./state"),x(function(t){x("./shim"),t.exports=function(o){var i={s:{}},r=i.s;o=o||{max:999,age:9e3},i.check=function(t){return!!r[t]&&e(t)};var e=i.track=function(t){var n=r[t]||(r[t]={});return n.was=i.now=+new Date,i.to||(i.to=setTimeout(i.drop,o.age+9)),e.ed&&e.ed(t),n};return i.drop=function(e){i.to=null,i.now=+new Date;var t=Object.keys(r);console.STAT&&console.STAT(i.now,+new Date-i.now,"dup drop keys"),setTimeout.each(t,function(t){var n=r[t];n&&(e||o.age)>i.now-n.was||delete r[t]},0,99)},i}})(x,"./dup"),x(function(t){x("./onto"),t.exports=function(t,n){if(this.on){var e=(this.opt||{}).lack||9e3;if("function"!=typeof t){if(!t)return;var o=t["#"]||t,i=(this.tag||"")[o];return i?(n&&(i=this.on(o,n),clearTimeout(i.err),i.err=setTimeout(function(){i.off()},e)),!0):void 0}o=n&&n["#"]||s(9);if(!t)return o;var r=this.on(o,t,n);return r.err=r.err||setTimeout(function(){r.off(),r.next({err:"Error: No ACK yet.",lack:!0})},e),o}};var s=String.random||function(){return Math.random().toString(36).slice(2)}})(x,"./ask"),x(function(t){function f(t){return t instanceof f?(this._={$:this}).$:this instanceof f?f.create(this._={$:this,opt:t}):new f(t)}function a(t,s){var a=+new Date,n=t._||{},u=n.DBG=t.DBG,c=t["#"],f=g(9),l=Object.keys(s||"").sort(),h=((s||"")._||"")["#"],p=(l.length,t.$._.root),d=s===p.graph[h];console.STAT&&console.STAT(a,((u||n).gk=+new Date)-a,"got keys"),s&&function t(){a=+new Date;for(var n,e,o,i=0,r={};i<9&&(n=l[i++]);)v(r,n,G(s,n),s[n],h);l=l.slice(i),(e={})[h]=r,r=e,d&&((o=function(){}).ram=o.faith=!0),e=l.length,console.STAT&&console.STAT(a,-(a-(a=+new Date)),"got copied some"),u&&(u.ga=+new Date),p.on("in",{"@":c,"#":f,put:r,"%":e?f=g(9):O,$:p.$,_:o,DBG:u}),console.STAT&&console.STAT(a,+new Date-a,"got in"),e&&setTimeout.turn(t)}(),s||p.on("in",{"@":t["#"]})}f.is=function(t){return t instanceof f||t&&t._&&t===t._.$||!1},f.version=.202,(f.chain=f.prototype).toJSON=function(){},x("./shim"),f.valid=x("./valid"),f.state=x("./state"),f.on=x("./onto"),f.dup=x("./dup"),f.ask=x("./ask"),function(){function b(t){if(t)if(t.out!==b){var n,e,o=this.as,i=o.at||o,r=i.$,s=i.dup,a=t.DBG;if((e=t["#"])||(e=t["#"]=g(9)),!s.check(e)){if(s.track(e),e=t._,t._="function"==typeof e?e:function(){},t.$&&t.$===(t.$._||"").$||(t.$=r),t["@"]&&!t.put&&(s=(o=t)["@"]||"",(n=s._)?(n.acks=(n.acks||0)+1,(n.err=o.err)&&(o["@"]=n["#"],w(n)),n.ok=o.ok||n.ok,n.stop||n.crack||(n.crack=n.match&&n.match.push(function(){c(n)})),c(n)):(e=(e=(e=o.$)&&(e=e._)&&(e=e.root)&&e.dup).check(s))&&(o["@"]=e["#"]||o["@"])),!i.ask(t["@"],t)){if(a&&(a.u=+new Date),t.put)return void u(t);t.get&&f.on.get(t,r)}a&&(a.uc=+new Date),this.to.next(t),a&&(a.ua=+new Date),t.nts||t.NTS||(t.out=b,i.on("out",t),a&&(a.ue=+new Date))}}else this.to.next(t)}function u(s){if(s){var a=s._||"",t=a.root=((a.$=s.$||"")._||"").root;if(s["@"]&&a.faith&&!a.miss)return s.out=b,void t.on("out",s);a.latch=t.hatch,a.match=t.hatch=[];var u,c,f,l,h,p,d,g,v,y=s.put,m=a.DBG=s.DBG,k=+new Date;n=n||k,y["#"]&&y["."]||(m&&(m.p=k),a["#"]=s["#"],a.msg=s,a.all=0,a.stun=1,u=Object.keys(y),console.STAT&&console.STAT(k,((m||a).pk=+new Date)-k,"put sort"),c=0,function t(n){if(f!=c){if(!(h=u[f=c]))return console.STAT&&console.STAT(k,((m||a).pd=+new Date)-k,"put"),void w(a);(p=y[h])?(v=p._)?h!==v["#"]?g=_+$(h)+"soul not same.":(d=v[">"])||(g=_+$(h)+"no state."):g=_+$(h)+"no meta.":g=_+$(h)+"no node.",l=Object.keys(p||{})}if(g)return s.err=a.err=g,void w(a);var e,o=0;for(n=n||0;n++<9&&(e=l[o++]);)if("_"!==e){var i=p[e],r=d[e];if(O===r){g=_+$(e)+"on"+$(h)+"no state.";break}if(!A(i)){g=_+$(e)+"on"+$(h)+"bad "+typeof i+$(i);break}!function t(n,e,o,i,r){var s=r._||"",a=s.root,u=a.graph;var c=u[o]||N,f=G(c,e,1),l=c[e];var h=s.DBG;(c=console.STAT)&&(u[o]&&l||(c.has=(c.has||0)+1));u=j();if(uT?T:c),void(console.STAT&&console.STAT((h||s).Hf=+new Date,c,"future"));if(i":i},ok:r.ok,_:s})}(i,e,h,r,s),++S}(l=l.slice(o)).length?D(t):(++c,l=null,t(n))}())}}function e(t){(u=(t._||"").DBG)&&(u.pa=+new Date,u.pm=u.pm||+new Date);var n=this.as,e=n.graph,o=t._,i=t.put,r=i["#"],s=i["."],a=i[":"],u=i[">"];t["#"];(i=o.msg)&&(i=i.put)&&(i=i[r])&&v(i,s,u,a,r),e[r]=v(e[r],s,u,a,r),(i=(n.next||"")[r])&&i.on("in",t),w(o),this.to.next(t)}function w(t,n){var e,o;t.stop||!t.err&&0<--t.stun||(t.stop=1,(e=t.root)&&((o=t.match).end=1,o===e.hatch&&(!(o=t.latch)||o.end?delete e.hatch:e.hatch=o),t.hatch&&t.hatch(),setTimeout.each(t.match,function(t){t&&t()}),!(n=t.msg)||t.err||n.err||(n.out=b,t.root.on("out",n),i())))}function c(t){t&&t.root&&(t.stun||t.acks!==t.all||t.root.on("in",{"@":t["#"],err:t.err,ok:t.err?O:t.ok||{"":1}}))}f.create=function(t){t.root=t.root||t,t.graph=t.graph||{},t.on=t.on||f.on,t.ask=t.ask||f.ask,t.dup=t.dup||f.dup();var n=t.$.opt(t.opt);return t.once||(t.on("in",b,t),t.on("out",b,t),t.on("put",e,t),f.on("create",t),t.on("create",t)),t.once=1,n},f.on.put=u;var n,_="Error: Invalid graph!",$=function(t){return" '"+(""+t).slice(0,9)+"...' "},x=JSON.stringify,T=2147483647,j=f.state,S=0,i=function(){999":v(r.graph[i.soul],n)}}),e))return}else{if(e=i.ask&&i.ask[""],(i.ask||(i.ask={}))[""]=i,p!==i.put&&(i.on("in",i),e))return;t.$=i.$}return r.ask(s,t),r.on("in",t)}if(n["."])return o.get?(t={get:{".":o.get},$:o.$},(i.ask||(i.ask={}))[o.get]=t.$._):t={get:o.lex?t.get:{},$:o.$},i.on("out",t);if(((o.ask||(o.ask={}))[""]=o).get)return n["."]=o.get,(i.ask||(i.ask={}))[o.get]=t.$._,i.on("out",t)}return i.on("out",t)}o.on("in",{put:o.put=p,$:o.$})},r.on.in=function(n,e){var o,t,i=(e=e||this.as).root,r=((o=n.$||(n.$=e.$))||"")._||d,s=n.put||"",a=s["#"],u=s["."],c=p!==s["="]?s["="]:s[":"],f=s[">"]||-1/0;if(p!==n.put&&(p===s["#"]||p===s["."]||p===s[":"]&&p===s["="]||p===s[">"]))return g(s)?void e.on("in",{$:r.back.$,put:{"#":a=r.back.soul,".":u=r.has||r.get,"=":s,">":v(r.back.put,u)},via:n}):(a=((s||"")._||"")["#"])?(o=e.root.$.get(a),setTimeout.each(Object.keys(s).sort(),function(t){"_"!=t&&p!==(f=v(s,t))&&e.on("in",{$:o,put:{"#":a,".":t,"=":s[t],">":f},VIA:n})})):void console.log("chain not yet supported for",s,"...",n,e);(n.seen||"")[e.id]||(((n.seen||(n.seen=function(){}))[e.id]=e)!==r&&(Object.keys(n).forEach(function(t){s[t]=n[t]},s={}),s.get=e.get||s.get,e.soul||e.has?r.soul&&(s.$=e.$,s.$$=s.$$||r.$):s.$$$=s.$$$||e.$,n=s),h(n,e),(e.soul||n.$$)&&f>=v(i.graph[a],u)&&((s=i.$.get(a)._).put=y(s.put,u,f,c,a)),!r.soul&&f>=v(i.graph[a],u)&&(t=(i.$.get(a)._.next||"")[u])&&(t.put=c,"string"==typeof(s=g(c))&&(t.put=i.$.get(s)._.put||c)),this.to&&this.to.next(n),e.any&&setTimeout.each(Object.keys(e.any),function(t){(t=e.any[t])&&t(n)},0,99),e.echo&&setTimeout.each(Object.keys(e.echo),function(t){(t=e.echo[t])&&t.on("in",n)},0,99),((n.$$||"")._||r).soul&&(t=e.next)&&(t=t[u])&&(s={},Object.keys(n).forEach(function(t){s[t]=n[t]}),s.$=(n.$$||n.$).get(s.get=u),delete s.$$,delete s.$$$,t.on("in",s)),l(n,e))},r.on.link=l,r.on.unlink=h;var p,d={},a=String.random,g=r.valid,u=function(t,n){return t&&Object.prototype.hasOwnProperty.call(t,n)},n=r.state,v=n.is,y=n.ify})(x,"./chain"),x(function(t){var g=x("./root");function r(t){var n=this.at||this.on;if(!t||n.soul||n.has)return this.off();if(t=(t=(t=t.$||t)._||t).id){n.map;if((n=this.seen||(this.seen={}))[t])return!0;n[t]=!0}}g.chain.get=function(t,n,e){var o;if("string"==typeof t){if(0==t.length)return(i=this.chain())._.err={err:g.log("0 length key!",t)},n&&n.call(i,i._.err),i;var i=(i=!(i=((f=this._).next||s)[t])?t&&function(t,n){var e=n._,o=e.next,i=n.chain()._;o=o||(e.next={});o[i.get=t]=i,n===e.root.$?i.soul=t:(e.soul||e.has)&&(i.has=t);return i}(t,this):i)&&i.$}else{if("function"==typeof t){if(!0===n)return function(t,n,e){var s,a=t._,u=0;if(s=a.soul||a.link)return n(s,e,a);if(a.jam)return a.jam.push([n,e]);a.jam=[[n,e]],t.get(function(t,n){if(!(v===t.put&&!a.root.opt.super&&(s=Object.keys(a.root.opt.peers).length)&&++u<=s)){n.rid(t);var e=(e=t.$)&&e._||{},o=0;for(s=a.jam,delete a.jam;r=s[o++];){var i=r[0],r=r[1];i&&i(e.link||e.soul||g.valid(t.put)||((t.put||{})._||{})["#"],r,t,n)}}},{out:{get:{".":!0}}})}(this,t,e),this;var c,f=(i=this)._,l=n||{},h=f.root;l.at=f,l.ok=t;var p={};function d(n,t,e){if(!d.stun&&(!(o=h.pass)||o[c])){var o,i=n.$._,r=(n.$$||"")._,s=(r||i).put,a=!i.has&&!i.soul,u={};if(!a&&v!==s||(s=v===((o=n.put)||"")["="]?v===(o||"")[":"]?o:o[":"]:o["="]),"string"==typeof(o=g.valid(s))&&(s=v===(o=h.$.get(o)._.put)?l.not?v:s:o),!l.not||v!==s){if(v===l.stun){if((o=h.stun)&&o.on&&(f.$.back(function(t){if(o.on(""+t.id,u={}),(u.run||0)u.run&&(u.stun&&!u.stun.end||(u.stun=o.on("stun"),u.stun=u.stun&&u.stun.last),u.stun&&!u.stun.end)))return void((u.stun.add||(u.stun.add={}))[c]=function(){d(n,t,1)});if(v===s&&(e=0),(o=h.hatch)&&!o.end&&v===l.hatch&&!e)return p[i.$._.id]?void 0:(p[i.$._.id]=1,void o.push(function(){d(n,t,1)}));p={}}if(h.pass){if(h.pass[c+i.id])return;h.pass[c+i.id]=1}l.on?l.ok.call(i.$,s,i.get,n,t||d):l.v2020?l.ok(n,t||d):(Object.keys(n).forEach(function(t){o[t]=n[t]},o={}),(n=o).put=s,l.ok.call(l.as,n,t||d))}}}return(((d.at=f).any||(f.any={}))[c=String.random(7)]=d).off=function(){d.stun=1,f.any&&delete f.any[c]},d.rid=r,d.id=l.run||++h.once,o=h.pass,(h.pass={})[c]=1,l.out=l.out||{get:{}},f.on("out",l.out),h.pass=o,i}if("number"==typeof t)return this.get(""+t,n,e);if("string"==typeof(o=a(t)))return this.get(o,n,e);(o=this.get.next)&&(i=o(this,t))}return i?n&&"function"==typeof n&&i.get(n,e):((i=this.chain())._.err={err:g.log("Invalid get request!",t)},n&&n.call(i,i._.err)),i};var v,s={},a=g.valid})(x,"./get"),x(function(t){var y=x("./root");function m(n,t){var e,o,i;t&&(t=(t._||"").id||t,e=n.root.stun||(n.root.stun={on:y.on}),o={},n.stun||(n.stun=e.on("stun",function(){})),(i=e.on(""+t))&&i.the.last.next(o),o.run>=n.run||e.on(""+t,function(t){return n.stun.end?(this.off(),void this.to.next(t)):(t.run=t.run||n.run,void(t.stun=t.stun||n.stun))}))}function k(n){var e,t,o,i,r;n.err?k.end(n.stun,n.root):n.todo.length||n.end||!Object.empty(n.wait)||(n.end=1,r=n.$.back(-1)._,e=r.root,t=r.ask(function(t){e.on("ack",t),t.err&&!t.lack&&y.log(t),++o>(n.acks||0)&&this.off(),n.ack&&n.ack(t,this)},n.opt),o=0,i=n.stun,(r=function(){i&&(k.end(i,e),setTimeout.each(Object.keys(i=i.add||""),function(t){(t=i[t])&&t()}))}).hatch=r,n.ack&&!n.ok&&(n.ok=n.acks||9),n.via._.on("out",{put:n.out=n.graph,ok:n.ok&&{"@":n.ok+1},opt:n.opt,"#":t,_:r}))}y.chain.put=function(t,n,g){var e=this,o=e._,i=o.root;(g=g||{}).root=o.root,g.run||(g.run=i.once),m(g,o.id),g.ack=g.ack||n,g.via=g.via||e,g.data=g.data||t,g.soul||(g.soul=o.soul||"string"==typeof n&&n);var v=g.state=g.state||y.state();return"function"==typeof t?t(function(t){g.data=t,e.put(void 0,void 0,g)}):g.soul?(g.$=i.$.get(g.soul),g.todo=[{it:g.data,ref:g.$}],g.turn=g.turn||r,g.ran=g.ran||k,function t(){var n,e,i,r,s,o,a,u=g.todo,c=u.pop(),f=c.it;c.ref&&c.ref._.id;if(m(g,c.ref),(r=c.todo)&&(f=f[e=r.pop()],r.length&&u.push(c)),e&&(u.path||(u.path=[])).push(e),!(n=b(f))&&!(s=y.is(f))){if(!Object.plain(f))return void k.err(g,"Invalid data: "+((o=f)&&(a=o.constructor)&&a.name||typeof o)+" at "+(g.via.back(function(t){t.get&&r.push(t.get)},r=[])||r.join("."))+"."+(u.path||[]).join("."));for(var l=g.seen||(g.seen=[]),h=l.length;h--;)if(f===(r=l[h]).it){n=f=r.link;break}}if(e&&n)c.node=w(c.node,e,v,f);else{if(!g.seen)return void k.err(g,"Data at root of graph must be a node (an object).");g.seen.push(i={it:f,link:{},todo:s?[]:Object.keys(f).sort().reverse(),path:(u.path||[]).slice(),up:c}),c.node=w(c.node,e,v,i.link),!s&&i.todo.length&&u.push(i);var p=g.seen.length;function d(t,n){var e=i.link["#"];n&&(n.off(),n.rid(t));var o=e||t.soul||(r=(t.$$||t.$)._||"").soul||r.link||((r=r.put||"")._||"")["#"]||r["#"]||((r=t.put||"")&&t.$$?r:r["="]||r[":"]||"")["#"];e||m(g,t.$),o||c.link["#"]?(o||(o=[],(t.$$||t.$).back(function(t){return(r=t.soul||t.link)?o.push(r):void o.push(t.get)}),o=o.reverse().join("/")),i.link["#"]=o,s||(((g.graph||(g.graph={}))[o]=i.node||(i.node={_:{}}))._["#"]=o),delete g.wait[p],i.wait&&setTimeout.each(i.wait,function(t){t&&t()}),g.ran(g)):(c.wait||(c.wait=[])).push(function(){d(t,n)})}(g.wait||(g.wait={}))[p]="",r=(i.ref=s?f:e?c.ref.get(e):c.ref)._,(r=f&&(f._||"")["#"]||r.soul||r.link)?d({soul:r}):i.ref.get(d,{run:g.run,v2020:1,out:{get:{".":" "}}})}if(!u.length)return g.ran(g);g.turn(t)}()):function(n){var e,t=n.via._;n.via=n.via.back(function(t){return t.soul||!t.get?t.$:(e=n.data,void((n.data={})[t.get]=e))}),n.via&&n.via._.soul||(n.via=t.root.$.get(((n.data||"")._||"")["#"]||t.$.back("opt.uuid")()));n.via.put(n.data,n.ack,n)}(g),e},k.end=function(t,n){t.end=e,t.the.to===t&&t===t.the.last&&delete n.stun,t.off()},k.err=function(t,n){(t.ack||e).call(t,t.out={err:t.err=y.log(n)}),t.ran(t)};var e=function(){},r=setTimeout.turn,b=y.valid,w=y.state.ify})(x,"./put"),x(function(t){var n=x("./root");x("./chain"),x("./back"),x("./put"),x("./get"),t.exports=n})(x,"./core"),x(function(t){var n=x("./root");x("./shim"),x("./onto"),x("./book"),x("./valid"),x("./state"),x("./dup"),x("./ask"),x("./core"),x("./on"),x("./map"),x("./set"),x("./mesh"),x("./websocket"),x("./localStorage"),t.exports=n})(x,"./index"),x(function(t){var g=x("./root");g.chain.on=function(t,n,e,o){var i=this._;i.root;if("string"==typeof t)return n?(o=i.on(t,n,e||i,o),e&&e.$&&(e.subs||(e.subs=[])).push(o),this):i.on(t);(n=!0===n?{change:!0}:n||{}).not=1,n.on=1;return this.get(t,n),this},g.chain.once=function(c,f){if(f=f||{},!c)return t=this,g.log.once("valonce","Chainable val is experimental, its behavior and API may change moving forward. Please play with it and report bugs and ideas on how to improve it."),(e=t.chain())._.nix=t.once(function(t,n){e._.on("in",this._)}),e._.lex=t._.lex,e;var t,e,l,h=this._,p=h.root,d=(h.put,String.random(7));return this.get(function(n,e,o,i){var r=this,s=r._,a=s.one||(s.one={});function u(t){s.has||s.soul||(s={put:n,get:e}),void 0===(l=s.put)&&(l=((o.$$||"")._||"").put),"string"!=typeof g.valid(l)||void 0!==(l=p.$.get(l)._.put)||t?i.stun||""!==a[d]&&(a[d]="",(h.soul||h.has)&&i.off(),c.call(r,l,s.get),clearTimeout(a[d])):a[d]=setTimeout(function(){u(1)},f.wait||99)}i.stun||""!==a[d]&&(!0!==(l=g.valid(n))?"string"!=typeof l&&(clearTimeout((h.one||"")[d]),clearTimeout(a[d]),a[d]=setTimeout(u,f.wait||99)):u())},{on:1}),this},g.chain.off=function(){var e,t=this._,o=t.back;if(o)return t.ack=0,(e=o.next)&&e[t.get]&&delete e[t.get],(e=o.any)&&(delete o.any,o.any={}),(e=o.ask)&&delete e[t.get],(e=o.put)&&delete e[t.get],(e=t.soul)&&delete o.root.graph[e],(e=t.map)&&Object.keys(e).forEach(function(t,n){(n=e[t]).link&&o.root.$.get(n.link).off()}),(e=t.next)&&Object.keys(e).forEach(function(t,n){e[t].$.off()}),t.on("off",{}),this}})(x,"./on"),x(function(t){var a=x("./root"),o=a.chain.get.next;function u(t){this.to.next(t);var n=this.as,e=t.$._,o=t.put;(e.soul||t.$$)&&((e=n.lex)&&!String.match(t.get||(o||"")["."],e["."]||e["#"]||e)||a.on.link(t,n))}a.chain.get.next=function(t,n){var e;return Object.plain(n)?(e=((e=n["#"])||"")["="]||e)?t.get(e):((e=t.chain()._).lex=n,t.on("in",function(t){String.match(t.get||(t.put||"")["."],n["."]||n["#"]||n)&&e.on("in",t),this.to.next(t)}),e.$):(o||c)(t,n)},a.chain.map=function(r,t,n){var e,s,o=this,i=o._;return Object.plain(r)&&(e=r["."]?r:{".":r},r=void 0),r?(a.log.once("mapfn","Map functions are experimental, their behavior and API may change moving forward. Please play with it and report bugs and ideas on how to improve it."),s=o.chain(),o.map().on(function(t,n,e,o){o=(r||c).call(this,t,n,e,o);if(void 0!==o){if(t===o)return s._.on("in",e);if(a.is(o))return s._.on("in",o._);var i={};Object.keys(e.put).forEach(function(t){i[t]=e.put[t]},i),i["="]=o,s._.on("in",{get:n,put:i})}})):(s=i.each)||((i.each=s=o.chain())._.lex=e||s._.lex||i.lex,s._.nix=o.back("nix"),o.on("in",u,s._)),s};var c=function(){}})(x,"./map"),x(function(t){var a=x("./root");a.chain.set=function(t,i,n){var e,r,s=this,o=s.back(-1);return i=i||function(){},(n=n||{}).item=n.item||t,(e=((t||"")._||"")["#"])&&((t={})["#"]=e),"string"==typeof(r=a.valid(t))?s.get(e=r).put(t,i,n):a.is(t)?(s.put(function(o){t.get(function(t,n,e){return t?((r={})[t]={"#":t},void o(r)):i.call(s,{err:a.log('Only a node can be linked! Not "'+e.put+'"!')})},!0)}),t):(Object.plain(t)&&(t=o.get(e=s.back("opt.uuid")()).put(t)),s.get(e||o.back("opt.uuid")(7)).put(t,i,n))}})(x,"./set"),x(function(t){x("./shim");function n(){}var r=JSON.parseAsync||function(t,n,e){var o=+new Date;try{n(void 0,JSON.parse(t,e),_.sucks(+new Date-o))}catch(t){n(t)}},_=JSON.stringifyAsync||function(t,n,e,o){var i=+new Date;try{n(void 0,JSON.stringify(t,e,o),_.sucks(+new Date-i))}catch(t){n(t)}};function e(u){var g=function(){},v=u.opt||{};v.log=v.log||console.log,v.gap=v.gap||v.wait||0,v.max=v.max||.3*(v.memory?999*v.memory*999:3e8),v.pack=v.pack||.01*v.max*.01,v.puff=v.puff||9;var y=setTimeout.turn||setTimeout,m=u.dup,h=m.check,k=m.track,i=(new Date,g.hear=function(t,s){if(t){if(v.max<=t.length)return g.say({dam:"!",err:"Message too big!"},s);g===this&&(i.d+=t.length||0,++i.c);var n,e=s.SH=+new Date,o=t[0];if("["===o)return r(t,function(t,i){if(t||!i)return g.say({dam:"!",err:"DAM JSON parse error."},s);console.STAT&&console.STAT(+new Date,i.length,"# on hear batch");var r=v.puff;!function t(){for(var n,e=+new Date,o=0;o<"])&&"string"==typeof r&&r.slice(0,99).split(",").forEach(function(t){this[t]=1},n._.yo={}),r=n.dam)return(k(o)||{}).via=e,void((r=g.hear[r])&&r(n,e,u));(r=n.ok)&&(n._.near=r["/"]);t=+new Date;a&&(a.is=t),e.SI=o,k.ed=function(t){o===t&&(k.ed=0,(t=m.s[o])&&(t.via=e,n.get&&(t.it=n)))},u.on("in",g.last=n),a&&(a.hd=+new Date),console.STAT&&console.STAT(t,+new Date-t,n.get?"msg get":n.put?"msg put":"msg"),k(o),s&&k(s),g.leap=g.last=null}};function b(t){var n=t.batch,e="string"==typeof n;if(e&&(n+="]"),t.batch=t.tail=null,n&&(e?!(n.length<3):n.length)){if(!e)try{n=1===n.length?n[0]:JSON.stringify(n)}catch(t){return v.log("DAM JSON stringify error",t)}n&&w(n,t)}}function w(n,e){try{var t=e.wire;e.say?e.say(n):t.send&&t.send(n),g.say.d+=n.length||0,++g.say.c}catch(t){(e.queue=e.queue||[]).push(n)}}i.c=i.d=0,function(){var p,d=0;function t(t,n){var e;return n instanceof Object?(Object.keys(n).sort().forEach(o,{to:e={},on:n}),e):n}function o(t){this.to[t]=this.on[t]}g.hash=function(o,i){var r,s,a,u=+new Date;_(o.put,function t(n,e){e=(s=s||(a=e||"")).slice(0,32768);r=String.hash(e,r),(s=s.slice(32768))?y(t,0):(console.STAT&&console.STAT(u,+new Date-u,"say json+hash"),o._.$put=a,o["##"]=r,g.say(o,i),delete o._.$put)},t)};g.say=function(r,s){var t;if((t=this)&&(t=t.to)&&t.next&&t.next(r),!r)return!1;var n,e,a,u=r["@"],c=r._||(r._=function(){}),o=r.DBG,i=+new Date;if(c.y=c.y||i,s||o&&(o.y=i),(n=r["#"])||(n=r["#"]=String.random(9)),p||k(n),(e=r["##"])||$===r.put||c.via||!u){if(!(s=!s&&u?(t=m.s[u])&&(t.via||(t=t.it)&&(t=t._)&&t.via)||(t=g.last)&&u===t["#"]&&g.leap:s)&&u)return m.s[u]?void 0:(console.STAT&&console.STAT(+new Date,++d,"total no peer to ack to"),!1);if(u&&!r.put&&!e&&((m.s[u]||"").it||"")["##"])return!1;if(!s&&g.way)return g.way(r);if(o&&(o.yh=+new Date),a=c.raw){if(o&&(o.yr=+new Date),!s||!s.id){if(!Object.plain(s||v.peers))return!1;var i=+new Date,f=(v.puff,v.peers),l=Object.keys(s||v.peers||{});return console.STAT&&console.STAT(i,+new Date-i,"peer keys"),void function t(){var n=+new Date;p=1;var e=c.raw;c.raw=a;for(var o,i=0;i<9&&(o=(l||"")[i++]);)(o=f[o]||(s||"")[o])&&g.say(r,o);c.raw=e,p=0,l=l.slice(i),console.STAT&&console.STAT(n,+new Date-n,"say loop"),l.length&&(y(t,0),u&&k(u))}()}if(!s.wire&&g.wire&&g.wire(s),n!==s.last){if(s.last=n,s===c.via)return!1;if((t=c.yo)&&(t[s.url]||t[s.pid]||t[s.id]))return!1;if(console.STAT&&console.STAT(i,((o||c).yp=+new Date)-(c.y||i),"say prep"),!p&&u&&k(u),s.batch){if(s.tail=(t=s.tail||0)+a.length,s.tail<=v.pack)return void(s.batch+=(t?",":"")+a);b(s)}s.batch="[";var h=+new Date;setTimeout(function(){console.STAT&&console.STAT(h,+new Date-h,"0ms TO"),b(s)},v.gap),w(a,s),console.STAT&&u===s.SI&&console.STAT(i,+new Date-s.SH,"say ack")}}else g.raw(r,s)}else g.hash(r,s)};g.say.c=g.say.d=0,g.raw=function(e,o){if(!e)return"";var i,r=e._||{};if(a=r.raw)return a;if("string"==typeof e)return e;var t=e["##"],n=e["@"];if(t&&n){if(!r.via&&h(n+t))return!1;if(a=(m.s[n]||"").it){if(t===a["##"])return!1;a["##"]||(a["##"]=t)}}if(!e.dam&&!e["@"]){var s,a,u=0,c=[];for(s in a=v.peers){var f=a[s];if(c.push(f.url||f.pid||f.id),6<++u)break}1<"]=c.join())}if(e.put&&(a=e.ok)&&(e.ok={"@":(a["@"]||1)-1,"/":a["/"]==e._.near?g.near:a["/"]}),i=r.$put)return a={},Object.keys(e).forEach(function(t){a[t]=e[t]}),a.put=":])([:",void _(a,function(t,n){t||(t=+new Date,a=n.indexOf('"put":":])([:"'),l($,n=n.slice(0,a+6)+i+n.slice(a+14)),console.STAT&&console.STAT(t,+new Date-t,"say slice"))});function l(t,n){t||(r.raw=n,g.say(e,o))}_(e,l)}}(),g.near=0,g.hi=function(n){var t;n.wire?(n.id?v.peers[n.url||n.id]=n:(t=n.id=n.id||n.url||String.random(9),g.say({dam:"?",pid:u.opt.pid},v.peers[t]=n),delete m.s[n.last]),n.met||(g.near++,n.met=+new Date,u.on("hi",n)),t=n.queue,n.queue=[],setTimeout.each(t||[],function(t){w(t,n)},0,9)):g.wire(n.length?{url:n,id:n}:n)},g.bye=function(t){t.met&&--g.near,delete t.met,u.on("bye",t);var n=+new Date;n-=t.met||n,g.bye.time=((g.bye.time||n)+n)/2},g.hear["!"]=function(t,n){v.log("Error:",t.err)},g.hear["?"]=function(t,n){t.pid&&(n.pid||(n.pid=t.pid),t["@"])||(g.say({dam:"?",pid:v.pid,"@":t["#"]},n),delete m.s[n.last])},g.hear.mob=function(t,n){!t.peers||(t=(t=Object.keys(t.peers))[Math.random()*t.length>>0])&&(g.bye(n),g.hi(t))},u.on("create",function(t){t.opt.pid=t.opt.pid||String.random(9),this.to.next(t),t.on("out",g.say)}),u.on("bye",function(t,n){t=v.peers[t.id||t]||t,this.to.next(t),t.bye?t.bye():(n=t.wire)&&n.close&&n.close(),delete v.peers[t.id],t.wire=null});var e={};return u.on("bye",function(t,n){this.to.next(t),(n=console.STAT)&&(n.peers=g.near),(n=t.url)&&(e[n]=!0,setTimeout(function(){delete e[n]},v.lack||9e3))}),u.on("hi",function(e,t){this.to.next(e),(t=console.STAT)&&(t.peers=g.near),v.super||(9999<(t=Object.keys(u.next||"")).length&&!console.SUBS&&console.log(console.SUBS="Warning: You have more than 10K live GETs, which might use more bandwidth than your screen can show - consider `.off()`."),setTimeout.each(t,function(n){var t=u.next[n];v.super||(t.ask||"")[""]?g.say({get:{"#":n}},e):setTimeout.each(Object.keys(t.ask||""),function(t){t&&g.say({"##":String.hash((u.graph[n]||"")[t]),get:{"#":n,".":t}},e)})}))}),g}_.sucks=function(t){99"],n[":"],e),f&&4999880"]||n["<"])||o===n["="]&&(e=n["*"]||n[">"]||n["<"],t.slice(0,(e||"").length)===e||o===n["*"]&&(o!==n[">"]&&o!==n["<"]?t>=n[">"]&&t<=n["<"]:o!==n[">"]&&t>=n[">"]||o!==n["<"]&&t<=n["<"])))},h.text.hash=h.text.hash||function(t,n){if(l("text.hash"),"string"==typeof t){if(n=n||0,!t.length)return n;for(var e=0,o=t.length;e { + if (line.trim().length > 0) { + const indent = line.match(/^\s*/)[0].length; + minIndent = Math.min(minIndent, indent); + } + }); + + // Remove common indentation + const cleanedLines = lines.map(line => { + if (line.trim().length > 0) { + return line.slice(minIndent); + } + return ''; + }); + + return cleanedLines.join('\n').trim(); +} + +function buildSea(arg) { + if (arg !== 'sea') { + console.error('Only "sea" argument is supported'); + process.exit(1); + } + + // Start with the USE function definition + let output = `;(function(){ + + /* UNBUILD */ + function USE(arg, req){ + return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){ + arg(mod = {exports: {}}); + USE[R(path)] = mod.exports; + } + function R(p){ + return p.split('/').slice(-1).toString().replace('.js',''); + } + } + if(typeof module !== "undefined"){ var MODULE = module } + /* UNBUILD */\n\n`; + + // Add each module wrapped in USE() + seaModules.forEach(name => { + try { + let code = read('sea/' + name + '.js'); + + // Clean up the code + code = normalizeContent(code); + + // Replace require() with USE(), but skip any requires within UNBUILD comments + let inUnbuild = false; + const lines = code.split('\n').map(line => { + if (line.includes('/* UNBUILD */')) { + inUnbuild = !inUnbuild; + return line; + } + if (!inUnbuild) { + return line.replace(/require\(/g, 'USE('); + } + return line; + }); + code = lines.join('\n'); + + // Add module with consistent indentation + output += ` ;USE(function(module){\n`; + output += code.split('\n').map(line => line.length ? ' ' + line : '').join('\n'); + output += `\n })(USE, './${name}');\n\n`; + } catch(e) { + console.error('Error processing ' + name + '.js:', e); + } + }); + + // Close IIFE + output += '}());'; + + // Write output + write('sea.js', output); + console.log('Built sea.js'); +} + +if (require.main === module) { + const arg = process.argv[2]; + buildSea(arg); +} + +module.exports = buildSea; diff --git a/lib/serve.js b/lib/serve.js index 01af8a3e..9a2aeb49 100644 --- a/lib/serve.js +++ b/lib/serve.js @@ -39,6 +39,7 @@ function serve(req, res, next){ var tmp; } var S = +new Date; var rs = fs.createReadStream(path); + if(req.url.slice(-3) === '.js'){ res.writeHead(200, {'Content-Type': 'text/javascript'}) } rs.on('open', function(){ console.STAT && console.STAT(S, +new Date - S, 'serve file open'); rs.pipe(res) }); rs.on('error', function(err){ res.end(404+'') }); rs.on('end', function(){ console.STAT && console.STAT(S, +new Date - S, 'serve file end') }); diff --git a/package-lock.json b/package-lock.json index 0af741e5..ce60f80b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,102 +1,140 @@ { "name": "gun", "version": "0.2020.1239", - "lockfileVersion": 1, + "lockfileVersion": 3, "requires": true, - "dependencies": { - "@peculiar/asn1-schema": { + "packages": { + "": { + "name": "gun", + "version": "0.2020.1239", + "license": "(Zlib OR MIT OR Apache-2.0)", + "dependencies": { + "ws": "^7.2.1" + }, + "devDependencies": { + "aws-sdk": "^2.528.0", + "emailjs": "^2.2.0", + "ip": "^1.1.5", + "mocha": "^6.2.0", + "uglify-js": "^3.6.0" + }, + "engines": { + "node": ">=0.8.4" + }, + "optionalDependencies": { + "@peculiar/webcrypto": "^1.1.1" + } + }, + "node_modules/@peculiar/asn1-schema": { "version": "2.0.37", "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.0.37.tgz", "integrity": "sha512-f/dozij2XCZZ7ayOWI88TbHt/1rk3zJ91O/xTtDdc8SttyF6pleu4RYBuFohkobA5HJn+bEcY6Cvq4x9feXokQ==", "optional": true, - "requires": { + "dependencies": { "@types/asn1js": "^2.0.2", "asn1js": "^2.1.1", "pvtsutils": "^1.1.7", "tslib": "^2.3.0" } }, - "@peculiar/json-schema": { + "node_modules/@peculiar/json-schema": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", "optional": true, - "requires": { + "dependencies": { "tslib": "^2.0.0" + }, + "engines": { + "node": ">=8.0.0" } }, - "@peculiar/webcrypto": { + "node_modules/@peculiar/webcrypto": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.1.7.tgz", "integrity": "sha512-aCNLYdHZkvGH+T8/YBOY33jrVGVuLIa3bpizeHXqwN+P4ZtixhA+kxEEWM1amZwUY2nY/iuj+5jdZn/zB7EPPQ==", "optional": true, - "requires": { + "dependencies": { "@peculiar/asn1-schema": "^2.0.32", "@peculiar/json-schema": "^1.1.12", "pvtsutils": "^1.1.6", "tslib": "^2.2.0", "webcrypto-core": "^1.2.0" + }, + "engines": { + "node": ">=10.12.0" } }, - "@types/asn1js": { + "node_modules/@types/asn1js": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@types/asn1js/-/asn1js-2.0.2.tgz", "integrity": "sha512-t4YHCgtD+ERvH0FyxvNlYwJ2ezhqw7t+Ygh4urQ7dJER8i185JPv6oIM3ey5YQmGN6Zp9EMbpohkjZi9t3UxwA==", "optional": true }, - "addressparser": { + "node_modules/addressparser": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/addressparser/-/addressparser-0.3.2.tgz", "integrity": "sha1-WYc/Nej89sc2HBAjkmHXbhU0i7I=", "dev": true }, - "ansi-colors": { + "node_modules/ansi-colors": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "ansi-styles": { + "node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, - "requires": { + "dependencies": { "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" } }, - "argparse": { + "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, - "requires": { + "dependencies": { "sprintf-js": "~1.0.2" } }, - "asn1js": { + "node_modules/asn1js": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-2.1.1.tgz", "integrity": "sha512-t9u0dU0rJN4ML+uxgN6VM2Z4H5jWIYm0w8LsZLzMJaQsgL3IJNbxHgmbWDvJAwspyHpDFuzUaUFh4c05UB4+6g==", "optional": true, - "requires": { - "pvutils": "^1.1.3" - }, "dependencies": { - "pvutils": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", - "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", - "optional": true - } + "pvutils": "latest" + }, + "engines": { + "node": ">=6.0.0" } }, - "aws-sdk": { + "node_modules/asn1js/node_modules/pvutils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", + "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", + "optional": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/aws-sdk": { "version": "2.955.0", "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.955.0.tgz", "integrity": "sha512-/C3pYcZxlhrhHBzW2eHCtBiMqFQlBB7mpcR/lpKzCg1AqxOuC3zXbrjH4qIhauNWeOzwX75O0wlvnPob1HvV9w==", "dev": true, - "requires": { + "hasInstallScript": true, + "dependencies": { "buffer": "4.9.2", "events": "1.1.1", "ieee754": "1.1.13", @@ -107,227 +145,272 @@ "uuid": "3.3.2", "xml2js": "0.4.19" }, - "dependencies": { - "buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "dev": true, - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - } + "engines": { + "node": ">= 0.8.0" } }, - "balanced-match": { + "node_modules/aws-sdk/node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dev": true, + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/aws-sdk/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "base64-js": { + "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, - "brace-expansion": { + "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "requires": { + "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "browser-stdout": { + "node_modules/browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, - "call-bind": { + "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", "dev": true, - "requires": { + "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "camelcase": { + "node_modules/camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "chalk": { + "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, - "requires": { + "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" }, - "dependencies": { - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "engines": { + "node": ">=4" } }, - "cliui": { + "node_modules/chalk/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cliui": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", "dev": true, - "requires": { + "dependencies": { "string-width": "^3.1.0", "strip-ansi": "^5.2.0", "wrap-ansi": "^5.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } } }, - "color-convert": { + "node_modules/cliui/node_modules/ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, - "requires": { + "dependencies": { "color-name": "1.1.3" } }, - "color-name": { + "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, - "concat-map": { + "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "debug": { + "node_modules/debug": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", "dev": true, - "requires": { + "dependencies": { "ms": "^2.1.1" } }, - "decamelize": { + "node_modules/decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "define-properties": { + "node_modules/define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, - "requires": { + "dependencies": { "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" } }, - "diff": { + "node_modules/diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.3.1" + } }, - "emailjs": { + "node_modules/emailjs": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/emailjs/-/emailjs-2.2.0.tgz", "integrity": "sha1-ulsj5KSwpFEPZS6HOxVOlAe2ygM=", "dev": true, - "requires": { + "dependencies": { "addressparser": "^0.3.2", "emailjs-mime-codec": "^2.0.7" } }, - "emailjs-base64": { + "node_modules/emailjs-base64": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/emailjs-base64/-/emailjs-base64-1.1.4.tgz", "integrity": "sha512-4h0xp1jgVTnIQBHxSJWXWanNnmuc5o+k4aHEpcLXSToN8asjB5qbXAexs7+PEsUKcEyBteNYsSvXUndYT2CGGA==", "dev": true }, - "emailjs-mime-codec": { + "node_modules/emailjs-mime-codec": { "version": "2.0.9", "resolved": "https://registry.npmjs.org/emailjs-mime-codec/-/emailjs-mime-codec-2.0.9.tgz", "integrity": "sha512-7qJo4pFGcKlWh/kCeNjmcgj34YoJWY0ekZXEHYtluWg4MVBnXqGM4CRMtZQkfYwitOhUgaKN5EQktJddi/YIDQ==", "dev": true, - "requires": { + "dependencies": { "emailjs-base64": "^1.1.4", "ramda": "^0.26.1", "text-encoding": "^0.7.0" } }, - "es-abstract": { + "node_modules/es-abstract": { "version": "1.18.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", "integrity": "sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==", "dev": true, - "requires": { + "dependencies": { "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", @@ -345,325 +428,479 @@ "string.prototype.trimstart": "^1.0.4", "unbox-primitive": "^1.0.1" }, - "dependencies": { - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - } + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "es-to-primitive": { + "node_modules/es-abstract/node_modules/object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, - "requires": { + "dependencies": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "escape-string-regexp": { + "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.8.0" + } }, - "esprima": { + "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } }, - "events": { + "node_modules/events": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.4.x" + } }, - "find-up": { + "node_modules/find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, - "requires": { + "dependencies": { "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" } }, - "flat": { + "node_modules/flat": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", "dev": true, - "requires": { + "dependencies": { "is-buffer": "~2.0.3" + }, + "bin": { + "flat": "cli.js" } }, - "fs.realpath": { + "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "function-bind": { + "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, - "get-caller-file": { + "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } }, - "get-intrinsic": { + "node_modules/get-intrinsic": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", "dev": true, - "requires": { + "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "glob": { + "node_modules/glob": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, - "requires": { + "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" } }, - "growl": { + "node_modules/growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true + "dev": true, + "engines": { + "node": ">=4.x" + } }, - "has": { + "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, - "requires": { + "dependencies": { "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" } }, - "has-bigints": { + "node_modules/has-bigints": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", - "dev": true + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "has-flag": { + "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "dev": true, + "engines": { + "node": ">=4" + } }, - "has-symbols": { + "node_modules/has-symbols": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "he": { + "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true + "dev": true, + "bin": { + "he": "bin/he" + } }, - "ieee754": { + "node_modules/ieee754": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", "dev": true }, - "inflight": { + "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, - "requires": { + "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, - "inherits": { + "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "ip": { + "node_modules/ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", "dev": true }, - "is-bigint": { + "node_modules/is-bigint": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==", - "dev": true + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "is-boolean-object": { + "node_modules/is-boolean-object": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==", "dev": true, - "requires": { + "dependencies": { "call-bind": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "is-buffer": { + "node_modules/is-buffer": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "dev": true + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } }, - "is-callable": { + "node_modules/is-callable": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "is-date-object": { + "node_modules/is-date-object": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "is-negative-zero": { + "node_modules/is-negative-zero": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "is-number-object": { + "node_modules/is-number-object": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz", "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "is-regex": { + "node_modules/is-regex": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", "dev": true, - "requires": { + "dependencies": { "call-bind": "^1.0.2", "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "is-string": { + "node_modules/is-string": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "is-symbol": { + "node_modules/is-symbol": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", "dev": true, - "requires": { + "dependencies": { "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "isexe": { + "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, - "jmespath": { + "node_modules/jmespath": { "version": "0.15.0", "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.6.0" + } }, - "js-yaml": { + "node_modules/js-yaml": { "version": "3.13.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", "dev": true, - "requires": { + "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "locate-path": { + "node_modules/locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, - "requires": { + "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" } }, - "lodash": { + "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "log-symbols": { + "node_modules/log-symbols": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", "dev": true, - "requires": { + "dependencies": { "chalk": "^2.0.1" + }, + "engines": { + "node": ">=4" } }, - "minimatch": { + "node_modules/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, - "requires": { + "dependencies": { "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "minimist": { + "node_modules/minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, - "mkdirp": { + "node_modules/mkdirp": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", "dev": true, - "requires": { + "dependencies": { "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" } }, - "mocha": { + "node_modules/mocha": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.3.tgz", "integrity": "sha512-0R/3FvjIGH3eEuG17ccFPk117XL2rWxatr81a57D+r/x2uTYZRbdZ4oVidEUMh2W2TJDa7MdAb12Lm2/qrKajg==", "dev": true, - "requires": { + "dependencies": { "ansi-colors": "3.2.3", "browser-stdout": "1.3.1", "debug": "3.2.6", @@ -687,254 +924,330 @@ "yargs": "13.3.2", "yargs-parser": "13.1.2", "yargs-unparser": "1.6.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 6.0.0" } }, - "ms": { + "node_modules/ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, - "node-environment-flags": { + "node_modules/node-environment-flags": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", "dev": true, - "requires": { + "dependencies": { "object.getownpropertydescriptors": "^2.0.3", "semver": "^5.7.0" } }, - "object-inspect": { + "node_modules/object-inspect": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", - "dev": true + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "object-keys": { + "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.4" + } }, - "object.assign": { + "node_modules/object.assign": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", "dev": true, - "requires": { + "dependencies": { "define-properties": "^1.1.2", "function-bind": "^1.1.1", "has-symbols": "^1.0.0", "object-keys": "^1.0.11" + }, + "engines": { + "node": ">= 0.4" } }, - "object.getownpropertydescriptors": { + "node_modules/object.getownpropertydescriptors": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==", "dev": true, - "requires": { + "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", "es-abstract": "^1.18.0-next.2" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "once": { + "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, - "requires": { + "dependencies": { "wrappy": "1" } }, - "p-limit": { + "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, - "requires": { + "dependencies": { "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "p-locate": { + "node_modules/p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, - "requires": { + "dependencies": { "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" } }, - "p-try": { + "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "path-exists": { + "node_modules/path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true + "dev": true, + "engines": { + "node": ">=4" + } }, - "path-is-absolute": { + "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "punycode": { + "node_modules/punycode": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", "dev": true }, - "pvtsutils": { + "node_modules/pvtsutils": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.2.0.tgz", "integrity": "sha512-IDefMJEQl7HX0FP2hIKJFnAR11klP1js2ixCrOaMhe3kXFK6RQ2ABUCuwWaaD4ib0hSbh2fGTICvWJJhDfNecA==", "optional": true, - "requires": { + "dependencies": { "tslib": "^2.2.0" } }, - "querystring": { + "node_modules/querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "dev": true, + "engines": { + "node": ">=0.4.x" + } }, - "ramda": { + "node_modules/ramda": { "version": "0.26.1", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz", "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==", "dev": true }, - "require-directory": { + "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "require-main-filename": { + "node_modules/require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, - "sax": { + "node_modules/sax": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=", "dev": true }, - "semver": { + "node_modules/semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true + "dev": true, + "bin": { + "semver": "bin/semver" + } }, - "set-blocking": { + "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, - "sprintf-js": { + "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, - "string.prototype.trimend": { + "node_modules/string.prototype.trimend": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", "dev": true, - "requires": { + "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "string.prototype.trimstart": { + "node_modules/string.prototype.trimstart": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", "dev": true, - "requires": { + "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "strip-json-comments": { + "node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "supports-color": { + "node_modules/supports-color": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", "dev": true, - "requires": { + "dependencies": { "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=6" } }, - "text-encoding": { + "node_modules/text-encoding": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.7.0.tgz", "integrity": "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==", + "deprecated": "no longer maintained", "dev": true }, - "tslib": { + "node_modules/tslib": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", "optional": true }, - "uglify-js": { + "node_modules/uglify-js": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.14.1.tgz", "integrity": "sha512-JhS3hmcVaXlp/xSo3PKY5R0JqKs5M3IV+exdLHW99qKvKivPO4Z8qbej6mte17SOPqAOVMjt/XGgWacnFSzM3g==", - "dev": true + "dev": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } }, - "unbox-primitive": { + "node_modules/unbox-primitive": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", "dev": true, - "requires": { + "dependencies": { "function-bind": "^1.1.1", "has-bigints": "^1.0.1", "has-symbols": "^1.0.2", "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "url": { + "node_modules/url": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", "dev": true, - "requires": { + "dependencies": { "punycode": "1.3.2", "querystring": "0.2.0" } }, - "uuid": { + "node_modules/uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", - "dev": true + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } }, - "webcrypto-core": { + "node_modules/webcrypto-core": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.2.0.tgz", "integrity": "sha512-p76Z/YLuE4CHCRdc49FB/ETaM4bzM3roqWNJeGs+QNY1fOTzKTOVnhmudW1fuO+5EZg6/4LG9NJ6gaAyxTk9XQ==", "optional": true, - "requires": { + "dependencies": { "@peculiar/asn1-schema": "^2.0.27", "@peculiar/json-schema": "^1.1.12", "asn1js": "^2.0.26", @@ -942,166 +1255,213 @@ "tslib": "^2.1.0" } }, - "which": { + "node_modules/which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, - "requires": { + "dependencies": { "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" } }, - "which-boxed-primitive": { + "node_modules/which-boxed-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", "dev": true, - "requires": { + "dependencies": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", "is-number-object": "^1.0.4", "is-string": "^1.0.5", "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "which-module": { + "node_modules/which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, - "wide-align": { + "node_modules/wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } + "string-width": "^1.0.2 || 2" } }, - "wrap-ansi": { + "node_modules/wide-align/node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wrap-ansi": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", "dev": true, - "requires": { + "dependencies": { "ansi-styles": "^3.2.0", "string-width": "^3.0.0", "strip-ansi": "^5.0.0" }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } + "engines": { + "node": ">=6" } }, - "wrappy": { + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, - "ws": { + "node_modules/ws": { "version": "7.5.3", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz", - "integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==" + "integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } }, - "xml2js": { + "node_modules/xml2js": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", "dev": true, - "requires": { + "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~9.0.1" } }, - "xmlbuilder": { + "node_modules/xmlbuilder": { "version": "9.0.7", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", - "dev": true + "dev": true, + "engines": { + "node": ">=4.0" + } }, - "y18n": { + "node_modules/y18n": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", "dev": true }, - "yargs": { + "node_modules/yargs": { "version": "13.3.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", "dev": true, - "requires": { + "dependencies": { "cliui": "^5.0.0", "find-up": "^3.0.0", "get-caller-file": "^2.0.1", @@ -1112,67 +1472,80 @@ "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^13.1.2" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } } }, - "yargs-parser": { + "node_modules/yargs-parser": { "version": "13.1.2", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", "dev": true, - "requires": { + "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, - "yargs-unparser": { + "node_modules/yargs-unparser": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", "dev": true, - "requires": { + "dependencies": { "flat": "^4.1.0", "lodash": "^4.17.15", "yargs": "^13.3.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" } } } diff --git a/package.json b/package.json index 4443403d..fab7265d 100644 --- a/package.json +++ b/package.json @@ -13,11 +13,12 @@ "https": "HTTPS_KEY=test/https/server.key HTTPS_CERT=test/https/server.crt npm start", "prepublishOnly": "npm run unbuild", "test": "echo 'Did you run PANIC holy-grail, 1~X, on-recover, etc.?' && mocha", - "testsea": "mocha test/sea/sea.js", + "testSea": "mocha test/sea/sea.js", "e2e": "mocha e2e/distributed.js", "docker": "hooks/build", "minify": "uglifyjs gun.js -o gun.min.js -c -m", "unbuild": "node lib/unbuild.js & npm run minify", + "buildSea": "node lib/build.js sea", "unbuildSea": "node lib/unbuild.js sea", "unbuildMeta": "node lib/unbuild.js lib/meta" }, diff --git a/sea.js b/sea.js index 839fb5e2..73cbc10b 100644 --- a/sea.js +++ b/sea.js @@ -19,8 +19,7 @@ // IT IS IMPLEMENTED IN A POLYFILL/SHIM APPROACH. // THIS IS AN EARLY ALPHA! - if(typeof self !== "undefined"){ module.window = self } // should be safe for at least browser/worker/nodejs, need to check other envs like RN etc. - if(typeof window !== "undefined"){ module.window = window } + module.window = (typeof globalThis !== "undefined" && typeof window === "undefined" && typeof WorkerGlobalScope !== "undefined") ? globalThis : (typeof window !== "undefined" ? window : undefined); var tmp = module.window || module, u; var SEA = tmp.SEA || {}; @@ -231,7 +230,7 @@ if(d){ jwk.d = d } return jwk; }; - + s.keyToJwk = function(keyBytes) { const keyB64 = keyBytes.toString('base64'); const k = keyB64.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, ''); @@ -288,12 +287,19 @@ cb = salt; salt = u; } - data = (typeof data == 'string')? data : await shim.stringify(data); + // Check if data is an ArrayBuffer, if so use Uint8Array to access the data + if(data instanceof ArrayBuffer){ + data = new Uint8Array(data); + data = new shim.TextDecoder("utf-8").decode(data); + } + data = (typeof data == 'string') ? data : await shim.stringify(data); if('sha' === (opt.name||'').toLowerCase().slice(0,3)){ var rsha = shim.Buffer.from(await sha(data, opt.name), 'binary').toString(opt.encode || 'base64') if(cb){ try{ cb(rsha) }catch(e){console.log(e)} } return rsha; } + if (typeof salt === "number") salt = salt.toString(); + if (typeof opt.salt === "number") opt.salt = opt.salt.toString(); salt = salt || shim.random(9); var key = await (shim.ossl || shim.subtle).importKey('raw', new shim.TextEncoder().encode(data), {name: opt.name || 'PBKDF2'}, false, ['deriveBits']); var work = await (shim.ossl || shim.subtle).deriveBits({ @@ -320,71 +326,168 @@ ;USE(function(module){ var SEA = USE('./root'); var shim = USE('./shim'); - var S = USE('./settings'); - SEA.name = SEA.name || (async (cb, opt) => { try { - if(cb){ try{ cb() }catch(e){console.log(e)} } - return; - } catch(e) { - console.log(e); - SEA.err = e; - if(SEA.throw){ throw e } - if(cb){ cb() } - return; - }}); + // P-256 curve constants + const n = BigInt("0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551"); + const P = BigInt("0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff"); + const A = BigInt("0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc"); + const B = BigInt("0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b"); // Missing B parameter + const G = { + x: BigInt("0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296"), + y: BigInt("0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5") + }; + + // Core ECC functions + function mod(a, m) { return ((a % m) + m) % m; } + + // Constant-time modular inverse using Fermat's Little Theorem (p is prime) + function modInv(a, p) { + // a^(p-2) mod p + return modPow(a, p - BigInt(2), p); + } + + // Constant-time modular exponentiation (square-and-multiply) + function modPow(base, exponent, modulus) { + if (modulus === BigInt(1)) return BigInt(0); + base = mod(base, modulus); + let result = BigInt(1); + while (exponent > BigInt(0)) { + if (exponent & BigInt(1)) { + result = mod(result * base, modulus); + } + exponent >>= BigInt(1); + base = mod(base * base, modulus); + } + return result; + } + + // Verify a point is on the curve + function isOnCurve(point) { + if (!point) return false; + // y² = x³ + ax + b (mod p) + const { x, y } = point; + const left = mod(y * y, P); + const right = mod(mod(mod(x * x, P) * x, P) + mod(A * x, P) + B, P); + return left === right; + } + + function pointAdd(p1, p2) { + if (p1 === null) return p2; if (p2 === null) return p1; + if (p1.x === p2.x && mod(p1.y + p2.y, P) === 0n) return null; + let lambda = p1.x === p2.x && p1.y === p2.y + ? mod((3n * mod(p1.x ** 2n, P) + A) * modInv(2n * p1.y, P), P) + : mod((mod(p2.y - p1.y, P)) * modInv(mod(p2.x - p1.x, P), P), P); + const x3 = mod(lambda ** 2n - p1.x - p2.x, P); + return { x: x3, y: mod(lambda * mod(p1.x - x3, P) - p1.y, P) }; + } + + function pointMult(k, point) { + let r = null, a = point; + while (k > 0n) { + if (k & 1n) r = pointAdd(r, a); + a = pointAdd(a, a); + k >>= 1n; + } + return r; + } - //SEA.pair = async (data, proof, cb) => { try { SEA.pair = SEA.pair || (async (cb, opt) => { try { + opt = opt || {}; + const subtle = shim.subtle, ecdhSubtle = shim.ossl || subtle; + let r = {}; - var ecdhSubtle = shim.ossl || shim.subtle; - // First: ECDSA keys for signing/verifying... - var sa = await shim.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, [ 'sign', 'verify' ]) - .then(async (keys) => { - // privateKey scope doesn't leak out from here! - //const { d: priv } = await shim.subtle.exportKey('jwk', keys.privateKey) - var key = {}; - key.priv = (await shim.subtle.exportKey('jwk', keys.privateKey)).d; - var pub = await shim.subtle.exportKey('jwk', keys.publicKey); - //const pub = Buff.from([ x, y ].join(':')).toString('base64') // old - key.pub = pub.x+'.'+pub.y; // new - // x and y are already base64 - // pub is UTF8 but filename/URL safe (https://www.ietf.org/rfc/rfc3986.txt) - // but split on a non-base64 letter. - return key; - }) - - // To include PGPv4 kind of keyId: - // const pubId = await SEA.keyid(keys.pub) - // Next: ECDH keys for encryption/decryption... + // Helper functions + const b64ToBI = s => { + let b64 = s.replace(/-/g, '+').replace(/_/g, '/'); + while (b64.length % 4) b64 += '='; + return BigInt('0x' + shim.Buffer.from(b64, 'base64').toString('hex')); + }; + const biToB64 = n => shim.Buffer.from(n.toString(16).padStart(64, '0'), 'hex') + .toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); + const pubFromPriv = priv => { + const pub = pointMult(priv, G); + if (!isOnCurve(pub)) throw new Error("Invalid point generated"); + return biToB64(pub.x) + '.' + biToB64(pub.y); + }; + const seedToKey = async (seed, salt) => { + const enc = new shim.TextEncoder(); + const buf = typeof seed === 'string' ? enc.encode(seed).buffer : + seed instanceof ArrayBuffer ? seed : + seed && seed.byteLength !== undefined ? (seed.buffer || seed) : null; + if (!buf) throw new Error("Invalid seed"); + const combined = new Uint8Array(buf.byteLength + enc.encode(salt).buffer.byteLength); + combined.set(new Uint8Array(buf), 0); + combined.set(new Uint8Array(enc.encode(salt).buffer), buf.byteLength); + const hash = await subtle.digest("SHA-256", combined.buffer); + let priv = BigInt("0x" + Array.from(new Uint8Array(hash)) + .map(b => b.toString(16).padStart(2, "0")).join("")) % n; + if (priv <= 0n || priv >= n) priv = (priv + 1n) % n; + return priv; + }; - try{ - var dh = await ecdhSubtle.generateKey({name: 'ECDH', namedCurve: 'P-256'}, true, ['deriveKey']) - .then(async (keys) => { - // privateKey scope doesn't leak out from here! - var key = {}; - key.epriv = (await ecdhSubtle.exportKey('jwk', keys.privateKey)).d; - var pub = await ecdhSubtle.exportKey('jwk', keys.publicKey); - //const epub = Buff.from([ ex, ey ].join(':')).toString('base64') // old - key.epub = pub.x+'.'+pub.y; // new - // ex and ey are already base64 - // epub is UTF8 but filename/URL safe (https://www.ietf.org/rfc/rfc3986.txt) - // but split on a non-base64 letter. - return key; - }) - }catch(e){ - if(SEA.window){ throw e } - if(e == 'Error: ECDH is not a supported algorithm'){ console.log('Ignoring ECDH...') } - else { throw e } - } dh = dh || {}; + if (opt.priv) { + const priv = b64ToBI(opt.priv); + r = { priv: opt.priv, pub: pubFromPriv(priv) }; + if (opt.epriv) { + r.epriv = opt.epriv; + r.epub = pubFromPriv(b64ToBI(opt.epriv)); + } else { + try { + const dh = await ecdhSubtle.generateKey({name: 'ECDH', namedCurve: 'P-256'}, true, ['deriveKey']) + .then(async k => ({ + epriv: (await ecdhSubtle.exportKey('jwk', k.privateKey)).d, + epub: (await ecdhSubtle.exportKey('jwk', k.publicKey)).x + '.' + + (await ecdhSubtle.exportKey('jwk', k.publicKey)).y + })); + r.epriv = dh.epriv; r.epub = dh.epub; + } catch(e) {} + } + } else if (opt.epriv) { + r = { epriv: opt.epriv, epub: pubFromPriv(b64ToBI(opt.epriv)) }; + if (opt.priv) { + r.priv = opt.priv; + r.pub = pubFromPriv(b64ToBI(opt.priv)); + } else { + const sa = await subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, ['sign', 'verify']) + .then(async k => ({ + priv: (await subtle.exportKey('jwk', k.privateKey)).d, + pub: (await subtle.exportKey('jwk', k.publicKey)).x + '.' + + (await subtle.exportKey('jwk', k.publicKey)).y + })); + r.priv = sa.priv; r.pub = sa.pub; + } + } else if (opt.seed) { + const signPriv = await seedToKey(opt.seed, "-sign"); + const encPriv = await seedToKey(opt.seed, "-encrypt"); + r = { + priv: biToB64(signPriv), pub: pubFromPriv(signPriv), + epriv: biToB64(encPriv), epub: pubFromPriv(encPriv) + }; + } else { + const sa = await subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, ['sign', 'verify']) + .then(async k => ({ + priv: (await subtle.exportKey('jwk', k.privateKey)).d, + pub: (await subtle.exportKey('jwk', k.publicKey)).x + '.' + + (await subtle.exportKey('jwk', k.publicKey)).y + })); + r = { pub: sa.pub, priv: sa.priv }; + try { + const dh = await ecdhSubtle.generateKey({name: 'ECDH', namedCurve: 'P-256'}, true, ['deriveKey']) + .then(async k => ({ + epriv: (await ecdhSubtle.exportKey('jwk', k.privateKey)).d, + epub: (await ecdhSubtle.exportKey('jwk', k.publicKey)).x + '.' + + (await ecdhSubtle.exportKey('jwk', k.publicKey)).y + })); + r.epub = dh.epub; r.epriv = dh.epriv; + } catch(e) {} + } - var r = { pub: sa.pub, priv: sa.priv, /* pubId, */ epub: dh.epub, epriv: dh.epriv } - if(cb){ try{ cb(r) }catch(e){console.log(e)} } + if(cb) try{ cb(r) }catch(e){ console.log(e) } return r; } catch(e) { - console.log(e); SEA.err = e; - if(SEA.throw){ throw e } - if(cb){ cb() } + if(SEA.throw) throw e; + if(cb) cb(); return; }}); @@ -398,35 +501,60 @@ var sha = USE('./sha256'); var u; + async function n(r, o, c) { + try { + if(!o.raw){ r = 'SEA' + await shim.stringify(r) } + if(c){ try{ c(r) }catch(e){} } + return r; + } catch(e) { return r } + } + + async function w(r, j, o, c) { + var x = { + m: j, + s: r.signature ? shim.Buffer.from(r.signature, 'binary').toString(o.encode || 'base64') : u, + a: shim.Buffer.from(r.authenticatorData, 'binary').toString('base64'), + c: shim.Buffer.from(r.clientDataJSON, 'binary').toString('base64') + }; + if (!x.s || !x.a || !x.c) throw "WebAuthn signature invalid"; + return n(x, o, c); + } + + async function k(p, j, o, c) { + var x = S.jwk(p.pub, p.priv); + if (!x) throw "Invalid key pair"; + var h = await sha(j); + var s = await (shim.ossl || shim.subtle).importKey('jwk', x, S.ecdsa.pair, false, ['sign']) + .then((k) => (shim.ossl || shim.subtle).sign(S.ecdsa.sign, k, new Uint8Array(h))) + .catch(() => { throw "SEA signature failed" }); + return n({m: j, s: shim.Buffer.from(s, 'binary').toString(o.encode || 'base64')}, o, c); + } + SEA.sign = SEA.sign || (async (data, pair, cb, opt) => { try { opt = opt || {}; - if(!(pair||opt).priv){ - if(!SEA.I){ throw 'No signing key.' } + if(u === data) throw '`undefined` not allowed.'; + if(!(pair||opt).priv && typeof pair !== 'function'){ + if(!SEA.I) throw 'No signing key.'; pair = await SEA.I(null, {what: data, how: 'sign', why: opt.why}); } - if(u === data){ throw '`undefined` not allowed.' } - var json = await S.parse(data); - var check = opt.check = opt.check || json; - if(SEA.verify && (SEA.opt.check(check) || (check && check.s && check.m)) - && u !== await SEA.verify(check, pair)){ // don't sign if we already signed it. - var r = await S.parse(check); - if(!opt.raw){ r = 'SEA' + await shim.stringify(r) } - if(cb){ try{ cb(r) }catch(e){console.log(e)} } - return r; - } - var pub = pair.pub; - var priv = pair.priv; - var jwk = S.jwk(pub, priv); - var hash = await sha(json); - var sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, {name: 'ECDSA', namedCurve: 'P-256'}, false, ['sign']) - .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! - var r = {m: json, s: shim.Buffer.from(sig, 'binary').toString(opt.encode || 'base64')} - if(!opt.raw){ r = 'SEA' + await shim.stringify(r) } - if(cb){ try{ cb(r) }catch(e){console.log(e)} } - return r; + var j = await S.parse(data); + var c = opt.check = opt.check || j; + + if(SEA.verify && (S.check(c) || (c && c.s && c.m)) + && u !== await SEA.verify(c, pair)){ + return n(await S.parse(c), opt, cb); + } + + if(typeof pair === 'function') { + var r = await pair(data); + return r.authenticatorData ? w(r, j, opt, cb) : + n({m: j, s: typeof r === 'string' ? r : + r.signature && shim.Buffer.from(r.signature, 'binary').toString(opt.encode || 'base64')}, opt, cb); + } + + return k(pair, j, opt, cb); } catch(e) { - console.log(e); SEA.err = e; if(SEA.throw){ throw e } if(cb){ cb() } @@ -443,34 +571,97 @@ var sha = USE('./sha256'); var u; - SEA.verify = SEA.verify || (async (data, pair, cb, opt) => { try { - var json = await S.parse(data); - if(false === pair){ // don't verify! - var raw = await S.parse(json.m); - if(cb){ try{ cb(raw) }catch(e){console.log(e)} } - return raw; + async function w(j, k, s) { + var a = new Uint8Array(shim.Buffer.from(j.a, 'base64')); + var c = shim.Buffer.from(j.c, 'base64').toString('utf8'); + var m = new TextEncoder().encode(j.m); + var e = btoa(String.fromCharCode(...new Uint8Array(m))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); + if (JSON.parse(c).challenge !== e) throw "Challenge verification failed"; + var h = await (shim.ossl || shim.subtle).digest( + {name: 'SHA-256'}, + new TextEncoder().encode(c) + ); + var d = new Uint8Array(a.length + h.byteLength); + d.set(a); + d.set(new Uint8Array(h), a.length); + if (s[0] !== 0x30) throw "Invalid DER signature format"; + var o = 2, r = new Uint8Array(64); + for(var i = 0; i < 2; i++) { + var l = s[o + 1]; + o += 2; + if (s[o] === 0x00) { o++; l--; } + var p = new Uint8Array(32).fill(0); + p.set(s.slice(o, o + l), 32 - l); + r.set(p, i * 32); + o += l; } - opt = opt || {}; - // SEA.I // verify is free! Requires no user permission. - var pub = pair.pub || pair; - 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']); - var hash = await sha(json.m); - var buf, sig, check, tmp; try{ - buf = shim.Buffer.from(json.s, opt.encode || 'base64'); // NEW DEFAULT! - sig = new Uint8Array(buf); - check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash)); - if(!check){ throw "Signature did not match." } - }catch(e){ - if(SEA.opt.fallback){ - return await SEA.opt.fall_verify(data, pair, cb, opt); - } - } - var r = check? await S.parse(json.m) : u; + return (shim.ossl || shim.subtle).verify({ name: 'ECDSA', hash: {name: 'SHA-256'} }, k, r, d); + } - if(cb){ try{ cb(r) }catch(e){console.log(e)} } - return r; + async function v(j, k, s, h) { + return (shim.ossl || shim.subtle).verify( + {name: 'ECDSA', hash: {name: 'SHA-256'}}, + k, s, new Uint8Array(h) + ); + } + + SEA.verify = SEA.verify || (async (d, p, cb, o) => { try { + var j = await S.parse(d); + if(false === p) return cb ? cb(await S.parse(j.m)) : await S.parse(j.m); + + o = o || {}; + var pub = p.pub || p; + var [x, y] = pub.split('.'); + + try { + var k = await (shim.ossl || shim.subtle).importKey('jwk', { + kty: 'EC', crv: 'P-256', x, y, ext: true, key_ops: ['verify'] + }, {name: 'ECDSA', namedCurve: 'P-256'}, false, ['verify']); + + var h = await sha(j.m); + var s = new Uint8Array(shim.Buffer.from(j.s || '', o.encode || 'base64')); + + var c = j.a && j.c ? await w(j, k, s) : await v(j, k, s, h); + + if(!c) throw "Signature did not match"; + + // Parse the message content + var r = await S.parse(j.m); + + // Handle encrypted data consistently + // SEA encrypted data can be in two formats: + // 1. A string starting with 'SEA' followed by JSON (e.g., 'SEA{"ct":"...","iv":"...","s":"..."}') + // 2. An object with ct, iv, and s properties + + // Case 1: Original message was already in SEA string format + if(typeof j.m === 'string' && j.m.startsWith('SEA{')) { + if(cb){ try{ cb(j.m) }catch(e){} } + return j.m; + } + + // Case 2: Result is an encrypted data object + // This ensures consistent formatting of encrypted data as SEA strings + if(r && typeof r === 'object' && + typeof r.ct === 'string' && + typeof r.iv === 'string' && + typeof r.s === 'string') { + // Format as standard SEA encrypted string + var seaStr = 'SEA' + JSON.stringify(r); + if(cb){ try{ cb(seaStr) }catch(e){} } + return seaStr; + } + + // Default case: Return parsed result as is + if(cb){ try{ cb(r) }catch(e){} } + return r; + } catch(e) { + if(SEA.opt.fallback){ + return await SEA.opt.fall_verify(d, p, cb, o); + } + if(cb){ cb() } + return; + } } catch(e) { - console.log(e); // mismatched owner FOR MARTTI SEA.err = e; if(SEA.throw){ throw e } if(cb){ cb() } @@ -478,43 +669,51 @@ }}); module.exports = SEA.verify; - // legacy & ossl memory leak mitigation: var knownKeys = {}; - var keyForPair = SEA.opt.slow_leak = pair => { + SEA.opt.slow_leak = pair => { if (knownKeys[pair]) return knownKeys[pair]; var jwk = S.jwk(pair); knownKeys[pair] = (shim.ossl || shim.subtle).importKey("jwk", jwk, {name: 'ECDSA', namedCurve: 'P-256'}, false, ["verify"]); return knownKeys[pair]; }; - var O = SEA.opt; SEA.opt.fall_verify = async function(data, pair, cb, opt, f){ - if(f === SEA.opt.fallback){ throw "Signature did not match" } f = f || 1; + if(f === SEA.opt.fallback){ throw "Signature did not match" } var tmp = data||''; data = SEA.opt.unpack(data) || data; - var json = await S.parse(data), pub = pair.pub || pair, key = await SEA.opt.slow_leak(pub); - 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. - var buf; var sig; var check; try{ - buf = shim.Buffer.from(json.s, opt.encode || 'base64') // NEW DEFAULT! - sig = new Uint8Array(buf) - check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash)) - if(!check){ throw "Signature did not match." } - }catch(e){ try{ - buf = shim.Buffer.from(json.s, 'utf8') // AUTO BACKWARD OLD UTF8 DATA! - sig = new Uint8Array(buf) - check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash)) - }catch(e){ - if(!check){ throw "Signature did not match." } - } + var json = await S.parse(data), key = await SEA.opt.slow_leak(pair.pub || pair); + var hash = (!f || 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); + + try { + var buf = shim.Buffer.from(json.s, opt.encode || 'base64'); + var sig = new Uint8Array(buf); + var check = await (shim.ossl || shim.subtle).verify( + {name: 'ECDSA', hash: {name: 'SHA-256'}}, + key, sig, new Uint8Array(hash) + ); + if(!check) throw ""; + } catch(e) { + try { + buf = shim.Buffer.from(json.s, 'utf8'); + sig = new Uint8Array(buf); + check = await (shim.ossl || shim.subtle).verify( + {name: 'ECDSA', hash: {name: 'SHA-256'}}, + key, sig, new Uint8Array(hash) + ); + if(!check) throw ""; + } catch(e){ throw "Signature did not match." } } - var r = check? await S.parse(json.m) : u; - O.fall_soul = tmp['#']; O.fall_key = tmp['.']; O.fall_val = data; O.fall_state = tmp['>']; + + var r = check ? await S.parse(json.m) : u; + SEA.opt.fall_soul = tmp['#']; SEA.opt.fall_key = tmp['.']; + SEA.opt.fall_val = data; SEA.opt.fall_state = tmp['>']; if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; } SEA.opt.fallback = 2; - })(USE, './verify'); ;USE(function(module){ @@ -527,7 +726,7 @@ opt = opt || {}; const combo = key + (salt || shim.random(8)).toString('utf8'); // new const hash = shim.Buffer.from(await sha256hash(combo), 'binary') - + const jwkKey = S.keyToJwk(hash) return await shim.subtle.importKey('jwk', jwkKey, {name:'AES-GCM'}, false, ['encrypt', 'decrypt']) } @@ -685,7 +884,6 @@ "cb": A callback function after all things are done. "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. */ - console.log('SEA.certify() is an early experimental community supported method that may change API behavior without warning in any future version.') certificants = (() => { var data = [] @@ -866,7 +1064,7 @@ var pass = pair && (pair.pub || pair.epub) ? pair : alias && typeof args[1] === 'string' ? args[1] : null; var cb = args.filter(arg => typeof arg === 'function')[0] || null; // cb now can stand anywhere, after alias/pass or pair 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 - + var gun = this, cat = (gun._), root = gun.back(-1); cb = cb || noop; opt = opt || {}; @@ -973,13 +1171,13 @@ var retries = typeof opt.retries === 'number' ? opt.retries : 9; var gun = this, cat = (gun._), root = gun.back(-1); - + if(cat.ing){ (cb || noop)({err: Gun.log("User is already being created or authenticated!"), wait: true}); return gun; } cat.ing = true; - + var act = {}, u; act.a = function(data){ if(!data){ return act.b() } @@ -1340,7 +1538,7 @@ return; // omit! } } - + if('~@' === soul){ // special case for shared system data, the list of aliases. check.alias(eve, msg, val, key, soul, at, no); return; } @@ -1357,18 +1555,23 @@ check.any(eve, msg, val, key, soul, at, no, at.user||''); return; eve.to.next(msg); // not handled } - check.hash = function(eve, msg, val, key, soul, at, no){ // mark unbuilt @i001962 's epic hex contrib! - SEA.work(val, null, function(data){ - function hexToBase64(hexStr) { - let base64 = ""; - for(let i = 0; i < hexStr.length; i++) { - base64 += !(i - 1 & 1) ? String.fromCharCode(parseInt(hexStr.substring(i - 1, i + 1), 16)) : ""} - return btoa(base64);} - if(data && data === key.split('#').slice(-1)[0]){ return eve.to.next(msg) } - else if (data && data === hexToBase64(key.split('#').slice(-1)[0])){ - return eve.to.next(msg) } + // Verify content-addressed data matches its hash + check.hash = function (eve, msg, val, key, soul, at, no) { + function base64ToHex(data) { + var binaryStr = atob(data); + var a = []; + for (var i = 0; i < binaryStr.length; i++) { + var hex = binaryStr.charCodeAt(i).toString(16); + a.push(hex.length === 1 ? "0" + hex : hex); + } + return a.join(""); + } + var hash = key.split('#').pop(); + SEA.work(val, null, function (b64hash) { + var hexhash = base64ToHex(b64hash), b64slice = b64hash.slice(-20), hexslice = hexhash.slice(-20); + if ([b64hash, b64slice, hexhash, hexslice].some(item => item.endsWith(hash))) return eve.to.next(msg); no("Data hash not same as hash!"); - }, {name: 'SHA-256'}); + }, { name: 'SHA-256' }); } check.alias = function(eve, msg, val, key, soul, at, no){ // Example: {_:#~@, ~@alice: {#~@alice}} if(!val){ return no("Data must exist!") } // data MUST exist @@ -1381,7 +1584,6 @@ no("Alias not same!"); // that way nobody can tamper with the list of public keys. }; check.pub = async function(eve, msg, val, key, soul, at, no, user, pub){ var tmp // Example: {_:#~asdf, hello:'world'~fdsa}} - const raw = await S.parse(val) || {} const verify = (certificate, certificant, cb) => { if (certificate.m && certificate.s && certificant && pub) // now verify certificate @@ -1415,43 +1617,56 @@ }) return } - + + const next = () => { + JSON.stringifyAsync(msg.put[':'], function(err,s){ + if(err){ return no(err || "Stringify error.") } + msg.put[':'] = s; + return eve.to.next(msg); + }) + } + + // Localize some opt props, and delete the original refs to prevent possible attacks + const opt = (msg._.msg || {}).opt || {} + const authenticator = opt.authenticator || (user._ || {}).sea; + const upub = opt.authenticator ? (opt.pub || (user.is || {}).pub || pub) : (user.is || {}).pub; + const cert = opt.cert; + delete opt.authenticator; delete opt.pub; + const raw = await S.parse(val) || {} + if ('pub' === key && '~' + pub === soul) { if (val === pub) return eve.to.next(msg) // the account MUST match `pub` property that equals the ID of the public key. return no("Account not same!") } - if ((tmp = user.is) && tmp.pub && !raw['*'] && !raw['+'] && (pub === tmp.pub || (pub !== tmp.pub && ((msg._.msg || {}).opt || {}).cert))){ + if ((user.is || authenticator) && upub && !raw['*'] && !raw['+'] && (pub === upub || (pub !== upub && cert))){ SEA.opt.pack(msg.put, packed => { - SEA.sign(packed, (user._).sea, async function(data) { + // Validate authenticator + if (!authenticator) return no("Missing authenticator"); + SEA.sign(packed, authenticator, async function(data) { if (u === data) return no(SEA.err || 'Signature fail.') + // Validate signature format + if (!data.m || !data.s) return no('Invalid signature format') + msg.put[':'] = {':': tmp = SEA.opt.unpack(data.m), '~': data.s} msg.put['='] = tmp - + // if writing to own graph, just allow it - if (pub === user.is.pub) { + if (pub === upub) { if (tmp = link_is(val)) (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1 - JSON.stringifyAsync(msg.put[':'], function(err,s){ - if(err){ return no(err || "Stringify error.") } - msg.put[':'] = s; - return eve.to.next(msg); - }) + next() return } - + // 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 - if (pub !== user.is.pub && ((msg._.msg || {}).opt || {}).cert) { - const cert = await S.parse(msg._.msg.opt.cert) + if (pub !== upub && cert) { + const _cert = await S.parse(cert) // even if cert exists, we must verify it - if (cert && cert.m && cert.s) - verify(cert, user.is.pub, _ => { - msg.put[':']['+'] = cert // '+' is a certificate - msg.put[':']['*'] = user.is.pub // '*' is pub of the user who puts - JSON.stringifyAsync(msg.put[':'], function(err,s){ - if(err){ return no(err || "Stringify error.") } - msg.put[':'] = s; - return eve.to.next(msg); - }) + if (_cert && _cert.m && _cert.s) + verify(_cert, upub, _ => { + msg.put[':']['+'] = _cert // '+' is a certificate + msg.put[':']['*'] = upub // '*' is pub of the user who puts + next() return }) } @@ -1465,7 +1680,7 @@ data = SEA.opt.unpack(data); 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. if ((tmp = link_is(data)) && pub === SEA.opt.pub(tmp)) (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1 - + // check if cert ('+') and putter's pub ('*') exist if (raw['+'] && raw['+']['m'] && raw['+']['s'] && raw['*']) // now verify certificate @@ -1535,6 +1750,6 @@ SEA.opt.shuffle_attack = 1546329600000; // Jan 1, 2019 var fl = Math.floor; // TODO: Still need to fix inconsistent state issue. // TODO: Potential bug? If pub/priv key starts with `-`? IDK how possible. - })(USE, './index'); -}()); + +}()); \ No newline at end of file diff --git a/sea/aeskey.js b/sea/aeskey.js index 3f0a3d57..db4e9024 100644 --- a/sea/aeskey.js +++ b/sea/aeskey.js @@ -9,7 +9,7 @@ opt = opt || {}; const combo = key + (salt || shim.random(8)).toString('utf8'); // new const hash = shim.Buffer.from(await sha256hash(combo), 'binary') - + const jwkKey = S.keyToJwk(hash) return await shim.subtle.importKey('jwk', jwkKey, {name:'AES-GCM'}, false, ['encrypt', 'decrypt']) } diff --git a/sea/auth.js b/sea/auth.js index 76a0a70c..7cb61bc8 100644 --- a/sea/auth.js +++ b/sea/auth.js @@ -11,13 +11,13 @@ var retries = typeof opt.retries === 'number' ? opt.retries : 9; var gun = this, cat = (gun._), root = gun.back(-1); - + if(cat.ing){ (cb || noop)({err: Gun.log("User is already being created or authenticated!"), wait: true}); return gun; } cat.ing = true; - + var act = {}, u; act.a = function(data){ if(!data){ return act.b() } diff --git a/sea/certify.js b/sea/certify.js index f052b190..74f5321a 100644 --- a/sea/certify.js +++ b/sea/certify.js @@ -12,7 +12,6 @@ "cb": A callback function after all things are done. "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. */ - console.log('SEA.certify() is an early experimental community supported method that may change API behavior without warning in any future version.') certificants = (() => { var data = [] diff --git a/sea/create.js b/sea/create.js index 5036fc56..05521bc7 100644 --- a/sea/create.js +++ b/sea/create.js @@ -9,7 +9,7 @@ var pass = pair && (pair.pub || pair.epub) ? pair : alias && typeof args[1] === 'string' ? args[1] : null; var cb = args.filter(arg => typeof arg === 'function')[0] || null; // cb now can stand anywhere, after alias/pass or pair 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 - + var gun = this, cat = (gun._), root = gun.back(-1); cb = cb || noop; opt = opt || {}; diff --git a/sea/index.js b/sea/index.js index de21f0e5..0e69a573 100644 --- a/sea/index.js +++ b/sea/index.js @@ -50,7 +50,7 @@ return; // omit! } } - + if('~@' === soul){ // special case for shared system data, the list of aliases. check.alias(eve, msg, val, key, soul, at, no); return; } @@ -67,18 +67,23 @@ check.any(eve, msg, val, key, soul, at, no, at.user||''); return; eve.to.next(msg); // not handled } - check.hash = function(eve, msg, val, key, soul, at, no){ // mark unbuilt @i001962 's epic hex contrib! - SEA.work(val, null, function(data){ - function hexToBase64(hexStr) { - let base64 = ""; - for(let i = 0; i < hexStr.length; i++) { - base64 += !(i - 1 & 1) ? String.fromCharCode(parseInt(hexStr.substring(i - 1, i + 1), 16)) : ""} - return btoa(base64);} - if(data && data === key.split('#').slice(-1)[0]){ return eve.to.next(msg) } - else if (data && data === hexToBase64(key.split('#').slice(-1)[0])){ - return eve.to.next(msg) } + // Verify content-addressed data matches its hash + check.hash = function (eve, msg, val, key, soul, at, no) { + function base64ToHex(data) { + var binaryStr = atob(data); + var a = []; + for (var i = 0; i < binaryStr.length; i++) { + var hex = binaryStr.charCodeAt(i).toString(16); + a.push(hex.length === 1 ? "0" + hex : hex); + } + return a.join(""); + } + var hash = key.split('#').pop(); + SEA.work(val, null, function (b64hash) { + var hexhash = base64ToHex(b64hash), b64slice = b64hash.slice(-20), hexslice = hexhash.slice(-20); + if ([b64hash, b64slice, hexhash, hexslice].some(item => item.endsWith(hash))) return eve.to.next(msg); no("Data hash not same as hash!"); - }, {name: 'SHA-256'}); + }, { name: 'SHA-256' }); } check.alias = function(eve, msg, val, key, soul, at, no){ // Example: {_:#~@, ~@alice: {#~@alice}} if(!val){ return no("Data must exist!") } // data MUST exist @@ -91,7 +96,6 @@ no("Alias not same!"); // that way nobody can tamper with the list of public keys. }; check.pub = async function(eve, msg, val, key, soul, at, no, user, pub){ var tmp // Example: {_:#~asdf, hello:'world'~fdsa}} - const raw = await S.parse(val) || {} const verify = (certificate, certificant, cb) => { if (certificate.m && certificate.s && certificant && pub) // now verify certificate @@ -99,7 +103,7 @@ if (u !== data && u !== data.e && msg.put['>'] && msg.put['>'] > parseFloat(data.e)) return no("Certificate expired.") // certificate expired // "data.c" = a list of certificants/certified users // "data.w" = lex WRITE permission, in the future, there will be "data.r" which means lex READ permission - if (u !== data && data.c && data.w && (data.c === certificant || data.c.indexOf('*' || certificant) > -1)) { + if (u !== data && data.c && data.w && (data.c === certificant || data.c.indexOf('*') > -1 || data.c.indexOf(certificant) > -1)) { // ok, now "certificant" is in the "certificants" list, but is "path" allowed? Check path let path = soul.indexOf('/') > -1 ? soul.replace(soul.substring(0, soul.indexOf('/') + 1), '') : '' String.match = String.match || Gun.text.match @@ -125,43 +129,56 @@ }) return } - + + const next = () => { + JSON.stringifyAsync(msg.put[':'], function(err,s){ + if(err){ return no(err || "Stringify error.") } + msg.put[':'] = s; + return eve.to.next(msg); + }) + } + + // Localize some opt props, and delete the original refs to prevent possible attacks + const opt = (msg._.msg || {}).opt || {} + const authenticator = opt.authenticator || (user._ || {}).sea; + const upub = opt.authenticator ? (opt.pub || (user.is || {}).pub || pub) : (user.is || {}).pub; + const cert = opt.cert; + delete opt.authenticator; delete opt.pub; + const raw = await S.parse(val) || {} + if ('pub' === key && '~' + pub === soul) { if (val === pub) return eve.to.next(msg) // the account MUST match `pub` property that equals the ID of the public key. return no("Account not same!") } - if ((tmp = user.is) && tmp.pub && !raw['*'] && !raw['+'] && (pub === tmp.pub || (pub !== tmp.pub && ((msg._.msg || {}).opt || {}).cert))){ + if ((user.is || authenticator) && upub && !raw['*'] && !raw['+'] && (pub === upub || (pub !== upub && cert))){ SEA.opt.pack(msg.put, packed => { - SEA.sign(packed, (user._).sea, async function(data) { + // Validate authenticator + if (!authenticator) return no("Missing authenticator"); + SEA.sign(packed, authenticator, async function(data) { if (u === data) return no(SEA.err || 'Signature fail.') + // Validate signature format + if (!data.m || !data.s) return no('Invalid signature format') + msg.put[':'] = {':': tmp = SEA.opt.unpack(data.m), '~': data.s} msg.put['='] = tmp - + // if writing to own graph, just allow it - if (pub === user.is.pub) { + if (pub === upub) { if (tmp = link_is(val)) (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1 - JSON.stringifyAsync(msg.put[':'], function(err,s){ - if(err){ return no(err || "Stringify error.") } - msg.put[':'] = s; - return eve.to.next(msg); - }) + next() return } - + // 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 - if (pub !== user.is.pub && ((msg._.msg || {}).opt || {}).cert) { - const cert = await S.parse(msg._.msg.opt.cert) + if (pub !== upub && cert) { + const _cert = await S.parse(cert) // even if cert exists, we must verify it - if (cert && cert.m && cert.s) - verify(cert, user.is.pub, _ => { - msg.put[':']['+'] = cert // '+' is a certificate - msg.put[':']['*'] = user.is.pub // '*' is pub of the user who puts - JSON.stringifyAsync(msg.put[':'], function(err,s){ - if(err){ return no(err || "Stringify error.") } - msg.put[':'] = s; - return eve.to.next(msg); - }) + if (_cert && _cert.m && _cert.s) + verify(_cert, upub, _ => { + msg.put[':']['+'] = _cert // '+' is a certificate + msg.put[':']['*'] = upub // '*' is pub of the user who puts + next() return }) } @@ -175,7 +192,7 @@ data = SEA.opt.unpack(data); 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. if ((tmp = link_is(data)) && pub === SEA.opt.pub(tmp)) (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1 - + // check if cert ('+') and putter's pub ('*') exist if (raw['+'] && raw['+']['m'] && raw['+']['s'] && raw['*']) // now verify certificate @@ -245,6 +262,5 @@ SEA.opt.shuffle_attack = 1546329600000; // Jan 1, 2019 var fl = Math.floor; // TODO: Still need to fix inconsistent state issue. // TODO: Potential bug? If pub/priv key starts with `-`? IDK how possible. - }()); \ No newline at end of file diff --git a/sea/pair.js b/sea/pair.js index d0d6f04c..f14133a2 100644 --- a/sea/pair.js +++ b/sea/pair.js @@ -2,71 +2,168 @@ var SEA = require('./root'); var shim = require('./shim'); - var S = require('./settings'); - SEA.name = SEA.name || (async (cb, opt) => { try { - if(cb){ try{ cb() }catch(e){console.log(e)} } - return; - } catch(e) { - console.log(e); - SEA.err = e; - if(SEA.throw){ throw e } - if(cb){ cb() } - return; - }}); + // P-256 curve constants + const n = BigInt("0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551"); + const P = BigInt("0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff"); + const A = BigInt("0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc"); + const B = BigInt("0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b"); // Missing B parameter + const G = { + x: BigInt("0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296"), + y: BigInt("0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5") + }; + + // Core ECC functions + function mod(a, m) { return ((a % m) + m) % m; } + + // Constant-time modular inverse using Fermat's Little Theorem (p is prime) + function modInv(a, p) { + // a^(p-2) mod p + return modPow(a, p - BigInt(2), p); + } + + // Constant-time modular exponentiation (square-and-multiply) + function modPow(base, exponent, modulus) { + if (modulus === BigInt(1)) return BigInt(0); + base = mod(base, modulus); + let result = BigInt(1); + while (exponent > BigInt(0)) { + if (exponent & BigInt(1)) { + result = mod(result * base, modulus); + } + exponent >>= BigInt(1); + base = mod(base * base, modulus); + } + return result; + } + + // Verify a point is on the curve + function isOnCurve(point) { + if (!point) return false; + // y² = x³ + ax + b (mod p) + const { x, y } = point; + const left = mod(y * y, P); + const right = mod(mod(mod(x * x, P) * x, P) + mod(A * x, P) + B, P); + return left === right; + } + + function pointAdd(p1, p2) { + if (p1 === null) return p2; if (p2 === null) return p1; + if (p1.x === p2.x && mod(p1.y + p2.y, P) === 0n) return null; + let lambda = p1.x === p2.x && p1.y === p2.y + ? mod((3n * mod(p1.x ** 2n, P) + A) * modInv(2n * p1.y, P), P) + : mod((mod(p2.y - p1.y, P)) * modInv(mod(p2.x - p1.x, P), P), P); + const x3 = mod(lambda ** 2n - p1.x - p2.x, P); + return { x: x3, y: mod(lambda * mod(p1.x - x3, P) - p1.y, P) }; + } + + function pointMult(k, point) { + let r = null, a = point; + while (k > 0n) { + if (k & 1n) r = pointAdd(r, a); + a = pointAdd(a, a); + k >>= 1n; + } + return r; + } - //SEA.pair = async (data, proof, cb) => { try { SEA.pair = SEA.pair || (async (cb, opt) => { try { + opt = opt || {}; + const subtle = shim.subtle, ecdhSubtle = shim.ossl || subtle; + let r = {}; - var ecdhSubtle = shim.ossl || shim.subtle; - // First: ECDSA keys for signing/verifying... - var sa = await shim.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, [ 'sign', 'verify' ]) - .then(async (keys) => { - // privateKey scope doesn't leak out from here! - //const { d: priv } = await shim.subtle.exportKey('jwk', keys.privateKey) - var key = {}; - key.priv = (await shim.subtle.exportKey('jwk', keys.privateKey)).d; - var pub = await shim.subtle.exportKey('jwk', keys.publicKey); - //const pub = Buff.from([ x, y ].join(':')).toString('base64') // old - key.pub = pub.x+'.'+pub.y; // new - // x and y are already base64 - // pub is UTF8 but filename/URL safe (https://www.ietf.org/rfc/rfc3986.txt) - // but split on a non-base64 letter. - return key; - }) - - // To include PGPv4 kind of keyId: - // const pubId = await SEA.keyid(keys.pub) - // Next: ECDH keys for encryption/decryption... + // Helper functions + const b64ToBI = s => { + let b64 = s.replace(/-/g, '+').replace(/_/g, '/'); + while (b64.length % 4) b64 += '='; + return BigInt('0x' + shim.Buffer.from(b64, 'base64').toString('hex')); + }; + const biToB64 = n => shim.Buffer.from(n.toString(16).padStart(64, '0'), 'hex') + .toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); + const pubFromPriv = priv => { + const pub = pointMult(priv, G); + if (!isOnCurve(pub)) throw new Error("Invalid point generated"); + return biToB64(pub.x) + '.' + biToB64(pub.y); + }; + const seedToKey = async (seed, salt) => { + const enc = new shim.TextEncoder(); + const buf = typeof seed === 'string' ? enc.encode(seed).buffer : + seed instanceof ArrayBuffer ? seed : + seed && seed.byteLength !== undefined ? (seed.buffer || seed) : null; + if (!buf) throw new Error("Invalid seed"); + const combined = new Uint8Array(buf.byteLength + enc.encode(salt).buffer.byteLength); + combined.set(new Uint8Array(buf), 0); + combined.set(new Uint8Array(enc.encode(salt).buffer), buf.byteLength); + const hash = await subtle.digest("SHA-256", combined.buffer); + let priv = BigInt("0x" + Array.from(new Uint8Array(hash)) + .map(b => b.toString(16).padStart(2, "0")).join("")) % n; + if (priv <= 0n || priv >= n) priv = (priv + 1n) % n; + return priv; + }; - try{ - var dh = await ecdhSubtle.generateKey({name: 'ECDH', namedCurve: 'P-256'}, true, ['deriveKey']) - .then(async (keys) => { - // privateKey scope doesn't leak out from here! - var key = {}; - key.epriv = (await ecdhSubtle.exportKey('jwk', keys.privateKey)).d; - var pub = await ecdhSubtle.exportKey('jwk', keys.publicKey); - //const epub = Buff.from([ ex, ey ].join(':')).toString('base64') // old - key.epub = pub.x+'.'+pub.y; // new - // ex and ey are already base64 - // epub is UTF8 but filename/URL safe (https://www.ietf.org/rfc/rfc3986.txt) - // but split on a non-base64 letter. - return key; - }) - }catch(e){ - if(SEA.window){ throw e } - if(e == 'Error: ECDH is not a supported algorithm'){ console.log('Ignoring ECDH...') } - else { throw e } - } dh = dh || {}; + if (opt.priv) { + const priv = b64ToBI(opt.priv); + r = { priv: opt.priv, pub: pubFromPriv(priv) }; + if (opt.epriv) { + r.epriv = opt.epriv; + r.epub = pubFromPriv(b64ToBI(opt.epriv)); + } else { + try { + const dh = await ecdhSubtle.generateKey({name: 'ECDH', namedCurve: 'P-256'}, true, ['deriveKey']) + .then(async k => ({ + epriv: (await ecdhSubtle.exportKey('jwk', k.privateKey)).d, + epub: (await ecdhSubtle.exportKey('jwk', k.publicKey)).x + '.' + + (await ecdhSubtle.exportKey('jwk', k.publicKey)).y + })); + r.epriv = dh.epriv; r.epub = dh.epub; + } catch(e) {} + } + } else if (opt.epriv) { + r = { epriv: opt.epriv, epub: pubFromPriv(b64ToBI(opt.epriv)) }; + if (opt.priv) { + r.priv = opt.priv; + r.pub = pubFromPriv(b64ToBI(opt.priv)); + } else { + const sa = await subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, ['sign', 'verify']) + .then(async k => ({ + priv: (await subtle.exportKey('jwk', k.privateKey)).d, + pub: (await subtle.exportKey('jwk', k.publicKey)).x + '.' + + (await subtle.exportKey('jwk', k.publicKey)).y + })); + r.priv = sa.priv; r.pub = sa.pub; + } + } else if (opt.seed) { + const signPriv = await seedToKey(opt.seed, "-sign"); + const encPriv = await seedToKey(opt.seed, "-encrypt"); + r = { + priv: biToB64(signPriv), pub: pubFromPriv(signPriv), + epriv: biToB64(encPriv), epub: pubFromPriv(encPriv) + }; + } else { + const sa = await subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, ['sign', 'verify']) + .then(async k => ({ + priv: (await subtle.exportKey('jwk', k.privateKey)).d, + pub: (await subtle.exportKey('jwk', k.publicKey)).x + '.' + + (await subtle.exportKey('jwk', k.publicKey)).y + })); + r = { pub: sa.pub, priv: sa.priv }; + try { + const dh = await ecdhSubtle.generateKey({name: 'ECDH', namedCurve: 'P-256'}, true, ['deriveKey']) + .then(async k => ({ + epriv: (await ecdhSubtle.exportKey('jwk', k.privateKey)).d, + epub: (await ecdhSubtle.exportKey('jwk', k.publicKey)).x + '.' + + (await ecdhSubtle.exportKey('jwk', k.publicKey)).y + })); + r.epub = dh.epub; r.epriv = dh.epriv; + } catch(e) {} + } - var r = { pub: sa.pub, priv: sa.priv, /* pubId, */ epub: dh.epub, epriv: dh.epriv } - if(cb){ try{ cb(r) }catch(e){console.log(e)} } + if(cb) try{ cb(r) }catch(e){ console.log(e) } return r; } catch(e) { - console.log(e); SEA.err = e; - if(SEA.throw){ throw e } - if(cb){ cb() } + if(SEA.throw) throw e; + if(cb) cb(); return; }}); diff --git a/sea/root.js b/sea/root.js index 83c0540b..bcbbbb94 100644 --- a/sea/root.js +++ b/sea/root.js @@ -5,8 +5,7 @@ // IT IS IMPLEMENTED IN A POLYFILL/SHIM APPROACH. // THIS IS AN EARLY ALPHA! - if(typeof self !== "undefined"){ module.window = self } // should be safe for at least browser/worker/nodejs, need to check other envs like RN etc. - if(typeof window !== "undefined"){ module.window = window } + module.window = (typeof globalThis !== "undefined" && typeof window === "undefined" && typeof WorkerGlobalScope !== "undefined") ? globalThis : (typeof window !== "undefined" ? window : undefined); var tmp = module.window || module, u; var SEA = tmp.SEA || {}; diff --git a/sea/settings.js b/sea/settings.js index 2fc07fe3..f1a636a1 100644 --- a/sea/settings.js +++ b/sea/settings.js @@ -19,7 +19,7 @@ if(d){ jwk.d = d } return jwk; }; - + s.keyToJwk = function(keyBytes) { const keyB64 = keyBytes.toString('base64'); const k = keyB64.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, ''); diff --git a/sea/sign.js b/sea/sign.js index 0374a715..1ed96a22 100644 --- a/sea/sign.js +++ b/sea/sign.js @@ -6,35 +6,60 @@ var sha = require('./sha256'); var u; + async function n(r, o, c) { + try { + if(!o.raw){ r = 'SEA' + await shim.stringify(r) } + if(c){ try{ c(r) }catch(e){} } + return r; + } catch(e) { return r } + } + + async function w(r, j, o, c) { + var x = { + m: j, + s: r.signature ? shim.Buffer.from(r.signature, 'binary').toString(o.encode || 'base64') : u, + a: shim.Buffer.from(r.authenticatorData, 'binary').toString('base64'), + c: shim.Buffer.from(r.clientDataJSON, 'binary').toString('base64') + }; + if (!x.s || !x.a || !x.c) throw "WebAuthn signature invalid"; + return n(x, o, c); + } + + async function k(p, j, o, c) { + var x = S.jwk(p.pub, p.priv); + if (!x) throw "Invalid key pair"; + var h = await sha(j); + var s = await (shim.ossl || shim.subtle).importKey('jwk', x, S.ecdsa.pair, false, ['sign']) + .then((k) => (shim.ossl || shim.subtle).sign(S.ecdsa.sign, k, new Uint8Array(h))) + .catch(() => { throw "SEA signature failed" }); + return n({m: j, s: shim.Buffer.from(s, 'binary').toString(o.encode || 'base64')}, o, c); + } + SEA.sign = SEA.sign || (async (data, pair, cb, opt) => { try { opt = opt || {}; - if(!(pair||opt).priv){ - if(!SEA.I){ throw 'No signing key.' } + if(u === data) throw '`undefined` not allowed.'; + if(!(pair||opt).priv && typeof pair !== 'function'){ + if(!SEA.I) throw 'No signing key.'; pair = await SEA.I(null, {what: data, how: 'sign', why: opt.why}); } - if(u === data){ throw '`undefined` not allowed.' } - var json = await S.parse(data); - var check = opt.check = opt.check || json; - if(SEA.verify && (SEA.opt.check(check) || (check && check.s && check.m)) - && u !== await SEA.verify(check, pair)){ // don't sign if we already signed it. - var r = await S.parse(check); - if(!opt.raw){ r = 'SEA' + await shim.stringify(r) } - if(cb){ try{ cb(r) }catch(e){console.log(e)} } - return r; - } - var pub = pair.pub; - var priv = pair.priv; - var jwk = S.jwk(pub, priv); - var hash = await sha(json); - var sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, {name: 'ECDSA', namedCurve: 'P-256'}, false, ['sign']) - .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! - var r = {m: json, s: shim.Buffer.from(sig, 'binary').toString(opt.encode || 'base64')} - if(!opt.raw){ r = 'SEA' + await shim.stringify(r) } - if(cb){ try{ cb(r) }catch(e){console.log(e)} } - return r; + var j = await S.parse(data); + var c = opt.check = opt.check || j; + + if(SEA.verify && (S.check(c) || (c && c.s && c.m)) + && u !== await SEA.verify(c, pair)){ + return n(await S.parse(c), opt, cb); + } + + if(typeof pair === 'function') { + var r = await pair(data); + return r.authenticatorData ? w(r, j, opt, cb) : + n({m: j, s: typeof r === 'string' ? r : + r.signature && shim.Buffer.from(r.signature, 'binary').toString(opt.encode || 'base64')}, opt, cb); + } + + return k(pair, j, opt, cb); } catch(e) { - console.log(e); SEA.err = e; if(SEA.throw){ throw e } if(cb){ cb() } diff --git a/sea/verify.js b/sea/verify.js index 794aa8eb..422b095e 100644 --- a/sea/verify.js +++ b/sea/verify.js @@ -1,82 +1,153 @@ ;(function(){ - var SEA = require('./root'); - var shim = require('./shim'); - var S = require('./settings'); - var sha = require('./sha256'); - var u; + var SEA = require('./root'); + var shim = require('./shim'); + var S = require('./settings'); + var sha = require('./sha256'); + var u; - SEA.verify = SEA.verify || (async (data, pair, cb, opt) => { try { - var json = await S.parse(data); - if(false === pair){ // don't verify! - var raw = await S.parse(json.m); - if(cb){ try{ cb(raw) }catch(e){console.log(e)} } - return raw; - } - opt = opt || {}; - // SEA.I // verify is free! Requires no user permission. - var pub = pair.pub || pair; - 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']); - var hash = await sha(json.m); - var buf, sig, check, tmp; try{ - buf = shim.Buffer.from(json.s, opt.encode || 'base64'); // NEW DEFAULT! - sig = new Uint8Array(buf); - check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash)); - if(!check){ throw "Signature did not match." } - }catch(e){ - if(SEA.opt.fallback){ - return await SEA.opt.fall_verify(data, pair, cb, opt); - } - } - var r = check? await S.parse(json.m) : u; + async function w(j, k, s) { + var a = new Uint8Array(shim.Buffer.from(j.a, 'base64')); + var c = shim.Buffer.from(j.c, 'base64').toString('utf8'); + var m = new TextEncoder().encode(j.m); + var e = btoa(String.fromCharCode(...new Uint8Array(m))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); + if (JSON.parse(c).challenge !== e) throw "Challenge verification failed"; + var h = await (shim.ossl || shim.subtle).digest( + {name: 'SHA-256'}, + new TextEncoder().encode(c) + ); + var d = new Uint8Array(a.length + h.byteLength); + d.set(a); + d.set(new Uint8Array(h), a.length); + if (s[0] !== 0x30) throw "Invalid DER signature format"; + var o = 2, r = new Uint8Array(64); + for(var i = 0; i < 2; i++) { + var l = s[o + 1]; + o += 2; + if (s[o] === 0x00) { o++; l--; } + var p = new Uint8Array(32).fill(0); + p.set(s.slice(o, o + l), 32 - l); + r.set(p, i * 32); + o += l; + } + return (shim.ossl || shim.subtle).verify({ name: 'ECDSA', hash: {name: 'SHA-256'} }, k, r, d); + } - if(cb){ try{ cb(r) }catch(e){console.log(e)} } + async function v(j, k, s, h) { + return (shim.ossl || shim.subtle).verify( + {name: 'ECDSA', hash: {name: 'SHA-256'}}, + k, s, new Uint8Array(h) + ); + } + + SEA.verify = SEA.verify || (async (d, p, cb, o) => { try { + var j = await S.parse(d); + if(false === p) return cb ? cb(await S.parse(j.m)) : await S.parse(j.m); + + o = o || {}; + var pub = p.pub || p; + var [x, y] = pub.split('.'); + + try { + var k = await (shim.ossl || shim.subtle).importKey('jwk', { + kty: 'EC', crv: 'P-256', x, y, ext: true, key_ops: ['verify'] + }, {name: 'ECDSA', namedCurve: 'P-256'}, false, ['verify']); + + var h = await sha(j.m); + var s = new Uint8Array(shim.Buffer.from(j.s || '', o.encode || 'base64')); + + var c = j.a && j.c ? await w(j, k, s) : await v(j, k, s, h); + + if(!c) throw "Signature did not match"; + + // Parse the message content + var r = await S.parse(j.m); + + // Handle encrypted data consistently + // SEA encrypted data can be in two formats: + // 1. A string starting with 'SEA' followed by JSON (e.g., 'SEA{"ct":"...","iv":"...","s":"..."}') + // 2. An object with ct, iv, and s properties + + // Case 1: Original message was already in SEA string format + if(typeof j.m === 'string' && j.m.startsWith('SEA{')) { + if(cb){ try{ cb(j.m) }catch(e){} } + return j.m; + } + + // Case 2: Result is an encrypted data object + // This ensures consistent formatting of encrypted data as SEA strings + if(r && typeof r === 'object' && + typeof r.ct === 'string' && + typeof r.iv === 'string' && + typeof r.s === 'string') { + // Format as standard SEA encrypted string + var seaStr = 'SEA' + JSON.stringify(r); + if(cb){ try{ cb(seaStr) }catch(e){} } + return seaStr; + } + + // Default case: Return parsed result as is + if(cb){ try{ cb(r) }catch(e){} } return r; } catch(e) { - console.log(e); // mismatched owner FOR MARTTI - SEA.err = e; - if(SEA.throw){ throw e } + if(SEA.opt.fallback){ + return await SEA.opt.fall_verify(d, p, cb, o); + } if(cb){ cb() } return; - }}); - - module.exports = SEA.verify; - // legacy & ossl memory leak mitigation: - - var knownKeys = {}; - var keyForPair = SEA.opt.slow_leak = pair => { - if (knownKeys[pair]) return knownKeys[pair]; - var jwk = S.jwk(pair); - knownKeys[pair] = (shim.ossl || shim.subtle).importKey("jwk", jwk, {name: 'ECDSA', namedCurve: 'P-256'}, false, ["verify"]); - return knownKeys[pair]; - }; - - var O = SEA.opt; - SEA.opt.fall_verify = async function(data, pair, cb, opt, f){ - if(f === SEA.opt.fallback){ throw "Signature did not match" } f = f || 1; - var tmp = data||''; - data = SEA.opt.unpack(data) || data; - var json = await S.parse(data), pub = pair.pub || pair, key = await SEA.opt.slow_leak(pub); - 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. - var buf; var sig; var check; try{ - buf = shim.Buffer.from(json.s, opt.encode || 'base64') // NEW DEFAULT! - sig = new Uint8Array(buf) - check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash)) - if(!check){ throw "Signature did not match." } - }catch(e){ try{ - buf = shim.Buffer.from(json.s, 'utf8') // AUTO BACKWARD OLD UTF8 DATA! - sig = new Uint8Array(buf) - check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash)) - }catch(e){ - if(!check){ throw "Signature did not match." } - } - } - var r = check? await S.parse(json.m) : u; - O.fall_soul = tmp['#']; O.fall_key = tmp['.']; O.fall_val = data; O.fall_state = tmp['>']; - if(cb){ try{ cb(r) }catch(e){console.log(e)} } - return r; } - SEA.opt.fallback = 2; + } catch(e) { + SEA.err = e; + if(SEA.throw){ throw e } + if(cb){ cb() } + return; + }}); - -}()); \ No newline at end of file + module.exports = SEA.verify; + + var knownKeys = {}; + SEA.opt.slow_leak = pair => { + if (knownKeys[pair]) return knownKeys[pair]; + var jwk = S.jwk(pair); + knownKeys[pair] = (shim.ossl || shim.subtle).importKey("jwk", jwk, {name: 'ECDSA', namedCurve: 'P-256'}, false, ["verify"]); + return knownKeys[pair]; + }; + + SEA.opt.fall_verify = async function(data, pair, cb, opt, f){ + if(f === SEA.opt.fallback){ throw "Signature did not match" } + var tmp = data||''; + data = SEA.opt.unpack(data) || data; + var json = await S.parse(data), key = await SEA.opt.slow_leak(pair.pub || pair); + var hash = (!f || 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); + + try { + var buf = shim.Buffer.from(json.s, opt.encode || 'base64'); + var sig = new Uint8Array(buf); + var check = await (shim.ossl || shim.subtle).verify( + {name: 'ECDSA', hash: {name: 'SHA-256'}}, + key, sig, new Uint8Array(hash) + ); + if(!check) throw ""; + } catch(e) { + try { + buf = shim.Buffer.from(json.s, 'utf8'); + sig = new Uint8Array(buf); + check = await (shim.ossl || shim.subtle).verify( + {name: 'ECDSA', hash: {name: 'SHA-256'}}, + key, sig, new Uint8Array(hash) + ); + if(!check) throw ""; + } catch(e){ throw "Signature did not match." } + } + + var r = check ? await S.parse(json.m) : u; + SEA.opt.fall_soul = tmp['#']; SEA.opt.fall_key = tmp['.']; + SEA.opt.fall_val = data; SEA.opt.fall_state = tmp['>']; + if(cb){ try{ cb(r) }catch(e){console.log(e)} } + return r; + } + SEA.opt.fallback = 2; + +}()); diff --git a/sea/work.js b/sea/work.js index 6feeabac..8ff08f21 100644 --- a/sea/work.js +++ b/sea/work.js @@ -13,12 +13,19 @@ cb = salt; salt = u; } - data = (typeof data == 'string')? data : await shim.stringify(data); + // Check if data is an ArrayBuffer, if so use Uint8Array to access the data + if(data instanceof ArrayBuffer){ + data = new Uint8Array(data); + data = new shim.TextDecoder("utf-8").decode(data); + } + data = (typeof data == 'string') ? data : await shim.stringify(data); if('sha' === (opt.name||'').toLowerCase().slice(0,3)){ var rsha = shim.Buffer.from(await sha(data, opt.name), 'binary').toString(opt.encode || 'base64') if(cb){ try{ cb(rsha) }catch(e){console.log(e)} } return rsha; } + if (typeof salt === "number") salt = salt.toString(); + if (typeof opt.salt === "number") opt.salt = opt.salt.toString(); salt = salt || shim.random(9); var key = await (shim.ossl || shim.subtle).importKey('raw', new shim.TextEncoder().encode(data), {name: opt.name || 'PBKDF2'}, false, ['deriveBits']); var work = await (shim.ossl || shim.subtle).deriveBits({ @@ -41,4 +48,4 @@ module.exports = SEA.work; -}()); \ No newline at end of file +}()); diff --git a/src/book.js b/src/book.js index 9a0c3b95..a1a5b932 100644 --- a/src/book.js +++ b/src/book.js @@ -83,6 +83,7 @@ function list(each){ each = each || function(x){return x} } function set(word, is){ + // TODO: Perf on random write is decent, but short keys or seq seems significantly slower. var b = this, has = b.all[word]; if(has){ return b(word, is) } // updates to in-memory items will always match exactly. var page = b.page(word=''+word), tmp; // before we assume this is an insert tho, we need to check @@ -103,21 +104,22 @@ function set(word, is){ function split(p, b){ // TODO: use closest hash instead of half. //console.time(); + //var S = performance.now(); var L = sort(p), l = L.length, i = l/2 >> 0, j = i, half = L[j], tmp; //console.timeEnd(); var next = {first: half.substring(), size: 0, substring: sub, toString: to, book: b, get: b, read: list}, f = next.from = []; - //console.time(); while(tmp = L[i++]){ f.push(tmp); next.size += (tmp.is||'').length||1; tmp.page = next; } - //console.timeEnd(); console.time(); p.from = p.from.slice(0, j); p.size -= next.size; b.list.splice(spot(next.first, b.list)+1, 0, next); // TODO: BUG! Make sure next.first is decoded text. // TODO: BUG! spot may need parse too? //console.timeEnd(); if(b.split){ b.split(next, p) } + //console.log(S = (performance.now() - S), 'split'); + //console.BIG = console.BIG > S? console.BIG : S; } function slot(t){ return heal((t=t||'').substring(1, t.length-1).split(t[0]), t[0]) } B.slot = slot; // TODO: check first=last & pass `s`. diff --git a/src/mesh.js b/src/mesh.js index 267096bd..6beee902 100644 --- a/src/mesh.js +++ b/src/mesh.js @@ -76,10 +76,10 @@ function Mesh(root){ if((tmp = msg['><']) && 'string' == typeof tmp){ tmp.slice(0,99).split(',').forEach(function(k){ this[k] = 1 }, (msg._).yo = {}) } // Peers already sent to, do not resend. // DAM ^ if(tmp = msg.dam){ + (dup_track(id)||{}).via = peer; if(tmp = mesh.hear[tmp]){ tmp(msg, peer, root); } - dup_track(id); return; } if(tmp = msg.ok){ msg._.near = tmp['/'] } diff --git a/src/root.js b/src/root.js index 6ad0a7a4..588d8b2f 100644 --- a/src/root.js +++ b/src/root.js @@ -300,14 +300,11 @@ var obj_each = function(o,f){ Object.keys(o).forEach(f,o) }, text_rand = String. Gun.log = function(){ return (!Gun.log.off && C.log.apply(C, arguments)), [].slice.call(arguments).join(' ') }; Gun.log.once = function(w,s,o){ return (o = Gun.log.once)[w] = o[w] || 0, o[w]++ || Gun.log(s) }; -if(typeof window !== "undefined"){ (window.GUN = window.Gun = Gun).window = window } +((typeof globalThis !== "undefined" && typeof window === "undefined" && typeof WorkerGlobalScope !== "undefined") ? ((globalThis.GUN = globalThis.Gun = Gun).window = globalThis) : (typeof window !== "undefined" ? ((window.GUN = window.Gun = Gun).window = window) : undefined)); try{ if(typeof MODULE !== "undefined"){ MODULE.exports = Gun } }catch(e){} module.exports = Gun; (Gun.window||{}).console = (Gun.window||{}).console || {log: function(){}}; (C = console).only = function(i, s){ return (C.only.i && i === C.only.i && C.only.i++) && (C.log.apply(C, arguments) || s) }; - -;"Please do not remove welcome log unless you are paying for a monthly sponsorship, thanks!"; -Gun.log.once("welcome", "Hello wonderful person! :) Thanks for using GUN, please ask for help on http://chat.gun.eco if anything takes you longer than 5min to figure out!"); }()); \ No newline at end of file diff --git a/src/websocket.js b/src/websocket.js index 67d6d6cd..0777055f 100644 --- a/src/websocket.js +++ b/src/websocket.js @@ -19,10 +19,10 @@ Gun.on('opt', function(root){ var mesh = opt.mesh = opt.mesh || Gun.Mesh(root); - var wire = mesh.wire || opt.wire; + var wired = mesh.wire || opt.wire; mesh.wire = opt.wire = open; function open(peer){ try{ - if(!peer || !peer.url){ return wire && wire(peer) } + if(!peer || !peer.url){ return wired && wired(peer) } var url = peer.url.replace(/^http/, 'ws'); var wire = peer.wire = new opt.WebSocket(url); wire.onclose = function(){ diff --git a/test/sea/sea.js b/test/sea/sea.js index ed090db0..b856ece5 100755 --- a/test/sea/sea.js +++ b/test/sea/sea.js @@ -1,3 +1,7 @@ +const exp = require('constants'); +const expect = require('../expect'); +const SeaArray = require('../../sea/array.js'); + var root; var Gun; (function(){ @@ -196,6 +200,30 @@ describe('SEA', function(){ done(); });});}); })*/ + + it('hash array buffer', function(done) { + (async function() { + // Create a random ArrayBuffer (buffer 1) + var buff1 = new ArrayBuffer(16); + var view1 = new Uint8Array(buff1); // Use a Uint8Array to modify the buffer + for (var i = 0; i < view1.length; i++) { + view1[i] = Math.floor(Math.random() * 256); + } + var hash1 = await SEA.work(buff1, "salt"); + + // Create another random ArrayBuffer (buffer 2) + var buff2 = new ArrayBuffer(16); + var view2 = new Uint8Array(buff2); + for (var i = 0; i < view2.length; i++) { + view2[i] = Math.floor(Math.random() * 256); + } + var hash2 = await SEA.work(buff2, "salt"); + + // Ensure the hashes are strings and different from each other + expect(typeof hash1 === "string" && typeof hash2 === "string" && hash1 !== hash2).to.be(true); + done(); // Signal that the test is complete + })(); + }); it('legacy', function(done){ (async function(){ var pw = 'test123'; @@ -235,7 +263,7 @@ describe('SEA', function(){ var alias = SEA.opt.unpack(await SEA.verify(old.alias, false), 'alias', old); expect(alias).to.be('alice'); alias = Gun.state.ify({}, tmp, 1, {'#': tmp}, tmp = '~@'+alias); - gun._.graph[tmp] = graph[tmp] = alias; + gun._.graph[tmp] = alias; //gun.on('test', {$: gun, put: graph}); var use = gun.user(); use.auth('alice', 'test123', function(ack){ @@ -287,9 +315,317 @@ describe('SEA', function(){ }())}) }); + describe('Seed-based Key Generation', function() { + this.timeout(5000); // Set timeout for all tests in this suite + + it('generates deterministic key pairs from same seed', async function () { + // Seed string tests + const pair1 = await SEA.pair(null, { seed: "my secret seed" }); + const pair2 = await SEA.pair(null, { seed: "my secret seed" }); + const pair3 = await SEA.pair(null, { seed: "not my seed" }); + + // Check if pairs with same seed are identical + const sameKeys = pair1.priv === pair2.priv && + pair1.pub === pair2.pub && + pair1.epriv === pair2.epriv && + pair1.epub === pair2.epub; + + // Check if pairs with different seeds are different + const differentKeys = pair1.priv !== pair3.priv && + pair1.pub !== pair3.pub && + pair1.epriv !== pair3.epriv && + pair1.epub !== pair3.epub; + + expect(sameKeys).to.be(true); + expect(differentKeys).to.be(true); + + // Test consistent generation across multiple calls + const numTests = 5; + const pairs = []; + const seed = "consistency test seed"; + + // Generate multiple pairs with the same seed + for (let i = 0; i < numTests; i++) { + pairs.push(await SEA.pair(null, { seed })); + } + + // Verify all pairs are identical + let allMatch = true; + for (let i = 1; i < numTests; i++) { + if (pairs[i].pub !== pairs[0].pub || + pairs[i].priv !== pairs[0].priv || + pairs[i].epub !== pairs[0].epub || + pairs[i].epriv !== pairs[0].epriv) { + allMatch = false; + break; + } + } + + expect(allMatch).to.be(true); + + // Test that the created pair works with SEA functions + var enc = await SEA.encrypt('hello self', pair1); + var data = await SEA.sign(enc, pair1); + var msg = await SEA.verify(data, pair1.pub); + expect(msg).to.be(enc); + var dec = await SEA.decrypt(msg, pair1); + expect(dec).to.be('hello self'); + var proof = await SEA.work(dec, pair1); + var check = await SEA.work('hello self', pair1); + expect(proof).to.be(check); + }); + + it('generates deterministic key pairs from ArrayBuffer seed', async function () { + // Create ArrayBuffer seeds + const textEncoder = new TextEncoder(); + const seedData1 = textEncoder.encode("my secret seed"); // Convert string to Uint8Array + const seedBuffer1 = seedData1.buffer; // Get the underlying ArrayBuffer + + // Create a second identical seed + const seedData2 = textEncoder.encode("my secret seed"); + const seedBuffer2 = seedData2.buffer; + + // Create a different seed + const seedData3 = textEncoder.encode("not my seed"); + const seedBuffer3 = seedData3.buffer; + + // Generate key pairs using ArrayBuffer seeds + const pair1 = await SEA.pair(null, { seed: seedBuffer1 }); + const pair2 = await SEA.pair(null, { seed: seedBuffer2 }); + const pair3 = await SEA.pair(null, { seed: seedBuffer3 }); + + // Check if pairs with same seed content are identical + const sameKeys = pair1.priv === pair2.priv && + pair1.pub === pair2.pub && + pair1.epriv === pair2.epriv && + pair1.epub === pair2.epub; + + // Check if pairs with different seeds are different + const differentKeys = pair1.priv !== pair3.priv && + pair1.pub !== pair3.pub && + pair1.epriv !== pair3.epriv && + pair1.epub !== pair3.epub; + + expect(sameKeys).to.be(true); + expect(differentKeys).to.be(true); + + // Test with different ways to create ArrayBuffer seeds + // Method 1: Direct encoding + const buffer1 = textEncoder.encode("buffer-seed-test").buffer; + + // Method 2: Clone buffer from another array + const tempArray = textEncoder.encode("buffer-seed-test"); + const buffer2 = tempArray.buffer.slice(0); + + // Generate key pairs + const bufPair1 = await SEA.pair(null, { seed: buffer1 }); + const bufPair2 = await SEA.pair(null, { seed: buffer2 }); + + // Keys should be identical + expect(bufPair1.pub).to.be(bufPair2.pub); + expect(bufPair1.priv).to.be(bufPair2.priv); + expect(bufPair1.epub).to.be(bufPair2.epub); + expect(bufPair1.epriv).to.be(bufPair2.epriv); + + // Test that different buffers produce different keys + const buffer3 = textEncoder.encode("different-buffer-seed").buffer; + const bufPair3 = await SEA.pair(null, { seed: buffer3 }); + + expect(bufPair1.pub).to.not.be(bufPair3.pub); + + // Test that the created pair works with SEA functions + var enc = await SEA.encrypt('hello self', bufPair1); + var data = await SEA.sign(enc, bufPair1); + var msg = await SEA.verify(data, bufPair1.pub); + expect(msg).to.be(enc); + var dec = await SEA.decrypt(msg, bufPair1); + expect(dec).to.be('hello self'); + var proof = await SEA.work(dec, bufPair1); + var check = await SEA.work('hello self', bufPair1); + expect(proof).to.be(check); + }); + + it('generate key pairs from private key', async function () { + var gun = Gun() + var user = gun.user() + const test1 = await SEA.pair(null, { seed: "seed" }); + const test2 = await SEA.pair(null, { priv: test1.priv }); + expect(test2.priv).to.be(test1.priv); + expect(test2.pub).to.be(test1.pub); + + // Test that the created pair works with SEA functions + var enc = await SEA.encrypt('hello self', test2); + var data = await SEA.sign(enc, test2); + var msg = await SEA.verify(data, test2.pub); + expect(msg).to.be(enc); + var dec = await SEA.decrypt(msg, test2); + expect(dec).to.be('hello self'); + var proof = await SEA.work(dec, test2); + var check = await SEA.work('hello self', test2); + expect(proof).to.be(check); + await user.auth(test2); + expect(user.is.pub).to.be(test2.pub); + expect(user.is.pub).to.be(test1.pub); + user.leave(); + const test3 = await SEA.pair(null, { epriv: test2.epriv }); + expect(test3.epriv).to.be(test2.epriv); + await user.auth(test3); + expect(user.is.epub).to.be(test3.epub); + expect(user.is.epub).to.be(test2.epub); + user.leave(); + }); + + it('handles different types of seed values correctly', async function () { + // Test different seed types + const testCases = [ + { type: "empty string", seed: "" }, + { type: "numeric", seed: "12345" }, + { type: "special chars", seed: "!@#$%^&*()" }, + { type: "long string", seed: "a".repeat(1000) }, + { type: "unicode", seed: "😀🔑🔒👍" } + ]; + + // Generate pairs for each test case + const results = []; + for (const test of testCases) { + try { + const pair = await SEA.pair(null, { seed: test.seed }); + + // Check if pair has all required properties + const isValid = pair && + typeof pair.pub === 'string' && + typeof pair.priv === 'string' && + typeof pair.epub === 'string' && + typeof pair.epriv === 'string'; + + results.push({ ...test, success: isValid, pair: pair }); + } catch (e) { + results.push({ ...test, success: false, error: e.message }); + } + } + + // All test cases should succeed + const allSucceeded = results.every(r => r.success); + expect(allSucceeded).to.be(true); + + // All pairs should be different from each other + const uniquePairs = new Set(results.map(r => r.pair?.pub)); + expect(uniquePairs.size).to.be(results.length); + + // Similar seeds should produce different key pairs + const seed1 = "test-seed"; + const seed2 = "test-seed1"; + const seed3 = "test-seed "; // note the space + const seed4 = "Test-seed"; // capitalization + + const pairs = await Promise.all([ + SEA.pair(null, { seed: seed1 }), + SEA.pair(null, { seed: seed2 }), + SEA.pair(null, { seed: seed3 }), + SEA.pair(null, { seed: seed4 }) + ]); + + // Check that all pairs are different + const [p1, p2, p3, p4] = pairs; + expect(p1.pub).to.not.equal(p2.pub); + expect(p1.pub).to.not.equal(p3.pub); + expect(p1.pub).to.not.equal(p4.pub); + expect(p2.pub).to.not.equal(p3.pub); + expect(p2.pub).to.not.equal(p4.pub); + expect(p3.pub).to.not.equal(p4.pub); + }); + + it('works with SEA operations (sign, verify, encrypt, decrypt)', async function () { + // Test with sign/verify + const seed = "sign-verify-seed"; + const pair = await SEA.pair(null, { seed }); + const message = "Hello deterministic world!"; + + // Test signing and verification + const signature = await SEA.sign(message, pair); + const verified = await SEA.verify(signature, pair.pub); + expect(verified).to.be(message); + + // Test with encrypt/decrypt + const encryptSeed = "encrypt-decrypt-seed"; + const encPair = await SEA.pair(null, { seed: encryptSeed }); + const secretMessage = "Secret deterministic message"; + + // Test encryption and decryption + const encrypted = await SEA.encrypt(secretMessage, encPair); + const decrypted = await SEA.decrypt(encrypted, encPair); + expect(decrypted).to.be(secretMessage); + + // Test with SEA.secret (key exchange) + const aliceSeed = "alice-deterministic"; + const bobSeed = "bob-deterministic"; + + const alice = await SEA.pair(null, { seed: aliceSeed }); + const bob = await SEA.pair(null, { seed: bobSeed }); + + // Generate shared secrets + const aliceShared = await SEA.secret(bob.epub, alice); + const bobShared = await SEA.secret(alice.epub, bob); + + expect(aliceShared).to.be(bobShared); + + // Test shared secret for encryption + const sharedMessage = "Secret shared deterministically"; + const sharedEncrypted = await SEA.encrypt(sharedMessage, aliceShared); + const sharedDecrypted = await SEA.decrypt(sharedEncrypted, bobShared); + + expect(sharedDecrypted).to.be(sharedMessage); + + // Test complete workflow + const workflowSeed = "workflow-test-seed"; + const workflowPair = await SEA.pair(null, { seed: workflowSeed }); + const workflowMessage = "hello deterministic self"; + + // Complete workflow: encrypt, sign, verify, decrypt + const wfEncrypted = await SEA.encrypt(workflowMessage, workflowPair); + const wfSigned = await SEA.sign(wfEncrypted, workflowPair); + const wfVerified = await SEA.verify(wfSigned, workflowPair.pub); + const wfDecrypted = await SEA.decrypt(wfVerified, workflowPair); + + expect(wfDecrypted).to.be(workflowMessage); + + // Test with SEA.work + const proof1 = await SEA.work(workflowMessage, workflowPair); + const proof2 = await SEA.work(workflowMessage, workflowPair); + + expect(proof1).to.be(proof2); + }); + }); + describe('User', function(){ var gun = Gun(), gtmp; + it("put to user graph without having to be authenticated (provide pair)", function(done){(async function(){ + var bob = await SEA.pair(); + gun.get(`~${bob.pub}`).get('test').put('this is Bob', (ack) => { + gun.get(`~${bob.pub}`).get('test').once((data) => { + expect(ack.err).to.not.be.ok() + expect(data).to.be('this is Bob') + done(); + }) + }, {opt: {authenticator: bob}}) + })()}); + + it("put to user graph using external authenticator (nested SEA.sign)", function(done){(async function(){ + var bob = await SEA.pair(); + async function authenticator(data) { + const sig = await SEA.sign(data, bob) + return sig + } + gun.get(`~${bob.pub}`).get('test').put('this is Bob', (ack) => { + gun.get(`~${bob.pub}`).get('test').once((data) => { + expect(ack.err).to.not.be.ok() + expect(data).to.be('this is Bob') + done(); + }) + }, {opt: {authenticator: authenticator}}) + })()}); + it('test', function(done){ var g = Gun(); user = g.user(); @@ -750,7 +1086,7 @@ describe('SEA', function(){ }); - describe.skip('Frozen', function () { + describe('Frozen', function () { it('Across spaces', function(done){ var gun = Gun(); var user = gun.user(); @@ -763,15 +1099,14 @@ describe('SEA', function(){ var data = "hello world"; var hash = await SEA.work(data, null, null, {name: "SHA-256"}); - gun.get('#users').get(hash).put(data); - - console.log(1); - gun.get('#users').map()/*.get('country')*/.on(data => console.log(data)); - + hash = hash.slice(-20); + await gun.get('#users').get(hash).put(data); + var test = await gun.get('#users').get(hash); + expect(test).to.be(data); + done(); }); }); }); }) }()); -