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: '