feat: create pair using priv/epriv

This commit is contained in:
noname 2025-01-26 12:10:38 +07:00
parent 6be276afe7
commit 7e254ae13f
3 changed files with 130 additions and 47 deletions

79
sea.js
View File

@ -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) {

View File

@ -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) {

View File

@ -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){