From 8851eed06cbf5ccb34235d6f649322a70bbf5bfe Mon Sep 17 00:00:00 2001 From: sairajzero Date: Sun, 14 Jul 2019 21:15:35 +0530 Subject: [PATCH 01/11] Adding SuperNode feature --- app/web/app.js | 29 +- app/web/home.js | 357 ++++++++++++++------- app/web/kBucket.js | 725 ++++++++++++++++++++++++++++++++++++++++++ app/web/profile.js | 11 +- app/web/registerID.js | 574 +++++++++++++++++++++++++++++++++ 5 files changed, 1575 insertions(+), 121 deletions(-) create mode 100644 app/web/kBucket.js diff --git a/app/web/app.js b/app/web/app.js index 298b36c..f2935d6 100644 --- a/app/web/app.js +++ b/app/web/app.js @@ -199,7 +199,34 @@ var encrypt = { privateKeyDecimal: privateKeyDecimal, privateKeyHex: privateKeyHex } - } + }, + createShamirsSecretShares: function (str, total_shares, threshold_limit) { + if (str.length > 0) { + // convert the text into a hex string + var strHex = shamirSecretShare.str2hex(str); + // split into total_shares shares, with a threshold of threshold_limit + var shares = shamirSecretShare.share(strHex, total_shares, threshold_limit); + return shares; + } + return false; + }, + verifyShamirsSecret: function (sharesArray, str) { + // combine sharesArray: + var comb = shamirSecretShare.combine(sharesArray); + //convert back to UTF string: + comb = shamirSecretShare.hex2str(comb); + return comb === str; + }, + retrieveShamirSecret: function (sharesArray) { + if (sharesArray.length > 0) { + // combine sharesArray: + var comb = shamirSecretShare.combine(sharesArray.slice(0, sharesArray.length)); + //convert back to UTF string: + comb = shamirSecretShare.hex2str(comb); + return comb; + } + return false; + } } function convertStringToInt(string){ diff --git a/app/web/home.js b/app/web/home.js index 0b88dad..392066e 100644 --- a/app/web/home.js +++ b/app/web/home.js @@ -1,5 +1,5 @@ var profiles = [] -var tweeterID; +var selfID; var selfWebsocket,followingWebSockets = []; var privKey; var following; @@ -20,28 +20,40 @@ function userDataStartUp(){ profiles = arrayToObject(result); console.log(profiles); sessionStorage.profiles = JSON.stringify(profiles); - getuserID().then(function(result){ - console.log(result); - tweeterID = result; - sessionStorage.privKey = privKey; - sessionStorage.selfID = tweeterID; - alert(`${tweeterID}\nWelcome ${profiles[tweeterID].name}`) - initselfWebSocket(); - listProfiles(); - getFollowinglistFromIDB().then(function(result){ - following = result; - console.log(following); - displayTweetsFromIDB().then(function(result){ - connectToAllFollowing(); + getSuperNodeListfromIDB().then(function(result){ + console.log(result) + superNodeList = result; + kBucketObj.launchKBucket().then(function(result){ + console.log(result) + getuserID().then(function(result){ + console.log(result); + selfID = result; + if(superNodeList.includes(selfID)) + modSuperNode = true; + sessionStorage.privKey = JSON.stringify(encrypt.createShamirsSecretShares(privKey,10,10)); + sessionStorage.selfID = selfID; + alert(`${selfID}\nWelcome ${profiles[selfID].name}`) + initselfWebSocket(); + listProfiles(); + getFollowinglistFromIDB().then(function(result){ + following = result; + console.log(following); + displayTweetsFromIDB().then(function(result){ + connectToAllFollowing(); + }).catch(function(error){ + console.log(error.message); + }) }).catch(function(error){ console.log(error.message); }) - }).catch(function(error){ - console.log(error.message); - }) - + }).catch(function (error) { + console.log(error.message); + }); + }).catch(function(error){ + console.log(error.message); + }); }).catch(function (error) { - console.log(error.message); + console.log(error.message); }); }).catch(function (error) { console.log(error.message); @@ -86,12 +98,14 @@ function getDatafromAPI(){ }; idb.onupgradeneeded = function(event) { var db = event.target.result; + var objectStore0 = event.target.result.createObjectStore("superNodes"); var objectStore1 = db.createObjectStore("profiles",{ keyPath: 'floID' }); objectStore1.createIndex('onionAddr', 'onionAddr', { unique: false }); objectStore1.createIndex('name', 'name', { unique: false }); objectStore1.createIndex('pubKey', 'pubKey', { unique: false }); var objectStore2 = db.createObjectStore("lastTx"); - var objectStore3 = db.createObjectStore("tweets",{ keyPath: 'id' }); + var objectStore3 = db.createObjectStore("tweets",{ keyPath: 'tweetID' }); + objectStore3.createIndex('tid', 'tid', { unique: false }); objectStore3.createIndex('floID', 'floID', { unique: false }); objectStore3.createIndex('time', 'time', { unique: false }); objectStore3.createIndex('data', 'data', { unique: false }); @@ -127,18 +141,26 @@ function getDatafromAPI(){ } response.items.reverse().forEach(function(tx){ try { - //if (tx.vin[0].addr != addr) - //return; - var data = JSON.parse(tx.floData).FLO_Tweet; - if(data !== undefined){ - if(encrypt.getFLOIDfromPubkeyHex(data.pubKey)!=tx.vin[0].addr) - throw("PublicKey doesnot match with floID") - data = {floID : tx.vin[0].addr, onionAddr : data.onionAddr, name : data.name, pubKey:data.pubKey}; - storedata(data).then(function (response) { - }).catch(function (error) { - console.log(error.message); - }); - } + if (tx.vin[0].addr == addr){ + var data = JSON.parse(tx.floData).FLO_chat_SuperNode; + if(data !== undefined){ + storeSuperNodeData(data).then(function (response) { + }).catch(function (error) { + console.log(error.message); + }); + } + }else{ + var data = JSON.parse(tx.floData).FLO_Tweet; + if(data !== undefined){ + if(encrypt.getFLOIDfromPubkeyHex(data.pubKey)!=tx.vin[0].addr) + throw("PublicKey doesnot match with floID") + data = {floID : tx.vin[0].addr, onionAddr : data.onionAddr, name : data.name, pubKey:data.pubKey}; + storedata(data).then(function (response) { + }).catch(function (error) { + console.log(error.message); + }); + } + } } catch (e) { //console.log(e) } @@ -156,11 +178,56 @@ function getDatafromAPI(){ ); } +function storeSuperNodeData(data){ + return new Promise( + function(resolve, reject) { + var idb = indexedDB.open("FLO_Tweet"); + idb.onerror = function(event) { + reject("Error in opening IndexedDB!"); + }; + idb.onsuccess = function(event) { + var db = event.target.result; + var obs = db.transaction('superNodes', "readwrite").objectStore('superNodes'); + if(data.addNodes) + for(var i=0; i -
- - ${cursor.value.text} - - - ${getTime(cursor.value.time)} - -
- `; - chat.appendChild(msgdiv); - }else if(cursor.value.type == "S"){ - var msgdiv = document.createElement('div'); - msgdiv.setAttribute("class", "row message-body"); - msgdiv.innerHTML = `
-
- ${cursor.value.text} - - - ${getTime(cursor.value.time)} - -
-
`; - chat.appendChild(msgdiv); - } - - cursor.continue(); - } else { - console.log('Entries all displayed.'); - resolve("Read Msg from IDB"); - } - }; - db.close(); - }; - } - ); -} - function initselfWebSocket(){ selfWebsocket = new WebSocket("ws://"+location.host+"/ws"); selfWebsocket.onopen = function(evt){ console.log("Connecting"); - var pass = sessionStorage.serverPass || prompt("Enter server password :"); + var pass = (sessionStorage.serverPass !== undefined ? encrypt.retrieveShamirSecret(JSON.parse(sessionStorage.serverPass)): prompt("Enter server password :")); selfWebsocket.send("$"+pass); - sessionStorage.serverPass = pass; + sessionStorage.serverPass = JSON.stringify(encrypt.createShamirsSecretShares(pass,5,5)); }; selfWebsocket.onclose = function(evt){ console.log("DISCONNECTED"); @@ -296,7 +294,7 @@ function initselfWebSocket(){ if(evt.data == "$Access Denied!"){ var pass = prompt("Access Denied! reEnter server password :"); selfWebsocket.send("$"+pass); - sessionStorage.serverPass = pass; + sessionStorage.serverPass = JSON.stringify(encrypt.createShamirsSecretShares(pass,5,5)); }else if(evt.data == "$Access Granted!") alert("Access Granted!") else{ @@ -320,6 +318,29 @@ function initselfWebSocket(){ db.close(); }; selfWebsocket.send(`U${data.floID}`); + }else if(data.fromSuperNode && following.includes(data.floID)){ + var tid = data.tid; + data = JSON.parse(data.data); + if(encrypt.verify(data.tweet,data.sign,profiles[data.floID].pubKey)){ + storeTweet({floID:data.floID,time:data.time,data:data.tweet},tid); + createTweetElement(data.floID,data.time,data.tweet); + } + }else if(modSuperNode){ + if(data.reqNewTweets){ + kBucketObj.determineClosestSupernode(data.floID).then(result=>{ + if(result[0].floID == selfID) + SuperNode_sendTweetsFromIDB(data.floID,data.tid,data.requestor); + }).catch(e => { + console.log(e.message); + }); + }else if(data.newSuperNodeTweet){ + kBucketObj.determineClosestSupernode(data.floID).then(result=>{ + if(result[0].floID == selfID) + storeSuperNodeTweet(data.data,data.tid); + }).catch(e => { + console.log(e.message); + }); + } } }catch(error){ console.log(error.message); @@ -337,18 +358,19 @@ function postTweet(){ tweetBox.value = ""; var time = (new Date).getTime(); var sign = encrypt.sign(tweet,privKey); - var data = JSON.stringify({floID:tweeterID,time:time,tweet:tweet,sign:sign}); + var data = JSON.stringify({floID:selfID,time:time,tweet:tweet,sign:sign}); console.log(data); selfWebsocket.send(data); - createTweetElement(tweeterID,time,tweet); - getLastTweetCount(tweeterID).then(function(result){ - storeTweet({floID:tweeterID,time:time,data:tweet},result+1); + createTweetElement(selfID,time,tweet); + getLastTweetCount(selfID).then(function(result){ + storeTweet({floID:selfID,time:time,data:tweet},result+1); + sendTweetToSuperNode(data,result+1); }).catch(function(error){ console.log(error.message); }); } -function storeTweet(data,id){ +function storeTweet(data,tid){ var idb = indexedDB.open("FLO_Tweet"); idb.onerror = function(event) { console.log("Error in opening IndexedDB!"); @@ -356,14 +378,30 @@ function storeTweet(data,id){ idb.onsuccess = function(event) { var db = event.target.result; var obs = db.transaction("tweets", "readwrite").objectStore("tweets"); - data.id = `${data.time}_${data.floID}`; + data.tweetID = `${data.time}_${data.floID}`; + data.tid = tid; obs.add(data); var obsL = db.transaction("lastTweet", "readwrite").objectStore("lastTweet"); - obsL.put(id,data.floID); + obsL.put(tid,data.floID); db.close(); }; } +function sendTweetToSuperNode(data,tid){ + kBucketObj.determineClosestSupernode(selfID).then(result=>{ + var superNodeWS = new WebSocket("ws://"+profiles[result[0].floID].onionAddr+"/ws"); + superNodeWS.onopen = function(ev){ + console.log(`Connected to self SuperNode!`); + var data = JSON.stringify({newSuperNodeTweet:true,floID:selfID,tid:tid,data:data}) + superNodeWS.send(data); + }; + superNodeWS.onerror = function(ev) {console.log(`self SuperNode is offline!`);}; + superNodeWS.onclose = function(ev) {console.log(`Disconnected from self SuperNode!`);}; + }).catch(e => { + console.log(e.message); + }); +} + function getFollowinglistFromIDB(){ return new Promise( function(resolve,reject){ @@ -402,7 +440,7 @@ function displayTweetsFromIDB(){ var cursor = event.target.result; if(cursor) { //console.log(cursor.value) - if(cursor.value.floID == tweeterID || following.includes(cursor.value.floID)) + if(cursor.value.floID == selfID || following.includes(cursor.value.floID)) createTweetElement(cursor.value.floID,cursor.value.time,cursor.value.data); cursor.continue(); }else{ @@ -453,6 +491,9 @@ function connectToAllFollowing(){ }; followingWebSockets[floid].onerror = function(ev) { console.log(`${floid} Server is offline!`); + //Ping SuperNode for any new tweets + pingSuperNodeforNewTweets(floid); + }; followingWebSockets[floid].onclose = function(ev) { console.log(`Disconnected from ${floid} Server!`); @@ -494,4 +535,92 @@ function getLastTweetCount(floid){ ); } - +function pingSuperNodeforNewTweets(floID){ + kBucketObj.determineClosestSupernode(floID).then(result=>{ + var superNodeWS = new WebSocket("ws://"+profiles[result[0].floID].onionAddr+"/ws"); + superNodeWS.onopen = function(ev){ + console.log(`Connected to ${floid}'s SuperNode!`); + getLastTweetCount(floID).then(function(result){ + var data = JSON.stringify({reqNewTweets:true,floID:floID,tid:result,requestor:selfID}) + superNodeWS.send(data); + }).catch(function(error){ + console.log(error.message); + }); + }; + superNodeWS.onerror = function(ev) {console.log(`${floid}'s SuperNode is offline!`);}; + superNodeWS.onclose = function(ev) {console.log(`Disconnected from ${floid}'s SuperNode!`);}; + }).catch(e => { + console.log(e.message); + }); +} + +function storeSuperNodeTweet(data,tid){ + var idb = indexedDB.open("FLO_Tweet",2); + idb.onerror = function(event) { + console.log("Error in opening IndexedDB!"); + }; + idb.onupgradeneeded = function(event){ + var objectStore = event.target.result.createObjectStore("superNodeTweet",{ keyPath: 'tweetID' }); + objectStore.createIndex('floID', 'floID', { unique: false }); + objectStore.createIndex('tid', 'tid', { unique: false }); + objectStore.createIndex('data', 'data', { unique: false }); + } + idb.onsuccess = function(event) { + var db = event.target.result; + var obs = db.transaction("superNodeTweet", "readwrite").objectStore("superNodeTweet"); + var parsedData = JSON.parse(data); + var tweetID = ''+parsedData.floID+'_'+parsedData.time; + obs.add({tweetID:tweetID,floID:parsedData.floID,tid:tid,data:data}); + db.close(); + }; +} + +function SuperNode_sendTweetsFromIDB(floID,tid,requestor){ + return new Promise( + function(resolve,reject){ + var requestorWS = new WebSocket("ws://"+profiles[requestor].onionAddr+"/ws"); + + requestorWS.onopen = function(ev){ + console.log(`sending ${floID} tweets to ${requestor} Server!`); + var idb = indexedDB.open("FLO_Tweet",2); + idb.onerror = function(event) { + reject("Error in opening IndexedDB!"); + }; + idb.onupgradeneeded = function(event){ + var objectStore = event.target.result.createObjectStore("superNodeTweet",{ keyPath: 'tweetID' }); + objectStore.createIndex('floID', 'floID', { unique: false }); + objectStore.createIndex('tid', 'tid', { unique: false }); + objectStore.createIndex('data', 'data', { unique: false }); + } + idb.onsuccess = function(event) { + var db = event.target.result; + var obs = db.transaction("superNodeTweet", "readwrite").objectStore("superNodeTweet"); + var curReq = obs.openCursor(); + curReq.onsuccess = function(event) { + var cursor = event.target.result; + if(cursor) { + if(cursor.value.floID == floID && cursor.value.tid > tid){ + data = JSON.stringify({fromSuperNode:true, floID:cursor.value.floID,tid:cursor.value.tid,data:cursor.value.data}) + requestorWS.send(data); + } + cursor.continue(); + }else{ + resolve("Displayed Tweets from IDB!"); + } + } + curReq.onerror = function(event){ + reject("Error in Reading tweets from IDB!"); + } + db.close(); + }; + }; + requestorWS.onerror = function(ev) { + console.log(`${requestor} Server is offline!`); + }; + requestorWS.onclose = function(ev) { + console.log(`Disconnected from ${requestor} Server!`); + }; + } + ); +} + diff --git a/app/web/kBucket.js b/app/web/kBucket.js new file mode 100644 index 0000000..32b9880 --- /dev/null +++ b/app/web/kBucket.js @@ -0,0 +1,725 @@ +var supernodeKBucket; +var superNodeList; +/*Kademlia DHT K-bucket implementation as a binary tree.*/ + + if (typeof reactor == "undefined" || !reactor) { + (function () { + + function Event(name) { + this.name = name; + this.callbacks = []; + } + Event.prototype.registerCallback = function (callback) { + this.callbacks.push(callback); + }; + + function Reactor() { + this.events = {}; + } + + Reactor.prototype.registerEvent = function (eventName) { + var event = new Event(eventName); + this.events[eventName] = event; + }; + + Reactor.prototype.dispatchEvent = function (eventName, eventArgs) { + this.events[eventName].callbacks.forEach(function (callback) { + callback(eventArgs); + }); + }; + + Reactor.prototype.addEventListener = function (eventName, callback) { + this.events[eventName].registerCallback(callback); + }; + + window.reactor = new Reactor(); + + })(); +} + +reactor.registerEvent('added'); +reactor.addEventListener('added', function (someObject) { + console.log('Added fired with data ' + someObject); +}); + +reactor.registerEvent('removed'); +reactor.addEventListener('removed', function (someObject) { + console.log('Removed fired with data ' + someObject); +}); + +reactor.registerEvent('updated'); +reactor.addEventListener('updated', function (someObject) { + console.log('Updated fired with data ' + someObject); +}); + +reactor.registerEvent('bucket_full'); +reactor.addEventListener('bucket_full', function (someObject) { + console.log('Bucket full ' + someObject); +}); + +/* +//Sample Usage +//Creating and defining the event +reactor.registerEvent('big bang'); +reactor.addEventListener('big bang', function(someObject){ +console.log('This is big bang listener yo!'+ someObject.a); +}); + +//Firing the event +reactor.dispatchEvent('big bang'); +reactor.dispatchEvent('big bang',{a:1}); +reactor.dispatchEvent('big bang',{a:55}); + +*/ + + +//Checking if existing NodeID can be used +//This first block of if will initialize the configuration of KBucket +//Add Events, Messaging between different K-Buckets, and attach relevant distributed data + + /** + * @param {Uint8Array} array1 + * @param {Uint8Array} array2 + * @return {Boolean} + */ + function arrayEquals(array1, array2) { + if (array1 === array2) { + return true + } + if (array1.length !== array2.length) { + return false + } + for (let i = 0, length = array1.length; i < length; ++i) { + if (array1[i] !== array2[i]) { + return false + } + } + return true + } + + function createNode() { + return { + contacts: [], + dontSplit: false, + left: null, + right: null + } + } + + function ensureInt8(name, val) { + if (!(val instanceof Uint8Array)) { + throw new TypeError(name + ' is not a Uint8Array') + } + } + + /** + * Implementation of a Kademlia DHT k-bucket used for storing + * contact (peer node) information. + * + * @extends EventEmitter + */ + function BuildKBucket(options = {}) { + /** + * `options`: + * `distance`: Function + * `function (firstId, secondId) { return distance }` An optional + * `distance` function that gets two `id` Uint8Arrays + * and return distance (as number) between them. + * `arbiter`: Function (Default: vectorClock arbiter) + * `function (incumbent, candidate) { return contact; }` An optional + * `arbiter` function that givent two `contact` objects with the same `id` + * returns the desired object to be used for updating the k-bucket. For + * more details, see [arbiter function](#arbiter-function). + * `localNodeId`: Uint8Array An optional Uint8Array representing the local node id. + * If not provided, a local node id will be created via `randomBytes(20)`. + * `metadata`: Object (Default: {}) Optional satellite data to include + * with the k-bucket. `metadata` property is guaranteed not be altered by, + * it is provided as an explicit container for users of k-bucket to store + * implementation-specific data. + * `numberOfNodesPerKBucket`: Integer (Default: 20) The number of nodes + * that a k-bucket can contain before being full or split. + * `numberOfNodesToPing`: Integer (Default: 3) The number of nodes to + * ping when a bucket that should not be split becomes full. KBucket will + * emit a `ping` event that contains `numberOfNodesToPing` nodes that have + * not been contacted the longest. + * + * @param {Object=} options optional + */ + + this.localNodeId = options.localNodeId || window.crypto.getRandomValues(new Uint8Array(20)) + this.numberOfNodesPerKBucket = options.numberOfNodesPerKBucket || 20 + this.numberOfNodesToPing = options.numberOfNodesToPing || 3 + this.distance = options.distance || this.distance + // use an arbiter from options or vectorClock arbiter by default + this.arbiter = options.arbiter || this.arbiter + this.metadata = Object.assign({}, options.metadata) + + ensureInt8('option.localNodeId as parameter 1', this.localNodeId) + + this.root = createNode() + + + /** + * Default arbiter function for contacts with the same id. Uses + * contact.vectorClock to select which contact to update the k-bucket with. + * Contact with larger vectorClock field will be selected. If vectorClock is + * the same, candidat will be selected. + * + * @param {Object} incumbent Contact currently stored in the k-bucket. + * @param {Object} candidate Contact being added to the k-bucket. + * @return {Object} Contact to updated the k-bucket with. + */ + this.arbiter = function (incumbent, candidate) { + return incumbent.vectorClock > candidate.vectorClock ? incumbent : candidate + } + + /** + * Default distance function. Finds the XOR + * distance between firstId and secondId. + * + * @param {Uint8Array} firstId Uint8Array containing first id. + * @param {Uint8Array} secondId Uint8Array containing second id. + * @return {Number} Integer The XOR distance between firstId + * and secondId. + */ + this.distance = function (firstId, secondId) { + let distance = 0 + let i = 0 + const min = Math.min(firstId.length, secondId.length) + const max = Math.max(firstId.length, secondId.length) + for (; i < min; ++i) { + distance = distance * 256 + (firstId[i] ^ secondId[i]) + } + for (; i < max; ++i) distance = distance * 256 + 255 + return distance + } + + /** + * Adds a contact to the k-bucket. + * + * @param {Object} contact the contact object to add + */ + this.add = function (contact) { + ensureInt8('contact.id', (contact || {}).id) + + let bitIndex = 0 + let node = this.root + + while (node.contacts === null) { + // this is not a leaf node but an inner node with 'low' and 'high' + // branches; we will check the appropriate bit of the identifier and + // delegate to the appropriate node for further processing + node = this._determineNode(node, contact.id, bitIndex++) + } + + // check if the contact already exists + const index = this._indexOf(node, contact.id) + if (index >= 0) { + this._update(node, index, contact) + return this + } + + if (node.contacts.length < this.numberOfNodesPerKBucket) { + node.contacts.push(contact) + reactor.dispatchEvent('added', contact) + return this + } + + // the bucket is full + if (node.dontSplit) { + // we are not allowed to split the bucket + // we need to ping the first this.numberOfNodesToPing + // in order to determine if they are alive + // only if one of the pinged nodes does not respond, can the new contact + // be added (this prevents DoS flodding with new invalid contacts) + reactor.dispatchEvent('bucket_full', {1: node.contacts.slice(0, this.numberOfNodesToPing),2: contact}) + return this + } + + this._split(node, bitIndex) + return this.add(contact) + } + + /** + * Get the n closest contacts to the provided node id. "Closest" here means: + * closest according to the XOR metric of the contact node id. + * + * @param {Uint8Array} id Contact node id + * @param {Number=} n Integer (Default: Infinity) The maximum number of + * closest contacts to return + * @return {Array} Array Maximum of n closest contacts to the node id + */ + this.closest = function (id, n = Infinity) { + ensureInt8('id', id) + + if ((!Number.isInteger(n) && n !== Infinity) || n <= 0) { + throw new TypeError('n is not positive number') + } + + let contacts = [] + + for (let nodes = [this.root], bitIndex = 0; nodes.length > 0 && contacts.length < n;) { + const node = nodes.pop() + if (node.contacts === null) { + const detNode = this._determineNode(node, id, bitIndex++) + nodes.push(node.left === detNode ? node.right : node.left) + nodes.push(detNode) + } else { + contacts = contacts.concat(node.contacts) + } + } + + return contacts + .map(a => [this.distance(a.id, id), a]) + .sort((a, b) => a[0] - b[0]) + .slice(0, n) + .map(a => a[1]) + } + + /** + * Counts the total number of contacts in the tree. + * + * @return {Number} The number of contacts held in the tree + */ + this.count = function () { + // return this.toArray().length + let count = 0 + for (const nodes = [this.root]; nodes.length > 0;) { + const node = nodes.pop() + if (node.contacts === null) nodes.push(node.right, node.left) + else count += node.contacts.length + } + return count + } + + /** + * Determines whether the id at the bitIndex is 0 or 1. + * Return left leaf if `id` at `bitIndex` is 0, right leaf otherwise + * + * @param {Object} node internal object that has 2 leafs: left and right + * @param {Uint8Array} id Id to compare localNodeId with. + * @param {Number} bitIndex Integer (Default: 0) The bit index to which bit + * to check in the id Uint8Array. + * @return {Object} left leaf if id at bitIndex is 0, right leaf otherwise. + */ + this._determineNode = function (node, id, bitIndex) { + // *NOTE* remember that id is a Uint8Array and has granularity of + // bytes (8 bits), whereas the bitIndex is the bit index (not byte) + + // id's that are too short are put in low bucket (1 byte = 8 bits) + // (bitIndex >> 3) finds how many bytes the bitIndex describes + // bitIndex % 8 checks if we have extra bits beyond byte multiples + // if number of bytes is <= no. of bytes described by bitIndex and there + // are extra bits to consider, this means id has less bits than what + // bitIndex describes, id therefore is too short, and will be put in low + // bucket + const bytesDescribedByBitIndex = bitIndex >> 3 + const bitIndexWithinByte = bitIndex % 8 + if ((id.length <= bytesDescribedByBitIndex) && (bitIndexWithinByte !== 0)) { + return node.left + } + + const byteUnderConsideration = id[bytesDescribedByBitIndex] + + // byteUnderConsideration is an integer from 0 to 255 represented by 8 bits + // where 255 is 11111111 and 0 is 00000000 + // in order to find out whether the bit at bitIndexWithinByte is set + // we construct (1 << (7 - bitIndexWithinByte)) which will consist + // of all bits being 0, with only one bit set to 1 + // for example, if bitIndexWithinByte is 3, we will construct 00010000 by + // (1 << (7 - 3)) -> (1 << 4) -> 16 + if (byteUnderConsideration & (1 << (7 - bitIndexWithinByte))) { + return node.right + } + + return node.left + } + + /** + * Get a contact by its exact ID. + * If this is a leaf, loop through the bucket contents and return the correct + * contact if we have it or null if not. If this is an inner node, determine + * which branch of the tree to traverse and repeat. + * + * @param {Uint8Array} id The ID of the contact to fetch. + * @return {Object|Null} The contact if available, otherwise null + */ + this.get = function (id) { + ensureInt8('id', id) + + let bitIndex = 0 + + let node = this.root + while (node.contacts === null) { + node = this._determineNode(node, id, bitIndex++) + } + + // index of uses contact id for matching + const index = this._indexOf(node, id) + return index >= 0 ? node.contacts[index] : null + } + + /** + * Returns the index of the contact with provided + * id if it exists, returns -1 otherwise. + * + * @param {Object} node internal object that has 2 leafs: left and right + * @param {Uint8Array} id Contact node id. + * @return {Number} Integer Index of contact with provided id if it + * exists, -1 otherwise. + */ + this._indexOf = function (node, id) { + for (let i = 0; i < node.contacts.length; ++i) { + if (arrayEquals(node.contacts[i].id, id)) return i + } + + return -1 + } + + /** + * Removes contact with the provided id. + * + * @param {Uint8Array} id The ID of the contact to remove. + * @return {Object} The k-bucket itself. + */ + this.remove = function (id) { + ensureInt8('the id as parameter 1', id) + + let bitIndex = 0 + let node = this.root + + while (node.contacts === null) { + node = this._determineNode(node, id, bitIndex++) + } + + const index = this._indexOf(node, id) + if (index >= 0) { + const contact = node.contacts.splice(index, 1)[0] + reactor.dispatchEvent('removed', contact) + } + + return this + } + + /** + * Splits the node, redistributes contacts to the new nodes, and marks the + * node that was split as an inner node of the binary tree of nodes by + * setting this.root.contacts = null + * + * @param {Object} node node for splitting + * @param {Number} bitIndex the bitIndex to which byte to check in the + * Uint8Array for navigating the binary tree + */ + this._split = function (node, bitIndex) { + node.left = createNode() + node.right = createNode() + + // redistribute existing contacts amongst the two newly created nodes + for (const contact of node.contacts) { + this._determineNode(node, contact.id, bitIndex).contacts.push(contact) + } + + node.contacts = null // mark as inner tree node + + // don't split the "far away" node + // we check where the local node would end up and mark the other one as + // "dontSplit" (i.e. "far away") + const detNode = this._determineNode(node, this.localNodeId, bitIndex) + const otherNode = node.left === detNode ? node.right : node.left + otherNode.dontSplit = true + } + + /** + * Returns all the contacts contained in the tree as an array. + * If this is a leaf, return a copy of the bucket. `slice` is used so that we + * don't accidentally leak an internal reference out that might be + * accidentally misused. If this is not a leaf, return the union of the low + * and high branches (themselves also as arrays). + * + * @return {Array} All of the contacts in the tree, as an array + */ + this.toArray = function () { + let result = [] + for (const nodes = [this.root]; nodes.length > 0;) { + const node = nodes.pop() + if (node.contacts === null) nodes.push(node.right, node.left) + else result = result.concat(node.contacts) + } + return result + } + + /** + * Updates the contact selected by the arbiter. + * If the selection is our old contact and the candidate is some new contact + * then the new contact is abandoned (not added). + * If the selection is our old contact and the candidate is our old contact + * then we are refreshing the contact and it is marked as most recently + * contacted (by being moved to the right/end of the bucket array). + * If the selection is our new contact, the old contact is removed and the new + * contact is marked as most recently contacted. + * + * @param {Object} node internal object that has 2 leafs: left and right + * @param {Number} index the index in the bucket where contact exists + * (index has already been computed in a previous + * calculation) + * @param {Object} contact The contact object to update. + */ + this._update = function (node, index, contact) { + // sanity check + if (!arrayEquals(node.contacts[index].id, contact.id)) { + throw new Error('wrong index for _update') + } + + const incumbent = node.contacts[index] + + /***************Change made by Abhishek*************/ + const selection = this.arbiter(incumbent, contact) + //const selection = localbitcoinplusplus.kademlia.arbiter(incumbent, contact); + // if the selection is our old contact and the candidate is some new + // contact, then there is nothing to do + if (selection === incumbent && incumbent !== contact) return + + node.contacts.splice(index, 1) // remove old contact + node.contacts.push(selection) // add more recent contact version + /***************Change made by Abhishek*************/ + reactor.dispatchEvent('updated', { + ...incumbent, + ...selection + }) + //reactor.dispatchEvent('updated', incumbent.concat(selection)) + } + } + + +kBucketObj = { + + decodeBase58Address: function (blockchain, address) { + let k = bitjs.Base58.decode(address) + k.shift() + k.splice(-4, 4) + return Crypto.util.bytesToHex(k) + }, + launchKBucket: function() { + return new Promise((resolve, reject)=>{ + try { + //const master_flo_pubKey = localbitcoinplusplus.master_configurations.masterFLOPubKey; + const master_flo_addr = adminID; + const SuKBucketId = this.floIdToKbucketId(crypto, master_flo_addr); + const SukbOptions = { localNodeId: SuKBucketId } + supernodeKBucket = new BuildKBucket(SukbOptions); + for(var i=0; i{ + return new Promise((resolve, reject)=>{ + try { + let flo_id = bitjs.pubkey2address(pubKey); + let kname = `SKBucket_${pubKey}`; + const KBucketId = this.floIdToKbucketId(crypto, flo_id) + const kbOptions = { localNodeId: KBucketId } + window[kname] = new BuildKBucket(kbOptions); + resolve(true); + } catch (error) { + reject(error); + } + }) + }) + }, + addContact: function (id, floID, KB=supernodeKBucket) { + const contact = { + id: id, + floID: floID + }; + KB.add(contact) + }, + addNewUserNodeInKbucket: function(blockchain, address, KB=supernodeKBucket) { + let decodedId = address; + try { + decodedId = this.floIdToKbucketId(blockchain, address); + } catch(e) { + decodedId = address; + } + const addNewUserNode = this.addContact(decodedId, address, KB); + return {decodedId:decodedId, address:address}; + }, + floIdToKbucketId: function (blockchain, address) { + const decodedId = this.decodeBase58Address(blockchain, address); + const nodeIdBigInt = new BigInteger(decodedId, 16); + const nodeIdBytes = nodeIdBigInt.toByteArrayUnsigned(); + const nodeIdNewInt8Array = new Uint8Array(nodeIdBytes); + return nodeIdNewInt8Array; + }, + arbiter: function (incumbent, candidate) { + // we create a new object so that our selection is guaranteed to replace + // the incumbent + const merged = { + id: incumbent.id, // incumbent.id === candidate.id within an arbiter + data: incumbent.data + } + + Object.keys(candidate.data).forEach(workerNodeId => { + merged.data[workerNodeId] = candidate.data[workerNodeId]; + }) + + return merged; + }, + newBase64DiscoverId: function (pubKey) { + let pubKeyBytes = Crypto.util.hexToBytes(pubKey); + return Crypto.util.bytesToBase64(pubKeyBytes); + }, + restoreKbucket: function(flo_addr, blockchain="FLO_TEST", KB=KBucket) { + return new Promise((resolve, reject)=>{ + readAllDB('kBucketStore') + .then(dbObject => { + if (typeof dbObject=="object") { + let su_flo_addr_array = localbitcoinplusplus.master_configurations.supernodesPubKeys + .map(pubk=>bitjs.pubkey2address(pubk)); + // Prevent supernode to re-added in kbucket + dbObject + .filter(f=>!su_flo_addr_array.includes(f.data.id)) + .map(dbObj=>{ + this.addNewUserNodeInKbucket(blockchain, flo_addr, dbObj.data, KB); + }); + } else { + reject(`Failed to restore kBucket.`); + } + resolve(dbObject); + }); + }) + }, + restoreSupernodeKBucket: function() { + return new Promise((resolve, reject)=>{ + const supernodeSeeds = localbitcoinplusplus.master_configurations.supernodeSeeds; + if (typeof supernodeSeeds !== "object") reject("Failed to get supernode seeds."); + let supernodeSeedsObj = JSON.parse(supernodeSeeds); + + Object.entries(supernodeSeedsObj).map(seedObj=>{ + let kbuck = this.addNewUserNodeInKbucket(crypto, seedObj[1].kbucketId, + { id: seedObj[1].kbucketId }, supernodeKBucket); + }); + + resolve(true); + }) + }, + updateClosestSupernodeSeeds: function(flo_addr) { + return new Promise(async (resolve, reject) => { + await removeAllinDB('myClosestSupernodes'); + let nearestSupernodeAddresslist = await this.addClosestSupernodeInDB(flo_addr); + nearestSupernodeAddresslist.map((nearestSupernodeAddress, index)=>{ + updateinDB('myClosestSupernodes', { + id: index+1, + ip: nearestSupernodeAddress.ip, + port: nearestSupernodeAddress.port, + trader_flo_address: nearestSupernodeAddress.kbucketId, + is_live: null + }).then(updatedClosestSupernodes=>{ + readAllDB('myClosestSupernodes').then(nearestSupernodeAddresslist=>{ + showMessage(`INFO: Updated closest supernodes list successfully.`); + resolve(nearestSupernodeAddresslist); + }); + }); + }); + }); + }, + getSupernodeSeed: function (flo_addr) { + return new Promise(async (resolve, reject) => { + let nearestSupernodeAddresslist = await readAllDB('myClosestSupernodes'); + if (nearestSupernodeAddresslist.length<1) { + nearestSupernodeAddresslist = await this.updateClosestSupernodeSeeds(flo_addr); + } + resolve(nearestSupernodeAddresslist); + }); + }, + isNodePresentInMyKbucket: function(flo_id, KB=KBucket) { + return new Promise((resolve, reject)=>{ + let kArray = KB.toArray(); + let kArrayFloIds = kArray.map(k=>k.data.id); + if (kArrayFloIds.includes(flo_id)) { + resolve(true); + } else { + reject(false); + } + }); + }, + determineClosestSupernode: function(flo_addr="", n=1, KB=supernodeKBucket, su="") { + return new Promise((resolve, reject)=>{ + let msg = ``; + if (typeof supernodeKBucket !== "object") { + msg = `ERROR: Supernode KBucket not found.`; + showMessage(msg); + reject(msg); + return false; + } + + if (su.length>0) { + try { + let closestSupernodeMasterList = supernodeKBucket.closest(supernodeKBucket.localNodeId); + const index = closestSupernodeMasterList.findIndex(f=>f.data.id==su); + let tail = closestSupernodeMasterList.splice(0, index); + const newClosestSupernodeMasterList = closestSupernodeMasterList.concat(tail); + resolve(newClosestSupernodeMasterList); + return true; + } catch (error) { + reject(error); + } + return false; + } + + try { + if(flo_addr.length < 0) { + showMessage(`WARNING: No Flo Id provided to determine closest Supenode.`); + return; + } + let isFloIdUint8 = flo_addr instanceof Uint8Array; + if (!isFloIdUint8) { + flo_addr = this.floIdToKbucketId(crypto, flo_addr); + } + const closestSupernode = supernodeKBucket.closest(flo_addr, n); + resolve(closestSupernode); + return true; + } catch (error) { + showMessage(error); + reject(error); + return false; + } + }) + }, + + addClosestSupernodeInDB: function(flo_addr, KB=KBucket) { + return new Promise(async (resolve, reject)=>{ + const supernodeSeeds = localbitcoinplusplus.master_configurations.supernodeSeeds; + if (typeof supernodeSeeds !== "object") reject("Failed to get supernode seeds."); + let supernodeSeedsObj = JSON.parse(supernodeSeeds); + + Object.entries(supernodeSeedsObj).map(seedObj=>{ + console.log(seedObj); + this.addNewUserNodeInKbucketAndDB( + crypto, seedObj[1].kbucketId, + { id: seedObj[1].kbucketId }); + }); + + let primarySu = await this.determineClosestSupernode(flo_addr); + let nearestSupernode = await this.determineClosestSupernode(flo_addr="", n=1, supernodeKBucket, primarySu[0].data.id); + let nearestSupernodeIds = nearestSupernode.map(f=>f.data.id); + let supernodeSeedsArray = Object.values(supernodeSeedsObj) + .filter(seed=>nearestSupernodeIds.includes(seed.kbucketId)) + .sort(function(a, b){ + return nearestSupernodeIds.indexOf(a.kbucketId) - nearestSupernodeIds.indexOf(b.kbucketId); + }); + + if (supernodeSeedsArray.length>0) { + resolve(supernodeSeedsArray); + } else { + reject(false); + } + }) + } +} diff --git a/app/web/profile.js b/app/web/profile.js index 66e2406..c6fa1b6 100644 --- a/app/web/profile.js +++ b/app/web/profile.js @@ -1,7 +1,7 @@ var profileWebsocket, selfWebsocket; var profiles; -var floID, privKey, selfID, serverPass; +var floID, privKey, selfID; function viewProfile(){ if( sessionStorage.profiles === undefined || sessionStorage.privKey === undefined || sessionStorage.selfID === undefined || sessionStorage.serverPass === undefined){ @@ -11,9 +11,8 @@ function viewProfile(){ } profiles = JSON.parse(sessionStorage.profiles); console.log(profiles); - privKey = sessionStorage.privKey; + privKey = encrypt.retrieveShamirSecret(JSON.parse(sessionStorage.privKey)); selfID = sessionStorage.selfID; - serverPass = sessionStorage.serverPass; var url = new URL(window.location.href); floID = url.searchParams.get("floID"); listProfiles(); @@ -76,7 +75,7 @@ function displayTweetFromIDB(floID){ obs.openCursor().onsuccess = function(event) { var cursor = event.target.result; if(cursor) { - console.log(cursor.value) + //console.log(cursor.value) if(cursor.value.floID == floID) createTweetElement(floID,cursor.value.time,cursor.value.data); cursor.continue(); @@ -196,8 +195,8 @@ function initselfWebSocket(){ selfWebsocket = new WebSocket("ws://"+location.host+"/ws"); selfWebsocket.onopen = function(evt){ console.log("Connecting"); - var pass = sessionStorage.serverPass; - selfWebsocket.send("$"+pass); + var serverPass = encrypt.retrieveShamirSecret(JSON.parse(sessionStorage.serverPass)); + selfWebsocket.send("$"+serverPass); }; selfWebsocket.onclose = function(evt){ console.log("DISCONNECTED"); diff --git a/app/web/registerID.js b/app/web/registerID.js index 8851c7b..8bc6bf3 100755 --- a/app/web/registerID.js +++ b/app/web/registerID.js @@ -6079,6 +6079,580 @@ Bitcoin.Util = { })("secp256k1"); + // secrets.js - by Alexander Stetsyuk - released under MIT License + (function (exports, global) { + var defaults = { + bits: 8, // default number of bits + radix: 16, // work with HEX by default + minBits: 3, + maxBits: 20, // this permits 1,048,575 shares, though going this high is NOT recommended in JS! + + bytesPerChar: 2, + maxBytesPerChar: 6, // Math.pow(256,7) > Math.pow(2,53) + + // Primitive polynomials (in decimal form) for Galois Fields GF(2^n), for 2 <= n <= 30 + // The index of each term in the array corresponds to the n for that polynomial + // i.e. to get the polynomial for n=16, use primitivePolynomials[16] + primitivePolynomials: [null, null, 1, 3, 3, 5, 3, 3, 29, 17, 9, 5, 83, 27, 43, 3, 45, 9, 39, 39, + 9, 5, 3, 33, 27, 9, 71, 39, 9, 5, 83 + ], + + // warning for insecure PRNG + warning: 'WARNING:\nA secure random number generator was not found.\nUsing Math.random(), which is NOT cryptographically strong!' + }; + + // Protected settings object + var config = {}; + + /** @expose **/ + exports.getConfig = function () { + return { + 'bits': config.bits, + 'unsafePRNG': config.unsafePRNG + }; + }; + + function init(bits) { + if (bits && (typeof bits !== 'number' || bits % 1 !== 0 || bits < defaults.minBits || bits > + defaults.maxBits)) { + throw new Error('Number of bits must be an integer between ' + defaults.minBits + ' and ' + + defaults.maxBits + ', inclusive.') + } + + config.radix = defaults.radix; + config.bits = bits || defaults.bits; + config.size = Math.pow(2, config.bits); + config.max = config.size - 1; + + // Construct the exp and log tables for multiplication. + var logs = [], + exps = [], + x = 1, + primitive = defaults.primitivePolynomials[config.bits]; + for (var i = 0; i < config.size; i++) { + exps[i] = x; + logs[x] = i; + x <<= 1; + if (x >= config.size) { + x ^= primitive; + x &= config.max; + } + } + + config.logs = logs; + config.exps = exps; + }; + + /** @expose **/ + exports.init = init; + + function isInited() { + if (!config.bits || !config.size || !config.max || !config.logs || !config.exps || config.logs.length !== + config.size || config.exps.length !== config.size) { + return false; + } + return true; + }; + + // Returns a pseudo-random number generator of the form function(bits){} + // which should output a random string of 1's and 0's of length `bits` + function getRNG() { + var randomBits, crypto; + + function construct(bits, arr, radix, size) { + var str = '', + i = 0, + len = arr.length - 1; + while (i < len || (str.length < bits)) { + str += padLeft(parseInt(arr[i], radix).toString(2), size); + i++; + } + str = str.substr(-bits); + if ((str.match(/0/g) || []).length === str.length) { // all zeros? + return null; + } else { + return str; + } + } + + // node.js crypto.randomBytes() + if (typeof require === 'function' && (crypto = require('crypto')) && (randomBits = crypto[ + 'randomBytes'])) { + return function (bits) { + var bytes = Math.ceil(bits / 8), + str = null; + + while (str === null) { + str = construct(bits, randomBits(bytes).toString('hex'), 16, 4); + } + return str; + } + } + + // browsers with window.crypto.getRandomValues() + if (global['crypto'] && typeof global['crypto']['getRandomValues'] === 'function' && typeof global[ + 'Uint32Array'] === 'function') { + crypto = global['crypto']; + return function (bits) { + var elems = Math.ceil(bits / 32), + str = null, + arr = new global['Uint32Array'](elems); + + while (str === null) { + crypto['getRandomValues'](arr); + str = construct(bits, arr, 10, 32); + } + + return str; + } + } + + // A totally insecure RNG!!! (except in Safari) + // Will produce a warning every time it is called. + config.unsafePRNG = true; + warn(); + + var bitsPerNum = 32; + var max = Math.pow(2, bitsPerNum) - 1; + return function (bits) { + var elems = Math.ceil(bits / bitsPerNum); + var arr = [], + str = null; + while (str === null) { + for (var i = 0; i < elems; i++) { + arr[i] = Math.floor(Math.random() * max + 1); + } + str = construct(bits, arr, 10, bitsPerNum); + } + return str; + }; + }; + + // Warn about using insecure rng. + // Called when Math.random() is being used. + function warn() { + global['console']['warn'](defaults.warning); + if (typeof global['alert'] === 'function' && config.alert) { + global['alert'](defaults.warning); + } + } + + // Set the PRNG to use. If no RNG function is supplied, pick a default using getRNG() + /** @expose **/ + exports.setRNG = function (rng, alert) { + if (!isInited()) { + this.init(); + } + config.unsafePRNG = false; + rng = rng || getRNG(); + + // test the RNG (5 times) + if (typeof rng !== 'function' || typeof rng(config.bits) !== 'string' || !parseInt(rng(config.bits), + 2) || rng(config.bits).length > config.bits || rng(config.bits).length < config.bits) { + throw new Error( + "Random number generator is invalid. Supply an RNG of the form function(bits){} that returns a string containing 'bits' number of random 1's and 0's." + ) + } else { + config.rng = rng; + } + config.alert = !!alert; + + return !!config.unsafePRNG; + }; + + function isSetRNG() { + return typeof config.rng === 'function'; + }; + + // Generates a random bits-length number string using the PRNG + /** @expose **/ + exports.random = function (bits) { + if (!isSetRNG()) { + this.setRNG(); + } + + if (typeof bits !== 'number' || bits % 1 !== 0 || bits < 2) { + throw new Error('Number of bits must be an integer greater than 1.') + } + + if (config.unsafePRNG) { + warn(); + } + return bin2hex(config.rng(bits)); + } + + // Divides a `secret` number String str expressed in radix `inputRadix` (optional, default 16) + // into `numShares` shares, each expressed in radix `outputRadix` (optional, default to `inputRadix`), + // requiring `threshold` number of shares to reconstruct the secret. + // Optionally, zero-pads the secret to a length that is a multiple of padLength before sharing. + /** @expose **/ + exports.share = function (secret, numShares, threshold, padLength, withoutPrefix) { + if (!isInited()) { + this.init(); + } + if (!isSetRNG()) { + this.setRNG(); + } + + padLength = padLength || 0; + + if (typeof secret !== 'string') { + throw new Error('Secret must be a string.'); + } + if (typeof numShares !== 'number' || numShares % 1 !== 0 || numShares < 2) { + throw new Error('Number of shares must be an integer between 2 and 2^bits-1 (' + config.max + + '), inclusive.') + } + if (numShares > config.max) { + var neededBits = Math.ceil(Math.log(numShares + 1) / Math.LN2); + throw new Error('Number of shares must be an integer between 2 and 2^bits-1 (' + config.max + + '), inclusive. To create ' + numShares + ' shares, use at least ' + neededBits + + ' bits.') + } + if (typeof threshold !== 'number' || threshold % 1 !== 0 || threshold < 2) { + throw new Error('Threshold number of shares must be an integer between 2 and 2^bits-1 (' + + config.max + '), inclusive.'); + } + if (threshold > config.max) { + var neededBits = Math.ceil(Math.log(threshold + 1) / Math.LN2); + throw new Error('Threshold number of shares must be an integer between 2 and 2^bits-1 (' + + config.max + '), inclusive. To use a threshold of ' + threshold + + ', use at least ' + neededBits + ' bits.'); + } + if (typeof padLength !== 'number' || padLength % 1 !== 0) { + throw new Error('Zero-pad length must be an integer greater than 1.'); + } + + if (config.unsafePRNG) { + warn(); + } + + secret = '1' + hex2bin(secret); // append a 1 so that we can preserve the correct number of leading zeros in our secret + secret = split(secret, padLength); + var x = new Array(numShares), + y = new Array(numShares); + for (var i = 0, len = secret.length; i < len; i++) { + var subShares = this._getShares(secret[i], numShares, threshold); + for (var j = 0; j < numShares; j++) { + x[j] = x[j] || subShares[j].x.toString(config.radix); + y[j] = padLeft(subShares[j].y.toString(2)) + (y[j] ? y[j] : ''); + } + } + var padding = config.max.toString(config.radix).length; + if (withoutPrefix) { + for (var i = 0; i < numShares; i++) { + x[i] = bin2hex(y[i]); + } + } else { + for (var i = 0; i < numShares; i++) { + x[i] = config.bits.toString(36).toUpperCase() + padLeft(x[i], padding) + bin2hex(y[i]); + } + } + + return x; + }; + + // This is the basic polynomial generation and evaluation function + // for a `config.bits`-length secret (NOT an arbitrary length) + // Note: no error-checking at this stage! If `secrets` is NOT + // a NUMBER less than 2^bits-1, the output will be incorrect! + /** @expose **/ + exports._getShares = function (secret, numShares, threshold) { + var shares = []; + var coeffs = [secret]; + + for (var i = 1; i < threshold; i++) { + coeffs[i] = parseInt(config.rng(config.bits), 2); + } + for (var i = 1, len = numShares + 1; i < len; i++) { + shares[i - 1] = { + x: i, + y: horner(i, coeffs) + } + } + return shares; + }; + + // Polynomial evaluation at `x` using Horner's Method + // TODO: this can possibly be sped up using other methods + // NOTE: fx=fx * x + coeff[i] -> exp(log(fx) + log(x)) + coeff[i], + // so if fx===0, just set fx to coeff[i] because + // using the exp/log form will result in incorrect value + function horner(x, coeffs) { + var logx = config.logs[x]; + var fx = 0; + for (var i = coeffs.length - 1; i >= 0; i--) { + if (fx === 0) { + fx = coeffs[i]; + continue; + } + fx = config.exps[(logx + config.logs[fx]) % config.max] ^ coeffs[i]; + } + return fx; + }; + + function inArray(arr, val) { + for (var i = 0, len = arr.length; i < len; i++) { + if (arr[i] === val) { + return true; + } + } + return false; + }; + + function processShare(share) { + + var bits = parseInt(share[0], 36); + if (bits && (typeof bits !== 'number' || bits % 1 !== 0 || bits < defaults.minBits || bits > + defaults.maxBits)) { + throw new Error('Number of bits must be an integer between ' + defaults.minBits + ' and ' + + defaults.maxBits + ', inclusive.') + } + + var max = Math.pow(2, bits) - 1; + var idLength = max.toString(config.radix).length; + + var id = parseInt(share.substr(1, idLength), config.radix); + if (typeof id !== 'number' || id % 1 !== 0 || id < 1 || id > max) { + throw new Error('Share id must be an integer between 1 and ' + config.max + ', inclusive.'); + } + share = share.substr(idLength + 1); + if (!share.length) { + throw new Error('Invalid share: zero-length share.') + } + return { + 'bits': bits, + 'id': id, + 'value': share + }; + }; + + /** @expose **/ + exports._processShare = processShare; + + // Protected method that evaluates the Lagrange interpolation + // polynomial at x=`at` for individual config.bits-length + // segments of each share in the `shares` Array. + // Each share is expressed in base `inputRadix`. The output + // is expressed in base `outputRadix' + function combine(at, shares) { + var setBits, share, x = [], + y = [], + result = '', + idx; + + for (var i = 0, len = shares.length; i < len; i++) { + share = processShare(shares[i]); + if (typeof setBits === 'undefined') { + setBits = share['bits']; + } else if (share['bits'] !== setBits) { + throw new Error('Mismatched shares: Different bit settings.') + } + + if (config.bits !== setBits) { + init(setBits); + } + + if (inArray(x, share['id'])) { // repeated x value? + continue; + } + + idx = x.push(share['id']) - 1; + share = split(hex2bin(share['value'])); + for (var j = 0, len2 = share.length; j < len2; j++) { + y[j] = y[j] || []; + y[j][idx] = share[j]; + } + } + + for (var i = 0, len = y.length; i < len; i++) { + result = padLeft(lagrange(at, x, y[i]).toString(2)) + result; + } + + if (at === 0) { // reconstructing the secret + var idx = result.indexOf('1'); //find the first 1 + return bin2hex(result.slice(idx + 1)); + } else { // generating a new share + return bin2hex(result); + } + }; + + // Combine `shares` Array into the original secret + /** @expose **/ + exports.combine = function (shares) { + return combine(0, shares); + }; + + // Generate a new share with id `id` (a number between 1 and 2^bits-1) + // `id` can be a Number or a String in the default radix (16) + /** @expose **/ + exports.newShare = function (id, shares) { + if (typeof id === 'string') { + id = parseInt(id, config.radix); + } + + var share = processShare(shares[0]); + var max = Math.pow(2, share['bits']) - 1; + + if (typeof id !== 'number' || id % 1 !== 0 || id < 1 || id > max) { + throw new Error('Share id must be an integer between 1 and ' + config.max + ', inclusive.'); + } + + var padding = max.toString(config.radix).length; + return config.bits.toString(36).toUpperCase() + padLeft(id.toString(config.radix), padding) + + combine(id, shares); + }; + + // Evaluate the Lagrange interpolation polynomial at x = `at` + // using x and y Arrays that are of the same length, with + // corresponding elements constituting points on the polynomial. + function lagrange(at, x, y) { + var sum = 0, + product, + i, j; + + for (var i = 0, len = x.length; i < len; i++) { + if (!y[i]) { + continue; + } + + product = config.logs[y[i]]; + for (var j = 0; j < len; j++) { + if (i === j) { + continue; + } + if (at === x[j]) { // happens when computing a share that is in the list of shares used to compute it + product = -1; // fix for a zero product term, after which the sum should be sum^0 = sum, not sum^1 + break; + } + product = (product + config.logs[at ^ x[j]] - config.logs[x[i] ^ x[j]] + config.max /* to make sure it's not negative */ ) % + config.max; + } + + sum = product === -1 ? sum : sum ^ config.exps[product]; // though exps[-1]= undefined and undefined ^ anything = anything in chrome, this behavior may not hold everywhere, so do the check + } + return sum; + }; + + /** @expose **/ + exports._lagrange = lagrange; + + // Splits a number string `bits`-length segments, after first + // optionally zero-padding it to a length that is a multiple of `padLength. + // Returns array of integers (each less than 2^bits-1), with each element + // representing a `bits`-length segment of the input string from right to left, + // i.e. parts[0] represents the right-most `bits`-length segment of the input string. + function split(str, padLength) { + if (padLength) { + str = padLeft(str, padLength) + } + var parts = []; + for (var i = str.length; i > config.bits; i -= config.bits) { + parts.push(parseInt(str.slice(i - config.bits, i), 2)); + } + parts.push(parseInt(str.slice(0, i), 2)); + return parts; + }; + + // Pads a string `str` with zeros on the left so that its length is a multiple of `bits` + function padLeft(str, bits) { + bits = bits || config.bits + var missing = str.length % bits; + return (missing ? new Array(bits - missing + 1).join('0') : '') + str; + }; + + function hex2bin(str) { + var bin = '', + num; + for (var i = str.length - 1; i >= 0; i--) { + num = parseInt(str[i], 16) + if (isNaN(num)) { + throw new Error('Invalid hex character.') + } + bin = padLeft(num.toString(2), 4) + bin; + } + return bin; + } + + function bin2hex(str) { + var hex = '', + num; + str = padLeft(str, 4); + for (var i = str.length; i >= 4; i -= 4) { + num = parseInt(str.slice(i - 4, i), 2); + if (isNaN(num)) { + throw new Error('Invalid binary character.') + } + hex = num.toString(16) + hex; + } + return hex; + } + + // Converts a given UTF16 character string to the HEX representation. + // Each character of the input string is represented by + // `bytesPerChar` bytes in the output string. + /** @expose **/ + exports.str2hex = function (str, bytesPerChar) { + if (typeof str !== 'string') { + throw new Error('Input must be a character string.'); + } + bytesPerChar = bytesPerChar || defaults.bytesPerChar; + + if (typeof bytesPerChar !== 'number' || bytesPerChar % 1 !== 0 || bytesPerChar < 1 || + bytesPerChar > defaults.maxBytesPerChar) { + throw new Error('Bytes per character must be an integer between 1 and ' + defaults.maxBytesPerChar + + ', inclusive.') + } + + var hexChars = 2 * bytesPerChar; + var max = Math.pow(16, hexChars) - 1; + var out = '', + num; + for (var i = 0, len = str.length; i < len; i++) { + num = str[i].charCodeAt(); + if (isNaN(num)) { + throw new Error('Invalid character: ' + str[i]); + } else if (num > max) { + var neededBytes = Math.ceil(Math.log(num + 1) / Math.log(256)); + throw new Error('Invalid character code (' + num + + '). Maximum allowable is 256^bytes-1 (' + max + + '). To convert this character, use at least ' + neededBytes + ' bytes.') + } else { + out = padLeft(num.toString(16), hexChars) + out; + } + } + return out; + }; + + // Converts a given HEX number string to a UTF16 character string. + /** @expose **/ + exports.hex2str = function (str, bytesPerChar) { + if (typeof str !== 'string') { + throw new Error('Input must be a hexadecimal string.'); + } + bytesPerChar = bytesPerChar || defaults.bytesPerChar; + + if (typeof bytesPerChar !== 'number' || bytesPerChar % 1 !== 0 || bytesPerChar < 1 || + bytesPerChar > defaults.maxBytesPerChar) { + throw new Error('Bytes per character must be an integer between 1 and ' + defaults.maxBytesPerChar + + ', inclusive.') + } + + var hexChars = 2 * bytesPerChar; + var out = ''; + str = padLeft(str, hexChars); + for (var i = 0, len = str.length; i < len; i += hexChars) { + out = String.fromCharCode(parseInt(str.slice(i, i + hexChars), 16)) + out; + } + return out; + }; + + // by default, initialize without an RNG + exports.init(); + })(typeof module !== 'undefined' && module['exports'] ? module['exports'] : (window['shamirSecretShare'] = {}), + typeof global !== 'undefined' ? global : window); + + //Script for AJAX, and register functions function ajax(method, uri){ var request = new XMLHttpRequest(); From c570c02ec6ea964a40536bb0bf1abf936b423592 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Sun, 14 Jul 2019 21:29:12 +0530 Subject: [PATCH 02/11] moved kbucket to app.js --- app/web/app.js | 728 ++++++++++++++++++++++++++++++++++++++++++++- app/web/kBucket.js | 725 -------------------------------------------- 2 files changed, 727 insertions(+), 726 deletions(-) delete mode 100644 app/web/kBucket.js diff --git a/app/web/app.js b/app/web/app.js index f2935d6..82f9af7 100644 --- a/app/web/app.js +++ b/app/web/app.js @@ -272,4 +272,730 @@ function listProfiles(){ profileList.appendChild(element); } //document.getElementById("profileInfo").style.display = "none"; -} \ No newline at end of file +} + +var supernodeKBucket; +var superNodeList; +/*Kademlia DHT K-bucket implementation as a binary tree.*/ + + if (typeof reactor == "undefined" || !reactor) { + (function () { + + function Event(name) { + this.name = name; + this.callbacks = []; + } + Event.prototype.registerCallback = function (callback) { + this.callbacks.push(callback); + }; + + function Reactor() { + this.events = {}; + } + + Reactor.prototype.registerEvent = function (eventName) { + var event = new Event(eventName); + this.events[eventName] = event; + }; + + Reactor.prototype.dispatchEvent = function (eventName, eventArgs) { + this.events[eventName].callbacks.forEach(function (callback) { + callback(eventArgs); + }); + }; + + Reactor.prototype.addEventListener = function (eventName, callback) { + this.events[eventName].registerCallback(callback); + }; + + window.reactor = new Reactor(); + + })(); +} + +reactor.registerEvent('added'); +reactor.addEventListener('added', function (someObject) { + console.log('Added fired with data ' + someObject); +}); + +reactor.registerEvent('removed'); +reactor.addEventListener('removed', function (someObject) { + console.log('Removed fired with data ' + someObject); +}); + +reactor.registerEvent('updated'); +reactor.addEventListener('updated', function (someObject) { + console.log('Updated fired with data ' + someObject); +}); + +reactor.registerEvent('bucket_full'); +reactor.addEventListener('bucket_full', function (someObject) { + console.log('Bucket full ' + someObject); +}); + +/* +//Sample Usage +//Creating and defining the event +reactor.registerEvent('big bang'); +reactor.addEventListener('big bang', function(someObject){ +console.log('This is big bang listener yo!'+ someObject.a); +}); + +//Firing the event +reactor.dispatchEvent('big bang'); +reactor.dispatchEvent('big bang',{a:1}); +reactor.dispatchEvent('big bang',{a:55}); + +*/ + + +//Checking if existing NodeID can be used +//This first block of if will initialize the configuration of KBucket +//Add Events, Messaging between different K-Buckets, and attach relevant distributed data + + /** + * @param {Uint8Array} array1 + * @param {Uint8Array} array2 + * @return {Boolean} + */ + function arrayEquals(array1, array2) { + if (array1 === array2) { + return true + } + if (array1.length !== array2.length) { + return false + } + for (let i = 0, length = array1.length; i < length; ++i) { + if (array1[i] !== array2[i]) { + return false + } + } + return true + } + + function createNode() { + return { + contacts: [], + dontSplit: false, + left: null, + right: null + } + } + + function ensureInt8(name, val) { + if (!(val instanceof Uint8Array)) { + throw new TypeError(name + ' is not a Uint8Array') + } + } + + /** + * Implementation of a Kademlia DHT k-bucket used for storing + * contact (peer node) information. + * + * @extends EventEmitter + */ + function BuildKBucket(options = {}) { + /** + * `options`: + * `distance`: Function + * `function (firstId, secondId) { return distance }` An optional + * `distance` function that gets two `id` Uint8Arrays + * and return distance (as number) between them. + * `arbiter`: Function (Default: vectorClock arbiter) + * `function (incumbent, candidate) { return contact; }` An optional + * `arbiter` function that givent two `contact` objects with the same `id` + * returns the desired object to be used for updating the k-bucket. For + * more details, see [arbiter function](#arbiter-function). + * `localNodeId`: Uint8Array An optional Uint8Array representing the local node id. + * If not provided, a local node id will be created via `randomBytes(20)`. + * `metadata`: Object (Default: {}) Optional satellite data to include + * with the k-bucket. `metadata` property is guaranteed not be altered by, + * it is provided as an explicit container for users of k-bucket to store + * implementation-specific data. + * `numberOfNodesPerKBucket`: Integer (Default: 20) The number of nodes + * that a k-bucket can contain before being full or split. + * `numberOfNodesToPing`: Integer (Default: 3) The number of nodes to + * ping when a bucket that should not be split becomes full. KBucket will + * emit a `ping` event that contains `numberOfNodesToPing` nodes that have + * not been contacted the longest. + * + * @param {Object=} options optional + */ + + this.localNodeId = options.localNodeId || window.crypto.getRandomValues(new Uint8Array(20)) + this.numberOfNodesPerKBucket = options.numberOfNodesPerKBucket || 20 + this.numberOfNodesToPing = options.numberOfNodesToPing || 3 + this.distance = options.distance || this.distance + // use an arbiter from options or vectorClock arbiter by default + this.arbiter = options.arbiter || this.arbiter + this.metadata = Object.assign({}, options.metadata) + + ensureInt8('option.localNodeId as parameter 1', this.localNodeId) + + this.root = createNode() + + + /** + * Default arbiter function for contacts with the same id. Uses + * contact.vectorClock to select which contact to update the k-bucket with. + * Contact with larger vectorClock field will be selected. If vectorClock is + * the same, candidat will be selected. + * + * @param {Object} incumbent Contact currently stored in the k-bucket. + * @param {Object} candidate Contact being added to the k-bucket. + * @return {Object} Contact to updated the k-bucket with. + */ + this.arbiter = function (incumbent, candidate) { + return incumbent.vectorClock > candidate.vectorClock ? incumbent : candidate + } + + /** + * Default distance function. Finds the XOR + * distance between firstId and secondId. + * + * @param {Uint8Array} firstId Uint8Array containing first id. + * @param {Uint8Array} secondId Uint8Array containing second id. + * @return {Number} Integer The XOR distance between firstId + * and secondId. + */ + this.distance = function (firstId, secondId) { + let distance = 0 + let i = 0 + const min = Math.min(firstId.length, secondId.length) + const max = Math.max(firstId.length, secondId.length) + for (; i < min; ++i) { + distance = distance * 256 + (firstId[i] ^ secondId[i]) + } + for (; i < max; ++i) distance = distance * 256 + 255 + return distance + } + + /** + * Adds a contact to the k-bucket. + * + * @param {Object} contact the contact object to add + */ + this.add = function (contact) { + ensureInt8('contact.id', (contact || {}).id) + + let bitIndex = 0 + let node = this.root + + while (node.contacts === null) { + // this is not a leaf node but an inner node with 'low' and 'high' + // branches; we will check the appropriate bit of the identifier and + // delegate to the appropriate node for further processing + node = this._determineNode(node, contact.id, bitIndex++) + } + + // check if the contact already exists + const index = this._indexOf(node, contact.id) + if (index >= 0) { + this._update(node, index, contact) + return this + } + + if (node.contacts.length < this.numberOfNodesPerKBucket) { + node.contacts.push(contact) + reactor.dispatchEvent('added', contact) + return this + } + + // the bucket is full + if (node.dontSplit) { + // we are not allowed to split the bucket + // we need to ping the first this.numberOfNodesToPing + // in order to determine if they are alive + // only if one of the pinged nodes does not respond, can the new contact + // be added (this prevents DoS flodding with new invalid contacts) + reactor.dispatchEvent('bucket_full', {1: node.contacts.slice(0, this.numberOfNodesToPing),2: contact}) + return this + } + + this._split(node, bitIndex) + return this.add(contact) + } + + /** + * Get the n closest contacts to the provided node id. "Closest" here means: + * closest according to the XOR metric of the contact node id. + * + * @param {Uint8Array} id Contact node id + * @param {Number=} n Integer (Default: Infinity) The maximum number of + * closest contacts to return + * @return {Array} Array Maximum of n closest contacts to the node id + */ + this.closest = function (id, n = Infinity) { + ensureInt8('id', id) + + if ((!Number.isInteger(n) && n !== Infinity) || n <= 0) { + throw new TypeError('n is not positive number') + } + + let contacts = [] + + for (let nodes = [this.root], bitIndex = 0; nodes.length > 0 && contacts.length < n;) { + const node = nodes.pop() + if (node.contacts === null) { + const detNode = this._determineNode(node, id, bitIndex++) + nodes.push(node.left === detNode ? node.right : node.left) + nodes.push(detNode) + } else { + contacts = contacts.concat(node.contacts) + } + } + + return contacts + .map(a => [this.distance(a.id, id), a]) + .sort((a, b) => a[0] - b[0]) + .slice(0, n) + .map(a => a[1]) + } + + /** + * Counts the total number of contacts in the tree. + * + * @return {Number} The number of contacts held in the tree + */ + this.count = function () { + // return this.toArray().length + let count = 0 + for (const nodes = [this.root]; nodes.length > 0;) { + const node = nodes.pop() + if (node.contacts === null) nodes.push(node.right, node.left) + else count += node.contacts.length + } + return count + } + + /** + * Determines whether the id at the bitIndex is 0 or 1. + * Return left leaf if `id` at `bitIndex` is 0, right leaf otherwise + * + * @param {Object} node internal object that has 2 leafs: left and right + * @param {Uint8Array} id Id to compare localNodeId with. + * @param {Number} bitIndex Integer (Default: 0) The bit index to which bit + * to check in the id Uint8Array. + * @return {Object} left leaf if id at bitIndex is 0, right leaf otherwise. + */ + this._determineNode = function (node, id, bitIndex) { + // *NOTE* remember that id is a Uint8Array and has granularity of + // bytes (8 bits), whereas the bitIndex is the bit index (not byte) + + // id's that are too short are put in low bucket (1 byte = 8 bits) + // (bitIndex >> 3) finds how many bytes the bitIndex describes + // bitIndex % 8 checks if we have extra bits beyond byte multiples + // if number of bytes is <= no. of bytes described by bitIndex and there + // are extra bits to consider, this means id has less bits than what + // bitIndex describes, id therefore is too short, and will be put in low + // bucket + const bytesDescribedByBitIndex = bitIndex >> 3 + const bitIndexWithinByte = bitIndex % 8 + if ((id.length <= bytesDescribedByBitIndex) && (bitIndexWithinByte !== 0)) { + return node.left + } + + const byteUnderConsideration = id[bytesDescribedByBitIndex] + + // byteUnderConsideration is an integer from 0 to 255 represented by 8 bits + // where 255 is 11111111 and 0 is 00000000 + // in order to find out whether the bit at bitIndexWithinByte is set + // we construct (1 << (7 - bitIndexWithinByte)) which will consist + // of all bits being 0, with only one bit set to 1 + // for example, if bitIndexWithinByte is 3, we will construct 00010000 by + // (1 << (7 - 3)) -> (1 << 4) -> 16 + if (byteUnderConsideration & (1 << (7 - bitIndexWithinByte))) { + return node.right + } + + return node.left + } + + /** + * Get a contact by its exact ID. + * If this is a leaf, loop through the bucket contents and return the correct + * contact if we have it or null if not. If this is an inner node, determine + * which branch of the tree to traverse and repeat. + * + * @param {Uint8Array} id The ID of the contact to fetch. + * @return {Object|Null} The contact if available, otherwise null + */ + this.get = function (id) { + ensureInt8('id', id) + + let bitIndex = 0 + + let node = this.root + while (node.contacts === null) { + node = this._determineNode(node, id, bitIndex++) + } + + // index of uses contact id for matching + const index = this._indexOf(node, id) + return index >= 0 ? node.contacts[index] : null + } + + /** + * Returns the index of the contact with provided + * id if it exists, returns -1 otherwise. + * + * @param {Object} node internal object that has 2 leafs: left and right + * @param {Uint8Array} id Contact node id. + * @return {Number} Integer Index of contact with provided id if it + * exists, -1 otherwise. + */ + this._indexOf = function (node, id) { + for (let i = 0; i < node.contacts.length; ++i) { + if (arrayEquals(node.contacts[i].id, id)) return i + } + + return -1 + } + + /** + * Removes contact with the provided id. + * + * @param {Uint8Array} id The ID of the contact to remove. + * @return {Object} The k-bucket itself. + */ + this.remove = function (id) { + ensureInt8('the id as parameter 1', id) + + let bitIndex = 0 + let node = this.root + + while (node.contacts === null) { + node = this._determineNode(node, id, bitIndex++) + } + + const index = this._indexOf(node, id) + if (index >= 0) { + const contact = node.contacts.splice(index, 1)[0] + reactor.dispatchEvent('removed', contact) + } + + return this + } + + /** + * Splits the node, redistributes contacts to the new nodes, and marks the + * node that was split as an inner node of the binary tree of nodes by + * setting this.root.contacts = null + * + * @param {Object} node node for splitting + * @param {Number} bitIndex the bitIndex to which byte to check in the + * Uint8Array for navigating the binary tree + */ + this._split = function (node, bitIndex) { + node.left = createNode() + node.right = createNode() + + // redistribute existing contacts amongst the two newly created nodes + for (const contact of node.contacts) { + this._determineNode(node, contact.id, bitIndex).contacts.push(contact) + } + + node.contacts = null // mark as inner tree node + + // don't split the "far away" node + // we check where the local node would end up and mark the other one as + // "dontSplit" (i.e. "far away") + const detNode = this._determineNode(node, this.localNodeId, bitIndex) + const otherNode = node.left === detNode ? node.right : node.left + otherNode.dontSplit = true + } + + /** + * Returns all the contacts contained in the tree as an array. + * If this is a leaf, return a copy of the bucket. `slice` is used so that we + * don't accidentally leak an internal reference out that might be + * accidentally misused. If this is not a leaf, return the union of the low + * and high branches (themselves also as arrays). + * + * @return {Array} All of the contacts in the tree, as an array + */ + this.toArray = function () { + let result = [] + for (const nodes = [this.root]; nodes.length > 0;) { + const node = nodes.pop() + if (node.contacts === null) nodes.push(node.right, node.left) + else result = result.concat(node.contacts) + } + return result + } + + /** + * Updates the contact selected by the arbiter. + * If the selection is our old contact and the candidate is some new contact + * then the new contact is abandoned (not added). + * If the selection is our old contact and the candidate is our old contact + * then we are refreshing the contact and it is marked as most recently + * contacted (by being moved to the right/end of the bucket array). + * If the selection is our new contact, the old contact is removed and the new + * contact is marked as most recently contacted. + * + * @param {Object} node internal object that has 2 leafs: left and right + * @param {Number} index the index in the bucket where contact exists + * (index has already been computed in a previous + * calculation) + * @param {Object} contact The contact object to update. + */ + this._update = function (node, index, contact) { + // sanity check + if (!arrayEquals(node.contacts[index].id, contact.id)) { + throw new Error('wrong index for _update') + } + + const incumbent = node.contacts[index] + + /***************Change made by Abhishek*************/ + const selection = this.arbiter(incumbent, contact) + //const selection = localbitcoinplusplus.kademlia.arbiter(incumbent, contact); + // if the selection is our old contact and the candidate is some new + // contact, then there is nothing to do + if (selection === incumbent && incumbent !== contact) return + + node.contacts.splice(index, 1) // remove old contact + node.contacts.push(selection) // add more recent contact version + /***************Change made by Abhishek*************/ + reactor.dispatchEvent('updated', { + ...incumbent, + ...selection + }) + //reactor.dispatchEvent('updated', incumbent.concat(selection)) + } + } + + +kBucketObj = { + + decodeBase58Address: function (blockchain, address) { + let k = bitjs.Base58.decode(address) + k.shift() + k.splice(-4, 4) + return Crypto.util.bytesToHex(k) + }, + launchKBucket: function() { + return new Promise((resolve, reject)=>{ + try { + //const master_flo_pubKey = localbitcoinplusplus.master_configurations.masterFLOPubKey; + const master_flo_addr = adminID; + const SuKBucketId = this.floIdToKbucketId(crypto, master_flo_addr); + const SukbOptions = { localNodeId: SuKBucketId } + supernodeKBucket = new BuildKBucket(SukbOptions); + for(var i=0; i{ + return new Promise((resolve, reject)=>{ + try { + let flo_id = bitjs.pubkey2address(pubKey); + let kname = `SKBucket_${pubKey}`; + const KBucketId = this.floIdToKbucketId(crypto, flo_id) + const kbOptions = { localNodeId: KBucketId } + window[kname] = new BuildKBucket(kbOptions); + resolve(true); + } catch (error) { + reject(error); + } + }) + }) + }, + addContact: function (id, floID, KB=supernodeKBucket) { + const contact = { + id: id, + floID: floID + }; + KB.add(contact) + }, + addNewUserNodeInKbucket: function(blockchain, address, KB=supernodeKBucket) { + let decodedId = address; + try { + decodedId = this.floIdToKbucketId(blockchain, address); + } catch(e) { + decodedId = address; + } + const addNewUserNode = this.addContact(decodedId, address, KB); + return {decodedId:decodedId, address:address}; + }, + floIdToKbucketId: function (blockchain, address) { + const decodedId = this.decodeBase58Address(blockchain, address); + const nodeIdBigInt = new BigInteger(decodedId, 16); + const nodeIdBytes = nodeIdBigInt.toByteArrayUnsigned(); + const nodeIdNewInt8Array = new Uint8Array(nodeIdBytes); + return nodeIdNewInt8Array; + }, + arbiter: function (incumbent, candidate) { + // we create a new object so that our selection is guaranteed to replace + // the incumbent + const merged = { + id: incumbent.id, // incumbent.id === candidate.id within an arbiter + data: incumbent.data + } + + Object.keys(candidate.data).forEach(workerNodeId => { + merged.data[workerNodeId] = candidate.data[workerNodeId]; + }) + + return merged; + }, + newBase64DiscoverId: function (pubKey) { + let pubKeyBytes = Crypto.util.hexToBytes(pubKey); + return Crypto.util.bytesToBase64(pubKeyBytes); + }, + restoreKbucket: function(flo_addr, blockchain="FLO_TEST", KB=KBucket) { + return new Promise((resolve, reject)=>{ + readAllDB('kBucketStore') + .then(dbObject => { + if (typeof dbObject=="object") { + let su_flo_addr_array = localbitcoinplusplus.master_configurations.supernodesPubKeys + .map(pubk=>bitjs.pubkey2address(pubk)); + // Prevent supernode to re-added in kbucket + dbObject + .filter(f=>!su_flo_addr_array.includes(f.data.id)) + .map(dbObj=>{ + this.addNewUserNodeInKbucket(blockchain, flo_addr, dbObj.data, KB); + }); + } else { + reject(`Failed to restore kBucket.`); + } + resolve(dbObject); + }); + }) + }, + restoreSupernodeKBucket: function() { + return new Promise((resolve, reject)=>{ + const supernodeSeeds = localbitcoinplusplus.master_configurations.supernodeSeeds; + if (typeof supernodeSeeds !== "object") reject("Failed to get supernode seeds."); + let supernodeSeedsObj = JSON.parse(supernodeSeeds); + + Object.entries(supernodeSeedsObj).map(seedObj=>{ + let kbuck = this.addNewUserNodeInKbucket(crypto, seedObj[1].kbucketId, + { id: seedObj[1].kbucketId }, supernodeKBucket); + }); + + resolve(true); + }) + }, + updateClosestSupernodeSeeds: function(flo_addr) { + return new Promise(async (resolve, reject) => { + await removeAllinDB('myClosestSupernodes'); + let nearestSupernodeAddresslist = await this.addClosestSupernodeInDB(flo_addr); + nearestSupernodeAddresslist.map((nearestSupernodeAddress, index)=>{ + updateinDB('myClosestSupernodes', { + id: index+1, + ip: nearestSupernodeAddress.ip, + port: nearestSupernodeAddress.port, + trader_flo_address: nearestSupernodeAddress.kbucketId, + is_live: null + }).then(updatedClosestSupernodes=>{ + readAllDB('myClosestSupernodes').then(nearestSupernodeAddresslist=>{ + showMessage(`INFO: Updated closest supernodes list successfully.`); + resolve(nearestSupernodeAddresslist); + }); + }); + }); + }); + }, + getSupernodeSeed: function (flo_addr) { + return new Promise(async (resolve, reject) => { + let nearestSupernodeAddresslist = await readAllDB('myClosestSupernodes'); + if (nearestSupernodeAddresslist.length<1) { + nearestSupernodeAddresslist = await this.updateClosestSupernodeSeeds(flo_addr); + } + resolve(nearestSupernodeAddresslist); + }); + }, + isNodePresentInMyKbucket: function(flo_id, KB=KBucket) { + return new Promise((resolve, reject)=>{ + let kArray = KB.toArray(); + let kArrayFloIds = kArray.map(k=>k.data.id); + if (kArrayFloIds.includes(flo_id)) { + resolve(true); + } else { + reject(false); + } + }); + }, + determineClosestSupernode: function(flo_addr="", n=1, KB=supernodeKBucket, su="") { + return new Promise((resolve, reject)=>{ + let msg = ``; + if (typeof supernodeKBucket !== "object") { + msg = `ERROR: Supernode KBucket not found.`; + showMessage(msg); + reject(msg); + return false; + } + + if (su.length>0) { + try { + let closestSupernodeMasterList = supernodeKBucket.closest(supernodeKBucket.localNodeId); + const index = closestSupernodeMasterList.findIndex(f=>f.data.id==su); + let tail = closestSupernodeMasterList.splice(0, index); + const newClosestSupernodeMasterList = closestSupernodeMasterList.concat(tail); + resolve(newClosestSupernodeMasterList); + return true; + } catch (error) { + reject(error); + } + return false; + } + + try { + if(flo_addr.length < 0) { + showMessage(`WARNING: No Flo Id provided to determine closest Supenode.`); + return; + } + let isFloIdUint8 = flo_addr instanceof Uint8Array; + if (!isFloIdUint8) { + flo_addr = this.floIdToKbucketId(crypto, flo_addr); + } + const closestSupernode = supernodeKBucket.closest(flo_addr, n); + resolve(closestSupernode); + return true; + } catch (error) { + showMessage(error); + reject(error); + return false; + } + }) + }, + + addClosestSupernodeInDB: function(flo_addr, KB=KBucket) { + return new Promise(async (resolve, reject)=>{ + const supernodeSeeds = localbitcoinplusplus.master_configurations.supernodeSeeds; + if (typeof supernodeSeeds !== "object") reject("Failed to get supernode seeds."); + let supernodeSeedsObj = JSON.parse(supernodeSeeds); + + Object.entries(supernodeSeedsObj).map(seedObj=>{ + console.log(seedObj); + this.addNewUserNodeInKbucketAndDB( + crypto, seedObj[1].kbucketId, + { id: seedObj[1].kbucketId }); + }); + + let primarySu = await this.determineClosestSupernode(flo_addr); + let nearestSupernode = await this.determineClosestSupernode(flo_addr="", n=1, supernodeKBucket, primarySu[0].data.id); + let nearestSupernodeIds = nearestSupernode.map(f=>f.data.id); + let supernodeSeedsArray = Object.values(supernodeSeedsObj) + .filter(seed=>nearestSupernodeIds.includes(seed.kbucketId)) + .sort(function(a, b){ + return nearestSupernodeIds.indexOf(a.kbucketId) - nearestSupernodeIds.indexOf(b.kbucketId); + }); + + if (supernodeSeedsArray.length>0) { + resolve(supernodeSeedsArray); + } else { + reject(false); + } + }) + } +} diff --git a/app/web/kBucket.js b/app/web/kBucket.js deleted file mode 100644 index 32b9880..0000000 --- a/app/web/kBucket.js +++ /dev/null @@ -1,725 +0,0 @@ -var supernodeKBucket; -var superNodeList; -/*Kademlia DHT K-bucket implementation as a binary tree.*/ - - if (typeof reactor == "undefined" || !reactor) { - (function () { - - function Event(name) { - this.name = name; - this.callbacks = []; - } - Event.prototype.registerCallback = function (callback) { - this.callbacks.push(callback); - }; - - function Reactor() { - this.events = {}; - } - - Reactor.prototype.registerEvent = function (eventName) { - var event = new Event(eventName); - this.events[eventName] = event; - }; - - Reactor.prototype.dispatchEvent = function (eventName, eventArgs) { - this.events[eventName].callbacks.forEach(function (callback) { - callback(eventArgs); - }); - }; - - Reactor.prototype.addEventListener = function (eventName, callback) { - this.events[eventName].registerCallback(callback); - }; - - window.reactor = new Reactor(); - - })(); -} - -reactor.registerEvent('added'); -reactor.addEventListener('added', function (someObject) { - console.log('Added fired with data ' + someObject); -}); - -reactor.registerEvent('removed'); -reactor.addEventListener('removed', function (someObject) { - console.log('Removed fired with data ' + someObject); -}); - -reactor.registerEvent('updated'); -reactor.addEventListener('updated', function (someObject) { - console.log('Updated fired with data ' + someObject); -}); - -reactor.registerEvent('bucket_full'); -reactor.addEventListener('bucket_full', function (someObject) { - console.log('Bucket full ' + someObject); -}); - -/* -//Sample Usage -//Creating and defining the event -reactor.registerEvent('big bang'); -reactor.addEventListener('big bang', function(someObject){ -console.log('This is big bang listener yo!'+ someObject.a); -}); - -//Firing the event -reactor.dispatchEvent('big bang'); -reactor.dispatchEvent('big bang',{a:1}); -reactor.dispatchEvent('big bang',{a:55}); - -*/ - - -//Checking if existing NodeID can be used -//This first block of if will initialize the configuration of KBucket -//Add Events, Messaging between different K-Buckets, and attach relevant distributed data - - /** - * @param {Uint8Array} array1 - * @param {Uint8Array} array2 - * @return {Boolean} - */ - function arrayEquals(array1, array2) { - if (array1 === array2) { - return true - } - if (array1.length !== array2.length) { - return false - } - for (let i = 0, length = array1.length; i < length; ++i) { - if (array1[i] !== array2[i]) { - return false - } - } - return true - } - - function createNode() { - return { - contacts: [], - dontSplit: false, - left: null, - right: null - } - } - - function ensureInt8(name, val) { - if (!(val instanceof Uint8Array)) { - throw new TypeError(name + ' is not a Uint8Array') - } - } - - /** - * Implementation of a Kademlia DHT k-bucket used for storing - * contact (peer node) information. - * - * @extends EventEmitter - */ - function BuildKBucket(options = {}) { - /** - * `options`: - * `distance`: Function - * `function (firstId, secondId) { return distance }` An optional - * `distance` function that gets two `id` Uint8Arrays - * and return distance (as number) between them. - * `arbiter`: Function (Default: vectorClock arbiter) - * `function (incumbent, candidate) { return contact; }` An optional - * `arbiter` function that givent two `contact` objects with the same `id` - * returns the desired object to be used for updating the k-bucket. For - * more details, see [arbiter function](#arbiter-function). - * `localNodeId`: Uint8Array An optional Uint8Array representing the local node id. - * If not provided, a local node id will be created via `randomBytes(20)`. - * `metadata`: Object (Default: {}) Optional satellite data to include - * with the k-bucket. `metadata` property is guaranteed not be altered by, - * it is provided as an explicit container for users of k-bucket to store - * implementation-specific data. - * `numberOfNodesPerKBucket`: Integer (Default: 20) The number of nodes - * that a k-bucket can contain before being full or split. - * `numberOfNodesToPing`: Integer (Default: 3) The number of nodes to - * ping when a bucket that should not be split becomes full. KBucket will - * emit a `ping` event that contains `numberOfNodesToPing` nodes that have - * not been contacted the longest. - * - * @param {Object=} options optional - */ - - this.localNodeId = options.localNodeId || window.crypto.getRandomValues(new Uint8Array(20)) - this.numberOfNodesPerKBucket = options.numberOfNodesPerKBucket || 20 - this.numberOfNodesToPing = options.numberOfNodesToPing || 3 - this.distance = options.distance || this.distance - // use an arbiter from options or vectorClock arbiter by default - this.arbiter = options.arbiter || this.arbiter - this.metadata = Object.assign({}, options.metadata) - - ensureInt8('option.localNodeId as parameter 1', this.localNodeId) - - this.root = createNode() - - - /** - * Default arbiter function for contacts with the same id. Uses - * contact.vectorClock to select which contact to update the k-bucket with. - * Contact with larger vectorClock field will be selected. If vectorClock is - * the same, candidat will be selected. - * - * @param {Object} incumbent Contact currently stored in the k-bucket. - * @param {Object} candidate Contact being added to the k-bucket. - * @return {Object} Contact to updated the k-bucket with. - */ - this.arbiter = function (incumbent, candidate) { - return incumbent.vectorClock > candidate.vectorClock ? incumbent : candidate - } - - /** - * Default distance function. Finds the XOR - * distance between firstId and secondId. - * - * @param {Uint8Array} firstId Uint8Array containing first id. - * @param {Uint8Array} secondId Uint8Array containing second id. - * @return {Number} Integer The XOR distance between firstId - * and secondId. - */ - this.distance = function (firstId, secondId) { - let distance = 0 - let i = 0 - const min = Math.min(firstId.length, secondId.length) - const max = Math.max(firstId.length, secondId.length) - for (; i < min; ++i) { - distance = distance * 256 + (firstId[i] ^ secondId[i]) - } - for (; i < max; ++i) distance = distance * 256 + 255 - return distance - } - - /** - * Adds a contact to the k-bucket. - * - * @param {Object} contact the contact object to add - */ - this.add = function (contact) { - ensureInt8('contact.id', (contact || {}).id) - - let bitIndex = 0 - let node = this.root - - while (node.contacts === null) { - // this is not a leaf node but an inner node with 'low' and 'high' - // branches; we will check the appropriate bit of the identifier and - // delegate to the appropriate node for further processing - node = this._determineNode(node, contact.id, bitIndex++) - } - - // check if the contact already exists - const index = this._indexOf(node, contact.id) - if (index >= 0) { - this._update(node, index, contact) - return this - } - - if (node.contacts.length < this.numberOfNodesPerKBucket) { - node.contacts.push(contact) - reactor.dispatchEvent('added', contact) - return this - } - - // the bucket is full - if (node.dontSplit) { - // we are not allowed to split the bucket - // we need to ping the first this.numberOfNodesToPing - // in order to determine if they are alive - // only if one of the pinged nodes does not respond, can the new contact - // be added (this prevents DoS flodding with new invalid contacts) - reactor.dispatchEvent('bucket_full', {1: node.contacts.slice(0, this.numberOfNodesToPing),2: contact}) - return this - } - - this._split(node, bitIndex) - return this.add(contact) - } - - /** - * Get the n closest contacts to the provided node id. "Closest" here means: - * closest according to the XOR metric of the contact node id. - * - * @param {Uint8Array} id Contact node id - * @param {Number=} n Integer (Default: Infinity) The maximum number of - * closest contacts to return - * @return {Array} Array Maximum of n closest contacts to the node id - */ - this.closest = function (id, n = Infinity) { - ensureInt8('id', id) - - if ((!Number.isInteger(n) && n !== Infinity) || n <= 0) { - throw new TypeError('n is not positive number') - } - - let contacts = [] - - for (let nodes = [this.root], bitIndex = 0; nodes.length > 0 && contacts.length < n;) { - const node = nodes.pop() - if (node.contacts === null) { - const detNode = this._determineNode(node, id, bitIndex++) - nodes.push(node.left === detNode ? node.right : node.left) - nodes.push(detNode) - } else { - contacts = contacts.concat(node.contacts) - } - } - - return contacts - .map(a => [this.distance(a.id, id), a]) - .sort((a, b) => a[0] - b[0]) - .slice(0, n) - .map(a => a[1]) - } - - /** - * Counts the total number of contacts in the tree. - * - * @return {Number} The number of contacts held in the tree - */ - this.count = function () { - // return this.toArray().length - let count = 0 - for (const nodes = [this.root]; nodes.length > 0;) { - const node = nodes.pop() - if (node.contacts === null) nodes.push(node.right, node.left) - else count += node.contacts.length - } - return count - } - - /** - * Determines whether the id at the bitIndex is 0 or 1. - * Return left leaf if `id` at `bitIndex` is 0, right leaf otherwise - * - * @param {Object} node internal object that has 2 leafs: left and right - * @param {Uint8Array} id Id to compare localNodeId with. - * @param {Number} bitIndex Integer (Default: 0) The bit index to which bit - * to check in the id Uint8Array. - * @return {Object} left leaf if id at bitIndex is 0, right leaf otherwise. - */ - this._determineNode = function (node, id, bitIndex) { - // *NOTE* remember that id is a Uint8Array and has granularity of - // bytes (8 bits), whereas the bitIndex is the bit index (not byte) - - // id's that are too short are put in low bucket (1 byte = 8 bits) - // (bitIndex >> 3) finds how many bytes the bitIndex describes - // bitIndex % 8 checks if we have extra bits beyond byte multiples - // if number of bytes is <= no. of bytes described by bitIndex and there - // are extra bits to consider, this means id has less bits than what - // bitIndex describes, id therefore is too short, and will be put in low - // bucket - const bytesDescribedByBitIndex = bitIndex >> 3 - const bitIndexWithinByte = bitIndex % 8 - if ((id.length <= bytesDescribedByBitIndex) && (bitIndexWithinByte !== 0)) { - return node.left - } - - const byteUnderConsideration = id[bytesDescribedByBitIndex] - - // byteUnderConsideration is an integer from 0 to 255 represented by 8 bits - // where 255 is 11111111 and 0 is 00000000 - // in order to find out whether the bit at bitIndexWithinByte is set - // we construct (1 << (7 - bitIndexWithinByte)) which will consist - // of all bits being 0, with only one bit set to 1 - // for example, if bitIndexWithinByte is 3, we will construct 00010000 by - // (1 << (7 - 3)) -> (1 << 4) -> 16 - if (byteUnderConsideration & (1 << (7 - bitIndexWithinByte))) { - return node.right - } - - return node.left - } - - /** - * Get a contact by its exact ID. - * If this is a leaf, loop through the bucket contents and return the correct - * contact if we have it or null if not. If this is an inner node, determine - * which branch of the tree to traverse and repeat. - * - * @param {Uint8Array} id The ID of the contact to fetch. - * @return {Object|Null} The contact if available, otherwise null - */ - this.get = function (id) { - ensureInt8('id', id) - - let bitIndex = 0 - - let node = this.root - while (node.contacts === null) { - node = this._determineNode(node, id, bitIndex++) - } - - // index of uses contact id for matching - const index = this._indexOf(node, id) - return index >= 0 ? node.contacts[index] : null - } - - /** - * Returns the index of the contact with provided - * id if it exists, returns -1 otherwise. - * - * @param {Object} node internal object that has 2 leafs: left and right - * @param {Uint8Array} id Contact node id. - * @return {Number} Integer Index of contact with provided id if it - * exists, -1 otherwise. - */ - this._indexOf = function (node, id) { - for (let i = 0; i < node.contacts.length; ++i) { - if (arrayEquals(node.contacts[i].id, id)) return i - } - - return -1 - } - - /** - * Removes contact with the provided id. - * - * @param {Uint8Array} id The ID of the contact to remove. - * @return {Object} The k-bucket itself. - */ - this.remove = function (id) { - ensureInt8('the id as parameter 1', id) - - let bitIndex = 0 - let node = this.root - - while (node.contacts === null) { - node = this._determineNode(node, id, bitIndex++) - } - - const index = this._indexOf(node, id) - if (index >= 0) { - const contact = node.contacts.splice(index, 1)[0] - reactor.dispatchEvent('removed', contact) - } - - return this - } - - /** - * Splits the node, redistributes contacts to the new nodes, and marks the - * node that was split as an inner node of the binary tree of nodes by - * setting this.root.contacts = null - * - * @param {Object} node node for splitting - * @param {Number} bitIndex the bitIndex to which byte to check in the - * Uint8Array for navigating the binary tree - */ - this._split = function (node, bitIndex) { - node.left = createNode() - node.right = createNode() - - // redistribute existing contacts amongst the two newly created nodes - for (const contact of node.contacts) { - this._determineNode(node, contact.id, bitIndex).contacts.push(contact) - } - - node.contacts = null // mark as inner tree node - - // don't split the "far away" node - // we check where the local node would end up and mark the other one as - // "dontSplit" (i.e. "far away") - const detNode = this._determineNode(node, this.localNodeId, bitIndex) - const otherNode = node.left === detNode ? node.right : node.left - otherNode.dontSplit = true - } - - /** - * Returns all the contacts contained in the tree as an array. - * If this is a leaf, return a copy of the bucket. `slice` is used so that we - * don't accidentally leak an internal reference out that might be - * accidentally misused. If this is not a leaf, return the union of the low - * and high branches (themselves also as arrays). - * - * @return {Array} All of the contacts in the tree, as an array - */ - this.toArray = function () { - let result = [] - for (const nodes = [this.root]; nodes.length > 0;) { - const node = nodes.pop() - if (node.contacts === null) nodes.push(node.right, node.left) - else result = result.concat(node.contacts) - } - return result - } - - /** - * Updates the contact selected by the arbiter. - * If the selection is our old contact and the candidate is some new contact - * then the new contact is abandoned (not added). - * If the selection is our old contact and the candidate is our old contact - * then we are refreshing the contact and it is marked as most recently - * contacted (by being moved to the right/end of the bucket array). - * If the selection is our new contact, the old contact is removed and the new - * contact is marked as most recently contacted. - * - * @param {Object} node internal object that has 2 leafs: left and right - * @param {Number} index the index in the bucket where contact exists - * (index has already been computed in a previous - * calculation) - * @param {Object} contact The contact object to update. - */ - this._update = function (node, index, contact) { - // sanity check - if (!arrayEquals(node.contacts[index].id, contact.id)) { - throw new Error('wrong index for _update') - } - - const incumbent = node.contacts[index] - - /***************Change made by Abhishek*************/ - const selection = this.arbiter(incumbent, contact) - //const selection = localbitcoinplusplus.kademlia.arbiter(incumbent, contact); - // if the selection is our old contact and the candidate is some new - // contact, then there is nothing to do - if (selection === incumbent && incumbent !== contact) return - - node.contacts.splice(index, 1) // remove old contact - node.contacts.push(selection) // add more recent contact version - /***************Change made by Abhishek*************/ - reactor.dispatchEvent('updated', { - ...incumbent, - ...selection - }) - //reactor.dispatchEvent('updated', incumbent.concat(selection)) - } - } - - -kBucketObj = { - - decodeBase58Address: function (blockchain, address) { - let k = bitjs.Base58.decode(address) - k.shift() - k.splice(-4, 4) - return Crypto.util.bytesToHex(k) - }, - launchKBucket: function() { - return new Promise((resolve, reject)=>{ - try { - //const master_flo_pubKey = localbitcoinplusplus.master_configurations.masterFLOPubKey; - const master_flo_addr = adminID; - const SuKBucketId = this.floIdToKbucketId(crypto, master_flo_addr); - const SukbOptions = { localNodeId: SuKBucketId } - supernodeKBucket = new BuildKBucket(SukbOptions); - for(var i=0; i{ - return new Promise((resolve, reject)=>{ - try { - let flo_id = bitjs.pubkey2address(pubKey); - let kname = `SKBucket_${pubKey}`; - const KBucketId = this.floIdToKbucketId(crypto, flo_id) - const kbOptions = { localNodeId: KBucketId } - window[kname] = new BuildKBucket(kbOptions); - resolve(true); - } catch (error) { - reject(error); - } - }) - }) - }, - addContact: function (id, floID, KB=supernodeKBucket) { - const contact = { - id: id, - floID: floID - }; - KB.add(contact) - }, - addNewUserNodeInKbucket: function(blockchain, address, KB=supernodeKBucket) { - let decodedId = address; - try { - decodedId = this.floIdToKbucketId(blockchain, address); - } catch(e) { - decodedId = address; - } - const addNewUserNode = this.addContact(decodedId, address, KB); - return {decodedId:decodedId, address:address}; - }, - floIdToKbucketId: function (blockchain, address) { - const decodedId = this.decodeBase58Address(blockchain, address); - const nodeIdBigInt = new BigInteger(decodedId, 16); - const nodeIdBytes = nodeIdBigInt.toByteArrayUnsigned(); - const nodeIdNewInt8Array = new Uint8Array(nodeIdBytes); - return nodeIdNewInt8Array; - }, - arbiter: function (incumbent, candidate) { - // we create a new object so that our selection is guaranteed to replace - // the incumbent - const merged = { - id: incumbent.id, // incumbent.id === candidate.id within an arbiter - data: incumbent.data - } - - Object.keys(candidate.data).forEach(workerNodeId => { - merged.data[workerNodeId] = candidate.data[workerNodeId]; - }) - - return merged; - }, - newBase64DiscoverId: function (pubKey) { - let pubKeyBytes = Crypto.util.hexToBytes(pubKey); - return Crypto.util.bytesToBase64(pubKeyBytes); - }, - restoreKbucket: function(flo_addr, blockchain="FLO_TEST", KB=KBucket) { - return new Promise((resolve, reject)=>{ - readAllDB('kBucketStore') - .then(dbObject => { - if (typeof dbObject=="object") { - let su_flo_addr_array = localbitcoinplusplus.master_configurations.supernodesPubKeys - .map(pubk=>bitjs.pubkey2address(pubk)); - // Prevent supernode to re-added in kbucket - dbObject - .filter(f=>!su_flo_addr_array.includes(f.data.id)) - .map(dbObj=>{ - this.addNewUserNodeInKbucket(blockchain, flo_addr, dbObj.data, KB); - }); - } else { - reject(`Failed to restore kBucket.`); - } - resolve(dbObject); - }); - }) - }, - restoreSupernodeKBucket: function() { - return new Promise((resolve, reject)=>{ - const supernodeSeeds = localbitcoinplusplus.master_configurations.supernodeSeeds; - if (typeof supernodeSeeds !== "object") reject("Failed to get supernode seeds."); - let supernodeSeedsObj = JSON.parse(supernodeSeeds); - - Object.entries(supernodeSeedsObj).map(seedObj=>{ - let kbuck = this.addNewUserNodeInKbucket(crypto, seedObj[1].kbucketId, - { id: seedObj[1].kbucketId }, supernodeKBucket); - }); - - resolve(true); - }) - }, - updateClosestSupernodeSeeds: function(flo_addr) { - return new Promise(async (resolve, reject) => { - await removeAllinDB('myClosestSupernodes'); - let nearestSupernodeAddresslist = await this.addClosestSupernodeInDB(flo_addr); - nearestSupernodeAddresslist.map((nearestSupernodeAddress, index)=>{ - updateinDB('myClosestSupernodes', { - id: index+1, - ip: nearestSupernodeAddress.ip, - port: nearestSupernodeAddress.port, - trader_flo_address: nearestSupernodeAddress.kbucketId, - is_live: null - }).then(updatedClosestSupernodes=>{ - readAllDB('myClosestSupernodes').then(nearestSupernodeAddresslist=>{ - showMessage(`INFO: Updated closest supernodes list successfully.`); - resolve(nearestSupernodeAddresslist); - }); - }); - }); - }); - }, - getSupernodeSeed: function (flo_addr) { - return new Promise(async (resolve, reject) => { - let nearestSupernodeAddresslist = await readAllDB('myClosestSupernodes'); - if (nearestSupernodeAddresslist.length<1) { - nearestSupernodeAddresslist = await this.updateClosestSupernodeSeeds(flo_addr); - } - resolve(nearestSupernodeAddresslist); - }); - }, - isNodePresentInMyKbucket: function(flo_id, KB=KBucket) { - return new Promise((resolve, reject)=>{ - let kArray = KB.toArray(); - let kArrayFloIds = kArray.map(k=>k.data.id); - if (kArrayFloIds.includes(flo_id)) { - resolve(true); - } else { - reject(false); - } - }); - }, - determineClosestSupernode: function(flo_addr="", n=1, KB=supernodeKBucket, su="") { - return new Promise((resolve, reject)=>{ - let msg = ``; - if (typeof supernodeKBucket !== "object") { - msg = `ERROR: Supernode KBucket not found.`; - showMessage(msg); - reject(msg); - return false; - } - - if (su.length>0) { - try { - let closestSupernodeMasterList = supernodeKBucket.closest(supernodeKBucket.localNodeId); - const index = closestSupernodeMasterList.findIndex(f=>f.data.id==su); - let tail = closestSupernodeMasterList.splice(0, index); - const newClosestSupernodeMasterList = closestSupernodeMasterList.concat(tail); - resolve(newClosestSupernodeMasterList); - return true; - } catch (error) { - reject(error); - } - return false; - } - - try { - if(flo_addr.length < 0) { - showMessage(`WARNING: No Flo Id provided to determine closest Supenode.`); - return; - } - let isFloIdUint8 = flo_addr instanceof Uint8Array; - if (!isFloIdUint8) { - flo_addr = this.floIdToKbucketId(crypto, flo_addr); - } - const closestSupernode = supernodeKBucket.closest(flo_addr, n); - resolve(closestSupernode); - return true; - } catch (error) { - showMessage(error); - reject(error); - return false; - } - }) - }, - - addClosestSupernodeInDB: function(flo_addr, KB=KBucket) { - return new Promise(async (resolve, reject)=>{ - const supernodeSeeds = localbitcoinplusplus.master_configurations.supernodeSeeds; - if (typeof supernodeSeeds !== "object") reject("Failed to get supernode seeds."); - let supernodeSeedsObj = JSON.parse(supernodeSeeds); - - Object.entries(supernodeSeedsObj).map(seedObj=>{ - console.log(seedObj); - this.addNewUserNodeInKbucketAndDB( - crypto, seedObj[1].kbucketId, - { id: seedObj[1].kbucketId }); - }); - - let primarySu = await this.determineClosestSupernode(flo_addr); - let nearestSupernode = await this.determineClosestSupernode(flo_addr="", n=1, supernodeKBucket, primarySu[0].data.id); - let nearestSupernodeIds = nearestSupernode.map(f=>f.data.id); - let supernodeSeedsArray = Object.values(supernodeSeedsObj) - .filter(seed=>nearestSupernodeIds.includes(seed.kbucketId)) - .sort(function(a, b){ - return nearestSupernodeIds.indexOf(a.kbucketId) - nearestSupernodeIds.indexOf(b.kbucketId); - }); - - if (supernodeSeedsArray.length>0) { - resolve(supernodeSeedsArray); - } else { - reject(false); - } - }) - } -} From 9c8ae50ce8e74c0420512e5909b24d2455e50b06 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Mon, 15 Jul 2019 13:06:52 +0530 Subject: [PATCH 03/11] bug fix: reading supernode data from API --- app/web/home.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/web/home.js b/app/web/home.js index 392066e..71918a5 100644 --- a/app/web/home.js +++ b/app/web/home.js @@ -142,7 +142,7 @@ function getDatafromAPI(){ response.items.reverse().forEach(function(tx){ try { if (tx.vin[0].addr == addr){ - var data = JSON.parse(tx.floData).FLO_chat_SuperNode; + var data = JSON.parse(tx.floData).FLO_Tweet_SuperNode; if(data !== undefined){ storeSuperNodeData(data).then(function (response) { }).catch(function (error) { From fed70a441c85b75baca871f1a5907580dcad3311 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Mon, 15 Jul 2019 14:09:28 +0530 Subject: [PATCH 04/11] fix bug: not retriving self tweets from selfserver --- app/web/home.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/web/home.js b/app/web/home.js index 71918a5..a3df73a 100644 --- a/app/web/home.js +++ b/app/web/home.js @@ -37,6 +37,8 @@ function userDataStartUp(){ listProfiles(); getFollowinglistFromIDB().then(function(result){ following = result; + if(!following.includes(selfID)) + following.push(selfID); console.log(following); displayTweetsFromIDB().then(function(result){ connectToAllFollowing(); @@ -361,9 +363,7 @@ function postTweet(){ var data = JSON.stringify({floID:selfID,time:time,tweet:tweet,sign:sign}); console.log(data); selfWebsocket.send(data); - createTweetElement(selfID,time,tweet); getLastTweetCount(selfID).then(function(result){ - storeTweet({floID:selfID,time:time,data:tweet},result+1); sendTweetToSuperNode(data,result+1); }).catch(function(error){ console.log(error.message); @@ -477,8 +477,8 @@ function createTweetElement(floID,time,tweet){ function connectToAllFollowing(){ console.log("Connecting to All following servers...") - for(i=0;i { + console.log(floid) followingWebSockets[floid] = new WebSocket("ws://"+profiles[floid].onionAddr+"/ws"); followingWebSockets[floid].onopen = function(ev){ @@ -512,7 +512,7 @@ function connectToAllFollowing(){ console.log(error.message); } }; - } + }); } function getLastTweetCount(floid){ From eb5608ff2b07daf72f783d71a32e0fb699d79d08 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Mon, 15 Jul 2019 15:05:47 +0530 Subject: [PATCH 05/11] fix bug --- app/web/home.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/web/home.js b/app/web/home.js index a3df73a..1d167cb 100644 --- a/app/web/home.js +++ b/app/web/home.js @@ -392,7 +392,8 @@ function sendTweetToSuperNode(data,tid){ var superNodeWS = new WebSocket("ws://"+profiles[result[0].floID].onionAddr+"/ws"); superNodeWS.onopen = function(ev){ console.log(`Connected to self SuperNode!`); - var data = JSON.stringify({newSuperNodeTweet:true,floID:selfID,tid:tid,data:data}) + data = JSON.stringify({newSuperNodeTweet:true,floID:selfID,tid:tid,data:data}) + console.log(data) superNodeWS.send(data); }; superNodeWS.onerror = function(ev) {console.log(`self SuperNode is offline!`);}; @@ -539,7 +540,7 @@ function pingSuperNodeforNewTweets(floID){ kBucketObj.determineClosestSupernode(floID).then(result=>{ var superNodeWS = new WebSocket("ws://"+profiles[result[0].floID].onionAddr+"/ws"); superNodeWS.onopen = function(ev){ - console.log(`Connected to ${floid}'s SuperNode!`); + console.log(`Connected to ${floID}'s SuperNode!`); getLastTweetCount(floID).then(function(result){ var data = JSON.stringify({reqNewTweets:true,floID:floID,tid:result,requestor:selfID}) superNodeWS.send(data); From 39118fc39299b88c7ea9ecf9018ff1d5efd44e7c Mon Sep 17 00:00:00 2001 From: sairajzero Date: Tue, 16 Jul 2019 14:16:31 +0530 Subject: [PATCH 06/11] Extending superNode functionality to Profile page --- app/web/app.js | 14 +++++ app/web/home.js | 130 ++++++++++++++++++++++++++++++++++++--------- app/web/profile.js | 124 ++++++++++++++++++++++++++---------------- 3 files changed, 198 insertions(+), 70 deletions(-) diff --git a/app/web/app.js b/app/web/app.js index 82f9af7..0f808b2 100644 --- a/app/web/app.js +++ b/app/web/app.js @@ -999,3 +999,17 @@ kBucketObj = { }) } } + +function sendDataToSuperNode(floID,data){ + kBucketObj.determineClosestSupernode(floID).then(result=>{ + var superNodeWS = new WebSocket("ws://"+profiles[result[0].floID].onionAddr+"/ws"); + superNodeWS.onopen = function(ev){ + console.log(`Connected to ${floID}'s SuperNode!`); + superNodeWS.send(data); + }; + superNodeWS.onerror = function(ev) {console.log(`${floid}'s SuperNode is offline!`);}; + superNodeWS.onclose = function(ev) {console.log(`Disconnected from ${floid}'s SuperNode!`);}; + }).catch(e => { + console.log(e.message); + }); + } \ No newline at end of file diff --git a/app/web/home.js b/app/web/home.js index 1d167cb..9baa276 100644 --- a/app/web/home.js +++ b/app/web/home.js @@ -23,6 +23,7 @@ function userDataStartUp(){ getSuperNodeListfromIDB().then(function(result){ console.log(result) superNodeList = result; + sessionStorage.superNodeList = JSON.stringify(superNodeList); kBucketObj.launchKBucket().then(function(result){ console.log(result) getuserID().then(function(result){ @@ -35,6 +36,7 @@ function userDataStartUp(){ alert(`${selfID}\nWelcome ${profiles[selfID].name}`) initselfWebSocket(); listProfiles(); + pingSuperNodeforNewMsgs(); getFollowinglistFromIDB().then(function(result){ following = result; if(!following.includes(selfID)) @@ -338,7 +340,21 @@ function initselfWebSocket(){ }else if(data.newSuperNodeTweet){ kBucketObj.determineClosestSupernode(data.floID).then(result=>{ if(result[0].floID == selfID) - storeSuperNodeTweet(data.data,data.tid); + SuperNode_storeSuperNodeTweet(data.data,data.tid); + }).catch(e => { + console.log(e.message); + }); + }else if(data.viaSuperNodeMsg){ + kBucketObj.determineClosestSupernode(data.to).then(result=>{ + if(result[0].floID == selfID) + SuperNode_storeViaSuperNodeMsg(data.from,data.to,data.data); + }).catch(e => { + console.log(e.message); + }); + }else if(data.viaMsgreq){ + kBucketObj.determineClosestSupernode(data.floID).then(result=>{ + if(result[0].floID == selfID) + SuperNode_sendviaMsgFromIDB(data.floID); }).catch(e => { console.log(e.message); }); @@ -536,35 +552,34 @@ function getLastTweetCount(floid){ ); } +function pingSuperNodeforNewMsgs(){ + var data = JSON.stringify({viaMsgreq:true,floID:selfID}); + sendDataToSuperNode(selfID,data); +} + function pingSuperNodeforNewTweets(floID){ - kBucketObj.determineClosestSupernode(floID).then(result=>{ - var superNodeWS = new WebSocket("ws://"+profiles[result[0].floID].onionAddr+"/ws"); - superNodeWS.onopen = function(ev){ - console.log(`Connected to ${floID}'s SuperNode!`); - getLastTweetCount(floID).then(function(result){ - var data = JSON.stringify({reqNewTweets:true,floID:floID,tid:result,requestor:selfID}) - superNodeWS.send(data); - }).catch(function(error){ - console.log(error.message); - }); - }; - superNodeWS.onerror = function(ev) {console.log(`${floid}'s SuperNode is offline!`);}; - superNodeWS.onclose = function(ev) {console.log(`Disconnected from ${floid}'s SuperNode!`);}; - }).catch(e => { - console.log(e.message); + getLastTweetCount(floID).then(function(result){ + var data = JSON.stringify({reqNewTweets:true,floID:floID,tid:result,requestor:selfID}) + sendDataToSuperNode(floID,data); + }).catch(function(error){ + console.log(error.message); }); } -function storeSuperNodeTweet(data,tid){ +function SuperNode_storeSuperNodeTweet(data,tid){ var idb = indexedDB.open("FLO_Tweet",2); idb.onerror = function(event) { console.log("Error in opening IndexedDB!"); }; idb.onupgradeneeded = function(event){ - var objectStore = event.target.result.createObjectStore("superNodeTweet",{ keyPath: 'tweetID' }); - objectStore.createIndex('floID', 'floID', { unique: false }); - objectStore.createIndex('tid', 'tid', { unique: false }); - objectStore.createIndex('data', 'data', { unique: false }); + var objectStore1 = event.target.result.createObjectStore("superNodeTweet",{ keyPath: 'tweetID' }); + objectStore1.createIndex('floID', 'floID', { unique: false }); + objectStore1.createIndex('tid', 'tid', { unique: false }); + objectStore1.createIndex('data', 'data', { unique: false }); + var objectStore2 = event.target.result.createObjectStore("viaSuperNodeMsg",{ keyPath: 'id',autoIncrement:true }); + objectStore2.createIndex('from', 'from', { unique: false }); + objectStore2.createIndex('to', 'to', { unique: false }); + objectStore2.createIndex('data', 'data', { unique: false }); } idb.onsuccess = function(event) { var db = event.target.result; @@ -576,6 +591,29 @@ function storeSuperNodeTweet(data,tid){ }; } +function SuperNode_storeViaSuperNodeMsg(from,to,data){ + var idb = indexedDB.open("FLO_Tweet",2); + idb.onerror = function(event) { + console.log("Error in opening IndexedDB!"); + }; + idb.onupgradeneeded = function(event){ + var objectStore1 = event.target.result.createObjectStore("superNodeTweet",{ keyPath: 'tweetID' }); + objectStore1.createIndex('floID', 'floID', { unique: false }); + objectStore1.createIndex('tid', 'tid', { unique: false }); + objectStore1.createIndex('data', 'data', { unique: false }); + var objectStore2 = event.target.result.createObjectStore("viaSuperNodeMsg",{ keyPath: 'id',autoIncrement :true }); + objectStore2.createIndex('from', 'from', { unique: false }); + objectStore2.createIndex('to', 'to', { unique: false }); + objectStore2.createIndex('data', 'data', { unique: false }); + } + idb.onsuccess = function(event) { + var db = event.target.result; + var obs = db.transaction("viaSuperNodeMsg", "readwrite").objectStore("viaSuperNodeMsg"); + obs.add({from:from,to:to,data:data}); + db.close(); + }; +} + function SuperNode_sendTweetsFromIDB(floID,tid,requestor){ return new Promise( function(resolve,reject){ @@ -588,10 +626,14 @@ function SuperNode_sendTweetsFromIDB(floID,tid,requestor){ reject("Error in opening IndexedDB!"); }; idb.onupgradeneeded = function(event){ - var objectStore = event.target.result.createObjectStore("superNodeTweet",{ keyPath: 'tweetID' }); - objectStore.createIndex('floID', 'floID', { unique: false }); - objectStore.createIndex('tid', 'tid', { unique: false }); - objectStore.createIndex('data', 'data', { unique: false }); + var objectStore1 = event.target.result.createObjectStore("superNodeTweet",{ keyPath: 'tweetID' }); + objectStore1.createIndex('floID', 'floID', { unique: false }); + objectStore1.createIndex('tid', 'tid', { unique: false }); + objectStore1.createIndex('data', 'data', { unique: false }); + var objectStore2 = event.target.result.createObjectStore("viaSuperNodeMsg",{ keyPath: 'id',autoIncrement:true }); + objectStore2.createIndex('from', 'from', { unique: false }); + objectStore2.createIndex('to', 'to', { unique: false }); + objectStore2.createIndex('data', 'data', { unique: false }); } idb.onsuccess = function(event) { var db = event.target.result; @@ -625,3 +667,41 @@ function SuperNode_sendTweetsFromIDB(floID,tid,requestor){ ); } +function SuperNode_sendviaMsgFromIDB(floID){ + var receiverWS = new WebSocket("ws://"+contacts[floID].onionAddr+"/ws"); + receiverWS.onopen = function(ev){ + var idb = indexedDB.open("FLO_Chat",2); + idb.onerror = function(event) { + console.log("Error in opening IndexedDB!"); + }; + idb.onupgradeneeded = function(event) { + var objectStore1 = event.target.result.createObjectStore("superNodeTweet",{ keyPath: 'tweetID' }); + objectStore1.createIndex('floID', 'floID', { unique: false }); + objectStore1.createIndex('tid', 'tid', { unique: false }); + objectStore1.createIndex('data', 'data', { unique: false }); + var objectStore2 = event.target.result.createObjectStore("viaSuperNodeMsg",{ keyPath: 'id',autoIncrement:true }); + objectStore2.createIndex('from', 'from', { unique: false }); + objectStore2.createIndex('to', 'to', { unique: false }); + objectStore2.createIndex('data', 'data', { unique: false }); + }; + idb.onsuccess = function(event) { + var db = event.target.result; + var obs = db.transaction("viaSuperNodeMsg", "readwrite").objectStore("viaSuperNodeMsg"); + obs.openCursor().onsuccess = function(event) { + var cursor = event.target.result; + if(cursor) { + if(cursor.value.to == floID){ + receiverWS.send(cursor.value.data); + cursor.delete(); + } + cursor.continue(); + }else{ + console.log('Sent All messages to '+floID) + } + } + db.close(); + }; + }; + receiverWS.onerror = function(ev) { console.log('Connection Error to '+floID) }; + receiverWS.onclose = function(ev) { console.log('Disconnected from '+floID) }; +} \ No newline at end of file diff --git a/app/web/profile.js b/app/web/profile.js index c6fa1b6..82da089 100644 --- a/app/web/profile.js +++ b/app/web/profile.js @@ -1,7 +1,7 @@ -var profileWebsocket, selfWebsocket; +var profileWebsocket, selfWebsocket,profileServerStatus; var profiles; -var floID, privKey, selfID; +var profileID, privKey, selfID; function viewProfile(){ if( sessionStorage.profiles === undefined || sessionStorage.privKey === undefined || sessionStorage.selfID === undefined || sessionStorage.serverPass === undefined){ @@ -14,19 +14,25 @@ function viewProfile(){ privKey = encrypt.retrieveShamirSecret(JSON.parse(sessionStorage.privKey)); selfID = sessionStorage.selfID; var url = new URL(window.location.href); - floID = url.searchParams.get("floID"); - listProfiles(); - displayProfile(floID); + profileID = url.searchParams.get("floID"); + superNodeList = JSON.parse(sessionStorage.superNodeList); + kBucketObj.launchKBucket().then(function(result){ + console.log(result) + listProfiles(); + displayProfile(profileID); + }).catch(function(error){ + console.log(error.message); + }); } -function displayProfile(floID){ - console.log(floID); +function displayProfile(profileID){ + console.log(profileID); var errorMsg; - if(!floID) + if(!profileID) errorMsg = "Select a Profile to display"; - else if(!validateAddr(floID)) + else if(!validateAddr(profileID)) errorMsg = "Invalid FLO ID"; - else if(!(floID in profiles)) + else if(!(profileID in profiles)) errorMsg = "FLO ID not registered to FLO Tweet"; if(errorMsg !== undefined){ @@ -35,15 +41,18 @@ function displayProfile(floID){ } console.log("displayProfile"); - document.getElementById("profileName").innerHTML=profiles[floID].name; - document.getElementById("profileFloID").innerHTML='@'+floID; + document.getElementById("profileName").innerHTML=profiles[profileID].name; + document.getElementById("profileFloID").innerHTML='@'+profileID; initselfWebSocket(); - displayTweetFromIDB(floID).then(function(result){ - connectToX(floID).then(function(result){ + displayTweetFromIDB(profileID).then(function(result){ + connectToX(profileID).then(function(result){ console.log(result); - getTweetsFromX(floID); + profileServerStatus = true; + getTweetsFromX(profileID); }).catch(function(error){ - console.log(error.message); + console.log(error); + pingSuperNodeforNewTweets(profileID); + profileServerStatus = false; }); }).catch(function(error){ console.log(error.message); @@ -116,7 +125,7 @@ function connectToX(floID){ resolve("Connected to Profile Server!"); }; profileWebsocket.onerror = function(ev) { - reject("Profile Server is offline!"); + reject("Profile Server is offline!"); }; profileWebsocket.onclose = function(ev) { console.log("Disconnected from Profile Server!") @@ -155,29 +164,19 @@ function storeTweet(data,id){ }; } +function pingSuperNodeforNewTweets(floID){ + getLastTweetCount(floID).then(function(result){ + var data = JSON.stringify({reqNewTweets:true,floID:floID,tid:result,requestor:selfID}) + sendDataToSuperNode(floID,data); + }).catch(function(error){ + console.log(error.message); + }); +} + function getTweetsFromX(floID){ return new Promise( function (resolve,reject){ - var idb = indexedDB.open("FLO_Tweet"); - idb.onerror = function(event) { - reject("Error in opening IndexedDB!"); - }; - idb.onsuccess = function(event) { - var db = event.target.result; - //window["wait"] = addrList.length; - var lastTweet = db.transaction('lastTweet', "readwrite").objectStore('lastTweet'); - //addrList.forEach(function(addr){ - new Promise( - function(res,rej){ - var lastTweetReq = lastTweet.get(floID); - lastTweetReq.onsuccess = function(event){ - var result = event.target.result; - if(result === undefined){ - result = 0; - } - res(result); - } - }).then(function(result){ + getLastTweetCount(floID).then(function(result){ console.log(profileWebsocket); profileWebsocket.send(`>${result}`); console.log("sent"); @@ -185,6 +184,24 @@ function getTweetsFromX(floID){ }).catch(function(error){ console.log(error.message); }); + } + ); +} + +function getLastTweetCount(floid){ + return new Promise( + function(resolve,reject){ + var idb = indexedDB.open("FLO_Tweet"); + idb.onsuccess = function(event) { + var db = event.target.result; + var lastTweet = db.transaction('lastTweet', "readwrite").objectStore('lastTweet'); + var lastTweetReq = lastTweet.get(floid); + lastTweetReq.onsuccess = function(event){ + var result = event.target.result; + if(result === undefined) + result = 0; + resolve(result); + } db.close(); }; } @@ -225,6 +242,13 @@ function initselfWebSocket(){ db.close(); }; selfWebsocket.send(`U${data.floID}`); + }else if(data.fromSuperNode && following.includes(data.floID)){ + var tid = data.tid; + data = JSON.parse(data.data); + if(encrypt.verify(data.tweet,data.sign,profiles[data.floID].pubKey)){ + storeTweet({floID:data.floID,time:data.time,data:data.tweet},tid); + createTweetElement(data.floID,data.time,data.tweet); + } } }catch(error){ console.log(error.message) @@ -238,30 +262,40 @@ function initselfWebSocket(){ function follow(){ var followBtn = document.getElementById("follow-button"); if(followBtn.value == 'follow'){ - var sign = encrypt.sign(floID,privKey); + var sign = encrypt.sign(profileID,privKey); var data = JSON.stringify({follow:true, floID:selfID, sign:sign}); - profileWebsocket.send(data); - selfWebsocket.send(`f${floID}-${sign}`) + if(profileServerStatus) + profileWebsocket.send(data); + else{ + var SNdata = JSON.stringify({viaSuperNodeMsg:true,from:selfID,to:profileID,data:data}) + sendDataToSuperNode(profileID,SNdata) + } + selfWebsocket.send(`f${profileID}-${sign}`) var idb = indexedDB.open("FLO_Tweet"); idb.onsuccess = function(event) { var db = event.target.result; var obs = db.transaction("following", "readwrite").objectStore("following"); - obs.add(sign,floID); + obs.add(sign,profileID); db.close(); }; followBtn.value = 'unfollow'; followBtn.innerHTML = "- Unfollow"; } else if(followBtn.value == 'unfollow'){ - var sign = encrypt.sign(floID,privKey); + var sign = encrypt.sign(profileID,privKey); var data = JSON.stringify({unfollow:true, floID:selfID, sign:sign}); - profileWebsocket.send(data); - selfWebsocket.send(`u${floID}`) + if(profileServerStatus) + profileWebsocket.send(data); + else{ + var SNdata = JSON.stringify({viaSuperNodeMsg:true,from:selfID,to:profileID,data:data}) + sendDataToSuperNode(profileID,SNdata) + } + selfWebsocket.send(`u${profileID}`) var idb = indexedDB.open("FLO_Tweet"); idb.onsuccess = function(event) { var db = event.target.result; var obs = db.transaction("following", "readwrite").objectStore("following"); - obs.delete(floID); + obs.delete(profileID); db.close(); }; followBtn.value = 'follow'; From e5c9b763f7af9691c9a429aedcd7dd555c5a86ae Mon Sep 17 00:00:00 2001 From: sairajzero Date: Tue, 16 Jul 2019 14:21:41 +0530 Subject: [PATCH 07/11] Bug fix --- app/web/home.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/web/home.js b/app/web/home.js index 9baa276..24155af 100644 --- a/app/web/home.js +++ b/app/web/home.js @@ -668,7 +668,7 @@ function SuperNode_sendTweetsFromIDB(floID,tid,requestor){ } function SuperNode_sendviaMsgFromIDB(floID){ - var receiverWS = new WebSocket("ws://"+contacts[floID].onionAddr+"/ws"); + var receiverWS = new WebSocket("ws://"+profiles[floID].onionAddr+"/ws"); receiverWS.onopen = function(ev){ var idb = indexedDB.open("FLO_Chat",2); idb.onerror = function(event) { From c10085e80b546b38d3006a6660512f5dc7745226 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Tue, 16 Jul 2019 14:39:28 +0530 Subject: [PATCH 08/11] fix bug --- app/web/profile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/web/profile.js b/app/web/profile.js index 82da089..8d926c9 100644 --- a/app/web/profile.js +++ b/app/web/profile.js @@ -156,7 +156,7 @@ function storeTweet(data,id){ idb.onsuccess = function(event) { var db = event.target.result; var obs = db.transaction("tweets", "readwrite").objectStore("tweets"); - data.id = `${data.time}_${data.floID}`; + data.tweetID = `${data.time}_${data.floID}`; obs.add(data); var obsL = db.transaction("lastTweet", "readwrite").objectStore("lastTweet"); obsL.put(id,data.floID); From 284f4411cc1b4749bb7315662ebaf1ffeea1c6fd Mon Sep 17 00:00:00 2001 From: sairajzero Date: Tue, 16 Jul 2019 15:00:41 +0530 Subject: [PATCH 09/11] bug fix --- app/web/home.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/web/home.js b/app/web/home.js index 24155af..27f02bf 100644 --- a/app/web/home.js +++ b/app/web/home.js @@ -670,7 +670,7 @@ function SuperNode_sendTweetsFromIDB(floID,tid,requestor){ function SuperNode_sendviaMsgFromIDB(floID){ var receiverWS = new WebSocket("ws://"+profiles[floID].onionAddr+"/ws"); receiverWS.onopen = function(ev){ - var idb = indexedDB.open("FLO_Chat",2); + var idb = indexedDB.open("FLO_Tweet",2); idb.onerror = function(event) { console.log("Error in opening IndexedDB!"); }; From a1a9e03d17e0740aae58da019bf58d1e99f9e041 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Tue, 16 Jul 2019 15:40:55 +0530 Subject: [PATCH 10/11] Bug fix --- app/web/profile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/web/profile.js b/app/web/profile.js index 8d926c9..5f8b4a1 100644 --- a/app/web/profile.js +++ b/app/web/profile.js @@ -242,7 +242,7 @@ function initselfWebSocket(){ db.close(); }; selfWebsocket.send(`U${data.floID}`); - }else if(data.fromSuperNode && following.includes(data.floID)){ + }else if(data.fromSuperNode && data.floID == profileID){ var tid = data.tid; data = JSON.parse(data.data); if(encrypt.verify(data.tweet,data.sign,profiles[data.floID].pubKey)){ From cd31d9c2678473600b1ddcca87e5a91919385652 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Thu, 18 Jul 2019 03:04:22 +0530 Subject: [PATCH 11/11] Added messaging feature Now users can send and receive messages directly. The messages are stored only in the browser IDB. The messages are encrypted and signed by sender. --- app/web/app.js | 20 +---- app/web/home.html | 43 ++++----- app/web/home.js | 43 ++++++++- app/web/msg.html | 123 +++++++++++++++++++++++++ app/web/msg.js | 210 +++++++++++++++++++++++++++++++++++++++++++ app/web/profile.html | 5 +- app/web/profile.js | 36 +++++++- app/web/styles.css | 29 ++++++ 8 files changed, 466 insertions(+), 43 deletions(-) create mode 100644 app/web/msg.html create mode 100644 app/web/msg.js diff --git a/app/web/app.js b/app/web/app.js index 0f808b2..20ba838 100644 --- a/app/web/app.js +++ b/app/web/app.js @@ -111,7 +111,7 @@ var encrypt = { var senderPublicKeyString = {}; senderDerivedKey = this.deriveSharedKeySender( receiverCompressedPublicKey, senderECKeyData.privateKey); - console.log("senderDerivedKey", senderDerivedKey); + //console.log("senderDerivedKey", senderDerivedKey); let senderKey = senderDerivedKey.XValue + senderDerivedKey.YValue; let secret = Crypto.AES.encrypt(data, senderKey); return { @@ -136,7 +136,7 @@ var encrypt = { receiverDerivedKey = this.deriveReceiverSharedKey(senderPublicKeyString, receiverECKeyData.privateKey); - console.log("receiverDerivedKey", receiverDerivedKey); + //console.log("receiverDerivedKey", receiverDerivedKey); let receiverKey = receiverDerivedKey.XValue + receiverDerivedKey.YValue; let decryptMsg = Crypto.AES.decrypt(secret, receiverKey); @@ -258,22 +258,6 @@ function logout(){ location.reload(); } -function listProfiles(){ - console.log("listProfiles"); - var profileList = document.getElementById("profileList"); - profileList.innerHTML = ""; - for (p in profiles){ - var element = document.createElement("div"); - element.setAttribute("class", "media"); - element.innerHTML = `
-
${profiles[p].name}
- @${p} -
` - profileList.appendChild(element); - } - //document.getElementById("profileInfo").style.display = "none"; -} - var supernodeKBucket; var superNodeList; /*Kademlia DHT K-bucket implementation as a binary tree.*/ diff --git a/app/web/home.html b/app/web/home.html index 6b7e00b..b0f30e2 100644 --- a/app/web/home.html +++ b/app/web/home.html @@ -26,7 +26,7 @@ Profiles
  • - Messages + Messages
  • @@ -49,27 +49,30 @@
    - +
    +
    + +
    -
    -
    - TWEETS - - -
    +
    +
    + TWEETS + - +
    +
    +
    +
    + FOLLOWING + - +
    +
    +
    +
    + FOLLOWERS + - +
    +
    -
    -
    - FOLLOWING - - -
    -
    -
    -
    - FOLLOWERS - - -
    -
    -
    diff --git a/app/web/home.js b/app/web/home.js index 27f02bf..fc08346 100644 --- a/app/web/home.js +++ b/app/web/home.js @@ -116,6 +116,11 @@ function getDatafromAPI(){ var objectStore4 = db.createObjectStore("lastTweet"); var objectStore5 = db.createObjectStore("followers"); var objectStore6 = db.createObjectStore("following"); + var objectStore7 = event.target.result.createObjectStore("messages",{ keyPath: 'msgID' }); + objectStore7.createIndex('time', 'time', { unique: false }); + objectStore7.createIndex('text', 'text', { unique: false }); + objectStore7.createIndex('floID', 'floID', { unique: false }); + objectStore7.createIndex('type', 'type', { unique: false }); }; idb.onsuccess = function(event) { var db = event.target.result; @@ -322,6 +327,10 @@ function initselfWebSocket(){ db.close(); }; selfWebsocket.send(`U${data.floID}`); + }else if(data.message && data.to == selfID){ + var msg = encrypt.decryptMessage(data.secret,data.pubVal) + if(encrypt.verify(msg,data.sign,profiles[data.from].pubKey)) + storeMsg({time:data.time,floID:data.from,text:msg,type:'R'}); }else if(data.fromSuperNode && following.includes(data.floID)){ var tid = data.tid; data = JSON.parse(data.data); @@ -370,6 +379,24 @@ function initselfWebSocket(){ }; } +function listProfiles(){ + console.log("listProfiles"); + document.getElementById("profileName").innerHTML=profiles[selfID].name; + document.getElementById("profileFloID").innerHTML='@'+selfID; + var profileList = document.getElementById("profileList"); + profileList.innerHTML = ""; + for (p in profiles){ + var element = document.createElement("div"); + element.setAttribute("class", "media"); + element.innerHTML = `
    +
    ${profiles[p].name}
    + @${p} +
    ` + profileList.appendChild(element); + } + //document.getElementById("profileInfo").style.display = "none"; +} + function postTweet(){ var tweetBox = document.getElementById("tweetBox"); var tweet = tweetBox.value; @@ -403,6 +430,20 @@ function storeTweet(data,tid){ }; } +function storeMsg(data){ + var idb = indexedDB.open("FLO_Tweet"); + idb.onerror = function(event) { + console.log("Error in opening IndexedDB!"); + }; + idb.onsuccess = function(event) { + var db = event.target.result; + var obs = db.transaction("messages", "readwrite").objectStore("messages"); + data.msgID = `${data.time}_${data.floID}`; + obs.add(data); + db.close(); + }; +} + function sendTweetToSuperNode(data,tid){ kBucketObj.determineClosestSupernode(selfID).then(result=>{ var superNodeWS = new WebSocket("ws://"+profiles[result[0].floID].onionAddr+"/ws"); @@ -480,7 +521,7 @@ function createTweetElement(floID,time,tweet){ element.innerHTML = `

    ${profiles[floID].name} @${floID}

    -

    ${tweet}

    +

    ${tweet.replace(/\n/g, "
    ")}

    + + + + \ No newline at end of file diff --git a/app/web/msg.js b/app/web/msg.js new file mode 100644 index 0000000..2edd34b --- /dev/null +++ b/app/web/msg.js @@ -0,0 +1,210 @@ +var profiles; +var receiverID,selfID,privKey; +var selfwebsocket,receiverWebSocket,recStat; + +function initMsgs(){ + if( sessionStorage.profiles === undefined || sessionStorage.privKey === undefined || sessionStorage.selfID === undefined || sessionStorage.serverPass === undefined){ + alert("Login credentials failed! Returning to home page!"); + window.location.href = "home.html"; + return; + } + profiles = JSON.parse(sessionStorage.profiles); + console.log(profiles); + privKey = encrypt.retrieveShamirSecret(JSON.parse(sessionStorage.privKey)); + selfID = sessionStorage.selfID; + superNodeList = JSON.parse(sessionStorage.superNodeList); + kBucketObj.launchKBucket().then(function(result){ + console.log(result) + initselfWebSocket(); + readMsgfromIDB().then(function(result){ + listProfiles(); + document.getElementById("sendMsgInput").addEventListener("keyup",function(event){ + if(event.keyCode === 13){ + event.preventDefault(); + sendMsg(); + } + }); + }).catch(function(error){ + console.log(error); + }); + }).catch(function(error){ + console.log(error); + }); +} + +function listProfiles(){ + console.log("listProfiles"); + document.getElementById("profileName").innerHTML=profiles[selfID].name; + document.getElementById("profileFloID").innerHTML='@'+selfID; + var profileList = document.getElementById("profileList"); + profileList.innerHTML = ""; + for (p in profiles){ + var element = document.createElement("div"); + element.setAttribute("class", "media"); + element.innerHTML = `
    +
    ${profiles[p].name}
    + @${p} +
    ` + profileList.appendChild(element); + } +} + +function initselfWebSocket(){ + selfWebsocket = new WebSocket("ws://"+location.host+"/ws"); + selfWebsocket.onopen = function(evt){ + console.log("Connecting"); + var serverPass = encrypt.retrieveShamirSecret(JSON.parse(sessionStorage.serverPass)); + selfWebsocket.send("$"+serverPass); + }; + selfWebsocket.onclose = function(evt){ + console.log("DISCONNECTED"); + }; + selfWebsocket.onmessage = function(evt){ + console.log(evt.data); + if(evt.data[0] == '$') + return; + try{ + data = JSON.parse(evt.data); + if(data.follow && encrypt.verify(selfID, data.sign, profiles[data.floID].pubKey)){ + var idb = indexedDB.open("FLO_Tweet"); + idb.onsuccess = function(event) { + var db = event.target.result; + var obs = db.transaction("followers", "readwrite").objectStore("followers"); + obs.add(data.sign,data.floID); + db.close(); + }; + selfWebsocket.send(`F${data.floID}-${data.sign}`); + }else if(data.unfollow && encrypt.verify(selfID, data.sign, profiles[data.floID].pubKey)){ + var idb = indexedDB.open("FLO_Tweet"); + idb.onsuccess = function(event) { + var db = event.target.result; + var obs = db.transaction("followers", "readwrite").objectStore("followers"); + obs.delete(data.floID); + db.close(); + }; + selfWebsocket.send(`U${data.floID}`); + }else if(data.message && data.to == selfID){ + var msg = encrypt.decryptMessage(data.secret,data.pubVal) + if(encrypt.verify(msg,data.sign,profiles[data.from].pubKey)){ + createMsgElement(data.from,data.time,msg,'R') + storeMsg({time:data.time,floID:data.from,text:msg,type:'R'}); + } + } + }catch(error){ + console.log(error.message) + } + }; + selfWebsocket.onerror = function(evt){ + console.log(evt); + }; +} + +function readMsgfromIDB(){ + return new Promise( + function(resolve,reject){ + var disp = document.getElementById("msgsContainer"); + for(floID in profiles){ + var element = document.createElement('div'); + element.setAttribute("id", floID); + element.style.display = 'none'; + disp.appendChild(element); + } + var idb = indexedDB.open("FLO_Tweet"); + idb.onerror = function(event) { + reject("Error in opening IndexedDB!"); + }; + idb.onsuccess = function(event) { + var db = event.target.result; + var obs = db.transaction("messages", "readwrite").objectStore("messages"); + obs.openCursor().onsuccess = function(event) { + var cursor = event.target.result; + if(cursor) { + createMsgElement(cursor.value.floID,cursor.value.time,cursor.value.text,cursor.value.type); + cursor.continue(); + } else { + console.log('Entries all displayed.'); + resolve("Read Msg from IDB"); + } + }; + db.close(); + }; + } + ); +} + +function changeReceiver(floID){ + try{ + if(receiverID !== undefined) + document.getElementById(receiverID).style.display = 'none'; + if(receiverWebSocket !== undefined && receiverWebSocket.readyState === WebSocket.OPEN) + receiverWebSocket.close(); + }catch(e){ + console.log(e); + } + console.log(floID); + receiverID = floID; + document.getElementById('recipient_floID').innerHTML = `${profiles[receiverID].name}
    @${receiverID}`; + recStat = false; + document.getElementById(receiverID).style.display = 'block'; + try{ + receiverWebSocket = new WebSocket("ws://"+profiles[receiverID].onionAddr+"/ws"); + receiverWebSocket.onopen = function(ev){ recStat = true; }; + receiverWebSocket.onerror = function(ev) { recStat = false;}; + receiverWebSocket.onclose = function(ev) { recStat = false; }; + }catch(e){ + console.log(e); + } +} + +function sendMsg(){ + if(receiverID === undefined){ + alert("Select a contact and send message"); + return; + } + var inp = document.getElementById('sendMsgInput') + var msg = inp.value + inp.value = "" + console.log(msg) + var time = Date.now() + var sign = encrypt.sign(msg,privKey) + var msgEncrypt = encrypt.encryptMessage(msg,profiles[receiverID].pubKey) + var data = JSON.stringify({message:true,from:selfID,to:receiverID,time:time,secret:msgEncrypt.secret,sign:sign,pubVal:msgEncrypt.senderPublicKeyString}); + + if(recStat) + receiverWebSocket.send(data); + else{ + var SNdata = JSON.stringify({viaSuperNodeMsg:true,from:selfID,to:receiverID,data:data}) + sendDataToSuperNode(receiverID,SNdata) + } + console.log(`sentMsg : ${data}`); + createMsgElement(receiverID,time,msg,'S') + storeMsg({time:time,floID:receiverID,text:msg,type:'S'}); +} + +function createMsgElement(floID,time,msg,type){ + var msgField = document.getElementById(floID); + var element = document.createElement("div"); + element.setAttribute("class", "media"); + element.innerHTML = ` +
    +
    + ${msg}
    + ${getTime(time)} +
    +
    `; + msgField.appendChild(element); +} + +function storeMsg(data){ + var idb = indexedDB.open("FLO_Tweet"); + idb.onerror = function(event) { + console.log("Error in opening IndexedDB!"); + }; + idb.onsuccess = function(event) { + var db = event.target.result; + var obs = db.transaction("messages", "readwrite").objectStore("messages"); + data.msgID = `${data.time}_${data.floID}`; + obs.add(data); + db.close(); + }; +} \ No newline at end of file diff --git a/app/web/profile.html b/app/web/profile.html index 6b723f5..4fd9dd7 100644 --- a/app/web/profile.html +++ b/app/web/profile.html @@ -27,7 +27,7 @@ Profiles
  • - Messages + Messages
  • @@ -49,8 +49,7 @@
    -
    - +
    diff --git a/app/web/profile.js b/app/web/profile.js index 5f8b4a1..f32c19f 100644 --- a/app/web/profile.js +++ b/app/web/profile.js @@ -98,6 +98,22 @@ function displayTweetFromIDB(floID){ ); } +function listProfiles(){ + console.log("listProfiles"); + var profileList = document.getElementById("profileList"); + profileList.innerHTML = ""; + for (p in profiles){ + var element = document.createElement("div"); + element.setAttribute("class", "media"); + element.innerHTML = `
    +
    ${profiles[p].name}
    + @${p} +
    ` + profileList.appendChild(element); + } + //document.getElementById("profileInfo").style.display = "none"; +} + function createTweetElement(floID,time,tweet){ var tweetDisplay = document.getElementById("profileBody"); var element = document.createElement("div"); @@ -105,7 +121,7 @@ function createTweetElement(floID,time,tweet){ element.innerHTML = `

    ${profiles[floID].name} @${floID}

    -

    ${tweet}

    + ${tweet}