diff --git a/src/bip39.js b/src/bip39.js index 3abfb57..f12f5ec 100644 --- a/src/bip39.js +++ b/src/bip39.js @@ -1,3 +1,4 @@ + var CryptoJS = require('crypto-js') var crypto = require('crypto') diff --git a/src/index-mobile.js b/src/index-mobile.js new file mode 100644 index 0000000..4cddf3b --- /dev/null +++ b/src/index-mobile.js @@ -0,0 +1,6 @@ + +module.exports = { + API: require('./ninki-api'), + Engine: require('./ninki-engine'), + UI: require('./ninki-ui-mobile') +} diff --git a/src/ninki-api.js b/src/ninki-api.js index 445f6cf..953f718 100644 --- a/src/ninki-api.js +++ b/src/ninki-api.js @@ -1,9 +1,9 @@ var jQuery = require('jquery-browserify'); var $ = require('jquery-browserify'); +var sanitizer = require('sanitizer'); var common = require('./common'); var config = require('./config'); - function API() { @@ -20,7 +20,7 @@ var setCSRFToken = function (securityToken) { }); }; -setCSRFToken($('meta[name="token"]').attr('content')); +//setCSRFToken($('meta[name="token"]').attr('content')); API.get = function (url, querydata, callback) { return this.get(url, querydata, callback); @@ -33,8 +33,6 @@ function get(url, querydata, callback) { }).fail(function (data, textStatus) { - - return callback(true, { textStatus: textStatus, data: data @@ -48,21 +46,34 @@ API.post = function (url, postData, callback) { function lpost(url, postData, callback) { + //https://ninkip2p.com + + var sessionToken = $('#API-Token').val(); + $.ajax({ - url: url, + url: "https://localhost:1111" + url, type: "POST", data: JSON.stringify(postData), contentType: "application/json; charset=utf-8", dataType: "json", + headers: { 'api-token': sessionToken }, success: function (data) { + + + data = sanitizer.sanitize(data); + + var jdata = JSON.parse(data); + + + if (jdata.error) { return callback(true, jdata.message); } if (!(typeof jdata.message === "undefined")) { return callback(false, jdata.message); } - return callback(false, data); + return callback(false, JSON.stringify(jdata)); }, fail: function (data, textStatus) { return callback(true, { @@ -75,9 +86,35 @@ function lpost(url, postData, callback) { console.log(data); if (data.status == 403) { //session has been lost - location.reload(); + + } + else if (data.status == 401) { + + // if (!window.cordova) { + // if (chrome) { + // if (chrome.runtime) { + // if (chrome.runtime.reload) { + // chrome.runtime.reload() + // } else { + // location.reload(); + // } + // } else { + // location.reload(); + // } + // //return callback(true, data.statusText); + // } else { + // location.reload(); + // } + // } else { + // + // } + + + } else { + return callback(true, data.responseText); } - return callback(true, data.responseText); + + } }); } @@ -113,11 +150,12 @@ API.getMasterPublicKeyFromUpstreamServer = function (guid, callback) { var responseBody = JSON.parse(response); var userToken = responseBody.UserToken; var ninkiKey = responseBody.NinkiMasterPublicKey; + var secret = responseBody.Secret; if (!responseBody.UserToken) { return callback(true, "ErrMasterKeyJSON"); } else { - return callback(null, ninkiKey, userToken); + return callback(null, ninkiKey, userToken, secret); } } }); @@ -125,9 +163,9 @@ API.getMasterPublicKeyFromUpstreamServer = function (guid, callback) { //function doesUsernameExist //verifies that the requested username does not already exist on our database -API.doesAccountExist = function (username,email, callback) { +API.doesAccountExist = function (username, email, callback) { - var postData = { username: username, email:email }; + var postData = { username: username, email: email }; lpost("/api/1/u/doesaccountexist", postData, function (err, response) { if (err) { @@ -164,18 +202,68 @@ API.getEmailValidation = function (guid, sharedid, token, callback) { } +API.getResetToken = function (guid, callback) { + API.post("/api/1/u/getresettoken", { + guid: guid + }, function (err, response) { + callback(err, response); + }); -API.getWalletFromServer = function (guid, twoFactorCode, callback) { +} - var postData = { guid: guid, twoFactorCode: twoFactorCode }; + +API.validateSecret = function (guid, secret, callback) { + + var postData = { guid: guid, secret: secret }; + return lpost("/api/1/u/validatesecret", postData, function (err, dataStr) { + + if (err) { + return callback(err, dataStr); + } else { + var data = JSON.parse(dataStr); + return callback(err, data); + } + }); +} + +API.updateSecretPacket = function (guid, sharedid, vc, iv, callback) { + + var postData = { guid: guid, sharedid: sharedid, vc: vc, iv: iv }; + return lpost("/api/1/u/updatesecretpacket", postData, function (err, dataStr) { + return callback(err, dataStr); + }); +} + +API.unlockaccount = function (guid, token, callback) { + + var postData = { guid: guid, token: token }; + return lpost("/api/1/u/unlockaccount", postData, function (err, dataStr) { + return callback(err, dataStr); + }); +} + + +API.getWalletFromServer = function (guid, secret, twoFactorCode, rememberTwoFactor, callback) { + + var postData = { guid: guid, secret: secret, twoFactorCode: twoFactorCode, rememberTwoFactor: rememberTwoFactor }; return lpost("/api/1/u/getaccountdetails", postData, function (err, dataStr) { if (err) { return callback(err, dataStr); } else { var data = JSON.parse(dataStr); + var currentToken = $("#API-Token").val(); + + //if (currentToken.length == 0) { + if (data.SessionToken) { + $("#API-Token").val(data.SessionToken); + } else { + $("#API-Token").val(''); + } + //} + return callback(err, data); } }); @@ -237,10 +325,25 @@ API.getNickname = function (guid, sharedid, callback) { }); } +API.getUserProfile = function (guid, sharedid, callback) { + var postData = { guid: guid, sharedid: sharedid }; + return lpost("/api/1/u/getuserprofile", postData, function (err, dataStr) { + return callback(err, dataStr); + }); +} + +API.updateUserProfile = function (guid, sharedid, profileImage, status, tax, callback) { + var postData = { guid: guid, sharedid: sharedid, profileImage: profileImage, status: status, tax: tax }; + return lpost("/api/1/u/updateuserprofile", postData, function (err, dataStr) { + return callback(err, dataStr); + }); +} + + //function returns all outputs unspent by the wallet -API.getUnspentOutputs = function (guid, callback) { +API.getUnspentOutputs = function (guid, sharedid, callback) { - var postData = { guid: guid }; + var postData = { guid: guid, sharedid: sharedid }; return lpost("/api/1/u/getunspentoutputs", postData, function (err, response) { var data1 = response; var data2 = JSON.parse(data1); @@ -248,6 +351,24 @@ API.getUnspentOutputs = function (guid, callback) { }); } +//function returns all outputs unspent by the wallet +API.getCoinProfile = function (guid, sharedid, callback) { + + var postData = { guid: guid, sharedid: sharedid }; + return lpost("/api/1/u/getcoinprofile", postData, function (err, response) { + var data = JSON.parse(response); + return callback(err, data); + }); +} + +API.getPrice = function (ccy, callback) { + + var postData = { ccy: ccy }; + return lpost("/api/1/u/getPrice", postData, function (err, response) { + var data = JSON.parse(response); + return callback(err, data); + }); +} API.getPendingUserRequests = function (guid, sharedid, callback) { @@ -273,6 +394,7 @@ API.getUserPacket = function (guid, sharedid, callback) { var postData = { guid: guid, sharedid: sharedid }; return lpost("/api/1/u/getuserpacket", postData, function (err, dataStr) { + var jdata = JSON.parse(dataStr); return callback(err, jdata); }); @@ -337,6 +459,8 @@ API.getInvoiceByUserList = function (guid, sharedid, callback) { } + + API.updateInvoice = function (guid, sharedid, username, invoiceId, transactionId, status, callback) { var postData = { guid: guid, sharedid: sharedid, userName: username, invoiceId: invoiceId, transactionId: transactionId, status: status }; return lpost("/api/1/u/updateinvoice", postData, function (err, dataStr) { @@ -344,5 +468,73 @@ API.updateInvoice = function (guid, sharedid, username, invoiceId, transactionId }); } -module.exports = API +API.getVersion = function (callback) { + var postData = {}; + return lpost("/api/1/u/getversion", postData, function (err, dataStr) { + return callback(err, dataStr); + }); +} + +API.registerDevice = function (guid, deviceName, deviceId, deviceModel, devicePIN, regToken, secret, callback) { + var postData = { guid: guid, deviceName: deviceName, deviceId: deviceId, deviceModel: deviceModel, devicePIN: devicePIN, regToken: regToken, secret: secret }; + return lpost("/api/1/u/registerdevice", postData, function (err, dataStr) { + return callback(err, dataStr); + }); +} + +API.getDeviceKey = function (guid, devicePIN, regToken, callback) { + var postData = { guid: guid, devicePIN: devicePIN, regToken: regToken}; + return lpost("/api/1/u/getdevicekey", postData, function (err, dataStr) { + return callback(err, dataStr); + }); +} + +API.destroyDevice = function (guid, regToken, callback) { + var postData = { guid: guid, regToken: regToken }; + return lpost("/api/1/u/destroydevice", postData, function (err, dataStr) { + return callback(err, dataStr); + }); +} + +API.destroyDevice2fa = function (guid, sharedid, deviceName, twoFactorCode, callback) { + var postData = { guid: guid, sharedid: sharedid, deviceName: deviceName, twoFactorCode: twoFactorCode }; + return lpost("/api/1/u/destroydevice2fa", postData, function (err, dataStr) { + return callback(err, dataStr); + }); +} + + + +API.createDevice = function (guid, sharedid, deviceName, callback) { + var postData = { guid: guid, sharedid: sharedid, deviceName: deviceName }; + return lpost("/api/1/u/createdevice", postData, function (err, dataStr) { + return callback(err, dataStr); + }); +} + + +API.getDevices = function (guid, sharedid, callback) { + var postData = { guid: guid, sharedid: sharedid }; + return lpost("/api/1/u/getdevices", postData, function (err, dataStr) { + var jdevs = JSON.parse(dataStr); + return callback(err, jdevs); + }); +} + +API.getDeviceToken = function (guid, sharedid, deviceName, twoFactorCode, callback) { + var postData = { guid: guid, sharedid: sharedid, deviceName: deviceName, twoFactorCode: twoFactorCode }; + return lpost("/api/1/u/getdevicetoken", postData, function (err, dataStr) { + return callback(err, dataStr); + }); +} + + + +API.getRecoveryPacket = function (guid, callback) { + var postData = { guid: guid }; + return lpost("/api/1/getrecoverypacket", postData, function (err, dataStr) { + return callback(err, dataStr); + }); +} +module.exports = API; \ No newline at end of file diff --git a/src/ninki-engine.js b/src/ninki-engine.js index cd99383..6c9258f 100644 --- a/src/ninki-engine.js +++ b/src/ninki-engine.js @@ -1,10 +1,13 @@ +//ninki-engine + var Bitcoin = require('bitcoinjs-lib'); var openpgp = require('openpgp'); var CryptoJS = require('crypto-js'); var API = require('./ninki-api'); var BIP39 = require('./bip39'); var uuid = require('node-uuid'); - +var sjcl = require('sjcl'); +var sanitizer = require('sanitizer'); function Engine() { @@ -13,14 +16,91 @@ function Engine() { this.m_sharedid = ''; this.m_twoFactorOnLogin = false; this.m_nickname = ''; + this.m_profileImage = ''; + this.m_statusText = ''; this.m_guid = ''; this.m_oguid = ''; this.m_password = ''; this.m_settings = {}; this.m_validate = false; this.m_fingerprint = ''; + this.m_secret = ''; + this.m_migrateBeta12fa = false; + this.m_invoiceTax = 0.1; + this.m_privKey = ''; + this.m_pubKey = ''; + this.m_APIToken = ''; + this.m_appInitialised = false; + m_this = this; + + this.appHasLoaded = appHasLoaded; + function appHasLoaded() { + + // m_this.m_password = ''; + + } + + + function isChromeApp() { + + if (window.cordova) { + return false; + } + + var is_chrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1; + if (is_chrome) { + if (chrome) { + if (chrome.app) { + if (chrome.app.runtime) { + return true; + } + } + } + } + return false; + } + + + function isBrowser() { + + if (window.cordova) { + return false; + } + + return !isChromeApp(); + } + + function getCookie(cname, callback) { + + + if (isChromeApp()) { + + chrome.storage.local.get(cname, function (result) { + + if (result[cname]) { + result = result[cname]; + } else { + result = ""; + } + + return callback(result); + + }); + + } else { + + if (localStorage[cname]) { + return callback(localStorage[cname]); + } else { + return callback(''); + } + + } + + } + this.isRealGuid = isRealGuid; //Checks if GUID is valid. Only takes lowercase, non-bracketed GUIDs. function isRealGuid(potentialGuidAsString) { @@ -41,13 +121,13 @@ function Engine() { //assert(element, "Element not specified"); element.val(uuid.v4()); } - + this.getguid = getguid; function getguid() { //assert(element, "Element not specified"); return uuid.v4(); } - + this.encrypt = encrypt; function encrypt(valueToEncrypt, passphrase) { @@ -71,6 +151,27 @@ function Engine() { }; + this.encryptNp = encryptNp; + function encryptNp(valueToEncrypt, passphrase) { + + valueToEncrypt = CryptoJS.enc.Hex.parse(valueToEncrypt); + + var key = CryptoJS.enc.Hex.parse(passphrase); + + var iv = new Uint8Array(32); + var ivbytes = []; + window.crypto.getRandomValues(iv); + for (var i = 0; i < iv.length; ++i) { + ivbytes[i] = iv[i]; + } + + var ivwords = Bitcoin.convert.bytesToWordArray(ivbytes); + + var encrypted = CryptoJS.AES.encrypt(valueToEncrypt, key, { iv: ivwords, padding: CryptoJS.pad.NoPadding }); + + return encrypted; + }; + this.decrypt = decrypt; function decrypt(encryptedObj, passphrase, iv) { @@ -84,1151 +185,2166 @@ function Engine() { return decryptjson; }; + this.decryptNp = decryptNp; + function decryptNp(encryptedObj, passphrase, iv) { + + var key = CryptoJS.enc.Hex.parse(passphrase); + var iv = CryptoJS.enc.Hex.parse(iv); + + var decryptedObject = CryptoJS.AES.decrypt(encryptedObj, key, { iv: iv, padding: CryptoJS.pad.NoPadding }); + var decrypthex = decryptedObject.toString(CryptoJS.enc.Hex); + return decrypthex; + }; - //create wallet - //create a new wallet and save to the server - this.createWallet = createWallet; - function createWallet(guid, password, username, emailAddress, callback) { - m_this.m_oguid = guid; + var hmac = function (key) { + var hasher = new sjcl.misc.hmac(key, sjcl.hash.sha1); + this.encrypt = function () { + return hasher.encrypt.apply(hasher, arguments); + }; + }; - var bytes = []; - for (var i = 0; i < guid.length; ++i) { - bytes.push(guid.charCodeAt(i)); - } - m_this.m_guid = Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(bytes)).toString(); + function pbkdf2(password, salt) { + var passwordSalt = sjcl.codec.utf8String.toBits(salt); + var derivedKey = sjcl.misc.pbkdf2(password, passwordSalt, 1000, 256, hmac); + var hexKey = sjcl.codec.hex.fromBits(derivedKey); + return hexKey; + } + this.setPass = setPass; + function setPass(pass, salt) { + m_this.m_password = pbkdf2(pass, salt); + } - //check if the username already exists - API.doesAccountExist(username.toLowerCase(), emailAddress.toLowerCase(), function (err, accExists) { + this.setStretchPass = setStretchPass; + function setStretchPass(pass) { + m_this.m_password = pass; + } - if (accExists.UserExists) { - return callback(true, "ErrUserExists"); - } - else if (accExists.EmailExists) { - return callback(true, "ErrEmailExists"); - } - else { + this.getHotHash = getHotHash; + function getHotHash(key, callback) { + var isWeb = isBrowser(); + //to do: validate key against stored public key + //needs to be done incase user changed their password on a different machine + if (isChromeApp()) { - //stretch the password with the local guid as a salt - m_this.m_password = CryptoJS.PBKDF2(password, m_this.m_oguid, { - keySize: 256 / 32, - iterations: 1000 - }).toString(); + chrome.storage.local.get("hk" + m_this.m_guid, function (result) { - password = ''; + if (result["hk" + m_this.m_guid]) { + var hk = result["hk" + m_this.m_guid]; + chrome.storage.local.get("hkiv" + m_this.m_guid, function (resultiv) { - //create a new wallet - makeNewWallet(username, emailAddress, function (err, walletInformation, userToken) { + if (resultiv["hkiv" + m_this.m_guid]) { + var hkiv = resultiv["hkiv" + m_this.m_guid]; - if (err) { + var hothash = ''; + var iserror = false; + try { + hothash = decryptNp(hk, m_this.m_password, hkiv); + } catch (error) { + iserror = true; + } + if (!iserror) { - return callback(true, "ErrCreateAccount"); + //validate against loaded hot public key + var validseed = true; + try { + var bipHot = Bitcoin.HDWallet.fromSeedHex(hothash); + if (m_this.m_walletinfo.hotPub != bipHot.toString()) { + validseed = false; + } + } catch (error) { - } else { + validseed = false; + } - m_this.m_sharedid = userToken; + if (validseed) { - //wallet creation is successful - //set variables - //add the usertoken to the wallet - walletInformation.wallet.googleAuthSecret = ""; - walletInformation.wallet.sharedid = userToken; + return callback(false, hothash); - //save the wallet to the server - API.post("/api/1/u/createaccount2", walletInformation.wallet, function (err, response) { + } else { - if (err) { + return callback(true, "invalid"); - return callback(err, "ErrSavePacket"); + } } else { - //pass back the wallet and info to the calling function - return callback(false, walletInformation); + + return callback(true, "missing"); } - }); - } + } else { - }); - } + return callback(true, "missing"); + } - }); - } + }); - this.openWalletAfterCreate = openWalletAfterCreate; - function openWalletAfterCreate(twoFactorCodeChk, callback) { + } else { + return callback(true, "missing"); + } - //check two factor code - if (twoFactorCodeChk != '') { - SetupTwoFactor(twoFactorCodeChk, function (err, wallet) { + }); - if (err) { + } else if (isWeb) { - return callback(err, wallet); + var hk = localStorage["hk" + m_this.m_guid]; + var hkiv = localStorage["hkiv" + m_this.m_guid]; - } else { + var hothash = ''; + var iserror = false; + try { + hothash = decryptNp(hk, m_this.m_password, hkiv); + } catch (error) { + iserror = true; + } + if (!iserror) { - m_this.m_twoFactorOnLogin = true; + //validate against loaded hot public key + var validseed = true; + try { + var bipHot = Bitcoin.HDWallet.fromSeedHex(hothash); + if (m_this.m_walletinfo.hotPub != bipHot.toString()) { + validseed = false; + } + } catch (error) { - getSettings(function (err, result) { + validseed = false; + } - callback(err, result); + if (validseed) { + + return callback(false, hothash); + + } else { + + return callback(true, "invalid"); - }); } - }); + } else { + + return callback(true, "missing"); + } } else { - getSettings(function (err, result) { - callback(err, result); + //not using the chrome app so must be mobile - }); + //use html5 localstorage - } + // localStorage - } + var hk = localStorage["ninki_h"]; - this.openWallet = openWallet; - function openWallet(guid, password, twoFactCode, callback) { - m_this.m_oguid = guid; + if (hk) { - var bytes = []; - for (var i = 0; i < guid.length; ++i) { - bytes.push(guid.charCodeAt(i)); - } + var hothash = ''; + var iserror = false; + try { - m_this.m_guid = Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(bytes)).toString(); + var enc = JSON.parse(hk); + hothash = decryptNp(enc.ct, key, enc.iv); - API.getWalletFromServer(m_this.m_guid, twoFactCode, function (err, wallet) { + } catch (error) { + iserror = true; + } + if (!iserror) { + + //validate against loaded hot public key + var validseed = true; + try { + var bipHot = Bitcoin.HDWallet.fromSeedHex(hothash); + if (m_this.m_walletinfo.hotPub != bipHot.toString()) { + validseed = false; + } + } catch (error) { - if (err) { - return callback(err, wallet); - } + validseed = false; + } + if (validseed) { - m_this.m_password = CryptoJS.PBKDF2(password, guid, { - keySize: 256 / 32, - iterations: 1000 - }).toString(); + //get the two factor code also - try { - var walletInformation = decrypt(wallet.Payload, m_this.m_password, wallet.IV); - } catch (err) { - return callback(true, "Incorrect password"); - } + var tft = localStorage["ninki_rem"]; + var jtft = JSON.parse(tft); - m_this.m_twoFactorOnLogin = wallet.TwoFactorOnLogin; - m_this.m_walletinfo = walletInformation; - m_this.m_sharedid = walletInformation.userToken; + var fatoken = decryptNp(jtft.ct, key, jtft.iv); + return callback(false, hothash, fatoken); - getSettings(function (err, result) { + } else { - callback(err, result); + return callback(true, "invalid"); - }); + } - }); + } else { - } + return callback(true, "missing"); + } + } else { + return callback(true, "missing"); + } - function getSettings(callback) { - //nickname in packet? - getNickname(function (err, nickname) { - m_this.m_nickname = nickname; + } - getFingerPrint(function (err, fingerprint) { - //display the secret on a div + } - var bip39 = new BIP39(); // 'en' is the default language - var fingermnem = bip39.entropyToMnemonic(fingerprint); - m_this.m_fingerprint = fingermnem; + this.saveHotHash = saveHotHash; + function saveHotHash(hotHash, callback) { - getAccountSettings(function (err, response) { - if (err) { + //before we encrypt validate the hash matches the logged in public key + var validseed = true; + try { + var bipHot = Bitcoin.HDWallet.fromSeedHex(hotHash); + if (m_this.m_walletinfo.hotPub != bipHot.toString()) { + validseed = false; + } + } catch (error) { - } else { + validseed = false; + } - var settingsObject = JSON.parse(response); - m_this.settings = settingsObject; - m_this.m_validate = !settingsObject.EmailVerified; - } + if (validseed) { - return callback(err, "ok"); + var encHotHash = encryptNp(hotHash, m_this.m_password); + var objhk = {}; + if (isChromeApp()) { + objhk['hk' + m_this.m_guid] = encHotHash.toString(); + chrome.storage.local.set(objhk, function () { + //console.log("saved"); + var objhkiv = {}; + objhkiv['hkiv' + m_this.m_guid] = encHotHash.iv.toString(); + chrome.storage.local.set(objhkiv, function () { + //console.log("saved"); + callback(false, "ok"); + }); }); + } else { - }); - }); + localStorage.setItem('hk' + m_this.m_guid, encHotHash.toString()); + localStorage.setItem('hkiv' + m_this.m_guid, encHotHash.iv.toString()); - } + callback(false, "ok"); + } - //function makeNewWallet - //this function calls the server which generates the Ninki key pair to be used for the wallet - //the server returns the public key to the client so that it can be saved in the user's encrypted packet - function makeNewWallet(nickname, email, callback) { + } else { - //TODO add some more param checking - //rename this function - API.getMasterPublicKeyFromUpstreamServer(m_this.m_oguid, function (err, ninkiPubKey, userToken) { - if (err) { - return callback(err, "ErrCreateAccount"); - } else { - makeNewWalletPacket(nickname, email, ninkiPubKey, userToken, function (err, walletInformation) { - if (err) { - return callback(err, response); - } else { - return callback(err, walletInformation, userToken); - } - }); - } - }); - } + callback(true, "invalid"); + } - function makeNewWalletPacket(nickname, emailAddress, ninkiPubKey, userToken, callback) { + } - var network = "mainnet"; - //get some random data for the cold key - var rngcold = new Uint8Array(32); - window.crypto.getRandomValues(rngcold); + function validateHotKey(callback) { - var coldKeyBytes = []; - for (var i = 0; i < rngcold.length; ++i) { - coldKeyBytes[i] = rngcold[i]; - } + //load the hotkey + //if not there return error - //get some random data for the hot key - var rnghot = new Uint8Array(32); - window.crypto.getRandomValues(rnghot); + //validate the hotkey + //if not there return error - var hotKeyBytes = []; - for (var i = 0; i < rnghot.length; ++i) { - hotKeyBytes[i] = rnghot[i]; - } - var bip39 = new BIP39(); // 'en' is the default language - var hotmnem = bip39.entropyToMnemonic(rnghot); - var coldmnem = bip39.entropyToMnemonic(rngcold); + } - //var seedtest = bip39.mnemonicToSeed(hotmnem, ''); + // createprog + // textMessageCreate + // createprogstatus - //hash the random data to generate the seed for the cold key space - //Cold key space - var coldHash = Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(coldKeyBytes)).toString(); - var coldWallet = Bitcoin.HDWallet.fromSeedHex(coldHash); - //get the keys as strings - var coldPriv = coldWallet.toString(" "); - var coldPub = coldWallet.toString(); + //create wallet + //create a new wallet and save to the server + this.createWallet = createWallet; + function createWallet(guid, password, username, emailAddress, callback) { - //hash the random data to generate the seed for the hot key space - //Hot key space - var hotHash = Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(hotKeyBytes)).toString(); + m_this.m_oguid = guid; - var hotWallet = Bitcoin.HDWallet.fromSeedHex(hotHash); - //get the keys as strings - var hotPriv = hotWallet.toString(" "); - var hotPub = hotWallet.toString(); + var bytes = []; + for (var i = 0; i < guid.length; ++i) { + bytes.push(guid.charCodeAt(i)); + } + m_this.m_guid = Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(bytes)).toString(); - //generate a pgp keypair - //this key pair will be used to allow the user's to communicate with their contacts - //the private key will be aes256 encrypted so use the userToken as the passphrase as the library - //doesn't support blank passphrases yet (and there is no way to change them) - var options = { numBits: 1024, userId: nickname, passphrase: userToken }; - var keypair = openpgp.generateKeyPair(options); - var privKeys = openpgp.key.readArmored(keypair.privateKeyArmored); - var publicKeys = openpgp.key.readArmored(keypair.publicKeyArmored); + //check if the username already exists + API.doesAccountExist(username.toLowerCase(), emailAddress.toLowerCase(), function (err, accExists) { + if (accExists.UserExists) { - //save the wallet keys and user token in an encrypted packet - //AES256 using PBKDF2 on the password and a unique salt + return callback(true, "ErrUserExists"); - var wal = { - coldPub: coldPub, - hotPub: hotPub, - ninkiPubKey: ninkiPubKey, - hotPriv: hotPriv, - hotHash: hotHash, - userToken: userToken - }; + } + else if (accExists.EmailExists) { - m_this.m_walletinfo = wal; + return callback(true, "ErrEmailExists"); - var encryptedPayload = encrypt(wal, m_this.m_password); + } + else { - //save the PGP keys in an encrypted packet - //AES256 using PBKDF2 on the password and a unique salt + $('#textMessageCreate').html('stretching password...'); - var encryptedUserPayload = encrypt({ - RSAPriv: keypair.privateKeyArmored, - RSAPub: keypair.publicKeyArmored - } - , m_this.m_password, m_this.m_oguid); - - //encrypt a shared secret - //this allows Ninki to validate that the user - //knows their password without having to hold any - //information about their password (for future use) - var secret = Bitcoin.Crypto.SHA256(userToken).toString(); - var encryptedSecret = encrypt(secret, m_this.m_password); - - - //create a packet to post to the server - //note: - // hot private key is encrypted in the payload - // PGP private key is encrypted in the payload - // all 3 wallet public keys are encrypted in the payload - // hot and cold wallet public keys are passed to the server - // public PGP key is passed to the server - - //TODO: move ninkiPhrase to server side - - var wallet = { - guid: m_this.m_oguid, - payload: encryptedPayload.toString(), - userPublicKey: keypair.publicKeyArmored, - userPayload: encryptedUserPayload.toString(), - hotPublicKey: hotPub, - coldPublicKey: coldPub, - nickName: nickname, - emailAddress: emailAddress, - secret: encryptedSecret.toString(), - ninkiPhrase: ninkiPubKey, - IVA: encryptedPayload.iv.toString(), - IVU: encryptedUserPayload.iv.toString(), - IVR: encryptedSecret.iv.toString() - }; + setTimeout(function () { - //the cold private key is discarded and is only displayed to the user - //as a mnemomic representation so that the user can write it down - //if this phrase is lost by the user it is unrecoverable + //stretch the password with the local guid as a salt + m_this.m_password = pbkdf2(password, m_this.m_oguid); + password = ''; - var walletInformation = { - wallet: wallet, - coldWalletPhrase: bip39.entropyToMnemonic(coldHash), - hotWalletPhrase: bip39.entropyToMnemonic(hotHash), - sharedid: userToken - } + //create a new wallet + makeNewWallet(username, emailAddress, function (err, walletInformation, userToken) { - //console.log(coldHash, hotHash); + if (err) { - return callback(null, walletInformation); - } + return callback(true, "ErrCreateAccount"); - function deriveChild(path, hdwallet) { + } else { - var e = path.split('/'); - var ret = hdwallet; - // Special cases: - if (path == 'm' || path == 'M' || path == 'm\'' || path == 'M\'') return this; + m_this.m_sharedid = userToken; - for (var i in e) { - var c = e[i]; + //wallet creation is successful + //set variables + //add the usertoken to the wallet + walletInformation.wallet.googleAuthSecret = ""; + walletInformation.wallet.sharedid = userToken; - if (i == 0) { - if (c != 'm') throw new Error("invalid path"); - continue; - } + // + var recpacket = encryptNp(m_this.m_password, m_this.m_walletinfo.hckey); - var use_private = (c.length > 1) && (c[c.length - 1] == '\''); - var child_index = parseInt(use_private ? c.slice(0, c.length - 1) : c) & 0x7fffffff; + walletInformation.wallet.recPacket = recpacket.toString(); + walletInformation.wallet.recPacketIV = recpacket.iv.toString(); - if (use_private) - child_index += 0x80000000; + //save the wallet to the server + $('#textMessageCreate').html('saving data...'); - ret = ret.derive(child_index); - } + setTimeout(function () { - return ret; - } + API.post("/api/1/u/createaccount2", walletInformation.wallet, function (err, response) { + if (err) { + return callback(err, "ErrSavePacket"); - //function aMultiSigHashForSigning - //TODO: rename - function aMultiSigHashForSigning3(publickey1, publickey2, publickey3, index, outputsToSpend, outputsToSend, addressToSend) { + } else { + //pass back the wallet and info to the calling function + return callback(false, walletInformation); + } + }); + }, 50); + } - //this function will create the temporary transaction - //with a single input used to generate the signature - //for the single input + }); - //instantiate a new transaction - var tx = new Bitcoin.Transaction(); + }, 50); + } - var ins = []; - var outs = []; + }); + } - //generate the script to sign off on - //using the users hotkey,coldkey and the ninki public key - //2 of... - var script = [0x52]; - //hotkey - script.push(33); - script = script.concat(publickey1); - //cold key - script.push(33); - script = script.concat(publickey2); - //ninki key - script.push(33); - script = script.concat(publickey3); - //..3 - script.push(0x53); - //..multisig - script.push(0xae); + //function makeNewWallet + //this function calls the server which generates the Ninki key pair to be used for the wallet + //the server returns the public key to the client so that it can be saved in the user's encrypted packet + function makeNewWallet(nickname, email, callback) { - //generate the same number of inputs as on the transaction to broadcast - //but replace the other inputs with a zero byte! (thanks satoshi-san) - for (var i = 0; i < outputsToSpend.length; i++) { - var p = outputsToSpend[i].transactionId + ':' + outputsToSpend[i].outputIndex.toString(); - tx.addInput(p); - if (i == index) { - tx.ins[i].script = new Bitcoin.Script(script); - } else { - tx.ins[i].script = new Bitcoin.Script([0]); - } - } - //mirror the outpurs in the transaction to broadcast - var test = ''; - for (var i = 0; i < outputsToSend.length; i++) { - var addr = new Bitcoin.Address(addressToSend[i]); - tx.addOutput(addressToSend[i], outputsToSend[i]); - } + //TODO add some more param checking + //rename this function + setTimeout(function () { + $('#textMessageCreate').html('creating account...'); - //hash the transaction-- this has will be used as an input to the signature function - var txHash = tx.hashTransactionForSignature(tx.ins[index].script, index, 1); + API.getMasterPublicKeyFromUpstreamServer(m_this.m_oguid, function (err, ninkiPubKey, userToken, secret) { + if (err) { + return callback(err, "ErrCreateAccount"); + } else { + makeNewWalletPacket(nickname, email, ninkiPubKey, userToken, secret, function (err, walletInformation) { + if (err) { + return callback(err, response); + } else { + return callback(err, walletInformation, userToken); + } + }); + } + }); + }, 50); + } - return txHash; - } + function makeNewWalletPacket(nickname, emailAddress, ninkiPubKey, userToken, secret, callback) { - //function aGetTransaction - //TODO: rename - //generateas a transaction from a set of keys, outputs, signatures and addresses to send to - function aGetTransaction(publickeys, outputsToSpend, outputsToSend, addressToSend, sigs) { + var network = "mainnet"; - //create a new transaction - var tx = new Bitcoin.Transaction(); - var ins = []; - var outs = []; + $('#textMessageCreate').html('getting entropy...'); - //generate the scripts to spend the outputs - for (var i = 0; i < outputsToSpend.length; i++) { + setTimeout(function () { + //get some random data for the cold key + var rngcold = new Uint8Array(32); + window.crypto.getRandomValues(rngcold); - var len = sigs[i].length; - var script = []; + var coldKeyBytes = []; + for (var i = 0; i < rngcold.length; ++i) { + coldKeyBytes[i] = rngcold[i]; + } - //append the signature - script = script.concat(sigs[i]); + //get some random data for the hot key + var rnghot = new Uint8Array(32); + window.crypto.getRandomValues(rnghot); - //prepend the length of the signature - script.unshift(len); - script.unshift(0x00); + var hotKeyBytes = []; + for (var i = 0; i < rnghot.length; ++i) { + hotKeyBytes[i] = rnghot[i]; + } - //push the script used to validate the spend - script.push(0x4c); - script.push(105); - script.push(0x52); - script.push(33); - script = script.concat(publickeys[i][0]); - script.push(33); - script = script.concat(publickeys[i][1]); - script.push(33); - script = script.concat(publickeys[i][2]); - script.push(0x53); - script.push(0xae); + var bip39 = new BIP39(); // 'en' is the default language + var hotmnem = bip39.entropyToMnemonic(rnghot); + var coldmnem = bip39.entropyToMnemonic(rngcold); - //add the input to the transaction referencing the output to spend - var p = outputsToSpend[i].transactionId + ':' + outputsToSpend[i].outputIndex.toString(); - tx.addInput(p); + //var seedtest = bip39.mnemonicToSeed(hotmnem, ''); - //set the script on the input - tx.ins[i].script = new Bitcoin.Script(script); + $('#textMessageCreate').html('creating cold keys...'); - } + setTimeout(function () { - //add the outputs to the transaction - for (var i = 0; i < outputsToSend.length; i++) { - tx.addOutput(addressToSend[i], outputsToSend[i]); - } + //hash the random data to generate the seed for the cold key space + //Cold key space + var coldHash = Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(coldKeyBytes)).toString(); - var txHash = Array.apply([], tx.serialize()); + var coldWallet = Bitcoin.HDWallet.fromSeedHex(coldHash); + //get the keys as strings + var coldPriv = coldWallet.toString(" "); + var coldPub = coldWallet.toString(); - return txHash; - } + $('#textMessageCreate').html('creating hot keys...'); - function aGetTransactionData(params, callback) { + setTimeout(function () { + //hash the random data to generate the seed for the hot key space + //Hot key space + var hotHash = Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(hotKeyBytes)).toString(); - var derivedPublicKeys = []; - var derivedPrivateKeys = []; + var hotWallet = Bitcoin.HDWallet.fromSeedHex(hotHash); + //get the keys as strings + var hotPriv = hotWallet.toString(" "); + var hotPub = hotWallet.toString(); - var signatures = []; - var hashesForSigning = []; - for (var i = 0; i < params.outputsToSpend.length; i++) { - var path = params.paths[i]; - //derive the hashes for signing off on each input - var hashForSigning = aMultiSigHashForSigning3(params.publicKeys[i][0], params.publicKeys[i][1], params.publicKeys[i][2], i, params.outputsToSpend, params.amountsToSend, params.addressToSend); - //add to collection so they can be provided to the server later - //this saves the same process having to be done on the server side - hashesForSigning.push(Bitcoin.convert.bytesToHex(hashForSigning)); + //create a key based on a hash of the hot + cold key + //this is used to encrypt the pbkdf password and so enables + //password reset if the user has access to the hot and cold key phrases - //get the user's hot private key - var key = params.userHotPrivKeys[i]; + var hckey = hotHash + coldHash; + var hcbkey = Bitcoin.convert.hexToBytes(hckey); + var hchkey = Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(hcbkey)).toString(); - //sign the input - var sig = key.sign(hashForSigning).concat([1]); + $('#textMessageCreate').html('creating pgp keys...'); - //add the signature - signatures.push(sig); - } - //get the transaction and return along with the hashes used to sign - var txn = aGetTransaction(params.publicKeys, params.outputsToSpend, params.amountsToSend, params.addressToSend, signatures); + setTimeout(function () { - //generate the signatures - //TO DO: check call back? - return callback("", hashesForSigning, Bitcoin.convert.bytesToHex(txn)); - } + //generate a pgp keypair + //this key pair will be used to allow the user's to communicate with their contacts + //the private key will be aes256 encrypted so use the userToken as the passphrase as the library + //doesn't support blank passphrases yet (and there is no way to change them) + var options = { numBits: 1024, userId: nickname, passphrase: userToken }; + var keypair = openpgp.generateKeyPair(options); - this.isAddressValid = isAddressValid; - function isAddressValid(address) { - var addrValid = true; - try { - var addressCheck = new Bitcoin.Address(address); - addressCheck.toString(); - } catch (err) { - addrValid = false; - } - return addrValid; - } + var privKeys = openpgp.key.readArmored(keypair.privateKeyArmored); + var publicKeys = openpgp.key.readArmored(keypair.publicKeyArmored); - this.sendTransaction = sendTransaction; - function sendTransaction(sendType, friendUserName, addressTo, amount, callback) { + setTimeout(function () { - var minersFee = 10000; - amount = Math.round(amount); + $('#textMessageCreate').html('encrypting data...'); + //save the wallet keys and user token in an encrypted packet + //AES256 using PBKDF2 on the password and a unique salt + var wal = { + coldPub: coldPub, + hotPub: hotPub, + ninkiPubKey: ninkiPubKey, + hotPriv: '', + hotHash: '', + userToken: userToken, + hckey: hchkey + }; - //setup handles to report back progress - var progressel = ''; - var messel = ''; + m_this.m_walletinfo = wal; + + var encryptedPayload = encrypt(wal, m_this.m_password); + + //save the PGP keys in an encrypted packet + //AES256 using PBKDF2 on the password and a unique salt + + var encryptedUserPayload = encrypt({ + RSAPriv: keypair.privateKeyArmored, + RSAPub: keypair.publicKeyArmored + }, m_this.m_password, m_this.m_oguid); + + //encrypt a shared secret + //this allows Ninki to validate that the user + //knows their password without having to hold any + //info about the password + + var encryptedSecret = encryptNp(secret, m_this.m_password); + + m_this.m_secret = secret; + + //create a packet to post to the server + //note: + // hot private key is encrypted in the payload + // PGP private key is encrypted in the payload + // all 3 wallet public keys are encrypted in the payload + // hot and cold wallet public keys are passed to the server + // public PGP key is passed to the server + + //TODO: move ninkiPhrase to server side + + var wallet = { + guid: m_this.m_oguid, + payload: encryptedPayload.toString(), + userPublicKey: keypair.publicKeyArmored, + userPayload: encryptedUserPayload.toString(), + hotPublicKey: hotPub, + coldPublicKey: coldPub, + nickName: nickname, + emailAddress: emailAddress, + secret: encryptedSecret.toString(), + ninkiPhrase: ninkiPubKey, + IVA: encryptedPayload.iv.toString(), + IVU: encryptedUserPayload.iv.toString(), + IVR: encryptedSecret.iv.toString() + }; - if (sendType == 'friend') { - messel = '#textMessageSend'; - progressel = '#sendfriendprogstatus'; - } + //the cold private key is discarded and is only displayed to the user + //as a mnemomic representation so that the user can write it down + //if this phrase is lost by the user it is unrecoverable - if (sendType == 'standard') { - messel = '#textMessageSendStd'; - progressel = '#sendstdprogstatus'; - } - if (sendType == 'invoice') { - messel = '#textMessageSendInv'; - progressel = '#sendinvprogstatus'; - } + //the hot private key is encrypted and saved locally + //encrypted with the user's password - //get the miners fee setting from account settings - //TODO: this can be pre-loaded - API.post("/api/1/u/getaccountsettings", { - guid: m_this.m_guid, - sharedid: m_this.m_sharedid - }, function (err, response) { - if (err) { + saveHotHash(hotHash, function (err, res) { - } else { + var walletInformation = { + wallet: wallet, + coldWalletPhrase: bip39.entropyToMnemonic(coldHash), + hotWalletPhrase: bip39.entropyToMnemonic(hotHash), + sharedid: userToken, + hckey: hchkey + } - var settingsObject = JSON.parse(response); - minersFee = settingsObject.MinersFee * 1.0; - } + //console.log(coldHash, hotHash); - }); + return callback(err, walletInformation); + + }); + }, 50); - //initialise the hot private key space - var bipHot = Bitcoin.HDWallet.fromBase58(m_this.m_walletinfo.hotPriv); + }, 50); - //initialise the 3 public keys - var bipHotPub = Bitcoin.HDWallet.fromBase58(m_this.m_walletinfo.hotPub); - var bipCold = Bitcoin.HDWallet.fromBase58(m_this.m_walletinfo.coldPub); - var bipNinki = Bitcoin.HDWallet.fromBase58(m_this.m_walletinfo.ninkiPubKey); + }, 50); + }, 50); - var pdata = { guid: m_this.m_guid, sharedid: m_this.m_sharedid }; + }, 50); + } - $(messel).html('Getting unspent outputs...'); + this.openWalletAfterCreate = openWalletAfterCreate; + function openWalletAfterCreate(twoFactorCodeChk, callback) { + //check two factor code + if (twoFactorCodeChk != '') { + SetupTwoFactor(twoFactorCodeChk, function (err, wallet) { - API.post("/api/1/u/getunspentoutputs", pdata, function (err, outputs) { + if (err) { - var outputs = JSON.parse(outputs); - var outputsToSpend = []; - var amountsToSend = []; - var addressToSend = []; - var userHotPrivKeys = []; + return callback(err, wallet); - var nodeLevels = []; - var publicKeys = []; - var packet = { addressToSend: addressToSend, amountsToSend: amountsToSend, outputsToSpend: outputsToSpend, userHotPrivKeys: userHotPrivKeys, guid: m_this.m_guid, paths: nodeLevels, publicKeys: publicKeys }; + } else { - //get outputs to spend, calculate change amount minus miners fee + m_this.m_twoFactorOnLogin = true; - $(progressel).width('20%'); + getPGPKeys(function (err, result) { - //iterate the unspent outputs and select the first n that equal the amount to spend - //TO DO: do this before doing any key derivation - var amountSoFar = 0; - for (var i = 0; i < outputs.length; i++) { + if (!err) { + getSettings(function (err, result) { - var pitem = outputs[i]; - var pout = { transactionId: pitem.TransactionId, outputIndex: pitem.OutputIndex, amount: pitem.Amount, address: pitem.Address } + callback(err, result); - nodeLevels.push(pitem.NodeLevel); + }); + } else { + callback(err, result); + } + }); + } - outputsToSpend.push(pout); + }); - //derive the private key to sue for signing - userHotPrivKeys.push(deriveChild(pitem.NodeLevel, bipHot).priv); + } else { - //derive the public keys to use for script generation - //this could be cached on the server as no privacy or hand-off issue that I can see - var dbipHotPub = deriveChild(pitem.NodeLevel, bipHotPub).pub.toBytes(); - var dbipColdPub = deriveChild(pitem.NodeLevel, bipCold).pub.toBytes(); - var dbipNinkiPub = deriveChild(pitem.NodeLevel, bipNinki).pub.toBytes(); + getPGPKeys(function (err, result) { - publicKeys.push([dbipHotPub, dbipColdPub, dbipNinkiPub]); + if (!err) { + getSettings(function (err, result) { - //add the amount - amountSoFar += pitem.Amount; + callback(err, result); - if ((amountSoFar - amount) >= minersFee) { - break; + }); + } else { + callback(err, result); } + }); + + } + + } - } - amountsToSend.push(amount); + this.openWallet2fa = openWallet2fa; + function openWallet2fa(twoFactCode, rememberTwoFactor, callback) { - //now create the change - //again move this before the key derivation - var changeAmount = amountSoFar - (amount + minersFee); + API.getWalletFromServer(m_this.m_guid, m_this.m_secret, twoFactCode, rememberTwoFactor, function (err, wallet) { - if (changeAmount < 0) { - //this message needs to be handled carefully - //as the wallet will most likely have pending confirmations - //rather than insufficent funds - return callback(true, "ErrInsufficientFunds"); + if (err) { + return callback(err, wallet); } - if (changeAmount > 0) { - amountsToSend.push(changeAmount); + try { + var walletInformation = decrypt(wallet.Payload, m_this.m_password, wallet.IV); + } catch (err) { + return callback(true, "Incorrect password"); } - //create a new address for my change to be sent back to me - //if we are sending money to a contact or paying an invoice - //we need to derive addresses on their behalf + //if any account still has a hotkey stored in their encrypted packet + //then remove it and resave the packet - if (sendType == 'friend' || sendType == 'invoice') { - var params = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: friendUserName, amount: amount }; + if (walletInformation.hotHash != '') { - //generate the address for the contact - //this must be done on the client - $(messel).html('Creating address...'); - createAddressForFriend(friendUserName, function (err, address) { + var hotHash = walletInformation.hotHash; - if (!err) { + walletInformation.hotPriv = ''; + walletInformation.hotHash = ''; + m_this.m_walletinfo = walletInformation; + saveHotHash(hotHash, function (err, result) { - $(progressel).width('40%'); - addressToSend.push(address); + if (!err) { - //create the change address, this must be done on the client - $(messel).html('Creating change address...'); + //double check it has been saved correctly + getHotHash("", function (err, result) { + if (!err) { + var packet = encrypt(walletInformation, m_this.m_password); + //now save the packet back to the server - createAddress('m/0/1', changeAmount, function (err, changeaddress) { + var postData = { twoFactorCode: twoFactCode, guid: m_this.m_guid, sharedid: walletInformation.userToken, accountPacket: packet.toString(), IVA: packet.iv.toString() }; + API.post("/api/1/u/migratepacket", postData, function (err, dataStr) { - if (!err) { - $(progressel).width('60%'); - if (changeAmount > 0) { - addressToSend.push(changeaddress); - } - //now get the transaction data - $(messel).html('Creating transaction...'); - aGetTransactionData(packet, function (err, hashesForSigning, rawTransaction) { - $(progressel).width('80%'); - var jsonSend = { guid: m_this.m_guid, hashesForSigning: hashesForSigning, rawTransaction: rawTransaction, pathsToSignWith: nodeLevels } - var jsonp1 = { jsonPacket: JSON.stringify(jsonSend), sharedid: m_this.m_sharedid, userName: friendUserName }; - $(messel).html('Counter-signing transaction...'); + }); - //send the transaction to the service for countersigning and broadcast to the network - API.post("/api/1/u/sendtransaction", jsonp1, function (err, result) { + } - if (!err) { - $(progressel).width('100%'); - $(messel).html('Transaction broadcast...'); - //error handling? + }); - //we have a transaction id so lets make a note of the transaction in the database - var params = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: friendUserName, transactionid: result, address: address, amount: amount }; - API.post("/api/1/u/createtransactionrecord", params, function (err, result) { - return callback(err, params.transactionid); - }); - } else { + } - //handle error messages returned from the server - if (result == 'ErrSingleTransactionLimit') { - $(progressel).width('100%'); - $(messel).html('Transaction Failed: Single limit exceeded'); - return callback(err, result); - } - if (result == 'ErrDailyTransactionLimit') { - $(progressel).width('100%'); - $(messel).html('Transaction Failed: Daily limit exceeded'); - return callback(err, result); - } + }); - if (result == 'ErrTransactionsPerDayLimit') { - $(progressel).width('100%'); - $(messel).html('Transaction Failed: Daily number limit exceeded'); - return callback(err, result); - } + } - if (result == 'ErrTransactionsPerHourLimit') { - $(progressel).width('100%'); - $(messel).html('Transaction Failed: Hourly number limit exceeded'); - return callback(err, result); - } - if (result == 'ErrInvalidRequest') { - $(progressel).width('100%'); - $(messel).html('Transaction Failed: Invalid request'); - return callback(err, result); - } + if (m_this.m_migrateBeta12fa) { - if (result == 'ErrBroadcastFailed') { - $(progressel).width('100%'); - $(messel).html('Transaction Failed: Not accepted'); - return callback(err, result); - } + //1. for migrations from beta1 we need to save a secret + var encryptedSecret = encryptNp(m_this.m_secret, m_this.m_password); - } - }); - }); - } else { - //create address error - if (changeaddress == 'ErrInvalidRequest') { - $(progressel).width('100%'); - $(messel).html('Transaction Failed: Invalid request'); - return callback(err, changeaddress); - } - } - }); - } else { + //we need to base this on account migration status - if (address == 'ErrInvalidRequest') { - $(progressel).width('100%'); - $(messel).html('Transaction Failed: Invalid request'); - return callback(err, address); - } - } - }); - } else { + //add twoFactCode here + API.updateSecretPacket(m_this.m_guid, walletInformation.userToken, encryptedSecret.toString(), encryptedSecret.iv.toString(), function (err, res) { - $(messel).html('Creating address...'); - addressToSend.push(addressTo); + if (err) { + return callback(err, res); + } else { + + + walletInformation.hotPriv = ''; + walletInformation.hotHash = ''; + walletInformation.hchkey = ''; + + m_this.m_twoFactorOnLogin = wallet.TwoFactorOnLogin; + m_this.m_walletinfo = walletInformation; + m_this.m_sharedid = walletInformation.userToken; + + + //if there is a cookie token then encrypt it + + if (wallet.CookieToken) { + var enc = encryptNp(wallet.CookieToken, m_this.m_password); + var ctok = {}; + ctok.ct = enc.toString(); + ctok.iv = enc.iv.toString(); + wallet.CookieToken = JSON.stringify(ctok); + } + + //save secret + + getPGPKeys(function (err, result) { + + if (!err) { + getSettings(function (err, result) { + if (!err) { + callback(err, wallet); + } else { + callback(err, result); + } + + }); + } else { + callback(err, result); + } + }); + + } + + }); + + } else { + + + //2. this is the standard login code execution + //eventually only this block will be required + + walletInformation.hotPriv = ''; + walletInformation.hotHash = ''; + walletInformation.hchkey = ''; + + m_this.m_twoFactorOnLogin = wallet.TwoFactorOnLogin; + m_this.m_walletinfo = walletInformation; + m_this.m_sharedid = walletInformation.userToken; + + //if there is a cookie token then encrypt it + + if (wallet.CookieToken) { + var enc = encryptNp(wallet.CookieToken, m_this.m_password); + var ctok = {}; + ctok.ct = enc.toString(); + ctok.iv = enc.iv.toString(); + wallet.CookieToken = JSON.stringify(ctok); + } + + //save secret + + getPGPKeys(function (err, result) { + + if (!err) { + getSettings(function (err, result) { + if (!err) { + callback(err, wallet); + } else { + callback(err, result); + } + + }); + } else { + callback(err, result); + } + }); + + } + + }); + + + + } + + + this.openWallet = openWallet; + function openWallet(guid, twoFactCode, callback) { + + m_this.m_oguid = guid; + + var bytes = []; + for (var i = 0; i < guid.length; ++i) { + bytes.push(guid.charCodeAt(i)); + } + + m_this.m_guid = Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(bytes)).toString(); + //m_this.m_password = pbkdf2(password, guid); + + + //first get the encrypted secret + //decrypt with password and pass secret to get the wallet back + + //if the user is from beta test 1 + //generate a secret on the server and return + //encrypt the secret on the client + //save directly back + + + API.post("/api/1/getrecoverypacket", { + guid: m_this.m_guid + }, function (err, response) { + + if (err) { + + callback(err, response); + + } else { + + //decrypt packet + + var jpacket = JSON.parse(response); + + if (!jpacket.Beta1) { + + + //1. Account has been migrated to beta2 + //we have a secret to validate against + + //2. validate againts secret and check to see if 2fa is enabled + //if it isn't we can get the packet then force the user to enable it + + var secret = decryptNp(jpacket.packet, m_this.m_password, jpacket.IV); + + m_this.m_secret = secret; + + + API.validateSecret(m_this.m_guid, m_this.m_secret, function (err, secvalid) { + + if (err) { + return callback(err, secvalid); + } + + if (secvalid.Locked == 0) { + + //either is an old acocunt with no 2fa or has a client token + //so we can get back the packet without 2fa + if (!secvalid.TwoFactorOnLogin || twoFactCode.length > 6) { + + API.getWalletFromServer(m_this.m_guid, m_this.m_secret, twoFactCode, false, function (err, wallet) { + + if (err) { + if (wallet == "TokenExpired") { + return callback(false, secvalid); + } else { + return callback(err, wallet); + } + } + + //if token has expired - return expiry message + // + + try { + var walletInformation = decrypt(wallet.Payload, m_this.m_password, wallet.IV); + } catch (err) { + return callback(true, "Incorrect password"); + } + + + //if the packet still stores the hot key + //it is an old beta account + //save the key to local storage + //blank the keys from the packet and resave + + //alert and remind the user to write down their hotkeys + + walletInformation.hotPriv = ''; + walletInformation.hotHash = ''; + walletInformation.hchkey = ''; + + + m_this.m_twoFactorOnLogin = wallet.TwoFactorOnLogin; + m_this.m_walletinfo = walletInformation; + m_this.m_sharedid = walletInformation.userToken; + + getPGPKeys(function (err, result) { + + if (!err) { + getSettings(function (err, result) { + + callback(err, result); + + }); + } else { + callback(err, result); + } + }); + + + }); + + } else { + + //is a migrated account with 2fa enabled + //return to get the user to enter the 2fa code + return callback(err, secvalid); + + } + } else { + + return callback(err, "Account is locked"); + + } + + }); + + } else { + + //migrate the Beta1 wallet + + //save the encypted packet + + + //3. this is a beta1 wallet with no secret + //4. if the user does not have 2fa setup + //get the packet using the old style, then set them up with a secret + if (!jpacket.Beta12fa) { + + //they don;t have 2fa enabled either so once their account is migrated + //we force them to setup 2fa + + //log in as normal + API.getWalletFromServer(m_this.m_guid, m_this.m_secret, twoFactCode, false, function (err, wallet) { + + if (err) { + return callback(err, wallet); + } + + try { + var walletInformation = decrypt(wallet.Payload, m_this.m_password, wallet.IV); + } catch (err) { + return callback(true, "Incorrect password"); + } + + + //now we now we have the correct password + //encrypt the secret and save back the packet + + var encryptedSecret = encryptNp(jpacket.Secret, m_this.m_password); + + //save secret + + m_this.m_secret = jpacket.Secret; + + //here we don't have 2fa yet... + + API.updateSecretPacket(m_this.m_guid, walletInformation.userToken, encryptedSecret.toString(), encryptedSecret.iv.toString(), function (err, res) { + + walletInformation.hotPriv = ''; + walletInformation.hotHash = ''; + walletInformation.hchkey = ''; + + m_this.m_twoFactorOnLogin = wallet.TwoFactorOnLogin; + m_this.m_walletinfo = walletInformation; + m_this.m_sharedid = walletInformation.userToken; + + getPGPKeys(function (err, result) { + + if (!err) { + getSettings(function (err, result) { + + callback(err, result); + + }); + } else { + callback(err, result); + } + }); + + }); + + }); + + + } else { + + + //the account is beta1 but has 2fa setup + //so we don't migrate the account yet + //instead we return so the user can enter their 2fa + + m_this.m_secret = jpacket.Secret; + m_this.m_migrateBeta12fa = true; + m_this.m_twoFactorOnLogin = true; + jpacket.TwoFactorOnLogin = true; + + callback(err, jpacket); + } + + + } + } + + }); + + } + + + function getPGPKeys(callback) { + + //get the pgp key pair + API.getUserPacket(m_this.m_guid, m_this.m_sharedid, function (err, encpacket) { + + //get the RSA private key from the encrypted payload + var rsaKeyPair = decrypt(encpacket.Payload, m_this.m_password, encpacket.IV); + + var publicKeys = openpgp.key.readArmored(rsaKeyPair.RSAPub); + var privKeys = openpgp.key.readArmored(rsaKeyPair.RSAPriv); + + m_this.m_privKey = privKeys.keys[0]; + m_this.m_pubKey = publicKeys.keys[0]; + + if (m_this.m_privKey.decrypt(m_this.m_sharedid)) { + callback(err, 'ok'); + } else { + callback(true, 'failed'); + } + + }); + + } + + function getSettings(callback) { + //nickname in packet? + getNickname(function (err, nickname) { + + m_this.m_nickname = nickname; + + + getUserProfile(function (err, result) { + + var userProfile = JSON.parse(result); + + m_this.m_profileImage = userProfile.ProfileImage; + m_this.m_statusText = userProfile.Status; + m_this.m_invoiceTax = userProfile.Tax; + + getFingerPrint(function (err, fingerprint) { + //display the secret on a div + + var bip39 = new BIP39(); // 'en' is the default language + var fingermnem = bip39.entropyToMnemonic(fingerprint); + m_this.m_fingerprint = fingermnem; + + getAccountSettings(function (err, response) { + if (err) { + + + } else { + + var settingsObject = JSON.parse(response); + m_this.m_settings = settingsObject; + m_this.m_validate = !settingsObject.EmailVerified; + } + + return callback(err, "ok"); + + }); + + }); + + }); + + }); + + } + + + function deriveChild(path, hdwallet) { + + var e = path.split('/'); + var ret = hdwallet; + // Special cases: + if (path == 'm' || path == 'M' || path == 'm\'' || path == 'M\'') return this; + + for (var i in e) { + var c = e[i]; + + if (i == 0) { + if (c != 'm') throw new Error("invalid path"); + continue; + } + + var use_private = (c.length > 1) && (c[c.length - 1] == '\''); + var child_index = parseInt(use_private ? c.slice(0, c.length - 1) : c) & 0x7fffffff; + + if (use_private) + child_index += 0x80000000; + + ret = ret.derive(child_index); + } + + return ret; + } + + + + + //function aMultiSigHashForSigning + //TODO: rename + function aMultiSigHashForSigning3(publickey1, publickey2, publickey3, index, outputsToSpend, outputsToSend, addressToSend) { + + + //this function will create the temporary transaction + //with a single input used to generate the signature + //for the single input + + //instantiate a new transaction + var tx = new Bitcoin.Transaction(); + + var ins = []; + var outs = []; + + //generate the script to sign off on + //using the users hotkey,coldkey and the ninki public key + //2 of... + var script = [0x52]; + //hotkey + script.push(33); + script = script.concat(publickey1); + //cold key + script.push(33); + script = script.concat(publickey2); + //ninki key + script.push(33); + script = script.concat(publickey3); + //..3 + script.push(0x53); + //..multisig + + script.push(0xae); + + //generate the same number of inputs as on the transaction to broadcast + //but replace the other inputs with a zero byte! (thanks satoshi-san) + for (var i = 0; i < outputsToSpend.length; i++) { + var p = outputsToSpend[i].transactionId + ':' + outputsToSpend[i].outputIndex.toString(); + tx.addInput(p); + if (i == index) { + tx.ins[i].script = new Bitcoin.Script(script); + } else { + tx.ins[i].script = new Bitcoin.Script([0]); + } + } + + //mirror the outpurs in the transaction to broadcast + var test = ''; + for (var i = 0; i < outputsToSend.length; i++) { + var addr = new Bitcoin.Address(addressToSend[i]); + tx.addOutput(addressToSend[i], outputsToSend[i]); + } + + //hash the transaction-- this has will be used as an input to the signature function + var txHash = tx.hashTransactionForSignature(tx.ins[index].script, index, 1); + + return txHash; + + } + + //function aGetTransaction + //TODO: rename + //generateas a transaction from a set of keys, outputs, signatures and addresses to send to + function aGetTransaction(publickeys, outputsToSpend, outputsToSend, addressToSend, sigs) { + + + //create a new transaction + var tx = new Bitcoin.Transaction(); + + var ins = []; + var outs = []; + + //generate the scripts to spend the outputs + for (var i = 0; i < outputsToSpend.length; i++) { + + var len = sigs[i].length; + var script = []; + + //append the signature + script = script.concat(sigs[i]); + + //prepend the length of the signature + script.unshift(len); + script.unshift(0x00); + + //push the script used to validate the spend + script.push(0x4c); + script.push(105); + script.push(0x52); + script.push(33); + script = script.concat(publickeys[i][0]); + script.push(33); + script = script.concat(publickeys[i][1]); + script.push(33); + script = script.concat(publickeys[i][2]); + script.push(0x53); + script.push(0xae); + + //add the input to the transaction referencing the output to spend + var p = outputsToSpend[i].transactionId + ':' + outputsToSpend[i].outputIndex.toString(); + tx.addInput(p); + + //set the script on the input + tx.ins[i].script = new Bitcoin.Script(script); + + } + + //add the outputs to the transaction + for (var i = 0; i < outputsToSend.length; i++) { + tx.addOutput(addressToSend[i], outputsToSend[i]); + } + + var txHash = Array.apply([], tx.serialize()); + + return txHash; + } + + + function aGetTransactionData(params, callback) { + + + var derivedPublicKeys = []; + var derivedPrivateKeys = []; + + var signatures = []; + var hashesForSigning = []; + for (var i = 0; i < params.outputsToSpend.length; i++) { + var path = params.paths[i]; + + //derive the hashes for signing off on each input + var hashForSigning = aMultiSigHashForSigning3(params.publicKeys[i][0], params.publicKeys[i][1], params.publicKeys[i][2], i, params.outputsToSpend, params.amountsToSend, params.addressToSend); + //add to collection so they can be provided to the server later + //this saves the same process having to be done on the server side + hashesForSigning.push(Bitcoin.convert.bytesToHex(hashForSigning)); + + //get the user's hot private key + var key = params.userHotPrivKeys[i]; + + //sign the input + var sig = key.sign(hashForSigning).concat([1]); + + //add the signature + signatures.push(sig); + } + + //get the transaction and return along with the hashes used to sign + var txn = aGetTransaction(params.publicKeys, params.outputsToSpend, params.amountsToSend, params.addressToSend, signatures); + + //generate the signatures + //TO DO: check call back? + return callback("", hashesForSigning, Bitcoin.convert.bytesToHex(txn)); + } + + this.isAddressValid = isAddressValid; + function isAddressValid(address) { + var addrValid = true; + try { + var addressCheck = new Bitcoin.Address(address); + addressCheck.toString(); + } catch (err) { + addrValid = false; + } + return addrValid; + } + + this.sendTransaction = sendTransaction; + function sendTransaction(sendType, friendUserName, addressTo, amount, twoFactorCode, callback) { + + + //setup handles to report back progress + var progressel = ''; + var messel = ''; + + if (sendType == 'friend') { + messel = '#textMessageSend'; + progressel = '#sendfriendprogstatus'; + } + + if (sendType == 'standard') { + messel = '#textMessageSendStd'; + progressel = '#sendstdprogstatus'; + } + + if (sendType == 'invoice') { + messel = '#textMessageSendInv'; + progressel = '#sendinvprogstatus'; + } + + + var minersFee = 10000; + //?? + amount = Math.round(amount); + + if (m_this.m_settings.MinersFee) { + minersFee = m_this.m_settings.MinersFee; + } + + + //in the case of mobile the twoFactorCode is actually the device key + //and will return a twofactor override code + + getHotHash(twoFactorCode, function (err, hothash, twoFactorOverride) { + + if (twoFactorOverride) { + twoFactorCode = twoFactorOverride; + } + + //initialise the hot private key space + var bipHot = Bitcoin.HDWallet.fromSeedHex(hothash); + + //initialise the 3 public keys + var bipHotPub = Bitcoin.HDWallet.fromBase58(m_this.m_walletinfo.hotPub); + var bipCold = Bitcoin.HDWallet.fromBase58(m_this.m_walletinfo.coldPub); + var bipNinki = Bitcoin.HDWallet.fromBase58(m_this.m_walletinfo.ninkiPubKey); + + + var pdata = { guid: m_this.m_guid, sharedid: m_this.m_sharedid }; + + + $(messel).html('Getting unspent outputs...'); + + + API.post("/api/1/u/getunspentoutputs", pdata, function (err, outputs) { + + var outputs = JSON.parse(outputs); + var outputsToSpend = []; + var amountsToSend = []; + var addressToSend = []; + var userHotPrivKeys = []; + + var nodeLevels = []; + var publicKeys = []; + var packet = { addressToSend: addressToSend, amountsToSend: amountsToSend, outputsToSpend: outputsToSpend, userHotPrivKeys: userHotPrivKeys, guid: m_this.m_guid, paths: nodeLevels, publicKeys: publicKeys }; + + //get outputs to spend, calculate change amount minus miners fee + + $(progressel).width('20%'); + + //iterate the unspent outputs and select the first n that equal the amount to spend + //TO DO: do this before doing any key derivation + var amountSoFar = 0; + for (var i = 0; i < outputs.length; i++) { + + var pitem = outputs[i]; + var pout = { transactionId: pitem.TransactionId, outputIndex: pitem.OutputIndex, amount: pitem.Amount, address: pitem.Address } + + nodeLevels.push(pitem.NodeLevel); + + outputsToSpend.push(pout); + + //derive the private key to use for signing + userHotPrivKeys.push(deriveChild(pitem.NodeLevel, bipHot).priv); + + //derive the public keys to use for script generation + //this could be cached on the server as no privacy or hand-off issue that I can see + + var dbipHotPub = ""; + var dbipColdPub = ""; + var dbipNinkiPub = ""; + + if (pitem.PK1.length > 0) { + + dbipHotPub = Bitcoin.convert.hexToBytes(pitem.PK1); + dbipColdPub = Bitcoin.convert.hexToBytes(pitem.PK2); + dbipNinkiPub = Bitcoin.convert.hexToBytes(pitem.PK3); + + } else { + + dbipHotPub = deriveChild(pitem.NodeLevel, bipHotPub).pub.toBytes(); + dbipColdPub = deriveChild(pitem.NodeLevel, bipCold).pub.toBytes(); + dbipNinkiPub = deriveChild(pitem.NodeLevel, bipNinki).pub.toBytes(); + + } + + publicKeys.push([dbipHotPub, dbipColdPub, dbipNinkiPub]); + + //add the amount + amountSoFar += pitem.Amount; + + if ((amountSoFar - amount) >= minersFee) { + break; + } + + } + + amountsToSend.push(amount); + + //now create the change + //again move this before the key derivation + var changeAmount = amountSoFar - (amount + minersFee); + + if (changeAmount < 0) { + //this message needs to be handled carefully + //as the wallet will most likely have pending confirmations + //rather than insufficent funds + return callback(true, "ErrInsufficientFunds"); + } - //create the change address, this must be done on the client - createAddress('m/0/1', changeAmount, function (err, changeaddress) { + if (changeAmount > 0) { + amountsToSend.push(changeAmount); + } - if (!err) { + //create a new address for my change to be sent back to me - $(progressel).width('40%'); - if (changeAmount > 0) { - addressToSend.push(changeaddress); - } + //if we are sending money to a contact or paying an invoice + //we need to derive addresses on their behalf + + if (sendType == 'friend' || sendType == 'invoice') { + var params = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: friendUserName, amount: amount }; + + //generate the address for the contact + //this must be done on the client + $(messel).html('Creating address...'); + createAddressForFriend(friendUserName, function (err, address) { + + if (!err) { + + $(progressel).width('40%'); + addressToSend.push(address); - //now get the transaction - $(messel).html('Creating transaction...'); - aGetTransactionData(packet, function (err, hashesForSigning, rawTransaction) { - $(progressel).width('60%'); - var jsonSend = { guid: m_this.m_guid, hashesForSigning: hashesForSigning, rawTransaction: rawTransaction, pathsToSignWith: nodeLevels } - var jsonp1 = { jsonPacket: JSON.stringify(jsonSend), sharedid: m_this.m_sharedid, userName: '' }; - $(messel).html('Counter-signing transaction...'); - API.post("/api/1/u/sendtransaction", jsonp1, function (err, result) { + //create the change address, this must be done on the client + $(messel).html('Creating change address...'); + + createAddress('m/0/1', changeAmount, function (err, changeaddress) { - $(progressel).width('80%'); if (!err) { - $(progressel).width('100%'); - $(messel).html('Transaction broadcast...'); + $(progressel).width('60%'); + if (changeAmount > 0) { + addressToSend.push(changeaddress); + } + //now get the transaction data + $(messel).html('Creating transaction...'); + aGetTransactionData(packet, function (err, hashesForSigning, rawTransaction) { + + $(progressel).width('80%'); + var jsonSend = { guid: m_this.m_guid, hashesForSigning: hashesForSigning, rawTransaction: rawTransaction, pathsToSignWith: nodeLevels } + var jsonp1 = { guid: m_this.m_guid, jsonPacket: JSON.stringify(jsonSend), sharedid: m_this.m_sharedid, twoFactorCode: twoFactorCode, userName: friendUserName }; + $(messel).html('Counter-signing transaction...'); + + //send the transaction to the service for countersigning and broadcast to the network + + + //requires 2 factor authentication + + API.post("/api/1/u/sendtransaction", jsonp1, function (err, result) { + + if (!err) { + $(progressel).width('100%'); + $(messel).html('Transaction broadcast...'); + //error handling? + + //we have a transaction id so lets make a note of the transaction in the database + var params = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: friendUserName, transactionid: result, address: address, amount: amount }; + API.post("/api/1/u/createtransactionrecord", params, function (err, result) { + return callback(err, params.transactionid); + }); + } else { + + //handle error messages returned from the server + if (result == 'ErrSingleTransactionLimit') { + $(progressel).width('100%'); + $(messel).html('Transaction Failed: Single limit exceeded'); + return callback(err, result); + } + + if (result == 'ErrDailyTransactionLimit') { + $(progressel).width('100%'); + $(messel).html('Transaction Failed: Daily limit exceeded'); + return callback(err, result); + } + + if (result == 'ErrTransactionsPerDayLimit') { + $(progressel).width('100%'); + $(messel).html('Transaction Failed: Daily number limit exceeded'); + return callback(err, result); + } + + if (result == 'ErrTransactionsPerHourLimit') { + $(progressel).width('100%'); + $(messel).html('Transaction Failed: Hourly number limit exceeded'); + return callback(err, result); + } + if (result == 'ErrInvalidRequest') { + $(progressel).width('100%'); + $(messel).html('Transaction Failed: Invalid request'); + return callback(err, result); + } + + if (result == 'ErrLocked') { + $(progressel).width('100%'); + $(messel).html('Transaction Failed: Account is unavailable'); + return callback(err, result); + } + + + if (result == 'ErrBroadcastFailed') { + $(progressel).width('100%'); + $(messel).html('Transaction Failed: Not accepted'); + return callback(err, result); + } + + if (result == "Invalid two factor code") { + $(progressel).width('100%'); + $(messel).html("Invalid two factor code"); + return callback(err, result); + } - //we have a transaction id so lets make a note of the transaction in the database - var params = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: 'External', transactionid: result, address: addressTo, amount: amount }; - API.post("/api/1/u/createtransactionrecord", params, function (err, result) { - return callback(err, result); + } + }); }); } else { - if (result == 'ErrSingleTransactionLimit') { + //create address error + if (changeaddress == 'ErrInvalidRequest') { $(progressel).width('100%'); - $(messel).html('Transaction Failed: Single limit exceeded'); - return callback(err, result); + $(messel).html('Transaction Failed: Invalid request'); + return callback(err, changeaddress); } + } + }); + } else { - if (result == 'ErrDailyTransactionLimit') { - $(progressel).width('100%'); - $(messel).html('Transaction Failed: Daily limit exceeded'); - return callback(err, result); - } + if (address == 'ErrInvalidRequest') { + $(progressel).width('100%'); + $(messel).html('Transaction Failed: Invalid request'); + return callback(err, address); + } - if (result == 'ErrTransactionsPerDayLimit') { - $(progressel).width('100%'); - $(messel).html('Transaction Failed: Daily number limit exceeded'); - return callback(err, result); - } + } + }); + } else { - if (result == 'ErrTransactionsPerHourLimit') { - $(progressel).width('100%'); - $(messel).html('Transaction Failed: Hourly number limit exceeded'); - return callback(err, result); - } - if (result == 'ErrInvalidRequest') { - $(progressel).width('100%'); - $(messel).html('Transaction Failed: Invalid request'); - return callback(err, result); - } + $(messel).html('Creating address...'); + + addressToSend.push(addressTo); + + //create the change address, this must be done on the client + createAddress('m/0/1', changeAmount, function (err, changeaddress) { + + if (!err) { + + $(progressel).width('40%'); + if (changeAmount > 0) { + addressToSend.push(changeaddress); + } + //now get the transaction + $(messel).html('Creating transaction...'); + aGetTransactionData(packet, function (err, hashesForSigning, rawTransaction) { + $(progressel).width('60%'); + var jsonSend = { guid: m_this.m_guid, hashesForSigning: hashesForSigning, rawTransaction: rawTransaction, pathsToSignWith: nodeLevels } + var jsonp1 = { guid: m_this.m_guid, jsonPacket: JSON.stringify(jsonSend), sharedid: m_this.m_sharedid, twoFactorCode: twoFactorCode, userName: '' }; + $(messel).html('Counter-signing transaction...'); + API.post("/api/1/u/sendtransaction", jsonp1, function (err, result) { - if (result == 'ErrBroadcastFailed') { + $(progressel).width('80%'); + if (!err) { $(progressel).width('100%'); - $(messel).html('Transaction Failed: Not accepted'); - return callback(err, result); - } + $(messel).html('Transaction broadcast...'); - } + + //we have a transaction id so lets make a note of the transaction in the database + var params = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: 'External', transactionid: result, address: addressTo, amount: amount }; + API.post("/api/1/u/createtransactionrecord", params, function (err, result) { + return callback(err, result); + }); + } else { + if (result == 'ErrSingleTransactionLimit') { + $(progressel).width('100%'); + $(messel).html('Transaction Failed: Single limit exceeded'); + return callback(err, result); + } + + if (result == 'ErrDailyTransactionLimit') { + $(progressel).width('100%'); + $(messel).html('Transaction Failed: Daily limit exceeded'); + return callback(err, result); + } + + if (result == 'ErrTransactionsPerDayLimit') { + $(progressel).width('100%'); + $(messel).html('Transaction Failed: Daily number limit exceeded'); + return callback(err, result); + } + + if (result == 'ErrTransactionsPerHourLimit') { + $(progressel).width('100%'); + $(messel).html('Transaction Failed: Hourly number limit exceeded'); + return callback(err, result); + } + + if (result == 'ErrInvalidRequest') { + $(progressel).width('100%'); + $(messel).html('Transaction Failed: Invalid request'); + return callback(err, result); + } + + + if (result == 'ErrBroadcastFailed') { + $(progressel).width('100%'); + $(messel).html('Transaction Failed: Not accepted'); + return callback(err, result); + } + + if (result == "Invalid two factor code") { + $(progressel).width('100%'); + $(messel).html("Invalid two factor code"); + return callback(err, result); + } + + + } + }); }); - }); + } else { + + //create address error + if (changeaddress == 'ErrInvalidRequest') { + $(progressel).width('100%'); + $(messel).html('Transaction Failed: Invalid request'); + return callback(err, changeaddress); + } + + } + }); + } + + }); + + }); + + + } + + //function createAddress + //this function creates an address for the user + this.createAddress = createAddress; + + function createAddress(nodePath, changeamount, callback) { + + + if (changeamount > 0) { + var postData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, pathToUse: nodePath }; + + //get the next leaf from the user's node space + API.post("/api/1/u/getnextleaf", postData, function (err, leaf) { + + var path = nodePath + '/' + leaf; + + //derive the 3 public keys for the new address + //TODO: possible to use an encrypted cache for performance improvements + + var bipHot = Bitcoin.HDWallet.fromBase58(m_this.m_walletinfo.hotPub); + var bipCold = Bitcoin.HDWallet.fromBase58(m_this.m_walletinfo.coldPub); + var bipNinki = Bitcoin.HDWallet.fromBase58(m_this.m_walletinfo.ninkiPubKey); + + var hotKey = deriveChild(path, bipHot); + var coldKey = deriveChild(path, bipCold); + var ninkiKey = deriveChild(path, bipNinki); + + //now create the multisig address + var script = [0x52]; + script.push(33); + script = script.concat(hotKey.pub.toBytes()); + script.push(33); + script = script.concat(coldKey.pub.toBytes()); + script.push(33); + script = script.concat(ninkiKey.pub.toBytes()); + script.push(0x53); + script.push(0xae); + var address = multiSig(script); + + //post the address back to the server + //this allows the server to monitor for balances etc. + var postData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, path: path, address: address, pk1: hotKey.pub.toString(), pk2: coldKey.pub.toString(), pk3: ninkiKey.pub.toString() }; + API.post("/api/1/u/createaddress", postData, function (err, result) { + if (!err) { + return callback(err, address); } else { + return callback(err, result); + } + + }); + + //now update the address to the server + + }); + + } else { + return callback(false, "skipped"); + } + + } + + //function createAddress + //this function creates an address on behalf of a user's contact + this.createAddressForFriend = createAddressForFriend; + function createAddressForFriend(username, callback) { + + var postData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: username }; + + + API.post("/api/1/u/getfriendpacket", postData, function (err, packet) { + + //get the packet from friend containing the public key set to + //be used for address generation and decrypt + + var msg = openpgp.message.readArmored(packet); + var decrypted = openpgp.decryptAndVerifyMessage(m_this.m_privKey, [m_this.m_pubKey], msg); + + var pubkeys = JSON.parse(sanitizer.sanitize(decrypted.text)); + //var pubkeys = decrypt(packet, password, params.oguid); + //only allow address to be created if the packet has been validated + if (pubkeys.validated) { + //get the next leaf on the contacts address space node assigned to this user + API.post("/api/1/u/getnextleafforfriend", postData, function (err, leaf) { + + //derive the public keys + var path = 'm/' + leaf; + + var bipHot = Bitcoin.HDWallet.fromBase58(pubkeys.hotPub); + var bipCold = Bitcoin.HDWallet.fromBase58(pubkeys.coldPub); + var bipNinki = Bitcoin.HDWallet.fromBase58(pubkeys.ninkiPub); + + var hotKey = deriveChild(path, bipHot); + var coldKey = deriveChild(path, bipCold); + var ninkiKey = deriveChild(path, bipNinki); + + //now create the multisig address + var script = [0x52]; + script.push(33); + script = script.concat(hotKey.pub.toBytes()); + script.push(33); + script = script.concat(coldKey.pub.toBytes()); + script.push(33); + script = script.concat(ninkiKey.pub.toBytes()); + script.push(0x53); + script.push(0xae); + var address = multiSig(script); + + //register the address with the server + var postData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: username, address: address, leaf: leaf, pk1: hotKey.pub.toString(), pk2: coldKey.pub.toString(), pk3: ninkiKey.pub.toString() }; + API.post("/api/1/u/createaddressforfriend", postData, function (err, result) { + + if (!err) { + return callback(err, address); + } else { + return callback(err, result); + } + + }); + + //now update the address to the server + + }); + } else { + + //something very bad has happened + //attempting to derive an address for a non validated contact + return callback(true, "ErrInvalid"); + } + + }); + + } + + //multi sig address hash + function multiSig(rs) { + var x = Bitcoin.Crypto.RIPEMD160(Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(rs))); + x = Bitcoin.convert.wordArrayToBytes(x); + x.unshift(0x5); + var r = x; + r = Bitcoin.Crypto.SHA256(Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(r))); + var checksum = Bitcoin.convert.wordArrayToBytes(r).slice(0, 4); + var address = Bitcoin.base58.encode(x.concat(checksum)); + return address; + } + + + + + this.signMessage = signMessage; + function signMessage(key, guid, callback) { + + if (key.length > 0) { + var bip39 = new BIP39(); + + var mkey = bip39.mnemonicToHex(key); + var bytes = []; + for (var i = 0; i < guid.length; ++i) { + bytes.push(guid.charCodeAt(i)); + } + + var message = Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(bytes)).toString(); + var skey = Bitcoin.HDWallet.fromSeedHex(mkey); + + var sig = Bitcoin.convert.bytesToHex(skey.priv.sign(Bitcoin.convert.hexToBytes(message))); + + callback(false, sig); + } else { + callback(false, ''); + } + + } + + + this.decodeKey = decodeKey; + function decodeKey(key) { + var bip39 = new BIP39(); + var mkey = bip39.mnemonicToHex(key); + return mkey; + } + + this.encodeKey = encodeKey; + function encodeKey(key) { + var bip39 = new BIP39(); + var mkey = bip39.entropyToMnemonic(key); + return mkey; + } + + + this.getPassKey = getPassKey; + function getPassKey(hotkey, coldkey, epass, iv, callback) { + + var bip39 = new BIP39(); + + var hkey = bip39.mnemonicToHex(hotkey); + var ckey = bip39.mnemonicToHex(coldkey); + + var hckey = Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(Bitcoin.convert.hexToBytes(hkey + ckey))).toString(); + + var passkey = decryptNp(epass, hckey, iv); + + callback(false, passkey); + + } + + + //reengineer this + //pull back validated proprty from database + //only do this part on friend selection + this.getUserNetwork = getUserNetwork; + function getUserNetwork(callback) { + var postData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid }; + API.post("/api/1/u/getusernetwork", postData, function (err, data) { + var friends = JSON.parse(data); + return callback(err, friends); + }); + } + + + this.getFriend = getFriend; + function getFriend(username, callback) { + + var postData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: username }; + + return API.post("/api/1/u/getfriend", postData, function (err, data) { + + var friend = JSON.parse(data); + + return callback(err, friend); + }); + + } + + + this.createFriend = createFriend; + function createFriend(username, uimessage, callback) { + + //get the next friend node + var node = ""; + var postData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: username }; + + if ($(uimessage)) { + $(uimessage).html('Assigning node...'); + } + + API.post("/api/1/u/getnextnodeforfriend", postData, function (err, node) { - //create address error - if (changeaddress == 'ErrInvalidRequest') { - $(progressel).width('100%'); - $(messel).html('Transaction Failed: Invalid request'); - return callback(err, changeaddress); - } + var bipHot = Bitcoin.HDWallet.fromBase58(m_this.m_walletinfo.hotPub); - } - }); - } + var bipCold = Bitcoin.HDWallet.fromBase58(m_this.m_walletinfo.coldPub); + var bipNinki = Bitcoin.HDWallet.fromBase58(m_this.m_walletinfo.ninkiPubKey); - }); + setTimeout(function () { - } + var hotKey = deriveChild(node, bipHot).toString(); - //function createAddress - //this function creates an address for the user - this.createAddress = createAddress; + if ($(uimessage)) { + $(uimessage).html('Deriving address.'); + } - function createAddress(nodePath, changeamount, callback) { + setTimeout(function () { + var coldKey = deriveChild(node, bipCold).toString(); + if ($(uimessage)) { + $(uimessage).html('Deriving address..'); + } - if (changeamount > 0) { - var postData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, pathToUse: nodePath }; + setTimeout(function () { - //get the next leaf from the user's node space - API.post("/api/1/u/getnextleaf", postData, function (err, leaf) { + var ninkiKey = deriveChild(node, bipNinki).toString(); + if ($(uimessage)) { + $(uimessage).html('Deriving address...'); + } + //get the friends public RSA key + var rsaKey = ''; - var path = nodePath + '/' + leaf; + $(uimessage).html('Get PGP keys...'); + API.post("/api/1/u/getrsakey", postData, function (err, rsaKey) { - //derive the 3 public keys for the new address - //TODO: possible to use an encrypted cache for performance improvements + var publicKeys = openpgp.key.readArmored(rsaKey); + if ($(uimessage)) { + $(uimessage).html('Encrypting data...'); + } - var bipHot = Bitcoin.HDWallet.fromBase58(m_this.m_walletinfo.hotPub); - var bipCold = Bitcoin.HDWallet.fromBase58(m_this.m_walletinfo.coldPub); - var bipNinki = Bitcoin.HDWallet.fromBase58(m_this.m_walletinfo.ninkiPubKey); + var pubKey = publicKeys.keys[0]; - var hotKey = deriveChild(path, bipHot); - var coldKey = deriveChild(path, bipCold); - var ninkiKey = deriveChild(path, bipNinki); + var message = hotKey + coldKey + ninkiKey; - //now create the multisig address - var script = [0x52]; - script.push(33); - script = script.concat(hotKey.pub.toBytes()); - script.push(33); - script = script.concat(coldKey.pub.toBytes()); - script.push(33); - script = script.concat(ninkiKey.pub.toBytes()); - script.push(0x53); - script.push(0xae); - var address = multiSig(script); + var encrypted = openpgp.signAndEncryptMessage([pubKey], m_this.m_privKey, message); - //post the address back to the server - //this allows the server to monitor for balances etc. - var postData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, path: path, address: address }; - API.post("/api/1/u/createaddress", postData, function (err, result) { - if (!err) { - return callback(err, address); - } else { - return callback(err, result); - } + var result = ""; - }); + var postFriendData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: username, node: node, packetForFriend: encrypted, validationHash: '' }; + API.post("/api/1/u/createfriend", postFriendData, function (err, result) { - //now update the address to the server + return callback(err, result); - }); + }); - } else { - return callback(false, "skipped"); - } + }); + + }, 50); + }, 50); + }, 50); + + }); } - //function createAddress - //this function creates an address on behalf of a user's contact - this.createAddressForFriend = createAddressForFriend; - function createAddressForFriend(username, callback) { + this.acceptFriendRequest = acceptFriendRequest; + function acceptFriendRequest(username, callback) { - var postData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: username }; - //get the encrypted packet for the user's contact - //don't need to call this??? - API.post("/api/1/u/getfriendpacket", postData, function (err, packet) { + var postData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: username }; + API.post("/api/1/u/getfriendrequestpacket", postData, function (err, packet) { //get the packet from friend containing the public key set to - //be used for address generation and decrypt + //be used for address generation - API.getUserPacket(m_this.m_guid, m_this.m_sharedid, function (err, encpacket) { + var message = packet; - //get the RSA private key from the encrypted payload - var rsaKeyPair = decrypt(encpacket.Payload, m_this.m_password, encpacket.IV); + var rsaKey = ''; + var postRSAData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: username }; + API.post("/api/1/u/getrsakey", postRSAData, function (err, rsaKey) { + + //friends public key + var publicKeys = openpgp.key.readArmored(rsaKey); - var publicKeys = openpgp.key.readArmored(rsaKeyPair.RSAPub); - var privKeys = openpgp.key.readArmored(rsaKeyPair.RSAPriv); + //we need to get friends public key here to verify the signature on the packet + //then out of band if they verify the signature belongs to them- they are good - var privKey = privKeys.keys[0]; var pubKey = publicKeys.keys[0]; - if (privKey.decrypt(m_this.m_sharedid)) { + var msg = openpgp.message.readArmored(message); + var decrypted = openpgp.decryptAndVerifyMessage(m_this.m_privKey, [pubKey], msg); - var msg = openpgp.message.readArmored(packet); - var decrypted = openpgp.decryptAndVerifyMessage(privKey, [pubKey], msg); + var isValid = decrypted.signatures[0].valid; + if (isValid) { + + var keys = sanitizer.sanitize(decrypted.text); - var pubkeys = JSON.parse(decrypted.text); - //var pubkeys = decrypt(packet, password, params.oguid); + var key1 = keys.substring(0, 111); + var key2 = keys.substring(111, 222); + var key3 = keys.substring(222, 333); - //get the next leaf on the contacts address space node assigned to this user - API.post("/api/1/u/getnextleafforfriend", postData, function (err, leaf) { - //derive the public keys - var path = 'm/' + leaf; + var packet = { + hotPub: key1, + coldPub: key2, + ninkiPub: key3, + rsaKey: rsaKey, + validated: false + }; - var bipHot = Bitcoin.HDWallet.fromBase58(pubkeys.hotPub); - var bipCold = Bitcoin.HDWallet.fromBase58(pubkeys.coldPub); - var bipNinki = Bitcoin.HDWallet.fromBase58(pubkeys.ninkiPub); + var encrypted = openpgp.signAndEncryptMessage([m_this.m_pubKey], m_this.m_privKey, JSON.stringify(packet)); - var hotKey = deriveChild(path, bipHot); - var coldKey = deriveChild(path, bipCold); - var ninkiKey = deriveChild(path, bipNinki); + postData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: username, packet: encrypted, validationHash: '' }; - //now create the multisig address - var script = [0x52]; - script.push(33); - script = script.concat(hotKey.pub.toBytes()); - script.push(33); - script = script.concat(coldKey.pub.toBytes()); - script.push(33); - script = script.concat(ninkiKey.pub.toBytes()); - script.push(0x53); - script.push(0xae); - var address = multiSig(script); + API.post("/api/1/u/updatefriend", postData, function (err, result) { + return callback(err, result); + }); + } - //register the address with the server - var postData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: username, address: address, leaf: leaf }; - API.post("/api/1/u/createaddressforfriend", postData, function (err, result) { + }); + }); - if (!err) { - return callback(err, address); - } else { - return callback(err, result); - } + } - }); - //now update the address to the server - }); - } + this.getFingerPrint = getFingerPrint; + function getFingerPrint(callback) { - }); + //now all we need to do is provide the fingerprint of the user's public key - }); + return callback(false, m_this.m_pubKey.primaryKey.fingerprint); } - //multi sig address hash - function multiSig(rs) { - var x = Bitcoin.Crypto.RIPEMD160(Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(rs))); - x = Bitcoin.convert.wordArrayToBytes(x); - x.unshift(0x5); - var r = x; - r = Bitcoin.Crypto.SHA256(Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(r))); - var checksum = Bitcoin.convert.wordArrayToBytes(r).slice(0, 4); - var address = Bitcoin.base58.encode(x.concat(checksum)); - return address; - } + this.verifyFriendData = verifyFriendData; + function verifyFriendData(username, code, callback) { + + //update packet with status as verified and log + //the verification code - this.getUserNetwork = getUserNetwork; - function getUserNetwork(callback) { + var postData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: username }; + API.post("/api/1/u/getfriendpacket", postData, function (err, packet) { - API.getUserPacket(m_this.m_guid, m_this.m_sharedid, function (err, encpacket) { - var rsaKeys = decrypt(encpacket.Payload, m_this.m_password, encpacket.IV); + //this is set to a fixed value, i wanted it to be set as blank as we encrypt these keys aes256 anyway + //there was no easy way to change this password in the library so i opted to go with a fixed password + //instead of a blank one - var privKeys = openpgp.key.readArmored(rsaKeys.RSAPriv); - var pubKeys = openpgp.key.readArmored(rsaKeys.RSAPub); - var privKey = privKeys.keys[0]; - var pubKey = pubKeys.keys[0]; + var msg = openpgp.message.readArmored(packet); + var decrypted = openpgp.decryptAndVerifyMessage(m_this.m_privKey, [m_this.m_pubKey], msg); - if (privKey.decrypt(m_this.m_sharedid)) { + var isValid = decrypted.signatures[0].valid; + if (isValid) { - var postData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid }; + var payload = JSON.parse(sanitizer.sanitize(decrypted.text)); - return API.post("/api/1/u/getusernetwork", postData, function (err, data) { + var publicKeysUsed = openpgp.key.readArmored(payload.rsaKey); + var pubKeyUsed = publicKeysUsed.keys[0]; - var friends = JSON.parse(data); + if (code == pubKeyUsed.primaryKey.fingerprint) { - for (var i = 0; i < friends.length; i++) { - var friend = friends[i]; - if (friend.packet != '') { + var reencrypt = { + hotPub: payload.hotPub, + coldPub: payload.coldPub, + ninkiPub: payload.ninkiPub, + rsaKey: payload.rsaKey, + validated: true + }; - var msg = openpgp.message.readArmored(friend.packet); - var decrypted = openpgp.decryptAndVerifyMessage(privKey, [pubKey], msg); + var encryptedPayload = openpgp.signAndEncryptMessage([m_this.m_pubKey], m_this.m_privKey, JSON.stringify(reencrypt)); - var friendprops = JSON.parse(decrypted.text); + postData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: username, packet: encryptedPayload, validationHash: code }; + + API.post("/api/1/u/updatefriend", postData, function (err, result) { + + return callback(err, result); + + }); + + return callback(err, true); + + } else { + + return callback(err, false); + + } - // var payload = decrypt(friend.packet, params.password, params.oguid); - if (friendprops.validated) { - friend.validated = true; - } - } - } - return callback(err, friends); - }); } }); } - this.createFriend = createFriend; - function createFriend(username, callback) { - - //get the next friend node - var node = ""; - var postData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: username }; - API.post("/api/1/u/getnextnodeforfriend", postData, function (err, node) { - var bipHot = Bitcoin.HDWallet.fromBase58(m_this.m_walletinfo.hotPub); - var bipCold = Bitcoin.HDWallet.fromBase58(m_this.m_walletinfo.coldPub); - var bipNinki = Bitcoin.HDWallet.fromBase58(m_this.m_walletinfo.ninkiPubKey); + //status + //0 pending + //1 paid + //2 rejected + //3 notsent - var hotKey = deriveChild(node, bipHot).toString(); - var coldKey = deriveChild(node, bipCold).toString(); - var ninkiKey = deriveChild(node, bipNinki).toString(); + this.createInvoice = createInvoice; + function createInvoice(username, invoice, callback) { - //get the friends public RSA key - var rsaKey = ''; - API.post("/api/1/u/getrsakey", postData, function (err, rsaKey) { + var packetForMe = ""; + var packetForThem = ""; - var publicKeys = openpgp.key.readArmored(rsaKey); + var jsonInvoice = JSON.stringify(invoice); - API.getUserPacket(m_this.m_guid, m_this.m_sharedid, function (err, encpacket) { + //get the contacts RSA key - //here we need to persist the public key used to - //encrypt the data + //encrypt the packet for me with my public rsa key and sign with my private key - //get the RSA private key from the encrypted payload - //TODO: we don;t need to do this bit as the private jey is already encrypted - var rsaPriv = decrypt(encpacket.Payload, m_this.m_password, encpacket.IV); + var rsaKey = ''; + var postRSAData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: username }; + API.post("/api/1/u/getrsakey", postRSAData, function (err, rsaKey) { - var privKeys = openpgp.key.readArmored(rsaPriv.RSAPriv); + var publicKeys = openpgp.key.readArmored(rsaKey); - var privKey = privKeys.keys[0]; - var pubKey = publicKeys.keys[0]; - if (privKey.decrypt(m_this.m_sharedid)) { + var pubKey = publicKeys.keys[0]; + //here we need to persist the public key used to + //encrypt the data - var message = hotKey + coldKey + ninkiKey; + //get the RSA private key from the encrypted payload - var encrypted = openpgp.signAndEncryptMessage([pubKey], privKey, message); + //generate a hash from the RSA key and public keys for verification + var message = jsonInvoice; - var result = ""; + var encrypted = openpgp.signAndEncryptMessage([pubKey], m_this.m_privKey, message); - var postFriendData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: username, node: node, packetForFriend: encrypted, validationHash: '' }; - API.post("/api/1/u/createfriend", postFriendData, function (err, result) { + //encrypt with my public key and sin with my priv key + var packetForMe = openpgp.signAndEncryptMessage([m_this.m_pubKey], m_this.m_privKey, message); - return callback(err, result); - }); + var result = ""; - } + var pdata = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, userName: username, packetForMe: packetForMe, packetForThem: encrypted }; + API.post("/api/1/u/createinvoice", pdata, function (err, invoiceid) { - }); + return callback(err, invoiceid); }); @@ -1236,391 +2352,408 @@ function Engine() { } - this.acceptFriendRequest = acceptFriendRequest; - function acceptFriendRequest(username, callback) { + this.UnpackInvoiceByMe = UnpackInvoiceByMe; + function UnpackInvoiceByMe(invoice, username, callback) { + //here decrypt the invoice with my private key - API.getUserPacket(m_this.m_guid, m_this.m_sharedid, function (err, encpacket) { + var msg = openpgp.message.readArmored(invoice.Packet); + var decrypted = openpgp.decryptAndVerifyMessage(m_this.m_privKey, [m_this.m_pubKey], msg); - //get the RSA private key from the encrypted payload - var rsaKeyPair = decrypt(encpacket.Payload, m_this.m_password, encpacket.IV); + var unpackedInvoice = JSON.parse(sanitizer.sanitize(decrypted.text)); - var postData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: username }; - API.post("/api/1/u/getfriendrequestpacket", postData, function (err, packet) { + callback(false, unpackedInvoice); - //get the packet from friend containing the public key set to - //be used for address generation - var message = packet; + } - var rsaKey = ''; - var postRSAData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: username }; - API.post("/api/1/u/getrsakey", postRSAData, function (err, rsaKey) { + this.UnpackInvoiceForMe = UnpackInvoiceForMe; + function UnpackInvoiceForMe(invoice, username, invtype, callback) { - //friends public key - var publicKeys = openpgp.key.readArmored(rsaKey); + //here decrypt the invoice with my private key - //we need to get friends public key here to verify the signature on the packet - //then out of band if they verify the signature belongs to them- they are good + var rsaKey = ''; + var postRSAData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: username }; + API.post("/api/1/u/getrsakey", postRSAData, function (err, rsaKey) { - //my private key - var privKeys = openpgp.key.readArmored(rsaKeyPair.RSAPriv); + //friends public key - var privKey = privKeys.keys[0]; - var pubKey = publicKeys.keys[0]; + //we need to get friends public key here to verify the signature on the packet + //then out of band if they verify the signature belongs to them- they are good - var success = privKey.decrypt(m_this.m_sharedid); + if (invtype == 'forme') { + var publicKeys = openpgp.key.readArmored(rsaKey); + pubKey = publicKeys.keys[0]; + } else { + pubKey = m_this.m_pubKey; + } - if (success) { + var msg = openpgp.message.readArmored(invoice.Packet); + var decrypted = openpgp.decryptAndVerifyMessage(m_this.m_privKey, [pubKey], msg); - var msg = openpgp.message.readArmored(message); - var decrypted = openpgp.decryptAndVerifyMessage(privKey, [pubKey], msg); + var isValid = decrypted.signatures[0].valid; + if (isValid) { + + var json = JSON.parse(sanitizer.sanitize(decrypted.text)); - var isValid = decrypted.signatures[0].valid; - if (isValid) { + //remove any xss data + callback(false, json); - var keys = decrypted.text; + } else { - var key1 = keys.substring(0, 111); - var key2 = keys.substring(111, 222); - var key3 = keys.substring(222, 333); + callback(false, "error"); - //do we really need to do this? - //the rsa private key is already stored encrypted - //why not re-encrypt with my public key - //then I don;t have to worry about re-encryptng - //the packet if I change my password + } - //so changing password would mean, re-encrypting the main packet - //and re-encrypting the PGP set only + }); - //encrypt and sign with my pgp key pair - //then validate signature on create address - var packet = { - hotPub: key1, - coldPub: key2, - ninkiPub: key3, - rsaKey: rsaKey, - validated: false - }; + } - //encrypt and sign the data using my pgp set - var myPublicKeys = openpgp.key.readArmored(rsaKeyPair.RSAPub); - var myPubKey = myPublicKeys.keys[0]; + //security + this.SaveTwoFactor = SaveTwoFactor; + function SaveTwoFactor(twoFactorCode, verifyToken, callback) { - var encrypted = openpgp.signAndEncryptMessage([myPubKey], privKey, JSON.stringify(packet)); + var postData = { + guid: m_this.m_guid, + sharedid: m_this.m_sharedid, + twoFactorOnLogin: true, + twoFactorCode: twoFactorCode, + verifyToken: verifyToken + }; - postData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: username, packet: encrypted, validationHash: '' }; + API.post("/api/1/u/updatetwofactor", postData, function (err, result) { - API.post("/api/1/u/updatefriend", postData, function (err, result) { - return callback(err, result); - }); - } - } - }); - }); - }); - } + if (!err) { + var bytes = []; + for (var i = 0; i < m_this.m_sharedid.length; ++i) { + bytes.push(m_this.m_sharedid.charCodeAt(i)); + } + var dpacket = Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(bytes)).toString(); + API.post("/api/1/verifyrecoverpacket", { guid: m_this.m_oguid, token: dpacket }, function (err, response) { - this.getFingerPrint = getFingerPrint; - function getFingerPrint(callback) { + callback(err, response); - API.getUserPacket(m_this.m_guid, m_this.m_sharedid, function (err, encpacket) { + }); - //get the RSA private key from the encrypted payload - var rsaKeyPair = decrypt(encpacket.Payload, m_this.m_password, encpacket.IV); - var publicKeys = openpgp.key.readArmored(rsaKeyPair.RSAPub); - var pubKey = publicKeys.keys[0]; + } else { - //now all we need to do is provide the fingerprint of the user's public key + callback(true, result); - return callback(err, pubKey.primaryKey.fingerprint); + } }); + } - this.verifyFriendData = verifyFriendData; - function verifyFriendData(username, code, callback) { - //update packet with status as verified and log - //the verification code + this.SetupTwoFactor = SaveTwoFactor; + function SetupTwoFactor(twoFactorCode, callback) { + + var postData = { + guid: m_this.m_guid, + sharedid: m_this.m_sharedid, + twoFactorOnLogin: true, + twoFactorCode: twoFactorCode, + verifyToken: '' + }; + + API.post("/api/1/u/updatetwofactor", postData, function (err, result) { + $('#API-Token').val(result); - API.getUserPacket(m_this.m_guid, m_this.m_sharedid, function (err, encpacket) { + callback(err, result); - var rsaKeyPair = decrypt(encpacket.Payload, m_this.m_password, encpacket.IV); + }); - var postData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: username }; - API.post("/api/1/u/getfriendpacket", postData, function (err, packet) { + } - var privKeys = openpgp.key.readArmored(rsaKeyPair.RSAPriv); - var pubKeys = openpgp.key.readArmored(rsaKeyPair.RSAPub); + this.ResetTwoFactor = ResetTwoFactor; + function ResetTwoFactor(fguid, fusername, fpwd, callback) { - var privKey = privKeys.keys[0]; - var pubKey = pubKeys.keys[0]; + //stretch password - //this is set to a fixed value, i wanted it to be set as blank as we encrypt these keys aes256 anyway - //there was no easy way to change this password in the library so i opted to go with a fixed password - //instead of a blank one - var success = privKey.decrypt(m_this.m_sharedid); + //download the recovery packet + //decrypt + //return shared secret + //no feedback apart from, please check your email + fpwd = pbkdf2(fpwd, fguid); - if (success) { + API.post("/api/1/getrecoverypacket", { + guid: fguid, + username: fusername + }, function (err, response) { - var msg = openpgp.message.readArmored(packet); - var decrypted = openpgp.decryptAndVerifyMessage(privKey, [pubKey], msg); + if (err) { - var isValid = decrypted.signatures[0].valid; - if (isValid) { + callback(err, response); - var payload = JSON.parse(decrypted.text); + } else { - var publicKeysUsed = openpgp.key.readArmored(payload.rsaKey); - var pubKeyUsed = publicKeysUsed.keys[0]; + //decrypt packet - if (code == pubKeyUsed.primaryKey.fingerprint) { + var jpacket = JSON.parse(response); - var reencrypt = { - hotPub: payload.hotPub, - coldPub: payload.coldPub, - ninkiPub: payload.ninkiPub, - rsaKey: payload.rsaKey, - validated: true - }; + try { + var dpacket = decrypt(jpacket.packet, fpwd, jpacket.IV); - var encryptedPayload = openpgp.signAndEncryptMessage([pubKey], privKey, JSON.stringify(reencrypt)); + API.post("/api/1/verifyrecoverpacket", { guid: fguid, token: dpacket }, function (err, response) { - postData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: username, packet: encryptedPayload, validationHash: code }; + callback(err, response); - API.post("/api/1/u/updatefriend", postData, function (err, result) { + }); + } catch (error) { - return callback(err, result); + callback(true, "error"); - }); + } - return callback(err, true); + } - } else { + fpwd = ''; - return callback(err, false); + }); - } + } - } - } - }); + this.ChangePassword = ChangePassword; + function ChangePassword(twoFactorCode, oldpassword, newpassword, progbar, progmess, reset, message1, message2, callback) { - }); - } + API.getWalletFromServer(m_this.m_guid, m_this.m_secret, twoFactorCode, false, function (err, wallet) { + if (!err) { + //check password strength - //status - //0 pending - //1 paid - //2 rejected - //3 notsent + //stretch old password + //verify that it matches the current one - this.createInvoice = createInvoice; - function createInvoice(username, invoice, callback) { + getHotHash("", function (err, hothash) { - var packetForMe = ""; - var packetForThem = ""; + $(progbar).width('40%'); + $(progmess).html('Securing password...'); + setTimeout(function () { - var jsonInvoice = JSON.stringify(invoice); + //if password reset do not pbkdf the password - //get the contacts RSA key + oldpassword = pbkdf2(oldpassword, m_this.m_oguid); - //encrypt the packet for me with my public rsa key and sign with my private key + //get the two packets + $(progbar).width('40%'); + $(progmess).html('Getting packets...'); - var rsaKey = ''; - var postRSAData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: username }; - API.post("/api/1/u/getrsakey", postRSAData, function (err, rsaKey) { + // $("#chngpwdprogbar").width('50%'); + //$("#chngpwdprogmess").html('Decrypting account packet...'); + //decrypt with the old password + var decryptedWithOld = true; + var decryptedPayload = ''; + try { + decryptedPayload = decrypt(wallet.Payload, m_this.m_password, wallet.IV); + } catch (err) { + decryptedWithOld = false; + } - var publicKeys = openpgp.key.readArmored(rsaKey); + if (decryptedWithOld) { - API.getUserPacket(m_this.m_guid, m_this.m_sharedid, function (err, encpacket) { + $(progbar).width('80%'); + $(progmess).html('Securing new password...'); + API.getUserPacket(m_this.m_guid, m_this.m_sharedid, function (err, encpacket) { - //here we need to persist the public key used to - //encrypt the data + var decryptedUsrWithOld = true; + var rsaKeyPair = ''; + try { + rsaKeyPair = decrypt(encpacket.Payload, m_this.m_password, encpacket.IV); + } catch (err) { + decryptedUsrWithOld = false; + } - //get the RSA private key from the encrypted payload - var rsaKeyPair = decrypt(encpacket.Payload, m_this.m_password, encpacket.IV); - var privKeys = openpgp.key.readArmored(rsaKeyPair.RSAPriv); - var myPublicKeys = openpgp.key.readArmored(rsaKeyPair.RSAPub); - var myPubKey = myPublicKeys.keys[0]; + if (decryptedUsrWithOld) { - var privKey = privKeys.keys[0]; - var pubKey = publicKeys.keys[0]; + //get the verification packet - if (privKey.decrypt(m_this.m_sharedid)) { - //generate a hash from the RSA key and public keys for verification - var message = jsonInvoice; + API.post("/api/1/getrecoverypacket", { + guid: m_this.m_guid + }, function (err, response) { - var encrypted = openpgp.signAndEncryptMessage([pubKey], privKey, message); - //encrypt with my public key and sin with my priv key - var packetForMe = openpgp.signAndEncryptMessage([myPubKey], privKey, message); + //decrypt packet + var decryptedVerWithOld = true; + var jpacket = JSON.parse(response); + var veripacket = ''; + try { + veripacket = decryptNp(jpacket.packet, m_this.m_password, jpacket.IV); + } + catch (verror) { + decryptedVerWithOld = false; + } - var result = ""; + if (decryptedVerWithOld) { - var pdata = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, userName: username, packetForMe: packetForMe, packetForThem: encrypted }; - API.post("/api/1/u/createinvoice", pdata, function (err, invoiceid) { + setTimeout(function () { - return callback(err, invoiceid); - }); + newpassword = pbkdf2(newpassword, m_this.m_oguid); - } - }); + $(progbar).width('80%'); + $(progmess).html('Encrypting account packet...'); - }); + var newpayloadsuccess = true; + var newpayload = ''; + var newusrpayload = ''; + var newveripacket = ''; + var newpasspacket = ''; + var newAIV = ''; + var newUIV = ''; + var newRIV = ''; + var newPIV = '' + try { - } + newpayload = encrypt(decryptedPayload, newpassword); + newusrpayload = encrypt(rsaKeyPair, newpassword); + newveripacket = encryptNp(veripacket, newpassword); - this.UnpackInvoiceByMe = UnpackInvoiceByMe; - function UnpackInvoiceByMe(invoice, username, callback) { + if (decryptedPayload.hckey) { + newpasspacket = encryptNp(newpassword, decryptedPayload.hckey); + } + newAIV = newpayload.iv.toString(); + newUIV = newusrpayload.iv.toString(); + newRIV = newveripacket.iv.toString(); - //here decrypt the invoice with my private key + if (decryptedPayload.hckey) { + newPIV = newpasspacket.iv.toString(); + } - API.getUserPacket(m_this.m_guid, m_this.m_sharedid, function (err, encpacket) { + } catch (err) { + newpayloadsuccess = false; + } - //get the RSA private key from the encrypted payload - var rsaKeyPair = decrypt(encpacket.Payload, m_this.m_password, encpacket.IV); + if (newpayloadsuccess) { - var publicKeys = openpgp.key.readArmored(rsaKeyPair.RSAPub); - var privKeys = openpgp.key.readArmored(rsaKeyPair.RSAPriv); + //$("#chngpwdprogbar").width('90%'); + //$("#chngpwdprogmess").html('Encrypting user packet...'); - var privKey = privKeys.keys[0]; - var pubKey = publicKeys.keys[0]; + //test decryption - then update + var testpayload = ''; + var testnewusrpayload = ''; + var testveripacket = ''; + var testpasspacket = ''; + var testsuccess = true; + try { - if (privKey.decrypt(m_this.m_sharedid)) { + testpayload = decrypt(newpayload.toString(), newpassword, newAIV); + testnewusrpayload = decrypt(newusrpayload.toString(), newpassword, newUIV); + testveripacket = decryptNp(newveripacket.toString(), newpassword, newRIV); - var msg = openpgp.message.readArmored(invoice.Packet); - var decrypted = openpgp.decryptAndVerifyMessage(privKey, [pubKey], msg); + if (decryptedPayload.hckey) { + testpasspacket = decryptNp(newpasspacket.toString(), decryptedPayload.hckey, newPIV) + } - var unpackedInvoice = JSON.parse(decrypted.text); + } catch (err) { + testsuccess = false; + } - callback(false, unpackedInvoice); + if (testsuccess) { - } else { + //save to the server + $(progbar).width('95%'); + $(progmess).html('Saving...'); - callback(true, "error"); - } + //TO DO: + //add in the re-encryption of the verification + //packet - }); - } + //if reset password then provide signed message and call reset function - this.UnpackInvoiceForMe = UnpackInvoiceForMe; - function UnpackInvoiceForMe(invoice, username, invtype, callback) { - //here decrypt the invoice with my private key - API.getUserPacket(m_this.m_guid, m_this.m_sharedid, function (err, encpacket) { + //need to add two factor here + //so 1. add two factor + //2. add way to save only the main packet - //get the RSA private key from the encrypted payload - var rsaKeyPair = decrypt(encpacket.Payload, m_this.m_password, encpacket.IV); + var postData = { twoFactorCode: twoFactorCode, guid: m_this.m_guid, sharedid: m_this.m_sharedid, accountPacket: newpayload.toString(), userPacket: newusrpayload.toString(), verifyPacket: newveripacket.toString(), passPacket: newpasspacket.toString(), IVA: newAIV, IVU: newUIV, IVR: newRIV, PIV: newPIV }; + API.post("/api/1/u/updatepackets", postData, function (err, dataStr) { + if (err) { + callback(true, "Error: Password not changed"); + } else { - var rsaKey = ''; - var postRSAData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: username }; - API.post("/api/1/u/getrsakey", postRSAData, function (err, rsaKey) { + if (dataStr == "ok") { - //friends public key + m_this.m_password = newpassword; + //if something goes wrong here + //the worst case scenario is the + //user has to reenter their hot key - //we need to get friends public key here to verify the signature on the packet - //then out of band if they verify the signature belongs to them- they are good + saveHotHash(hothash, function (err, result) { - //my private key - var privKeys = openpgp.key.readArmored(rsaKeyPair.RSAPriv); - var pubKey = null; - if (invtype == 'forme') { - var publicKeys = openpgp.key.readArmored(rsaKey); - pubKey = publicKeys.keys[0]; - } else { - var publicKeys = openpgp.key.readArmored(rsaKeyPair.RSAPub); - pubKey = publicKeys.keys[0]; - } + callback(false, ''); - var privKey = privKeys.keys[0]; + }); - var success = privKey.decrypt(m_this.m_sharedid); - if (success) { + } else { - var msg = openpgp.message.readArmored(invoice.Packet); - var decrypted = openpgp.decryptAndVerifyMessage(privKey, [pubKey], msg); + callback(true, "Error: Password not changed"); + } - var isValid = decrypted.signatures[0].valid; - if (isValid) { + } + }); - var json = JSON.parse(decrypted.text); - callback(false, json); - } else { + } else { - callback(false, "error"); + callback(true, "Error: Password not changed"); - } + } + } else { - } + callback(true, "Error: Password not changed"); - }); + } + }, 500); - }); + } else { - } + callback(true, "Error: Password not changed"); + } - //security - this.SaveTwoFactor = SaveTwoFactor; - function SaveTwoFactor(twoFactorCode, verifyToken, callback) { + }); - var postData = { - guid: m_this.m_guid, - sharedid: m_this.m_sharedid, - twoFactorOnLogin: true, - twoFactorCode: twoFactorCode, - verifyToken: verifyToken - }; + } - API.post("/api/1/u/updatetwofactor", postData, function (err, result) { + }); - if (!err) { - var bytes = []; - for (var i = 0; i < m_this.m_sharedid.length; ++i) { - bytes.push(m_this.m_sharedid.charCodeAt(i)); - } + } else { - var dpacket = Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(bytes)).toString(); + } - API.post("/api/1/verifyrecoverpacket", { guid: m_this.m_oguid, token: dpacket }, function (err, response) { - callback(err, response); + }, 500); }); } else { - callback(true, result); + callback(true, wallet); } }); @@ -1628,44 +2761,28 @@ function Engine() { } - this.SetupTwoFactor = SaveTwoFactor; - function SetupTwoFactor(twoFactorCode, callback) { - - var postData = { - guid: m_this.m_guid, - sharedid: m_this.m_sharedid, - twoFactorOnLogin: true, - twoFactorCode: twoFactorCode, - verifyToken: '' - }; - - API.post("/api/1/u/updatetwofactor", postData, function (err, result) { - - callback(err, result); - - }); - - } + //////////////////////////////////////////////////////////////////////////////////////////// - this.ResetTwoFactor = ResetTwoFactor; - function ResetTwoFactor(fguid, fusername, fpwd, callback) { - //stretch password + this.ResetPassword = ResetPassword; + function ResetPassword(guid, twofactor, resetKey, newpassword, progbar, progmess, hckey, message1, message2, callback) { - //download the recovery packet - //decrypt - //return shared secret - //no feedback apart from, please check your email + var sharedid = ''; + var oguid = guid; + //get secret and decrypt with resetKey + //pass is message1 and message2 + //return secret and sharedid + //continue as normal + var bytes = []; + for (var i = 0; i < guid.length; ++i) { + bytes.push(guid.charCodeAt(i)); + } - fpwd = CryptoJS.PBKDF2(fpwd, fguid, { - keySize: 256 / 32, - iterations: 1000 - }).toString(); + guid = Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(bytes)).toString(); API.post("/api/1/getrecoverypacket", { - guid: fguid, - username: fusername + guid: guid }, function (err, response) { if (err) { @@ -1678,239 +2795,242 @@ function Engine() { var jpacket = JSON.parse(response); - try { - var dpacket = decrypt(jpacket.packet, fpwd, jpacket.IV); - - API.post("/api/1/verifyrecoverpacket", { guid: fguid, token: dpacket }, function (err, response) { + if (!jpacket.Beta1) { - callback(err, response); + var secret = decryptNp(jpacket.packet, resetKey, jpacket.IV); - }); - } catch (error) { + API.getWalletFromServer(guid, secret, twofactor, false, function (err, wallet) { - callback(true, "error"); + if (!err) { - } + try { + var walletInformation = decrypt(wallet.Payload, resetKey, wallet.IV); + } catch (err) { + return callback(true, "Incorrect password"); + } - } + sharedid = walletInformation.userToken; - fpwd = ''; - }); + //check password strength - } + //stretch old password + //verify that it matches the current one + $(progbar).width('40%'); + $(progmess).html('Securing password...'); + setTimeout(function () { + //if password reset do not pbkdf the password - this.ChangePassword = ChangePassword; - function ChangePassword(twoFactorCode, oldpassword, newpassword, progbar, progmess, callback) { + oldpassword = resetKey; + //if (oldpassword == m_this.m_password) { - API.getWalletFromServer(m_this.m_guid, twoFactorCode, function (err, wallet) { + //get the two packets + $(progbar).width('40%'); + $(progmess).html('Getting packets...'); - if (!err) { - //check password strength - //stretch old password - //verify that it matches the current one - $(progbar).width('40%'); - $(progmess).html('Securing password...'); - setTimeout(function () { + // $("#chngpwdprogbar").width('50%'); + //$("#chngpwdprogmess").html('Decrypting account packet...'); + //decrypt with the old password + var decryptedWithOld = true; + var decryptedPayload = ''; + try { + decryptedPayload = decrypt(wallet.Payload, resetKey, wallet.IV); + } catch (err) { + decryptedWithOld = false; + } - oldpassword = CryptoJS.PBKDF2(oldpassword, m_this.m_oguid, { - keySize: 256 / 32, - iterations: 1000 - }).toString(); + if (decryptedWithOld) { - if (oldpassword == m_this.m_password) { + $(progbar).width('80%'); + $(progmess).html('Securing new password...'); + API.getUserPacket(guid, sharedid, function (err, encpacket) { + var decryptedUsrWithOld = true; + var rsaKeyPair = ''; + try { + rsaKeyPair = decrypt(encpacket.Payload, resetKey, encpacket.IV); + } catch (err) { + decryptedUsrWithOld = false; + } - //get the two packets - $(progbar).width('40%'); - $(progmess).html('Getting packets...'); + if (decryptedUsrWithOld) { + //get the verification packet - // $("#chngpwdprogbar").width('50%'); - //$("#chngpwdprogmess").html('Decrypting account packet...'); - //decrypt with the old password - var decryptedWithOld = true; - var decryptedPayload = ''; - try { - decryptedPayload = decrypt(wallet.Payload, m_this.m_password, wallet.IV); - } catch (err) { - decryptedWithOld = false; - } + API.post("/api/1/getrecoverypacket", { + guid: guid + }, function (err, response) { - if (decryptedWithOld) { - $(progbar).width('80%'); - $(progmess).html('Securing new password...'); - API.getUserPacket(m_this.m_guid, m_this.m_sharedid, function (err, encpacket) { + //decrypt packet + var decryptedVerWithOld = true; + var jpacket = JSON.parse(response); + var veripacket = ''; + try { + veripacket = decryptNp(jpacket.packet, resetKey, jpacket.IV); + } + catch (verror) { + decryptedVerWithOld = false; + } - var decryptedUsrWithOld = true; - var rsaKeyPair = ''; - try { - rsaKeyPair = decrypt(encpacket.Payload, m_this.m_password, encpacket.IV); - } catch (err) { - decryptedUsrWithOld = false; - } + if (decryptedVerWithOld) { - if (decryptedUsrWithOld) { + setTimeout(function () { - //get the verification packet + newpassword = pbkdf2(newpassword, oguid); - API.post("/api/1/getrecoverypacket", { - guid: m_this.m_guid, - username: m_this.m_nickname - }, function (err, response) { + $(progbar).width('80%'); + $(progmess).html('Encrypting account packet...'); - //decrypt packet - var decryptedVerWithOld = true; - var jpacket = JSON.parse(response); - var veripacket = ''; - try { - veripacket = decrypt(jpacket.packet, m_this.m_password, jpacket.IV); - } - catch (verror) { - decryptedVerWithOld = false; - } + var newpayloadsuccess = true; + var newpayload = ''; + var newusrpayload = ''; + var newveripacket = ''; + var newpasspacket = ''; + var newAIV = ''; + var newUIV = ''; + var newRIV = ''; + var newPIV = '' + try { + newpayload = encrypt(decryptedPayload, newpassword); + newusrpayload = encrypt(rsaKeyPair, newpassword); + newveripacket = encryptNp(veripacket, newpassword); + newpasspacket = encryptNp(newpassword, decryptedPayload.hckey); - if (decryptedVerWithOld) { + //and encrypt the new password with the hotkey seed - setTimeout(function () { + newAIV = newpayload.iv.toString(); + newUIV = newusrpayload.iv.toString(); + newRIV = newveripacket.iv.toString(); + newPIV = newpasspacket.iv.toString(); + } catch (err) { + newpayloadsuccess = false; + } - newpassword = CryptoJS.PBKDF2(newpassword, m_this.m_oguid, { - keySize: 256 / 32, - iterations: 1000 - }).toString(); + if (newpayloadsuccess) { + //$("#chngpwdprogbar").width('90%'); + //$("#chngpwdprogmess").html('Encrypting user packet...'); - $(progbar).width('80%'); - $(progmess).html('Encrypting account packet...'); + //test decryption - then update + var testpayload = ''; + var testnewusrpayload = ''; + var testveripacket = ''; + var testpasspacket = ''; + var testsuccess = true; + try { + testpayload = decrypt(newpayload.toString(), newpassword, newAIV); + testnewusrpayload = decrypt(newusrpayload.toString(), newpassword, newUIV); + testveripacket = decryptNp(newveripacket.toString(), newpassword, newRIV); + testpasspacket = decryptNp(newpasspacket.toString(), decryptedPayload.hckey, newPIV); - var newpayloadsuccess = true; - var newpayload = ''; - var newusrpayload = ''; - var newveripacket = ''; - var newAIV = ''; - var newUIV = ''; - var newRIV = ''; - try { + } catch (err) { + testsuccess = false; + } - newpayload = encrypt(decryptedPayload, newpassword); - newusrpayload = encrypt(rsaKeyPair, newpassword); - newveripacket = encrypt(veripacket, newpassword); + if (testsuccess) { - newAIV = newpayload.iv.toString(); - newUIV = newusrpayload.iv.toString(); - newRIV = newveripacket.iv.toString(); + //save to the server + $(progbar).width('95%'); + $(progmess).html('Saving...'); - } catch (err) { - newpayloadsuccess = false; - } - if (newpayloadsuccess) { + //TO DO: + //add in the re-encryption of the verification + //packet - //$("#chngpwdprogbar").width('90%'); - //$("#chngpwdprogmess").html('Encrypting user packet...'); - //test decryption - then update - var testpayload = ''; - var testnewusrpayload = ''; - var testveripacket = ''; - var testsuccess = true; - try { - testpayload = decrypt(newpayload.toString(), newpassword, newAIV); - testnewusrpayload = decrypt(newusrpayload.toString(), newpassword, newUIV); - testveripacket = decrypt(newveripacket.toString(), newpassword, newRIV); - } catch (err) { - testsuccess = false; - } + //if reset password then provide signed message and call reset function - if (testsuccess) { + var postData = { guid: guid, sharedid: sharedid, accountPacket: newpayload.toString(), userPacket: newusrpayload.toString(), verifyPacket: newveripacket.toString(), passPacket: newpasspacket.toString(), IVA: newAIV, IVU: newUIV, IVR: newRIV, PIV: newPIV }; + API.post("/api/1/u/updatepackets", postData, function (err, dataStr) { + if (err) { + callback(true, "Error: Password not changed"); + } else { - //save to the server - $(progbar).width('95%'); - $(progmess).html('Saving...'); + if (dataStr == "ok") { - //TO DO: - //add in the re-encryption of the verification - //packet + callback(false, newpassword); - var postData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, accountPacket: newpayload.toString(), userPacket: newusrpayload.toString(), verifyPacket: newveripacket.toString(), IVA: newAIV, IVU: newUIV, IVR: newRIV }; - API.post("/api/1/u/updatepackets", postData, function (err, dataStr) { - if (err) { - callback(true, "Error: Password not changed"); - } else { + } else { - if (dataStr == "ok") { + callback(true, "Error: Password not changed"); + } - callback(false, newpassword); + } + }); - } else { - callback(true, "Error: Password not changed"); - } + } else { + + callback(true, "Error: Password not changed"); } - }); + } else { - } else { + callback(true, "Error: Password not changed"); - callback(true, "Error: Password not changed"); + } + }, 500); - } } else { callback(true, "Error: Password not changed"); - } - }, 500); - } else { - callback(true, "Error: Password not changed"); - } + }); + } }); + + } else { + } - }); + //} else { + // callback(true, "You entered your current password incorrectly"); + //} + }, 500); } else { - + callback(true, wallet); } + }); - } else { - callback(true, "You entered your current password incorrectly"); - } - - }, 500); + } - } else { - callback(true, wallet); } + }); } + + + //////////////////////////////////////////////////////////////////////////////////////////// + this.EmailValidationForTwoFactor = EmailValidationForTwoFactor; function EmailValidationForTwoFactor(vtoken, status, callback) { @@ -1951,7 +3071,21 @@ function Engine() { API.post("/api/1/u/updateaccountsettings", postdata , function (err, response) { - callback(err, response); + + if (!err) { + + getAccountSettings(function (err, res) { + + var settingsObject = JSON.parse(res); + m_this.m_settings = settingsObject; + callback(err, response); + }); + + } else { + callback(err, response); + } + + }); } @@ -1974,6 +3108,54 @@ function Engine() { } + + this.getBackup = getBackup; + function getBackup(twoFactorCode, callback) { + + API.getWalletFromServer(m_this.m_guid, m_this.m_secret, twoFactorCode, false, function (err, wallet) { + + if (err) { + + return callback(err, wallet); + + } + + var walletInformation = {}; + try { + walletInformation = decrypt(wallet.Payload, m_this.m_password, wallet.IV); + + var result = {}; + result.ninkiPubKey = walletInformation.ninkiPubKey; + + getHotHash("", function (err, hotHash) { + + + result.hotHash = hotHash; + + walletInformation = {}; + + callback(err, result); + + }); + + + } catch (err) { + return callback(true, "Incorrect password"); + } + + }); + + } + + this.createS3Policy = createS3Policy; + function createS3Policy(callback) { + Ninki.API.post("/api/1/u/createS3Policy", { guid: m_this.m_guid }, function (err, result) { + + callback(err, result); + + }); + } + this.emailGUID = emailGUID; function emailGUID(userName, callback) { API.emailGUID(userName, callback); @@ -1991,7 +3173,7 @@ function Engine() { if (err) { callback(err, accExists); } else { - callback(err, accExists.UserExists); + callback(err, accExists.UserExists); } }); @@ -2008,8 +3190,18 @@ function Engine() { } this.getWalletFromServer = getWalletFromServer; - function getWalletFromServer(twoFactorCode, callback) { - API.getWalletFromServer(m_this.m_guid, m_this.m_sharedid, twoFactorCode, callback); + function getWalletFromServer(secret, twoFactorCode, rememberTwoFactor, callback) { + API.getWalletFromServer(m_this.m_guid, secret, twoFactorCode, rememberTwoFactor, callback); + } + + this.getRecoveryPacket = getRecoveryPacket; + function getRecoveryPacket(callback) { + API.getRecoveryPacket(m_this.m_guid, callback); + } + + this.validateSecret = validateSecret; + function validateSecret(secret, callback) { + API.validateSecret(m_this.m_guid, secret, callback); } this.getBalance = getBalance; @@ -2032,11 +3224,37 @@ function Engine() { API.getUnconfirmedBalance(m_this.m_guid, m_this.m_sharedid, callback); } + this.getCoinProfile = getCoinProfile; + function getCoinProfile(callback) { + API.getCoinProfile(m_this.m_guid, m_this.m_sharedid, callback); + } + + this.getNickname = getNickname; function getNickname(callback) { API.getNickname(m_this.m_guid, m_this.m_sharedid, callback); } + this.getUserProfile = getUserProfile; + function getUserProfile(callback) { + API.getUserProfile(m_this.m_guid, m_this.m_sharedid, callback); + } + + this.updateUserProfile = updateUserProfile; + function updateUserProfile(profileImage, status, tax, callback) { + API.updateUserProfile(m_this.m_guid, m_this.m_sharedid, profileImage, status, tax, function (err, result) { + + if (!err) { + m_this.m_statusText = status; + m_this.m_profileImage = profileImage; + m_this.m_invoiceTax = tax; + } + + callback(err, result); + + }); + } + this.getUnspentOutputs = getUnspentOutputs; function getUnspentOutputs(callback) { API.getUnspentOutputs(m_this.m_guid, m_this.m_sharedid, callback); @@ -2087,6 +3305,101 @@ function Engine() { API.updateInvoice(m_this.m_guid, m_this.m_sharedid, username, invoiceId, transactionId, status, callback); } + this.getVersion = getVersion; + function getVersion(callback) { + API.getVersion(callback); + } + + this.registerDevice = registerDevice; + function registerDevice(guid, deviceName, deviceId, deviceModel, devicePIN, regToken, secret, callback) { + API.registerDevice(guid, deviceName, deviceId, deviceModel, devicePIN, regToken, secret, callback); + } + + this.getDeviceKey = getDeviceKey; + function getDeviceKey(devicePIN, callback) { + + var deviceid = "DEVICE123456789"; + if (window.cordova) { + deviceid = window.device.uuid; + } + + //hash the pin and device id + var pinhash = deviceid + devicePIN; + var bytes = []; + for (var i = 0; i < pinhash.length; ++i) { + bytes.push(pinhash.charCodeAt(i)); + } + + pinhash = Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(bytes)).toString(); + + getCookie("ninki_reg", function (regToken) { + + API.getDeviceKey(m_this.m_guid, pinhash, regToken, function (err, ekey) { + + if (!err) { + var jekey = JSON.parse(ekey); + + if (jekey.DeviceKey.length > 0) { + + callback(err, jekey); + + } else { + + callback(err, jekey); + } + } else { + callback(true, ekey); + } + + }); + + }); + + } + + + this.destroyDevice = destroyDevice; + function destroyDevice(callback) { + + getCookie("ninki_reg", function (regToken) { + + API.destroyDevice(m_this.m_guid, regToken, function (err, ekey) { + + callback(err, ekey); + + }); + + }); + + } + + + this.destroyDevice2fa = destroyDevice2fa; + function destroyDevice2fa(deviceName, twoFactor, callback) { + + API.destroyDevice2fa(m_this.m_guid, m_this.m_sharedid, deviceName, twoFactor, function (err, ekey) { + + callback(err, ekey); + + }); + + } + + + this.createDevice = createDevice; + function createDevice(deviceName, callback) { + API.createDevice(m_this.m_guid, m_this.m_sharedid, deviceName, callback); + } + + this.getDevices = getDevices; + function getDevices(callback) { + API.getDevices(m_this.m_guid, m_this.m_sharedid, callback); + } + + this.getDeviceToken = getDeviceToken; + function getDeviceToken(deviceName, twoFactorCode, callback) { + API.getDeviceToken(m_this.m_guid, m_this.m_sharedid, deviceName, twoFactorCode, callback); + } } module.exports = Engine \ No newline at end of file diff --git a/src/ninki-ui-mobile.js b/src/ninki-ui-mobile.js new file mode 100644 index 0000000..55f5fd2 --- /dev/null +++ b/src/ninki-ui-mobile.js @@ -0,0 +1,5419 @@ +var Bitcoin = require('bitcoinjs-lib'); +var BIP39 = require('./bip39'); + +function UI() { + + + + + var Engine = new Ninki.Engine(); + + + // var WALLETINFORMATION = {}; + // var SHAREDID = ''; + + // var TWOFACTORONLOGIN = false; + // var NICKNAME = ''; + // var guid = ''; + // var oguid = ''; + // var password = ''; + + + var FRIENDSLIST = {}; + var COINUNIT = 'BTC'; + var SELECTEDFRIEND = ''; + var noAlert = false; + var norefresh = false; + + var trasactionFilterOn = false; + var allTransactions = []; + var filteredTransactions = []; + var pagedTransactions = []; + var currentTransactionFilter = ''; + var transactionSortOn = true; + var currentTransactionSort = 'DateDesc'; + var transactionsPerPage = 10; + var currentPageIndex = 0; + var transactionIndex = {}; + + var filteredByMeInvoices = []; + var pagedByMeInvoices = []; + var invoicesByMePerPage = 10; + var currentByMeInvoicePageIndex = 0; + + var filteredForMeInvoices = []; + var pagedForMeInvoices = []; + var invoicesForMePerPage = 10; + var currentForMeInvoicePageIndex = 0; + + var currentInvoiceFilter = ''; + var currentByMeInvoiceFilter = ''; + var invoiceFilterOn = false; + var invoiceByMeFilterOn = false; + + var contactPhraseCache = {}; + + + var ua = window.navigator.userAgent; + var msie = ua.indexOf("MSIE"); + + + + if (msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./)) { + document.write("Internet Explorer is not supported. Please use the latest Chrome, Safari or Firefox."); + + } + + if (typeof window.crypto.getRandomValues == 'undefined') { + document.write("This browser does not support window.crypto. Please use the latest chrome, safari or firefox."); + } + + var spinneropts = { + lines: 13, // The number of lines to draw + length: 0, // The length of each line + width: 7, // The line thickness + radius: 30, // The radius of the inner circle + corners: 1, // Corner roundness (0..1) + rotate: 0, // The rotation offset + direction: 1, // 1: clockwise, -1: counterclockwise + color: '#000', // #rgb or #rrggbb or array of colors + speed: 1, // Rounds per second + trail: 60, // Afterglow percentage + shadow: false, // Whether to render a shadow + hwaccel: false, // Whether to use hardware acceleration + className: 'spinner', // The CSS class to assign to the spinner + zIndex: 2e9, // The z-index (defaults to 2000000000) + top: '50%', // Top position relative to parent + left: '50%' // Left position relative to parent + }; + + + + function setCookie(cname, cvalue) { + + + if (isChromeApp()) { + + var obj = {}; + obj[cname] = cvalue; + chrome.storage.local.set(obj, function () { + + console.log("saved"); + + }); + + } + else { + + localStorage[cname] = cvalue; + + } + + + } + + function isChromeApp() { + + if (window.cordova) { + return false; + } + + + var is_chrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1; + if (is_chrome) { + if (chrome) { + if (chrome.app) { + if (chrome.app.runtime) { + return true; + } + } + } + } + return false; + } + + + function getCookie(cname, callback) { + + + if (isChromeApp()) { + + chrome.storage.local.get(cname, function (result) { + + if (result[cname]) { + result = result[cname]; + } else { + result = ""; + } + + return callback(result); + + }); + + } else { + + if (localStorage[cname]) { + return callback(localStorage[cname]); + } else { + return callback(''); + } + + } + + } + + function deleteCookie(cname) { + + + if (isChromeApp()) { + + chrome.storage.local.remove(cname, function () { + + console.log("deleted"); + + }); + + } else { + + localStorage.removeItem(cname); + + } + } + + + function getLocalTime(datetime) { + + var timestamp = datetime, + t = new Date(datetime), + hours = t.getHours(), + min = t.getMinutes() + '', + pm = false, + months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; + + if (hours > 11) { + hours = hours - 12; + pm = true; + } + + if (hours == 0) hours = 12; + if (min.length == 1) min = '0' + min; + + return (hours + ':' + min + ' ' + (pm ? 'pm' : 'am')); + + } + + + var profilepagestate = ''; + var networkpagestate = ''; + var friendpagestate = ''; + var menustate = ''; + var cl = ''; + + jQuery(document).ready(function () { + + + var $body = jQuery('body'); + + /* bind events */ + $(document).on('focus', 'input', function (e) { + $(".footer").hide(); + }); + + + $(document).on('blur', 'input', function (e) { + $(".footer").show(); + }); + + $("#dashsend").hide(); + $("#dashreceive").hide(); + $("#dashcontact").hide(); + + + $("#addcontactmodal").hide(); + + + + $('.num').bind('touchstart', function () { + $(this).removeClass('numtapoff'); + + cl = 'b' + (Math.floor(Math.random() * 6) + 1) + ''; + //alert(cl); + $(this).addClass(cl); + + }) + + $('.num').bind('touchend', function () { + + var num = $(this); + var text = $.trim(num.find('.txt').clone().children().remove().end().text()); + var loginpinno = $('#loginpinno'); + + $(loginpinno).val(loginpinno.val() + text); + + if ($(loginpinno).val().length == 4) { + + $("#pinscreen").hide(); + + $("#btnLoginPIN").click(); + + + } + + + + var self = this; + setTimeout(function () { + //alert(cl); + $(self).removeClass('b1 b2 b3 b4 b5 b6'); + $(self).addClass('numtapoff'); + }, 100); + + }) + + + + + + // $(".num").hammer({ + // drag: false, + // transform: false, + // hold: false, + // touch: false, + // tap: true, + // tapAlways: false, + // swipe: false, + // release: false, + // tapMaxTime: 1000 + // }).bind("tap", function (ev) { + + // $(this).addClass('numtap'); + + // // var num = $(this); + // // var text = $.trim(num.find('.txt').clone().children().remove().end().text()); + // // var telNumber = $('#telNumber'); + // // $(telNumber).val(telNumber.val() + text); + // }); + + // $(".num").click(function () { + + // $(this).addClass('numtap'); + + // // var num = $(this); + // // var text = $.trim(num.find('.txt').clone().children().remove().end().text()); + // // var telNumber = $('#telNumber'); + // // $(telNumber).val(telNumber.val() + text); + // }); + + + $("#btnmenuprofile").hammer({ + drag: false, + transform: false, + hold: false, + touch: false, + tap: true, + tapAlways: false, + swipe: false, + release: false, + tapMaxTime: 1000 + }).bind("tap", function (ev) { + + displayProfile(); + + + }); + + $("#btnmenuprofile").hammer({ + drag: false, + transform: false, + hold: false, + touch: true, + tap: false, + tapAlways: false, + swipe: false, + release: false + }).bind("touch", function (ev) { + + displayProfile(); + + + }); + + + $("#btnmenuprofile").hammer({ + drag: false, + transform: false, + hold: false, + touch: false, + tap: false, + tapAlways: false, + swipe: false, + release: true + }).bind("release", function (ev) { + + displayProfile(); + + + }); + + $("#btnmenuprofile").hammer({ + drag: false, + transform: false, + hold: true, + touch: false, + tap: false, + tapAlways: false, + swipe: false, + release: false + }).bind("hold", function (ev) { + + displayProfile(); + + + }); + + function displayProfile() { + + + if (menustate != 'profile') { + menustate = 'profile'; + $("#settings").hide(); + $("#network").hide(); + $("#dashboard").show(); + $("#networklist").hide(); + $("#invoices").hide(); + + } else { + profilehome(); + } + + $("#btnmenusettings").attr('style', 'background-color:#ffffff'); + $("#btnmenunetwork").attr('style', 'background-color:#ffffff'); + $("#btnmenuprofile").attr('style', 'background-color:#eaeef1'); + + } + + + $("#btnmenunetwork").hammer({ + drag: false, + transform: false, + hold: false, + touch: false, + tap: true, + tapAlways: false, + swipe: false, + release: false, + tapMaxTime: 1000 + }).bind("tap", function (ev) { + + displayNetwork(); + + }); + + $("#btnmenunetwork").hammer({ + drag: false, + transform: false, + hold: false, + touch: true, + tap: false, + tapAlways: false, + swipe: false, + release: false + }).bind("touch", function (ev) { + + displayNetwork(); + + }); + + $("#btnmenunetwork").hammer({ + drag: false, + transform: false, + hold: true, + touch: false, + tap: false, + tapAlways: false, + swipe: false, + release: true + }).bind("release", function (ev) { + + displayNetwork(); + + }); + + + $("#btnmenunetwork").hammer({ + drag: false, + transform: false, + hold: true, + touch: false, + tap: false, + tapAlways: false, + swipe: false, + release: false + }).bind("hold", function (ev) { + + displayNetwork(); + + }); + + function displayNetwork() { + + if (menustate != "network") { + menustate = "network"; + $("#settings").hide(); + $("#network").show(); + $("#dashboard").hide(); + $("#networklist").show(); + + + if (networkpagestate == "invoice") { + + + $("#network").hide(); + + $("#invoices").show(); + } + + } else { + + networkhome(); + } + + $("#btnmenusettings").attr('style', 'background-color:#ffffff'); + $("#btnmenuprofile").attr('style', 'background-color:#ffffff'); + $("#btnmenunetwork").attr('style', 'background-color:#eaeef1'); + } + + + $("#invformelink").hammer(null).bind("tap", function () { + $("#invformetab").show(); + $("#invbymetab").hide(); + $("#liby").removeClass('active'); + $("#lifor").addClass('active'); + }); + + $("#invbymelink").hammer(null).bind("tap", function () { + $("#invbymetab").show(); + $("#invformetab").hide(); + $("#lifor").removeClass('active'); + $("#liby").addClass('active'); + }); + + //tapsendfriend + //tapinvoicefriend + $("#tapsendfriend").hammer(null).bind("tap", function () { + $("#networksend").show(); + $("#pnlfriendinv").hide(); + networkpagestate = "friend"; + friendpagestate = "send"; + }); + + $("#tapinvoicefriend").hammer(null).bind("tap", function () { + $("#pnlfriendinv").show(); + $("#networksend").hide(); + networkpagestate = "friend"; + friendpagestate = "invoice"; + + }); + + function networkhome() { + if (networkpagestate == "invoice") { + $('#network').show(); + $('#invoices').hide(); + networkpagestate = "friend"; + friendpagestate = "invoice"; + + + } else { + $("#pnlfriend").hide(); + $("#myfriends").show(); + $("#networklist").show(); + networkpagestate = ""; + } + + } + + function profilehome() { + + + $("#dashprofile").show(); + $("#dashsend").hide(); + $("#dashreceive").hide(); + $("#dashcontact").hide(); + $('#invoices').hide(); + $("#dashboard").show(); + profilepagestate = ""; + + } + + + $("#btnmenusettings").hammer({ + drag: false, + transform: false, + hold: false, + touch: false, + tap: true, + tapAlways: false, + swipe: false, + release: false, + tapMaxTime: 1000 + }).bind("tap", function (ev) { + + menustate = "settings"; + + $("#settings").show(); + $("#network").hide(); + $("#networklist").hide(); + $("#dashboard").hide(); + + $("#btnmenusettings").attr('style', 'background-color:#eaeef1'); + $("#btnmenuprofile").attr('style', 'background-color:#ffffff'); + $("#btnmenunetwork").attr('style', 'background-color:#ffffff'); + }); + + $("#tapsend").hammer(null).bind("tap", function () { + $("#dashprofile").hide(); + $("#dashsend").show(); + $("#dashreceive").hide(); + $("#dashcontact").hide(); + //$("#toAddress").focus(); + profilepagestate = "send"; + menustate = "profile" + + + }); + + $("#tapreceive").hammer(null).bind("tap", function () { + $("#dashprofile").hide(); + $("#dashsend").hide(); + $("#dashreceive").show(); + $("#dashcontact").hide(); + profilepagestate = "receive"; + menustate = "profile" + + }); + + $("#taprequest").hammer(null).bind("tap", function () { + $("#dashprofile").hide(); + $("#dashsend").hide(); + $("#dashreceive").hide(); + $("#dashcontact").show(); + profilepagestate = "contact"; + menustate = "profile" + + }); + + + $('#imgProfileContainer').show(); + $("#dropzone").hide(); + $("#btnSaveProfile").hide(); + $("#btnCancelProfile").hide(); + $("#statusedit").hide(); + $("#imgreset").hide(); + + + $("#btnEditProfile").click(function () { + + key = ''; + + $('#imgProfileContainer').hide(); + $("#dropzone").show(); + $("#btnSaveProfile").show(); + $("#btnCancelProfile").show(); + $("#btnEditProfile").hide(); + $("#statusedit").show(); + $("#profnmests").hide(); + $("#imgreset").show(); + $("#txtStatusText").val(Engine.m_statusText); + + }); + + $("#imgreset").click(function () { + + Engine.m_profileImage = ""; + imgReset = true; + + var imageSrc = "images/avatar/256px/Avatar-" + pad(Engine.m_nickname.length) + ".png"; + + document.getElementById('imgProfile').src = imageSrc; + $('#imgProfileContainer').show(); + $('#dropzone').hide(); + $('progressNumber').html(''); + $("#imgreset").hide(); + + }); + + var imgReset = false; + $("#btnSaveProfile").click(function () { + + //updateUserProfile + + var statusText = $("#txtStatusText").val(); + + if (key == '' && !imgReset) { + key = Engine.m_profileImage; + } + + Engine.updateUserProfile(key, statusText, Engine.m_invoiceTax, function (err, result) { + + if (!err) { + + var imageSrc = "images/avatar/256px/Avatar-" + pad(Engine.m_nickname.length) + ".png"; + var imageSrcSmall = "images/avatar/64px/Avatar-" + pad(Engine.m_nickname.length) + ".png"; + + if (key != '' && !imgReset) { + imageSrc = "https://ninkip2p.imgix.net/" + key + "?crop=faces&fit=crop&h=256&w=256&mask=ellipse&border=1,d0d0d0"; + imageSrcSmall = "https://ninkip2p.imgix.net/" + key + "?crop=faces&fit=crop&h=64&w=64&mask=ellipse&border=1,d0d0d0"; + } + + $("#imgProfile").attr("src", imageSrc); + $("#imgtoprightprofile").attr("src", imageSrcSmall); + + $('#imgProfileContainer').show(); + $("#dropzone").hide(); + $("#btnSaveProfile").hide(); + $("#btnCancelProfile").hide(); + $("#btnEditProfile").show(); + $("#statusedit").hide(); + $("#mystatus").html(statusText); + $("#txtStatusText").val(statusText); + $("#profnmests").show(); + $("#imgreset").hide(); + imgReset = false; + $("#profileimgfile").val(''); + $("#progressNumber").val(''); + + } + + }); + + }); + + $("#btnCancelProfile").click(function () { + + $('#imgProfileContainer').show(); + $("#dropzone").hide(); + $("#btnSaveProfile").hide(); + $("#btnCancelProfile").hide(); + $("#btnEditProfile").show(); + + //reset profile image + + var imageSrc = "images/avatar/256px/Avatar-" + pad(Engine.m_nickname.length) + ".png"; + + if (Engine.m_profileImage != '') { + imageSrc = "https://ninkip2p.imgix.net/" + Engine.m_profileImage + "?crop=faces&fit=crop&h=256&w=256&mask=ellipse&border=1,d0d0d0"; + } + + $("#imgProfile").attr("src", imageSrc); + + $("#statusedit").hide(); + $("#profnmests").show(); + $("#imgreset").hide(); + }); + + var obj = $("#dropzone"); + + obj.click(function () { + $("#profileimgfile").click(); + }); + + $("#profileimgfile").change(function (e) { + + var control = document.getElementById("profileimgfile"); + var files = control.files; + //alert(files[0]); + //We need to send dropped files to Server + handleFileUpload(files, obj); + + }); + + obj.on('dragenter', function (e) { + e.stopPropagation(); + e.preventDefault(); + $(this).css('border', '2px solid #0B85A1'); + }); + + obj.on('dragover', function (e) { + e.stopPropagation(); + e.preventDefault(); + }); + + obj.on('drop', function (e) { + + $(this).addClass("b-dashed"); + $(this).addClass("b-light"); + e.preventDefault(); + var files = e.originalEvent.dataTransfer.files; + //alert(files[0]); + //We need to send dropped files to Server + handleFileUpload(files, obj); + }); + + + $(document).on('dragenter', function (e) { + e.stopPropagation(); + e.preventDefault(); + }); + + $(document).on('dragover', function (e) { + e.stopPropagation(); + e.preventDefault(); + obj.css('border', '2px dotted #0B85A1'); + }); + + $(document).on('drop', function (e) { + e.stopPropagation(); + e.preventDefault(); + }); + + var key = ''; + function handleFileUpload(files, obj) { + + if (files.length > 0) { + var file = files[0]; + var fd = new FormData(); + + key = "images\/" + Engine.m_nickname + '_' + (new Date).getTime() + + Ninki.API.post("/api/1/u/createS3Policy", { test: 'test' }, function (err, result) { + + var policy = JSON.parse(result); + + fd.append('key', key); + fd.append('acl', 'public-read'); + fd.append('Content-Type', file.type); + fd.append('bucket', 'ninkip2pimgstore'); + fd.append('AWSAccessKeyId', 'AKIAINOU56ATQFS3CLFQ'); + fd.append('policy', policy.s3Policy); + fd.append('signature', policy.s3Signature); + fd.append("file", file); + //fd.append("success_action_redirect", "https://localhost:1111/ok"); + + + var xhr = new XMLHttpRequest(); + + xhr.upload.addEventListener("progress", uploadProgress, false); + xhr.addEventListener("load", uploadComplete, false); + xhr.addEventListener("error", uploadFailed, false); + xhr.addEventListener("abort", uploadCanceled, false); + + xhr.open('POST', 'https://ninkip2pimgstore.s3-us-west-1.amazonaws.com/', true); //MUST BE LAST LINE BEFORE YOU SEND + + xhr.send(fd); + + }); + } + + } + + + + function uploadProgress(evt) { + if (evt.lengthComputable) { + var percentComplete = Math.round(evt.loaded * 100 / evt.total); + document.getElementById('progressNumber').innerHTML = percentComplete.toString() + '%'; + } + else { + document.getElementById('progressNumber').innerHTML = 'unable to compute'; + } + } + + function uploadComplete(evt) { + /* This event is raised when the server send back a response */ + //alert("Done - " + evt.target.responseText); + document.getElementById('imgProfile').src = 'https://ninkip2p.imgix.net/' + key + "?fit=crop&crop=faces&h=128&w=128&mask=ellipse&border=1,d0d0d0"; + $('#imgProfileContainer').show(); + $('#dropzone').hide(); + $('progressNumber').html(''); + $("#imgreset").hide(); + + } + + function uploadFailed(evt) { + alert("There was an error attempting to upload the file." + evt); + } + + function uploadCanceled(evt) { + alert("The upload has been canceled by the user or the browser dropped the connection."); + } + + var options = {}; + options.ui = { + container: "#pwd-container", + showVerdictsInsideProgressBar: true, + showPopover: true, + showErrors: true, + viewports: { + progress: ".pwstrength_viewport_progress" + } + }; + options.common = { + debug: true, + onLoad: function () { + $('#messages').text('Start typing password'); + }, + onKeyUp: function () { + $("#createwalletalert").fadeOut(100); + } + }; + + + $('#createWalletStart #cpassword').pwstrength(options); + + + var optionschng = {}; + optionschng.ui = { + container: "#newpwd-container", + showVerdictsInsideProgressBar: true, + showPopover: true, + showErrors: true, + viewports: { + progress: ".newpwstrength_viewport_progress" + } + }; + optionschng.common = { + debug: true, + onLoad: function () { + $('#messages').text('Start typing password'); + }, + onKeyUp: function () { + $("#createwalletalert").fadeOut(100); + } + }; + + $('#newpassword1').pwstrength(optionschng); + + }); + + + + + $('#btnPassphraseLogin').keydown(function (e) { + if (e.keyCode == 13) { + e.preventDefault(); + return false; + } + }); + + $('#frmSaveTwoFactor').keydown(function (e) { + if (e.keyCode == 13) { + e.preventDefault(); + return false; + } + }); + + $('#phrase2fa').keydown(function (e) { + if (e.keyCode == 13) { + e.preventDefault(); + return false; + } + }); + + + $(document).on("keydown", function (e) { + if (e.which === 8 && !$(e.target).is("input, textarea")) { + e.preventDefault(); + } + }); + + + $(document).ready(function () { + + + $('body').on('click', function (e) { + //did not click a popover toggle or popover + if ($(e.target).data('toggle') !== 'popover' + && $(e.target).parents('.popover.in').length === 0) { + $('[data-toggle="popover"]').popover('hide'); + } + }); + + + + getCookie('guid', function (res) { + if (res.length == 0) { + + var betafrom = 'December 12, 2009 12:00 pm GMT'; + var betato = 'December 12, 2009 01:00 pm GMT'; + + betafrom = getLocalTime(betafrom); + betato = getLocalTime(betato); + + $('#betafrom').html(betafrom); + $('#betato').html(betato); + + $('#basicModal').modal('show'); + + $("#btnDeclineBeta").click(function () { + window.location.href = '/' + }); + + } + }); + + + + $("#btnAcceptBeta").click(function () { + $("#openWalletStart #password").focus(); + }); + + + + + + $("#btncreatewallet").click(function () { + showCreateWalletStart(); + }); + + + $("#pairdeviceblob").change(function () { + + $("#loginpin").hide(); + $("#pairstep1").hide(); + $("#pairstep2").show(); + + + }); + + + $("#btnUnpair").click(function () { + + + getCookie("guid", function (guid) { + + Engine.m_oguid = guid; + + var bytes = []; + for (var i = 0; i < guid.length; ++i) { + bytes.push(guid.charCodeAt(i)); + } + + Engine.m_guid = Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(bytes)).toString(); + + + Engine.destroyDevice(function (err, res) { + + deleteCookie("ninki_rem"); + deleteCookie("ninki_p"); + deleteCookie("ninki_reg"); + deleteCookie("ninki_h"); + + //call to server + location.reload(); + + }); + + }); + + + }); + + + $("#btnPairDevice").click(function () { + + var deviceid = "DEVICE123456789"; + + if (window.cordova) { + deviceid = window.device.uuid; + } + + var blob = $('#pairdeviceblob').val(); + var pin = $('#pairdevicepinnumber').val(); + var pwd = $('#pairpwd').val(); + + + + var splitBlob = blob.split('|'); + + var enck = splitBlob[0]; + var iv = splitBlob[1]; + var guid = splitBlob[2]; + var deviceName = splitBlob[3]; + var regToken = splitBlob[4]; + + + Engine.setPass(pwd, guid); + + pwd = Engine.m_password; + + var bytes = []; + for (var i = 0; i < guid.length; ++i) { + bytes.push(guid.charCodeAt(i)); + } + + var hashguid = Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(bytes)).toString(); + + + Engine.m_guid = hashguid; + Engine.m_oguid = guid; + + //first validate the password with the secret + Engine.getRecoveryPacket(function (err, response) { + + if (err) { + + $('#pairdevicealertmessage').html(response); + + } else { + + //decrypt packet + + var jpacket = JSON.parse(response); + + var secret = Engine.decryptNp(jpacket.packet, pwd, jpacket.IV); + + Engine.validateSecret(secret, function (err, secvalid) { + + if (!err) { + + + //hash the pin and device id + + var pinhash = deviceid + pin; + bytes = []; + for (var i = 0; i < pinhash.length; ++i) { + bytes.push(pinhash.charCodeAt(i)); + } + + pinhash = Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(bytes)).toString(); + + + //new register device + + + //enter password + //stretch + //get validate + //if valid + //choose a PIN + //register + + + var devplatform = "platform"; + var devmodel = "model"; + + if (window.cordova) { + devplatform = window.device.platform; + devmodel = window.device.model; + } + + + Engine.registerDevice(hashguid, deviceName, devplatform, devmodel, pinhash, regToken, secret, function (err, result) { + + if (!err) { + + var dk = JSON.parse(result); + + if (dk.DeviceKey.length > 0) { + + var decblob = Engine.decryptNp(enck, dk.DeviceKey, iv); + + //slice it up + //64 64 64 + var hk = decblob.substring(0, 64); + var fatoken = decblob.substring(64, 128); + + var encp = Engine.encryptNp(pwd, dk.DeviceKey); + result = ''; + + var ptok = {}; + ptok.ct = encp.toString(); + ptok.iv = encp.iv.toString(); + var ptoken = JSON.stringify(ptok); + + + var enc = Engine.encryptNp(fatoken, dk.DeviceKey); + var ctok = {}; + ctok.ct = enc.toString(); + ctok.iv = enc.iv.toString(); + + var ctoken = JSON.stringify(ctok); + + + var ench = Engine.encryptNp(hk, dk.DeviceKey); + var htok = {}; + htok.ct = ench.toString(); + htok.iv = ench.iv.toString(); + + var hkey = JSON.stringify(htok); + + dk.DeviceKey = ''; + + //login using the credentials + //get a session + //then call register PIN + //this will return the encryption key + //encrypt the password and store in local storage + + setCookie("guid", guid); + + Engine.openWallet(guid, fatoken, function (err, result) { + + if (!err) { + + if (!result.TwoFactorOnLogin) { + + setCookie("ninki_rem", ctoken); + setCookie("ninki_p", ptoken); + setCookie("ninki_reg", regToken); + setCookie("ninki_h", hkey); + + $('#pairDevice').hide(); + + initialiseUI(); + + } else { + + + $('#pairdevicealertmessage').html("could not pair"); + $('#pairdevicealert').show(); + } + + + } else { + + $('#pairdevicealertmessage').html(result); + $('#pairdevicealert').show(); + } + + }); + } else { + + $('#pairdevicealertmessage').html("The pairing token has expired"); + $('#pairdevicealert').show(); + } + + + } else { + + $('#pairdevicealertmessage').html(result); + $('#pairdevicealert').show(); + + } + + }); + + } else { + + $('#pairdevicealertmessage').html(secvalid); + $('#pairdevicealert').show(); + + } + + }); + + } + + }); + + + }); + + $("#btnLoginPIN").click(function () { + + var pin = $("#loginpinno").val(); + + $("#enterpinalert").hide(); + + if (pin.length == 4) { + + getCookie("guid", function (guid) { + + Engine.m_oguid = guid; + + var bytes = []; + for (var i = 0; i < guid.length; ++i) { + bytes.push(guid.charCodeAt(i)); + } + + Engine.m_guid = Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(bytes)).toString(); + + + Engine.getDeviceKey(pin, function (err, ekeyv) { + + //decrypt the passcode + + if (!err) { + + getCookie("ninki_p", function (result) { + + var enc = JSON.parse(result); + result = ''; + Engine.setStretchPass(Engine.decryptNp(enc.ct, ekeyv.DeviceKey, enc.iv)); + + getCookie("ninki_rem", function (res) { + + if (res.length > 0) { + var enc = JSON.parse(res); + var fatoken = Engine.decryptNp(enc.ct, ekeyv.DeviceKey, enc.iv); + + //get the two factor token + + //do we need to open wallet ? + if (!Engine.m_appInitialised) { + Engine.openWallet(guid, fatoken, function (err, result) { + + if (!err) { + + if (result.TwoFactorOnLogin) { + + $("#loginpinno").val(''); + $("#enterpinalert").show(); + $("#enterpinalertmessage").html('Token has expired'); + + } else { + + $("#loginpin").hide(); + $("#loginpinno").val(''); + + initialiseUI(); + Engine.m_appInitialised = true; + } + + } else { + + } + + }); + + } else { + + if (ekeyv.SessionToken) { + $("#API-Token").val(ekeyv.SessionToken); + } + //set new session key + + $("#loginpin").hide(); + $("#loginpinno").val(''); + + initialiseUI(); + + } + + } + + }); + + }); + + } else { + + + if (ekeyv == "ErrDeviceDestroyed") { + + deleteCookie("ninki_reg"); + deleteCookie("ninki_p"); + deleteCookie("ninki_rem"); + deleteCookie("guid"); + + location.reload(); + } + + $("#loginpinno").val(''); + $("#enterpinalert").show(); + $("#enterpinalertmessage").html(ekeyv); + + } + + }); + + }); + + } + + }); + + $("#btnLogin").click(function () { + + if (!ensureOpenWalletGuidAndPasswordValid()) return; + + $("#imgopenwaiting").show(); + $("#btnLogin").prop('disabled', true); + $("#btnLogin").addClass('disabled'); + var target = document.getElementById('imgopenwaiting'); + var spinner = new Spinner(spinneropts).spin(target); + + + setTimeout(function () { + + var guid = $('#openWalletStart input#guid').val(); + var password = $('#openWalletStart input#password').val(); + var twoFactorCode = $('#openWalletStart input#twoFactorCode').val(); + + //decrypt the password using the key sent back from the server + + + Engine.setPass(password, guid); + + getCookie("ninki_rem", function (res) { + + if (res.length > 0) { + var enc = JSON.parse(res); + twoFactorCode = Engine.decryptNp(enc.ct, Engine.m_password, enc.iv); + } + + setCookie('guid', guid); + + Engine.openWallet(guid, twoFactorCode, function (err, result) { + + $("#imgopenwaiting").hide(); + $("#btnLogin").prop('disabled', false); + $("#btnLogin").removeClass('disabled'); + + if (!err) { + + $("#imgopenwaiting").hide(); + $("#openwalletalert").hide(); + + if (result.TwoFactorOnLogin) { + + //delete any old 2 factor tokens + + deleteCookie("ninki_rem"); + + if (!result.Beta12fa) { + + $('#openWalletStart input#password').val(''); + + if (result.TwoFactorOnLogin) { + $("#siguid").hide(); + $("#silguid").hide(); + $("#sipwd").hide(); + $("#si2fa").show(); + $("#sib1").hide(); + $("#sib2").show(); + $('#openWalletStart input#twoFactorCode').focus(); + + } + } else { + + //for beta1 migrations + $("#siguid").hide(); + $("#silguid").hide(); + $("#sipwd").hide(); + $("#si2fa").show(); + $("#sib1").hide(); + $("#sib2").show(); + $('#openWalletStart input#twoFactorCode').focus(); + + } + + } else { + + + + //initiate 2fa setup modal + if (!m_this.m_twoFactorOnLogin) { + $("#twofactorsettings").show(); + $("#2famodal").modal('show'); + + $("#twofactorsettings").show(); + $("#btnSetupTwoFactor").hide(); + $("#savetwofactorerror").hide(); + $("#setup2faemail").hide(); + $("#setup2faqr").show(); + + showSettingsTwoFactorQr(); + + } else { + + initialiseUI(); + + } + + } + + $("#unlockaccount").hide(); + + } else { + $("#imgopenwaiting").hide(); + $("#openwalletalert").show(); + + if (result == "ErrAccount") { + $("#openwalletalertmessage").html("Incorrect password"); + } + + if (result == "ErrLocked") { + $("#openwalletalertmessage").html("Your account has been locked."); + $("#unlockaccount").show(); + } else { + $("#unlockaccount").hide(); + } + } + + }); + + }); + }, 100); + }); + + + $("#btn2faLogin").click(function () { + + + $("#img2faopenwaiting").show(); + $("#btn2faLogin").prop('disabled', true); + $("#btn2faLogin").addClass('disabled'); + var target = document.getElementById('img2faopenwaiting'); + var spinner = new Spinner(spinneropts).spin(target); + + + setTimeout(function () { + + var twoFactorCode = $('#openWalletStart input#twoFactorCode').val(); + var rememberTwoFactor = $('#twofactorremember')[0].checked; + + Engine.openWallet2fa(twoFactorCode, rememberTwoFactor, function (err, result) { + + if (err) { + + $("#img2faopenwaiting").hide(); + $("#openwalletalert").show(); + $("#openwalletalertmessage").html(result); + $("#btn2faLogin").prop('disabled', false); + $("#btn2faLogin").removeClass('disabled'); + } else { + + if (result.CookieToken) { + + setCookie("ninki_rem", result.CookieToken); + } + + initialiseUI(); + } + }); + + }, 100); + + }); + + + + $("#btnEmailGuid").click(function () { + + var userName = $("#txtlostguidusername").val(); + + Engine.emailGUID(userName, function (err, response) { + + showOpenWalletStart(); + }); + + }); + + $("#btnaddfriend").click(function () { + + addFriend($('input#friend').val()); + + }); + + $("#btngenaddr").click(function () { + + generateAddressClient(); + + }); + + + $("#showopenwallet").click(function () { + + showOpenWalletStart(); + + }); + + $("#btnopenwalletrec").click(function () { + + showOpenWalletStart(); + + }); + + + $("#showopenwalletcr").click(function () { + + showOpenWalletStart(); + + }); + + + + $("#btnsendmoneystd").hammer(null).bind("tap", function () { + + sendMoneyStd(); + + }); + + + $("#btnCreate").click(function () { + + if ($("#frmcreate").parsley('validate')) { + + //check password strength + if (($(".password-verdict").html() == 'Strong' || $(".password-verdict").html() == 'Very Strong')) { + + //can we remove this check? + if (ensureCreateWalletGuidNicknameAndPasswordValid()) { + + $("#imgcreatewaiting").show(); + $("#btnCreate").prop('disabled', true); + $("#btnCreate").addClass('disabled'); + $("#lnkOpenWallet").hide(); + + var target = document.getElementById('imgcreatewaiting'); + var spinner = new Spinner(spinneropts).spin(target); + + //error handling here? + + var guid = $('#createWalletStart input#guid').val(); + var username = $("#createWalletStart input#nickname").val(); + var password = $('#createWalletStart input#cpassword').val(); + var emailAddress = $('#createWalletStart input#emailaddress').val(); + + + Engine.createWallet(guid, password, username, emailAddress, function (err, result) { + + //move error handling and ui elements to here + $("#createWalletStart input#nickname").css("border-color", "#ccc"); + if (err) { + if (result == "ErrUserExists") { + + $("#createWalletStart input#nickname").css("border-color", "#ffaaaa"); + $("#imgcreatewaiting").hide(); + + $("#createwalletalert").show(); + $("#createwalletalertmessage").html("The username already exists"); + + $("#btnCreate").prop('disabled', false); + $("#btnCreate").removeClass('disabled'); + $("#lnkOpenWallet").show(); + } + if (result == "ErrEmailExists") { + + $("#createWalletStart input#emailaddress").css("border-color", "#ffaaaa"); + $("#imgcreatewaiting").hide(); + + $("#createwalletalert").show(); + $("#createwalletalertmessage").html("The email address is already in use"); + + $("#btnCreate").prop('disabled', false); + $("#btnCreate").removeClass('disabled'); + $("#lnkOpenWallet").show(); + } + + if (result == "ErrCreateAccount") { + + $("#imgcreatewaiting").hide(); + $("#btnCreate").prop('disabled', false); + $("#btnCreate").removeClass('disabled'); + $("#lnkOpenWallet").show(); + + } + + } else { + + //set variables for the session + $("#createWalletStart").hide(); + $('#createWalletStart input#cpassword').val(''); + $('#createWalletStart input#password1').val(''); + + $("#hotWalletPhrase").text(result.hotWalletPhrase); + $("#coldWalletPhrase").text(result.coldWalletPhrase); + //$("#ninkiWalletPhrase").text(result.ninkiWalletPhrase); + + $("#walletGuid").text($('input#guid').val()); + $("#showPhrases").show(); + $("#securitywizard").show(); + + if ($('#createWalletStart input#showQrCheckbox')[0].checked) { + //create two factor setting in databse and display qr code + $("#no2famessage").hide(); + showTwoFactorQr(); + } else { + $("#no2famessage").show(); + } + } + }); + } + + } else { + + //password not strong + $("#createwalletalert").show(); + $("#createwalletalertmessage").html("Password must be Strong- ideally Very Strong"); + } + + } + + }); + + $("#btnPassphraseLogin").click(function () { + + + var isvalid = true; + + if ($('#createWalletStart input#showQrCheckbox')[0].checked) { + isvalid = $("#phrase2fa").parsley('validate'); + } + + if (isvalid) { + + var twoFactorCodeChk = $('#twoFactorCodeCheck').val(); + + var target = document.getElementById('imgphrasewaiting'); + var spinner = new Spinner(spinneropts).spin(target); + + $("#imgphrasewaiting").show(); + + setCookie('guid', Engine.m_oguid); + + Engine.openWalletAfterCreate(twoFactorCodeChk, function (err, result) { + + if (err) { + + $("#imgphrasewaiting").hide(); + $("#phraseloginerror").show(); + $("#phraseloginerrormessage").html(result); + + } else { + + + initialiseUI(); + $("#imgphrasewaiting").hide(); + $("#phraseloginerror").hide(); + $("#validateemail").show(); + $("#step4").show(); + $("#step3").hide(); + $("#listep3").removeClass("active"); + $("#listep4").addClass("active"); + $("#prgsecwiz").width('100%'); + $(".next").hide(); + $(".previous").hide(); + } + + + }); + } + + }); + + + $("#btnEmailValidate").click(function () { + + var token = $("#txtEmailToken").val(); + + Engine.getEmailValidation(token, function (err, response) { + + if (err) { + + } else { + + if (response != "Valid") { + $("#valemailerror").show(); + + if (response == "Expired") { + $("#valemailerrormessage").html('Your token has expired'); + } + if (response == "Invalid") { + $("#valemailerrormessage").html('Your token is not valid'); + } + + + + } else { + + if ($('#createWalletStart input#showQrCheckbox')[0].checked) { + //TWOFACTORONLOGIN = true; + } + + //initialiseUI(); + Engine.m_validate = false; + + //readAccountSettingsFromServerAndPopulateForm(); + $("#securitywizard").hide(); + $("#validateemail").hide(); + $("#mainWallet").show(); + $("#valemailerror").hide(); + } + } + + }); + + + //call to verify token + + + }); + + //wallet security wizard + + var step = 1; + $("#step1").show(); + $("#prgsecwiz").width('25%'); + $(".previous").hide(); + $(".next").click(function () { + + if (step == 2) { + $("#step3").show(); + $("#step2").hide(); + $("#listep2").removeClass("active"); + $("#listep3").addClass("active"); + $("#prgsecwiz").width('75%'); + $(".next").hide(); + $(".previous").show(); + step++; + } + + if (step == 1) { + $("#step2").show(); + $("#step1").hide(); + $("#listep1").removeClass("active"); + $("#listep2").addClass("active"); + $("#prgsecwiz").width('50%'); + $(".previous").show(); + + step++; + } + + }); + + $(".previous").click(function () { + + if (step == 2) { + $("#step1").show(); + $("#step2").hide(); + $("#listep2").removeClass("active"); + $("#listep1").addClass("active"); + $("#prgsecwiz").width('25%'); + $(".previous").hide(); + $(".next").show(); + step--; + } + + if (step == 3) { + $("#step2").show(); + $("#step3").hide(); + $("#listep3").removeClass("active"); + $("#listep2").addClass("active"); + $("#prgsecwiz").width('50%'); + $(".previous").show(); + $(".next").show(); + step--; + } + + if (step == 4) { + $("#step3").show(); + $("#step4").hide(); + $("#listep4").removeClass("active"); + $("#listep3").addClass("active"); + $("#prgsecwiz").width('75%'); + $(".previous").show(); + $(".next").hide(); + step--; + } + + }); + + + + + + + + + $("#btnReset2fa").click(function () { + + //stretch password + //download the recovery packet + //decrypt + //return shared secret + //no feedback apart from, please check your email + var fguid = $("#txtreset2faguid").val(); + var fusername = $("#txtreset2fausername").val(); + var fpwd = $("#txtreset2fapassword").val(); + + $("#reset2faerror").hide(); + $("#reset2fasuccess").hide(); + + Engine.ResetTwoFactor(fguid, fusername, fpwd, function (err, results) { + + if (err) { + $("#reset2faerror").show(); + $("#reset2faerrormessage").html('There was an error'); + } else { + $("#validate2fareset").show(); + $("#reset2fa").hide(); + } + }); + + }); + + + $("#btnhidekeys").click(function () { + $("#secdisphrase").hide(); + $("#secdisninki").hide(); + $("#btnhidekeys").hide(); + $("#btndisplaykeys").show(); + }); + + $("#btndisplaykeys").click(function () { + + //get the wallet packet + //secdisphrase + + var ninkiPub = Engine.m_walletinfo.ninkiPubKey; + var phrase = Engine.m_walletinfo.hotHash; + + var bip39 = new BIP39(); // 'en' is the default language + var hotmnem = bip39.entropyToMnemonic(phrase); + + $("#secdisphrase").html(hotmnem); + $("#secdisninki").html(ninkiPub); + $("#secdisphrase").show(); + $("#secdisninki").show(); + $("#btnhidekeys").show(); + $("#btndisplaykeys").hide(); + + }); + + + //depreciated + $("#btn2faResetValidate").click(function () { + + var vtoken = $("#txt2faResetToken").val(); + + Engine.EmailValidationForTwoFactor(vtoken, 0, function (err, response) { + if (err) { + + } else { + + if (response != "Valid") { + $("#val2fatokenerror").show(); + + if (response == "Expired") { + $("#val2fatokenerrormessage").html('Your token has expired'); + } + if (response == "Invalid") { + $("#val2fatokenerrormessage").html('Your token is not valid'); + } + + } else { + $("#val2fatoken").hide(); + $("#val2fatokenerror").hide(); + location.reload(); + } + } + + }); + + }); + + + //transaction filters + + $("#optDay").click(function () { + trasactionFilterOn = true; + currentTransactionFilter = 'Day'; + lastNoOfTrans = -1; + updateTransactions(); + }); + + $("#optWeek").click(function () { + trasactionFilterOn = true; + currentTransactionFilter = 'Week'; + lastNoOfTrans = -1; + updateTransactions(); + }); + + $("#optMonth").click(function () { + trasactionFilterOn = true; + currentTransactionFilter = 'Month'; + lastNoOfTrans = -1; + updateTransactions(); + }); + + $('#btntransearch').click(function () { + trasactionFilterOn = true; + currentTransactionFilter = 'Search'; + lastNoOfTrans = -1; + updateTransactions(); + }); + + $('#btntranclear').click(function () { + trasactionFilterOn = false; + currentTransactionFilter = ''; + lastNoOfTrans = -1; + updateTransactions(); + $("#optDay").removeClass('active'); + $("#optWeek").removeClass('active'); + $("#optMonth").removeClass('active'); + $("#txttransearch").val(''); + + }); + + + $('#thtrandate').click(function () { + + //reset contact widget + if (currentTransactionSort == 'ContactDesc') { + $('#thtrancontact').toggleClass('active'); + } + + if (currentTransactionSort == 'DateDesc') { + currentTransactionSort = 'DateAsc'; + } else { + currentTransactionSort = 'DateDesc'; + } + + trasactionSortOn = true; + lastNoOfTrans = -1; + updateTransactions(); + }); + + $('#thtrancontact').click(function () { + + //reset date widget + if (currentTransactionSort == 'DateDesc') { + $('#thtrandate').toggleClass('active'); + } + + if (currentTransactionSort == 'ContactAsc') { + currentTransactionSort = 'ContactDesc'; + } else { + currentTransactionSort = 'ContactAsc'; + } + + trasactionSortOn = true; + lastNoOfTrans = -1; + updateTransactions(); + }); + + $('#tpagfirst').click(function () { + currentPageIndex = 0; + lastNoOfTrans = -1; + updateTransactions(); + }); + + $('#tpaglast').click(function () { + currentPageIndex = Math.floor((filteredTransactions.length / transactionsPerPage)); + lastNoOfTrans = -1; + updateTransactions(); + + }); + + $('#tpagnext').click(function () { + if (currentPageIndex < Math.floor((filteredTransactions.length / transactionsPerPage))) { + currentPageIndex = currentPageIndex + 1; + lastNoOfTrans = -1; + updateTransactions(); + } + }); + + $('#tpagprev').click(function () { + if (currentPageIndex > 0) { + currentPageIndex = currentPageIndex - 1; + lastNoOfTrans = -1; + updateTransactions(); + } + }); + + + //invoice filters + //for me + $("#optPending").click(function () { + invoiceFilterOn = true; + currentInvoiceFilter = 'Pending'; + lastInvoiceToPayCount = -1; + showInvoiceList(); + }); + + $("#optPaid").click(function () { + invoiceFilterOn = true; + currentInvoiceFilter = 'Paid'; + lastInvoiceToPayCount = -1; + showInvoiceList(); + }); + + $("#optRejected").click(function () { + invoiceFilterOn = true; + currentInvoiceFilter = 'Rejected'; + lastInvoiceToPayCount = -1; + showInvoiceList(); + }); + + $("#optClearInvoice").click(function () { + invoiceFilterOn = false; + currentInvoiceFilter = ''; + lastInvoiceToPayCount = -1; + showInvoiceList(); + }); + + $('#btnSearchInvForMe').click(function () { + currentForMeInvoicePageIndex = 0; + invoiceFilterOn = true; + currentInvoiceFilter = 'Search'; + lastInvoiceToPayCount = -1; + showInvoiceList(); + }); + + + + + + //by me + //invoice filters + $("#optByMePending").click(function () { + invoiceByMeFilterOn = true; + currentByMeInvoiceFilter = 'Pending'; + lastInvoiceByUserCount = -1; + showInvoiceByUserList(); + }); + + $("#optByMePaid").click(function () { + invoiceByMeFilterOn = true; + currentByMeInvoiceFilter = 'Paid'; + lastInvoiceByUserCount = -1; + showInvoiceByUserList(); + }); + + $("#optByMeRejected").click(function () { + invoiceByMeFilterOn = true; + currentByMeInvoiceFilter = 'Rejected'; + lastInvoiceByUserCount = -1; + showInvoiceByUserList(); + }); + + $("#optByMeClear").click(function () { + invoiceByMeFilterOn = false; + currentByMeInvoiceFilter = ''; + lastInvoiceByUserCount = -1; + showInvoiceByUserList(); + }); + + $('#btnSearchInvByMe').click(function () { + currentByMeInvoicePageIndex = 0; + invoiceByMeFilterOn = true; + currentByMeInvoiceFilter = 'Search'; + lastInvoiceByUserCount = -1; + showInvoiceByUserList(); + }); + + $('#ibmpagfirst').click(function () { + currentByMeInvoicePageIndex = 0; + lastInvoiceByMeNetCount = -1; + showInvoiceByUserList(); + }); + + $('#ibmpaglast').click(function () { + currentByMeInvoicePageIndex = Math.floor((filteredByMeInvoices.length / invoicesByMePerPage)); + lastInvoiceByMeNetCount = -1; + showInvoiceByUserList(); + }); + + $('#ibmpagnext').click(function () { + if (currentByMeInvoicePageIndex < Math.floor((filteredByMeInvoices.length / invoicesByMePerPage))) { + currentByMeInvoicePageIndex = currentByMeInvoicePageIndex + 1; + lastInvoiceByMeNetCount = -1; + showInvoiceByUserList(); + } + }); + + $('#ibmpagprev').click(function () { + if (currentByMeInvoicePageIndex > 0) { + currentByMeInvoicePageIndex = currentByMeInvoicePageIndex - 1; + lastInvoiceByMeNetCount = -1; + showInvoiceByUserList(); + } + }); + + + //for me + + $('#ifmpagfirst').click(function () { + currentForMeInvoicePageIndex = 0; + lastInvoiceToPayCount = -1; + showInvoiceList(); + }); + + $('#ifmpaglast').click(function () { + currentForMeInvoicePageIndex = Math.floor((filteredForMeInvoices.length / invoicesForMePerPage)); + lastInvoiceToPayCount = -1; + showInvoiceList(); + }); + + $('#ifmpagnext').click(function () { + if (currentForMeInvoicePageIndex < Math.floor((filteredForMeInvoices.length / invoicesForMePerPage))) { + currentForMeInvoicePageIndex = currentForMeInvoicePageIndex + 1; + lastInvoiceToPayCount = -1; + showInvoiceList(); + } + }); + + $('#ifmpagprev').click(function () { + if (currentForMeInvoicePageIndex > 0) { + currentForMeInvoicePageIndex = currentForMeInvoicePageIndex - 1; + lastInvoiceToPayCount = -1; + showInvoiceList(); + } + }); + + + + getCookie('ninki_reg', function (res) { + + if (res.length > 0) { + + showOpenWalletStart(); + + } else { + + showCreateWalletStart(); + } + }) + + $("#password").keypress(function (e) { + if (e.which == 13) { + $("#btnLogin").click(); + } + }); + + + $("#twoFactorCode").keypress(function (e) { + if (e.which == 13) { + $("#btn2faLogin").click(); + } + }); + + $("#password1").keypress(function (e) { + if (e.which == 13) { + $("#btnCreate").click(); + } + }); + + + $("#cpassword").blur(function () { + $(".popover.fade.bottom.in").hide(); + }); + + $("#cpassword").focus(function () { + $(".popover.fade.bottom.in").show(); + }); + + $("#newpassword1").blur(function () { + $(".popover.fade.bottom.in").hide(); + }); + + $("#newpassword1").focus(function () { + $(".popover.fade.bottom.in").show(); + }); + + //$("#balance").html("... BTC"); + + $("#mainWallet").hide(); + $('#message').hide(); + $("#openwalletalert").hide(); + $("#createwalletalert").hide(); + $("#imgopenwaiting").hide(); + $("#imgcreatewaiting").hide(); + $("#showPhrases").hide(); + $("#twoFactorQr").hide(); + $("#2factor1").hide(); + $("#securitywizard").hide(); + $("#secdisphrase").hide(); + $("#secdisninki").hide(); + $("#btnhidekeys").hide(); + + $("#btnSendToFriend").hammer(null).bind("tap", function () { + + + sendMoney(SELECTEDFRIEND, 0); + + + }); + $("#sendfriendprog").hide(); + + $("#hforgotguid").click(function () { + + $("#createWalletStart").hide(); + $("#openWalletStart").hide(); + $("#lostguid").show(); + $("#reset2fa").hide(); + }); + + + $("#hlost2fa").click(function () { + + $("#createWalletStart").hide(); + $("#openWalletStart").hide(); + $("#lostguid").hide(); + $("#reset2fa").show(); + + }); + + + $("#emailresend").click(function () { + + Engine.sendWelcomeDetails(function (err, result) { + + if (!err) { + + $("#emailresendmessage").show(); + $("#emailresend").hide(); + //email has been resent, please check your email + } + + }); + + }); + + + $("#cpassword").on('change keyup', function () { + + $("#password1").parsley("validate"); + + }); + + + $("#twoFactorCodeCheck").on('change keyup', function () { + + $("#phraseloginerror").fadeOut(100); + + }); + + + $("#btnChangePassword").click(function () { + + $("#chngpwssuc").hide(); + $("#chngpwerr").hide(); + $("#chngpwdprog").hide(); + $("#chngpwdprogbar").width('0%'); + + var newpassword = $("#newpassword1").val(); + var oldpassword = $("#oldpwd").val(); + var twoFactorCode = $("#txtTwoFactorCodeForChangePwd").val(); + + if ($("#frmpwdchange").parsley('validate')) { + + $("#chngpwdprog").show(); + $("#chngpwdprogmess").show(); + $("#chngpwdprogbar").width('10%'); + $("#chngpwdprogmess").html('Getting packet...'); + + + if (oldpassword != newpassword) { + + //check password strength + if (($(".newpwstrength_viewport_progress .password-verdict").html() == 'Strong' || $(".newpwstrength_viewport_progress .password-verdict").html() == 'Very Strong')) { + + //stretch old password + //verify that it matches the current one + $("#chngpwdprogbar").width('20%'); + $("#chngpwdprogmess").html('Getting details...'); + + setTimeout(function () { + + Engine.ChangePassword(twoFactorCode, oldpassword, newpassword, "#chngpwdprogbar", "#chngpwdprogmess", function (err, results) { + + if (err) { + $("#chngpwerr").show(); + $("#chngpwerrmess").html(results); + $("#chngpwdprogmess").hide(); + $("#chngpwdprog").hide(); + + } else { + + password = results; + $("#chngpwerr").hide(); + $("#chngpwdprogbar").width('100%'); + $("#chngpwdprog").hide(); + $("#chngpwdprogmess").hide(); + $("#chngpwdprogbar").hide(); + $("#chngpwssuc").show(); + + $("#newpassword1").val(''); + $("#newpassword2").val(''); + $("#oldpwd").val(''); + $("#txtTwoFactorCodeForChangePwd").val(''); + + } + + }); + + + }, 500); + } + + } else { + $("#chngpwerr").show(); + $("#chngpwerrmess").html("Passwords are the same. Password not updated"); + $("#chngpwdprogmess").hide(); + $("#chngpwdprog").hide(); + } + + } + }); + + + $("#btnVerify").click(function () { + + var code = $("#txtCode").val(); + + $("#txtCode").css("border-color", "#ccc"); + $("#validatefail").hide(); + $("#validatesuccess").hide(); + + var bip39 = new BIP39(); + code = bip39.mnemonicToHex(code); + + if (code.length != 40) { + $("#txtCode").css("border-color", "#ffaaaa"); + return; + } + + //get the hash to validate against + //this will confirm that my friend has the same keys + //i orginally packaged for him + + Engine.verifyFriendData(SELECTEDFRIEND, code, function (err, result) { + + if (result) { + $("#validateform").hide(); + $("#validatesuccess").show(); + $("#txtCode").val(''); + selectedFriend.validated = true; + FRIENDSLIST[selectedFriend.userName].validated = true; + updateSelectedFriend(); + + //update list also + + //find friend in list and update the validated icon + $("#myfriends #seltarget" + selectedFriend.userName).html('
Date: '; + popcontent += trdate; + popcontent += '
'; + + popcontent += 'TransactionId
'; + popcontent += ''; + popcontent += transactions[i].TransactionId; + popcontent += '
'; + + popcontent += 'Address: '; + popcontent += transactions[i].Address; + popcontent += '
'; + + popcontent += 'Amount: '; + popcontent += convertFromSatoshis(transactions[i].Amount, COINUNIT) + ' '; + popcontent += COINUNIT + '
'; + + popcontent += 'Send/Receive: '; + popcontent += transactions[i].TransType; + popcontent += '
'; + + $("#btnpop" + i).popover({ + placement: 'left', // top, bottom, left or right + title: 'Transaction Details', + html: 'true', + content: 'Date: '; - popcontent += transactions[i].TransDateTime; + popcontent += _.escape(trdate); popcontent += '
'; popcontent += 'TransactionId
'; - popcontent += '';
- popcontent += transactions[i].TransactionId;
+ popcontent += ' ';
+ popcontent += _.escape(transactions[i].TransactionId);
popcontent += ' Address: ';
- popcontent += transactions[i].Address;
+ popcontent += _.escape(transactions[i].Address);
popcontent += ' Amount: ';
- popcontent += convertFromSatoshis(transactions[i].Amount, COINUNIT) + ' ';
- popcontent += COINUNIT + '
Send/Receive: '; - popcontent += transactions[i].TransType; + popcontent += _.escape(transactions[i].TransType); popcontent += '
'; $("#btnpop" + i).popover({ - placement: 'right', // top, bottom, left or right - title: 'Transaction Details', + placement: 'left', // top, bottom, left or right + title: 'Transaction Details', html: 'true', content: '