diff --git a/src/ninki-api.js b/src/ninki-api.js index ff64747..43f2c30 100644 --- a/src/ninki-api.js +++ b/src/ninki-api.js @@ -135,91 +135,91 @@ function lpost(url, postData, callback) { } else { - $.ajax({ - url: "https://api.ninkip2p.com" + url, - type: "POST", - timeout: 10000, - data: JSON.stringify(postData), - contentType: "application/json; charset=utf-8", - dataType: "json", - headers: { 'api-token': apiToken }, - 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")) { + $.ajax({ + url: "https://api.ninkip2p.com:443" + url, + type: "POST", + timeout: 10000, + data: JSON.stringify(postData), + contentType: "application/json; charset=utf-8", + dataType: "json", + headers: { 'api-token': apiToken }, + 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, jdata.message); - } + } - return callback(false, JSON.stringify(jdata)); - }, - fail: function (data, textStatus) { + return callback(false, JSON.stringify(jdata)); + }, + fail: function (data, textStatus) { - data = sanitizer.sanitize(data); - textStatus = sanitizer.sanitize(textStatus); + data = sanitizer.sanitize(data); + textStatus = sanitizer.sanitize(textStatus); - return callback(true, { - textStatus: textStatus, - data: data - }); - }, - error: function (data) { + return callback(true, { + textStatus: textStatus, + data: data + }); + }, + error: function (data) { - if (data.statusText == "timeout") { + if (data.statusText == "timeout") { - return callback(true, "Could not connect to the Ninki server. Please try again. If the problem persists email support@ninkip2p.com."); + return callback(true, "Could not connect to the Ninki server. Please try again. If the problem persists email support@ninkip2p.com."); - } + } - if (data.status == 0) { + if (data.status == 0) { - return callback(true, "Could not connect to the network. Please check that you are connected to the internet."); + return callback(true, "Could not connect to the network. Please check that you are connected to the internet."); - } + } - if (data.status == 403) { - //session has been lost + if (data.status == 403) { + //session has been lost - } else if (data.status == 401) { + } else if (data.status == 401) { - if (!window.cordova) { - if (chrome) { - if (chrome.runtime) { - if (chrome.runtime.reload) { - chrome.runtime.reload() + 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(); + //location.reload(); } - //return callback(true, data.statusText); } else { - //location.reload(); + return callback(true, sanitizer.sanitize(data.statusText)); } - } else { - return callback(true, sanitizer.sanitize(data.statusText)); - } - } else { + } else { - data.responseText = sanitizer.sanitize(data.responseText); + data.responseText = sanitizer.sanitize(data.responseText); - return callback(true, data.responseText); - } + return callback(true, data.responseText); + } - } - }); + } + }); } } @@ -246,7 +246,7 @@ API.getMasterPublicKeyFromUpstreamServer = function (guid, callback) { var postData = { guid: guid }; - return lpost("/api/1/u/createaccount", postData, function (err, response) { + return lpost("/api/2/u/createaccount", postData, function (err, response) { if (err) { return callback(err, response); @@ -266,6 +266,10 @@ 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) { @@ -606,6 +610,23 @@ API.getTransactionsForNetwork = function (guid, sharedid, username, callback) { }; +API.getTimeline = function (guid, sharedid, callback) { + + var postData = { guid: guid, sharedid: sharedid }; + + lpost("/api/1/u/gettimeline", postData, function (err, transactions) { + + if (!err) { + var jtran = JSON.parse(transactions); + return callback(err, jtran); + } else { + return callback(err, transactions); + } + + }); + +}; + API.getInvoiceList = function (guid, sharedid, callback) { var postData = { guid: guid, sharedid: sharedid }; @@ -701,7 +722,7 @@ API.registerDevice = function (guid, deviceName, deviceId, deviceModel, devicePI 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 lpost("/api/2/u/getdevicekey", postData, function (err, dataStr) { return callback(err, dataStr); }); }; @@ -750,7 +771,7 @@ API.getDeviceToken = function (guid, sharedid, deviceName, twoFactorCode, callba }; API.getDeviceTokenForApp = function (guid, sharedid, deviceName, callback) { - var postData = { guid: guid, sharedid: sharedid, deviceName: deviceName}; + var postData = { guid: guid, sharedid: sharedid, deviceName: deviceName }; return lpost("/api/1/u/getdevicetokenforapp", postData, function (err, dataStr) { return callback(err, dataStr); }); @@ -781,4 +802,82 @@ API.createBackupCodes = function (guid, sharedid, twoFactorCode, callback) { }; +API.updateEmailAddress = function (guid, sharedid, emailAddress, callback) { + var postData = { guid: guid, sharedid: sharedid, emailAddress: emailAddress }; + return lpost("/api/1/u/updateemailaddress", postData, function (err, dataStr) { + return callback(err, dataStr); + }); +}; + + +API.createAccountSecPub = function (guid, sharedid, secretPub, callback) { + var postData = { guid: guid, sharedid: sharedid, secretPub: secretPub }; + return lpost("/api/1/u/createaccountsecpub", postData, function (err, dataStr) { + return callback(err, dataStr); + }); +}; + +API.getAccountSecPub = function (guid, sharedid, callback) { + var postData = { guid: guid, sharedid: sharedid}; + return lpost("/api/1/u/getaccountsecpub", postData, function (err, dataStr) { + return callback(err, dataStr); + }); +}; + +API.removeAccountSecPub = function (guid, sharedid, callback) { + var postData = { guid: guid, sharedid: sharedid }; + return lpost("/api/1/u/removeaccountsecpub", postData, function (err, dataStr) { + return callback(err, dataStr); + }); +}; + +API.getGUIDByMPKH = function (mpkh, callback) { + var postData = { mpkh: mpkh }; + return lpost("/api/1/getguidbympkh", postData, function (err, dataStr) { + return callback(err, dataStr); + }); +}; + + +API.requestAuthMigration = function (guid, secret, authreqtoken, callback) { + var postData = { guid: guid, secret: secret, authreqtoken: authreqtoken }; + return lpost("/api/1/u/requestauthmigration", postData, function (err, dataStr) { + return callback(err, dataStr); + }); +}; + +API.getAuthMigrationRequest = function (guid, secret, callback) { + var postData = { guid: guid, secret: secret}; + return lpost("/api/1/u/getauthmigrationrequest", postData, function (err, dataStr) { + + if (!err) { + var jtran = JSON.parse(dataStr); + return callback(err, jtran); + } else { + return callback(err, dataStr); + } + + }); +}; + +API.authMigration = function (guid, sharedid, twoFactorToken, authreqtoken, callback) { + var postData = { guid: guid, sharedid: sharedid, twoFactorToken: twoFactorToken, authreqtoken: authreqtoken }; + return lpost("/api/1/u/authmigration", postData, function (err, dataStr) { + return callback(err, dataStr); + }); +}; + +/// + +API.getAuthMigrationToken = function (guid, secret, authreqtoken, callback) { + var postData = { guid: guid, secret: secret, authreqtoken: authreqtoken }; + return lpost("/api/1/u/getauthmigrationtoken", postData, function (err, dataStr) { + return callback(err, dataStr); + }); +}; + +// + + + module.exports = API; \ No newline at end of file diff --git a/src/ninki-device.js b/src/ninki-device.js index cbadec8..2a67c98 100644 --- a/src/ninki-device.js +++ b/src/ninki-device.js @@ -95,6 +95,7 @@ function Device() { if (typeof window.cordova === 'undefined') { + //switch to false for mob return true; } else { @@ -232,7 +233,7 @@ function Device() { this.getSecureStorageObject = getSecureStorageObject; - function getSecureStorageObject(cname, key, decryptor, callback) { + function getSecureStorageObject(cname, key, decryptor, asbytes, callback) { if (isChromeApp()) { @@ -240,7 +241,7 @@ function Device() { result = result[cname]; - if (result != "") { + if (!(typeof result === 'undefined')) { var decryptok = true; var datac = ""; try { @@ -251,20 +252,31 @@ function Device() { if (enc.expiry) { var currentdate = new Date(); if (((new Date) - new Date(enc.date)) < enc.expiry) { - datac = decryptor(enc.ct, key, enc.iv); + datac = decryptor(enc.ct, key, enc.iv, asbytes); } } } else { - datac = decryptor(enc.ct, key, enc.iv); + datac = decryptor(enc.ct, key, enc.iv, asbytes); + } } catch (error) { decryptok = false; } + result = ""; + if (decryptok) { + result = datac; + } + return callback(result); + + } else { + + return callback(""); + } - return callback(result); + }); @@ -284,17 +296,18 @@ function Device() { if (enc.expiry) { var currentdate = new Date(); if (((new Date) - new Date(enc.date)) < enc.expiry) { - datac = decryptor(enc.ct, key, enc.iv); + datac = decryptor(enc.ct, key, enc.iv, asbytes); } } } else { - datac = decryptor(enc.ct, key, enc.iv); + datac = decryptor(enc.ct, key, enc.iv, asbytes); } } catch (error) { decryptok = false; } + result = ""; if (decryptok) { result = datac; } diff --git a/src/ninki-engine-mar.js b/src/ninki-engine-mar.js new file mode 100644 index 0000000..83eea6e --- /dev/null +++ b/src/ninki-engine-mar.js @@ -0,0 +1,4357 @@ +//ninki-engine + +var Bitcoin = require('bitcoinjs-lib'); +var openpgp = require('openpgp'); +var CryptoJS = require('crypto-js'); +var API = require('./ninki-api'); +var device = require('./ninki-device'); +var BIP39 = require('./bip39'); +var uuid = require('node-uuid'); +var sjcl = require('sjcl'); +var sanitizer = require('sanitizer'); +var crypto = require('crypto'); + +function Engine() { + + this.m_network = "mainnet"; + this.m_walletinfo = {}; + 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_privKeyRaw = ''; + this.m_pubKeyRaw = ''; + this.m_APIToken = ''; + this.m_appInitialised = false; + this.m_watchOnly = false; + + + m_this = this; + + this.Device = new device(); + + + this.appHasInit = appHasInit; + function appHasInit() { + + if (m_this.m_sharedid.length > 0) { + return true; + } else { + return false; + } + + } + + this.serialize = serialize; + function serialize() { + + var serTarget = {}; + serTarget.m_walletinfo = m_this.m_walletinfo; + serTarget.m_sharedid = m_this.m_sharedid; + serTarget.m_twoFactorOnLogin = m_this.m_twoFactorOnLogin; + serTarget.m_nickname = m_this.m_nickname; + //serTarget.m_profileImage = m_this.m_profileImage; + //serTarget.m_statusText = m_this.m_statusText; + serTarget.m_guid = m_this.m_guid; + //serTarget.m_settings = m_this.m_settings; + serTarget.m_fingerprint = m_this.m_fingerprint; + //serTarget.m_secret = m_this.m_secret; + serTarget.m_invoiceTax = m_this.m_invoiceTax; + + serTarget.m_privKeyRaw = m_this.m_privKeyRaw; + serTarget.m_pubKeyRaw = m_this.m_pubKeyRaw; + + return JSON.stringify(serTarget); + + } + + this.initialize = initialize; + function initialize(cache) { + + m_this.m_walletinfo = cache.m_walletinfo; + m_this.m_sharedid = cache.m_sharedid; + m_this.m_twoFactorOnLogin = cache.m_twoFactorOnLogin; + m_this.m_nickname = cache.m_nickname; + //m_this.m_profileImage = cache.m_profileImage; + //m_this.m_statusText = cache.m_statusText; + m_this.m_guid = cache.m_guid; + //m_this.m_settings = cache.m_settings; + m_this.m_fingerprint = cache.m_fingerprint; + //m_this.m_secret = cache.m_secret; + m_this.m_invoiceTax = cache.m_invoiceTax; + + var privKeys = openpgp.key.readArmored(cache.m_privKeyRaw); + var publicKeys = openpgp.key.readArmored(cache.m_pubKeyRaw); + + if (privKeys.keys[0].decrypt(m_this.m_sharedid)) { + m_this.m_privKey = privKeys.keys[0]; + } + + m_this.m_pubKey = publicKeys.keys[0]; + + cache = {}; + } + + this.appHasLoaded = appHasLoaded; + function appHasLoaded() { + + // m_this.m_password = ''; + + } + + + this.isRealGuid = isRealGuid; + //Checks if GUID is valid. Only takes lowercase, non-bracketed GUIDs. + function isRealGuid(potentialGuidAsString) { + + if (!potentialGuidAsString) return false; + if (typeof potentialGuidAsString != 'string') return false; + if (potentialGuidAsString.length == 0) return false; + + var guidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/; + var match = potentialGuidAsString.match(guidRegex); + return match ? true : false; + } + + //remove this method + //generate server side + this.fillElementWithGuid = fillElementWithGuid; + function fillElementWithGuid(element) { + //assert(element, "Element not specified"); + element.val(uuid.v4()); + } + + this.getguid = getguid; + function getguid() { + //assert(element, "Element not specified"); + return uuid.v4(); + } + + + function getRandomValues(b) { + var rng = new Uint8Array(b); + window.crypto.getRandomValues(rng); + return rng; + } + + + this.encrypt = encrypt; + function encrypt(valueToEncrypt, passphrase) { + + valueToEncrypt = JSON.stringify(valueToEncrypt); + + var key = CryptoJS.enc.Hex.parse(passphrase); + + var iv = getRandomValues(32); + + var ivbytes = []; + 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 }); + + return encrypted; + }; + + + this.encryptNp = encryptNp; + function encryptNp(valueToEncrypt, passphrase) { + + valueToEncrypt = CryptoJS.enc.Hex.parse(valueToEncrypt); + + var key = CryptoJS.enc.Hex.parse(passphrase); + + var iv = getRandomValues(32); + + + var ivbytes = []; + 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) { + + var key = CryptoJS.enc.Hex.parse(passphrase); + var iv = CryptoJS.enc.Hex.parse(iv); + + var decryptedObject = CryptoJS.AES.decrypt(encryptedObj, key, { iv: iv }); + + var decryptutf = decryptedObject.toString(CryptoJS.enc.Utf8); + var decryptjson = JSON.parse(decryptutf); + 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; + }; + + var hmac = function (key) { + var hasher = new sjcl.misc.hmac(key, sjcl.hash.sha1); + this.encrypt = function () { + return hasher.encrypt.apply(hasher, arguments); + }; + }; + + this.pbkdf2 = pbkdf2; + 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); + } + + this.setStretchPass = setStretchPass; + function setStretchPass(pass) { + m_this.m_password = pass; + } + + + this.getEncHotHash = getEncHotHash; + function getEncHotHash(callback) { + + m_this.Device.getStorageItem("hk" + m_this.m_guid, function (hk) { + + if (hk) { + + m_this.Device.getStorageItem("hkiv" + m_this.m_guid, function (hkiv) { + + if (hkiv) { + + var data = "{\"enc\":\"" + hk + "\", \"iv\":\"" + hkiv + "\"}"; + + return callback(false, data); + + } else { + + return callback(true, "ErrMissing"); + } + + }); + + } else { + + return callback(true, "ErrMissing"); + + } + + }); + + + } + + + this.restoreHotHash = restoreHotHash; + + function restoreHotHash(hothash, callback) { + + //validate against loaded hot public key + var validseed = true; + try { + var bipHot = Bitcoin.HDWallet.fromSeedHex(hothash, m_this.m_network); + if (m_this.m_walletinfo.hotPub != bipHot.toString()) { + validseed = false; + } + } catch (error) { + + validseed = false; + } + + if (validseed) { + + saveHotHash(hothash, function (err, result) { + + if (!err) { + return callback(false, hothash); + } else { + return callback(err, result); + } + + }); + + } else { + + return callback(true, "ErrSeedInvalid"); + + } + + + } + + + this.getHotHash = getHotHash; + function getHotHash(key, callback) { + + //to do: validate key against stored public key + //needs to be done incase user changed their password on a different machine + + if (m_this.Device.isChromeApp() || m_this.Device.isBrowser() || m_this.Device.isNode()) { + + m_this.Device.getStorageItem("hk" + m_this.m_guid, function (result) { + + var hk = result; + + m_this.Device.getStorageItem("hkiv" + m_this.m_guid, function (resultiv) { + + var hkiv = resultiv + + var hothash = ''; + + var iserror = false; + try { + hothash = decryptNp(hk, m_this.m_password, hkiv); + } catch (error) { + iserror = true; + } + + if (!iserror) { + + //validate against loaded hot public key + var validseed = true; + try { + var bipHot = Bitcoin.HDWallet.fromSeedHex(hothash, m_this.m_network); + if (m_this.m_walletinfo.hotPub != bipHot.toString()) { + validseed = false; + } + } catch (error) { + + validseed = false; + } + + if (validseed) { + + return callback(false, hothash); + + } else { + + return callback(true, "invalid"); + + } + + + } else { + + return callback(true, "missing"); + } + + }); + + + + }); + + } else { + + + m_this.Device.getStorageItem("ninki_h", function (hk) { + + + if (hk) { + + var hothash = ''; + var iserror = false; + try { + + var enc = JSON.parse(hk); + hothash = decryptNp(enc.ct, key, enc.iv); + + } catch (error) { + iserror = true; + } + if (!iserror) { + + //validate against loaded hot public key + var validseed = true; + try { + var bipHot = Bitcoin.HDWallet.fromSeedHex(hothash, m_this.m_network); + if (m_this.m_walletinfo.hotPub != bipHot.toString()) { + validseed = false; + } + } catch (error) { + + validseed = false; + } + + if (validseed) { + + //get the two factor code also + + m_this.Device.getStorageItem("ninki_rem", function (tft) { + + if (tft != "") { + var jtft = JSON.parse(tft); + + var fatoken = decryptNp(jtft.ct, key, jtft.iv); + + return callback(false, hothash, fatoken); + } else { + + return callback(false, hothash, ""); + + } + + }); + + } else { + + return callback(true, "invalid"); + + } + + } else { + + return callback(true, "missing"); + } + + } else { + + return callback(true, "missing"); + + } + + + }); + + } + + } + + this.saveHotHash = saveHotHash; + function saveHotHash(hotHash, callback) { + + //before we encrypt validate the hash matches the logged in public key + var validseed = true; + try { + var bipHot = Bitcoin.HDWallet.fromSeedHex(hotHash, m_this.m_network); + if (m_this.m_walletinfo.hotPub != bipHot.toString()) { + validseed = false; + } + } catch (error) { + validseed = false; + } + + if (validseed) { + + var encHotHash = encryptNp(hotHash, m_this.m_password); + + if (m_this.Device.isChromeApp() || m_this.Device.isBrowser() || m_this.Device.isNode()) { + + m_this.Device.setStorageItem('hk' + m_this.m_guid, encHotHash.toString()); + m_this.Device.setStorageItem('hkiv' + m_this.m_guid, encHotHash.iv.toString()); + + } else { + + var htok = {}; + htok.ct = encHotHash.toString(); + htok.iv = encHotHash.iv.toString(); + var hkey = JSON.stringify(htok); + + m_this.Device.setStorageItem('ninki_h', hkey); + + } + + callback(false, "ok"); + + } else { + + callback(true, "invalid"); + + } + + + } + + + function validateHotKey(callback) { + + //load the hotkey + //if not there return error + + //validate the hotkey + //if not there return error + + + } + + + //create wallet + //create a new wallet and save to the server + this.createWallet = createWallet; + function createWallet(guid, password, username, emailAddress, callback, progress) { + + 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(); + + + //check if the username already exists + API.doesAccountExist(username.toLowerCase(), emailAddress.toLowerCase(), function (err, accExists) { + + if (accExists.UserExists) { + + return callback(true, "ErrUserExists"); + + } + else if (accExists.EmailExists) { + + return callback(true, "ErrEmailExists"); + + } + else { + + + progress('stretching password...'); + + + setTimeout(function () { + + //stretch the password with the local guid as a salt + m_this.m_password = pbkdf2(password, m_this.m_oguid); + + password = ''; + + //create a new wallet + makeNewWallet(username, emailAddress, function (err, walletInformation, userToken) { + + if (err) { + + return callback(true, "ErrCreateAccount"); + + } else { + + m_this.m_sharedid = userToken; + + //wallet creation is successful + //set variables + //add the usertoken to the wallet + walletInformation.wallet.googleAuthSecret = ""; + walletInformation.wallet.sharedid = userToken; + + // + var recpacket = encryptNp(m_this.m_password, m_this.m_walletinfo.hckey); + + walletInformation.wallet.recPacket = recpacket.toString(); + walletInformation.wallet.recPacketIV = recpacket.iv.toString(); + + //save the wallet to the server + progress('saving data...'); + + setTimeout(function () { + + API.post("/api/1/u/createaccount2", walletInformation.wallet, function (err, response) { + + if (err) { + + return callback(err, "ErrSavePacket"); + + } else { + + //set the session + API.registerToken(response); + m_this.m_APIToken = response; + + //pass back the wallet and info to the calling function + return callback(false, walletInformation); + } + }); + + }, 50); + + } + + }, progress); + + }, 50); + } + + }); + } + + + //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, progress) { + + + //TODO add some more param checking + //rename this function + setTimeout(function () { + + + progress('creating account...'); + + 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, walletInformation); + } else { + return callback(err, walletInformation, userToken); + } + }, progress); + } + }); + }, 50); + } + + + function makeNewWalletPacket(nickname, emailAddress, ninkiPubKey, userToken, secret, callback, progress) { + + + progress('getting entropy...'); + + setTimeout(function () { + + + //what to do if running in node + // crypto provider module + + + + var rngcold = getRandomValues(16); + + var coldKeyBytes = []; + for (var i = 0; i < rngcold.length; ++i) { + coldKeyBytes[i] = rngcold[i]; + } + + //get some random data for the hot key + var rnghot = getRandomValues(16); + + var hotKeyBytes = []; + for (var i = 0; i < rnghot.length; ++i) { + hotKeyBytes[i] = rnghot[i]; + } + + progress('creating cold keys...'); + + setTimeout(function () { + + + var coldHash = Bitcoin.convert.bytesToHex(coldKeyBytes); + + var coldWallet = Bitcoin.HDWallet.fromSeedHex(coldHash, m_this.m_network); + //get the keys as strings + var coldPriv = coldWallet.toString(" "); + var coldPub = coldWallet.toString(); + + progress('creating hot keys...'); + + setTimeout(function () { + + + var hotHash = Bitcoin.convert.bytesToHex(hotKeyBytes); + + var hotWallet = Bitcoin.HDWallet.fromSeedHex(hotHash, m_this.m_network); + //get the keys as strings + var hotPriv = hotWallet.toString(" "); + var hotPub = hotWallet.toString(); + + + //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 + + var hckey = hotHash + coldHash; + var hcbkey = Bitcoin.convert.hexToBytes(hckey); + var hchkey = Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(hcbkey)).toString(); + + progress('creating pgp keys...'); + + + setTimeout(function () { + + //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); + + setTimeout(function () { + + //$('#textMessageCreate').text('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 + }; + + 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() + }; + + //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 + + + //the hot private key is encrypted and saved locally + //encrypted with the user's password + + saveHotHash(hotHash, function (err, res) { + + var bip39 = new BIP39(); + var walletInformation = { + wallet: wallet, + coldWalletPhrase: bip39.entropyToMnemonic(coldHash), + hotWalletPhrase: bip39.entropyToMnemonic(hotHash), + sharedid: userToken, + hckey: hchkey + }; + + + return callback(err, walletInformation); + + }); + + }, 50); + + }, 50); + + }, 50); + + }, 50); + + }, 50); + } + + + this.openWalletAfterCreate = openWalletAfterCreate; + function openWalletAfterCreate(twoFactorCodeChk, callback) { + + //check two factor code + if (twoFactorCodeChk != '') { + SetupTwoFactor(twoFactorCodeChk, function (err, wallet) { + + if (err) { + + return callback(err, wallet); + + } else { + + m_this.m_twoFactorOnLogin = true; + + getSettings(function (err, result) { + + callback(err, result); + + }); + + } + + }); + + } else { + + getSettings(function (err, result) { + + callback(err, result); + + }); + + } + + } + + + this.migrateHotKeyFromPacket = migrateHotKeyFromPacket; + function migrateHotKeyFromPacket(twoFactCode, callback) { + + + 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"); + } + + //if any account still has a hotkey stored in their encrypted packet + //then remove it and resave the packet + + + // show a notice to the user that their hotkey has been migrated locally + // they should write this down as they will need t to setup their wallet + // on a new PC or if they uninstall the chrome app + + if (walletInformation.hotHash != '') { + + var hotHash = walletInformation.hotHash; + + walletInformation.hotPriv = ''; + walletInformation.hotHash = ''; + m_this.m_walletinfo = walletInformation; + saveHotHash(hotHash, function (err, result) { + + if (!err) { + + //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 + + 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) { + + return callback(false, "ok"); + + }); + + } + + }); + + } + + + }); + + } + + }); + + } + + this.openWallet2fa = openWallet2fa; + function openWallet2fa(twoFactCode, rememberTwoFactor, callback) { + + API.getWalletFromServer(m_this.m_guid, m_this.m_secret, twoFactCode, rememberTwoFactor, 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"); + } + + if (m_this.m_migrateBeta12fa) { + + //1. for migrations from beta1 we need to save a secret + + var encryptedSecret = encryptNp(m_this.m_secret, m_this.m_password); + + //we need to base this on account migration status + + + //add twoFactCode here + + API.updateSecretPacket(m_this.m_guid, walletInformation.userToken, encryptedSecret.toString(), encryptedSecret.iv.toString(), function (err, res) { + + + 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 + + + getSettings(function (err, result) { + if (!err) { + callback(err, wallet); + } 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 + + getSettings(function (err, result) { + if (!err) { + callback(err, wallet); + } 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_APIToken = wallet.SessionToken; + m_this.m_twoFactorOnLogin = wallet.TwoFactorOnLogin; + m_this.m_walletinfo = walletInformation; + m_this.m_sharedid = walletInformation.userToken; + + + getSettings(function (err, result) { + + callback(err, result); + + }); + + + }); + + } else { + + m_this.m_settings = {}; + m_this.m_settings.BackupIndex = secvalid.BackupIndex; + m_this.m_settings.HasBackupCodes = secvalid.HasBackupCodes; + + //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; + + + getSettings(function (err, result) { + + 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? + getUserData(function (err, data) { + + if (!err) { + + data = JSON.parse(data); + + m_this.m_nickname = data.Nickname; + + m_this.m_profileImage = data.ProfileImage; + m_this.m_statusText = data.Status; + m_this.m_invoiceTax = data.Tax; + + + //display the secret on a div + + var rsaKeyPair = decrypt(data.Payload, m_this.m_password, data.IV); + + var publicKeys = openpgp.key.readArmored(rsaKeyPair.RSAPub); + var privKeys = openpgp.key.readArmored(rsaKeyPair.RSAPriv); + + + m_this.m_pubKeyRaw = rsaKeyPair.RSAPub; + m_this.m_privKeyRaw = 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)) { + var bip39 = new BIP39(); // 'en' is the default language + var fingermnem = bip39.entropyToMnemonic(m_this.m_pubKey.primaryKey.fingerprint); + + m_this.m_fingerprint = fingermnem; + m_this.m_settings = data.Settings; + m_this.m_validate = !data.Settings.EmailVerified; + + return callback(err, "ok"); + } else { + callback(true, 'failed'); + } + } else { + + return callback(err, data); + } + + }); + + } + + + 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; + } + + var corNodesDone = 0; + var corNodesToProcess = 0; + + function deriveKeys(mpk, nodes, cdcallback) { + + if (m_this.Device.isiOS()) { + + var mkpder = []; + + corNodesToProcess = nodes.length; + cordovaDeriveKey(mpk, nodes, mkpder, function (result) { + + + + return cdcallback(mkpder); + + }); + } else { + + var ret = []; + for (var i = 0; i < nodes.length; i++) { + ret.push(deriveChild(nodes[i], mpk).priv); + } + return cdcallback(ret); + + } + + } + + function cordovaDeriveKey(mpk, nodes, mkpder, derfinished) { + + + if (corNodesDone == corNodesToProcess) { + + + return derfinished(mkpder) + + } else { + + var nls = nodes[corNodesDone].split("/"); + var tnode = nls[2] * 1; + var leaf = nls[3] * 1; + + cordova.exec( + function callback(data) { + + corNodesDone++; + + mkpder.push(data); + + cordovaDeriveKey(mpk, nodes, mkpder, derfinished); + + + }, + function errorHandler(err) { + + + }, + 'ECPlugin', + 'cordovaECDerKey', + [mpk.toString(" "), tnode, leaf] + ); + } + + + } + + + this.sendTransaction = sendTransaction; + function sendTransaction(sendType, friendUserName, addressTo, amount, twoFactorCode, callback, statuscallback) { + + var debug = true; + var debugtime = 5; + corNodesDone = 0; + corNodesToProcess = 0; + 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, m_this.m_network); + + // + + + 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 }; + + statuscallback('Preparing transaction...', '10%'); + + API.post("/api/1/u/getunspentoutputs", pdata, function (err, outputs) { + + + if (!err) { + + + if (debug) { + console.log('getunspentoutputs...success'); + } + + + 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 + + + //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 + + if (debug) { + console.log('derive the private key to use for signing ' + i); + } + + + //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) { + + if (debug) { + console.log('using cached public key ' + i); + } + + dbipHotPub = Bitcoin.convert.hexToBytes(pitem.PK1); + dbipColdPub = Bitcoin.convert.hexToBytes(pitem.PK2); + dbipNinkiPub = Bitcoin.convert.hexToBytes(pitem.PK3); + + } else { + + if (debug) { + console.log('no cache - deriving ' + i); + } + + 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; + } + + } + + + + //call to cordova plugin for private key derivation + + + 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"); + } + + if (changeAmount > 0) { + amountsToSend.push(changeAmount); + } + + + if (debug) { + console.log('change amount...' + changeAmount); + } + + //create a new address for my change to be sent back to me + statuscallback('Preparing transaction...', '20%'); + //if we are sending money to a contact or paying an invoice + //we need to derive addresses on their behalf + + + + + deriveKeys(bipHot, nodeLevels, function (ret) { + + + if (m_this.Device.isiOS()) { + + for (var i = 0; i < ret.length; i++) { + + var nkey = new Bitcoin.ECKey(ret[i]); + + userHotPrivKeys.push(nkey); + + } + + } else { + + packet.userHotPrivKeys = ret; + } + + + + + + 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 + + + statuscallback('creating address...', '30%'); + createAddressForFriend(friendUserName, function (err, address) { + + if (!err) { + + + //statuscallback(null, '40%'); + + addressToSend.push(address); + + //create the change address, this must be done on the client + statuscallback('Creating change address...', '40%'); + + + createAddress('m/0/1', changeAmount, function (err, changeaddress) { + + if (!err) { + + if (changeAmount > 0) { + addressToSend.push(changeaddress); + } + + //now get the transaction data + statuscallback('Building transaction...', '60%'); + setTimeout(function () { + aGetTransactionData(packet, function (err, hashesForSigning, rawTransaction) { + + if (!err) { + + statuscallback(null, '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 + }; + + + statuscallback('Counter-signing transaction...', '80%'); + + //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) { + + + statuscallback('Transaction broadcast...', '100%'); + + return callback(err, result); + + + } else { + + //handle error messages returned from the server + if (result == 'ErrSingleTransactionLimit') { + + statuscallback('Transaction Failed: Single limit exceeded', '0%'); + + return callback(err, result); + + } + + if (result == 'ErrDailyTransactionLimit') { + + statuscallback('Transaction Failed: Daily limit exceeded', '0%'); + + return callback(err, result); + } + + if (result == 'ErrTransactionsPerDayLimit') { + + statuscallback('Transaction Failed: Daily number limit exceeded', '0%'); + + return callback(err, result); + } + + if (result == 'ErrTransactionsPerHourLimit') { + + statuscallback('Transaction Failed: Hourly number limit exceeded', '0%'); + + return callback(err, result); + } + + if (result == 'ErrInvalidRequest') { + + statuscallback('Transaction Failed: Invalid request', '0%'); + + return callback(err, result); + } + + + if (result == 'ErrBroadcastFailed') { + + statuscallback('Transaction Failed: Not accepted', '0%'); + + return callback(err, result); + } + + if (result == "Invalid two factor code") { + + statuscallback("Invalid two factor code", '0%'); + + return callback(err, result); + } + + if (result == "ErrInsufficientFunds") { + statuscallback("Transaction Failed: Insufficient funds", '0%'); + + return callback(err, result); + } + + if (result == "ErrLocked") { + + statuscallback("Contact account is unavailable", '0%'); + + return callback(err, result); + } + + statuscallback('Transaction Failed:' + result, '0%'); + + return callback(err, result); + + } + }); + + } + + + }); + }, debugtime); + } else { + //create address error + + statuscallback(changeaddress, '0%'); + return callback(err, changeaddress); + + } + }); + } else { + + + statuscallback(address, '0%'); + return callback(err, address); + + + } + }); + } else { + + statuscallback('Creating change address...', '20%'); + + addressToSend.push(addressTo); + + if (debug) { + console.log("start creating change address"); + } + //create the change address, this must be done on the client + createAddress('m/0/1', changeAmount, function (err, changeaddress) { + + if (!err) { + + if (debug) { + console.log("finished creating change address"); + } + + + if (changeAmount > 0) { + addressToSend.push(changeaddress); + } + + //now get the transaction + + statuscallback('Creating transaction...', '40%'); + setTimeout(function () { + + aGetTransactionData(packet, function (err, hashesForSigning, rawTransaction) { + + if (!err) { + + statuscallback('Counter-signing transaction...', '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: '' + }; + + + API.post("/api/1/u/sendtransaction", jsonp1, function (err, result) { + + statuscallback(null, '80%'); + + if (!err) { + + statuscallback('Transaction broadcast', '100%'); + + return callback(err, result); + + + } else { + + if (result == 'ErrSingleTransactionLimit') { + + statuscallback('Transaction Failed: Single limit exceeded', '0%'); + + return callback(err, result); + + } + + if (result == 'ErrDailyTransactionLimit') { + + statuscallback('Transaction Failed: Daily limit exceeded', '0%'); + + return callback(err, result); + } + + if (result == 'ErrTransactionsPerDayLimit') { + + statuscallback('Transaction Failed: Daily number limit exceeded', '0%'); + + return callback(err, result); + } + + if (result == 'ErrTransactionsPerHourLimit') { + + statuscallback('Transaction Failed: Hourly number limit exceeded', '0%'); + + return callback(err, result); + } + + if (result == 'ErrInvalidRequest') { + + statuscallback('Transaction Failed: Invalid request', '0%'); + + return callback(err, result); + } + + + if (result == 'ErrBroadcastFailed') { + + statuscallback('Transaction Failed: Not accepted', '0%'); + + return callback(err, result); + } + + if (result == "Invalid two factor code") { + + statuscallback("Invalid two factor code", '0%'); + + return callback(err, result); + } + + if (result == "ErrInsufficientFunds") { + + statuscallback("Transaction Failed: Insufficient funds", '0%'); + + return callback(err, result); + } + + statuscallback('Transaction Failed:' + result, '0%'); + + return callback(err, result); + + } + }); + + } else { + + statuscallback('error creating transaction', '0%'); + + return callback(err, 'error creating transaction'); + + } + + }); + }, 50); + } else { + + //create address error + + statuscallback(changeaddress, '0%'); + + return callback(err, changeaddress); + + } + }); + } + }); + + } else { + + statuscallback(outputs, '0%'); + + return callback(err, outputs); + + } + + }); + + }); + + + + } + + //function createAddress + //this function creates an address for the user + this.createAddress = createAddress; + + function createAddress(nodePath, changeamount, cacallback) { + + + if (changeamount > 0) { + + var snode = nodePath.split('/'); + + 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) { + + if (!err) { + + var path = nodePath + '/' + leaf; + + + if (m_this.Device.isiOS()) { + + var tnode = snode[2] * 1; + + var hotKey = []; + var coldKey = []; + var ninkiKey = []; + + var hhotKey = ''; + var hcoldKey = ''; + var hninkiKey = ''; + + cordova.exec( + function callback(data) { + + hhotKey = data; + + hotKey = Bitcoin.convert.hexToBytes(data); + + + cordova.exec( + function callback(data) { + + hcoldKey = data; + + + coldKey = Bitcoin.convert.hexToBytes(data); + + cordova.exec( + function callback(data) { + + hninkiKey = data; + + + + ninkiKey = Bitcoin.convert.hexToBytes(data); + + + var script = [0x52]; + + script.push(33); + script = script.concat(hotKey); + script.push(33); + script = script.concat(coldKey); + script.push(33); + script = script.concat(ninkiKey); + 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: hhotKey, + pk2: hcoldKey, + pk3: hninkiKey + }; + API.post("/api/1/u/createaddress", postData, function (err, result) { + + if (!err) { + return cacallback(err, address); + } else { + return cacallback(err, result); + } + + }); + + }, + function errorHandler(err) { + //alert('Error'); + }, + 'ECPlugin', + 'cordovaECMult', + [m_this.m_walletinfo.ninkiPubKey, tnode, leaf] + ); + + }, + function errorHandler(err) { + //alert('Error'); + }, + 'ECPlugin', + 'cordovaECMult', + [m_this.m_walletinfo.coldPub, tnode, leaf] + ); + + }, + function errorHandler(err) { + //alert('Error'); + }, + 'ECPlugin', + 'cordovaECMult', + [m_this.m_walletinfo.hotPub, tnode, leaf] + ); + + } else { + + + //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 cacallback(err, address); + } else { + return cacallback(err, result); + } + + }); + + } + + } else { + + return cacallback(err, leaf); + + } + + //now update the address to the server + + }); + + } else { + return cacallback(false, "skipped"); + } + + } + + //function createAddress + //this function creates an address on behalf of a user's contact + this.createAddressForFriend = createAddressForFriend; + function createAddressForFriend(username, cacallback) { + + var postData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: username }; + + + API.post("/api/1/u/getfriendpacket", postData, function (err, packet) { + + if (!err) { + + //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) { + + if (!err) { + + //derive the public keys + var path = 'm/' + leaf; + + + if (m_this.Device.isiOS()) { + + + var nleaf = leaf * 1; + + var hotKey = []; + var coldKey = []; + var ninkiKey = []; + + var hhotKey = ''; + var hcoldKey = ''; + var hninkiKey = ''; + + cordova.exec( + function callback(data) { + + hhotKey = data; + + hotKey = Bitcoin.convert.hexToBytes(data); + + + cordova.exec( + function callback(data) { + + hcoldKey = data; + + coldKey = Bitcoin.convert.hexToBytes(data); + + cordova.exec( + function callback(data) { + + hninkiKey = data; + + ninkiKey = Bitcoin.convert.hexToBytes(data); + + + var script = [0x52]; + + script.push(33); + script = script.concat(hotKey); + script.push(33); + script = script.concat(coldKey); + script.push(33); + script = script.concat(ninkiKey); + 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. + //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: hhotKey, + pk2: hcoldKey, + pk3: hninkiKey + }; + API.post("/api/1/u/createaddressforfriend", postData, function (err, result) { + + if (!err) { + return cacallback(err, address); + } else { + return cacallback(err, result); + } + + }); + + }, + function errorHandler(err) { + //alert('Error'); + }, + 'ECPlugin', + 'cordovaECMultCached', + [pubkeys.ninkiPub, nleaf] + ); + + }, + function errorHandler(err) { + //alert('Error'); + }, + 'ECPlugin', + 'cordovaECMultCached', + [pubkeys.coldPub, nleaf] + ); + + }, + function errorHandler(err) { + //alert('Error'); + }, + 'ECPlugin', + 'cordovaECMultCached', + [pubkeys.hotPub, nleaf] + ); + + + } else { + + + 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 cacallback(err, address); + } else { + return cacallback(err, result); + } + + }); + } + + } else { + + return cacallback(err, leaf); + + } + + //now update the address to the server + + }); + + + } else { + + //something very bad has happened + //attempting to derive an address for a non validated contact + return cacallback(true, "ErrInvalid"); + } + + } else { + + return cacallback(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); + if (m_this.m_network == "testnet") { + x.unshift(196); + } else { + 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, m_this.m_network); + + 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) { + if (!err) { + var friends = JSON.parse(data); + return callback(err, friends); + } else { + return callback(err, data); + } + }); + } + + + 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) { + if (!err) { + var friend = JSON.parse(data); + return callback(err, friend); + } else { + return callback(err, data); + } + }); + + } + + //add error handling here + //very impoirtant for mobile + // + + + this.createFriend = createFriend; + function createFriend(username, uimessage, cbcallback) { + + //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) { + + if (m_this.Device.isiOS()) { + + var nls = node.split("/"); + var tnode = nls[2] * 1; + + + + var hotKey = ''; + var coldKey = ''; + var ninkiKey = ''; + + cordova.exec(function callback(data) { + + hotKey = Bitcoin.HDWallet.fromHex(data).toString(); + + cordova.exec(function callback(data) { + + coldKey = Bitcoin.HDWallet.fromHex(data).toString(); + + cordova.exec(function callback(data) { + + ninkiKey = Bitcoin.HDWallet.fromHex(data).toString(); + + + API.post("/api/1/u/getrsakey", postData, function (err, rsaKey) { + + var publicKeys = openpgp.key.readArmored(rsaKey); + + var pubKey = publicKeys.keys[0]; + + var message = hotKey + coldKey + ninkiKey; + + var encrypted = openpgp.signAndEncryptMessage([pubKey], m_this.m_privKey, message) + + 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) { + + return cbcallback(err, result); + + }); + + }); + + }, + function errorHandler(err) { + alert(err); + }, + 'ECPlugin', + 'cordovaDerMPK', + [m_this.m_walletinfo.ninkiPubKey, tnode] + ); + + }, function errorHandler(err) { + alert(err); + }, + 'ECPlugin', + 'cordovaDerMPK', + [m_this.m_walletinfo.coldPub, tnode] + ); + + }, function errorHandler(err) { + alert(err); + }, + 'ECPlugin', + 'cordovaDerMPK', + [m_this.m_walletinfo.hotPub, tnode] + ); + + + } else { + + + 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(node, bipHot).toString(); + + var coldKey = deriveChild(node, bipCold).toString(); + + var ninkiKey = deriveChild(node, bipNinki).toString(); + + //get the friends public RSA key + var rsaKey = ''; + + API.post("/api/1/u/getrsakey", postData, function (err, rsaKey) { + + var publicKeys = openpgp.key.readArmored(rsaKey); + + var pubKey = publicKeys.keys[0]; + + var message = hotKey + coldKey + ninkiKey; + + var encrypted = openpgp.signAndEncryptMessage([pubKey], m_this.m_privKey, message); + + 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) { + + return cbcallback(err, result); + + }); + + }); + + } + + }); + } + + + this.acceptFriend = acceptFriend; + function acceptFriend(username, callback) { + + + //need to add error handling and messages here + + m_this.acceptFriendRequest(username, function (err, secret) { + + if (err) { + + return callback(err, secret); + + } else { + + m_this.isNetworkExist(username, function (err, result) { + + //if (!err) { + + if (!result) { + + m_this.createFriend(username, '', function (err, result) { + + if (err) { + + return callback(err, result); + } else { + + return callback(err, result); + } + }); + + } else { + + return callback(err, result); + } + //} else { + // return callback(err, result); + //} + + }); + } + + }); + + + } + + + + this.acceptFriendRequest = acceptFriendRequest; + function acceptFriendRequest(username, callback, progress) { + + if (progress) { + progress("Getting request.."); + } + + 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 + + + if (progress) { + progress("Encrypting data..."); + } + + 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) { + + //friends public key + var publicKeys = openpgp.key.readArmored(rsaKey); + + //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 pubKey = publicKeys.keys[0]; + + var msg = openpgp.message.readArmored(message); + var decrypted = openpgp.decryptAndVerifyMessage(m_this.m_privKey, [pubKey], msg); + + var isValid = decrypted.signatures[0].valid; + if (isValid) { + + var keys = sanitizer.sanitize(decrypted.text); + + var key1 = keys.substring(0, 111); + var key2 = keys.substring(111, 222); + var key3 = keys.substring(222, 333); + + + var packet = { + hotPub: key1, + coldPub: key2, + ninkiPub: key3, + rsaKey: rsaKey, + validated: false + }; + + if (progress) { + progress("Signing data..."); + } + + + var encrypted = openpgp.signAndEncryptMessage([m_this.m_pubKey], m_this.m_privKey, JSON.stringify(packet)); + + postData = { + guid: m_this.m_guid, + sharedid: m_this.m_sharedid, + username: username, + packet: encrypted, + validationHash: '' + }; + + API.post("/api/1/u/updatefriend", postData, function (err, result) { + return callback(err, result); + }); + } + + }); + }); + + } + + + 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); + + } + + this.verifyFriendData = verifyFriendData; + function verifyFriendData(username, code, callback, progress) { + + //update packet with status as verified and log + //the verification code + + if (progress) { + progress("Getting packet..."); + } + + var postData = { guid: m_this.m_guid, sharedid: m_this.m_sharedid, username: username }; + API.post("/api/1/u/getfriendpacket", postData, function (err, packet) { + + if (!err) { + + var msg = openpgp.message.readArmored(packet); + var decrypted = openpgp.decryptAndVerifyMessage(m_this.m_privKey, [m_this.m_pubKey], msg); + + var isValid = decrypted.signatures[0].valid; + if (isValid) { + + var payload = JSON.parse(sanitizer.sanitize(decrypted.text)); + + var publicKeysUsed = openpgp.key.readArmored(payload.rsaKey); + var pubKeyUsed = publicKeysUsed.keys[0]; + + if (code == pubKeyUsed.primaryKey.fingerprint) { + + + if (progress) { + progress("Verifying..."); + } + + var reencrypt = { + hotPub: payload.hotPub, + coldPub: payload.coldPub, + ninkiPub: payload.ninkiPub, + rsaKey: payload.rsaKey, + validated: true + }; + + var encryptedPayload = openpgp.signAndEncryptMessage([m_this.m_pubKey], m_this.m_privKey, JSON.stringify(reencrypt)); + + postData = { + guid: m_this.m_guid, + sharedid: m_this.m_sharedid, + username: username, + packet: encryptedPayload, + validationHash: code + }; + + + if (progress) { + progress("Updating..."); + } + + API.post("/api/1/u/updatefriend", postData, function (err, result) { + + return callback(err, result); + + }); + + + } else { + + return callback(err, false); + + } + + } + } else { + + return callback(err, packet); + + } + + + }); + } + + + //status + //0 pending + //1 paid + //2 rejected + //3 notsent + + this.createInvoice = createInvoice; + function createInvoice(username, invoice, callback) { + + var packetForMe = ""; + var packetForThem = ""; + + var jsonInvoice = JSON.stringify(invoice); + + //get the contacts RSA key + + //encrypt the packet for me with my public rsa key and sign with my private key + + 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 publicKeys = openpgp.key.readArmored(rsaKey); + + + var pubKey = publicKeys.keys[0]; + + //here we need to persist the public key used to + //encrypt the data + + //get the RSA private key from the encrypted payload + + //generate a hash from the RSA key and public keys for verification + var message = jsonInvoice; + + var encrypted = openpgp.signAndEncryptMessage([pubKey], m_this.m_privKey, message); + + //encrypt with my public key and sin with my priv key + var packetForMe = openpgp.signAndEncryptMessage([m_this.m_pubKey], m_this.m_privKey, message); + + + 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); + + }); + + }); + + } + + this.UnpackInvoiceByMe = UnpackInvoiceByMe; + function UnpackInvoiceByMe(invoice, username, callback) { + + + //here decrypt the invoice with my private key + + var msg = openpgp.message.readArmored(invoice.Packet); + var decrypted = openpgp.decryptAndVerifyMessage(m_this.m_privKey, [m_this.m_pubKey], msg); + + var unpackedInvoice = JSON.parse(sanitizer.sanitize(decrypted.text)); + + callback(false, unpackedInvoice); + + + } + + this.UnpackInvoiceForMe = UnpackInvoiceForMe; + function UnpackInvoiceForMe(invoice, username, invtype, callback) { + + //here decrypt the invoice with my private key + + 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 + + //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 + + if (invtype == 'forme') { + var publicKeys = openpgp.key.readArmored(rsaKey); + pubKey = publicKeys.keys[0]; + } else { + pubKey = m_this.m_pubKey; + } + + var msg = openpgp.message.readArmored(invoice.Packet); + var decrypted = openpgp.decryptAndVerifyMessage(m_this.m_privKey, [pubKey], msg); + + var isValid = decrypted.signatures[0].valid; + if (isValid) { + + var json = JSON.parse(sanitizer.sanitize(decrypted.text)); + + //remove any xss data + callback(false, json); + + } else { + + callback(false, "error"); + + } + + }); + + + } + + + //security + this.SaveTwoFactor = SaveTwoFactor; + function SaveTwoFactor(twoFactorCode, verifyToken, callback) { + + var postData = { + guid: m_this.m_guid, + sharedid: m_this.m_sharedid, + twoFactorCode: twoFactorCode, + verifyToken: verifyToken + }; + + API.post("/api/1/u/updatetwofactor", postData, function (err, result) { + + if (!err) { + + API.registerToken(result); + m_this.m_APIToken = result; + + callback(false, result); + + } else { + + callback(true, result); + + } + }); + + } + + + this.SetupTwoFactor = SetupTwoFactor; + function SetupTwoFactor(twoFactorCode, callback) { + + var postData = { + guid: m_this.m_guid, + sharedid: m_this.m_sharedid, + twoFactorCode: twoFactorCode, + verifyToken: '' + }; + + API.post("/api/1/u/updatetwofactor", postData, function (err, result) { + + if (!err) { + + API.registerToken(result); + + } + + callback(err, result); + + }); + + } + + + this.ResetTwoFactor = ResetTwoFactor; + function ResetTwoFactor(fguid, fusername, fpwd, callback) { + + //stretch password + + //download the recovery packet + //decrypt + //return shared secret + //no feedback apart from, please check your email + fpwd = pbkdf2(fpwd, fguid); + + API.post("/api/1/getrecoverypacket", { + guid: fguid, + username: fusername + }, function (err, response) { + + if (err) { + + callback(err, response); + + } else { + + //decrypt packet + + 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) { + + callback(err, response); + + }); + } catch (error) { + + callback(true, "error"); + + } + + } + + fpwd = ''; + + }); + + } + + + this.ChangePassword = ChangePassword; + function ChangePassword(twoFactorCode, oldpassword, newpassword, callback, statusUpdate) { + + + API.getWalletFromServer(m_this.m_guid, m_this.m_secret, twoFactorCode, false, function (err, wallet) { + + if (!err) { + + //check password strength + + //stretch old password + //verify that it matches the current one + + getHotHash("", function (err, hothash) { + + + statusUpdate('Securing password...', '20%'); + + + setTimeout(function () { + + //if password reset do not pbkdf the password + + oldpassword = pbkdf2(oldpassword, m_this.m_oguid); + + //get the two packets + + statusUpdate('Getting packets...', '40%'); + + //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; + } + + if (decryptedWithOld) { + + + statusUpdate('Securing new password...', '80%'); + + API.getUserPacket(m_this.m_guid, m_this.m_sharedid, function (err, encpacket) { + + var decryptedUsrWithOld = true; + var rsaKeyPair = ''; + try { + rsaKeyPair = decrypt(encpacket.Payload, m_this.m_password, encpacket.IV); + } catch (err) { + decryptedUsrWithOld = false; + } + + + if (decryptedUsrWithOld) { + + //get the verification packet + + + API.post("/api/1/getrecoverypacket", { + guid: m_this.m_guid + }, function (err, response) { + + + if (!err) { + + //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; + } + + + if (decryptedVerWithOld) { + + setTimeout(function () { + + + newpassword = pbkdf2(newpassword, m_this.m_oguid); + + + statusUpdate('Encrypting account packet...', '80%'); + + + 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); + + if (decryptedPayload.hckey) { + newpasspacket = encryptNp(newpassword, decryptedPayload.hckey); + } + + newAIV = newpayload.iv.toString(); + newUIV = newusrpayload.iv.toString(); + newRIV = newveripacket.iv.toString(); + + if (decryptedPayload.hckey) { + newPIV = newpasspacket.iv.toString(); + } + + } catch (err) { + newpayloadsuccess = false; + } + + if (newpayloadsuccess) { + + //$("#chngpwdprogbar").width('90%'); + //$("#chngpwdprogmess").text('Encrypting user 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); + + if (decryptedPayload.hckey) { + testpasspacket = decryptNp(newpasspacket.toString(), decryptedPayload.hckey, newPIV) + } + + } catch (err) { + testsuccess = false; + } + + if (testsuccess) { + + //save to the server + + statusUpdate('Saving...', '95%'); + + //TO DO: + //add in the re-encryption of the verification + //packet + + + //if reset password then provide signed message and call reset function + + + //need to add two factor here + //so 1. add two factor + //2. add way to save only the main packet + + 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 { + + if (dataStr == "ok") { + + + + m_this.Device.getStorageItem("tfso" + m_this.m_guid, function (tfso) { + + if (tfso != "") { + var enc = JSON.parse(tfso); + var token = decryptNp(enc.ct, m_this.m_password, enc.iv); + tfso = encryptNp(token, newpassword); + + var ctok = {}; + ctok.ct = tfso.toString(); + ctok.iv = tfso.iv.toString(); + m_this.Device.setStorageItem("tfso" + m_this.m_guid, JSON.stringify(ctok)); + + } + + m_this.m_password = newpassword; + + //if something goes wrong here + //the worst case scenario is the + //user has to reenter their hot key + + saveHotHash(hothash, function (err, result) { + + callback(false, ''); + + }); + + }); + + + + + + + + + + + } else { + + callback(true, "Error: Password not changed"); + } + + } + }); + + + } else { + + callback(true, "Error: Password not changed"); + + } + } else { + + callback(true, "Error: Password not changed"); + + } + }, 500); + + } else { + + callback(true, "Error: Password not changed"); + } + + } else { + + callback(true, "Error: Password not changed"); + + } + + + }); + + } + + }); + + + } else { + + } + + + }, 500); + + }); + + } else { + + callback(true, wallet); + + } + }); + + } + + + //////////////////////////////////////////////////////////////////////////////////////////// + + + this.ResetPassword = ResetPassword; + function ResetPassword(guid, twofactor, resetKey, newpassword, progbar, progmess, hckey, message1, message2, callback) { + + 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)); + } + + guid = Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(bytes)).toString(); + + + API.post("/api/1/getrecoverypacket", { + guid: guid + }, function (err, response) { + + if (err) { + + callback(err, response); + + } else { + + //decrypt packet + + var jpacket = JSON.parse(response); + + if (!jpacket.Beta1) { + + var secret = decryptNp(jpacket.packet, resetKey, jpacket.IV); + + API.getWalletFromServer(guid, secret, twofactor, false, function (err, wallet) { + + if (!err) { + + try { + var walletInformation = decrypt(wallet.Payload, resetKey, wallet.IV); + } catch (err) { + return callback(true, "Incorrect password"); + } + + sharedid = walletInformation.userToken; + + + //check password strength + + //stretch old password + //verify that it matches the current one + + $(progbar).width('40%'); + $(progmess).text('Securing password...'); + setTimeout(function () { + + //if password reset do not pbkdf the password + + + oldpassword = resetKey; + + //if (oldpassword == m_this.m_password) { + + + //get the two packets + $(progbar).width('40%'); + $(progmess).text('Getting packets...'); + + + // $("#chngpwdprogbar").width('50%'); + //$("#chngpwdprogmess").text('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; + } + + if (decryptedWithOld) { + + $(progbar).width('80%'); + $(progmess).text('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; + } + + + if (decryptedUsrWithOld) { + + //get the verification packet + + + API.post("/api/1/getrecoverypacket", { + guid: guid + }, function (err, response) { + + + if (!err) { + + //decrypt packet + var decryptedVerWithOld = true; + var jpacket = JSON.parse(response); + var veripacket = ''; + try { + veripacket = decryptNp(jpacket.packet, resetKey, jpacket.IV); + } + catch (verror) { + decryptedVerWithOld = false; + } + + + if (decryptedVerWithOld) { + + setTimeout(function () { + + + newpassword = pbkdf2(newpassword, oguid); + + + $(progbar).width('80%'); + $(progmess).text('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); + newpasspacket = encryptNp(newpassword, decryptedPayload.hckey); + + //and encrypt the new password with the hotkey seed + + newAIV = newpayload.iv.toString(); + newUIV = newusrpayload.iv.toString(); + newRIV = newveripacket.iv.toString(); + newPIV = newpasspacket.iv.toString(); + + } catch (err) { + newpayloadsuccess = false; + } + + if (newpayloadsuccess) { + + //$("#chngpwdprogbar").width('90%'); + //$("#chngpwdprogmess").text('Encrypting user 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); + + } catch (err) { + testsuccess = false; + } + + if (testsuccess) { + + //save to the server + $(progbar).width('95%'); + $(progmess).text('Saving...'); + + + //TO DO: + //add in the re-encryption of the verification + //packet + + + //if reset password then provide signed message and call reset function + + 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 { + + if (dataStr == "ok") { + + + callback(false, newpassword); + + + } else { + + callback(true, "Error: Password not changed"); + } + + } + }); + + + } else { + + callback(true, "Error: Password not changed"); + + } + } else { + + callback(true, "Error: Password not changed"); + + } + }, 500); + + } else { + + callback(true, "Error: Password not changed"); + } + + } else { + + callback(true, "Error: Password not changed"); + + } + + + }); + + } + + }); + + + } else { + + } + + //} else { + // callback(true, "You entered your current password incorrectly"); + //} + + }, 500); + + } else { + callback(true, wallet); + } + }); + + } + + } + + }); + + } + + + //////////////////////////////////////////////////////////////////////////////////////////// + + this.EmailValidationForTwoFactor = EmailValidationForTwoFactor; + function EmailValidationForTwoFactor(vtoken, status, callback) { + + API.post("/api/1/getemailvalidationtwofactor", { + guid: m_this.m_guid, + token: vtoken + }, function (err, response) { + + if (!err) { + m_this.m_twoFactorOnLogin = true; + } + + callback(err, response); + }); + + } + + this.getAccountSettings = getAccountSettings; + function getAccountSettings(callback) { + API.post("/api/1/u/getaccountsettings", { + guid: m_this.m_guid, + sharedid: m_this.m_sharedid + }, function (err, response) { + callback(err, response); + }); + } + + this.updateAccountSettings = updateAccountSettings; + function updateAccountSettings(jsonPacket, twoFactorCode, twoFactorSend, callback) { + + + + //only request a new token + + + var postdata = { + guid: m_this.m_guid, + sharedid: m_this.m_sharedid, + jsonPacket: JSON.stringify(jsonPacket), + twoFactorCode: twoFactorCode, + twoFactorSend: twoFactorSend + }; + + API.post("/api/2/u/updateaccountsettings", postdata + , function (err, response) { + + if (!err) { + + if (response != "ok") { + + var ret = JSON.parse(response); + + if (ret.Token) { + + var enc = encryptNp(ret.Token, m_this.m_password); + var ctok = {}; + ctok.ct = enc.toString(); + ctok.iv = enc.iv.toString(); + m_this.Device.setStorageItem("tfso" + m_this.m_guid, JSON.stringify(ctok)); + + } + } + + getAccountSettings(function (err, res) { + + if (!err) { + + var settingsObject = JSON.parse(res); + m_this.m_settings = settingsObject; + callback(err, response); + } else { + + callback(err, res); + } + + }); + + } else { + callback(err, response); + } + + + }); + + } + + this.getTwoFactorImg = getTwoFactorImg; + function getTwoFactorImg(callback) { + + var postData = { + guid: m_this.m_guid, + sharedid: m_this.m_sharedid + }; + + API.post("/api/1/gettwofactorimg", postData, function (err, twoFactorQrImgUrl) { + + if (!err) { + callback(false, twoFactorQrImgUrl) + } + }); + + } + + this.getNewTwoFactorImg = getNewTwoFactorImg; + function getNewTwoFactorImg(twoFactorCode, callback) { + + var postData = { + guid: m_this.m_guid, + sharedid: m_this.m_sharedid, + twoFactorCode: twoFactorCode + }; + + API.post("/api/1/getnewtwofactorimg", postData, function (err, twoFactorQrImgUrl) { + + callback(err, twoFactorQrImgUrl) + + }); + + } + + + 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) { + + if (!err) { + + result.hotHash = hotHash; + } + else { + + result.hotHash = "Unavailable"; + } + + walletInformation = {}; + + callback(false, 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); + } + + this.getMasterPublicKeyFromUpstreamServer = getMasterPublicKeyFromUpstreamServer; + function getMasterPublicKeyFromUpstreamServer(guid, callback) { + API.getMasterPublicKeyFromUpstreamServer(guid, callback); + } + + this.doesUsernameExist = doesUsernameExist; + function doesUsernameExist(username, callback) { + API.doesAccountExist(username, '', function (err, accExists) { + + if (err) { + callback(err, accExists); + } else { + callback(err, accExists.UserExists); + } + + }); + } + + this.doesEmailExist = doesEmailExist; + function doesEmailExist(email, callback) { + API.doesAccountExist('', email, function (err, accExists) { + + if (err) { + callback(err, accExists); + } else { + callback(err, accExists.EmailExists); + } + + }); + } + + this.sendWelcomeDetails = sendWelcomeDetails; + function sendWelcomeDetails(callback) { + API.sendWelcomeDetails(m_this.m_guid, m_this.m_sharedid, callback); + } + + this.getEmailValidation = getEmailValidation; + function getEmailValidation(token, callback) { + API.getEmailValidation(m_this.m_guid, m_this.m_sharedid, token, callback); + } + + this.getWalletFromServer = getWalletFromServer; + 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; + function getBalance(callback) { + API.getBalance(m_this.m_guid, m_this.m_sharedid, callback); + } + + this.getusernetworkcategory = getusernetworkcategory; + function getusernetworkcategory(callback) { + API.getusernetworkcategory(callback); + } + + this.updateusernetworkcategory = updateusernetworkcategory; + function updateusernetworkcategory(username, category, callback) { + API.updateusernetworkcategory(m_this.m_guid, m_this.m_sharedid, username, category, callback); + } + + this.getUnconfirmedBalance = getUnconfirmedBalance; + function getUnconfirmedBalance(callback) { + 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.getAccountData = getAccountData; + function getAccountData(callback) { + API.getAccountData(m_this.m_guid, m_this.m_sharedid, callback); + } + + this.getUserData = getUserData; + function getUserData(callback) { + API.getUserData(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); + } + + this.getPendingUserRequests = getPendingUserRequests; + function getPendingUserRequests(callback) { + API.getPendingUserRequests(m_this.m_guid, m_this.m_sharedid, callback); + } + + this.getFriendRequests = getFriendRequests; + function getFriendRequests(callback) { + API.getFriendRequests(m_this.m_guid, m_this.m_sharedid, callback); + } + + this.getUserPacket = getUserPacket; + function getUserPacket(callback) { + API.getUserPacket(m_this.m_guid, m_this.m_sharedid, callback); + } + + this.isNetworkExist = isNetworkExist; + function isNetworkExist(username, callback) { + API.isNetworkExist(m_this.m_guid, m_this.m_sharedid, username, callback); + } + + this.rejectFriendRequest = rejectFriendRequest; + function rejectFriendRequest(username, callback) { + API.rejectFriendRequest(m_this.m_guid, m_this.m_sharedid, username, callback); + } + + this.getTransactionRecords = getTransactionRecords; + function getTransactionRecords(callback) { + API.getTransactionRecords(m_this.m_guid, m_this.m_sharedid, callback); + } + + this.getTransactionFeed = getTransactionFeed; + function getTransactionFeed(callback) { + API.getTransactionFeed(m_this.m_guid, m_this.m_sharedid, callback); + } + + this.getTransactionsForNetwork = getTransactionsForNetwork; + function getTransactionsForNetwork(username, callback) { + API.getTransactionsForNetwork(m_this.m_guid, m_this.m_sharedid, username, callback); + } + + + + this.getInvoiceList = getInvoiceList; + function getInvoiceList(callback) { + API.getInvoiceList(m_this.m_guid, m_this.m_sharedid, callback); + } + + this.getInvoiceByUserList = getInvoiceByUserList; + function getInvoiceByUserList(callback) { + API.getInvoiceByUserList(m_this.m_guid, m_this.m_sharedid, callback); + } + + + this.getInvoicesToPayNetwork = getInvoicesToPayNetwork; + function getInvoicesToPayNetwork(username, callback) { + API.getInvoicesToPayNetwork(m_this.m_guid, m_this.m_sharedid, username, callback); + } + + this.getInvoicesByUserNetwork = getInvoicesByUserNetwork; + function getInvoicesByUserNetwork(username, callback) { + API.getInvoicesByUserNetwork(m_this.m_guid, m_this.m_sharedid, username, callback); + } + + + this.updateInvoice = updateInvoice; + function updateInvoice(username, invoiceId, transactionId, status, callback) { + 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 (m_this.Device.isCordova()) { + 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(); + + m_this.Device.getStorageItem("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) { + + + if (jekey.SessionToken) { + API.registerToken(jekey.SessionToken); + m_this.m_APIToken = jekey.SessionToken; + callback(err, jekey); + } + + + + } else { + + callback(err, jekey); + } + } else { + callback(true, ekey); + } + + }); + + }); + + } + + + this.destroyDevice = destroyDevice; + function destroyDevice(callback) { + + m_this.Device.getStorageItem("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); + } + + this.getDeviceTokenForApp = getDeviceTokenForApp; + function getDeviceTokenForApp(deviceName, callback) { + API.getDeviceTokenForApp(m_this.m_guid, m_this.m_sharedid, deviceName, callback); + } + + this.getLimitStatus = getLimitStatus; + function getLimitStatus(callback) { + API.getLimitStatus(m_this.m_guid, m_this.m_sharedid, function (err, res) { + + if (!err) { + var jlimits = JSON.parse(res); + return callback(err, jlimits); + + } else { + return callback(err, res); + } + + }); + } + + + this.createBackupCodes = createBackupCodes; + function createBackupCodes(twoFactorCode, callback) { + API.createBackupCodes(m_this.m_guid, m_this.m_sharedid, twoFactorCode, function (err, res) { + + if (!err) { + var jcodes = JSON.parse(res); + return callback(err, jcodes); + + } else { + return callback(err, res); + } + + }); + } + + + + + this.get2faOverride = get2faOverride; + function get2faOverride(amount, callback) { + + m_this.Device.getStorageItem("tfso" + m_this.m_guid, function (res) { + + if (res == "") { + + return callback(false, ""); + + } + + getLimitStatus(function (err, limits) { + + if (!err) { + + var twofareq = false; + if ((limits.No24hr + 1) > limits.NoOfTransactionsPerDay) { + twofareq = true; + } + if ((limits.No1hr + 1) > limits.NoOfTransactionsPerHour) { + twofareq = true; + } + + if ((amount) > limits.SingleTransactionLimit) { + twofareq = true; + } + if ((limits.TotalAmount24hr + amount) > limits.DailyTransactionLimit) { + twofareq = true; + } + + if (twofareq) { + + callback(err, ""); + + } else { + + if (res.length > 0) { + var enc = JSON.parse(res); + twoFactorCode = decryptNp(enc.ct, m_this.m_password, enc.iv); + callback(err, twoFactorCode); + + } else { + + callback(err, ""); + } + + } + } else { + + callback(err, limits); + + } + + + }); + + }); + + + } + +} + +module.exports = Engine; diff --git a/src/ninki-engine.js b/src/ninki-engine.js index a3e8efd..d37b857 100644 --- a/src/ninki-engine.js +++ b/src/ninki-engine.js @@ -29,15 +29,22 @@ function Engine() { this.m_secret = ''; this.m_migrateBeta12fa = false; this.m_invoiceTax = 0.1; + this.m_offlineKeyBackup = false; + //keys for PGP this.m_privKey = ''; this.m_pubKey = ''; this.m_privKeyRaw = ''; this.m_pubKeyRaw = ''; + //online EC key + this.m_onlineKey = []; + this.m_deviceKey = []; + this.m_deviceSecKey = []; + this.m_regToken = ''; + this.m_deviceToken = []; this.m_APIToken = ''; this.m_appInitialised = false; this.m_watchOnly = false; - m_this = this; this.Device = new device(); @@ -60,7 +67,7 @@ function Engine() { var serTarget = {}; serTarget.m_walletinfo = m_this.m_walletinfo; serTarget.m_sharedid = m_this.m_sharedid; - serTarget.m_twoFactorOnLogin = m_this.m_twoFactorOnLogin; + //serTarget.m_twoFactorOnLogin = m_this.m_twoFactorOnLogin; serTarget.m_nickname = m_this.m_nickname; //serTarget.m_profileImage = m_this.m_profileImage; //serTarget.m_statusText = m_this.m_statusText; @@ -82,7 +89,7 @@ function Engine() { m_this.m_walletinfo = cache.m_walletinfo; m_this.m_sharedid = cache.m_sharedid; - m_this.m_twoFactorOnLogin = cache.m_twoFactorOnLogin; + //m_this.m_twoFactorOnLogin = cache.m_twoFactorOnLogin; m_this.m_nickname = cache.m_nickname; //m_this.m_profileImage = cache.m_profileImage; //m_this.m_statusText = cache.m_statusText; @@ -101,9 +108,18 @@ function Engine() { m_this.m_pubKey = publicKeys.keys[0]; + cache = {}; } + + this.setSecDeviceKey = setSecDeviceKey; + function setSecDeviceKey() { + //set a secondary encryption key that does not + //require the PIN each time to retreive + m_this.m_deviceSecKey = SHA256(m_this.m_deviceKey); + } + this.appHasLoaded = appHasLoaded; function appHasLoaded() { @@ -140,40 +156,112 @@ function Engine() { } - function getRandomValues(b) { + //use this in production + + function getRandomValues(b) { var rng = new Uint8Array(b); - if (typeof window === 'undefined') { + window.crypto.getRandomValues(rng); + return rng; + } - //this is to support the test scripts - //which are executed using node.js - try { - var buf = crypto.randomBytes(256); + this.generateToken = generateToken; + function generateToken() { + + var rng = getRandomValues(32); + + var bytes = []; + for (var i = 0; i < rng.length; ++i) { + bytes[i] = rng[i]; + } + + return Bitcoin.convert.bytesToHex(bytes); + + } + + + //only use for testing + + // function getRandomValues(b) { - rng = new Uint8Array(buf); + // var rng = new Uint8Array(b); + // if (typeof window === 'undefined') { + // //this is to support the test scripts + // //which are executed using node.js + // var buf = crypto.randomBytes(256); + // rng = new Uint8Array(buf); - } catch (ex) { - // handle error - // most likely, entropy sources are drained + // } else { - console.log(ex); + // window.crypto.getRandomValues(rng); + // } + + // return rng; + // } + + sjcl.codec.bytes = { + /** Convert from a bitArray to an array of bytes. */ + fromBits: function (arr) { + var out = [], bl = sjcl.bitArray.bitLength(arr), i, tmp; + for (i = 0; i < bl / 8; i++) { + if ((i & 3) === 0) { + tmp = arr[i / 4]; + } + out.push(tmp >>> 24); + tmp <<= 8; } + return out; + }, + /** Convert from an array of bytes to a bitArray. */ + toBits: function (bytes) { + var out = [], i, tmp = 0; + for (i = 0; i < bytes.length; i++) { + tmp = tmp << 8 | bytes[i]; + if ((i & 3) === 3) { + out.push(tmp); + tmp = 0; + } + } + if (i & 3) { + out.push(sjcl.bitArray.partial(8 * (i & 3), tmp)); + } + return out; + } + }; - } else { + this.zeroOnlineKey = zeroOnlineKey; + function zeroOnlineKey() { + for (var i = 0; i < this.m_onlineKey.length; ++i) { + this.m_onlineKey[i] = 0; + } + } - window.crypto.getRandomValues(rng); + this.zeroDeviceKey = zeroDeviceKey; + function zeroDeviceKey() { + for (var i = 0; i < this.m_deviceKey.length; ++i) { + this.m_deviceKey[i] = 0; } + } - return rng; + function zeroWordArray(obj) { + for (var i = 0; i < obj.words.length; ++i) { + obj.words[i] = 0; + } } + this.zeroByteArray = zeroByteArray; + function zeroByteArray(obj) { + for (var i = 0; i < obj.length; ++i) { + obj[i] = 0; + } + } this.encrypt = encrypt; function encrypt(valueToEncrypt, passphrase) { valueToEncrypt = JSON.stringify(valueToEncrypt); - var key = CryptoJS.enc.Hex.parse(passphrase); + var key = Bitcoin.convert.bytesToWordArray(passphrase); var iv = getRandomValues(32); @@ -186,6 +274,8 @@ function Engine() { var encrypted = CryptoJS.AES.encrypt(valueToEncrypt, key, { iv: ivwords }); + zeroWordArray(key); + return encrypted; }; @@ -193,13 +283,12 @@ function Engine() { this.encryptNp = encryptNp; function encryptNp(valueToEncrypt, passphrase) { - valueToEncrypt = CryptoJS.enc.Hex.parse(valueToEncrypt); + valueToEncrypt = Bitcoin.convert.bytesToWordArray(valueToEncrypt); - var key = CryptoJS.enc.Hex.parse(passphrase); + var key = Bitcoin.convert.bytesToWordArray(passphrase); var iv = getRandomValues(32); - var ivbytes = []; for (var i = 0; i < iv.length; ++i) { ivbytes[i] = iv[i]; @@ -209,34 +298,68 @@ function Engine() { var encrypted = CryptoJS.AES.encrypt(valueToEncrypt, key, { iv: ivwords, padding: CryptoJS.pad.NoPadding }); + zeroWordArray(key); + return encrypted; }; this.decrypt = decrypt; function decrypt(encryptedObj, passphrase, iv) { - var key = CryptoJS.enc.Hex.parse(passphrase); + //word arrays + var key = Bitcoin.convert.bytesToWordArray(passphrase); var iv = CryptoJS.enc.Hex.parse(iv); var decryptedObject = CryptoJS.AES.decrypt(encryptedObj, key, { iv: iv }); + zeroWordArray(key); + var decryptutf = decryptedObject.toString(CryptoJS.enc.Utf8); + + zeroWordArray(decryptedObject); + var decryptjson = JSON.parse(decryptutf); return decryptjson; }; + this.decryptNp = decryptNp; - function decryptNp(encryptedObj, passphrase, iv) { + function decryptNp(encryptedObj, passphrase, iv, asbytes) { - var key = CryptoJS.enc.Hex.parse(passphrase); + //word arrays + var key = Bitcoin.convert.bytesToWordArray(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; + zeroWordArray(key); + + var ret = null; + if (asbytes) { + ret = Bitcoin.convert.wordArrayToBytes(decryptedObject); + } else { + ret = decryptedObject.toString(CryptoJS.enc.Hex); + } + + zeroWordArray(decryptedObject); + + return ret; + }; + function SHA256(bytes) { + + var wa = Bitcoin.convert.bytesToWordArray(bytes); + var wa2 = Bitcoin.Crypto.SHA256(wa); + var ret = Bitcoin.convert.wordArrayToBytes(wa2); + + zeroWordArray(wa); + zeroWordArray(wa2); + + return ret; + + } + var hmac = function (key) { var hasher = new sjcl.misc.hmac(key, sjcl.hash.sha1); this.encrypt = function () { @@ -246,15 +369,20 @@ function Engine() { this.pbkdf2 = pbkdf2; 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; + + var bkey = sjcl.codec.bytes.fromBits(derivedKey); + + return bkey; } this.setPass = setPass; function setPass(pass, salt) { m_this.m_password = pbkdf2(pass, salt); + m_this.m_deviceSecKey = m_this.m_password; + } this.setStretchPass = setStretchPass; @@ -293,7 +421,6 @@ function Engine() { }); - } @@ -315,7 +442,8 @@ function Engine() { if (validseed) { - saveHotHash(hothash, function (err, result) { + + saveHotHash(Bitcoin.convert.hexToBytes(hothash), function (err, result) { if (!err) { return callback(false, hothash); @@ -335,13 +463,28 @@ function Engine() { } + this.getTwoFactorToken = getTwoFactorToken; + function getTwoFactorToken(key, callback) { + + m_this.Device.getStorageItem("ninki_rem", function (tft) { + + var jtft = JSON.parse(tft); + + var fatoken = decryptNp(jtft.ct, key, jtft.iv); + + return callback(false, fatoken); + + }); + + } + this.getHotHash = getHotHash; function getHotHash(key, callback) { //to do: validate key against stored public key //needs to be done incase user changed their password on a different machine - if (m_this.Device.isChromeApp() || m_this.Device.isBrowser() || m_this.Device.isNode()) { + if (m_this.Device.isChromeApp() || m_this.Device.isNode() || m_this.Device.isBrowser()) { m_this.Device.getStorageItem("hk" + m_this.m_guid, function (result) { @@ -355,7 +498,8 @@ function Engine() { var iserror = false; try { - hothash = decryptNp(hk, m_this.m_password, hkiv); + //get hothash as bytes + m_this.m_onlineKey = decryptNp(hk, m_this.m_password, hkiv, true); } catch (error) { iserror = true; } @@ -365,7 +509,7 @@ function Engine() { //validate against loaded hot public key var validseed = true; try { - var bipHot = Bitcoin.HDWallet.fromSeedHex(hothash, m_this.m_network); + var bipHot = new Bitcoin.HDWallet(m_this.m_onlineKey, m_this.m_network); if (m_this.m_walletinfo.hotPub != bipHot.toString()) { validseed = false; } @@ -376,7 +520,30 @@ function Engine() { if (validseed) { - return callback(false, hothash); + if (m_this.m_deviceKey.length > 0) { + //incase we are simulating mobile devices on chrome + m_this.Device.getStorageItem("ninki_rem", function (tft) { + + if (tft != "") { + + var jtft = JSON.parse(tft); + + var fatoken = decryptNp(jtft.ct, m_this.m_deviceKey, jtft.iv); + + return callback(false, "", fatoken); + } else { + + return callback(false, "", ""); + + } + + }); + } else { + + return callback(false, "", ""); + + } + } else { @@ -409,7 +576,8 @@ function Engine() { try { var enc = JSON.parse(hk); - hothash = decryptNp(enc.ct, key, enc.iv); + //get hothash as bytes + m_this.m_onlineKey = decryptNp(enc.ct, m_this.m_deviceKey, enc.iv, true); } catch (error) { iserror = true; @@ -419,7 +587,7 @@ function Engine() { //validate against loaded hot public key var validseed = true; try { - var bipHot = Bitcoin.HDWallet.fromSeedHex(hothash, m_this.m_network); + var bipHot = new Bitcoin.HDWallet(m_this.m_onlineKey, m_this.m_network); if (m_this.m_walletinfo.hotPub != bipHot.toString()) { validseed = false; } @@ -437,12 +605,12 @@ function Engine() { if (tft != "") { var jtft = JSON.parse(tft); - var fatoken = decryptNp(jtft.ct, key, jtft.iv); + var fatoken = decryptNp(jtft.ct, m_this.m_deviceKey, jtft.iv); - return callback(false, hothash, fatoken); + return callback(false, "", fatoken); } else { - return callback(false, hothash, ""); + return callback(false, "", ""); } @@ -478,7 +646,7 @@ function Engine() { //before we encrypt validate the hash matches the logged in public key var validseed = true; try { - var bipHot = Bitcoin.HDWallet.fromSeedHex(hotHash, m_this.m_network); + var bipHot = new Bitcoin.HDWallet(hotHash, m_this.m_network); if (m_this.m_walletinfo.hotPub != bipHot.toString()) { validseed = false; } @@ -490,7 +658,7 @@ function Engine() { var encHotHash = encryptNp(hotHash, m_this.m_password); - if (m_this.Device.isChromeApp() || m_this.Device.isBrowser() || m_this.Device.isNode()) { + if (m_this.Device.isChromeApp() || m_this.Device.isNode() || m_this.Device.isBrowser()) { m_this.Device.setStorageItem('hk' + m_this.m_guid, encHotHash.toString()); m_this.Device.setStorageItem('hkiv' + m_this.m_guid, encHotHash.iv.toString()); @@ -518,18 +686,6 @@ function Engine() { } - function validateHotKey(callback) { - - //load the hotkey - //if not there return error - - //validate the hotkey - //if not there return error - - - } - - //create wallet //create a new wallet and save to the server this.createWallet = createWallet; @@ -572,7 +728,7 @@ function Engine() { password = ''; //create a new wallet - makeNewWallet(username, emailAddress, function (err, walletInformation, userToken) { + makeNewWallet(username, emailAddress, getKeys, function (err, walletInformation, userToken) { if (err) { @@ -629,10 +785,82 @@ function Engine() { } + + //create wallet + //create a new wallet and save to the server + this.createWalletApp = createWalletApp; + function createWalletApp(guid, username, callback, progress) { + + 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(); + + + //create a new wallet + + makeNewWallet(username, '', getKeysForMobile, function (err, walletInformation, userToken) { + + if (err) { + + return callback(true, "ErrCreateAccount"); + + } else { + + m_this.m_sharedid = userToken; + + //wallet creation is successful + //set variables + //add the usertoken to the wallet + walletInformation.wallet.googleAuthSecret = ""; + walletInformation.wallet.sharedid = userToken; + + // + var recpacket = encryptNp(m_this.m_password, m_this.m_walletinfo.hckey); + + walletInformation.wallet.recPacket = recpacket.toString(); + walletInformation.wallet.recPacketIV = recpacket.iv.toString(); + + //save the wallet to the server + progress('saving data...'); + + setTimeout(function () { + + API.post("/api/1/u/createaccount2", walletInformation.wallet, function (err, response) { + + if (err) { + + return callback(err, "ErrSavePacket"); + + } else { + + //set the session + API.registerToken(response); + m_this.m_APIToken = response; + + //pass back the wallet and info to the calling function + return callback(false, walletInformation); + } + }); + + }, 50); + + } + + }, progress); + + } + + + //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, progress) { + function makeNewWallet(nickname, email, getkeys, callback, progress) { //TODO add some more param checking @@ -646,7 +874,7 @@ function Engine() { if (err) { return callback(err, "ErrCreateAccount"); } else { - makeNewWalletPacket(nickname, email, ninkiPubKey, userToken, secret, function (err, walletInformation) { + makeNewWalletPacket(nickname, email, ninkiPubKey, userToken, secret, getkeys, function (err, walletInformation) { if (err) { return callback(err, walletInformation); } else { @@ -659,173 +887,395 @@ function Engine() { } - function makeNewWalletPacket(nickname, emailAddress, ninkiPubKey, userToken, secret, callback, progress) { + function getKeys(dummy, callback) { + var rnghot = getRandomValues(16); - progress('getting entropy...'); + var hotKeyBytes = []; + for (var i = 0; i < rnghot.length; ++i) { + hotKeyBytes[i] = rnghot[i]; + } - setTimeout(function () { + var rngcold = getRandomValues(16); + + var coldKeyBytes = []; + for (var i = 0; i < rngcold.length; ++i) { + coldKeyBytes[i] = rngcold[i]; + } - //what to do if running in node - // crypto provider module - var rngcold = getRandomValues(16); + callback(false, coldKeyBytes, hotKeyBytes); - var coldKeyBytes = []; - for (var i = 0; i < rngcold.length; ++i) { - coldKeyBytes[i] = rngcold[i]; - } + } + + + // + //method to derive hot and cold keys + //with the ability to deterministically + //recover the cold key using: + + //hotkey + //secret public key + //random data on device + + //this means that the user does not have to write down the cold key immediately + + //an attacker needs: + //access to the device + //access to hot key + //access to Ninki server + //access to Ninki decryption keys + + //one time derivation secured by: + //PIN number + //access to device + + function getKeysForMobile(userToken, callback) { + + //get 128 bits of entropy for the hot key + var rnghot = getRandomValues(16); + + var hotKeyBytes = []; + for (var i = 0; i < rnghot.length; ++i) { + hotKeyBytes[i] = rnghot[i]; + } + + zeroByteArray(rnghot); + + //get 128 bits of entropy for the secret key + //used to derive the cold key + var secretEntropy = getRandomValues(16); + + var secretEntropyBytes = []; + for (var i = 0; i < secretEntropy.length; ++i) { + secretEntropyBytes[i] = secretEntropy[i]; + } + + zeroByteArray(secretEntropy); + + var deviceEntropy = getRandomValues(16); + //get 128 bits of entropy for the device key + var deviceEntropyBytes = []; + for (var i = 0; i < 16; ++i) { + deviceEntropyBytes[i] = deviceEntropy[i]; + } + + zeroByteArray(deviceEntropy); - //get some random data for the hot key - var rnghot = getRandomValues(16); + //get ECKey for secretEntropy + var secretKey = new Bitcoin.ECKey(secretEntropyBytes, false); - var hotKeyBytes = []; - for (var i = 0; i < rnghot.length; ++i) { - hotKeyBytes[i] = rnghot[i]; + //derive the public EC key + var secretPubKey = secretKey.getPub(); + + secretPubKey.priv = 0; + + zeroByteArray(secretEntropyBytes); + + //verify the public key has been stored on the server + //before continuing + + API.createAccountSecPub(m_this.m_oguid, userToken, secretPubKey.toString(), function (err, result) { + + if (!err) { + + //m_this.m_oguid + + //get the device key + var deviceKey = new Bitcoin.ECKey(deviceEntropyBytes, false); + + //EC multiply to get the cold key entropy + var coldKey = secretPubKey.multiply(deviceKey); + + deviceKey.priv = 0; + + var bcoldkey = coldKey.toBytes(); + + //create the final cold key + var rngcold = SHA256(bcoldkey); + + //get the first 128 bits + var coldKeyBytes = []; + for (var i = 0; i < 16; ++i) { + coldKeyBytes[i] = rngcold[i]; + } + + zeroByteArray(rngcold); + zeroByteArray(bcoldkey); + + //set password to sha256 of the hotkey seed + m_this.m_password = SHA256(hotKeyBytes); + + //save the device entropy encrypted with the password + //we don't have the device enc key yet at this stage + m_this.Device.setSecureStorageObject("dpk", deviceEntropyBytes, m_this.m_password, m_this.encryptNp); + + //clear buffer + zeroByteArray(deviceEntropyBytes); + + return callback(err, coldKeyBytes, hotKeyBytes, secretPubKey.toString()); + + } else { + + return callback(err); } - progress('creating cold keys...'); + }); - setTimeout(function () { + } + //recover cold key for mobile + this.recoverColdKeyForMobile = recoverColdKeyForMobile; + function recoverColdKeyForMobile(callback) { - var coldHash = Bitcoin.convert.bytesToHex(coldKeyBytes); + API.getAccountSecPub(m_this.m_guid, m_this.m_sharedid, function (err, secretpub) { - var coldWallet = Bitcoin.HDWallet.fromSeedHex(coldHash, m_this.m_network); - //get the keys as strings - var coldPriv = coldWallet.toString(" "); - var coldPub = coldWallet.toString(); + m_this.m_password = SHA256(m_this.m_onlineKey); - progress('creating hot keys...'); + m_this.Device.getSecureStorageObject("dpk", m_this.m_password, m_this.decryptNp, false, function (deviceent) { - setTimeout(function () { + zeroByteArray(m_this.m_password); + + var secretKey = new Bitcoin.ECPubKey(secretpub, false); + + var deviceKey = new Bitcoin.ECKey(Bitcoin.convert.hexToBytes(deviceent), false); + + deviceent = []; + + var coldKey = secretKey.multiply(deviceKey); + + deviceKey.priv = 0 + + var bcoldkey = coldKey.toBytes(); + + coldKey = 0; + + var rngcold = SHA256(bcoldkey); + + zeroByteArray(bcoldkey); + + var coldKeyBytes = []; + for (var i = 0; i < 16; ++i) { + coldKeyBytes[i] = rngcold[i]; + } + + zeroByteArray(rngcold); + + return callback(coldKeyBytes); + + }); + + }); + + } + + + this.destroySecretPub = destroySecretPub; + function destroySecretPub(callback) { + + API.removeAccountSecPub(m_this.m_guid, m_this.m_sharedid, function (err, res) { + + if (!err) { + + m_this.Device.deleteStorageItem("dpk"); + + m_this.m_offlineKeyBackup = true; + + return callback(err, res); + + } else { - var hotHash = Bitcoin.convert.bytesToHex(hotKeyBytes); + //make the user click again and retry + //we might as well delete the secret anyway + //as long as either are destroyed the key is + //unrecoverable + m_this.Device.deleteStorageItem("dpk"); - var hotWallet = Bitcoin.HDWallet.fromSeedHex(hotHash, m_this.m_network); - //get the keys as strings - var hotPriv = hotWallet.toString(" "); - var hotPub = hotWallet.toString(); + return callback(err, res); + } + + }); + + + } + + + function makeNewWalletPacket(nickname, emailAddress, ninkiPubKey, userToken, secret, getkeys, callback, progress) { + + + progress('getting entropy...'); + + setTimeout(function () { + //getKeysForMobile(); - //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 + getkeys(userToken, function (err, coldKeyBytes, hotKeyBytes) { - var hckey = hotHash + coldHash; - var hcbkey = Bitcoin.convert.hexToBytes(hckey); - var hchkey = Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(hcbkey)).toString(); - progress('creating pgp keys...'); + if (!err) { + progress('creating cold keys...'); setTimeout(function () { - //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 coldHash = Bitcoin.convert.bytesToHex(coldKeyBytes); + + var coldWallet = new Bitcoin.HDWallet(coldKeyBytes, m_this.m_network); + + var coldPub = coldWallet.toString(); + + coldWallet.priv.priv = 0; + coldWallet.priv = 0; + zeroByteArray(coldWallet.chaincode); - var privKeys = openpgp.key.readArmored(keypair.privateKeyArmored); - var publicKeys = openpgp.key.readArmored(keypair.publicKeyArmored); + progress('creating hot keys...'); setTimeout(function () { - //$('#textMessageCreate').text('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 - }; + var hotWallet = new Bitcoin.HDWallet(hotKeyBytes, m_this.m_network); + //get the keys as strings - 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() - }; + var hotPub = hotWallet.toString(); - //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 + hotWallet.priv.priv = 0; + hotWallet.priv = 0; + zeroByteArray(hotWallet.chaincode); + //create a key based on a hash of the hot + cold key + //this is used to encrypt the pbkdf password and so enables - //the hot private key is encrypted and saved locally - //encrypted with the user's password + //password reset if the user has access to the hot and cold key phrases + var hcbkey = []; + hcbkey = hcbkey.concat(hotKeyBytes); + hcbkey = hcbkey.concat(coldKeyBytes); - saveHotHash(hotHash, function (err, res) { + var hchkey = Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(hcbkey)).toString(); - var bip39 = new BIP39(); - var walletInformation = { - wallet: wallet, - coldWalletPhrase: bip39.entropyToMnemonic(coldHash), - hotWalletPhrase: bip39.entropyToMnemonic(hotHash), - sharedid: userToken, - hckey: hchkey - }; + zeroByteArray(hcbkey); + progress('creating pgp keys...'); - return callback(err, walletInformation); - }); + setTimeout(function () { + + //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); + + setTimeout(function () { + + //save the wallet public 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 + }; + + 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(Bitcoin.convert.hexToBytes(secret), m_this.m_password); + + m_this.m_secret = secret; + + //create a packet to post to the server + //note: + // 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 + + 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() + }; + + //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 + + + //the hot private key is encrypted and saved locally + //encrypted with the user's password + + var bip39 = new BIP39(); + var walletInformation = { + wallet: wallet, + coldWalletPhrase: bip39.entropyToMnemonic(Bitcoin.convert.bytesToHex(coldKeyBytes)), + hotWalletPhrase: bip39.entropyToMnemonic(Bitcoin.convert.bytesToHex(hotKeyBytes)), + sharedid: userToken, + hckey: hchkey + }; + + //set the online key + m_this.m_onlineKey = hotKeyBytes; + + zeroByteArray(coldKeyBytes); + + //*if mobile, don't save this locally until PIN is chosen + if (m_this.Device.isCordova()) { + + return callback(err, walletInformation); + + } else { + + saveHotHash(hotKeyBytes, function (err, res) { + + return callback(err, walletInformation); + + }); + + } + + }, 50); + + }, 50); }, 50); }, 50); - }, 50); - - }, 50); + } + }); }, 50); } @@ -836,6 +1286,7 @@ function Engine() { //check two factor code if (twoFactorCodeChk != '') { + SetupTwoFactor(twoFactorCodeChk, function (err, wallet) { if (err) { @@ -907,6 +1358,8 @@ function Engine() { //double check it has been saved correctly getHotHash("", function (err, result) { + m_this.zeroOnlineKey(); + if (!err) { var packet = encrypt(walletInformation, m_this.m_password); @@ -986,7 +1439,7 @@ function Engine() { //if there is a cookie token then encrypt it if (wallet.CookieToken) { - var enc = encryptNp(wallet.CookieToken, m_this.m_password); + var enc = encryptNp(Bitcoin.convert.hexToBytes(wallet.CookieToken), m_this.m_password); var ctok = {}; ctok.ct = enc.toString(); ctok.iv = enc.iv.toString(); @@ -1026,7 +1479,7 @@ function Engine() { //if there is a cookie token then encrypt it if (wallet.CookieToken) { - var enc = encryptNp(wallet.CookieToken, m_this.m_password); + var enc = encryptNp(Bitcoin.convert.hexToBytes(wallet.CookieToken), m_this.m_password); var ctok = {}; ctok.ct = enc.toString(); ctok.iv = enc.iv.toString(); @@ -1529,6 +1982,12 @@ function Engine() { this.isAddressValid = isAddressValid; function isAddressValid(address) { var addrValid = true; + + if (m_this.m_network == "mainnet") { + if (address[0] != "1" && address[0] != "3") { + return false; + } + } try { var addressCheck = new Bitcoin.Address(address); addressCheck.toString(); @@ -1550,8 +2009,6 @@ function Engine() { corNodesToProcess = nodes.length; cordovaDeriveKey(mpk, nodes, mkpder, function (result) { - - return cdcallback(mkpder); }); @@ -1625,8 +2082,6 @@ function Engine() { //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) { @@ -1634,10 +2089,11 @@ function Engine() { } - - //initialise the hot private key space - var bipHot = Bitcoin.HDWallet.fromSeedHex(hothash, m_this.m_network); + var bipHot = new Bitcoin.HDWallet(m_this.m_onlineKey, m_this.m_network); + + //zero out buffers + m_this.zeroOnlineKey(); // @@ -1781,11 +2237,8 @@ function Engine() { //we need to derive addresses on their behalf - - deriveKeys(bipHot, nodeLevels, function (ret) { - if (m_this.Device.isiOS()) { for (var i = 0; i < ret.length; i++) { @@ -1802,7 +2255,8 @@ function Engine() { } - + //zero out bipHot buffers + bipHot.priv = 0; if (sendType == 'friend' || sendType == 'invoice') { @@ -1828,7 +2282,7 @@ function Engine() { addressToSend.push(address); //create the change address, this must be done on the client - statuscallback('Creating change address...', '40%'); + statuscallback('Creating change address...', '60%'); createAddress('m/0/1', changeAmount, function (err, changeaddress) { @@ -1840,7 +2294,7 @@ function Engine() { } //now get the transaction data - statuscallback('Building transaction...', '60%'); + statuscallback('Building transaction...', '80%'); setTimeout(function () { aGetTransactionData(packet, function (err, hashesForSigning, rawTransaction) { @@ -1872,8 +2326,9 @@ function Engine() { API.post("/api/1/u/sendtransaction", jsonp1, function (err, result) { - if (!err) { + bipHot.priv = 0; + if (!err) { statuscallback('Transaction broadcast...', '100%'); @@ -1978,7 +2433,7 @@ function Engine() { }); } else { - statuscallback('Creating change address...', '20%'); + statuscallback('Creating change address...', '40%'); addressToSend.push(addressTo); @@ -2001,14 +2456,14 @@ function Engine() { //now get the transaction - statuscallback('Creating transaction...', '40%'); + statuscallback('Creating transaction...', '60%'); setTimeout(function () { aGetTransactionData(packet, function (err, hashesForSigning, rawTransaction) { if (!err) { - statuscallback('Counter-signing transaction...', '60%'); + statuscallback('Counter-signing transaction...', '80%'); var jsonSend = { guid: m_this.m_guid, @@ -2027,7 +2482,9 @@ function Engine() { API.post("/api/1/u/sendtransaction", jsonp1, function (err, result) { - statuscallback(null, '80%'); + bipHot.priv = 0; + + //statuscallback(null, '80%'); if (!err) { @@ -2138,8 +2595,6 @@ function Engine() { }); - - } //function createAddress @@ -2162,7 +2617,6 @@ function Engine() { var path = nodePath + '/' + leaf; - if (m_this.Device.isiOS()) { var tnode = snode[2] * 1; @@ -2176,31 +2630,29 @@ function Engine() { var hninkiKey = ''; cordova.exec( + function callback(data) { hhotKey = data; hotKey = Bitcoin.convert.hexToBytes(data); - cordova.exec( + function callback(data) { hcoldKey = data; - coldKey = Bitcoin.convert.hexToBytes(data); cordova.exec( + function callback(data) { hninkiKey = data; - - ninkiKey = Bitcoin.convert.hexToBytes(data); - var script = [0x52]; script.push(33); @@ -2268,52 +2720,92 @@ function Engine() { //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); + //so, do we still do this on first addreess generate or on account create? + //would add 6-7 seconds to account creation process - var hotKey = deriveChild(path, bipHot); + var cachepath = nodePath.replace('/', ''); + cachepath = cachepath.replace('/', ''); - var coldKey = deriveChild(path, bipCold); + m_this.Device.getSecureStorageObject("pubcache" + cachepath, m_this.m_deviceSecKey, m_this.decrypt, false, function (pubcache) { - var ninkiKey = deriveChild(path, bipNinki); + //if this is - //now create the multisig address - var script = [0x52]; + //logic ot reset if any issue with cache/decryption + if (pubcache == '') { - 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); + //derive the root node path and cache - var address = multiSig(script); + var obipHot = Bitcoin.HDWallet.fromBase58(m_this.m_walletinfo.hotPub); + var obipCold = Bitcoin.HDWallet.fromBase58(m_this.m_walletinfo.coldPub); + var obipNinki = Bitcoin.HDWallet.fromBase58(m_this.m_walletinfo.ninkiPubKey); - //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 cacallback(err, address); - } else { - return cacallback(err, result); + var hotKey = deriveChild(nodePath, obipHot); + var coldKey = deriveChild(nodePath, obipCold); + var ninkiKey = deriveChild(nodePath, obipNinki); + + var opubcache = {}; + opubcache.hotKey = hotKey.toBase58(); + opubcache.coldKey = coldKey.toBase58(); + opubcache.ninkiKey = ninkiKey.toBase58(); + + + m_this.Device.setSecureStorageObject("pubcache" + cachepath, opubcache, m_this.m_deviceSecKey, m_this.encrypt); + + pubcache = opubcache; + } - }); + var bipHot = Bitcoin.HDWallet.fromBase58(pubcache.hotKey); + var bipCold = Bitcoin.HDWallet.fromBase58(pubcache.coldKey); + var bipNinki = Bitcoin.HDWallet.fromBase58(pubcache.ninkiKey); + + + var hotKey = bipHot.derive(leaf); + + var coldKey = bipCold.derive(leaf); + + var ninkiKey = bipNinki.derive(leaf); + + //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 cacallback(err, address); + } else { + return cacallback(err, result); + } + + }); + }); } } else { @@ -2583,7 +3075,7 @@ function Engine() { function decodeKey(key) { var bip39 = new BIP39(); var mkey = bip39.mnemonicToHex(key); - return mkey; + return Bitcoin.convert.hexToBytes(mkey); } this.encodeKey = encodeKey; @@ -2644,6 +3136,10 @@ function Engine() { } + //add error handling here + //very impoirtant for mobile + // + this.createFriend = createFriend; function createFriend(username, uimessage, cbcallback) { @@ -3250,7 +3746,9 @@ function Engine() { //if password reset do not pbkdf the password - oldpassword = pbkdf2(oldpassword, m_this.m_oguid); + if (oldpassword != m_this.m_password) { + oldpassword = pbkdf2(oldpassword, m_this.m_oguid); + } //get the two packets @@ -3260,7 +3758,7 @@ function Engine() { var decryptedWithOld = true; var decryptedPayload = ''; try { - decryptedPayload = decrypt(wallet.Payload, m_this.m_password, wallet.IV); + decryptedPayload = decrypt(wallet.Payload, oldpassword, wallet.IV); } catch (err) { decryptedWithOld = false; } @@ -3298,7 +3796,7 @@ function Engine() { var jpacket = JSON.parse(response); var veripacket = ''; try { - veripacket = decryptNp(jpacket.packet, m_this.m_password, jpacket.IV); + veripacket = decryptNp(jpacket.packet, m_this.m_password, jpacket.IV, true); } catch (verror) { decryptedVerWithOld = false; @@ -3332,7 +3830,7 @@ function Engine() { newveripacket = encryptNp(veripacket, newpassword); if (decryptedPayload.hckey) { - newpasspacket = encryptNp(newpassword, decryptedPayload.hckey); + newpasspacket = encryptNp(newpassword, Bitcoin.convert.hexToBytes(decryptedPayload.hckey)); } newAIV = newpayload.iv.toString(); @@ -3362,7 +3860,7 @@ function Engine() { testpayload = decrypt(newpayload.toString(), newpassword, newAIV); testnewusrpayload = decrypt(newusrpayload.toString(), newpassword, newUIV); - testveripacket = decryptNp(newveripacket.toString(), newpassword, newRIV); + testveripacket = decryptNp(newveripacket.toString(), newpassword, newRIV, true); if (decryptedPayload.hckey) { testpasspacket = decryptNp(newpasspacket.toString(), decryptedPayload.hckey, newPIV) @@ -3416,7 +3914,7 @@ function Engine() { if (tfso != "") { var enc = JSON.parse(tfso); - var token = decryptNp(enc.ct, m_this.m_password, enc.iv); + var token = decryptNp(enc.ct, m_this.m_password, enc.iv, true); tfso = encryptNp(token, newpassword); var ctok = {}; @@ -3432,7 +3930,9 @@ function Engine() { //the worst case scenario is the //user has to reenter their hot key - saveHotHash(hothash, function (err, result) { + saveHotHash(m_this.m_onlineKey, function (err, result) { + + m_this.zeroOnlineKey(); callback(false, ''); @@ -3441,14 +3941,6 @@ function Engine() { }); - - - - - - - - } else { callback(true, "Error: Password not changed"); @@ -3490,7 +3982,7 @@ function Engine() { } else { - + callback(true, "Current password incorrect"); } @@ -3847,7 +4339,7 @@ function Engine() { if (ret.Token) { - var enc = encryptNp(ret.Token, m_this.m_password); + var enc = encryptNp(Bitcoin.convert.hexToBytes(ret.Token), m_this.m_password); var ctok = {}; ctok.ct = enc.toString(); ctok.iv = enc.iv.toString(); @@ -3935,6 +4427,8 @@ function Engine() { getHotHash("", function (err, hotHash) { + //m_this.zeroOnlineKey(); + if (!err) { result.hotHash = hotHash; @@ -3973,6 +4467,64 @@ function Engine() { API.emailGUID(userName, callback); } + this.getGUIDByMPKH = getGUIDByMPKH; + function getGUIDByMPKH(phrase, callback) { + + + + //get seed from mnemonic + //derive master public key + //hash master public key + + var bip39 = new BIP39(); + + //convert to master key + var mkey = bip39.mnemonicToHex(phrase); + + if (mkey) { + + var hd = Bitcoin.HDWallet.fromSeedHex(mkey, m_this.m_network); + //get master public key + var mpk = hd.toString(); + + //hash the master public key + var bytes = []; + for (var i = 0; i < mpk.length; ++i) { + bytes.push(mpk.charCodeAt(i)); + } + + var mpkh = Bitcoin.Crypto.SHA256(Bitcoin.convert.bytesToWordArray(bytes)).toString(); + + //lookup account guid + return API.getGUIDByMPKH(mpkh, callback); + + + } else { + + return callback(true, "InvalidPhrase"); + + } + + + } + + + this.setPasswordApp = setPasswordApp; + function setPasswordApp(phrase, callback) { + + var bip39 = new BIP39(); + + //convert to master key + var mkey = bip39.mnemonicToHex(phrase); + + var bytes = Bitcoin.convert.hexToBytes(mkey); + + var mpkh = SHA256(bytes); + + m_this.m_password = mpkh; + + } + this.getMasterPublicKeyFromUpstreamServer = getMasterPublicKeyFromUpstreamServer; function getMasterPublicKeyFromUpstreamServer(guid, callback) { API.getMasterPublicKeyFromUpstreamServer(guid, callback); @@ -4134,7 +4686,10 @@ function Engine() { API.getTransactionsForNetwork(m_this.m_guid, m_this.m_sharedid, username, callback); } - + this.getTimeline = getTimeline; + function getTimeline(callback) { + API.getTimeline(m_this.m_guid, m_this.m_sharedid, callback); + } this.getInvoiceList = getInvoiceList; function getInvoiceList(callback) { @@ -4170,7 +4725,21 @@ function Engine() { this.registerDevice = registerDevice; function registerDevice(guid, deviceName, deviceId, deviceModel, devicePIN, regToken, secret, callback) { - API.registerDevice(guid, deviceName, deviceId, deviceModel, devicePIN, regToken, secret, callback); + + API.registerDevice(guid, deviceName, deviceId, deviceModel, devicePIN, regToken, secret, function (err, res) { + + var jekey = JSON.parse(res); + + m_this.m_deviceKey = Bitcoin.convert.hexToBytes(jekey.DeviceKey); + + if (jekey.DeviceKey.length > 0) { + jekey.DeviceKey = 'valid'; + } + + callback(err, res); + + + }); } this.getDeviceKey = getDeviceKey; @@ -4195,22 +4764,24 @@ function Engine() { API.getDeviceKey(m_this.m_guid, pinhash, regToken, function (err, ekey) { if (!err) { + var jekey = JSON.parse(ekey); if (jekey.DeviceKey.length > 0) { + m_this.m_deviceKey = Bitcoin.convert.hexToBytes(jekey.DeviceKey); if (jekey.SessionToken) { API.registerToken(jekey.SessionToken); m_this.m_APIToken = jekey.SessionToken; - callback(err, jekey); + callback(err, ""); } - + jekey.DeviceKey = []; } else { - callback(err, jekey); + callback(err, ""); } } else { callback(true, ekey); @@ -4302,7 +4873,15 @@ function Engine() { }); } + this.updateEmailAddress = updateEmailAddress; + function updateEmailAddress(emailAddress, callback) { + API.updateEmailAddress(m_this.m_oguid, m_this.m_sharedid, emailAddress, function (err, res) { + return callback(err, res); + + + }); + } this.get2faOverride = get2faOverride; @@ -4366,6 +4945,56 @@ function Engine() { } + + // + this.requestAuthMigration = requestAuthMigration; + function requestAuthMigration(token, callback) { + + API.requestAuthMigration(m_this.m_guid, m_this.m_secret, token, function (err, res) { + + return callback(err, res); + + }); + + } + + this.getAuthMigrationRequest = getAuthMigrationRequest; + function getAuthMigrationRequest(callback) { + + API.getAuthMigrationRequest(m_this.m_guid, m_this.m_sharedid, function (err, res) { + + return callback(err, res); + + }); + } + + + this.authMigration = authMigration; + function authMigration(enckey, token, callback) { + + getTwoFactorToken(enckey, function (err, twoFactorToken) { + + API.authMigration(m_this.m_guid, m_this.m_sharedid, twoFactorToken, token, function (err, res) { + + return callback(err, res); + + }); + + }); + } + + + this.getAuthMigrationToken = getAuthMigrationToken; + function getAuthMigrationToken(token, callback) { + + API.getAuthMigrationToken(m_this.m_guid, m_this.m_secret, token, function (err, res) { + + return callback(err, res); + + }); + } + + } module.exports = Engine; diff --git a/src/ninki-ui-mobile.js b/src/ninki-ui-mobile.js index 83195b5..9f2c871 100644 --- a/src/ninki-ui-mobile.js +++ b/src/ninki-ui-mobile.js @@ -61,6 +61,9 @@ function UI() { var ONE_HOUR = 60 * 60 * 1000; + + var tmpAuthIdentifier = ''; + window.isSessionLive = function (callback) { Ninki.API.getPrice(Engine.m_guid, Engine.m_settings.LocalCurrency, function (err, result) { @@ -76,6 +79,34 @@ function UI() { } + window.setKeyboardScroll = function () { + + if (window.cordova) { + cordova.plugins.Keyboard.disableScroll(true); + } + + } + + + + window.hideSecScreens = function () { + + if ($(".blind").is(":visible")) { + + $(".blind").hide(); + $("#seccheck").removeClass("invis"); + $("#seccheck").show(); + + } + + window.resetPin(); + + $('#sendstdpin').val(''); + $("#paddelconf").hide(); + $('.numdone').attr("style", "background-color:white"); + + } + window.showLoginPIN = function () { @@ -95,7 +126,6 @@ function UI() { } - if (window.cordova) { cordova.plugins.Keyboard.close(); } @@ -103,13 +133,26 @@ function UI() { $("#isactive").val(0); + if ($(".blind").is(":visible")) { + + window.clearAuthInterval(); + $(".blind").hide(); + $("#seccheck").removeClass("invis"); + $("#seccheck").show(); + + } + window.resetPin(); + $('#sendstdpin').val(''); + $("#paddelconf").hide(); $("#paddel").hide(); + + $('.numdone').attr("style", "background-color:white"); $("#loginpinno").val(''); @@ -134,6 +177,9 @@ function UI() { $(".footer").hide(); + $(".bootbox").hide(); + + } @@ -264,6 +310,19 @@ function UI() { }); + setTimeout(function () { + + if (window.cordova) { + if (cordova.plugins) { + if (cordova.plugins.Keyboard) { + cordova.plugins.Keyboard.disableScroll(true); + } + } + } + + }, 1000); + + bootbox.setDefaults({ 'backdrop': false, 'animate': true }); //guid @@ -275,67 +334,49 @@ function UI() { Engine.Device.getStorageItem("ninki_reg", function (reg) { if (reg) { + isPairing = false; $("#loginpin").show(); $("#pinimage").show(); } else { - isPairing = true; - $("#pairDevice").show(); + + Engine.fillElementWithGuid($("#createWalletStart input#guid")); + + isCreate = true; + $("#createWalletStart").show(); $("#pinpair").show(); } }); - //create wallet area - - Engine.fillElementWithGuid($("#createWalletStart input#guid")); + //create wallet area - //$('#nickname').val($('#guid').val().substring(0, 7)); - //$('#emailaddress').val($('#guid').val().substring(0, 7) + '@ninkip2p.com'); - $('#createaccount').bind("touchstart", function () { + $('#createaccount').bind("click", function () { - Engine.Device.deleteStorageItem("dataCache"); //Engine.Device.deleteStorageItem("balance"); - - if (window.cordova) { - cordova.plugins.Keyboard.disableScroll(true); - } - - isCreate = true; $("#pairDevice").hide(); - + $("#createWalletStart").show(); }); - - - - $('#createaccount').bind("touchend", function () { - - setTimeout(function () { - - $("#createWalletStart").show(); - - }, 100); - - }); - $('#closetos').bind("touchstart", function () { - $("#tos").removeClass("slideUp"); - $("#tos").addClass("invis"); + //$("#tos").removeClass("slideUp"); + //$("#tos").addClass("invis"); $("#tos").hide(); - + $('#createWalletStart').show(); }); $('#termslink').bind("click", function () { - $("#tos").removeClass("invis"); - $("#tos").addClass("slideUp"); + //$("#tos").removeClass("invis"); + //$("#tos").addClass("slideUp"); + + $('#createWalletStart').hide(); $('#tos').show(); }); @@ -364,13 +405,13 @@ function UI() { $("#cpassword").blur(function () { $(".popover.fade.bottom.in").hide(); - $("#pwdmeter").fadeOut(500); + //$("#pwdmeter").fadeOut(500); }); $("#cpassword").focus(function (e) { $(".popover.fade.bottom.in").show(); - $("#pwdmeter").fadeIn(500); + //$("#pwdmeter").fadeIn(500); }); @@ -393,12 +434,32 @@ function UI() { if (step == 2) { + Engine.Device.deleteStorageItem("ok_disp"); + + $("#secheckkeys").show(); + $("#secchevkeys").hide(); + + $("#sechevemail i").removeClass("text-muted"); + $("#sechevemail i").addClass("text-primary"); + + $("#hotWalletPhrase").text(''); + $("#btnPairDevice").removeClass("disabled"); - $('#btnUnpair').hide(); - $('#loginpin').show(); $("#hotkeystep").hide(); + $("#imgphrasewaiting").hide(); + + $("#seccheck").removeClass("invis"); + $("#seccheck").show(); + + displaySecurityScore(); + + //$("#tfastep").show(); + //$('#btnUnpair').hide(); + //$('#loginpin').show(); + + setTimeout(function () { $("#hotkeystep").removeClass("fadeInRightBig"); $("#hotkeystep").addClass("fadeInLeftBig"); @@ -413,20 +474,33 @@ function UI() { if (step == 1) { - $("#hotkeystep").show(); - $("#coldkeystep").hide(); - setTimeout(function () { - $("#coldkeystep").removeClass("fadeInRightBig"); - $("#coldkeystep").addClass("fadeInLeftBig"); - }, 1000); + Engine.destroySecretPub(function (err, res) { + if (!err) { - $(".previous").show(); + $("#coldWalletPhrase").text(''); - $(".next").hide(); + $("#hotkeystep").show(); + $("#coldkeystep").hide(); - step++; + setTimeout(function () { + $("#coldkeystep").removeClass("fadeInRightBig"); + $("#coldkeystep").addClass("fadeInLeftBig"); + }, 1000); + + + Engine.Device.setStorageItem("ok_disp", "1"); + + $(".previous").show(); + + $(".next").hide(); + + step++; + + } + + }); } }); @@ -438,7 +512,6 @@ function UI() { $("#coldkeystep").show(); $("#hotkeystep").hide(); - $("#hotkeystep").removeClass("fadeInLeftBig"); $("#hotkeystep").addClass("fadeInRightBig"); @@ -473,13 +546,13 @@ function UI() { }); - - $("#coldcheck").bind("touchstart", function () { $("#coldcheckicon").removeClass("fa-square-o"); $("#coldcheckicon").addClass("fa-check-square"); $("#coldcheckicon").addClass("text-success"); + $("#coldfinalwarning").show(); + $(".next").show(); @@ -544,8 +617,6 @@ function UI() { var specificField = $('#emailaddress').parsley(); window.ParsleyUI.addError(specificField, "emailaddressError", "This email address is already taken"); - $("#btnCreate").prop('disabled', true); - $("#btnCreate").addClass('disabled'); $("#emailaddress").css("border-color", "#ffaaaa"); isCreateEmailValid = false; @@ -556,7 +627,7 @@ function UI() { } - validateCreateForm(); + //validateCreateForm(); }); } else { @@ -569,7 +640,7 @@ function UI() { $("#emailaddress").parsley('reset'); isCreateNicknameValid = false; - validateCreateForm(); + // validateCreateForm(); } }); @@ -578,11 +649,20 @@ function UI() { $("#nickname").focus(function () { + $("#pairsection").hide(); + var specificField = $('#nickname').parsley(); window.ParsleyUI.removeError(specificField, "nicknameError"); }); + + $("#nickname").blur(function () { + + $("#pairsection").show(); + + }); + $("#emailaddress").focus(function () { var specificField = $('#emailaddress').parsley(); @@ -590,337 +670,314 @@ function UI() { }); - $("#nickname").blur(function () { - - var nicknme = $("#nickname").val(); - if (nicknme.length > 2) { - if ($("#nickname").parsley().isValid()) { + $("#password1, #cpassword, #emailaddress").change(function () { + //validateCreateForm(); - //check email address valid - Engine.doesUsernameExist(nicknme, function (err, res) { + }); - if (res) { - $("#btnCreate").prop('disabled', true); - $("#btnCreate").addClass('disabled'); - $("#nickname").css("border-color", "#ffaaaa"); + var showlogo = true; - var specificField = $('#nickname').parsley(); - window.ParsleyUI.addError(specificField, "nicknameError", "This username is already taken"); - isCreateNicknameValid = false; + $("#createback").bind("touchstart", function () { - } else { + isCreate = false; + $("#pairDevice").show(); + $("#createWalletStart").hide(); - $("#nickname").css("border-color", "#ccc"); - isCreateNicknameValid = true; - } + }); - validateCreateForm(); - }); + var slideShowDone = false; + function runSlideShow() { + setTimeout(function () { - } else { + $("#crprog1").hide(); + $("#crprog2").show(); - $("#nickname").parsley().validate(); - } + setTimeout(function () { - } else { + $("#crprog2").hide(); + $("#crprog3").show(); + setTimeout(function () { - $("#nickname").parsley('reset'); - isCreateNicknameValid = false; - validateCreateForm(); - $("#nickname").css("border-color", "#ffaaaa"); - } + slideShowDone = true; - }); + }, 5000); + }, 5000); - $("#password1, #cpassword, #emailaddress").change(function () { - validateCreateForm(); + }, 5000); - }); + } + function showCreateAccPIN() { - var showlogo = true; - $("#password1, #cpassword, #nickname, #emailaddress").focus(function () { - showlogo = false; - $("#createheader").slideUp(); - //$("#createheadermini").slideDown(); - termslink - $("#termslink").fadeOut(); - $("#createback").fadeOut(); - }); + if (slideShowDone) { - $("#password1, #cpassword, #nickname, #emailaddress").blur(function () { - showlogo = true; - setTimeout(function () { - if (showlogo) { + //set variables for the session + $("#createWalletStart").hide(); + $("#createWalletProgress").hide(); - $("#termslink").fadeIn(); - $("#createback").fadeIn(); + $('#createWalletStart input#cpassword').val(''); + $('#createWalletStart input#password1').val(''); - } - }, 50); - }); + //save the encrypted hot key in local storage - $("#createback").bind("touchstart", function () { + $("#walletGuid").text($('input#guid').val()); - isCreate = false; - $("#pairDevice").show(); - $("#createWalletStart").hide(); - }); + //$("#showPhrases").show(); + //$("#securitywizard").show(); - function validateCreateForm() { + //$(".next").hide(); - if (($(".password-verdict").html() == 'Strong' || $(".password-verdict").html() == 'Very Strong')) { + //$("#no2famessage").hide(); - if (isCreateNicknameValid && isCreateEmailValid) { + step = 1; + //$("#coldkeystep").show(); + //$(".previous").hide(); - $("#btnCreate").prop('disabled', false); - $("#btnCreate").removeClass('disabled'); - } else { + $('#loginpin').show(); - $("#btnCreate").prop('disabled', true); - $("#btnCreate").addClass('disabled'); + } else { - } + setTimeout(function () { - } else { + showCreateAccPIN(); - $("#btnCreate").prop('disabled', true); - $("#btnCreate").addClass('disabled'); + }, 2000); } } + $("#btnCreate").bind("touchstart", function () { + $("#btnCreate").button('loading'); + + deleteDeviceStorage(); + showlogo = false; - if (isCreateNicknameValid && isCreateEmailValid) { + window.setKeyboardScroll(); - if ($("#frmcreate").parsley().isValid()) { + if ($("#frmcreate").parsley().isValid()) { - //check password strength - if (($(".password-verdict").html() == 'Strong' || $(".password-verdict").html() == 'Very Strong')) { + //check password strength + //if (($(".password-verdict").html() == 'Strong' || $(".password-verdict").html() == 'Very Strong')) { - var nicknme = $("#nickname").val(); - //check email address valid - Engine.doesUsernameExist(nicknme, function (err, res) { + var nicknme = $("#nickname").val(); + //check email address valid + Engine.doesUsernameExist(nicknme, function (err, res) { - if (!res) { + if (!res) { - var emailaddr = $("#emailaddress").val(); - //check email address valid - Engine.doesEmailExist(emailaddr, function (err, res) { + var emailaddr = $("#emailaddress").val(); + //check email address valid + //Engine.doesEmailExist(emailaddr, function (err, res) { - if (!res) { + //if (!res) { - $("#imgcreatewaiting").show(); - $("#btnCreate").prop('disabled', true); - $("#btnCreate").addClass('disabled'); - $("#lnkOpenWallet").hide(); + $("#imgcreatewaiting").show(); + //$("#btnCreate").prop('disabled', true); + //$("#btnCreate").addClass('disabled'); + $("#lnkOpenWallet").hide(); - //error handling here? + //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(); + 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.m_nickname = username; + Engine.m_nickname = username; - $("#createWalletStart").hide(); - $("#createWalletProgress").show(); + $("#createWalletStart").hide(); + $("#createWalletProgress").show(); + runSlideShow(); + setTimeout(function () { - setTimeout(function () { + Engine.createWalletApp(guid, username, function (err, result) { - 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) { - //move error handling and ui elements to here - $("#createWalletStart input#nickname").css("border-color", "#ccc"); - if (err) { - if (result == "ErrUserExists") { + $("#btnCreate").button('reset'); - $("#createWalletStart input#nickname").css("border-color", "#ffaaaa"); - $("#imgcreatewaiting").hide(); + if (result == "ErrUserExists") { - $("#createwalletalert").show(); - $("#createwalletalertmessage").text("The username already exists"); + $("#createWalletStart input#nickname").css("border-color", "#ffaaaa"); + $("#imgcreatewaiting").hide(); - $("#btnCreate").prop('disabled', false); - $("#btnCreate").removeClass('disabled'); - $("#lnkOpenWallet").show(); - } - if (result == "ErrEmailExists") { + $("#createwalletalert").show(); + $("#createwalletalertmessage").text("The username already exists"); - $("#createWalletStart input#emailaddress").css("border-color", "#ffaaaa"); - $("#imgcreatewaiting").hide(); + $("#btnCreate").prop('disabled', false); + $("#btnCreate").removeClass('disabled'); + $("#lnkOpenWallet").show(); + } + if (result == "ErrEmailExists") { - $("#createwalletalert").show(); - $("#createwalletalertmessage").text("The email address is already in use"); + $("#createWalletStart input#emailaddress").css("border-color", "#ffaaaa"); + $("#imgcreatewaiting").hide(); - $("#btnCreate").prop('disabled', false); - $("#btnCreate").removeClass('disabled'); - $("#lnkOpenWallet").show(); - } + $("#createwalletalert").show(); + $("#createwalletalertmessage").text("The email address is already in use"); - if (result == "ErrCreateAccount") { + $("#btnCreate").prop('disabled', false); + $("#btnCreate").removeClass('disabled'); + $("#lnkOpenWallet").show(); + } - $("#imgcreatewaiting").hide(); - $("#btnCreate").prop('disabled', false); - $("#btnCreate").removeClass('disabled'); - $("#lnkOpenWallet").show(); + if (result == "ErrCreateAccount") { - $("#createwalletalert").show(); - $("#createwalletalertmessage").text("Error"); + $("#imgcreatewaiting").hide(); + $("#btnCreate").prop('disabled', false); + $("#btnCreate").removeClass('disabled'); + $("#lnkOpenWallet").show(); - } + $("#createwalletalert").show(); + $("#createwalletalertmessage").text("Error"); - if (result == "ErrSavePacket") { + } - $("#imgcreatewaiting").hide(); - $("#btnCreate").prop('disabled', false); - $("#btnCreate").removeClass('disabled'); - $("#lnkOpenWallet").show(); + if (result == "ErrSavePacket") { - $("#createwalletalert").show(); - $("#createwalletalertmessage").text("Error"); + $("#imgcreatewaiting").hide(); + $("#btnCreate").prop('disabled', false); + $("#btnCreate").removeClass('disabled'); + $("#lnkOpenWallet").show(); - } + $("#createwalletalert").show(); + $("#createwalletalertmessage").text("Error"); + } - } else { + } else { - $("#hotWalletPhrase").text(result.hotWalletPhrase); - $("#coldWalletPhrase").text(result.coldWalletPhrase); - $("#coldWalletPhrasePrintText").text(result.coldWalletPhrase); - if (Engine.Device.isiOS()) { - deviceName = "My iPhone"; - } else { - deviceName = "My Android"; - } + $("#hotWalletPhrase").text(result.hotWalletPhrase); + $("#coldWalletPhrase").text(result.coldWalletPhrase); + $("#coldWalletPhrasePrintText").text(result.coldWalletPhrase); - //now we perform a pairing of the device with the account - Engine.getDeviceTokenForApp(deviceName, function (err, response) { + if (Engine.Device.isiOS()) { + deviceName = "My iPhone"; + } else { + deviceName = "My Android"; + } - if (!err) { + //now we perform a pairing of the device with the account + Engine.getDeviceTokenForApp(deviceName, function (err, response) { - response = JSON.parse(response); + if (!err) { - //registration token for this pairing attempt - regToken = response.RegToken; - fatoken = response.DeviceToken; + response = JSON.parse(response); - Engine.getHotHash(Engine.m_password, function (err, hothash) { + //registration token for this pairing attempt - if (!err) { + Engine.m_deviceKey = Bitcoin.convert.hexToBytes(response.DeviceKey); + Engine.m_deviceToken = Bitcoin.convert.hexToBytes(response.DeviceToken) + Engine.m_regToken = response.RegToken; - //encrypt the user's password with the encryption key + response = []; - Engine.Device.setStorageItem("ninki_reg", response.RegToken); - Engine.Device.setSecureStorageObject("ninki_rem", response.DeviceToken, response.DeviceKey, Engine.encryptNp); - Engine.Device.setSecureStorageObject("ninki_p", Engine.m_password, response.DeviceKey, Engine.encryptNp); - Engine.Device.setSecureStorageObject("ninki_h", hothash, response.DeviceKey, Engine.encryptNp); + //Engine.getHotHash('', function (err, hothash) { - result = ''; + //if (!err) { - //save all the tokens - //we should now be in the same state as if we have just - //scanned a qr code to pair the phone - //and entered a password + //encrypt the user's password with the encryption key - //initialiseUI(); - //Engine.m_validate = false; + Engine.Device.setStorageItem("ninki_reg", Engine.m_regToken); - //set variables for the session - $("#createWalletStart").hide(); - $("#createWalletProgress").hide(); + Engine.Device.setSecureStorageObject("ninki_rem", Engine.m_deviceToken, Engine.m_deviceKey, Engine.encryptNp); + //Engine.Device.setSecureStorageObject("ninki_p", Engine.m_password, Engine.m_deviceKey, Engine.encryptNp); + Engine.Device.setSecureStorageObject("ninki_h", Engine.m_onlineKey, Engine.m_deviceKey, Engine.encryptNp); - $('#createWalletStart input#cpassword').val(''); - $('#createWalletStart input#password1').val(''); + Engine.zeroOnlineKey(); - //save the encrypted hot key in local storage - $("#walletGuid").text($('input#guid').val()); - $("#showPhrases").show(); - $("#securitywizard").show(); + result = ''; - $(".next").hide(); + //save all the tokens + //we should now be in the same state as if we have just + //scanned a qr code to pair the phone + //and entered a password - $("#no2famessage").hide(); + //initialiseUI(); + //Engine.m_validate = false; - step = 1; - $("#coldkeystep").show(); - $(".previous").hide(); - } + showCreateAccPIN(); - }); - } + //} - }); + //}); + } - //showTwoFactorQr(); + }); - } - }, function (txtprogress) { - $("#progresstext").text(txtprogress); + //showTwoFactorQr(); - }); + } + }, function (txtprogress) { - }, 100); - } else { + $("#progresstext").text(txtprogress); - isCreateEmailValid = false; - validateCreateForm(); - } - }); + }); - } else { + }, 100); + // } else { - isCreateNicknameValid = false; - validateCreateForm(); - } - }); + // isCreateEmailValid = false; + //validateCreateForm(); + // } + // }); } else { - //password not strong - $("#createwalletalert").show(); - $("#createwalletalertmessage").text("Password must be Strong- ideally Very Strong"); + $("#btnCreate").button('reset'); + + isCreateNicknameValid = false; + + $("#nickname").css("border-color", "#ffaaaa"); + + //validateCreateForm(); } + }); - } else { - $("#frmcreate").parsley().validate(); - } + + } else { + + $("#btnCreate").button('reset'); + + $("#frmcreate").parsley().validate(); } + }); @@ -964,6 +1021,56 @@ function UI() { }); + $("#tapemailvalclose").bind("touchstart", function () { + + if (!(typeof window.app === 'undefined')) { + app.isScanning = false; + } + + $("#emailvalstep").hide(); + + $("#imgphrasewaiting").hide(); + + $("#seccheck").removeClass("invis"); + $("#seccheck").show(); + + }); + + $("#tapofflineclose").bind("touchstart", function () { + + $("#coldkeystep").hide(); + + $("#seccheck").removeClass("invis"); + $("#seccheck").show(); + + }); + + $("#taponlineclose").bind("touchstart", function () { + + $("#hotkeystep").hide(); + + $("#seccheck").removeClass("invis"); + $("#seccheck").show(); + + }); + + + $("#tapemailstepclose").bind("touchstart", function () { + + if (!(typeof window.app === 'undefined')) { + app.isScanning = false; + } + + $("#emailstep").hide(); + + $("#imgphrasewaiting").hide(); + + $("#seccheck").removeClass("invis"); + $("#seccheck").show(); + + }); + + $("#tfaoptsetuplater, #tfaoptclose").bind("touchstart", function () { @@ -973,12 +1080,16 @@ function UI() { $("#tfastep").hide(); - $("#imgphrasewaiting").hide(); - $("#welcome").removeClass("invis"); - $("#welcome").addClass("slideUp"); - $("#welcome").show(); + $("#seccheck").removeClass("invis"); + $("#seccheck").show(); + + $("#imgphrasewaiting").hide(); + + // $("#welcome").removeClass("invis"); + // $("#welcome").addClass("slideUp"); + // $("#welcome").show(); }); @@ -1118,11 +1229,15 @@ function UI() { app.isScanning = false; } + + Engine.m_settings.TwoFactor = true; + $("#secheck2fa").show(); + $("#sechev2fa").hide(); + $("#tfastep").hide(); - $("#welcome").removeClass("invis"); - $("#welcome").addClass("slideUp"); - $("#welcome").show(); + $("#seccheck").removeClass("invis"); + $("#seccheck").show(); } @@ -1133,7 +1248,7 @@ function UI() { - $("#btnEmailValidate").bind("touchstart", function () { + $("#btnEmailValidate").hammer(null).bind("tap", function () { //app.isScanning = false; @@ -1143,271 +1258,914 @@ function UI() { var ttext = text.trim().replace(" ", ""); - //$('#txtEmailToken').val(''); + $('#txtEmailToken').val(ttext); $("#txtEmailToken").trigger('change'); - }, function () { + }, function () { + + //console.log("paste error"); + + }); + } + + }); + + $("#txtEmailToken").change(function () { + + var token = $("#txtEmailToken").val(); + + $("#btnEmailValidate").prop('disabled', true); + + Engine.getEmailValidation(token, function (err, response) { + + if (err) { + $("#btnEmailValidate").prop('disabled', false); + } else { + + if (response != "Valid") { + + if (response == "Expired") { + $("#valemailerror").text('Your token has expired'); + } + if (response == "Invalid") { + $("#valemailerror").text('Your token is not valid'); + } + + $("#btnEmailValidate").prop('disabled', false); + + + $("#valemailerror").show(); + $("#valemailerror").fadeOut(2000); + + } else { + + Engine.m_settings.EmailVerified = true; + + + displaySecurityScore(); + + + Engine.m_validate = false; + + $('#createWalletStart').hide(); + $('#emailstep').hide(); + + + $("#pairspinner").hide(); + + $("#secheckemail").show(); + $("#sechevemail").hide(); + + $("#sechev2fa i").removeClass("text-muted"); + $("#sechev2fa i").addClass("text-primary"); + + + $("#seccheck").removeClass("invis"); + $("#seccheck").show(); + + $("#validateemail").hide(); + $("#valemailerror").hide(); + $("#btnEmailValidate").prop('disabled', false); + + } + } + + }); + + + //call to verify token + + }); + + + //after the user has chosen their PIN + //register the pin with the server + + + $("#emailresend").click(function () { + + Engine.sendWelcomeDetails(function (err, result) { + + if (!err) { + + $("#emailresendmessage").show(); + $("#emailresend").hide(); + //email has been resent, please check your email + } + + }); + + }); + + function showTwoFactorQr() { + + $("#twoFactorQr").show(); + $("#2factor1").show(); + + Engine.getTwoFactorImg(function (err, twoFASecret) { + + var nickname = m_this.m_nickname; + var data = "otpauth://totp/Ninki:" + nickname + "?secret=" + twoFASecret + "&issuer=Ninki"; + var options = { text: data, width: 128, height: 128 }; + + $('#tfarawcode').text(twoFASecret); + + $('#twoFactorQrImg').text(''); + $('#twoFactorQrImg').qrcode(options); + + }); + + } + + + //end create wallet area + + + + $("#mainWallet").hide(); + + //$("#dashreceive").hide(); + //$("#dashcontact").hide(); + + + $("#addcontactmodal").hide(); + + + $('#stdselcu').click(function () { + + sendAmount = ''; + + $('#stdselunit').text(COINUNIT); + + + stdAmountConvCoin = true; + + + if (stdAmountConvCoin) { + + if (COINUNIT == "Bits") { + + $("#numcdot").hide(); + + } else { + + $("#numcdot").show(); + + } + + } else { + + $("#numcdot").show(); + + } + + + updateStdAmount(); + + }); + + + $('#stdsellc').click(function () { + + + sendAmount = ''; + + $('#stdselunit').text(Engine.m_settings.LocalCurrency); + stdAmountConvCoin = false; + + + if (stdAmountConvCoin) { + + if (COINUNIT == "Bits") { + + $("#numcdot").hide(); + + } else { + + $("#numcdot").show(); + + } + + } else { + + $("#numcdot").show(); + + } + + updateStdAmount(); + + + }); + + + + $('.scoinunit').bind('click', function () { + + $('.scoinunit').find("label").html(''); + + $(this).find("label").html(''); + + var sel = $.trim($(this).text()); + + Engine.Device.setStorageItem("coinunit", sel); + + Engine.m_settings.CoinUnit = sel; + + COINUNIT = sel; + + prevtransfeed = -1; + prevNetworkTransCount = -1; + + updateUI(); + + }); + + + + + + $('.sccy').bind('click', function () { + + $('.sccy').find("label").html(''); + + $(this).find("label").html(''); + + var sel = $.trim($(this).text()); + + Engine.Device.setStorageItem("currency", sel); + + Engine.m_settings.LocalCurrency = sel; + + updatePrice(); + + }); + + + + + + + + $('#taptfa').bind('touchstart', function () { + + + if (!Engine.m_settings.TwoFactor) { + + $("#seccheck").addClass("invis"); + $("#seccheck").removeClass("slideUp"); + $("#seccheck").hide(); + + showTwoFactorQr(); + + $("#tfastep").show(); + $("#tfaoption").hide(); + $("#tfaoptionsetuppnl").show(); + + } + + + }); + + + $('#tapemailsec').bind('touchstart', function () { + + + Engine.Device.getStorageItem("ok_disp", function (res) { + + + if (!Engine.m_settings.EmailVerified && Engine.m_offlineKeyBackup && res != "1") { + + $("#seccheck").addClass("invis"); + $("#seccheck").removeClass("slideUp"); + $("#seccheck").hide(); + $("#mainWallet").hide() + + $("#emailvalstep").show(); + + } + + }); + + + }); + + $('#btnRegEmail').bind('touchstart', function () { + + + $("#frmEmail").parsley().validate(); + + if ($("#frmEmail").parsley().isValid()) { + + var emailAddress = $("#txtEmailAddress").val(); + + Engine.updateEmailAddress(emailAddress, function (err, result) { + + Engine.sendWelcomeDetails(function (err, result) { + + $("#emailvalstep").hide(); + $("#emailstep").show(); + + }); + + }); + + } + + }); + + + + window.clearAuthInterval = function () { + + clearAuthInterval(); + + }; + + + $('#btnCloseAuthDevice').bind('touchstart', function () { + + $('#sendstdpin').val(''); + + clearAuthInterval(); + + $("#mainWallet").show(); + $("#seccheck").show(); + $("#authrequestitem").hide(); + $("#deviceauth").hide(); + + }); + + + var checkAuthInterval = null; + function clearCheckAuth() { + + clearInterval(checkAuthInterval); + + } + + $('#btnAuthDevice').bind('touchstart', function () { + + var pin = $('#sendstdpin').val(); + + if (pin.length == 4) { + + Engine.getDeviceKey(pin, function (err, ekey) { + + $("#sendstdpin").val(''); + + if (!err) { + + Engine.authMigration(Engine.m_deviceKey, tmpAuthIdentifier, function (err, result) { + + if (!err) { + + + $("#mainWallet").show(); + $("#seccheck").show(); + $("#deviceauth").hide(); + $("#desktopintro").hide(); + $("#desktopphrase").hide(); + + //now listen to update sec checklist + + checkAuthInterval = setInterval(function () { + + Engine.getAccountSettings(function (err, res) { + + if (!err) { + + //only set the settings we need to refresh for the mobile apps + var settingsObject = JSON.parse(res); + Engine.m_settings.TwoFactor = settingsObject.TwoFactor; + Engine.m_settings.EmailVerified = settingsObject.EmailVerified; + + if (Engine.m_settings.TwoFactor) { + displaySecurityScore(); + clearCheckAuth(); + $("#secheck2fa").show(); + $("#sechev2fa").hide(); + } + + } + + }); + + + + }, 5000); + + } else { + + + clearAuthInterval(); + bootbox.alert("Your auth token has expired. Please exit and try again.", function () { + + $("#authrequestitem").hide(); + + }); + + + } + + }); + + } else { + + clearAuthInterval(); + bootbox.alert("PIN failed. Please exit and try again.", function () { + + $("#authrequestitem").hide(); + + }); + + } + + }); + } else { + + clearAuthInterval(); + bootbox.alert("Invalid PIN. Please exit and try again.", function () { + + $("#authrequestitem").hide(); + + }); + + } + + + + }); + + + function authDevice() { + + //get the twofactor token + var pin = $('#sendstdpin').val(); + + Engine.getDeviceKey(pin, function (err, ekey) { + + if (!err) { + + Engine.getHotHash('', function (err, hotHash) { + + Engine.zeroDeviceKey(); + + if (!err) { + + var bip39 = new BIP39(); // 'en' is the default language + var hotmnem = bip39.entropyToMnemonic(Bitcoin.convert.bytesToHex(Engine.m_onlineKey)); + + Engine.zeroOnlineKey(); + + $("#hotWalletDesktopPhrase").text(hotmnem); + + hotmnem = []; + + $('#pinconfirm').hide(); + $('#desktopphrase').show(); + $("#pinconfdets").show(); + $("#paddelconf").hide(); + + $('#pinconfirm').hide(); + $('.numdone').attr("style", "background-color:white"); + + //we will resuse in the next step + //$("#sendstdpin").val(''); + + sendmode = ""; + pintaps = 0; + prevpin = ''; + + window.resetPin(); + + getAuthReq(); + + } + + }); + + } else { + + $('.numdone').attr("style", "background-color:white"); + + $("#sendstdpin").val(''); + pintaps = 0; + prevpin = ''; + $("#paddelconf").hide(); + + window.resetPin(); + + if (ekey.substring(0, 6) == "ErrPIN") { + + var attempts = ekey.substring(7, 8); + + $("#pinconfcount").effect("shake"); + + } else if (ekey.substring(0, 10) == "ErrBlocked") { + + //if the login attempt has been blocked + //display a countdown to the user + //indicating when they can next attempt to + //login + + var seconds = ekey.substring(11, ekey.length) * 1.0; + + setCountdown(seconds); + + // later on this timer may be stopped + + $("#pincounter").effect("shake"); + + } else { + + bootbox.alert(ekey); + + } + } + + }); + + } + + + $('#tapclosedeskintro').bind('touchstart', function () { + + $('#desktopintro').hide(); + $("#mainWallet").show(); + $("#seccheck").show(); + + }); + + $('#tapclosedeskphrase').bind('touchstart', function () { + + //resetpin + $('#sendstdpin').val(''); + + $('#desktopphrase').hide(); + $("#mainWallet").show(); + $("#seccheck").show(); + + }); + + + + + $('#tapchrome').bind('touchstart', function () { + + Engine.Device.getStorageItem("ok_disp", function (res) { + + if (!Engine.m_settings.TwoFactor && Engine.m_settings.EmailVerified && Engine.m_offlineKeyBackup && res != "1") { + + $('#desktopintro').show(); + $("#mainWallet").hide(); + $("#seccheck").hide(); + + } + + }); + + }); + + + $('#tapdesktopnext').bind('touchstart', function () { + + sendmode = "deviceauth" + $("#mainWallet").hide(); + $("#desktopintro").hide(); + + $("#pinconfdets").hide(); + $("#pinconfirm").addClass("blind"); + $("#pinconfirm").show(); + + + }); + + var authInterval = null; + var runAuthCheck = false; + function clearAuthInterval() { + + runAuthCheck = false; + clearInterval(authInterval); + + } + + + function checker() { + + if (runAuthCheck) { + + Engine.getAuthMigrationRequest(function (err, result) { + + if (!err) { + + if (result.length == 1) { + + if (result[0]) { + + console.log(result[0].authreqtoken); + + tmpAuthIdentifier = result[0].authreqtoken; + + $("#authrequestitem").show(); + + clearAuthInterval(); + + $("#mainWallet").hide(); + $("#seccheck").hide(); + + $("#deviceauth").show(); + + $("#desktopphrase").hide(); + + } + + } else { + + $("#authrequestitem").hide(); + } + - //console.log("paste error"); + } }); - } - }); + } - $("#txtEmailToken").change(function () { + } - var token = $("#txtEmailToken").val(); - $("#btnEmailValidate").prop('disabled', true); - Engine.getEmailValidation(token, function (err, response) { + function getAuthReq() { - if (err) { - $("#btnEmailValidate").prop('disabled', false); - } else { + $("#authrequestitem").hide(); - if (response != "Valid") { + if (!Engine.m_settings.TwoFactor) { - if (response == "Expired") { - $("#valemailerror").text('Your token has expired'); - } - if (response == "Invalid") { - $("#valemailerror").text('Your token is not valid'); - } + runAuthCheck = true; + checker(); + authInterval = setInterval(checker, 5000); - $("#btnEmailValidate").prop('disabled', false); + } + } - $("#valemailerror").show(); - $("#valemailerror").fadeOut(2000); - } else { + $('#tapdesktopnext2').bind('touchstart', function () { + $("#authrequestitem").hide(); + $("#mainWallet").hide(); + $("#seccheck").hide(); - Engine.m_validate = false; + $("#deviceauth").show(); + }); - $('#createWalletStart').hide(); - $('#emailstep').hide(); - // $("#btnPairDevice").removeClass("disabled"); - $("#tfastep").show(); - $("#pairspinner").hide(); - //$('#btnUnpair').hide(); - //$('#loginpin').show(); + $('#tapkeybackup').bind('touchstart', function () { + Engine.Device.getStorageItem("ok_disp", function (res) { - $("#validateemail").hide(); - $("#valemailerror").hide(); - $("#btnEmailValidate").prop('disabled', false); + if (!Engine.m_offlineKeyBackup || res == "1") { + sendmode = "viewkey" + $("#mainWallet").hide(); + $("#seccheck").hide(); + $("#pinconfirm").addClass("blind"); + $("#pinconfdets").hide(); + $("#pinconfirm").show(); - } } }); - - //call to verify token - }); + $('#tapsecwarning').bind('touchstart', function () { - //after the user has chosen their PIN - //register the pin with the server - - - $("#emailresend").click(function () { - Engine.sendWelcomeDetails(function (err, result) { + $("#seccheck").addClass("invis"); + $("#seccheck").removeClass("slideUp"); + $("#seccheck").hide(); - if (!err) { + $("#secwarning").removeClass("invis"); + $("#secwarning").show(); - $("#emailresendmessage").show(); - $("#emailresend").hide(); - //email has been resent, please check your email - } + $("#secphrasepnl").hide(); + $("#tapsecok").show(); + $("#tapseccancel").text("Cancel"); - }); + $("#secwarn").show(); + $("#sechold").hide(); }); - function showTwoFactorQr() { - - $("#twoFactorQr").show(); - $("#2factor1").show(); + $('#tapunlock').bind('touchstart', function () { - Engine.getTwoFactorImg(function (err, twoFASecret) { - var nickname = $("#createWalletStart input#nickname").val(); - var data = "otpauth://totp/Ninki:" + nickname + "?secret=" + twoFASecret + "&issuer=Ninki"; - var options = { text: data, width: 128, height: 128 }; + Engine.Device.getStorageItem("pair", function (res) { - $('#tfarawcode').text(twoFASecret); + if (res == "") { - $('#twoFactorQrImg').text(''); - $('#twoFactorQrImg').qrcode(options); + if (Engine.m_offlineKeyBackup) { + $("#tapsecwarning").show(); + } else { + $("#tapsecwarning").hide(); + } - }); + } else { - } + Engine.m_offlineKeyBackup = true; + Engine.m_settings.EmailVerified = true; + Engine.m_settings.TwoFactor = true; + $("#secheckemail").show(); + $("#sechevemail").hide(); + $("#secheck2fa").show(); + $("#sechev2fa").hide(); + $("#secheckkeys").show(); + $("#secchevkeys").hide(); + $("#tapsecwarning").show(); - //end create wallet area + } + displaySecurityScore(); + $("#seccheck").removeClass("invis"); + //$("#seccheck").addClass("slideUp"); + $("#seccheck").show(); - $("#mainWallet").hide(); + $(".footer").hide(); + $("#settingsheader").hide(); + $("#mainWallet").hide(); - //$("#dashreceive").hide(); - //$("#dashcontact").hide(); + }); - $("#addcontactmodal").hide(); - $('#stdselcu').click(function () { - sendAmount = ''; - $('#stdselunit').text(COINUNIT); + }); - stdAmountConvCoin = true; - if (stdAmountConvCoin) { + $('#tapacclimits').bind('touchstart', function () { - if (COINUNIT == "Bits") { - $("#numcdot").hide(); - } else { + $("#settingsacclimits").removeClass("invis"); + //$("#seccheck").addClass("slideUp"); + $("#settingsacclimits").show(); - $("#numcdot").show(); + $(".footer").hide(); + $("#settingsheader").hide(); + $("#mainWallet").hide(); - } + //get account limits from settings - } else { + Engine.getLimitStatus(function (err, limits) { - $("#numcdot").show(); + $("#limitdaily").text(formatCoinAmount(convertFromSatoshis(limits.DailyTransactionLimit, COINUNIT)) + ' ' + COINUNIT); + $("#limitsingle").text(formatCoinAmount(convertFromSatoshis(limits.SingleTransactionLimit, COINUNIT)) + ' ' + COINUNIT); + $("#limittranday").text(limits.NoOfTransactionsPerDay); + $("#limittranhour").text(limits.NoOfTransactionsPerHour); - } + //+ " (" + convertFromSatoshis(limits.DailyTransactionLimit - limits.TotalAmount24hr, COINUNIT) + ")" + //+ " (" + (limits.NoOfTransactionsPerDay - limits.No24hr) + ")" + //+ " (" + (limits.NoOfTransactionsPerHour - limits.No1hr) + ")" + }); - updateStdAmount(); }); + $('#tapcloseacclimits').bind('touchstart', function () { - $('#stdsellc').click(function () { - + $("#settingsacclimits").hide(); - sendAmount = ''; + $(".footer").show(); + $("#settingsheader").show(); + $("#mainWallet").show(); - $('#stdselunit').text(Engine.m_settings.LocalCurrency); - stdAmountConvCoin = false; + }); + $('#tapcoinunit').bind('touchstart', function () { - if (stdAmountConvCoin) { + $("#settingscoinunit").removeClass("invis"); + //$("#seccheck").addClass("slideUp"); + $("#settingscoinunit").show(); - if (COINUNIT == "Bits") { + $(".footer").hide(); + $("#settingsheader").hide(); + $("#mainWallet").hide(); - $("#numcdot").hide(); - } else { + }); - $("#numcdot").show(); + $('#tapclosecoinunit').bind('touchstart', function () { - } + $("#settingscoinunit").hide(); - } else { + $(".footer").show(); + $("#settingsheader").show(); + $("#mainWallet").show(); - $("#numcdot").show(); + }); - } + $('#tapcurrency').bind('touchstart', function () { - updateStdAmount(); + $("#settingscurrency").removeClass("invis"); + //$("#seccheck").addClass("slideUp"); + $("#settingscurrency").show(); + $(".footer").hide(); + $("#settingsheader").hide(); + $("#mainWallet").hide(); }); + $('#tapclosecurrency').bind('touchstart', function () { + $("#settingscurrency").hide(); - $('.scoinunit').bind('click', function () { + $(".footer").show(); + $("#settingsheader").show(); + $("#mainWallet").show(); - $('.scoinunit').find("label").html(''); + }); - $(this).find("label").html(''); - var sel = $.trim($(this).text()); - Engine.Device.setStorageItem("coinunit", sel); - Engine.m_settings.CoinUnit = sel; + function displaySecurityScore() { - COINUNIT = sel; - prevtransfeed = -1; - prevNetworkTransCount = -1; - updateUI(); + Engine.Device.getStorageItem("ok_disp", function (res) { - }); + var score = 0; + if (Engine.m_offlineKeyBackup && res != "1") { + score++; + } + if (Engine.m_settings.EmailVerified) { + score++; + } + //replace with chrome app proof + 2fa backup codes + if (Engine.m_settings.TwoFactor) { + score++; + } + var score = Math.round((score / 3) * 100); - $('.sccy').bind('click', function () { + $("#securityscore").easyPieChart(); - $('.sccy').find("label").html(''); + $('#securityscore').data('easyPieChart').update(score); - $(this).find("label").html(''); + }); - var sel = $.trim($(this).text()); + } - Engine.Device.setStorageItem("currency", sel); + $('#tapseccancel').bind('touchstart', function () { - Engine.m_settings.LocalCurrency = sel; - updatePrice(); + $("#secwarning").addClass("invis"); + $("#secwarning").removeClass("slideUp"); + $("#secwarning").hide(); - }); + $("#seccheck").removeClass("invis"); + $("#seccheck").show(); - $('#tapunlock').bind('touchstart', function () { + // $("#mainWallet").show(); + // $("#settingsheader").show(); + // $(".footer").show(); + + - $("#settingsheader").hide(); - $(".footer").hide(); - $("#secwarning").removeClass("invis"); - $("#secwarning").addClass("slideUp"); - $("#secwarning").show(); $("#secphrasepnl").hide(); - $("#tapsecok").show(); - $("#tapseccancel").text("Cancel"); - $("#mainWallet").hide(); - $("#secwarn").show(); - $("#sechold").hide(); + $("#secphrase").text(''); }); - $('#tapseccancel').bind('touchstart', function () { + $('#btnCloseSecList').bind('touchstart', function () { - $("#secwarning").addClass("invis"); - $("#secwarning").removeClass("slideUp"); - $("#secwarning").hide(); + //$("#seccheck").addClass("invis"); + $("#seccheck").removeClass("slideUp"); + $("#seccheck").hide(); $("#mainWallet").show(); $("#settingsheader").show(); $(".footer").show(); @@ -1423,6 +2181,7 @@ function UI() { + $('#tapsecok').bind('touchstart', function () { @@ -1431,6 +2190,7 @@ function UI() { $("#secwarning").hide(); $("#pinconfdets").hide(); + $("#pinconfirm").addClass("blind"); $("#pinconfirm").show(); }); @@ -1443,6 +2203,7 @@ function UI() { }); + function displayKey() { var pin = $('#sendstdpin').val(); @@ -1451,23 +2212,91 @@ function UI() { if (!err) { - Engine.getHotHash(ekey.DeviceKey, function (err, hotHash) { + Engine.getHotHash('', function (err, hotHash) { if (!err) { + var bip39 = new BIP39(); // 'en' is the default language - var hotmnem = bip39.entropyToMnemonic(hotHash); - $("#secphrase").text(hotmnem); - $("#pinconfirm").hide(); - $("#pinconfdets").show(); + var hotmnem = bip39.entropyToMnemonic(Bitcoin.convert.bytesToHex(Engine.m_onlineKey)); + + Engine.zeroDeviceKey(); + + Engine.Device.getStorageItem("ok_disp", function (res) { + + if (!Engine.m_offlineKeyBackup || res == "1") { + + $("#hotWalletPhrase").text(hotmnem); + + hotmnem = []; - $("#secwarn").hide(); - $("#sechold").show(); + $("#seccheck").addClass("invis"); + $("#seccheck").removeClass("slideUp"); + $("#seccheck").hide(); + $("#showPhrases").show(); + $(".next").hide(); + + $("#no2famessage").hide(); + + if (!Engine.m_offlineKeyBackup) { + + step = 1; + $("#coldkeystep").show(); + $(".previous").hide(); + $("#coldfinalwarning").hide(); + + $("#coldcheckicon").addClass("fa-square-o"); + $("#coldcheckicon").removeClass("fa-check-square"); + $("#coldcheckicon").removeClass("text-success"); + + Engine.recoverColdKeyForMobile(function (coldkey) { + + var bip39 = new BIP39(); // 'en' is the default language + var coldmnem = bip39.entropyToMnemonic(Bitcoin.convert.bytesToHex(coldkey)); + + Engine.zeroByteArray(coldkey); + Engine.zeroOnlineKey(); + + $("#coldWalletPhrase").text(coldmnem); + + coldmnem = []; + + }); + + + + } else { + + step = 2; + $("#hotkeystep").show(); + $("#hotcheckicon").addClass("fa-square-o"); + $("#hotcheckicon").removeClass("fa-check-square"); + $("#hotcheckicon").removeClass("text-success"); + + } + + //temporary storage for testing + + } else { + + $("#tapsecok").hide(); + $("#tapseccancel").text("Close"); + $("#secphrase").text(hotmnem); + $("#secwarn").hide(); + $("#sechold").show(); + $("#secwarning").removeClass("slideUp"); + $("#secwarning").show(); + $("#secphrasepnl").show(); + $("#secphrase").hide(); + + } + + }); + + + $("#pinconfirm").hide(); + $("#pinconfdets").show(); - $("#secwarning").removeClass("slideUp"); - $("#secwarning").show(); - $("#secphrasepnl").show(); - $("#secphrase").hide(); window.resetPin(); $("#paddelconf").hide(); @@ -1475,22 +2304,22 @@ function UI() { $('.numdone').attr("style", "background-color:white"); $("#sendstdpin").val(''); - - $("#tapsecok").hide(); - $("#tapseccancel").text("Close"); - } }); + + } else { + window.resetPin(); + + $("#paddelconf").hide(); + $('.numdone').attr("style", "background-color:white"); $("#sendstdpin").val(''); - pintaps = 0; - prevpin = ''; if (ekey.substring(0, 6) == "ErrPIN") { @@ -1499,6 +2328,21 @@ function UI() { $("#pinconfcount").effect("shake"); + } else if (ekey.substring(0, 10) == "ErrBlocked") { + + //if the login attempt has been blocked + //display a countdown to the user + //indicating when they can next attempt to + //login + + var seconds = ekey.substring(11, ekey.length) * 1.0; + + setCountdown(seconds); + + // later on this timer may be stopped + + $("#pincounter").effect("shake"); + } else { bootbox.alert(ekey); @@ -1517,10 +2361,12 @@ function UI() { $('#copyvalphrase').bind('touchstart', function () { if (window.cordova) { - cordova.plugins.clipboard.copy($('#codeForFriend').text(), function () { + + cordova.plugins.clipboard.copy($('#hdcodeForFriend').val(), function () { $('#valcodepanel').addClass("backgroundAnimated"); setTimeout(function () { + $('#valcodepanel').removeClass("backgroundAnimated"); }, 1000); @@ -1530,6 +2376,12 @@ function UI() { console.log("copy error"); }); + } else { + $('#valcodepanel').addClass("backgroundAnimated"); + setTimeout(function () { + + $('#valcodepanel').removeClass("backgroundAnimated"); + }, 1000); } }); @@ -1711,6 +2563,8 @@ function UI() { + + window.resetPin = function () { pintaps = 0; @@ -1804,6 +2658,7 @@ function UI() { loginPIN(); } + //only if fail } @@ -1920,10 +2775,17 @@ function UI() { displayKey(); + } else if (sendmode == 'deviceauth') { + + authDevice(); + } + + + //only if fail } @@ -2144,11 +3006,11 @@ function UI() { $("#dashprofile").show(); - $("#dashsend").addClass("invis"); - $("#dashsend").removeClass("slideUp"); + //$("#dashsend").addClass("invis"); + //$("#dashsend").removeClass("slideUp"); $("#dashsend").hide(); - $("#dashreceive").addClass("invis"); + // $("#dashreceive").addClass("invis"); $("#dashreceive").removeClass("slideUp"); $("#dashreceive").hide(); @@ -2191,8 +3053,8 @@ function UI() { $('#paddelconf').hide(); $("#pinconfirm").hide(); - $("#dashsend").addClass("invis"); - $("#dashsend").removeClass("slideUp"); + //$("#dashsend").addClass("invis"); + //$("#dashsend").removeClass("slideUp"); $("#dashsend").hide(); $("#dashsendamt").show(); @@ -2250,6 +3112,8 @@ function UI() { clearInterval(window.updateUIInterval); } + $("#pinconfdets").show(); + $("#pinconfirm").removeClass("blind"); $("#pinconfirm").show(); if (paddr.label) { @@ -2272,8 +3136,8 @@ function UI() { $("#sendstds2amt").text(formatCoinAmount($("#hdamount").val()) + ' ' + COINUNIT); - $("#dashsend").addClass("invis"); - $("#dashsend").removeClass("slideUp"); + //$("#dashsend").addClass("invis"); + //$("#dashsend").removeClass("slideUp"); $("#dashsend").hide(); @@ -2294,8 +3158,8 @@ function UI() { updateStdAmount(); - $("#dashsend").addClass("invis"); - $("#dashsend").removeClass("slideUp"); + //$("#dashsend").addClass("invis"); + //$("#dashsend").removeClass("slideUp"); $("#dashsend").hide(); $("#dashsendamt").show(); @@ -2307,8 +3171,8 @@ function UI() { } else { - $("#dashsend").addClass("invis"); - $("#dashsend").removeClass("slideUp"); + //$("#dashsend").addClass("invis"); + //$("#dashsend").removeClass("slideUp"); $("#dashsend").hide(); $("#dashsendamt").show(); @@ -2444,12 +3308,20 @@ function UI() { } else if (sendmode == "net") { closeSendNet(); } else if (sendmode == "viewkey") { - $("#settingsheader").show(); - $(".footer").show(); + + + $("#seccheck").removeClass("invis"); + $("#seccheck").show(); + $("#pinconfirm").hide(); + //$("#mainWallet").show(); + + } else if (sendmode == "deviceauth") { + $("#pinconfirm").hide(); - $("#mainWallet").show(); + $("#desktopintro").show(); - } else { + } + else { $("#pinconfirm").hide(); $("#invoices").show(); } @@ -2514,6 +3386,8 @@ function UI() { clearInterval(window.updateUIInterval); } + $("#pinconfdets").show(); + $("#pinconfirm").removeClass("blind"); $("#pinconfirm").show(); if (sendmode == 'std') { @@ -2529,8 +3403,8 @@ function UI() { $("#sendstds2amt").text(formatCoinAmount($("#hdamount").val()) + ' ' + COINUNIT); - $("#dashsend").addClass("invis"); - $("#dashsend").removeClass("slideUp"); + //$("#dashsend").addClass("invis"); + //$("#dashsend").removeClass("slideUp"); $("#dashsend").hide(); @@ -2574,8 +3448,8 @@ function UI() { $("#sendsubheading").text("send to " + SELECTEDFRIEND); $("#btnStdSndDone").hide(); - $("#dashsend").addClass("invis"); - $("#dashsend").removeClass("slideUp"); + //$("#dashsend").addClass("invis"); + //$("#dashsend").removeClass("slideUp"); $("#dashsend").hide(); $("#friendheader").hide(); @@ -2658,6 +3532,25 @@ function UI() { + Engine.Device.getStorageItem("ok_disp", function (res) { + + if (res == "1") { + + bootbox.dialog({ title: '
 Security Warning
', message: 'Backup your keys in Settings > Security Checklist.

Currently, if you lose access to this device, you will lose access to any bitcoins you have stored in your wallet.', closeButton: false, buttons: { + main: { + label: "I Understand", + className: "btn-warning", + callback: function () { + + } + } + } + }); + } + + }); + + //dashreceive $("#dashheader").hide(); $("#dashprofile").hide(); @@ -2666,7 +3559,7 @@ function UI() { $("#dashreceive").show(); $("#dashreceive").removeClass("invis"); - $("#dashreceive").addClass("slideUp"); + //$("#dashreceive").addClass("slideUp"); $(".footer").hide(); @@ -2692,6 +3585,7 @@ function UI() { $("#taprequest").bind('touchstart', function () { + window.setKeyboardScroll(); $("#friend").css("border-color", "#ccc"); $("#addcontactalert").hide(); @@ -2714,9 +3608,9 @@ function UI() { $("#pairdeviceblob").change(function () { + isPairing = true; - Engine.Device.deleteStorageItem("dataCache"); - //Engine.Device.deleteStorageItem("balance"); + deleteDeviceStorage(); if ($("#pairdeviceblob").val().length > 10) { @@ -2729,10 +3623,12 @@ function UI() { $("#loginpin").hide(); $("#pairstep1").hide(); - + $("#createWalletStart").hide(); + $("#pairDevice").show(); $("#pairstep2").show(); $("#pairpwd").focus(); + } else { bootbox.alert("There was a pairing error, please try again."); @@ -2749,55 +3645,12 @@ function UI() { }); - $("#btnUnpair").click(function () { - - bootbox.confirm("Are you sure?", function (result) { - - if (result) { - - Engine.Device.getStorageItem("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) { - - //call to server - location.reload(); - - }); - - //always destroy locally - Engine.Device.deleteStorageItem("dataCache"); - //Engine.Device.deleteStorageItem("balance"); - Engine.Device.deleteStorageItem("ninki_rem"); - Engine.Device.deleteStorageItem("ninki_p"); - Engine.Device.deleteStorageItem("ninki_reg"); - Engine.Device.deleteStorageItem("ninki_h"); - Engine.Device.deleteStorageItem("guid"); - Engine.Device.deleteStorageItem("coinunit"); - Engine.Device.deleteStorageItem("currency"); - - }); - - } - - }); - - }); - $("#btnPairDevice").bind('touchstart', function () { $("#pairpwd").blur(); - + isCreate = false; + isPairing = true; pairDevice(); @@ -2837,7 +3690,26 @@ function UI() { $("#sendfriendprog").hide(); - $("#btnVerify").bind('touchstart', function () { + + $("#pastevalidate").bind('touchstart', function () { + + if (window.cordova) { + + cordova.plugins.clipboard.paste(function (text) { + + $("#txtCode").val(text); + $("#txtCode").trigger('change'); + + }, function () { + + //console.log("paste error"); + + }); + } + + }); + + $("#txtCode").bind('change', function () { var code = $("#txtCode").val(); @@ -2845,11 +3717,15 @@ function UI() { $("#validatefail").hide(); $("#validatesuccess").hide(); - var bip39 = new BIP39(); - code = bip39.mnemonicToHex(code); + + if (code.length > 40) { + var bip39 = new BIP39(); + code = bip39.mnemonicToHex(code); + } if (code.length != 40) { - $("#txtCode").css("border-color", "#ffaaaa"); + + bootbox.alert("Invalid verification code"); return; } @@ -2886,8 +3762,11 @@ function UI() { } else { - $("#btnVerify").removeClass("disabled"); - $("#validatefail").show(); + + //$("#btnVerify").removeClass("disabled"); + //$("#validatefail").show(); + bootbox.alert("Invalid verification code"); + $("#validateformmess").text(''); } }, function (message) { @@ -2930,9 +3809,10 @@ function UI() { } else { - $("#btnVerify").removeClass("disabled"); + + bootbox.alert("Invalid verification code"); $("#validateformmess").text(''); - $("#validatefail").show(); + } } @@ -2990,9 +3870,12 @@ function UI() { clearInterval(window.updateUIInterval); } + $("#pinconfdets").show(); + $("#pinconfirm").removeClass("blind"); $("#pinconfirm").show(); + }); $("#btnrejectinvoice").bind('touchstart', function () { @@ -3413,7 +4296,9 @@ function UI() { setTimeout(function () { - Engine.sendTransaction('invoice', friend, '', amount, ekey.DeviceKey, function (err, transactionid) { + Engine.sendTransaction('invoice', friend, '', amount, "", function (err, transactionid) { + + Engine.zeroDeviceKey(); if (!err) { @@ -3469,11 +4354,20 @@ function UI() { $('#textMessageSendStd').text('Transaction Failed: Insufficient funds'); } + + $('.numdone').attr("style", "background-color:white"); + + $("#sendstdpin").val(''); + pintaps = 0; + prevpin = ''; + //return to send screen setTimeout(function () { $("#btnStdSndDone").show(); }, 100); + + } }, function (message, progress) { @@ -3499,6 +4393,8 @@ function UI() { pintaps = 0; prevpin = ''; + window.resetPin(); + if (ekey.substring(0, 6) == "ErrPIN") { @@ -3506,6 +4402,21 @@ function UI() { $("#pinconfcount").effect("shake"); + } else if (ekey.substring(0, 10) == "ErrBlocked") { + + //if the login attempt has been blocked + //display a countdown to the user + //indicating when they can next attempt to + //login + + var seconds = ekey.substring(11, ekey.length) * 1.0; + + setCountdown(seconds); + + // later on this timer may be stopped + + $("#pincounter").effect("shake"); + } else { bootbox.alert(ekey); @@ -3603,11 +4514,11 @@ function UI() { function initialiseDashboardFromCache(callback) { - $("#dashsend").addClass("invis"); - $("#dashsend").removeClass("slideUp"); + //$("#dashsend").addClass("invis"); + //$("#dashsend").removeClass("slideUp"); $("#dashsend").hide(); - $("#dashreceive").addClass("invis"); + // $("#dashreceive").addClass("invis"); $("#dashreceive").removeClass("slideUp"); $("#dashreceive").hide(); @@ -3633,7 +4544,33 @@ function UI() { COINUNIT = Engine.m_settings.CoinUnit; $("#mynickname").text(Engine.m_nickname); - $("#codeForFriend").text(Engine.m_fingerprint); + + var fprint = Engine.m_pubKey.primaryKey.fingerprint; + $("#hdcodeForFriend").val(fprint); + $("#codeForFriend").text(fprint); + + // var ffprint = ''; + // for (var i = 0; i < fprint.length; i++) { + // if (i % 4 == 0) { + // ffprint = ffprint + ' ' + fprint[i]; + // } else { + // ffprint = ffprint + fprint[i]; + // } + + // if (i == 19) { + + // $("#codeForFriend").text(ffprint); + // ffprint = ""; + // } + // if (i == 39) { + // $("#codeForFriend2").text(ffprint); + // } + // } + + + + + //prep the network tab $("#networklist").show(); @@ -3670,11 +4607,11 @@ function UI() { function initialiseDashboard(callback) { - $("#dashsend").addClass("invis"); - $("#dashsend").removeClass("slideUp"); + //$("#dashsend").addClass("invis"); + //$("#dashsend").removeClass("slideUp"); $("#dashsend").hide(); - $("#dashreceive").addClass("invis"); + // $("#dashreceive").addClass("invis"); $("#dashreceive").removeClass("slideUp"); $("#dashreceive").hide(); @@ -3713,7 +4650,29 @@ function UI() { $("#imgProfile").attr("src", imageSrc); $("#imgtoprightprofile").attr("src", imageSrcSmall); - $("#codeForFriend").text(Engine.m_fingerprint); + var fprint = Engine.m_pubKey.primaryKey.fingerprint; + + $("#codeForFriend").text(fprint); + $("#hdcodeForFriend").val(fprint); + + + // var ffprint = ''; + // for (var i = 0; i < fprint.length; i++) { + // if (i % 4 == 0) { + // ffprint = ffprint + ' ' + fprint[i]; + // } else { + // ffprint = ffprint + fprint[i]; + // } + + // if (i == 19) { + + // $("#codeForFriend").text(ffprint); + // ffprint = ""; + // } + // if (i == 39) { + // $("#codeForFriend2").text(ffprint); + // } + // } //prep the network tab @@ -4264,179 +5223,178 @@ function UI() { - if (friends.length == 0) { - $('#welcomenet').show(); + if (friends.length == 0 && lastNoOfFriends == 0) { + //$('#welcomenet').show(); } else { $('#welcomenet').hide(); - } + //$("#nfriends").text(friends.length); - //$("#nfriends").text(friends.length); + if (friends.length > lastNoOfFriends) { - if (friends.length > lastNoOfFriends) { + lastNoOfFriends = friends.length; - lastNoOfFriends = friends.length; + FRIENDSLIST = {}; - FRIENDSLIST = {}; - - for (var i = 0; i < friends.length; i++) { - FRIENDSLIST[friends[i].userName] = friends[i]; - } + for (var i = 0; i < friends.length; i++) { + FRIENDSLIST[friends[i].userName] = friends[i]; + } - //if selected friend is not isend and isreceive - //then find in list and update + //if selected friend is not isend and isreceive + //then find in list and update - // if (selectedFriend != null) { + // if (selectedFriend != null) { - // if (!selectedFriend.ICanSend || !selectedFriend.ICanReceive) { - // selectedFriend = FRIENDSLIST[selectedFriend.userName]; - // updateSelectedFriend(); - // } + // if (!selectedFriend.ICanSend || !selectedFriend.ICanReceive) { + // selectedFriend = FRIENDSLIST[selectedFriend.userName]; + // updateSelectedFriend(); + // } - // } + // } - $("#nfriends").text(friends.length); - $("#myfriends").text(''); + $("#nfriends").text(friends.length); + $("#myfriends").text(''); - var grouptemplate = ''; + var grouptemplate = ''; - var friendsgroup = _.groupBy(friends, function (item) { return item.category; }); + var friendsgroup = _.groupBy(friends, function (item) { return item.category; }); - grouptemplate += '
'; + grouptemplate += '
'; - var k = 0; - var g = 1; - for (var key in friendsgroup) { + var k = 0; + var g = 1; + for (var key in friendsgroup) { - friends = friendsgroup[key]; + friends = friendsgroup[key]; - grouptemplate += '
'; - grouptemplate += '
'; - grouptemplate += ''; - grouptemplate += _.escape(key); - grouptemplate += ''; - grouptemplate += '
'; - grouptemplate += '
'; + grouptemplate += '
'; + grouptemplate += '
'; + grouptemplate += ''; + grouptemplate += _.escape(key); + grouptemplate += ''; + grouptemplate += '
'; + grouptemplate += '
'; - for (var i = 0; i < friendsgroup[key].length; i++) { + for (var i = 0; i < friendsgroup[key].length; i++) { - var frnd = FRIENDSLIST[friends[i].userName]; + var frnd = FRIENDSLIST[friends[i].userName]; - var length = frnd.userName.length; - if (length > 20) { - length = 20; - } + var length = frnd.userName.length; + if (length > 20) { + length = 20; + } - var imageSrc = "images/avatar/64px/Avatar-" + pad(length) + ".png"; + var imageSrc = "images/avatar/64px/Avatar-" + pad(length) + ".png"; - if (frnd.profileImage != '') { - imageSrc = "https://ninkip2p.imgix.net/" + _.escape(frnd.profileImage) + "?crop=faces&fit=crop&h=256&w=256&mask=ellipse&border=1,d0d0d0"; - imageSrcSmall = "https://ninkip2p.imgix.net/" + _.escape(frnd.profileImage) + "?crop=faces&fit=crop&h=128&w=128&mask=ellipse&border=1,d0d0d0"; - } + if (frnd.profileImage != '') { + imageSrc = "https://ninkip2p.imgix.net/" + _.escape(frnd.profileImage) + "?crop=faces&fit=crop&h=256&w=256&mask=ellipse&border=1,d0d0d0"; + imageSrcSmall = "https://ninkip2p.imgix.net/" + _.escape(frnd.profileImage) + "?crop=faces&fit=crop&h=128&w=128&mask=ellipse&border=1,d0d0d0"; + } - var template = ''; - g++; - } - grouptemplate += '
'; + grouptemplate += '
'; + grouptemplate += '
'; + g++; + } - $("#myfriends").html(grouptemplate); + grouptemplate += '
'; + $("#myfriends").html(grouptemplate); - $('#myfriends #accordion2').on('touchstart.collapse.data-api', '[data-toggle=collapse]', function (e) { - var $this = $(this); - $this.click(); + $('#myfriends #accordion2').on('touchstart.collapse.data-api', '[data-toggle=collapse]', function (e) { + var $this = $(this); - //href, target = $this.attr('data-target') || e.preventDefault() || (href = $this.attr('href')) //strip for ie7 - //, - //option = $(target).data('collapse') ? 'show' : $this.data() - //$(target).collapse(option) - }); + $this.click(); + //href, target = $this.attr('data-target') || e.preventDefault() || (href = $this.attr('href')) //strip for ie7 + //, + //option = $(target).data('collapse') ? 'show' : $this.data() + //$(target).collapse(option) + }); - var k = 0; - var g = 1; - for (var key in friendsgroup) { - friends = friendsgroup[key]; - for (var i = 0; i < friendsgroup[key].length; i++) { + var k = 0; + var g = 1; + for (var key in friendsgroup) { friends = friendsgroup[key]; + for (var i = 0; i < friendsgroup[key].length; i++) { + friends = friendsgroup[key]; - //var btnfriend = $("#myfriends #friend" + k).get(); - //var hammertime = new Hammer(btnfriend[0]); - $("#myfriends #friend" + k).hammer(null).bind("tap", { userName: friends[i].userName }, function (ev) { + //var btnfriend = $("#myfriends #friend" + k).get(); + //var hammertime = new Hammer(btnfriend[0]); + $("#myfriends #friend" + k).hammer(null).bind("tap", { userName: friends[i].userName }, function (ev) { - if (!scrollingnetlist) { - SELECTEDFRIEND = ev.data.userName; - selectedFriend = FRIENDSLIST[ev.data.userName]; - prevNetworkTransCount = 0; + if (!scrollingnetlist) { - networkpagestate = "friend"; - friendpagestate = "send"; - $("#networklistheader").hide(); - $("#friendheader").show(); - $("#pnlfriend").show(); + SELECTEDFRIEND = ev.data.userName; + selectedFriend = FRIENDSLIST[ev.data.userName]; + prevNetworkTransCount = 0; - $('#netpayfeed').html(''); - $("#networkpayments").show(); - $("#networklist").hide(); + networkpagestate = "friend"; + friendpagestate = "send"; + $("#networklistheader").hide(); + $("#friendheader").show(); + $("#pnlfriend").show(); - $("#pnlfriendinv").hide(); + $('#netpayfeed').html(''); + $("#networkpayments").show(); + $("#networklist").hide(); - //window.scrollTo(0, 0); + $("#pnlfriendinv").hide(); - updateSelectedFriend(); + //window.scrollTo(0, 0); - } + updateSelectedFriend(); - }); + } + + }); - ////console.log("added click " + k + " for " + friends[i].userName); + ////console.log("added click " + k + " for " + friends[i].userName); - k++; + k++; + } + g++; } - g++; - } + } } if (callback) { @@ -4638,7 +5596,7 @@ function UI() { if (friends.length > 0) { $("#dashrequests").show(); - + $('#welcomenet').hide(); } else { $("#dashrequests").hide(); } @@ -4649,6 +5607,7 @@ function UI() { lastNoOfFriendsReq = friends.length; + $("#friendreq").text(''); for (var i = 0; i < friends.length; i++) { @@ -4728,7 +5687,7 @@ function UI() { $('#btnContactReject').bind('touchstart', function () { - rejectFriend(event.data.userName, function (err, res) { + rejectFriend(selectedFriendRequest, function (err, res) { if (!err) { @@ -4749,6 +5708,163 @@ function UI() { + var prevtimeline = -1; + var timelineCache = []; + var allTimeline = []; + + function showTransactionFeedNew(callback) { + + Engine.getTimeline(function (err, timeline) { + + + if (!err) { + + if (timeline.length == 0) { + $('#welcometran').show(); + $('#nowelcome').hide(); + + } else { + $('#welcometran').hide(); + $('#nowelcome').show(); + } + + + allTimeline = timeline; + timelineCache = timeline; + + if (timeline.length != prevtimeline) { + + for (var i = 0; i < allTimeline.length; i++) { + var d1 = new Date(allTimeline[i].TransDateTime); + allTimeline[i].JsDate = new Date(timeline[i].TimelineDate.match(/\d+/)[0] * 1); + } + + prevtimeline = timeline.length; + + $('#transfeed').empty(); + + var template = ''; + + for (var i = 0; i < timeline.length && i < 51; i++) { + + var length = timeline[i].UserName.length; + if (length > 20) { + length = 20; + } + + var imageSrcSmall = "images/avatar/64px/Avatar-" + pad(length) + ".png"; + + if (timeline[i].UserName != 'External') { + if (timeline[i].UserNameImage) { + if (timeline[i].UserNameImage != '') { + imageSrcSmall = "https://ninkip2p.imgix.net/" + _.escape(timeline[i].UserNameImage) + "?crop=faces&fit=crop&h=128&w=128&mask=ellipse&border=1,d0d0d0"; + } + } + } + + var amountLabel = ""; + var friendLabel = ""; + + if (timeline[i].TimelineType == 'TS') { + amountLabel = "sent " + formatCoinAmount(convertFromSatoshis(timeline[i].Amount, COINUNIT)) + " " + _.escape(COINUNIT); + //amountLabel = " sent 1 BTC"; + friendLabel = "to " + _.escape(timeline[i].UserName); + } + + if (timeline[i].TimelineType == 'TR') { + amountLabel = "received " + formatCoinAmount(convertFromSatoshis(timeline[i].Amount, COINUNIT)) + " " + _.escape(COINUNIT); + //amountLabel = " received 1 BTC"; + friendLabel = "from " + _.escape(timeline[i].UserName); + } + + + if (timeline[i].TimelineType == "IS") { + amountLabel = "invoice"; + friendLabel = "sent to " + _.escape(timeline[i].UserName); + } + + if (timeline[i].TimelineType == "IR") { + amountLabel = "invoice"; + friendLabel = "from " + _.escape(timeline[i].UserName); + } + + if (timeline[i].TimelineType == "FRS") { + amountLabel = "contact request"; + friendLabel = "sent to " + _.escape(timeline[i].UserName); + } + + if (timeline[i].TimelineType == "FRR") { + amountLabel = "contact request"; + friendLabel = "from " + _.escape(timeline[i].UserName); + } + + var trdate = new Date(timeline[i].TimelineDate.match(/\d+/)[0] * 1); + var timeLabel = prettydate.format(trdate); + + template += ''; + template += ''; + template += '...'; + + template += ''; + template += ''; + template += ''; + template += amountLabel; + template += ''; + + template += ''; + template += '
'; + template += timeLabel; + template += '
'; + template += '
'; + + template += ''; + template += ''; + + if (timeline[i].TimelineType == 'TS' || timeline[i].TimelineType == 'TR') { + template += '
'; + if (timeline[i].Confirmations < 6) { + template += ''; + template += _.escape(timeline[i].Confirmations); + template += ''; + } + template += '
'; + } + + template += ''; + template += friendLabel; + template += ''; + template += '
'; + template += '
'; + + } + + $('#transfeed').html(template); + + } + + + //cache main screen items + //transaction list / price / balance + + if (callback) { + return callback(err, "ok"); + } + + } else { + + if (callback) { + return callback(err, transactions); + } + } + + }); + + } + + + var prevtransfeed = -1; var transactionCache = []; @@ -4758,10 +5874,14 @@ function UI() { if (!err) { + if (transactions.length == 0) { $('#welcometran').show(); + $('#nowelcome').hide(); + } else { $('#welcometran').hide(); + $('#nowelcome').show(); } @@ -4792,7 +5912,7 @@ function UI() { length = 20; } - var imageSrcSmall = "images/avatar/32px/Avatar-" + pad(length) + ".png"; + var imageSrcSmall = "images/avatar/64px/Avatar-" + pad(length) + ".png"; if (transactions[i].UserName != 'External') { if (transactions[i].UserNameImage) { @@ -4819,7 +5939,7 @@ function UI() { var trdate = new Date(transactions[i].TransDateTime.match(/\d+/)[0] * 1); var timeLabel = prettydate.format(trdate); - template += ''; + template += ''; template += ''; template += '' + _.escape(address) + ''); - - prevtransfeed = -1; showTransactionFeed(); @@ -5385,6 +6520,21 @@ function UI() { $("#pinconfcount").effect("shake"); + } else if (ekey.substring(0, 10) == "ErrBlocked") { + + //if the login attempt has been blocked + //display a countdown to the user + //indicating when they can next attempt to + //login + + var seconds = ekey.substring(11, ekey.length) * 1.0; + + setCountdown(seconds); + + // later on this timer may be stopped + + $("#pincounter").effect("shake"); + } else { bootbox.alert(ekey); @@ -5687,7 +6837,7 @@ function UI() { $("#acceptcontactprognum").text('100%'); $("#acceptcontactprog").width('100%'); selectedFriendRequest = ''; - },500); + }, 500); updateFriendRequests(function (err, result) { @@ -6306,11 +7456,15 @@ function UI() { } - function rejectFriend(username) { + function rejectFriend(username, callback) { Engine.rejectFriendRequest(username, function (err, result) { - updateFriendRequests(); + if (!err) { + updateFriendRequests(); + } + + return callback(err, result); }); } @@ -6411,6 +7565,7 @@ function UI() { //is the app previosuly intialised + if (Engine.m_appInitialised) { //if so simply change the UI state @@ -6470,20 +7625,24 @@ function UI() { Engine.m_settings.EmailVerified = settingsObject.EmailVerified; - if (!Engine.m_settings.EmailVerified) { - - - - $(".footer").hide(); - $('#dashheader').hide(); - $("#mainWallet").hide(); - $("#emailstep").show(); - - app.isScanning = true; + if (Engine.m_settings.TwoFactor) { + $("#secheck2fa").show(); + $("#sechev2fa").hide(); + } else { + $("#secheck2fa").hide(); + $("#sechev2fa").show(); + } + if (Engine.m_settings.EmailVerified) { + $("#secheckemail").show(); + $("#sechevemail").hide(); + } else { + $("#secheckemail").hide(); + $("#sechevemail").show(); } + } }); @@ -6496,6 +7655,46 @@ function UI() { Engine.m_profileImage = data.ProfileImage; Engine.m_statusText = data.Status; + m_this.m_offlineKeyBackup = data.OfflineKeyBackup; + + var currentBalanceInSatoshis = convertToSatoshis(currentBalance, COINUNIT); + + + Engine.Device.getStorageItem("ok_disp", function (res) { + + if (res == "1") { + + if (currentBalanceInSatoshis >= 2500000) { + + bootbox.dialog({ title: '
 Security Warning
', message: 'Backup your keys in Settings > Security Checklist.

Currently, if you lose access to this device, you will lose access to any bitcoins you have stored in your wallet.', closeButton: false, buttons: { + main: { + label: "I Understand", + className: "btn-warning", + callback: function () { + + } + } + } + }); + + } + + } + + }); + + Engine.Device.getStorageItem("ok_disp", function (res) { + + if (Engine.m_offlineKeyBackup && res == "") { + $("#secheckkeys").show(); + $("#secchevkeys").hide(); + + } else { + $("#secheckkeys").hide(); + $("#secchevkeys").show(); + } + + }); updateProfile(); @@ -6525,268 +7724,277 @@ function UI() { cordova.plugins.Keyboard.disableScroll(true); } - //first check for a cache - //this should be there after the first time a packet is intialised - Engine.Device.getStorageItem("dataCache", function (res) { - - if (res.length == 0) { + //initialise from cache + Engine.Device.getSecureStorageObject("dataCache", Engine.m_deviceKey, Engine.decrypt, false, function (res) { - //if the app has not been initialised then we need to download the wallet data + if (res != "") { - $("#pairspinner").show(); + Engine.initialize(JSON.parse(res)); + Engine.setSecDeviceKey(); + Engine.zeroDeviceKey(); var target = document.getElementById('pairspinner'); var spinner = new Spinner(spinneropts).spin(target); pinlock = false; + $("#pairspinner").show(); $("#pinspinner").hide(); $('.numdone').attr("style", "background-color:white"); $("#loginpin").hide(); $("#loginpinno").val(''); - $("#paddel").hide(); $("#pinloginmessage").text("Enter your PIN number"); + //initilaise the UI elements + initialiseDashboardFromCache(function () { - //get the encrypted user's password from local storage - Engine.Device.getSecureStorageObject("ninki_p", ekeyv.DeviceKey, Engine.decryptNp, function (result) { + Engine.Device.getStorageItem("price", function (res) { - Engine.setStretchPass(result); + displayPrice(JSON.parse(res)); - //get the encrypted 2fa override token from local storage - Engine.Device.getSecureStorageObject("ninki_rem", ekeyv.DeviceKey, Engine.decryptNp, function (fatoken) { + }); - if (fatoken.length > 0) { + updateUI(); - //use the token to open the wallet - Engine.openWallet(guid, fatoken, function (err, result) { + Engine.m_appInitialised = true; - if (!err) { + $("#isactive").val(1); - //if this is true it means the 2fa token has expired - if (result.TwoFactorOnLogin) { + $("#pairspinner").hide(); - $("#pairspinner").hide(); - $("#loginpinno").val(''); - pinlock = false; - //$("#enterpinalert").show(); - //$("#enterpinalertmessage").text('Token has expired'); + $('#dashboard').show(); + $('#dashheader').show(); - bootbox.alert("Your token has expired. Please repair your device", function () { + $("#mainWallet").show(); + $("#footermode").val(1); + $(".footer").show(); - Engine.Device.deleteStorageItem("ninki_reg"); - Engine.Device.deleteStorageItem("ninki_p"); - Engine.Device.deleteStorageItem("ninki_rem"); - Engine.Device.deleteStorageItem("guid"); + $("#nonlogin").show(); - location.reload(); - }); + if (!(typeof window.app === 'undefined')) { + app.isScanning = false; + } - } else { + //refresh account settings in case they were updated + //in the chrome app + Engine.getAccountSettings(function (err, res) { - //create an encrypted cache to enable quick reloading of app state + if (!err) { - var dataToCache = Engine.serialize(); + //only set the settings we need to refresh for the mobile apps + var settingsObject = JSON.parse(res); - Engine.Device.setSecureStorageObject("dataCache", dataToCache, ekeyv.DeviceKey, Engine.encrypt); + Engine.m_settings.MinersFee = settingsObject.MinersFee; + Engine.m_settings.TwoFactor = settingsObject.TwoFactor; + Engine.m_settings.EmailVerified = settingsObject.EmailVerified; + if (Engine.m_settings.TwoFactor) { + $("#secheck2fa").show(); + $("#sechev2fa").hide(); + } else { + $("#secheck2fa").hide(); + $("#sechev2fa").show(); + } - //initilaise the UI elements - initialiseDashboard(function () { + if (Engine.m_settings.EmailVerified) { + $("#secheckemail").show(); + $("#sechevemail").hide(); + } else { + $("#secheckemail").hide(); + $("#sechevemail").show(); + } - Engine.m_appInitialised = true; - $("#isactive").val(1); - $("#pairspinner").hide(); + //refresh user profile data incase they updated + //it via the chrome app + Engine.getUserProfile(function (err, data) { - $('#dashboard').show(); - $('#dashheader').show(); + data = JSON.parse(data); - $("#mainWallet").show(); - $("#footermode").val(1); - $(".footer").show(); + m_this.m_profileImage = data.ProfileImage; + m_this.m_statusText = data.Status; + m_this.m_offlineKeyBackup = data.OfflineKeyBackup; - $("#nonlogin").show(); + var currentBalanceInSatoshis = convertToSatoshis(currentBalance, COINUNIT); - if (!(typeof window.app === 'undefined')) { - app.isScanning = false; - } + Engine.Device.getStorageItem("ok_disp", function (res) { + if (res == "1") { - }); + if (currentBalanceInSatoshis >= 2500000) { + bootbox.dialog({ title: '
 Security Warning
', message: 'Backup your keys in Settings > Security Checklist.

Currently, if you lose access to this device, you will lose access to any bitcoins you have stored in your wallet.', closeButton: false, buttons: { + main: { + label: "I Understand", + className: "btn-warning", + callback: function () { - } + } + } + } - } else { + }); + + } + } - if (result == "ErrLocked") { + }); - bootbox.alert("Your account is locked. Please unlock your account using the Chrome App"); + Engine.Device.getStorageItem("ok_disp", function (res) { + if (Engine.m_offlineKeyBackup && res == "") { + $("#secheckkeys").show(); + $("#secchevkeys").hide(); } else { + $("#secheckkeys").hide(); + $("#secchevkeys").show(); + } + - bootbox.alert(result); + if (Engine.m_offlineKeyBackup && res == "" && !Engine.m_settings.EmailVerified) { + + $("#sechevemail i").removeClass("text-muted"); + $("#sechevemail i").addClass("text-primary"); } - pinlock = false; + if (Engine.m_offlineKeyBackup && res == "" && Engine.m_settings.EmailVerified) { - $("#pairspinner").hide(); - $('.numdone').attr("style", "background-color:white"); - $("#loginpin").show(); - $("#loginpinno").val(''); - $("#paddel").hide(); - $("#pinloginmessage").text("Enter your PIN number"); + $("#sechev2fa i").removeClass("text-muted"); + $("#sechev2fa i").addClass("text-primary"); - if (!(typeof window.app === 'undefined')) { - app.isScanning = false; } - } + }); + + updateProfile(); }); + } }); }); - } else { - //initialise from cache - Engine.Device.getSecureStorageObject("dataCache", ekeyv.DeviceKey, Engine.decrypt, function (res) { + } else { + //if the app has not been initialised then we need to download the wallet data - if (res != "") { + //test this execution - Engine.initialize(JSON.parse(res)); - var target = document.getElementById('pairspinner'); - var spinner = new Spinner(spinneropts).spin(target); - pinlock = false; + $("#pairspinner").show(); - $("#pairspinner").show(); - $("#pinspinner").hide(); - $('.numdone').attr("style", "background-color:white"); - $("#loginpin").hide(); - $("#loginpinno").val(''); - $("#paddel").hide(); - $("#pinloginmessage").text("Enter your PIN number"); + var target = document.getElementById('pairspinner'); + var spinner = new Spinner(spinneropts).spin(target); + pinlock = false; + $("#pinspinner").hide(); + $('.numdone').attr("style", "background-color:white"); + $("#loginpin").hide(); + $("#loginpinno").val(''); - Engine.Device.getSecureStorageObject("ninki_p", ekeyv.DeviceKey, Engine.decryptNp, function (result) { + $("#paddel").hide(); + $("#pinloginmessage").text("Enter your PIN number"); - //decrypt the password using the encryption key sent from the server - //and set the password - Engine.setStretchPass(result); - //initilaise the UI elements - initialiseDashboardFromCache(function () { + //get the encrypted user's password from local storage + Engine.Device.getSecureStorageObject("ninki_p", Engine.m_deviceKey, Engine.decryptNp, true, function (result) { - Engine.Device.getStorageItem("price", function (res) { + Engine.setStretchPass(result); - displayPrice(JSON.parse(res)); + //get the encrypted 2fa override token from local storage + Engine.Device.getSecureStorageObject("ninki_rem", Engine.m_deviceKey, Engine.decryptNp, true, function (fatoken) { - }); + if (fatoken.length > 0) { + //use the token to open the wallet + Engine.openWallet(guid, Bitcoin.convert.bytesToHex(fatoken), function (err, result) { - updateUI(); + if (!err) { - Engine.m_appInitialised = true; + //if this is true it means the 2fa token has expired + if (result.TwoFactorOnLogin) { + $("#pairspinner").hide(); + $("#loginpinno").val(''); + pinlock = false; + } else { + //create an encrypted cache to enable quick reloading of app state - $("#isactive").val(1); + var dataToCache = Engine.serialize(); - $("#pairspinner").hide(); + Engine.Device.setSecureStorageObject("dataCache", dataToCache, Engine.m_deviceKey, Engine.encrypt); - $('#dashboard').show(); - $('#dashheader').show(); + //initilaise the UI elements + initialiseDashboard(function () { - $("#mainWallet").show(); - $("#footermode").val(1); - $(".footer").show(); - $("#nonlogin").show(); + Engine.m_appInitialised = true; + $("#isactive").val(1); + $("#pairspinner").hide(); + $('#dashboard').show(); + $('#dashheader').show(); + $("#mainWallet").show(); + $("#footermode").val(1); + $(".footer").show(); + $("#nonlogin").show(); - if (!(typeof window.app === 'undefined')) { - app.isScanning = false; - } + if (!(typeof window.app === 'undefined')) { + app.isScanning = false; + } - //refresh account settings in case they were updated - //in the chrome app - Engine.getAccountSettings(function (err, res) { - if (!err) { + }); - //only set the settings we need to refresh for the mobile apps - var settingsObject = JSON.parse(res); - Engine.m_settings.MinersFee = settingsObject.MinersFee; - Engine.m_settings.TwoFactor = settingsObject.TwoFactor; - Engine.m_settings.EmailVerified = settingsObject.EmailVerified; + } - if (!Engine.m_settings.EmailVerified) { + } else { - $(".footer").hide(); - $('#dashheader').hide(); - $("#mainWallet").hide(); - $("#emailstep").show(); - app.isScanning = true; + bootbox.alert("Please re-pair your device from the Chrome App."); - } + pinlock = false; + if (!(typeof window.app === 'undefined')) { + app.isScanning = false; } - }); - - //refresh user profile data incase they updated - //it via the chrome app - Engine.getUserProfile(function (err, data) { - - data = JSON.parse(data); - - m_this.m_profileImage = data.ProfileImage; - m_this.m_statusText = data.Status; - - updateProfile(); - - }); + $("#pairspinner").hide(); + $('.numdone').attr("style", "background-color:white"); + $("#loginpin").show(); + $("#loginpinno").val(''); + $("#paddel").hide(); + $("#pinloginmessage").text("Enter your PIN number"); + } }); - }); - - } else { - - Engine.Device.deleteStorageItem("dataCache"); - //Engine.Device.deleteStorageItem("balance"); - bootbox.alert("Your cache has expired. Please try again.", function () { - - location.reload(); + } - }); - } + }); }); } @@ -6801,20 +8009,7 @@ function UI() { if (ekeyv == "ErrDeviceDestroyed") { - Engine.Device.deleteStorageItem("ninki_reg"); - Engine.Device.deleteStorageItem("ninki_p"); - Engine.Device.deleteStorageItem("ninki_rem"); - Engine.Device.deleteStorageItem("guid"); - - bootbox.alert("Too many failed attempts. The device has been unpaired.", function () { - - $("#loginpin").hide(); - $("#mainWallet").hide(); - $("#pairDevice").show(); - - location.reload(); - - }); + //remove this functionality } else { @@ -6822,9 +8017,15 @@ function UI() { pinlock = false; $("#loginpinno").val(''); + $("#sendstdpin").val(''); $('.numdone').attr("style", "background-color:white"); $("#paddel").hide(); + window.resetPin(); + + $("#paddelconf").hide(); + + if (ekeyv.substring(0, 6) == "ErrPIN") { var attempts = ekeyv.substring(7, 8); @@ -6832,6 +8033,22 @@ function UI() { $("#pinloginmessage").text("Incorrect PIN " + attempts + "/3 attempts"); $("#pincounter").effect("shake"); + } else if (ekeyv.substring(0, 10) == "ErrBlocked") { + + //if the login attempt has been blocked + //display a countdown to the user + //indicating when they can next attempt to + //login + + var seconds = ekeyv.substring(11, ekeyv.length) * 1.0; + + + setCountdown(seconds); + + // later on this timer may be stopped + + $("#pincounter").effect("shake"); + } else { bootbox.alert(ekeyv); @@ -6857,11 +8074,56 @@ function UI() { } + var countdownId = null; + + function setCountdown(seconds) { + + window.hideSecScreens(); + + showLoginPIN(); + + window.clearInterval(countdownId); + + var d1 = new Date(), d2 = new Date(d1); + + d2.setSeconds(d1.getSeconds() + seconds); + + countdownId = countdown(d2, + function (ts) { + var testdate = new Date(); + if (ts.value > 0) { + + window.clearInterval(countdownId); + $("#pinloginmessage").text("Enter your PIN number"); + } else { + $("#pinloginmessage").text(ts); + } + }, countdown.SECONDS | countdown.MINUTES | countdown.HOURS); + + } + + + function deleteDeviceStorage() { + + Engine.Device.deleteStorageItem("dataCache"); + Engine.Device.deleteStorageItem("ninki_rem"); + Engine.Device.deleteStorageItem("ninki_p"); + Engine.Device.deleteStorageItem("ninki_reg"); + Engine.Device.deleteStorageItem("ninki_h"); + Engine.Device.deleteStorageItem("guid"); + Engine.Device.deleteStorageItem("coinunit"); + Engine.Device.deleteStorageItem("currency"); + Engine.Device.deleteStorageItem("pubcachem00"); + Engine.Device.deleteStorageItem("pubcachem01"); + Engine.Device.deleteStorageItem("pair"); + Engine.Device.deleteStorageItem("price"); + Engine.Device.deleteStorageItem("dpk"); + + } //device paring temp variables var deviceName = ''; - var regToken = ''; var secret = ''; var enck = ''; var iv = ''; @@ -6905,7 +8167,7 @@ function UI() { deviceName = splitBlob[3]; //registration token for this pairing attempt - regToken = splitBlob[4]; + Engine.m_regToken = splitBlob[4]; //password enetered by the user Engine.setPass(pwd, guid); @@ -6953,8 +8215,10 @@ function UI() { //show pin screen - $('#pairDevice').hide(); + $('#createWalletStart').hide(); $("#btnPairDevice").removeClass("disabled"); + + $('#pairDevice').hide(); $('#loginpin').show(); if (!(typeof window.app === 'undefined')) { @@ -7065,7 +8329,7 @@ function UI() { //their chosen PIN number is registered with the server vi a hash of the PIN + Device uuid //a 256 bit encryption key is generated on the server using a CSPRNG, this is used to encrypt data stored on the device - Engine.registerDevice(Engine.m_guid, deviceName, devplatform, devmodel, pinhash, regToken, secret, function (err, result) { + Engine.registerDevice(Engine.m_guid, deviceName, devplatform, devmodel, pinhash, Engine.m_regToken, secret, function (err, result) { if (!err) { @@ -7076,16 +8340,15 @@ function UI() { //the server returns the encryption key //whcih is used to decrypt the hotkey and 2fa override token - var decblob = Engine.decryptNp(enck, dk.DeviceKey, iv); - - //slice it up - //64 64 - + Engine.m_deviceKey = Bitcoin.convert.hexToBytes(dk.DeviceKey); + var decblob = Engine.decryptNp(enck, Engine.m_deviceKey, iv); + //slice it up var hk = ''; var fatoken = ''; + //supports 128bit and 256bit keys if (decblob.length == 96) { hk = decblob.substring(0, 32); fatoken = decblob.substring(32, 96); @@ -7094,6 +8357,13 @@ function UI() { fatoken = decblob.substring(64, 128); } + console.log('Hot key...'); + console.log(hk); + console.log('2fa token...'); + console.log(fatoken); + console.log('Dev key...'); + console.log(dk.DeviceKey); + //test opening the wallet @@ -7105,23 +8375,27 @@ function UI() { //if succesfull store the encrypted data in local storage Engine.Device.setStorageItem("guid", Engine.m_oguid); - Engine.Device.setStorageItem("ninki_reg", regToken); + Engine.Device.setStorageItem("ninki_reg", Engine.m_regToken); + Engine.Device.setStorageItem("pair", "1"); + - Engine.Device.setSecureStorageObject("ninki_rem", fatoken, dk.DeviceKey, Engine.encryptNp); - Engine.Device.setSecureStorageObject("ninki_p", Engine.m_password, dk.DeviceKey, Engine.encryptNp); - Engine.Device.setSecureStorageObject("ninki_h", hk, dk.DeviceKey, Engine.encryptNp); + Engine.Device.setSecureStorageObject("ninki_rem", Bitcoin.convert.hexToBytes(fatoken), Engine.m_deviceKey, Engine.encryptNp); + //Engine.Device.setSecureStorageObject("ninki_p", Engine.m_password, Engine.m_deviceKey, Engine.encryptNp); + Engine.Device.setSecureStorageObject("ninki_h", Bitcoin.convert.hexToBytes(hk), Engine.m_deviceKey, Engine.encryptNp); var dataToCache = Engine.serialize(); - Engine.Device.setSecureStorageObject("dataCache", dataToCache, dk.DeviceKey, Engine.encrypt); + Engine.Device.setSecureStorageObject("dataCache", dataToCache, Engine.m_deviceKey, Engine.encrypt); - pinlock = false; - isPairing = false; + Engine.setSecDeviceKey(); - //cache the main account data + Engine.zeroDeviceKey(); - dk.DeviceKey = ''; + Engine.zeroByteArray(Engine.m_password); + pinlock = false; + isPairing = false; + isCreate = false; $("#loginpinno").val(''); $("#paddel").hide(); @@ -7317,12 +8591,10 @@ function UI() { secret = Engine.decryptNp(jpacket.packet, Engine.m_password, jpacket.IV); - Engine.registerDevice(Engine.m_guid, deviceName, devplatform, devmodel, pinhash, regToken, secret, function (err, result) { + Engine.registerDevice(Engine.m_guid, deviceName, devplatform, devmodel, pinhash, Engine.m_regToken, secret, function (err, result) { if (!err) { - var dk = JSON.parse(result); - if (!(typeof window.app === 'undefined')) { app.isScanning = true; } @@ -7331,6 +8603,9 @@ function UI() { //test opening the wallet + var fatoken = Bitcoin.convert.bytesToHex(Engine.m_deviceToken); + Engine.zeroByteArray(Engine.m_deviceToken); + Engine.openWallet(Engine.m_oguid, fatoken, function (err, result) { if (!err) { @@ -7340,7 +8615,12 @@ function UI() { //if succesfull store the encrypted data in local storage var dataToCache = Engine.serialize(); - Engine.Device.setSecureStorageObject("dataCache", dataToCache, dk.DeviceKey, Engine.encrypt); + + Engine.Device.setSecureStorageObject("dataCache", dataToCache, Engine.m_deviceKey, Engine.encrypt); + + Engine.setSecDeviceKey(); + + Engine.zeroDeviceKey(); pinlock = false; @@ -7374,7 +8654,7 @@ function UI() { //show optional 2fa screen - $("#emailstep").show(); + //$("#emailstep").show(); $("#pairspinner").hide(); @@ -7382,17 +8662,29 @@ function UI() { isCreate = false; - app.isScanning = true; $("#isactive").val(1); + $("#loginpin").hide(); + + + $('#welcome').hide(); + $('#dashboard').show(); + $('#dashheader').show(); + + $("#footermode").val(1); + $("#mainWallet").show(); + $(".footer").show(); + + + if (!(typeof window.app === 'undefined')) { + app.isScanning = true; + } + }); } else { - //if (!(typeof window.app === 'undefined')) { - // app.isScanning = false; - //} $("#pairspinner").hide(); bootbox.alert("Could not pair", function () { @@ -7445,8 +8737,6 @@ function UI() { }); - - } @@ -7454,8 +8744,8 @@ function UI() { function closeSendNet() { - $("#dashsend").addClass("invis"); - $("#dashsend").removeClass("slideUp"); + //$("#dashsend").addClass("invis"); + //$("#dashsend").removeClass("slideUp"); $("#dashsend").hide(); $("#dashsendamt").addClass("invis"); @@ -7474,7 +8764,7 @@ function UI() { $(".footer").show(); - $("#dashreceive").addClass("invis"); + // $("#dashreceive").addClass("invis"); $("#dashreceive").removeClass("slideUp"); $("#dashreceive").hide(); @@ -7499,8 +8789,8 @@ function UI() { function closeSendStd() { - $("#dashsend").removeClass("slideUp"); - $("#dashsend").addClass("invis"); + //$("#dashsend").removeClass("slideUp"); + //$("#dashsend").addClass("invis"); $("#dashsend").hide(); $("#dashsendamt").removeClass("slideUp"); @@ -7514,7 +8804,7 @@ function UI() { $(".footer").show(); $("#dashreceive").removeClass("slideUp"); - $("#dashreceive").addClass("invis"); + // $("#dashreceive").addClass("invis"); $("#dashreceive").hide(); $("#dashcontact").removeClass("slideUp"); diff --git a/src/ninki-ui.js b/src/ninki-ui.js index 38ef252..60e8d3b 100644 --- a/src/ninki-ui.js +++ b/src/ninki-ui.js @@ -136,6 +136,54 @@ function UI() { + //Runkeeper smart contact prototype + + + $("#btnCreateContract").click(function () { + + var runkeeperUsername = $("#txtRunkeeperUser").val(); + var goal = $("#txtDistance").val(); + var settledate = $("#txtByDate").val(); + + + //call for user details first + + Ninki.API.createRunKeeperContract("41580225", goal, settledate, function (err, data) { + + //now save back contract specification to the database + + var title = "Run " + goal + " by " + settledate; + var description = ''; + var contractAgent = "RealityKeys"; + var contractType = "RunKeeper"; + var serviceUserName = runkeeperUsername; + var serviceUserId = "41580225"; + var conditionData = ''; + var expiryDate = settledate; + var agentURL = ""; + + + Engine.createContractSpec(title, description, contractAgent, + contractType, data.yes_pubkey, data.no_pubkey, agentURL, + serviceUserName, serviceUserId, + conditionData, data.machine_resolution_scheduled_datetime, function (err, res) { + + + + + }); + + }); + + }); + + + //------------------------------------- + + + + + $("#btnBackupCodes").click(function () { @@ -169,12 +217,25 @@ function UI() { $("#btnPairUseBackups").click(function () { + + $("#pairerror").hide(); $("#pairqr2fa").hide(); $("#pairusebackup").show(); }); + $("#btnLoadFromBackup").click(function () { + + $("#mobilelogin").hide(); + $("#userlogin").hide(); + $("#guidsec").show(); + $("#openWalletStart").show(); + + }); + + + $("#btnBackupBeta").click(function () { @@ -194,7 +255,30 @@ function UI() { }); + + + //file-input + var tmpContent = {}; + function readSingleFile(e) { + var file = e.target.files[0]; + if (!file) { + return; + } + var reader = new FileReader(); + reader.onload = function (e) { + var contents = e.target.result; + displayContents(contents); + }; + reader.readAsText(file); + } + + function displayContents(contents) { + var element = document.getElementById('file-content'); + element.innerHTML = contents; + } + + $("#btnBrowseRestore").click(function () { //, accepts: [{ extensions: ['json']}] chrome.fileSystem.chooseEntry({ type: 'openFile' }, function (readOnlyEntry) { @@ -665,7 +749,7 @@ function UI() { } }; options.common = { - debug: true, + debug: false, onLoad: function () { $('#messages').text('Start typing password'); }, @@ -675,12 +759,32 @@ function UI() { }; + $('#createWalletStart #cpassword').pwstrength(options); + var optionsmob = {}; + optionsmob.ui = { + container: "#mobpwdcontainer", + showVerdictsInsideProgressBar: true, + showPopover: true, + showErrors: true, + viewports: { + progress: ".pwstrength_viewport_progress" + } + }; + optionsmob.common = { + debug: false, + onLoad: function () { + $('#messages').text('Start typing password'); + }, + onKeyUp: function () { + $("#createwalletalert").fadeOut(100); + } + }; - $('#createWalletStart #cpassword').pwstrength(options); + $('#mobilepassword #mobpwd').pwstrength(optionsmob); var optionschng = {}; @@ -977,15 +1081,34 @@ function UI() { if (!err) { - var data = hothash + jresult.DeviceToken; + //TO DO: + //stream bytes from node? + + var hplust = []; + hplust = hplust.concat(hplust, m_this.m_onlineKey); + + //clear buffer + Engine.zeroOnlineKey(); + + hplust = hplust.concat(Bitcoin.convert.hexToBytes(jresult.DeviceToken)); + + var deviceKey = Bitcoin.convert.hexToBytes(jresult.DeviceKey); + + + jresult.DeviceToken = ''; + jresult.DeviceKey = ''; + + var encdata = Engine.encryptNp(hplust, deviceKey); - var encdata = Engine.encryptNp(data, jresult.DeviceKey); + //zero out buffers + Engine.zeroByteArray(hplust); + Engine.zeroByteArray(deviceKey); var data = encdata.toString() + '|' + encdata.iv.toString() + '|' + Engine.m_oguid + '|' + currentDevice.DeviceName + '|' + jresult.RegToken; var options = { text: data, width: 256, height: 256, ecLevel: 'H' }; - $('#qrdevice').text(data); + // $('#qrdevice').text(data); $('#qrdevice').qrcode(options); $('#pairqr2fa').hide(); @@ -1121,14 +1244,22 @@ function UI() { $("#btnLogin").click(function () { - if (!ensureOpenWalletGuidAndPasswordValid()) return; + $("#btnLogin").button('loading'); - $("#imgopenwaiting").show(); - $("#btnLogin").prop('disabled', true); - $("#btnLogin").addClass('disabled'); - var target = document.getElementById('imgopenwaiting'); - var spinner = new Spinner(spinneropts).spin(target); + if ($("#openWalletStart input#password").val().length == 0) { + $("#openWalletStart input#password").css("border-color", "#ffaaaa"); + + setTimeout(function () { + $("#btnLogin").button('reset'); + + }, 10); + + return; + + } else { + $("#openWalletStart input#password").css("border-color", "#ccc"); + } setTimeout(function () { @@ -1147,8 +1278,6 @@ function UI() { Engine.Device.setStorageItem('guid', guid); - - Engine.openWallet(guid, twoFactorCode, function (err, result) { //$("#imgopenwaiting").hide(); @@ -1156,6 +1285,26 @@ function UI() { if (!err) { + //in case of someone setting up via hot key + //check for phrase and save + var phrase = $("#txtOnlineKeyPhrase").val(); + + if (phrase.length > 0) { + var hotkeydecode = Engine.decodeKey(phrase); + if (hotkeydecode) { + Engine.saveHotHash(hotkeydecode, function (err, result) { + + if (!err) { + + + } + + }); + + } + } + + //$("#imgopenwaiting").hide(); $("#openwalletalert").hide(); @@ -1188,15 +1337,13 @@ function UI() { $("#si2fa").show(); $("#sib1").hide(); $("#sib2").show(); - $("#signdiff").hide(); - $("#crwallet").hide(); - $('#openWalletStart input#twoFactorCode').focus(); + //$("#signdiff").hide(); + //$("#crwallet").hide(); - $("#btnLogin").prop('disabled', false); - $("#btnLogin").removeClass('disabled'); - $("#imgopenwaiting").hide(); + $('#openWalletStart input#twoFactorCode').focus(); + $("#btnLogin").button('reset'); } else { @@ -1208,9 +1355,8 @@ function UI() { $("#sib1").hide(); $("#sib2").show(); $('#openWalletStart input#twoFactorCode').focus(); - $("#btnLogin").prop('disabled', false); - $("#btnLogin").removeClass('disabled'); - $("#imgopenwaiting").hide(); + + $("#btnLogin").button('reset'); } } else { @@ -1228,10 +1374,8 @@ function UI() { $("#savetwofactorerror").hide(); $("#setup2faemail").hide(); $("#setup2faqr").show(); - $("#btnLogin").prop('disabled', false); - $("#btnLogin").removeClass('disabled'); - $("#imgopenwaiting").hide(); + $("#btnLogin").button('reset'); showMissingTwoFactorQr(); @@ -1246,14 +1390,10 @@ function UI() { Engine.Device.setStorageItem("user", Engine.m_nickname); Engine.Device.setStorageItem("userimg", Engine.m_profileImage); - $("#btnLogin").prop('disabled', false); - $("#btnLogin").removeClass('disabled'); - $("#imgopenwaiting").hide(); - + $("#btnLogin").button('reset'); }); - } else { //display a screen advising the user to write down their hot key @@ -1261,11 +1401,7 @@ function UI() { //then migrate the packet //initialise ui - $("#btnLogin").prop('disabled', false); - $("#btnLogin").removeClass('disabled'); - $("#imgopenwaiting").hide(); - - + $("#btnLogin").button('reset'); if (Engine.m_walletinfo.hotHash != '') { Engine.saveHotHash(Engine.m_walletinfo.hotHash, function (err, result) { @@ -1290,9 +1426,7 @@ function UI() { } else { - $("#btnLogin").prop('disabled', false); - $("#btnLogin").removeClass('disabled'); - $("#imgopenwaiting").hide(); + $("#btnLogin").button('reset'); $("#openwalletalert").show(); @@ -1354,13 +1488,7 @@ function UI() { $("#btn2faLogin").click(function () { - - $("#img2faopenwaiting").show(); - $("#btn2faLogin").prop('disabled', true); - $("#btn2faLogin").addClass('disabled'); - - var target = document.getElementById('img2faopenwaiting'); - var spinner = new Spinner(spinneropts).spin(target); + $("#btn2faLogin").button('loading'); setTimeout(function () { @@ -1375,11 +1503,11 @@ function UI() { if (err) { - $("#img2faopenwaiting").hide(); + $("#openwalletalert").show(); $("#openwalletalertmessage").text(result); - $("#btn2faLogin").prop('disabled', false); - $("#btn2faLogin").removeClass('disabled'); + + $("#btn2faLogin").button('reset'); } else { @@ -1390,13 +1518,40 @@ function UI() { if (Engine.m_walletinfo.hotHash == '') { - initialiseUI(function (err, result) { - Engine.Device.setStorageItem("user", Engine.m_nickname); - Engine.Device.setStorageItem("userimg", Engine.m_profileImage); + var phrase = $("#txtOnlineKeyPhrase").val(); - }); + if (phrase.length > 0) { + var hotkeydecode = Engine.decodeKey(phrase); + if (hotkeydecode) { + Engine.saveHotHash(hotkeydecode, function (err, result) { + + hotkeydecode = ''; + phrase = ''; + + $("#txtOnlineKeyPhrase").val('') + initialiseUI(function (err, result) { + + Engine.Device.setStorageItem("user", Engine.m_nickname); + Engine.Device.setStorageItem("userimg", Engine.m_profileImage); + + }); + + }); + } else { + phrase = ''; + } + } else { + + initialiseUI(function (err, result) { + + Engine.Device.setStorageItem("user", Engine.m_nickname); + Engine.Device.setStorageItem("userimg", Engine.m_profileImage); + + }); + + } } else { @@ -1416,11 +1571,12 @@ function UI() { }); } else { - $("#img2faopenwaiting").hide(); + $("#openwalletalert").show(); $("#openwalletalertmessage").text("Invalid two factor code"); - $("#btn2faLogin").prop('disabled', false); - $("#btn2faLogin").removeClass('disabled'); + + $("#btn2faLogin").button('reset'); + } }, 100); @@ -1430,8 +1586,6 @@ function UI() { $("#btnLoginBackupDone").click(function () { - - var twoFactorCode = $('#txtLoginBackupCode').val(); if (twoFactorCode.length == 8) { @@ -1488,6 +1642,385 @@ function UI() { + + + + var checkForToken = null; + var tempTwoFactorOverride = ''; + $("#btnOnlineKey").click(function () { + + $("#btnOnlineKey").button('loading'); + + var phrase = $("#txtOnlineKeyPhrase").val(); + + var mpkh = ''; + + Engine.getGUIDByMPKH(phrase, function (err, lookupGUID) { + + if (!err) { + + Engine.m_oguid = lookupGUID; + + //set the password as the sha256 of the seed + Engine.setPasswordApp(phrase); + + Engine.openWallet(lookupGUID, '', function (err, result) { + + console.log(result); + + + //check to see if email has been validated + + + if (!err) { + + + //if (result.TwoFactorOnLogin) { + + var gentoken = Engine.generateToken(); + + + Engine.requestAuthMigration(gentoken, function (err, res) { + + $("#txtOnlineKeyPhrase").val(''); + $("#btnOnlineKey").button('reset'); + $("#mobileauth").show(); + $("#mobilelogin").hide(); + $("#onlinekeyentererror").hide(); + + }); + + checkForToken = setInterval(function () { + + //check for token + + Engine.getAuthMigrationToken(gentoken, function (err, tfatoken) { + + if (!err) { + + if (tfatoken) { + + tempTwoFactorOverride = tfatoken; + + clearInterval(checkForToken); + + Engine.openWallet(lookupGUID, tempTwoFactorOverride, function (err, result) { + + console.log(result); + + if (!err) { + + if (result == "ok") { + + Engine.m_twoFactorOnLogin = false; + + //save the encrypted seed to storage + var hotkeydecode = Engine.decodeKey(phrase); + if (hotkeydecode) { + Engine.saveHotHash(hotkeydecode, function (err, result) { + + if (!err) { + //if auth then + $("#mobileauth").hide(); + $("#mobilepassword").show(); + + //mobemailopt + + $("#btnOnlineKey").button('reset'); + + if (Engine.m_validate) { + + $("#mobemailopt").show(); + $("#mobemail").prop('data-parsley-required', true); + } else { + + $("#mobemailopt").hide(); + $("#mobemail").removeAttr('data-parsley-required'); + } + + } else { + + $("#btnOnlineKey").button('reset'); + + } + + }); + } + + } else { + + $("#btnOnlineKey").button('reset'); + + } + + } else { + + $("#btnOnlineKey").button('reset'); + + } + + }); + + } + + } else { + + $("#btnOnlineKey").button('reset'); + + } + + }); + + }, 1000); + + //make an auth request + //guid + secret + + // } + + } else { + + + //right account but hash of key is not the password + //must have already changed password with chrome app + + //stash the phrase + //perform regular login + //save enc online key + $("#btnOnlineKey").button('reset'); + + if (result == "ErrAccount") { + + + $("#loginuser").hide(); + $("#loginimg").hide(); + + Engine.Device.deleteStorageItem("user"); + Engine.Device.deleteStorageItem("userimg"); + Engine.Device.setStorageItem('guid', Engine.m_oguid); + + $("#mobilelogin").hide(); + $("#openWalletStart #guid").val(Engine.m_oguid); + $("#guidsec").hide(); + + showOpenWalletStart(); + } + + } + + }); + + } else { + + var mess = lookupGUID; + if (lookupGUID == "InvalidPhrase") { + mess = "Invalid phrase"; + } + + if (lookupGUID == "ErrFail") { + mess = "Account not found"; + } + + $("#onlinekeyentererror").show(); + $("#onlinekeyentererrormessage").text(mess); + $("#btnOnlineKey").button('reset'); + + } + + }); + + + }); + + + $("#btnMobDeviceAuth").click(function () { + + + + }); + + + + + $("#btnMobPwdConfirm").click(function () { + + if ($("#frmMobPwd").parsley().isValid()) { + + if (($(".password-verdict").html() == 'Strong' || $(".password-verdict").html() == 'Very Strong')) { + + //initiate 2fa setup modal + if (!Engine.m_twoFactorOnLogin) { + + $("#mobilepassword").hide(); + + showMobTwoFactorQr(); + + $("#mobile2fa").show(); + + } + + } + } + + else { + + $("#frmMobPwd").parsley().validate(); + } + + }); + + + + function showMobTwoFactorQr() { + + $("#savemob2faerror").hide(); + + var token = ""; + + + Engine.getNewTwoFactorImg(tempTwoFactorOverride, function (err, twoFASecret) { + + if (!err) { + + var data = "otpauth://totp/Ninki:" + Engine.m_nickname + "?secret=" + twoFASecret + "&issuer=Ninki"; + var options = { text: data, width: 172, height: 172 }; + + $('#mob2faqr').text(''); + $('#mob2faqr').qrcode(options); + + $("#mob2faqr").show(); + $("#savemob2faerror").hide(); + + } else { + + $("#savemob2faerror").show(); + + } + }); + + + + + + } + + + $("#btnMobComplete").click(function () { + + + if ($("#frmMob2fa").parsley().isValid()) { + + + $("#btnMobComplete").button('loading'); + + if (Engine.m_validate) { + + var emailAddress = $("#mobemail").val(); + + Engine.updateEmailAddress(emailAddress, function (err, result) { + + Engine.sendWelcomeDetails(function (err, result) { + + + + }); + + }); + + } + + + //setup two factor code + //use auth token + + var twoFactCode = ''; + + + + //update email address if necessary + $("#savemob2faerror").hide(); + + + + + $("#mobsetupprog").show(); + + var twoFactorCode = $("#txtMob2faCode").val(); + + Engine.SetupTwoFactor(twoFactorCode, function (err, res) { + + + //now change the password + if (!err) { + + Engine.m_twoFactorOnLogin = true; + + var password = $("#mobpwd").val(); + + Engine.ChangePassword(twoFactorCode, Engine.m_password, password, function (err, res) { + + if (!err) { + + $("#mobsetupprogbar").width("100%"); + + setTimeout(function () { + + initialiseUI(function (err, result) { + + $("#btnMobComplete").button('reset'); + $("#mobile2fa").hide(); + + + Engine.Device.setStorageItem("user", Engine.m_nickname); + Engine.Device.setStorageItem("userimg", Engine.m_profileImage); + Engine.Device.setStorageItem('guid', Engine.m_oguid); + + }); + + }, 100); + + + } else { + + + $("#btnMobComplete").button('reset'); + + $("#savemob2faerror").text(res); + $("#savemob2faerror").show(); + + } + + + }, function (status, percent) { + + + $("#mobsetupprogbar").width(percent); + $("#mobsetupprogmess").text(status); + + }); + + } else { + + $("#btnMobComplete").button('reset'); + + $("#savemob2faerror").text(res); + $("#savemob2faerror").show(); + } + + }); + + } else { + + $("#frmMob2fa").parsley().validate(); + + } + + + + }); + + + $("#btngenaddr").click(function () { generateAddressClient(); @@ -1510,7 +2043,11 @@ function UI() { $("#showopenwalletcr").click(function () { - showOpenWalletStart(); + + $("#introduction").show(); + $("#createWalletStart").hide(); + + // showOpenWalletStart(); }); @@ -1771,10 +2308,10 @@ function UI() { $("#btnCreate").click(function () { - if ($("#frmcreate").parsley('validate')) { + if ($("#frmcreate").parsley().isValid()) { //check password strength - if (($(".password-verdict").html() == 'Strong' || $(".password-verdict").html() == 'Very Strong')) { + if (($("#createWalletStart .password-verdict").html() == 'Strong' || $("#createWalletStart .password-verdict").html() == 'Very Strong')) { //can we remove this check? if (ensureCreateWalletGuidNicknameAndPasswordValid()) { @@ -1875,8 +2412,14 @@ function UI() { //password not strong $("#createwalletalert").show(); $("#createwalletalertmessage").text("Password must be Strong- ideally Very Strong"); + + } + } else { + + $("#frmcreate").parsley().validate(); + } }); @@ -1923,6 +2466,11 @@ function UI() { $("#imgphrasewaiting").show(); + $("#loginuser").hide(); + $("#loginimg").hide(); + + Engine.Device.deleteStorageItem("user"); + Engine.Device.deleteStorageItem("userimg"); Engine.Device.setStorageItem('guid', Engine.m_oguid); Engine.openWalletAfterCreate(twoFactorCodeChk, function (err, result) { @@ -1936,7 +2484,8 @@ function UI() { } else { - initialiseUI(); + initialiseUI(null, true); + $("#btnPassphraseLogin").prop('disabled', false); $("#imgphrasewaiting").hide(); $("#phraseloginerror").hide(); @@ -2008,6 +2557,54 @@ function UI() { }); + + $("#btnEmailValidateMob").click(function () { + + var token = $("#txtEmailTokenMob").val(); + + $("#btnEmailValidateMob").prop('disabled', true); + + Engine.getEmailValidation(token, function (err, response) { + + if (err) { + $("#btnEmailValidateMob").prop('disabled', false); + } else { + + if (response != "Valid") { + $("#valemailerrormob").show(); + + if (response == "Expired") { + $("#valemailerrormessagemob").text('Your token has expired'); + } + if (response == "Invalid") { + $("#valemailerrormessagemob").text('Your token is not valid'); + } + + $("#btnEmailValidateMob").prop('disabled', false); + + } else { + + + //initialiseUI(); + Engine.m_validate = false; + + $("#validateemailmob").hide(); + $("#mainWallet").show(); + $("#valemailerrormob").hide(); + $("#btnEmailValidateMob").prop('disabled', false); + } + } + + }); + + + //call to verify token + + + }); + + + //wallet security wizard var step = 1; @@ -2149,8 +2746,12 @@ function UI() { var phrase = result.hotHash; if (phrase != "Unavailable") { + var bip39 = new BIP39(); // 'en' is the default language - var hotmnem = bip39.entropyToMnemonic(phrase); + var hotmnem = bip39.entropyToMnemonic(Bitcoin.convert.bytesToHex(Engine.m_onlineKey)); + + Engine.zeroOnlineKey(); + $("#secdisphrase").text(hotmnem); $("#secdisphrase").show(); } @@ -2469,12 +3070,15 @@ function UI() { $("#guidsec").hide(); - if (Engine.Device.isChromeApp()) { + //if (Engine.Device.isChromeApp()) { + + Engine.Device.getStorageItem('user', function (uname) { + + //console.write(res); + $("#loginuser").text(uname); - Engine.Device.getStorageItem('user', function (uname) { - //console.write(res); - $("#loginuser").text(uname); + if (Engine.Device.isChromeApp()) { Engine.Device.getStorageItem('userimg', function (res) { @@ -2498,11 +3102,19 @@ function UI() { }); + } else { + + $("#loginimg").hide(); + + } + - }); - } + + }); + + //} showOpenWalletStart(); @@ -2516,12 +3128,53 @@ function UI() { $("#btnsigndiff").click(function () { - $("#guidsec").show(); - $("#userlogin").hide(); + $("#introduction").show(); + //$("#userlogin").hide(); + $("#openWalletStart").hide(); + + //showOpenWalletStart(); + + }); + + + $(".cancelmobset").click(function () { + + $("#introduction").show(); + //$("#userlogin").hide(); + $("#openWalletStart").hide(); + $("#mobileauth").hide(); + $("#mobilepassword").hide(); + $("#mobile2fa").hide(); + + clearInterval(checkForToken); + + }); + + $("#exusercancel").click(function () { + + + $("#introduction").hide(); + + $("#openWalletStart").show(); + + showOpenWalletStart(); }); + //exuserdesk + + $("#exuserdesk").click(function () { + + $("#introduction").hide(); + $("#mobilelogin").show(); + + $("#mobsetups1").hide(); + $("#mobsetups2").show(); + $("#mobsetups2instruct").hide(); + + + }); $("#printCold").click(function () { @@ -2574,6 +3227,35 @@ function UI() { showOpenWalletStart(); }); + $("#btnMobileLogin").click(function () { + $("#introduction").hide(); + $("#mobilelogin").show(); + + $("#mobsetups2instruct").show(); + + $("#mobsetups1").show(); + $("#mobsetups2").hide(); + + }); + + $("#btnmoblogcancel").click(function () { + + $("#introduction").show(); + $("#mobilelogin").hide(); + $("#mobsetups1").show(); + $("#mobsetups2").hide(); + $("#onlinekeyentererror").hide(); + + }); + + + $("#btnMobSetups1").click(function () { + + $("#mobsetups2").show(); + $("#mobsetups1").hide(); + + }); + $("#password").keypress(function (e) { if (e.which == 13) { @@ -2611,6 +3293,14 @@ function UI() { $(".popover.fade.bottom.in").show(); }); + $("#mobpwd").blur(function () { + $(".popover.fade.bottom.in").hide(); + }); + + $("#mobpwd").focus(function () { + $(".popover.fade.bottom.in").show(); + }); + $("#balance").text("... BTC"); $("#mainWallet").hide(); $('#message').hide(); @@ -2678,6 +3368,22 @@ function UI() { }); + $("#emailresendmob").click(function () { + + Engine.sendWelcomeDetails(function (err, result) { + + if (!err) { + + $("#emailresendmessagemob").show(); + $("#emailresendmob").hide(); + //email has been resent, please check your email + } + + }); + + }); + + $("#cpassword").on('change keyup', function () { $("#password1").parsley("validate"); @@ -2760,6 +3466,13 @@ function UI() { }, 500); + } else { + + $("#chngpwerr").show(); + $("#chngpwerrmess").text("New password is not strong enough"); + $("#chngpwdprogmess").hide(); + $("#chngpwdprog").hide(); + } } else { @@ -2790,8 +3503,10 @@ function UI() { $("#validatefail").hide(); $("#validatesuccess").hide(); - var bip39 = new BIP39(); - code = bip39.mnemonicToHex(code); + if (code.length > 40) { + var bip39 = new BIP39(); + code = bip39.mnemonicToHex(code); + } if (code.length != 40) { $("#txtCode").css("border-color", "#ffaaaa"); @@ -2927,12 +3642,19 @@ function UI() { //ok //logout(); + Engine.m_twoFactorOnLogin = true; + $("#twofactorsettings").hide(); $("#2famodal").modal('hide'); $("#twoFactorCodeFor2fa").val(''); $("#txtsettings2fa").val(''); + $("#mobilelogin").hide(); + + + initialiseUI(); + } else { //error @@ -3057,6 +3779,8 @@ function UI() { $("#sendinvs2").hide(); $("#sendinvs2").show(); + $("#sendinvs3").hide(); + $("#invmodal").modal('show'); $("#twofactreqinv").show(); @@ -3093,6 +3817,9 @@ function UI() { $("#sendinvs2").hide(); $("#sendinvs2").show(); + + $("#sendinvs3").hide(); + $("#invmodal").modal('show'); $("#twofactreqinv").show(); @@ -3108,6 +3835,9 @@ function UI() { $("#sendinvs2").hide(); $("#sendinvs2").show(); + + $("#sendinvs3").hide(); + $("#invmodal").modal('show'); $("#twofactreqinv").hide(); @@ -4334,7 +5064,7 @@ function UI() { - function initialiseUI(callback) { + function initialiseUI(callback, skipemail) { //check if hot key is available //if not prompt the user to enter their hot key @@ -4351,6 +5081,9 @@ function UI() { $("#hotkeyenter").show(); } + //zero buffers after key check + m_this.zeroOnlineKey(); + var length = Engine.m_nickname.length; if (length > 20) { @@ -4405,7 +5138,7 @@ function UI() { $("#imgtoprightprofile").attr("src", imageSrcSmall); } - $("#codeForFriend").text(Engine.m_fingerprint); + $("#codeForFriend").text(Engine.m_pubKey.primaryKey.fingerprint); Engine.getusernetworkcategory(function (err, categories) { @@ -4459,28 +5192,20 @@ function UI() { $("#openWalletStart").hide(); $("#createWalletStart").hide(); - if (Engine.m_validate) { + if (!skipemail) { - $("#securitywizard").show(); - $("#step4").show(); - $("#step1").hide(); - $("#step2").hide(); - $("#step3").hide(); - $("#listep1").removeClass("active"); - $("#listep2").removeClass("active"); - $("#listep3").removeClass("active"); - $("#listep4").addClass("active"); - $("#prgsecwiz").width('100%'); - $(".next").hide(); - $(".previous").hide(); - $("#validateemail").show(); - $("#mainWallet").hide(); + if (Engine.m_validate) { - } else { + $("#validateemailmob").show(); + $("#mainWallet").hide(); - $("#securitywizard").hide(); - $("#mainWallet").show(); - $("#validateemail").hide(); + + } else { + + $("#securitywizard").hide(); + $("#mainWallet").show(); + $("#validateemail").hide(); + } } updateUI(); @@ -4627,6 +5352,7 @@ function UI() { } function showOpenWalletStart() { + $("#openWalletStart").show(); $("#createWalletStart").hide(); $("#lostguid").hide(); @@ -4636,6 +5362,15 @@ function UI() { $("#signdiff").show(); $("#crwallet").show(); + + + $("#siguid").show(); + $("#silguid").show(); + $("#sipwd").show(); + $("#si2fa").hide(); + $("#sib1").show(); + $("#sib2").hide(); + } function showTwoFactorQr() { @@ -4694,9 +5429,13 @@ function UI() { } - } + + + + + function showSettingsBackupTwoFactorQr() { //$("#twoFactorCodeFor2faError").hide(); @@ -5144,9 +5883,6 @@ function UI() { }); - //$("#balance").text(Math.floor((Math.random() * 100) + 1) + " BTC"); - //$("#message").fadeToggle(3000); - } function updateNetwork() { @@ -5167,128 +5903,132 @@ function UI() { Engine.getDevices(function (err, devices) { - if (devices.length == lastNoOfDevices) { + if (!err) { - for (var i = 0; i < devices.length; i++) { + if (devices.length == lastNoOfDevices) { - if (!lastDevices[i].IsPaired && devices[i].IsPaired) { + for (var i = 0; i < devices.length; i++) { + if (!lastDevices[i].IsPaired && devices[i].IsPaired) { - $('#qrdevice').text(''); - $('#pairqr2fa').show(); - $('#pairqrscan').hide(); + $('#qrdevice').text(''); - $("#pairdevicemodal").modal('hide'); - $("#pairdeviceqr").hide(); + $('#pairqr2fa').show(); + $('#pairqrscan').hide(); - } + $("#pairdevicemodal").modal('hide'); + $("#pairdeviceqr").hide(); - if (devices[i].IsPaired != lastDevices[i].IsPaired) { - lastNoOfDevices = 0; - break; + } + + if (devices[i].IsPaired != lastDevices[i].IsPaired) { + lastNoOfDevices = 0; + break; + } } + } - } + if (devices.length > lastNoOfDevices) { - if (devices.length > lastNoOfDevices) { + lastNoOfDevices = devices.length; - lastNoOfDevices = devices.length; + lastDevices = devices; - lastDevices = devices; + var template = ''; - var template = ''; + $('#devices').text(''); - $('#devices').text(''); + if (!err) { - if (!err) { + for (var i = 0; i < devices.length; i++) { - for (var i = 0; i < devices.length; i++) { + template += '
'; + template += ''; - template += ''; + } - } + $('#devices').html(template); - $('#devices').html(template); + for (var i = 0; i < devices.length; i++) { - for (var i = 0; i < devices.length; i++) { + $("#devices #pair" + i).click({ device: devices[i] }, function (event) { - $("#devices #pair" + i).click({ device: devices[i] }, function (event) { + currentDevice = event.data.device; + //here initiate the modal phone pairing screen - currentDevice = event.data.device; - //here initiate the modal phone pairing screen + //get the device 2fa code + //generate a qr containing + //password, hotkey, 2fa code, guid - //get the device 2fa code - //generate a qr containing - //password, hotkey, 2fa code, guid + //event.data.deviceName - //event.data.deviceName + $('#qrdevice').text(''); + $('#pairerror').hide(); + if (!event.data.device.IsPaired) { - $('#qrdevice').text(''); - $('#pairerror').hide(); - if (!event.data.device.IsPaired) { + $('#pairqrscan').hide(); + $("#pairdevicemodal").modal('show'); + $("#pairdeviceqr").show(); + $('#pairqr2fa').show(); + $("#pairheading").text("Pair " + event.data.device.DeviceName); + $("#btnShowPairQr").text("Pair"); + $('#btnPairUseBackups').hide(); - $('#pairqrscan').hide(); - $("#pairdevicemodal").modal('show'); - $("#pairdeviceqr").show(); - $('#pairqr2fa').show(); - $("#pairheading").text("Pair " + event.data.device.DeviceName); - $("#btnShowPairQr").text("Pair"); - $('#btnPairUseBackups').hide(); + } else { - } else { + $('#pairqrscan').hide(); + $("#pairdevicemodal").modal('show'); + $("#pairdeviceqr").show(); + $('#pairqr2fa').show(); + $("#pairheading").text("Unpair " + event.data.device.DeviceName); + $("#btnShowPairQr").text("Unpair"); + $('#qrdevice').text(''); + $('#btnPairUseBackups').show(); - $('#pairqrscan').hide(); - $("#pairdevicemodal").modal('show'); - $("#pairdeviceqr").show(); - $('#pairqr2fa').show(); - $("#pairheading").text("Unpair " + event.data.device.DeviceName); - $("#btnShowPairQr").text("Unpair"); - $('#qrdevice').text(''); - $('#btnPairUseBackups').show(); + $('#upb' + Engine.m_settings.BackupIndex).attr("style", "border-color:red"); - $('#upb' + Engine.m_settings.BackupIndex).attr("style", "border-color:red"); + //$('#qrdevice').qrcode(''); - //$('#qrdevice').qrcode(''); + } - } + }); + } - }); } } @@ -5371,154 +6111,162 @@ function UI() { if (!err) { - $("#nfriends").text(friends.length); - if (friends.length > lastNoOfFriends) { + if (friends.length == 0 && lastNoOfFriends == 0) { + + $("#networkpholder").show(); - lastNoOfFriends = friends.length; + } else { - FRIENDSLIST = {}; + $("#networkpholder").hide(); - for (var i = 0; i < friends.length; i++) { - FRIENDSLIST[friends[i].userName] = friends[i]; - } + if (friends.length > lastNoOfFriends) { - //if selected friend is not isend and isreceive - //then find in list and update + lastNoOfFriends = friends.length; - if (selectedFriend != null) { + FRIENDSLIST = {}; - if (!selectedFriend.ICanSend || !selectedFriend.ICanReceive) { - selectedFriend = FRIENDSLIST[selectedFriend.userName]; - updateSelectedFriend(); + for (var i = 0; i < friends.length; i++) { + FRIENDSLIST[friends[i].userName] = friends[i]; } - } + //if selected friend is not isend and isreceive + //then find in list and update + + if (selectedFriend != null) { + + if (!selectedFriend.ICanSend || !selectedFriend.ICanReceive) { + selectedFriend = FRIENDSLIST[selectedFriend.userName]; + updateSelectedFriend(); + } + } - $("#nfriends").text(friends.length); - $("#myfriends").text(''); + $("#nfriends").text(friends.length); + $("#myfriends").text(''); - var grouptemplate = ''; - var friendsgroup = _.groupBy(friends, function (item) { return item.category; }); + var grouptemplate = ''; - grouptemplate += '
'; + var friendsgroup = _.groupBy(friends, function (item) { return item.category; }); - var k = 0; - var g = 1; - for (var key in friendsgroup) { + grouptemplate += '
'; - friends = friendsgroup[key]; + var k = 0; + var g = 1; + for (var key in friendsgroup) { - grouptemplate += '
'; - grouptemplate += '
'; - grouptemplate += ''; - grouptemplate += key; - grouptemplate += ''; - grouptemplate += '
'; - grouptemplate += '
'; - grouptemplate += '
'; + friends = friendsgroup[key]; - for (var i = 0; i < friendsgroup[key].length; i++) { + grouptemplate += '
'; + grouptemplate += '
'; + grouptemplate += ''; + grouptemplate += key; + grouptemplate += ''; + grouptemplate += '
'; + grouptemplate += '
'; + grouptemplate += '
'; - var frnd = FRIENDSLIST[friends[i].userName]; + for (var i = 0; i < friendsgroup[key].length; i++) { - var template = ''; + grouptemplate += '
'; + g++; } grouptemplate += '
'; - grouptemplate += '
'; - grouptemplate += '
'; - g++; - } - grouptemplate += '
'; + $("#myfriends").html(grouptemplate); - $("#myfriends").html(grouptemplate); - - var k = 0; - var g = 1; - for (var key in friendsgroup) { - - friends = friendsgroup[key]; - for (var i = 0; i < friendsgroup[key].length; i++) { + var k = 0; + var g = 1; + for (var key in friendsgroup) { friends = friendsgroup[key]; + for (var i = 0; i < friendsgroup[key].length; i++) { + friends = friendsgroup[key]; - var length = friends[i].userName.length; - if (length > 20) { - length = 20; - } - var imageSrc = "images/avatar/64px/Avatar-" + pad(length) + ".png"; + var length = friends[i].userName.length; + if (length > 20) { + length = 20; + } - if (friends[i].profileImage != '') { - imageSrc = "https://ninkip2p.imgix.net/" + _.escape(friends[i].profileImage) + "?crop=faces&fit=crop&h=256&w=256&mask=ellipse&border=1,d0d0d0"; - imageSrcSmall = "https://ninkip2p.imgix.net/" + _.escape(friends[i].profileImage) + "?crop=faces&fit=crop&h=64&w=64&mask=ellipse&border=1,d0d0d0"; - } + var imageSrc = "images/avatar/64px/Avatar-" + pad(length) + ".png"; - //$("#myfriends #imgfriend" + k) - - if (Engine.Device.isChromeApp()) { - var xhrsm = new XMLHttpRequest(); - xhrsm.open('GET', imageSrc, true); - xhrsm.responseType = 'blob'; - xhrsm.index = k; - xhrsm.onload = function (e) { - $("#myfriends #imgfriend" + this.index).attr("src", window.URL.createObjectURL(this.response)); - }; - xhrsm.send(); - } else { - $("#myfriends #imgfriend" + k).attr("src", imageSrc); - } + if (friends[i].profileImage != '') { + imageSrc = "https://ninkip2p.imgix.net/" + _.escape(friends[i].profileImage) + "?crop=faces&fit=crop&h=256&w=256&mask=ellipse&border=1,d0d0d0"; + imageSrcSmall = "https://ninkip2p.imgix.net/" + _.escape(friends[i].profileImage) + "?crop=faces&fit=crop&h=64&w=64&mask=ellipse&border=1,d0d0d0"; + } + //$("#myfriends #imgfriend" + k) + + if (Engine.Device.isChromeApp()) { + var xhrsm = new XMLHttpRequest(); + xhrsm.open('GET', imageSrc, true); + xhrsm.responseType = 'blob'; + xhrsm.index = k; + xhrsm.onload = function (e) { + $("#myfriends #imgfriend" + this.index).attr("src", window.URL.createObjectURL(this.response)); + }; + xhrsm.send(); + } else { + $("#myfriends #imgfriend" + k).attr("src", imageSrc); + } - $("#myfriends #friend" + k).click({ userName: friends[i].userName }, function (event) { - SELECTEDFRIEND = event.data.userName; - selectedFriend = FRIENDSLIST[event.data.userName]; + $("#myfriends #friend" + k).click({ userName: friends[i].userName }, function (event) { - //depreciate + SELECTEDFRIEND = event.data.userName; + selectedFriend = FRIENDSLIST[event.data.userName]; + //depreciate - updateSelectedFriend(); - $("#friendAmount").keyup(); + updateSelectedFriend(); + $("#friendAmount").keyup(); - }); - console.log("added click " + k + " for " + friends[i].userName); - k++; + }); + console.log("added click " + k + " for " + friends[i].userName); + + k++; + } + g++; } - g++; - } + } } return callback(false, "done"); @@ -5784,7 +6532,7 @@ function UI() { } var template = '
  • ' + - 'John said' + + '' + '' + '
    ' + '' + _.escape(friends[i].userName) + '' + @@ -6009,7 +6757,7 @@ function UI() { '' + _.escape(trdate) + ''; template += '' + - 'John said ' + + ' ' + tref + '' + dirTemplate + ''; @@ -6111,7 +6859,7 @@ function UI() { Engine.createAddress('m/0/0', 1, function (err, newAddress, path) { - var options = { text: newAddress, width: 172, height: 172 }; + var options = { text: 'bitcoin:' + newAddress, width: 172, height: 172 }; $('#requestaddressqr').text(''); $('#requestaddressqr').qrcode(options); @@ -6479,30 +7227,6 @@ function UI() { } - - function ensureOpenWalletGuidAndPasswordValid() { - - if (Engine.isRealGuid($("#openWalletStart input#guid").val())) { - $("#openWalletStart input#guid").css("border-color", "#ccc"); - } else { - $("#openWalletStart input#guid").css("border-color", "#ffaaaa"); - } - - if ($("#openWalletStart input#password").val().length == 0) { - $("#openWalletStart input#password").css("border-color", "#ffaaaa"); - } else { - $("#openWalletStart input#password").css("border-color", "#ccc"); - } - - if (!Engine.isRealGuid($("#openWalletStart input#guid").val()) || - $("#openWalletStart input#password").val().length == 0) { - - return false; - } - - return true; - } - function ensureCreateWalletGuidNicknameAndPasswordValid() { if (Engine.isRealGuid($("#createWalletStart input#guid").val())) { diff --git a/tests/test-create-account-1.js b/tests/test-create-account-1.js index 66a66fe..bba2a39 100644 --- a/tests/test-create-account-1.js +++ b/tests/test-create-account-1.js @@ -124,6 +124,8 @@ describe('Account Utilities', function () { var token = "test"; + console.log("calling getEmailValidation"); + engine.getEmailValidation(token, function (err, response) { console.log(response); @@ -143,6 +145,9 @@ describe('Account Utilities', function () { }); + }, function (progress) { + + }); }); diff --git a/tests/test-create-account-2.js b/tests/test-create-account-2.js index 6398c59..8478d75 100644 --- a/tests/test-create-account-2.js +++ b/tests/test-create-account-2.js @@ -25,6 +25,7 @@ describe('Create Account Function', function () { it("Creates a new account", function (done) { + engine.createWallet(guid, password, username, emailAddress, function (err, result) { @@ -61,6 +62,8 @@ describe('Create Account Function', function () { var token = "test"; + console.log("calling getEmailValidation"); + engine.getEmailValidation(token, function (err, response) { console.log(response); @@ -80,6 +83,9 @@ describe('Create Account Function', function () { }); + }, function (progress) { + + }); }); diff --git a/wallet.crx b/wallet.crx index 12bae03..7dcc015 100644 Binary files a/wallet.crx and b/wallet.crx differ