Skip to content

Instantly share code, notes, and snippets.

@jabis
Last active July 15, 2023 10:48
Show Gist options
  • Save jabis/a3b7d5b475ef23fb859d3acabe9325cb to your computer and use it in GitHub Desktop.
Save jabis/a3b7d5b475ef23fb859d3acabe9325cb to your computer and use it in GitHub Desktop.
Gun user-space and public graph get/set with signing and encryption
/**
* get and put encrypted and/or signed material to paths in Gun
* FIXME: Investigate why root level put doesn't work
*
* DONE: Add signing to make objects unwritable by others
* Changes:
* - 25.02.2020
* - added mergedeep to better merge deeper objects between themselves
* - added pair.osign option to only sign not encrypt when passing existing pairs
**/
(async function(){
var Nug = this.Nug = null;
var atoob = this.atoob = function atoob(arr){
var obj = {};
Gun.list.map(arr, function(v,f,t){
if(Gun.list.is(v) || Gun.obj.is(v)){
obj[f] = atoob(v);
return;
}
obj[f] = v;
});
return obj;
};
const getNug = this.getNug = function(){
if(Nug) return Nug.back(-1);
if(Gun) Nug = new Gun(location.protocol+"//"+location.host+"/gun");
return Nug.back(-1);
}
const mrgdeep = this.mrgdeep = function mrgdeep(...objects) {
const isObject = obj => obj && typeof obj === 'object';
return objects.reduce((prev, obj) => {
Object.keys(obj).forEach(key => {
const pVal = prev[key];
const oVal = obj[key];
if (Array.isArray(pVal) && Array.isArray(oVal)) {
prev[key] = [...pVal, ...oVal].filter((element, index, array) => array.indexOf(element) === index);
}
else if (isObject(pVal) && isObject(oVal)) {
prev[key] = mrgdeep(pVal, oVal);
}
else {
prev[key] = oVal;
}
});
return prev;
}, {});
}
const decEnc = this.decEnc = async(...args)=> {
console.log(args);
let data = args.length > 0 ? args.shift() : false; // data as first argument
let pair = args.length > 0 ? args.shift() : false; // pair to use in decrypting/encrypting as second argument
let mode = args.length > 0 ? args.shift() : "decrypt"; // mode optional encrypt/decrypt on SEA defaults to decrypt
//TODO return Promise.reject if we don't have all mandatory arguments passed
let secret = await SEA.secret(pair.epub, pair);
let it = await SEA[mode](data, secret);
return it;
};
const createRWEResource = this.createRWEResource = async(...args)=> {
let key = args.shift();
let data = args.shift();
let encIt = args.length>0 ? args.shift() : false;
let addUuid = args.length>0 ? args.shift() : false;
const uuid = gun._.opt.uuid();
let pair = await SEA.pair();
if(typeOf(encIt) == "object" && encIt.priv) {
pair = encIt;
var onlysign = pair.osign? true:false;
if(pair.hasOwnProperty('osign')) delete pair["osign"];
if(onlysign) encIt = false;
}
let id = "~"+pair.pub;
if(addUuid) id = id + "." + uuid;
let nug = getNug();
if(!encIt){
let datax = {"#":id,'.':key,':':data,'>':Gun.state()}
let signed = await SEA.sign(datax,pair);
let putsi = await nug.get(id).get(key).put(signed).then();
console.log("putsi",putsi);
return {id:id,key:key,ref:nug.get(id).get(key),pair:pair};
}
else {
return await decEnc(data,pair,"encrypt").then(async(enc)=>{
let datax = {"#":id,'.':key,':':'a'+enc,'>':Gun.state()}
let signed = await SEA.sign(datax,pair);
console.log(pair,signed);
let putsi = await nug.get(id).get(key).put(signed).then()
console.log("putsi",putsi);
return {id:id,key:key,ref:nug.get(id).get(key),pair:pair};
});
}
};
Gun.chain.putsenc = async function(){
let args = Array.from(arguments);
let self = this;
let gun = this.back(-1);
let data = args.shift();
let pair = args.length > 0 ? args.shift() : (gun.user().is ? gun.user()._.sea : null);
let genuuid = args.length > 0 ? args.shift() : false;
let onlysign = args.length > 0 ? args.shift() : false;
if(!pair) return Promise.reject("No keypair provided or not logged in");
if(onlysign) pair.osign=true;
return self.once(async function(olddata,key) {
return await createRWEResource(key, data, pair, genuuid);
});
};
Gun.chain.putenc = async function() {
let args = Array.from(arguments);
let self = this;
let gun = this.back(-1);
let nug = getNug();
let data = args.shift();
let me = gun.user().is;
let pair = args.length > 0 ? args.shift() : me ? gun.user()._.sea : null;
let onlysign = args.length > 0 ? args.shift() : false;
//TODO: Implement checking with old pair as well as throw if error
if (!pair) return Promise.reject("No keypair provided or not logged in");
if(onlysign) pair.osign = true;
return this.once(async function(olddata, key) {
console.log("old data", olddata);
//TODO: Check the onlysign and skip decrypt and encrypt in that case
if(typeof olddata === "string" && /^(aSEA)/.test(olddata)) olddata = olddata.slice(1)
return await decEnc(olddata, pair)
.then(async old => {
console.log("old", old);
var nd = null;
if (!old) {
nd = data;
} else {
if (typeof old === "object" && data && typeof data ==="object") {
// trying to simulate regular put
nd = mrgdeep(old, data);
} else {
nd = data;
}
}
console.log("new data", nd);
return await decEnc(nd, pair, "encrypt").then(async (enc) => {
console.log("encrypted", enc);
if (!me && pair && pair.priv) {
let id = "~"+pair.pub;
let signed = await SEA.sign(
{
"#": id,
".": key,
":": "a"+enc,
">": Gun.state()
},
pair
);
console.log("signed",signed);
return await nug.get(id).get(key).put(signed).then();
} else {
return await self.put("a"+enc).then();
}
});
})
.catch(err => {
console.log("whoops", err);
return gun;
});
});
};
Gun.chain.getenc = async function(){
let args = Array.from(arguments);
let self = this;
let gun = this.back(-1);
let path = args.length > 0 ? args.shift() : false;
let pair = args.length > 0 ? args.shift() : (gun.user().is ? gun.user()._.sea : null)
if(!pair) return Promise.reject("No keypair provided or not logged in");
let data;
if(!path) {
data = await self.once().then();
} else {
data = await this.get(path).then();
}
//console.log(data,pair,path);
if(typeof data === "string" && /^(aSEA)/.test(data)) data = data.slice(1)
let dec = await decEnc(data, pair);
return dec ? dec : null;
};
const putMyDataEnc = this.putMyDataEnc = async (...args) => {
//let args = arguments; Array.from(arguments);
console.log(args);
let cb = false;
if(args && args.length > 0 && typeof args[args.length-1] === "function"){
cb = args.pop(); // if we have a cb function in last of the list, then pop that
}
let path = args.length > 0 ? args.shift() : false; // path as first argument
let data = args.length > 0 ? args.shift() : false; // data as second argument
let pair = args.length > 0 ? args.shift() : false; // pair optional pair to use in encrypting
if(!path || !data) return Promise.reject("No path or data!");
const me = gun.user();
if (me.is) {
//authenticated
const mypair = me._.sea;
let usePair = mypair;
if(pair && pair.epub && pair.epriv) usePair = pair;
let enc = await decEnc(data, usePair,"encrypt");
if (cb) {
return me
.get(path)
.put(enc,cb)
} else {
return await me
.get(path)
.put(enc)
.then();
}
} else {
return Promise.reject("Not authenticated");
}
};
const getMyDataEnc = this.getMyDataEnc = async (...args)=>{
console.log(args);
let path = args.shift(); // path as first argument
let pair = args.length > 0 ? args.shift() : false; // optional pair to decrypt with
const me = gun.user();
if(me.is) { //authenticated
const mypair = me._.sea;
let usePair = mypair;
if(pair && pair.epub && pair.epriv) usePair = pair;
let data = await me
.get(path)
.then()
//console.log(data);
let dec = await decEnc(data, usePair)
return dec;
} else {
return Promise.reject("Not authenticated")
}
};
// USAGE:
// creating a new resource with generated keypair signature: {keyname} to store to, {data} to house {pair} a pair or boolean, {uuid} whether to append random uuid to pubkey
let testresource1 = await createRWEResource("testing/encryption/321",{'data':'I','want':'to',encrypt:'true'},true,true);
console.log(testresource1); // {id:created id, key: referenced key, ref:gun node, pair: if not your user-pair save this if you ever want to touch this node again}
// creating/overwriting a resource with your user keypair directly to gun.user().get("testing/encryption/321")
let mypair = gun.user()._.sea;
testresource2 = await createRWEResource("testing/encryption/321",{'data':'I','dontwant':'to',encrypt:'true'},mypair);
console.log(testresource2);
//decode with decEnc
let id = testresource2.id;
let key = testresource2.key;
let setti = testresource2.ref;
let pair = testresource2.pair;
let val = await setti.once(Gun.log).then();
let dec = await decEnc(val.slice(1),testresource2.pair)
console.log(dec)
//{data: "I", dontwant: "to", encrypt: "true"}
let example = await gun.get("testing/encryption/123").get("example").putsenc({"encrypted":"stuff"}); // overwrite whole example
console.log(example);
let readexample = await gun.get("testing/encryption/123").getenc("example");
console.log(readexample);
let updateexample = await gun.get("testing/encryption/123").get("example").putenc({"stuff":"encrypted stuff"});
console.log(updateexample);
setTimeout(async()=>{
let x = await gun.get("testing/encryption/123").getenc("example");
console.log("some wait time so we wont resolve from localstorage",x);
},250)
putMyDataEnc("testing/encryption/123",{socks:"wet"}).then((d)=>{ console.log(d); }).catch((err)=>{ console.log(err); })
getMyDataEnc("testing/encryption/123").then((d)=>{ console.log(d); }).catch((err)=>{ console.log(err); })
//testing decryption of the node:
gun.user().get("testing/encryption/123").once(function(data){
decEnc(data,gun.user()._.sea).then((d)=>{console.log(d)}).catch((err)=>{ console.log(err); })
})
})();
@jabis
Copy link
Author

jabis commented Jan 8, 2020

Hi, just to let you know I haven't forgotten you guys, working on this now for the rest of the week :)
@zilveer @anoxxy, updating now with yet incomplete version

@zilveer
Copy link

zilveer commented Jan 8, 2020

@jabis That is great to hear, hope you pet is better now!

Would you mind to add some kind of callback functionaltity to putenc, putsenc as it works in put(obj, cb)?
It would also be awesome if you can add the callback feature to the standalone functionalities!

Best regards Zilveer

@jabis
Copy link
Author

jabis commented Jan 8, 2020

I'm not a fan of callback-hell but I'll see if I can make it somewhat compatible. The thing is I'm plucking this out of the framework and replacing things that I rely on elsewhere with vanilla, so it's a little chore to do :D

@jabis
Copy link
Author

jabis commented Jan 8, 2020

@zilveer https://gist.github.com/jabis/a3b7d5b475ef23fb859d3acabe9325cb#file-myhelper-js-L24-L65 here I'm now trying upon creation to return the ref to the gun chain for further use, well we'll see at the end of the week if I can wrap my head around how to mold the Gun.chain to somewhat sane implementation ^^

@jabis
Copy link
Author

jabis commented Jan 8, 2020

Note: that's now write protected and put(s)enc can't yet touch it - have to solve the signing (Nug to use) there, but gun.get('~createdpubkeystuff').getenc("testing/encryption/321",pair) is indeed working already

@jabis
Copy link
Author

jabis commented Jan 8, 2020

image

@jabis
Copy link
Author

jabis commented Jan 8, 2020

@zilveer, all my methods work with await or .then(function(woot){ console.log(woot); }) instead of adding callbacks

@jabis
Copy link
Author

jabis commented Jan 8, 2020

image

@jabis
Copy link
Author

jabis commented Jan 8, 2020

Now you can manipulate data with
createRWEResource/getenc/put(s)enc like:

testresource = await createRWEResource("testing/encryption/321",{'data':'I','want':'to',encrypt:'true'},gun.user()._.sea);
console.log(testresource); // {id,key,gunref,pair}
//and directly read with getenc (well might need a little setTimeout if like me it takes awhile to push changes to peers)
await gun.user().getenc("testing/encryption/321");
//and directly write with putenc 
await gun.user().get("testing/encryption/321").putenc({some:"more",data:"here"});

Note createRWEResource currenctly acts like .putsenc overwriting the data

tidbit: the method createRWEResource comes from create ReadWriteprotectedEncryptedResource - gonna come up with a better name soon :D

@zilveer
Copy link

zilveer commented Jan 10, 2020

@jabis wow such improvement over the current coding. You should indeed have this as a repo instead for GUN or else it is somehow "hidden" from public.

I really like what you have achieved so far, thumbs up !

Regards Zilveer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment