diff --git a/web/cashtab/package-lock.json b/web/cashtab/package-lock.json --- a/web/cashtab/package-lock.json +++ b/web/cashtab/package-lock.json @@ -10,6 +10,7 @@ "@ant-design/icons": "^4.3.0", "@babel/core": "^7.16.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3", + "@psf/bitcoincashjs-lib": "^4.0.2", "@svgr/webpack": "^6.3.1", "@testing-library/jest-dom": "^5.16.2", "@testing-library/react": "^12.1.3", @@ -17,6 +18,7 @@ "@testing-library/user-event": "^13.5.0", "@zxing/library": "0.8.0", "antd": "^4.9.3", + "assert": "^2.0.0", "babel-jest": "^27.4.2", "babel-loader": "^8.2.3", "babel-plugin-named-asset-import": "^0.3.8", @@ -5605,6 +5607,17 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, + "node_modules/assert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", + "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", + "dependencies": { + "es6-object-assign": "^1.1.0", + "is-nan": "^1.2.1", + "object-is": "^1.0.1", + "util": "^0.12.0" + } + }, "node_modules/ast-types-flow": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", @@ -8198,6 +8211,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es6-object-assign": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", + "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==" + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -10794,6 +10812,20 @@ "node": ">=6" } }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -10818,6 +10850,21 @@ "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==" }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", @@ -19620,6 +19667,18 @@ "requires-port": "^1.0.0" } }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -24612,6 +24671,17 @@ } } }, + "assert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", + "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", + "requires": { + "es6-object-assign": "^1.1.0", + "is-nan": "^1.2.1", + "object-is": "^1.0.1", + "util": "^0.12.0" + } + }, "ast-types-flow": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", @@ -26610,6 +26680,11 @@ "is-symbol": "^1.0.2" } }, + "es6-object-assign": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", + "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==" + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -28471,6 +28546,14 @@ "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==" }, + "is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, "is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -28489,6 +28572,15 @@ "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==" }, + "is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, "is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", @@ -34837,6 +34929,18 @@ "requires-port": "^1.0.0" } }, + "util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "requires": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/web/cashtab/package.json b/web/cashtab/package.json --- a/web/cashtab/package.json +++ b/web/cashtab/package.json @@ -6,6 +6,7 @@ "@ant-design/icons": "^4.3.0", "@babel/core": "^7.16.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3", + "@psf/bitcoincashjs-lib": "^4.0.2", "@svgr/webpack": "^6.3.1", "@testing-library/jest-dom": "^5.16.2", "@testing-library/react": "^12.1.3", @@ -13,6 +14,7 @@ "@testing-library/user-event": "^13.5.0", "@zxing/library": "0.8.0", "antd": "^4.9.3", + "assert": "^2.0.0", "babel-jest": "^27.4.2", "babel-loader": "^8.2.3", "babel-plugin-named-asset-import": "^0.3.8", diff --git a/web/cashtab/src/hooks/useWallet.js b/web/cashtab/src/hooks/useWallet.js --- a/web/cashtab/src/hooks/useWallet.js +++ b/web/cashtab/src/hooks/useWallet.js @@ -3,6 +3,8 @@ import useInterval from './useInterval'; import useBCH from 'hooks/useBCH'; import BigNumber from 'bignumber.js'; +import Bitcoin from '@psf/bitcoincashjs-lib'; +import coininfo from 'utils/coininfo'; import { loadStoredWallet, isValidStoredWallet, @@ -373,6 +375,20 @@ const masterHDNode = BCH.HDNode.fromSeed(rootSeedBuffer); + // temporary comparison of masterHDNode between BCH-JS and bitcoincashjs-lib + let localMasterHDNode = Bitcoin.HDNode.fromSeedBuffer( + rootSeedBuffer, + coininfo.bitcoincash.main.toBitcoinJS(), + ); + + if ( + JSON.stringify(masterHDNode) === JSON.stringify(localMasterHDNode) + ) { + console.log( + 'migrateLegacyWallet(): BCH-JS masterHDNode matches localMasterHDNode from bitcoincashjs-lib', + ); + } + const Path245 = await deriveAccount(BCH, { masterHDNode, path: "m/44'/245'/0'/0/0", @@ -434,6 +450,19 @@ const rootSeedBuffer = await BCH.Mnemonic.toSeed(mnemonic); const masterHDNode = BCH.HDNode.fromSeed(rootSeedBuffer); + // temporary comparison of masterHDNode between BCH-JS and bitcoincashjs-lib + let localMasterHDNode = Bitcoin.HDNode.fromSeedBuffer( + rootSeedBuffer, + coininfo.bitcoincash.main.toBitcoinJS(), + ); + if ( + JSON.stringify(masterHDNode) === JSON.stringify(localMasterHDNode) + ) { + console.log( + 'getWalletDetails(): BCH-JS masterHDNode matches localMasterHDNode from bitcoincashjs-lib', + ); + } + const Path245 = await deriveAccount(BCH, { masterHDNode, path: "m/44'/245'/0'/0/0", diff --git a/web/cashtab/src/utils/coininfo.js b/web/cashtab/src/utils/coininfo.js new file mode 100644 --- /dev/null +++ b/web/cashtab/src/utils/coininfo.js @@ -0,0 +1,72 @@ +// JavaScript component for eCash specific information such as version numbers, DNS seeds, etc +// implementation reference: https://github.com/cryptocoinjs/coininfo/blob/master/lib/coininfo.js + +let Buffer = require('safe-buffer').Buffer; + +// required for browserify +let coins = [require('./ecashCoinInfo')]; + +let supportedCoins = {}; +const ecash = coins[0]; + +let unit = ecash.main.unit.toLowerCase(); +let name = ecash.main.name.toLowerCase(); +ecash.main.testnet = false; +ecash.main.toBitcoinJS = toBitcoinJS.bind(ecash.main); +ecash.main.toBitcore = toBitcore.bind(ecash.main); +supportedCoins[unit] = ecash.main; +supportedCoins[name] = ecash.main; + +function coininfo(input) { + let coin = input.toLowerCase(); + + if (!(coin in supportedCoins)) { + return null; + } else { + return supportedCoins[coin]; + } +} + +coins.forEach(function (coin) { + coininfo[coin.main.name.toLowerCase()] = coin; +}); + +// for use with bitcoinjs-lib +function toBitcoinJS() { + return Object.assign({}, this, { + messagePrefix: + this.messagePrefix || '\x18' + this.name + ' Signed Message:\n', + bech32: this.bech32, + bip32: { + public: (this.versions.bip32 || {}).public, + private: (this.versions.bip32 || {}).private, + }, + pubKeyHash: this.versions.public, + scriptHash: this.versions.scripthash, + wif: this.versions.private, + dustThreshold: null, // TODO + }); +} + +// for use with Bitcore +function toBitcore() { + // reverse magic + let nm = Buffer.allocUnsafe(4); + nm.writeUInt32BE(this.protocol ? this.protocol.magic : 0, 0); + nm = nm.readUInt32LE(0); + + return Object.assign({}, this, { + name: this.testnet ? 'testnet' : 'livenet', + alias: this.testnet ? 'testnet' : 'mainnet', + pubkeyhash: this.versions.public, + privatekey: this.versions.private, + scripthash: this.versions.scripthash, + xpubkey: (this.versions.bip32 || {}).public, + xprivkey: (this.versions.bip32 || {}).private, + networkMagic: nm, + port: this.port, + dnsSeeds: this.seedsDns || [], + }); +} + +module.exports = coininfo; diff --git a/web/cashtab/src/utils/ecashCoinInfo.js b/web/cashtab/src/utils/ecashCoinInfo.js new file mode 100644 --- /dev/null +++ b/web/cashtab/src/utils/ecashCoinInfo.js @@ -0,0 +1,117 @@ +/* + info from: + https://github.com/Bitcoin-ABC/bitcoin-abc/blob/master/src/chainparams.cpp + + Reference object: https://github.com/cryptocoinjs/coininfo/blob/master/lib/coins/bch.js + + Note: the bch object in the @psf/coininfo library (i.e. in this object) is slightly different to the canonical library referenced in the @psf/coininfo npm site +*/ + +let common = { + name: 'BitcoinCash', + per1: 1e8, + unit: 'BCH', +}; + +let main = Object.assign( + {}, + { + hashGenesisBlock: + '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f', + // nDefaultPort + port: 8333, + portRpc: 8332, + protocol: { + // pchMessageStart + magic: 0xd9b4bef9, // careful, sent over wire as little endian + }, + // vSeeds + seedsDns: [ + 'seed.bitcoinabc.org', + 'seed-abc.bitcoinforks.org', + 'btccash-seeder.bitcoinunlimited.info', + 'seed.bitprim.org', + 'seed.deadalnix.me', + 'seeder.criptolayer.net', + ], + // base58Prefixes + versions: { + bip32: { + private: 0x0488ade4, + public: 0x0488b21e, + }, + bip44: 145, + private: 0x80, + public: 0x00, + scripthash: 0x05, + messagePrefix: '\x18BitcoinCash Signed Message:\n', + }, + }, + common, +); + +// test obj kept otherwise output comparison tests will be false +let test = Object.assign( + {}, + { + hashGenesisBlock: + '000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943', + port: 18333, + portRpc: 18332, + protocol: { + magic: 0x0709110b, + }, + seedsDns: [ + 'testnet-seed.bitcoinabc.org', + 'testnet-seed-abc.bitcoinforks.org', + 'testnet-seed.bitprim.org', + 'testnet-seed.deadalnix.me', + 'testnet-seeder.criptolayer.net', + ], + versions: { + bip32: { + private: 0x04358394, + public: 0x043587cf, + }, + bip44: 1, + private: 0xef, + public: 0x6f, + scripthash: 0xc4, + messagePrefix: '\x18BitcoinCash Signed Message:\n', + }, + }, + common, +); + +// regtest obj kept otherwise output comparison tests will be false +let regtest = Object.assign( + {}, + { + hashGenesisBlock: + '0x0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206', + port: 18444, + portRpc: 18332, + protocol: { + magic: 0xdab5bffa, + }, + seedsDns: [], + versions: { + bip32: { + private: 0x04358394, + public: 0x043587cf, + }, + bip44: 1, + private: 0xef, + public: 0x6f, + scripthash: 0xc4, + messagePrefix: '\x18BitcoinCash Signed Message:\n', + }, + }, + common, +); + +module.exports = { + main, + test, + regtest, +};