From 49cbebf765b44c5085d95542897aedbbc6223112 Mon Sep 17 00:00:00 2001 From: philip glazman <8378656+MangoSalad@users.noreply.github.com> Date: Fri, 26 Oct 2018 15:08:09 +0200 Subject: [PATCH 01/19] rsk accepted as network --- lib/api_client.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/api_client.js b/lib/api_client.js index b3b41b9..0141ea7 100644 --- a/lib/api_client.js +++ b/lib/api_client.js @@ -145,13 +145,15 @@ APIClient.normalizeNetworkFromOptions = function(options) { if (options.network) { var lower = options.network.toLowerCase(); - var m = lower.match(/^([rt])?(btc|bch|bcc)$/); + var m = lower.match(/^([rt])?(btc|bch|bcc|rsk)$/); if (!m) { throw new Error("Invalid network [" + options.network + "]"); } if (m[2] === 'btc') { network = "BTC"; + } else if (m[2] === 'rsk') { + network = "RSK"; } else { network = "BCC"; } @@ -1351,7 +1353,6 @@ APIClient.CREATE_WALLET_PROGRESS_DONE = 100; */ APIClient.prototype.createNewWallet = function(options, cb) { /* jshint -W071, -W074 */ - var self = this; if (typeof options !== "object") { @@ -1655,7 +1656,6 @@ APIClient.prototype._createNewWalletV3 = function(options) { if (options.primaryPrivateKey) { throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey"); } - // seed should be provided or generated options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8); From e6c8b68b2183791498e8fca697db4a1a4ae1dbba Mon Sep 17 00:00:00 2001 From: philip glazman <8378656+MangoSalad@users.noreply.github.com> Date: Thu, 8 Nov 2018 00:10:36 +0100 Subject: [PATCH 02/19] add ethereumjs. generate etheruem address client side, build tx client side and send to sdk [incomplete] --- .gitmodules | 3 + lib/api_client.js | 156 ++++++++- lib/rsk-wallet.js | 671 +++++++++++++++++++++++++++++++++++++++ package.json | 13 +- vendor/ethereumjs-wallet | 1 + 5 files changed, 840 insertions(+), 4 deletions(-) create mode 100644 lib/rsk-wallet.js create mode 160000 vendor/ethereumjs-wallet diff --git a/.gitmodules b/.gitmodules index 62df9e1..0c94998 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "vendor/asmcrypto.js"] path = vendor/asmcrypto.js url = https://github.com/vibornoff/asmcrypto.js +[submodule "vendor/ethereumjs-wallet"] + path = vendor/ethereumjs-wallet + url = https://github.com/ethereumjs/ethereumjs-wallet/ diff --git a/lib/api_client.js b/lib/api_client.js index 0141ea7..2ed77bb 100644 --- a/lib/api_client.js +++ b/lib/api_client.js @@ -16,7 +16,8 @@ var _ = require('lodash'), blocktrail = require('./blocktrail'), randomBytes = require('randombytes'), CryptoJS = require('crypto-js'), - webworkifier = require('./webworkifier'); + webworkifier = require('./webworkifier'), + RskWallet = require('./rsk-wallet'); /** * @@ -102,6 +103,7 @@ var APIClient = function(options) { options.apiNetwork = options.apiNetwork || normalizedNetwork[3]; self.bitcoinCash = options.network === "BCC"; + self.chain = options.network; self.regtest = options.regtest; self.testnet = options.testnet; self.network = networkFromOptions(self); @@ -1322,6 +1324,86 @@ APIClient.prototype.initWallet = function(options, cb) { return deferred.promise; }; +/** + * initialize an existing RSK wallet + * + * Either takes two argument: + * @param options object {} + * @param [cb] function callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys) + * + * @returns {q.Promise} + */ +APIClient.prototype.initRskWallet = function(options, cb) { + var self = this; + + if (typeof options !== "object") { + // get the old-style arguments + options = { + identifier: arguments[0], + passphrase: arguments[1] + }; + + cb = arguments[2]; + } + + if (options.check_backup_key) { + if (typeof options.check_backup_key !== "string") { + throw new Error("Invalid input, must provide the backup key as a string (the xpub)"); + } + } + + var deferred = q.defer(); + deferred.promise.spreadNodeify(cb); + + var identifier = options.identifier; + + if (!identifier) { + deferred.reject(new blocktrail.WalletInitError("Identifier is required")); + return deferred.promise; + } + + deferred.resolve(self.blocktrailClient.get("/wallet/" + identifier, null, true).then(function(result) { + var keyIndex = options.keyIndex || result.key_index; + + options.walletVersion = result.wallet_version; + + var primaryPublicKeys = _.mapValues(result.primary_public_keys, function(primaryPublicKey) { + return bitcoin.HDNode.fromBase58(primaryPublicKey[0], self.network); + }); + + // initialize wallet + var wallet = new RskWallet( + self, + identifier, + options.walletVersion, + result.primary_mnemonic, + result.encrypted_primary_seed, + result.encrypted_secret, + primaryPublicKeys, + keyIndex, + result.segwit || 0, + self.testnet, + self.regtest, + result.checksum, + result.upgrade_key_index, + options.useCashAddress, + options.bypassNewAddressCheck + ); + + wallet.recoverySecret = result.recovery_secret; + + if (!options.readOnly) { + return wallet.unlock(options).then(function() { + return wallet; + }); + } else { + return wallet; + } + })); + + return deferred.promise; +}; + APIClient.CREATE_WALLET_PROGRESS_START = 0; APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET = 4; APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY = 5; @@ -1944,6 +2026,78 @@ APIClient.prototype.getNewDerivation = function(identifier, path, cb) { return self.blocktrailClient.post("/wallet/" + identifier + "/path", null, {path: path}, cb); }; +/** + * get a new derivation number for specified parent path + * eg; m/44'/1'/0/0 results in m/44'/1'/0/0/0 and next time in m/44'/1'/0/0/1 and next time in m/44'/1'/0/0/2 + * + * @param identifier string the wallet identifier + * @param path string the parent path for which to get a new derivation, + * can be suffixed with /* to make it clear on which level the derivations hould be + * @param [cb] function callback(err, result) + * @returns {q.Promise} + */ +APIClient.prototype.getRskNewDerivation = function(identifier, path, cb) { + var self = this; + + return self.blocktrailClient.post("/wallet/" + identifier + "/path", null, {path: path}, cb); +}; + +/** + * use the API to get account details for an Rootstock Address + * + * the return object has the following format: + * { + * "confirmed_balance" => 32746327, + * "address" => "0xaddress", + * "path" => "m/44'/1'/0'/0/13", + * "nonce" => 3, + * } + * + * @param identifier string the wallet identifier + * @param address string address associated with account + * @param options + * @param [cb] function callback(err, utxos, fee, change) + * @returns {q.Promise} + */ +APIClient.prototype.getRskAccount = function(identifier, address, options, cb) { + var self = this; + + if (typeof options === "function") { + cb = options; + options = {}; + } + + options = options || {}; + + var deferred = q.defer(); + deferred.promise.spreadNodeify(cb); + + var params = { + address: address, + }; + + deferred.resolve( + self.blocktrailClient.post("/wallet/" + identifier + "/account", params).then( + function(result) { + return { + "confirmed_balance": result.confirmed_balance, + "address" : result.address, + "path": result.path, + "nonce": result.nonce, + }; + }, + function(err) { + if (err.message.match(/too low to pay the fee/)) { + throw blocktrail.WalletFeeError(err); + } + + throw err; + } + ) + ); + + return deferred.promise; +}; /** * delete the wallet diff --git a/lib/rsk-wallet.js b/lib/rsk-wallet.js new file mode 100644 index 0000000..8f759db --- /dev/null +++ b/lib/rsk-wallet.js @@ -0,0 +1,671 @@ +var _ = require('lodash'); +var assert = require('assert'); +var q = require('q'); +var async = require('async'); +var bitcoin = require('bitcoinjs-lib'); +var bitcoinMessage = require('bitcoinjs-message'); +var blocktrail = require('./blocktrail'); +var CryptoJS = require('crypto-js'); +var Encryption = require('./encryption'); +var EncryptionMnemonic = require('./encryption_mnemonic'); +var SizeEstimation = require('./size_estimation'); +var bip39 = require('bip39'); +var Wallet = require('./wallet'); +var ethereumjs = require('ethereumjs-wallet') +var ethutil = require('ethereumjs-util') +var ethhdkey = require('ethereumjs-wallet/hdkey') +const ethtx = require('ethereumjs-tx') + + +var SignMode = { + SIGN: "sign", + DONT_SIGN: "dont_sign" +}; + +/** + * + * @param sdk APIClient SDK instance used to do requests + * @param identifier string identifier of the wallet + * @param walletVersion string + * @param primaryMnemonic string primary mnemonic + * @param encryptedPrimarySeed + * @param encryptedSecret + * @param primaryPublicKeys string primary mnemonic + * @param backupPublicKey string BIP32 master pubKey M/ + * @param blocktrailPublicKeys array list of blocktrail pubKeys indexed by keyIndex + * @param keyIndex int key index to use + * @param segwit int segwit toggle from server + * @param testnet bool testnet + * @param regtest bool regtest + * @param checksum string + * @param upgradeToKeyIndex int + * @param useNewCashAddr bool flag to opt in to bitcoin cash cashaddr's + * @param bypassNewAddressCheck bool flag to indicate if wallet should/shouldn't derive new address locally to verify api + * @constructor + * @internal + */ +var RskWallet = function( + sdk, + identifier, + walletVersion, + primaryMnemonic, + encryptedPrimarySeed, + encryptedSecret, + primaryPublicKeys, + keyIndex, + segwit, + testnet, + regtest, + checksum, + upgradeToKeyIndex, + useNewCashAddr, + bypassNewAddressCheck +) { + /* jshint -W071 */ + var self = this; + + self.sdk = sdk; + self.identifier = identifier; + self.walletVersion = walletVersion; + self.locked = true; + self.bypassNewAddressCheck = !!bypassNewAddressCheck; + self.bitcoinCash = self.sdk.bitcoinCash; + self.segwit = !!segwit; + self.useNewCashAddr = !!useNewCashAddr; + assert(!self.segwit || !self.bitcoinCash); + + self.testnet = testnet; + self.regtest = regtest; + if (self.bitcoinCash) { + if (self.regtest) { + self.network = bitcoin.networks.bitcoincashregtest; + } else if (self.testnet) { + self.network = bitcoin.networks.bitcoincashtestnet; + } else { + self.network = bitcoin.networks.bitcoincash; + } + } else { + if (self.regtest) { + self.network = bitcoin.networks.regtest; + } else if (self.testnet) { + self.network = bitcoin.networks.testnet; + } else { + self.network = bitcoin.networks.bitcoin; + } + } + + assert(_.every(primaryPublicKeys, function(primaryPublicKey) { return primaryPublicKey instanceof bitcoin.HDNode; })); + + // v1 + self.primaryMnemonic = primaryMnemonic; + + // v2 & v3 + self.encryptedPrimarySeed = encryptedPrimarySeed; + self.encryptedSecret = encryptedSecret; + + self.primaryPrivateKey = null; + + self.primaryPublicKeys = primaryPublicKeys; + self.keyIndex = keyIndex; + + self.chain = RskWallet.CHAIN_RSK_DEFAULT; + self.changeChain = RskWallet.CHAIN_RSK_DEFAULT; + + self.checksum = checksum; + self.upgradeToKeyIndex = upgradeToKeyIndex; + + self.secret = null; + self.seedHex = null; +}; + +RskWallet.CHAIN_RSK_DEFAULT = 5; + +RskWallet.PAY_PROGRESS_START = 0; +RskWallet.PAY_PROGRESS_COIN_SELECTION = 10; +RskWallet.PAY_PROGRESS_CHANGE_ADDRESS = 20; +RskWallet.PAY_PROGRESS_SIGN = 30; +RskWallet.PAY_PROGRESS_SEND = 40; +RskWallet.PAY_PROGRESS_DONE = 100; + + +RskWallet.prototype.unlock = function(options, cb) { + var self = this; + + var deferred = q.defer(); + deferred.promise.nodeify(cb); + + // avoid modifying passed options + options = _.merge({}, options); + + q.fcall(function() { + switch (self.walletVersion) { + case Wallet.WALLET_VERSION_V1: + return self.unlockV1(options); + + case Wallet.WALLET_VERSION_V2: + return self.unlockV2(options); + + case Wallet.WALLET_VERSION_V3: + return self.unlockV3(options); + + default: + return q.reject(new blocktrail.WalletInitError("Invalid wallet version")); + } + }).then( + function(primaryPrivateKey) { + self.primaryPrivateKey = primaryPrivateKey; + + // create a checksum of our private key which we'll later use to verify we used the right password + var checksum = self.primaryPrivateKey.getAddress(); + + // check if we've used the right passphrase + if (checksum !== self.checksum) { + throw new blocktrail.WalletChecksumError("Generated checksum [" + checksum + "] does not match " + + "[" + self.checksum + "], most likely due to incorrect password"); + } + + self.locked = false; + + // if the response suggests we should upgrade to a different blocktrail cosigning key then we should + if (typeof self.upgradeToKeyIndex !== "undefined" && self.upgradeToKeyIndex !== null) { + return self.upgradeKeyIndex(self.upgradeToKeyIndex); + } + } + ).then( + function(r) { + deferred.resolve(r); + }, + function(e) { + deferred.reject(e); + } + ); + + return deferred.promise; +}; + +RskWallet.prototype.unlockV3 = function(options, cb) { + var self = this; + console.log("in unlock") + + var deferred = q.defer(); + deferred.promise.nodeify(cb); + + deferred.resolve(q.fcall(function() { + return q.when() + .then(function() { + /* jshint -W071, -W074 */ + options.encryptedPrimarySeed = typeof options.encryptedPrimarySeed !== "undefined" ? options.encryptedPrimarySeed : self.encryptedPrimarySeed; + options.encryptedSecret = typeof options.encryptedSecret !== "undefined" ? options.encryptedSecret : self.encryptedSecret; + + if (options.secret) { + self.secret = options.secret; + } + + if (options.primaryPrivateKey) { + throw new blocktrail.WalletInitError("specifying primaryPrivateKey has been deprecated"); + } + + if (options.primarySeed) { + self.primarySeed = options.primarySeed; + } else if (options.secret) { + return self.sdk.promisedDecrypt(new Buffer(options.encryptedPrimarySeed, 'base64'), self.secret) + .then(function(primarySeed) { + self.primarySeed = primarySeed; + }, function() { + throw new blocktrail.WalletDecryptError("Failed to decrypt primarySeed"); + }); + } else { + // avoid conflicting options + if (options.passphrase && options.password) { + throw new blocktrail.WalletCreateError("Can't specify passphrase and password"); + } + // normalize passphrase/password + options.passphrase = options.passphrase || options.password; + delete options.password; + + return self.sdk.promisedDecrypt(new Buffer(options.encryptedSecret, 'base64'), new Buffer(options.passphrase)) + .then(function(secret) { + self.secret = secret; + }, function() { + throw new blocktrail.WalletDecryptError("Failed to decrypt secret"); + }) + .then(function() { + return self.sdk.promisedDecrypt(new Buffer(options.encryptedPrimarySeed, 'base64'), self.secret) + .then(function(primarySeed) { + self.primarySeed = primarySeed; + }, function() { + throw new blocktrail.WalletDecryptError("Failed to decrypt primarySeed"); + }); + }); + } + }) + .then(function() { + return bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network); + }) + ; + })); + + return deferred.promise; +}; + +/** + * generate a new derived private key and return the new address for it + * + * @param [chainIdx] int + * @param [cb] function callback(err, address) + * @returns {q.Promise} + */ +RskWallet.prototype.getNewAddress = function(chainIdx, cb) { + var self = this; + + // chainIdx is optional + if (typeof chainIdx === "function") { + cb = chainIdx; + chainIdx = null; + } + + var deferred = q.defer(); + deferred.promise.spreadNodeify(cb); + + // Only enter if it's not an integer + if (chainIdx !== parseInt(chainIdx, 10)) { + // deal with undefined or null, assume defaults + if (typeof chainIdx === "undefined" || chainIdx === null) { + chainIdx = self.chain; + } else { + // was a variable but not integer + deferred.reject(new Error("Invalid chain index")); + return deferred.promise; + } + } + + deferred.resolve(self.sdk.getRskNewDerivation(self.identifier, "M/" + self.keyIndex + "'/" + chainIdx) + .then(function(newDerivation) { + var path = newDerivation.path.replace("\'","").replace('9999',"9999'"); + console.log(path) + var addressFromServer = newDerivation.address; + + var verifyAddress = self.getAddressByPath(path); + + console.log("Server addy:",addressFromServer) + console.log("Client addy:", verifyAddress) + + if (verifyAddress !== addressFromServer) { + throw new blocktrail.WalletAddressError("Failed to verify address [" + naddressFromServer + "] !== [" + verifyAddress + "]"); + } + + return [verifyAddress, path]; + }) + ); + return deferred.promise; +}; + +/** + * get address for specified path + * + * @param path + * @returns string + */ +RskWallet.prototype.getAddressByPath = function(path) { + var self = this; + // console.log("checking this path: ", path); + // console.log("primary priv key:",self.primaryPrivateKey); + //bitcoin + var derivedPrimaryPublicKey = self.getPrimaryPublicKey(path); + console.log("btc derived key:",derivedPrimaryPublicKey); + // var addy = derivedPrimaryPublicKey.keyPair.getAddress(); + // console.log("Bitcoin Address: ",addy); + + //rsk + // derivedPrimaryPublicKey = self.getPrimaryPublicKeyRsk(path); + // console.log("eth derived key: ",derivedPrimaryPublicKey) + var derivedEthAddy = ethhdkey.fromExtendedKey(derivedPrimaryPublicKey.toBase58()) + var address = derivedEthAddy.getWallet().getAddressString() + console.log("rsk addy: ",address) + return address +}; + +// RskWallet.prototype.getPrimaryPublicKeyRsk = function(path) { +// var self = this; +// console.log("getprimarypublickey") +// path = path.replace("m", "M"); + +// var keyIndex = path.split("/")[1].replace("'", ""); + +// if (!self.primaryPublicKeys[keyIndex]) { +// if (self.primaryPrivateKey) { +// self.primaryPublicKeys[keyIndex] = self.deriveByPathRsk(self.primaryPrivateKey, "M/" + keyIndex + "'", "m"); +// } else { +// throw new blocktrail.KeyPathError("Wallet.getPrimaryPublicKey keyIndex (" + keyIndex + ") is unknown to us"); +// } +// } +// var primaryPublicKey = self.primaryPublicKeys[keyIndex]; +// return self.deriveByPathRsk(self.primaryPrivateKey, path, "M/" + keyIndex + "'"); +// }; + +/** + * get primary public key by path + * first level of the path is used as keyIndex to find the correct key in the dict + * + * @param path string + * @returns {bitcoin.HDNode} + */ +RskWallet.prototype.getPrimaryPublicKey = function(path) { + var self = this; + + path = path.replace("m", "M"); + + var keyIndex = path.split("/")[1].replace("'", ""); + + if (!self.primaryPublicKeys[keyIndex]) { + if (self.primaryPrivateKey) { + self.primaryPublicKeys[keyIndex] = self.deriveByPath(self.primaryPrivateKey, "M/" + keyIndex + "'", "m"); + } else { + throw new blocktrail.KeyPathError("Wallet.getPrimaryPublicKey keyIndex (" + keyIndex + ") is unknown to us"); + } + } + + var primaryPublicKey = self.primaryPublicKeys[keyIndex]; + return self.deriveByPath(primaryPublicKey, path, "M/" + keyIndex + "'"); +}; + +/** + * create derived key from parent key by path + * + * @param hdKey {bitcoin.HDNode} + * @param path string + * @param keyPath string + * @returns {bitcoin.HDNode} + */ +RskWallet.prototype.deriveByPath = function(hdKey, path, keyPath) { + console.log("derive by path") + keyPath = keyPath || (!!hdKey.keyPair.d ? "m" : "M"); + + if (path[0].toLowerCase() !== "m" || keyPath[0].toLowerCase() !== "m") { + throw new blocktrail.KeyPathError("Wallet.deriveByPath only works with absolute paths. (" + path + ", " + keyPath + ")"); + } + + if (path[0] === "m" && keyPath[0] === "M") { + throw new blocktrail.KeyPathError("Wallet.deriveByPath can't derive private path from public parent. (" + path + ", " + keyPath + ")"); + } + + // if the desired path is public while the input is private + var toPublic = path[0] === "M" && keyPath[0] === "m"; + if (toPublic) { + // derive the private path, convert to public when returning + path[0] = "m"; + } + + // keyPath should be the parent parent of path + if (path.toLowerCase().indexOf(keyPath.toLowerCase()) !== 0) { + throw new blocktrail.KeyPathError("Wallet.derivePath requires path (" + path + ") to be a child of keyPath (" + keyPath + ")"); + } + + // remove the part of the path we already have + path = path.substr(keyPath.length); + + // iterate over the chunks and derive + var newKey = hdKey; + path.replace(/^\//, "").split("/").forEach(function(chunk) { + if (!chunk) { + return; + } + + if (chunk.indexOf("'") !== -1) { + chunk = parseInt(chunk.replace("'", ""), 10) + bitcoin.HDNode.HIGHEST_BIT; + } + + newKey = newKey.derive(parseInt(chunk, 10)); + }); + + if (toPublic) { + return newKey.neutered(); + } else { + return newKey; + } +}; + +RskWallet.prototype.decodeAddress = function(address) { + return (ethutil.isValidAddress(address) ? {address: address, decoded: address, type: "rsk"} : new blocktrail.InvalidAddressError(err.message) ); +}; + +RskWallet.prototype.pay = function(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, twoFactorToken, options, cb) { + + /* jshint -W071 */ + var self = this; + + if (typeof changeAddress === "function") { + cb = changeAddress; + changeAddress = null; + } else if (typeof allowZeroConf === "function") { + cb = allowZeroConf; + allowZeroConf = false; + } else if (typeof randomizeChangeIdx === "function") { + cb = randomizeChangeIdx; + randomizeChangeIdx = true; + } else if (typeof feeStrategy === "function") { + cb = feeStrategy; + feeStrategy = null; + } else if (typeof twoFactorToken === "function") { + cb = twoFactorToken; + twoFactorToken = null; + } else if (typeof options === "function") { + cb = options; + options = {}; + } + + randomizeChangeIdx = typeof randomizeChangeIdx !== "undefined" ? randomizeChangeIdx : true; + feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL; + options = options || {}; + var checkFee = typeof options.checkFee !== "undefined" ? options.checkFee : true; + + var deferred = q.defer(); + deferred.promise.nodeify(cb); + + if (self.locked) { + deferred.reject(new blocktrail.WalletLockedError("Wallet needs to be unlocked to send coins")); + return deferred.promise; + } + + q.nextTick(function() { + deferred.notify(RskWallet.PAY_PROGRESS_START); + self.buildTransaction(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, options) + .then( + function(r) { return r; }, + function(e) { deferred.reject(e); }, + function(progress) { + deferred.notify(progress); + } + ) + .spread( + function(tx, utxos) { + + deferred.notify(RskWallet.PAY_PROGRESS_SEND); + + var data = { + signed_transaction: tx.toHex(), + base_transaction: tx.__toBuffer(null, null, false).toString('hex') + }; + + console.log(tx) + console.log(data) + + return self.sendTransaction(data, utxos.map(function(utxo) { return utxo['path']; }), checkFee, twoFactorToken, options.prioboost, options) + .then(function(result) { + deferred.notify(RskWallet.PAY_PROGRESS_DONE); + + if (!result || !result['complete'] || result['complete'] === 'false') { + deferred.reject(new blocktrail.TransactionSignError("Failed to completely sign transaction")); + } else { + return result['txid']; + } + }); + }, + function(e) { + throw e; + } + ) + .then( + function(r) { deferred.resolve(r); }, + function(e) { deferred.reject(e); } + ) + ; + }); + + return deferred.promise; +}; + +RskWallet.prototype.buildTransaction = function(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, options, cb) { + /* jshint -W071 */ + var self = this; + + if (typeof changeAddress === "function") { + cb = changeAddress; + changeAddress = null; + } else if (typeof allowZeroConf === "function") { + cb = allowZeroConf; + allowZeroConf = false; + } else if (typeof randomizeChangeIdx === "function") { + cb = randomizeChangeIdx; + randomizeChangeIdx = true; + } else if (typeof feeStrategy === "function") { + cb = feeStrategy; + feeStrategy = null; + } else if (typeof options === "function") { + cb = options; + options = {}; + } + + randomizeChangeIdx = typeof randomizeChangeIdx !== "undefined" ? randomizeChangeIdx : true; + feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL; + options = options || {}; + + + var txb; + var deferred = q.defer(); + + async.waterfall([ + /** + * init transaction builder + * + * @param cb + */ + function(cb) { + // blank ethereum transaction + // @TODO - other chain ids - testnet,regtest for rsk? + // chainId 31 is for mainnet rsk + txb = new ethtx(null,31); + + cb(); + }, + /** + * add transaction params + * + * @param cb + */ + function(cb) { + txb.nonce = 0; + txb.gasPrice = 10; + txb.gasLimit = 100; + txb.value = 0; + txb.data = 0; + txb.to = "0x0000000000000000000000000000000000000000" + + cb(); + }, + /** + * estimate fee to verify that the API is not providing us wrong data + * + * @param cb + */ + function(cb) { + // use web3.eth.gasPrice to get gas price for given network + cb(); + }, + /** + * sign transaction + * + * @param cb + */ + function(cb) { + var i, privKey, path + + deferred.notify(RskWallet.PAY_PROGRESS_SIGN); + + path = utxos[i]['path'].replace("M", "m"); + + // todo: regenerate scripts for path and compare for utxo (paranoid mode) + if (self.primaryPrivateKey) { + privKey = self.deriveByPath(self.primaryPrivateKey, path, "m").keyPair.toWIF(); + } + else { + throw new Error("No master privateKey present"); + } + console.log(privkey); + txb.sign(privkey); + serializedTx = txb.serialize(); + + cb(); + }, + ], function(err) { + if (err) { + deferred.reject(new blocktrail.WalletSendError(err)); + return; + } + + deferred.resolve([tx, utxos]); + }); + + return deferred.promise; +}; + +RskWallet.prototype.sendTransaction = function(txHex, paths, checkFee, twoFactorToken, prioboost, options, cb) { + var self = this; + + if (typeof twoFactorToken === "function") { + cb = twoFactorToken; + twoFactorToken = null; + prioboost = false; + } else if (typeof prioboost === "function") { + cb = prioboost; + prioboost = false; + } else if (typeof options === "function") { + cb = options; + options = {}; + } + + var deferred = q.defer(); + deferred.promise.nodeify(cb); + + self.sdk.sendTransaction(self.identifier, txHex, paths, checkFee, twoFactorToken, prioboost, options) + .then( + function(result) { + deferred.resolve(result); + }, + function(e) { + if (e.requires_2fa) { + deferred.reject(new blocktrail.WalletMissing2FAError()); + } else if (e.message.match(/Invalid two_factor_token/)) { + deferred.reject(new blocktrail.WalletInvalid2FAError()); + } else { + deferred.reject(e); + } + } + ) + ; + + return deferred.promise; +}; + +RskWallet.prototype.getAccount = function(address, options, cb) { + var self = this; + + if (typeof options === "function") { + cb = options; + options = {}; + } + + return self.sdk.getRskAccount(self.identifier, address, options, cb); +}; + +module.exports = RskWallet; \ No newline at end of file diff --git a/package.json b/package.json index b462b32..5b49658 100644 --- a/package.json +++ b/package.json @@ -57,12 +57,19 @@ "create-hmac": "^1.1.3", "crypto-js": "^3.1.5", "debug": "^2.6.9", + "ethereumjs-tx": "^1.3.7", + "ethereumjs-util": "^6.0.0", + "ethereumjs-wallet": "^0.6.2", + "grunt-contrib-connect": "^1.0.2", + "is-arrayish": "^0.3.2", "lodash": "~4.17.2", "pkginfo": "^0.4.1", "promise": "^6.1.0", "q": "1.0.1", - "randombytes": "^2.0.1", + "randombytes": "^2.0.6", "sjcl": "git://github.com/blocktrail/sjcl.git#minify-library", + "spdx-exceptions": "^2.2.0", + "spdx-license-ids": "^3.0.1", "superagent": "^3.8.1", "superagent-http-signature": "0.1.3", "superagent-promise": "^0.2.0", @@ -84,10 +91,10 @@ "devDependencies": { "blocktrail-sdk-backup-generator": "^0.2.0", "brfs": "*", - "browserify": "*", + "browserify": "^16.2.3", "browserify-versionify": "^1.0.6", "coveralls": "^2.13.1", - "grunt": "~0.4.2", + "grunt": "^0.4.5", "grunt-browserify": "git://github.com/jmreidy/grunt-browserify.git#4f96beb75d27fdebc4359e08e7db4c514f6265a8", "grunt-contrib-concat": "~0.5.1", "grunt-contrib-connect": "^0.7.1", diff --git a/vendor/ethereumjs-wallet b/vendor/ethereumjs-wallet new file mode 160000 index 0000000..3927a0e --- /dev/null +++ b/vendor/ethereumjs-wallet @@ -0,0 +1 @@ +Subproject commit 3927a0e5403ee79036dd0dcd0d97f2a5d7420f97 From e0bd7b3a55ed1276aa4aece5a57afae54d3bfb18 Mon Sep 17 00:00:00 2001 From: philip glazman <8378656+MangoSalad@users.noreply.github.com> Date: Thu, 8 Nov 2018 21:15:06 +0100 Subject: [PATCH 03/19] build and send transaction for rsk network --- lib/api_client.js | 65 ++++++++++++++-- lib/rsk-wallet.js | 184 ++++++++++++++++++++++++---------------------- 2 files changed, 155 insertions(+), 94 deletions(-) diff --git a/lib/api_client.js b/lib/api_client.js index 2ed77bb..a81ff8a 100644 --- a/lib/api_client.js +++ b/lib/api_client.js @@ -2079,12 +2079,11 @@ APIClient.prototype.getRskAccount = function(identifier, address, options, cb) { deferred.resolve( self.blocktrailClient.post("/wallet/" + identifier + "/account", params).then( function(result) { - return { - "confirmed_balance": result.confirmed_balance, - "address" : result.address, - "path": result.path, - "nonce": result.nonce, - }; + return [result.confirmed_balance, + result.address, + result.path, + result.nonce, + ]; }, function(err) { if (err.message.match(/too low to pay the fee/)) { @@ -2278,6 +2277,60 @@ APIClient.prototype.sendTransaction = function(identifier, txHex, paths, checkFe ); }; +APIClient.prototype.sendRskTransaction = function(identifier, txHex, paths, checkFee, twoFactorToken, prioboost, options, cb) { + var self = this; + + if (typeof twoFactorToken === "function") { + cb = twoFactorToken; + twoFactorToken = null; + prioboost = false; + } else if (typeof prioboost === "function") { + cb = prioboost; + prioboost = false; + } else if (typeof options === "function") { + cb = options; + options = {}; + } + + var data = { + paths: paths, + two_factor_token: twoFactorToken + }; + if (typeof txHex === "string") { + data.raw_transaction = txHex; + } else if (typeof txHex === "object") { + Object.keys(txHex).map(function(key) { + data[key] = txHex[key]; + }); + } + + var postOptions = { + check_fee: checkFee ? 1 : 0, + prioboost: prioboost ? 1 : 0 + }; + + var bip70 = false; + if (options.bip70PaymentUrl) { + bip70 = true; + postOptions.bip70PaymentUrl = options.bip70PaymentUrl; + + if (options.bip70MerchantData && options.bip70MerchantData instanceof Uint8Array) { + // Encode merchant data to base64 + var decoder = new TextDecoder('utf8'); + var bip70MerchantData = btoa(decoder.decode(options.bip70MerchantData)); + + postOptions.bip70MerchantData = bip70MerchantData; + } + } + + return self.blocktrailClient.post( + "/wallet/" + identifier + "/send", + postOptions, + data, + cb + ); +}; + /** * setup a webhook for this wallet * diff --git a/lib/rsk-wallet.js b/lib/rsk-wallet.js index 8f759db..08601b5 100644 --- a/lib/rsk-wallet.js +++ b/lib/rsk-wallet.js @@ -308,17 +308,11 @@ RskWallet.prototype.getNewAddress = function(chainIdx, cb) { */ RskWallet.prototype.getAddressByPath = function(path) { var self = this; - // console.log("checking this path: ", path); - // console.log("primary priv key:",self.primaryPrivateKey); //bitcoin var derivedPrimaryPublicKey = self.getPrimaryPublicKey(path); console.log("btc derived key:",derivedPrimaryPublicKey); - // var addy = derivedPrimaryPublicKey.keyPair.getAddress(); - // console.log("Bitcoin Address: ",addy); - + //rsk - // derivedPrimaryPublicKey = self.getPrimaryPublicKeyRsk(path); - // console.log("eth derived key: ",derivedPrimaryPublicKey) var derivedEthAddy = ethhdkey.fromExtendedKey(derivedPrimaryPublicKey.toBase58()) var address = derivedEthAddy.getWallet().getAddressString() console.log("rsk addy: ",address) @@ -355,6 +349,7 @@ RskWallet.prototype.getPrimaryPublicKey = function(path) { path = path.replace("m", "M"); + console.log(path); var keyIndex = path.split("/")[1].replace("'", ""); if (!self.primaryPublicKeys[keyIndex]) { @@ -418,6 +413,7 @@ RskWallet.prototype.deriveByPath = function(hdKey, path, keyPath) { newKey = newKey.derive(parseInt(chunk, 10)); }); + console.log(toPublic); if (toPublic) { return newKey.neutered(); } else { @@ -469,8 +465,7 @@ RskWallet.prototype.pay = function(pay, changeAddress, allowZeroConf, randomizeC q.nextTick(function() { deferred.notify(RskWallet.PAY_PROGRESS_START); - self.buildTransaction(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, options) - .then( + self.buildTransaction(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, options).then( function(r) { return r; }, function(e) { deferred.reject(e); }, function(progress) { @@ -478,19 +473,18 @@ RskWallet.prototype.pay = function(pay, changeAddress, allowZeroConf, randomizeC } ) .spread( - function(tx, utxos) { + function(tx) { deferred.notify(RskWallet.PAY_PROGRESS_SEND); - var data = { - signed_transaction: tx.toHex(), - base_transaction: tx.__toBuffer(null, null, false).toString('hex') - }; + // var data = { + // signed_transaction: tx.toHex(), + // base_transaction: tx.__toBuffer(null, null, false).toString('hex') + // }; console.log(tx) - console.log(data) - return self.sendTransaction(data, utxos.map(function(utxo) { return utxo['path']; }), checkFee, twoFactorToken, options.prioboost, options) + return self.sendTransaction(tx, checkFee, twoFactorToken, options.prioboost, options) .then(function(result) { deferred.notify(RskWallet.PAY_PROGRESS_DONE); @@ -541,85 +535,99 @@ RskWallet.prototype.buildTransaction = function(pay, changeAddress, allowZeroCon options = options || {}; - var txb; + var deferred = q.defer(); - async.waterfall([ - /** - * init transaction builder - * - * @param cb - */ - function(cb) { - // blank ethereum transaction - // @TODO - other chain ids - testnet,regtest for rsk? - // chainId 31 is for mainnet rsk - txb = new ethtx(null,31); - - cb(); - }, - /** - * add transaction params - * - * @param cb - */ - function(cb) { - txb.nonce = 0; - txb.gasPrice = 10; - txb.gasLimit = 100; - txb.value = 0; - txb.data = 0; - txb.to = "0x0000000000000000000000000000000000000000" - - cb(); - }, - /** - * estimate fee to verify that the API is not providing us wrong data - * - * @param cb - */ - function(cb) { - // use web3.eth.gasPrice to get gas price for given network - cb(); - }, - /** - * sign transaction - * - * @param cb - */ - function(cb) { - var i, privKey, path - - deferred.notify(RskWallet.PAY_PROGRESS_SIGN); - - path = utxos[i]['path'].replace("M", "m"); - - // todo: regenerate scripts for path and compare for utxo (paranoid mode) - if (self.primaryPrivateKey) { - privKey = self.deriveByPath(self.primaryPrivateKey, path, "m").keyPair.toWIF(); - } - else { - throw new Error("No master privateKey present"); - } - console.log(privkey); - txb.sign(privkey); - serializedTx = txb.serialize(); + // @TODO - get chainIdx and childIdx at init method + var path = "M/" + self.keyIndex + "'/" + 5 + "/" + 0; + console.log(path); + var accountAddress = self.getAddressByPath(path); - cb(); - }, - ], function(err) { - if (err) { - deferred.reject(new blocktrail.WalletSendError(err)); - return; - } + q.nextTick(function() { + deferred.resolve( + self.getAccount(accountAddress).spread(function(confirmed_balance,address,path,nonce){ + var txb; + var deferred = q.defer(); + async.waterfall([ + /** + * init transaction builder + * + * @param cb + */ + function(cb) { + // blank ethereum transaction + // @TODO - other chain ids - testnet,regtest for rsk? + // chainId 31 is for mainnet rsk + txb = new ethtx(null,31); + + cb(); + }, + /** + * add transaction params + * + * @param cb + */ + function(cb) { + txb.nonce = 0; + txb.gasPrice = 10; + txb.gasLimit = 100; + txb.value = 0; + txb.data = 0; + txb.to = "0x0000000000000000000000000000000000000000" + + cb(); + }, + /** + * estimate fee to verify that the API is not providing us wrong data + * + * @param cb + */ + function(cb) { + // use web3.eth.gasPrice to get gas price for given network + cb(); + }, + /** + * sign transaction + * + * @param cb + */ + function(cb) { + var i, privKey + + path = path.replace("M","m"); + + deferred.notify(RskWallet.PAY_PROGRESS_SIGN); + console.log(self.primaryPrivateKey); + // todo: regenerate scripts for path and compare for utxo (paranoid mode) + if (self.primaryPrivateKey) { + privKey = self.deriveByPath(self.primaryPrivateKey, path, "m"); + } + else { + throw new Error("No master privateKey present"); + } + txb.sign(ethutil.toBuffer("0x"+privKey.keyPair.d.toBuffer().toString('hex'))); + serializedTx = txb.serialize(); + + cb(); + }, + ], function(err) { + if (err) { + deferred.reject(new blocktrail.WalletSendError(err)); + return; + } - deferred.resolve([tx, utxos]); + deferred.resolve([txb]); + }); + + return deferred.promise; + }) + ) }); - + return deferred.promise; }; -RskWallet.prototype.sendTransaction = function(txHex, paths, checkFee, twoFactorToken, prioboost, options, cb) { +RskWallet.prototype.sendTransaction = function(txHex, checkFee, twoFactorToken, prioboost, options, cb) { var self = this; if (typeof twoFactorToken === "function") { @@ -637,7 +645,7 @@ RskWallet.prototype.sendTransaction = function(txHex, paths, checkFee, twoFactor var deferred = q.defer(); deferred.promise.nodeify(cb); - self.sdk.sendTransaction(self.identifier, txHex, paths, checkFee, twoFactorToken, prioboost, options) + self.sdk.sendTransaction(self.identifier, txHex, checkFee, twoFactorToken, prioboost, options) .then( function(result) { deferred.resolve(result); From 96b3ba4081dc9db35ef45df1c16a15e1a4979b24 Mon Sep 17 00:00:00 2001 From: philip glazman <8378656+MangoSalad@users.noreply.github.com> Date: Tue, 13 Nov 2018 11:51:43 +0100 Subject: [PATCH 04/19] removed rsk-wallet class and included rsk functionality in wallet. --- lib/api_client.js | 173 ++++-------- lib/rsk-wallet.js | 679 ---------------------------------------------- lib/wallet.js | 333 ++++++++++++++++++++++- 3 files changed, 385 insertions(+), 800 deletions(-) delete mode 100644 lib/rsk-wallet.js diff --git a/lib/api_client.js b/lib/api_client.js index a81ff8a..9776f4e 100644 --- a/lib/api_client.js +++ b/lib/api_client.js @@ -1281,114 +1281,63 @@ APIClient.prototype.initWallet = function(options, cb) { throw new Error("Backup key returned from server didn't match our own copy"); } } - var backupPublicKey = bitcoin.HDNode.fromBase58(result.backup_public_key[0], self.network); - var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) { - return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network); - }); - var primaryPublicKeys = _.mapValues(result.primary_public_keys, function(primaryPublicKey) { - return bitcoin.HDNode.fromBase58(primaryPublicKey[0], self.network); - }); - - // initialize wallet - var wallet = new Wallet( - self, - identifier, - options.walletVersion, - result.primary_mnemonic, - result.encrypted_primary_seed, - result.encrypted_secret, - primaryPublicKeys, - backupPublicKey, - blocktrailPublicKeys, - keyIndex, - result.segwit || 0, - self.testnet, - self.regtest, - result.checksum, - result.upgrade_key_index, - options.useCashAddress, - options.bypassNewAddressCheck - ); - - wallet.recoverySecret = result.recovery_secret; - - if (!options.readOnly) { - return wallet.unlock(options).then(function() { - return wallet; - }); - } else { - return wallet; - } - })); - - return deferred.promise; -}; - -/** - * initialize an existing RSK wallet - * - * Either takes two argument: - * @param options object {} - * @param [cb] function callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys) - * - * @returns {q.Promise} - */ -APIClient.prototype.initRskWallet = function(options, cb) { - var self = this; - - if (typeof options !== "object") { - // get the old-style arguments - options = { - identifier: arguments[0], - passphrase: arguments[1] - }; - - cb = arguments[2]; - } - - if (options.check_backup_key) { - if (typeof options.check_backup_key !== "string") { - throw new Error("Invalid input, must provide the backup key as a string (the xpub)"); - } - } - - var deferred = q.defer(); - deferred.promise.spreadNodeify(cb); - var identifier = options.identifier; - - if (!identifier) { - deferred.reject(new blocktrail.WalletInitError("Identifier is required")); - return deferred.promise; - } - - deferred.resolve(self.blocktrailClient.get("/wallet/" + identifier, null, true).then(function(result) { - var keyIndex = options.keyIndex || result.key_index; + var backupPublicKey,blocktrailPublicKeys,primaryPublicKeys, wallet; - options.walletVersion = result.wallet_version; - - var primaryPublicKeys = _.mapValues(result.primary_public_keys, function(primaryPublicKey) { + primaryPublicKeys = _.mapValues(result.primary_public_keys, function(primaryPublicKey) { return bitcoin.HDNode.fromBase58(primaryPublicKey[0], self.network); }); + + if (self.chain === "RSK") + { + wallet = new Wallet( + self, + identifier, + options.walletVersion, + result.primary_mnemonic, + result.encrypted_primary_seed, + result.encrypted_secret, + primaryPublicKeys, + backupPublicKey, + blocktrailPublicKeys, + keyIndex, + result.segwit || 0, + self.testnet, + self.regtest, + result.checksum, + result.upgrade_key_index, + options.useCashAddress, + options.bypassNewAddressCheck + ); + } + else + { + backupPublicKey = bitcoin.HDNode.fromBase58(result.backup_public_key[0], self.network); + blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) { + return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network); + }); - // initialize wallet - var wallet = new RskWallet( - self, - identifier, - options.walletVersion, - result.primary_mnemonic, - result.encrypted_primary_seed, - result.encrypted_secret, - primaryPublicKeys, - keyIndex, - result.segwit || 0, - self.testnet, - self.regtest, - result.checksum, - result.upgrade_key_index, - options.useCashAddress, - options.bypassNewAddressCheck - ); + // initialize wallet + wallet = new Wallet( + self, + identifier, + options.walletVersion, + result.primary_mnemonic, + result.encrypted_primary_seed, + result.encrypted_secret, + primaryPublicKeys, + backupPublicKey, + blocktrailPublicKeys, + keyIndex, + result.segwit || 0, + self.testnet, + self.regtest, + result.checksum, + result.upgrade_key_index, + options.useCashAddress, + options.bypassNewAddressCheck + ); + } wallet.recoverySecret = result.recovery_secret; @@ -2277,7 +2226,7 @@ APIClient.prototype.sendTransaction = function(identifier, txHex, paths, checkFe ); }; -APIClient.prototype.sendRskTransaction = function(identifier, txHex, paths, checkFee, twoFactorToken, prioboost, options, cb) { +APIClient.prototype.sendRskTransaction = function(identifier, txHex, checkFee, twoFactorToken, prioboost, options, cb) { var self = this; if (typeof twoFactorToken === "function") { @@ -2293,7 +2242,7 @@ APIClient.prototype.sendRskTransaction = function(identifier, txHex, paths, chec } var data = { - paths: paths, + // paths: paths, two_factor_token: twoFactorToken }; if (typeof txHex === "string") { @@ -2309,20 +2258,6 @@ APIClient.prototype.sendRskTransaction = function(identifier, txHex, paths, chec prioboost: prioboost ? 1 : 0 }; - var bip70 = false; - if (options.bip70PaymentUrl) { - bip70 = true; - postOptions.bip70PaymentUrl = options.bip70PaymentUrl; - - if (options.bip70MerchantData && options.bip70MerchantData instanceof Uint8Array) { - // Encode merchant data to base64 - var decoder = new TextDecoder('utf8'); - var bip70MerchantData = btoa(decoder.decode(options.bip70MerchantData)); - - postOptions.bip70MerchantData = bip70MerchantData; - } - } - return self.blocktrailClient.post( "/wallet/" + identifier + "/send", postOptions, diff --git a/lib/rsk-wallet.js b/lib/rsk-wallet.js deleted file mode 100644 index 08601b5..0000000 --- a/lib/rsk-wallet.js +++ /dev/null @@ -1,679 +0,0 @@ -var _ = require('lodash'); -var assert = require('assert'); -var q = require('q'); -var async = require('async'); -var bitcoin = require('bitcoinjs-lib'); -var bitcoinMessage = require('bitcoinjs-message'); -var blocktrail = require('./blocktrail'); -var CryptoJS = require('crypto-js'); -var Encryption = require('./encryption'); -var EncryptionMnemonic = require('./encryption_mnemonic'); -var SizeEstimation = require('./size_estimation'); -var bip39 = require('bip39'); -var Wallet = require('./wallet'); -var ethereumjs = require('ethereumjs-wallet') -var ethutil = require('ethereumjs-util') -var ethhdkey = require('ethereumjs-wallet/hdkey') -const ethtx = require('ethereumjs-tx') - - -var SignMode = { - SIGN: "sign", - DONT_SIGN: "dont_sign" -}; - -/** - * - * @param sdk APIClient SDK instance used to do requests - * @param identifier string identifier of the wallet - * @param walletVersion string - * @param primaryMnemonic string primary mnemonic - * @param encryptedPrimarySeed - * @param encryptedSecret - * @param primaryPublicKeys string primary mnemonic - * @param backupPublicKey string BIP32 master pubKey M/ - * @param blocktrailPublicKeys array list of blocktrail pubKeys indexed by keyIndex - * @param keyIndex int key index to use - * @param segwit int segwit toggle from server - * @param testnet bool testnet - * @param regtest bool regtest - * @param checksum string - * @param upgradeToKeyIndex int - * @param useNewCashAddr bool flag to opt in to bitcoin cash cashaddr's - * @param bypassNewAddressCheck bool flag to indicate if wallet should/shouldn't derive new address locally to verify api - * @constructor - * @internal - */ -var RskWallet = function( - sdk, - identifier, - walletVersion, - primaryMnemonic, - encryptedPrimarySeed, - encryptedSecret, - primaryPublicKeys, - keyIndex, - segwit, - testnet, - regtest, - checksum, - upgradeToKeyIndex, - useNewCashAddr, - bypassNewAddressCheck -) { - /* jshint -W071 */ - var self = this; - - self.sdk = sdk; - self.identifier = identifier; - self.walletVersion = walletVersion; - self.locked = true; - self.bypassNewAddressCheck = !!bypassNewAddressCheck; - self.bitcoinCash = self.sdk.bitcoinCash; - self.segwit = !!segwit; - self.useNewCashAddr = !!useNewCashAddr; - assert(!self.segwit || !self.bitcoinCash); - - self.testnet = testnet; - self.regtest = regtest; - if (self.bitcoinCash) { - if (self.regtest) { - self.network = bitcoin.networks.bitcoincashregtest; - } else if (self.testnet) { - self.network = bitcoin.networks.bitcoincashtestnet; - } else { - self.network = bitcoin.networks.bitcoincash; - } - } else { - if (self.regtest) { - self.network = bitcoin.networks.regtest; - } else if (self.testnet) { - self.network = bitcoin.networks.testnet; - } else { - self.network = bitcoin.networks.bitcoin; - } - } - - assert(_.every(primaryPublicKeys, function(primaryPublicKey) { return primaryPublicKey instanceof bitcoin.HDNode; })); - - // v1 - self.primaryMnemonic = primaryMnemonic; - - // v2 & v3 - self.encryptedPrimarySeed = encryptedPrimarySeed; - self.encryptedSecret = encryptedSecret; - - self.primaryPrivateKey = null; - - self.primaryPublicKeys = primaryPublicKeys; - self.keyIndex = keyIndex; - - self.chain = RskWallet.CHAIN_RSK_DEFAULT; - self.changeChain = RskWallet.CHAIN_RSK_DEFAULT; - - self.checksum = checksum; - self.upgradeToKeyIndex = upgradeToKeyIndex; - - self.secret = null; - self.seedHex = null; -}; - -RskWallet.CHAIN_RSK_DEFAULT = 5; - -RskWallet.PAY_PROGRESS_START = 0; -RskWallet.PAY_PROGRESS_COIN_SELECTION = 10; -RskWallet.PAY_PROGRESS_CHANGE_ADDRESS = 20; -RskWallet.PAY_PROGRESS_SIGN = 30; -RskWallet.PAY_PROGRESS_SEND = 40; -RskWallet.PAY_PROGRESS_DONE = 100; - - -RskWallet.prototype.unlock = function(options, cb) { - var self = this; - - var deferred = q.defer(); - deferred.promise.nodeify(cb); - - // avoid modifying passed options - options = _.merge({}, options); - - q.fcall(function() { - switch (self.walletVersion) { - case Wallet.WALLET_VERSION_V1: - return self.unlockV1(options); - - case Wallet.WALLET_VERSION_V2: - return self.unlockV2(options); - - case Wallet.WALLET_VERSION_V3: - return self.unlockV3(options); - - default: - return q.reject(new blocktrail.WalletInitError("Invalid wallet version")); - } - }).then( - function(primaryPrivateKey) { - self.primaryPrivateKey = primaryPrivateKey; - - // create a checksum of our private key which we'll later use to verify we used the right password - var checksum = self.primaryPrivateKey.getAddress(); - - // check if we've used the right passphrase - if (checksum !== self.checksum) { - throw new blocktrail.WalletChecksumError("Generated checksum [" + checksum + "] does not match " + - "[" + self.checksum + "], most likely due to incorrect password"); - } - - self.locked = false; - - // if the response suggests we should upgrade to a different blocktrail cosigning key then we should - if (typeof self.upgradeToKeyIndex !== "undefined" && self.upgradeToKeyIndex !== null) { - return self.upgradeKeyIndex(self.upgradeToKeyIndex); - } - } - ).then( - function(r) { - deferred.resolve(r); - }, - function(e) { - deferred.reject(e); - } - ); - - return deferred.promise; -}; - -RskWallet.prototype.unlockV3 = function(options, cb) { - var self = this; - console.log("in unlock") - - var deferred = q.defer(); - deferred.promise.nodeify(cb); - - deferred.resolve(q.fcall(function() { - return q.when() - .then(function() { - /* jshint -W071, -W074 */ - options.encryptedPrimarySeed = typeof options.encryptedPrimarySeed !== "undefined" ? options.encryptedPrimarySeed : self.encryptedPrimarySeed; - options.encryptedSecret = typeof options.encryptedSecret !== "undefined" ? options.encryptedSecret : self.encryptedSecret; - - if (options.secret) { - self.secret = options.secret; - } - - if (options.primaryPrivateKey) { - throw new blocktrail.WalletInitError("specifying primaryPrivateKey has been deprecated"); - } - - if (options.primarySeed) { - self.primarySeed = options.primarySeed; - } else if (options.secret) { - return self.sdk.promisedDecrypt(new Buffer(options.encryptedPrimarySeed, 'base64'), self.secret) - .then(function(primarySeed) { - self.primarySeed = primarySeed; - }, function() { - throw new blocktrail.WalletDecryptError("Failed to decrypt primarySeed"); - }); - } else { - // avoid conflicting options - if (options.passphrase && options.password) { - throw new blocktrail.WalletCreateError("Can't specify passphrase and password"); - } - // normalize passphrase/password - options.passphrase = options.passphrase || options.password; - delete options.password; - - return self.sdk.promisedDecrypt(new Buffer(options.encryptedSecret, 'base64'), new Buffer(options.passphrase)) - .then(function(secret) { - self.secret = secret; - }, function() { - throw new blocktrail.WalletDecryptError("Failed to decrypt secret"); - }) - .then(function() { - return self.sdk.promisedDecrypt(new Buffer(options.encryptedPrimarySeed, 'base64'), self.secret) - .then(function(primarySeed) { - self.primarySeed = primarySeed; - }, function() { - throw new blocktrail.WalletDecryptError("Failed to decrypt primarySeed"); - }); - }); - } - }) - .then(function() { - return bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network); - }) - ; - })); - - return deferred.promise; -}; - -/** - * generate a new derived private key and return the new address for it - * - * @param [chainIdx] int - * @param [cb] function callback(err, address) - * @returns {q.Promise} - */ -RskWallet.prototype.getNewAddress = function(chainIdx, cb) { - var self = this; - - // chainIdx is optional - if (typeof chainIdx === "function") { - cb = chainIdx; - chainIdx = null; - } - - var deferred = q.defer(); - deferred.promise.spreadNodeify(cb); - - // Only enter if it's not an integer - if (chainIdx !== parseInt(chainIdx, 10)) { - // deal with undefined or null, assume defaults - if (typeof chainIdx === "undefined" || chainIdx === null) { - chainIdx = self.chain; - } else { - // was a variable but not integer - deferred.reject(new Error("Invalid chain index")); - return deferred.promise; - } - } - - deferred.resolve(self.sdk.getRskNewDerivation(self.identifier, "M/" + self.keyIndex + "'/" + chainIdx) - .then(function(newDerivation) { - var path = newDerivation.path.replace("\'","").replace('9999',"9999'"); - console.log(path) - var addressFromServer = newDerivation.address; - - var verifyAddress = self.getAddressByPath(path); - - console.log("Server addy:",addressFromServer) - console.log("Client addy:", verifyAddress) - - if (verifyAddress !== addressFromServer) { - throw new blocktrail.WalletAddressError("Failed to verify address [" + naddressFromServer + "] !== [" + verifyAddress + "]"); - } - - return [verifyAddress, path]; - }) - ); - return deferred.promise; -}; - -/** - * get address for specified path - * - * @param path - * @returns string - */ -RskWallet.prototype.getAddressByPath = function(path) { - var self = this; - //bitcoin - var derivedPrimaryPublicKey = self.getPrimaryPublicKey(path); - console.log("btc derived key:",derivedPrimaryPublicKey); - - //rsk - var derivedEthAddy = ethhdkey.fromExtendedKey(derivedPrimaryPublicKey.toBase58()) - var address = derivedEthAddy.getWallet().getAddressString() - console.log("rsk addy: ",address) - return address -}; - -// RskWallet.prototype.getPrimaryPublicKeyRsk = function(path) { -// var self = this; -// console.log("getprimarypublickey") -// path = path.replace("m", "M"); - -// var keyIndex = path.split("/")[1].replace("'", ""); - -// if (!self.primaryPublicKeys[keyIndex]) { -// if (self.primaryPrivateKey) { -// self.primaryPublicKeys[keyIndex] = self.deriveByPathRsk(self.primaryPrivateKey, "M/" + keyIndex + "'", "m"); -// } else { -// throw new blocktrail.KeyPathError("Wallet.getPrimaryPublicKey keyIndex (" + keyIndex + ") is unknown to us"); -// } -// } -// var primaryPublicKey = self.primaryPublicKeys[keyIndex]; -// return self.deriveByPathRsk(self.primaryPrivateKey, path, "M/" + keyIndex + "'"); -// }; - -/** - * get primary public key by path - * first level of the path is used as keyIndex to find the correct key in the dict - * - * @param path string - * @returns {bitcoin.HDNode} - */ -RskWallet.prototype.getPrimaryPublicKey = function(path) { - var self = this; - - path = path.replace("m", "M"); - - console.log(path); - var keyIndex = path.split("/")[1].replace("'", ""); - - if (!self.primaryPublicKeys[keyIndex]) { - if (self.primaryPrivateKey) { - self.primaryPublicKeys[keyIndex] = self.deriveByPath(self.primaryPrivateKey, "M/" + keyIndex + "'", "m"); - } else { - throw new blocktrail.KeyPathError("Wallet.getPrimaryPublicKey keyIndex (" + keyIndex + ") is unknown to us"); - } - } - - var primaryPublicKey = self.primaryPublicKeys[keyIndex]; - return self.deriveByPath(primaryPublicKey, path, "M/" + keyIndex + "'"); -}; - -/** - * create derived key from parent key by path - * - * @param hdKey {bitcoin.HDNode} - * @param path string - * @param keyPath string - * @returns {bitcoin.HDNode} - */ -RskWallet.prototype.deriveByPath = function(hdKey, path, keyPath) { - console.log("derive by path") - keyPath = keyPath || (!!hdKey.keyPair.d ? "m" : "M"); - - if (path[0].toLowerCase() !== "m" || keyPath[0].toLowerCase() !== "m") { - throw new blocktrail.KeyPathError("Wallet.deriveByPath only works with absolute paths. (" + path + ", " + keyPath + ")"); - } - - if (path[0] === "m" && keyPath[0] === "M") { - throw new blocktrail.KeyPathError("Wallet.deriveByPath can't derive private path from public parent. (" + path + ", " + keyPath + ")"); - } - - // if the desired path is public while the input is private - var toPublic = path[0] === "M" && keyPath[0] === "m"; - if (toPublic) { - // derive the private path, convert to public when returning - path[0] = "m"; - } - - // keyPath should be the parent parent of path - if (path.toLowerCase().indexOf(keyPath.toLowerCase()) !== 0) { - throw new blocktrail.KeyPathError("Wallet.derivePath requires path (" + path + ") to be a child of keyPath (" + keyPath + ")"); - } - - // remove the part of the path we already have - path = path.substr(keyPath.length); - - // iterate over the chunks and derive - var newKey = hdKey; - path.replace(/^\//, "").split("/").forEach(function(chunk) { - if (!chunk) { - return; - } - - if (chunk.indexOf("'") !== -1) { - chunk = parseInt(chunk.replace("'", ""), 10) + bitcoin.HDNode.HIGHEST_BIT; - } - - newKey = newKey.derive(parseInt(chunk, 10)); - }); - - console.log(toPublic); - if (toPublic) { - return newKey.neutered(); - } else { - return newKey; - } -}; - -RskWallet.prototype.decodeAddress = function(address) { - return (ethutil.isValidAddress(address) ? {address: address, decoded: address, type: "rsk"} : new blocktrail.InvalidAddressError(err.message) ); -}; - -RskWallet.prototype.pay = function(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, twoFactorToken, options, cb) { - - /* jshint -W071 */ - var self = this; - - if (typeof changeAddress === "function") { - cb = changeAddress; - changeAddress = null; - } else if (typeof allowZeroConf === "function") { - cb = allowZeroConf; - allowZeroConf = false; - } else if (typeof randomizeChangeIdx === "function") { - cb = randomizeChangeIdx; - randomizeChangeIdx = true; - } else if (typeof feeStrategy === "function") { - cb = feeStrategy; - feeStrategy = null; - } else if (typeof twoFactorToken === "function") { - cb = twoFactorToken; - twoFactorToken = null; - } else if (typeof options === "function") { - cb = options; - options = {}; - } - - randomizeChangeIdx = typeof randomizeChangeIdx !== "undefined" ? randomizeChangeIdx : true; - feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL; - options = options || {}; - var checkFee = typeof options.checkFee !== "undefined" ? options.checkFee : true; - - var deferred = q.defer(); - deferred.promise.nodeify(cb); - - if (self.locked) { - deferred.reject(new blocktrail.WalletLockedError("Wallet needs to be unlocked to send coins")); - return deferred.promise; - } - - q.nextTick(function() { - deferred.notify(RskWallet.PAY_PROGRESS_START); - self.buildTransaction(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, options).then( - function(r) { return r; }, - function(e) { deferred.reject(e); }, - function(progress) { - deferred.notify(progress); - } - ) - .spread( - function(tx) { - - deferred.notify(RskWallet.PAY_PROGRESS_SEND); - - // var data = { - // signed_transaction: tx.toHex(), - // base_transaction: tx.__toBuffer(null, null, false).toString('hex') - // }; - - console.log(tx) - - return self.sendTransaction(tx, checkFee, twoFactorToken, options.prioboost, options) - .then(function(result) { - deferred.notify(RskWallet.PAY_PROGRESS_DONE); - - if (!result || !result['complete'] || result['complete'] === 'false') { - deferred.reject(new blocktrail.TransactionSignError("Failed to completely sign transaction")); - } else { - return result['txid']; - } - }); - }, - function(e) { - throw e; - } - ) - .then( - function(r) { deferred.resolve(r); }, - function(e) { deferred.reject(e); } - ) - ; - }); - - return deferred.promise; -}; - -RskWallet.prototype.buildTransaction = function(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, options, cb) { - /* jshint -W071 */ - var self = this; - - if (typeof changeAddress === "function") { - cb = changeAddress; - changeAddress = null; - } else if (typeof allowZeroConf === "function") { - cb = allowZeroConf; - allowZeroConf = false; - } else if (typeof randomizeChangeIdx === "function") { - cb = randomizeChangeIdx; - randomizeChangeIdx = true; - } else if (typeof feeStrategy === "function") { - cb = feeStrategy; - feeStrategy = null; - } else if (typeof options === "function") { - cb = options; - options = {}; - } - - randomizeChangeIdx = typeof randomizeChangeIdx !== "undefined" ? randomizeChangeIdx : true; - feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL; - options = options || {}; - - - - var deferred = q.defer(); - - // @TODO - get chainIdx and childIdx at init method - var path = "M/" + self.keyIndex + "'/" + 5 + "/" + 0; - console.log(path); - var accountAddress = self.getAddressByPath(path); - - q.nextTick(function() { - deferred.resolve( - self.getAccount(accountAddress).spread(function(confirmed_balance,address,path,nonce){ - var txb; - var deferred = q.defer(); - async.waterfall([ - /** - * init transaction builder - * - * @param cb - */ - function(cb) { - // blank ethereum transaction - // @TODO - other chain ids - testnet,regtest for rsk? - // chainId 31 is for mainnet rsk - txb = new ethtx(null,31); - - cb(); - }, - /** - * add transaction params - * - * @param cb - */ - function(cb) { - txb.nonce = 0; - txb.gasPrice = 10; - txb.gasLimit = 100; - txb.value = 0; - txb.data = 0; - txb.to = "0x0000000000000000000000000000000000000000" - - cb(); - }, - /** - * estimate fee to verify that the API is not providing us wrong data - * - * @param cb - */ - function(cb) { - // use web3.eth.gasPrice to get gas price for given network - cb(); - }, - /** - * sign transaction - * - * @param cb - */ - function(cb) { - var i, privKey - - path = path.replace("M","m"); - - deferred.notify(RskWallet.PAY_PROGRESS_SIGN); - console.log(self.primaryPrivateKey); - // todo: regenerate scripts for path and compare for utxo (paranoid mode) - if (self.primaryPrivateKey) { - privKey = self.deriveByPath(self.primaryPrivateKey, path, "m"); - } - else { - throw new Error("No master privateKey present"); - } - txb.sign(ethutil.toBuffer("0x"+privKey.keyPair.d.toBuffer().toString('hex'))); - serializedTx = txb.serialize(); - - cb(); - }, - ], function(err) { - if (err) { - deferred.reject(new blocktrail.WalletSendError(err)); - return; - } - - deferred.resolve([txb]); - }); - - return deferred.promise; - }) - ) - }); - - return deferred.promise; -}; - -RskWallet.prototype.sendTransaction = function(txHex, checkFee, twoFactorToken, prioboost, options, cb) { - var self = this; - - if (typeof twoFactorToken === "function") { - cb = twoFactorToken; - twoFactorToken = null; - prioboost = false; - } else if (typeof prioboost === "function") { - cb = prioboost; - prioboost = false; - } else if (typeof options === "function") { - cb = options; - options = {}; - } - - var deferred = q.defer(); - deferred.promise.nodeify(cb); - - self.sdk.sendTransaction(self.identifier, txHex, checkFee, twoFactorToken, prioboost, options) - .then( - function(result) { - deferred.resolve(result); - }, - function(e) { - if (e.requires_2fa) { - deferred.reject(new blocktrail.WalletMissing2FAError()); - } else if (e.message.match(/Invalid two_factor_token/)) { - deferred.reject(new blocktrail.WalletInvalid2FAError()); - } else { - deferred.reject(e); - } - } - ) - ; - - return deferred.promise; -}; - -RskWallet.prototype.getAccount = function(address, options, cb) { - var self = this; - - if (typeof options === "function") { - cb = options; - options = {}; - } - - return self.sdk.getRskAccount(self.identifier, address, options, cb); -}; - -module.exports = RskWallet; \ No newline at end of file diff --git a/lib/wallet.js b/lib/wallet.js index d1d245e..41ca6f8 100644 --- a/lib/wallet.js +++ b/lib/wallet.js @@ -11,6 +11,11 @@ var EncryptionMnemonic = require('./encryption_mnemonic'); var SizeEstimation = require('./size_estimation'); var bip39 = require('bip39'); +// Libraries for rootstock, ethereum key management. +var ethereumKey = require('ethereumjs-wallet/hdkey') +var ethutil = require('ethereumjs-util') +var ethtx = require('ethereumjs-tx') + var SignMode = { SIGN: "sign", DONT_SIGN: "dont_sign" @@ -90,9 +95,12 @@ var Wallet = function( } } - assert(backupPublicKey instanceof bitcoin.HDNode); assert(_.every(primaryPublicKeys, function(primaryPublicKey) { return primaryPublicKey instanceof bitcoin.HDNode; })); - assert(_.every(blocktrailPublicKeys, function(blocktrailPublicKey) { return blocktrailPublicKey instanceof bitcoin.HDNode; })); + + if (self.sdk.chain != "RSK") { + assert(backupPublicKey instanceof bitcoin.HDNode); + assert(_.every(blocktrailPublicKeys, function(blocktrailPublicKey) { return blocktrailPublicKey instanceof bitcoin.HDNode; })); + } // v1 self.primaryMnemonic = primaryMnemonic; @@ -148,6 +156,7 @@ Wallet.PAY_PROGRESS_DONE = 100; Wallet.CHAIN_BTC_DEFAULT = 0; Wallet.CHAIN_BTC_SEGWIT = 2; Wallet.CHAIN_BCC_DEFAULT = 1; +Wallet.CHAIN_RSK_DEFAULT = 5; Wallet.FEE_STRATEGY_FORCE_FEE = blocktrail.FEE_STRATEGY_FORCE_FEE; Wallet.FEE_STRATEGY_BASE_FEE = blocktrail.FEE_STRATEGY_BASE_FEE; @@ -695,6 +704,80 @@ Wallet.prototype.upgradeKeyIndex = function(keyIndex, cb) { return deferred.promise; }; +/** + * generate a new derived private key and return the rootstock address for it + * + * @param [chainIdx] int + * @param [cb] function callback(err, address) + * @returns {q.Promise} + */ +Wallet.prototype.getRskAddress = function(chainIdx, cb) { + var self = this; + + // chainIdx is optional + if (typeof chainIdx === "function") { + cb = chainIdx; + chainIdx = null; + } + + var deferred = q.defer(); + deferred.promise.spreadNodeify(cb); + + // Only enter if it's not an integer + if (chainIdx !== parseInt(chainIdx, 10)) { + // deal with undefined or null, assume defaults + if (typeof chainIdx === "undefined" || chainIdx === null) { + chainIdx = Wallet.CHAIN_RSK_DEFAULT; + } else { + // was a variable but not integer + deferred.reject(new Error("Invalid chain index")); + return deferred.promise; + } + } + + // Get Rootstock address from server. + deferred.resolve(self.sdk.getRskNewDerivation(self.identifier, "M/" + self.keyIndex + "'/" + chainIdx) + .then(function(newDerivation) { + + var path = newDerivation.path.replace("\'","").replace('9999',"9999'"); + var addressFromServer = newDerivation.address; + + // Verify Rootstock address client-side. + var verifyAddress = self.getRskAddressByPath(path); + + if (verifyAddress !== addressFromServer) { + throw new blocktrail.WalletAddressError("Failed to verify address [" + addressFromServer + "] !== [" + verifyAddress + "]"); + } + + return [verifyAddress, path]; + })); + + return deferred.promise; +}; + +/** + * get rootstock address for specified path + * + * @param path + * @returns string + */ +Wallet.prototype.getRskAddressByPath = function(path) { + var self = this; + + // Get the derived primary public key. + var derivedPrimaryPublicKey = self.getPrimaryPublicKey(path); + + // Encode the derived key to base58. + var encodedPublicKey = derivedPrimaryPublicKey.toBase58(); + + // Instantiate an HD ethereum key using the extended public key. + var derivedEthereumKey = ethereumKey.fromExtendedKey(encodedPublicKey); + + // Use the public key to encode an ethereum address (without address checksum). + var address = derivedEthereumKey.getWallet().getAddressString(); + return address; +}; + /** * generate a new derived private key and return the new address for it * @@ -1886,4 +1969,250 @@ Wallet.deriveByPath = function(hdKey, path, keyPath) { } }; +Wallet.prototype.rskPay = function(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, twoFactorToken, options, cb) { + + /* jshint -W071 */ + var self = this; + + if (typeof changeAddress === "function") { + cb = changeAddress; + changeAddress = null; + } else if (typeof allowZeroConf === "function") { + cb = allowZeroConf; + allowZeroConf = false; + } else if (typeof randomizeChangeIdx === "function") { + cb = randomizeChangeIdx; + randomizeChangeIdx = true; + } else if (typeof feeStrategy === "function") { + cb = feeStrategy; + feeStrategy = null; + } else if (typeof twoFactorToken === "function") { + cb = twoFactorToken; + twoFactorToken = null; + } else if (typeof options === "function") { + cb = options; + options = {}; + } + + randomizeChangeIdx = typeof randomizeChangeIdx !== "undefined" ? randomizeChangeIdx : true; + feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL; + options = options || {}; + var checkFee = typeof options.checkFee !== "undefined" ? options.checkFee : true; + + var deferred = q.defer(); + deferred.promise.nodeify(cb); + + if (self.locked) { + deferred.reject(new blocktrail.WalletLockedError("Wallet needs to be unlocked to send coins")); + return deferred.promise; + } + + q.nextTick(function() { + + self.buildRskTransaction(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, options).then( + function(r) { return r; }, + function(e) { deferred.reject(e); }, + ) + .spread( + function(tx) { + + // var data = { + // signed_transaction: tx.toHex(), + // base_transaction: tx.__toBuffer(null, null, false).toString('hex') + // }; + + console.log(tx) + + return self.sendRskTransaction(tx, checkFee, twoFactorToken, options.prioboost, options) + .then(function(result) { + + + if (!result || !result['complete'] || result['complete'] === 'false') { + deferred.reject(new Error("Failed to send transaction.")); + } else { + return result['txid']; + } + }); + }, + function(e) { + throw e; + } + ) + .then( + function(r) { deferred.resolve(r); }, + function(e) { deferred.reject(e); } + ) + ; + }); + + return deferred.promise; +}; + +Wallet.prototype.buildRskTransaction = function(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, options, cb) { + /* jshint -W071 */ + var self = this; + + if (typeof changeAddress === "function") { + cb = changeAddress; + changeAddress = null; + } else if (typeof allowZeroConf === "function") { + cb = allowZeroConf; + allowZeroConf = false; + } else if (typeof randomizeChangeIdx === "function") { + cb = randomizeChangeIdx; + randomizeChangeIdx = true; + } else if (typeof feeStrategy === "function") { + cb = feeStrategy; + feeStrategy = null; + } else if (typeof options === "function") { + cb = options; + options = {}; + } + + randomizeChangeIdx = typeof randomizeChangeIdx !== "undefined" ? randomizeChangeIdx : true; + feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL; + options = options || {}; + + + + var deferred = q.defer(); + + // @TODO - get chainIdx and childIdx at init method + var path = "M/" + self.keyIndex + "'/" + 5 + "/" + 0; + console.log(path); + var accountAddress = self.getAddressByPath(path); + + q.nextTick(function() { + deferred.resolve( + self.getRskAccount(accountAddress).spread(function(confirmed_balance,address,path,nonce){ + var txb, serializedTx; + var deferred = q.defer(); + async.waterfall([ + /** + * init transaction builder + * + * @param cb + */ + function(cb) { + // blank ethereum transaction + // @TODO - other chain ids - testnet,regtest for rsk? + // chainId 31 is for mainnet rsk + txb = new ethtx(null,31); + + cb(); + }, + /** + * add transaction params + * + * @param cb + */ + function(cb) { + txb.nonce = 0; + txb.gasPrice = 10; + txb.gasLimit = 100; + txb.value = 0; + txb.data = 0; + txb.to = "0x0000000000000000000000000000000000000000" + + cb(); + }, + /** + * estimate fee to verify that the API is not providing us wrong data + * + * @param cb + */ + function(cb) { + // use web3.eth.gasPrice to get gas price for given network + cb(); + }, + /** + * sign transaction + * + * @param cb + */ + function(cb) { + var i, privKey + + path = path.replace("M","m"); + + + console.log(self.primaryPrivateKey); + // todo: regenerate scripts for path and compare for utxo (paranoid mode) + if (self.primaryPrivateKey) { + privKey = self.deriveByPath(self.primaryPrivateKey, path, "m"); + } + else { + throw new Error("No master privateKey present"); + } + txb.sign(ethutil.toBuffer("0x"+privKey.keyPair.d.toBuffer().toString('hex'))); + serializedTx = txb.serialize(); + + cb(); + }, + ], function(err) { + if (err) { + deferred.reject(new blocktrail.WalletSendError(err)); + return; + } + + deferred.resolve([serializedTx]); + }); + + return deferred.promise; + }) + ) + }); + + return deferred.promise; +}; + +Wallet.prototype.sendRskTransaction = function(tx, checkFee, twoFactorToken, prioboost, options, cb) { + var self = this; + + if (typeof twoFactorToken === "function") { + cb = twoFactorToken; + twoFactorToken = null; + prioboost = false; + } else if (typeof prioboost === "function") { + cb = prioboost; + prioboost = false; + } else if (typeof options === "function") { + cb = options; + options = {}; + } + + var deferred = q.defer(); + deferred.promise.nodeify(cb); + + self.sdk.sendRskTransaction(self.identifier, tx.toString('hex'), checkFee, twoFactorToken, prioboost, options) + .then( + function(result) { + deferred.resolve(result); + }, + function(e) { + if (e.requires_2fa) { + deferred.reject(new blocktrail.WalletMissing2FAError()); + } else if (e.message.match(/Invalid two_factor_token/)) { + deferred.reject(new blocktrail.WalletInvalid2FAError()); + } else { + deferred.reject(e); + } + } + ) + ; + + return deferred.promise; +}; + +Wallet.prototype.getRskAccount = function(address, options, cb) { + var self = this; + + if (typeof options === "function") { + cb = options; + options = {}; + } + + return self.sdk.getRskAccount(self.identifier, address, options, cb); +}; + module.exports = Wallet; From cf488924b81b0da6eb994f32e79f35deb34a51fb Mon Sep 17 00:00:00 2001 From: philip glazman <8378656+MangoSalad@users.noreply.github.com> Date: Fri, 16 Nov 2018 11:46:20 +0100 Subject: [PATCH 05/19] remove rsk-wallet as seperate package --- lib/api_client.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/api_client.js b/lib/api_client.js index 9776f4e..e78a9ee 100644 --- a/lib/api_client.js +++ b/lib/api_client.js @@ -16,8 +16,7 @@ var _ = require('lodash'), blocktrail = require('./blocktrail'), randomBytes = require('randombytes'), CryptoJS = require('crypto-js'), - webworkifier = require('./webworkifier'), - RskWallet = require('./rsk-wallet'); + webworkifier = require('./webworkifier'); /** * From c72af797a77b56d4383ab88b4ac4615ab14847c2 Mon Sep 17 00:00:00 2001 From: philip glazman <8378656+MangoSalad@users.noreply.github.com> Date: Fri, 16 Nov 2018 15:19:02 +0100 Subject: [PATCH 06/19] refactored buildtransaction in wallet and removed duplicate derivation method in apiclient --- lib/api_client.js | 27 +++------- lib/wallet.js | 124 ++++++++++++++++++---------------------------- package.json | 6 +-- 3 files changed, 58 insertions(+), 99 deletions(-) diff --git a/lib/api_client.js b/lib/api_client.js index e78a9ee..13fae97 100644 --- a/lib/api_client.js +++ b/lib/api_client.js @@ -133,6 +133,10 @@ var APIClient = function(options) { }; +APIClient.BITCOIN_NETWORK = "BTC"; +APIClient.BITCOIN_CASH_NETWORK = "BCH"; +APIClient.ROOTSTOCK_NETWORK = "RSK"; + APIClient.normalizeNetworkFromOptions = function(options) { /* jshint -W071, -W074 */ var network = 'BTC'; @@ -1287,7 +1291,8 @@ APIClient.prototype.initWallet = function(options, cb) { return bitcoin.HDNode.fromBase58(primaryPublicKey[0], self.network); }); - if (self.chain === "RSK") + // Initializing a rootstock wallet does not require backup public key and blocktrail public key. + if (self.chain == APIClient.ROOTSTOCK_NETWORK) { wallet = new Wallet( self, @@ -1974,22 +1979,6 @@ APIClient.prototype.getNewDerivation = function(identifier, path, cb) { return self.blocktrailClient.post("/wallet/" + identifier + "/path", null, {path: path}, cb); }; -/** - * get a new derivation number for specified parent path - * eg; m/44'/1'/0/0 results in m/44'/1'/0/0/0 and next time in m/44'/1'/0/0/1 and next time in m/44'/1'/0/0/2 - * - * @param identifier string the wallet identifier - * @param path string the parent path for which to get a new derivation, - * can be suffixed with /* to make it clear on which level the derivations hould be - * @param [cb] function callback(err, result) - * @returns {q.Promise} - */ -APIClient.prototype.getRskNewDerivation = function(identifier, path, cb) { - var self = this; - - return self.blocktrailClient.post("/wallet/" + identifier + "/path", null, {path: path}, cb); -}; - /** * use the API to get account details for an Rootstock Address * @@ -2034,10 +2023,6 @@ APIClient.prototype.getRskAccount = function(identifier, address, options, cb) { ]; }, function(err) { - if (err.message.match(/too low to pay the fee/)) { - throw blocktrail.WalletFeeError(err); - } - throw err; } ) diff --git a/lib/wallet.js b/lib/wallet.js index 41ca6f8..7b311d7 100644 --- a/lib/wallet.js +++ b/lib/wallet.js @@ -13,8 +13,8 @@ var bip39 = require('bip39'); // Libraries for rootstock, ethereum key management. var ethereumKey = require('ethereumjs-wallet/hdkey') -var ethutil = require('ethereumjs-util') -var ethtx = require('ethereumjs-tx') +var ethUtil = require('ethereumjs-util') +var ethTransaction = require('ethereumjs-tx') var SignMode = { SIGN: "sign", @@ -97,6 +97,7 @@ var Wallet = function( assert(_.every(primaryPublicKeys, function(primaryPublicKey) { return primaryPublicKey instanceof bitcoin.HDNode; })); + // If this is not rootstock, make sure back up and blocktrail keys are present. if (self.sdk.chain != "RSK") { assert(backupPublicKey instanceof bitcoin.HDNode); assert(_.every(blocktrailPublicKeys, function(blocktrailPublicKey) { return blocktrailPublicKey instanceof bitcoin.HDNode; })); @@ -165,6 +166,10 @@ Wallet.FEE_STRATEGY_OPTIMAL = blocktrail.FEE_STRATEGY_OPTIMAL; Wallet.FEE_STRATEGY_LOW_PRIORITY = blocktrail.FEE_STRATEGY_LOW_PRIORITY; Wallet.FEE_STRATEGY_MIN_RELAY_FEE = blocktrail.FEE_STRATEGY_MIN_RELAY_FEE; +Wallet.RSK_STANDARD_TRANSACTION_GAS_LIMIT = 21000; +Wallet.RSK_MINIMUM_GAS_PRICE = 0; + + Wallet.prototype.isSegwit = function() { return !!this.segwit; }; @@ -736,15 +741,15 @@ Wallet.prototype.getRskAddress = function(chainIdx, cb) { } // Get Rootstock address from server. - deferred.resolve(self.sdk.getRskNewDerivation(self.identifier, "M/" + self.keyIndex + "'/" + chainIdx) + deferred.resolve(self.sdk.getNewDerivation(self.identifier, "M/" + self.keyIndex + "'/" + chainIdx) .then(function(newDerivation) { - var path = newDerivation.path.replace("\'","").replace('9999',"9999'"); var addressFromServer = newDerivation.address; // Verify Rootstock address client-side. var verifyAddress = self.getRskAddressByPath(path); + // Check that server side address is equal to our client side address. if (verifyAddress !== addressFromServer) { throw new blocktrail.WalletAddressError("Failed to verify address [" + addressFromServer + "] !== [" + verifyAddress + "]"); } @@ -1969,21 +1974,12 @@ Wallet.deriveByPath = function(hdKey, path, keyPath) { } }; -Wallet.prototype.rskPay = function(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, twoFactorToken, options, cb) { +Wallet.prototype.rskPay = function(pay, feeStrategy, twoFactorToken, options, cb) { /* jshint -W071 */ var self = this; - if (typeof changeAddress === "function") { - cb = changeAddress; - changeAddress = null; - } else if (typeof allowZeroConf === "function") { - cb = allowZeroConf; - allowZeroConf = false; - } else if (typeof randomizeChangeIdx === "function") { - cb = randomizeChangeIdx; - randomizeChangeIdx = true; - } else if (typeof feeStrategy === "function") { + if (typeof feeStrategy === "function") { cb = feeStrategy; feeStrategy = null; } else if (typeof twoFactorToken === "function") { @@ -1994,7 +1990,6 @@ Wallet.prototype.rskPay = function(pay, changeAddress, allowZeroConf, randomizeC options = {}; } - randomizeChangeIdx = typeof randomizeChangeIdx !== "undefined" ? randomizeChangeIdx : true; feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL; options = options || {}; var checkFee = typeof options.checkFee !== "undefined" ? options.checkFee : true; @@ -2008,25 +2003,14 @@ Wallet.prototype.rskPay = function(pay, changeAddress, allowZeroConf, randomizeC } q.nextTick(function() { - - self.buildRskTransaction(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, options).then( + self.buildRskTransaction(pay, feeStrategy, options).then( function(r) { return r; }, function(e) { deferred.reject(e); }, ) .spread( - function(tx) { - - // var data = { - // signed_transaction: tx.toHex(), - // base_transaction: tx.__toBuffer(null, null, false).toString('hex') - // }; - - console.log(tx) - - return self.sendRskTransaction(tx, checkFee, twoFactorToken, options.prioboost, options) + function(tx,signedTx) { + return self.sendRskTransaction(signedTx, checkFee, twoFactorToken, options.prioboost, options) .then(function(result) { - - if (!result || !result['complete'] || result['complete'] === 'false') { deferred.reject(new Error("Failed to send transaction.")); } else { @@ -2048,20 +2032,11 @@ Wallet.prototype.rskPay = function(pay, changeAddress, allowZeroConf, randomizeC return deferred.promise; }; -Wallet.prototype.buildRskTransaction = function(pay, changeAddress, allowZeroConf, randomizeChangeIdx, feeStrategy, options, cb) { +Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { /* jshint -W071 */ var self = this; - if (typeof changeAddress === "function") { - cb = changeAddress; - changeAddress = null; - } else if (typeof allowZeroConf === "function") { - cb = allowZeroConf; - allowZeroConf = false; - } else if (typeof randomizeChangeIdx === "function") { - cb = randomizeChangeIdx; - randomizeChangeIdx = true; - } else if (typeof feeStrategy === "function") { + if (typeof feeStrategy === "function") { cb = feeStrategy; feeStrategy = null; } else if (typeof options === "function") { @@ -2069,18 +2044,13 @@ Wallet.prototype.buildRskTransaction = function(pay, changeAddress, allowZeroCon options = {}; } - randomizeChangeIdx = typeof randomizeChangeIdx !== "undefined" ? randomizeChangeIdx : true; feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL; options = options || {}; - - var deferred = q.defer(); - // @TODO - get chainIdx and childIdx at init method - var path = "M/" + self.keyIndex + "'/" + 5 + "/" + 0; - console.log(path); - var accountAddress = self.getAddressByPath(path); + var path = "M/" + self.keyIndex + "'/" + Wallet.CHAIN_RSK_DEFAULT + "/" + 0; + var accountAddress = self.getRskAddressByPath(path); q.nextTick(function() { deferred.resolve( @@ -2094,10 +2064,15 @@ Wallet.prototype.buildRskTransaction = function(pay, changeAddress, allowZeroCon * @param cb */ function(cb) { - // blank ethereum transaction - // @TODO - other chain ids - testnet,regtest for rsk? - // chainId 31 is for mainnet rsk - txb = new ethtx(null,31); + // ChainId 30 is Rootstock Mainnet + // ChainId 31 is Rootstock Testnet + + if( self.testnet || self.regtest ) { + txb = new ethTransaction(null,31); + } + else { + txb = new ethTransaction(null,30); + } cb(); }, @@ -2107,44 +2082,39 @@ Wallet.prototype.buildRskTransaction = function(pay, changeAddress, allowZeroCon * @param cb */ function(cb) { - txb.nonce = 0; - txb.gasPrice = 10; - txb.gasLimit = 100; - txb.value = 0; + + //@TODO - implement safety check for nonce + txb.nonce = nonce; + txb.gasPrice = Wallet.RSK_MINIMUM_GAS_PRICE; + txb.gasLimit = Wallet.RSK_STANDARD_TRANSACTION_GAS_LIMIT; + txb.value = pay.value; txb.data = 0; - txb.to = "0x0000000000000000000000000000000000000000" + txb.to = pay.to; cb(); }, - /** - * estimate fee to verify that the API is not providing us wrong data - * - * @param cb - */ - function(cb) { - // use web3.eth.gasPrice to get gas price for given network - cb(); - }, /** * sign transaction * * @param cb */ function(cb) { - var i, privKey + var privKey; path = path.replace("M","m"); - - - console.log(self.primaryPrivateKey); - // todo: regenerate scripts for path and compare for utxo (paranoid mode) + if (self.primaryPrivateKey) { - privKey = self.deriveByPath(self.primaryPrivateKey, path, "m"); + privKey = Wallet.deriveByPath(self.primaryPrivateKey, path, "m"); } else { throw new Error("No master privateKey present"); } - txb.sign(ethutil.toBuffer("0x"+privKey.keyPair.d.toBuffer().toString('hex'))); + + txb.sign(ethUtil.toBuffer("0x"+privKey.keyPair.d.toBuffer().toString('hex'))); + + // @TODO - validate transaction before serialization + // txb.validate(); + serializedTx = txb.serialize(); cb(); @@ -2155,7 +2125,7 @@ Wallet.prototype.buildRskTransaction = function(pay, changeAddress, allowZeroCon return; } - deferred.resolve([serializedTx]); + deferred.resolve([txb,serializedTx]); }); return deferred.promise; @@ -2184,7 +2154,7 @@ Wallet.prototype.sendRskTransaction = function(tx, checkFee, twoFactorToken, pri var deferred = q.defer(); deferred.promise.nodeify(cb); - self.sdk.sendRskTransaction(self.identifier, tx.toString('hex'), checkFee, twoFactorToken, prioboost, options) + self.sdk.sendRskTransaction(self.identifier, tx, checkFee, twoFactorToken, prioboost, options) .then( function(result) { deferred.resolve(result); @@ -2215,4 +2185,8 @@ Wallet.prototype.getRskAccount = function(address, options, cb) { return self.sdk.getRskAccount(self.identifier, address, options, cb); }; +Wallet.prototype.decodeRskAddress = function(address) { + return (ethUtil.isValidAddress(address) ? {address: address, decoded: address, type: "rsk"} : new blocktrail.InvalidAddressError(err.message) ); +}; + module.exports = Wallet; diff --git a/package.json b/package.json index 5b49658..b4da196 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "pkginfo": "^0.4.1", "promise": "^6.1.0", "q": "1.0.1", - "randombytes": "^2.0.6", + "randombytes": "^2.0.1", "sjcl": "git://github.com/blocktrail/sjcl.git#minify-library", "spdx-exceptions": "^2.2.0", "spdx-license-ids": "^3.0.1", @@ -91,10 +91,10 @@ "devDependencies": { "blocktrail-sdk-backup-generator": "^0.2.0", "brfs": "*", - "browserify": "^16.2.3", + "browserify": "*", "browserify-versionify": "^1.0.6", "coveralls": "^2.13.1", - "grunt": "^0.4.5", + "grunt": "^0.4.2", "grunt-browserify": "git://github.com/jmreidy/grunt-browserify.git#4f96beb75d27fdebc4359e08e7db4c514f6265a8", "grunt-contrib-concat": "~0.5.1", "grunt-contrib-connect": "^0.7.1", From 708f48bbfeda79683c931f7908c0d2730da1815d Mon Sep 17 00:00:00 2001 From: philip glazman <8378656+MangoSalad@users.noreply.github.com> Date: Mon, 19 Nov 2018 17:44:37 +0100 Subject: [PATCH 07/19] transaction serialized to hex and sent to server --- lib/api_client.js | 15 ++++----------- lib/wallet.js | 7 +++---- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/lib/api_client.js b/lib/api_client.js index 13fae97..1317427 100644 --- a/lib/api_client.js +++ b/lib/api_client.js @@ -164,7 +164,7 @@ APIClient.normalizeNetworkFromOptions = function(options) { } prefix = m[1]; - if (prefix) { + if (prefix && network != "RSK") { // if there's a prefix then we're "done", won't apply options.regtest and options.testnet after done = true; if (prefix === 'r') { @@ -2226,17 +2226,10 @@ APIClient.prototype.sendRskTransaction = function(identifier, txHex, checkFee, t } var data = { - // paths: paths, - two_factor_token: twoFactorToken + two_factor_token: twoFactorToken, + raw_transaction: txHex }; - if (typeof txHex === "string") { - data.raw_transaction = txHex; - } else if (typeof txHex === "object") { - Object.keys(txHex).map(function(key) { - data[key] = txHex[key]; - }); - } - + var postOptions = { check_fee: checkFee ? 1 : 0, prioboost: prioboost ? 1 : 0 diff --git a/lib/wallet.js b/lib/wallet.js index 7b311d7..d6d1758 100644 --- a/lib/wallet.js +++ b/lib/wallet.js @@ -1975,7 +1975,6 @@ Wallet.deriveByPath = function(hdKey, path, keyPath) { }; Wallet.prototype.rskPay = function(pay, feeStrategy, twoFactorToken, options, cb) { - /* jshint -W071 */ var self = this; @@ -2048,6 +2047,7 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { options = options || {}; var deferred = q.defer(); + deferred.promise.nodeify(cb); var path = "M/" + self.keyIndex + "'/" + Wallet.CHAIN_RSK_DEFAULT + "/" + 0; var accountAddress = self.getRskAddressByPath(path); @@ -2066,7 +2066,7 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { function(cb) { // ChainId 30 is Rootstock Mainnet // ChainId 31 is Rootstock Testnet - + if( self.testnet || self.regtest ) { txb = new ethTransaction(null,31); } @@ -2115,7 +2115,7 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { // @TODO - validate transaction before serialization // txb.validate(); - serializedTx = txb.serialize(); + serializedTx = txb.serialize().toString('hex'); cb(); }, @@ -2181,7 +2181,6 @@ Wallet.prototype.getRskAccount = function(address, options, cb) { cb = options; options = {}; } - return self.sdk.getRskAccount(self.identifier, address, options, cb); }; From 5eeefd30d7b534babaa6af1c543db6b908e1d5c5 Mon Sep 17 00:00:00 2001 From: philip glazman <8378656+MangoSalad@users.noreply.github.com> Date: Thu, 22 Nov 2018 16:26:04 +0100 Subject: [PATCH 08/19] added tx id to be passed to backend --- lib/api_client.js | 5 +++-- lib/wallet.js | 18 ++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/api_client.js b/lib/api_client.js index 1317427..1e9809b 100644 --- a/lib/api_client.js +++ b/lib/api_client.js @@ -2210,7 +2210,7 @@ APIClient.prototype.sendTransaction = function(identifier, txHex, paths, checkFe ); }; -APIClient.prototype.sendRskTransaction = function(identifier, txHex, checkFee, twoFactorToken, prioboost, options, cb) { +APIClient.prototype.sendRskTransaction = function(identifier, txHex, txid, checkFee, twoFactorToken, prioboost, options, cb) { var self = this; if (typeof twoFactorToken === "function") { @@ -2227,7 +2227,8 @@ APIClient.prototype.sendRskTransaction = function(identifier, txHex, checkFee, t var data = { two_factor_token: twoFactorToken, - raw_transaction: txHex + raw_transaction: txHex, + transaction_id: txid }; var postOptions = { diff --git a/lib/wallet.js b/lib/wallet.js index d6d1758..6b056d3 100644 --- a/lib/wallet.js +++ b/lib/wallet.js @@ -2007,8 +2007,8 @@ Wallet.prototype.rskPay = function(pay, feeStrategy, twoFactorToken, options, cb function(e) { deferred.reject(e); }, ) .spread( - function(tx,signedTx) { - return self.sendRskTransaction(signedTx, checkFee, twoFactorToken, options.prioboost, options) + function(signedTx,txid) { + return self.sendRskTransaction(signedTx,txid, checkFee, twoFactorToken, options.prioboost, options) .then(function(result) { if (!result || !result['complete'] || result['complete'] === 'false') { deferred.reject(new Error("Failed to send transaction.")); @@ -2055,7 +2055,7 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { q.nextTick(function() { deferred.resolve( self.getRskAccount(accountAddress).spread(function(confirmed_balance,address,path,nonce){ - var txb, serializedTx; + var txb, serializedTx, transaction, txId; var deferred = q.defer(); async.waterfall([ /** @@ -2115,8 +2115,10 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { // @TODO - validate transaction before serialization // txb.validate(); - serializedTx = txb.serialize().toString('hex'); - + transaction = txb.serialize(); + serializedTx = transaction.toString('hex'); + txId = new ethTransaction(serializedTx).hash().toString('hex'); + cb(); }, ], function(err) { @@ -2125,7 +2127,7 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { return; } - deferred.resolve([txb,serializedTx]); + deferred.resolve([serializedTx,txId]); }); return deferred.promise; @@ -2136,7 +2138,7 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { return deferred.promise; }; -Wallet.prototype.sendRskTransaction = function(tx, checkFee, twoFactorToken, prioboost, options, cb) { +Wallet.prototype.sendRskTransaction = function(tx,txid, checkFee, twoFactorToken, prioboost, options, cb) { var self = this; if (typeof twoFactorToken === "function") { @@ -2154,7 +2156,7 @@ Wallet.prototype.sendRskTransaction = function(tx, checkFee, twoFactorToken, pri var deferred = q.defer(); deferred.promise.nodeify(cb); - self.sdk.sendRskTransaction(self.identifier, tx, checkFee, twoFactorToken, prioboost, options) + self.sdk.sendRskTransaction(self.identifier, tx, txid, checkFee, twoFactorToken, prioboost, options) .then( function(result) { deferred.resolve(result); From 1574f2823b0c264e9c12b56fbf8830ea320f319a Mon Sep 17 00:00:00 2001 From: philip glazman <8378656+MangoSalad@users.noreply.github.com> Date: Wed, 28 Nov 2018 11:37:34 +0100 Subject: [PATCH 09/19] removed ethereumjs-wallet as submodule. found equivalent npm package --- .gitmodules | 3 --- vendor/ethereumjs-wallet | 1 - 2 files changed, 4 deletions(-) delete mode 160000 vendor/ethereumjs-wallet diff --git a/.gitmodules b/.gitmodules index 0c94998..62df9e1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,6 +4,3 @@ [submodule "vendor/asmcrypto.js"] path = vendor/asmcrypto.js url = https://github.com/vibornoff/asmcrypto.js -[submodule "vendor/ethereumjs-wallet"] - path = vendor/ethereumjs-wallet - url = https://github.com/ethereumjs/ethereumjs-wallet/ diff --git a/vendor/ethereumjs-wallet b/vendor/ethereumjs-wallet deleted file mode 160000 index 3927a0e..0000000 --- a/vendor/ethereumjs-wallet +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3927a0e5403ee79036dd0dcd0d97f2a5d7420f97 From 02e672228ce95bfdfb234473afd6b6d35b10be63 Mon Sep 17 00:00:00 2001 From: philip glazman <8378656+MangoSalad@users.noreply.github.com> Date: Wed, 28 Nov 2018 15:25:46 +0100 Subject: [PATCH 10/19] documentation, refactored --- lib/api_client.js | 28 +++++------- lib/wallet.js | 108 +++++++++++++++++++++++++++++++++------------- 2 files changed, 89 insertions(+), 47 deletions(-) diff --git a/lib/api_client.js b/lib/api_client.js index 1e9809b..07cb11e 100644 --- a/lib/api_client.js +++ b/lib/api_client.js @@ -164,7 +164,7 @@ APIClient.normalizeNetworkFromOptions = function(options) { } prefix = m[1]; - if (prefix && network != "RSK") { + if (prefix && network !== "RSK") { // if there's a prefix then we're "done", won't apply options.regtest and options.testnet after done = true; if (prefix === 'r') { @@ -1292,7 +1292,7 @@ APIClient.prototype.initWallet = function(options, cb) { }); // Initializing a rootstock wallet does not require backup public key and blocktrail public key. - if (self.chain == APIClient.ROOTSTOCK_NETWORK) + if (self.chain === APIClient.ROOTSTOCK_NETWORK) { wallet = new Wallet( self, @@ -2016,11 +2016,13 @@ APIClient.prototype.getRskAccount = function(identifier, address, options, cb) { deferred.resolve( self.blocktrailClient.post("/wallet/" + identifier + "/account", params).then( function(result) { - return [result.confirmed_balance, - result.address, - result.path, - result.nonce, - ]; + var response = { + confirmed_balance: result.confirmed_balance, + address: result.address, + path: result.path, + nonce: result.nonce + } + return [response]; }, function(err) { throw err; @@ -2210,16 +2212,12 @@ APIClient.prototype.sendTransaction = function(identifier, txHex, paths, checkFe ); }; -APIClient.prototype.sendRskTransaction = function(identifier, txHex, txid, checkFee, twoFactorToken, prioboost, options, cb) { +APIClient.prototype.sendRskTransaction = function(identifier, txHex, twoFactorToken, options, cb) { var self = this; if (typeof twoFactorToken === "function") { cb = twoFactorToken; twoFactorToken = null; - prioboost = false; - } else if (typeof prioboost === "function") { - cb = prioboost; - prioboost = false; } else if (typeof options === "function") { cb = options; options = {}; @@ -2228,17 +2226,11 @@ APIClient.prototype.sendRskTransaction = function(identifier, txHex, txid, check var data = { two_factor_token: twoFactorToken, raw_transaction: txHex, - transaction_id: txid }; - var postOptions = { - check_fee: checkFee ? 1 : 0, - prioboost: prioboost ? 1 : 0 - }; return self.blocktrailClient.post( "/wallet/" + identifier + "/send", - postOptions, data, cb ); diff --git a/lib/wallet.js b/lib/wallet.js index 6b056d3..94398f2 100644 --- a/lib/wallet.js +++ b/lib/wallet.js @@ -12,9 +12,9 @@ var SizeEstimation = require('./size_estimation'); var bip39 = require('bip39'); // Libraries for rootstock, ethereum key management. -var ethereumKey = require('ethereumjs-wallet/hdkey') -var ethUtil = require('ethereumjs-util') -var ethTransaction = require('ethereumjs-tx') +var ethKey = require('ethereumjs-wallet/hdkey'); +var ethUtil = require('ethereumjs-util'); +var ethTransaction = require('ethereumjs-tx'); var SignMode = { SIGN: "sign", @@ -98,7 +98,7 @@ var Wallet = function( assert(_.every(primaryPublicKeys, function(primaryPublicKey) { return primaryPublicKey instanceof bitcoin.HDNode; })); // If this is not rootstock, make sure back up and blocktrail keys are present. - if (self.sdk.chain != "RSK") { + if (self.sdk.chain !== "RSK") { assert(backupPublicKey instanceof bitcoin.HDNode); assert(_.every(blocktrailPublicKeys, function(blocktrailPublicKey) { return blocktrailPublicKey instanceof bitcoin.HDNode; })); } @@ -743,7 +743,7 @@ Wallet.prototype.getRskAddress = function(chainIdx, cb) { // Get Rootstock address from server. deferred.resolve(self.sdk.getNewDerivation(self.identifier, "M/" + self.keyIndex + "'/" + chainIdx) .then(function(newDerivation) { - var path = newDerivation.path.replace("\'","").replace('9999',"9999'"); + var path = newDerivation.path; var addressFromServer = newDerivation.address; // Verify Rootstock address client-side. @@ -776,7 +776,7 @@ Wallet.prototype.getRskAddressByPath = function(path) { var encodedPublicKey = derivedPrimaryPublicKey.toBase58(); // Instantiate an HD ethereum key using the extended public key. - var derivedEthereumKey = ethereumKey.fromExtendedKey(encodedPublicKey); + var derivedEthereumKey = ethKey.fromExtendedKey(encodedPublicKey); // Use the public key to encode an ethereum address (without address checksum). var address = derivedEthereumKey.getWallet().getAddressString(); @@ -1974,6 +1974,16 @@ Wallet.deriveByPath = function(hdKey, path, keyPath) { } }; +/** + * Performs steps to create a valid transaction for rootstock networks. + * + * @param pay {value, to} value of transaction to create and recipient address + * @param feeStrategy {number} + * @param twoFactorToken + * @param options + * @param cb + * @returns {promise} + */ Wallet.prototype.rskPay = function(pay, feeStrategy, twoFactorToken, options, cb) { /* jshint -W071 */ var self = this; @@ -1989,9 +1999,8 @@ Wallet.prototype.rskPay = function(pay, feeStrategy, twoFactorToken, options, cb options = {}; } - feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL; + feeStrategy = feeStrategy || Wallet.RSK_MINIMUM_GAS_PRICE; options = options || {}; - var checkFee = typeof options.checkFee !== "undefined" ? options.checkFee : true; var deferred = q.defer(); deferred.promise.nodeify(cb); @@ -2007,13 +2016,13 @@ Wallet.prototype.rskPay = function(pay, feeStrategy, twoFactorToken, options, cb function(e) { deferred.reject(e); }, ) .spread( - function(signedTx,txid) { - return self.sendRskTransaction(signedTx,txid, checkFee, twoFactorToken, options.prioboost, options) + function(transaction) { + return self.sendRskTransaction(transaction.tx, twoFactorToken, options) .then(function(result) { if (!result || !result['complete'] || result['complete'] === 'false') { deferred.reject(new Error("Failed to send transaction.")); } else { - return result['txid']; + return transaction.txId; } }); }, @@ -2031,6 +2040,18 @@ Wallet.prototype.rskPay = function(pay, feeStrategy, twoFactorToken, options, cb return deferred.promise; }; +/** + * Builds a standard rootstock transaction and serializes the valid transaction. + * Standard transactions should only pay to other externally owned accounts. + * Standard transactions cannot interact with methods defined by contract accounts. + * + * @todo: Implement dynamic fee policy. + * @param pay {to, value} + * @param feeStrategy {number} + * @param options + * @param cb + * @returns {promise} + */ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { /* jshint -W071 */ var self = this; @@ -2043,7 +2064,8 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { options = {}; } - feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL; + // If no feeStrategy is provided, use the minimum gas price. + feeStrategy = feeStrategy || Wallet.RSK_MINIMUM_GAS_PRICE; options = options || {}; var deferred = q.defer(); @@ -2054,8 +2076,14 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { q.nextTick(function() { deferred.resolve( - self.getRskAccount(accountAddress).spread(function(confirmed_balance,address,path,nonce){ - var txb, serializedTx, transaction, txId; + // Get details about the account we plan to spend from. + self.getRskAccount(accountAddress).spread(function(result){ + var confirmed_balance = result.confirmed_balance; + var address = result.address; + var path = result.path; + var nonce = result.nonce; + + var txb, transaction,tx, txId; var deferred = q.defer(); async.waterfall([ /** @@ -2083,14 +2111,19 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { */ function(cb) { - //@TODO - implement safety check for nonce txb.nonce = nonce; - txb.gasPrice = Wallet.RSK_MINIMUM_GAS_PRICE; + txb.gasPrice = feeStrategy; txb.gasLimit = Wallet.RSK_STANDARD_TRANSACTION_GAS_LIMIT; txb.value = pay.value; - txb.data = 0; txb.to = pay.to; + // Check if transaction value < confirmed balance of account. + let maximumGasFee = txb.gasLimit * txb.gasPrice; + let totalTxValue = maximumGasFee + txb.value; + if ( totalTxValue > confirmed_balance) { + throw new Error("Total transaction value is more than account value.") + } + cb(); }, /** @@ -2112,12 +2145,24 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { txb.sign(ethUtil.toBuffer("0x"+privKey.keyPair.d.toBuffer().toString('hex'))); - // @TODO - validate transaction before serialization - // txb.validate(); + // Validate transaction before serialization. + if (!txb.validate()) { + throw new Error("Unable to create a valid transaction."); + } + + let serializedTx = txb.serialize(); + tx = serializedTx.toString('hex'); + txId = new ethTransaction(tx).hash().toString('hex'); + + transaction = { + tx: tx, + txId: "Ox"+txId + } - transaction = txb.serialize(); - serializedTx = transaction.toString('hex'); - txId = new ethTransaction(serializedTx).hash().toString('hex'); + // Check if the provided account matches the finished valid transaction. + if (address !== "0x"+txb.from.toString('hex')) { + throw new Error("Problem signing the transaction."); + } cb(); }, @@ -2127,7 +2172,7 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { return; } - deferred.resolve([serializedTx,txId]); + deferred.resolve([transaction]); }); return deferred.promise; @@ -2138,16 +2183,21 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { return deferred.promise; }; -Wallet.prototype.sendRskTransaction = function(tx,txid, checkFee, twoFactorToken, prioboost, options, cb) { +/** + * Prepares to broadcast serialized rootstock transaction. + * + * @param tx + * @param twoFactorToken + * @param options + * @param cb + * @returns {promise} + */ +Wallet.prototype.sendRskTransaction = function(tx, twoFactorToken, options, cb) { var self = this; if (typeof twoFactorToken === "function") { cb = twoFactorToken; twoFactorToken = null; - prioboost = false; - } else if (typeof prioboost === "function") { - cb = prioboost; - prioboost = false; } else if (typeof options === "function") { cb = options; options = {}; @@ -2156,7 +2206,7 @@ Wallet.prototype.sendRskTransaction = function(tx,txid, checkFee, twoFactorToken var deferred = q.defer(); deferred.promise.nodeify(cb); - self.sdk.sendRskTransaction(self.identifier, tx, txid, checkFee, twoFactorToken, prioboost, options) + self.sdk.sendRskTransaction(self.identifier, tx, twoFactorToken, options) .then( function(result) { deferred.resolve(result); From b539bc4443b621bdd20a42a7b3c3f6c41c70818b Mon Sep 17 00:00:00 2001 From: philip glazman <8378656+MangoSalad@users.noreply.github.com> Date: Wed, 28 Nov 2018 15:45:13 +0100 Subject: [PATCH 11/19] fixed style errors --- lib/api_client.js | 27 +++++++++++------------- lib/wallet.js | 52 +++++++++++++++++++++++------------------------ 2 files changed, 38 insertions(+), 41 deletions(-) diff --git a/lib/api_client.js b/lib/api_client.js index 07cb11e..0755931 100644 --- a/lib/api_client.js +++ b/lib/api_client.js @@ -158,7 +158,7 @@ APIClient.normalizeNetworkFromOptions = function(options) { if (m[2] === 'btc') { network = "BTC"; } else if (m[2] === 'rsk') { - network = "RSK"; + network = "RSK"; } else { network = "BCC"; } @@ -1290,10 +1290,9 @@ APIClient.prototype.initWallet = function(options, cb) { primaryPublicKeys = _.mapValues(result.primary_public_keys, function(primaryPublicKey) { return bitcoin.HDNode.fromBase58(primaryPublicKey[0], self.network); }); - + // Initializing a rootstock wallet does not require backup public key and blocktrail public key. - if (self.chain === APIClient.ROOTSTOCK_NETWORK) - { + if (self.chain === APIClient.ROOTSTOCK_NETWORK) { wallet = new Wallet( self, identifier, @@ -1313,15 +1312,13 @@ APIClient.prototype.initWallet = function(options, cb) { options.useCashAddress, options.bypassNewAddressCheck ); - } - else - { + } else { backupPublicKey = bitcoin.HDNode.fromBase58(result.backup_public_key[0], self.network); blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) { return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network); }); - - // initialize wallet + + // initialize wallet wallet = new Wallet( self, identifier, @@ -1998,7 +1995,7 @@ APIClient.prototype.getNewDerivation = function(identifier, path, cb) { */ APIClient.prototype.getRskAccount = function(identifier, address, options, cb) { var self = this; - + if (typeof options === "function") { cb = options; options = {}; @@ -2010,18 +2007,18 @@ APIClient.prototype.getRskAccount = function(identifier, address, options, cb) { deferred.promise.spreadNodeify(cb); var params = { - address: address, + address: address }; deferred.resolve( self.blocktrailClient.post("/wallet/" + identifier + "/account", params).then( function(result) { - var response = { + var response = { confirmed_balance: result.confirmed_balance, address: result.address, path: result.path, nonce: result.nonce - } + }; return [response]; }, function(err) { @@ -2225,9 +2222,9 @@ APIClient.prototype.sendRskTransaction = function(identifier, txHex, twoFactorTo var data = { two_factor_token: twoFactorToken, - raw_transaction: txHex, + raw_transaction: txHex }; - + return self.blocktrailClient.post( "/wallet/" + identifier + "/send", diff --git a/lib/wallet.js b/lib/wallet.js index 94398f2..e64b954 100644 --- a/lib/wallet.js +++ b/lib/wallet.js @@ -748,7 +748,7 @@ Wallet.prototype.getRskAddress = function(chainIdx, cb) { // Verify Rootstock address client-side. var verifyAddress = self.getRskAddressByPath(path); - + // Check that server side address is equal to our client side address. if (verifyAddress !== addressFromServer) { throw new blocktrail.WalletAddressError("Failed to verify address [" + addressFromServer + "] !== [" + verifyAddress + "]"); @@ -756,7 +756,7 @@ Wallet.prototype.getRskAddress = function(chainIdx, cb) { return [verifyAddress, path]; })); - + return deferred.promise; }; @@ -2014,8 +2014,7 @@ Wallet.prototype.rskPay = function(pay, feeStrategy, twoFactorToken, options, cb self.buildRskTransaction(pay, feeStrategy, options).then( function(r) { return r; }, function(e) { deferred.reject(e); }, - ) - .spread( + ).spread( function(transaction) { return self.sendRskTransaction(transaction.tx, twoFactorToken, options) .then(function(result) { @@ -2025,7 +2024,7 @@ Wallet.prototype.rskPay = function(pay, feeStrategy, twoFactorToken, options, cb return transaction.txId; } }); - }, + }, function(e) { throw e; } @@ -2095,10 +2094,9 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { // ChainId 30 is Rootstock Mainnet // ChainId 31 is Rootstock Testnet - if( self.testnet || self.regtest ) { + if (self.testnet || self.regtest) { txb = new ethTransaction(null,31); - } - else { + } else { txb = new ethTransaction(null,30); } @@ -2110,7 +2108,7 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { * @param cb */ function(cb) { - + txb.nonce = nonce; txb.gasPrice = feeStrategy; txb.gasLimit = Wallet.RSK_STANDARD_TRANSACTION_GAS_LIMIT; @@ -2118,10 +2116,10 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { txb.to = pay.to; // Check if transaction value < confirmed balance of account. - let maximumGasFee = txb.gasLimit * txb.gasPrice; - let totalTxValue = maximumGasFee + txb.value; - if ( totalTxValue > confirmed_balance) { - throw new Error("Total transaction value is more than account value.") + var maximumGasFee = txb.gasLimit * txb.gasPrice; + var totalTxValue = maximumGasFee + txb.value; + if (totalTxValue > confirmed_balance) { + throw new Error("Total transaction value is more than account value."); } cb(); @@ -2134,36 +2132,35 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { function(cb) { var privKey; - path = path.replace("M","m"); + path = path.replace("M", "m"); if (self.primaryPrivateKey) { privKey = Wallet.deriveByPath(self.primaryPrivateKey, path, "m"); - } - else { + } else { throw new Error("No master privateKey present"); } - txb.sign(ethUtil.toBuffer("0x"+privKey.keyPair.d.toBuffer().toString('hex'))); - + txb.sign(ethUtil.toBuffer("0x" + privKey.keyPair.d.toBuffer().toString('hex'))); + // Validate transaction before serialization. if (!txb.validate()) { throw new Error("Unable to create a valid transaction."); } - let serializedTx = txb.serialize(); + var serializedTx = txb.serialize(); tx = serializedTx.toString('hex'); txId = new ethTransaction(tx).hash().toString('hex'); transaction = { tx: tx, - txId: "Ox"+txId - } + txId: "Ox" + txId + }; // Check if the provided account matches the finished valid transaction. - if (address !== "0x"+txb.from.toString('hex')) { + if (address !== "0x" + txb.from.toString('hex')) { throw new Error("Problem signing the transaction."); } - + cb(); }, ], function(err) { @@ -2174,10 +2171,10 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { deferred.resolve([transaction]); }); - + return deferred.promise; }) - ) + ); }); return deferred.promise; @@ -2237,7 +2234,10 @@ Wallet.prototype.getRskAccount = function(address, options, cb) { }; Wallet.prototype.decodeRskAddress = function(address) { - return (ethUtil.isValidAddress(address) ? {address: address, decoded: address, type: "rsk"} : new blocktrail.InvalidAddressError(err.message) ); + return ( + ethUtil.isValidAddress(address) ? + {address: address, decoded: address, type: "rsk"} : + new blocktrail.InvalidAddressError("Invalid Rootstock Address.")); }; module.exports = Wallet; From c1b35a28e5cfec22349ea9c83b444b61b079fc1e Mon Sep 17 00:00:00 2001 From: philip glazman <8378656+MangoSalad@users.noreply.github.com> Date: Fri, 30 Nov 2018 17:26:50 +0100 Subject: [PATCH 12/19] Need to flag if rootstock wallet is testnet/regtest for the transaction chainid but cannot use existing flags because need xpub for eth wallet instantiation. Added balance check --- lib/api_client.js | 9 ++++++++- lib/wallet.js | 11 ++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/api_client.js b/lib/api_client.js index 0755931..757bc68 100644 --- a/lib/api_client.js +++ b/lib/api_client.js @@ -103,6 +103,7 @@ var APIClient = function(options) { self.bitcoinCash = options.network === "BCC"; self.chain = options.network; + self.rskTestnet = normalizedNetwork[4]; self.regtest = options.regtest; self.testnet = options.testnet; self.network = networkFromOptions(self); @@ -143,6 +144,7 @@ APIClient.normalizeNetworkFromOptions = function(options) { var testnet = false; var regtest = false; var apiNetwork = "BTC"; + var rskTestnet = false; var prefix; var done = false; @@ -173,6 +175,11 @@ APIClient.normalizeNetworkFromOptions = function(options) { } else if (prefix === 't') { testnet = true; } + } else if (prefix && network === "RSK") { + done = true; + if (prefix === 'r' || prefix === 't') { + rskTestnet = true; + } } } @@ -190,7 +197,7 @@ APIClient.normalizeNetworkFromOptions = function(options) { apiNetwork = (prefix || "") + network; - return [network, testnet, regtest, apiNetwork]; + return [network, testnet, regtest, apiNetwork, rskTestnet]; }; APIClient.updateHostOptions = function(options) { diff --git a/lib/wallet.js b/lib/wallet.js index e64b954..13dd0aa 100644 --- a/lib/wallet.js +++ b/lib/wallet.js @@ -2094,7 +2094,7 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { // ChainId 30 is Rootstock Mainnet // ChainId 31 is Rootstock Testnet - if (self.testnet || self.regtest) { + if (self.sdk.rskTestnet) { txb = new ethTransaction(null,31); } else { txb = new ethTransaction(null,30); @@ -2114,11 +2114,12 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { txb.gasLimit = Wallet.RSK_STANDARD_TRANSACTION_GAS_LIMIT; txb.value = pay.value; txb.to = pay.to; - + // Check if transaction value < confirmed balance of account. - var maximumGasFee = txb.gasLimit * txb.gasPrice; - var totalTxValue = maximumGasFee + txb.value; - if (totalTxValue > confirmed_balance) { + var maximumGasFee = Wallet.RSK_STANDARD_TRANSACTION_GAS_LIMIT * (feeStrategy ? feeStrategy : 1); + var totalTxValue = maximumGasFee + pay.value; + + if (BigInt(totalTxValue) >= BigInt(confirmed_balance)) { throw new Error("Total transaction value is more than account value."); } From fb8bcb560edf203fd3cb990431832885af70ca6e Mon Sep 17 00:00:00 2001 From: philip glazman <8378656+MangoSalad@users.noreply.github.com> Date: Tue, 4 Dec 2018 14:13:29 +0100 Subject: [PATCH 13/19] typo --- lib/wallet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/wallet.js b/lib/wallet.js index 13dd0aa..d8fea09 100644 --- a/lib/wallet.js +++ b/lib/wallet.js @@ -2154,7 +2154,7 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { transaction = { tx: tx, - txId: "Ox" + txId + txId: "0x" + txId }; // Check if the provided account matches the finished valid transaction. From 05eca65f8f2fd2e41169aa1d93d5a508d2446841 Mon Sep 17 00:00:00 2001 From: philip glazman <8378656+MangoSalad@users.noreply.github.com> Date: Tue, 4 Dec 2018 14:13:29 +0100 Subject: [PATCH 14/19] typo From cfe98f1c168f511d959fbf0fd116f14106d9acf0 Mon Sep 17 00:00:00 2001 From: philip glazman <8378656+MangoSalad@users.noreply.github.com> Date: Tue, 4 Dec 2018 17:16:45 +0100 Subject: [PATCH 15/19] add biginteger, web3. fixed check for balance --- lib/wallet.js | 18 ++++++++++++------ package.json | 2 ++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/wallet.js b/lib/wallet.js index d8fea09..53dca0b 100644 --- a/lib/wallet.js +++ b/lib/wallet.js @@ -15,6 +15,7 @@ var bip39 = require('bip39'); var ethKey = require('ethereumjs-wallet/hdkey'); var ethUtil = require('ethereumjs-util'); var ethTransaction = require('ethereumjs-tx'); +var web3 = require('web3'); var SignMode = { SIGN: "sign", @@ -2112,15 +2113,20 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { txb.nonce = nonce; txb.gasPrice = feeStrategy; txb.gasLimit = Wallet.RSK_STANDARD_TRANSACTION_GAS_LIMIT; - txb.value = pay.value; + txb.value = web3.utils.toHex(pay.value); txb.to = pay.to; // Check if transaction value < confirmed balance of account. - var maximumGasFee = Wallet.RSK_STANDARD_TRANSACTION_GAS_LIMIT * (feeStrategy ? feeStrategy : 1); - var totalTxValue = maximumGasFee + pay.value; - - if (BigInt(totalTxValue) >= BigInt(confirmed_balance)) { - throw new Error("Total transaction value is more than account value."); + { + var gasLimit = web3.utils.toBN(Wallet.RSK_STANDARD_TRANSACTION_GAS_LIMIT); + var gasPrice = web3.utils.toBN((feeStrategy ? feeStrategy : 1)); + var maximumGasFee = gasLimit.mul(gasPrice) + var totalTxValue = web3.utils.toBN(pay.value).add(maximumGasFee); + var balance = web3.utils.toBN(confirmed_balance); + + if (totalTxValue.gte(balance)) { + throw new Error("Total transaction value is more than account value."); + } } cb(); diff --git a/package.json b/package.json index b4da196..fa83385 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "dependencies": { "assert-plus": "0.1.5", "async": "0.9.0", + "big-integer": "^1.6.39", "bip39": "git://github.com/blocktrail/bip39.git#sjcl-browser-bip39", "bitcoinjs-lib": "git://github.com/blocktrail/bitcoinjs-lib.git#137add06e7bba65efa0e52cf10391177e614f13f", "bitcoinjs-message": "^1.0.1", @@ -73,6 +74,7 @@ "superagent": "^3.8.1", "superagent-http-signature": "0.1.3", "superagent-promise": "^0.2.0", + "web3": "^1.0.0-beta.36", "webworkify": "^1.4.0" }, "author": { From e1cff2f9c73c14070d34e1631afcb35ca7728131 Mon Sep 17 00:00:00 2001 From: philip glazman <8378656+MangoSalad@users.noreply.github.com> Date: Wed, 5 Dec 2018 15:05:17 +0100 Subject: [PATCH 16/19] encoding --- lib/api_client.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/api_client.js b/lib/api_client.js index 757bc68..1eced6e 100644 --- a/lib/api_client.js +++ b/lib/api_client.js @@ -17,6 +17,7 @@ var _ = require('lodash'), randomBytes = require('randombytes'), CryptoJS = require('crypto-js'), webworkifier = require('./webworkifier'); + encoding = require('text-encoding'); /** * From 62292895184d4594957d37e6651f88195721f49f Mon Sep 17 00:00:00 2001 From: philip glazman <8378656+MangoSalad@users.noreply.github.com> Date: Mon, 10 Dec 2018 13:47:08 +0100 Subject: [PATCH 17/19] getRskAccount - change from object back to array --- lib/api_client.js | 13 ++++++------- lib/wallet.js | 8 ++++---- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/api_client.js b/lib/api_client.js index 1eced6e..8ba4d56 100644 --- a/lib/api_client.js +++ b/lib/api_client.js @@ -2021,13 +2021,12 @@ APIClient.prototype.getRskAccount = function(identifier, address, options, cb) { deferred.resolve( self.blocktrailClient.post("/wallet/" + identifier + "/account", params).then( function(result) { - var response = { - confirmed_balance: result.confirmed_balance, - address: result.address, - path: result.path, - nonce: result.nonce - }; - return [response]; + return [ + result.confirmed_balance, + result.address, + result.path, + result.nonce + ]; }, function(err) { throw err; diff --git a/lib/wallet.js b/lib/wallet.js index 53dca0b..7512a6b 100644 --- a/lib/wallet.js +++ b/lib/wallet.js @@ -2078,10 +2078,10 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { deferred.resolve( // Get details about the account we plan to spend from. self.getRskAccount(accountAddress).spread(function(result){ - var confirmed_balance = result.confirmed_balance; - var address = result.address; - var path = result.path; - var nonce = result.nonce; + var confirmed_balance = result[0]; + var address = result[1]; + var path = result[2]; + var nonce = result[3]; var txb, transaction,tx, txId; var deferred = q.defer(); From 6c6b2d4ac6164048d1b41943e549db5580fc339c Mon Sep 17 00:00:00 2001 From: philip glazman <8378656+MangoSalad@users.noreply.github.com> Date: Mon, 17 Dec 2018 14:16:33 +0100 Subject: [PATCH 18/19] fix getrskaccount, small refactor --- lib/api_client.js | 2 +- lib/wallet.js | 17 ++++++----------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/lib/api_client.js b/lib/api_client.js index 8ba4d56..89ab479 100644 --- a/lib/api_client.js +++ b/lib/api_client.js @@ -2022,7 +2022,7 @@ APIClient.prototype.getRskAccount = function(identifier, address, options, cb) { self.blocktrailClient.post("/wallet/" + identifier + "/account", params).then( function(result) { return [ - result.confirmed_balance, + String(result.confirmed_balance), result.address, result.path, result.nonce diff --git a/lib/wallet.js b/lib/wallet.js index 7512a6b..31366df 100644 --- a/lib/wallet.js +++ b/lib/wallet.js @@ -752,9 +752,8 @@ Wallet.prototype.getRskAddress = function(chainIdx, cb) { // Check that server side address is equal to our client side address. if (verifyAddress !== addressFromServer) { - throw new blocktrail.WalletAddressError("Failed to verify address [" + addressFromServer + "] !== [" + verifyAddress + "]"); + deferred.reject(new Error("Failed to verify address [" + addressFromServer + "] !== [" + verifyAddress + "]")); } - return [verifyAddress, path]; })); @@ -2077,11 +2076,7 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { q.nextTick(function() { deferred.resolve( // Get details about the account we plan to spend from. - self.getRskAccount(accountAddress).spread(function(result){ - var confirmed_balance = result[0]; - var address = result[1]; - var path = result[2]; - var nonce = result[3]; + self.getRskAccount(accountAddress).spread(function(confirmed_balance,address,path,nonce){ var txb, transaction,tx, txId; var deferred = q.defer(); @@ -2110,10 +2105,11 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { */ function(cb) { + value = web3.utils.toWei(pay.value); txb.nonce = nonce; txb.gasPrice = feeStrategy; txb.gasLimit = Wallet.RSK_STANDARD_TRANSACTION_GAS_LIMIT; - txb.value = web3.utils.toHex(pay.value); + txb.value = web3.utils.toHex(value); txb.to = pay.to; // Check if transaction value < confirmed balance of account. @@ -2121,10 +2117,9 @@ Wallet.prototype.buildRskTransaction = function(pay,feeStrategy, options, cb) { var gasLimit = web3.utils.toBN(Wallet.RSK_STANDARD_TRANSACTION_GAS_LIMIT); var gasPrice = web3.utils.toBN((feeStrategy ? feeStrategy : 1)); var maximumGasFee = gasLimit.mul(gasPrice) - var totalTxValue = web3.utils.toBN(pay.value).add(maximumGasFee); + var totalTxValue = web3.utils.toBN(value).add(maximumGasFee); var balance = web3.utils.toBN(confirmed_balance); - - if (totalTxValue.gte(balance)) { + if (totalTxValue.gt(balance)) { throw new Error("Total transaction value is more than account value."); } } From 860b07e837a0645e984a83e62d9c7e7f28724d8e Mon Sep 17 00:00:00 2001 From: Thomas Kerin Date: Wed, 19 Dec 2018 15:03:58 +0100 Subject: [PATCH 19/19] bad rebase, remove text encoding again --- lib/api_client.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/api_client.js b/lib/api_client.js index 89ab479..59e79d5 100644 --- a/lib/api_client.js +++ b/lib/api_client.js @@ -17,7 +17,6 @@ var _ = require('lodash'), randomBytes = require('randombytes'), CryptoJS = require('crypto-js'), webworkifier = require('./webworkifier'); - encoding = require('text-encoding'); /** *