diff --git a/sea.js b/sea.js index 4d1d1ea2..3c85cbbd 100644 --- a/sea.js +++ b/sea.js @@ -336,33 +336,64 @@ SEA.pair = SEA.pair || (async (cb, opt) => { try { var ecdhSubtle = shim.ossl || shim.subtle; - var sa; + var sa = {}; var dh = {}; - if(opt && opt.seed){ - const e = new shim.TextEncoder(); - const h = await shim.subtle.digest('SHA-256', e.encode(opt.seed+'-sign')); - sa = { - priv: shim.Buffer.from(h).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43) - }; - const [x,y,eh] = await Promise.all([ - shim.subtle.digest('SHA-256', e.encode(sa.priv+'-x')), - shim.subtle.digest('SHA-256', e.encode(sa.priv+'-y')), - shim.subtle.digest('SHA-256', e.encode(opt.seed+'-encrypt')) - ]); - sa.pub = shim.Buffer.from(x).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43) + - '.' + - shim.Buffer.from(y).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43); + const e = new shim.TextEncoder(); - dh.epriv = shim.Buffer.from(eh).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43); - const [ex,ey] = await Promise.all([ - shim.subtle.digest('SHA-256', e.encode(dh.epriv+'-x')), - shim.subtle.digest('SHA-256', e.encode(dh.epriv+'-y')) + // Helper function to generate pub from priv + const pubFromPriv = async (priv) => { + const [x,y] = await Promise.all([ + shim.subtle.digest('SHA-256', e.encode(priv+'-x')), + shim.subtle.digest('SHA-256', e.encode(priv+'-y')) ]); - dh.epub = shim.Buffer.from(ex).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43) + - '.' + - shim.Buffer.from(ey).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43); - } else { + return shim.Buffer.from(x).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43) + + '.' + + shim.Buffer.from(y).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43); + } + + // Helper function to generate epub from epriv + const epubFromEpriv = async (epriv) => { + const [ex,ey] = await Promise.all([ + shim.subtle.digest('SHA-256', e.encode(epriv+'-x')), + shim.subtle.digest('SHA-256', e.encode(epriv+'-y')) + ]); + return shim.Buffer.from(ex).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43) + + '.' + + shim.Buffer.from(ey).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43); + } + + if(opt && opt.seed){ // Case 0: Generate from seed + const h = await shim.subtle.digest('SHA-256', e.encode(opt.seed+'-sign')); + sa.priv = shim.Buffer.from(h).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43); + sa.pub = await pubFromPriv(sa.priv); + const eh = await shim.subtle.digest('SHA-256', e.encode(opt.seed+'-encrypt')); + dh.epriv = shim.Buffer.from(eh).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43); + dh.epub = await epubFromEpriv(dh.epriv); + } + else if(opt && opt.priv){ // Case 1 & 3: Given priv + sa.priv = opt.priv; + sa.pub = await pubFromPriv(sa.priv); + if(!opt.epriv){ // Case 1: Generate epriv + // Generate a virtual seed from priv to maintain compatibility + const virtualSeed = sa.priv; + const eh = await shim.subtle.digest('SHA-256', e.encode(virtualSeed+'-encrypt')); + dh.epriv = shim.Buffer.from(eh).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43); + } else { // Case 3: Use provided epriv + dh.epriv = opt.epriv; + } + dh.epub = await epubFromEpriv(dh.epriv); + } + else if(opt && opt.epriv){ // Case 2: Given epriv + dh.epriv = opt.epriv; + dh.epub = await epubFromEpriv(dh.epriv); + // Generate a virtual seed from epriv to maintain compatibility + const virtualSeed = dh.epriv; + const h = await shim.subtle.digest('SHA-256', e.encode(virtualSeed+'-sign')); + sa.priv = shim.Buffer.from(h).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43); + sa.pub = await pubFromPriv(sa.priv); + } + else { // Case 4: Generate new keypair // First: ECDSA keys for signing/verifying... const keys = await shim.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, [ 'sign', 'verify' ]); const priv = await shim.subtle.exportKey('jwk', keys.privateKey); @@ -388,7 +419,7 @@ } } - var r = { pub: sa.pub, priv: sa.priv, /* pubId, */ epub: dh.epub, epriv: dh.epriv } + var r = { pub: sa.pub, priv: sa.priv, epub: dh.epub, epriv: dh.epriv } if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; } catch(e) { diff --git a/sea/pair.js b/sea/pair.js index 4f2c7ef3..3410b93e 100644 --- a/sea/pair.js +++ b/sea/pair.js @@ -18,33 +18,64 @@ SEA.pair = SEA.pair || (async (cb, opt) => { try { var ecdhSubtle = shim.ossl || shim.subtle; - var sa; + var sa = {}; var dh = {}; - if(opt && opt.seed){ - const e = new shim.TextEncoder(); - const h = await shim.subtle.digest('SHA-256', e.encode(opt.seed+'-sign')); - sa = { - priv: shim.Buffer.from(h).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43) - }; - const [x,y,eh] = await Promise.all([ - shim.subtle.digest('SHA-256', e.encode(sa.priv+'-x')), - shim.subtle.digest('SHA-256', e.encode(sa.priv+'-y')), - shim.subtle.digest('SHA-256', e.encode(opt.seed+'-encrypt')) + const e = new shim.TextEncoder(); + + // Helper function to generate pub from priv + const pubFromPriv = async (priv) => { + const [x,y] = await Promise.all([ + shim.subtle.digest('SHA-256', e.encode(priv+'-x')), + shim.subtle.digest('SHA-256', e.encode(priv+'-y')) ]); - sa.pub = shim.Buffer.from(x).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43) + - '.' + - shim.Buffer.from(y).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43); - - dh.epriv = shim.Buffer.from(eh).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43); + return shim.Buffer.from(x).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43) + + '.' + + shim.Buffer.from(y).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43); + } + + // Helper function to generate epub from epriv + const epubFromEpriv = async (epriv) => { const [ex,ey] = await Promise.all([ - shim.subtle.digest('SHA-256', e.encode(dh.epriv+'-x')), - shim.subtle.digest('SHA-256', e.encode(dh.epriv+'-y')) + shim.subtle.digest('SHA-256', e.encode(epriv+'-x')), + shim.subtle.digest('SHA-256', e.encode(epriv+'-y')) ]); - dh.epub = shim.Buffer.from(ex).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43) + - '.' + - shim.Buffer.from(ey).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43); - } else { + return shim.Buffer.from(ex).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43) + + '.' + + shim.Buffer.from(ey).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43); + } + + if(opt && opt.seed){ // Case 0: Generate from seed + const h = await shim.subtle.digest('SHA-256', e.encode(opt.seed+'-sign')); + sa.priv = shim.Buffer.from(h).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43); + sa.pub = await pubFromPriv(sa.priv); + const eh = await shim.subtle.digest('SHA-256', e.encode(opt.seed+'-encrypt')); + dh.epriv = shim.Buffer.from(eh).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43); + dh.epub = await epubFromEpriv(dh.epriv); + } + else if(opt && opt.priv){ // Case 1 & 3: Given priv + sa.priv = opt.priv; + sa.pub = await pubFromPriv(sa.priv); + if(!opt.epriv){ // Case 1: Generate epriv + // Generate a virtual seed from priv to maintain compatibility + const virtualSeed = sa.priv; + const eh = await shim.subtle.digest('SHA-256', e.encode(virtualSeed+'-encrypt')); + dh.epriv = shim.Buffer.from(eh).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43); + } else { // Case 3: Use provided epriv + dh.epriv = opt.epriv; + } + dh.epub = await epubFromEpriv(dh.epriv); + } + else if(opt && opt.epriv){ // Case 2: Given epriv + dh.epriv = opt.epriv; + dh.epub = await epubFromEpriv(dh.epriv); + // Generate a virtual seed from epriv to maintain compatibility + const virtualSeed = dh.epriv; + const h = await shim.subtle.digest('SHA-256', e.encode(virtualSeed+'-sign')); + sa.priv = shim.Buffer.from(h).toString('base64').replace(/[+/=]/g,c=>({'+':'-','/':'_','=':''})[c]).slice(0,43); + sa.pub = await pubFromPriv(sa.priv); + } + else { // Case 4: Generate new keypair // First: ECDSA keys for signing/verifying... const keys = await shim.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, [ 'sign', 'verify' ]); const priv = await shim.subtle.exportKey('jwk', keys.privateKey); @@ -70,7 +101,7 @@ } } - var r = { pub: sa.pub, priv: sa.priv, /* pubId, */ epub: dh.epub, epriv: dh.epriv } + var r = { pub: sa.pub, priv: sa.priv, epub: dh.epub, epriv: dh.epriv } if(cb){ try{ cb(r) }catch(e){console.log(e)} } return r; } catch(e) { diff --git a/test/sea/sea.js b/test/sea/sea.js index 01022480..dec9e4c7 100755 --- a/test/sea/sea.js +++ b/test/sea/sea.js @@ -1,3 +1,6 @@ +const exp = require('constants'); +const expect = require('../expect'); + var root; var Gun; (function(){ @@ -74,6 +77,24 @@ describe('SEA', function(){ expect(sameKeys).to.be(true); expect(differentKeys).to.be(true); }); + it('generate key pairs from private key', async function () { + var gun = Gun() + var user = gun.user() + const test1 = await SEA.pair(null, { seed: "seed" }); + const test2 = await SEA.pair(null, { priv: test1.priv }); + expect(test2.priv).to.be(test1.priv); + expect(test2.pub).to.be(test1.pub); + await user.auth(test2); + expect(user.is.pub).to.be(test2.pub); + expect(user.is.pub).to.be(test1.pub); + user.leave(); + const test3 = await SEA.pair(null, { epriv: test2.epriv }); + expect(test3.epriv).to.be(test2.epriv); + await user.auth(test3); + expect(user.is.epub).to.be(test3.epub); + expect(user.is.epub).to.be(test2.epub); + user.leave(); + }); it('quickstart', function(done){ SEA.pair(function(pair){ SEA.encrypt('hello self', pair, function(enc){