From be0de5cf5622682038425f83890432b1d87d8a2d Mon Sep 17 00:00:00 2001 From: Mark Nadal Date: Sat, 7 Apr 2018 13:50:53 -0700 Subject: [PATCH] migrate SEA, some improvement, some regression --- gun.min.js | 2 +- sea/aescbc.js | 12 ++ sea/authenticate.js | 17 +-- sea/buffer.js | 2 - sea/decrypt.js | 26 ++++ sea/encrypt.js | 30 ++++ sea/https.js | 11 +- sea/index.js | 104 +++++-------- sea/indexed.js | 78 ---------- sea/leave.js | 15 +- sea/login.js | 23 ++- sea/pair.js | 52 +++++++ sea/parse.js | 6 +- sea/persist.js | 8 +- sea/query.js | 15 +- sea/recall.js | 40 ++--- sea/remember.js | 46 ------ sea/root.js | 10 ++ sea/sea.js | 348 +++++++++++++------------------------------- sea/settings.js | 39 ++--- sea/sha1.js | 5 +- sea/sha256.js | 20 ++- sea/shim.js | 38 +++++ sea/sign.js | 35 +++++ sea/update.js | 17 ++- sea/user.js | 128 +++++++++------- sea/verify.js | 34 +++++ sea/webcrypto.js | 25 ---- sea/work.js | 49 +++++++ 29 files changed, 606 insertions(+), 629 deletions(-) create mode 100644 sea/aescbc.js create mode 100644 sea/decrypt.js create mode 100644 sea/encrypt.js delete mode 100644 sea/indexed.js create mode 100644 sea/pair.js delete mode 100644 sea/remember.js create mode 100644 sea/root.js create mode 100644 sea/shim.js create mode 100644 sea/sign.js create mode 100644 sea/verify.js delete mode 100644 sea/webcrypto.js create mode 100644 sea/work.js diff --git a/gun.min.js b/gun.min.js index 7fea8d1a..868cc1e8 100644 --- a/gun.min.js +++ b/gun.min.js @@ -1 +1 @@ -!function(){function t(n){function o(t){return t.split("/").slice(-1).toString().replace(".js","")}return n.slice?t[o(n)]:function(e,i){n(e={exports:{}}),t[o(i)]=e.exports}}var n;"undefined"!=typeof window&&(n=window),"undefined"!=typeof global&&(n=global),n=n||{};var o=n.console||{log:function(){}};if("undefined"!=typeof module)var e=module;t(function(t){var n={};n.fns=n.fn={is:function(t){return!!t&&"function"==typeof t}},n.bi={is:function(t){return t instanceof Boolean||"boolean"==typeof t}},n.num={is:function(t){return!e(t)&&(t-parseFloat(t)+1>=0||1/0===t||-(1/0)===t)}},n.text={is:function(t){return"string"==typeof t}},n.text.ify=function(t){return n.text.is(t)?t:"undefined"!=typeof JSON?JSON.stringify(t):t&&t.toString?t.toString():t},n.text.random=function(t,n){var o="";for(t=t||24,n=n||"0123456789ABCDEFGHIJKLMNOPQRSTUVWXZabcdefghijklmnopqrstuvwxyz";t>0;)o+=n.charAt(Math.floor(Math.random()*n.length)),t--;return o},n.text.match=function(t,o){function e(t,n){for(var o,e=-1,i=0;o=n[i++];)if(!~(e=t.indexOf(o,e+1)))return!1;return!0}var i=!1;if(t=t||"",o=n.text.is(o)?{"=":o}:o||{},n.obj.has(o,"~")&&(t=t.toLowerCase(),o["="]=(o["="]||o["~"]).toLowerCase()),n.obj.has(o,"="))return t===o["="];if(n.obj.has(o,"*")){if(t.slice(0,o["*"].length)!==o["*"])return!1;i=!0,t=t.slice(o["*"].length)}if(n.obj.has(o,"!")){if(t.slice(-o["!"].length)!==o["!"])return!1;i=!0}if(n.obj.has(o,"+")&&n.list.map(n.list.is(o["+"])?o["+"]:[o["+"]],function(n){return t.indexOf(n)>=0?void(i=!0):!0}))return!1;if(n.obj.has(o,"-")&&n.list.map(n.list.is(o["-"])?o["-"]:[o["-"]],function(n){return t.indexOf(n)<0?void(i=!0):!0}))return!1;if(n.obj.has(o,">")){if(!(t>o[">"]))return!1;i=!0}if(n.obj.has(o,"<")){if(!(tn?-1:n>o?1:0):0}},n.list.map=function(t,n,o){return a(t,n,o)},n.list.index=1,n.obj={is:function(t){return t?t instanceof Object&&t.constructor===Object||"Object"===Object.prototype.toString.call(t).match(/^\[object (\w+)\]$/)[1]:!1}},n.obj.put=function(t,n,o){return(t||{})[n]=o,t},n.obj.has=function(t,n){return t&&Object.prototype.hasOwnProperty.call(t,n)},n.obj.del=function(t,n){return t?(t[n]=null,delete t[n],t):void 0},n.obj.as=function(t,n,o,e){return t[n]=t[n]||(e===o?{}:o)},n.obj.ify=function(t){if(r(t))return t;try{t=JSON.parse(t)}catch(n){t={}}return t},function(){function t(t,n){u(this,n)&&o!==this[n]||(this[n]=t)}var o;n.obj.to=function(n,o){return o=o||{},a(n,t,o),o}}(),n.obj.copy=function(t){return t?JSON.parse(JSON.stringify(t)):t},function(){function t(t,n){var o=this.n;if(!o||!(n===o||r(o)&&u(o,n)))return n?!0:void 0}n.obj.empty=function(n,o){return n&&a(n,t,{n:o})?!1:!0}}(),function(){function t(n,o){return 2===arguments.length?(t.r=t.r||{},void(t.r[n]=o)):(t.r=t.r||[],void t.r.push(n))}var i=Object.keys;n.obj.map=function(a,s,f){var c,l,p,g,h,d=0,v=o(s);if(t.r=null,i&&r(a)&&(g=i(a),h=!0),e(a)||g)for(l=(g||a).length;l>d;d++){var b=d+n.list.index;if(v){if(p=h?s.call(f||this,a[g[d]],g[d],t):s.call(f||this,a[d],b,t),p!==c)return p}else if(s===a[h?g[d]:d])return g?g[d]:b}else for(d in a)if(v){if(u(a,d)&&(p=f?s.call(f,a[d],d,t):s(a[d],d,t),p!==c))return p}else if(s===a[d])return d;return v?t.r:n.list.index?0:-1}}(),n.time={},n.time.is=function(t){return t?t instanceof Date:+(new Date).getTime()};var o=n.fn.is,e=n.list.is,i=n.obj,r=i.is,u=i.has,a=i.map;t.exports=n})(t,"./type"),t(function(t){t.exports=function n(t,o,e){if(!t)return{to:n};var t=(this.tag||(this.tag={}))[t]||(this.tag[t]={tag:t,to:n._={next:function(t){var n;(n=this.to)&&n.next(t)}}});if(o instanceof Function){var i={off:n.off||(n.off=function(){return this.next===n._.next?!0:(this===this.the.last&&(this.the.last=this.back),this.to.back=this.back,this.next=n._.next,this.back.to=this.to,void(this.the.last===this.the&&delete this.on.tag[this.the.tag]))}),to:n._,next:o,the:t,on:this,as:e};return(i.back=t.last||t).to=i,t.last=i}return(t=t.to).next(o),t}})(t,"./onto"),t(function(t){function n(t,n,e,i,r){if(n>t)return{defer:!0};if(e>n)return{historical:!0};if(n>e)return{converge:!0,incoming:!0};if(n===e){if(i=o(i)||"",r=o(r)||"",i===r)return{state:!0};if(r>i)return{converge:!0,current:!0};if(i>r)return{converge:!0,incoming:!0}}return{err:"Invalid CRDT Data: "+i+" to "+r+" at "+n+" to "+e+"!"}}if("undefined"==typeof JSON)throw new Error("JSON is not included in this browser. Please load it first: ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js");var o=JSON.stringify;t.exports=n})(t,"./HAM"),t(function(n){var o=t("./type"),e={};e.is=function(t){return t===i?!1:null===t?!0:t===1/0?!1:s(t)||u(t)||a(t)?!0:e.rel.is(t)||!1},e.rel={_:"#"},function(){function t(t,n){var o=this;return o.id?o.id=!1:n==r&&s(t)?void(o.id=t):o.id=!1}e.rel.is=function(n){if(n&&n[r]&&!n._&&c(n)){var o={};if(p(n,t,o),o.id)return o.id}return!1}}(),e.rel.ify=function(t){return l({},r,t)},o.obj.has._=".";var i,r=e.rel._,u=o.bi.is,a=o.num.is,s=o.text.is,f=o.obj,c=f.is,l=f.put,p=f.map;n.exports=e})(t,"./val"),t(function(n){var o=t("./type"),e=t("./val"),i={_:"_"};i.soul=function(t,n){return t&&t._&&t._[n||p]},i.soul.ify=function(t,n){return n="string"==typeof n?{soul:n}:n||{},t=t||{},t._=t._||{},t._[p]=n.soul||t._[p]||l(),t},i.soul._=e.rel._,function(){function t(t,n){return n!==i._?e.is(t)?void(this.cb&&this.cb.call(this.as,t,n,this.n,this.s)):!0:void 0}i.is=function(n,o,e){var r;return a(n)&&(r=i.soul(n))?!f(n,t,{as:e,cb:o,s:r,n:n}):!1}}(),function(){function t(t,n){var o,i,r=this.o;return r.map?(o=r.map.call(this.as,t,""+n,r.node),void(i===o?s(r.node,n):r.node&&(r.node[n]=o))):void(e.is(t)&&(r.node[n]=t))}i.ify=function(n,o,e){return o?"string"==typeof o?o={soul:o}:o instanceof Function&&(o={map:o}):o={},o.map&&(o.node=o.map.call(e,n,r,o.node||{})),(o.node=i.soul.ify(o.node||{},o))&&f(n,t,{o:o,as:e}),o.node}}();var r,u=o.obj,a=u.is,s=u.del,f=u.map,c=o.text,l=c.random,p=i.soul._;n.exports=i})(t,"./node"),t(function(n){function o(){var t;return t=f?c+f.now():r(),t>u?(a=0,u=t+o.drift):u=t+(a+=1)/s+o.drift}var e=t("./type"),i=t("./node"),r=e.time.is,u=-(1/0),a=0,s=1e3,f="undefined"!=typeof performance?performance.timing&&performance:!1,c=f&&f.timing&&f.timing.navigationStart||(f=!1);o._=">",o.drift=0,o.is=function(t,n,e){var i=n&&t&&t[x]&&t[x][o._]||e;if(i)return _(i=i[n])?i:-(1/0)},o.lex=function(){return o().toString(36).replace(".","")},o.ify=function(t,n,e,r,u){if(!t||!t[x]){if(!u)return;t=i.soul.ify(t,u)}var a=g(t[x],o._);return l!==n&&n!==x&&(_(e)&&(a[n]=e),l!==r&&(t[n]=r)),t},o.to=function(t,n,e){var r=t[n];return d(r)&&(r=b(r)),o.ify(e,n,o.is(t,n),r,i.soul(t))},function(){function t(t,n){x!==n&&o.ify(this.o,n,this.s)}o.map=function(n,e,i){var r,u=d(u=n||e)?u:null;return n=k(n=n||e)?n:null,u&&!n?(e=_(e)?e:o(),u[x]=u[x]||{},v(u,t,{o:u,s:e}),u):(i=i||d(e)?e:r,e=_(e)?e:o(),function(o,u,a,s){return n?(n.call(i||this||{},o,u,a,s),void(h(a,u)&&r===a[u]||t.call({o:a,s:e},o,u))):(t.call({o:a,s:e},o,u),o)})}}();var l,p=e.obj,g=p.as,h=p.has,d=p.is,v=p.map,b=p.copy,m=e.num,_=m.is,y=e.fn,k=y.is,x=i._;n.exports=o})(t,"./state"),t(function(n){var o=t("./type"),e=t("./val"),i=t("./node"),r={};!function(){function t(t,o){return t&&o===i.soul(t)&&i.is(t,this.fn,this.as)?void(this.cb&&(n.n=t,n.as=this.as,this.cb.call(n.as,t,o,n))):!0}function n(t){t&&i.is(n.n,t,n.as)}r.is=function(n,o,e,i){return n&&s(n)&&!l(n)?!g(n,t,{cb:o,fn:e,as:i}):!1}}(),function(){function t(t,o){var r;return(r=p(t,o))?r:(o.env=t,o.soul=a,i.ify(o.obj,n,o)&&(t.graph[e.rel.is(o.rel)]=o.node),o)}function n(n,o,r){var a,s,p=this,g=p.env;if(i._===o&&c(n,e.rel._))return r._;if(a=l(n,o,r,p,g)){if(o||(p.node=p.node||r||{},c(n,i._)&&(p.node._=h(n._)),p.node=i.soul.ify(p.node,e.rel.is(p.rel)),p.rel=p.rel||e.rel.ify(i.soul(p.node))),(s=g.map)&&(s.call(g.as||{},n,o,r,p),c(r,o))){if(n=r[o],u===n)return void f(r,o);if(!(a=l(n,o,r,p,g)))return}if(!o)return p.node;if(!0===a)return n;if(s=t(g,{obj:n,path:p.path.concat(o)}),s.node)return s.rel}}function a(t){var n=this,o=e.rel.is(n.rel),r=n.env.graph;n.rel=n.rel||e.rel.ify(t),n.rel[e.rel._]=t,n.node&&n.node[i._]&&(n.node[i._][e.rel._]=t),c(r,o)&&(r[t]=r[o],f(r,o))}function l(t,n,i,r,u){var a;return e.is(t)?!0:s(t)?1:(a=u.invalid)?(t=a.call(u.as||{},t,n,i),l(t,n,i,r,u)):(u.err="Invalid value at '"+r.path.concat(n).join(".")+"'!",void(o.list.is(t)&&(u.err+=" Use `.set(item)` instead of an Array.")))}function p(t,n){for(var o,e=t.seen,i=e.length;i--;)if(o=e[i],n.obj===o.obj)return o;e.push(n)}r.ify=function(n,o,i){var r={path:[],obj:n};return o?"string"==typeof o?o={soul:o}:o instanceof Function&&(o.map=o):o={},o.soul&&(r.rel=e.rel.ify(o.soul)),o.graph=o.graph||{},o.seen=o.seen||[],o.as=o.as||i,t(o,r),o.root=r.node,o.graph}}(),r.node=function(t){var n=i.soul(t);if(n)return p({},n,t)},function(){function t(t,n){var o,u;if(i._===n){if(l(t,e.rel._))return;return void(this.obj[n]=h(t))}return(o=e.rel.is(t))?(u=this.opt.seen[o])?void(this.obj[n]=u):void(this.obj[n]=this.opt.seen[o]=r.to(this.graph,o,this.opt)):void(this.obj[n]=t)}r.to=function(n,o,e){if(n){var i={};return e=e||{seen:{}},g(n[o],t,{obj:i,graph:n,opt:e}),i}}}();var u,a=(o.fn.is,o.obj),s=a.is,f=a.del,c=a.has,l=a.empty,p=a.put,g=a.map,h=a.copy;n.exports=r})(t,"./graph"),t(function(n){t("./onto"),n.exports=function(t,n){if(this.on){if(!(t instanceof Function)){if(!t||!n)return;var o=t["#"]||t,e=(this.tag||empty)[o];if(!e)return;return e=this.on(o,n),clearTimeout(e.err),!0}var o=n&&n["#"]||Math.random().toString(36).slice(2);if(!t)return o;var i=this.on(o,t,n);return i.err=i.err||setTimeout(function(){i.next({err:"Error: No ACK received yet."}),i.off()},(this.opt||{}).lack||9e3),o}}})(t,"./ask"),t(function(n){function o(t){var n={s:{}};return t=t||{max:1e3,age:9e3},n.check=function(t){var o;return(o=n.s[t])?o.pass?o.pass=!1:n.track(t):!1},n.track=function(o,r){var u=n.s[o]||(n.s[o]={});return u.was=i(),r&&(u.pass=!0),n.to||(n.to=setTimeout(function(){var o=i();e.obj.map(n.s,function(i,r){t.age>o-i.was||e.obj.del(n.s,r)}),n.to=null},t.age+9)),u},n}var e=t("./type"),i=e.time.is;n.exports=o})(t,"./dup"),t(function(n){function i(t){return t instanceof i?(this._={gun:this}).gun:this instanceof i?i.create(this._={gun:this,opt:t}):new i(t)}i.is=function(t){return t instanceof i},i.version=.9,i.chain=i.prototype,i.chain.toJSON=function(){};var r=t("./type");r.obj.to(r,i),i.HAM=t("./HAM"),i.val=t("./val"),i.node=t("./node"),i.state=t("./state"),i.graph=t("./graph"),i.on=t("./onto"),i.ask=t("./ask"),i.dup=t("./dup"),function(){function t(t){var n,o,e=this,r=e.as,u=r.gun;(o=t["#"])||(o=t["#"]=c(9)),(n=r.dup).check(o)||(n.track(o),r.ask(t["@"],t)||(t.get&&i.on.get(t,u),t.put&&i.on.put(t,u)),r.on("out",t))}i.create=function(n){n.root=n.root||n,n.graph=n.graph||{},n.on=n.on||i.on,n.ask=n.ask||i.ask,n.dup=n.dup||i.dup();var o=n.gun.opt(n.opt);return n.once||(n.on("in",t,n),n.on("out",t,n)),n.once=1,o}}(),function(){function t(t,n,o,e){var r=this,u=i.state.is(o,n);if(!u)return r.err="Error: No state on '"+n+"' in node '"+e+"'!";var a=r.graph[e]||_,s=i.state.is(a,n,!0),f=a[n],c=i.HAM(r.machine,u,s,t,f);return c.incoming?(r.put[e]=i.state.to(o,n,r.put[e]),(r.diff||(r.diff={}))[e]=i.state.to(o,n,r.diff[e]),void(r.souls[e]=!0)):void(c.defer&&(r.defer=u<(r.defer||1/0)?u:r.defer))}function n(t,n){var i=this,u=i.gun._,a=(u.next||_)[n];if(!a)return void(i.souls[n]=!1);var s=i.map[n]={put:t,get:n,gun:a.gun},f={ctx:i,msg:s};i.async=!!u.tag.node,i.ack&&(s["@"]=i.ack),d(t,o,f),i.async&&(i.and||u.on("node",function(t){this.to.next(t),t===i.map[t.get]&&(i.souls[t.get]=!1,d(t.put,e,t),d(i.souls,function(t){return t?t:void 0})||i.c||(i.c=1,this.off(),u.stop={},d(i.map,r,i)))}),i.and=!0,u.on("node",s))}function o(t,n){var o=this.ctx,e=o.graph,r=this.msg,u=r.get,a=r.put,s=r.gun._;e[u]=i.state.to(a,n,e[u]),o.async||(s.put=i.state.to(a,n,s.put))}function e(t,n){var o=this,e=o.put,r=o.gun._;r.put=i.state.to(e,n,r.put)}function r(t){t.gun&&t.gun._.on("in",t)}i.on.put=function(o,e){var a=e._,s={gun:e,graph:a.graph,put:{},map:{},souls:{},machine:i.state(),ack:o["@"]};return i.graph.is(o.put,null,t,s)||(s.err="Error: Invalid graph!"),s.err?a.on("in",{"@":o["#"],err:i.log(s.err)}):(d(s.put,n,s),s.async||(a.stop={},d(s.map,r,s)),u!==s.defer&&setTimeout(function(){i.on.put(o,e)},s.defer-s.machine),void(s.diff&&a.on("put",h(o,{put:s.diff}))))},i.on.get=function(t,n){var o=n._,e=t.get[b],r=o.graph[e],u=t.get[m],a=o.next||(o.next={}),s=a[e];if(!r||!s)return o.on("get",t);if(u){if(!g(r,u))return o.on("get",t);r=i.state.to(r,u)}else r=i.obj.copy(r);r=i.graph.node(r),o.on("in",{"@":t["#"],how:"mem",put:r,gun:n}),o.on("get",t)}}(),function(){i.chain.opt=function(t){t=t||{};var n=this,o=n._,e=t.peers||t;return p(t)||(t={}),p(o.opt)||(o.opt=t),f(e)&&(e=[e]),a(e)&&(e=d(e,function(t,n,o){o(t,{url:t})}),p(o.opt.peers)||(o.opt.peers={}),o.opt.peers=h(e,o.opt.peers)),o.opt.peers=o.opt.peers||{},h(t,o.opt),i.on("opt",o),o.opt.uuid=o.opt.uuid||function(){return v()+c(12)},n}}();var u,a=i.list.is,s=i.text,f=s.is,c=s.random,l=i.obj,p=l.is,g=l.has,h=l.to,d=l.map,v=(l.copy,i.state.lex),b=i.val.rel._,m=".",_=(i.node._,i.val.rel.is,{});o.debug=function(t,n){return o.debug.i&&t===o.debug.i&&o.debug.i++&&(o.log.apply(o,arguments)||n)},i.log=function(){return!i.log.off&&o.log.apply(o,arguments),[].slice.call(arguments).join(" ")},i.log.once=function(t,n,o){return(o=i.log.once)[t]=o[t]||0,o[t]++||i.log(n)},i.log.once("welcome","Hello wonderful person! :) Thanks for using GUN, feel free to ask for help on https://gitter.im/amark/gun and ask StackOverflow questions tagged with 'gun'!"),"undefined"!=typeof window&&(window.Gun=i);try{"undefined"!=typeof e&&(e.exports=i)}catch(y){}n.exports=i})(t,"./root"),t(function(){var n=t("./root");n.chain.back=function(t,i){var r;if(t=t||1,-1===t||1/0===t)return this._.root.gun;if(1===t)return(this._.back||this._).gun;var u=this,a=u._;if("string"==typeof t&&(t=t.split(".")),!(t instanceof Array)){if(t instanceof Function){for(var s,r={back:a};(r=r.back)&&!(s=t(r,i)););return s}return n.num.is(t)?(a.back||a).gun.back(t-1):this}var f=0,c=t.length,r=a;for(f;c>f;f++)r=(r||e)[t[f]];return o!==r?i?u:r:(r=a.back)?r.gun.back(t,i):void 0};var o,e={}})(t,"./back"),t(function(){function n(t){var n,o=this.as,e=o.back,i=o.root;if(t.gun||(t.gun=o.gun),this.to.next(t),n=t.get){if(n["#"]||o.soul){if(n["#"]=n["#"]||o.soul,t["#"]||(t["#"]=m(9)),e=i.gun.get(n["#"])._,n=n["."]){if(h(e.put,n))return void e.on("in",{gun:e.gun,put:c.state.to(e.put,n),get:e.get})}else{if(h(e,"put")&&e.on("in",e),e.ack)return;t.gun=e.gun,e.ack=-1}return i.ask(f,t),i.on("in",t)}if(i.now&&(i.now[o.id]=i.now[o.id]||!0),n["."])return o.get?(t={get:{".":o.get},gun:o.gun},(e.ask||(e.ask={}))[o.get]=t.gun._,e.on("out",t)):(t={get:{},gun:o.gun},e.on("out",t));if(o.ack=o.ack||-1,o.get)return t.gun=o.gun,n["."]=o.get,(e.ask||(e.ask={}))[o.get]=t.gun._,e.on("out",t)}return e.on("out",t)}function o(t){var n,o=this,r=this.as,s=t.gun,f=s._,p=t.put;if(r.get&&t.get!==r.get&&(t=v(t,{get:r.get})),r.has&&f!==r&&(t=v(t,{gun:r.gun}),f.ack&&(r.ack=f.ack)),y===r.get&&p&&p["#"]&&(r._id=p["#"]),l===p){if(o.to.next(t),r.soul)return;return i(r,t,o),r.has&&a(r,t),d(f.echo,r.id),void d(r.map,f.id)}return r.soul?(o.to.next(t),i(r,t,o),void b(p,u,{at:t,cat:r})):(n=c.val.rel.is(p))?(e(r,t,f,n),o.to.next(t),void i(r,t,o)):c.val.is(p)?(r.has||r.soul?a(r,t):(f.has||f.soul)&&((f.echo||(f.echo={}))[r.id]=r,(r.map||(r.map={}))[f.id]=r.map[f.id]||{at:f}),o.to.next(t),void i(r,t,o)):(r.has&&f!==r&&h(f,"put")&&(r.put=f.put),(n=c.node.soul(p))&&f.has&&(f.put=r.root.gun.get(n)._.put),o.to.next(t),i(r,t,o),e(r,t,f,n),void b(p,u,{at:t,cat:r}))}function e(t,n,o,i){if(i&&y!==t.get){var r=t.root.gun.get(i)._;if(t.has?o=r:o.has&&e(o,n,o,i),o!==t){(o.echo||(o.echo={}))[t.id]=t,t.has&&!(t.map||p)[o.id]&&a(t,n),r=(t.map||(t.map={}))[o.id]=t.map[o.id]||{at:o};var u=t.root.now;if(i===r.rel){if(!u)return;if(l===u[t.id])return;if((u._||(u._={}))[t.id]===i)return;u._[t.id]=i}s(t,r.rel=i)}}}function i(t,n,o){t.echo&&(t.has&&(n=v(n,{event:o})),b(t.echo,r,n))}function r(t){t.on("in",this)}function u(t,n){var o,e,i=this.cat,r=i.next||p,u=this.at;(y!==n||r[n])&&(e=r[n])&&(e.has?(t&&t[_]&&c.val.rel.is(t)===c.node.soul(e.put)||(e.put=t),o=e.gun):o=u.gun.get(n),e.on("in",{put:t,get:n,gun:o,via:u}))}function a(t,n){if(t.has||t.soul){var o=t.map,e=t.root;t.map=null,(e.now&&e.now[t.id]||n["@"]||null!==o)&&(l===o&&c.val.rel.is(t.put)||(b(o,function(n){(n=n.at)&&d(n.echo,t.id)}),b(t.next,function(t,n){t.put=l,t.ack&&(t.ack=-1),t.on("in",{get:n,gun:t.gun,put:l})})))}}function s(t,n){var o=t.root.gun.get(n)._;(!t.ack||(o.on("out",{get:{"#":n}}),t.ask))&&(b(t.ask||t.next,function(t,o){t.on("out",{get:{"#":n,".":o}})}),c.obj.del(t,"ask"))}function f(t){var n=this.as,o=n.get||p,e=n.gun._,i=(t.put||p)[o["#"]];if(e.ack&&(e.ack=e.ack+1||1),!t.put||o["."]&&!h(i,e.get)){if(e.put!==l)return;return void e.on("in",{get:e.get,put:e.put=l,gun:e.gun,"@":t["@"]})}return y==o["."]?void e.on("in",{get:e.get,put:i[e.get],gun:e.gun,"@":t["@"]}):(t.gun=e.root.gun,void c.on.put(t,e.root.gun))}var c=t("./root");c.chain.chain=function(){var t,e=this._,i=new this.constructor(this),r=i._;return r.root=t=e.root,r.id=++t.once,r.back=this._,r.on=c.on,r.on("in",o,r),r.on("out",n,r),i};var l,p={},g=c.obj,h=g.has,d=(g.put,g.del),v=g.to,b=g.map,m=c.text.random,_=c.val.rel._,y=c.node._})(t,"./chain"),t(function(){function n(t,n){var o=n._,e=o.next,i=n.chain(),r=i._;return e||(e=o.next={}),e[r.get=t]=r,n===o.root.gun?r.soul=t:(o.soul||o.has)&&(r.has=t),r}function o(t){var n,o=this,e=o.as,r=t.gun,a=r._,f=a.root,c=t.put;return(n=f.now)&&o!==n[e.now]?o.to.next(t):(i===c&&(c=a.put),(n=c)&&n[s._]&&(n=s.is(n))&&(n=a.root.gun.get(n)._,i!==n.put&&(t=u(t,{put:n.put}))),e.use(t,t.event||o),void o.to.next(t))}var e=t("./root");e.chain.get=function(t,i,r){var u;if("string"!=typeof t){if(t instanceof Function){u=this;var s,c=u._,l=c.root,p=l.now;return r=i||{},r.use=t,r.out=r.out||{},r.out.get=r.out.get||{},s=c.on("in",o,r),(l.now={$:1})[r.now=c.id]=s,c.on("out",r.out),l.now=p,u}return a(t)?this.get(""+t,i,r):((r=this.chain())._.err={err:e.log("Invalid get request!",t)},i&&i.call(r,r._.err),r)}var p,g=this,h=g._,d=h.next||f;return(u=d[t])||(u=n(t,g)),u=u.gun,(p=h.stun)&&(u._.stun=u._.stun||p),i&&i instanceof Function&&u.get(i,r),u};var i,r=e.obj,u=(r.has,e.obj.to),a=e.num.is,s=e.val.rel,f=(e.node._,{})})(t,"./get"),t(function(){function n(t){t.batch=i;var n=t.opt||{},o=t.env=c.state.map(u,n.state);return o.soul=t.soul,t.graph=c.graph.ify(t.data,o,t),o.err?((t.ack||b).call(t,t.out={err:c.log(o.err)}),void(t.res&&t.res())):void t.batch()}function e(t){return void(t&&t())}function i(){var t=this;t.graph&&!d(t.stun,r)&&(t.res=t.res||function(t){t&&t()},t.res(function(){var n=t.gun.back(-1)._,o=n.ask(function(n){this.off(),t.ack&&t.ack(n,this)},t.opt),e=n.root.now;p.del(n.root,"now"),n.root.PUT=!0;var i=n.root.stop;t.ref._.now=!0,t.ref._.on("out",{gun:t.ref,put:t.out=t.env.graph,opt:t.opt,"#":o}),p.del(t.ref._,"now"),p.del(n.root,"PUT"),n.root.now=e,n.root.stop=i},t),t.res&&t.res())}function r(t){return t?!0:void 0}function u(t,n,o,e){var i=this;!n&&e.path.length&&(i.res||m)(function(){var t=e.path,n=i.ref,o=(i.opt,0),r=t.length;for(o;r>o;o++)n=n.get(t[o]);if(c.node.soul(e.obj)){var u=c.node.soul(e.obj)||(n.back("opt.uuid")||c.text.random)();return u?(n.back(-1).get(u),void e.soul(u)):((i.stun=i.stun||{})[t]=!0,void n.back("opt.uuid")(function(o,r){return o?c.log(o):(n.back(-1).get(r),e.soul(r),i.stun[t]=!1,void i.batch())}))}(i.stun=i.stun||{})[t]=!0,n.get("_").get(a,{as:{at:e,as:i}})},{as:i,at:e})}function a(t,n){var o=this.as,e=o.at;if(o=o.as,t.gun&&t.gun._.back){var i=t.gun._,r=i,u=(t.put||v)["#"];n.off(),i=t.gun._.back;var a=a||c.node.soul(e.obj)||c.node.soul(i.put)||c.val.rel.is(i.put)||u||r._id||(o.gun.back("opt.uuid")||c.text.random)();return a?void s(i,r._id=r._id||a,e,o):void i.gun.back("opt.uuid")(function(t,n){return t?c.log(t):void s(i,r._id=r._id||n,e,o)})}}function s(t,n,o,e){t.gun.back(-1).get(n),o.soul(n),e.stun[o.path]=!1,e.batch()}function f(t,n){var e=this.as;if(t.gun&&t.gun._){if(t.err)return void o.log("Please report this as an issue! Put.any.err");var i,r=t.gun._.back,u=r.put,a=e.opt||{};if(!(i=e.ref)||!i._.now){if(n.off(),e.ref!==e.gun){if(i=e.gun._.get||r.get,!i)return void o.log("Please report this as an issue! Put.no.get");e.data=h({},i,e.data),i=null}if(l===u){if(!r.get)return;r.soul||(i=r.gun.back(function(t){return t.soul?t.soul:void(e.data=h({},t.get,e.data))})),i=i||r.get,r=r.root.gun.get(i)._,e.not=e.soul=i,u=e.data}return e.not||(e.soul=c.node.soul(u))||(e.path&&g(e.data)?e.soul=(a.uuid||r.root.opt.uuid||c.text.random)():(_==t.get&&(e.soul=(t.put||v)["#"]||t._id),e.soul=e.soul||t.soul||r.soul||(a.uuid||r.root.opt.uuid||c.text.random)()),e.soul)?void e.ref.put(e.data,e.soul,e):void e.ref.back("opt.uuid")(function(t,n){return t?c.log(t):void e.ref.put(e.data,e.soul=n,e)})}}}var c=t("./root");c.chain.put=function(t,o,i){var r,u=this,a=u._,s=a.root.gun;return i=i||{},i.data=t,i.gun=i.gun||u,"string"==typeof o?i.soul=o:i.ack=o,a.soul&&(i.soul=a.soul),i.soul||s===u?g(i.data)?(i.soul=i.soul||(i.not=c.node.soul(i.data)||(s._.opt.uuid||c.text.random)()),i.soul?(i.gun=u=s.get(i.soul),i.ref=i.gun,n(i),u):(s._.opt.uuid(function(t,n){return t?c.log(t):void(i.ref||i.gun).put(i.data,i.soul=n,i)}),u)):((i.ack||b).call(i,i.out={err:c.log("Data saved to the root level of the graph must be a node (an object), not a",typeof i.data,'of "'+i.data+'"!')}),i.res&&i.res(),u):c.is(t)?(t.get("_").get(function(t,n,e){return n.off(),(e=t.gun)&&(e=e._.back)&&e.soul?void u.put(c.val.rel.ify(e.soul),o,i):c.log("The reference you are saving is a",typeof t.put,'"'+i.put+'", not a node (object)!')}),u):(i.ref=i.ref||s._===(r=a.back)?u:r.gun,i.ref._.soul&&c.val.is(i.data)&&a.get?(i.data=h({},a.get,i.data),i.ref.put(i.data,i.soul,i),u):(i.ref.get("_").get(f,{as:i}),i.out||(i.res=i.res||e,i.gun._.stun=i.ref._.stun),u))};var l,p=c.obj,g=p.is,h=p.put,d=p.map,v={},b=function(){},m=function(t,n){t.call(n||v)},_=c.node._})(t,"./put"),t(function(n){var o=t("./root");t("./chain"),t("./back"),t("./put"),t("./get"),n.exports=o})(t,"./index"),t(function(){function n(t,n){var o,r=this,u=t.gun,a=u._,f=a.put||t.put,o=r.last,c=a.id+t.get;if(i!==f){if(f&&f[s._]&&(o=s.is(f))){if(o=a.root.gun.get(o)._,i===o.put)return;f=o.put}r.change&&(f=t.put),(o.put!==f||o.get!==c||e.node.soul(f))&&(o.put=f,o.get=c,a.last=f,r.as?r.ok.call(r.as,t,n):r.ok.call(u,f,t.get,t,n))}}function o(t,n,r){var u,a=this.as,f=a.cat,c=t.gun,l=c._,p=l.put||t.put;if(u=e.node.soul(p)||s.is(p)){if(u=f.root.gun.get(u)._,i===u.put)return;p=u.put}if(n.wait&&clearTimeout(n.wait),!r)return void(n.wait=setTimeout(function(){o.call({as:a},t,n,n.wait||1)},a.wait||99));if(f.has||f.soul){if(n.off())return}else{if((a.seen=a.seen||{})[l.id])return;a.seen[l.id]=!0}a.ok.call(t.gun||a.gun,p,t.get)}var e=t("./index");e.chain.on=function(t,o,e,i){var r,u,a=this,s=a._;if("string"==typeof t)return o?(r=s.on(t,o,e||s,i),e&&e.gun&&(e.subs||(e.subs=[])).push(r),u=function(){r&&r.off&&r.off(),u.off()},u.off=a.off.bind(a)||f,a.off=u,a):s.on(t);var c=o;return c=!0===c?{change:!0}:c||{},c.ok=t,c.last={},a.get(n,c),a},e.chain.val=function(t,n){return e.log.once("onceval","Future Breaking API Change: .val -> .once, apologies unexpected."),this.once(t,n)},e.chain.once=function(t,n){var r=this,u=r._,a=u.put;if(0=(n.batch||1e3)?s():void(e||(e=setTimeout(s,n.wait||1)))}),t.on("get",function(o){this.to.next(o);var e,i,r,a=o.get;if(a&&(e=a["#"])){var s=a["."];i=u[e]||r,i&&s&&(i=Gun.state.to(i,s)),(i||Gun.obj.empty(n.peers))&&t.on("in",{"@":o["#"],put:Gun.graph.node(i),how:"lS"})}});var a=function(t,n,o,e){u[e]=Gun.state.to(o,n,u[e])},s=function(){var a;r=0,clearTimeout(e),e=!1;var s=i;i={};try{o.setItem(n.file,JSON.stringify(u))}catch(f){Gun.log(a=f||"localStorage failure")}(a||Gun.obj.empty(n.peers))&&Gun.obj.map(s,function(n,o){t.on("in",{"@":o,err:a,ok:0})})}}})}})(t,"./adapters/localStorage"),t(function(n){function o(t){var n=function(){};return n.out=function(o){var e;return this.to&&this.to.next(o),(e=o["@"])&&(e=t.dup.s[e])&&(e=e.it)&&e.mesh?(n.say(o,e.mesh.via),void(e["##"]=o["##"])):void n.say(o)},n.hear=function(o,i){if(o){var r,u,a=t.dup,s=o[0];try{o=JSON.parse(o)}catch(f){}if("{"===s){if(a.check(r=o["#"]))return;if(a.track(r,!0).it=o,(s=o["@"])&&o.put&&(u=o["##"]||(o["##"]=n.hash(o)),(s+=u)!=r)){if(a.check(s))return;(s=a.s)[u]=s[r]}return(o.mesh=function(){}).via=i,(s=o["><"])&&(o.mesh.to=e.obj.map(s.split(","),function(t,n,o){o(t,!0)})),void t.on("in",o)}if("["!==s);else for(var c,l=0;c=o[l++];)n.hear(c,i)}},function(){function o(t,n){var o=n.wire;try{o.send?o.readyState===o.OPEN?o.send(t):(n.queue=n.queue||[]).push(t):n.say&&n.say(t)}catch(e){(n.queue=n.queue||[]).push(t)}}n.say=function(i,u){if(!u)return void e.obj.map(t.opt.peers,function(t){n.say(i,t)});var a,s,f,c=u.wire||t.opt.wire&&t.opt.wire(u);if(c&&(s=i.mesh||r,u!==s.via&&((f=s.raw)||(f=n.raw(i)),!((a=i["@"])&&(a=t.dup.s[a])&&(a=a.it)&&a.get&&a["##"]&&a["##"]===i["##"]||(a=s.to)&&(a[u.url]||a[u.id]))))){if(u.batch)return void u.batch.push(f);u.batch=[],setTimeout(function(){var t=u.batch;t&&(u.batch=null,t.length&&o(JSON.stringify(t),u))},t.opt.wait||1),o(f,u)}}}(),function(){function r(t,n){var o;return n instanceof Object?(e.obj.map(Object.keys(n).sort(),u,{to:o={},on:n}),o):n}function u(t){this.to[t]=this.on[t]}n.raw=function(o){if(!o)return"";var u,f,c,l=t.dup,p=o.mesh||{};if(c=p.raw)return c;if("string"==typeof o)return o;o["@"]&&(c=o.put)&&((f=o["##"])||(u=a(c,r)||"",f=n.hash(o,u),o["##"]=f),(c=l.s)[f=o["@"]+f]=c[o["#"]],o["#"]=f,u&&((o=e.obj.to(o)).put=s));var g=0,h=[];e.obj.map(t.opt.peers,function(t){return h.push(t.url||t.id),++g>9?!0:void 0}),o["><"]=h.join();var d=a(o);return i!==u&&(d=d.replace('"'+s+'"',u)),p&&(p.raw=d),d},n.hash=function(t,n){return o.hash(n||a(t.put,r)||"")||t["#"]||e.text.random(9)};var a=JSON.stringify,s=":])([:"}(),n.hi=function(o){t.on("hi",o);var i=o.queue;o.queue=[],e.obj.map(i,function(t){n.say(t,o)})},n}var e=t("../type");o.hash=function(t){if("string"!=typeof t)return{err:1};var n=0;if(!t.length)return n;for(var o,e=0,i=t.length;i>e;++e)o=t.charCodeAt(e),n=(n<<5)-n+o,n|=0;return n};var i,r={};Object.keys=Object.keys||function(t){return map(t,function(t,n,o){o(n)})};try{n.exports=o}catch(u){}})(t,"./adapters/mesh"),t(function(){var n=t("../index");n.Mesh=t("./mesh"),n.on("opt",function(t){function o(n){if(n&&n.url){var o=n.url.replace("http","ws"),r=n.wire=new i.WebSocket(o);return r.onclose=function(){t.on("bye",n),e(n)},r.onerror=function(t){e(n),t&&"ECONNREFUSED"===t.code},r.onopen=function(){a.hi(n)},r.onmessage=function(t){t&&a.hear(t.data||t,n)},r}}function e(t){clearTimeout(t.defer),t.defer=setTimeout(function(){o(t)},2e3)}this.to.next(t);var i=t.opt;if(!t.once&&!1!==i.WebSocket){var r;"undefined"!=typeof window&&(r=window),"undefined"!=typeof global&&(r=global),r=r||{};var u=i.WebSocket||r.WebSocket||r.webkitWebSocket||r.mozWebSocket;if(u){i.WebSocket=u;var a=i.mesh=i.mesh||n.Mesh(t);t.on("out",a.out),i.wire=i.wire||o}}})})(t,"./adapters/websocket")}(); \ No newline at end of file +!function(){function t(n){function o(t){return t.split("/").slice(-1).toString().replace(".js","")}return n.slice?t[o(n)]:function(e,i){n(e={exports:{}}),t[o(i)]=e.exports}}var n;"undefined"!=typeof window&&(n=window),"undefined"!=typeof global&&(n=global);var o=(n=n||{}).console||{log:function(){}};if("undefined"!=typeof module)var e=module;t(function(t){var n={};n.fns=n.fn={is:function(t){return!!t&&"function"==typeof t}},n.bi={is:function(t){return t instanceof Boolean||"boolean"==typeof t}},n.num={is:function(t){return!e(t)&&(t-parseFloat(t)+1>=0||1/0===t||-1/0===t)}},n.text={is:function(t){return"string"==typeof t}},n.text.ify=function(t){return n.text.is(t)?t:"undefined"!=typeof JSON?JSON.stringify(t):t&&t.toString?t.toString():t},n.text.random=function(t,n){var o="";for(t=t||24,n=n||"0123456789ABCDEFGHIJKLMNOPQRSTUVWXZabcdefghijklmnopqrstuvwxyz";t>0;)o+=n.charAt(Math.floor(Math.random()*n.length)),t--;return o},n.text.match=function(t,o){var e=!1;if(t=t||"",o=n.text.is(o)?{"=":o}:o||{},n.obj.has(o,"~")&&(t=t.toLowerCase(),o["="]=(o["="]||o["~"]).toLowerCase()),n.obj.has(o,"="))return t===o["="];if(n.obj.has(o,"*")){if(t.slice(0,o["*"].length)!==o["*"])return!1;e=!0,t=t.slice(o["*"].length)}if(n.obj.has(o,"!")){if(t.slice(-o["!"].length)!==o["!"])return!1;e=!0}if(n.obj.has(o,"+")&&n.list.map(n.list.is(o["+"])?o["+"]:[o["+"]],function(n){if(!(t.indexOf(n)>=0))return!0;e=!0}))return!1;if(n.obj.has(o,"-")&&n.list.map(n.list.is(o["-"])?o["-"]:[o["-"]],function(n){if(!(t.indexOf(n)<0))return!0;e=!0}))return!1;if(n.obj.has(o,">")){if(!(t>o[">"]))return!1;e=!0}if(n.obj.has(o,"<")){if(!(to?1:0):0}},n.list.map=function(t,n,o){return a(t,n,o)},n.list.index=1,n.obj={is:function(t){return!!t&&(t instanceof Object&&t.constructor===Object||"Object"===Object.prototype.toString.call(t).match(/^\[object (\w+)\]$/)[1])}},n.obj.put=function(t,n,o){return(t||{})[n]=o,t},n.obj.has=function(t,n){return t&&Object.prototype.hasOwnProperty.call(t,n)},n.obj.del=function(t,n){if(t)return t[n]=null,delete t[n],t},n.obj.as=function(t,n,o,e){return t[n]=t[n]||(e===o?{}:o)},n.obj.ify=function(t){if(r(t))return t;try{t=JSON.parse(t)}catch(n){t={}}return t},function(){function t(t,n){u(this,n)&&o!==this[n]||(this[n]=t)}var o;n.obj.to=function(n,o){return o=o||{},a(n,t,o),o}}(),n.obj.copy=function(t){return t?JSON.parse(JSON.stringify(t)):t},function(){function t(t,n){var o=this.n;if(!o||!(n===o||r(o)&&u(o,n)))return!!n||void 0}n.obj.empty=function(n,o){return!n||!a(n,t,{n:o})}}(),function(){function t(n,o){if(2===arguments.length)return t.r=t.r||{},void(t.r[n]=o);t.r=t.r||[],t.r.push(n)}var i=Object.keys;n.obj.map=function(a,s,f){var c,l,p,g,h,d=0,v=o(s);if(t.r=null,i&&r(a)&&(g=i(a),h=!0),e(a)||g)for(l=(g||a).length;d",o.drift=0,o.is=function(t,n,e){var i=n&&t&&t[y]&&t[y][o._]||e;if(i)return m(i=i[n])?i:-1/0},o.lex=function(){return o().toString(36).replace(".","")},o.ify=function(t,n,e,r,u){if(!t||!t[y]){if(!u)return;t=i.soul.ify(t,u)}var a=g(t[y],o._);return l!==n&&n!==y&&(m(e)&&(a[n]=e),l!==r&&(t[n]=r)),t},o.to=function(t,n,e){var r=t[n];return d(r)&&(r=b(r)),o.ify(e,n,o.is(t,n),r,i.soul(t))},function(){function t(t,n){y!==n&&o.ify(this.o,n,this.s)}o.map=function(n,e,i){var r,u=d(u=n||e)?u:null;return n=_(n=n||e)?n:null,u&&!n?(e=m(e)?e:o(),u[y]=u[y]||{},v(u,t,{o:u,s:e}),u):(i=i||d(e)?e:r,e=m(e)?e:o(),function(o,u,a,s){if(!n)return t.call({o:a,s:e},o,u),o;n.call(i||this||{},o,u,a,s),h(a,u)&&r===a[u]||t.call({o:a,s:e},o,u)})}}();var l,p=e.obj,g=p.as,h=p.has,d=p.is,v=p.map,b=p.copy,m=e.num.is,_=e.fn.is,y=i._;n.exports=o})(t,"./state"),t(function(n){var o=t("./type"),e=t("./val"),i=t("./node"),r={};!function(){function t(t,o){if(!t||o!==i.soul(t)||!i.is(t,this.fn,this.as))return!0;this.cb&&(n.n=t,n.as=this.as,this.cb.call(n.as,t,o,n))}function n(t){t&&i.is(n.n,t,n.as)}r.is=function(n,o,e,i){return!(!n||!s(n)||l(n))&&!g(n,t,{cb:o,fn:e,as:i})}}(),function(){function t(t,o){var r;return(r=p(t,o))?r:(o.env=t,o.soul=a,i.ify(o.obj,n,o)&&(t.graph[e.rel.is(o.rel)]=o.node),o)}function n(n,o,r){var a,s,p=this,g=p.env;if(i._===o&&c(n,e.rel._))return r._;if(a=l(n,o,r,p,g)){if(o||(p.node=p.node||r||{},c(n,i._)&&(p.node._=h(n._)),p.node=i.soul.ify(p.node,e.rel.is(p.rel)),p.rel=p.rel||e.rel.ify(i.soul(p.node))),(s=g.map)&&(s.call(g.as||{},n,o,r,p),c(r,o))){if(n=r[o],u===n)return void f(r,o);if(!(a=l(n,o,r,p,g)))return}if(!o)return p.node;if(!0===a)return n;if((s=t(g,{obj:n,path:p.path.concat(o)})).node)return s.rel}}function a(t){var n=this,o=e.rel.is(n.rel),r=n.env.graph;n.rel=n.rel||e.rel.ify(t),n.rel[e.rel._]=t,n.node&&n.node[i._]&&(n.node[i._][e.rel._]=t),c(r,o)&&(r[t]=r[o],f(r,o))}function l(t,n,i,r,u){var a;return!!e.is(t)||(s(t)?1:(a=u.invalid)?(t=a.call(u.as||{},t,n,i),l(t,n,i,r,u)):(u.err="Invalid value at '"+r.path.concat(n).join(".")+"'!",void(o.list.is(t)&&(u.err+=" Use `.set(item)` instead of an Array."))))}function p(t,n){for(var o,e=t.seen,i=e.length;i--;)if(o=e[i],n.obj===o.obj)return o;e.push(n)}r.ify=function(n,o,i){var r={path:[],obj:n};return o?"string"==typeof o?o={soul:o}:o instanceof Function&&(o.map=o):o={},o.soul&&(r.rel=e.rel.ify(o.soul)),o.graph=o.graph||{},o.seen=o.seen||[],o.as=o.as||i,t(o,r),o.root=r.node,o.graph}}(),r.node=function(t){var n=i.soul(t);if(n)return p({},n,t)},function(){function t(t,n){var o,u;if(i._!==n)(o=e.rel.is(t))?(u=this.opt.seen[o])?this.obj[n]=u:this.obj[n]=this.opt.seen[o]=r.to(this.graph,o,this.opt):this.obj[n]=t;else{if(l(t,e.rel._))return;this.obj[n]=h(t)}}r.to=function(n,o,e){if(n){var i={};return e=e||{seen:{}},g(n[o],t,{obj:i,graph:n,opt:e}),i}}}();o.fn.is;var u,a=o.obj,s=a.is,f=a.del,c=a.has,l=a.empty,p=a.put,g=a.map,h=a.copy;n.exports=r})(t,"./graph"),t(function(n){t("./onto"),n.exports=function(t,n){if(this.on){if(!(t instanceof Function)){if(!t||!n)return;var o=t["#"]||t,e=(this.tag||empty)[o];if(!e)return;return e=this.on(o,n),clearTimeout(e.err),!0}o=n&&n["#"]||Math.random().toString(36).slice(2);if(!t)return o;var i=this.on(o,t,n);return i.err=i.err||setTimeout(function(){i.next({err:"Error: No ACK received yet."}),i.off()},(this.opt||{}).lack||9e3),o}}})(t,"./ask"),t(function(n){var o=t("./type"),e=o.time.is;n.exports=function(t){var n={s:{}};return t=t||{max:1e3,age:9e3},n.check=function(t){var o;return!!(o=n.s[t])&&(o.pass?o.pass=!1:n.track(t))},n.track=function(i,r){var u=n.s[i]||(n.s[i]={});return u.was=e(),r&&(u.pass=!0),n.to||(n.to=setTimeout(function(){var i=e();o.obj.map(n.s,function(e,r){t.age>i-e.was||o.obj.del(n.s,r)}),n.to=null},t.age+9)),u},n}})(t,"./dup"),t(function(n){function i(t){return t instanceof i?(this._={gun:this}).gun:this instanceof i?i.create(this._={gun:this,opt:t}):new i(t)}i.is=function(t){return t instanceof i},i.version=.9,i.chain=i.prototype,i.chain.toJSON=function(){};var r=t("./type");r.obj.to(r,i),i.HAM=t("./HAM"),i.val=t("./val"),i.node=t("./node"),i.state=t("./state"),i.graph=t("./graph"),i.on=t("./onto"),i.ask=t("./ask"),i.dup=t("./dup"),function(){function t(t){var n,o,e=this.as,r=e.gun;(o=t["#"])||(o=t["#"]=c(9)),(n=e.dup).check(o)||(n.track(o),e.ask(t["@"],t)||(t.get&&i.on.get(t,r),t.put&&i.on.put(t,r)),e.on("out",t))}i.create=function(n){n.root=n.root||n,n.graph=n.graph||{},n.on=n.on||i.on,n.ask=n.ask||i.ask,n.dup=n.dup||i.dup();var o=n.gun.opt(n.opt);return n.once||(n.on("in",t,n),n.on("out",t,n)),n.once=1,o}}(),function(){function t(t,n,o,e){var r=this,u=i.state.is(o,n);if(!u)return r.err="Error: No state on '"+n+"' in node '"+e+"'!";var a=r.graph[e]||_,s=i.state.is(a,n,!0),f=a[n],c=i.HAM(r.machine,u,s,t,f);c.incoming?(r.put[e]=i.state.to(o,n,r.put[e]),(r.diff||(r.diff={}))[e]=i.state.to(o,n,r.diff[e]),r.souls[e]=!0):c.defer&&(r.defer=u<(r.defer||1/0)?u:r.defer)}function n(t,n){var i=this,u=i.gun._,a=(u.next||_)[n];if(a){var s=i.map[n]={put:t,get:n,gun:a.gun},f={ctx:i,msg:s};i.async=!!u.tag.node,i.ack&&(s["@"]=i.ack),d(t,o,f),i.async&&(i.and||u.on("node",function(t){this.to.next(t),t===i.map[t.get]&&(i.souls[t.get]=!1,d(t.put,e,t),d(i.souls,function(t){if(t)return t})||i.c||(i.c=1,this.off(),u.stop={},d(i.map,r,i)))}),i.and=!0,u.on("node",s))}else i.souls[n]=!1}function o(t,n){var o=this.ctx,e=o.graph,r=this.msg,u=r.get,a=r.put,s=r.gun._;e[u]=i.state.to(a,n,e[u]),o.async||(s.put=i.state.to(a,n,s.put))}function e(t,n){var o=this,e=o.put,r=o.gun._;r.put=i.state.to(e,n,r.put)}function r(t,n){t.gun&&t.gun._.on("in",t)}i.on.put=function(o,e){var a=e._,s={gun:e,graph:a.graph,put:{},map:{},souls:{},machine:i.state(),ack:o["@"]};if(i.graph.is(o.put,null,t,s)||(s.err="Error: Invalid graph!"),s.err)return a.on("in",{"@":o["#"],err:i.log(s.err)});d(s.put,n,s),s.async||(a.stop={},d(s.map,r,s)),u!==s.defer&&setTimeout(function(){i.on.put(o,e)},s.defer-s.machine),s.diff&&a.on("put",h(o,{put:s.diff}))},i.on.get=function(t,n){var o=n._,e=t.get[b],r=o.graph[e],u=t.get[m],a=(o.next||(o.next={}))[e];if(!r||!a)return o.on("get",t);if(u){if(!g(r,u))return o.on("get",t);r=i.state.to(r,u)}else r=i.obj.copy(r);r=i.graph.node(r),o.on("in",{"@":t["#"],how:"mem",put:r,gun:n}),o.on("get",t)}}(),i.chain.opt=function(t){t=t||{};var n=this,o=n._,e=t.peers||t;return p(t)||(t={}),p(o.opt)||(o.opt=t),f(e)&&(e=[e]),a(e)&&(e=d(e,function(t,n,o){o(t,{url:t})}),p(o.opt.peers)||(o.opt.peers={}),o.opt.peers=h(e,o.opt.peers)),o.opt.peers=o.opt.peers||{},h(t,o.opt),i.on("opt",o),o.opt.uuid=o.opt.uuid||function(){return v()+c(12)},n};var u,a=i.list.is,s=i.text,f=s.is,c=s.random,l=i.obj,p=l.is,g=l.has,h=l.to,d=l.map,v=(l.copy,i.state.lex),b=i.val.rel._,m=".",_=(i.node._,i.val.rel.is,{});o.debug=function(t,n){return o.debug.i&&t===o.debug.i&&o.debug.i++&&(o.log.apply(o,arguments)||n)},i.log=function(){return!i.log.off&&o.log.apply(o,arguments),[].slice.call(arguments).join(" ")},i.log.once=function(t,n,o){return(o=i.log.once)[t]=o[t]||0,o[t]++||i.log(n)},i.log.once("welcome","Hello wonderful person! :) Thanks for using GUN, feel free to ask for help on https://gitter.im/amark/gun and ask StackOverflow questions tagged with 'gun'!"),"undefined"!=typeof window&&(window.Gun=i);try{void 0!==e&&(e.exports=i)}catch(t){}n.exports=i})(t,"./root"),t(function(n){var o=t("./root");o.chain.back=function(t,n){if(-1===(t=t||1)||1/0===t)return this._.root.gun;if(1===t)return(this._.back||this._).gun;var r=this,u=r._;if("string"==typeof t&&(t=t.split(".")),!(t instanceof Array)){if(t instanceof Function){for(var a,s={back:u};(s=s.back)&&!(a=t(s,n)););return a}return o.num.is(t)?(u.back||u).gun.back(t-1):this}var f=0,c=t.length,s=u;for(f;f .once, apologies unexpected."),this.once(t,n)},i.chain.once=function(t,n){var o=this,u=o._,a=u.put;if(0=(n.batch||1e3))return s();o||(o=setTimeout(s,n.wait||1))}),t.on("get",function(o){this.to.next(o);var e,i,r=o.get;if(r&&(e=r["#"])){var a=r["."];(i=u[e]||void 0)&&a&&(i=Gun.state.to(i,a)),(i||Gun.obj.empty(n.peers))&&t.on("in",{"@":o["#"],put:Gun.graph.node(i),how:"lS"})}});var a=function(t,n,o,e){u[e]=Gun.state.to(o,n,u[e])},s=function(){var a;r=0,clearTimeout(o),o=!1;var s=i;i={};try{e.setItem(n.file,JSON.stringify(u))}catch(t){Gun.log(a=t||"localStorage failure")}(a||Gun.obj.empty(n.peers))&&Gun.obj.map(s,function(n,o){t.on("in",{"@":o,err:a,ok:0})})}}})}})(t,"./adapters/localStorage"),t(function(n){function o(t){var n=function(){};return n.out=function(o){var e;if(this.to&&this.to.next(o),(e=o["@"])&&(e=t.dup.s[e])&&(e=e.it)&&e.mesh)return n.say(o,e.mesh.via),void(e["##"]=o["##"]);n.say(o)},n.hear=function(o,i){if(o){var r,u,a=t.dup,s=o[0];try{o=JSON.parse(o)}catch(t){}if("{"===s){if(a.check(r=o["#"]))return;if(a.track(r,!0).it=o,(s=o["@"])&&o.put&&(u=o["##"]||(o["##"]=n.hash(o)),(s+=u)!=r)){if(a.check(s))return;(s=a.s)[u]=s[r]}return(o.mesh=function(){}).via=i,(s=o["><"])&&(o.mesh.to=e.obj.map(s.split(","),function(t,n,o){o(t,!0)})),void t.on("in",o)}if("["!==s);else for(var f,c=0;f=o[c++];)n.hear(f,i)}},function(){function o(t,n){var o=n.wire;try{o.send?o.readyState===o.OPEN?o.send(t):(n.queue=n.queue||[]).push(t):n.say&&n.say(t)}catch(o){(n.queue=n.queue||[]).push(t)}}n.say=function(i,u){if(u){var a,s,f;(u.wire||t.opt.wire&&t.opt.wire(u))&&(s=i.mesh||r,u!==s.via&&((f=s.raw)||(f=n.raw(i)),(a=i["@"])&&(a=t.dup.s[a])&&(a=a.it)&&a.get&&a["##"]&&a["##"]===i["##"]||(a=s.to)&&(a[u.url]||a[u.id])||(u.batch?u.batch.push(f):(u.batch=[],setTimeout(function(){var t=u.batch;t&&(u.batch=null,t.length&&o(JSON.stringify(t),u))},t.opt.wait||1),o(f,u)))))}else e.obj.map(t.opt.peers,function(t){n.say(i,t)})}}(),function(){function r(t,n){var o;return n instanceof Object?(e.obj.map(Object.keys(n).sort(),u,{to:o={},on:n}),o):n}function u(t){this.to[t]=this.on[t]}n.raw=function(o){if(!o)return"";var u,f,c,l=t.dup,p=o.mesh||{};if(c=p.raw)return c;if("string"==typeof o)return o;o["@"]&&(c=o.put)&&((f=o["##"])||(u=a(c,r)||"",f=n.hash(o,u),o["##"]=f),(c=l.s)[f=o["@"]+f]=c[o["#"]],o["#"]=f,u&&((o=e.obj.to(o)).put=s));var g=0,h=[];e.obj.map(t.opt.peers,function(t){if(h.push(t.url||t.id),++g>9)return!0}),o["><"]=h.join();var d=a(o);return i!==u&&(d=d.replace('"'+s+'"',u)),p&&(p.raw=d),d},n.hash=function(t,n){return o.hash(n||a(t.put,r)||"")||t["#"]||e.text.random(9)};var a=JSON.stringify,s=":])([:"}(),n.hi=function(o){t.on("hi",o);var i=o.queue;o.queue=[],e.obj.map(i,function(t){n.say(t,o)})},n}var e=t("../type");o.hash=function(t){if("string"!=typeof t)return{err:1};var n=0;if(!t.length)return n;for(var o=0,e=t.length;o { + //const combo = shim.Buffer.concat([shim.Buffer.from(key, 'utf8'), salt || shim.random(8)]).toString('utf8') // old + const combo = key + (salt || shim.random(8)).toString('utf8'); // new + const hash = shim.Buffer.from(await sha256hash(combo), 'binary') + return await shim.subtle.importKey('raw', new Uint8Array(hash), 'AES-CBC', false, ['encrypt', 'decrypt']) + } + module.exports = importGen; + \ No newline at end of file diff --git a/sea/authenticate.js b/sea/authenticate.js index 00312a02..9a9f7b6f 100644 --- a/sea/authenticate.js +++ b/sea/authenticate.js @@ -1,13 +1,12 @@ - // TODO: BUG! `SEA` needs to be USED! - const Gun = (typeof window !== 'undefined' ? window : global).Gun || require('gun/gun') var SEA = require('./sea'); - var queryGunAliases = require('./query'); - var parseProps = require('./parse'); + var Gun = SEA.Gun; + const queryGunAliases = require('./query') + const parseProps = require('./parse') // This is internal User authentication func. - const authenticate = async (alias, pass, root) => { + const authenticate = async (alias, pass, gunRoot) => { // load all public keys associated with the username alias we want to log in with. - const aliases = (await queryGunAliases(alias, root)) + const aliases = (await queryGunAliases(alias, gunRoot)) .filter(({ pub, at: { put } = {} } = {}) => !!pub && !!put) // Got any? if (!aliases.length) { @@ -20,16 +19,16 @@ // attempt to PBKDF2 extend the password with the salt. (Verifying the signature gives us the plain text salt.) const auth = parseProps(at.put.auth) // NOTE: aliasquery uses `gun.get` which internally SEA.read verifies the data for us, so we do not need to re-verify it here. - // SEA.read(at.put.auth, pub).then(function(auth){ + // SEA.verify(at.put.auth, pub).then(function(auth){ try { - const proof = await SEA.proof(pass, auth.salt) + const proof = await SEA.work(pass, auth.s) const props = { pub, proof, at } // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force. /* MARK TO @mhelander : pub vs epub!??? */ const { salt } = auth - const sea = await SEA.dec(auth.auth, { pub, key: proof }) + const sea = await SEA.decrypt(auth.ek, proof) if (!sea) { err = 'Failed to decrypt secret!' return diff --git a/sea/buffer.js b/sea/buffer.js index e798a233..76411ee9 100644 --- a/sea/buffer.js +++ b/sea/buffer.js @@ -73,7 +73,5 @@ SafeBuffer.prototype.from = SafeBuffer.from SafeBuffer.prototype.toString = SeaArray.prototype.toString - const Buffer = SafeBuffer - if(typeof window !== 'undefined'){ window.Buffer = window.Buffer || Buffer } module.exports = SafeBuffer; \ No newline at end of file diff --git a/sea/decrypt.js b/sea/decrypt.js new file mode 100644 index 00000000..5357b41c --- /dev/null +++ b/sea/decrypt.js @@ -0,0 +1,26 @@ + + var SEA = require('./root'); + var shim = require('./shim'); + var S = require('./settings'); + var aescbckey = require('./aescbc'); + var parse = require('./parse'); + + SEA.decrypt = async (data, pair, cb) => { try { + const key = pair.epriv || pair; + const json = parse(data) + const ct = await aescbckey(key, shim.Buffer.from(json.s, 'utf8')) + .then((aes) => shim.subtle.decrypt({ // Keeping aesKey scope as private as possible... + name: 'AES-CBC', iv: new Uint8Array(shim.Buffer.from(json.iv, 'utf8')) + }, aes, new Uint8Array(shim.Buffer.from(json.ct, 'utf8')))) + const r = parse(new shim.TextDecoder('utf8').decode(ct)) + + if(cb){ cb(r) } + return r; + } catch(e) { + SEA.err = e; + if(cb){ cb() } + return; + }} + + module.exports = SEA.decrypt; + \ No newline at end of file diff --git a/sea/encrypt.js b/sea/encrypt.js new file mode 100644 index 00000000..dc93d805 --- /dev/null +++ b/sea/encrypt.js @@ -0,0 +1,30 @@ + + var SEA = require('./root'); + var shim = require('./shim'); + var S = require('./settings'); + var aescbckey = require('./aescbc'); + + SEA.encrypt = async (data, pair, cb) => { try { + const key = pair.epriv || pair; + const msg = JSON.stringify(data) + const rand = {s: shim.random(8), iv: shim.random(16)}; + const ct = await aescbckey(key, rand.s) + .then((aes) => shim.subtle.encrypt({ // Keeping the AES key scope as private as possible... + name: 'AES-CBC', iv: new Uint8Array(rand.iv) + }, aes, new shim.TextEncoder().encode(msg))) + const r = 'SEA'+JSON.stringify({ + ct: shim.Buffer.from(ct, 'binary').toString('utf8'), + iv: rand.iv.toString('utf8'), + s: rand.s.toString('utf8') + }); + + if(cb){ cb(r) } + return r; + } catch(e) { + SEA.err = e; + if(cb){ cb() } + return; + }} + + module.exports = SEA.encrypt; + \ No newline at end of file diff --git a/sea/https.js b/sea/https.js index bfc7087e..86d83150 100644 --- a/sea/https.js +++ b/sea/https.js @@ -1,13 +1,6 @@ - /* - Security, Encryption, and Authorization: SEA.js - */ - - // NECESSARY PRE-REQUISITE: http://gun.js.org/explainers/data/security.html - - /* THIS IS AN EARLY ALPHA!!! */ - - if(typeof window !== 'undefined'){ + var SEA = require('./root'); + if(SEA.window){ if(location.protocol.indexOf('s') < 0 && location.host.indexOf('localhost') < 0 && location.protocol.indexOf('file:') < 0){ diff --git a/sea/index.js b/sea/index.js index e8398f44..3b74efa5 100644 --- a/sea/index.js +++ b/sea/index.js @@ -1,6 +1,6 @@ const Gun = (typeof window !== 'undefined' ? window : global).Gun || require('gun/gun') - var SEA = require('./sea'); + const SEA = require('./sea') // After we have a GUN extension to make user registration/login easy, we then need to handle everything else. // We do this with a GUN adapter, we first listen to when a gun instance is created (and when its options change) @@ -9,12 +9,11 @@ at.sea = {own: {}}; var uuid = at.opt.uuid || Gun.state.lex; at.opt.uuid = function(cb){ // TODO: consider async/await and drop callback pattern... - if(!cb){ return } - var id = uuid(), pair = at.user && (at.user._).sea; - if(!pair){ return id } - SEA.sign(id, pair).then(function(sig){ - cb(null, id + '~' + sig); - }).catch(function(e){cb(e)}); + var id = uuid(), pub = at.user; + if(!pub || !(pub = at.user._.sea) || !(pub = pub.pub)){ return id } + id = id + '~' + pub; + if(cb){ cb(null, id) } + return id; } at.on('in', security, at); // now listen to all input data, acting as a firewall. at.on('out', signature, at); // and output listeners, to encrypt outgoing data. @@ -42,7 +41,7 @@ var to = this.to, vertex = (msg.gun._).put, c = 0, d; Gun.node.is(msg.put, function(val, key, node){ c++; // for each property on the node // TODO: consider async/await use here... - SEA.read(val, false).then(function(data){ c--; // false just extracts the plain data. + SEA.verify(val, false).then(function(data){ c--; // false just extracts the plain data. node[key] = val = data; // transform to plain value. if(d && !c && (c = -1)){ to.next(msg) } }); @@ -50,19 +49,6 @@ d = true; if(d && !c){ to.next(msg) } return; - /*var to = this.to, ctx = this.as; - var own = ctx.sea.own, soul = msg.get, c = 0; - var pub = own[soul] || soul.slice(4), vertex = (msg.gun._).put; - Gun.node.is(msg.put, function(val, key, node){ c++; // for each property on the node. - SEA.read(val, pub).then(function(data){ c--; - vertex[key] = node[key] = val = data; // verify signature and get plain value. - if(val && val['#'] && (key = Gun.val.rel.is(val))){ // if it is a relation / edge - if('alias/' !== soul.slice(0,6)){ own[key] = pub; } // associate the public key with a node if it is itself - } - if(!c && (c = -1)){ to.next(msg) } - }); - }); - if(!c){ to.next(msg) }*/ } // signature handles data output, it is a proxy to the security function. @@ -96,7 +82,7 @@ } if(msg.put){ // potentially parallel async operations!!! - var check = {}, on = Gun.on(), each = {}, u; + var check = {}, each = {}, u; each.node = function(node, soul){ if(Gun.obj.empty(node, '_')){ return check['node'+soul] = 0 } // ignore empty updates, don't reject them. Gun.obj.map(node, each.way, {soul: soul, node: node}); @@ -115,15 +101,6 @@ } each.any(val, key, node, soul, msg.user); return; return each.end({err: "No other data allowed!"}); - /*if(!(tmp = at.user)){ return } - if(soul.slice(4) === (tmp = tmp._).pub){ // not a special case, if we are logged in and have outbound data on us. - each.user(val, key, node, soul, { - pub: tmp.pub, priv: tmp.sea.priv, epub: tmp.sea.epub, epriv: tmp.sea.epriv - }); - } - if((tmp = sea.own[soul])){ // not special case, if we receive an update on an ID associated with a public key, then - each.own(val, key, node, soul, tmp); - }*/ }; each.alias = function(val, key, node, soul){ // Example: {_:#alias, alias/alice: {#alias/alice}} if(!val){ return each.end({err: "Data must exist!"}) } // data MUST exist @@ -142,8 +119,8 @@ } check['user'+soul+key] = 1; if(user && (user = user._) && user.sea && pub === user.pub){ - var id = Gun.text.random(3); - SEA.write(val, Gun.obj.to(user.sea, {pub: user.pub, epub: user.epub})).then(function(data){ var rel; + //var id = Gun.text.random(3); + SEA.sign(val, user.sea).then(function(data){ var rel; if(rel = Gun.val.rel.is(val)){ (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true; } @@ -151,42 +128,33 @@ check['user'+soul+key] = 0; each.end({ok: 1}); }); + // TODO: Handle error!!!! return; } // TODO: consider async/await and drop callback pattern... - SEA.read(val, pub).then(function(data){ var rel, tmp; + SEA.verify(val, pub).then(function(data){ var rel, tmp; if(u === data){ // make sure the signature matches the account it claims to be on. return each.end({err: "Unverified data."}); // reject any updates that are signed with a mismatched account. } if((rel = Gun.val.rel.is(data)) && (tmp = rel.split('~')) && 2 === tmp.length){ - SEA.verify(tmp[0], pub, tmp[1]).then(function(ok){ - if(!ok){ return each.end({err: "Signature did not match account."}) } + if(pub === tmp[1]){ (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true; - check['user'+soul+key] = 0; - each.end({ok: 1}); - }); - return; + } } check['user'+soul+key] = 0; each.end({ok: 1}); }); }; - each.any = function(val, key, node, soul, user){ var tmp; + each.any = function(val, key, node, soul, user){ var tmp, pub; if(!user || !(user = user._) || !(user = user.sea)){ - if(user = at.sea.own[soul]){ + if((tmp = soul.split('~')) && 2 == tmp.length){ check['any'+soul+key] = 1; - user = Gun.obj.map(user, function(a,b){ return b }); - // TODO: consider async/await and drop callback pattern... - SEA.read(val, user).then(function(data){ var rel; - if(!data){ return each.end({err: "Mismatched owner on '" + key + "'.", }) } + SEA.verify(val, (pub = tmp[1])).then(function(data){ var rel; + if(!data){ return each.end({err: "Mismatched owner on '" + key + "'."}) } if((rel = Gun.val.rel.is(data)) && (tmp = rel.split('~')) && 2 === tmp.length){ - SEA.verify(tmp[0], user, tmp[1]).then(function(ok){ - if(!ok){ return each.end({err: "Signature did not match account."}) } - (at.sea.own[rel] = at.sea.own[rel] || {})[user] = true; - check['any'+soul+key] = 0; - each.end({ok: 1}); - }); - return; + if(pub === tmp[1]){ + (at.sea.own[rel] = at.sea.own[rel] || {})[pub] = true; + } } check['any'+soul+key] = 0; each.end({ok: 1}); @@ -194,12 +162,6 @@ return; } check['any'+soul+key] = 1; - if((tmp = soul.split('~')) && 2 == tmp.length){ - setTimeout(function(){ // hacky idea, what would be better? - each.any(val, key, node, soul); - },1); - return; - } at.on('secure', function(msg){ this.off(); check['any'+soul+key] = 0; each.end(msg || {err: "Data cannot be modified."}); @@ -208,26 +170,26 @@ return; } if(!(tmp = soul.split('~')) || 2 !== tmp.length){ - each.end({err: "Soul is not signed at '" + key + "'."}); + each.end({err: "Soul is missing public key at '" + key + "'."}); return; } - var other = Gun.obj.map(at.sea.own[soul], function(v, p){ + var pub = tmp[1]; + if(pub !== user.pub){ + each.any(val, key, node, soul); + return; + } + /*var other = Gun.obj.map(at.sea.own[soul], function(v, p){ if(user.pub !== p){ return p } }); if(other){ each.any(val, key, node, soul); return; - } + }*/ check['any'+soul+key] = 1; - // TODO: consider async/await and drop callback pattern... - SEA.verify(tmp[0], user.pub, tmp[1]).then(function(ok){ - if(!ok){ return each.end({err: "Signature did not match account at '" + key + "'."}) } - (at.sea.own[soul] = at.sea.own[soul] || {})[user.pub] = true; - SEA.write(val, user).then(function(data){ - node[key] = data; - check['any'+soul+key] = 0; - each.end({ok: 1}); - }); + SEA.sign(val, user).then(function(data){ + node[key] = data; + check['any'+soul+key] = 0; + each.end({ok: 1}); }); } each.end = function(ctx){ // TODO: Can't you just switch this to each.end = cb? diff --git a/sea/indexed.js b/sea/indexed.js deleted file mode 100644 index 6daba8a1..00000000 --- a/sea/indexed.js +++ /dev/null @@ -1,78 +0,0 @@ - - // This is safe class to operate with IndexedDB data - all methods are Promise - function EasyIndexedDB(objectStoreName, dbName = 'GunDB', dbVersion = 1) { - // Private internals, including constructor props - const runTransaction = (fn_) => new Promise((resolve, reject) => { - const open = indexedDB.open(dbName, dbVersion) // Open (or create) the DB - open.onerror = (e) => { - reject(new Error('IndexedDB error:', e)) - } - open.onupgradeneeded = () => { - const db = open.result // Create the schema; props === current version - db.createObjectStore(objectStoreName, { keyPath: 'id' }) - } - let result - open.onsuccess = () => { // Start a new transaction - const db = open.result - const tx = db.transaction(objectStoreName, 'readwrite') - const store = tx.objectStore(objectStoreName) - tx.oncomplete = () => { - db.close() // Close the db when the transaction is done - resolve(result) // Resolves result returned by action function fn_ - } - result = fn_(store) - } - }) - - Object.assign(this, { - async wipe() { // Wipe IndexedDB completedy! - return runTransaction((store) => { - const act = store.clear() - act.onsuccess = () => {} - }) - }, - async put(id, props) { - const data = Object.assign({}, props, { id }) - return runTransaction((store) => { store.put(data) }) - }, - async get(id, prop) { - return runTransaction((store) => new Promise((resolve) => { - const getData = store.get(id) - getData.onsuccess = () => { - const { result = {} } = getData - resolve(result[prop]) - } - })) - } - }) - } - - let indexedDB - let funcsSetter - - if (typeof __webpack_require__ === 'function' || typeof window !== 'undefined') { - funcsSetter = () => window - indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB || window.shimIndexedDB - } else { - funcsSetter = () => { - const { TextEncoder, TextDecoder } = require('text-encoding') - // Let's have Storage for NodeJS / testing - const sessionStorage = new require('node-localstorage').LocalStorage('.sessionStorage') - const localStorage = new require('node-localstorage').LocalStorage('.localStorage') - return { TextEncoder, TextDecoder, sessionStorage, localStorage } - } - indexedDB = require('fake-indexeddb') - } - - const { TextEncoder, TextDecoder, sessionStorage, localStorage } = funcsSetter() - - if (typeof __webpack_require__ !== 'function' && typeof global !== 'undefined') { - global.sessionStorage = sessionStorage - global.localStorage = localStorage - } - - const seaIndexedDb = new EasyIndexedDB('SEA', 'GunDB', 1) // This is IndexedDB used by Gun SEA - EasyIndexedDB.scope = seaIndexedDb; // for now. This module should not export an instance of itself! - - module.exports = EasyIndexedDB; - \ No newline at end of file diff --git a/sea/leave.js b/sea/leave.js index 040f7878..c0cb03d6 100644 --- a/sea/leave.js +++ b/sea/leave.js @@ -1,22 +1,21 @@ - var authPersist = require('./persist'); - var authsettings = require('./settings'); - var seaIndexedDb = require('./indexed').scope; - var seaIndexedDb = require('./indexed').scope; + const authPersist = require('./persist') + const authsettings = require('./settings') + //const { scope: seaIndexedDb } = require('./indexed') // This internal func executes logout actions - const authLeave = async (root, alias = root._.user._.alias) => { - var user = root._.user._ || {}; + const authLeave = async (gunRoot, alias = gunRoot._.user._.alias) => { + var user = gunRoot._.user._ || {}; [ 'get', 'soul', 'ack', 'put', 'is', 'alias', 'pub', 'epub', 'sea' ].map((key) => delete user[key]) if(user.gun){ delete user.gun.is; } // Let's use default - root.user(); + gunRoot.user(); // Removes persisted authentication & CryptoKeys try { await authPersist({ alias }) } catch (e) {} //eslint-disable-line no-empty return { ok: 0 } } - module.exports = authLeave; + module.exports = authLeave \ No newline at end of file diff --git a/sea/login.js b/sea/login.js index 1a391258..877378e5 100644 --- a/sea/login.js +++ b/sea/login.js @@ -1,26 +1,33 @@ - var authPersist = require('./persist'); + const authPersist = require('./persist') // This internal func finalizes User authentication - const finalizeLogin = async (alias, key, root, opts) => { - const { user } = root._ + const finalizeLogin = async (alias, key, gunRoot, opts) => { + const { user } = gunRoot._ // add our credentials in-memory only to our root gun instance user._ = key.at.gun._ // so that way we can use the credentials to encrypt/decrypt data - user._.is = user.is = {} // that is input/output through gun (see below) const { pub, priv, epub, epriv } = key + user._.is = user.is = {alias: alias, pub: pub}; Object.assign(user._, { alias, pub, epub, sea: { pub, priv, epub, epriv } }) //console.log("authorized", user._); // persist authentication - await authPersist(user._, key.proof, opts) - // emit an auth event, useful for page redirects and stuff. + //await authPersist(user._, key.proof, opts) // temporarily disabled + // emit an auth event, useful for page redirects and stuff. + if(typeof window !== 'undefined'){ + var tmp = window.sessionStorage; + if(tmp && gunRoot._.opt.remember){ + window.sessionStorage.alias = alias; + window.sessionStorage.tmp = key; + } + } try { - root._.on('auth', user._) + gunRoot._.on('auth', user._) } catch (e) { console.log('Your \'auth\' callback crashed with:', e) } // returns success with the user data credentials. return user._ } - module.exports = finalizeLogin; + module.exports = finalizeLogin \ No newline at end of file diff --git a/sea/pair.js b/sea/pair.js new file mode 100644 index 00000000..b603f791 --- /dev/null +++ b/sea/pair.js @@ -0,0 +1,52 @@ + + var SEA = require('./root'); + var shim = require('./shim'); + var S = require('./settings'); + var Buff = (typeof Buffer !== 'undefined')? Buffer : shim.Buffer; + + //SEA.pair = async (data, proof, cb) => { try { + SEA.pair = async (cb) => { try { + + const ecdhSubtle = shim.ossl || shim.subtle + // First: ECDSA keys for signing/verifying... + const { pub, priv } = await shim.subtle.generateKey(S.ecdsa.pair, true, [ 'sign', 'verify' ]) + .then(async (keys) => { + // privateKey scope doesn't leak out from here! + const { d: priv } = await shim.subtle.exportKey('jwk', keys.privateKey) + const { x, y } = await shim.subtle.exportKey('jwk', keys.publicKey) + //const pub = Buff.from([ x, y ].join(':')).toString('base64') // old + const pub = x+'.'+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 { pub, priv } + }) + + // To include PGPv4 kind of keyId: + // const pubId = await SEA.keyid(keys.pub) + // Next: ECDH keys for encryption/decryption... + + const { epub, epriv } = await ecdhSubtle.generateKey(S.ecdh, true, ['deriveKey']) + .then(async (keys) => { + // privateKey scope doesn't leak out from here! + const { d: epriv } = await ecdhSubtle.exportKey('jwk', keys.privateKey) + const { x, y } = await ecdhSubtle.exportKey('jwk', keys.publicKey) + //const epub = Buff.from([ ex, ey ].join(':')).toString('base64') // old + const epub = x+'.'+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 { epub, epriv } + }) + + const r = { pub, priv, /* pubId, */ epub, epriv } + if(cb){ cb(r) } + return r; + } catch(e) { + SEA.err = e; + if(cb){ cb() } + return; + }} + + module.exports = SEA.pair; + \ No newline at end of file diff --git a/sea/parse.js b/sea/parse.js index a4599367..7372bc1f 100644 --- a/sea/parse.js +++ b/sea/parse.js @@ -1,9 +1,11 @@ - const parseProps = (props) => { + module.exports = (props) => { try { + if(props.slice && 'SEA{' === props.slice(0,4)){ + props = props.slice(3); + } return props.slice ? JSON.parse(props) : props } catch (e) {} //eslint-disable-line no-empty return props } - module.exports = parseProps; \ No newline at end of file diff --git a/sea/persist.js b/sea/persist.js index 6ddb73a1..c43587e1 100644 --- a/sea/persist.js +++ b/sea/persist.js @@ -1,8 +1,8 @@ const Gun = (typeof window !== 'undefined' ? window : global).Gun || require('gun/gun') - var Buffer = require('./buffer'); - var authsettings = require('./settings'); - var updateStorage = require('./update'); + const Buffer = require('./buffer') + const authsettings = require('./settings') + const updateStorage = require('./update') // This internal func persists User authentication if so configured const authPersist = async (user, proof, opts) => { // opts = { pin: 'string' } @@ -33,5 +33,5 @@ } return await updateStorage()({ alias: 'delete' }) } - module.exports = authPersist; + module.exports = authPersist \ No newline at end of file diff --git a/sea/query.js b/sea/query.js index 34d0b053..ee43a568 100644 --- a/sea/query.js +++ b/sea/query.js @@ -1,10 +1,11 @@ - const Gun = (typeof window !== 'undefined' ? window : global).Gun || require('gun/gun') + var SEA = require('./sea'); + var Gun = SEA.Gun; // This is internal func queries public key(s) for alias. - const queryGunAliases = (alias, root) => new Promise((resolve, reject) => { + const queryGunAliases = (alias, gunRoot) => new Promise((resolve, reject) => { // load all public keys associated with the username alias we want to log in with. - root.get(`alias/${alias}`).get((rat, rev) => { - rev.off() + gunRoot.get(`alias/${alias}`).get((rat, rev) => { + rev.off(); if (!rat.put) { // if no user, don't do anything. const err = 'No user!' @@ -12,7 +13,7 @@ return reject({ err }) } // then figuring out all possible candidates having matching username - let aliases = [] + const aliases = [] let c = 0 // TODO: how about having real chainable map without callback ? Gun.obj.map(rat.put, (at, pub) => { @@ -22,7 +23,7 @@ } ++c // grab the account associated with this public key. - root.get(pub).get((at, ev) => { + gunRoot.get(pub).get((at, ev) => { pub = pub.slice(4) ev.off() --c @@ -39,5 +40,5 @@ } }) }) - module.exports = queryGunAliases; + module.exports = queryGunAliases \ No newline at end of file diff --git a/sea/recall.js b/sea/recall.js index 258740c2..97a7489d 100644 --- a/sea/recall.js +++ b/sea/recall.js @@ -1,14 +1,18 @@ - // TODO: BUG! `SEA` needs to be USED! + // TODO: BUG! `SEA` needs to be USEd! const Gun = (typeof window !== 'undefined' ? window : global).Gun || require('gun/gun') - var Buffer = require('./buffer'); - var authsettings = require('./settings'); - var seaIndexedDb = require('./indexed').scope; - var queryGunAliases = require('./query'); - var parseProps = require('./parse'); - var updateStorage = require('./update'); + + const Buffer = require('./buffer') + const authsettings = require('./settings') + //const { scope: seaIndexedDb } = require('./indexed') + const queryGunAliases = require('./query') + const parseProps = require('./parse') + const updateStorage = require('./update') + const SEA = require('./sea') + const finalizeLogin = require('./login') + // This internal func recalls persisted User authentication if so configured - const authRecall = async (root, authprops) => { + const authRecall = async (gunRoot, authprops) => { // window.sessionStorage only holds signed { alias, pin } !!! const remember = authprops || sessionStorage.getItem('remember') const { alias = sessionStorage.getItem('user'), pin: pIn } = authprops || {} // @mhelander what is pIn? @@ -31,13 +35,13 @@ } } const readAndDecrypt = async (data, pub, key) => - parseProps(await SEA.dec(await SEA.read(data, pub), key)) + parseProps(await SEA.decrypt(await SEA.verify(data, pub), key)) // Already authenticated? - if (root._.user - && Gun.obj.has(root._.user._, 'pub') - && Gun.obj.has(root._.user._, 'sea')) { - return root._.user._ // Yes, we're done here. + if (gunRoot._.user + && Gun.obj.has(gunRoot._.user._, 'pub') + && Gun.obj.has(gunRoot._.user._, 'sea')) { + return gunRoot._.user._ // Yes, we're done here. } // No, got persisted 'alias'? if (!alias) { @@ -51,7 +55,7 @@ } } // Yes, let's get (all?) matching aliases - const aliases = (await queryGunAliases(alias, root)) + const aliases = (await queryGunAliases(alias, gunRoot)) .filter(({ pub } = {}) => !!pub) // Got any? if (!aliases.length) { @@ -64,7 +68,7 @@ .all(aliases.filter(({ at: { put } = {} }) => !!put) .map(async ({ at, pub }) => { const readStorageData = async (args) => { - const props = args || parseProps(await SEA.read(remember, pub, true)) + const props = args || parseProps(await SEA.verify(remember, pub, true)) let { pin, alias: aLias } = props const data = (!pin && alias === aLias) @@ -94,7 +98,7 @@ try { // auth parsing or decryption fails or returns empty - silently done const { auth } = at.put.auth - const sea = await SEA.dec(auth, proof) + const sea = await SEA.decrypt(auth, proof) if (!sea) { err = 'Failed to decrypt private key!' return @@ -124,12 +128,12 @@ const pinProp = pIN && { pin: Buffer.from(pIN, 'base64').toString('utf8') } - return await finalizeLogin(alias, user, root, pinProp) + return await finalizeLogin(alias, user, gunRoot, pinProp) } catch (e) { // TODO: right log message ? Gun.log('Failed to finalize login with new password!') const { err = '' } = e || {} throw { err: `Finalizing new password login failed! Reason: ${err}` } } } - module.exports = authRecall; + module.exports = authRecall \ No newline at end of file diff --git a/sea/remember.js b/sea/remember.js deleted file mode 100644 index aa6dc758..00000000 --- a/sea/remember.js +++ /dev/null @@ -1,46 +0,0 @@ - - var Buffer = require('./buffer'); - var sha256hash = require('./sha256'); - var wc = require('./webcrypto'); - var subtle = wc.subtle; - var seaIndexedDb = require('./indexed').scope; - var settings = require('./settings'); - var authsettings = settings.recall; - const makeKey = async (p, s) => { - const ps = Buffer.concat([Buffer.from(p, 'utf8'), s]).toString('utf8') - return Buffer.from(await sha256hash(ps), 'binary') - } - // This recalls Web Cryptography API CryptoKeys from IndexedDB or creates & stores - // {pub, key}|proof, salt, optional:['sign'] - const recallCryptoKey = async (p, s, o = [ 'encrypt', 'decrypt' ]) => { - const importKey = async (key) => { - const hashedKey = await makeKey((Gun.obj.has(key, 'key') && key.key) || key, s || getRandomBytes(8)) - return await subtle.importKey( - 'raw', - new Uint8Array(hashedKey), - 'AES-CBC', - false, - o - ) - } - - if (authsettings.validity && typeof window !== 'undefined' - && Gun.obj.has(p, 'pub') && Gun.obj.has(p, 'key')) { - const { pub: id } = p - const importAndStoreKey = async () => { - const key = await importKey(p) - await seaIndexedDb.put(id, { key }) - return key - } - if (Gun.obj.has(p, 'set')) { - return importAndStoreKey() // proof update so overwrite - } - const aesKey = await seaIndexedDb.get(id, 'key') - return aesKey ? aesKey : importAndStoreKey() - } - - // No secure store usage - return importKey(p) - } - module.exports = recallCryptoKey; - \ No newline at end of file diff --git a/sea/root.js b/sea/root.js new file mode 100644 index 00000000..23cd2bcc --- /dev/null +++ b/sea/root.js @@ -0,0 +1,10 @@ + + // Security, Encryption, and Authorization: SEA.js + // MANDATORY READING: http://gun.js.org/explainers/data/security.html + // THIS IS AN EARLY ALPHA! + + function SEA(){} + if(typeof window !== "undefined"){ SEA.window = window } + + module.exports = SEA; + \ No newline at end of file diff --git a/sea/sea.js b/sea/sea.js index 64f74116..dc1413b0 100644 --- a/sea/sea.js +++ b/sea/sea.js @@ -1,255 +1,102 @@ - const Gun = (typeof window !== 'undefined' ? window : global).Gun || require('gun/gun') - - var wc = require('./webcrypto'); - var subtle = wc.subtle; - var getRandomBytes = wc.random; - var EasyIndexedDB = require('./indexed'); - var SafeBuffer = require('./buffer'); + // Old Code... + const { + crypto, + subtle, + ossl, + random: getRandomBytes, + TextEncoder, + TextDecoder + } = require('./shim') + const EasyIndexedDB = require('./indexed') + const Buffer = require('./buffer') var settings = require('./settings'); - var pbKdf2 = settings.pbkdf2; - var ecdsaKeyProps = settings.ecdsa.pair; - var ecdhKeyProps = settings.ecdh; - var keysToEcdsaJwk = settings.jwk; - var ecdsaSignProps = settings.ecdsa.sign; - var sha256hash = require('./sha256'); - var recallCryptoKey = require('./remember'); - var parseProps = require('./parse'); - + const { + pbkdf2: pbKdf2, + ecdsa: { pair: ecdsaKeyProps, sign: ecdsaSignProps }, + ecdh: ecdhKeyProps, + jwk: keysToEcdsaJwk + } = require('./settings') + const sha1hash = require('./sha1') + const sha256hash = require('./sha256') + const recallCryptoKey = require('./remember') + const parseProps = require('./parse') + // Practical examples about usage found from ./test/common.js - const SEA = { - // This is easy way to use IndexedDB, all methods are Promises - EasyIndexedDB, - // This is Buffer used in SEA and usable from Gun/SEA application also. - // For documentation see https://nodejs.org/api/buffer.html - Buffer: SafeBuffer, - // These SEA functions support now ony Promises or - // async/await (compatible) code, use those like Promises. - // - // Creates a wrapper library around Web Crypto API - // for various AES, ECDSA, PBKDF2 functions we called above. - async proof(pass, salt) { - try { - if (typeof window !== 'undefined') { - // For browser subtle works fine - const key = await subtle.importKey( - 'raw', new TextEncoder().encode(pass), { name: 'PBKDF2' }, false, ['deriveBits'] - ) - const result = await subtle.deriveBits({ - name: 'PBKDF2', - iterations: pbKdf2.iter, - salt: new TextEncoder().encode(salt), - hash: pbKdf2.hash, - }, key, pbKdf2.ks * 8) - pass = getRandomBytes(pass.length) // Erase passphrase for app - return Buffer.from(result, 'binary').toString('base64') - } - // For NodeJS crypto.pkdf2 rocks - const hash = crypto.pbkdf2Sync( - pass, - new TextEncoder().encode(salt), - pbKdf2.iter, - pbKdf2.ks, - pbKdf2.hash.replace('-', '').toLowerCase() - ) - pass = getRandomBytes(pass.length) // Erase passphrase for app - return hash && hash.toString('base64') - } catch (e) { - Gun.log(e) - throw e - } - }, - // Calculate public key KeyID aka PGPv4 (result: 8 bytes as hex string) - async keyid(pub) { - try { - // base64('base64(x):base64(y)') => Buffer(xy) - const pb = Buffer.concat( - Buffer.from(pub, 'base64').toString('utf8').split(':') - .map((t) => Buffer.from(t, 'base64')) - ) - // id is PGPv4 compliant raw key - const id = Buffer.concat([ - Buffer.from([0x99, pb.length / 0x100, pb.length % 0x100]), pb - ]) - const sha1 = await sha1hash(id) - const hash = Buffer.from(sha1, 'binary') - return hash.toString('hex', hash.length - 8) // 16-bit ID as hex - } catch (e) { - Gun.log(e) - throw e - } - }, - async pair() { - try { - const ecdhSubtle = wc.ossl || subtle - // First: ECDSA keys for signing/verifying... - const { pub, priv } = await subtle.generateKey(ecdsaKeyProps, true, [ 'sign', 'verify' ]) - .then(async ({ publicKey, privateKey }) => { - const { d: priv } = await subtle.exportKey('jwk', privateKey) - // privateKey scope doesn't leak out from here! - const { x, y } = await subtle.exportKey('jwk', publicKey) - const pub = Buffer.from([ x, y ].join(':')).toString('base64') - return { pub, priv } - }) - // To include PGPv4 kind of keyId: - // const pubId = await SEA.keyid(keys.pub) - // Next: ECDH keys for encryption/decryption... - const { epub, epriv } = await ecdhSubtle.generateKey(ecdhKeyProps, true, ['deriveKey']) - .then(async ({ publicKey, privateKey }) => { - // privateKey scope doesn't leak out from here! - const { d: epriv } = await ecdhSubtle.exportKey('jwk', privateKey) - const { x, y } = await ecdhSubtle.exportKey('jwk', publicKey) - const epub = Buffer.from([ x, y ].join(':')).toString('base64') - return { epub, epriv } - }) - return { pub, priv, /* pubId, */ epub, epriv } - } catch (e) { - Gun.log(e) - throw e - } - }, - // Derive shared secret from other's pub and my epub/epriv - async derive(pub, { epub, epriv }) { - try { - const { importKey, deriveKey, exportKey } = subtleossl || subtle - const keystoecdhjwk = (pub, priv) => { - const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') - const jwk = priv ? { d: priv, key_ops: ['decrypt'] } : { key_ops: ['encrypt'] } - return Object.assign(jwk, { - kty: 'EC', - crv: 'P-256', - ext: false, - x, - y - }) - } - const pubLic = await importKey('jwk', keystoecdhjwk(pub), ecdhKeyProps, false, ['deriveKey']) - const props = Object.assign({}, ecdhKeyProps, { public: pubLic }) - const derived = await importKey('jwk', keystoecdhjwk(epub, epriv), ecdhKeyProps, false, ['deriveKey']) - .then(async (privKey) => { - // privateKey scope doesn't leak out from here! - const derivedKey = await deriveKey(props, privKey, { name: 'AES-CBC', length: 256 }, true, [ 'encrypt', 'decrypt' ]) - return exportKey('jwk', derivedKey).then(({ k }) => k) - }) - return derived - } catch (e) { - Gun.log(e) - throw e - } - }, - async sign(data, { pub, priv }) { - try { - const jwk = keysToEcdsaJwk(pub, priv) - const hash = await sha256hash(data) - // privateKey scope doesn't leak out from here! - const binSig = await subtle.importKey(...jwk, ecdsaKeyProps, false, ['sign']) - .then((privKey) => subtle.sign(ecdsaSignProps, privKey, new Uint8Array(hash))) - return Buffer.from(binSig, 'binary').toString('base64') - } catch (e) { - Gun.log(e) - throw e - } - }, - async verify(data, pub, sig) { - try { - const jwk = keysToEcdsaJwk(pub) - const key = await subtle.importKey(...jwk, ecdsaKeyProps, false, ['verify']) - const hash = await sha256hash(data) - const ss = new Uint8Array(Buffer.from(sig, 'base64')) - return await subtle.verify(ecdsaSignProps, key, ss, new Uint8Array(hash)) - } catch (e) { - Gun.log(e) - throw e - } - }, - async enc(data, priv) { - try { - const rands = { s: getRandomBytes(8), iv: getRandomBytes(16) } - const r = Object.keys(rands) - .reduce((obj, key) => Object.assign(obj, { [key]: rands[key].toString('hex') }), {}) - try { - data = (data.slice && data) || JSON.stringify(data) - } catch(e) {} //eslint-disable-line no-empty - const ct = await recallCryptoKey(priv, rands.s) - .then((aesKey) => subtle.encrypt({ // Keeping aesKey scope as private as possible... - name: 'AES-CBC', iv: new Uint8Array(rands.iv) - }, aesKey, new TextEncoder().encode(data))) - Object.assign(r, { ct: Buffer.from(ct, 'binary').toString('base64') }) - return JSON.stringify(r) - } catch (e) { - Gun.log(e) - throw e - } - }, - async dec(data, priv) { - try { - const { s, iv, ct } = parseProps(data) - const mm = { s, iv, ct } - const rands = [ 'iv', 's' ].reduce((obj, key) => Object.assign(obj, { - [key]: new Uint8Array(Buffer.from(mm[key], 'hex')) - }), {}) - const binCt = await recallCryptoKey(priv, rands.s) - .then((aesKey) => subtle.decrypt({ // Keeping aesKey scope as private as possible... - name: 'AES-CBC', iv: rands.iv - }, aesKey, new Uint8Array(Buffer.from(mm.ct, 'base64')))) - return parseProps(new TextDecoder('utf8').decode(binCt)) - } catch (e) { - Gun.log(e) - throw e - } - }, - async write(data, keys) { - try { - // TODO: something's bugging double 'SEA[]' treatment to mm... - let m = data - if (m && m.slice && 'SEA[' === m.slice(0, 4)) { - return m - } - if (data && data.slice) { - // Needs to remove previous signature envelope - while ('SEA[' === m.slice(0, 4)) { - try { - m = JSON.parse(m.slice(3))[0] - } catch (e){ - break - } - } - } - m = (m && m.slice) ? m : JSON.stringify(m) - const signature = await SEA.sign(m, keys) - return `SEA${JSON.stringify([ m, signature ])}` - } catch (e) { - Gun.log(e) - throw e - } - }, - async read(data, pub) { - try { - let d - if (!data) { - return false === pub ? data : undefined - } - if (!data.slice || 'SEA[' !== data.slice(0, 4)) { - return false === pub ? data : undefined - } - let m = parseProps(data.slice(3)) || '' - d = parseProps(m[0]) - if (false === pub) { - return d - } - return (await SEA.verify(m[0], pub, m[1])) ? d : undefined - } catch (e) { - Gun.log(e) - throw e - } + const SEA = require('./root'); + SEA.work = require('./work'); + SEA.sign = require('./sign'); + SEA.verify = require('./verify'); + SEA.encrypt = require('./encrypt'); + SEA.decrypt = require('./decrypt'); + + SEA.random = getRandomBytes; + + // This is easy way to use IndexedDB, all methods are Promises + // Note: Not all SEA interfaces have to support this. + SEA.EasyIndexedDB = EasyIndexedDB; + + // This is Buffer used in SEA and usable from Gun/SEA application also. + // For documentation see https://nodejs.org/api/buffer.html + SEA.Buffer = Buffer; + + // These SEA functions support now ony Promises or + // async/await (compatible) code, use those like Promises. + // + // Creates a wrapper library around Web Crypto API + // for various AES, ECDSA, PBKDF2 functions we called above. + // Calculate public key KeyID aka PGPv4 (result: 8 bytes as hex string) + SEA.keyid = async (pub) => { + try { + // base64('base64(x):base64(y)') => Buffer(xy) + const pb = Buffer.concat( + Buffer.from(pub, 'base64').toString('utf8').split(':') + .map((t) => Buffer.from(t, 'base64')) + ) + // id is PGPv4 compliant raw key + const id = Buffer.concat([ + Buffer.from([0x99, pb.length / 0x100, pb.length % 0x100]), pb + ]) + const sha1 = await sha1hash(id) + const hash = Buffer.from(sha1, 'binary') + return hash.toString('hex', hash.length - 8) // 16-bit ID as hex + } catch (e) { + console.log(e) + throw e } } - // Usage of the SEA object changed! Now use like this: - // const gun = new Gun() - // const SEA = gun.SEA() - //Gun.SEA = () => SEA - Gun.SEA = SEA - + // Derive shared secret from other's pub and my epub/epriv + SEA.derive = async (pub, { epub, epriv }) => { + try { + const ecdhSubtle = ossl || subtle + const keysToEcdhJwk = (pub, d) => { // d === priv + const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') + const jwk = d ? { d } : {} + return [ // Use with spread returned value... + 'jwk', + { ...jwk, x, y, kty: 'EC', crv: 'P-256', ext: true }, + ecdhKeyProps + ] + } + const pubKeyData = keysToEcdhJwk(pub) + const props = { + ...ecdhKeyProps, + public: await ecdhSubtle.importKey(...pubKeyData, true, []) + } + const privKeyData = keysToEcdhJwk(epub, epriv) + const derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveKey']) + .then(async (privKey) => { + // privateKey scope doesn't leak out from here! + const derivedKey = await ecdhSubtle.deriveKey(props, privKey, { name: 'AES-CBC', length: 256 }, true, [ 'encrypt', 'decrypt' ]) + return ecdhSubtle.exportKey('jwk', derivedKey).then(({ k }) => k) + }) + return derived + } catch (e) { + console.log(e) + throw e + } + } + // all done! // Obviously it is missing MANY necessary features. This is only an alpha release. // Please experiment with it, audit what I've done so far, and complain about what needs to be added. @@ -259,8 +106,9 @@ // But all other behavior needs to be equally easy, like opinionated ways of // Adding friends (trusted public keys), sending private messages, etc. // Cheers! Tell me what you think. + var Gun = (SEA.window||{}).Gun || require('./gun'); + Gun.SEA = SEA; + SEA.Gun = Gun; - try { - module.exports = SEA - } catch (e) {} //eslint-disable-line no-empty + module.exports = SEA \ No newline at end of file diff --git a/sea/settings.js b/sea/settings.js index d908b95f..25c6aaa6 100644 --- a/sea/settings.js +++ b/sea/settings.js @@ -1,8 +1,8 @@ - var Buffer = require('./buffer'); - var settings = {}; + const Buffer = require('./buffer') + const settings = {} // Encryption parameters - const pbKdf2 = { hash: 'SHA-256', iter: 50000, ks: 64 } + const pbkdf2 = { hash: 'SHA-256', iter: 100000, ks: 64 } const ecdsaSignProps = { name: 'ECDSA', hash: { name: 'SHA-256' } } const ecdsaKeyProps = { name: 'ECDSA', namedCurve: 'P-256' } @@ -16,21 +16,24 @@ // These are used to persist user's authentication "session" const authsettings = Object.assign({}, _initial_authsettings) // This creates Web Cryptography API compliant JWK for sign/verify purposes - const keysToEcdsaJwk = (pub, priv) => { - const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') - const jwk = priv ? { d: priv, key_ops: ['sign'] } : { key_ops: ['verify'] } - return [ // Use with spread returned value... - 'jwk', - Object.assign(jwk, { x, y, kty: 'EC', crv: 'P-256', ext: false }) - ] + const keysToEcdsaJwk = (pub, d) => { // d === priv + //const [ x, y ] = Buffer.from(pub, 'base64').toString('utf8').split(':') // old + const [ x, y ] = pub.split('.') // new + var jwk = { kty: "EC", crv: "P-256", x: x, y: y, ext: true } + jwk.key_ops = d ? ['sign'] : ['verify']; + if(d){ jwk.d = d } + return jwk; } - settings.pbkdf2 = pbKdf2; - settings.ecdsa = {}; - settings.ecdsa.pair = ecdsaKeyProps; - settings.ecdsa.sign = ecdsaSignProps; - settings.ecdh = ecdhKeyProps; - settings.jwk = keysToEcdsaJwk; - settings.recall = authsettings; - module.exports = settings; + Object.assign(settings, { + pbkdf2, + ecdsa: { + pair: ecdsaKeyProps, + sign: ecdsaSignProps + }, + ecdh: ecdhKeyProps, + jwk: keysToEcdsaJwk, + recall: authsettings + }) + module.exports = settings \ No newline at end of file diff --git a/sea/sha1.js b/sea/sha1.js index cc2c35a0..d6e0e7f5 100644 --- a/sea/sha1.js +++ b/sea/sha1.js @@ -1,5 +1,6 @@ // This internal func returns SHA-1 hashed data for KeyID generation - const sha1hash = (b) => (subtleossl || subtle).digest('SHA-1', new ArrayBuffer(b)) - module.exports = sha1hash; + const { subtle, ossl = subtle } = require('./shim') + const sha1hash = (b) => ossl.digest({name: 'SHA-1'}, new ArrayBuffer(b)) + module.exports = sha1hash \ No newline at end of file diff --git a/sea/sha256.js b/sea/sha256.js index 783182ec..43f97dc9 100644 --- a/sea/sha256.js +++ b/sea/sha256.js @@ -1,17 +1,15 @@ - var wc = require('./webcrypto'); - var subtle = wc.subtle; - var getRandomBytes = wc.random; - var Buffer = require('./buffer'); - var parseProps = require('./parse'); - var settings = require('./settings'); - var pbKdf2 = settings.pbkdf2; + const { + subtle, ossl = subtle, random: getRandomBytes, TextEncoder, TextDecoder + } = require('./shim') + const Buffer = require('./buffer') + const parse = require('./parse') + const { pbkdf2 } = require('./settings') // This internal func returns SHA-256 hashed data for signing const sha256hash = async (mm) => { - const hashSubtle = wc.ossl || subtle - const m = parseProps(mm) - const hash = await hashSubtle.digest(pbKdf2.hash, new TextEncoder().encode(m)) + const m = parse(mm) + const hash = await ossl.digest({name: pbkdf2.hash}, new TextEncoder().encode(m)) return Buffer.from(hash) } - module.exports = sha256hash; + module.exports = sha256hash \ No newline at end of file diff --git a/sea/shim.js b/sea/shim.js new file mode 100644 index 00000000..92fcb349 --- /dev/null +++ b/sea/shim.js @@ -0,0 +1,38 @@ + + const Buffer = require('./buffer') + const api = {Buffer: Buffer} + + if (typeof __webpack_require__ === 'function' || typeof window !== 'undefined') { + const { msCrypto, crypto = msCrypto } = window // STD or M$ + const { webkitSubtle, subtle = webkitSubtle } = crypto // STD or iSafari + const { TextEncoder, TextDecoder } = window + Object.assign(api, { + crypto, + subtle, + TextEncoder, + TextDecoder, + random: (len) => Buffer.from(crypto.getRandomValues(new Uint8Array(Buffer.alloc(len)))) + }) + } else { + try{ + const crypto = require('crypto') + //const WebCrypto = require('node-webcrypto-ossl') + //const { subtle: ossl } = new WebCrypto({directory: 'key_storage'}) // ECDH + const { subtle } = require('@trust/webcrypto') // All but ECDH + const { TextEncoder, TextDecoder } = require('text-encoding') + Object.assign(api, { + crypto, + subtle, + //ossl, + TextEncoder, + TextDecoder, + random: (len) => Buffer.from(crypto.randomBytes(len)) + }) + }catch(e){ + console.log("@trust/webcrypto and text-encoding are not included by default, you must add it to your package.json!"); + TRUST_WEBCRYPTO_OR_TEXT_ENCODING_NOT_INSTALLED; + } + } + + module.exports = api + \ No newline at end of file diff --git a/sea/sign.js b/sea/sign.js new file mode 100644 index 00000000..4044b3e8 --- /dev/null +++ b/sea/sign.js @@ -0,0 +1,35 @@ + + var SEA = require('./root'); + var shim = require('./shim'); + var S = require('./settings'); + var sha256hash = require('./sha256'); + + SEA.sign = async (data, pair, cb) => { try { + if(data.slice + && 'SEA{' === data.slice(0,4) + && '"m":' === data.slice(4,8)){ + // TODO: This would prevent pair2 signing pair1's signature. + // So we may want to change this in the future. + // but for now, we want to prevent duplicate double signature. + if(cb){ cb(data) } + return data; + } + const pub = pair.pub + const priv = pair.priv + const jwk = S.jwk(pub, priv) + const msg = JSON.stringify(data) + const hash = await sha256hash(msg) + const sig = await shim.subtle.importKey('jwk', jwk, S.ecdsa.pair, false, ['sign']) + .then((key) => shim.subtle.sign(S.ecdsa.sign, key, new Uint8Array(hash))) // privateKey scope doesn't leak out from here! + const r = 'SEA'+JSON.stringify({m: msg, s: shim.Buffer.from(sig, 'binary').toString('utf8')}); + + if(cb){ cb(r) } + return r; + } catch(e) { + SEA.err = e; + if(cb){ cb() } + return; + }} + + module.exports = SEA.sign; + \ No newline at end of file diff --git a/sea/update.js b/sea/update.js index 9b319cdd..fccac4a7 100644 --- a/sea/update.js +++ b/sea/update.js @@ -1,8 +1,9 @@ // TODO: BUG! `SEA` needs to be USED! const Gun = (typeof window !== 'undefined' ? window : global).Gun || require('gun/gun') - var authsettings = require('./settings'); - var seaIndexedDb = require('./indexed').scope; + const authsettings = require('./settings') + const SEA = require('./sea'); + //const { scope: seaIndexedDb } = require('./indexed') // This updates sessionStorage & IndexedDB to persist authenticated "session" const updateStorage = (proof, key, pin) => async (props) => { if (!Gun.obj.has(props, 'alias')) { @@ -16,16 +17,16 @@ const remember = { alias, pin } try { - const signed = await SEA.write(JSON.stringify(remember), key) + const signed = await SEA.sign(JSON.stringify(remember), key) sessionStorage.setItem('user', alias) sessionStorage.setItem('remember', signed) - const encrypted = await SEA.enc(props, pin) + const encrypted = await SEA.encrypt(props, pin) if (encrypted) { - const auth = await SEA.write(encrypted, key) - await seaIndexedDb.wipe() + const auth = await SEA.sign(encrypted, key) + await seaIndexedDb.wipe() // NO! Do not do this. It ruins other people's sessionStorage code. This is bad/wrong, commenting it out. await seaIndexedDb.put(id, { auth }) } @@ -36,12 +37,12 @@ } // Wiping IndexedDB completely when using random PIN - await seaIndexedDb.wipe() + await seaIndexedDb.wipe() // NO! Do not do this. It ruins other people's sessionStorage code. This is bad/wrong, commenting it out. // And remove sessionStorage data sessionStorage.removeItem('user') sessionStorage.removeItem('remember') return props } - module.exports = updateStorage; + module.exports = updateStorage \ No newline at end of file diff --git a/sea/user.js b/sea/user.js index 3fe1d2c6..c3176dce 100644 --- a/sea/user.js +++ b/sea/user.js @@ -1,17 +1,22 @@ // How does it work? // TODO: Bug! Need to include SEA! - const Gun = (typeof window !== 'undefined' ? window : global).Gun || require('gun/gun') - var SEA = require('./sea'); - var authRecall = require('./recall'); - var authenticate = require('./authenticate'); - var finalizeLogin = require('./login'); - var authLeave = require('./leave'); + //const Gun = (typeof window !== 'undefined' ? window : global).Gun || require('gun/gun') + + const SEA = require('./sea') + const authRecall = require('./recall') + const authsettings = require('./settings') + const authenticate = require('./authenticate') + const finalizeLogin = require('./login') + const authLeave = require('./leave') + const { recall: _initial_authsettings } = require('./settings') + const Gun = SEA.Gun; + // let's extend the gun chain with a `user` function. // only one user can be logged in at a time, per gun instance. Gun.chain.user = function() { - const root = this.back(-1) // always reference the root gun instance. - let user = root._.user || (root._.user = root.chain()); // create a user context. + const gunRoot = this.back(-1) // always reference the root gun instance. + const user = gunRoot._.user || (gunRoot._.user = gunRoot.chain()); // create a user context. // then methods... [ 'create', // factory 'auth', // login @@ -22,11 +27,12 @@ ].map((method)=> user[method] = User[method]) return user // return the user! } + var u; function User(){} // Well first we have to actually create a user. That is what this function does. Object.assign(User, { async create(username, pass, cb) { - const root = this.back(-1) + const gunRoot = this.back(-1) var gun = this, cat = (gun._); cb = cb || function(){}; if(cat.ing){ @@ -37,7 +43,7 @@ var p = new Promise((resolve, reject) => { // Because no Promises or async // Because more than 1 user might have the same username, we treat the alias as a list of those users. if(cb){ resolve = reject = cb } - root.get(`alias/${username}`).get(async (at, ev) => { + gunRoot.get(`alias/${username}`).get(async (at, ev) => { ev.off() if (at.put) { // If we can enforce that a user name is already taken, it might be nice to try, but this is not guaranteed. @@ -50,27 +56,32 @@ const salt = Gun.text.random(64) // pseudo-randomly create a salt, then use CryptoJS's PBKDF2 function to extend the password with it. try { - const proof = await SEA.proof(pass, salt) + const proof = await SEA.work(pass, salt) // this will take some short amount of time to produce a proof, which slows brute force attacks. const pairs = await SEA.pair() // now we have generated a brand new ECDSA key pair for the user account. const { pub, priv, epriv } = pairs // the user's public key doesn't need to be signed. But everything else needs to be signed with it! - const alias = await SEA.write(username, pairs) - const epub = await SEA.write(pairs.epub, pairs) + const alias = await SEA.sign(username, pairs) + if(u === alias){ throw SEA.err } + const epub = await SEA.sign(pairs.epub, pairs) + if(u === epub){ throw SEA.err } // to keep the private key safe, we AES encrypt it with the proof of work! - const auth = await SEA.enc({ priv, epriv }, { pub: pairs.epub, key: proof }) + const auth = await SEA.encrypt({ priv, epriv }, proof) .then((auth) => // TODO: So signedsalt isn't needed? - // SEA.write(salt, pairs).then((signedsalt) => - SEA.write({ salt, auth }, pairs) + // SEA.sign(salt, pairs).then((signedsalt) => + SEA.sign({ek: auth, s: salt}, pairs) // ) ).catch((e) => { Gun.log('SEA.en or SEA.write calls failed!'); cat.ing = false; gun.leave(); reject(e) }) const user = { alias, pub, epub, auth } const tmp = `pub/${pairs.pub}` // awesome, now we can actually save the user with their public key as their ID. - root.get(tmp).put(user) + try{ + + gunRoot.get(tmp).put(user) + }catch(e){console.log(e)} // next up, we want to associate the alias with the public key. So we add it to the alias list. - root.get(`alias/${username}`).put(Gun.obj.put({}, tmp, Gun.val.rel.ify(tmp))) + gunRoot.get(`alias/${username}`).put(Gun.obj.put({}, tmp, Gun.val.rel.ify(tmp))) // callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack) setTimeout(() => { cat.ing = false; resolve({ ok: 0, pub: pairs.pub}) }, 10) // TODO: BUG! If `.auth` happens synchronously after `create` finishes, auth won't work. This setTimeout is a temporary hack until we can properly fix it. } catch (e) { @@ -81,15 +92,15 @@ } }) }) - return gun; // gun chain commands must return gun chains! + return gun // gun chain commands must return gun chains! }, // now that we have created a user, we want to authenticate them! - async auth(alias, pass, cb, opts) { - if(cb && !(cb instanceof Function)){ opts = cb; cb = function(){} } - const { pin, newpass } = opts || {} - const root = this.back(-1) - cb = cb || function(){}; - + async auth(alias, pass, cb, opt) { + const opts = opt || (typeof cb !== 'function' && cb) + let { pin, newpass } = opts || {} + const gunRoot = this.back(-1) + cb = typeof cb === 'function' ? cb : () => {} + newpass = newpass || (opts||{}).change; var gun = this, cat = (gun._); if(cat.ing){ cb({err: "User is already being created or authenticated!", wait: true}); @@ -99,7 +110,7 @@ if (!pass && pin) { try { - var r = await authRecall(root, { alias, pin }) + var r = await authRecall(gunRoot, { alias, pin }) return cat.ing = false, cb(r), gun; } catch (e) { var err = { err: 'Auth attempt failed! Reason: No session data for alias & PIN' } @@ -115,7 +126,7 @@ } try { - const keys = await authenticate(alias, pass, root) + const keys = await authenticate(alias, pass, gunRoot) if (!keys) { return putErr('Auth attempt failed!')({ message: 'No keys' }) } @@ -124,14 +135,14 @@ if (newpass) { // password update so encrypt private key using new pwd + salt try { - const salt = Gun.text.random(64) - const encSigAuth = await SEA.proof(newpass, salt) + const salt = Gun.text.random(64); + const encSigAuth = await SEA.work(newpass, salt) .then((key) => - SEA.enc({ priv, epriv }, { pub, key, set: true }) - .then((auth) => SEA.write({ salt, auth }, keys)) + SEA.encrypt({ priv, epriv }, { pub, key, set: true }) + .then((auth) => SEA.sign({ salt, auth }, keys)) ) - const signedEpub = await SEA.write(epub, keys) - const signedAlias = await SEA.write(alias, keys) + const signedEpub = await SEA.sign(epub, keys) + const signedAlias = await SEA.sign(alias, keys) const user = { pub, alias: signedAlias, @@ -139,18 +150,18 @@ epub: signedEpub } // awesome, now we can update the user using public key ID. - root.get(`pub/${user.pub}`).put(user) + gunRoot.get(`pub/${user.pub}`).put(user) // then we're done - var login = finalizeLogin(alias, keys, root, { pin }) + const login = finalizeLogin(alias, keys, gunRoot, { pin }) login.catch(putErr('Failed to finalize login with new password!')) - return cat.ing = false, cb(login), gun; + return cat.ing = false, cb(await login), gun } catch (e) { return putErr('Password set attempt failed!')(e) } } else { - var login = finalizeLogin(alias, keys, root, { pin }) + const login = finalizeLogin(alias, keys, gunRoot, { pin }) login.catch(putErr('Finalizing login failed!')) - return cat.ing = false, cb(login), gun; + return cat.ing = false, cb(await login), gun; } } catch (e) { return putErr('Auth attempt failed!')(e) @@ -162,18 +173,18 @@ }, // If authenticated user wants to delete his/her account, let's support it! async delete(alias, pass) { - const root = this.back(-1) + const gunRoot = this.back(-1) try { - const { pub } = await authenticate(alias, pass, root) - await authLeave(root, alias) + const { pub } = await authenticate(alias, pass, gunRoot) + await authLeave(gunRoot, alias) // Delete user data - root.get(`pub/${pub}`).put(null) + gunRoot.get(`pub/${pub}`).put(null) // Wipe user data from memory - const { user = { _: {} } } = root._; + const { user = { _: {} } } = gunRoot._; // TODO: is this correct way to 'logout' user from Gun.User ? [ 'alias', 'sea', 'pub' ].map((key) => delete user._[key]) user._.is = user.is = {} - root.user() + gunRoot.user() return { ok: 0 } // TODO: proper return codes??? } catch (e) { Gun.log('User.delete failed! Error:', e) @@ -182,11 +193,25 @@ }, // If authentication is to be remembered over reloads or browser closing, // set validity time in minutes. - async recall(setvalidity, options) { - const root = this.back(-1) + async recall(setvalidity, options) { + const gunRoot = this.back(-1) let validity let opts + + var o = setvalidity; + if(o && o.sessionStorage){ + if(typeof window !== 'undefined'){ + var tmp = window.sessionStorage; + if(tmp){ + gunRoot._.opt.remember = true; + if(tmp.alias && tmp.tmp){ + gunRoot.user().auth(tmp.alias, tmp.tmp); + } + } + } + return this; + } if (!Gun.val.is(setvalidity)) { opts = setvalidity @@ -208,7 +233,7 @@ authsettings.hook = (Gun.obj.has(opts, 'hook') && typeof opts.hook === 'function') ? opts.hook : _initial_authsettings.hook // All is good. Should we do something more with actual recalled data? - return await authRecall(root) + return await authRecall(gunRoot) } catch (e) { const err = 'No session!' Gun.log(err) @@ -218,11 +243,11 @@ } }, async alive() { - const root = this.back(-1) + const gunRoot = this.back(-1) try { // All is good. Should we do something more with actual recalled data? - await authRecall(root) - return root._.user._ + await authRecall(gunRoot) + return gunRoot._.user._ } catch (e) { const err = 'No session!' Gun.log(err) @@ -239,6 +264,5 @@ }) } } - - module.exports = User; + module.exports = User \ No newline at end of file diff --git a/sea/verify.js b/sea/verify.js new file mode 100644 index 00000000..d89a6d49 --- /dev/null +++ b/sea/verify.js @@ -0,0 +1,34 @@ + + var SEA = require('./root'); + var shim = require('./shim'); + var S = require('./settings'); + var sha256hash = require('./sha256'); + var parse = require('./parse'); + var u; + + SEA.verify = async (data, pair, cb) => { try { + const json = parse(data) + if(false === pair){ // don't verify! + const raw = (json === data)? json : parse(json.m) + if(cb){ cb(raw) } + return raw; + } + const pub = pair.pub || pair + const jwk = S.jwk(pub) + const key = await shim.subtle.importKey('jwk', jwk, S.ecdsa.pair, false, ['verify']) + const hash = await sha256hash(json.m) + const sig = new Uint8Array(shim.Buffer.from(json.s, 'utf8')) + const check = await shim.subtle.verify(S.ecdsa.sign, key, sig, new Uint8Array(hash)) + if(!check){ throw "Signature did not match." } + const r = check? parse(json.m) : u; + + if(cb){ cb(r) } + return r; + } catch(e) { + SEA.err = e; + if(cb){ cb() } + return; + }} + + module.exports = SEA.verify; + \ No newline at end of file diff --git a/sea/webcrypto.js b/sea/webcrypto.js deleted file mode 100644 index 7397994b..00000000 --- a/sea/webcrypto.js +++ /dev/null @@ -1,25 +0,0 @@ - - - let subtle - let subtleossl - let getRandomBytes - let crypto - var Buffer = require('./buffer'); - var wc; - var api = {}; - - if (typeof __webpack_require__ === 'function' || typeof window !== 'undefined') { - api.crypto = wc = window.crypto || window.msCrypto // STD or M$ - api.subtle = subtle = wc.subtle || wc.webkitSubtle // STD or iSafari - api.random = getRandomBytes = (len) => Buffer.from(wc.getRandomValues(new Uint8Array(Buffer.alloc(len)))) - } else { - api.crypto = crypto = require('crypto') - const WebCrypto = require('node-webcrypto-ossl') - const webcrypto = new WebCrypto({directory: 'key_storage'}) - api.ossl = subtleossl = webcrypto.subtle - api.subtle = subtle = require('@trust/webcrypto').subtle // All but ECDH - api.random = getRandomBytes = (len) => Buffer.from(crypto.randomBytes(len)) - } - - module.exports = api; - \ No newline at end of file diff --git a/sea/work.js b/sea/work.js new file mode 100644 index 00000000..8e8f4c1f --- /dev/null +++ b/sea/work.js @@ -0,0 +1,49 @@ + + var SEA = require('./root'); + var shim = require('./shim'); + var S = require('./settings'); + var u; + + SEA.work = async (data, pair, cb) => { try { // used to be named `proof` + var salt = pair.epub || pair; // epub not recommended, salt should be random! + if(salt instanceof Function){ + cb = salt; + salt = u; + } + salt = salt || shim.random(9); + if (SEA.window) { + // For browser subtle works fine + const key = await shim.subtle.importKey( + 'raw', new shim.TextEncoder().encode(data), { name: 'PBKDF2' }, false, ['deriveBits'] + ) + const result = await shim.subtle.deriveBits({ + name: 'PBKDF2', + iterations: S.pbkdf2.iter, + salt: new shim.TextEncoder().encode(salt), + hash: S.pbkdf2.hash, + }, key, S.pbkdf2.ks * 8) + data = shim.random(data.length) // Erase data in case of passphrase + const r = shim.Buffer.from(result, 'binary').toString('utf8') + if(cb){ cb(r) } + return r; + } + // For NodeJS crypto.pkdf2 rocks + const hash = crypto.pbkdf2Sync( + data, + new shim.TextEncoder().encode(salt), + S.pbkdf2.iter, + S.pbkdf2.ks, + S.pbkdf2.hash.replace('-', '').toLowerCase() + ) + data = shim.random(data.length) // Erase passphrase for app + const r = hash && hash.toString('utf8') + if(cb){ cb(r) } + return r; + } catch(e) { + SEA.err = e; + if(cb){ cb() } + return; + }} + + module.exports = SEA.work; + \ No newline at end of file